From ad1fab9ac6b07ab90897844c375e725600856fab Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 7 Sep 2022 01:01:16 -0400 Subject: [PATCH 001/182] [KrylovSolvers] inner constructors -> outer constructors --- src/krylov_solvers.jl | 1420 ++++++++++++++++++++--------------------- 1 file changed, 710 insertions(+), 710 deletions(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 8a109a2be..a6da85bd5 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -68,29 +68,29 @@ mutable struct MinresSolver{T,FC,S} <: KrylovSolver{T,FC,S} err_vec :: Vector{T} warm_start :: Bool stats :: SimpleStats{T} +end - function MinresSolver(n, m, S; window :: Int=5) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - r1 = S(undef, n) - r2 = S(undef, n) - w1 = S(undef, n) - w2 = S(undef, n) - y = S(undef, n) - v = S(undef, 0) - err_vec = zeros(T, window) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, r1, r2, w1, w2, y, v, err_vec, false, stats) - return solver - end +function MinresSolver(n, m, S; window :: Int=5) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + r1 = S(undef, n) + r2 = S(undef, n) + w1 = S(undef, n) + w2 = S(undef, n) + y = S(undef, n) + v = S(undef, 0) + err_vec = zeros(T, window) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = MinresSolver{T,FC,S}(Δx, x, r1, r2, w1, w2, y, v, err_vec, false, stats) + return solver +end - function MinresSolver(A, b; window :: Int=5) - n, m = size(A) - S = ktypeof(b) - MinresSolver(n, m, S, window=window) - end +function MinresSolver(A, b; window :: Int=5) + n, m = size(A) + S = ktypeof(b) + MinresSolver(n, m, S, window=window) end """ @@ -112,26 +112,26 @@ mutable struct CgSolver{T,FC,S} <: KrylovSolver{T,FC,S} z :: S warm_start :: Bool stats :: SimpleStats{T} +end - function CgSolver(n, m, S) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - r = S(undef, n) - p = S(undef, n) - Ap = S(undef, n) - z = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, r, p, Ap, z, false, stats) - return solver - end +function CgSolver(n, m, S) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + r = S(undef, n) + p = S(undef, n) + Ap = S(undef, n) + z = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CgSolver{T,FC,S}(Δx, x, r, p, Ap, z, false, stats) + return solver +end - function CgSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CgSolver(n, m, S) - end +function CgSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CgSolver(n, m, S) end """ @@ -154,27 +154,27 @@ mutable struct CrSolver{T,FC,S} <: KrylovSolver{T,FC,S} Mq :: S warm_start :: Bool stats :: SimpleStats{T} +end - function CrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - r = S(undef, n) - p = S(undef, n) - q = S(undef, n) - Ar = S(undef, n) - Mq = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, r, p, q, Ar, Mq, false, stats) - return solver - end +function CrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + r = S(undef, n) + p = S(undef, n) + q = S(undef, n) + Ar = S(undef, n) + Mq = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CrSolver{T,FC,S}(Δx, x, r, p, q, Ar, Mq, false, stats) + return solver +end - function CrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CrSolver(n, m, S) - end +function CrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CrSolver(n, m, S) end """ @@ -200,30 +200,30 @@ mutable struct SymmlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} sprod :: Vector{T} warm_start :: Bool stats :: SymmlqStats{T} +end - function SymmlqSolver(n, m, S; window :: Int=5) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - Mvold = S(undef, n) - Mv = S(undef, n) - Mv_next = S(undef, n) - w̅ = S(undef, n) - v = S(undef, 0) - clist = zeros(T, window) - zlist = zeros(T, window) - sprod = ones(T, window) - stats = SymmlqStats(0, false, T[], Union{T, Missing}[], T[], Union{T, Missing}[], T(NaN), T(NaN), "unknown") - solver = new{T,FC,S}(Δx, x, Mvold, Mv, Mv_next, w̅, v, clist, zlist, sprod, false, stats) - return solver - end +function SymmlqSolver(n, m, S; window :: Int=5) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + Mvold = S(undef, n) + Mv = S(undef, n) + Mv_next = S(undef, n) + w̅ = S(undef, n) + v = S(undef, 0) + clist = zeros(T, window) + zlist = zeros(T, window) + sprod = ones(T, window) + stats = SymmlqStats(0, false, T[], Union{T, Missing}[], T[], Union{T, Missing}[], T(NaN), T(NaN), "unknown") + solver = SymmlqSolver{T,FC,S}(Δx, x, Mvold, Mv, Mv_next, w̅, v, clist, zlist, sprod, false, stats) + return solver +end - function SymmlqSolver(A, b; window :: Int=5) - n, m = size(A) - S = ktypeof(b) - SymmlqSolver(n, m, S, window=window) - end +function SymmlqSolver(A, b; window :: Int=5) + n, m = size(A) + S = ktypeof(b) + SymmlqSolver(n, m, S, window=window) end """ @@ -246,27 +246,27 @@ mutable struct CgLanczosSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S warm_start :: Bool stats :: LanczosStats{T} +end - function CgLanczosSolver(n, m, S) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - Mv = S(undef, n) - Mv_prev = S(undef, n) - p = S(undef, n) - Mv_next = S(undef, n) - v = S(undef, 0) - stats = LanczosStats(0, false, T[], false, T(NaN), T(NaN), "unknown") - solver = new{T,FC,S}(Δx, x, Mv, Mv_prev, p, Mv_next, v, false, stats) - return solver - end +function CgLanczosSolver(n, m, S) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + Mv = S(undef, n) + Mv_prev = S(undef, n) + p = S(undef, n) + Mv_next = S(undef, n) + v = S(undef, 0) + stats = LanczosStats(0, false, T[], false, T(NaN), T(NaN), "unknown") + solver = CgLanczosSolver{T,FC,S}(Δx, x, Mv, Mv_prev, p, Mv_next, v, false, stats) + return solver +end - function CgLanczosSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CgLanczosSolver(n, m, S) - end +function CgLanczosSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CgLanczosSolver(n, m, S) end """ @@ -294,34 +294,34 @@ mutable struct CgLanczosShiftSolver{T,FC,S} <: KrylovSolver{T,FC,S} converged :: BitVector not_cv :: BitVector stats :: LanczosShiftStats{T} +end - function CgLanczosShiftSolver(n, m, nshifts, S) - FC = eltype(S) - T = real(FC) - Mv = S(undef, n) - Mv_prev = S(undef, n) - Mv_next = S(undef, n) - v = S(undef, 0) - x = [S(undef, n) for i = 1 : nshifts] - p = [S(undef, n) for i = 1 : nshifts] - σ = Vector{T}(undef, nshifts) - δhat = Vector{T}(undef, nshifts) - ω = Vector{T}(undef, nshifts) - γ = Vector{T}(undef, nshifts) - rNorms = Vector{T}(undef, nshifts) - indefinite = BitVector(undef, nshifts) - converged = BitVector(undef, nshifts) - not_cv = BitVector(undef, nshifts) - stats = LanczosShiftStats(0, false, [T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") - solver = new{T,FC,S}(Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) - return solver - end +function CgLanczosShiftSolver(n, m, nshifts, S) + FC = eltype(S) + T = real(FC) + Mv = S(undef, n) + Mv_prev = S(undef, n) + Mv_next = S(undef, n) + v = S(undef, 0) + x = [S(undef, n) for i = 1 : nshifts] + p = [S(undef, n) for i = 1 : nshifts] + σ = Vector{T}(undef, nshifts) + δhat = Vector{T}(undef, nshifts) + ω = Vector{T}(undef, nshifts) + γ = Vector{T}(undef, nshifts) + rNorms = Vector{T}(undef, nshifts) + indefinite = BitVector(undef, nshifts) + converged = BitVector(undef, nshifts) + not_cv = BitVector(undef, nshifts) + stats = LanczosShiftStats(0, false, [T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") + solver = CgLanczosShiftSolver{T,FC,S}(Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) + return solver +end - function CgLanczosShiftSolver(A, b, nshifts) - n, m = size(A) - S = ktypeof(b) - CgLanczosShiftSolver(n, m, nshifts, S) - end +function CgLanczosShiftSolver(A, b, nshifts) + n, m = size(A) + S = ktypeof(b) + CgLanczosShiftSolver(n, m, nshifts, S) end """ @@ -345,28 +345,28 @@ mutable struct MinresQlpSolver{T,FC,S} <: KrylovSolver{T,FC,S} vₖ :: S warm_start :: Bool stats :: SimpleStats{T} +end - function MinresQlpSolver(n, m, S) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - wₖ₋₁ = S(undef, n) - wₖ = S(undef, n) - M⁻¹vₖ₋₁ = S(undef, n) - M⁻¹vₖ = S(undef, n) - x = S(undef, n) - p = S(undef, n) - vₖ = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats) - return solver - end +function MinresQlpSolver(n, m, S) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + wₖ₋₁ = S(undef, n) + wₖ = S(undef, n) + M⁻¹vₖ₋₁ = S(undef, n) + M⁻¹vₖ = S(undef, n) + x = S(undef, n) + p = S(undef, n) + vₖ = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = MinresQlpSolver{T,FC,S}(Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats) + return solver +end - function MinresQlpSolver(A, b) - n, m = size(A) - S = ktypeof(b) - MinresQlpSolver(n, m, S) - end +function MinresQlpSolver(A, b) + n, m = size(A) + S = ktypeof(b) + MinresQlpSolver(n, m, S) end """ @@ -393,31 +393,31 @@ mutable struct DqgmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} H :: Vector{FC} warm_start :: Bool stats :: SimpleStats{T} +end - function DqgmresSolver(n, m, memory, S) - memory = min(n, memory) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - t = S(undef, n) - z = S(undef, 0) - w = S(undef, 0) - P = [S(undef, n) for i = 1 : memory] - V = [S(undef, n) for i = 1 : memory] - c = Vector{T}(undef, memory) - s = Vector{FC}(undef, memory) - H = Vector{FC}(undef, memory+2) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, t, z, w, P, V, c, s, H, false, stats) - return solver - end +function DqgmresSolver(n, m, memory, S) + memory = min(n, memory) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + t = S(undef, n) + z = S(undef, 0) + w = S(undef, 0) + P = [S(undef, n) for i = 1 : memory] + V = [S(undef, n) for i = 1 : memory] + c = Vector{T}(undef, memory) + s = Vector{FC}(undef, memory) + H = Vector{FC}(undef, memory+2) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = DqgmresSolver{T,FC,S}(Δx, x, t, z, w, P, V, c, s, H, false, stats) + return solver +end - function DqgmresSolver(A, b, memory = 20) - n, m = size(A) - S = ktypeof(b) - DqgmresSolver(n, m, memory, S) - end +function DqgmresSolver(A, b, memory = 20) + n, m = size(A) + S = ktypeof(b) + DqgmresSolver(n, m, memory, S) end """ @@ -443,30 +443,30 @@ mutable struct DiomSolver{T,FC,S} <: KrylovSolver{T,FC,S} H :: Vector{FC} warm_start :: Bool stats :: SimpleStats{T} +end - function DiomSolver(n, m, memory, S) - memory = min(n, memory) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - t = S(undef, n) - z = S(undef, 0) - w = S(undef, 0) - P = [S(undef, n) for i = 1 : memory] - V = [S(undef, n) for i = 1 : memory] - L = Vector{FC}(undef, memory) - H = Vector{FC}(undef, memory+2) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, t, z, w, P, V, L, H, false, stats) - return solver - end +function DiomSolver(n, m, memory, S) + memory = min(n, memory) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + t = S(undef, n) + z = S(undef, 0) + w = S(undef, 0) + P = [S(undef, n) for i = 1 : memory] + V = [S(undef, n) for i = 1 : memory] + L = Vector{FC}(undef, memory) + H = Vector{FC}(undef, memory+2) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = DiomSolver{T,FC,S}(Δx, x, t, z, w, P, V, L, H, false, stats) + return solver +end - function DiomSolver(A, b, memory = 20) - n, m = size(A) - S = ktypeof(b) - DiomSolver(n, m, memory, S) - end +function DiomSolver(A, b, memory = 20) + n, m = size(A) + S = ktypeof(b) + DiomSolver(n, m, memory, S) end """ @@ -491,29 +491,29 @@ mutable struct UsymlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} q :: S warm_start :: Bool stats :: SimpleStats{T} +end - function UsymlqSolver(n, m, S) - FC = eltype(S) - T = real(FC) - uₖ₋₁ = S(undef, m) - uₖ = S(undef, m) - p = S(undef, m) - Δx = S(undef, 0) - x = S(undef, m) - d̅ = S(undef, m) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - q = S(undef, n) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(uₖ₋₁, uₖ, p, Δx, x, d̅, vₖ₋₁, vₖ, q, false, stats) - return solver - end +function UsymlqSolver(n, m, S) + FC = eltype(S) + T = real(FC) + uₖ₋₁ = S(undef, m) + uₖ = S(undef, m) + p = S(undef, m) + Δx = S(undef, 0) + x = S(undef, m) + d̅ = S(undef, m) + vₖ₋₁ = S(undef, n) + vₖ = S(undef, n) + q = S(undef, n) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = UsymlqSolver{T,FC,S}(uₖ₋₁, uₖ, p, Δx, x, d̅, vₖ₋₁, vₖ, q, false, stats) + return solver +end - function UsymlqSolver(A, b) - n, m = size(A) - S = ktypeof(b) - UsymlqSolver(n, m, S) - end +function UsymlqSolver(A, b) + n, m = size(A) + S = ktypeof(b) + UsymlqSolver(n, m, S) end """ @@ -539,30 +539,30 @@ mutable struct UsymqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} p :: S warm_start :: Bool stats :: SimpleStats{T} +end - function UsymqrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - q = S(undef, n) - Δx = S(undef, 0) - x = S(undef, m) - wₖ₋₂ = S(undef, m) - wₖ₋₁ = S(undef, m) - uₖ₋₁ = S(undef, m) - uₖ = S(undef, m) - p = S(undef, m) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(vₖ₋₁, vₖ, q, Δx, x, wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, p, false, stats) - return solver - end +function UsymqrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + vₖ₋₁ = S(undef, n) + vₖ = S(undef, n) + q = S(undef, n) + Δx = S(undef, 0) + x = S(undef, m) + wₖ₋₂ = S(undef, m) + wₖ₋₁ = S(undef, m) + uₖ₋₁ = S(undef, m) + uₖ = S(undef, m) + p = S(undef, m) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = UsymqrSolver{T,FC,S}(vₖ₋₁, vₖ, q, Δx, x, wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, p, false, stats) + return solver +end - function UsymqrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - UsymqrSolver(n, m, S) - end +function UsymqrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + UsymqrSolver(n, m, S) end """ @@ -594,36 +594,36 @@ mutable struct TricgSolver{T,FC,S} <: KrylovSolver{T,FC,S} vₖ :: S warm_start :: Bool stats :: SimpleStats{T} +end - function TricgSolver(n, m, S) - FC = eltype(S) - T = real(FC) - y = S(undef, m) - N⁻¹uₖ₋₁ = S(undef, m) - N⁻¹uₖ = S(undef, m) - p = S(undef, m) - gy₂ₖ₋₁ = S(undef, m) - gy₂ₖ = S(undef, m) - x = S(undef, n) - M⁻¹vₖ₋₁ = S(undef, n) - M⁻¹vₖ = S(undef, n) - q = S(undef, n) - gx₂ₖ₋₁ = S(undef, n) - gx₂ₖ = S(undef, n) - Δx = S(undef, 0) - Δy = S(undef, 0) - uₖ = S(undef, 0) - vₖ = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) - return solver - end +function TricgSolver(n, m, S) + FC = eltype(S) + T = real(FC) + y = S(undef, m) + N⁻¹uₖ₋₁ = S(undef, m) + N⁻¹uₖ = S(undef, m) + p = S(undef, m) + gy₂ₖ₋₁ = S(undef, m) + gy₂ₖ = S(undef, m) + x = S(undef, n) + M⁻¹vₖ₋₁ = S(undef, n) + M⁻¹vₖ = S(undef, n) + q = S(undef, n) + gx₂ₖ₋₁ = S(undef, n) + gx₂ₖ = S(undef, n) + Δx = S(undef, 0) + Δy = S(undef, 0) + uₖ = S(undef, 0) + vₖ = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = TricgSolver{T,FC,S}(y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) + return solver +end - function TricgSolver(A, b) - n, m = size(A) - S = ktypeof(b) - TricgSolver(n, m, S) - end +function TricgSolver(A, b) + n, m = size(A) + S = ktypeof(b) + TricgSolver(n, m, S) end """ @@ -659,40 +659,40 @@ mutable struct TrimrSolver{T,FC,S} <: KrylovSolver{T,FC,S} vₖ :: S warm_start :: Bool stats :: SimpleStats{T} +end - function TrimrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - y = S(undef, m) - N⁻¹uₖ₋₁ = S(undef, m) - N⁻¹uₖ = S(undef, m) - p = S(undef, m) - gy₂ₖ₋₃ = S(undef, m) - gy₂ₖ₋₂ = S(undef, m) - gy₂ₖ₋₁ = S(undef, m) - gy₂ₖ = S(undef, m) - x = S(undef, n) - M⁻¹vₖ₋₁ = S(undef, n) - M⁻¹vₖ = S(undef, n) - q = S(undef, n) - gx₂ₖ₋₃ = S(undef, n) - gx₂ₖ₋₂ = S(undef, n) - gx₂ₖ₋₁ = S(undef, n) - gx₂ₖ = S(undef, n) - Δx = S(undef, 0) - Δy = S(undef, 0) - uₖ = S(undef, 0) - vₖ = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) - return solver - end +function TrimrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + y = S(undef, m) + N⁻¹uₖ₋₁ = S(undef, m) + N⁻¹uₖ = S(undef, m) + p = S(undef, m) + gy₂ₖ₋₃ = S(undef, m) + gy₂ₖ₋₂ = S(undef, m) + gy₂ₖ₋₁ = S(undef, m) + gy₂ₖ = S(undef, m) + x = S(undef, n) + M⁻¹vₖ₋₁ = S(undef, n) + M⁻¹vₖ = S(undef, n) + q = S(undef, n) + gx₂ₖ₋₃ = S(undef, n) + gx₂ₖ₋₂ = S(undef, n) + gx₂ₖ₋₁ = S(undef, n) + gx₂ₖ = S(undef, n) + Δx = S(undef, 0) + Δy = S(undef, 0) + uₖ = S(undef, 0) + vₖ = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = TrimrSolver{T,FC,S}(y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) + return solver +end - function TrimrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - TrimrSolver(n, m, S) - end +function TrimrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + TrimrSolver(n, m, S) end """ @@ -721,33 +721,33 @@ mutable struct TrilqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} wₖ₋₂ :: S warm_start :: Bool stats :: AdjointStats{T} +end - function TrilqrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - uₖ₋₁ = S(undef, m) - uₖ = S(undef, m) - p = S(undef, m) - d̅ = S(undef, m) - Δx = S(undef, 0) - x = S(undef, m) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - q = S(undef, n) - Δy = S(undef, 0) - y = S(undef, n) - wₖ₋₃ = S(undef, n) - wₖ₋₂ = S(undef, n) - stats = AdjointStats(0, false, false, T[], T[], "unknown") - solver = new{T,FC,S}(uₖ₋₁, uₖ, p, d̅, Δx, x, vₖ₋₁, vₖ, q, Δy, y, wₖ₋₃, wₖ₋₂, false, stats) - return solver - end +function TrilqrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + uₖ₋₁ = S(undef, m) + uₖ = S(undef, m) + p = S(undef, m) + d̅ = S(undef, m) + Δx = S(undef, 0) + x = S(undef, m) + vₖ₋₁ = S(undef, n) + vₖ = S(undef, n) + q = S(undef, n) + Δy = S(undef, 0) + y = S(undef, n) + wₖ₋₃ = S(undef, n) + wₖ₋₂ = S(undef, n) + stats = AdjointStats(0, false, false, T[], T[], "unknown") + solver = TrilqrSolver{T,FC,S}(uₖ₋₁, uₖ, p, d̅, Δx, x, vₖ₋₁, vₖ, q, Δy, y, wₖ₋₃, wₖ₋₂, false, stats) + return solver +end - function TrilqrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - TrilqrSolver(n, m, S) - end +function TrilqrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + TrilqrSolver(n, m, S) end """ @@ -772,29 +772,29 @@ mutable struct CgsSolver{T,FC,S} <: KrylovSolver{T,FC,S} vw :: S warm_start :: Bool stats :: SimpleStats{T} +end - function CgsSolver(n, m, S) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - r = S(undef, n) - u = S(undef, n) - p = S(undef, n) - q = S(undef, n) - ts = S(undef, n) - yz = S(undef, 0) - vw = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, r, u, p, q, ts, yz, vw, false, stats) - return solver - end +function CgsSolver(n, m, S) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + r = S(undef, n) + u = S(undef, n) + p = S(undef, n) + q = S(undef, n) + ts = S(undef, n) + yz = S(undef, 0) + vw = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CgsSolver{T,FC,S}(Δx, x, r, u, p, q, ts, yz, vw, false, stats) + return solver +end - function CgsSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CgsSolver(n, m, S) - end +function CgsSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CgsSolver(n, m, S) end """ @@ -819,29 +819,29 @@ mutable struct BicgstabSolver{T,FC,S} <: KrylovSolver{T,FC,S} t :: S warm_start :: Bool stats :: SimpleStats{T} +end - function BicgstabSolver(n, m, S) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - r = S(undef, n) - p = S(undef, n) - v = S(undef, n) - s = S(undef, n) - qd = S(undef, n) - yz = S(undef, 0) - t = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, r, p, v, s, qd, yz, t, false, stats) - return solver - end +function BicgstabSolver(n, m, S) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + r = S(undef, n) + p = S(undef, n) + v = S(undef, n) + s = S(undef, n) + qd = S(undef, n) + yz = S(undef, 0) + t = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = BicgstabSolver{T,FC,S}(Δx, x, r, p, v, s, qd, yz, t, false, stats) + return solver +end - function BicgstabSolver(A, b) - n, m = size(A) - S = ktypeof(b) - BicgstabSolver(n, m, S) - end +function BicgstabSolver(A, b) + n, m = size(A) + S = ktypeof(b) + BicgstabSolver(n, m, S) end """ @@ -866,29 +866,29 @@ mutable struct BilqSolver{T,FC,S} <: KrylovSolver{T,FC,S} d̅ :: S warm_start :: Bool stats :: SimpleStats{T} +end - function BilqSolver(n, m, S) - FC = eltype(S) - T = real(FC) - uₖ₋₁ = S(undef, n) - uₖ = S(undef, n) - q = S(undef, n) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - p = S(undef, n) - Δx = S(undef, 0) - x = S(undef, n) - d̅ = S(undef, n) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, d̅, false, stats) - return solver - end +function BilqSolver(n, m, S) + FC = eltype(S) + T = real(FC) + uₖ₋₁ = S(undef, n) + uₖ = S(undef, n) + q = S(undef, n) + vₖ₋₁ = S(undef, n) + vₖ = S(undef, n) + p = S(undef, n) + Δx = S(undef, 0) + x = S(undef, n) + d̅ = S(undef, n) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = BilqSolver{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, d̅, false, stats) + return solver +end - function BilqSolver(A, b) - n, m = size(A) - S = ktypeof(b) - BilqSolver(n, m, S) - end +function BilqSolver(A, b) + n, m = size(A) + S = ktypeof(b) + BilqSolver(n, m, S) end """ @@ -914,30 +914,30 @@ mutable struct QmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} wₖ₋₁ :: S warm_start :: Bool stats :: SimpleStats{T} +end - function QmrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - uₖ₋₁ = S(undef, n) - uₖ = S(undef, n) - q = S(undef, n) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - p = S(undef, n) - Δx = S(undef, 0) - x = S(undef, n) - wₖ₋₂ = S(undef, n) - wₖ₋₁ = S(undef, n) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, wₖ₋₂, wₖ₋₁, false, stats) - return solver - end +function QmrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + uₖ₋₁ = S(undef, n) + uₖ = S(undef, n) + q = S(undef, n) + vₖ₋₁ = S(undef, n) + vₖ = S(undef, n) + p = S(undef, n) + Δx = S(undef, 0) + x = S(undef, n) + wₖ₋₂ = S(undef, n) + wₖ₋₁ = S(undef, n) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = QmrSolver{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, wₖ₋₂, wₖ₋₁, false, stats) + return solver +end - function QmrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - QmrSolver(n, m, S) - end +function QmrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + QmrSolver(n, m, S) end """ @@ -966,33 +966,33 @@ mutable struct BilqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} wₖ₋₂ :: S warm_start :: Bool stats :: AdjointStats{T} +end - function BilqrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - uₖ₋₁ = S(undef, n) - uₖ = S(undef, n) - q = S(undef, n) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - p = S(undef, n) - Δx = S(undef, 0) - x = S(undef, n) - Δy = S(undef, 0) - y = S(undef, n) - d̅ = S(undef, n) - wₖ₋₃ = S(undef, n) - wₖ₋₂ = S(undef, n) - stats = AdjointStats(0, false, false, T[], T[], "unknown") - solver = new{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, Δy, y, d̅, wₖ₋₃, wₖ₋₂, false, stats) - return solver - end +function BilqrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + uₖ₋₁ = S(undef, n) + uₖ = S(undef, n) + q = S(undef, n) + vₖ₋₁ = S(undef, n) + vₖ = S(undef, n) + p = S(undef, n) + Δx = S(undef, 0) + x = S(undef, n) + Δy = S(undef, 0) + y = S(undef, n) + d̅ = S(undef, n) + wₖ₋₃ = S(undef, n) + wₖ₋₂ = S(undef, n) + stats = AdjointStats(0, false, false, T[], T[], "unknown") + solver = BilqrSolver{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, Δy, y, d̅, wₖ₋₃, wₖ₋₂, false, stats) + return solver +end - function BilqrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - BilqrSolver(n, m, S) - end +function BilqrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + BilqrSolver(n, m, S) end """ @@ -1013,26 +1013,26 @@ mutable struct CglsSolver{T,FC,S} <: KrylovSolver{T,FC,S} q :: S Mr :: S stats :: SimpleStats{T} +end - function CglsSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - p = S(undef, m) - s = S(undef, m) - r = S(undef, n) - q = S(undef, n) - Mr = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, p, s, r, q, Mr, stats) - return solver - end +function CglsSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + p = S(undef, m) + s = S(undef, m) + r = S(undef, n) + q = S(undef, n) + Mr = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CglsSolver{T,FC,S}(x, p, s, r, q, Mr, stats) + return solver +end - function CglsSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CglsSolver(n, m, S) - end +function CglsSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CglsSolver(n, m, S) end """ @@ -1055,28 +1055,28 @@ mutable struct CrlsSolver{T,FC,S} <: KrylovSolver{T,FC,S} s :: S Ms :: S stats :: SimpleStats{T} +end - function CrlsSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - p = S(undef, m) - Ar = S(undef, m) - q = S(undef, m) - r = S(undef, n) - Ap = S(undef, n) - s = S(undef, n) - Ms = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, p, Ar, q, r, Ap, s, Ms, stats) - return solver - end +function CrlsSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + p = S(undef, m) + Ar = S(undef, m) + q = S(undef, m) + r = S(undef, n) + Ap = S(undef, n) + s = S(undef, n) + Ms = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CrlsSolver{T,FC,S}(x, p, Ar, q, r, Ap, s, Ms, stats) + return solver +end - function CrlsSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CrlsSolver(n, m, S) - end +function CrlsSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CrlsSolver(n, m, S) end """ @@ -1098,27 +1098,27 @@ mutable struct CgneSolver{T,FC,S} <: KrylovSolver{T,FC,S} s :: S z :: S stats :: SimpleStats{T} +end - function CgneSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - p = S(undef, m) - Aᵀz = S(undef, m) - r = S(undef, n) - q = S(undef, n) - s = S(undef, 0) - z = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, p, Aᵀz, r, q, s, z, stats) - return solver - end +function CgneSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + p = S(undef, m) + Aᵀz = S(undef, m) + r = S(undef, n) + q = S(undef, n) + s = S(undef, 0) + z = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CgneSolver{T,FC,S}(x, p, Aᵀz, r, q, s, z, stats) + return solver +end - function CgneSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CgneSolver(n, m, S) - end +function CgneSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CgneSolver(n, m, S) end """ @@ -1140,27 +1140,27 @@ mutable struct CrmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} Mq :: S s :: S stats :: SimpleStats{T} +end - function CrmrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - p = S(undef, m) - Aᵀr = S(undef, m) - r = S(undef, n) - q = S(undef, n) - Mq = S(undef, 0) - s = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, p, Aᵀr, r, q, Mq, s, stats) - return solver - end +function CrmrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + p = S(undef, m) + Aᵀr = S(undef, m) + r = S(undef, n) + q = S(undef, n) + Mq = S(undef, 0) + s = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CrmrSolver{T,FC,S}(x, p, Aᵀr, r, q, Mq, s, stats) + return solver +end - function CrmrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CrmrSolver(n, m, S) - end +function CrmrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CrmrSolver(n, m, S) end """ @@ -1184,29 +1184,29 @@ mutable struct LslqSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S err_vec :: Vector{T} stats :: LSLQStats{T} +end - function LslqSolver(n, m, S; window :: Int=5) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᵀu = S(undef, m) - w̄ = S(undef, m) - Mu = S(undef, n) - Av = S(undef, n) - u = S(undef, 0) - v = S(undef, 0) - err_vec = zeros(T, window) - stats = LSLQStats(0, false, false, T[], T[], T[], false, T[], T[], "unknown") - solver = new{T,FC,S}(x, Nv, Aᵀu, w̄, Mu, Av, u, v, err_vec, stats) - return solver - end +function LslqSolver(n, m, S; window :: Int=5) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + Nv = S(undef, m) + Aᵀu = S(undef, m) + w̄ = S(undef, m) + Mu = S(undef, n) + Av = S(undef, n) + u = S(undef, 0) + v = S(undef, 0) + err_vec = zeros(T, window) + stats = LSLQStats(0, false, false, T[], T[], T[], false, T[], T[], "unknown") + solver = LslqSolver{T,FC,S}(x, Nv, Aᵀu, w̄, Mu, Av, u, v, err_vec, stats) + return solver +end - function LslqSolver(A, b; window :: Int=5) - n, m = size(A) - S = ktypeof(b) - LslqSolver(n, m, S, window=window) - end +function LslqSolver(A, b; window :: Int=5) + n, m = size(A) + S = ktypeof(b) + LslqSolver(n, m, S, window=window) end """ @@ -1230,29 +1230,29 @@ mutable struct LsqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S err_vec :: Vector{T} stats :: SimpleStats{T} +end - function LsqrSolver(n, m, S; window :: Int=5) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᵀu = S(undef, m) - w = S(undef, m) - Mu = S(undef, n) - Av = S(undef, n) - u = S(undef, 0) - v = S(undef, 0) - err_vec = zeros(T, window) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, Nv, Aᵀu, w, Mu, Av, u, v, err_vec, stats) - return solver - end +function LsqrSolver(n, m, S; window :: Int=5) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + Nv = S(undef, m) + Aᵀu = S(undef, m) + w = S(undef, m) + Mu = S(undef, n) + Av = S(undef, n) + u = S(undef, 0) + v = S(undef, 0) + err_vec = zeros(T, window) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = LsqrSolver{T,FC,S}(x, Nv, Aᵀu, w, Mu, Av, u, v, err_vec, stats) + return solver +end - function LsqrSolver(A, b; window :: Int=5) - n, m = size(A) - S = ktypeof(b) - LsqrSolver(n, m, S, window=window) - end +function LsqrSolver(A, b; window :: Int=5) + n, m = size(A) + S = ktypeof(b) + LsqrSolver(n, m, S, window=window) end """ @@ -1277,30 +1277,30 @@ mutable struct LsmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S err_vec :: Vector{T} stats :: LsmrStats{T} +end - function LsmrSolver(n, m, S; window :: Int=5) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᵀu = S(undef, m) - h = S(undef, m) - hbar = S(undef, m) - Mu = S(undef, n) - Av = S(undef, n) - u = S(undef, 0) - v = S(undef, 0) - err_vec = zeros(T, window) - stats = LsmrStats(0, false, false, T[], T[], zero(T), zero(T), zero(T), zero(T), zero(T), "unknown") - solver = new{T,FC,S}(x, Nv, Aᵀu, h, hbar, Mu, Av, u, v, err_vec, stats) - return solver - end +function LsmrSolver(n, m, S; window :: Int=5) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + Nv = S(undef, m) + Aᵀu = S(undef, m) + h = S(undef, m) + hbar = S(undef, m) + Mu = S(undef, n) + Av = S(undef, n) + u = S(undef, 0) + v = S(undef, 0) + err_vec = zeros(T, window) + stats = LsmrStats(0, false, false, T[], T[], zero(T), zero(T), zero(T), zero(T), zero(T), "unknown") + solver = LsmrSolver{T,FC,S}(x, Nv, Aᵀu, h, hbar, Mu, Av, u, v, err_vec, stats) + return solver +end - function LsmrSolver(A, b; window :: Int=5) - n, m = size(A) - S = ktypeof(b) - LsmrSolver(n, m, S, window=window) - end +function LsmrSolver(A, b; window :: Int=5) + n, m = size(A) + S = ktypeof(b) + LsmrSolver(n, m, S, window=window) end """ @@ -1325,30 +1325,30 @@ mutable struct LnlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S q :: S stats :: LNLQStats{T} +end - function LnlqSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᵀu = S(undef, m) - y = S(undef, n) - w̄ = S(undef, n) - Mu = S(undef, n) - Av = S(undef, n) - u = S(undef, 0) - v = S(undef, 0) - q = S(undef, 0) - stats = LNLQStats(0, false, T[], false, T[], T[], "unknown") - solver = new{T,FC,S}(x, Nv, Aᵀu, y, w̄, Mu, Av, u, v, q, stats) - return solver - end +function LnlqSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + Nv = S(undef, m) + Aᵀu = S(undef, m) + y = S(undef, n) + w̄ = S(undef, n) + Mu = S(undef, n) + Av = S(undef, n) + u = S(undef, 0) + v = S(undef, 0) + q = S(undef, 0) + stats = LNLQStats(0, false, T[], false, T[], T[], "unknown") + solver = LnlqSolver{T,FC,S}(x, Nv, Aᵀu, y, w̄, Mu, Av, u, v, q, stats) + return solver +end - function LnlqSolver(A, b) - n, m = size(A) - S = ktypeof(b) - LnlqSolver(n, m, S) - end +function LnlqSolver(A, b) + n, m = size(A) + S = ktypeof(b) + LnlqSolver(n, m, S) end """ @@ -1373,30 +1373,30 @@ mutable struct CraigSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S w2 :: S stats :: SimpleStats{T} +end - function CraigSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᵀu = S(undef, m) - y = S(undef, n) - w = S(undef, n) - Mu = S(undef, n) - Av = S(undef, n) - u = S(undef, 0) - v = S(undef, 0) - w2 = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, Nv, Aᵀu, y, w, Mu, Av, u, v, w2, stats) - return solver - end +function CraigSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + Nv = S(undef, m) + Aᵀu = S(undef, m) + y = S(undef, n) + w = S(undef, n) + Mu = S(undef, n) + Av = S(undef, n) + u = S(undef, 0) + v = S(undef, 0) + w2 = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CraigSolver{T,FC,S}(x, Nv, Aᵀu, y, w, Mu, Av, u, v, w2, stats) + return solver +end - function CraigSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CraigSolver(n, m, S) - end +function CraigSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CraigSolver(n, m, S) end """ @@ -1423,32 +1423,32 @@ mutable struct CraigmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} v :: S q :: S stats :: SimpleStats{T} +end - function CraigmrSolver(n, m, S) - FC = eltype(S) - T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᵀu = S(undef, m) - d = S(undef, m) - y = S(undef, n) - Mu = S(undef, n) - w = S(undef, n) - wbar = S(undef, n) - Av = S(undef, n) - u = S(undef, 0) - v = S(undef, 0) - q = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(x, Nv, Aᵀu, d, y, Mu, w, wbar, Av, u, v, q, stats) - return solver - end +function CraigmrSolver(n, m, S) + FC = eltype(S) + T = real(FC) + x = S(undef, m) + Nv = S(undef, m) + Aᵀu = S(undef, m) + d = S(undef, m) + y = S(undef, n) + Mu = S(undef, n) + w = S(undef, n) + wbar = S(undef, n) + Av = S(undef, n) + u = S(undef, 0) + v = S(undef, 0) + q = S(undef, 0) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = CraigmrSolver{T,FC,S}(x, Nv, Aᵀu, d, y, Mu, w, wbar, Av, u, v, q, stats) + return solver +end - function CraigmrSolver(A, b) - n, m = size(A) - S = ktypeof(b) - CraigmrSolver(n, m, S) - end +function CraigmrSolver(A, b) + n, m = size(A) + S = ktypeof(b) + CraigmrSolver(n, m, S) end """ @@ -1476,31 +1476,31 @@ mutable struct GmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} warm_start :: Bool inner_iter :: Int stats :: SimpleStats{T} +end - function GmresSolver(n, m, memory, S) - memory = min(n, memory) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - w = S(undef, n) - p = S(undef, 0) - q = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] - c = Vector{T}(undef, memory) - s = Vector{FC}(undef, memory) - z = Vector{FC}(undef, memory) - R = Vector{FC}(undef, div(memory * (memory+1), 2)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, w, p, q, V, c, s, z, R, false, 0, stats) - return solver - end +function GmresSolver(n, m, memory, S) + memory = min(n, memory) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + w = S(undef, n) + p = S(undef, 0) + q = S(undef, 0) + V = [S(undef, n) for i = 1 : memory] + c = Vector{T}(undef, memory) + s = Vector{FC}(undef, memory) + z = Vector{FC}(undef, memory) + R = Vector{FC}(undef, div(memory * (memory+1), 2)) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = GmresSolver{T,FC,S}(Δx, x, w, p, q, V, c, s, z, R, false, 0, stats) + return solver +end - function GmresSolver(A, b, memory = 20) - n, m = size(A) - S = ktypeof(b) - GmresSolver(n, m, memory, S) - end +function GmresSolver(A, b, memory = 20) + n, m = size(A) + S = ktypeof(b) + GmresSolver(n, m, memory, S) end """ @@ -1526,30 +1526,30 @@ mutable struct FomSolver{T,FC,S} <: KrylovSolver{T,FC,S} U :: Vector{FC} warm_start :: Bool stats :: SimpleStats{T} +end - function FomSolver(n, m, memory, S) - memory = min(n, memory) - FC = eltype(S) - T = real(FC) - Δx = S(undef, 0) - x = S(undef, n) - w = S(undef, n) - p = S(undef, 0) - q = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] - l = Vector{FC}(undef, memory) - z = Vector{FC}(undef, memory) - U = Vector{FC}(undef, div(memory * (memory+1), 2)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(Δx, x, w, p, q, V, l, z, U, false, stats) - return solver - end +function FomSolver(n, m, memory, S) + memory = min(n, memory) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + w = S(undef, n) + p = S(undef, 0) + q = S(undef, 0) + V = [S(undef, n) for i = 1 : memory] + l = Vector{FC}(undef, memory) + z = Vector{FC}(undef, memory) + U = Vector{FC}(undef, div(memory * (memory+1), 2)) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = FomSolver{T,FC,S}(Δx, x, w, p, q, V, l, z, U, false, stats) + return solver +end - function FomSolver(A, b, memory = 20) - n, m = size(A) - S = ktypeof(b) - FomSolver(n, m, memory, S) - end +function FomSolver(A, b, memory = 20) + n, m = size(A) + S = ktypeof(b) + FomSolver(n, m, memory, S) end """ @@ -1582,37 +1582,37 @@ mutable struct GpmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} R :: Vector{FC} warm_start :: Bool stats :: SimpleStats{T} +end - function GpmrSolver(n, m, memory, S) - memory = min(n + m, memory) - FC = eltype(S) - T = real(FC) - wA = S(undef, 0) - wB = S(undef, 0) - dA = S(undef, n) - dB = S(undef, m) - Δx = S(undef, 0) - Δy = S(undef, 0) - x = S(undef, n) - y = S(undef, m) - q = S(undef, 0) - p = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] - U = [S(undef, m) for i = 1 : memory] - gs = Vector{FC}(undef, 4 * memory) - gc = Vector{T}(undef, 4 * memory) - zt = Vector{FC}(undef, 2 * memory) - R = Vector{FC}(undef, memory * (2memory + 1)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = new{T,FC,S}(wA, wB, dA, dB, Δx, Δy, x, y, q, p, V, U, gs, gc, zt, R, false, stats) - return solver - end +function GpmrSolver(n, m, memory, S) + memory = min(n + m, memory) + FC = eltype(S) + T = real(FC) + wA = S(undef, 0) + wB = S(undef, 0) + dA = S(undef, n) + dB = S(undef, m) + Δx = S(undef, 0) + Δy = S(undef, 0) + x = S(undef, n) + y = S(undef, m) + q = S(undef, 0) + p = S(undef, 0) + V = [S(undef, n) for i = 1 : memory] + U = [S(undef, m) for i = 1 : memory] + gs = Vector{FC}(undef, 4 * memory) + gc = Vector{T}(undef, 4 * memory) + zt = Vector{FC}(undef, 2 * memory) + R = Vector{FC}(undef, memory * (2memory + 1)) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = GpmrSolver{T,FC,S}(wA, wB, dA, dB, Δx, Δy, x, y, q, p, V, U, gs, gc, zt, R, false, stats) + return solver +end - function GpmrSolver(A, b, memory = 20) - n, m = size(A) - S = ktypeof(b) - GpmrSolver(n, m, memory, S) - end +function GpmrSolver(A, b, memory = 20) + n, m = size(A) + S = ktypeof(b) + GpmrSolver(n, m, memory, S) end """ From 7c5e2337fc2231300496f27cc2ee80f52e18c807 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 7 Sep 2022 00:34:21 -0400 Subject: [PATCH 002/182] Add a reference to BiCG paper --- src/bilq.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bilq.jl b/src/bilq.jl index 39725fbfe..b41f890a8 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -39,9 +39,10 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. -#### Reference +#### References * A. Montoison and D. Orban, [*BiLQ: An Iterative Method for Nonsymmetric Linear Systems with a Quasi-Minimum Error Property*](https://doi.org/10.1137/19M1290991), SIAM Journal on Matrix Analysis and Applications, 41(3), pp. 1145--1166, 2020. +* R. Fletcher, [*Conjugate gradient methods for indefinite systems*](https://doi.org/10.1007/BFb0080116), Numerical Analysis, pp. 73--89, 1976. """ function bilq end From 3cc80c6d154be9799ddc6eb05311349138795b37 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Wed, 7 Sep 2022 14:04:59 -0400 Subject: [PATCH 003/182] Update src/bilq.jl --- src/bilq.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bilq.jl b/src/bilq.jl index b41f890a8..ce84d3ec1 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -42,7 +42,7 @@ and `false` otherwise. #### References * A. Montoison and D. Orban, [*BiLQ: An Iterative Method for Nonsymmetric Linear Systems with a Quasi-Minimum Error Property*](https://doi.org/10.1137/19M1290991), SIAM Journal on Matrix Analysis and Applications, 41(3), pp. 1145--1166, 2020. -* R. Fletcher, [*Conjugate gradient methods for indefinite systems*](https://doi.org/10.1007/BFb0080116), Numerical Analysis, pp. 73--89, 1976. +* R. Fletcher, [*Conjugate gradient methods for indefinite systems*](https://doi.org/10.1007/BFb0080116), Numerical Analysis, Springer, pp. 73--89, 1976. """ function bilq end From 69e7c1d80068356fdcbe376d7c9c508c8b503b28 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 18 Aug 2022 23:12:15 -0400 Subject: [PATCH 004/182] [documentation] Add a section about preconditioners --- docs/make.jl | 3 +- docs/src/preconditioners.md | 63 +++++++++++++++++++++++++++++++++++++ src/cgne.jl | 18 +++++------ src/crmr.jl | 24 +++++++------- src/krylov_solvers.jl | 6 ++-- test/test_cgne.jl | 14 ++++----- test/test_crmr.jl | 10 +++--- 7 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 docs/src/preconditioners.md diff --git a/docs/make.jl b/docs/make.jl index 57ad87cd2..48263fe25 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,12 +15,13 @@ makedocs( "Krylov methods" => ["Symmetric positive definite linear systems" => "solvers/spd.md", "Symmetric indefinite linear systems" => "solvers/sid.md", "Unsymmetric linear systems" => "solvers/unsymmetric.md", - "Least-norm problems" => "solvers/ln.md", + "Minimum-norm problems" => "solvers/ln.md", "Least-squares problems" => "solvers/ls.md", "Adjoint systems" => "solvers/as.md", "Saddle-point and symmetric quasi-definite systems" => "solvers/sp_sqd.md", "Generalized saddle-point and unsymmetric partitioned systems" => "solvers/gsp.md"], "In-place methods" => "inplace.md", + "Preconditioners" => "preconditioners.md", "GPU support" => "gpu.md", "Warm start" => "warm_start.md", "Factorization-free operators" => "factorization-free.md", diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md new file mode 100644 index 000000000..22b0f445f --- /dev/null +++ b/docs/src/preconditioners.md @@ -0,0 +1,63 @@ +## [Preconditioners](@id preconditioners) + +The solvers in Krylov.jl support preconditioners that modify a given linear systems $Ax = b$ into a form that allows a faster convergence. + +It exists three variants of preconditioning: + +| Left preconditioning | Two-sided preconditioning | Right preconditioning | +|:--------------------:|:--------------------------:|:-------------------------:| +| $MAx = Mb$ | $MANy = Mb$~~with~~$x = Ny$| $ANy = b$~~with~~$x = Ny$ | + +#### Unsymmetric linear systems + +A Krylov method dedicated to unsymmetric systems allows the three variants. +We provide these preconditioners with the arguments `M` and `N`. +It concerns the methods [`CGS`](@ref cgs), [`BiCGSTAB`](@ref bicgstab), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres), [`DIOM`](@ref diom) and [`FOM`](@ref fom). + +#### Symmetric linear systems + +When $A$ is symmetric, we can only use the centered / split preconditioning $LAL^Tx = Lb$. +It is a special case of two-sided preconditioning $M=L=N^T$ that maintains the symmetry of the linear systems. +Krylov methods dedicated to symmetric systems take directly as input a symmetric positive preconditioner $P=LL^T$. +We provide this preconditioner with the argument `M` in [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref cg_lanczos), [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift), [`CR`](@ref cr), [`MINRES`](@ref minres) and [`MINRES-QLP`](@ref minres_qlp). + +#### Least-squares problems + +For linear least-squares problem $\min \|b - Ax\|^2_2$, a preconditioner `M` modifies the problem such that $\min \|b - Ax\|^2_M$ is solved. +It is equivalent to solve the normal equation $A^TMAx = A^TMb$ instead of $A^TAx = A^Tb$. +We provide a symmetric positive definite preconditioner with the argument `M` in [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). + +#### Minimum-norm problems + +For minimum-norm problem $\min \|x\|^2_2$~~s.t.~~$Ax = b$, a preconditioner `N` modifies the problem such that $\min \|x\|^2_{N^{-1}}$~~s.t.~~$Ax = b$ is solved. +It is equivalent to solve the normal equation $ANA^Tx = b$ instead of $AA^Tx = b$. +We provide a symmetric positive definite preconditioner with the argument `N` in [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig) and [`CRAIGMR`](@ref craigmr). + +#### Saddle-point and symmetric quasi-definite systems + +When a symmetric system $Kz = d$ has the 2x2 block structure +```math + \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, +``` +where $E$ and $F$ are symmetric positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this structure if preconditioners `M` and `N` such that $M = E^{-1}$ and $N = F^{-1}$ are available. + +#### Generalized saddle-point and unsymmetric partitioned systems + +When an unsymmetric system $Kz = d$ has the 2x2 block structure +```math + \begin{bmatrix} \lambda M & A \\ B & \mu N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, +``` +[`GPMR`](@ref gpmr) can take advantage of this structure if preconditioners `C`, `D`, `E` and `F` such that $CE = M^{-1}$ and $DF = N^{-1}$ are available. + +!!! tip + A preconditioner `P` only needs to support the operation `mul!(y, P, x)` to be used in Krylov.jl. + +!!! note + Our implementations of [`BiLQ`](@ref bilq), [`QMR`](@ref qmr), [`BiLQR`](@ref bilqr), [`USYMLQ`](@ref usymlq), [`USYMQR`](@ref usymqr) and [`TriLQR`](@ref trilqr) don't support preconditioning. + +## Packages that provide preconditioners + +- [IncompleteLU.jl](https://github.com/haampie/IncompleteLU.jl) implements the left-looking or Crout version of ILU decompositions. +- [ILUZero.jl](https://github.com/mcovalt/ILUZero.jl) is a Julia implementation of incomplete LU factorization with zero level of fill-in. +- [LimitedLDLFactorizations.jl](https://github.com/JuliaSmoothOptimizers/LimitedLDLFactorizations.jl) for limited-memory LDLᵀ factorization of symmetric matrices. +- [AlgebraicMultigrid.jl](https://github.com/JuliaLinearAlgebra/AlgebraicMultigrid.jl) provides two algebraic multigrid (AMG) preconditioners. diff --git a/src/cgne.jl b/src/cgne.jl index 2859414e1..2f720b57c 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -31,7 +31,7 @@ export cgne, cgne! """ (x, stats) = cgne(A, b::AbstractVector{FC}; - M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), + N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) @@ -60,7 +60,7 @@ CGNE produces monotonic errors ‖x-x*‖₂ but not residuals ‖r‖₂. It is formally equivalent to CRAIG, though can be slightly less accurate, but simpler to implement. Only the x-part of the solution is returned. -A preconditioner M may be provided in the form of a linear operator. +A preconditioner N may be provided in the form of a linear operator. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -88,7 +88,7 @@ See [`CgneSolver`](@ref) for more details about the `solver`. function cgne! end function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), + N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} @@ -96,8 +96,8 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("CGNE: system of %d equations in %d variables\n", m, n) - # Tests M = Iₙ - MisI = (M === I) + # Tests N = Iₙ + NisI = (N === I) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -107,16 +107,16 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; Aᵀ = A' # Set up workspace. - allocate_if(!MisI, solver, :z, S, m) + allocate_if(!NisI, solver, :z, S, m) allocate_if(λ > 0, solver, :s, S, m) x, p, Aᵀz, r, q, s, stats = solver.x, solver.p, solver.Aᵀz, solver.r, solver.q, solver.s, solver.stats rNorms = stats.residuals reset!(stats) - z = MisI ? r : solver.z + z = NisI ? r : solver.z x .= zero(FC) r .= b - MisI || mulorldiv!(z, M, r, ldiv) + NisI || mulorldiv!(z, N, r, ldiv) rNorm = @knrm2(m, r) # Marginally faster than norm(r) history && push!(rNorms, rNorm) if rNorm == 0 @@ -158,7 +158,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; α = γ / δ @kaxpy!(n, α, p, x) # Faster than x = x + α * p @kaxpy!(m, -α, q, r) # Faster than r = r - α * q - MisI || mulorldiv!(z, M, r, ldiv) + NisI || mulorldiv!(z, N, r, ldiv) γ_next = @kdotr(m, r, z) # Faster than γ_next = dot(r, z) β = γ_next / γ mul!(Aᵀz, Aᵀ, z) diff --git a/src/crmr.jl b/src/crmr.jl index deb5cf79f..6ed2b3c60 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -12,7 +12,7 @@ # # AAᵀy = b. # -# This method is equivalent to Craig-MR, described in +# This method is equivalent to CRAIGMR, described in # # D. Orban and M. Arioli. Iterative Solution of Symmetric Quasi-Definite Linear Systems, # Volume 3 of Spotlights. SIAM, Philadelphia, PA, 2017. @@ -29,7 +29,7 @@ export crmr, crmr! """ (x, stats) = crmr(A, b::AbstractVector{FC}; - M=I, λ::T=zero(T), atol::T=√eps(T), + N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) @@ -58,7 +58,7 @@ CRMR produces monotonic residuals ‖r‖₂. It is formally equivalent to CRAIG-MR, though can be slightly less accurate, but simpler to implement. Only the x-part of the solution is returned. -A preconditioner M may be provided. +A preconditioner N may be provided. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -86,7 +86,7 @@ See [`CrmrSolver`](@ref) for more details about the `solver`. function crmr! end function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, λ :: T=zero(T), atol :: T=√eps(T), + N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} @@ -94,8 +94,8 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("CRMR: system of %d equations in %d variables\n", m, n) - # Tests M = Iₙ - MisI = (M === I) + # Tests N = Iₙ + NisI = (N === I) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -105,16 +105,16 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; Aᵀ = A' # Set up workspace. - allocate_if(!MisI, solver, :Mq, S, m) + allocate_if(!NisI, solver, :Nq, S, m) allocate_if(λ > 0, solver, :s , S, m) x, p, Aᵀr, r = solver.x, solver.p, solver.Aᵀr, solver.r q, s, stats = solver.q, solver.s, solver.stats rNorms, ArNorms = stats.residuals, stats.Aresiduals reset!(stats) - Mq = MisI ? q : solver.Mq + Nq = NisI ? q : solver.Nq x .= zero(FC) # initial estimation x = 0 - mulorldiv!(r, M, b, ldiv) # initial residual r = M * (b - Ax) = M * b + mulorldiv!(r, N, b, ldiv) # initial residual r = M * (b - Ax) = M * b bNorm = @knrm2(m, r) # norm(b - A * x0) if x0 ≠ 0. rNorm = bNorm # + λ * ‖x0‖ if x0 ≠ 0 and λ > 0. history && push!(rNorms, rNorm) @@ -149,10 +149,10 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; while ! (solved || inconsistent || tired || user_requested_exit) mul!(q, A, p) λ > 0 && @kaxpy!(m, λ, s, q) # q = q + λ * s - MisI || mulorldiv!(Mq, M, q, ldiv) - α = γ / @kdotr(m, q, Mq) # Compute qᵗ * M * q + NisI || mulorldiv!(Nq, N, q, ldiv) + α = γ / @kdotr(m, q, Nq) # Compute qᵗ * M * q @kaxpy!(n, α, p, x) # Faster than x = x + α * p - @kaxpy!(m, -α, Mq, r) # Faster than r = r - α * Mq + @kaxpy!(m, -α, Nq, r) # Faster than r = r - α * Nq rNorm = @knrm2(m, r) # norm(r) mul!(Aᵀr, Aᵀ, r) γ_next = @kdotr(n, Aᵀr, Aᵀr) # Faster than γ_next = dot(Aᵀr, Aᵀr) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index a6da85bd5..d557d91ae 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1137,7 +1137,7 @@ mutable struct CrmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} Aᵀr :: S r :: S q :: S - Mq :: S + Nq :: S s :: S stats :: SimpleStats{T} end @@ -1150,10 +1150,10 @@ function CrmrSolver(n, m, S) Aᵀr = S(undef, m) r = S(undef, n) q = S(undef, n) - Mq = S(undef, 0) + Nq = S(undef, 0) s = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CrmrSolver{T,FC,S}(x, p, Aᵀr, r, q, Mq, s, stats) + solver = CrmrSolver{T,FC,S}(x, p, Aᵀr, r, q, Nq, s, stats) return solver end diff --git a/test/test_cgne.jl b/test/test_cgne.jl index 64cbc0ea7..a48b4569e 100644 --- a/test/test_cgne.jl +++ b/test/test_cgne.jl @@ -1,6 +1,6 @@ -function test_cgne(A, b; λ=0.0, M=I) +function test_cgne(A, b; λ=0.0, N=I) (nrow, ncol) = size(A) - (x, stats) = cgne(A, b, λ=λ, M=M) + (x, stats) = cgne(A, b, λ=λ, N=N) r = b - A * x if λ > 0 s = r / sqrt(λ) @@ -69,8 +69,8 @@ end @test stats.status == "x = 0 is a zero-residual solution" # Test with Jacobi (or diagonal) preconditioner - A, b, M = square_preconditioned(FC=FC) - (x, stats, resid) = test_cgne(A, b, M=M) + A, b, N = square_preconditioned(FC=FC) + (x, stats, resid) = test_cgne(A, b, N=N) @test(resid ≤ cgne_tol) @test(stats.solved) (xI, xmin, xmin_norm) = check_min_norm(A, b, x) @@ -81,8 +81,8 @@ end A = 0.5 * [19.0 17.0 15.0 13.0 11.0 9.0 7.0 5.0 3.0 1.0; 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0] b = [1.0; 0.0] - M = Diagonal(1 ./ (A * A')) - (x, stats, resid) = test_cgne(A, b, M=M) + N = Diagonal(1 ./ (A * A')) + (x, stats, resid) = test_cgne(A, b, N=N) @test(resid ≤ cgne_tol) @test(stats.solved) (xI, xmin, xmin_norm) = check_min_norm(A, b, x) @@ -92,7 +92,7 @@ end for transpose ∈ (false, true) A, b, c, D = small_sp(transpose, FC=FC) D⁻¹ = inv(D) - (x, stats) = cgne(A, b, M=D⁻¹, λ=1.0) + (x, stats) = cgne(A, b, N=D⁻¹, λ=1.0) end # test callback function diff --git a/test/test_crmr.jl b/test/test_crmr.jl index 6354f329f..d0f902df6 100644 --- a/test/test_crmr.jl +++ b/test/test_crmr.jl @@ -1,6 +1,6 @@ -function test_crmr(A, b; λ=0.0, M=I, history=false) +function test_crmr(A, b; λ=0.0, N=I, history=false) (nrow, ncol) = size(A) - (x, stats) = crmr(A, b, λ=λ, M=M, history=history) + (x, stats) = crmr(A, b, λ=λ, N=N, history=history) r = b - A * x if λ > 0 s = r / sqrt(λ) @@ -76,8 +76,8 @@ end A = 0.5 * [19.0 17.0 15.0 13.0 11.0 9.0 7.0 5.0 3.0 1.0; 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0] b = [1.0; 0.0] - M = Diagonal(1 ./ (A * A')) - (x, stats, resid) = test_crmr(A, b, M=M) + N = Diagonal(1 ./ (A * A')) + (x, stats, resid) = test_crmr(A, b, N=N) @test(resid ≤ crmr_tol) @test(stats.solved) (xI, xmin, xmin_norm) = check_min_norm(A, b, x) @@ -87,7 +87,7 @@ end for transpose ∈ (false, true) A, b, c, D = small_sp(transpose, FC=FC) D⁻¹ = inv(D) - (x, stats) = crmr(A, b, M=D⁻¹, λ=1.0) + (x, stats) = crmr(A, b, N=D⁻¹, λ=1.0) end # test callback function From cce31e670b0a62888e1a7cbb506ff72c337c122f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 19 Aug 2022 03:24:51 -0400 Subject: [PATCH 005/182] Add more details about preconditioned LS and LN problems --- docs/src/preconditioners.md | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 22b0f445f..0f51e27fb 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -4,9 +4,9 @@ The solvers in Krylov.jl support preconditioners that modify a given linear syst It exists three variants of preconditioning: -| Left preconditioning | Two-sided preconditioning | Right preconditioning | -|:--------------------:|:--------------------------:|:-------------------------:| -| $MAx = Mb$ | $MANy = Mb$~~with~~$x = Ny$| $ANy = b$~~with~~$x = Ny$ | +| Left preconditioning | Two-sided preconditioning | Right preconditioning | +|:--------------------:|:-------------------------------:|:------------------------------:| +| $MAx = Mb$ | $MANy = Mb~~\text{with}~~x = Ny$| $ANy = b~~\text{with}~~x = Ny$ | #### Unsymmetric linear systems @@ -23,31 +23,56 @@ We provide this preconditioner with the argument `M` in [`SYMMLQ`](@ref symmlq), #### Least-squares problems -For linear least-squares problem $\min \|b - Ax\|^2_2$, a preconditioner `M` modifies the problem such that $\min \|b - Ax\|^2_M$ is solved. -It is equivalent to solve the normal equation $A^TMAx = A^TMb$ instead of $A^TAx = A^Tb$. +| Formulation | Without preconditioning | With preconditioning | +|:---------------------:|:-----------------------:|:-----------------------:| +| least-squares problem | $\min \\|b - Ax\\|^2_2$ | $\min \\|b - Ax\\|^2_M$ | +| Normal equation | $A^TAx = A^Tb$ | $A^TMAx = A^TMb$ | +| Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} M & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | + We provide a symmetric positive definite preconditioner with the argument `M` in [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). +A second positive definite preconditioner `N` is supported by [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). +It is dedicated to regularized least-squares problems. + +| Formulation | Without preconditioning | With preconditioning | +|:---------------------:|:-----------------------------------------------:|:------------------------------------------------------:| +| least-squares problem | $\min \\|b - Ax\\|^2_2 + \lambda^2 \\|x\\|^2_2$ | $\min \\|b - Ax\\|^2_M + \lambda^2 \\|x\\|^2_{N^{-1}}$ | +| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TMA + \lambda^2 N^{-1})x = A^TMb$ | +| Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} M & A \\ A^T & -\lambda^2 N \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | + #### Minimum-norm problems -For minimum-norm problem $\min \|x\|^2_2$~~s.t.~~$Ax = b$, a preconditioner `N` modifies the problem such that $\min \|x\|^2_{N^{-1}}$~~s.t.~~$Ax = b$ is solved. -It is equivalent to solve the normal equation $ANA^Tx = b$ instead of $AA^Tx = b$. +| Formulation | Without preconditioning | With preconditioning | +|:--------------------:|:---------------------------------------:|:----------------------------------------------:| +| minimum-norm problem | $\min \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \\|x\\|^2_{N^{-1}}~~\text{s.t.}~~Ax = b$ | +| Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $ANA^Ty = b~~\text{with}~~x = NA^Ty$ | +| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -N & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | + We provide a symmetric positive definite preconditioner with the argument `N` in [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig) and [`CRAIGMR`](@ref craigmr). +A second positive definite preconditioner `M` is supported by [`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr). +It is dedicated to penalized minimum-norm problems. + +| Formulation | Without preconditioning | With preconditioning | +|:--------------------:|:-------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:| +| minimum-norm problem | $\min \\|x\\|^2_2 + \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \\|x\\|^2_{N^{-1}} + \\|y\\|^2_{M^{-1}}~~\text{s.t.}~~Ax + \lambda^2 M^{-1}y = b$ | +| Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(ANA^T + \lambda^2 M^{-1})y = b~~\text{with}~~x = NA^Ty$ | +| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -N^{-1} & A^T \\ \phantom{-}A & \lambda^2 M^{-1} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | #### Saddle-point and symmetric quasi-definite systems When a symmetric system $Kz = d$ has the 2x2 block structure ```math - \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, + \begin{bmatrix} \tau M^{-1} & \phantom{-}A \\ A^T & \nu N^{-1} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` -where $E$ and $F$ are symmetric positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this structure if preconditioners `M` and `N` such that $M = E^{-1}$ and $N = F^{-1}$ are available. +where $M^{-1}$ and $N^{-1}$ are symmetric positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this structure if preconditioners `M` and `N` that model $M$ and $N$ are available. #### Generalized saddle-point and unsymmetric partitioned systems When an unsymmetric system $Kz = d$ has the 2x2 block structure ```math - \begin{bmatrix} \lambda M & A \\ B & \mu N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, + \begin{bmatrix} \lambda M^{-1} & A \\ B & \mu N^{-1} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` -[`GPMR`](@ref gpmr) can take advantage of this structure if preconditioners `C`, `D`, `E` and `F` such that $CE = M^{-1}$ and $DF = N^{-1}$ are available. +[`GPMR`](@ref gpmr) can take advantage of this structure if preconditioners `C`, `D`, `E` and `F` such that $CE = M$ and $DF = N$ are available. !!! tip A preconditioner `P` only needs to support the operation `mul!(y, P, x)` to be used in Krylov.jl. From 58a28db778e35c26a988a13d9dec2bc6b95f845f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 22 Aug 2022 01:05:20 -0400 Subject: [PATCH 006/182] [documentation] Add more details about preconditioners --- docs/make.jl | 2 +- docs/src/preconditioners.md | 126 ++++++++++++++++++++++++------------ 2 files changed, 85 insertions(+), 43 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 48263fe25..f59bfac0c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,7 +15,7 @@ makedocs( "Krylov methods" => ["Symmetric positive definite linear systems" => "solvers/spd.md", "Symmetric indefinite linear systems" => "solvers/sid.md", "Unsymmetric linear systems" => "solvers/unsymmetric.md", - "Minimum-norm problems" => "solvers/ln.md", + "Least-norm problems" => "solvers/ln.md", "Least-squares problems" => "solvers/ls.md", "Adjoint systems" => "solvers/as.md", "Saddle-point and symmetric quasi-definite systems" => "solvers/sp_sqd.md", diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 0f51e27fb..15cb3f362 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -1,81 +1,122 @@ ## [Preconditioners](@id preconditioners) -The solvers in Krylov.jl support preconditioners that modify a given linear systems $Ax = b$ into a form that allows a faster convergence. +The solvers in Krylov.jl support preconditioners, i.e., transformations that modify a linear systems $Ax = b$ into an equivalent form that may yield faster convergence in finite-precision arithmetic. +Preconditioning can be used to reduce the condition number of the problem or clusterize its eigenvalues for instance. -It exists three variants of preconditioning: +The design of preconditioners is highly dependent on the origin of the problem and most preconditioners need to take application dependent information and structures into account. +Specialized preconditioners generally outperform generic preconditioners such as incomplete factorizations. -| Left preconditioning | Two-sided preconditioning | Right preconditioning | -|:--------------------:|:-------------------------------:|:------------------------------:| -| $MAx = Mb$ | $MANy = Mb~~\text{with}~~x = Ny$| $ANy = b~~\text{with}~~x = Ny$ | +The construction of a preconditioner necessitates a trade-off because we need to apply it at least once per iteration within a Krylov method. +Hence, a preconditioner must be constructed such that it is cheap to apply, while also capturing the characteristics of the original system in some sense. -#### Unsymmetric linear systems +There exist three variants of preconditioning: -A Krylov method dedicated to unsymmetric systems allows the three variants. -We provide these preconditioners with the arguments `M` and `N`. -It concerns the methods [`CGS`](@ref cgs), [`BiCGSTAB`](@ref bicgstab), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres), [`DIOM`](@ref diom) and [`FOM`](@ref fom). +| Left preconditioning | Two-sided preconditioning | Right preconditioning | +|:----------------------------------:|:----------------------------------------------------------------------:|:--------------------------------------------:| +| $P_{\ell}^{-1}Ax = P_{\ell}^{-1}b$ | $P_{\ell}^{-1}AP_r^{-1}y = P_{\ell}^{-1}b~~\text{with}~~x = P_r^{-1}y$ | $AP_r^{-1}y = b~~\text{with}~~x = P_r^{-1}y$ | -#### Symmetric linear systems +where $P_{\ell}$ and $P_r$ are square and nonsingular. -When $A$ is symmetric, we can only use the centered / split preconditioning $LAL^Tx = Lb$. -It is a special case of two-sided preconditioning $M=L=N^T$ that maintains the symmetry of the linear systems. -Krylov methods dedicated to symmetric systems take directly as input a symmetric positive preconditioner $P=LL^T$. -We provide this preconditioner with the argument `M` in [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref cg_lanczos), [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift), [`CR`](@ref cr), [`MINRES`](@ref minres) and [`MINRES-QLP`](@ref minres_qlp). +We consider that $P_{\ell}^1$ and $P_r^1$ are the default preconditioners in Krylov.jl and that we can apply them with the operation $y \leftarrow P^{-1} * x$. +It is also common to call $P_{\ell}$ and $P_r$ the preconditioners if the equivalent operation $y \leftarrow P \\ x$ is available. +We support both approach thanks to the argument `ldiv` of the Krylov solvers. -#### Least-squares problems +!!! tip + A preconditioner only needs to support the operation `mul!(y, P⁻¹, x)` when `ldiv=false` or `ldiv!(y, P, x)` when `ldiv=true` to be used in Krylov.jl. + +#### Square non-Hermitian linear systems + +Methods concerned: [`CGS`](@ref cgs), [`BiCGSTAB`](@ref bicgstab), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres), [`DIOM`](@ref diom) and [`FOM`](@ref fom). + +A Krylov method dedicated to non-Hermitian linear systems allows the three variants. + +| Preconditioners | $P_{\ell}^{-1}$ | $P_{\ell}$ | $P_r^{-1}$ | $P_r$ | +|:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | + +#### Hermitian linear systems + +Methods concerned: [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref cg_lanczos), [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift), [`CR`](@ref cr), [`MINRES`](@ref minres) and [`MINRES-QLP`](@ref minres_qlp). + +When $A$ is symmetric, we can only use the centered preconditioning $L^{-1}AL^{-T}x = L^{-1}b$. +This split preconditioning is a special case of two-sided preconditioning $P_{\ell} = L = P_r^T$ that maintains the symmetry / hermicity of the linear systems. + +| Preconditioners | $P^{-1} = L^{-T}L^{-1}$ | $P = LL^{T}$ | +|:---------------:|:-----------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | -| Formulation | Without preconditioning | With preconditioning | -|:---------------------:|:-----------------------:|:-----------------------:| -| least-squares problem | $\min \\|b - Ax\\|^2_2$ | $\min \\|b - Ax\\|^2_M$ | -| Normal equation | $A^TAx = A^Tb$ | $A^TMAx = A^TMb$ | -| Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} M & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | +The preconditioner must be symmetric positive definite. -We provide a symmetric positive definite preconditioner with the argument `M` in [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). +#### Linear least-squares problems -A second positive definite preconditioner `N` is supported by [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). -It is dedicated to regularized least-squares problems. +Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). + +| Formulation | Without preconditioning | With preconditioning | +|:---------------------:|:-----------------------:|:------------------------------:| +| least-squares problem | $\min \\|b - Ax\\|^2_2$ | $\min \\|b - Ax\\|^2_{E^{-1}}$ | +| Normal equation | $A^TAx = A^Tb$ | $A^TE^{-1}Ax = A^TE^{-1}b$ | +| Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | + +| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | +|:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | + +[`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr) can handle a second preconditioner `N` for regularized least-squares problems. | Formulation | Without preconditioning | With preconditioning | |:---------------------:|:-----------------------------------------------:|:------------------------------------------------------:| -| least-squares problem | $\min \\|b - Ax\\|^2_2 + \lambda^2 \\|x\\|^2_2$ | $\min \\|b - Ax\\|^2_M + \lambda^2 \\|x\\|^2_{N^{-1}}$ | -| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TMA + \lambda^2 N^{-1})x = A^TMb$ | -| Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} M & A \\ A^T & -\lambda^2 N \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | +| least-squares problem | $\min \\|b - Ax\\|^2_2 + \lambda^2 \\|x\\|^2_2$ | $\min \\|b - Ax\\|^2_{E^{-1}} + \lambda^2 \\|x\\|^2_F$ | +| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | +| Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | + +The preconditioners must be symmetric positive definite. + +#### Linear least-norm problems -#### Minimum-norm problems +Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig) and [`CRAIGMR`](@ref craigmr). | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:---------------------------------------:|:----------------------------------------------:| -| minimum-norm problem | $\min \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \\|x\\|^2_{N^{-1}}~~\text{s.t.}~~Ax = b$ | -| Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $ANA^Ty = b~~\text{with}~~x = NA^Ty$ | -| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -N & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | +| minimum-norm problem | $\min \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \\|x\\|^2_F~~\text{s.t.}~~Ax = b$ | +| Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $AF^{-1}A^Ty = b~~\text{with}~~x = F^{-1}A^Ty$ | +| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | -We provide a symmetric positive definite preconditioner with the argument `N` in [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig) and [`CRAIGMR`](@ref craigmr). -A second positive definite preconditioner `M` is supported by [`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr). -It is dedicated to penalized minimum-norm problems. +| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | +|:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | + +[`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr) can handle a second preconditioner `M` for penalized minimum-norm problems. | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:-------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:| -| minimum-norm problem | $\min \\|x\\|^2_2 + \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \\|x\\|^2_{N^{-1}} + \\|y\\|^2_{M^{-1}}~~\text{s.t.}~~Ax + \lambda^2 M^{-1}y = b$ | -| Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(ANA^T + \lambda^2 M^{-1})y = b~~\text{with}~~x = NA^Ty$ | -| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -N^{-1} & A^T \\ \phantom{-}A & \lambda^2 M^{-1} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | +| minimum-norm problem | $\min \\|x\\|^2_2 + \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \\|x\\|^2_F + \\|y\\|^2_E~~\text{s.t.}~~Ax + \lambda^2 Ey = b$ | +| Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(AF^{-1}A^T + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Ty$ | +| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & \lambda^2 E \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | #### Saddle-point and symmetric quasi-definite systems When a symmetric system $Kz = d$ has the 2x2 block structure ```math - \begin{bmatrix} \tau M^{-1} & \phantom{-}A \\ A^T & \nu N^{-1} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, + \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` -where $M^{-1}$ and $N^{-1}$ are symmetric positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this structure if preconditioners `M` and `N` that model $M$ and $N$ are available. +where $E$ and $F$ are symmetric positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this structure if preconditioners `M` and `N` that model the inverse of $E$ and $F$ are available. + +| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | +|:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | + #### Generalized saddle-point and unsymmetric partitioned systems When an unsymmetric system $Kz = d$ has the 2x2 block structure ```math - \begin{bmatrix} \lambda M^{-1} & A \\ B & \mu N^{-1} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, + \begin{bmatrix} \lambda M & A \\ B & \mu N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` -[`GPMR`](@ref gpmr) can take advantage of this structure if preconditioners `C`, `D`, `E` and `F` such that $CE = M$ and $DF = N$ are available. +[`GPMR`](@ref gpmr) can take advantage of this structure if we model the inverse of $M$ and $N$ with the help of preconditioners `C`, `D`, `E` and `F`. -!!! tip - A preconditioner `P` only needs to support the operation `mul!(y, P, x)` to be used in Krylov.jl. +| Relations | $CE = M^{-1}$ | $EC = M$ | $DF = N^{-1}$ | $FD = N$ | +|:---------------:|:---------------------------:|:--------------------------:|:---------------------------:|:--------------------------:| +| Arguments | `C` / `E` with `ldiv=false` | `C` / `E` with `ldiv=true` | `D` / `F` with `ldiv=false` | `D` / `F` with `ldiv=true` | !!! note Our implementations of [`BiLQ`](@ref bilq), [`QMR`](@ref qmr), [`BiLQR`](@ref bilqr), [`USYMLQ`](@ref usymlq), [`USYMQR`](@ref usymqr) and [`TriLQR`](@ref trilqr) don't support preconditioning. @@ -86,3 +127,4 @@ When an unsymmetric system $Kz = d$ has the 2x2 block structure - [ILUZero.jl](https://github.com/mcovalt/ILUZero.jl) is a Julia implementation of incomplete LU factorization with zero level of fill-in. - [LimitedLDLFactorizations.jl](https://github.com/JuliaSmoothOptimizers/LimitedLDLFactorizations.jl) for limited-memory LDLᵀ factorization of symmetric matrices. - [AlgebraicMultigrid.jl](https://github.com/JuliaLinearAlgebra/AlgebraicMultigrid.jl) provides two algebraic multigrid (AMG) preconditioners. +- [RandomizedPreconditioners.jl](https://github.com/tjdiamandis/RandomizedPreconditioners.jl) uses randomized numerical linear algebra to construct approximate inverses of matrices. From 08c0e1ed6e6f06892c80ff0227cbe51b5d5f822e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 24 Aug 2022 09:24:37 -0400 Subject: [PATCH 007/182] Final version of preconditioners.md --- docs/src/preconditioners.md | 113 ++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 15cb3f362..416c3fd6b 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -1,4 +1,4 @@ -## [Preconditioners](@id preconditioners) +# [Preconditioners](@id preconditioners) The solvers in Krylov.jl support preconditioners, i.e., transformations that modify a linear systems $Ax = b$ into an equivalent form that may yield faster convergence in finite-precision arithmetic. Preconditioning can be used to reduce the condition number of the problem or clusterize its eigenvalues for instance. @@ -6,7 +6,7 @@ Preconditioning can be used to reduce the condition number of the problem or clu The design of preconditioners is highly dependent on the origin of the problem and most preconditioners need to take application dependent information and structures into account. Specialized preconditioners generally outperform generic preconditioners such as incomplete factorizations. -The construction of a preconditioner necessitates a trade-off because we need to apply it at least once per iteration within a Krylov method. +The construction of a preconditioner also necessitates a trade-off because we need to apply it at least once per iteration within a Krylov method. Hence, a preconditioner must be constructed such that it is cheap to apply, while also capturing the characteristics of the original system in some sense. There exist three variants of preconditioning: @@ -17,37 +17,40 @@ There exist three variants of preconditioning: where $P_{\ell}$ and $P_r$ are square and nonsingular. -We consider that $P_{\ell}^1$ and $P_r^1$ are the default preconditioners in Krylov.jl and that we can apply them with the operation $y \leftarrow P^{-1} * x$. -It is also common to call $P_{\ell}$ and $P_r$ the preconditioners if the equivalent operation $y \leftarrow P \\ x$ is available. -We support both approach thanks to the argument `ldiv` of the Krylov solvers. +We consider that $P_{\ell}^{-1}$ and $P_r^{-1}$ are the default preconditioners in Krylov.jl and that we can apply them with the operation $y \leftarrow P^{-1} * x$. +It is also common to call $P_{\ell}$ and $P_r$ the preconditioners if the equivalent operation $y \leftarrow P~\backslash~x$ is available. +Krylov.jl supports both approach thanks to the argument `ldiv` of the Krylov solvers. + +## How to use preconditioners in Krylov.jl? !!! tip A preconditioner only needs to support the operation `mul!(y, P⁻¹, x)` when `ldiv=false` or `ldiv!(y, P, x)` when `ldiv=true` to be used in Krylov.jl. -#### Square non-Hermitian linear systems +### Square non-Hermitian linear systems Methods concerned: [`CGS`](@ref cgs), [`BiCGSTAB`](@ref bicgstab), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres), [`DIOM`](@ref diom) and [`FOM`](@ref fom). -A Krylov method dedicated to non-Hermitian linear systems allows the three variants. +A Krylov method dedicated to non-Hermitian linear systems allows the three variants of preconditioning. | Preconditioners | $P_{\ell}^{-1}$ | $P_{\ell}$ | $P_r^{-1}$ | $P_r$ | |:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| | Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | -#### Hermitian linear systems +### Hermitian linear systems Methods concerned: [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref cg_lanczos), [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift), [`CR`](@ref cr), [`MINRES`](@ref minres) and [`MINRES-QLP`](@ref minres_qlp). -When $A$ is symmetric, we can only use the centered preconditioning $L^{-1}AL^{-T}x = L^{-1}b$. -This split preconditioning is a special case of two-sided preconditioning $P_{\ell} = L = P_r^T$ that maintains the symmetry / hermicity of the linear systems. +When $A$ is Hermitian, we can only use the centered preconditioning $L^{-1}AL^{-T}y = L^{-1}b$ with $x = L^{-T}y$. +This split preconditioning is a special case of two-sided preconditioning $P_{\ell} = L = P_r^T$ that maintains the hermicity of the linear systems. | Preconditioners | $P^{-1} = L^{-T}L^{-1}$ | $P = LL^{T}$ | |:---------------:|:-----------------------:|:--------------------:| | Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | -The preconditioner must be symmetric positive definite. +!!! warning + The preconditioner `M` must be hermitian and positive definite. -#### Linear least-squares problems +### Linear least-squares problems Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). @@ -57,11 +60,7 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) | Normal equation | $A^TAx = A^Tb$ | $A^TE^{-1}Ax = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | -| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | -|:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| -| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | - -[`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr) can handle a second preconditioner `N` for regularized least-squares problems. +[`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr) also handle regularized least-squares problems. | Formulation | Without preconditioning | With preconditioning | |:---------------------:|:-----------------------------------------------:|:------------------------------------------------------:| @@ -69,9 +68,14 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) | Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | -The preconditioners must be symmetric positive definite. +| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | +|:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | -#### Linear least-norm problems +!!! warning + The preconditioners `M` and `N` must be hermitian and positive definite. + +### Linear least-norm problems Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig) and [`CRAIGMR`](@ref craigmr). @@ -81,11 +85,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) | Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $AF^{-1}A^Ty = b~~\text{with}~~x = F^{-1}A^Ty$ | | Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | -| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | -|:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| -| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | - -[`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr) can handle a second preconditioner `M` for penalized minimum-norm problems. +[`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr) also handle penalized minimum-norm problems. | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:-------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:| @@ -93,34 +93,46 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) | Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(AF^{-1}A^T + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Ty$ | | Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & \lambda^2 E \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | -#### Saddle-point and symmetric quasi-definite systems +| Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | +|:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | + +!!! warning + The preconditioners `M` and `N` must be hermitian and positive definite. -When a symmetric system $Kz = d$ has the 2x2 block structure +### Saddle-point and symmetric quasi-definite systems + +When a Hermitian system $Kz = d$ has the 2x2 block structure ```math \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` -where $E$ and $F$ are symmetric positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this structure if preconditioners `M` and `N` that model the inverse of $E$ and $F$ are available. +where $E$ and $F$ are Hermitian and positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this form if preconditioners `M` and `N` that model the inverse of $E$ and $F$ are available. | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | |:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| | Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | +!!! warning + The preconditioners `M` and `N` must be hermitian and positive definite. -#### Generalized saddle-point and unsymmetric partitioned systems +### Generalized saddle-point and unsymmetric partitioned systems -When an unsymmetric system $Kz = d$ has the 2x2 block structure +When an non-Hermitian system $Kz = d$ has the 2x2 block structure ```math \begin{bmatrix} \lambda M & A \\ B & \mu N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` [`GPMR`](@ref gpmr) can take advantage of this structure if we model the inverse of $M$ and $N$ with the help of preconditioners `C`, `D`, `E` and `F`. -| Relations | $CE = M^{-1}$ | $EC = M$ | $DF = N^{-1}$ | $FD = N$ | -|:---------------:|:---------------------------:|:--------------------------:|:---------------------------:|:--------------------------:| -| Arguments | `C` / `E` with `ldiv=false` | `C` / `E` with `ldiv=true` | `D` / `F` with `ldiv=false` | `D` / `F` with `ldiv=true` | +| Relations | $CE = M^{-1}$ | $EC = M$ | $DF = N^{-1}$ | $FD = N$ | +|:---------------:|:-----------------------------:|:----------------------------:|:-----------------------------:|:----------------------------:| +| Arguments | `C` and `E` with `ldiv=false` | `C` and `E` with `ldiv=true` | `D` and `F` with `ldiv=false` | `D` and `F` with `ldiv=true` | !!! note Our implementations of [`BiLQ`](@ref bilq), [`QMR`](@ref qmr), [`BiLQR`](@ref bilqr), [`USYMLQ`](@ref usymlq), [`USYMQR`](@ref usymqr) and [`TriLQR`](@ref trilqr) don't support preconditioning. +!!! info + The default value of a preconditioner in Krylov.jl is the identity operator `I`. + ## Packages that provide preconditioners - [IncompleteLU.jl](https://github.com/haampie/IncompleteLU.jl) implements the left-looking or Crout version of ILU decompositions. @@ -128,3 +140,40 @@ When an unsymmetric system $Kz = d$ has the 2x2 block structure - [LimitedLDLFactorizations.jl](https://github.com/JuliaSmoothOptimizers/LimitedLDLFactorizations.jl) for limited-memory LDLᵀ factorization of symmetric matrices. - [AlgebraicMultigrid.jl](https://github.com/JuliaLinearAlgebra/AlgebraicMultigrid.jl) provides two algebraic multigrid (AMG) preconditioners. - [RandomizedPreconditioners.jl](https://github.com/tjdiamandis/RandomizedPreconditioners.jl) uses randomized numerical linear algebra to construct approximate inverses of matrices. + +## Examples + +```julia +using Krylov +n, m = size(A) +d = [A[i,i] ≠ 0 ? 1 / abs(A[i,i]) : 1 for i=1:n] # Jacobi preconditioner +P⁻¹ = diagm(d) +x, stats = symmlq(A, b, M=P⁻¹) +``` + +```julia +using Krylov +n, m = size(A) +d = [1 / norm(A[:,i]) for i=1:m] # diagonal preconditioner +P⁻¹ = diagm(d) +x, stats = minres(A, b, M=P⁻¹) +``` + +```julia +using IncompleteLU, Krylov +Pℓ = ilu(A) +x, stats = gmres(A, b, M=Pℓ, ldiv=true) # left preconditioning +``` + +```julia +using LimitedLDLFactorizations, Krylov +P = lldl(A) +P.D .= abs.(P.D) +x, stats = cg(A, b, M=P, ldiv=true) # centered preconditioning +``` + +```julia +using ILUZero, Krylov +Pᵣ = ilu0(A) +x, stats = bicgstab(A, b, N=Pᵣ, ldiv=true) # right preconditioning +``` From 016d6c66d35eddce4dd1859e151b7c1428596376 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 2 Sep 2022 18:10:02 -0400 Subject: [PATCH 008/182] Fix tests with CGNE --- test/test_cgne.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_cgne.jl b/test/test_cgne.jl index a48b4569e..c1a3e798b 100644 --- a/test/test_cgne.jl +++ b/test/test_cgne.jl @@ -1,6 +1,6 @@ -function test_cgne(A, b; λ=0.0, N=I) +function test_cgne(A, b; λ=0.0, N=I, history=false) (nrow, ncol) = size(A) - (x, stats) = cgne(A, b, λ=λ, N=N) + (x, stats) = cgne(A, b, λ=λ, N=N, history=history) r = b - A * x if λ > 0 s = r / sqrt(λ) From 5d1a87f819424af856a7f5ee2788eda6844fee61 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:54:18 -0400 Subject: [PATCH 009/182] Apply suggestions from @dpo Co-authored-by: Dominique --- docs/src/preconditioners.md | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 416c3fd6b..e57baf842 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -1,12 +1,12 @@ # [Preconditioners](@id preconditioners) The solvers in Krylov.jl support preconditioners, i.e., transformations that modify a linear systems $Ax = b$ into an equivalent form that may yield faster convergence in finite-precision arithmetic. -Preconditioning can be used to reduce the condition number of the problem or clusterize its eigenvalues for instance. +Preconditioning can be used to reduce the condition number of the problem or cluster its eigenvalues or singular values for instance. -The design of preconditioners is highly dependent on the origin of the problem and most preconditioners need to take application dependent information and structures into account. +The design of preconditioners is highly dependent on the origin of the problem and most preconditioners need to take application-dependent information and structure into account. Specialized preconditioners generally outperform generic preconditioners such as incomplete factorizations. -The construction of a preconditioner also necessitates a trade-off because we need to apply it at least once per iteration within a Krylov method. +The construction of a preconditioner necessitates trade-offs because we need to apply it at least once per iteration within a Krylov method. Hence, a preconditioner must be constructed such that it is cheap to apply, while also capturing the characteristics of the original system in some sense. There exist three variants of preconditioning: @@ -17,14 +17,14 @@ There exist three variants of preconditioning: where $P_{\ell}$ and $P_r$ are square and nonsingular. -We consider that $P_{\ell}^{-1}$ and $P_r^{-1}$ are the default preconditioners in Krylov.jl and that we can apply them with the operation $y \leftarrow P^{-1} * x$. +In Krylov.jl , we call $P_{\ell}^{-1}$ and $P_r^{-1}$ the preconditioners and we assume that we can apply them with the operation $y \leftarrow P^{-1} * x$. It is also common to call $P_{\ell}$ and $P_r$ the preconditioners if the equivalent operation $y \leftarrow P~\backslash~x$ is available. -Krylov.jl supports both approach thanks to the argument `ldiv` of the Krylov solvers. +Krylov.jl supports both approaches thanks to the argument `ldiv` of the Krylov solvers. ## How to use preconditioners in Krylov.jl? !!! tip - A preconditioner only needs to support the operation `mul!(y, P⁻¹, x)` when `ldiv=false` or `ldiv!(y, P, x)` when `ldiv=true` to be used in Krylov.jl. + A preconditioner only need support the operation `mul!(y, P⁻¹, x)` when `ldiv=false` or `ldiv!(y, P, x)` when `ldiv=true` to be used in Krylov.jl. ### Square non-Hermitian linear systems @@ -40,8 +40,9 @@ A Krylov method dedicated to non-Hermitian linear systems allows the three varia Methods concerned: [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref cg_lanczos), [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift), [`CR`](@ref cr), [`MINRES`](@ref minres) and [`MINRES-QLP`](@ref minres_qlp). -When $A$ is Hermitian, we can only use the centered preconditioning $L^{-1}AL^{-T}y = L^{-1}b$ with $x = L^{-T}y$. -This split preconditioning is a special case of two-sided preconditioning $P_{\ell} = L = P_r^T$ that maintains the hermicity of the linear systems. +When $A$ is Hermitian, we can only use centered preconditioning $L^{-1}AL^{-T}y = L^{-1}b$ with $x = L^{-T}y$. +Centered preconditioning is a special case of two-sided preconditioning with $P_{\ell} = L = P_r^T$ that maintains hermicity. +However, there is no need to specify $L$ and one may specify $M$ directly. | Preconditioners | $P^{-1} = L^{-T}L^{-1}$ | $P = LL^{T}$ | |:---------------:|:-----------------------:|:--------------------:| @@ -56,7 +57,7 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) | Formulation | Without preconditioning | With preconditioning | |:---------------------:|:-----------------------:|:------------------------------:| -| least-squares problem | $\min \\|b - Ax\\|^2_2$ | $\min \\|b - Ax\\|^2_{E^{-1}}$ | +| least-squares problem | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_2$ | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}}$ | | Normal equation | $A^TAx = A^Tb$ | $A^TE^{-1}Ax = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | @@ -64,7 +65,7 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) | Formulation | Without preconditioning | With preconditioning | |:---------------------:|:-----------------------------------------------:|:------------------------------------------------------:| -| least-squares problem | $\min \\|b - Ax\\|^2_2 + \lambda^2 \\|x\\|^2_2$ | $\min \\|b - Ax\\|^2_{E^{-1}} + \lambda^2 \\|x\\|^2_F$ | +| least-squares problem | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_2 + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_2$ | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}} + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_F$ | | Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | @@ -81,7 +82,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:---------------------------------------:|:----------------------------------------------:| -| minimum-norm problem | $\min \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \\|x\\|^2_F~~\text{s.t.}~~Ax = b$ | +| minimum-norm problem | $\min \\tfrac{1}{2} \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \\tfrac{1}{2} \\|x\\|^2_F~~\text{s.t.}~~Ax = b$ | | Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $AF^{-1}A^Ty = b~~\text{with}~~x = F^{-1}A^Ty$ | | Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | @@ -89,7 +90,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:-------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:| -| minimum-norm problem | $\min \\|x\\|^2_2 + \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \\|x\\|^2_F + \\|y\\|^2_E~~\text{s.t.}~~Ax + \lambda^2 Ey = b$ | +| minimum-norm problem | $\min \\tfrac{1}{2} \\|x\\|^2_2 + \\tfrac{1}{2} \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \\tfrac{1}{2} \\|x\\|^2_F + \\tfrac{1}{2} \\|y\\|^2_E~~\text{s.t.}~~Ax + \lambda^2 Ey = b$ | | Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(AF^{-1}A^T + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Ty$ | | Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & \lambda^2 E \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | @@ -102,11 +103,9 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) ### Saddle-point and symmetric quasi-definite systems -When a Hermitian system $Kz = d$ has the 2x2 block structure +[`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of the structure of Hermitian systems $Kz = d$ with the 2x2 block structure ```math \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, -``` -where $E$ and $F$ are Hermitian and positive definite, [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of this form if preconditioners `M` and `N` that model the inverse of $E$ and $F$ are available. | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | |:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| @@ -117,11 +116,9 @@ where $E$ and $F$ are Hermitian and positive definite, [`TriCG`](@ref tricg) and ### Generalized saddle-point and unsymmetric partitioned systems -When an non-Hermitian system $Kz = d$ has the 2x2 block structure +[`GPMR`](@ref gpmr) can take advantage of the structure of general square systems $Kz = d$ with the 2x2 block structure ```math \begin{bmatrix} \lambda M & A \\ B & \mu N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, -``` -[`GPMR`](@ref gpmr) can take advantage of this structure if we model the inverse of $M$ and $N$ with the help of preconditioners `C`, `D`, `E` and `F`. | Relations | $CE = M^{-1}$ | $EC = M$ | $DF = N^{-1}$ | $FD = N$ | |:---------------:|:-----------------------------:|:----------------------------:|:-----------------------------:|:----------------------------:| @@ -135,8 +132,8 @@ When an non-Hermitian system $Kz = d$ has the 2x2 block structure ## Packages that provide preconditioners -- [IncompleteLU.jl](https://github.com/haampie/IncompleteLU.jl) implements the left-looking or Crout version of ILU decompositions. -- [ILUZero.jl](https://github.com/mcovalt/ILUZero.jl) is a Julia implementation of incomplete LU factorization with zero level of fill-in. +- [IncompleteLU.jl](https://github.com/haampie/IncompleteLU.jl) implements the left-looking and Crout versions of ILU decompositions. +- [ILUZero.jl](https://github.com/mcovalt/ILUZero.jl) is a Julia implementation of incomplete LU factorization with zero level of fill-in. - [LimitedLDLFactorizations.jl](https://github.com/JuliaSmoothOptimizers/LimitedLDLFactorizations.jl) for limited-memory LDLᵀ factorization of symmetric matrices. - [AlgebraicMultigrid.jl](https://github.com/JuliaLinearAlgebra/AlgebraicMultigrid.jl) provides two algebraic multigrid (AMG) preconditioners. - [RandomizedPreconditioners.jl](https://github.com/tjdiamandis/RandomizedPreconditioners.jl) uses randomized numerical linear algebra to construct approximate inverses of matrices. From 5d5484737fb7f0ffe53e5710cc28e1f3e882284e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 18:37:27 -0400 Subject: [PATCH 010/182] Update test_solvers.jl --- test/test_solvers.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 468fa5a05..a6003088b 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -631,7 +631,7 @@ function test_solvers(FC) │ Aᵀr│ Vector{$FC}│ 64│ │ r│ Vector{$FC}│ 32│ │ q│ Vector{$FC}│ 32│ - │ Mq│ Vector{$FC}│ 0│ + │ Nq│ Vector{$FC}│ 0│ │ s│ Vector{$FC}│ 0│ └──────────┴───────────────┴─────────────────┘ """ From c9674536f0bde06530abdf36297b2fd687e30763 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 18:37:54 -0400 Subject: [PATCH 011/182] Update preconditioners.md --- docs/src/preconditioners.md | 52 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index e57baf842..1bc9b1a7f 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -23,8 +23,9 @@ Krylov.jl supports both approaches thanks to the argument `ldiv` of the Krylov s ## How to use preconditioners in Krylov.jl? -!!! tip - A preconditioner only need support the operation `mul!(y, P⁻¹, x)` when `ldiv=false` or `ldiv!(y, P, x)` when `ldiv=true` to be used in Krylov.jl. +!!! info + - A preconditioner only need support the operation `mul!(y, P⁻¹, x)` when `ldiv=false` or `ldiv!(y, P, x)` when `ldiv=true` to be used in Krylov.jl. + - The default value of a preconditioner in Krylov.jl is the identity operator `I`. ### Square non-Hermitian linear systems @@ -42,11 +43,11 @@ Methods concerned: [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref When $A$ is Hermitian, we can only use centered preconditioning $L^{-1}AL^{-T}y = L^{-1}b$ with $x = L^{-T}y$. Centered preconditioning is a special case of two-sided preconditioning with $P_{\ell} = L = P_r^T$ that maintains hermicity. -However, there is no need to specify $L$ and one may specify $M$ directly. +However, there is no need to specify $L$ and one may specify $P_c = LL^T$ or its inverse directly. -| Preconditioners | $P^{-1} = L^{-T}L^{-1}$ | $P = LL^{T}$ | -|:---------------:|:-----------------------:|:--------------------:| -| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | +| Preconditioners | $P_c^{-1}$ | $P_c$ | +|:---------------:|:-------------------------:|:--------------------:| +| Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | !!! warning The preconditioner `M` must be hermitian and positive definite. @@ -55,18 +56,18 @@ However, there is no need to specify $L$ and one may specify $M$ directly. Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). -| Formulation | Without preconditioning | With preconditioning | -|:---------------------:|:-----------------------:|:------------------------------:| -| least-squares problem | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_2$ | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}}$ | -| Normal equation | $A^TAx = A^Tb$ | $A^TE^{-1}Ax = A^TE^{-1}b$ | +| Formulation | Without preconditioning | With preconditioning | +|:---------------------:|:------------------------------------:|:-------------------------------------------:| +| least-squares problem | $\min \tfrac{1}{2} \\|b - Ax\\|^2_2$ | $\min \tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}}$ | +| Normal equation | $A^TAx = A^Tb$ | $A^TE^{-1}Ax = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr) also handle regularized least-squares problems. -| Formulation | Without preconditioning | With preconditioning | -|:---------------------:|:-----------------------------------------------:|:------------------------------------------------------:| -| least-squares problem | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_2 + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_2$ | $\min \\tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}} + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_F$ | -| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | +| Formulation | Without preconditioning | With preconditioning | +|:---------------------:|:--------------------------------------------------------------------------:|:---------------------------------------------------------------------------------:| +| least-squares problem | $\min \tfrac{1}{2} \\|b - Ax\\|^2_2 + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_2$ | $\min \tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}} + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_F$ | +| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | @@ -80,18 +81,18 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig) and [`CRAIGMR`](@ref craigmr). -| Formulation | Without preconditioning | With preconditioning | -|:--------------------:|:---------------------------------------:|:----------------------------------------------:| -| minimum-norm problem | $\min \\tfrac{1}{2} \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \\tfrac{1}{2} \\|x\\|^2_F~~\text{s.t.}~~Ax = b$ | -| Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $AF^{-1}A^Ty = b~~\text{with}~~x = F^{-1}A^Ty$ | +| Formulation | Without preconditioning | With preconditioning | +|:--------------------:|:----------------------------------------------------:|:----------------------------------------------------:| +| minimum-norm problem | $\min \tfrac{1}{2} \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \tfrac{1}{2} \\|x\\|^2_F~~\text{s.t.}~~Ax = b$ | +| Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $AF^{-1}A^Ty = b~~\text{with}~~x = F^{-1}A^Ty$ | | Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | [`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr) also handle penalized minimum-norm problems. -| Formulation | Without preconditioning | With preconditioning | -|:--------------------:|:-------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:| -| minimum-norm problem | $\min \\tfrac{1}{2} \\|x\\|^2_2 + \\tfrac{1}{2} \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \\tfrac{1}{2} \\|x\\|^2_F + \\tfrac{1}{2} \\|y\\|^2_E~~\text{s.t.}~~Ax + \lambda^2 Ey = b$ | -| Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(AF^{-1}A^T + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Ty$ | +| Formulation | Without preconditioning | With preconditioning | +|:--------------------:|:---------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| +| minimum-norm problem | $\min \tfrac{1}{2} \\|x\\|^2_2 + \tfrac{1}{2} \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \tfrac{1}{2} \\|x\\|^2_F + \tfrac{1}{2} \\|y\\|^2_E~~\text{s.t.}~~Ax + \lambda^2 Ey = b$ | +| Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(AF^{-1}A^T + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Ty$ | | Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & \lambda^2 E \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | @@ -106,7 +107,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of the structure of Hermitian systems $Kz = d$ with the 2x2 block structure ```math \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, - +``` | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | |:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| | Arguments | `M` with `ldiv=false` | `M` with `ldiv=true` | `N` with `ldiv=false` | `N` with `ldiv=true` | @@ -119,7 +120,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) [`GPMR`](@ref gpmr) can take advantage of the structure of general square systems $Kz = d$ with the 2x2 block structure ```math \begin{bmatrix} \lambda M & A \\ B & \mu N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, - +``` | Relations | $CE = M^{-1}$ | $EC = M$ | $DF = N^{-1}$ | $FD = N$ | |:---------------:|:-----------------------------:|:----------------------------:|:-----------------------------:|:----------------------------:| | Arguments | `C` and `E` with `ldiv=false` | `C` and `E` with `ldiv=true` | `D` and `F` with `ldiv=false` | `D` and `F` with `ldiv=true` | @@ -127,9 +128,6 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) !!! note Our implementations of [`BiLQ`](@ref bilq), [`QMR`](@ref qmr), [`BiLQR`](@ref bilqr), [`USYMLQ`](@ref usymlq), [`USYMQR`](@ref usymqr) and [`TriLQR`](@ref trilqr) don't support preconditioning. -!!! info - The default value of a preconditioner in Krylov.jl is the identity operator `I`. - ## Packages that provide preconditioners - [IncompleteLU.jl](https://github.com/haampie/IncompleteLU.jl) implements the left-looking and Crout versions of ILU decompositions. From 55cbe4e374331d6cffc86b67ab838ca0e86065b1 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 18:42:53 -0400 Subject: [PATCH 012/182] fix \tfrac in preconditioners.jl --- docs/src/preconditioners.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 1bc9b1a7f..7f3fb931e 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -64,10 +64,10 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr) also handle regularized least-squares problems. -| Formulation | Without preconditioning | With preconditioning | -|:---------------------:|:--------------------------------------------------------------------------:|:---------------------------------------------------------------------------------:| -| least-squares problem | $\min \tfrac{1}{2} \\|b - Ax\\|^2_2 + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_2$ | $\min \tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}} + \\tfrac{1}{2} \lambda^2 \\|x\\|^2_F$ | -| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | +| Formulation | Without preconditioning | With preconditioning | +|:---------------------:|:-------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:| +| least-squares problem | $\min \tfrac{1}{2} \\|b - Ax\\|^2_2 + \tfrac{1}{2} \lambda^2 \\|x\\|^2_2$ | $\min \tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}} + \tfrac{1}{2} \lambda^2 \\|x\\|^2_F$ | +| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | | Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | From dd4277086b1f8d9ea8ff8429b8914e65ba963322 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Tue, 6 Sep 2022 21:26:33 -0400 Subject: [PATCH 013/182] Update docs/src/preconditioners.md Co-authored-by: Dominique --- docs/src/preconditioners.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 7f3fb931e..9e248b994 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -1,6 +1,6 @@ # [Preconditioners](@id preconditioners) -The solvers in Krylov.jl support preconditioners, i.e., transformations that modify a linear systems $Ax = b$ into an equivalent form that may yield faster convergence in finite-precision arithmetic. +The solvers in Krylov.jl support preconditioners, i.e., transformations that modify a linear system $Ax = b$ into an equivalent form that may yield faster convergence in finite-precision arithmetic. Preconditioning can be used to reduce the condition number of the problem or cluster its eigenvalues or singular values for instance. The design of preconditioners is highly dependent on the origin of the problem and most preconditioners need to take application-dependent information and structure into account. From 645e094ede5d034b63de3524d2717e0b19dcedc1 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 21:32:52 -0400 Subject: [PATCH 014/182] A^T -> A^H --- docs/src/preconditioners.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 9e248b994..133020dc0 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -41,9 +41,9 @@ A Krylov method dedicated to non-Hermitian linear systems allows the three varia Methods concerned: [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CG-LANCZOS`](@ref cg_lanczos), [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift), [`CR`](@ref cr), [`MINRES`](@ref minres) and [`MINRES-QLP`](@ref minres_qlp). -When $A$ is Hermitian, we can only use centered preconditioning $L^{-1}AL^{-T}y = L^{-1}b$ with $x = L^{-T}y$. -Centered preconditioning is a special case of two-sided preconditioning with $P_{\ell} = L = P_r^T$ that maintains hermicity. -However, there is no need to specify $L$ and one may specify $P_c = LL^T$ or its inverse directly. +When $A$ is Hermitian, we can only use centered preconditioning $L^{-1}AL^{-H}y = L^{-1}b$ with $x = L^{-H}y$. +Centered preconditioning is a special case of two-sided preconditioning with $P_{\ell} = L = P_r^H$ that maintains hermicity. +However, there is no need to specify $L$ and one may specify $P_c = LL^H$ or its inverse directly. | Preconditioners | $P_c^{-1}$ | $P_c$ | |:---------------:|:-------------------------:|:--------------------:| @@ -59,16 +59,16 @@ Methods concerned: [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`LSLQ`](@ref lslq) | Formulation | Without preconditioning | With preconditioning | |:---------------------:|:------------------------------------:|:-------------------------------------------:| | least-squares problem | $\min \tfrac{1}{2} \\|b - Ax\\|^2_2$ | $\min \tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}}$ | -| Normal equation | $A^TAx = A^Tb$ | $A^TE^{-1}Ax = A^TE^{-1}b$ | -| Augmented system | $\begin{bmatrix} I & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | +| Normal equation | $A^HAx = A^Hb$ | $A^HE^{-1}Ax = A^HE^{-1}b$ | +| Augmented system | $\begin{bmatrix} I & A \\ A^H & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^H & 0 \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr) also handle regularized least-squares problems. | Formulation | Without preconditioning | With preconditioning | |:---------------------:|:-------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:| | least-squares problem | $\min \tfrac{1}{2} \\|b - Ax\\|^2_2 + \tfrac{1}{2} \lambda^2 \\|x\\|^2_2$ | $\min \tfrac{1}{2} \\|b - Ax\\|^2_{E^{-1}} + \tfrac{1}{2} \lambda^2 \\|x\\|^2_F$ | -| Normal equation | $(A^TA + \lambda^2 I)x = A^Tb$ | $(A^TE^{-1}A + \lambda^2 F)x = A^TE^{-1}b$ | -| Augmented system | $\begin{bmatrix} I & A \\ A^T & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^T & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | +| Normal equation | $(A^HA + \lambda^2 I)x = A^Hb$ | $(A^HE^{-1}A + \lambda^2 F)x = A^HE^{-1}b$ | +| Augmented system | $\begin{bmatrix} I & A \\ A^H & -\lambda^2 I \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | $\begin{bmatrix} E & A \\ A^H & -\lambda^2 F \end{bmatrix} \begin{bmatrix} r \\ x \end{bmatrix} = \begin{bmatrix} b \\ 0 \end{bmatrix}$ | | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | |:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| @@ -84,16 +84,16 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:----------------------------------------------------:|:----------------------------------------------------:| | minimum-norm problem | $\min \tfrac{1}{2} \\|x\\|^2_2~~\text{s.t.}~~Ax = b$ | $\min \tfrac{1}{2} \\|x\\|^2_F~~\text{s.t.}~~Ax = b$ | -| Normal equation | $AA^Ty = b~~\text{with}~~x = A^Ty$ | $AF^{-1}A^Ty = b~~\text{with}~~x = F^{-1}A^Ty$ | -| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | +| Normal equation | $AA^Hy = b~~\text{with}~~x = A^Hy$ | $AF^{-1}A^Hy = b~~\text{with}~~x = F^{-1}A^Hy$ | +| Augmented system | $\begin{bmatrix} -I & A^H \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^H \\ \phantom{-}A & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | [`LNLQ`](@ref lslq), [`CRAIG`](@ref lsqr) and [`CRAIGMR`](@ref lsmr) also handle penalized minimum-norm problems. | Formulation | Without preconditioning | With preconditioning | |:--------------------:|:---------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| | minimum-norm problem | $\min \tfrac{1}{2} \\|x\\|^2_2 + \tfrac{1}{2} \\|y\\|^2_2~~\text{s.t.}~~Ax + \lambda^2 y = b$ | $\min \tfrac{1}{2} \\|x\\|^2_F + \tfrac{1}{2} \\|y\\|^2_E~~\text{s.t.}~~Ax + \lambda^2 Ey = b$ | -| Normal equation | $(AA^T + \lambda^2 I)y = b~~\text{with}~~x = A^Ty$ | $(AF^{-1}A^T + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Ty$ | -| Augmented system | $\begin{bmatrix} -I & A^T \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^T \\ \phantom{-}A & \lambda^2 E \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | +| Normal equation | $(AA^H + \lambda^2 I)y = b~~\text{with}~~x = A^Hy$ | $(AF^{-1}A^H + \lambda^2 E)y = b~~\text{with}~~x = F^{-1}A^Hy$ | +| Augmented system | $\begin{bmatrix} -I & A^H \\ \phantom{-}A & \lambda^2 I \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | $\begin{bmatrix} -F & A^H \\ \phantom{-}A & \lambda^2 E \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 0 \\ b \end{bmatrix}$ | | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | |:---------------:|:-----------------------:|:--------------------:|:-----------------------:|:--------------------:| @@ -106,7 +106,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr) can take advantage of the structure of Hermitian systems $Kz = d$ with the 2x2 block structure ```math - \begin{bmatrix} \tau E & \phantom{-}A \\ A^T & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, + \begin{bmatrix} \tau E & \phantom{-}A \\ A^H & \nu F \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix}, ``` | Preconditioners | $E^{-1}$ | $E$ | $F^{-1}$ | $F$ | |:---------------:|:---------------------:|:--------------------:|:---------------------:|:--------------------:| From 330304a3478519acbbf72fc268f9666d215cc26f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 18:59:51 -0400 Subject: [PATCH 015/182] =?UTF-8?q?[documentation]=20Use=20A=E1=B4=B4=20in?= =?UTF-8?q?stead=20of=20A=E1=B5=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- docs/src/examples/tricg.md | 12 ++++++------ docs/src/examples/trimr.md | 6 +++--- docs/src/gpu.md | 2 +- docs/src/index.md | 6 +++--- docs/src/warm_start.md | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a4664e187..ced20f308 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Overdetermined sytems are less common but also occur. 4. Adjoint systems

- Ax = b   and   Aᵀy = c + Ax = b   and   Aᴴy = c

where **_A_** can have any shape. @@ -81,7 +81,7 @@ where **_A_** can have any shape.

[M     A]  [x] = [b]
- [Aᵀ   -N]  [y]    [c] + [Aᴴ   -N]  [y]    [c]

where **_A_** can have any shape. @@ -94,7 +94,7 @@ where **_A_** can have any shape. [B   N]  [y]    [c]

-where **_A_** can have any shape and **_B_** has the shape of **_Aᵀ_**. +where **_A_** can have any shape and **_B_** has the shape of **_Aᴴ_**. **_A_**, **_B_**, **_b_** and **_c_** must be all nonzero. Krylov solvers are particularly appropriate in situations where such problems must be solved but a factorization is not possible, either because: diff --git a/docs/src/examples/tricg.md b/docs/src/examples/tricg.md index e981c2f7e..61750de5f 100644 --- a/docs/src/examples/tricg.md +++ b/docs/src/examples/tricg.md @@ -14,7 +14,7 @@ N = diagm(0 => [5.0 * i for i = 1:n]) c = -b # [I A] [x] = [b] -# [Aᵀ -I] [y] [c] +# [Aᴴ -I] [y] [c] (x, y, stats) = tricg(A, b, c) K = [eye(m) A; A' -eye(n)] B = [b; c] @@ -23,7 +23,7 @@ resid = norm(r) @printf("TriCG: Relative residual: %8.1e\n", resid) # [-I A] [x] = [b] -# [ Aᵀ I] [y] [c] +# [ Aᴴ I] [y] [c] (x, y, stats) = tricg(A, b, c, flip=true) K = [-eye(m) A; A' eye(n)] B = [b; c] @@ -32,7 +32,7 @@ resid = norm(r) @printf("TriCG: Relative residual: %8.1e\n", resid) # [I A] [x] = [b] -# [Aᵀ I] [y] [c] +# [Aᴴ I] [y] [c] (x, y, stats) = tricg(A, b, c, spd=true) K = [eye(m) A; A' eye(n)] B = [b; c] @@ -41,7 +41,7 @@ resid = norm(r) @printf("TriCG: Relative residual: %8.1e\n", resid) # [-I A] [x] = [b] -# [ Aᵀ -I] [y] [c] +# [ Aᴴ -I] [y] [c] (x, y, stats) = tricg(A, b, c, snd=true) K = [-eye(m) A; A' -eye(n)] B = [b; c] @@ -50,7 +50,7 @@ resid = norm(r) @printf("TriCG: Relative residual: %8.1e\n", resid) # [τI A] [x] = [b] -# [ Aᵀ νI] [y] [c] +# [ Aᴴ νI] [y] [c] (τ, ν) = (1e-4, 1e2) (x, y, stats) = tricg(A, b, c, τ=τ, ν=ν) K = [τ*eye(m) A; A' ν*eye(n)] @@ -60,7 +60,7 @@ resid = norm(r) @printf("TriCG: Relative residual: %8.1e\n", resid) # [M⁻¹ A ] [x] = [b] -# [Aᵀ -N⁻¹] [y] [c] +# [Aᴴ -N⁻¹] [y] [c] (x, y, stats) = tricg(A, b, c, M=M, N=N, verbose=1) K = [inv(M) A; A' -inv(N)] H = BlockDiagonalOperator(M, N) diff --git a/docs/src/examples/trimr.md b/docs/src/examples/trimr.md index 2aa48be1e..adc4e82e5 100644 --- a/docs/src/examples/trimr.md +++ b/docs/src/examples/trimr.md @@ -14,7 +14,7 @@ m, n = size(A) c = -b # [D A] [x] = [b] -# [Aᵀ 0] [y] [c] +# [Aᴴ 0] [y] [c] llt_D = cholesky(D) opD⁻¹ = LinearOperator(Float64, 5, 5, true, true, (y, v) -> ldiv!(y, llt_D, v)) opH⁻¹ = BlockDiagonalOperator(opD⁻¹, eye(n)) @@ -34,7 +34,7 @@ N = diagm(0 => [5.0 * i for i = 1:n]) c = -b # [I A] [x] = [b] -# [Aᵀ -I] [y] [c] +# [Aᴴ -I] [y] [c] (x, y, stats) = trimr(A, b, c) K = [eye(m) A; A' -eye(n)] B = [b; c] @@ -43,7 +43,7 @@ resid = norm(r) @printf("TriMR: Relative residual: %8.1e\n", resid) # [M A] [x] = [b] -# [Aᵀ -N] [y] [c] +# [Aᴴ -N] [y] [c] ldlt_M = ldl(M) ldlt_N = ldl(N) opM⁻¹ = LinearOperator(Float64, size(M,1), size(M,2), true, true, (y, v) -> ldiv!(y, ldlt_M, v)) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 4c9887f24..3c6bc1e29 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -50,7 +50,7 @@ using CUDA, CUDA.CUSPARSE A_gpu = CuSparseMatrixCSC(A_cpu) # A = CuSparseMatrixCSR(A_cpu) b_gpu = CuVector(b_cpu) -# LLᵀ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices +# LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ic02(A_gpu, 'O') # Solve Py = x diff --git a/docs/src/index.md b/docs/src/index.md index ce657436d..00694b4de 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -46,7 +46,7 @@ Overdetermined sytems are less common but also occur. 4 - Adjoint systems ```math - Ax = b \quad \text{and} \quad A^T y = c + Ax = b \quad \text{and} \quad A^H y = c ``` where **_A_** can have any shape. @@ -54,7 +54,7 @@ where **_A_** can have any shape. 5 - Saddle-point and symmetric quasi-definite (SQD) systems ```math - \begin{bmatrix} M & \phantom{-}A \\ A^T & -N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \left(\begin{bmatrix} b \\ 0 \end{bmatrix},\begin{bmatrix} 0 \\ c \end{bmatrix},\begin{bmatrix} b \\ c \end{bmatrix}\right) + \begin{bmatrix} M & \phantom{-}A \\ A^H & -N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \left(\begin{bmatrix} b \\ 0 \end{bmatrix},\begin{bmatrix} 0 \\ c \end{bmatrix},\begin{bmatrix} b \\ c \end{bmatrix}\right) ``` where **_A_** can have any shape. @@ -65,7 +65,7 @@ where **_A_** can have any shape. \begin{bmatrix} M & A \\ B & N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix} ``` -where **_A_** can have any shape and **_B_** has the shape of **_Aᵀ_**. +where **_A_** can have any shape and **_B_** has the shape of **_Aᴴ_**. **_A_**, **_B_**, **_b_** and **_c_** must be all nonzero. Krylov solvers are particularly appropriate in situations where such problems must be solved but a factorization is not possible, either because: diff --git a/docs/src/warm_start.md b/docs/src/warm_start.md index 030cad6c0..e1d680efd 100644 --- a/docs/src/warm_start.md +++ b/docs/src/warm_start.md @@ -41,14 +41,14 @@ Explicit restarts cannot be avoided in certain block methods, such as TriMR, due ```julia # [E A] [x] = [b] -# [Aᵀ F] [y] [c] +# [Aᴴ F] [y] [c] M = inv(E) N = inv(F) x₀, y₀, stats = trimr(A, b, c, M=M, N=N) # E and F are not available inside TriMR b₀ = b - Ex₀ - Ay -c₀ = c - Aᵀx₀ - Fy +c₀ = c - Aᴴx₀ - Fy Δx, Δy, stats = trimr(A, b₀, c₀, M=M, N=N) x = x₀ + Δx From 604c96066cc6bf24e9dfe05b92592204c08973c4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 19:04:15 -0400 Subject: [PATCH 016/182] =?UTF-8?q?[test]=20Use=20A=E1=B4=B4=20instead=20o?= =?UTF-8?q?f=20A=E1=B5=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/get_div_grad.jl | 4 ++-- test/test_allocations.jl | 16 ++++++++-------- test/test_bicgstab.jl | 4 ++-- test/test_bilq.jl | 4 ++-- test/test_bilqr.jl | 4 ++-- test/test_cgs.jl | 4 ++-- test/test_qmr.jl | 4 ++-- test/test_solvers.jl | 16 ++++++++-------- test/test_trilqr.jl | 2 +- test/test_utils.jl | 6 +++--- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/test/get_div_grad.jl b/test/get_div_grad.jl index 6d6bf012e..ae27e5061 100644 --- a/test/get_div_grad.jl +++ b/test/get_div_grad.jl @@ -1,8 +1,8 @@ # Identity matrix. eye(n::Int; FC=Float64) = sparse(one(FC) * I, n, n) -# Compute the energy norm ‖r‖ₚ = √(rᵀPr) where P is a symmetric and positive definite matrix. -metric(r, P) = sqrt(dot(r, P * r)) +# Compute the energy norm ‖r‖ₚ = √(rᴴPr) where P is a symmetric and positive definite matrix. +metric(r, P) = sqrt(real(dot(r, P * r))) # Based on Lars Ruthotto's initial implementation. function get_div_grad(n1 :: Int, n2 :: Int, n3 :: Int) diff --git a/test/test_allocations.jl b/test/test_allocations.jl index 4c6817499..790fcc7a8 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -254,7 +254,7 @@ @testset "CGNE" begin # CGNE needs: - # - 3 n-vectors: x, p, Aᵀz + # - 3 n-vectors: x, p, Aᴴz # - 2 m-vectors: r, q storage_cgne(n, m) = 3 * n + 2 * m storage_cgne_bytes(n, m) = nbits * storage_cgne(n, m) @@ -272,7 +272,7 @@ @testset "CRMR" begin # CRMR needs: - # - 3 n-vectors: x, p, Aᵀr + # - 3 n-vectors: x, p, Aᴴr # - 2 m-vectors: r, q storage_crmr(n, m) = 3 * n + 2 * m storage_crmr_bytes(n, m) = nbits * storage_crmr(n, m) @@ -290,7 +290,7 @@ @testset "LNLQ" begin # LNLQ needs: - # - 3 n-vectors: x, v, Aᵀu + # - 3 n-vectors: x, v, Aᴴu # - 4 m-vectors: y, w̄, u, Av storage_lnlq(n, m) = 3 * n + 4 * m storage_lnlq_bytes(n, m) = nbits * storage_lnlq(n, m) @@ -308,7 +308,7 @@ @testset "CRAIG" begin # CRAIG needs: - # - 3 n-vectors: x, v, Aᵀu + # - 3 n-vectors: x, v, Aᴴu # - 4 m-vectors: y, w, u, Av storage_craig(n, m) = 3 * n + 4 * m storage_craig_bytes(n, m) = nbits * storage_craig(n, m) @@ -326,7 +326,7 @@ @testset "CRAIGMR" begin # CRAIGMR needs: - # - 4 n-vectors: x, v, Aᵀu, d + # - 4 n-vectors: x, v, Aᴴu, d # - 5 m-vectors: y, u, w, wbar, Av storage_craigmr(n, m) = 4 * n + 5 * m storage_craigmr_bytes(n, m) = nbits * storage_craigmr(n, m) @@ -362,7 +362,7 @@ @testset "LSLQ" begin # LSLQ needs: - # - 4 m-vectors: x_lq, v, Aᵀu, w̄ (= x_cg) + # - 4 m-vectors: x_lq, v, Aᴴu, w̄ (= x_cg) # - 2 n-vectors: u, Av storage_lslq(n, m) = 4 * m + 2 * n storage_lslq_bytes(n, m) = nbits * storage_lslq(n, m) @@ -398,7 +398,7 @@ @testset "LSQR" begin # LSQR needs: - # - 4 m-vectors: x, v, w, Aᵀu + # - 4 m-vectors: x, v, w, Aᴴu # - 2 n-vectors: u, Av storage_lsqr(n, m) = 4 * m + 2 * n storage_lsqr_bytes(n, m) = nbits * storage_lsqr(n, m) @@ -416,7 +416,7 @@ @testset "LSMR" begin # LSMR needs: - # - 5 m-vectors: x, v, h, hbar, Aᵀu + # - 5 m-vectors: x, v, h, hbar, Aᴴu # - 2 n-vectors: u, Av storage_lsmr(n, m) = 5 * m + 2 * n storage_lsmr_bytes(n, m) = nbits * storage_lsmr(n, m) diff --git a/test/test_bicgstab.jl b/test/test_bicgstab.jl index ce4e6dcd4..6817acf3d 100644 --- a/test/test_bicgstab.jl +++ b/test/test_bicgstab.jl @@ -82,10 +82,10 @@ @test(resid ≤ bicgstab_tol) @test(stats.solved) - # Test bᵀc == 0 + # Test bᴴc == 0 A, b, c = bc_breakdown(FC=FC) (x, stats) = bicgstab(A, b, c=c) - @test stats.status == "Breakdown bᵀc = 0" + @test stats.status == "Breakdown bᴴc = 0" # test callback function solver = BicgstabSolver(A, b) diff --git a/test/test_bilq.jl b/test/test_bilq.jl index 900d1f6e5..40b9872db 100644 --- a/test/test_bilq.jl +++ b/test/test_bilq.jl @@ -66,10 +66,10 @@ @test(resid ≤ bilq_tol) @test(stats.solved) - # Test bᵀc == 0 + # Test bᴴc == 0 A, b, c = bc_breakdown(FC=FC) (x, stats) = bilq(A, b, c=c) - @test stats.status == "Breakdown bᵀc = 0" + @test stats.status == "Breakdown bᴴc = 0" # test callback function diff --git a/test/test_bilqr.jl b/test/test_bilqr.jl index 6dab06ec7..fd46aade4 100644 --- a/test/test_bilqr.jl +++ b/test/test_bilqr.jl @@ -46,10 +46,10 @@ @test(resid_dual ≤ bilqr_tol) @test(stats.solved_dual) - # Test bᵀc == 0 + # Test bᴴc == 0 A, b, c = bc_breakdown(FC=FC) (x, t, stats) = bilqr(A, b, c) - @test stats.status == "Breakdown bᵀc = 0" + @test stats.status == "Breakdown bᴴc = 0" # test callback function A, b, c = adjoint_pde(FC=FC) diff --git a/test/test_cgs.jl b/test/test_cgs.jl index 5c505bb70..832cd76c3 100644 --- a/test/test_cgs.jl +++ b/test/test_cgs.jl @@ -74,10 +74,10 @@ @test(resid ≤ cgs_tol) @test(stats.solved) - # Test bᵀc == 0 + # Test bᴴc == 0 A, b, c = bc_breakdown(FC=FC) (x, stats) = cgs(A, b, c=c) - @test stats.status == "Breakdown bᵀc = 0" + @test stats.status == "Breakdown bᴴc = 0" # test callback function A, b = sparse_laplacian(FC=FC) diff --git a/test/test_qmr.jl b/test/test_qmr.jl index 184b9877d..4a6b8c1c9 100644 --- a/test/test_qmr.jl +++ b/test/test_qmr.jl @@ -58,10 +58,10 @@ @test(resid ≤ qmr_tol) @test(stats.solved) - # Test bᵀc == 0 + # Test bᴴc == 0 A, b, c = bc_breakdown(FC=FC) (x, stats) = qmr(A, b, c=c) - @test stats.status == "Breakdown bᵀc = 0" + @test stats.status == "Breakdown bᴴc = 0" # test callback function solver = QmrSolver(A, b) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index a6003088b..6f60cb737 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -628,7 +628,7 @@ function test_solvers(FC) ├──────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 64│ │ p│ Vector{$FC}│ 64│ - │ Aᵀr│ Vector{$FC}│ 64│ + │ Aᴴr│ Vector{$FC}│ 64│ │ r│ Vector{$FC}│ 32│ │ q│ Vector{$FC}│ 32│ │ Nq│ Vector{$FC}│ 0│ @@ -694,7 +694,7 @@ function test_solvers(FC) ├─────────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 64│ │ Nv│ Vector{$FC}│ 64│ - │ Aᵀu│ Vector{$FC}│ 64│ + │ Aᴴu│ Vector{$FC}│ 64│ │ d│ Vector{$FC}│ 64│ │ y│ Vector{$FC}│ 32│ │ Mu│ Vector{$FC}│ 32│ @@ -719,7 +719,7 @@ function test_solvers(FC) ├──────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 64│ │ p│ Vector{$FC}│ 64│ - │ Aᵀz│ Vector{$FC}│ 64│ + │ Aᴴz│ Vector{$FC}│ 64│ │ r│ Vector{$FC}│ 32│ │ q│ Vector{$FC}│ 32│ │ s│ Vector{$FC}│ 0│ @@ -739,7 +739,7 @@ function test_solvers(FC) ├──────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 64│ │ Nv│ Vector{$FC}│ 64│ - │ Aᵀu│ Vector{$FC}│ 64│ + │ Aᴴu│ Vector{$FC}│ 64│ │ y│ Vector{$FC}│ 32│ │ w̄│ Vector{$FC}│ 32│ │ Mu│ Vector{$FC}│ 32│ @@ -762,7 +762,7 @@ function test_solvers(FC) ├───────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 64│ │ Nv│ Vector{$FC}│ 64│ - │ Aᵀu│ Vector{$FC}│ 64│ + │ Aᴴu│ Vector{$FC}│ 64│ │ y│ Vector{$FC}│ 32│ │ w│ Vector{$FC}│ 32│ │ Mu│ Vector{$FC}│ 32│ @@ -785,7 +785,7 @@ function test_solvers(FC) ├──────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 32│ │ Nv│ Vector{$FC}│ 32│ - │ Aᵀu│ Vector{$FC}│ 32│ + │ Aᴴu│ Vector{$FC}│ 32│ │ w̄│ Vector{$FC}│ 32│ │ Mu│ Vector{$FC}│ 64│ │ Av│ Vector{$FC}│ 64│ @@ -826,7 +826,7 @@ function test_solvers(FC) ├──────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 32│ │ Nv│ Vector{$FC}│ 32│ - │ Aᵀu│ Vector{$FC}│ 32│ + │ Aᴴu│ Vector{$FC}│ 32│ │ w│ Vector{$FC}│ 32│ │ Mu│ Vector{$FC}│ 64│ │ Av│ Vector{$FC}│ 64│ @@ -869,7 +869,7 @@ function test_solvers(FC) ├──────────┼───────────────┼─────────────────┤ │ x│ Vector{$FC}│ 32│ │ Nv│ Vector{$FC}│ 32│ - │ Aᵀu│ Vector{$FC}│ 32│ + │ Aᴴu│ Vector{$FC}│ 32│ │ h│ Vector{$FC}│ 32│ │ hbar│ Vector{$FC}│ 32│ │ Mu│ Vector{$FC}│ 64│ diff --git a/test/test_trilqr.jl b/test/test_trilqr.jl index 7d7927372..baf8a597e 100644 --- a/test/test_trilqr.jl +++ b/test/test_trilqr.jl @@ -74,7 +74,7 @@ @test(resid_dual ≤ trilqr_tol) @test(stats.solved_dual) - # Test consistent Ax = b and inconsistent Aᵀt = c. + # Test consistent Ax = b and inconsistent Aᴴt = c. A, b, c = rectangular_adjoint(FC=FC) (x, t, stats) = trilqr(A, b, c) diff --git a/test/test_utils.jl b/test/test_utils.jl index ed72056b6..fbfe2e4e0 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -175,10 +175,10 @@ function square_adjoint(n :: Int=100; FC=Float64) return A, b, c end -# Adjoint systems with Ax = b underdetermined consistent and Aᵀt = c overdetermined insconsistent. +# Adjoint systems with Ax = b underdetermined consistent and Aᴴt = c overdetermined insconsistent. function rectangular_adjoint(n :: Int=10, m :: Int=25; FC=Float64) - Aᵀ, c = over_inconsistent(m, n; FC=FC) - A = adjoint(Aᵀ) + Aᴴ, c = over_inconsistent(m, n; FC=FC) + A = adjoint(Aᴴ) b = A * ones(FC, m) return A, b, c end From 73e95a799b87dc6eb097b324fa3e8a618f6cece3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 6 Sep 2022 21:22:06 -0400 Subject: [PATCH 017/182] =?UTF-8?q?[code]=20Use=20A=E1=B4=B4=20instead=20o?= =?UTF-8?q?f=20A=E1=B5=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bicgstab.jl | 6 ++--- src/bilq.jl | 36 +++++++++++++-------------- src/bilqr.jl | 54 ++++++++++++++++++++--------------------- src/cg_lanczos.jl | 8 +++--- src/cg_lanczos_shift.jl | 10 ++++---- src/cgls.jl | 22 ++++++++--------- src/cgne.jl | 14 +++++------ src/cgs.jl | 4 +-- src/cr.jl | 38 ++++++++++++++--------------- src/craig.jl | 24 +++++++++--------- src/craigmr.jl | 34 +++++++++++++------------- src/crls.jl | 24 +++++++++--------- src/crmr.jl | 26 ++++++++++---------- src/fom.jl | 2 +- src/gmres.jl | 4 +-- src/gpmr.jl | 12 ++++----- src/krylov_solvers.jl | 48 ++++++++++++++++++------------------ src/krylov_utils.jl | 8 +++--- src/lnlq.jl | 42 ++++++++++++++++---------------- src/lslq.jl | 36 +++++++++++++-------------- src/lsmr.jl | 38 ++++++++++++++--------------- src/lsqr.jl | 34 +++++++++++++------------- src/minres.jl | 8 +++--- src/minres_qlp.jl | 4 +-- src/qmr.jl | 30 +++++++++++------------ src/tricg.jl | 26 ++++++++++---------- src/trilqr.jl | 28 ++++++++++----------- src/trimr.jl | 20 +++++++-------- src/usymlq.jl | 12 ++++----- src/usymqr.jl | 26 ++++++++++---------- 30 files changed, 339 insertions(+), 339 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index c3b914599..3e5635775 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -26,10 +26,10 @@ export bicgstab, bicgstab! Solve the square linear system Ax = b using the BICGSTAB method. BICGSTAB requires two initial vectors `b` and `c`. -The relation `bᵀc ≠ 0` must be satisfied and by default `c = b`. +The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. The Biconjugate Gradient Stabilized method is a variant of BiCG, like CGS, -but using different updates for the Aᵀ-sequence in order to obtain smoother +but using different updates for the Aᴴ-sequence in order to obtain smoother convergence than CGS. If BICGSTAB stagnates, we recommend DQGMRES and BiLQ as alternative methods for unsymmetric square systems. @@ -157,7 +157,7 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; if next_ρ == 0 stats.niter = 0 stats.solved, stats.inconsistent = false, false - stats.status = "Breakdown bᵀc = 0" + stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver end diff --git a/src/bilq.jl b/src/bilq.jl index ce84d3ec1..f40538245 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -24,7 +24,7 @@ export bilq, bilq! Solve the square linear system Ax = b using the BiLQ method. BiLQ is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. -The relation `bᵀc ≠ 0` must be satisfied and by default `c = b`. +The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. When `A` is symmetric and `b = c`, BiLQ is equivalent to SYMMLQ. An option gives the possibility of transferring to the BiCG point, @@ -90,7 +90,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab ktypeof(c) == S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. uₖ₋₁, uₖ, q, vₖ₋₁, vₖ = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ @@ -127,25 +127,25 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, bNorm) # Initialize the Lanczos biorthogonalization process. - cᵗb = @kdot(n, c, r₀) # ⟨c,r₀⟩ - if cᵗb == 0 + cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ + if cᴴb == 0 stats.niter = 0 stats.solved = false stats.inconsistent = false - stats.status = "Breakdown bᵀc = 0" + stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver end - βₖ = √(abs(cᵗb)) # β₁γ₁ = cᵀ(b - Ax₀) - γₖ = cᵗb / βₖ # β₁γ₁ = cᵀ(b - Ax₀) + βₖ = √(abs(cᴴb)) # β₁γ₁ = cᴴ(b - Ax₀) + γₖ = cᴴb / βₖ # β₁γ₁ = cᴴ(b - Ax₀) vₖ₋₁ .= zero(FC) # v₀ = 0 uₖ₋₁ .= zero(FC) # u₀ = 0 vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ uₖ .= c ./ conj(γₖ) # u₁ = c / γ̄₁ cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᵀ + d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᴴ ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations @@ -165,10 +165,10 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab # Continue the Lanczos biorthogonalization process. # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᵀUₖ = Uₖ(Tₖ)ᵀ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ - mul!(p, Aᵀ, uₖ) # Forms uₖ₊₁ : p ← Aᵀuₖ + mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ @@ -178,9 +178,9 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - pᵗq = @kdot(n, p, q) # pᵗq = ⟨p,q⟩ - βₖ₊₁ = √(abs(pᵗq)) # βₖ₊₁ = √(|pᵗq|) - γₖ₊₁ = pᵗq / βₖ₊₁ # γₖ₊₁ = pᵗq / βₖ₊₁ + pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ + βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) + γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ # Update the LQ factorization of Tₖ = L̅ₖQₖ. # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] @@ -235,7 +235,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ end - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᵀ. + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᴴ. # [d̅ₖ₋₁ vₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * vₖ # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ if iter ≥ 2 @@ -258,13 +258,13 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - if pᵗq ≠ 0 + if pᴴq ≠ 0 @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p end # Compute ⟨vₖ,vₖ₊₁⟩ and ‖vₖ₊₁‖ - vₖᵀvₖ₊₁ = @kdot(n, vₖ₋₁, vₖ) + vₖᴴvₖ₊₁ = @kdot(n, vₖ₋₁, vₖ) norm_vₖ₊₁ = @knrm2(n, vₖ) # Compute BiLQ residual norm @@ -274,7 +274,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab else μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ - θₖ = conj(μₖ) * ωₖ * vₖᵀvₖ₊₁ + θₖ = conj(μₖ) * ωₖ * vₖᴴvₖ₊₁ rNorm_lq = sqrt(abs2(μₖ) * norm_vₖ^2 + abs2(ωₖ) * norm_vₖ₊₁^2 + 2 * real(θₖ)) end history && push!(rNorms, rNorm_lq) @@ -300,7 +300,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab solved_lq = rNorm_lq ≤ ε solved_cg = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) tired = iter ≥ itmax - breakdown = !solved_lq && !solved_cg && (pᵗq == 0) + breakdown = !solved_lq && !solved_cg && (pᴴq == 0) kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm_lq) end (verbose > 0) && @printf("\n") diff --git a/src/bilqr.jl b/src/bilqr.jl index 09fef1f6c..7284597dc 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -1,5 +1,5 @@ # An implementation of BILQR for the solution of square -# consistent linear adjoint systems Ax = b and Aᵀy = c. +# consistent linear adjoint systems Ax = b and Aᴴy = c. # # This method is described in # @@ -24,11 +24,11 @@ export bilqr, bilqr! Combine BiLQ and QMR to solve adjoint systems. [0 A] [y] = [b] - [Aᵀ 0] [x] [c] + [Aᴴ 0] [x] [c] -The relation `bᵀc ≠ 0` must be satisfied. +The relation `bᴴc ≠ 0` must be satisfied. BiLQ is used for solving primal system `Ax = b`. -QMR is used for solving dual system `Aᵀy = c`. +QMR is used for solving dual system `Aᴴy = c`. An option gives the possibility of transferring from the BiLQ point to the BiCG point, when it exists. The transfer is based on the residual norm. @@ -94,7 +94,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: ktypeof(c) == S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. uₖ₋₁, uₖ, q, vₖ₋₁, vₖ = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ @@ -109,7 +109,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: if warm_start mul!(r₀, A, Δx) @kaxpby!(n, one(FC), b, -one(FC), r₀) - mul!(s₀, Aᵀ, Δy) + mul!(s₀, Aᴴ, Δy) @kaxpby!(n, one(FC), c, -one(FC), s₀) end @@ -117,7 +117,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: x .= zero(FC) # x₀ bNorm = @knrm2(n, r₀) # rNorm = ‖r₀‖ - # Initial solution t₀ and residual norm ‖s₀‖ = ‖c - Aᵀy₀‖. + # Initial solution t₀ and residual norm ‖s₀‖ = ‖c - Aᴴy₀‖. t .= zero(FC) # t₀ cNorm = @knrm2(n, s₀) # sNorm = ‖s₀‖ @@ -132,34 +132,34 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e\n", iter, bNorm, cNorm) # Initialize the Lanczos biorthogonalization process. - cᵗb = @kdot(n, s₀, r₀) # ⟨s₀,r₀⟩ = ⟨c - Aᵀy₀,b - Ax₀⟩ - if cᵗb == 0 + cᴴb = @kdot(n, s₀, r₀) # ⟨s₀,r₀⟩ = ⟨c - Aᴴy₀,b - Ax₀⟩ + if cᴴb == 0 stats.niter = 0 stats.solved_primal = false stats.solved_dual = false - stats.status = "Breakdown bᵀc = 0" + stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver end # Set up workspace. - βₖ = √(abs(cᵗb)) # β₁γ₁ = (c - Aᵀy₀)ᵀ(b - Ax₀) - γₖ = cᵗb / βₖ # β₁γ₁ = (c - Aᵀy₀)ᵀ(b - Ax₀) + βₖ = √(abs(cᴴb)) # β₁γ₁ = (c - Aᴴy₀)ᴴ(b - Ax₀) + γₖ = cᴴb / βₖ # β₁γ₁ = (c - Aᴴy₀)ᴴ(b - Ax₀) vₖ₋₁ .= zero(FC) # v₀ = 0 uₖ₋₁ .= zero(FC) # u₀ = 0 vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= s₀ ./ conj(γₖ) # u₁ = (c - Aᵀy₀) / γ̄₁ + uₖ .= s₀ ./ conj(γₖ) # u₁ = (c - Aᴴy₀) / γ̄₁ cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᵀ + d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᴴ ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations ψbarₖ₋₁ = ψₖ₋₁ = zero(FC) # ψₖ₋₁ and ψbarₖ are the last components of h̅ₖ = Qₖγ̄₁e₁ norm_vₖ = bNorm / βₖ # ‖vₖ‖ is used for residual norm estimates ϵₖ₋₃ = λₖ₋₂ = zero(FC) # Components of Lₖ₋₁ - wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Uₖ(Lₖ)⁻ᵀ - wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Lₖ)⁻ᵀ + wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Uₖ(Lₖ)⁻ᴴ + wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Lₖ)⁻ᴴ τₖ = zero(T) # τₖ is used for the dual residual norm estimate # Stopping criterion. @@ -180,10 +180,10 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Continue the Lanczos biorthogonalization process. # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᵀUₖ = Uₖ(Tₖ)ᵀ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ - mul!(p, Aᵀ, uₖ) # Forms uₖ₊₁ : p ← Aᵀuₖ + mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ @@ -193,9 +193,9 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - pᵗq = @kdot(n, p, q) # pᵗq = ⟨p,q⟩ - βₖ₊₁ = √(abs(pᵗq)) # βₖ₊₁ = √(|pᵗq|) - γₖ₊₁ = pᵗq / βₖ₊₁ # γₖ₊₁ = pᵗq / βₖ₊₁ + pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ + βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) + γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ # Update the LQ factorization of Tₖ = L̅ₖQₖ. # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] @@ -251,7 +251,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ end - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᵀ. + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᴴ. # [d̅ₖ₋₁ vₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * vₖ # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ if iter ≥ 2 @@ -271,7 +271,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: end # Compute ⟨vₖ,vₖ₊₁⟩ and ‖vₖ₊₁‖ - vₖᵀvₖ₊₁ = @kdot(n, vₖ, q) / βₖ₊₁ + vₖᴴvₖ₊₁ = @kdot(n, vₖ, q) / βₖ₊₁ norm_vₖ₊₁ = @knrm2(n, q) / βₖ₊₁ # Compute BiLQ residual norm @@ -281,7 +281,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: else μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ - θₖ = conj(μₖ) * ωₖ * vₖᵀvₖ₊₁ + θₖ = conj(μₖ) * ωₖ * vₖᴴvₖ₊₁ rNorm_lq = sqrt(abs2(μₖ) * norm_vₖ^2 + abs2(ωₖ) * norm_vₖ₊₁^2 + 2 * real(θₖ)) end history && push!(rNorms, rNorm_lq) @@ -318,7 +318,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: ψbarₖ = sₖ * ψbarₖ₋₁ end - # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Uₖ₋₁)(Lₖ₋₁)⁻ᵀ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Uₖ₋₁)ᵀ. + # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Uₖ₋₁)(Lₖ₋₁)⁻ᴴ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Uₖ₋₁)ᵀ. # w₁ = u₁ / δ̄₁ if iter == 2 wₖ₋₁ = wₖ₋₂ @@ -372,7 +372,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - if pᵗq ≠ zero(FC) + if pᴴq ≠ zero(FC) @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p end @@ -392,7 +392,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: user_requested_exit = callback(solver) :: Bool tired = iter ≥ itmax - breakdown = !solved_lq && !solved_cg && (pᵗq == 0) + breakdown = !solved_lq && !solved_cg && (pᴴq == 0) kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf("%5d %7s %7.1e\n", iter, "", sNorm) kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf("%5d %7.1e %7s\n", iter, rNorm_lq, "") diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index a8e24f02f..2f2dae16d 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -111,7 +111,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F Mv .= b end MisI || mulorldiv!(v, M, Mv, ldiv) # v₁ = M⁻¹r₀ - β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᵀ M v₁ + β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᴴ M v₁ σ = β rNorm = σ history && push!(rNorms, rNorm) @@ -157,10 +157,10 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F # Form next Lanczos vector. # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ - δ = @kdotr(n, v, Mv_next) # δₖ = vₖᵀ A vₖ + δ = @kdotr(n, v, Mv_next) # δₖ = vₖᴴ A vₖ # Check curvature. Exit fast if requested. - # It is possible to show that σₖ² (δₖ - ωₖ₋₁ / γₖ₋₁) = pₖᵀ A pₖ. + # It is possible to show that σₖ² (δₖ - ωₖ₋₁ / γₖ₋₁) = pₖᴴ A pₖ. γ = one(T) / (δ - ω / γ) # γₖ = 1 / (δₖ - ωₖ₋₁ / γₖ₋₁) indefinite |= (γ ≤ 0) (check_curvature & indefinite) && continue @@ -172,7 +172,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F end @. Mv = Mv_next # Mvₖ ← Mvₖ₊₁ MisI || mulorldiv!(v, M, Mv, ldiv) # vₖ₊₁ = M⁻¹ * Mvₖ₊₁ - β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᵀ M vₖ₊₁ + β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᴴ M vₖ₊₁ @kscal!(n, one(FC) / β, v) # vₖ₊₁ ← vₖ₊₁ / βₖ₊₁ MisI || @kscal!(n, one(FC) / β, Mv) # Mvₖ₊₁ ← Mvₖ₊₁ / βₖ₊₁ Anorm2 += β_prev^2 + β^2 + δ^2 # Use ‖Tₖ₊₁‖₂ as increasing approximation of ‖A‖₂. diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 01f11e41f..ff873e5b4 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -92,7 +92,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr end Mv .= b # Mv₁ ← b MisI || mulorldiv!(v, M, Mv, ldiv) # v₁ = M⁻¹ * Mv₁ - β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᵀ M v₁ + β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᴴ M v₁ rNorms .= β if history for i = 1 : nshifts @@ -157,7 +157,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr # Form next Lanczos vector. # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ - δ = @kdotr(n, v, Mv_next) # δₖ = vₖᵀ A vₖ + δ = @kdotr(n, v, Mv_next) # δₖ = vₖᴴ A vₖ @kaxpy!(n, -δ, Mv, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - δₖMvₖ if iter > 0 @kaxpy!(n, -β, Mv_prev, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - βₖMvₖ₋₁ @@ -165,12 +165,12 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr end @. Mv = Mv_next # Mvₖ ← Mvₖ₊₁ MisI || mulorldiv!(v, M, Mv, ldiv) # vₖ₊₁ = M⁻¹ * Mvₖ₊₁ - β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᵀ M vₖ₊₁ + β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᴴ M vₖ₊₁ @kscal!(n, one(FC) / β, v) # vₖ₊₁ ← vₖ₊₁ / βₖ₊₁ MisI || @kscal!(n, one(FC) / β, Mv) # Mvₖ₊₁ ← Mvₖ₊₁ / βₖ₊₁ - # Check curvature: vₖᵀ(A + sᵢI)vₖ = vₖᵀAvₖ + sᵢ‖vₖ‖² = δₖ + ρₖ * sᵢ with ρₖ = ‖vₖ‖². - # It is possible to show that σₖ² (δₖ + ρₖ * sᵢ - ωₖ₋₁ / γₖ₋₁) = pₖᵀ (A + sᵢ I) pₖ. + # Check curvature: vₖᴴ(A + sᵢI)vₖ = vₖᴴAvₖ + sᵢ‖vₖ‖² = δₖ + ρₖ * sᵢ with ρₖ = ‖vₖ‖². + # It is possible to show that σₖ² (δₖ + ρₖ * sᵢ - ωₖ₋₁ / γₖ₋₁) = pₖᴴ (A + sᵢ I) pₖ. MisI || (ρ = @kdotr(n, v, v)) for i = 1 : nshifts δhat[i] = δ + ρ * shifts[i] diff --git a/src/cgls.jl b/src/cgls.jl index f5529fbfb..43fa5a6b6 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -5,7 +5,7 @@ # # equivalently, of the normal equations # -# AᵀAx = Aᵀb. +# AᴴAx = Aᴴb. # # CGLS is formally equivalent to applying the conjugate gradient method # to the normal equations but should be more stable. It is also formally @@ -45,11 +45,11 @@ Solve the regularized linear least-squares problem using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CG to the normal equations - (AᵀA + λI) x = Aᵀb + (AᴴA + λI) x = Aᴴb but is more stable. -CGLS produces monotonic residuals ‖r‖₂ but not optimality residuals ‖Aᵀr‖₂. +CGLS produces monotonic residuals ‖r‖₂ but not optimality residuals ‖Aᴴr‖₂. It is formally equivalent to LSQR, though can be slightly less accurate, but simpler to implement. @@ -95,7 +95,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :Mr, S, m) @@ -117,9 +117,9 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; return solver end MisI || mulorldiv!(Mr, M, r, ldiv) - mul!(s, Aᵀ, Mr) + mul!(s, Aᴴ, Mr) p .= s - γ = @kdotr(n, s, s) # γ = sᵀs + γ = @kdotr(n, s, s) # γ = sᴴs iter = 0 itmax == 0 && (itmax = m + n) @@ -128,7 +128,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) ε = atol + rtol * ArNorm - (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᵀr‖", "‖r‖") + (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) status = "unknown" @@ -140,8 +140,8 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; while ! (solved || tired || user_requested_exit) mul!(q, A, p) MisI || mulorldiv!(Mq, M, q, ldiv) - δ = @kdotr(m, q, Mq) # δ = qᵀMq - λ > 0 && (δ += λ * @kdotr(n, p, p)) # δ = δ + pᵀp + δ = @kdotr(m, q, Mq) # δ = qᴴMq + λ > 0 && (δ += λ * @kdotr(n, p, p)) # δ = δ + pᴴp α = γ / δ # if a trust-region constraint is give, compute step to the boundary @@ -154,9 +154,9 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kaxpy!(n, α, p, x) # Faster than x = x + α * p @kaxpy!(m, -α, q, r) # Faster than r = r - α * q MisI || mulorldiv!(Mr, M, r, ldiv) - mul!(s, Aᵀ, Mr) + mul!(s, Aᴴ, Mr) λ > 0 && @kaxpy!(n, -λ, x, s) # s = A' * r - λ * x - γ_next = @kdotr(n, s, s) # γ_next = sᵀs + γ_next = @kdotr(n, s, s) # γ_next = sᴴs β = γ_next / γ @kaxpby!(n, one(FC), s, β, p) # p = s + βp γ = γ_next diff --git a/src/cgne.jl b/src/cgne.jl index 2f720b57c..68039d2de 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -10,7 +10,7 @@ # and is equivalent to applying the conjugate gradient method # to the linear system # -# AAᵀy = b. +# AAᴴy = b. # # This method is also known as Craig's method, CGME, and other # names, and is described in @@ -46,7 +46,7 @@ using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CG to the normal equations of the second kind - (AAᵀ + λI) y = b + (AAᴴ + λI) y = b but is more stable. When λ = 0, this method solves the minimum-norm problem @@ -104,12 +104,12 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!NisI, solver, :z, S, m) allocate_if(λ > 0, solver, :s, S, m) - x, p, Aᵀz, r, q, s, stats = solver.x, solver.p, solver.Aᵀz, solver.r, solver.q, solver.s, solver.stats + x, p, Aᴴz, r, q, s, stats = solver.x, solver.p, solver.Aᴴz, solver.r, solver.q, solver.s, solver.stats rNorms = stats.residuals reset!(stats) z = NisI ? r : solver.z @@ -126,7 +126,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; return solver end λ > 0 && (s .= r) - mul!(p, Aᵀ, z) + mul!(p, Aᴴ, z) # Use ‖p‖ to detect inconsistent system. # An inconsistent system will necessarily have AA' singular. @@ -161,8 +161,8 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; NisI || mulorldiv!(z, N, r, ldiv) γ_next = @kdotr(m, r, z) # Faster than γ_next = dot(r, z) β = γ_next / γ - mul!(Aᵀz, Aᵀ, z) - @kaxpby!(n, one(FC), Aᵀz, β, p) # Faster than p = Aᵀz + β * p + mul!(Aᴴz, Aᴴ, z) + @kaxpby!(n, one(FC), Aᴴz, β, p) # Faster than p = Aᴴz + β * p pNorm = @knrm2(n, p) if λ > 0 @kaxpby!(m, one(FC), r, β, s) # s = r + β * s diff --git a/src/cgs.jl b/src/cgs.jl index c1eb1056e..592eb1b2d 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -21,7 +21,7 @@ export cgs, cgs! Solve the consistent linear system Ax = b using conjugate gradient squared algorithm. CGS requires two initial vectors `b` and `c`. -The relation `bᵀc ≠ 0` must be satisfied and by default `c = b`. +The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. From "Iterative Methods for Sparse Linear Systems (Y. Saad)" : @@ -142,7 +142,7 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst if ρ == 0 stats.niter = 0 stats.solved, stats.inconsistent = false, false - stats.status = "Breakdown bᵀc = 0" + stats.status = "Breakdown bᴴc = 0" solver.warm_start =false return solver end diff --git a/src/cr.jl b/src/cr.jl index c678c7d29..4405eda76 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -149,7 +149,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; (verbose > 0) && @printf("%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") kdisplay(iter, verbose) && @printf(" %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) - descent = pr > 0 # pᵀr > 0 means p is a descent direction + descent = pr > 0 # pᴴr > 0 means p is a descent direction solved = rNorm ≤ ε tired = iter ≥ itmax on_boundary = false @@ -161,7 +161,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; if linesearch if (pAp ≤ γ * pNorm²) || (ρ ≤ γ * rNorm²) npcurv = true - (verbose > 0) && @printf("nonpositive curvature detected: pᵀAp = %8.1e and rᵀAr = %8.1e\n", pAp, ρ) + (verbose > 0) && @printf("nonpositive curvature detected: pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) stats.solved = solved stats.inconsistent = false stats.status = "nonpositive curvature" @@ -182,16 +182,16 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; tr = maximum(to_boundary(x, r, radius; flip = false, xNorm2 = xNorm², dNorm2 = rNorm²)) (verbose > 0) && @printf("t1 = %8.1e, t2 = %8.1e and tr = %8.1e\n", t1, t2, tr) - if abspAp ≤ γ * pNorm * @knrm2(n, q) # pᵀAp ≃ 0 + if abspAp ≤ γ * pNorm * @knrm2(n, q) # pᴴAp ≃ 0 npcurv = true # nonpositive curvature - (verbose > 0) && @printf("pᵀAp = %8.1e ≃ 0\n", pAp) - if abspr ≤ γ * pNorm * rNorm # pᵀr ≃ 0 - (verbose > 0) && @printf("pᵀr = %8.1e ≃ 0, redefining p := r\n", pr) + (verbose > 0) && @printf("pᴴAp = %8.1e ≃ 0\n", pAp) + if abspr ≤ γ * pNorm * rNorm # pᴴr ≃ 0 + (verbose > 0) && @printf("pᴴr = %8.1e ≃ 0, redefining p := r\n", pr) p = r # - ∇q(x) q = Ar - # q(x + αr) = q(x) - α ‖r‖² + ½ α² rᵀAr - # 1) if rᵀAr > 0, the quadratic decreases from α = 0 to α = ‖r‖² / rᵀAr - # 2) if rᵀAr ≤ 0, the quadratic decreases to -∞ in the direction r + # q(x + αr) = q(x) - α ‖r‖² + ½ α² rᴴAr + # 1) if rᴴAr > 0, the quadratic decreases from α = 0 to α = ‖r‖² / rᴴAr + # 2) if rᴴAr ≤ 0, the quadratic decreases to -∞ in the direction r if ρ > 0 # case 1 (verbose > 0) && @printf("quadratic is convex in direction r, curv = %8.1e\n", ρ) α = min(tr, rNorm² / ρ) @@ -200,12 +200,12 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; α = tr end else - # q_p = q(x + α_p * p) - q(x) = -α_p * rᵀp + ½ (α_p)² * pᵀAp - # q_r = q(x + α_r * r) - q(x) = -α_r * ‖r‖² + ½ (α_r)² * rᵀAr + # q_p = q(x + α_p * p) - q(x) = -α_p * rᴴp + ½ (α_p)² * pᴴAp + # q_r = q(x + α_r * r) - q(x) = -α_r * ‖r‖² + ½ (α_r)² * rᴴAr # Δ = q_p - q_r. If Δ > 0, r is followed, else p is followed α = descent ? t1 : t2 ρ > 0 && (tr = min(tr, rNorm² / ρ)) - Δ = -α * pr + tr * rNorm² - (tr)^2 * ρ / 2 # as pᵀAp = 0 + Δ = -α * pr + tr * rNorm² - (tr)^2 * ρ / 2 # as pᴴAp = 0 if Δ > 0 # direction r engenders a better decrease (verbose > 0) && @printf("direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) (verbose > 0) && @printf("redefining p := r\n") @@ -218,7 +218,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; end elseif pAp > 0 && ρ > 0 # no negative curvature - (verbose > 0) && @printf("positive curvatures along p and r. pᵀAp = %8.1e and rᵀAr = %8.1e\n", pAp, ρ) + (verbose > 0) && @printf("positive curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) α = ρ / @kdotr(n, q, Mq) if α ≥ t1 α = t1 @@ -227,8 +227,8 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; elseif pAp > 0 && ρ < 0 npcurv = true - (verbose > 0) && @printf("pᵀAp = %8.1e > 0 and rᵀAr = %8.1e < 0\n", pAp, ρ) - # q_p is minimal for α_p = rᵀp / pᵀAp + (verbose > 0) && @printf("pᴴAp = %8.1e > 0 and rᴴAr = %8.1e < 0\n", pAp, ρ) + # q_p is minimal for α_p = rᴴp / pᴴAp α = descent ? min(t1, pr / pAp) : max(t2, pr / pAp) Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 if Δ > 0 @@ -243,7 +243,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; elseif pAp < 0 && ρ > 0 npcurv = true - (verbose > 0) && @printf("pᵀAp = %8.1e < 0 and rᵀAr = %8.1e > 0\n", pAp, ρ) + (verbose > 0) && @printf("pᴴAp = %8.1e < 0 and rᴴAr = %8.1e > 0\n", pAp, ρ) α = descent ? t1 : t2 tr = min(tr, rNorm² / ρ) Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 @@ -259,7 +259,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; elseif pAp < 0 && ρ < 0 npcurv = true - (verbose > 0) && @printf("negative curvatures along p and r. pᵀAp = %8.1e and rᵀAr = %8.1e\n", pAp, ρ) + (verbose > 0) && @printf("negative curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) α = descent ? t1 : t2 Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 if Δ > 0 @@ -330,9 +330,9 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; solver.warm_start = false return solver end - pr = rNorm² + β * pr - β * α * pAp # pᵀr + pr = rNorm² + β * pr - β * α * pAp # pᴴr abspr = abs(pr) - pAp = ρ + β^2 * pAp # pᵀq + pAp = ρ + β^2 * pAp # pᴴq abspAp = abs(pAp) descent = pr > 0 diff --git a/src/craig.jl b/src/craig.jl index 20597ea02..5759e31df 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -11,7 +11,7 @@ # and is equivalent to applying the conjugate gradient method # to the linear system # -# AAᵀy = b. +# AAᴴy = b. # # This method, sometimes known under the name CRAIG, is the # Golub-Kahan implementation of CGNE, and is described in @@ -52,14 +52,14 @@ regularization parameter. This method is equivalent to CGNE but is more stable. For a system in the form Ax = b, Craig's method is equivalent to applying -CG to AAᵀy = b and recovering x = Aᵀy. Note that y are the Lagrange +CG to AAᴴy = b and recovering x = Aᴴy. Note that y are the Lagrange multipliers of the least-norm problem minimize ‖x‖ s.t. Ax = b. If `λ > 0`, CRAIG solves the symmetric and quasi-definite system - [ -F Aᵀ ] [ x ] [ 0 ] + [ -F Aᴴ ] [ x ] [ 0 ] [ A λ²E ] [ y ] = [ b ], where E and F are symmetric and positive definite. @@ -70,12 +70,12 @@ The system above represents the optimality conditions of min ‖x‖²_F + λ²‖y‖²_E s.t. Ax + λ²Ey = b. -For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᵀKx`. -CRAIG is then equivalent to applying CG to `(AF⁻¹Aᵀ + λ²E)y = b` with `Fx = Aᵀy`. +For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᴴKx`. +CRAIG is then equivalent to applying CG to `(AF⁻¹Aᴴ + λ²E)y = b` with `Fx = Aᴴy`. If `λ = 0`, CRAIG solves the symmetric and indefinite system - [ -F Aᵀ ] [ x ] [ 0 ] + [ -F Aᴴ ] [ x ] [ 0 ] [ A 0 ] [ y ] = [ b ]. The system above represents the optimality conditions of @@ -134,13 +134,13 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :u , S, m) allocate_if(!NisI, solver, :v , S, n) allocate_if(λ > 0, solver, :w2, S, n) - x, Nv, Aᵀu, y, w = solver.x, solver.Nv, solver.Aᵀu, solver.y, solver.w + x, Nv, Aᴴu, y, w = solver.x, solver.Nv, solver.Aᴴu, solver.y, solver.w Mu, Av, w2, stats = solver.Mu, solver.Av, solver.w2, solver.stats rNorms = stats.residuals reset!(stats) @@ -180,7 +180,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; Anorm² = zero(T) # Estimate of ‖A‖²_F. Anorm = zero(T) - Dnorm² = zero(T) # Estimate of ‖(AᵀA)⁻¹‖². + Dnorm² = zero(T) # Estimate of ‖(AᴴA)⁻¹‖². Acond = zero(T) # Estimate of cond(A). xNorm² = zero(T) # Estimate of ‖x‖². xNorm = zero(T) @@ -212,9 +212,9 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; while ! (solved || inconsistent || ill_cond || tired || user_requested_exit) # Generate the next Golub-Kahan vectors - # 1. αₖ₊₁Nvₖ₊₁ = Aᵀuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᵀu, Aᵀ, u) - @kaxpby!(n, one(FC), Aᵀu, -β, Nv) + # 1. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) if α == 0 diff --git a/src/craigmr.jl b/src/craigmr.jl index e08bb9c36..854e3df98 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -10,7 +10,7 @@ # and is equivalent to applying the conjugate residual method # to the linear system # -# AAᵀy = b. +# AAᴴy = b. # # This method is equivalent to CRMR, and is described in # @@ -44,7 +44,7 @@ using the CRAIGMR method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying the Conjugate Residuals method to the normal equations of the second kind - (AAᵀ + λ²I) y = b + (AAᴴ + λ²I) y = b but is more stable. When λ = 0, this method solves the minimum-norm problem @@ -52,7 +52,7 @@ but is more stable. When λ = 0, this method solves the minimum-norm problem If `λ > 0`, CRAIGMR solves the symmetric and quasi-definite system - [ -F Aᵀ ] [ x ] [ 0 ] + [ -F Aᴴ ] [ x ] [ 0 ] [ A λ²E ] [ y ] = [ b ], where E and F are symmetric and positive definite. @@ -63,12 +63,12 @@ The system above represents the optimality conditions of min ‖x‖²_F + λ²‖y‖²_E s.t. Ax + λ²Ey = b. -For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᵀKx`. -CRAIGMR is then equivalent to applying MINRES to `(AF⁻¹Aᵀ + λ²E)y = b` with `Fx = Aᵀy`. +For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᴴKx`. +CRAIGMR is then equivalent to applying MINRES to `(AF⁻¹Aᴴ + λ²E)y = b` with `Fx = Aᴴy`. If `λ = 0`, CRAIGMR solves the symmetric and indefinite system - [ -F Aᵀ ] [ x ] [ 0 ] + [ -F Aᴴ ] [ x ] [ 0 ] [ A 0 ] [ y ] = [ b ]. The system above represents the optimality conditions of @@ -129,20 +129,20 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :u, S, m) allocate_if(!NisI, solver, :v, S, n) allocate_if(λ > 0, solver, :q, S, n) - x, Nv, Aᵀu, d, y, Mu = solver.x, solver.Nv, solver.Aᵀu, solver.d, solver.y, solver.Mu + x, Nv, Aᴴu, d, y, Mu = solver.x, solver.Nv, solver.Aᴴu, solver.d, solver.y, solver.Mu w, wbar, Av, q, stats = solver.w, solver.wbar, solver.Av, solver.q, solver.stats rNorms, ArNorms = stats.residuals, stats.Aresiduals reset!(stats) u = MisI ? Mu : solver.u v = NisI ? Nv : solver.v - # Compute y such that AAᵀy = b. Then recover x = Aᵀy. + # Compute y such that AAᴴy = b. Then recover x = Aᴴy. x .= zero(FC) y .= zero(FC) Mu .= b @@ -161,9 +161,9 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # β₁Mu₁ = b. @kscal!(m, one(FC)/β, u) MisI || @kscal!(m, one(FC)/β, Mu) - # α₁Nv₁ = Aᵀu₁. - mul!(Aᵀu, Aᵀ, u) - Nv .= Aᵀu + # α₁Nv₁ = Aᴴu₁. + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) Anorm² = α * α @@ -171,10 +171,10 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᵀr‖", "β", "α", "cos", "sin", "‖A‖²") + (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β, α, β, α, 0, 1, Anorm²) - # Aᵀb = 0 so x = 0 is a minimum least-squares solution + # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false @@ -288,9 +288,9 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # xₖ = Dₖzₖ @kaxpy!(n, ζ, d, x) - # 2. αₖ₊₁Nvₖ₊₁ = Aᵀuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᵀu, Aᵀ, u) - @kaxpby!(n, one(FC), Aᵀu, -β, Nv) + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) Anorm² = Anorm² + α * α # = ‖Lₖ‖ diff --git a/src/crls.jl b/src/crls.jl index 6410fb836..b041f8e9f 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -5,7 +5,7 @@ # # equivalently, of the linear system # -# AᵀAx = Aᵀb. +# AᴴAx = Aᴴb. # # This implementation follows the formulation given in # @@ -37,11 +37,11 @@ Solve the linear least-squares problem using the Conjugate Residuals (CR) method. This method is equivalent to applying MINRES to the normal equations - (AᵀA + λI) x = Aᵀb. + (AᴴA + λI) x = Aᴴb. This implementation recurs the residual r := b - Ax. -CRLS produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᵀr‖₂. +CRLS produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr‖₂. It is formally equivalent to LSMR, though can be substantially less accurate, but simpler to implement. @@ -86,7 +86,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :Ms, S, m) @@ -112,13 +112,13 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; end MisI || mulorldiv!(Mr, M, r, ldiv) - mul!(Ar, Aᵀ, Mr) # - λ * x0 if x0 ≠ 0. + mul!(Ar, Aᴴ, Mr) # - λ * x0 if x0 ≠ 0. mul!(s, A, Ar) MisI || mulorldiv!(Ms, M, s, ldiv) p .= Ar Ap .= s - mul!(q, Aᵀ, Ms) # Ap + mul!(q, Aᴴ, Ms) # Ap λ > 0 && @kaxpy!(n, λ, p, q) # q = q + λ * p γ = @kdotr(m, s, Ms) # Faster than γ = dot(s, Ms) iter = 0 @@ -128,7 +128,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; λ > 0 && (γ += λ * ArNorm * ArNorm) history && push!(ArNorms, ArNorm) ε = atol + rtol * ArNorm - (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᵀr‖", "‖r‖") + (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) status = "unknown" @@ -147,11 +147,11 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; if radius > 0 pNorm = @knrm2(n, p) if @kdotr(m, Ap, Ap) ≤ ε * sqrt(qNorm²) * pNorm # the quadratic is constant in the direction p - psd = true # det(AᵀA) = 0 - p = Ar # p = Aᵀr + psd = true # det(AᴴA) = 0 + p = Ar # p = Aᴴr pNorm² = ArNorm * ArNorm - mul!(q, Aᵀ, s) - α = min(ArNorm^2 / γ, maximum(to_boundary(x, p, radius, flip = false, dNorm2 = pNorm²))) # the quadratic is minimal in the direction Aᵀr for α = ‖Ar‖²/γ + mul!(q, Aᴴ, s) + α = min(ArNorm^2 / γ, maximum(to_boundary(x, p, radius, flip = false, dNorm2 = pNorm²))) # the quadratic is minimal in the direction Aᴴr for α = ‖Ar‖²/γ else pNorm² = pNorm * pNorm σ = maximum(to_boundary(x, p, radius, flip = false, dNorm2 = pNorm²)) @@ -177,7 +177,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kaxpby!(n, one(FC), Ar, β, p) # Faster than p = Ar + β * p @kaxpby!(m, one(FC), s, β, Ap) # Faster than Ap = s + β * Ap MisI || mulorldiv!(MAp, M, Ap, ldiv) - mul!(q, Aᵀ, MAp) + mul!(q, Aᴴ, MAp) λ > 0 && @kaxpy!(n, λ, p, q) # q = q + λ * p γ = γ_next diff --git a/src/crmr.jl b/src/crmr.jl index 6ed2b3c60..3fff12b08 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -10,7 +10,7 @@ # and is equivalent to applying the conjugate residual method # to the linear system # -# AAᵀy = b. +# AAᴴy = b. # # This method is equivalent to CRAIGMR, described in # @@ -44,7 +44,7 @@ using the Conjugate Residual (CR) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CR to the normal equations of the second kind - (AAᵀ + λI) y = b + (AAᴴ + λI) y = b but is more stable. When λ = 0, this method solves the minimum-norm problem @@ -102,19 +102,19 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!NisI, solver, :Nq, S, m) allocate_if(λ > 0, solver, :s , S, m) - x, p, Aᵀr, r = solver.x, solver.p, solver.Aᵀr, solver.r + x, p, Aᴴr, r = solver.x, solver.p, solver.Aᴴr, solver.r q, s, stats = solver.q, solver.s, solver.stats rNorms, ArNorms = stats.residuals, stats.Aresiduals reset!(stats) Nq = NisI ? q : solver.Nq x .= zero(FC) # initial estimation x = 0 - mulorldiv!(r, N, b, ldiv) # initial residual r = M * (b - Ax) = M * b + mulorldiv!(r, N, b, ldiv) # initial residual r = N * (b - Ax) = N * b bNorm = @knrm2(m, r) # norm(b - A * x0) if x0 ≠ 0. rNorm = bNorm # + λ * ‖x0‖ if x0 ≠ 0 and λ > 0. history && push!(rNorms, rNorm) @@ -126,9 +126,9 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; return solver end λ > 0 && (s .= r) - mul!(Aᵀr, Aᵀ, r) # - λ * x0 if x0 ≠ 0. - p .= Aᵀr - γ = @kdotr(n, Aᵀr, Aᵀr) # Faster than γ = dot(Aᵀr, Aᵀr) + mul!(Aᴴr, Aᴴ, r) # - λ * x0 if x0 ≠ 0. + p .= Aᴴr + γ = @kdotr(n, Aᴴr, Aᴴr) # Faster than γ = dot(Aᴴr, Aᴴr) λ > 0 && (γ += λ * rNorm * rNorm) iter = 0 itmax == 0 && (itmax = m + n) @@ -137,7 +137,7 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(ArNorms, ArNorm) ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᵀr‖", "‖r‖") + (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) status = "unknown" @@ -150,16 +150,16 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; mul!(q, A, p) λ > 0 && @kaxpy!(m, λ, s, q) # q = q + λ * s NisI || mulorldiv!(Nq, N, q, ldiv) - α = γ / @kdotr(m, q, Nq) # Compute qᵗ * M * q + α = γ / @kdotr(m, q, Nq) # Compute qᴴ * N * q @kaxpy!(n, α, p, x) # Faster than x = x + α * p @kaxpy!(m, -α, Nq, r) # Faster than r = r - α * Nq rNorm = @knrm2(m, r) # norm(r) - mul!(Aᵀr, Aᵀ, r) - γ_next = @kdotr(n, Aᵀr, Aᵀr) # Faster than γ_next = dot(Aᵀr, Aᵀr) + mul!(Aᴴr, Aᴴ, r) + γ_next = @kdotr(n, Aᴴr, Aᴴr) # Faster than γ_next = dot(Aᴴr, Aᴴr) λ > 0 && (γ_next += λ * rNorm * rNorm) β = γ_next / γ - @kaxpby!(n, one(FC), Aᵀr, β, p) # Faster than p = Aᵀr + β * p + @kaxpby!(n, one(FC), Aᴴr, β, p) # Faster than p = Aᴴr + β * p if λ > 0 @kaxpby!(m, one(FC), r, β, s) # s = r + β * s end diff --git a/src/fom.jl b/src/fom.jl index fcae5cf62..b212129ef 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -211,7 +211,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; mul!(w, A, p) # w ← AN⁻¹vₖ MisI || mulorldiv!(q, M, w, ldiv) # q ← M⁻¹AN⁻¹vₖ for i = 1 : inner_iter - U[nr+i] = @kdot(n, V[i], q) # hᵢₖ = qᵀvᵢ + U[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq @kaxpy!(n, -U[nr+i], V[i], q) # q ← q - hᵢₖvᵢ end diff --git a/src/gmres.jl b/src/gmres.jl index 388a4ab96..32999aa23 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -214,7 +214,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; mul!(w, A, p) # w ← AN⁻¹vₖ MisI || mulorldiv!(q, M, w, ldiv) # q ← M⁻¹AN⁻¹vₖ for i = 1 : inner_iter - R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = qᵀvᵢ + R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ end @@ -245,7 +245,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # [s̄ₖ -cₖ] [hₖ₊₁.ₖ] [ 0 ] (c[inner_iter], s[inner_iter], R[nr+inner_iter]) = sym_givens(R[nr+inner_iter], Hbis) - # Update zₖ = (Qₖ)ᵀβe₁ + # Update zₖ = (Qₖ)ᴴβe₁ ζₖ₊₁ = conj(s[inner_iter]) * z[inner_iter] z[inner_iter] = c[inner_iter] * z[inner_iter] diff --git a/src/gpmr.jl b/src/gpmr.jl index b10942995..82499b50e 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -28,7 +28,7 @@ GPMR solves the unsymmetric partitioned linear system [ B μI ] [ y ] [ c ], where λ and μ are real or complex numbers. -`A` can have any shape and `B` has the shape of `Aᵀ`. +`A` can have any shape and `B` has the shape of `Aᴴ`. `A`, `B`, `b` and `c` must be all nonzero. This implementation allows left and right block diagonal preconditioners @@ -172,7 +172,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: gs .= zero(FC) # Givens sines used for the factorization QₖRₖ = Sₖ₊₁.ₖ. gc .= zero(T) # Givens cosines used for the factorization QₖRₖ = Sₖ₊₁.ₖ. R .= zero(FC) # Upper triangular matrix Rₖ. - zt .= zero(FC) # Rₖzₖ = tₖ with (tₖ, τbar₂ₖ₊₁, τbar₂ₖ₊₂) = (Qₖ)ᵀ(βe₁ + γe₂). + zt .= zero(FC) # Rₖzₖ = tₖ with (tₖ, τbar₂ₖ₊₁, τbar₂ₖ₊₂) = (Qₖ)ᴴ(βe₁ + γe₂). # Warm-start # If λ ≠ 0, Cb₀ = Cb - CAΔy - λΔx because CM = Iₘ and E = Iₘ @@ -259,8 +259,8 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: DisI || mulorldiv!(p, D, dB, ldiv) # p = DBEvₖ for i = 1 : iter - hᵢₖ = @kdot(m, V[i], q) # hᵢ.ₖ = vᵢAuₖ - fᵢₖ = @kdot(n, U[i], p) # fᵢ.ₖ = uᵢBvₖ + hᵢₖ = @kdot(m, V[i], q) # hᵢ.ₖ = (vᵢ)ᴴq + fᵢₖ = @kdot(n, U[i], p) # fᵢ.ₖ = (uᵢ)ᴴp @kaxpy!(m, -hᵢₖ, V[i], q) # q ← q - hᵢ.ₖvᵢ @kaxpy!(n, -fᵢₖ, U[i], p) # p ← p - fᵢ.ₖuᵢ R[nr₂ₖ + 2i-1] = hᵢₖ @@ -270,8 +270,8 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: # Reorthogonalization of the Krylov basis. if reorthogonalization for i = 1 : iter - Htmp = @kdot(m, V[i], q) # hₜₘₚ = qᵀvᵢ - Ftmp = @kdot(n, U[i], p) # fₜₘₚ = pᵀuᵢ + Htmp = @kdot(m, V[i], q) # hₜₘₚ = (vᵢ)ᴴq + Ftmp = @kdot(n, U[i], p) # fₜₘₚ = (uᵢ)ᴴp @kaxpy!(m, -Htmp, V[i], q) # q ← q - hₜₘₚvᵢ @kaxpy!(n, -Ftmp, U[i], p) # p ← p - fₜₘₚuᵢ R[nr₂ₖ + 2i-1] += Htmp # hᵢ.ₖ = hᵢ.ₖ + hₜₘₚ diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index d557d91ae..abd0c7352 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1092,7 +1092,7 @@ may be used in order to create these vectors. mutable struct CgneSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S p :: S - Aᵀz :: S + Aᴴz :: S r :: S q :: S s :: S @@ -1105,13 +1105,13 @@ function CgneSolver(n, m, S) T = real(FC) x = S(undef, m) p = S(undef, m) - Aᵀz = S(undef, m) + Aᴴz = S(undef, m) r = S(undef, n) q = S(undef, n) s = S(undef, 0) z = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CgneSolver{T,FC,S}(x, p, Aᵀz, r, q, s, z, stats) + solver = CgneSolver{T,FC,S}(x, p, Aᴴz, r, q, s, z, stats) return solver end @@ -1134,7 +1134,7 @@ may be used in order to create these vectors. mutable struct CrmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S p :: S - Aᵀr :: S + Aᴴr :: S r :: S q :: S Nq :: S @@ -1147,13 +1147,13 @@ function CrmrSolver(n, m, S) T = real(FC) x = S(undef, m) p = S(undef, m) - Aᵀr = S(undef, m) + Aᴴr = S(undef, m) r = S(undef, n) q = S(undef, n) Nq = S(undef, 0) s = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CrmrSolver{T,FC,S}(x, p, Aᵀr, r, q, Nq, s, stats) + solver = CrmrSolver{T,FC,S}(x, p, Aᴴr, r, q, Nq, s, stats) return solver end @@ -1176,7 +1176,7 @@ may be used in order to create these vectors. mutable struct LslqSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S Nv :: S - Aᵀu :: S + Aᴴu :: S w̄ :: S Mu :: S Av :: S @@ -1191,7 +1191,7 @@ function LslqSolver(n, m, S; window :: Int=5) T = real(FC) x = S(undef, m) Nv = S(undef, m) - Aᵀu = S(undef, m) + Aᴴu = S(undef, m) w̄ = S(undef, m) Mu = S(undef, n) Av = S(undef, n) @@ -1199,7 +1199,7 @@ function LslqSolver(n, m, S; window :: Int=5) v = S(undef, 0) err_vec = zeros(T, window) stats = LSLQStats(0, false, false, T[], T[], T[], false, T[], T[], "unknown") - solver = LslqSolver{T,FC,S}(x, Nv, Aᵀu, w̄, Mu, Av, u, v, err_vec, stats) + solver = LslqSolver{T,FC,S}(x, Nv, Aᴴu, w̄, Mu, Av, u, v, err_vec, stats) return solver end @@ -1222,7 +1222,7 @@ may be used in order to create these vectors. mutable struct LsqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S Nv :: S - Aᵀu :: S + Aᴴu :: S w :: S Mu :: S Av :: S @@ -1237,7 +1237,7 @@ function LsqrSolver(n, m, S; window :: Int=5) T = real(FC) x = S(undef, m) Nv = S(undef, m) - Aᵀu = S(undef, m) + Aᴴu = S(undef, m) w = S(undef, m) Mu = S(undef, n) Av = S(undef, n) @@ -1245,7 +1245,7 @@ function LsqrSolver(n, m, S; window :: Int=5) v = S(undef, 0) err_vec = zeros(T, window) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = LsqrSolver{T,FC,S}(x, Nv, Aᵀu, w, Mu, Av, u, v, err_vec, stats) + solver = LsqrSolver{T,FC,S}(x, Nv, Aᴴu, w, Mu, Av, u, v, err_vec, stats) return solver end @@ -1268,7 +1268,7 @@ may be used in order to create these vectors. mutable struct LsmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S Nv :: S - Aᵀu :: S + Aᴴu :: S h :: S hbar :: S Mu :: S @@ -1284,7 +1284,7 @@ function LsmrSolver(n, m, S; window :: Int=5) T = real(FC) x = S(undef, m) Nv = S(undef, m) - Aᵀu = S(undef, m) + Aᴴu = S(undef, m) h = S(undef, m) hbar = S(undef, m) Mu = S(undef, n) @@ -1293,7 +1293,7 @@ function LsmrSolver(n, m, S; window :: Int=5) v = S(undef, 0) err_vec = zeros(T, window) stats = LsmrStats(0, false, false, T[], T[], zero(T), zero(T), zero(T), zero(T), zero(T), "unknown") - solver = LsmrSolver{T,FC,S}(x, Nv, Aᵀu, h, hbar, Mu, Av, u, v, err_vec, stats) + solver = LsmrSolver{T,FC,S}(x, Nv, Aᴴu, h, hbar, Mu, Av, u, v, err_vec, stats) return solver end @@ -1316,7 +1316,7 @@ may be used in order to create these vectors. mutable struct LnlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S Nv :: S - Aᵀu :: S + Aᴴu :: S y :: S w̄ :: S Mu :: S @@ -1332,7 +1332,7 @@ function LnlqSolver(n, m, S) T = real(FC) x = S(undef, m) Nv = S(undef, m) - Aᵀu = S(undef, m) + Aᴴu = S(undef, m) y = S(undef, n) w̄ = S(undef, n) Mu = S(undef, n) @@ -1341,7 +1341,7 @@ function LnlqSolver(n, m, S) v = S(undef, 0) q = S(undef, 0) stats = LNLQStats(0, false, T[], false, T[], T[], "unknown") - solver = LnlqSolver{T,FC,S}(x, Nv, Aᵀu, y, w̄, Mu, Av, u, v, q, stats) + solver = LnlqSolver{T,FC,S}(x, Nv, Aᴴu, y, w̄, Mu, Av, u, v, q, stats) return solver end @@ -1364,7 +1364,7 @@ may be used in order to create these vectors. mutable struct CraigSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S Nv :: S - Aᵀu :: S + Aᴴu :: S y :: S w :: S Mu :: S @@ -1380,7 +1380,7 @@ function CraigSolver(n, m, S) T = real(FC) x = S(undef, m) Nv = S(undef, m) - Aᵀu = S(undef, m) + Aᴴu = S(undef, m) y = S(undef, n) w = S(undef, n) Mu = S(undef, n) @@ -1389,7 +1389,7 @@ function CraigSolver(n, m, S) v = S(undef, 0) w2 = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CraigSolver{T,FC,S}(x, Nv, Aᵀu, y, w, Mu, Av, u, v, w2, stats) + solver = CraigSolver{T,FC,S}(x, Nv, Aᴴu, y, w, Mu, Av, u, v, w2, stats) return solver end @@ -1412,7 +1412,7 @@ may be used in order to create these vectors. mutable struct CraigmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} x :: S Nv :: S - Aᵀu :: S + Aᴴu :: S d :: S y :: S Mu :: S @@ -1430,7 +1430,7 @@ function CraigmrSolver(n, m, S) T = real(FC) x = S(undef, m) Nv = S(undef, m) - Aᵀu = S(undef, m) + Aᴴu = S(undef, m) d = S(undef, m) y = S(undef, n) Mu = S(undef, n) @@ -1441,7 +1441,7 @@ function CraigmrSolver(n, m, S) v = S(undef, 0) q = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CraigmrSolver{T,FC,S}(x, Nv, Aᵀu, d, y, Mu, w, wbar, Av, u, v, q, stats) + solver = CraigmrSolver{T,FC,S}(x, Nv, Aᴴu, d, y, Mu, w, wbar, Av, u, v, q, stats) return solver end diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 6f0c1c382..c61bf2e5e 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -164,14 +164,14 @@ function to_boundary(x :: Vector{T}, d :: Vector{T}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where T <: Number radius > 0 || error("radius must be positive") - # ‖d‖² σ² + 2 xᵀd σ + (‖x‖² - radius²). - xd = dot(x, d) - flip && (xd = -xd) + # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - radius²). + rxd = real(dot(x, d)) + flip && (rxd = -rxd) dNorm2 == zero(T) && (dNorm2 = dot(d, d)) dNorm2 == zero(T) && error("zero direction") xNorm2 == zero(T) && (xNorm2 = dot(x, x)) (xNorm2 ≤ radius * radius) || error(@sprintf("outside of the trust region: ‖x‖²=%7.1e, Δ²=%7.1e", xNorm2, radius * radius)) - roots = roots_quadratic(dNorm2, 2 * xd, xNorm2 - radius * radius) + roots = roots_quadratic(dNorm2, 2 * rxd, xNorm2 - radius * radius) return roots # `σ1` and `σ2` end diff --git a/src/lnlq.jl b/src/lnlq.jl index a1f890de2..db0a7c951 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -9,9 +9,9 @@ # and is equivalent to applying the SYMMLQ method # to the linear system # -# AAᵀy = b with x = Aᵀy and can be reformulated as +# AAᴴy = b with x = Aᴴy and can be reformulated as # -# [ -I Aᵀ ][ x ] = [ 0 ] +# [ -I Aᴴ ][ x ] = [ 0 ] # [ A ][ y ] [ b ]. # # This method is based on the Golub-Kahan bidiagonalization process and is described in @@ -41,14 +41,14 @@ Find the least-norm solution of the consistent linear system using the LNLQ method, where λ ≥ 0 is a regularization parameter. For a system in the form Ax = b, LNLQ method is equivalent to applying -SYMMLQ to AAᵀy = b and recovering x = Aᵀy but is more stable. +SYMMLQ to AAᴴy = b and recovering x = Aᴴy but is more stable. Note that y are the Lagrange multipliers of the least-norm problem minimize ‖x‖ s.t. Ax = b. If `λ > 0`, LNLQ solves the symmetric and quasi-definite system - [ -F Aᵀ ] [ x ] [ 0 ] + [ -F Aᴴ ] [ x ] [ 0 ] [ A λ²E ] [ y ] = [ b ], where E and F are symmetric and positive definite. @@ -59,12 +59,12 @@ The system above represents the optimality conditions of min ‖x‖²_F + λ²‖y‖²_E s.t. Ax + λ²Ey = b. -For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᵀKx`. -LNLQ is then equivalent to applying SYMMLQ to `(AF⁻¹Aᵀ + λ²E)y = b` with `Fx = Aᵀy`. +For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᴴKx`. +LNLQ is then equivalent to applying SYMMLQ to `(AF⁻¹Aᴴ + λ²E)y = b` with `Fx = Aᴴy`. If `λ = 0`, LNLQ solves the symmetric and indefinite system - [ -F Aᵀ ] [ x ] [ 0 ] + [ -F Aᴴ ] [ x ] [ 0 ] [ A 0 ] [ y ] = [ b ]. The system above represents the optimality conditions of @@ -126,13 +126,13 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :u, S, m) allocate_if(!NisI, solver, :v, S, n) allocate_if(λ > 0, solver, :q, S, n) - x, Nv, Aᵀu, y, w̄ = solver.x, solver.Nv, solver.Aᵀu, solver.y, solver.w̄ + x, Nv, Aᴴu, y, w̄ = solver.x, solver.Nv, solver.Aᴴu, solver.y, solver.w̄ Mu, Av, q, stats = solver.Mu, solver.Av, solver.q, solver.stats rNorms, xNorms, yNorms = stats.residuals, stats.error_bnd_x, stats.error_bnd_y reset!(stats) @@ -179,9 +179,9 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; MisI || @kscal!(m, one(FC) / βₖ, Mu) end - # α₁Nv₁ = Aᵀu₁. - mul!(Aᵀu, Aᵀ, u) - Nv .= Aᵀu + # α₁Nv₁ = Aᴴu₁. + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu NisI || mulorldiv!(v, N, Nv, ldiv) # v₁ = N⁻¹ * Nv₁ αₖ = sqrt(@kdotr(n, v, Nv)) # α₁ = ‖v₁‖_N if αₖ ≠ 0 @@ -190,8 +190,8 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; end w̄ .= u # Direction w̄₁ - cₖ = zero(T) # Givens cosines used for the LQ factorization of (Lₖ)ᵀ - sₖ = zero(FC) # Givens sines used for the LQ factorization of (Lₖ)ᵀ + cₖ = zero(T) # Givens cosines used for the LQ factorization of (Lₖ)ᴴ + sₖ = zero(FC) # Givens sines used for the LQ factorization of (Lₖ)ᴴ ζₖ₋₁ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ ηₖ = zero(FC) # Coefficient of M̅ₖ @@ -214,7 +214,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; αhatₖ = αₖ end - # Begin the LQ factorization of (Lₖ)ᵀ = M̅ₖQₖ. + # Begin the LQ factorization of (Lₖ)ᴴ = M̅ₖQₖ. # [ α₁ β₂ 0 • • • 0 ] [ ϵ₁ 0 • • • • 0 ] # [ 0 α₂ • • • ] [ η₂ ϵ₂ • • ] # [ • • • • • • ] [ 0 • • • • ] @@ -225,7 +225,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ϵbarₖ = αhatₖ # ϵbar₁ = αhat₁ - # Hₖ = Bₖ(Lₖ)ᵀ = [ Lₖ(Lₖ)ᵀ ] ⟹ (Hₖ₋₁)ᵀ = [Lₖ₋₁Mₖ₋₁ 0] Qₖ + # Hₖ = Bₖ(Lₖ)ᴴ = [ Lₖ(Lₖ)ᴴ ] ⟹ (Hₖ₋₁)ᴴ = [Lₖ₋₁Mₖ₋₁ 0] Qₖ # [ αₖβₖ₊₁(eₖ)ᵀ ] # # Solve Lₖtₖ = β₁e₁ and M̅ₖz̅ₖ = tₖ @@ -273,7 +273,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Continue the generalized Golub-Kahan bidiagonalization. # AVₖ = MUₖ₊₁Bₖ - # AᵀUₖ₊₁ = NVₖ(Bₖ)ᵀ + αₖ₊₁Nvₖ₊₁(eₖ₊₁)ᵀ = NVₖ₊₁(Lₖ₊₁)ᵀ + # AᴴUₖ₊₁ = NVₖ(Bₖ)ᴴ + αₖ₊₁Nvₖ₊₁(eₖ₊₁)ᴴ = NVₖ₊₁(Lₖ₊₁)ᴴ # # [ α₁ 0 • • • • 0 ] # [ β₂ α₂ • • ] @@ -296,9 +296,9 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; MisI || @kscal!(m, one(FC) / βₖ₊₁, Mu) end - # αₖ₊₁Nvₖ₊₁ = Aᵀuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᵀu, Aᵀ, u) - @kaxpby!(n, one(FC), Aᵀu, -βₖ₊₁, Nv) + # αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -βₖ₊₁, Nv) NisI || mulorldiv!(v, N, Nv, ldiv) # vₖ₊₁ = N⁻¹ * Nvₖ₊₁ αₖ₊₁ = sqrt(@kdotr(n, v, Nv)) # αₖ₊₁ = ‖vₖ₊₁‖_N if αₖ₊₁ ≠ 0 @@ -353,7 +353,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ρbar = ssig * μbar + csig * σₑₛₜ end - # Continue the LQ factorization of (Lₖ₊₁)ᵀ. + # Continue the LQ factorization of (Lₖ₊₁)ᴴ. # [ηₖ ϵbarₖ βₖ₊₁] [1 0 0 ] = [ηₖ ϵₖ 0 ] # [0 0 αₖ₊₁] [0 cₖ₊₁ sₖ₊₁] [0 ηₖ₊₁ ϵbarₖ₊₁] # [0 sₖ₊₁ -cₖ₊₁] diff --git a/src/lslq.jl b/src/lslq.jl index 908de19c5..d43d4a089 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -5,7 +5,7 @@ # # equivalently, of the normal equations # -# AᵀAx = Aᵀb. +# AᴴAx = Aᴴb. # # LSLQ is formally equivalent to applying SYMMLQ to the normal equations # but should be more stable. @@ -41,7 +41,7 @@ Solve the regularized linear least-squares problem using the LSLQ method, where λ ≥ 0 is a regularization parameter. LSLQ is formally equivalent to applying SYMMLQ to the normal equations - (AᵀA + λ²I) x = Aᵀb + (AᴴA + λ²I) x = Aᴴb but is more stable. @@ -62,7 +62,7 @@ but is more stable. If `λ > 0`, we solve the symmetric and quasi-definite system [ E A ] [ r ] [ b ] - [ Aᵀ -λ²F ] [ x ] = [ 0 ], + [ Aᴴ -λ²F ] [ x ] = [ 0 ], where E and F are symmetric and positive definite. Preconditioners M = E⁻¹ ≻ 0 and N = F⁻¹ ≻ 0 may be provided in the form of linear operators. @@ -72,19 +72,19 @@ The system above represents the optimality conditions of minimize ‖b - Ax‖²_E⁻¹ + λ²‖x‖²_F. -For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᵀKx`. -LSLQ is then equivalent to applying SYMMLQ to `(AᵀE⁻¹A + λ²F)x = AᵀE⁻¹b` with `r = E⁻¹(b - Ax)`. +For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᴴKx`. +LSLQ is then equivalent to applying SYMMLQ to `(AᴴE⁻¹A + λ²F)x = AᴴE⁻¹b` with `r = E⁻¹(b - Ax)`. If `λ = 0`, we solve the symmetric and indefinite system [ E A ] [ r ] [ b ] - [ Aᵀ 0 ] [ x ] = [ 0 ]. + [ Aᴴ 0 ] [ x ] = [ 0 ]. The system above represents the optimality conditions of minimize ‖b - Ax‖²_E⁻¹. -In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᵀr` should be measured. +In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. * `λ` is a regularization parameter (see the problem statement above) @@ -116,8 +116,8 @@ In this case, `N` can still be specified and indicates the weighted norm in whic The iterations stop as soon as one of the following conditions holds true: * the optimality residual is sufficiently small (`stats.status = "found approximate minimum least-squares solution"`) in the sense that either - * ‖Aᵀr‖ / (‖A‖ ‖r‖) ≤ atol, or - * 1 + ‖Aᵀr‖ / (‖A‖ ‖r‖) ≤ 1 + * ‖Aᴴr‖ / (‖A‖ ‖r‖) ≤ atol, or + * 1 + ‖Aᴴr‖ / (‖A‖ ‖r‖) ≤ 1 * an approximate zero-residual solution has been found (`stats.status = "found approximate zero-residual solution"`) in the sense that either * ‖r‖ / ‖b‖ ≤ btol + atol ‖A‖ * ‖xᴸ‖ / ‖b‖, or * 1 + ‖r‖ / ‖b‖ ≤ 1 @@ -177,12 +177,12 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :u, S, m) allocate_if(!NisI, solver, :v, S, n) - x, Nv, Aᵀu, w̄ = solver.x, solver.Nv, solver.Aᵀu, solver.w̄ + x, Nv, Aᴴu, w̄ = solver.x, solver.Nv, solver.Aᴴu, solver.w̄ Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats rNorms, ArNorms, err_lbnds = stats.residuals, stats.Aresiduals, stats.err_lbnds err_ubnds_lq, err_ubnds_cg = stats.err_ubnds_lq, stats.err_ubnds_cg @@ -213,12 +213,12 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kscal!(m, one(FC)/β₁, u) MisI || @kscal!(m, one(FC)/β₁, Mu) - mul!(Aᵀu, Aᵀ, u) - Nv .= Aᵀu + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) # = α₁ - # Aᵀb = 0 so x = 0 is a minimum least-squares solution + # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false @@ -274,7 +274,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᵀr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") + (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) status = "unknown" @@ -298,9 +298,9 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kscal!(m, one(FC)/β, u) MisI || @kscal!(m, one(FC)/β, Mu) - # 2. αₖ₊₁Nvₖ₊₁ = Aᵀuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᵀu, Aᵀ, u) - @kaxpby!(n, one(FC), Aᵀu, -β, Nv) + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) if α ≠ 0 diff --git a/src/lsmr.jl b/src/lsmr.jl index f4d8349d1..78db5db59 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -5,7 +5,7 @@ # # equivalently, of the normal equations # -# AᵀAx = Aᵀb. +# AᴴAx = Aᴴb. # # LSMR is formally equivalent to applying MINRES to the normal equations # but should be more stable. It is also formally equivalent to CRLS though @@ -46,21 +46,21 @@ Solve the regularized linear least-squares problem using the LSMR method, where λ ≥ 0 is a regularization parameter. LSMR is formally equivalent to applying MINRES to the normal equations - (AᵀA + λ²I) x = Aᵀb + (AᴴA + λ²I) x = Aᴴb (and therefore to CRLS) but is more stable. -LSMR produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᵀr‖₂. +LSMR produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr‖₂. It is formally equivalent to CRLS, though can be substantially more accurate. LSMR can be also used to find a null vector of a singular matrix A -by solving the problem `min ‖Aᵀx - b‖` with any nonzero vector `b`. -At a minimizer, the residual vector `r = b - Aᵀx` will satisfy `Ar = 0`. +by solving the problem `min ‖Aᴴx - b‖` with any nonzero vector `b`. +At a minimizer, the residual vector `r = b - Aᴴx` will satisfy `Ar = 0`. If `λ > 0`, we solve the symmetric and quasi-definite system [ E A ] [ r ] [ b ] - [ Aᵀ -λ²F ] [ x ] = [ 0 ], + [ Aᴴ -λ²F ] [ x ] = [ 0 ], where E and F are symmetric and positive definite. Preconditioners M = E⁻¹ ≻ 0 and N = F⁻¹ ≻ 0 may be provided in the form of linear operators. @@ -70,19 +70,19 @@ The system above represents the optimality conditions of minimize ‖b - Ax‖²_E⁻¹ + λ²‖x‖²_F. -For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᵀKx`. -LSMR is then equivalent to applying MINRES to `(AᵀE⁻¹A + λ²F)x = AᵀE⁻¹b` with `r = E⁻¹(b - Ax)`. +For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᴴKx`. +LSMR is then equivalent to applying MINRES to `(AᴴE⁻¹A + λ²F)x = AᴴE⁻¹b` with `r = E⁻¹(b - Ax)`. If `λ = 0`, we solve the symmetric and indefinite system [ E A ] [ r ] [ b ] - [ Aᵀ 0 ] [ x ] = [ 0 ]. + [ Aᴴ 0 ] [ x ] = [ 0 ]. The system above represents the optimality conditions of minimize ‖b - Ax‖²_E⁻¹. -In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᵀr` should be measured. +In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, @@ -134,12 +134,12 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :u, S, m) allocate_if(!NisI, solver, :v, S, n) - x, Nv, Aᵀu, h, hbar = solver.x, solver.Nv, solver.Aᵀu, solver.h, solver.hbar + x, Nv, Aᴴu, h, hbar = solver.x, solver.Nv, solver.Aᴴu, solver.h, solver.hbar Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats rNorms, ArNorms = stats.residuals, stats.Aresiduals reset!(stats) @@ -166,8 +166,8 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kscal!(m, one(FC)/β₁, u) MisI || @kscal!(m, one(FC)/β₁, Mu) - mul!(Aᵀu, Aᵀ, u) - Nv .= Aᵀu + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) @@ -210,10 +210,10 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᵀr‖", "β", "α", "cos", "sin", "‖A‖²") + (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm²) - # Aᵀb = 0 so x = 0 is a minimum least-squares solution + # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false @@ -248,9 +248,9 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kscal!(m, one(FC)/β, u) MisI || @kscal!(m, one(FC)/β, Mu) - # 2. αₖ₊₁Nvₖ₊₁ = Aᵀuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᵀu, Aᵀ, u) - @kaxpby!(n, one(FC), Aᵀu, -β, Nv) + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) if α ≠ 0 diff --git a/src/lsqr.jl b/src/lsqr.jl index dd3779dce..083b2f9f9 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -5,7 +5,7 @@ # # equivalently, of the normal equations # -# AᵀAx = Aᵀb. +# AᴴAx = Aᴴb. # # LSQR is formally equivalent to applying the conjugate gradient method # to the normal equations but should be more stable. It is also formally @@ -45,17 +45,17 @@ Solve the regularized linear least-squares problem using the LSQR method, where λ ≥ 0 is a regularization parameter. LSQR is formally equivalent to applying CG to the normal equations - (AᵀA + λ²I) x = Aᵀb + (AᴴA + λ²I) x = Aᴴb (and therefore to CGLS) but is more stable. -LSQR produces monotonic residuals ‖r‖₂ but not optimality residuals ‖Aᵀr‖₂. +LSQR produces monotonic residuals ‖r‖₂ but not optimality residuals ‖Aᴴr‖₂. It is formally equivalent to CGLS, though can be slightly more accurate. If `λ > 0`, LSQR solves the symmetric and quasi-definite system [ E A ] [ r ] [ b ] - [ Aᵀ -λ²F ] [ x ] = [ 0 ], + [ Aᴴ -λ²F ] [ x ] = [ 0 ], where E and F are symmetric and positive definite. Preconditioners M = E⁻¹ ≻ 0 and N = F⁻¹ ≻ 0 may be provided in the form of linear operators. @@ -65,19 +65,19 @@ The system above represents the optimality conditions of minimize ‖b - Ax‖²_E⁻¹ + λ²‖x‖²_F. -For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᵀKx`. -LSQR is then equivalent to applying CG to `(AᵀE⁻¹A + λ²F)x = AᵀE⁻¹b` with `r = E⁻¹(b - Ax)`. +For a symmetric and positive definite matrix `K`, the K-norm of a vector `x` is `‖x‖²_K = xᴴKx`. +LSQR is then equivalent to applying CG to `(AᴴE⁻¹A + λ²F)x = AᴴE⁻¹b` with `r = E⁻¹(b - Ax)`. If `λ = 0`, we solve the symmetric and indefinite system [ E A ] [ r ] [ b ] - [ Aᵀ 0 ] [ x ] = [ 0 ]. + [ Aᴴ 0 ] [ x ] = [ 0 ]. The system above represents the optimality conditions of minimize ‖b - Ax‖²_E⁻¹. -In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᵀr` should be measured. +In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, @@ -129,12 +129,12 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ktypeof(b) == S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :u, S, m) allocate_if(!NisI, solver, :v, S, n) - x, Nv, Aᵀu, w = solver.x, solver.Nv, solver.Aᵀu, solver.w + x, Nv, Aᴴu, w = solver.x, solver.Nv, solver.Aᴴu, solver.w Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats rNorms, ArNorms = stats.residuals, stats.Aresiduals reset!(stats) @@ -162,8 +162,8 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kscal!(m, one(FC)/β₁, u) MisI || @kscal!(m, one(FC)/β₁, Mu) - mul!(Aᵀu, Aᵀ, u) - Nv .= Aᵀu + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu NisI || mulorldiv!(v, N, Nv, ldiv) Anorm² = @kdotr(n, v, Nv) Anorm = sqrt(Anorm²) @@ -184,7 +184,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᵀr‖", "compat", "backwrd", "‖A‖", "κ(A)") + (verbose > 0) && @printf("%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)") kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond) rNorm = β₁ @@ -194,7 +194,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(rNorms, r2Norm) ArNorm = ArNorm0 = α * β history && push!(ArNorms, ArNorm) - # Aᵀb = 0 so x = 0 is a minimum least-squares solution + # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false @@ -237,9 +237,9 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; Anorm² = Anorm² + α * α + β * β # = ‖B_{k-1}‖² λ > 0 && (Anorm² += λ²) - # 2. αₖ₊₁Nvₖ₊₁ = Aᵀuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᵀu, Aᵀ, u) - @kaxpby!(n, one(FC), Aᵀu, -β, Nv) + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) NisI || mulorldiv!(v, N, Nv, ldiv) α = sqrt(@kdotr(n, v, Nv)) if α ≠ 0 diff --git a/src/minres.jl b/src/minres.jl index cbaefee9f..d3b8732ee 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -50,7 +50,7 @@ MINRES is formally equivalent to applying CR to Ax=b when A is positive definite, but is typically more stable and also applies to the case where A is indefinite. -MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᵀr‖₂. +MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr‖₂. A preconditioner M may be provided in the form of a linear operator and is assumed to be symmetric and positive definite. @@ -189,7 +189,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = 2*n) - (verbose > 0) && @printf("%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᵀr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") + (verbose > 0) && @printf("%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) tol = atol + rtol * β₁ @@ -241,7 +241,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; ϵ = sn * β δbar = -cs * β root = sqrt(γbar * γbar + δbar * δbar) - ArNorm = ϕbar * root # = ‖Aᵀrₖ₋₁‖ + ArNorm = ϕbar * root # = ‖Aᴴrₖ₋₁‖ history && push!(ArNorms, ArNorm) # Compute the next plane rotation. @@ -295,7 +295,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2) if iter == 1 && β / β₁ ≤ 10 * ϵM - # Aᵀb = 0 so x = 0 is a minimum least-squares solution + # Aᴴb = 0 so x = 0 is a minimum least-squares solution stats.niter = 0 stats.solved, stats.inconsistent = true, true stats.status = "x is a minimum least-squares solution" diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index bbfbf856b..509a7ef4e 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -246,7 +246,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F # [sₖ -cₖ] [βₖ₊₁ ] [0 ] (cₖ, sₖ, λₖ) = sym_givens(λbarₖ, βₖ₊₁) - # Compute [ zₖ ] = (Qₖ)ᵀβ₁e₁ + # Compute [ zₖ ] = (Qₖ)ᴴβ₁e₁ # [ζbarₖ₊₁] # # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] @@ -312,7 +312,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F τₖ = (ξₖ - ψbarₖ₋₁ * τₖ₋₁) / μbarₖ end - # Compute directions wₖ₋₂, ẘₖ₋₁ and w̄ₖ, last columns of Wₖ = Vₖ(Pₖ)ᵀ + # Compute directions wₖ₋₂, ẘₖ₋₁ and w̄ₖ, last columns of Wₖ = Vₖ(Pₖ)ᴴ if iter == 1 # w̅₁ = v₁ @. wₖ = vₖ diff --git a/src/qmr.jl b/src/qmr.jl index eb4a4eb46..d4b684601 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -32,7 +32,7 @@ export qmr, qmr! Solve the square linear system Ax = b using the QMR method. QMR is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. -The relation `bᵀc ≠ 0` must be satisfied and by default `c = b`. +The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. When `A` is symmetric and `b = c`, QMR is equivalent to MINRES. QMR can be warm-started from an initial guess `x0` with the method @@ -96,7 +96,7 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst ktypeof(c) == S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ, solver.p @@ -133,18 +133,18 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) # Initialize the Lanczos biorthogonalization process. - cᵗb = @kdot(n, c, r₀) # ⟨c,r₀⟩ - if cᵗb == 0 + cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ + if cᴴb == 0 stats.niter = 0 stats.solved = false stats.inconsistent = false - stats.status = "Breakdown bᵀc = 0" + stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver end - βₖ = √(abs(cᵗb)) # β₁γ₁ = cᵀ(b - Ax₀) - γₖ = cᵗb / βₖ # β₁γ₁ = cᵀ(b - Ax₀) + βₖ = √(abs(cᴴb)) # β₁γ₁ = cᴴ(b - Ax₀) + γₖ = cᴴb / βₖ # β₁γ₁ = cᴴ(b - Ax₀) vₖ₋₁ .= zero(FC) # v₀ = 0 uₖ₋₁ .= zero(FC) # u₀ = 0 vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ @@ -153,7 +153,7 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst sₖ₋₂ = sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Rₖ)⁻¹ wₖ₋₁ .= zero(FC) # Column k-1 of Wₖ = Vₖ(Rₖ)⁻¹ - ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᵀβ₁e₁ + ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᴴβ₁e₁ τₖ = @kdotr(n, vₖ, vₖ) # τₖ is used for the residual norm estimate # Stopping criterion. @@ -169,10 +169,10 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst # Continue the Lanczos biorthogonalization process. # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᵀUₖ = Uₖ(Tₖ)ᵀ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ - mul!(p, Aᵀ, uₖ) # Forms uₖ₊₁ : p ← Aᵀuₖ + mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ @@ -182,9 +182,9 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - pᵗq = @kdot(n, p, q) # pᵗq = ⟨p,q⟩ - βₖ₊₁ = √(abs(pᵗq)) # βₖ₊₁ = √(|pᵗq|) - γₖ₊₁ = pᵗq / βₖ₊₁ # γₖ₊₁ = pᵗq / βₖ₊₁ + pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ + βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) + γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. # [ Oᵀ ] @@ -271,7 +271,7 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - if pᵗq ≠ zero(FC) + if pᴴq ≠ zero(FC) @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p end @@ -303,7 +303,7 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst resid_decrease_lim = rNorm ≤ ε solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax - breakdown = !solved && (pᵗq == 0) + breakdown = !solved && (pᴴq == 0) kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) end (verbose > 0) && @printf("\n") diff --git a/src/tricg.jl b/src/tricg.jl index 5acff2d52..7c140a821 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -25,7 +25,7 @@ export tricg, tricg! TriCG solves the symmetric linear system [ τE A ] [ x ] = [ b ] - [ Aᵀ νF ] [ y ] [ c ], + [ Aᴴ νF ] [ y ] [ c ], where τ and ν are real numbers, E = M⁻¹ ≻ 0 and F = N⁻¹ ≻ 0. `b` and `c` must both be nonzero. @@ -133,7 +133,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: warm_start && (ν ≠ 0) && !NisI && error("Warm-start with preconditioners is not supported.") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :vₖ, S, m) @@ -164,12 +164,12 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: N⁻¹uₖ₋₁ .= zero(FC) # u₀ = 0 # [ τI A ] [ xₖ ] = [ b - τΔx - AΔy ] = [ b₀ ] - # [ Aᵀ νI ] [ yₖ ] [ c - AᵀΔx - νΔy ] [ c₀ ] + # [ Aᴴ νI ] [ yₖ ] [ c - AᴴΔx - νΔy ] [ c₀ ] if warm_start mul!(b₀, A, Δy) (τ ≠ 0) && @kaxpy!(m, τ, Δx, b₀) @kaxpby!(m, one(FC), b, -one(FC), b₀) - mul!(c₀, Aᵀ, Δx) + mul!(c₀, Aᴴ, Δx) (ν ≠ 0) && @kaxpy!(n, ν, Δy, c₀) @kaxpby!(n, one(FC), c, -one(FC), c₀) end @@ -196,7 +196,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: error("c must be nonzero") end - # Initialize directions Gₖ such that Lₖ(Gₖ)ᵀ = (Wₖ)ᵀ + # Initialize directions Gₖ such that L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ gx₂ₖ₋₁ .= zero(FC) gy₂ₖ₋₁ .= zero(FC) gx₂ₖ .= zero(FC) @@ -231,10 +231,10 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Continue the orthogonal tridiagonalization process. # AUₖ = EVₖTₖ + βₖ₊₁Evₖ₊₁(eₖ)ᵀ = EVₖ₊₁Tₖ₊₁.ₖ - # AᵀVₖ = FUₖ(Tₖ)ᵀ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴVₖ = FUₖ(Tₖ)ᴴ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , uₖ) # Forms Evₖ₊₁ : q ← Auₖ - mul!(p, Aᵀ, vₖ) # Forms Fuₖ₊₁ : p ← Aᵀvₖ + mul!(p, Aᴴ, vₖ) # Forms Fuₖ₊₁ : p ← Aᴴvₖ if iter ≥ 2 @kaxpy!(m, -γₖ, M⁻¹vₖ₋₁, q) # q ← q - γₖ * M⁻¹vₖ₋₁ @@ -254,14 +254,14 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # [0 u₁ ••• 0 uₖ] # # rₖ = [ b ] - [ τE A ] [ xₖ ] = [ b ] - [ τE A ] Wₖzₖ - # [ c ] [ Aᵀ νF ] [ yₖ ] [ c ] [ Aᵀ νF ] + # [ c ] [ Aᴴ νF ] [ yₖ ] [ c ] [ Aᴴ νF ] # # block-Lanczos formulation : [ τE A ] Wₖ = [ E 0 ] Wₖ₊₁Sₖ₊₁.ₖ - # [ Aᵀ νF ] [ 0 F ] + # [ Aᴴ νF ] [ 0 F ] # - # TriCG subproblem : (Wₖ)ᵀ * rₖ = 0 ↔ Sₖ.ₖzₖ = β₁e₁ + γ₁e₂ + # TriCG subproblem : (Wₖ)ᴴ * rₖ = 0 ↔ Sₖ.ₖzₖ = β₁e₁ + γ₁e₂ # - # Update the LDLᵀ factorization of Sₖ.ₖ. + # Update the LDLᴴ factorization of Sₖ.ₖ. # # [ τ α₁ γ₂ 0 • • • • 0 ] # [ ᾱ₁ ν β₂ • • ] @@ -306,7 +306,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: π₂ₖ = -(δₖ * d₂ₖ₋₁ * π₂ₖ₋₁ + λₖ * d₂ₖ₋₂ * π₂ₖ₋₂ + ηₖ * d₂ₖ₋₃ * π₂ₖ₋₃) / d₂ₖ end - # Solve Gₖ = Wₖ(Lₖ)⁻ᵀ ⟷ L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ. + # Solve Gₖ = Wₖ(Lₖ)⁻ᴴ ⟷ L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ. if iter == 1 # [ 1 0 ] [ gx₁ gy₁ ] = [ v₁ 0 ] # [ δ̄₁ 1 ] [ gx₂ gy₂ ] [ 0 u₁ ] @@ -342,7 +342,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Compute vₖ₊₁ and uₖ₊₁ MisI || mulorldiv!(vₖ₊₁, M, q, ldiv) # βₖ₊₁vₖ₊₁ = MAuₖ - γₖvₖ₋₁ - αₖvₖ - NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᵀvₖ - βₖuₖ₋₁ - ᾱₖuₖ + NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᴴvₖ - βₖuₖ₋₁ - ᾱₖuₖ βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, q)) # βₖ₊₁ = ‖vₖ₊₁‖_E γₖ₊₁ = sqrt(@kdotr(n, uₖ₊₁, p)) # γₖ₊₁ = ‖uₖ₊₁‖_F diff --git a/src/trilqr.jl b/src/trilqr.jl index edcb4c9b9..6b0948984 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -1,5 +1,5 @@ # An implementation of TRILQR for the solution of square or -# rectangular consistent linear adjoint systems Ax = b and Aᵀy = c. +# rectangular consistent linear adjoint systems Ax = b and Aᴴy = c. # # This method is described in # @@ -24,10 +24,10 @@ export trilqr, trilqr! Combine USYMLQ and USYMQR to solve adjoint systems. [0 A] [y] = [b] - [Aᵀ 0] [x] [c] + [Aᴴ 0] [x] [c] USYMLQ is used for solving primal system `Ax = b`. -USYMQR is used for solving dual system `Aᵀy = c`. +USYMQR is used for solving dual system `Aᴴy = c`. An option gives the possibility of transferring from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm. @@ -93,7 +93,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ktypeof(c) == S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. uₖ₋₁, uₖ, p, d̅, x, stats = solver.uₖ₋₁, solver.uₖ, solver.p, solver.d̅, solver.x, solver.stats @@ -107,7 +107,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : if warm_start mul!(r₀, A, Δx) @kaxpby!(n, one(FC), b, -one(FC), r₀) - mul!(s₀, Aᵀ, Δy) + mul!(s₀, Aᴴ, Δy) @kaxpby!(n, one(FC), c, -one(FC), s₀) end @@ -115,7 +115,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : x .= zero(FC) # x₀ bNorm = @knrm2(m, r₀) # rNorm = ‖r₀‖ - # Initial solution y₀ and residual s₀ = c - Aᵀy₀. + # Initial solution y₀ and residual s₀ = c - Aᴴy₀. t .= zero(FC) # t₀ cNorm = @knrm2(n, s₀) # sNorm = ‖s₀‖ @@ -136,17 +136,17 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : vₖ₋₁ .= zero(FC) # v₀ = 0 uₖ₋₁ .= zero(FC) # u₀ = 0 vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= s₀ ./ γₖ # u₁ = (c - Aᵀy₀) / γ₁ + uₖ .= s₀ ./ γₖ # u₁ = (c - Aᴴy₀) / γ₁ cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᵀ + d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᴴ ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations ψbarₖ₋₁ = ψₖ₋₁ = zero(FC) # ψₖ₋₁ and ψbarₖ are the last components of h̅ₖ = Qₖγ₁e₁ ϵₖ₋₃ = λₖ₋₂ = zero(FC) # Components of Lₖ₋₁ - wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Vₖ(Lₖ)⁻ᵀ - wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Lₖ)⁻ᵀ + wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Vₖ(Lₖ)⁻ᴴ + wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Lₖ)⁻ᴴ # Stopping criterion. inconsistent = false @@ -166,10 +166,10 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Continue the SSY tridiagonalization process. # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᵀVₖ = Uₖ(Tₖ)ᵀ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ - mul!(p, Aᵀ, vₖ) # Forms uₖ₊₁ : p ← Aᵀvₖ + mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ @@ -236,7 +236,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ end - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᵀ. + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᴴ. # [d̅ₖ₋₁ uₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * uₖ # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ if iter ≥ 2 @@ -295,7 +295,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ψbarₖ = sₖ * ψbarₖ₋₁ end - # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Vₖ₋₁)(Lₖ₋₁)⁻ᵀ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Vₖ₋₁)ᵀ. + # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Vₖ₋₁)(Lₖ₋₁)⁻ᴴ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Vₖ₋₁)ᵀ. # w₁ = v₁ / δ̄₁ if iter == 2 wₖ₋₁ = wₖ₋₂ diff --git a/src/trimr.jl b/src/trimr.jl index bc53633c2..7dd826edf 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -25,7 +25,7 @@ export trimr, trimr! TriMR solves the symmetric linear system [ τE A ] [ x ] = [ b ] - [ Aᵀ νF ] [ y ] [ c ], + [ Aᴴ νF ] [ y ] [ c ], where τ and ν are real numbers, E = M⁻¹ ≻ 0, F = N⁻¹ ≻ 0. `b` and `c` must both be nonzero. @@ -137,7 +137,7 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: warm_start && (ν ≠ 0) && !NisI && error("Warm-start with preconditioners is not supported.") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. allocate_if(!MisI, solver, :vₖ, S, m) @@ -169,12 +169,12 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: N⁻¹uₖ₋₁ .= zero(FC) # u₀ = 0 # [ τI A ] [ xₖ ] = [ b - τΔx - AΔy ] = [ b₀ ] - # [ Aᵀ νI ] [ yₖ ] [ c - AᵀΔx - νΔy ] [ c₀ ] + # [ Aᴴ νI ] [ yₖ ] [ c - AᴴΔx - νΔy ] [ c₀ ] if warm_start mul!(b₀, A, Δy) (τ ≠ 0) && @kaxpy!(m, τ, Δx, b₀) @kaxpby!(m, one(FC), b, -one(FC), b₀) - mul!(c₀, Aᵀ, Δx) + mul!(c₀, Aᴴ, Δx) (ν ≠ 0) && @kaxpy!(n, ν, Δy, c₀) @kaxpby!(n, one(FC), c, -one(FC), c₀) end @@ -244,10 +244,10 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Continue the orthogonal tridiagonalization process. # AUₖ = EVₖTₖ + βₖ₊₁Evₖ₊₁(eₖ)ᵀ = EVₖ₊₁Tₖ₊₁.ₖ - # AᵀVₖ = FUₖ(Tₖ)ᵀ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴVₖ = FUₖ(Tₖ)ᴴ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , uₖ) # Forms Evₖ₊₁ : q ← Auₖ - mul!(p, Aᵀ, vₖ) # Forms Fuₖ₊₁ : p ← Aᵀvₖ + mul!(p, Aᴴ, vₖ) # Forms Fuₖ₊₁ : p ← Aᴴvₖ if iter ≥ 2 @kaxpy!(m, -γₖ, M⁻¹vₖ₋₁, q) # q ← q - γₖ * M⁻¹vₖ₋₁ @@ -261,7 +261,7 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Compute vₖ₊₁ and uₖ₊₁ MisI || mulorldiv!(vₖ₊₁, M, q, ldiv) # βₖ₊₁vₖ₊₁ = MAuₖ - γₖvₖ₋₁ - αₖvₖ - NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᵀvₖ - βₖuₖ₋₁ - ᾱₖuₖ + NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᴴvₖ - βₖuₖ₋₁ - ᾱₖuₖ βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, q)) # βₖ₊₁ = ‖vₖ₊₁‖_E γₖ₊₁ = sqrt(@kdotr(n, uₖ₊₁, p)) # γₖ₊₁ = ‖uₖ₊₁‖_F @@ -282,10 +282,10 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # [0 u₁ ••• 0 uₖ] # # rₖ = [ b ] - [ τE A ] [ xₖ ] = [ b ] - [ τE A ] Wₖzₖ - # [ c ] [ Aᵀ νF ] [ yₖ ] [ c ] [ Aᵀ νF ] + # [ c ] [ Aᴴ νF ] [ yₖ ] [ c ] [ Aᴴ νF ] # # block-Lanczos formulation : [ τE A ] Wₖ = [ E 0 ] Wₖ₊₁Sₖ₊₁.ₖ - # [ Aᵀ νF ] [ 0 F ] + # [ Aᴴ νF ] [ 0 F ] # # TriMR subproblem : min ‖ rₖ ‖ ↔ min ‖ Sₖ₊₁.ₖzₖ - β₁e₁ - γ₁e₂ ‖ # @@ -419,7 +419,7 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: @kswap(gy₂ₖ₋₂, gy₂ₖ) end - # Update p̅ₖ = (Qₖ)ᵀ * (β₁e₁ + γ₁e₂) + # Update p̅ₖ = (Qₖ)ᴴ * (β₁e₁ + γ₁e₂) πbis₂ₖ = c₁ₖ * πbar₂ₖ πbis₂ₖ₊₂ = conj(s₁ₖ) * πbar₂ₖ # diff --git a/src/usymlq.jl b/src/usymlq.jl index 71670c80f..29cd704c7 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -31,7 +31,7 @@ export usymlq, usymlq! Solve the linear system Ax = b using the USYMLQ method. USYMLQ is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. -The vector `c` is only used to initialize the process and a default value can be `b` or `Aᵀb` depending on the shape of `A`. +The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. The error norm ‖x - x*‖ monotonously decreases in USYMLQ. It's considered as a generalization of SYMMLQ. @@ -103,7 +103,7 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ktypeof(c) == S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. uₖ₋₁, uₖ, p, Δx, x = solver.uₖ₋₁, solver.uₖ, solver.p, solver.Δx, solver.x @@ -146,7 +146,7 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : uₖ .= c ./ γₖ # u₁ = c / γ₁ cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᵀ + d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᴴ ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and Lₖ modified over the course of two iterations @@ -164,10 +164,10 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Continue the SSY tridiagonalization process. # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᵀVₖ = Uₖ(Tₖ)ᵀ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ - mul!(p, Aᵀ, vₖ) # Forms uₖ₊₁ : p ← Aᵀvₖ + mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ @@ -233,7 +233,7 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ end - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᵀ. + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᴴ. # [d̅ₖ₋₁ uₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * uₖ # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ if iter ≥ 2 diff --git a/src/usymqr.jl b/src/usymqr.jl index 863390c3f..45c95c88d 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -31,7 +31,7 @@ export usymqr, usymqr! Solve the linear system Ax = b using the USYMQR method. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. -The vector `c` is only used to initialize the process and a default value can be `b` or `Aᵀb` depending on the shape of `A`. +The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. The residual norm ‖b - Ax‖ monotonously decreases in USYMQR. It's considered as a generalization of MINRES. @@ -100,13 +100,13 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ktypeof(c) == S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A - Aᵀ = A' + Aᴴ = A' # Set up workspace. vₖ₋₁, vₖ, q, Δx, x, p = solver.vₖ₋₁, solver.vₖ, solver.q, solver.Δx, solver.x, solver.p wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, stats = solver.wₖ₋₂, solver.wₖ₋₁, solver.uₖ₋₁, solver.uₖ, solver.stats warm_start = solver.warm_start - rNorms, AᵀrNorms = stats.residuals, stats.Aresiduals + rNorms, AᴴrNorms = stats.residuals, stats.Aresiduals reset!(stats) r₀ = warm_start ? q : b @@ -133,7 +133,7 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ε = atol + rtol * rNorm κ = zero(T) - (verbose > 0) && @printf("%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᵀrₖ₋₁‖") + (verbose > 0) && @printf("%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") kdisplay(iter, verbose) && @printf("%5d %7.1e %7s\n", iter, rNorm, "✗ ✗ ✗ ✗") βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ @@ -146,7 +146,7 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : sₖ₋₂ = sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Rₖ)⁻¹ wₖ₋₁ .= zero(FC) # Column k-1 of Wₖ = Uₖ(Rₖ)⁻¹ - ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᵀβ₁e₁ + ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᴴβ₁e₁ # Stopping criterion. solved = rNorm ≤ ε @@ -161,10 +161,10 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Continue the SSY tridiagonalization process. # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᵀVₖ = Uₖ(Tₖ)ᵀ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᵀ + # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ - mul!(p, Aᵀ, vₖ) # Forms uₖ₊₁ : p ← Aᵀvₖ + mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ @@ -254,9 +254,9 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : rNorm = abs(ζbarₖ₊₁) history && push!(rNorms, rNorm) - # Compute ‖Aᵀrₖ₋₁‖ = |ζbarₖ| * √(|δbarₖ|² + |λbarₖ|²). - AᵀrNorm = abs(ζbarₖ) * √(abs2(δbarₖ) + abs2(cₖ₋₁ * γₖ₊₁)) - history && push!(AᵀrNorms, AᵀrNorm) + # Compute ‖Aᴴrₖ₋₁‖ = |ζbarₖ| * √(|δbarₖ|² + |λbarₖ|²). + AᴴrNorm = abs(ζbarₖ) * √(abs2(δbarₖ) + abs2(cₖ₋₁ * γₖ₊₁)) + history && push!(AᴴrNorms, AᴴrNorm) # Compute uₖ₊₁ and uₖ₊₁. @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ @@ -286,12 +286,12 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : βₖ = βₖ₊₁ # Update stopping criterion. - iter == 1 && (κ = atol + rtol * AᵀrNorm) + iter == 1 && (κ = atol + rtol * AᴴrNorm) user_requested_exit = callback(solver) :: Bool solved = rNorm ≤ ε - inconsistent = !solved && AᵀrNorm ≤ κ + inconsistent = !solved && AᴴrNorm ≤ κ tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e\n", iter, rNorm, AᵀrNorm) + kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) end (verbose > 0) && @printf("\n") tired && (status = "maximum number of iterations exceeded") From fc8677ba1fa0a5254f3072f3c19482cecdac17f5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 9 Sep 2022 13:43:59 -0400 Subject: [PATCH 018/182] Test Krylov macros --- src/krylov_utils.jl | 151 ++++++++++++----------- test/test_aux.jl | 288 ++++++++++++++++++++++++++------------------ 2 files changed, 246 insertions(+), 193 deletions(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index c61bf2e5e..46c9d6cd6 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -175,6 +175,49 @@ function to_boundary(x :: Vector{T}, d :: Vector{T}, return roots # `σ1` and `σ2` end +""" + s = vec2str(x; ndisp) + +Display an array in the form + + [ -3.0e-01 -5.1e-01 1.9e-01 ... -2.3e-01 -4.4e-01 2.4e-01 ] + +with (ndisp - 1)/2 elements on each side. +""" +function vec2str(x :: AbstractVector{T}; ndisp :: Int=7) where T <: Union{AbstractFloat, Missing} + n = length(x) + if n ≤ ndisp + ndisp = n + nside = n + else + nside = max(1, div(ndisp - 1, 2)) + end + s = "[" + i = 1 + while i ≤ nside + if x[i] !== missing + s *= @sprintf("%8.1e ", x[i]) + else + s *= " ✗✗✗✗ " + end + i += 1 + end + if i ≤ div(n, 2) + s *= "... " + end + i = max(i, n - nside + 1) + while i ≤ n + if x[i] !== missing + s *= @sprintf("%8.1e ", x[i]) + else + s *= " ✗✗✗✗ " + end + i += 1 + end + s *= "]" + return s +end + """ S = ktypeof(v) @@ -209,76 +252,75 @@ end Create an AbstractVector of storage type `S` of length `n` only composed of zero. """ -@inline kzeros(S, n) = fill!(S(undef, n), zero(eltype(S))) +kzeros(S, n) = fill!(S(undef, n), zero(eltype(S))) """ v = kones(S, n) Create an AbstractVector of storage type `S` of length `n` only composed of one. """ -@inline kones(S, n) = fill!(S(undef, n), one(eltype(S))) +kones(S, n) = fill!(S(undef, n), one(eltype(S))) -@inline allocate_if(bool, solver, v, S, n) = bool && isempty(solver.:($v)) && (solver.:($v) = S(undef, n)) +allocate_if(bool, solver, v, S, n) = bool && isempty(solver.:($v)) && (solver.:($v) = S(undef, n)) -@inline kdisplay(iter, verbose) = (verbose > 0) && (mod(iter, verbose) == 0) +kdisplay(iter, verbose) = (verbose > 0) && (mod(iter, verbose) == 0) -@inline mulorldiv!(y, P, x, ldiv::Bool) = ldiv ? ldiv!(y, P, x) : mul!(y, P, x) +mulorldiv!(y, P, x, ldiv::Bool) = ldiv ? ldiv!(y, P, x) : mul!(y, P, x) -@inline krylov_dot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasReal = BLAS.dot(n, x, dx, y, dy) -@inline krylov_dot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasComplex = BLAS.dotc(n, x, dx, y, dy) -@inline krylov_dot(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: Number = dot(x, y) +kdot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasReal = BLAS.dot(n, x, dx, y, dy) +kdot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasComplex = BLAS.dotc(n, x, dx, y, dy) +kdot(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = dot(x, y) -@inline krylov_dotr(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: AbstractFloat = krylov_dot(n, x, dx, y, dy) -@inline krylov_dotr(n :: Integer, x :: AbstractVector{Complex{T}}, dx :: Integer, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = real(krylov_dot(n, x, dx, y, dy)) +kdotr(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: AbstractFloat = kdot(n, x, dx, y, dy) +kdotr(n :: Integer, x :: AbstractVector{Complex{T}}, dx :: Integer, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = real(kdot(n, x, dx, y, dy)) -@inline krylov_norm2(n :: Integer, x :: Vector{T}, dx :: Integer) where T <: BLAS.BlasFloat = BLAS.nrm2(n, x, dx) -@inline krylov_norm2(n :: Integer, x :: AbstractVector{T}, dx :: Integer) where T <: Number = norm(x) +knrm2(n :: Integer, x :: Vector{T}, dx :: Integer) where T <: BLAS.BlasFloat = BLAS.nrm2(n, x, dx) +knrm2(n :: Integer, x :: AbstractVector{T}, dx :: Integer) where T <: FloatOrComplex = norm(x) -@inline krylov_scal!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer) where T <: BLAS.BlasFloat = BLAS.scal!(n, s, x, dx) -@inline krylov_scal!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer) where T <: Number = (x .*= s) -@inline krylov_scal!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer) where T <: AbstractFloat = krylov_scal!(n, Complex{T}(s), x, dx) +kscal!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer) where T <: BLAS.BlasFloat = BLAS.scal!(n, s, x, dx) +kscal!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer) where T <: FloatOrComplex = (x .*= s) +kscal!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer) where T <: AbstractFloat = kscal!(n, Complex{T}(s), x, dx) -@inline krylov_axpy!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.axpy!(n, s, x, dx, y, dy) -@inline krylov_axpy!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: Number = axpy!(s, x, y) -@inline krylov_axpy!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = krylov_axpy!(n, Complex{T}(s), x, dx, y, dy) +kaxpy!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.axpy!(n, s, x, dx, y, dy) +kaxpy!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = axpy!(s, x, y) +kaxpy!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpy!(n, Complex{T}(s), x, dx, y, dy) -@inline krylov_axpby!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer, t :: T, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.axpby!(n, s, x, dx, t, y, dy) -@inline krylov_axpby!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer, t :: T, y :: AbstractVector{T}, dy :: Integer) where T <: Number = axpby!(s, x, t, y) -@inline krylov_axpby!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: Complex{T}, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = krylov_axpby!(n, Complex{T}(s), x, dx, t, y, dy) -@inline krylov_axpby!(n :: Integer, s :: Complex{T}, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: T, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = krylov_axpby!(n, s, x, dx, Complex{T}(t), y, dy) -@inline krylov_axpby!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: T, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = krylov_axpby!(n, Complex{T}(s), x, dx, Complex{T}(t), y, dy) +kaxpby!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer, t :: T, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.axpby!(n, s, x, dx, t, y, dy) +kaxpby!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer, t :: T, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = axpby!(s, x, t, y) +kaxpby!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: Complex{T}, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpby!(n, Complex{T}(s), x, dx, t, y, dy) +kaxpby!(n :: Integer, s :: Complex{T}, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: T, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpby!(n, s, x, dx, Complex{T}(t), y, dy) +kaxpby!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: T, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpby!(n, Complex{T}(s), x, dx, Complex{T}(t), y, dy) -@inline krylov_copy!(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.blascopy!(n, x, dx, y, dy) -@inline krylov_copy!(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: Number = copyto!(y, x) +kcopy!(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.blascopy!(n, x, dx, y, dy) +kcopy!(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = copyto!(y, x) # the macros are just for readability, so we don't have to write the increments (always equal to 1) - macro kdot(n, x, y) - return esc(:(krylov_dot($n, $x, 1, $y, 1))) + return esc(:(Krylov.kdot($n, $x, 1, $y, 1))) end macro kdotr(n, x, y) - return esc(:(krylov_dotr($n, $x, 1, $y, 1))) + return esc(:(Krylov.kdotr($n, $x, 1, $y, 1))) end macro knrm2(n, x) - return esc(:(krylov_norm2($n, $x, 1))) + return esc(:(Krylov.knrm2($n, $x, 1))) end macro kscal!(n, s, x) - return esc(:(krylov_scal!($n, $s, $x, 1))) + return esc(:(Krylov.kscal!($n, $s, $x, 1))) end macro kaxpy!(n, s, x, y) - return esc(:(krylov_axpy!($n, $s, $x, 1, $y, 1))) + return esc(:(Krylov.kaxpy!($n, $s, $x, 1, $y, 1))) end macro kaxpby!(n, s, x, t, y) - return esc(:(krylov_axpby!($n, $s, $x, 1, $t, $y, 1))) + return esc(:(Krylov.kaxpby!($n, $s, $x, 1, $t, $y, 1))) end macro kcopy!(n, x, y) - return esc(:(krylov_copy!($n, $x, 1, $y, 1))) + return esc(:(Krylov.kcopy!($n, $x, 1, $y, 1))) end macro kswap(x, y) @@ -292,46 +334,3 @@ end macro kref!(n, x, y, c, s) return esc(:(reflect!($x, $y, $c, $s))) end - -""" - s = vec2str(x; ndisp) - -Display an array in the form - - [ -3.0e-01 -5.1e-01 1.9e-01 ... -2.3e-01 -4.4e-01 2.4e-01 ] - -with (ndisp - 1)/2 elements on each side. -""" -function vec2str(x :: AbstractVector{T}; ndisp :: Int=7) where T <: Union{AbstractFloat, Missing} - n = length(x) - if n ≤ ndisp - ndisp = n - nside = n - else - nside = max(1, div(ndisp - 1, 2)) - end - s = "[" - i = 1 - while i ≤ nside - if x[i] !== missing - s *= @sprintf("%8.1e ", x[i]) - else - s *= " ✗✗✗✗ " - end - i += 1 - end - if i ≤ div(n, 2) - s *= "... " - end - i = max(i, n - nside + 1) - while i ≤ n - if x[i] !== missing - s *= @sprintf("%8.1e ", x[i]) - else - s *= " ✗✗✗✗ " - end - i += 1 - end - s *= "]" - return s -end diff --git a/test/test_aux.jl b/test/test_aux.jl index 11bdb7c2d..215ffc4b8 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -1,119 +1,173 @@ @testset "aux" begin - # test Givens reflector corner cases - (c, s, ρ) = Krylov.sym_givens(0.0, 0.0) - @test (c == 1.0) && (s == 0.0) && (ρ == 0.0) - - a = 3.14 - (c, s, ρ) = Krylov.sym_givens(a, 0.0) - @test (c == 1.0) && (s == 0.0) && (ρ == a) - (c, s, ρ) = Krylov.sym_givens(-a, 0.0) - @test (c == -1.0) && (s == 0.0) && (ρ == a) - - b = 3.14 - (c, s, ρ) = Krylov.sym_givens(0.0, b) - @test (c == 0.0) && (s == 1.0) && (ρ == b) - (c, s, ρ) = Krylov.sym_givens(0.0, -b) - @test (c == 0.0) && (s == -1.0) && (ρ == b) - - (c, s, ρ) = Krylov.sym_givens(Complex(0.0), Complex(0.0)) - @test (c == 1.0) && (s == Complex(0.0)) && (ρ == Complex(0.0)) - - a = Complex(1.0, 1.0) - (c, s, ρ) = Krylov.sym_givens(a, Complex(0.0)) - @test (c == 1.0) && (s == Complex(0.0)) && (ρ == a) - (c, s, ρ) = Krylov.sym_givens(-a, Complex(0.0)) - @test (c == 1.0) && (s == Complex(0.0)) && (ρ == -a) - - b = Complex(1.0, 1.0) - (c, s, ρ) = Krylov.sym_givens(Complex(0.0), b) - @test (c == 0.0) && (s == Complex(1.0)) && (ρ == b) - (c, s, ρ) = Krylov.sym_givens(Complex(0.0), -b) - @test (c == 0.0) && (s == Complex(1.0)) && (ρ == -b) - - # test roots of a quadratic - roots = Krylov.roots_quadratic(0.0, 0.0, 0.0) - @test length(roots) == 1 - @test roots[1] == 0.0 - - roots = Krylov.roots_quadratic(0.0, 0.0, 1.0) - @test length(roots) == 0 - - roots = Krylov.roots_quadratic(0.0, 3.14, -1.0) - @test length(roots) == 1 - @test roots[1] == 1.0 / 3.14 - - roots = Krylov.roots_quadratic(1.0, 0.0, 1.0) - @test length(roots) == 0 - - roots = Krylov.roots_quadratic(1.0, 0.0, 0.0) - @test length(roots) == 2 - @test roots[1] == 0.0 - @test roots[2] == 0.0 - - roots = Krylov.roots_quadratic(1.0, 3.0, 2.0) - @test length(roots) == 2 - @test roots[1] ≈ -2.0 - @test roots[2] ≈ -1.0 - - roots = Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) - @test length(roots) == 0 - - # ill-conditioned quadratic - roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) - @test length(roots) == 2 - @test roots[1] == 1.0e+13 - @test roots[2] == 0.0 - - # iterative refinement is crucial! - roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) - @test length(roots) == 2 - @test roots[1] == 1.0e+13 - @test roots[2] == -1.0e-05 - - # not ill-conditioned quadratic - roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) - @test length(roots) == 2 - @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) - @test isapprox(roots[2], -1.0, rtol=1.0e-6) - - roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) - @test length(roots) == 2 - @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) - @test isapprox(roots[2], -1.0, rtol=1.0e-6) - - # test trust-region boundary - x = ones(5) - d = ones(5); d[1:2:5] .= -1 - @test_throws ErrorException Krylov.to_boundary(x, d, -1.0) - @test_throws ErrorException Krylov.to_boundary(x, d, 0.5) - @test_throws ErrorException Krylov.to_boundary(x, zeros(5), 1.0) - @test maximum(Krylov.to_boundary(x, d, 5.0)) ≈ 2.209975124224178 - @test minimum(Krylov.to_boundary(x, d, 5.0)) ≈ -1.8099751242241782 - @test maximum(Krylov.to_boundary(x, d, 5.0, flip=true)) ≈ 1.8099751242241782 - @test minimum(Krylov.to_boundary(x, d, 5.0, flip=true)) ≈ -2.209975124224178 - - # test kzeros and kones - @test Krylov.kzeros(Vector{Float64}, 10) == zeros(10) - @test Krylov.kones(Vector{Float64}, 10) == ones(10) - - # test ktypeof - a = rand(Float32, 10) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float32} - @test Krylov.ktypeof(b) == Vector{Float32} - - a = rand(Float64, 10) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float64} - @test Krylov.ktypeof(b) == Vector{Float64} - - a = sprand(Float32, 10, 0.5) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float32} - @test Krylov.ktypeof(b) == Vector{Float32} - - a = sprand(Float64, 10, 0.5) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float64} - @test Krylov.ktypeof(b) == Vector{Float64} + + @testset "sym_givens" begin + # test Givens reflector corner cases + (c, s, ρ) = Krylov.sym_givens(0.0, 0.0) + @test (c == 1.0) && (s == 0.0) && (ρ == 0.0) + + a = 3.14 + (c, s, ρ) = Krylov.sym_givens(a, 0.0) + @test (c == 1.0) && (s == 0.0) && (ρ == a) + (c, s, ρ) = Krylov.sym_givens(-a, 0.0) + @test (c == -1.0) && (s == 0.0) && (ρ == a) + + b = 3.14 + (c, s, ρ) = Krylov.sym_givens(0.0, b) + @test (c == 0.0) && (s == 1.0) && (ρ == b) + (c, s, ρ) = Krylov.sym_givens(0.0, -b) + @test (c == 0.0) && (s == -1.0) && (ρ == b) + + (c, s, ρ) = Krylov.sym_givens(Complex(0.0), Complex(0.0)) + @test (c == 1.0) && (s == Complex(0.0)) && (ρ == Complex(0.0)) + + a = Complex(1.0, 1.0) + (c, s, ρ) = Krylov.sym_givens(a, Complex(0.0)) + @test (c == 1.0) && (s == Complex(0.0)) && (ρ == a) + (c, s, ρ) = Krylov.sym_givens(-a, Complex(0.0)) + @test (c == 1.0) && (s == Complex(0.0)) && (ρ == -a) + + b = Complex(1.0, 1.0) + (c, s, ρ) = Krylov.sym_givens(Complex(0.0), b) + @test (c == 0.0) && (s == Complex(1.0)) && (ρ == b) + (c, s, ρ) = Krylov.sym_givens(Complex(0.0), -b) + @test (c == 0.0) && (s == Complex(1.0)) && (ρ == -b) + end + + @testset "roots_quadratic" begin + # test roots of a quadratic + roots = Krylov.roots_quadratic(0.0, 0.0, 0.0) + @test length(roots) == 1 + @test roots[1] == 0.0 + + roots = Krylov.roots_quadratic(0.0, 0.0, 1.0) + @test length(roots) == 0 + + roots = Krylov.roots_quadratic(0.0, 3.14, -1.0) + @test length(roots) == 1 + @test roots[1] == 1.0 / 3.14 + + roots = Krylov.roots_quadratic(1.0, 0.0, 1.0) + @test length(roots) == 0 + + roots = Krylov.roots_quadratic(1.0, 0.0, 0.0) + @test length(roots) == 2 + @test roots[1] == 0.0 + @test roots[2] == 0.0 + + roots = Krylov.roots_quadratic(1.0, 3.0, 2.0) + @test length(roots) == 2 + @test roots[1] ≈ -2.0 + @test roots[2] ≈ -1.0 + + roots = Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) + @test length(roots) == 0 + + # ill-conditioned quadratic + roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) + @test length(roots) == 2 + @test roots[1] == 1.0e+13 + @test roots[2] == 0.0 + + # iterative refinement is crucial! + roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) + @test length(roots) == 2 + @test roots[1] == 1.0e+13 + @test roots[2] == -1.0e-05 + + # not ill-conditioned quadratic + roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) + @test length(roots) == 2 + @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) + @test isapprox(roots[2], -1.0, rtol=1.0e-6) + + roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) + @test length(roots) == 2 + @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) + @test isapprox(roots[2], -1.0, rtol=1.0e-6) + end + + @testset "to_boundary" begin + # test trust-region boundary + x = ones(5) + d = ones(5); d[1:2:5] .= -1 + @test_throws ErrorException Krylov.to_boundary(x, d, -1.0) + @test_throws ErrorException Krylov.to_boundary(x, d, 0.5) + @test_throws ErrorException Krylov.to_boundary(x, zeros(5), 1.0) + @test maximum(Krylov.to_boundary(x, d, 5.0)) ≈ 2.209975124224178 + @test minimum(Krylov.to_boundary(x, d, 5.0)) ≈ -1.8099751242241782 + @test maximum(Krylov.to_boundary(x, d, 5.0, flip=true)) ≈ 1.8099751242241782 + @test minimum(Krylov.to_boundary(x, d, 5.0, flip=true)) ≈ -2.209975124224178 + end + + @testset "kzeros" begin + # test kzeros + @test Krylov.kzeros(Vector{Float64}, 10) == zeros(Float64, 10) + @test Krylov.kzeros(Vector{ComplexF32}, 10) == zeros(ComplexF32, 10) + end + + @testset "kones" begin + # test kones + @test Krylov.kones(Vector{Float64}, 10) == ones(Float64, 10) + @test Krylov.kones(Vector{ComplexF32}, 10) == ones(ComplexF32, 10) + end + + @testset "ktypeof" begin + # test ktypeof + a = rand(Float32, 10) + b = view(a, 4:8) + @test Krylov.ktypeof(a) == Vector{Float32} + @test Krylov.ktypeof(b) == Vector{Float32} + + a = rand(Float64, 10) + b = view(a, 4:8) + @test Krylov.ktypeof(a) == Vector{Float64} + @test Krylov.ktypeof(b) == Vector{Float64} + + a = sprand(Float32, 10, 0.5) + b = view(a, 4:8) + @test Krylov.ktypeof(a) == Vector{Float32} + @test Krylov.ktypeof(b) == Vector{Float32} + + a = sprand(Float64, 10, 0.5) + b = view(a, 4:8) + @test Krylov.ktypeof(a) == Vector{Float64} + @test Krylov.ktypeof(b) == Vector{Float64} + end + + @testset "macros" begin + # test macros + for FC ∈ (Float16, Float32, Float64, Complex{Float16}, Complex{Float32}, Complex{Float64}) + n = 10 + x = rand(FC, n) + y = rand(FC, n) + a = rand(FC) + b = rand(FC) + c = rand(FC) + s = rand(FC) + + T = real(FC) + a2 = rand(T) + b2 = rand(T) + + Krylov.@kdot(n, x, y) + + Krylov.@kdotr(n, x, y) + + Krylov.@knrm2(n, x) + + Krylov.@kaxpy!(n, a, x, y) + Krylov.@kaxpy!(n, a2, x, y) + + Krylov.@kaxpby!(n, a, x, b, y) + Krylov.@kaxpby!(n, a2, x, b, y) + Krylov.@kaxpby!(n, a, x, b2, y) + Krylov.@kaxpby!(n, a2, x, b2, y) + + Krylov.@kcopy!(n, x, y) + + Krylov.@kswap(x, y) + + Krylov.@kref!(n, x, y, c, s) + end + end end From e7cd9b8b08da8710342cd8c0101d15becdf9e45e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 9 Sep 2022 15:21:45 -0400 Subject: [PATCH 019/182] Use buildkite to test the GPU support --- .buildkite/pipeline.yml | 65 ++++++++++++++++++++++++++++++ test/gpu/amd.jl | 77 ++++++++++++++++++++++++++++++++++++ test/gpu/intel.jl | 84 +++++++++++++++++++++++++++++++++++++++ test/gpu/metal.jl | 88 +++++++++++++++++++++++++++++++++++++++++ test/gpu/nvidia.jl | 77 ++++++++++++++++++++++++++++++++++++ test/test_aux.jl | 2 +- 6 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 .buildkite/pipeline.yml create mode 100644 test/gpu/amd.jl create mode 100644 test/gpu/intel.jl create mode 100644 test/gpu/metal.jl create mode 100644 test/gpu/nvidia.jl diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 000000000..24c01d7f0 --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,65 @@ +steps: + - label: "Nvidia GPUs -- CUDA.jl" + plugins: + - JuliaCI/julia#v1: + version: 1.8 + agents: + queue: "juliagpu" + cuda: "*" + command: | + julia --color=yes --project -e ' + using Pkg + Pkg.add("CUDA") + Pkg.instantiate() + include("test/gpu/nvidia.jl")' + timeout_in_minutes: 30 + + - label: "AMD GPUs -- AMDGPU.jl" + plugins: + - JuliaCI/julia#v1: + version: 1.8 + agents: + queue: "juliagpu" + rocm: "*" + rocmgpu: "gfx908" + env: + JULIA_AMDGPU_CORE_MUST_LOAD: "1" + JULIA_AMDGPU_HIP_MUST_LOAD: "1" + JULIA_AMDGPU_DISABLE_ARTIFACTS: "1" + command: | + julia --color=yes --project -e ' + using Pkg + Pkg.add("AMDGPU") + Pkg.instantiate() + include("test/gpu/amd.jl")' + timeout_in_minutes: 30 + + - label: "Intel GPUs -- oneAPI.jl" + plugins: + - JuliaCI/julia#v1: + version: 1.8 + agents: + queue: "juliagpu" + intel: "*" + command: | + julia --color=yes --project -e ' + using Pkg + Pkg.add("oneAPI") + Pkg.instantiate() + include("test/gpu/intel.jl")' + timeout_in_minutes: 30 + + - label: "Apple M1 GPUs -- Metal.jl" + plugins: + - JuliaCI/julia#v1: + version: 1.8 + agents: + queue: "juliagpu" + metal: "*" + command: | + julia --color=yes --project -e ' + using Pkg + Pkg.add("Metal") + Pkg.instantiate() + include("test/gpu/metal.jl")' + timeout_in_minutes: 30 diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl new file mode 100644 index 000000000..f1193b235 --- /dev/null +++ b/test/gpu/amd.jl @@ -0,0 +1,77 @@ +using LinearAlgebra, SparseArrays, Test +using Krylov, AMDGPU + +@testset "AMD -- AMDGPU.jl" begin + + @test AMDGPU.functional() + AMDGPU.allowscalar(false) + + for FC in (Float32, Float64, ComplexF32, ComplexF64) + S = ROCVector{FC} + T = real(FC) + n = 10 + x = rand(FC, n) + x = S(x) + y = rand(FC, n) + y = S(y) + a = rand(FC) + b = rand(FC) + s = rand(FC) + a2 = rand(T) + b2 = rand(T) + c = rand(T) + + @testset "kdot -- $FC" begin + Krylov.@kdot(n, x, y) + end + + @testset "kdotr -- $FC" begin + Krylov.@kdotr(n, x, y) + end + + @testset "knrm2 -- $FC" begin + Krylov.@knrm2(n, x) + end + + @testset "kaxpy! -- $FC" begin + Krylov.@kaxpy!(n, a, x, y) + Krylov.@kaxpy!(n, a2, x, y) + end + + @testset "kaxpby! -- $FC" begin + Krylov.@kaxpby!(n, a, x, b, y) + Krylov.@kaxpby!(n, a2, x, b, y) + Krylov.@kaxpby!(n, a, x, b2, y) + Krylov.@kaxpby!(n, a2, x, b2, y) + end + + @testset "kcopy! -- $FC" begin + Krylov.@kcopy!(n, x, y) + end + + @testset "kswap -- $FC" begin + Krylov.@kswap(x, y) + end + + # @testset "kref! -- $FC" begin + # Krylov.@kref!(n, x, y, c, s) + # end + + ε = eps(T) + A = rand(FC, n, n) + A = ROCMatrix{FC}(A) + b = rand(FC, n) + b = ROCVector{FC}(b) + + @testset "GMRES -- $FC" begin + x, stats = gmres(A, b) + @test norm(b - A * x) ≤ √ε + end + + @testset "CG -- $FC" begin + C = A * A' + x, stats = cg(C, b) + @test stats.solved + end + end +end diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl new file mode 100644 index 000000000..4fa4192c6 --- /dev/null +++ b/test/gpu/intel.jl @@ -0,0 +1,84 @@ +using LinearAlgebra, SparseArrays, Test +using Krylov, oneAPI + +import Krylov.kdot +function kdot(n :: Integer, x :: oneVector{T}, dx :: Integer, y :: oneVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex + z = similar(x) + z .= conj.(x) .* y + reduce(+, z) +end + +@testset "Intel -- oneAPI.jl" begin + + @test oneAPI.functional() + oneAPI.allowscalar(false) + + for FC ∈ (Float32, ComplexF32) + S = oneVector{FC} + T = real(FC) + n = 10 + x = rand(FC, n) + x = S(x) + y = rand(FC, n) + y = S(y) + a = rand(FC) + b = rand(FC) + s = rand(FC) + a2 = rand(T) + b2 = rand(T) + c = rand(T) + + @testset "kdot -- $FC" begin + Krylov.@kdot(n, x, y) + end + + @testset "kdotr -- $FC" begin + Krylov.@kdotr(n, x, y) + end + + @testset "knrm2 -- $FC" begin + Krylov.@knrm2(n, x) + end + + @testset "kaxpy! -- $FC" begin + Krylov.@kaxpy!(n, a, x, y) + Krylov.@kaxpy!(n, a2, x, y) + end + + @testset "kaxpby! -- $FC" begin + Krylov.@kaxpby!(n, a, x, b, y) + Krylov.@kaxpby!(n, a2, x, b, y) + Krylov.@kaxpby!(n, a, x, b2, y) + Krylov.@kaxpby!(n, a2, x, b2, y) + end + + @testset "kcopy! -- $FC" begin + Krylov.@kcopy!(n, x, y) + end + + @testset "kswap -- $FC" begin + Krylov.@kswap(x, y) + end + + # @testset "kref! -- $FC" begin + # Krylov.@kref!(n, x, y, c, s) + # end + + ε = eps(T) + A = rand(FC, n, n) + A = oneMatrix{FC}(A) + b = rand(FC, n) + b = oneVector{FC}(b) + + @testset "GMRES -- $FC" begin + x, stats = gmres(A, b) + @test norm(b - A * x) ≤ √ε + end + + @testset "CG -- $FC" begin + C = A * A' + x, stats = cg(C, b) + @test stats.solved + end + end +end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl new file mode 100644 index 000000000..af386f513 --- /dev/null +++ b/test/gpu/metal.jl @@ -0,0 +1,88 @@ +using LinearAlgebra, SparseArrays, Test +using Krylov, Metal + +# https://github.com/JuliaGPU/Metal.jl/pull/48 +const MtlVector{T} = MtlArray{T,1} +const MtlMatrix{T} = MtlArray{T,2} + +import Krylov.kdot +function kdot(n :: Integer, x :: MtlVector{T}, dx :: Integer, y :: MtlVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex + z = similar(x) + z .= conj.(x) .* y + reduce(+, z) +end + +@testset "Apple M1 GPUs -- Metal.jl" begin + + # @test Metal.functional() + Metal.allowscalar(false) + + for FC in (Float32, ComplexF32) + S = MtlVector{FC} + T = real(FC) + n = 10 + x = rand(FC, n) + x = S(x) + y = rand(FC, n) + y = S(y) + a = rand(FC) + b = rand(FC) + s = rand(FC) + a2 = rand(T) + b2 = rand(T) + c = rand(T) + + @testset "kdot -- $FC" begin + Krylov.@kdot(n, x, y) + end + + @testset "kdotr -- $FC" begin + Krylov.@kdotr(n, x, y) + end + + @testset "knrm2 -- $FC" begin + Krylov.@knrm2(n, x) + end + + @testset "kaxpy! -- $FC" begin + Krylov.@kaxpy!(n, a, x, y) + Krylov.@kaxpy!(n, a2, x, y) + end + + @testset "kaxpby! -- $FC" begin + Krylov.@kaxpby!(n, a, x, b, y) + Krylov.@kaxpby!(n, a2, x, b, y) + Krylov.@kaxpby!(n, a, x, b2, y) + Krylov.@kaxpby!(n, a2, x, b2, y) + end + + @testset "kcopy! -- $FC" begin + Krylov.@kcopy!(n, x, y) + end + + @testset "kswap -- $FC" begin + Krylov.@kswap(x, y) + end + + # @testset "kref! -- $FC" begin + # Krylov.@kref!(n, x, y, c, s) + # end + + ε = eps(T) + A = rand(FC, n, n) + A = MtlMatrix{FC}(A) + b = rand(FC, n) + b = MtlVector{FC}(b) + + @testset "GMRES -- $FC" begin + x, stats = gmres(A, b) + @test norm(b - A * x) ≤ √ε + end + + @testset "CG -- $FC" begin + C = A * A' + x, stats = cg(C, b) + @test stats.solved + end + end +end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl new file mode 100644 index 000000000..824d65239 --- /dev/null +++ b/test/gpu/nvidia.jl @@ -0,0 +1,77 @@ +using LinearAlgebra, SparseArrays, Test +using Krylov, CUDA, CUDA.CUSPARSE + +@testset "Nvidia -- CUDA.jl" begin + + @test CUDA.functional() + CUDA.allowscalar(false) + + for FC in (Float32, Float64, ComplexF32, ComplexF64) + S = CuVector{FC} + T = real(FC) + n = 10 + x = rand(FC, n) + x = S(x) + y = rand(FC, n) + y = S(y) + a = rand(FC) + b = rand(FC) + s = rand(FC) + a2 = rand(T) + b2 = rand(T) + c = rand(T) + + @testset "kdot -- $FC" begin + Krylov.@kdot(n, x, y) + end + + @testset "kdotr -- $FC" begin + Krylov.@kdotr(n, x, y) + end + + @testset "knrm2 -- $FC" begin + Krylov.@knrm2(n, x) + end + + @testset "kaxpy! -- $FC" begin + Krylov.@kaxpy!(n, a, x, y) + Krylov.@kaxpy!(n, a2, x, y) + end + + @testset "kaxpby! -- $FC" begin + Krylov.@kaxpby!(n, a, x, b, y) + Krylov.@kaxpby!(n, a2, x, b, y) + Krylov.@kaxpby!(n, a, x, b2, y) + Krylov.@kaxpby!(n, a2, x, b2, y) + end + + @testset "kcopy! -- $FC" begin + Krylov.@kcopy!(n, x, y) + end + + @testset "kswap -- $FC" begin + Krylov.@kswap(x, y) + end + + @testset "kref! -- $FC" begin + Krylov.@kref!(n, x, y, c, s) + end + + ε = eps(T) + A = rand(FC, n, n) + A = CuMatrix{FC}(A) + b = rand(FC, n) + b = CuVector{FC}(b) + + @testset "GMRES -- $FC" begin + x, stats = gmres(A, b) + @test norm(b - A * x) ≤ √ε + end + + @testset "CG -- $FC" begin + C = A * A' + x, stats = cg(C, b) + @test stats.solved + end + end +end diff --git a/test/test_aux.jl b/test/test_aux.jl index 215ffc4b8..5a4d094c7 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -136,7 +136,7 @@ @testset "macros" begin # test macros - for FC ∈ (Float16, Float32, Float64, Complex{Float16}, Complex{Float32}, Complex{Float64}) + for FC ∈ (Float16, Float32, Float64, ComplexF16, ComplexF32, ComplexF64) n = 10 x = rand(FC, n) y = rand(FC, n) From fc8bb5983fadede4235b971c339fdcbd46c56b3e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 10 Sep 2022 00:31:35 -0400 Subject: [PATCH 020/182] Update workflows --- .github/workflows/Breakage.yml | 12 ++++----- .github/workflows/CommentPR.yml | 35 ++++++++++++++++++------ .github/workflows/CompatHelper.yml | 41 +++++++++++++++++++++++------ .github/workflows/Documentation.yml | 2 +- .github/workflows/ci.yml | 8 +++--- 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/.github/workflows/Breakage.yml b/.github/workflows/Breakage.yml index 266eed3cc..8fd92afdd 100644 --- a/.github/workflows/Breakage.yml +++ b/.github/workflows/Breakage.yml @@ -24,14 +24,14 @@ jobs: pkgversion: [latest, stable] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Install Julia - uses: julia-actions/setup-julia@v1 with: version: '1' arch: x64 - - uses: actions/cache@v1 + - uses: actions/cache@v3 env: cache-name: cache-artifacts with: @@ -85,7 +85,7 @@ jobs: end; end' - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: pr path: pr/ @@ -94,9 +94,9 @@ jobs: needs: break runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: pr path: pr/ @@ -127,7 +127,7 @@ jobs: fi done >> MSG - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: pr path: pr/ diff --git a/.github/workflows/CommentPR.yml b/.github/workflows/CommentPR.yml index 14f6dcd47..479d50dc5 100644 --- a/.github/workflows/CommentPR.yml +++ b/.github/workflows/CommentPR.yml @@ -39,16 +39,35 @@ jobs: - run: unzip pr.zip - name: 'Comment on PR' - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - var fs = require('fs'); - var issue_number = Number(fs.readFileSync('./NR')); - var msg = fs.readFileSync('./MSG', 'utf8'); - await github.issues.createComment({ + var fs = require('fs') + var msg = fs.readFileSync('./MSG', 'utf8') + + // Get the existing comments. + const {data: comments} = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: issue_number, - body: msg - }); + issue_number: context.payload.number + }) + + // Find any comment already made by the bot. + const botComment = comments.find(comment => comment.user.id === 41898282) + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: msg + }) + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + body: msg + }) + } diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index b546a8082..7a9c79fd4 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -1,19 +1,44 @@ name: CompatHelper - on: schedule: - - cron: '00 00 * * *' - + - cron: 0 0 * * * + workflow_dispatch: +permissions: + contents: write + pull-requests: write jobs: CompatHelper: runs-on: ubuntu-latest steps: - - uses: julia-actions/setup-julia@latest + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v1 with: version: '1' - - name: CompatHelper - run: julia -e 'using Pkg; Pkg.add("CompatHelper")' - - name: CompatHelper.main() + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: julia -e 'using CompatHelper; CompatHelper.main()' + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index be0b86584..fef36054d 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@latest with: version: '1' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 409e0d146..9e1791f48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,12 +31,12 @@ jobs: arch: x64 allow_failure: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v3 env: cache-name: cache-artifacts with: @@ -49,6 +49,6 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 with: - file: lcov.info + files: lcov.info From 26c485641793badb14d1cea7b69320bb1f7e9471 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 10 Sep 2022 01:58:51 -0400 Subject: [PATCH 021/182] Update CommentPR.yml --- .github/workflows/CommentPR.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CommentPR.yml b/.github/workflows/CommentPR.yml index 479d50dc5..043113f74 100644 --- a/.github/workflows/CommentPR.yml +++ b/.github/workflows/CommentPR.yml @@ -44,13 +44,14 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | var fs = require('fs') + var issue_number = Number(fs.readFileSync('./NR')) var msg = fs.readFileSync('./MSG', 'utf8') // Get the existing comments. const {data: comments} = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.payload.number + issue_number: issue_number }) // Find any comment already made by the bot. @@ -67,7 +68,7 @@ jobs: await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.payload.number, + issue_number: issue_number, body: msg }) } From b19c74b454b7fedd23844976cc9403cfcb7efe75 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 13 Sep 2022 01:31:30 -0400 Subject: [PATCH 022/182] [documentation] update gpu.md --- docs/make.jl | 2 +- docs/src/gpu.md | 113 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 101 insertions(+), 14 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index f59bfac0c..0ad50d52f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,7 +6,7 @@ makedocs( linkcheck = true, strict = true, format = Documenter.HTML(assets = ["assets/style.css"], - ansicolor=true, + ansicolor = true, prettyurls = get(ENV, "CI", nothing) == "true", collapselevel = 1), sitename = "Krylov.jl", diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 3c6bc1e29..fc7a05587 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -1,6 +1,15 @@ -## GPU support +# [GPU support](@id gpu) -All solvers in Krylov.jl can be used with `CuArrays` and allow computations with Nvidia GPU. Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to GPU format (`CuMatrix` and `CuVector`). +Krylov methods are well suited for GPU computations because they only require matrix-vector products ($u \leftarrow Av$, $u \leftarrow A^{H}w$) and vector operations ($\|v\|$, $u^H v$, $v \leftarrow \alpha u + \beta v$), which are easily parallelizable. + +The implementations in Krylov.jl are generic so as to take advantage of the multiple dispatch and broadcast features of Julia. +It allows the implementations to be specialized automatically by the compiler for both CPU and GPU usages. +Thus, Krylov.jl works with GPU backends that build on [GPUArrays.jl](https://github.com/JuliaGPU/GPUArrays.jl), such as [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl), [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl), [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) or [Metal.jl](https://github.com/JuliaGPU/Metal.jl). + +## Nvidia GPUs + +All solvers in Krylov.jl can be used with [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) and allow computations with Nvidia GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`CuMatrix` and `CuVector`). ```julia using CUDA, Krylov @@ -13,11 +22,11 @@ b_cpu = rand(20) A_gpu = CuMatrix(A_cpu) b_gpu = CuVector(b_cpu) -# Solve a square and dense system on GPU +# Solve a square and dense system on a Nivida GPU x, stats = bilq(A_gpu, b_gpu) ``` -Sparse matrices have a specific storage on GPU (`CuSparseMatrixCSC` or `CuSparseMatrixCSR`): +Sparse matrices have a specific storage on Nvidia GPUs (`CuSparseMatrixCSC` or `CuSparseMatrixCSR`): ```julia using CUDA, Krylov @@ -31,7 +40,7 @@ b_cpu = rand(200) A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) -# Solve a rectangular and sparse system on GPU +# Solve a rectangular and sparse system on a Nvidia GPU x, stats = lsmr(A_gpu, b_gpu) ``` @@ -47,14 +56,14 @@ using SparseArrays, Krylov, LinearOperators using CUDA, CUDA.CUSPARSE # Transfer the linear system from the CPU to the GPU -A_gpu = CuSparseMatrixCSC(A_cpu) # A = CuSparseMatrixCSR(A_cpu) +A_gpu = CuSparseMatrixCSC(A_cpu) # A_gpu = CuSparseMatrixCSR(A_cpu) b_gpu = CuVector(b_cpu) # LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ic02(A_gpu, 'O') # Solve Py = x -function ldiv!(y, P, x) +function ldiv_ic0!(y, P, x) copyto!(y, x) # Variant for CuSparseMatrixCSR sv2!('T', 'U', 'N', 1.0, P, y, 'O') # sv2!('N', 'L', 'N', 1.0, P, y, 'O') sv2!('N', 'U', 'N', 1.0, P, y, 'O') # sv2!('T', 'L', 'N', 1.0, P, y, 'O') @@ -65,10 +74,10 @@ end n = length(b_gpu) T = eltype(b_gpu) symmetric = hermitian = true -opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv!(y, P, x)) +opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x)) # Solve a symmetric positive definite system with an incomplete Cholesky preconditioner on GPU -(x, stats) = cg(A_gpu, b_gpu, M=opM) +x, stats = cg(A_gpu, b_gpu, M=opM) ``` ### Example with a general square system @@ -84,14 +93,14 @@ A_cpu = A_cpu[p,:] b_cpu = b_cpu[p] # Transfer the linear system from the CPU to the GPU -A_gpu = CuSparseMatrixCSC(A_cpu) # A = CuSparseMatrixCSR(A_cpu) +A_gpu = CuSparseMatrixCSC(A_cpu) # A_gpu = CuSparseMatrixCSR(A_cpu) b_gpu = CuVector(b_cpu) # LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ilu02(A_gpu, 'O') # Solve Py = x -function ldiv!(y, P, x) +function ldiv_ilu0!(y, P, x) copyto!(y, x) # Variant for CuSparseMatrixCSR sv2!('N', 'L', 'N', 1.0, P, y, 'O') # sv2!('N', 'L', 'U', 1.0, P, y, 'O') sv2!('N', 'U', 'U', 1.0, P, y, 'O') # sv2!('N', 'U', 'N', 1.0, P, y, 'O') @@ -102,8 +111,86 @@ end n = length(b_gpu) T = eltype(b_gpu) symmetric = hermitian = false -opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv!(y, P, x)) +opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(y, P, x)) # Solve an unsymmetric system with an incomplete LU preconditioner on GPU -(x, stats) = bicgstab(A_gpu, b_gpu, M=opM) +x, stats = bicgstab(A_gpu, b_gpu, M=opM) ``` + +## AMD GPUs + +All solvers in Krylov.jl can be used with [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) and allow computations with AMD GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`ROCMatrix` and `ROCVector`). + +```julia +using Krylov, AMDGPU + +# CPU Arrays +A_cpu = rand(ComplexF64, 20, 20) +A_cpu = A_cpu + A_cpu' +b_cpu = rand(ComplexF64, 20) + +A = A + A' +A_gpu = ROCMatrix(A) +b_gpu = ROCVector(b) + +# Solve a dense hermitian system on an AMD GPU +x, stats = minres(A_gpu, b_gpu) +``` + +!!! info + The library `rocSPARSE` is not interfaced yet in AMDGPU.jl and only dense linear systems are supported. + +## Intel GPUs + +All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) and allow computations with Intel GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`oneMatrix` and `oneVector`). + +```julia +using Krylov, oneAPI + +T = Float32 # oneAPI.jl also works with ComplexF32 +m = 20 +n = 10 + +# CPU Arrays +A_cpu = rand(T, m, n) +b_cpu = rand(T, m) + +# GPU Arrays +A_gpu = oneMatrix(A_cpu) +b_gpu = oneVector(b_cpu) + +# Solve a dense least-squares problem on an Intel GPU +x, stats = lsqr(A_gpu, b_gpu) +``` + +!!! warning + The library `oneMKL` is not interfaced yet in oneAPI.jl and all BLAS routines (dot, norm, mul!, etc.) dispatch to generic fallbacks. + +## Apple M1 GPUs + +All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [Metal.jl](https://github.com/JuliaGPU/Metal.jl) and allow computations with Apple M1 GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`MtlMatrix` and `MtlVector`). + +```julia +using Krylov, Metal + +T = Float32 # Metal.jl also works with ComplexF32 +n = 10 +n = 20 + +# CPU Arrays +A_cpu = rand(T, n, m) +b_cpu = rand(T, n) + +# GPU Arrays +A_gpu = MtlMatrix(A_cpu) +b_gpu = MtlVector(b_cpu) + +# Solve a dense least-norm problem on an Apple M1 GPU +x, stats = craig(A_gpu, b_gpu) +``` + +!!! warning + Metal.jl is under heavy development and is considered experimental for now. From 3c67dfb9da554a5126e680bf204d48722e8a94b5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 13 Sep 2022 01:32:54 -0400 Subject: [PATCH 023/182] [documentation] Test the code of GPU backends with buildkite --- .buildkite/pipeline.yml | 1 + test/gpu/amd.jl | 10 +++++ test/gpu/intel.jl | 16 ++++++-- test/gpu/metal.jl | 16 ++++++-- test/gpu/nvidia.jl | 89 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 125 insertions(+), 7 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 24c01d7f0..963eb619b 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -10,6 +10,7 @@ steps: julia --color=yes --project -e ' using Pkg Pkg.add("CUDA") + Pkg.add("LinearOperators") Pkg.instantiate() include("test/gpu/nvidia.jl")' timeout_in_minutes: 30 diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index f1193b235..c161b1900 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -6,6 +6,16 @@ using Krylov, AMDGPU @test AMDGPU.functional() AMDGPU.allowscalar(false) + @testset "documentation" begin + A_cpu = rand(ComplexF64, 20, 20) + A_cpu = A_cpu + A_cpu' + b_cpu = rand(ComplexF64, 20) + A = A + A' + A_gpu = ROCMatrix(A) + b_gpu = ROCVector(b) + x, stats = minres(A_gpu, b_gpu) + end + for FC in (Float32, Float64, ComplexF32, ComplexF64) S = ROCVector{FC} T = real(FC) diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index 4fa4192c6..12d9232fd 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -1,11 +1,10 @@ using LinearAlgebra, SparseArrays, Test using Krylov, oneAPI +# https://github.com/JuliaGPU/GPUArrays.jl/pull/427 import Krylov.kdot function kdot(n :: Integer, x :: oneVector{T}, dx :: Integer, y :: oneVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex - z = similar(x) - z .= conj.(x) .* y - reduce(+, z) + return mapreduce(dot, +, x, y) end @testset "Intel -- oneAPI.jl" begin @@ -13,6 +12,17 @@ end @test oneAPI.functional() oneAPI.allowscalar(false) + @testset "documentation" begin + T = Float32 + m = 20 + n = 10 + A_cpu = rand(T, m, n) + b_cpu = rand(T, m) + A_gpu = oneMatrix(A_cpu) + b_gpu = oneVector(b_cpu) + x, stats = lsqr(A_gpu, b_gpu) + end + for FC ∈ (Float32, ComplexF32) S = oneVector{FC} T = real(FC) diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index af386f513..a4ce46922 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -5,11 +5,10 @@ using Krylov, Metal const MtlVector{T} = MtlArray{T,1} const MtlMatrix{T} = MtlArray{T,2} +# https://github.com/JuliaGPU/GPUArrays.jl/pull/427 import Krylov.kdot function kdot(n :: Integer, x :: MtlVector{T}, dx :: Integer, y :: MtlVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex - z = similar(x) - z .= conj.(x) .* y - reduce(+, z) + return mapreduce(dot, +, x, y) end @testset "Apple M1 GPUs -- Metal.jl" begin @@ -17,6 +16,17 @@ end # @test Metal.functional() Metal.allowscalar(false) + @testset "documentation" begin + T = Float32 + n = 10 + n = 20 + A_cpu = rand(T, n, m) + b_cpu = rand(T, n) + A_gpu = MtlMatrix(A_cpu) + b_gpu = MtlVector(b_cpu) + x, stats = craig(A_gpu, b_gpu) + end + for FC in (Float32, ComplexF32) S = MtlVector{FC} T = real(FC) diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 824d65239..33e0a4ba8 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -1,11 +1,98 @@ using LinearAlgebra, SparseArrays, Test -using Krylov, CUDA, CUDA.CUSPARSE +using LinearOperators, Krylov, CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER + +include("../test_utils.jl") @testset "Nvidia -- CUDA.jl" begin @test CUDA.functional() CUDA.allowscalar(false) + @testset "documentation" begin + A_cpu = rand(20, 20) + b_cpu = rand(20) + A_gpu = CuMatrix(A_cpu) + b_gpu = CuVector(b_cpu) + x, stats = bilq(A_gpu, b_gpu) + + A_cpu = sprand(200, 100, 0.3) + b_cpu = rand(200) + A_gpu = CuSparseMatrixCSC(A_cpu) + b_gpu = CuVector(b_cpu) + x, stats = lsmr(A_gpu, b_gpu) + + @testset "ic0" begin + A_cpu, b_cpu = sparse_laplacian() + + b_gpu = CuVector(b_cpu) + n = length(b_gpu) + T = eltype(b_gpu) + symmetric = hermitian = true + + A_gpu = CuSparseMatrixCSC(A_cpu) + P = ic02(A_gpu, 'O') + function ldiv_ic0!(y, P, x) + copyto!(y, x) + sv2!('T', 'U', 'N', 1.0, P, y, 'O') + sv2!('N', 'U', 'N', 1.0, P, y, 'O') + return y + end + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x)) + x, stats = cg(A_gpu, b_gpu, M=opM) + @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + + A_gpu = CuSparseMatrixCSR(A_cpu) + P = ic02(A_gpu, 'O') + function ldiv_ic0!(y, P, x) + copyto!(y, x) + sv2!('N', 'L', 'N', 1.0, P, y, 'O') + sv2!('T', 'L', 'N', 1.0, P, y, 'O') + return y + end + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x)) + x, stats = cg(A_gpu, b_gpu, M=opM) + @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + end + + @testset "ilu0" begin + A_cpu, b_cpu = polar_poisson() + + p = zfd(A_cpu, 'O') + p .+= 1 + A_cpu = A_cpu[p,:] + b_cpu = b_cpu[p] + + b_gpu = CuVector(b_cpu) + n = length(b_gpu) + T = eltype(b_gpu) + symmetric = hermitian = false + + A_gpu = CuSparseMatrixCSC(A_cpu) + P = ilu02(A_gpu, 'O') + function ldiv_ilu0!(y, P, x) + copyto!(y, x) + sv2!('N', 'L', 'N', 1.0, P, y, 'O') + sv2!('N', 'U', 'U', 1.0, P, y, 'O') + return y + end + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(y, P, x)) + x, stats = bicgstab(A_gpu, b_gpu, M=opM) + @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + + A_gpu = CuSparseMatrixCSR(A_cpu) + P = ilu02(A_gpu, 'O') + function ldiv_ilu0!(y, P, x) + copyto!(y, x) + sv2!('N', 'L', 'U', 1.0, P, y, 'O') + sv2!('N', 'U', 'N', 1.0, P, y, 'O') + return y + end + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(y, P, x)) + x, stats = bicgstab(A_gpu, b_gpu, M=opM) + @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + end + end + for FC in (Float32, Float64, ComplexF32, ComplexF64) S = CuVector{FC} T = real(FC) From a97909a7ad033a2b719690daa428df0795006b71 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 13 Sep 2022 01:40:12 -0400 Subject: [PATCH 024/182] Fix few typos --- docs/src/gpu.md | 7 +++---- test/gpu/amd.jl | 5 ++--- test/gpu/metal.jl | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index fc7a05587..3fb68dd0d 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -130,9 +130,8 @@ A_cpu = rand(ComplexF64, 20, 20) A_cpu = A_cpu + A_cpu' b_cpu = rand(ComplexF64, 20) -A = A + A' -A_gpu = ROCMatrix(A) -b_gpu = ROCVector(b) +A_gpu = ROCMatrix(A_cpu) +b_gpu = ROCVector(b_cpu) # Solve a dense hermitian system on an AMD GPU x, stats = minres(A_gpu, b_gpu) @@ -178,7 +177,7 @@ using Krylov, Metal T = Float32 # Metal.jl also works with ComplexF32 n = 10 -n = 20 +m = 20 # CPU Arrays A_cpu = rand(T, n, m) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index c161b1900..c5dbdd0af 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -10,9 +10,8 @@ using Krylov, AMDGPU A_cpu = rand(ComplexF64, 20, 20) A_cpu = A_cpu + A_cpu' b_cpu = rand(ComplexF64, 20) - A = A + A' - A_gpu = ROCMatrix(A) - b_gpu = ROCVector(b) + A_gpu = ROCMatrix(A_cpu) + b_gpu = ROCVector(b_cpu) x, stats = minres(A_gpu, b_gpu) end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index a4ce46922..9e7f101d1 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -19,7 +19,7 @@ end @testset "documentation" begin T = Float32 n = 10 - n = 20 + m = 20 A_cpu = rand(T, n, m) b_cpu = rand(T, n) A_gpu = MtlMatrix(A_cpu) From b5c2ea97358fdc92d266ecee2280eb1ed269ff42 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:43:47 -0400 Subject: [PATCH 025/182] Apply suggestions from code review Co-authored-by: Dominique --- docs/src/gpu.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 3fb68dd0d..4ce8ee448 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -1,15 +1,15 @@ # [GPU support](@id gpu) -Krylov methods are well suited for GPU computations because they only require matrix-vector products ($u \leftarrow Av$, $u \leftarrow A^{H}w$) and vector operations ($\|v\|$, $u^H v$, $v \leftarrow \alpha u + \beta v$), which are easily parallelizable. +Krylov methods are well suited for GPU computations because they only require matrix-vector products ($u \leftarrow Av$, $u \leftarrow A^{H}w$) and vector operations ($\|v\|$, $u^H v$, $v \leftarrow \alpha u + \beta v$), which are highly parallelizable. The implementations in Krylov.jl are generic so as to take advantage of the multiple dispatch and broadcast features of Julia. -It allows the implementations to be specialized automatically by the compiler for both CPU and GPU usages. +Those allow the implementations to be specialized automatically by the compiler for both CPU and GPU. Thus, Krylov.jl works with GPU backends that build on [GPUArrays.jl](https://github.com/JuliaGPU/GPUArrays.jl), such as [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl), [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl), [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) or [Metal.jl](https://github.com/JuliaGPU/Metal.jl). ## Nvidia GPUs -All solvers in Krylov.jl can be used with [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) and allow computations with Nvidia GPUs. -Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`CuMatrix` and `CuVector`). +All solvers in Krylov.jl can be used with [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) and allow computations on Nvidia GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to the related GPU format (`CuMatrix` and `CuVector`). ```julia using CUDA, Krylov @@ -22,7 +22,7 @@ b_cpu = rand(20) A_gpu = CuMatrix(A_cpu) b_gpu = CuVector(b_cpu) -# Solve a square and dense system on a Nivida GPU +# Solve a square and dense system on an Nivida GPU x, stats = bilq(A_gpu, b_gpu) ``` @@ -119,8 +119,8 @@ x, stats = bicgstab(A_gpu, b_gpu, M=opM) ## AMD GPUs -All solvers in Krylov.jl can be used with [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) and allow computations with AMD GPUs. -Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`ROCMatrix` and `ROCVector`). +All solvers in Krylov.jl can be used with [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) and allow computations on AMD GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to the related GPU format (`ROCMatrix` and `ROCVector`). ```julia using Krylov, AMDGPU @@ -133,7 +133,7 @@ b_cpu = rand(ComplexF64, 20) A_gpu = ROCMatrix(A_cpu) b_gpu = ROCVector(b_cpu) -# Solve a dense hermitian system on an AMD GPU +# Solve a dense Hermitian system on an AMD GPU x, stats = minres(A_gpu, b_gpu) ``` @@ -142,8 +142,8 @@ x, stats = minres(A_gpu, b_gpu) ## Intel GPUs -All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) and allow computations with Intel GPUs. -Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`oneMatrix` and `oneVector`). +All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) and allow computations on Intel GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to the related GPU format (`oneMatrix` and `oneVector`). ```julia using Krylov, oneAPI @@ -169,8 +169,8 @@ x, stats = lsqr(A_gpu, b_gpu) ## Apple M1 GPUs -All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [Metal.jl](https://github.com/JuliaGPU/Metal.jl) and allow computations with Apple M1 GPUs. -Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to related GPU format (`MtlMatrix` and `MtlVector`). +All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [Metal.jl](https://github.com/JuliaGPU/Metal.jl) and allow computations on Apple M1 GPUs. +Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to the related GPU format (`MtlMatrix` and `MtlVector`). ```julia using Krylov, Metal From bcbc6aa07f5752067ce1552ebd499588907ac9d6 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 13 Sep 2022 19:14:11 -0400 Subject: [PATCH 026/182] [GPU] Remove random tests --- test/gpu/amd.jl | 21 +++++++++++++-------- test/gpu/intel.jl | 23 ++++++++++++++--------- test/gpu/metal.jl | 21 +++++++++++++-------- test/gpu/nvidia.jl | 21 +++++++++++++-------- 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index c5dbdd0af..03ada1d4d 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -1,6 +1,8 @@ using LinearAlgebra, SparseArrays, Test using Krylov, AMDGPU +include("../test_utils.jl") + @testset "AMD -- AMDGPU.jl" begin @test AMDGPU.functional() @@ -67,20 +69,23 @@ using Krylov, AMDGPU # end ε = eps(T) - A = rand(FC, n, n) - A = ROCMatrix{FC}(A) - b = rand(FC, n) - b = ROCVector{FC}(b) + atol = √ε + rtol = √ε @testset "GMRES -- $FC" begin + A, b = nonsymmetric_indefinite(FC=FC) + A = ROCMatrix{FC}(A) + b = ROCVector{FC}(b) x, stats = gmres(A, b) - @test norm(b - A * x) ≤ √ε + @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin - C = A * A' - x, stats = cg(C, b) - @test stats.solved + A, b = symmetric_definite(FC=FC) + A = ROCMatrix{FC}(A) + b = ROCVector{FC}(b) + x, stats = cg(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) end end end diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index 12d9232fd..e6826e9e9 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -1,8 +1,10 @@ using LinearAlgebra, SparseArrays, Test using Krylov, oneAPI -# https://github.com/JuliaGPU/GPUArrays.jl/pull/427 +include("../test_utils.jl") + import Krylov.kdot +# https://github.com/JuliaGPU/GPUArrays.jl/pull/427 function kdot(n :: Integer, x :: oneVector{T}, dx :: Integer, y :: oneVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex return mapreduce(dot, +, x, y) end @@ -75,20 +77,23 @@ end # end ε = eps(T) - A = rand(FC, n, n) - A = oneMatrix{FC}(A) - b = rand(FC, n) - b = oneVector{FC}(b) + atol = √ε + rtol = √ε @testset "GMRES -- $FC" begin + A, b = nonsymmetric_indefinite(FC=FC) + A = oneMatrix{FC}(A) + b = oneVector{FC}(b) x, stats = gmres(A, b) - @test norm(b - A * x) ≤ √ε + @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin - C = A * A' - x, stats = cg(C, b) - @test stats.solved + A, b = symmetric_definite(FC=FC) + A = oneMatrix{FC}(A) + b = oneVector{FC}(b) + x, stats = cg(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) end end end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 9e7f101d1..774ccc10c 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -1,6 +1,8 @@ using LinearAlgebra, SparseArrays, Test using Krylov, Metal +include("../test_utils.jl") + # https://github.com/JuliaGPU/Metal.jl/pull/48 const MtlVector{T} = MtlArray{T,1} const MtlMatrix{T} = MtlArray{T,2} @@ -79,20 +81,23 @@ end # end ε = eps(T) - A = rand(FC, n, n) - A = MtlMatrix{FC}(A) - b = rand(FC, n) - b = MtlVector{FC}(b) + atol = √ε + rtol = √ε @testset "GMRES -- $FC" begin + A, b = nonsymmetric_indefinite(FC=FC) + A = MtlMatrix{FC}(A) + b = MtlVector{FC}(b) x, stats = gmres(A, b) - @test norm(b - A * x) ≤ √ε + @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin - C = A * A' - x, stats = cg(C, b) - @test stats.solved + A, b = symmetric_definite(FC=FC) + A = MtlMatrix{FC}(A) + b = MtlVector{FC}(b) + x, stats = cg(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) end end end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 33e0a4ba8..8dfb61b0f 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -3,6 +3,8 @@ using LinearOperators, Krylov, CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER include("../test_utils.jl") +include("../test_utils.jl") + @testset "Nvidia -- CUDA.jl" begin @test CUDA.functional() @@ -145,20 +147,23 @@ include("../test_utils.jl") end ε = eps(T) - A = rand(FC, n, n) - A = CuMatrix{FC}(A) - b = rand(FC, n) - b = CuVector{FC}(b) + atol = √ε + rtol = √ε @testset "GMRES -- $FC" begin + A, b = nonsymmetric_indefinite(FC=FC) + A = CuMatrix{FC}(A) + b = CuVector{FC}(b) x, stats = gmres(A, b) - @test norm(b - A * x) ≤ √ε + @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin - C = A * A' - x, stats = cg(C, b) - @test stats.solved + A, b = symmetric_definite(FC=FC) + A = CuMatrix{FC}(A) + b = CuVector{FC}(b) + x, stats = cg(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) end end end From db3520fca3583dc0ab877490fdd220381766bd05 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 15 Sep 2022 14:38:24 -0400 Subject: [PATCH 027/182] Add more examples with the preconditioners --- docs/src/preconditioners.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 133020dc0..e37ab378a 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -172,3 +172,38 @@ using ILUZero, Krylov Pᵣ = ilu0(A) x, stats = bicgstab(A, b, N=Pᵣ, ldiv=true) # right preconditioning ``` + +```julia +using LDLFactorizations, Krylov + +M = ldl(E) +N = ldl(F) + +# [E A] [x] = [b] +# [Aᴴ F] [y] [c] +x, y, stats = tricg(A, b, c, M=M, N=N, ldiv=true) +``` + +```julia +using SuiteSparse, Krylov +import LinearAlgebra.ldiv! + +M = cholesky(E) + +# ldiv! is not implemented for the sparse Cholesky factorization (SuiteSparse.CHOLMOD) +ldiv!(y::Vector{T}, F::SuiteSparse.CHOLMOD.Factor{T}, x::Vector{T}) where T = (y .= F \ x) + +# [E A] [x] = [b] +# [Aᴴ 0] [y] [c] +x, y, stats = trimr(A, b, c, M=M, sp=true, ldiv=true) +``` + +```julia +using Krylov + +C = lu(M) + +# [M A] [x] = [b] +# [B 0] [y] [c] +x, y, stats = gpmr(A, B, b, c, C=C, gsp=true, ldiv=true) +``` From 6208825cf5dd146b9033b17a39087c173234c348 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Thu, 15 Sep 2022 14:41:30 -0400 Subject: [PATCH 028/182] Update docs/src/preconditioners.md --- docs/src/preconditioners.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index e37ab378a..6e2039634 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -179,8 +179,8 @@ using LDLFactorizations, Krylov M = ldl(E) N = ldl(F) -# [E A] [x] = [b] -# [Aᴴ F] [y] [c] +# [E A] [x] = [b] +# [Aᴴ -F] [y] [c] x, y, stats = tricg(A, b, c, M=M, N=N, ldiv=true) ``` From 66e1fd140f1963089cc7e0a5a0e11cef9b41c6ab Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 15 Sep 2022 14:59:32 -0400 Subject: [PATCH 029/182] Release 0.8.4 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a91e07b8a..74005745f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Krylov" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.8.3" +version = "0.8.4" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 794483b8f92934e81a9efe352239b9e7b7cf057e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 12 Sep 2022 16:32:11 -0400 Subject: [PATCH 030/182] [documentation] update callbacks.md --- docs/src/callbacks.md | 71 +++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index f44018687..2fd69c768 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -1,6 +1,7 @@ -## Callbacks +# [Callbacks](@id callbacks) -Each Krylov method is able to call a callback function as `callback(solver)` at each iteration. The callback should return `true` if the main loop should terminate, and `false` otherwise. +Each Krylov method is able to call a callback function as `callback(solver)` at each iteration. +The callback should return `true` if the main loop should terminate, and `false` otherwise. If the method terminated because of the callback, the output status will be `"user-requested exit"`. For example, if the user defines `my_callback(solver::MinresSolver)`, it can be passed to the solver using @@ -11,33 +12,71 @@ For example, if the user defines `my_callback(solver::MinresSolver)`, it can be If you need to write a callback that uses variables that are not in the `MinresSolver`, use a closure: ```julia -function my_callback2(solver::MinresSolver, A, b, storage_vec, tol::Float64) - mul!(storage_vec, A, solver.x) - storage_vec .-= b - return norm(storage_vec) ≤ tol # tolerance based on the 2-norm of the residual +function my_callback2(solver::MinresSolver, A, b, r, tol) + mul!(r, A, solver.x) + r .-= b # r := b - Ax + bool = norm(r) ≤ tol # tolerance based on the 2-norm of the residual + return bool end -storage_vec = similar(b) -(x, stats) = minres(A, b, callback = solver -> my_callback2(solver, A, b, storage_vec, 0.1)) +r = similar(b) +(x, stats) = minres(A, b, callback = solver -> my_callback2(solver, A, b, r, 1e-6)) ``` Alternatively, use a structure and make it callable: ```julia -mutable struct MyCallback3{S, M} +mutable struct my_callback3{S, M} A::M b::S - storage_vec::S + r::S tol::Float64 end -MyCallback3(A, b; tol = 0.1) = MyCallback3(A, b, similar(b), tol) -function (my_cb::MyCallback3)(solver) - mul!(my_cb.storage_vec, my_cb.A, solver.x) - my_cb.storage_vec .-= my_cb.b - return norm(my_cb.storage_vec) ≤ my_cb.tol # tolerance based on the 2-norm of the residual +my_callback3(A, b; tol=1e-6) = my_callback3(A, b, similar(b), tol) # Outer constructor + +function (my_cb::my_callback3)(solver) + mul!(my_cb.r, my_cb.A, solver.x) + my_cb.r .-= my_cb.b + bool = norm(my_cb.r) ≤ my_cb.tol + return bool end -my_cb = MyCallback3(A, b; tol = 0.1) +my_cb = my_callback3(A, b) (x, stats) = minres(A, b, callback = my_cb) ``` + +Although the main goal of a callback is to add new stopping conditions, it can also retrieve informations from the workspace of a Krylov method along the iterations. +We now illustrate how to store all iterates $x_k$ of the GMRES method. + +```julia +S = Krylov.ktypeof(b) +global X = S[] # Storage for GMRES iterates + +function gmres_callback(solver) + z = solver.z + k = solver.inner_iter + nr = sum(1:k) + V = solver.V + R = solver.R + y = copy(z) + + # Solve Rk * yk = zk + for i = k : -1 : 1 + pos = nr + i - k + for j = k : -1 : i+1 + y[i] = y[i] - R[pos] * y[j] + pos = pos - j + 1 + end + y[i] = y[i] / R[pos] + end + + # xk = Vk * yk + xk = sum(V[i] * y[i] for i = 1:k) + push!(X, xk) + + return false # We don't want to add new stopping conditions +end + +(x, stats) = gmres(A, b, callback = gmres_callback) +``` From b0d20791c5e3e3fa21de0353cba0772cd29adf58 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Fri, 16 Sep 2022 11:33:56 -0400 Subject: [PATCH 031/182] Update docs/src/callbacks.md Co-authored-by: Dominique --- docs/src/callbacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index 2fd69c768..552f7a1c4 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -46,7 +46,7 @@ my_cb = my_callback3(A, b) (x, stats) = minres(A, b, callback = my_cb) ``` -Although the main goal of a callback is to add new stopping conditions, it can also retrieve informations from the workspace of a Krylov method along the iterations. +Although the main goal of a callback is to add new stopping conditions, it can also retrieve information from the workspace of a Krylov method along the iterations. We now illustrate how to store all iterates $x_k$ of the GMRES method. ```julia From 212a266edb30b182fd1f185dc712990ac6c937cb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 21 Sep 2022 19:16:56 -0400 Subject: [PATCH 032/182] Update callbacks.md --- docs/src/callbacks.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index 552f7a1c4..91e0b521c 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -3,47 +3,45 @@ Each Krylov method is able to call a callback function as `callback(solver)` at each iteration. The callback should return `true` if the main loop should terminate, and `false` otherwise. If the method terminated because of the callback, the output status will be `"user-requested exit"`. -For example, if the user defines `my_callback(solver::MinresSolver)`, it can be passed to the solver using +For example, if the user defines `minres_callback(solver::MinresSolver)`, it can be passed to the solver using ```julia -(x, stats) = minres(A, b, callback = my_callback) +(x, stats) = minres(A, b, callback = minres_callback) ``` -If you need to write a callback that uses variables that are not in the `MinresSolver`, use a closure: +If you need to write a callback that uses variables that are not in a `KrylovSolver`, use a closure: ```julia -function my_callback2(solver::MinresSolver, A, b, r, tol) +function custom_stopping_condition(solver::KrylovSolver, A, b, r, tol) mul!(r, A, solver.x) r .-= b # r := b - Ax bool = norm(r) ≤ tol # tolerance based on the 2-norm of the residual return bool end -r = similar(b) -(x, stats) = minres(A, b, callback = solver -> my_callback2(solver, A, b, r, 1e-6)) +cg_callback(solver) = custom_stopping_condition(solver, A, b, r, tol) +(x, stats) = cg(A, b, callback = cg_callback) ``` Alternatively, use a structure and make it callable: ```julia -mutable struct my_callback3{S, M} - A::M - b::S - r::S - tol::Float64 +mutable struct CallbackWorkspace{T} + A::Matrix{T} + b::Vector{T} + r::Vector{T} + tol::T end -my_callback3(A, b; tol=1e-6) = my_callback3(A, b, similar(b), tol) # Outer constructor - -function (my_cb::my_callback3)(solver) - mul!(my_cb.r, my_cb.A, solver.x) - my_cb.r .-= my_cb.b - bool = norm(my_cb.r) ≤ my_cb.tol +function (workspace::CallbackWorkspace)(solver::KrylovSolver) + mul!(workspace.r, workspace.A, solver.x) + workspace.r .-= workspace.b + bool = norm(workspace.r) ≤ workspace.tol return bool end -my_cb = my_callback3(A, b) -(x, stats) = minres(A, b, callback = my_cb) +bicgstab_callback = CallbackWorkspace(A, b, r, tol) +(x, stats) = bicgstab(A, b, callback = bicgstab_callback) ``` Although the main goal of a callback is to add new stopping conditions, it can also retrieve information from the workspace of a Krylov method along the iterations. From 611b3c2632c40e6e77fcc4da4cbe4abc32c79246 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 23 Sep 2022 10:07:18 -0400 Subject: [PATCH 033/182] Add vector_to_matrix function --- src/krylov_utils.jl | 18 ++++++++++++++++++ test/gpu/amd.jl | 6 ++++++ test/gpu/intel.jl | 6 ++++++ test/gpu/metal.jl | 6 ++++++ test/gpu/nvidia.jl | 8 ++++++-- test/test_aux.jl | 9 +++++++++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 46c9d6cd6..b16da57c0 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -247,6 +247,24 @@ function ktypeof(v::S) where S <: SubArray return ktypeof(v.parent) end +""" + M = vector_to_matrix(S) + +Return the dense matrix storage type `M` related to the dense vector storage type `S`. +""" +function vector_to_matrix(::Type{S}) where S <: DenseVector + V = hasproperty(S, :body) ? S.body : S + par = V.parameters + npar = length(par) + (2 ≤ npar ≤ 3) || error("Type $S is not supported.") + if npar == 2 + M = V.name.wrapper{par[1], 2} + else + M = V.name.wrapper{par[1], 2, par[3]} + end + return M +end + """ v = kzeros(S, n) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index 03ada1d4d..baad2bdcf 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -68,6 +68,12 @@ include("../test_utils.jl") # Krylov.@kref!(n, x, y, c, s) # end + @testset "vector_to_matrix" begin + S = ROCVector{FC} + M = Krylov.vector_to_matrix(S) + @test M == ROCMatrix{FC} + end + ε = eps(T) atol = √ε rtol = √ε diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index e6826e9e9..67ad0a7d5 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -76,6 +76,12 @@ end # Krylov.@kref!(n, x, y, c, s) # end + @testset "vector_to_matrix" begin + S = oneVector{FC} + M = Krylov.vector_to_matrix(S) + @test M == oneMatrix{FC} + end + ε = eps(T) atol = √ε rtol = √ε diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 774ccc10c..35325c863 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -80,6 +80,12 @@ end # Krylov.@kref!(n, x, y, c, s) # end + @testset "vector_to_matrix" begin + S = MtlVector{FC} + M = Krylov.vector_to_matrix(S) + @test M == MtlMatrix{FC} + end + ε = eps(T) atol = √ε rtol = √ε diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 8dfb61b0f..8faed479a 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -3,8 +3,6 @@ using LinearOperators, Krylov, CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER include("../test_utils.jl") -include("../test_utils.jl") - @testset "Nvidia -- CUDA.jl" begin @test CUDA.functional() @@ -146,6 +144,12 @@ include("../test_utils.jl") Krylov.@kref!(n, x, y, c, s) end + @testset "vector_to_matrix" begin + S = CuVector{FC} + M = Krylov.vector_to_matrix(S) + @test M == CuMatrix{FC} + end + ε = eps(T) atol = √ε rtol = √ε diff --git a/test/test_aux.jl b/test/test_aux.jl index 5a4d094c7..5ac2b401c 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -134,6 +134,15 @@ @test Krylov.ktypeof(b) == Vector{Float64} end + @testset "vector_to_matrix" begin + # test vector_to_matrix + for FC in (Float32, Float64, ComplexF32, ComplexF64) + S = Vector{FC} + M = Krylov.vector_to_matrix(S) + @test M == Matrix{FC} + end + end + @testset "macros" begin # test macros for FC ∈ (Float16, Float32, Float64, ComplexF16, ComplexF32, ComplexF64) From a01a78d68cca7e3f694fe67438ef3377eeedc185 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 23 Sep 2022 10:12:21 -0400 Subject: [PATCH 034/182] Update buildkite pipeline --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 963eb619b..73121253c 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -22,7 +22,7 @@ steps: agents: queue: "juliagpu" rocm: "*" - rocmgpu: "gfx908" + rocmgpu: "*" env: JULIA_AMDGPU_CORE_MUST_LOAD: "1" JULIA_AMDGPU_HIP_MUST_LOAD: "1" From 4af9334c95683904bdd271c008cbd93016db8a8b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 23 Sep 2022 10:18:14 -0400 Subject: [PATCH 035/182] Update api.md --- docs/src/api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/api.md b/docs/src/api.md index 7f2f4dff7..bf3d5c783 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -60,4 +60,5 @@ Krylov.vec2str Krylov.ktypeof Krylov.kzeros Krylov.kones +Krylov.vector_to_matrix ``` From 2fe79648713e12b59d417ced877398e85edfd5a8 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 20 Sep 2022 21:40:35 -0400 Subject: [PATCH 036/182] An implementation of FGMRES --- docs/src/api.md | 1 + docs/src/inplace.md | 2 +- docs/src/preconditioners.md | 2 +- docs/src/solvers/unsymmetric.md | 7 + src/Krylov.jl | 1 + src/fgmres.jl | 332 ++++++++++++++++++++++++++++++++ src/krylov_solvers.jl | 56 +++++- test/runtests.jl | 1 + test/test_allocations.jl | 20 ++ test/test_fgmres.jl | 145 ++++++++++++++ test/test_mp.jl | 2 +- test/test_solvers.jl | 36 ++++ test/test_utils.jl | 21 ++ test/test_warm_start.jl | 5 + 14 files changed, 627 insertions(+), 4 deletions(-) create mode 100644 src/fgmres.jl create mode 100644 test/test_fgmres.jl diff --git a/docs/src/api.md b/docs/src/api.md index bf3d5c783..bad8b9245 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -48,6 +48,7 @@ LnlqSolver CraigSolver CraigmrSolver GpmrSolver +FgmresSolver ``` ## Utilities diff --git a/docs/src/inplace.md b/docs/src/inplace.md index 71a4e25de..9950575fe 100644 --- a/docs/src/inplace.md +++ b/docs/src/inplace.md @@ -15,7 +15,7 @@ Given an operator `A` and a right-hand side `b`, you can create a `KrylovSolver` For example, use `S = Vector{Float64}` if you want to solve linear systems in double precision on the CPU and `S = CuVector{Float32}` if you want to solve linear systems in single precision on an Nvidia GPU. !!! note - `DiomSolver`, `FomSolver`, `DqgmresSolver`, `GmresSolver`, `GpmrSolver` and `CgLanczosShiftSolver` require an additional argument (`memory` or `nshifts`). + `DiomSolver`, `FomSolver`, `DqgmresSolver`, `GmresSolver`, `FgmresSolver`, `GpmrSolver` and `CgLanczosShiftSolver` require an additional argument (`memory` or `nshifts`). The workspace is always the first argument of the in-place methods: diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 6e2039634..60258868b 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -29,7 +29,7 @@ Krylov.jl supports both approaches thanks to the argument `ldiv` of the Krylov s ### Square non-Hermitian linear systems -Methods concerned: [`CGS`](@ref cgs), [`BiCGSTAB`](@ref bicgstab), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres), [`DIOM`](@ref diom) and [`FOM`](@ref fom). +Methods concerned: [`CGS`](@ref cgs), [`BiCGSTAB`](@ref bicgstab), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres), [`FGMRES`](@ref fgmres), [`DIOM`](@ref diom) and [`FOM`](@ref fom). A Krylov method dedicated to non-Hermitian linear systems allows the three variants of preconditioning. diff --git a/docs/src/solvers/unsymmetric.md b/docs/src/solvers/unsymmetric.md index 280908ea5..e559145a2 100644 --- a/docs/src/solvers/unsymmetric.md +++ b/docs/src/solvers/unsymmetric.md @@ -71,3 +71,10 @@ dqgmres! gmres gmres! ``` + +## FGMRES + +```@docs +fgmres +fgmres! +``` diff --git a/src/Krylov.jl b/src/Krylov.jl index b714ccd79..7c480896f 100644 --- a/src/Krylov.jl +++ b/src/Krylov.jl @@ -19,6 +19,7 @@ include("diom.jl") include("fom.jl") include("dqgmres.jl") include("gmres.jl") +include("fgmres.jl") include("gpmr.jl") diff --git a/src/fgmres.jl b/src/fgmres.jl new file mode 100644 index 000000000..eb6ced660 --- /dev/null +++ b/src/fgmres.jl @@ -0,0 +1,332 @@ +# An implementation of FGMRES for the solution of the square linear system Ax = b. +# +# This method is described in +# +# Y. Saad, A Flexible Inner-Outer Preconditioned GMRES Algorithms. +# SIAM Journal on Scientific Computing, Vol. 14(2), pp. 461--469, 1993. +# +# Alexis Montoison, +# Montreal, September 2022. + +export fgmres, fgmres! + +""" + (x, stats) = fgmres(A, b::AbstractVector{FC}; memory::Int=20, + M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), + reorthogonalization::Bool=false, itmax::Int=0, + restart::Bool=false, verbose::Int=0, history::Bool=false, + ldiv::Bool=false, callback=solver->false) + +`T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. +`FC` is `T` or `Complex{T}`. + +Solve the linear system Ax = b using FGMRES method. + +FGMRES computes a sequence of approximate solutions with the minimal residual property. +FGMRES is a variant of GMRES that allows changes in the right preconditioning at every step. + +This implementation allows a left preconditioner M and a flexible right preconditioner N. +A situation in which the preconditioner is "not constant" is when a relaxation-type method, +a Chebyshev iteration or another Krylov subspace method is used as a preconditioner. +Compared to GMRES, there is no additional cost incurred in the arithmetic but the memory requirement almost doubles. + +Full reorthogonalization is available with the `reorthogonalization` option. + +If `restart = true`, the restarted version FGMRES(k) is used with `k = memory`. +If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. +More storage will be allocated only if the number of iterations exceed `memory`. + +FGMRES can be warm-started from an initial guess `x0` with the method + + (x, stats) = fgmres(A, b, x0; kwargs...) + +where `kwargs` are the same keyword arguments as above. + +The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, +and `false` otherwise. + +#### Reference + +* Y. Saad, [*A Flexible Inner-Outer Preconditioned GMRES Algorithm*](https://doi.org/10.1137/0914028), SIAM Journal on Scientific Computing, Vol. 14(2), pp. 461--469, 1993. +""" +function fgmres end + +function fgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex + solver = FgmresSolver(A, b, memory) + fgmres!(solver, A, b, x0; kwargs...) + return (solver.x, solver.stats) +end + +function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex + solver = FgmresSolver(A, b, memory) + fgmres!(solver, A, b; kwargs...) + return (solver.x, solver.stats) +end + +""" + solver = fgmres!(solver::FgmresSolver, A, b; kwargs...) + solver = fgmres!(solver::FgmresSolver, A, b, x0; kwargs...) + +where `kwargs` are keyword arguments of [`fgmres`](@ref). + +Note that the `memory` keyword argument is the only exception. +It's required to create a `FgmresSolver` and can't be changed later. + +See [`FgmresSolver`](@ref) for more details about the `solver`. +""" +function fgmres! end + +function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + warm_start!(solver, x0) + fgmres!(solver, A, b; kwargs...) + return solver +end + +function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; + M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), + reorthogonalization :: Bool=false, itmax :: Int=0, + restart :: Bool=false, verbose :: Int=0, history :: Bool=false, + ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + + m, n = size(A) + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf("FGMRES: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) == S || error("ktypeof(b) ≠ $S") + + # Set up workspace. + allocate_if(!MisI , solver, :q , S, n) + allocate_if(restart, solver, :Δx, S, n) + Δx, x, w, V, Z = solver.Δx, solver.x, solver.w, solver.V, solver.Z + z, c, s, R, stats = solver.z, solver.c, solver.s, solver.R, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + q = MisI ? w : solver.q + r₀ = MisI ? w : solver.q + xr = restart ? Δx : x + + # Initial solution x₀. + x .= zero(FC) + + # Initial residual r₀. + if warm_start + mul!(w, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), w) + restart && @kaxpy!(n, one(FC), Δx, x) + else + w .= b + end + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M⁻¹(b - Ax₀) + β = @knrm2(n, r₀) # β = ‖r₀‖₂ + + rNorm = β + history && push!(rNorms, β) + ε = atol + rtol * rNorm + + if β == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end + + mem = length(c) # Memory + npass = 0 # Number of pass + + iter = 0 # Cumulative number of iterations + inner_iter = 0 # Number of iterations in a pass + + itmax == 0 && (itmax = 2*n) + inner_itmax = itmax + + (verbose > 0) && @printf("%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf("%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) + + # Stopping criterion + breakdown = false + inconsistent = false + solved = rNorm ≤ ε + tired = iter ≥ itmax + inner_tired = inner_iter ≥ inner_itmax + status = "unknown" + user_requested_exit = false + + while !(solved || tired || breakdown || user_requested_exit) + + # Initialize workspace. + nr = 0 # Number of coefficients stored in Rₖ. + for i = 1 : mem + V[i] .= zero(FC) # Orthogonal basis of {Mr₀, MANₖr₀, ..., (MANₖ)ᵏ⁻¹r₀}. + Z[i] .= zero(FC) # Z = [N₁v₁, ..., Nₖvₖ] + end + s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. + c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. + R .= zero(FC) # Upper triangular matrix Rₖ. + z .= zero(FC) # Right-hand of the least squares problem min ‖Hₖ₊₁.ₖyₖ - βe₁‖₂. + + if restart + xr .= zero(FC) # xr === Δx when restart is set to true + if npass ≥ 1 + mul!(w, A, x) + @kaxpby!(n, one(FC), b, -one(FC), w) + MisI || mulorldiv!(r₀, M, w, ldiv) + end + end + + # Initial ζ₁ and V₁ + β = @knrm2(n, r₀) + z[1] = β + @. V[1] = r₀ / rNorm + + npass = npass + 1 + solver.inner_iter = 0 + inner_tired = false + + while !(solved || inner_tired || breakdown || user_requested_exit) + + # Update iteration index + solver.inner_iter = solver.inner_iter + 1 + inner_iter = solver.inner_iter + + # Update workspace if more storage is required and restart is set to false + if !restart && (inner_iter > mem) + for i = 1 : inner_iter + push!(R, zero(FC)) + end + push!(s, zero(FC)) + push!(c, zero(T)) + push!(Z, S(undef, n)) + end + + # Continue the process. + # MAZₖ = Vₖ₊₁Hₖ₊₁.ₖ + mulorldiv!(Z[inner_iter], N, V[inner_iter], ldiv) # zₖ ← Nₖvₖ + mul!(w, A, Z[inner_iter]) # w ← Azₖ + MisI || mulorldiv!(q, M, w, ldiv) # q ← MAzₖ + for i = 1 : inner_iter + R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq + @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ + end + + # Reorthogonalization of the basis. + if reorthogonalization + for i = 1 : inner_iter + Htmp = @kdot(n, V[i], q) + R[nr+i] += Htmp + @kaxpy!(n, -Htmp, V[i], q) + end + end + + # Compute hₖ₊₁.ₖ + Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + + # Update the QR factorization of Hₖ₊₁.ₖ. + # Apply previous Givens reflections Ωᵢ. + # [cᵢ sᵢ] [ r̄ᵢ.ₖ ] = [ rᵢ.ₖ ] + # [s̄ᵢ -cᵢ] [rᵢ₊₁.ₖ] [r̄ᵢ₊₁.ₖ] + for i = 1 : inner_iter-1 + Rtmp = c[i] * R[nr+i] + s[i] * R[nr+i+1] + R[nr+i+1] = conj(s[i]) * R[nr+i] - c[i] * R[nr+i+1] + R[nr+i] = Rtmp + end + + # Compute and apply current Givens reflection Ωₖ. + # [cₖ sₖ] [ r̄ₖ.ₖ ] = [rₖ.ₖ] + # [s̄ₖ -cₖ] [hₖ₊₁.ₖ] [ 0 ] + (c[inner_iter], s[inner_iter], R[nr+inner_iter]) = sym_givens(R[nr+inner_iter], Hbis) + + # Update zₖ = (Qₖ)ᴴβe₁ + ζₖ₊₁ = conj(s[inner_iter]) * z[inner_iter] + z[inner_iter] = c[inner_iter] * z[inner_iter] + + # Update residual norm estimate. + # ‖ M⁻¹(b - Axₖ) ‖₂ = |ζₖ₊₁| + rNorm = abs(ζₖ₊₁) + history && push!(rNorms, rNorm) + + # Update the number of coefficients in Rₖ + nr = nr + inner_iter + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + resid_decrease_lim = rNorm ≤ ε + breakdown = Hbis ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax + solver.inner_iter = inner_iter + kdisplay(iter+inner_iter, verbose) && @printf("%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + + # Compute vₖ₊₁ + if !(solved || inner_tired || breakdown) + if !restart && (inner_iter ≥ mem) + push!(V, S(undef, n)) + push!(z, zero(FC)) + end + @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q + z[inner_iter+1] = ζₖ₊₁ + end + + user_requested_exit = callback(solver) :: Bool + end + + # Compute y by solving Ry = z with backward substitution. + y = z # yᵢ = ζᵢ + for i = inner_iter : -1 : 1 + pos = nr + i - inner_iter # position of rᵢ.ₖ + for j = inner_iter : -1 : i+1 + y[i] = y[i] - R[pos] * y[j] # yᵢ ← yᵢ - rᵢⱼyⱼ + pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + end + # Rₖ can be singular if the system is inconsistent + if abs(R[pos]) ≤ btol + y[i] = zero(FC) + inconsistent = true + else + y[i] = y[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ + end + end + + # Form xₖ = N₁v₁y₁ + ... + Nₖvₖyₖ = z₁y₁ + ... + zₖyₖ + for i = 1 : inner_iter + @kaxpy!(n, y[i], Z[i], xr) + end + restart && @kaxpy!(n, one(FC), xr, x) + + # Update inner_itmax, iter and tired variables. + inner_itmax = inner_itmax - inner_iter + iter = iter + inner_iter + tired = iter ≥ itmax + end + (verbose > 0) && @printf("\n") + + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + inconsistent && (status = "found approximate least-squares solution") + user_requested_exit && (status = "user-requested exit") + + # Update x + warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver +end diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index abd0c7352..b37ccd575 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -3,7 +3,7 @@ CgLanczosShiftSolver, MinresQlpSolver, DqgmresSolver, DiomSolver, UsymlqSolver, UsymqrSolver, TricgSolver, TrimrSolver, TrilqrSolver, CgsSolver, BicgstabSolver, BilqSolver, QmrSolver, BilqrSolver, CglsSolver, CrlsSolver, CgneSolver, CrmrSolver, LslqSolver, LsqrSolver, LsmrSolver, LnlqSolver, CraigSolver, CraigmrSolver, -GmresSolver, FomSolver, GpmrSolver +GmresSolver, FomSolver, GpmrSolver, FgmresSolver export solve!, solution, nsolution, statistics, issolved, issolved_primal, issolved_dual, niterations, Aprod, Atprod, Bprod, warm_start! @@ -20,6 +20,7 @@ const KRYLOV_SOLVERS = Dict( :fom => :FomSolver , :dqgmres => :DqgmresSolver , :gmres => :GmresSolver , + :fgmres => :FgmresSolver , :gpmr => :GpmrSolver , :usymlq => :UsymlqSolver , :usymqr => :UsymqrSolver , @@ -1503,6 +1504,58 @@ function GmresSolver(A, b, memory = 20) GmresSolver(n, m, memory, S) end +""" +Type for storing the vectors required by the in-place version of FGMRES. + +The outer constructors + + solver = FgmresSolver(n, m, memory, S) + solver = FgmresSolver(A, b, memory = 20) + +may be used in order to create these vectors. +`memory` is set to `n` if the value given is larger than `n`. +""" +mutable struct FgmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} + Δx :: S + x :: S + w :: S + q :: S + V :: Vector{S} + Z :: Vector{S} + c :: Vector{T} + s :: Vector{FC} + z :: Vector{FC} + R :: Vector{FC} + warm_start :: Bool + inner_iter :: Int + stats :: SimpleStats{T} +end + +function FgmresSolver(n, m, memory, S) + memory = min(n, memory) + FC = eltype(S) + T = real(FC) + Δx = S(undef, 0) + x = S(undef, n) + w = S(undef, n) + q = S(undef, 0) + V = [S(undef, n) for i = 1 : memory] + Z = [S(undef, n) for i = 1 : memory] + c = Vector{T}(undef, memory) + s = Vector{FC}(undef, memory) + z = Vector{FC}(undef, memory) + R = Vector{FC}(undef, div(memory * (memory+1), 2)) + stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + solver = FgmresSolver{T,FC,S}(Δx, x, w, q, V, Z, c, s, z, R, false, 0, stats) + return solver +end + +function FgmresSolver(A, b, memory = 20) + n, m = size(A) + S = ktypeof(b) + FgmresSolver(n, m, memory, S) +end + """ Type for storing the vectors required by the in-place version of FOM. @@ -1704,6 +1757,7 @@ for (KS, fun, nsol, nA, nAt, warm_start) in [ (MinresQlpSolver , :minres_qlp! , 1, 1, 0, true ) (QmrSolver , :qmr! , 1, 1, 1, true ) (GmresSolver , :gmres! , 1, 1, 0, true ) + (FgmresSolver , :fgmres! , 1, 1, 0, true ) (FomSolver , :fom! , 1, 1, 0, true ) (GpmrSolver , :gpmr! , 2, 1, 0, true ) ] diff --git a/test/runtests.jl b/test/runtests.jl index 99ab25fda..75e8f0941 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,6 +5,7 @@ include("test_utils.jl") include("test_aux.jl") include("test_stats.jl") +include("test_fgmres.jl") include("test_gpmr.jl") include("test_fom.jl") include("test_gmres.jl") diff --git a/test/test_allocations.jl b/test/test_allocations.jl index 790fcc7a8..b29f11631 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -218,6 +218,26 @@ @test inplace_gmres_bytes == 0 end + @testset "FGMRES" begin + # FGMRES needs: + # - 2 n-vectors: x, w + # - 2 n*(mem)-matrix: V, Z + # - 3 mem-vectors: c, s, z + # - 1 (mem*(mem+1)/2)-vector: R + storage_fgmres(mem, n) = (2 * n) + (2 * n * mem) + (3 * mem) + (mem * (mem+1) / 2) + storage_fgmres_bytes(mem, n) = nbits * storage_fgmres(mem, n) + + expected_fgmres_bytes = storage_fgmres_bytes(mem, n) + fgmres(A, b, memory=mem) # warmup + actual_fgmres_bytes = @allocated fgmres(A, b, memory=mem) + @test expected_fgmres_bytes ≤ actual_fgmres_bytes ≤ 1.02 * expected_fgmres_bytes + + solver = FgmresSolver(A, b, mem) + fgmres!(solver, A, b) # warmup + inplace_fgmres_bytes = @allocated fgmres!(solver, A, b) + @test inplace_fgmres_bytes == 0 + end + @testset "CGS" begin # CGS needs: # 6 n-vectors: x, r, u, p, q, ts diff --git a/test/test_fgmres.jl b/test/test_fgmres.jl new file mode 100644 index 000000000..e640da46e --- /dev/null +++ b/test/test_fgmres.jl @@ -0,0 +1,145 @@ +@testset "fgmres" begin + fgmres_tol = 1.0e-6 + + for FC in (Float64, ComplexF64) + @testset "Data Type: $FC" begin + + # Symmetric and positive definite system. + A, b = symmetric_definite(FC=FC) + (x, stats) = fgmres(A, b) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Symmetric indefinite variant. + A, b = symmetric_indefinite(FC=FC) + (x, stats) = fgmres(A, b) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Nonsymmetric and positive definite systems. + A, b = nonsymmetric_definite(FC=FC) + (x, stats) = fgmres(A, b) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Nonsymmetric indefinite variant. + A, b = nonsymmetric_indefinite(FC=FC) + (x, stats) = fgmres(A, b) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Symmetric indefinite variant, almost singular. + A, b = almost_singular(FC=FC) + (x, stats) = fgmres(A, b) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ 100 * fgmres_tol) + @test(stats.solved) + + # Singular system. + A, b = square_inconsistent(FC=FC) + (x, stats) = fgmres(A, b) + r = b - A * x + Aresid = norm(A' * r) / norm(A' * b) + @test(Aresid ≤ fgmres_tol) + @test(stats.inconsistent) + + # Test b == 0 + A, b = zero_rhs(FC=FC) + (x, stats) = fgmres(A, b) + @test norm(x) == 0 + @test stats.status == "x = 0 is a zero-residual solution" + + # Poisson equation in polar coordinates. + A, b = polar_poisson(FC=FC) + (x, stats) = fgmres(A, b, reorthogonalization=true) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Left preconditioning + A, b, M = square_preconditioned(FC=FC) + (x, stats) = fgmres(A, b, M=M) + r = b - A * x + resid = norm(M * r) / norm(M * b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Right preconditioning + A, b, N = square_preconditioned(FC=FC) + (x, stats) = fgmres(A, b, N=N) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Split preconditioning + A, b, M, N = two_preconditioners(FC=FC) + (x, stats) = fgmres(A, b, M=M, N=N) + r = b - A * x + resid = norm(M * r) / norm(M * b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) + + # Restart + for restart ∈ (false, true) + memory = 10 + + A, b = sparse_laplacian(FC=FC) + (x, stats) = fgmres(A, b, restart=restart, memory=memory) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.niter > memory) + @test(stats.solved) + + M = Diagonal(1 ./ diag(A)) + (x, stats) = fgmres(A, b, M=M, restart=restart, memory=memory) + r = b - A * x + resid = norm(M * r) / norm(M * b) + @test(resid ≤ fgmres_tol) + @test(stats.niter > memory) + @test(stats.solved) + + N = Diagonal(1 ./ diag(A)) + (x, stats) = fgmres(A, b, N=N, restart=restart, memory=memory) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.niter > memory) + @test(stats.solved) + + N = Diagonal(1 ./ sqrt.(diag(A))) + N = Diagonal(1 ./ sqrt.(diag(A))) + (x, stats) = fgmres(A, b, M=M, N=N, restart=restart, memory=memory) + r = b - A * x + resid = norm(M * r) / norm(M * b) + @test(resid ≤ fgmres_tol) + @test(stats.niter > memory) + @test(stats.solved) + end + + # test callback function + # A, b = sparse_laplacian(FC=FC) + # solver = FgmresSolver(A, b) + # tol = 1.0e-1 + # N = Diagonal(1 ./ diag(A)) + # stor = StorageGetxRestartedGmres(solver, N = N) + # storage_vec = similar(b) + # fgmres!(solver, A, b, N = N, atol = 0.0, rtol = 0.0, restart = true, callback = solver -> restarted_fgmres_callback_n2(solver, A, b, stor, N, storage_vec, tol)) + # @test solver.stats.status == "user-requested exit" + # @test norm(A * x - b) ≤ tol + # + # @test_throws TypeError fgmres(A, b, callback = solver -> "string", history = true) + end + end +end diff --git a/test/test_mp.jl b/test/test_mp.jl index b7aa43d38..6b6d58450 100644 --- a/test/test_mp.jl +++ b/test/test_mp.jl @@ -3,7 +3,7 @@ for fn in (:cg, :cgls, :usymqr, :cgne, :cgs, :crmr, :cg_lanczos, :dqgmres, :diom, :cr, :gpmr, :lslq, :lsqr, :lsmr, :lnlq, :craig, :bicgstab, :craigmr, :crls, :symmlq, :minres, :bilq, :minres_qlp, :qmr, :usymlq, :tricg, :trimr, :trilqr, :bilqr, :gmres, :fom, - :cg_lanczos_shift) + :fgmres, :cg_lanczos_shift) for T in (Float16, Float32, Float64, BigFloat) for FC in (T, Complex{T}) A = spdiagm(-1 => -ones(FC,n-1), 0 => 3*ones(FC,n), 1 => -ones(FC,n-1)) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 6f60cb737..17b3edf0b 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -21,6 +21,7 @@ function test_solvers(FC) fom_solver = $(KRYLOV_SOLVERS[:fom])($n, $n, $mem, $S) dqgmres_solver = $(KRYLOV_SOLVERS[:dqgmres])($n, $n, $mem, $S) gmres_solver = $(KRYLOV_SOLVERS[:gmres])($n, $n, $mem, $S) + fgmres_solver = $(KRYLOV_SOLVERS[:fgmres])($n, $n, $mem, $S) cr_solver = $(KRYLOV_SOLVERS[:cr])($n, $n, $S) crmr_solver = $(KRYLOV_SOLVERS[:crmr])($m, $n, $S) cgs_solver = $(KRYLOV_SOLVERS[:cgs])($n, $n, $S) @@ -144,6 +145,16 @@ function test_solvers(FC) @test nsolution(solver) == 1 @test issolved(solver) + solver = solve!(fgmres_solver, A, b) + niter = niterations(solver) + @test niter > 0 + @test Aprod(solver) == niter + @test Atprod(solver) == 0 + @test statistics(solver) === solver.stats + @test solution(solver, 1) === solver.x + @test nsolution(solver) == 1 + @test issolved(solver) + solver = solve!(cr_solver, A, b) niter = niterations(solver) @test niter > 0 @@ -596,6 +607,31 @@ function test_solvers(FC) """ @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) + io = IOBuffer() + show(io, fgmres_solver, show_stats=false) + showed = String(take!(io)) + expected = """ + ┌────────────┬───────────────────┬─────────────────┐ + │FgmresSolver│ Precision: $FC │Architecture: CPU│ + ├────────────┼───────────────────┼─────────────────┤ + │ Attribute│ Type│ Size│ + ├────────────┼───────────────────┼─────────────────┤ + │ Δx│ Vector{$FC}│ 0│ + │ x│ Vector{$FC}│ 64│ + │ w│ Vector{$FC}│ 64│ + │ q│ Vector{$FC}│ 0│ + │ V│Vector{Vector{$FC}}│ 10 x 64│ + │ Z│Vector{Vector{$FC}}│ 10 x 64│ + │ c│ Vector{$T}│ 10│ + │ s│ Vector{$FC}│ 10│ + │ z│ Vector{$FC}│ 10│ + │ R│ Vector{$FC}│ 55│ + │ warm_start│ Bool│ 0│ + │ inner_iter│ Int64│ 0│ + └────────────┴───────────────────┴─────────────────┘ + """ + @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) + io = IOBuffer() show(io, cr_solver, show_stats=false) showed = String(take!(io)) diff --git a/test/test_utils.jl b/test/test_utils.jl index fbfe2e4e0..dba687c82 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -470,3 +470,24 @@ function restarted_gmres_callback_n2(solver::GmresSolver, A, b, stor, N, storage storage_vec .-= b return (norm(storage_vec) ≤ tol) end + +# Successive over-relaxation (SOR) method +function sor!(x, A, b, ω, k) + x .= 0 + n = length(x) + for iter = 1:k + for i = 1:n + sum1 = sum(A[i,j] * x[j] for j = 1:i-1; init = 0) + sum2 = sum(A[i,j] * x[j] for j = i+1:n; init = 0) + x[i] = (1 - ω) * x[i] + (ω / A[i,i]) * (b[i] - sum1 - sum2) + end + end + return x +end + +function test_sor() + A = [4 -1 -6 0; -5 -4 10 8; 0 9 4 -2; 1 0 -7 5] + b = [2; 21; -12; -6] + ω = 0.5 + return A, b, ω +end diff --git a/test/test_warm_start.jl b/test/test_warm_start.jl index 66a1cbea7..232a5a9cf 100644 --- a/test/test_warm_start.jl +++ b/test/test_warm_start.jl @@ -70,6 +70,11 @@ function test_warm_start(FC) resid = norm(r) / norm(b) @test(resid ≤ tol) + x, stats = fgmres(A, b, x0) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = bicgstab(A, b, x0) r = b - A * x resid = norm(r) / norm(b) From 6fa3132881a28ffa0d597d0d8f23ba4b91eee998 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 21 Sep 2022 18:21:32 -0400 Subject: [PATCH 037/182] Add a test with a variable preconditioner --- src/fgmres.jl | 4 ++-- test/test_fgmres.jl | 33 +++++++++++++++++++++------------ test/test_utils.jl | 21 --------------------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/fgmres.jl b/src/fgmres.jl index eb6ced660..ca5a44096 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -29,6 +29,7 @@ This implementation allows a left preconditioner M and a flexible right precondi A situation in which the preconditioner is "not constant" is when a relaxation-type method, a Chebyshev iteration or another Krylov subspace method is used as a preconditioner. Compared to GMRES, there is no additional cost incurred in the arithmetic but the memory requirement almost doubles. +Thus, GMRES is recommended if the right preconditioner N is identical as each iteration. Full reorthogonalization is available with the `reorthogonalization` option. @@ -93,9 +94,8 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("FGMRES: system of size %d\n", n) - # Check M = Iₙ and N = Iₙ + # Check M = Iₙ MisI = (M === I) - NisI = (N === I) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") diff --git a/test/test_fgmres.jl b/test/test_fgmres.jl index e640da46e..9bb73d3e4 100644 --- a/test/test_fgmres.jl +++ b/test/test_fgmres.jl @@ -1,3 +1,16 @@ +import LinearAlgebra.mul! + +mutable struct FlexiblePreconditioner{T,S} + D::Diagonal{T, S} + ω::T +end + +function mul!(y::Vector, P::FlexiblePreconditioner, x::Vector) + P.ω = -P.ω + mul!(y, P.D, x) + y .*= P.ω +end + @testset "fgmres" begin fgmres_tol = 1.0e-6 @@ -128,18 +141,14 @@ @test(stats.solved) end - # test callback function - # A, b = sparse_laplacian(FC=FC) - # solver = FgmresSolver(A, b) - # tol = 1.0e-1 - # N = Diagonal(1 ./ diag(A)) - # stor = StorageGetxRestartedGmres(solver, N = N) - # storage_vec = similar(b) - # fgmres!(solver, A, b, N = N, atol = 0.0, rtol = 0.0, restart = true, callback = solver -> restarted_fgmres_callback_n2(solver, A, b, stor, N, storage_vec, tol)) - # @test solver.stats.status == "user-requested exit" - # @test norm(A * x - b) ≤ tol - # - # @test_throws TypeError fgmres(A, b, callback = solver -> "string", history = true) + A, b = polar_poisson(FC=FC) + J = inv(Diagonal(A)) # Jacobi preconditioner + N = FlexiblePreconditioner(J, 1.0) + (x, stats) = fgmres(A, b, N=N) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ fgmres_tol) + @test(stats.solved) end end end diff --git a/test/test_utils.jl b/test/test_utils.jl index dba687c82..fbfe2e4e0 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -470,24 +470,3 @@ function restarted_gmres_callback_n2(solver::GmresSolver, A, b, stor, N, storage storage_vec .-= b return (norm(storage_vec) ≤ tol) end - -# Successive over-relaxation (SOR) method -function sor!(x, A, b, ω, k) - x .= 0 - n = length(x) - for iter = 1:k - for i = 1:n - sum1 = sum(A[i,j] * x[j] for j = 1:i-1; init = 0) - sum2 = sum(A[i,j] * x[j] for j = i+1:n; init = 0) - x[i] = (1 - ω) * x[i] + (ω / A[i,i]) * (b[i] - sum1 - sum2) - end - end - return x -end - -function test_sor() - A = [4 -1 -6 0; -5 -4 10 8; 0 9 4 -2; 1 0 -7 5] - b = [2; 21; -12; -6] - ω = 0.5 - return A, b, ω -end From bd315942a0515eeaf524ab4b5977892dd5797eec Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Sun, 25 Sep 2022 20:09:56 -0400 Subject: [PATCH 038/182] Apply suggestions from code review Co-authored-by: Dominique --- src/fgmres.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fgmres.jl b/src/fgmres.jl index ca5a44096..cdc317e1d 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -22,22 +22,22 @@ export fgmres, fgmres! Solve the linear system Ax = b using FGMRES method. -FGMRES computes a sequence of approximate solutions with the minimal residual property. -FGMRES is a variant of GMRES that allows changes in the right preconditioning at every step. +FGMRES computes a sequence of approximate solutions with minimum residual. +FGMRES is a variant of GMRES that allows changes in the right preconditioner at each iteration. This implementation allows a left preconditioner M and a flexible right preconditioner N. A situation in which the preconditioner is "not constant" is when a relaxation-type method, a Chebyshev iteration or another Krylov subspace method is used as a preconditioner. Compared to GMRES, there is no additional cost incurred in the arithmetic but the memory requirement almost doubles. -Thus, GMRES is recommended if the right preconditioner N is identical as each iteration. +Thus, GMRES is recommended if the right preconditioner N is constant. Full reorthogonalization is available with the `reorthogonalization` option. If `restart = true`, the restarted version FGMRES(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. -More storage will be allocated only if the number of iterations exceed `memory`. +More storage will be allocated only if the number of iterations exceeds `memory`. -FGMRES can be warm-started from an initial guess `x0` with the method +FGMRES can be warm-started from an initial guess `x0` with (x, stats) = fgmres(A, b, x0; kwargs...) @@ -169,7 +169,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; nr = 0 # Number of coefficients stored in Rₖ. for i = 1 : mem V[i] .= zero(FC) # Orthogonal basis of {Mr₀, MANₖr₀, ..., (MANₖ)ᵏ⁻¹r₀}. - Z[i] .= zero(FC) # Z = [N₁v₁, ..., Nₖvₖ] + Z[i] .= zero(FC) # Zₖ = [N₁v₁, ..., Nₖvₖ] end s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. From 45d5a0f05b3a4c50a80148be0c4d0df321ddfe74 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 25 Sep 2022 20:14:19 -0400 Subject: [PATCH 039/182] Add fgmres in docs/src/factorization-free.md --- docs/src/factorization-free.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/factorization-free.md b/docs/src/factorization-free.md index aa0f51f07..235d7daef 100644 --- a/docs/src/factorization-free.md +++ b/docs/src/factorization-free.md @@ -10,8 +10,8 @@ Some methods only require `A * v` products, whereas other ones also require `A' |:--------------------------------------:|:----------------------------------------:| | CG, CR | CGLS, CRLS, CGNE, CRMR | | SYMMLQ, CG-LANCZOS, MINRES, MINRES-QLP | LSLQ, LSQR, LSMR, LNLQ, CRAIG, CRAIGMR | -| DIOM, FOM, DQGMRES, GMRES | BiLQ, QMR, BiLQR, USYMLQ, USYMQR, TriLQR | -| CGS, BICGSTAB | TriCG, TriMR, USYMLQR | +| DIOM, FOM, DQGMRES, GMRES, FGMRES | BiLQ, QMR, BiLQR, USYMLQ, USYMQR, TriLQR | +| CGS, BICGSTAB | TriCG, TriMR | Preconditioners `M`, `N`, `C`, `D`, `E` or `F` can be also linear operators and must implement `mul!` or `ldiv!`. From 837f7c915941230d5917034d5128a9fcf8366191 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Sun, 25 Sep 2022 20:34:08 -0400 Subject: [PATCH 040/182] Update src/fgmres.jl Co-authored-by: Dominique --- src/fgmres.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fgmres.jl b/src/fgmres.jl index cdc317e1d..b4cc213e1 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -20,7 +20,7 @@ export fgmres, fgmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using FGMRES method. +Solve the linear system Ax = b using FGMRES. FGMRES computes a sequence of approximate solutions with minimum residual. FGMRES is a variant of GMRES that allows changes in the right preconditioner at each iteration. From e2a6737e3a952ab0f09feed3e791284c217d2dd8 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 25 Sep 2022 21:16:18 -0400 Subject: [PATCH 041/182] Update multiple docstrings and comments --- docs/src/factorization-free.md | 3 ++ src/bicgstab.jl | 4 +- src/bilq.jl | 4 +- src/bilqr.jl | 2 +- src/cg.jl | 4 +- src/cg_lanczos.jl | 2 +- src/cgs.jl | 2 +- src/cr.jl | 2 +- src/diom.jl | 75 ++++++++++++++-------------- src/dqgmres.jl | 89 ++++++++++++++++------------------ src/fgmres.jl | 2 +- src/fom.jl | 24 ++++----- src/gmres.jl | 26 +++++----- src/gpmr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 4 +- src/symmlq.jl | 4 +- src/tricg.jl | 2 +- src/trilqr.jl | 2 +- src/trimr.jl | 2 +- src/usymlq.jl | 2 +- src/usymqr.jl | 4 +- 23 files changed, 127 insertions(+), 138 deletions(-) diff --git a/docs/src/factorization-free.md b/docs/src/factorization-free.md index 235d7daef..81f995810 100644 --- a/docs/src/factorization-free.md +++ b/docs/src/factorization-free.md @@ -13,6 +13,9 @@ Some methods only require `A * v` products, whereas other ones also require `A' | DIOM, FOM, DQGMRES, GMRES, FGMRES | BiLQ, QMR, BiLQR, USYMLQ, USYMQR, TriLQR | | CGS, BICGSTAB | TriCG, TriMR | +!!! info + GPMR is the only method that requires `A * v` and `B * w` products. + Preconditioners `M`, `N`, `C`, `D`, `E` or `F` can be also linear operators and must implement `mul!` or `ldiv!`. We strongly recommend [LinearOperators.jl](https://github.com/JuliaSmoothOptimizers/LinearOperators.jl) to model matrix-free operators, but other packages such as [LinearMaps.jl](https://github.com/JuliaLinearAlgebra/LinearMaps.jl), [DiffEqOperators.jl](https://github.com/SciML/DiffEqOperators.jl) or your own operator can be used as well. diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 3e5635775..25abd01e6 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -24,7 +24,7 @@ export bicgstab, bicgstab! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b using the BICGSTAB method. +Solve the square linear system Ax = b using BICGSTAB. BICGSTAB requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. @@ -42,7 +42,7 @@ Information will be displayed every `verbose` iterations. This implementation allows a left preconditioner `M` and a right preconditioner `N`. -BICGSTAB can be warm-started from an initial guess `x0` with the method +BICGSTAB can be warm-started from an initial guess `x0` with (x, stats) = bicgstab(A, b, x0; kwargs...) diff --git a/src/bilq.jl b/src/bilq.jl index f40538245..8dbd46c51 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -21,7 +21,7 @@ export bilq, bilq! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b using the BiLQ method. +Solve the square linear system Ax = b using BiLQ. BiLQ is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. @@ -30,7 +30,7 @@ When `A` is symmetric and `b = c`, BiLQ is equivalent to SYMMLQ. An option gives the possibility of transferring to the BiCG point, when it exists. The transfer is based on the residual norm. -BiLQ can be warm-started from an initial guess `x0` with the method +BiLQ can be warm-started from an initial guess `x0` with (x, stats) = bilq(A, b, x0; kwargs...) diff --git a/src/bilqr.jl b/src/bilqr.jl index 7284597dc..479e01319 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -33,7 +33,7 @@ QMR is used for solving dual system `Aᴴy = c`. An option gives the possibility of transferring from the BiLQ point to the BiCG point, when it exists. The transfer is based on the residual norm. -BiLQR can be warm-started from initial guesses `x0` and `y0` with the method +BiLQR can be warm-started from initial guesses `x0` and `y0` with (x, y, stats) = bilqr(A, b, c, x0, y0; kwargs...) diff --git a/src/cg.jl b/src/cg.jl index 8a974accc..212c68484 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -26,7 +26,7 @@ export cg, cg! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -The conjugate gradient method to solve the symmetric linear system Ax=b. +The conjugate gradient method to solve the symmetric linear system Ax = b. The method does _not_ abort if A is not definite. @@ -37,7 +37,7 @@ M also indicates the weighted norm in which residuals are measured. If `itmax=0`, the default number of iterations is set to `2 * n`, with `n = length(b)`. -CG can be warm-started from an initial guess `x0` with the method +CG can be warm-started from an initial guess `x0` with (x, stats) = cg(A, b, x0; kwargs...) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 2f2dae16d..4e503f09a 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -32,7 +32,7 @@ The method does _not_ abort if A is not definite. A preconditioner M may be provided in the form of a linear operator and is assumed to be hermitian and positive definite. -CG-LANCZOS can be warm-started from an initial guess `x0` with the method +CG-LANCZOS can be warm-started from an initial guess `x0` with (x, stats) = cg_lanczos(A, b, x0; kwargs...) diff --git a/src/cgs.jl b/src/cgs.jl index 592eb1b2d..37a1c4137 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -40,7 +40,7 @@ TFQMR and BICGSTAB were developed to remedy this difficulty.» This implementation allows a left preconditioner M and a right preconditioner N. -CGS can be warm-started from an initial guess `x0` with the method +CGS can be warm-started from an initial guess `x0` with (x, stats) = cgs(A, b, x0; kwargs...) diff --git a/src/cr.jl b/src/cr.jl index 4405eda76..0e93e7eaa 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -34,7 +34,7 @@ In a linesearch context, 'linesearch' must be set to 'true'. If `itmax=0`, the default number of iterations is set to `2 * n`, with `n = length(b)`. -CR can be warm-started from an initial guess `x0` with the method +CR can be warm-started from an initial guess `x0` with (x, stats) = cr(A, b, x0; kwargs...) diff --git a/src/diom.jl b/src/diom.jl index 9c6b9767b..168bc5b94 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -20,7 +20,7 @@ export diom, diom! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the consistent linear system Ax = b using direct incomplete orthogonalization method. +Solve the consistent linear system Ax = b using DIOM. DIOM only orthogonalizes the new vectors of the Krylov basis against the `memory` most recent vectors. If CG is well defined on `Ax = b` and `memory = 2`, DIOM is theoretically equivalent to CG. @@ -33,11 +33,8 @@ An advantage of DIOM is that nonsymmetric or symmetric indefinite or both nonsym and indefinite systems of linear equations can be handled by this single algorithm. This implementation allows a left preconditioner M and a right preconditioner N. -- Left preconditioning : M⁻¹Ax = M⁻¹b -- Right preconditioning : AN⁻¹u = b with x = N⁻¹u -- Split preconditioning : M⁻¹AN⁻¹u = M⁻¹b with x = N⁻¹u -DIOM can be warm-started from an initial guess `x0` with the method +DIOM can be warm-started from an initial guess `x0` with (x, stats) = diom(A, b, x0; kwargs...) @@ -121,7 +118,7 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; else t .= b end - MisI || mulorldiv!(r₀, M, t, ldiv) # M⁻¹(b - Ax₀) + MisI || mulorldiv!(r₀, M, t, ldiv) # M(b - Ax₀) rNorm = @knrm2(n, r₀) # β = ‖r₀‖₂ history && push!(rNorms, rNorm) if rNorm == 0 @@ -141,14 +138,14 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; mem = length(L) # Memory for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(M⁻¹AN⁻¹, M⁻¹b). - P[i] .= zero(FC) # Directions for x : Pₘ = N⁻¹Vₘ(Uₘ)⁻¹. + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). + P[i] .= zero(FC) # Directions for x : Pₖ = NVₖ(Uₖ)⁻¹. end - H .= zero(FC) # Last column of the band hessenberg matrix Hₘ = LₘUₘ. - # Each column has at most mem + 1 nonzero elements. hᵢ.ₘ is stored as H[m-i+2]. - # m-i+2 represents the indice of the diagonal where hᵢ.ₘ is located. - # In addition of that, the last column of Uₘ is stored in H. - L .= zero(FC) # Last mem pivots of Lₘ. + H .= zero(FC) # Last column of the band hessenberg matrix Hₖ = LₖUₖ. + # Each column has at most mem + 1 nonzero elements. hᵢ.ₖ is stored as H[k-i+2]. + # k-i+2 represents the indice of the diagonal where hᵢ.ₖ is located. + # In addition of that, the last column of Uₖ is stored in H. + L .= zero(FC) # Last mem pivots of Lₖ. # Initial ξ₁ and V₁. ξ = rNorm @@ -166,19 +163,19 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 # Set position in circulars stacks. - pos = mod(iter-1, mem) + 1 # Position corresponding to pₘ and vₘ in circular stacks P and V. - next_pos = mod(iter, mem) + 1 # Position corresponding to vₘ₊₁ in the circular stack V. + pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. + next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. # Incomplete Arnoldi procedure. z = NisI ? V[pos] : solver.z - NisI || mulorldiv!(z, N, V[pos], ldiv) # N⁻¹vₘ, forms pₘ - mul!(t, A, z) # AN⁻¹vₘ - MisI || mulorldiv!(w, M, t, ldiv) # M⁻¹AN⁻¹vₘ, forms vₘ₊₁ + NisI || mulorldiv!(z, N, V[pos], ldiv) # Nvₖ, forms pₖ + mul!(t, A, z) # ANvₖ + MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ for i = max(1, iter-mem+1) : iter ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. diag = iter - i + 2 - H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₘ = ⟨M⁻¹AN⁻¹vₘ , vᵢ⟩ - @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₘ * vᵢ + H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ , vᵢ⟩ + @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ end # Partial reorthogonalization of the Krylov basis. @@ -192,56 +189,56 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; end end - # Compute hₘ₊₁.ₘ and vₘ₊₁. - H[1] = @knrm2(n, w) # hₘ₊₁.ₘ = ‖vₘ₊₁‖₂ - if H[1] ≠ 0 # hₘ₊₁.ₘ = 0 ⇒ "lucky breakdown" - @. V[next_pos] = w / H[1] # vₘ₊₁ = w / hₘ₊₁.ₘ + # Compute hₖ₊₁.ₖ and vₖ₊₁. + H[1] = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + if H[1] ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" + @. V[next_pos] = w / H[1] # vₖ₊₁ = w / hₖ₊₁.ₖ end - # It's possible that uₘ₋ₘₑₘ.ₘ ≠ 0 when m ≥ mem + 1 + # It's possible that uₖ₋ₘₑₘ.ₖ ≠ 0 when k ≥ mem + 1 if iter ≥ mem + 2 - H[mem+2] = zero(FC) # hₘ₋ₘₑₘ.ₘ = 0 + H[mem+2] = zero(FC) # hₖ₋ₘₑₘ.ₖ = 0 end # Update the LU factorization with partial pivoting of H. - # Compute the last column of Uₘ. + # Compute the last column of Uₖ. if iter ≥ 2 for i = max(2,iter-mem+1) : iter lpos = mod(i-1, mem) + 1 # Position corresponding to lᵢ.ᵢ₋₁ in the circular stack L. diag = iter - i + 2 next_diag = diag + 1 - # uᵢ.ₘ ← hᵢ.ₘ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₘ + # uᵢ.ₖ ← hᵢ.ₖ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₖ H[diag] = H[diag] - L[lpos] * H[next_diag] end - # Compute ξₘ the last component of zₘ = β(Lₘ)⁻¹e₁. - # ξₘ = -lₘ.ₘ₋₁ * ξₘ₋₁ + # Compute ξₖ the last component of zₖ = β(Lₖ)⁻¹e₁. + # ξₖ = -lₖ.ₖ₋₁ * ξₖ₋₁ ξ = - L[pos] * ξ end - # Compute next pivot lₘ₊₁.ₘ = hₘ₊₁.ₘ / uₘ.ₘ + # Compute next pivot lₖ₊₁.ₖ = hₖ₊₁.ₖ / uₖ.ₖ L[next_pos] = H[1] / H[2] - # Compute the direction pₘ, the last column of Pₘ = N⁻¹Vₘ(Uₘ)⁻¹. + # Compute the direction pₖ, the last column of Pₖ = NVₖ(Uₖ)⁻¹. for i = max(1,iter-mem) : iter-1 ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. diag = iter - i + 2 if ipos == pos - # pₐᵤₓ ← -hₘ₋ₘₑₘ.ₘ * pₘ₋ₘₑₘ + # pₐᵤₓ ← -hₖ₋ₘₑₘ.ₖ * pₖ₋ₘₑₘ @kscal!(n, -H[diag], P[pos]) else - # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₘ * pᵢ + # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₖ * pᵢ @kaxpy!(n, -H[diag], P[ipos], P[pos]) end end - # pₐᵤₓ ← pₐᵤₓ + N⁻¹vₘ + # pₐᵤₓ ← pₐᵤₓ + Nvₖ @kaxpy!(n, one(FC), z, P[pos]) - # pₘ = pₐᵤₓ / uₘ.ₘ + # pₖ = pₐᵤₓ / uₖ.ₖ @. P[pos] = P[pos] / H[2] - # Update solution xₘ. - # xₘ = xₘ₋₁ + ξₘ * pₘ + # Update solution xₖ. + # xₖ = xₖ₋₁ + ξₖ * pₖ @kaxpy!(n, ξ, P[pos], x) # Compute residual norm. - # ‖ M⁻¹(b - Axₘ) ‖₂ = hₘ₊₁.ₘ * |ξₘ / uₘ.ₘ| + # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ξₖ / uₖ.ₖ| rNorm = real(H[1]) * abs(ξ / H[2]) history && push!(rNorms, rNorm) diff --git a/src/dqgmres.jl b/src/dqgmres.jl index ab7c490a6..1b6dd8d75 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -20,7 +20,7 @@ export dqgmres, dqgmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the consistent linear system Ax = b using DQGMRES method. +Solve the consistent linear system Ax = b using DQGMRES. DQGMRES algorithm is based on the incomplete Arnoldi orthogonalization process and computes a sequence of approximate solutions with the quasi-minimal residual property. @@ -33,11 +33,8 @@ Otherwise, DQGMRES interpolates between MINRES and GMRES and is similar to MINRE Partial reorthogonalization is available with the `reorthogonalization` option. This implementation allows a left preconditioner M and a right preconditioner N. -- Left preconditioning : M⁻¹Ax = M⁻¹b -- Right preconditioning : AN⁻¹u = b with x = N⁻¹u -- Split preconditioning : M⁻¹AN⁻¹u = M⁻¹b with x = N⁻¹u -DQGMRES can be warm-started from an initial guess `x0` with the method +DQGMRES can be warm-started from an initial guess `x0` with (x, stats) = dqgmres(A, b, x0; kwargs...) @@ -121,7 +118,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; else t .= b end - MisI || mulorldiv!(r₀, M, t, ldiv) # M⁻¹(b - Ax₀) + MisI || mulorldiv!(r₀, M, t, ldiv) # M(b - Ax₀) rNorm = @knrm2(n, r₀) # β = ‖r₀‖₂ history && push!(rNorms, rNorm) if rNorm == 0 @@ -142,23 +139,23 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Set up workspace. mem = length(c) # Memory. for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(M⁻¹AN⁻¹, M⁻¹b). - P[i] .= zero(FC) # Directions for x : Pₘ = N⁻¹Vₘ(Rₘ)⁻¹. + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). + P[i] .= zero(FC) # Directions for x : Pₖ = NVₖ(Rₖ)⁻¹. end - c .= zero(T) # Last mem Givens cosines used for the factorization QₘRₘ = Hₘ. - s .= zero(FC) # Last mem Givens sines used for the factorization QₘRₘ = Hₘ. - H .= zero(FC) # Last column of the band hessenberg matrix Hₘ. - # Each column has at most mem + 1 nonzero elements. hᵢ.ₘ is stored as H[m-i+2]. - # m-i+2 represents the indice of the diagonal where hᵢ.ₘ is located. - # In addition of that, the last column of Rₘ is also stored in H. + c .= zero(T) # Last mem Givens cosines used for the factorization QₖRₖ = Hₖ. + s .= zero(FC) # Last mem Givens sines used for the factorization QₖRₖ = Hₖ. + H .= zero(FC) # Last column of the band hessenberg matrix Hₖ. + # Each column has at most mem + 1 nonzero elements. hᵢ.ₖ is stored as H[k-i+2]. + # k-i+2 represents the indice of the diagonal where hᵢ.ₖ is located. + # In addition of that, the last column of Rₖ is also stored in H. # Initial γ₁ and V₁. - γₘ = rNorm # γₘ and γₘ₊₁ are the last components of gₘ, right-hand of the least squares problem min ‖ Hₘyₘ - gₘ ‖₂. + γₖ = rNorm # γₖ and γₖ₊₁ are the last components of gₖ, right-hand of the least squares problem min ‖ Hₖyₖ - gₖ ‖₂. @. V[1] = r₀ / rNorm # The following stopping criterion compensates for the lag in the # residual, but usually increases the number of iterations. - # solved = sqrt(max(1, iter-mem+1)) * |γₘ₊₁| ≤ ε + # solved = sqrt(max(1, iter-mem+1)) * |γₖ₊₁| ≤ ε solved = rNorm ≤ ε # less accurate, but acceptable. tired = iter ≥ itmax status = "unknown" @@ -170,19 +167,19 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 # Set position in circulars stacks. - pos = mod(iter-1, mem) + 1 # Position corresponding to pₘ and vₘ in circular stacks P and V. - next_pos = mod(iter, mem) + 1 # Position corresponding to vₘ₊₁ in the circular stack V. + pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. + next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. # Incomplete Arnoldi procedure. z = NisI ? V[pos] : solver.z - NisI || mulorldiv!(z, N, V[pos], ldiv) # N⁻¹vₘ, forms pₘ - mul!(t, A, z) # AN⁻¹vₘ - MisI || mulorldiv!(w, M, t, ldiv) # M⁻¹AN⁻¹vₘ, forms vₘ₊₁ + NisI || mulorldiv!(z, N, V[pos], ldiv) # Nvₖ, forms pₖ + mul!(t, A, z) # ANvₖ + MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ for i = max(1, iter-mem+1) : iter ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. diag = iter - i + 2 - H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₘ = ⟨M⁻¹AN⁻¹vₘ , vᵢ⟩ - @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₘ * vᵢ + H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ , vᵢ⟩ + @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ end # Partial reorthogonalization of the Krylov basis. @@ -196,14 +193,14 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; end end - # Compute hₘ₊₁.ₘ and vₘ₊₁. - H[1] = @knrm2(n, w) # hₘ₊₁.ₘ = ‖vₘ₊₁‖₂ - if H[1] ≠ 0 # hₘ₊₁.ₘ = 0 ⇒ "lucky breakdown" - @. V[next_pos] = w / H[1] # vₘ₊₁ = w / hₘ₊₁.ₘ + # Compute hₖ₊₁.ₖ and vₖ₊₁. + H[1] = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + if H[1] ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" + @. V[next_pos] = w / H[1] # vₖ₊₁ = w / hₖ₊₁.ₖ end - # rₘ₋ₘₑₘ.ₘ ≠ 0 when m ≥ mem + 1 + # rₖ₋ₘₑₘ.ₖ ≠ 0 when k ≥ mem + 1 if iter ≥ mem + 2 - H[mem+2] = zero(FC) # hₘ₋ₘₑₘ.ₘ = 0 + H[mem+2] = zero(FC) # hₖ₋ₘₑₘ.ₖ = 0 end # Update the QR factorization of H. @@ -217,41 +214,41 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; H[next_diag] = H_aux end - # Compute and apply current Givens reflection Ωₘ. - # [cₘ sₘ] [ hₘ.ₘ ] = [ρₘ] - # [sₘ -cₘ] [hₘ₊₁.ₘ] [0 ] + # Compute and apply current Givens reflection Ωₖ. + # [cₖ sₖ] [ hₖ.ₖ ] = [ρₖ] + # [sₖ -cₖ] [hₖ₊₁.ₖ] [0 ] (c[pos], s[pos], H[2]) = sym_givens(H[2], H[1]) - γₘ₊₁ = conj(s[pos]) * γₘ - γₘ = c[pos] * γₘ + γₖ₊₁ = conj(s[pos]) * γₖ + γₖ = c[pos] * γₖ - # Compute the direction pₘ, the last column of Pₘ = N⁻¹Vₘ(Rₘ)⁻¹. + # Compute the direction pₖ, the last column of Pₖ = NVₖ(Rₖ)⁻¹. for i = max(1,iter-mem) : iter-1 ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. diag = iter - i + 2 if ipos == pos - # pₐᵤₓ ← -hₘ₋ₘₑₘ.ₘ * pₘ₋ₘₑₘ + # pₐᵤₓ ← -hₖ₋ₘₑₘ.ₖ * pₖ₋ₘₑₘ @kscal!(n, -H[diag], P[pos]) else - # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₘ * pᵢ + # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₖ * pᵢ @kaxpy!(n, -H[diag], P[ipos], P[pos]) end end - # pₐᵤₓ ← pₐᵤₓ + N⁻¹vₘ + # pₐᵤₓ ← pₐᵤₓ + Nvₖ @kaxpy!(n, one(FC), z, P[pos]) - # pₘ = pₐᵤₓ / hₘ.ₘ + # pₖ = pₐᵤₓ / hₖ.ₖ @. P[pos] = P[pos] / H[2] - # Compute solution xₘ. - # xₘ ← xₘ₋₁ + γₘ * pₘ - @kaxpy!(n, γₘ, P[pos], x) + # Compute solution xₖ. + # xₖ ← xₖ₋₁ + γₖ * pₖ + @kaxpy!(n, γₖ, P[pos], x) # Update residual norm estimate. - # ‖ M⁻¹(b - Axₘ) ‖₂ ≈ |γₘ₊₁| - rNorm = abs(γₘ₊₁) + # ‖ M(b - Axₖ) ‖₂ ≈ |γₖ₊₁| + rNorm = abs(γₖ₊₁) history && push!(rNorms, rNorm) - # Update γₘ. - γₘ = γₘ₊₁ + # Update γₖ. + γₖ = γₖ₊₁ # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. diff --git a/src/fgmres.jl b/src/fgmres.jl index b4cc213e1..635e7241e 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -124,7 +124,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; else w .= b end - MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M⁻¹(b - Ax₀) + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) β = @knrm2(n, r₀) # β = ‖r₀‖₂ rNorm = β diff --git a/src/fom.jl b/src/fom.jl index b212129ef..95bcc97d1 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -20,22 +20,18 @@ export fom, fom! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using FOM method. +Solve the linear system Ax = b using FOM. FOM algorithm is based on the Arnoldi process and a Galerkin condition. This implementation allows a left preconditioner M and a right preconditioner N. -- Left preconditioning : M⁻¹Ax = M⁻¹b -- Right preconditioning : AN⁻¹u = b with x = N⁻¹u -- Split preconditioning : M⁻¹AN⁻¹u = M⁻¹b with x = N⁻¹u - Full reorthogonalization is available with the `reorthogonalization` option. If `restart = true`, the restarted version FOM(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. -More storage will be allocated only if the number of iterations exceed `memory`. +More storage will be allocated only if the number of iterations exceeds `memory`. -FOM can be warm-started from an initial guess `x0` with the method +FOM can be warm-started from an initial guess `x0` with (x, stats) = fom(A, b, x0; kwargs...) @@ -124,7 +120,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; else w .= b end - MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M⁻¹(b - Ax₀) + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) β = @knrm2(n, r₀) # β = ‖r₀‖₂ rNorm = β @@ -167,7 +163,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Initialize workspace. nr = 0 # Number of coefficients stored in Uₖ. for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(M⁻¹AN⁻¹, M⁻¹r₀). + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). end l .= zero(FC) # Lower unit triangular matrix Lₖ. U .= zero(FC) # Upper triangular matrix Uₖ. @@ -207,9 +203,9 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Continue the Arnoldi process. p = NisI ? V[inner_iter] : solver.p - NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← N⁻¹vₖ - mul!(w, A, p) # w ← AN⁻¹vₖ - MisI || mulorldiv!(q, M, w, ldiv) # q ← M⁻¹AN⁻¹vₖ + NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← Nvₖ + mul!(w, A, p) # w ← ANvₖ + MisI || mulorldiv!(q, M, w, ldiv) # q ← MANvₖ for i = 1 : inner_iter U[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq @kaxpy!(n, -U[nr+i], V[i], q) # q ← q - hᵢₖvᵢ @@ -240,7 +236,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; l[inner_iter] = Hbis / U[nr+inner_iter] # Update residual norm estimate. - # ‖ M⁻¹(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ζₖ / uₖ.ₖ| + # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ζₖ / uₖ.ₖ| rNorm = Hbis * abs(z[inner_iter] / U[nr+inner_iter]) history && push!(rNorms, rNorm) @@ -280,7 +276,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; y[i] = y[i] / U[pos] # yᵢ ← yᵢ / rᵢᵢ end - # Form xₖ = N⁻¹Vₖyₖ + # Form xₖ = NVₖyₖ for i = 1 : inner_iter @kaxpy!(n, y[i], V[i], xr) end diff --git a/src/gmres.jl b/src/gmres.jl index 32999aa23..b145b512b 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -20,22 +20,18 @@ export gmres, gmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using GMRES method. +Solve the linear system Ax = b using GMRES. -GMRES algorithm is based on the Arnoldi process and computes a sequence of approximate solutions with the minimal residual property. +GMRES algorithm is based on the Arnoldi process and computes a sequence of approximate solutions with the minimum residual. This implementation allows a left preconditioner M and a right preconditioner N. -- Left preconditioning : M⁻¹Ax = M⁻¹b -- Right preconditioning : AN⁻¹u = b with x = N⁻¹u -- Split preconditioning : M⁻¹AN⁻¹u = M⁻¹b with x = N⁻¹u - Full reorthogonalization is available with the `reorthogonalization` option. If `restart = true`, the restarted version GMRES(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. -More storage will be allocated only if the number of iterations exceed `memory`. +More storage will be allocated only if the number of iterations exceeds `memory`. -GMRES can be warm-started from an initial guess `x0` with the method +GMRES can be warm-started from an initial guess `x0` with (x, stats) = gmres(A, b, x0; kwargs...) @@ -124,7 +120,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; else w .= b end - MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M⁻¹(b - Ax₀) + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) β = @knrm2(n, r₀) # β = ‖r₀‖₂ rNorm = β @@ -168,7 +164,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Initialize workspace. nr = 0 # Number of coefficients stored in Rₖ. for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(M⁻¹AN⁻¹, M⁻¹r₀). + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). end s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. @@ -210,9 +206,9 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Continue the Arnoldi process. p = NisI ? V[inner_iter] : solver.p - NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← N⁻¹vₖ - mul!(w, A, p) # w ← AN⁻¹vₖ - MisI || mulorldiv!(q, M, w, ldiv) # q ← M⁻¹AN⁻¹vₖ + NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← Nvₖ + mul!(w, A, p) # w ← ANvₖ + MisI || mulorldiv!(q, M, w, ldiv) # q ← MANvₖ for i = 1 : inner_iter R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ @@ -250,7 +246,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; z[inner_iter] = c[inner_iter] * z[inner_iter] # Update residual norm estimate. - # ‖ M⁻¹(b - Axₖ) ‖₂ = |ζₖ₊₁| + # ‖ M(b - Axₖ) ‖₂ = |ζₖ₊₁| rNorm = abs(ζₖ₊₁) history && push!(rNorms, rNorm) @@ -299,7 +295,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; end end - # Form xₖ = N⁻¹Vₖyₖ + # Form xₖ = NVₖyₖ for i = 1 : inner_iter @kaxpy!(n, y[i], V[i], xr) end diff --git a/src/gpmr.jl b/src/gpmr.jl index 82499b50e..528bd522d 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -58,7 +58,7 @@ Full reorthogonalization is available with the `reorthogonalization` option. Additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations. -GPMR can be warm-started from initial guesses `x0` and `y0` with the method +GPMR can be warm-started from initial guesses `x0` and `y0` with (x, y, stats) = gpmr(A, B, b, c, x0, y0; kwargs...) diff --git a/src/minres.jl b/src/minres.jl index d3b8732ee..c95048bbc 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -55,7 +55,7 @@ MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr A preconditioner M may be provided in the form of a linear operator and is assumed to be symmetric and positive definite. -MINRES can be warm-started from an initial guess `x0` with the method +MINRES can be warm-started from an initial guess `x0` with (x, stats) = minres(A, b, x0; kwargs...) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 509a7ef4e..cb70754b8 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -34,7 +34,7 @@ A preconditioner M may be provided in the form of a linear operator and is assumed to be symmetric and positive definite. M also indicates the weighted norm in which residuals are measured. -MINRES-QLP can be warm-started from an initial guess `x0` with the method +MINRES-QLP can be warm-started from an initial guess `x0` with (x, stats) = minres_qlp(A, b, x0; kwargs...) diff --git a/src/qmr.jl b/src/qmr.jl index d4b684601..fe0fab65c 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -29,13 +29,13 @@ export qmr, qmr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b using the QMR method. +Solve the square linear system Ax = b using QMR. QMR is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. When `A` is symmetric and `b = c`, QMR is equivalent to MINRES. -QMR can be warm-started from an initial guess `x0` with the method +QMR can be warm-started from an initial guess `x0` with (x, stats) = qmr(A, b, x0; kwargs...) diff --git a/src/symmlq.jl b/src/symmlq.jl index 7b889c715..efbd751aa 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -30,12 +30,12 @@ Solve the shifted linear system using the SYMMLQ method, where λ is a shift parameter, and A is square and symmetric. -SYMMLQ produces monotonic errors ‖x*-x‖₂. +SYMMLQ produces monotonic errors ‖x* - x‖₂. A preconditioner M may be provided in the form of a linear operator and is assumed to be symmetric and positive definite. -SYMMLQ can be warm-started from an initial guess `x0` with the method +SYMMLQ can be warm-started from an initial guess `x0` with (x, stats) = symmlq(A, b, x0; kwargs...) diff --git a/src/tricg.jl b/src/tricg.jl index 7c140a821..8d0a41ce3 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -53,7 +53,7 @@ TriCG stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + Additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations. -TriCG can be warm-started from initial guesses `x0` and `y0` with the method +TriCG can be warm-started from initial guesses `x0` and `y0` with (x, y, stats) = tricg(A, b, c, x0, y0; kwargs...) diff --git a/src/trilqr.jl b/src/trilqr.jl index 6b0948984..60663ff55 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -32,7 +32,7 @@ USYMQR is used for solving dual system `Aᴴy = c`. An option gives the possibility of transferring from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm. -TriLQR can be warm-started from initial guesses `x0` and `y0` with the method +TriLQR can be warm-started from initial guesses `x0` and `y0` with (x, y, stats) = trilqr(A, b, c, x0, y0; kwargs...) diff --git a/src/trimr.jl b/src/trimr.jl index 7dd826edf..041a5ffff 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -53,7 +53,7 @@ TriMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + Additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations. -TriMR can be warm-started from initial guesses `x0` and `y0` with the method +TriMR can be warm-started from initial guesses `x0` and `y0` with (x, y, stats) = trimr(A, b, c, x0, y0; kwargs...) diff --git a/src/usymlq.jl b/src/usymlq.jl index 29cd704c7..acec8d77e 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -41,7 +41,7 @@ In all cases, problems must be consistent. An option gives the possibility of transferring to the USYMCG point, when it exists. The transfer is based on the residual norm. -USYMLQ can be warm-started from an initial guess `x0` with the method +USYMLQ can be warm-started from an initial guess `x0` with (x, stats) = usymlq(A, b, c, x0; kwargs...) diff --git a/src/usymqr.jl b/src/usymqr.jl index 45c95c88d..13c19efa8 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -28,7 +28,7 @@ export usymqr, usymqr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using the USYMQR method. +Solve the linear system Ax = b using USYMQR. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. @@ -38,7 +38,7 @@ It's considered as a generalization of MINRES. It can also be applied to under-determined and over-determined problems. USYMQR finds the minimum-norm solution if problems are inconsistent. -USYMQR can be warm-started from an initial guess `x0` with the method +USYMQR can be warm-started from an initial guess `x0` with (x, stats) = usymqr(A, b, c, x0; kwargs...) From a3511627d71787174ed1ed9309715784dc9fd0ab Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 26 Sep 2022 00:29:52 -0400 Subject: [PATCH 042/182] Reduce the storage required by DIOM and DQGMRES --- src/diom.jl | 88 ++++++++++++++++++++++------------------ src/dqgmres.jl | 50 ++++++++++++----------- src/krylov_solvers.jl | 8 ++-- test/test_allocations.jl | 13 +++--- test/test_diom.jl | 2 +- 5 files changed, 86 insertions(+), 75 deletions(-) diff --git a/src/diom.jl b/src/diom.jl index 168bc5b94..77e73c414 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -136,20 +136,23 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) - mem = length(L) # Memory + mem = length(V) # Memory for i = 1 : mem V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). - P[i] .= zero(FC) # Directions for x : Pₖ = NVₖ(Uₖ)⁻¹. + end + for i = 1 : mem-1 + P[i] .= zero(FC) # Directions Pₖ = NVₖ(Uₖ)⁻¹. end H .= zero(FC) # Last column of the band hessenberg matrix Hₖ = LₖUₖ. - # Each column has at most mem + 1 nonzero elements. hᵢ.ₖ is stored as H[k-i+2]. - # k-i+2 represents the indice of the diagonal where hᵢ.ₖ is located. + # Each column has at most mem + 1 nonzero elements. + # hᵢ.ₖ is stored as H[k-i+1], i ≤ k. hₖ₊₁.ₖ is not stored in H. + # k-i+1 represents the indice of the diagonal where hᵢ.ₖ is located. # In addition of that, the last column of Uₖ is stored in H. - L .= zero(FC) # Last mem pivots of Lₖ. + L .= zero(FC) # Last mem-1 pivots of Lₖ. # Initial ξ₁ and V₁. ξ = rNorm - @. V[1] = r₀ / rNorm + V[1] .= r₀ ./ rNorm # Stopping criterion. solved = rNorm ≤ ε @@ -163,8 +166,8 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 # Set position in circulars stacks. - pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. - next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. + pos = mod(iter-1, mem) + 1 # Position corresponding to vₖ in the circular stack V. + next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. # Incomplete Arnoldi procedure. z = NisI ? V[pos] : solver.z @@ -172,17 +175,17 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; mul!(t, A, z) # ANvₖ MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ for i = max(1, iter-mem+1) : iter - ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. - diag = iter - i + 2 - H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ , vᵢ⟩ - @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ + ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. + diag = iter - i + 1 + H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ, vᵢ⟩ + @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ end # Partial reorthogonalization of the Krylov basis. if reorthogonalization for i = max(1, iter-mem+1) : iter ipos = mod(i-1, mem) + 1 - diag = iter - i + 2 + diag = iter - i + 1 Htmp = @kdot(n, w, V[ipos]) H[diag] += Htmp @kaxpy!(n, -Htmp, V[ipos], w) @@ -190,56 +193,61 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; end # Compute hₖ₊₁.ₖ and vₖ₊₁. - H[1] = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ - if H[1] ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" - @. V[next_pos] = w / H[1] # vₖ₊₁ = w / hₖ₊₁.ₖ - end - # It's possible that uₖ₋ₘₑₘ.ₖ ≠ 0 when k ≥ mem + 1 - if iter ≥ mem + 2 - H[mem+2] = zero(FC) # hₖ₋ₘₑₘ.ₖ = 0 + Haux = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + if Haux ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" + V[next_pos] .= w ./ Haux # vₖ₊₁ = w / hₖ₊₁.ₖ end - # Update the LU factorization with partial pivoting of H. + # Update the LU factorization of Hₖ. # Compute the last column of Uₖ. if iter ≥ 2 - for i = max(2,iter-mem+1) : iter - lpos = mod(i-1, mem) + 1 # Position corresponding to lᵢ.ᵢ₋₁ in the circular stack L. - diag = iter - i + 2 + # u₁.ₖ ← h₁.ₖ if iter ≤ mem + # uₖ₋ₘₑₘ₊₁.ₖ ← hₖ₋ₘₑₘ₊₁.ₖ if iter ≥ mem + 1 + for i = max(2,iter-mem+2) : iter + lpos = mod(i-1, mem-1) + 1 # Position corresponding to lᵢ.ᵢ₋₁ in the circular stack L. + diag = iter - i + 1 next_diag = diag + 1 # uᵢ.ₖ ← hᵢ.ₖ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₖ H[diag] = H[diag] - L[lpos] * H[next_diag] + if i == iter + # Compute ξₖ the last component of zₖ = β(Lₖ)⁻¹e₁. + # ξₖ = -lₖ.ₖ₋₁ * ξₖ₋₁ + ξ = - L[lpos] * ξ + end end - # Compute ξₖ the last component of zₖ = β(Lₖ)⁻¹e₁. - # ξₖ = -lₖ.ₖ₋₁ * ξₖ₋₁ - ξ = - L[pos] * ξ end # Compute next pivot lₖ₊₁.ₖ = hₖ₊₁.ₖ / uₖ.ₖ - L[next_pos] = H[1] / H[2] + next_lpos = mod(iter, mem-1) + 1 + L[next_lpos] = Haux / H[1] + + ppos = mod(iter-1, mem-1) + 1 # Position corresponding to pₖ in the circular stack P. # Compute the direction pₖ, the last column of Pₖ = NVₖ(Uₖ)⁻¹. - for i = max(1,iter-mem) : iter-1 - ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. - diag = iter - i + 2 - if ipos == pos - # pₐᵤₓ ← -hₖ₋ₘₑₘ.ₖ * pₖ₋ₘₑₘ - @kscal!(n, -H[diag], P[pos]) + # u₁.ₖp₁ + ... + uₖ.ₖpₖ = Nvₖ if k ≤ mem + # uₖ₋ₘₑₘ₊₁.ₖpₖ₋ₘₑₘ₊₁ + ... + uₖ.ₖpₖ = Nvₖ if k ≥ mem + 1 + for i = max(1,iter-mem+1) : iter-1 + ipos = mod(i-1, mem-1) + 1 # Position corresponding to pᵢ in the circular stack P. + diag = iter - i + 1 + if ipos == ppos + # pₖ ← -uₖ₋ₘₑₘ₊₁.ₖ * pₖ₋ₘₑₘ₊₁ + @kscal!(n, -H[diag], P[ppos]) else - # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₖ * pᵢ - @kaxpy!(n, -H[diag], P[ipos], P[pos]) + # pₖ ← pₖ - uᵢ.ₖ * pᵢ + @kaxpy!(n, -H[diag], P[ipos], P[ppos]) end end # pₐᵤₓ ← pₐᵤₓ + Nvₖ - @kaxpy!(n, one(FC), z, P[pos]) + @kaxpy!(n, one(FC), z, P[ppos]) # pₖ = pₐᵤₓ / uₖ.ₖ - @. P[pos] = P[pos] / H[2] + P[ppos] .= P[ppos] ./ H[1] # Update solution xₖ. # xₖ = xₖ₋₁ + ξₖ * pₖ - @kaxpy!(n, ξ, P[pos], x) + @kaxpy!(n, ξ, P[ppos], x) # Compute residual norm. # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ξₖ / uₖ.ₖ| - rNorm = real(H[1]) * abs(ξ / H[2]) + rNorm = Haux * abs(ξ / H[1]) history && push!(rNorms, rNorm) # Stopping conditions that do not depend on user input. diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 1b6dd8d75..aa6e245ba 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -137,7 +137,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) # Set up workspace. - mem = length(c) # Memory. + mem = length(V) # Memory. for i = 1 : mem V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). P[i] .= zero(FC) # Directions for x : Pₖ = NVₖ(Rₖ)⁻¹. @@ -145,13 +145,14 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; c .= zero(T) # Last mem Givens cosines used for the factorization QₖRₖ = Hₖ. s .= zero(FC) # Last mem Givens sines used for the factorization QₖRₖ = Hₖ. H .= zero(FC) # Last column of the band hessenberg matrix Hₖ. - # Each column has at most mem + 1 nonzero elements. hᵢ.ₖ is stored as H[k-i+2]. - # k-i+2 represents the indice of the diagonal where hᵢ.ₖ is located. + # Each column has at most mem + 1 nonzero elements. + # hᵢ.ₖ is stored as H[k-i+1], i ≤ k. hₖ₊₁.ₖ is not stored in H. + # k-i+1 represents the indice of the diagonal where hᵢ.ₖ is located. # In addition of that, the last column of Rₖ is also stored in H. # Initial γ₁ and V₁. γₖ = rNorm # γₖ and γₖ₊₁ are the last components of gₖ, right-hand of the least squares problem min ‖ Hₖyₖ - gₖ ‖₂. - @. V[1] = r₀ / rNorm + V[1] .= r₀ ./ rNorm # The following stopping criterion compensates for the lag in the # residual, but usually increases the number of iterations. @@ -167,8 +168,8 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 # Set position in circulars stacks. - pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. - next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. + pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. + next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. # Incomplete Arnoldi procedure. z = NisI ? V[pos] : solver.z @@ -176,17 +177,17 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; mul!(t, A, z) # ANvₖ MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ for i = max(1, iter-mem+1) : iter - ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. - diag = iter - i + 2 - H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ , vᵢ⟩ - @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ + ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. + diag = iter - i + 1 + H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ, vᵢ⟩ + @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ end # Partial reorthogonalization of the Krylov basis. if reorthogonalization for i = max(1, iter-mem+1) : iter ipos = mod(i-1, mem) + 1 - diag = iter - i + 2 + diag = iter - i + 1 Htmp = @kdot(n, w, V[ipos]) H[diag] += Htmp @kaxpy!(n, -Htmp, V[ipos], w) @@ -194,37 +195,38 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; end # Compute hₖ₊₁.ₖ and vₖ₊₁. - H[1] = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ - if H[1] ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" - @. V[next_pos] = w / H[1] # vₖ₊₁ = w / hₖ₊₁.ₖ + Haux = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + if Haux ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" + V[next_pos] .= w ./ Haux # vₖ₊₁ = w / hₖ₊₁.ₖ end # rₖ₋ₘₑₘ.ₖ ≠ 0 when k ≥ mem + 1 + # We don't want to use rₖ₋₁₋ₘₑₘ.ₖ₋₁ when we compute rₖ₋ₘₑₘ.ₖ if iter ≥ mem + 2 - H[mem+2] = zero(FC) # hₖ₋ₘₑₘ.ₖ = 0 + H[mem+1] = zero(FC) # rₖ₋ₘₑₘ.ₖ = 0 end - # Update the QR factorization of H. + # Update the QR factorization of Hₖ. # Apply mem previous Givens reflections Ωᵢ. for i = max(1,iter-mem) : iter-1 - irot_pos = mod(i-1, mem) + 1 # Position corresponding to cᵢ and sᵢ in circular stacks c and s. - diag = iter - i + 1 + irot_pos = mod(i-1, mem) + 1 # Position corresponding to cᵢ and sᵢ in circular stacks c and s. + diag = iter - i next_diag = diag + 1 - H_aux = c[irot_pos] * H[next_diag] + s[irot_pos] * H[diag] + Htmp = c[irot_pos] * H[next_diag] + s[irot_pos] * H[diag] H[diag] = conj(s[irot_pos]) * H[next_diag] - c[irot_pos] * H[diag] - H[next_diag] = H_aux + H[next_diag] = Htmp end # Compute and apply current Givens reflection Ωₖ. # [cₖ sₖ] [ hₖ.ₖ ] = [ρₖ] # [sₖ -cₖ] [hₖ₊₁.ₖ] [0 ] - (c[pos], s[pos], H[2]) = sym_givens(H[2], H[1]) + (c[pos], s[pos], H[1]) = sym_givens(H[1], Haux) γₖ₊₁ = conj(s[pos]) * γₖ γₖ = c[pos] * γₖ # Compute the direction pₖ, the last column of Pₖ = NVₖ(Rₖ)⁻¹. for i = max(1,iter-mem) : iter-1 - ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. - diag = iter - i + 2 + ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. + diag = iter - i + 1 if ipos == pos # pₐᵤₓ ← -hₖ₋ₘₑₘ.ₖ * pₖ₋ₘₑₘ @kscal!(n, -H[diag], P[pos]) @@ -236,7 +238,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # pₐᵤₓ ← pₐᵤₓ + Nvₖ @kaxpy!(n, one(FC), z, P[pos]) # pₖ = pₐᵤₓ / hₖ.ₖ - @. P[pos] = P[pos] / H[2] + P[pos] .= P[pos] ./ H[1] # Compute solution xₖ. # xₖ ← xₖ₋₁ + γₖ * pₖ diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index b37ccd575..f94efd2f9 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -409,7 +409,7 @@ function DqgmresSolver(n, m, memory, S) V = [S(undef, n) for i = 1 : memory] c = Vector{T}(undef, memory) s = Vector{FC}(undef, memory) - H = Vector{FC}(undef, memory+2) + H = Vector{FC}(undef, memory+1) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") solver = DqgmresSolver{T,FC,S}(Δx, x, t, z, w, P, V, c, s, H, false, stats) return solver @@ -455,10 +455,10 @@ function DiomSolver(n, m, memory, S) t = S(undef, n) z = S(undef, 0) w = S(undef, 0) - P = [S(undef, n) for i = 1 : memory] + P = [S(undef, n) for i = 1 : memory-1] V = [S(undef, n) for i = 1 : memory] - L = Vector{FC}(undef, memory) - H = Vector{FC}(undef, memory+2) + L = Vector{FC}(undef, memory-1) + H = Vector{FC}(undef, memory) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") solver = DiomSolver{T,FC,S}(Δx, x, t, z, w, P, V, L, H, false, stats) return solver diff --git a/test/test_allocations.jl b/test/test_allocations.jl index b29f11631..308da1597 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -141,10 +141,11 @@ @testset "DIOM" begin # DIOM needs: # - 2 n-vectors: x, t - # - 2 (n*mem)-matrices: P, V - # - 1 mem-vector: L - # - 1 (mem+2)-vector: H - storage_diom(mem, n) = (2 * n) + (2 * n * mem) + (mem) + (mem + 2) + # - 1 (n*mem)-matrix: V + # - 1 n*(mem-1)-matrix: P + # - 1 (mem-1)-vector: L + # - 1 mem-vector: H + storage_diom(mem, n) = (2 * n) + (n * mem) + (n * (mem-1)) + (mem-1) + (mem) storage_diom_bytes(mem, n) = nbits * storage_diom(mem, n) expected_diom_bytes = storage_diom_bytes(mem, n) @@ -183,8 +184,8 @@ # - 2 n-vectors: x, t # - 2 (n*mem)-matrices: P, V # - 2 mem-vectors: c, s - # - 1 (mem+2)-vector: H - storage_dqgmres(mem, n) = (2 * n) + (2 * n * mem) + (2 * mem) + (mem + 2) + # - 1 (mem+1)-vector: H + storage_dqgmres(mem, n) = (2 * n) + (2 * n * mem) + (2 * mem) + (mem + 1) storage_dqgmres_bytes(mem, n) = nbits * storage_dqgmres(mem, n) expected_dqgmres_bytes = storage_dqgmres_bytes(mem, n) diff --git a/test/test_diom.jl b/test/test_diom.jl index 4f1a8ecea..62a38b198 100644 --- a/test/test_diom.jl +++ b/test/test_diom.jl @@ -60,7 +60,7 @@ # Poisson equation in polar coordinates. A, b = polar_poisson(FC=FC) - (x, stats) = diom(A, b, memory=200) + (x, stats) = diom(A, b, memory=150) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ diom_tol) From 29cf54bec5fdb6482b933dcd7b8fd02150e3183f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 26 Sep 2022 00:45:27 -0400 Subject: [PATCH 043/182] Update test_solvers.jl --- test/test_solvers.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 17b3edf0b..a706cf3d0 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -526,10 +526,10 @@ function test_solvers(FC) │ t│ Vector{$FC}│ 64│ │ z│ Vector{$FC}│ 0│ │ w│ Vector{$FC}│ 0│ - │ P│Vector{Vector{$FC}}│ 10 x 64│ + │ P│Vector{Vector{$FC}}│ 9 x 64│ │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ L│ Vector{$FC}│ 10│ - │ H│ Vector{$FC}│ 12│ + │ L│ Vector{$FC}│ 9│ + │ H│ Vector{$FC}│ 10│ │warm_start│ Bool│ 0│ └──────────┴───────────────────┴─────────────────┘ """ @@ -576,7 +576,7 @@ function test_solvers(FC) │ V│Vector{Vector{$FC}}│ 10 x 64│ │ c│ Vector{$T}│ 10│ │ s│ Vector{$FC}│ 10│ - │ H│ Vector{$FC}│ 12│ + │ H│ Vector{$FC}│ 11│ │ warm_start│ Bool│ 0│ └─────────────┴───────────────────┴─────────────────┘ """ From facd67db97a2d4adf6d77abd35d1c99b0e5a6233 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 26 Sep 2022 15:35:37 -0400 Subject: [PATCH 044/182] Add a comment about complex matrices in gpu.md --- docs/src/gpu.md | 3 +++ test/gpu/nvidia.jl | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 4ce8ee448..20bea1656 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -80,6 +80,9 @@ opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x) x, stats = cg(A_gpu, b_gpu, M=opM) ``` +!!! note + You need to replace `'T'` by `'C'` in `ldiv_ic0!` if `A_gpu` is a complex matrix. + ### Example with a general square system ```julia diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 8faed479a..c72c5c6ba 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -31,25 +31,25 @@ include("../test_utils.jl") A_gpu = CuSparseMatrixCSC(A_cpu) P = ic02(A_gpu, 'O') - function ldiv_ic0!(y, P, x) + function ldiv_csc_ic0!(y, P, x) copyto!(y, x) sv2!('T', 'U', 'N', 1.0, P, y, 'O') sv2!('N', 'U', 'N', 1.0, P, y, 'O') return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csc_ic0!(y, P, x)) x, stats = cg(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 A_gpu = CuSparseMatrixCSR(A_cpu) P = ic02(A_gpu, 'O') - function ldiv_ic0!(y, P, x) + function ldiv_csr_ic0!(y, P, x) copyto!(y, x) sv2!('N', 'L', 'N', 1.0, P, y, 'O') sv2!('T', 'L', 'N', 1.0, P, y, 'O') return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csr_ic0!(y, P, x)) x, stats = cg(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 end @@ -69,25 +69,25 @@ include("../test_utils.jl") A_gpu = CuSparseMatrixCSC(A_cpu) P = ilu02(A_gpu, 'O') - function ldiv_ilu0!(y, P, x) + function ldiv_csc_ilu0!(y, P, x) copyto!(y, x) sv2!('N', 'L', 'N', 1.0, P, y, 'O') sv2!('N', 'U', 'U', 1.0, P, y, 'O') return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csc_ilu0!(y, P, x)) x, stats = bicgstab(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 A_gpu = CuSparseMatrixCSR(A_cpu) P = ilu02(A_gpu, 'O') - function ldiv_ilu0!(y, P, x) + function ldiv_csr_ilu0!(y, P, x) copyto!(y, x) sv2!('N', 'L', 'U', 1.0, P, y, 'O') sv2!('N', 'U', 'N', 1.0, P, y, 'O') return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csr_ilu0!(y, P, x)) x, stats = bicgstab(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 end From 460087ac87fda7b0fd5fbf8c4377ef48f69878b2 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 26 Sep 2022 15:49:10 -0400 Subject: [PATCH 045/182] Fix a typo in gpu.md --- docs/src/gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 20bea1656..69aef5c60 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -40,7 +40,7 @@ b_cpu = rand(200) A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) -# Solve a rectangular and sparse system on a Nvidia GPU +# Solve a rectangular and sparse system on an Nvidia GPU x, stats = lsmr(A_gpu, b_gpu) ``` From fd1f1ba6ce58c5e33710f2186e969c5ef5d9af32 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 26 Sep 2022 01:21:40 -0400 Subject: [PATCH 046/182] Remove allocations in roots_quadratic --- src/krylov_utils.jl | 37 +++++++++++++++++++++---------------- test/test_aux.jl | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index b16da57c0..c3dce2817 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -111,10 +111,10 @@ function roots_quadratic(q₂ :: T, q₁ :: T, q₀ :: T; # Case where q(x) is linear. if q₂ == zero(T) if q₁ == zero(T) - root = [zero(T)] - q₀ == zero(T) || (root = T[]) + root = tuple(zero(T)) + q₀ == zero(T) || (root = tuple()) else - root = [-q₀ / q₁] + root = tuple(-q₀ / q₁) end return root end @@ -123,26 +123,31 @@ function roots_quadratic(q₂ :: T, q₁ :: T, q₀ :: T; rhs = √eps(T) * q₁ * q₁ if abs(q₀ * q₂) > rhs ρ = q₁ * q₁ - 4 * q₂ * q₀ - ρ < 0 && return T[] + ρ < 0 && return tuple() d = -(q₁ + copysign(sqrt(ρ), q₁)) / 2 - roots = [d / q₂, q₀ / d] + root1 = d / q₂ + root2 = q₀ / d else # Ill-conditioned quadratic. - roots = [-q₁ / q₂, zero(T)] + root1 = -q₁ / q₂ + root2 = zero(T) end # Perform a few Newton iterations to improve accuracy. - for k = 1 : 2 - root = roots[k] - for it = 1 : nitref - q = (q₂ * root + q₁) * root + q₀ - dq = 2 * q₂ * root + q₁ - dq == zero(T) && continue - root = root - q / dq - end - roots[k] = root + for it = 1 : nitref + q = (q₂ * root1 + q₁) * root1 + q₀ + dq = 2 * q₂ * root1 + q₁ + dq == zero(T) && continue + root1 = root1 - q / dq + end + + for it = 1 : nitref + q = (q₂ * root2 + q₁) * root2 + q₀ + dq = 2 * q₂ * root2 + q₁ + dq == zero(T) && continue + root2 = root2 - q / dq end - return roots + return (root1, root2) end diff --git a/test/test_aux.jl b/test/test_aux.jl index 5ac2b401c..72815ff2f 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -36,54 +36,76 @@ @testset "roots_quadratic" begin # test roots of a quadratic roots = Krylov.roots_quadratic(0.0, 0.0, 0.0) + allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 0.0) @test length(roots) == 1 @test roots[1] == 0.0 + @test allocations == 0 roots = Krylov.roots_quadratic(0.0, 0.0, 1.0) + allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 1.0) @test length(roots) == 0 + @test allocations == 0 roots = Krylov.roots_quadratic(0.0, 3.14, -1.0) + allocations = @allocated Krylov.roots_quadratic(0.0, 3.14, -1.0) @test length(roots) == 1 @test roots[1] == 1.0 / 3.14 + @test allocations == 0 roots = Krylov.roots_quadratic(1.0, 0.0, 1.0) + allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 1.0) @test length(roots) == 0 + @test allocations == 0 roots = Krylov.roots_quadratic(1.0, 0.0, 0.0) + allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 0.0) @test length(roots) == 2 @test roots[1] == 0.0 @test roots[2] == 0.0 + @test allocations == 0 roots = Krylov.roots_quadratic(1.0, 3.0, 2.0) + allocations = @allocated Krylov.roots_quadratic(1.0, 3.0, 2.0) @test length(roots) == 2 @test roots[1] ≈ -2.0 @test roots[2] ≈ -1.0 + @test allocations == 0 roots = Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) + allocations = @allocated Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) @test length(roots) == 0 + @test allocations == 0 # ill-conditioned quadratic roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) + allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) @test length(roots) == 2 @test roots[1] == 1.0e+13 @test roots[2] == 0.0 + @test allocations == 0 # iterative refinement is crucial! roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) + allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) @test length(roots) == 2 @test roots[1] == 1.0e+13 @test roots[2] == -1.0e-05 + @test allocations == 0 # not ill-conditioned quadratic roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) + allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) @test length(roots) == 2 @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) @test isapprox(roots[2], -1.0, rtol=1.0e-6) + @test allocations == 0 roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) + allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) @test length(roots) == 2 @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) @test isapprox(roots[2], -1.0, rtol=1.0e-6) + @test allocations == 0 end @testset "to_boundary" begin From fb509d68eef6d39bb5a5bdebc49aa98507efcf92 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 26 Sep 2022 12:39:09 -0400 Subject: [PATCH 047/182] Test allocations of roots_quadratic for Julia >= 1.8 --- test/test_aux.jl | 57 +++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/test/test_aux.jl b/test/test_aux.jl index 72815ff2f..6f4f90398 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -36,76 +36,89 @@ @testset "roots_quadratic" begin # test roots of a quadratic roots = Krylov.roots_quadratic(0.0, 0.0, 0.0) - allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 0.0) @test length(roots) == 1 @test roots[1] == 0.0 - @test allocations == 0 roots = Krylov.roots_quadratic(0.0, 0.0, 1.0) - allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 1.0) @test length(roots) == 0 - @test allocations == 0 roots = Krylov.roots_quadratic(0.0, 3.14, -1.0) - allocations = @allocated Krylov.roots_quadratic(0.0, 3.14, -1.0) @test length(roots) == 1 @test roots[1] == 1.0 / 3.14 - @test allocations == 0 roots = Krylov.roots_quadratic(1.0, 0.0, 1.0) - allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 1.0) @test length(roots) == 0 - @test allocations == 0 roots = Krylov.roots_quadratic(1.0, 0.0, 0.0) - allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 0.0) @test length(roots) == 2 @test roots[1] == 0.0 @test roots[2] == 0.0 - @test allocations == 0 roots = Krylov.roots_quadratic(1.0, 3.0, 2.0) - allocations = @allocated Krylov.roots_quadratic(1.0, 3.0, 2.0) @test length(roots) == 2 @test roots[1] ≈ -2.0 @test roots[2] ≈ -1.0 - @test allocations == 0 roots = Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) - allocations = @allocated Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) @test length(roots) == 0 - @test allocations == 0 # ill-conditioned quadratic roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) - allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) @test length(roots) == 2 @test roots[1] == 1.0e+13 @test roots[2] == 0.0 - @test allocations == 0 # iterative refinement is crucial! roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) - allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) @test length(roots) == 2 @test roots[1] == 1.0e+13 @test roots[2] == -1.0e-05 - @test allocations == 0 # not ill-conditioned quadratic roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) - allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) @test length(roots) == 2 @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) @test isapprox(roots[2], -1.0, rtol=1.0e-6) - @test allocations == 0 roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) - allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) @test length(roots) == 2 @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) @test isapprox(roots[2], -1.0, rtol=1.0e-6) - @test allocations == 0 + + if VERSION ≥ v"1.8" + allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 0.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 1.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(0.0, 3.14, -1.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 1.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 0.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(1.0, 3.0, 2.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) + @test allocations == 0 + + allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) + @test allocations == 0 + end end @testset "to_boundary" begin From 1a582cd01f8481528397a58f3c238c335d004318 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 27 Sep 2022 07:21:12 -0400 Subject: [PATCH 048/182] Improve roots_quadratic --- src/cg.jl | 2 +- src/cgls.jl | 2 +- src/cr.jl | 4 +-- src/crls.jl | 4 +-- src/krylov_utils.jl | 78 ++++++++++++++++++++++--------------------- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- test/test_aux.jl | 81 +++++++++++++++++---------------------------- 8 files changed, 80 insertions(+), 95 deletions(-) diff --git a/src/cg.jl b/src/cg.jl index 212c68484..68a6e415d 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -164,7 +164,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; α = γ / pAp # Compute step size to boundary if applicable. - σ = radius > 0 ? maximum(to_boundary(x, p, radius, dNorm2=pNorm²)) : α + σ = radius > 0 ? maximum(to_boundary(n, x, p, radius, dNorm2=pNorm²)) : α kdisplay(iter, verbose) && @printf("%8.1e %8.1e %8.1e\n", pAp, α, σ) diff --git a/src/cgls.jl b/src/cgls.jl index 43fa5a6b6..a8d6e3c94 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -145,7 +145,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; α = γ / δ # if a trust-region constraint is give, compute step to the boundary - σ = radius > 0 ? maximum(to_boundary(x, p, radius)) : α + σ = radius > 0 ? maximum(to_boundary(n, x, p, radius)) : α if (radius > 0) & (α > σ) α = σ on_boundary = true diff --git a/src/cr.jl b/src/cr.jl index 0e93e7eaa..c18501b09 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -176,10 +176,10 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; (verbose > 0) && @printf("radius = %8.1e > 0 and ‖x‖ = %8.1e\n", radius, xNorm) # find t1 > 0 and t2 < 0 such that ‖x + ti * p‖² = radius² (i = 1, 2) xNorm² = xNorm * xNorm - t = to_boundary(x, p, radius; flip = false, xNorm2 = xNorm², dNorm2 = pNorm²) + t = to_boundary(n, x, p, radius; flip = false, xNorm2 = xNorm², dNorm2 = pNorm²) t1 = maximum(t) # > 0 t2 = minimum(t) # < 0 - tr = maximum(to_boundary(x, r, radius; flip = false, xNorm2 = xNorm², dNorm2 = rNorm²)) + tr = maximum(to_boundary(n, x, r, radius; flip = false, xNorm2 = xNorm², dNorm2 = rNorm²)) (verbose > 0) && @printf("t1 = %8.1e, t2 = %8.1e and tr = %8.1e\n", t1, t2, tr) if abspAp ≤ γ * pNorm * @knrm2(n, q) # pᴴAp ≃ 0 diff --git a/src/crls.jl b/src/crls.jl index b041f8e9f..329b5a5fe 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -151,10 +151,10 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; p = Ar # p = Aᴴr pNorm² = ArNorm * ArNorm mul!(q, Aᴴ, s) - α = min(ArNorm^2 / γ, maximum(to_boundary(x, p, radius, flip = false, dNorm2 = pNorm²))) # the quadratic is minimal in the direction Aᴴr for α = ‖Ar‖²/γ + α = min(ArNorm^2 / γ, maximum(to_boundary(n, x, p, radius, flip = false, dNorm2 = pNorm²))) # the quadratic is minimal in the direction Aᴴr for α = ‖Ar‖²/γ else pNorm² = pNorm * pNorm - σ = maximum(to_boundary(x, p, radius, flip = false, dNorm2 = pNorm²)) + σ = maximum(to_boundary(n, x, p, radius, flip = false, dNorm2 = pNorm²)) if α ≥ σ α = σ on_boundary = true diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index c3dce2817..575179eec 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -92,8 +92,8 @@ function sym_givens(a :: Complex{T}, b :: Complex{T}) where T <: AbstractFloat return (c, s, ρ) end -@inline sym_givens(a :: Complex{T}, b :: T) where T <: AbstractFloat = sym_givens(a, Complex{T}(b)) -@inline sym_givens(a :: T, b :: Complex{T}) where T <: AbstractFloat = sym_givens(Complex{T}(a), b) +sym_givens(a :: Complex{T}, b :: T) where T <: AbstractFloat = sym_givens(a, Complex{T}(b)) +sym_givens(a :: T, b :: Complex{T}) where T <: AbstractFloat = sym_givens(Complex{T}(a), b) """ roots = roots_quadratic(q₂, q₁, q₀; nitref) @@ -111,19 +111,19 @@ function roots_quadratic(q₂ :: T, q₁ :: T, q₀ :: T; # Case where q(x) is linear. if q₂ == zero(T) if q₁ == zero(T) - root = tuple(zero(T)) - q₀ == zero(T) || (root = tuple()) + q₀ == zero(T) || error("The quadratic `q` doesn't have real roots.") + root = zero(T) else - root = tuple(-q₀ / q₁) + root = -q₀ / q₁ end - return root + return (root, root) end # Case where q(x) is indeed quadratic. rhs = √eps(T) * q₁ * q₁ if abs(q₀ * q₂) > rhs ρ = q₁ * q₁ - 4 * q₂ * q₀ - ρ < 0 && return tuple() + ρ < 0 && return error("The quadratic `q` doesn't have real roots.") d = -(q₁ + copysign(sqrt(ρ), q₁)) / 2 root1 = d / q₂ root2 = q₀ / d @@ -150,36 +150,6 @@ function roots_quadratic(q₂ :: T, q₁ :: T, q₀ :: T; return (root1, root2) end - -""" - roots = to_boundary(x, d, radius; flip, xNorm2, dNorm2) - -Given a trust-region radius `radius`, a vector `x` lying inside the -trust-region and a direction `d`, return `σ1` and `σ2` such that - - ‖x + σi d‖ = radius, i = 1, 2 - -in the Euclidean norm. If known, ‖x‖² may be supplied in `xNorm2`. - -If `flip` is set to `true`, `σ1` and `σ2` are computed such that - - ‖x - σi d‖ = radius, i = 1, 2. -""" -function to_boundary(x :: Vector{T}, d :: Vector{T}, - radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where T <: Number - radius > 0 || error("radius must be positive") - - # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - radius²). - rxd = real(dot(x, d)) - flip && (rxd = -rxd) - dNorm2 == zero(T) && (dNorm2 = dot(d, d)) - dNorm2 == zero(T) && error("zero direction") - xNorm2 == zero(T) && (xNorm2 = dot(x, x)) - (xNorm2 ≤ radius * radius) || error(@sprintf("outside of the trust region: ‖x‖²=%7.1e, Δ²=%7.1e", xNorm2, radius * radius)) - roots = roots_quadratic(dNorm2, 2 * rxd, xNorm2 - radius * radius) - return roots # `σ1` and `σ2` -end - """ s = vec2str(x; ndisp) @@ -357,3 +327,37 @@ end macro kref!(n, x, y, c, s) return esc(:(reflect!($x, $y, $c, $s))) end + +""" + roots = to_boundary(n, x, d, radius; flip, xNorm2, dNorm2) + +Given a trust-region radius `radius`, a vector `x` lying inside the +trust-region and a direction `d`, return `σ1` and `σ2` such that + + ‖x + σi d‖ = radius, i = 1, 2 + +in the Euclidean norm. +`n` is the length of vectors `x` and `d`. +If known, ‖x‖² and ‖d‖² may be supplied with `xNorm2` and `dNorm2`. + +If `flip` is set to `true`, `σ1` and `σ2` are computed such that + + ‖x - σi d‖ = radius, i = 1, 2. +""" +function to_boundary(n :: Int, x :: Vector{T}, d :: Vector{T}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where T <: FloatOrComplex + radius > 0 || error("radius must be positive") + + # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - Δ²). + rxd = @kdotr(n, x, d) + flip && (rxd = -rxd) + dNorm2 == zero(T) && (dNorm2 = @kdot(n, d, d)) + dNorm2 == zero(T) && error("zero direction") + xNorm2 == zero(T) && (xNorm2 = @kdot(n, x, x)) + radius2 = radius * radius + (xNorm2 ≤ radius2) || error(@sprintf("outside of the trust region: ‖x‖²=%7.1e, Δ²=%7.1e", xNorm2, radius2)) + + # q₂ = ‖d‖², q₁ = xᴴd + dᴴx, q₀ = ‖x‖² - Δ² + # ‖x‖² ≤ Δ² ⟹ (q₁)² - 4 * q₂ * q₀ ≥ 0 + roots = roots_quadratic(dNorm2, 2 * rxd, xNorm2 - radius2) + return roots # `σ1` and `σ2` +end diff --git a/src/lsmr.jl b/src/lsmr.jl index 78db5db59..79d2543fb 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -287,7 +287,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # the step ϕ/ρ is not necessarily positive σ = ζ / (ρ * ρbar) if radius > 0 - t1, t2 = to_boundary(x, hbar, radius) + t1, t2 = to_boundary(n, x, hbar, radius) tmax, tmin = max(t1, t2), min(t1, t2) on_boundary = σ > tmax || σ < tmin σ = σ > 0 ? min(σ, tmax) : max(σ, tmin) diff --git a/src/lsqr.jl b/src/lsqr.jl index 083b2f9f9..e4973bd38 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -283,7 +283,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # the step ϕ/ρ is not necessarily positive σ = ϕ / ρ if radius > 0 - t1, t2 = to_boundary(x, w, radius) + t1, t2 = to_boundary(n, x, w, radius) tmax, tmin = max(t1, t2), min(t1, t2) on_boundary = σ > tmax || σ < tmin σ = σ > 0 ? min(σ, tmax) : max(σ, tmin) diff --git a/test/test_aux.jl b/test/test_aux.jl index 6f4f90398..f844368e8 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -36,102 +36,83 @@ @testset "roots_quadratic" begin # test roots of a quadratic roots = Krylov.roots_quadratic(0.0, 0.0, 0.0) - @test length(roots) == 1 @test roots[1] == 0.0 + @test roots[2] == 0.0 - roots = Krylov.roots_quadratic(0.0, 0.0, 1.0) - @test length(roots) == 0 + @test_throws ErrorException Krylov.roots_quadratic(0.0, 0.0, 1.0) roots = Krylov.roots_quadratic(0.0, 3.14, -1.0) - @test length(roots) == 1 @test roots[1] == 1.0 / 3.14 + @test roots[2] == 1.0 / 3.14 - roots = Krylov.roots_quadratic(1.0, 0.0, 1.0) - @test length(roots) == 0 + @test_throws ErrorException Krylov.roots_quadratic(1.0, 0.0, 1.0) roots = Krylov.roots_quadratic(1.0, 0.0, 0.0) - @test length(roots) == 2 @test roots[1] == 0.0 @test roots[2] == 0.0 roots = Krylov.roots_quadratic(1.0, 3.0, 2.0) - @test length(roots) == 2 @test roots[1] ≈ -2.0 @test roots[2] ≈ -1.0 - roots = Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) - @test length(roots) == 0 + @test_throws ErrorException Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) # ill-conditioned quadratic roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) - @test length(roots) == 2 @test roots[1] == 1.0e+13 @test roots[2] == 0.0 # iterative refinement is crucial! roots = Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) - @test length(roots) == 2 @test roots[1] == 1.0e+13 @test roots[2] == -1.0e-05 # not ill-conditioned quadratic roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) - @test length(roots) == 2 @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) @test isapprox(roots[2], -1.0, rtol=1.0e-6) roots = Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) - @test length(roots) == 2 @test isapprox(roots[1], 1.0e+7, rtol=1.0e-6) @test isapprox(roots[2], -1.0, rtol=1.0e-6) - if VERSION ≥ v"1.8" - allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 0.0) - @test allocations == 0 - - allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 1.0) - @test allocations == 0 - - allocations = @allocated Krylov.roots_quadratic(0.0, 3.14, -1.0) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(0.0, 0.0, 0.0) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 1.0) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(0.0, 3.14, -1.0) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 0.0) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(1.0, 0.0, 0.0) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(1.0, 3.0, 2.0) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(1.0, 3.0, 2.0) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(1.0e+8, 1.0, 1.0) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=0) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(-1.0e-8, 1.0e+5, 1.0, nitref=1) - @test allocations == 0 + allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) + @test allocations == 0 - allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=0) - @test allocations == 0 - - allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) - @test allocations == 0 - end + allocations = @allocated Krylov.roots_quadratic(-1.0e-7, 1.0, 1.0, nitref=1) + @test allocations == 0 end @testset "to_boundary" begin # test trust-region boundary - x = ones(5) - d = ones(5); d[1:2:5] .= -1 - @test_throws ErrorException Krylov.to_boundary(x, d, -1.0) - @test_throws ErrorException Krylov.to_boundary(x, d, 0.5) - @test_throws ErrorException Krylov.to_boundary(x, zeros(5), 1.0) - @test maximum(Krylov.to_boundary(x, d, 5.0)) ≈ 2.209975124224178 - @test minimum(Krylov.to_boundary(x, d, 5.0)) ≈ -1.8099751242241782 - @test maximum(Krylov.to_boundary(x, d, 5.0, flip=true)) ≈ 1.8099751242241782 - @test minimum(Krylov.to_boundary(x, d, 5.0, flip=true)) ≈ -2.209975124224178 + n = 5 + x = ones(n) + d = ones(n); d[1:2:n] .= -1 + @test_throws ErrorException Krylov.to_boundary(n, x, d, -1.0) + @test_throws ErrorException Krylov.to_boundary(n, x, d, 0.5) + @test_throws ErrorException Krylov.to_boundary(n, x, zeros(n), 1.0) + @test maximum(Krylov.to_boundary(n, x, d, 5.0)) ≈ 2.209975124224178 + @test minimum(Krylov.to_boundary(n, x, d, 5.0)) ≈ -1.8099751242241782 + @test maximum(Krylov.to_boundary(n, x, d, 5.0, flip=true)) ≈ 1.8099751242241782 + @test minimum(Krylov.to_boundary(n, x, d, 5.0, flip=true)) ≈ -2.209975124224178 end @testset "kzeros" begin From c45a492f4375e8e5648e58c2fcd2fa13b9e6ebbd Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 27 Sep 2022 20:45:49 -0400 Subject: [PATCH 049/182] Move callback_utils.jl file --- src/Krylov.jl | 2 - src/callback_utils.jl | 50 -------------- test/callback_utils.jl | 152 +++++++++++++++++++++++++++++++++++++++++ test/test_utils.jl | 108 +---------------------------- 4 files changed, 153 insertions(+), 159 deletions(-) delete mode 100644 src/callback_utils.jl create mode 100644 test/callback_utils.jl diff --git a/src/Krylov.jl b/src/Krylov.jl index 7c480896f..e3903e124 100644 --- a/src/Krylov.jl +++ b/src/Krylov.jl @@ -50,6 +50,4 @@ include("lnlq.jl") include("craig.jl") include("craigmr.jl") -include("callback_utils.jl") - end diff --git a/src/callback_utils.jl b/src/callback_utils.jl deleted file mode 100644 index eac362e5d..000000000 --- a/src/callback_utils.jl +++ /dev/null @@ -1,50 +0,0 @@ -export StorageGetxRestartedGmres - -export get_x_restarted_gmres! - -mutable struct StorageGetxRestartedGmres{S} - x::S - y::S - p::S -end -StorageGetxRestartedGmres(solver::GmresSolver; N = I) = - StorageGetxRestartedGmres(similar(solver.x), similar(solver.z), (N === I) ? similar(solver.p) : similar(solver.x)) - -function get_x_restarted_gmres!(solver::GmresSolver{T,FC,S}, A, - stor::StorageGetxRestartedGmres{S}, N) where {T,FC,S} - NisI = (N === I) - x2, y2, p2 = stor.x, stor.y, stor.p - n = size(A, 2) - # Compute yₖ by solving Rₖyₖ = zₖ with backward substitution. - nr = sum(1:solver.inner_iter) - y = solver.z # yᵢ = zᵢ - y2 .= y - R = solver.R - V = solver.V - x2 .= solver.Δx - for i = solver.inner_iter : -1 : 1 - pos = nr + i - solver.inner_iter # position of rᵢ.ₖ - for j = solver.inner_iter : -1 : i+1 - y2[i] = y2[i] - R[pos] * y2[j] # yᵢ ← yᵢ - rᵢⱼyⱼ - pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ - end - # Rₖ can be singular if the system is inconsistent - if abs(R[pos]) ≤ eps(T)^(3/4) - y2[i] = zero(FC) - inconsistent = true - else - y2[i] = y2[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ - end - end - - # Form xₖ = N⁻¹Vₖyₖ - for i = 1 : solver.inner_iter - @kaxpy!(n, y2[i], V[i], x2) - end - if !NisI - p2 .= solver.p - p2 .= x2 - mul!(x2, N, p2) - end - x2 .+= solver.x -end diff --git a/test/callback_utils.jl b/test/callback_utils.jl new file mode 100644 index 000000000..c5993c2a3 --- /dev/null +++ b/test/callback_utils.jl @@ -0,0 +1,152 @@ +mutable struct StorageGetxRestartedGmres{S} + x::S + y::S + p::S +end +StorageGetxRestartedGmres(solver::GmresSolver; N = I) = + StorageGetxRestartedGmres(similar(solver.x), similar(solver.z), (N === I) ? similar(solver.p) : similar(solver.x)) + +function get_x_restarted_gmres!(solver::GmresSolver{T,FC,S}, A, + stor::StorageGetxRestartedGmres{S}, N) where {T,FC,S} + NisI = (N === I) + x2, y2, p2 = stor.x, stor.y, stor.p + n = size(A, 2) + # Compute yₖ by solving Rₖyₖ = zₖ with backward substitution. + nr = sum(1:solver.inner_iter) + y = solver.z # yᵢ = zᵢ + y2 .= y + R = solver.R + V = solver.V + x2 .= solver.Δx + for i = solver.inner_iter : -1 : 1 + pos = nr + i - solver.inner_iter # position of rᵢ.ₖ + for j = solver.inner_iter : -1 : i+1 + y2[i] = y2[i] - R[pos] * y2[j] # yᵢ ← yᵢ - rᵢⱼyⱼ + pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + end + # Rₖ can be singular if the system is inconsistent + if abs(R[pos]) ≤ eps(T)^(3/4) + y2[i] = zero(FC) + inconsistent = true + else + y2[i] = y2[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ + end + end + + # Form xₖ = N⁻¹Vₖyₖ + for i = 1 : solver.inner_iter + Krylov.@kaxpy!(n, y2[i], V[i], x2) + end + if !NisI + p2 .= solver.p + p2 .= x2 + mul!(x2, N, p2) + end + x2 .+= solver.x +end + +mutable struct TestCallbackN2{T, S, M} + A::M + b::S + storage_vec::S + tol::T +end +TestCallbackN2(A, b; tol = 0.1) = TestCallbackN2(A, b, similar(b), tol) + +function (cb_n2::TestCallbackN2)(solver) + mul!(cb_n2.storage_vec, cb_n2.A, solver.x) + cb_n2.storage_vec .-= cb_n2.b + return norm(cb_n2.storage_vec) ≤ cb_n2.tol +end + +mutable struct TestCallbackN2Adjoint{T, S, M} + A::M + b::S + c::S + storage_vec1::S + storage_vec2::S + tol::T +end +TestCallbackN2Adjoint(A, b, c; tol = 0.1) = TestCallbackN2Adjoint(A, b, c, similar(b), similar(c), tol) + +function (cb_n2::TestCallbackN2Adjoint)(solver) + mul!(cb_n2.storage_vec1, cb_n2.A, solver.x) + cb_n2.storage_vec1 .-= cb_n2.b + mul!(cb_n2.storage_vec2, cb_n2.A', solver.y) + cb_n2.storage_vec2 .-= cb_n2.c + return (norm(cb_n2.storage_vec1) ≤ cb_n2.tol && norm(cb_n2.storage_vec2) ≤ cb_n2.tol) +end + +mutable struct TestCallbackN2Shifts{T, S, M} + A::M + b::S + shifts::Vector{T} + tol::T +end +TestCallbackN2Shifts(A, b, shifts; tol = 0.1) = TestCallbackN2Shifts(A, b, shifts, tol) + +function (cb_n2::TestCallbackN2Shifts)(solver) + r = residuals(cb_n2.A, cb_n2.b, cb_n2.shifts, solver.x) + return all(map(norm, r) .≤ cb_n2.tol) +end + +mutable struct TestCallbackN2LS{T, S, M} + A::M + b::S + λ::T + storage_vec1::S + storage_vec2::S + tol::T +end +TestCallbackN2LS(A, b, λ; tol = 0.1) = TestCallbackN2LS(A, b, λ, similar(b), similar(b, size(A, 2)), tol) + +function (cb_n2::TestCallbackN2LS)(solver) + mul!(cb_n2.storage_vec1, cb_n2.A, solver.x) + cb_n2.storage_vec1 .-= cb_n2.b + mul!(cb_n2.storage_vec2, cb_n2.A', cb_n2.storage_vec1) + cb_n2.storage_vec2 .+= cb_n2.λ .* solver.x + return norm(cb_n2.storage_vec2) ≤ cb_n2.tol +end + +mutable struct TestCallbackN2LN{T, S, M} + A::M + b::S + λ::T + storage_vec::S + tol::T +end +TestCallbackN2LN(A, b, λ; tol = 0.1) = TestCallbackN2LN(A, b, λ, similar(b), tol) + +function (cb_n2::TestCallbackN2LN)(solver) + mul!(cb_n2.storage_vec, cb_n2.A, solver.x) + cb_n2.storage_vec .-= cb_n2.b + cb_n2.λ != 0 && (cb_n2.storage_vec .+= sqrt(cb_n2.λ) .* solver.s) + return norm(cb_n2.storage_vec) ≤ cb_n2.tol +end + +mutable struct TestCallbackN2SaddlePts{T, S, M} + A::M + b::S + c::S + storage_vec1::S + storage_vec2::S + tol::T +end +TestCallbackN2SaddlePts(A, b, c; tol = 0.1) = + TestCallbackN2SaddlePts(A, b, c, similar(b), similar(c), tol) + +function (cb_n2::TestCallbackN2SaddlePts)(solver) + mul!(cb_n2.storage_vec1, cb_n2.A, solver.y) + cb_n2.storage_vec1 .+= solver.x .- cb_n2.b + mul!(cb_n2.storage_vec2, cb_n2.A', solver.x) + cb_n2.storage_vec2 .-= solver.y .+ cb_n2.c + return (norm(cb_n2.storage_vec1) ≤ cb_n2.tol && norm(cb_n2.storage_vec2) ≤ cb_n2.tol) +end + +function restarted_gmres_callback_n2(solver::GmresSolver, A, b, stor, N, storage_vec, tol) + get_x_restarted_gmres!(solver, A, stor, N) + x = stor.x + mul!(storage_vec, A, x) + storage_vec .-= b + return (norm(storage_vec) ≤ tol) +end diff --git a/test/test_utils.jl b/test/test_utils.jl index fbfe2e4e0..0ac2e1538 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -1,6 +1,7 @@ include("get_div_grad.jl") include("gen_lsq.jl") include("check_min_norm.jl") +include("callback_utils.jl") # Symmetric and positive definite systems. function symmetric_definite(n :: Int=10; FC=Float64) @@ -363,110 +364,3 @@ function check_reset(stats :: KS) where KS <: Krylov.KrylovStats end end end - -# Test callback -mutable struct TestCallbackN2{T, S, M} - A::M - b::S - storage_vec::S - tol::T -end -TestCallbackN2(A, b; tol = 0.1) = TestCallbackN2(A, b, similar(b), tol) - -function (cb_n2::TestCallbackN2)(solver) - mul!(cb_n2.storage_vec, cb_n2.A, solver.x) - cb_n2.storage_vec .-= cb_n2.b - return norm(cb_n2.storage_vec) ≤ cb_n2.tol -end - -mutable struct TestCallbackN2Adjoint{T, S, M} - A::M - b::S - c::S - storage_vec1::S - storage_vec2::S - tol::T -end -TestCallbackN2Adjoint(A, b, c; tol = 0.1) = TestCallbackN2Adjoint(A, b, c, similar(b), similar(c), tol) - -function (cb_n2::TestCallbackN2Adjoint)(solver) - mul!(cb_n2.storage_vec1, cb_n2.A, solver.x) - cb_n2.storage_vec1 .-= cb_n2.b - mul!(cb_n2.storage_vec2, cb_n2.A', solver.y) - cb_n2.storage_vec2 .-= cb_n2.c - return (norm(cb_n2.storage_vec1) ≤ cb_n2.tol && norm(cb_n2.storage_vec2) ≤ cb_n2.tol) -end - -mutable struct TestCallbackN2Shifts{T, S, M} - A::M - b::S - shifts::Vector{T} - tol::T -end -TestCallbackN2Shifts(A, b, shifts; tol = 0.1) = TestCallbackN2Shifts(A, b, shifts, tol) - -function (cb_n2::TestCallbackN2Shifts)(solver) - r = residuals(cb_n2.A, cb_n2.b, cb_n2.shifts, solver.x) - return all(map(norm, r) .≤ cb_n2.tol) -end - -mutable struct TestCallbackN2LS{T, S, M} - A::M - b::S - λ::T - storage_vec1::S - storage_vec2::S - tol::T -end -TestCallbackN2LS(A, b, λ; tol = 0.1) = TestCallbackN2LS(A, b, λ, similar(b), similar(b, size(A, 2)), tol) - -function (cb_n2::TestCallbackN2LS)(solver) - mul!(cb_n2.storage_vec1, cb_n2.A, solver.x) - cb_n2.storage_vec1 .-= cb_n2.b - mul!(cb_n2.storage_vec2, cb_n2.A', cb_n2.storage_vec1) - cb_n2.storage_vec2 .+= cb_n2.λ .* solver.x - return norm(cb_n2.storage_vec2) ≤ cb_n2.tol -end - -mutable struct TestCallbackN2LN{T, S, M} - A::M - b::S - λ::T - storage_vec::S - tol::T -end -TestCallbackN2LN(A, b, λ; tol = 0.1) = TestCallbackN2LN(A, b, λ, similar(b), tol) - -function (cb_n2::TestCallbackN2LN)(solver) - mul!(cb_n2.storage_vec, cb_n2.A, solver.x) - cb_n2.storage_vec .-= cb_n2.b - cb_n2.λ != 0 && (cb_n2.storage_vec .+= sqrt(cb_n2.λ) .* solver.s) - return norm(cb_n2.storage_vec) ≤ cb_n2.tol -end - -mutable struct TestCallbackN2SaddlePts{T, S, M} - A::M - b::S - c::S - storage_vec1::S - storage_vec2::S - tol::T -end -TestCallbackN2SaddlePts(A, b, c; tol = 0.1) = - TestCallbackN2SaddlePts(A, b, c, similar(b), similar(c), tol) - -function (cb_n2::TestCallbackN2SaddlePts)(solver) - mul!(cb_n2.storage_vec1, cb_n2.A, solver.y) - cb_n2.storage_vec1 .+= solver.x .- cb_n2.b - mul!(cb_n2.storage_vec2, cb_n2.A', solver.x) - cb_n2.storage_vec2 .-= solver.y .+ cb_n2.c - return (norm(cb_n2.storage_vec1) ≤ cb_n2.tol && norm(cb_n2.storage_vec2) ≤ cb_n2.tol) -end - -function restarted_gmres_callback_n2(solver::GmresSolver, A, b, stor, N, storage_vec, tol) - get_x_restarted_gmres!(solver, A, stor, N) - x = stor.x - mul!(storage_vec, A, x) - storage_vec .-= b - return (norm(storage_vec) ≤ tol) -end From 100792f45f37c777f3bbf98cdfc1290b7be6184f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 27 Sep 2022 20:46:06 -0400 Subject: [PATCH 050/182] Update tips.md --- docs/src/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tips.md b/docs/src/tips.md index 604c0633d..ca3d927bd 100644 --- a/docs/src/tips.md +++ b/docs/src/tips.md @@ -23,7 +23,7 @@ BLAS.set_num_threads(N) # 1 ≤ N ≤ NMAX BLAS.get_num_threads() ``` -The recommended number of BLAS threads is the number of physical and not logical cores, which is in general `N = NMAX / 2`. +The recommended number of BLAS threads is the number of physical and not logical cores, which is in general `N = NMAX / 2` if your CPU supports simultaneous multithreading (SMT). By default Julia ships with OpenBLAS but it's also possible to use Intel MKL BLAS and LAPACK with [MKL.jl](https://github.com/JuliaLinearAlgebra/MKL.jl). From 426557de1a0cf962186ab6001b61ab510c3759fc Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Wed, 28 Sep 2022 00:15:20 -0400 Subject: [PATCH 051/182] Add six Krylov processes --- docs/make.jl | 1 + docs/src/graphics/arnoldi.png | Bin 0 -> 118889 bytes docs/src/graphics/golub_kahan.png | Bin 0 -> 135734 bytes docs/src/graphics/hermitian_lanczos.png | Bin 0 -> 107418 bytes docs/src/graphics/montoison_orban.png | Bin 0 -> 176065 bytes docs/src/graphics/nonhermitian_lanczos.png | Bin 0 -> 136590 bytes docs/src/graphics/saunders_simon_yip.png | Bin 0 -> 149026 bytes docs/src/processes.md | 278 +++++++++++++ src/Krylov.jl | 1 + src/krylov_processes.jl | 459 +++++++++++++++++++++ test/gpu/amd.jl | 14 +- test/gpu/gpu.jl | 38 ++ test/gpu/intel.jl | 14 +- test/gpu/metal.jl | 14 +- test/gpu/nvidia.jl | 14 +- test/runtests.jl | 1 + test/test_processes.jl | 148 +++++++ 17 files changed, 962 insertions(+), 20 deletions(-) create mode 100644 docs/src/graphics/arnoldi.png create mode 100644 docs/src/graphics/golub_kahan.png create mode 100644 docs/src/graphics/hermitian_lanczos.png create mode 100644 docs/src/graphics/montoison_orban.png create mode 100644 docs/src/graphics/nonhermitian_lanczos.png create mode 100644 docs/src/graphics/saunders_simon_yip.png create mode 100644 docs/src/processes.md create mode 100644 src/krylov_processes.jl create mode 100644 test/gpu/gpu.jl create mode 100644 test/test_processes.jl diff --git a/docs/make.jl b/docs/make.jl index 0ad50d52f..db49cb759 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -12,6 +12,7 @@ makedocs( sitename = "Krylov.jl", pages = ["Home" => "index.md", "API" => "api.md", + "Krylov processes" => "processes.md", "Krylov methods" => ["Symmetric positive definite linear systems" => "solvers/spd.md", "Symmetric indefinite linear systems" => "solvers/sid.md", "Unsymmetric linear systems" => "solvers/unsymmetric.md", diff --git a/docs/src/graphics/arnoldi.png b/docs/src/graphics/arnoldi.png new file mode 100644 index 0000000000000000000000000000000000000000..9ef8bd3a3a54cfe2456d0c566f95e67a4d000f8b GIT binary patch literal 118889 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
k%Hif4pb{T}%N+d0ly1|@|9NYQyV$_JX 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 GP5%o5W$J|h literal 0 HcmV?d00001 diff --git a/docs/src/graphics/golub_kahan.png b/docs/src/graphics/golub_kahan.png new file mode 100644 index 0000000000000000000000000000000000000000..3baec4b4a040921ba555e4113f11a635dbf35e7e GIT binary patch literal 135734 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 T5C1$)euMUL{U7p=+F$)|8gO!b literal 0 HcmV?d00001 diff --git a/docs/src/graphics/hermitian_lanczos.png b/docs/src/graphics/hermitian_lanczos.png new file mode 100644 index 0000000000000000000000000000000000000000..c70082e72d1133e16f5fd6557b2f4c8c860b20e3 GIT binary patch literal 107418 zcmd43byQaE)-`_Hh}bmP(k&t(f+C=TAT5nj(nvR07=U09N=S*c2#7RTfJh@Pp)`Vo zbboW7@BPk;=Q)4>#*Z=1I6iW_@4c^UU2DxX=Un@Ng8aGdz}Cpf z%Fe{ver!Xr7(T>IeCVvL(N%j>Ypa7Qrj|w|MQ%OlbD&3RCQrand*er#w z^QgFh;6MGf=dO0jF= zi^BZ(_HyRlFY)W|7KT#(elcf7oTny<-xT z->7#Q)M_O#-?^cg`p)&kvv%hu#_R|o$A`t7<`$KzsQK76Ihi;m{?~@lnpTh7 zd}(;byGyoZZh68!nDMeoAwSonQn9_~A9q>kpQ2%*`pnyC;obAL_^9u_Gfyj;n$(n2 zUMVL8YrPD9`gB8(%qAx%r_3$?d8daD#|SEGYSP~b;ZJ7s6{7j)?_H}da4)l!d2ge6 zW7Q#I-Y@Zw#gI&U^z+lbPc=0)gPyM28t?4-QeIy7eWJ6_4bj2NbMn7jvoFi6QN}tt zbeF_rW@ntZ_b!Fx#-gu`0$^qokZ50saek55la#mh(V z8kAB92?s(^m-sj@&84S! zFr2Ws&vE%R<#lP5z;~A4?|!(w<;amEXRm1$x=vsJ@qV4Nn;Xp;)@3`H%c=-goYLZIB5bbnQ-{Px`{w(h1{;IILY-_#b(xYobg`N9_mHoO=~saGN-dkxh8ukqAvCVh^opw?d^~6KjnHfIW04j|7!@(P8Jpx)*u;` zG+iBA@hw}n95=1sNO}|z@zBqYLezDN4Chk1Oo2TpK{4iC4u9*Ge8*_5i4pq2ev%7BT9=@1Lc7SfOIZhf2dTS|$N#h%NL zo<6;O=g!9PQ*KRzO7A3JwdB~u%74!?Z>~+g%yZ}7J=vK=-9nM3G`#?7F3tC)rKN+k zK^&J$OMGdv+Em!7-dT0{>^>rG;v@ElLBNdCb7|t7v#aY@p*^mRRbhfV&6{60nrUSk zZ!ah;^nkAy!ths!#!rDW;LXj=RrZBSn@+U5&|?ZDvL^ z3cp`YQCGa*O2UCE3Fguo?^<1*6(Kx=Ij8VgiHV8cfe%k0L@D-*=A18m6~p4R`IvCUF8Zg>o|)KCoc$Wa zaZk6+sv8^bPF*Zs{N{<2q4HYSb-QC^E-6;-VyLPSI@$%)%$<^Gw z-Q6)xjINn)t-EwHmOGrfvgX2lA3iKeHz?b+d-qkv@B;EK3$gxA5`tQI~y5J2=U-_BeW*Cz;414Y@1Jj; z?eYr9a~Rfddu!_ar*|vqbfQX(a=g5Hk-IR?w#5YPNvF}bLU-f-_1(v4XlUH$`$E(L z7?2vLpPl~DC%ZO^BvryG!{WsIs$T_j2no@$v9WCm-2T{e!uq@pz~EI8j;cfEEQ9UL@ydkF{& zaY25J>+o%MwUqnB!ESABHRJP|@1sF|NG{>0ArM48H9fsgLPA1}{O!ML@nDcDCwZe< zn(p=>nK&0Q>rT-?F0Ce$Vk!E0msKCrBqcVQ zrG^#1e*KzO#GY09A-&OCYZDVzZtWa}M?<@*$PaD2yJMfv+qZ9H3B(sVaP}_6&Dhu@V#`y?tU;yUdQ!}$M4k!TG4I$fs@`B~ zavf+RX7#zK%Q3{fkI44_xGPQE6_?h#i^KEJ_zXy-Aa>P{#VgZ1vCG(Tzk!-an)6m^ zx5L93MV-eN72q-AEt4g_-#S@h7+qZ_lWyGje)W}}o66_QlgoSmKDfBLk+;a9_% zWVO`VBvp>Ng@unu_bC(HI(c_cGY>H_Z9-BD^@`h{Xs%6-;_)6{pjGu%Zjr>*)qH_;>HHpBVx${(o5pYcNS;I7}}l6{P*S94fxkR+sl0MhD0=PUL@mP^2Qh9Uj9TW!|jvp z+<8t?aw8@ACT$%ZJpCqdaq+h%b?ZGnJu{4@Dles~84Ne2p1*M6jEoF| zJLmMzojP^u=fJ={RGyd%PnlU2WA2ofT}r!)+V&Ia2PlbezqqI06DHXh32*WQ#n{av zjw579{Jv!bq;Pi^?e6aWF+9vMzZe)4g%hzBo^QQTWr4!-2oPZ3#tHg@!!UX`RK2}w2 z7ZVdpP)|SH={8ol-pzwtOIms}V6M$jJ=2}LcMl^!p(fu82-umVntT_#{h_h3G0A|L ziRmY9G{<$C{k2}P|FxPIAL{CM>+0&7)W6({Uu?0qwys*C;nDq!XqrI1dA{s&cFl<| zIro@4DOEjvy;+v#WP4Yap84Xv1D^L0XFqW3(me&gyv>A#gBBSqe#<2U}XG(Y)dF;c{V9GhK&dsQrSb!a6p)E4umln*$ZkDfng z%f004DpLRL8?kV;wF(xU1$3kz=-v1XO0P@}*5L>~ImJxk)XAe9ZcYoyvQ|=h(lm*0 zy|Oenkz?EEqqc}EuOrzFR7(N*?%uWQ%J|(LpxnDy%dcNQ;OEDT%VhsmoMUWwgUIam z$N2@nabT*r$pQ(GhodTW{W>phVR|%Vv1oB&p}XvWSD*mDzAh5>;P>pbk>$O6_d2v$ zwddT#cNz4QK9mj_4m)nTC-j7+59+l3U~RPPY`)tpH<1Sb8V?E!Ym9k%Elq48MFD|5 zJL$x|Fgs?QwO)cZ_HzT9p_iXD8q>A#MLiZ&?6zZ-DN*L`wq{<7 zRZG>}Y#=Qyt^eWHru)9WXSKD%U-5ErbMG)UH4R-xdi~Mc`?Aq`VQQ$Q&@J0o+*{%% zVD**iaN&f@X)mhP?fNT`$?r7LPE1Ukzj(1hJD_O3pCN*7|F`=3H~J;y7VSB^oyIz) zq^0i~hoz=+{c1|x=H=xDB-LVavlDwg5xKgw<R=Ao0XBFp-FWrR&6+33s6gHpgMx@>c@@AG!+F8oaJd9hnu*7X=w3*a~2jUoy)+R zwTa3sQ^UGvBRcOYNPUXim9K?J_9!+l>N9xt;#_E~K<4i@`j4x|5j_(YsPLv-SWzQT=Yq z`qt_+{^LW2?Mbhdg7L}8r;Ih9KAokY(A-=@{fY{gIY* z{U7gdZnpbfALonmP+QJ+@Zjm;+3Jc4S^ME8IX^mnX^MR!DuEoA<*h4`IUkISSUlCe z-Tbg&SSe11D%n5XWzx9Zrf9BvTk_|fJ9ad=M!L<8asfFV1dniMmqc%G?|py|pNXC_ zbI;2-!X-{!9xM%JS)Dlu%BK!5SeTf$9@1SA+q`ij8Nj|`tn_w-8b!KM1vQ^({n4r- zpt5cd0U`$Tb91!&2GDI~t3}RF4s5%0>C(^serDhe7GwY>;3`(fu}-d{l?8J}R1!Ho zK|#U7$Py%QO2B+-9GjSP0sHlTe)QHy40n(J{G?z0gh?dw=JMh!jfRFsMpqxlr}rq1vytbJy2gmT`1^H@7gH=8f$Ft))LV{!_oJ$m8&-Q#$6wh%M8I zwA|+RXoo^YUkML1A#F(^jY>T~X7u^~*4EqC*Oyf%?``j=^70EAU4Wr-B26tV>J#NGktPd* z7A*=JH*I==4n1NS5zdW-8`qkRjMELQ$6Q?W^XJbPql)15H;BXZOwjrTD)rNLL3VbT zi{V0wft(sj1L|Y#xdwk))>J_iQAuD6Y;(x;K`GS)YNQ2+$)Ce%#lh`7%*@hYMCb}X z@Pox!Um?I_PLq_XTc{bxswlw{92*;pp63x~Cpz)xMC$4k?etppNM*PXA>vTqiYhN6 zZTCIH$oKm7>$O9}y}d_0m!~gBFoe6r#l<~{^jgsn5)t_|m8KYbSIBcIC#8AdyLB-Y zvDLthe6IU(#H7g%eMZecZiy@I0Y=;-K}Iy=g&teB)4vqFXZr2c1CU5QfLF4eLi~M< z=#p{=JMy!e2m~wbOZ8{=v1>1NTB z;Eb@KnKgM+^PKII*45aks$Jl$Xda1_Nd~T{E>%l#Vs65qTnO3Oeymf6 zBLwssJ?EbaM8V3QZ}FFC5Mxwanr~~}xCD%G{Ez+t9h6_wxW=0I4a zW-2w>fTpPsWfj}G@vCxm+s88=9X)#V^Gh2!;cVoM@!44#>s?yqbX-ZrWHR5e>aS#=e05&ipvp`jr*w3mJ}z5xL=-(Jb>g@%xmoBIIN$Z&_? z*eux(BSlRd@6kOG5NK z6Q}8NOusR=8i=|4k(^urBc+E{?6g`jTH1&))xW4=R|hIzi7ezOQ!7#Mcee%qg>l}h z#~GGC31yhyR37SWruELEj=>zbZ1ZA2QYIju%nTQf^4vhA*YU;v;xn61;|zVcvJ~mL zaDo4bM&~(ia;ncDwTuoJoIiiR*Fu>{O9-P3fC@?QvjiOLY_3cT##n70YXZP&gD}|Dn>bL`80eD97;yd~tLE%@7rG&7MHTUE8&W)s>-Q9zfY^BO_<>7=U)xg2&4IK*j@UYj?shmPB3Val}72DOrtPB~h^gxvXJy3?3$xigbY z;I5O7BfM3zYDY$e55cNK|vpf@ocU)s8JXGqYe7%ATr+a0~^{@!N4zh~2| z&ewQt9Jdt~Pc+&XM;p4%61mBEK*Xk7ve0J3`t_1&x`oFDCHi!;r?|4WopxUsiVu}g z0;S!`PjVcrJ!4&oN*U_0lqQncOw=+zdV#NI0R@>ueIDvc2`Aj=uBWOWk@n+F^y+-h zLq+aA^4x1>WzjmK2Ni2CH#axIc!@05gbK)B^8&>coB0@0${^)U`4^%Z=$kZ%(`+{& zuQ8vb!k*e1Lb{KQ@O}C6sA7@3%Tr^~Dm_0a0G6#DlgaAoF={E@+R!Qc=8X*v$#{5p zr*}qB% zyx6KBTW)Y)!TG}BBNNQYrmwGWzfd&W>E^oF_We6Uga>O# zR)`lDHXlUQ9;9c2t9jY|`Ow+BgAa!W1}-1W-2v1>;<+}*Yqa$Z0L$P*jcAEYayIep zbZ_s}zOXsE`ibZ+`ZV-tN?&(un&-_{;1Uv2=kULM`}Tc*{}XJ|D~CJi1# zw~IdC-kK_>3R$+jswJ$O(x4%{qRJ+PKLrDD6+^q!^6WVsxxM*1iMUT1mfOs zr{P9Se7H2$YJ^k2zG@{H`$`0dhWfmJf4XWCkcNnG>BWb1{F^VS9@P7PUI2ii+z%e3 z?YXz1@caN`$rP%&6$niQtlm(^%4q)BSO3PB7xprW*q^u6LN6~i_hSQz;BWXGN7Qep z-`Pd8k zxD|eniHRYR2+9kQp1Ox0lo-?$&^a+9&T0G*<}1U6znM*d;CyrS)8^#QGBPr_kz%#) z_)m(WVx>-xS)G=amV<>tgSvFir+!p^t%X73MBy)sV0oy+neGC(^2RvFAQG4ekQ4U$FU;?=s2! zt0B=qNsL5rRZkZwnPexST|ctu%C?S78E(0;ZUZlx7F@`@4MNvFF^`33K>-c9LE!N; zGL4^8#@qCi<~UEp^v@%gR(&0i`)nuChK}eS2%U%%xu}0EtuG!wKGgc+(IXOg3XlsG zm-f65WK+K0_mwL39Vid_C2>hz2{H=0W0J$m!>IB^FwsNc1#L+QRI_FL&#FD3*=OXm zXa!dLnVK-S(ODVg)|QqiZ~-lO4#^tJYxJ#zIkqW@tRFwFq%tr^dd$!T%EZ-qB8)#2 zuP$2`N~z=`H)xJ*Ys5C~I&dJ(DIaBTL|rvmtug;MR*$(a%b;urqqwKGZJ4j`HpmVJ zN(BY@zYWNe2>)LeCtaslfxtM~?`+Ve4k7VulX;1JPENzE{T_k*whhqd0r=3Wm~_2! zJ9g|~?#S6hB3ifaO3KR7YRN{+MPY|eo@BtUo}F@gcVW4`qhq_(Wmd{LFL4+Dh=eMtF=*QsLS}KTrR9CBW0D(pz$sL z1#U0^>FD6mqmthoM_TyE4juJXxES>8*?mw;QQEn-%+nQK@`SL>&%YP9PCYP{*CQM~ z4DhX9;GC`&mX^lj?%`n+pqFi>gK|W6=xrO+LH1lPef`5|7w>_{_`1KRMtt=%{`9sj zJR$-BZ3o2Ra-VebW^T~%O389vPKLPvN%3qd8*U&>^TuJDw4ubjCt%PR^D0H`YndLU5%yeB?+~bsDdz z`&{i0M`dGGT(mBqOGHF-fI0M#%eB0UG$4UDSpvnc({au=Td(gg95UL9p1feP`lM@i zns)9Uut#N}U-*o^Qd+cTYy#Qt-e=s&MgeN$c%sQ3UiV2YvS`(pW^&koTk-Mn7d-qr zI4zrB?=u6iXndAi5~$)H7au=N>7JXH7lRaLTw?zHU24dv0?nQAHp0Xodj+pN3tGjhXcg3{syQ82_Vb z$)0F;n2D)`eyCu^S$+^~-HYl2h0k28fc>do$wwqLHbtG;5b<2-W=JF(dk{I?0FU>H zBW$4S)tl}lBRdU#$gu1={#}zD!X3=NgrgT5@x5$h;r!S9dy8C(L!=}ne|)9 zIlFq$=xZQfF3(z0OWa#C!4X<{s?inK*-^`Sxh;tdr{@7!u0R7Y?z^n+SH)^%U|4L8t>Ci`YFKqL)$~wD&Sg)GWCJTDd^=kfzC@`pwLV6 zg%9I-ds&OQwS}~#ox|_8#$K^fTdw=hHaRRAo`ODu{CZ1G3lCOVB;U(ZD<><hm$~dkI=O;`O?%Rj6LCzC+R&mDY;)B;Uiz4V>@?0| zTwYqjKbnrX?``&HJiM0qPAw#)p_ z&>GoB4hY-6Oc8G>cbNZU1V4aUaw@o?H`&=C`_B3l78X_yJAmF#)tI$ye66E3?4qEs zYiVie4aD%LnT)hr^$I)A%I@EKh`6|Kne%XJHmCm?9s!G%Hwns* zQV-gC1o=m%48T4jVi~M`*{mTU`kNyP(bFEv{(1Fy`F+Ws)0?JS0-vAhvfsadzj~fM z+iTrIAEf9X%d=gAiNy^R1k8RTV@2-$@nvfiMR|4gJDG67bu|xGb8Y*`3B2>I6P!S1 zsYPAPCgzLJZlJ?aAIpf%^?ujc*_ojarbIRVSuVDipo}i$UhoJW(jsqCQS$ajX>K-s z%ASJ8j9zTMcP}c*W>m+rTbs6B|M_v#?XgJ6TBfZkYvf1vPV!U*E{H%xPb!)Qid;iB zv7F5cT6>mEo${GPlxD*TiPvCuzxW%Ou$?(8YuWF#yx?&A`|ptv!?OBwU2+B5X!tUn zDa<>@kACv@E|ELayTHCrT%(2{IodAjtQX`-H1|MfS8kJ{dgY2Ah%2cI(Sg3vQL7*! zmFsBJcJ0}73+J4Ou^f-ZchzD|b4y!vUueCmq?NsEbnP19pZLYUqo{h|j>-hs5uKa5 z$sn}6@xTW4FE2>XcD%D>fXV;`Kf~|>@=wWNefK;_ZT0vn%`S7sefu2RUXnM49yg6~ zqRs*KC)5`317{KH1m6vPO&`~3$#>#GC#-6_M)Z&AE)#KzHJZcjJ%0Q++QsUZg<0;x z%x_;dG~U2}+$~PbTXjk`!?0YR&Xu_Pa z&0ug#fM%eu-$bU5T~5goeV4B9aI7lVZlD&8C!xF&h){#C^R3-sPR@(*sZnXENFoWk zg?V+$*m7Rm-t!SN@NUJx`@tOG28}Pg zV$zWCSi^ww?Ss86`XEZdRMob^ydwff)Q7=(8IU$Dnb!h(-vLC09XIIcDGT^l$lP_kV4eR`Con57t;5pG^^;K z{$rQXuHt~GsDm3fZ#Lr+PF7&8#eq6*-b6tF8BxHK@d}<|%JbZ!2U0j7Y!bqnfl{&H ze7N(%RHA$s6Th*huS}doq?pGzOnRv^NWer@F)n!;YSa3pCcWS#IR&(@$3a1H%Afm3 zM*NV(u0l0XhmV9nb>{GIl3fftv3=dTb#vgw6wqHL_sXwXF{v~K@<#vtr;_+5L&Ej> zWx@VXf;s0Nx2eR#5eRAf*?T)0pW6(8uAD=~BZ$4i+=4wlzA-lYlV3nR4uCZsI9k+7rU9Q*V|GLzdcW zZBsST#GZ{Q{3nk^c?~t=c2ts7F4(q%mo(`8^0)<^N;;3k#IJO6hE5Y)s#-i|)d=tk!AL@BSCsfBA@lWK_RAAFK~yQ1m3UOcd{N z=qTpoPh>*49|B+e1P?xk-kWLLlfg!P8MJs0LS7aPQ%}#90<&BJ<)~@t=;+u|;F8(e zRkdy5*%X(ar3Kq%C~!DGSJ7tR#y;Ul^1_7zSHvbj5!Xd@&e50CbiMza6IlD4FJV%afK$B9^kf>a>KRK!~MPn~?!6)BA z0N)2wf0XJ=EA@{*La z4dm9Lha4i(Y?#=0dRtg}+|$Il@~>Y%0z&>698@m$}oItzrM zr-u$e>z17CuOhU)u;m23Vle{$Yi60z z?ejv1D;*NfDFeKJYu9?5qY@mVx(p*(K<}y<_-NXTy$+zMEkV3G_%y(mb^}CO4^FN+ z_<+_-9$Gi9xL{g=p!B#?sfr1~vsrG+gjxzx`?N1L7fPvjjQqX9pg0wF;_HXs!f-)z z=EPJI1Mm@g{Hba&Lra3PkU#ST$bSc;(0D1;(ZW$e&Znm*M>la>Dn*p#t!ko@R6Lic zXu)~os><^64`?WfrmL|N96LmqI+ImY-7Ba|Pr4Q<$-R3kzc$Tr?<^2q{llSGR8Z|S z4iy&NXN6B9ZlwC;*cC|PgeByJW!p<-^&Dha5L;DCpV#((>a4Xx@u+c4e*)p77;MW* zR@MIwbO#xlR@C{;hh>;$-gvEg+K;xkviE3yLrx~xLegpEg1XFWm!^Jyr$Kih>-rLAOpG!+65>-Az{Q*LO%)YN1alhcMz2!3vAg3xf*;WIa#v zDl91LPLKJy5|#@DURGO*sy&y$HXRsI?Aydv<_w@ZJk!=TzIN>%!E7LA;La2n?JV3z z+9h!XdJe490z5WrffF_PTLnv$o=^rh60cRe5NHW?E>GB6dC>wz^j-a<1t^lvsj`iN zqG2g~_Ej|ep@9543Au00o8@iG^R=5miHn;{>cU(9@!SCq!wFjJyn;-cJ&%W6XGR{N zR+(mjL@d6a5#9a1UH>`+X40Eqv->^%WmsS`JLk{%08W80%5&8eZy_7fp0J=rh7ABU z(C~83a34C%^`zUGF`%j&5&SuMdEIE8HGhK;<8F{ZBkw;{X@AYY;5LYY50J3c{My(5 zf}i1n)fhT0`RLFr<0JX5(@FAL0sBwcw-H7tcukI=U;;+-yD*~tBOF*LBHzOA!IpO7 z)YY}?qegB&RD&XA?#Zp16xe93+uGKq|9?BqVlVNJpFX9Eyb}Pjk)E^zBG_%b9%!}n zN^}coeM6qEBTR=5ty^814CwrgY<%bG)2FQydPb$e=8cy{pK(Cjn#_!5dVl{*4wRf< zl?rxaou`m0)EQ4AiLR`yY)?^Weer6yW4~0iWWVI0nHpE4%!Fk0*l~4_LJ7G90 zmu^QDuPjuIOu$>>i|{ph!LZn~)??1TLY|V;hwRh>rm24(+?Qd^o+~~GOZpD@s?4dX zsJ8Iv7Sw9KnrPPZ%1YQ(Nl%FKh2JqBg7q+V9Em*_@T)zr0-fY-B27FbSA&uHnq;QSvPmrg4jh@(Ffo8eo<}wyY3_jEq3f_l z+7XdGNW}!VgutSzq2bXy`yu@xrV~|tu+u@&8t!=EB`m5;*g3}D5%30wK;Yy|a<%%p zYn``Bm-g)018?$67hRA;$$e5&ujs`+MgQdyo&k!`Qu%W54nb%s7&qzA^5D6FaPfE1Ph`i#pcC4N9yI0LudIJDVc9b?$6S$FqY-l?Qc!2nDv3(08W}J92jEux1m}B9>t@hUL^FEF+k433 zI~p#O#~bKrpHtew0PT9sU~+o8B)iMgT>4eoPGDJL0OF6pmK#u(4Ztx!`3_kpG53r% zTJ1m{y`uMjj%F~M)0|M}e^FR?3J~JP%ldSH_{bg~+0mTBAg zA8wxQB=}HYv0R{bTbN=atO3)WgT&rlZ9$Jc4m&o|qxQEGZZj6{f67_jq^I)Sf?Qkxe?}-s=gk6gvKk^kevETS&3X7{7P}>py|Uzrm<*0=zxh0r&Bn z7Yx?()I_(Z+i_{<)LuzvBpDhR{S;rF-wETs(Vu7UE5!TDjZ7XWu1UVG0+e6;%+P(@ zfAFAAlxMKj0G?)c7iI2ZECr3p{#RyQMTPQOpvP7gyH*K#eGU@W(E|WfpRo46#DPFF zDJ+c=gN2LL$#;as_Z$RNKE!9Zkj=;5?(Nu|kW`Q|I4I5GvO1WlDnoj;*Oqvuf>g8NthudKGSmhJ!a_V$Bmm$P# zeKmp)JQv~kse}0#@cW#(){5NEha+_?l2DtHAN|5+6UqmX9^5BNXr3&-eEBj;uh>&W*4Puz#7yJ2@02U$ znKNfdQ#^~$n^_{=Zy`^cW?@o6DbqcL4SuAT4yRt6u%w57`e^c4fy?A|V27t=D#?wZ zaoL3h1+{UqG*1oI815A(^k@9c5S}nG?*QUH9)K(Y=6H|S%6xoN5lZ(x%~WaV#t7+k zK&&loZ82bVGsr9dxI#AWl^X_)NCR^4Sj#MWM)h?4O@PyJ{94bUhxBGVfGH9*EYI;8 zJL}R5<(LA4A0ev^;5o|a)lCEmVF2B1ccKiX=Z)vGYl>d+en6NKxI%oul7AT`xNlfV zb`w@uocFjaE@5Fh5jM5?2@fs$H2f4!BC(?ltGlR^ZWH+VJ6)85q-f#Qh-do zfdtn!(e4&bc<;(_7!FqZ4yZxVZx6L9wRIl9bg=Rn5br{+-dp|I)ve77&pqsNbLf*f5x*EMYoEJ<)RDYP(`&JD%} zT!HEUgVWd0)B)HkL@eQFD*;Bjc#?Dkl-YH{2k;J55Q=3KVu!gz#C0k`o=C+rZ3??z z2-I*T(rH|Z5b?$#D|;!lNv zkqbTk0SKlx-^tq0(2zv(ocp<@0d^b5pI%BzdkCN|3F|NLI8jx4Jt1K|uTIHDZ~qa& z%W_ihN1x5?VBOitbF8*N4Odo{=0Tv>FMW^)0zj~PqmOQB`?{F+19dbtOZ3;HLuc)M za<^Z=m_YCz zL`^zsAR;XM0E~n$BHtIfFNTL$fx}qP39x~_WLnJ0&-VpQ@Br|95C`)8bPb`(xl5NW>aOV49u#X%Q6SvdsZg1W$ zdHwnuOgYeO-MST){R`+IeGtnUWz?_+eGO!beK0UO$_=KDVg_8r9uPQG(A5Z|ErAU? z#(sXfNBEMuVXW3ix96Ic3w%yo~K!_YXytaJFX`(Q)wLtgNOS1;8d zz|W8J>eZ{=V0ydZ|KG(V8yx7@h{@A*^vn|w@b05&jp#vophhL22JWT7>Dmh^GSKf^ z^w}*o<3G;qrlw}bT1mmTHPSo=cvt7sH!{MBj>usKtWp?>n83Lj=uwn#_R7LA&jhfgY^lxl>$}8PmokZQf&TWdU%v1zZPt8%=}6!aJ#=?;X`BLLN0$^UbnOR_F0jY zQoq0QxkQ{2-G~!}F2cos)!LfpokeSmQ!x_udC!?s&>2mvB*+M_G)_A+==5Yef1>uO z`DugLX%OZ)AnJVOO$`#XB!~wA59ri$L-CP@4_O>^k~OoEQdU=?ncWBXZMNV6LNlXu zP5%_r%?9ZbS++Q3%hl+%igSuoNhO~@4;G%W{9Q&kJUk1%cO5=62C)0){`Z`_;gf+0(mPd17{H8?3X-3tJC;Mq$ocCUo@Pu$zQneFH*E~l`!W|209@f z6j7==xR-jme8?#h(F$bH00~=AR&iD}m#o<2Ln;d`*{dGc_-7tX=GWLsbP7*k4lLr9 z7H|>YwQ5Ei+~jU5Dkk8K&q(Q#3JQ<<11`d(`56a)y0i*s7Yu0=Os#P;-Jf=fH@LWu zf7lX(X6s{3%_S_a4_Y=d6z{?^!WQ#Iq^JPn1>E?{mk)@jAxb!LwD28qY%ky=DJB$% zJCFp8^^i+2ZnU-4pl{_hoW~D{M;sa6aIvR~rKhLsgQG|c$QG%bC2?{R7$&prm zsF;l@Ohc6faf=jffPH74o}8HYfYE2dct{jQLJfz6ZyzjOi5E~%19jhvj66Whu74A3VM!g14U9bZ^(iL{Mm%Z+|~J*af-^yxSyVu9>Dni~KKjxq~};Ms{uo(S%9 zKy23`H^V-Eh?jR40lOe#rGsZEUxwycON?~ED1~P^;UWsprnqg|Q*;CSiCKgc-9ma; zVNWtLGXD7e`yoc&y76eCb`=rb3eYsx_zChhDjgaa=% zzu`SdbW{po5X2~i8;Hq%kf+2{WEA`x)BzK(Abx+qfhDFo@rh4}QBZZ~AVkhD?4gPV zpY;=s7BPkh;Do}1*^9d{s>EXXG5VgamcFUb6Y_vJbnS!3j_pAePf&e{KYl9QPPZ}gBh}o z$dA-XRNj~@zEb&&PvkeI3O<5tA21Y$=4D8g^Qqz83BMGeUW0!`jc1l!e|?(wUX&)E z+fQ$rUT6ka+`(cUqovsKodqL2FiUdsVt^{PY`Qn zbMzG9m_0SqVgU4|a5IqF+a-CW5_J>DKi>IxuUQ=wojhRMD$U=DbyrgU; zYot?pOmKjYyI9?Y8CU}Bx4pAWhTaX!(giAZl}AUJSXhYH6Nq3^2IZ8PGaSOyDf2KP zh&QEZxc*9v51+~W>r3DNhR+wSDPdSR_o~=s5pY*MT>^~IH^|?NYRy$ua>P`JFHcdz zx(%BXE)FVp(>&y8DTNgZd9a~$A&&?)xR0bldAx;hw6M9d_s;tv1ym>g<> zAU2b=HvRh_;tx06v$r&g>U2<$4`72IJ=*1c=T1%@)!L7c-fS(q{#O{1!hNFX|N6-L zM6#s+|9nK$(qjSz2rCwO#kIj`^=!-Q=x)*g|eBHp)i=~B&-I3y=>GBSnJr*GiB z8z~69_z~ayZz%oKpzY0Yi1J(}UyT;8dQO9#z)K2((c|{~nzcuB}Fpk<7*R5f7u1ISkv_0!=3PfjMt5*dI%C0AG1l@vXf9rf13pE6%>K$)n5Cur}GTdh&UYa;lZ)@F*7pO5x7BDchYJ+1H6zIf3O zccD1A2qOuR6Hz|>R=}EYu&|uleESUEmqaWix)FO=Nr+cQrNE?=LlPE8`xO;m*Q*VpiIAl*s+Z5}Gij1FKkN_p+^>T^353$7Ge@ z0xSj77;b#7osGr^f2)h#ss{m_vxm}npV0O9-MF$s_>(73roM8dJqyogxMk+mKQIuF zG(h`MHC0m;-G-AQxa)uayx-&0+ZZ`II%)<-t=Pig5fIoHK-7n1XJ@xg%La2Q0!#%4 z2CDe!bjv>p4mPgK!&|0UoYvEZ3E9K|VO^|7|4eBo$n*)d!0&~y+?rE<+d&q>;MpK$hEtM(Jo2@ryOOxwWvMDAyq5e(U#@?DxpRZ4#nL3u$ANm zD~crW>qA&MX@kF4ZGLnvs*MZwDpgGFZPT&ClvU>C!JnfiP=sBaohkE};dBFO$o9&g zWC~yNSG;f!0VY@7_3%s<1O@Cf-NW4~YoJ6_FNeSxfeTnHmB;4rjHvuLzW)afJ?rt$ z>hKT|ii6bMR?<0*cNWhbN81ze5tHuE515EJuHYi|Qo;0M8p>6Ce)6BU8V^drI=&oNSjveLwEG zsSFAZfJWg<9w-bvo!r55o+jWN-f#qxLad4!A2k@mn95-EMg% z3#{Akj{Ig5zrSjr(_JMhm8UP!Gg0L-^YC1Pf3c)?`=|=)K<(|FVhI2j`FT|BMxY#1 z!C!v`C4>~^xgvB%z!bP0VCz42GKW7fFG>?*jGUSnV-i4Yk(wA_`pwA5IF70LO`A6L z1%!!k9mD7|PHIW(3)$uQwirfbi=dR)V%Ml2eqW=KfLK(9RMy$II40Ul0! zxJAr?^`z}y$>Z)p0(6_CO16MgtFr?pL0tC7SgqN#8v-&q3V3%!b&La8GCNxq(v@7` z9>TKuO&V*tVn&u4b=SadD=IL1_c+#jXE)iT4q)xEW4i9ahXQ0Uoj5wOY}W zmzPf;-J3wDypr$1O%J~218=FW8oA=i18jArd4sdWfuVXMiBk6vv$aSuG3~7*gg@r znG|3@Rmi)Dwe#?|fiTPRS<3b8{%U=kgF_Z7N&{Huq{Y$vOJHLEOiw$U|9!F*OyF_6 zd~1GwehBf&o69UD)Y3cGu}5WbGAk0*nfX%bMG$OozxEmBr_1Zv5Y^Bzfat zWx>CY^hE%TBK;}Xw`XUoDk~enLnPz!?1vA39Ldu6)zkBmwL*GBdeeo0hlXRWT-zzo;8IlwVzZY))iLFfJ`It~l7zH5j9>yTAJg#)J>W zPM`}v|K!!h=P}66W}O8tQg;_tQjWUE!mL&g`riTgV<{bzC1wOwKGS;5^0gLy!P1|K zuXwt*rr#uCS79E8r{Dz((3*KLyTw5luho}`0BN9Po{^IeP zm$iAfZOz)PylmLm2q7Q=ca5aX%c~&1G8YTfI`GB=FMK(7YPQ`j&wbwZW-gFP(xphT zB(!*#LZF29OROij7>s&7nL3wX4y@7!dn$OOIA~d4zkPe>#R;6C39~!Ik|l=UZ+`}U$=Gi-e-b=LD$LdWeK}kr|U3RnCmT2~^hAeQ~t>3hy8eMJ>b}!jO~v zi4x;jwFFKq{pYRyu7igVf$En| z{U7q)JRs+^fB(J=*>_5^?p})TP2|dEuzMj%2t+WA=09q^{_mX?0AKY*922i7LKI^mBP(_LvE*SfomOjur? zzS))a1E#cIGlzC}uy{Dg^bZb>0C)R_>MtLF2-ThrBe9C_pK-udP#`G5{$-obxjRmd z^E2+*=Fn_ak>7}R21u>^_qrJZZ&rs=x201|xGF>(xp z`2YHJ`MMS1$@%qxSroml6njeU*Qi#-3;+Fpk~WMP;R2~5LRC(SRd1S!9=xonZ29g$ zU|=+wJAFa45GU@(*gQJ51ATj>z$I2ZfBMw4gRFAu3d!xvOaU$CFrly0a@*ht*RLM} zU>ffz4&<&y(HAete+c_0-M8;LwbOYFp*~-P?bOJPDG@fBwL3$K zf@6R3ZLOp+r&(YA^7X3~6RniPhX&g|KCcNNspxyS>BR_sI$AUS z>eY!9q-(?lrbml98@$z+JSaEW^$O-*>plb$Pplxbi%?JY-Tb22u7)u}R%IN2_w`fq zYks_q(ca35KkIY94meXR`jn-vbvtINJ_w!?i!$LY0e`HzkShok6-iYo&NntT7St=i zy}<(eA*LKc!Rd|jW-a3vo#75YHY<0c>MleN*vI%1R%{~5_A#sta;bR)++*c5dBOw} zgb{`~%V2pG>>gR9#Q9eJzK&TvYPPHgM^4+-Q@F}Ri^_Or)ttRdiTp&HV+d(fPY-Ea z?;BAyMLayDLEb_38#g*YB`PhwW=cfK>U8Q@4kHeswQj^Vadj^lH^oI(`RxuV5_G?) z8HmkvNRcQFC=Ctp9+PU(;&x%75tV7%Hf3QS&_2_Yyf@aob zqveQh)({h)*7Psv?w<=>&DCw7Td=YKWHU)xsBnW3vhr>L#1owW655>tbY zkaU1{OX#-^7?%@=Ix?4I&mEk@_;v6G_jIzUo2s7-Um=y?C&4tOtdilxfz z%+jszVvRL?)Tr1$N{3YU^7rfK>X4U|W5IPWzP>d@PHinLnAVaXK@r(j^XL1Dt3E5i z=oE4+6F34b%TZU~dzf#2DYe%o@7$8yiY?5{bbQ;SXOk>`kelCO_98-G1D`tgwJCW5 z4o~2WB(ybHlg!c4QBC|r@6xtiyId*_6)eTRe*Zop?je0XKX2};a+s-w$16XfSHo0o zxbihDQ!WiS%tN0R>+g3OYu?9s56MAM!We+3b?L3!;-RBQFFbhPeDAlX_76ol{fz+( zpA1G&&r&!Xbv;WMxhz~|U~cj`Vr|WG;nt5kC+CXcVq)0g`phY#^tMm8zaKbIwp%yL zx;uL|1yG4%JFvRWAmZ{5_11C}1Y8~l1&eF`^@^i0X8D@ta|&|U2GyH0ZnyKy=Q78j z!v}RS#t}$}tK-)`$yw(1OI_I^dD}LL>YD7-u%u3t>i1v0dKDYRl`31I9Vv>^U;FPy zK(0{V{R}FtaLQ`6hCzD(p4+r5sVh@99@c@=-%a|j7NE(o?jG8;i~c1o{#}Y``3Bw;7*QcfYF^AQJ1_ex`!P>=PZ?FRV-2^WWFaU&xt_o0+~*}Xx5Mw=;y#}(jS(TbKiLA?V>-E z{fhGUQ4ks!b-ke`TKdJ&2kU(TUQyK_F?ZG$T_qwwQ8T4{d9`Yvd8|YFh0}y@Tirc5cyT#~^ffRhcEPhVT zI4=(&rvPe*gbw75Q9ehVi&<9F{d|tx?~aEykhpm)-w{5|)Z+fr&+VI>iW8;29V4}K zXhqs};D0IcLf{LdNPTLxditGFQcPo-@lxWoM7;>y9hW(dI@rB?_Z9Y|A9qNHuDOC?yTwaX{-kGo%qIsIMv z3fj-*y|a#vo{qN8koYA^wq)yS9K}-ic%2y?j&DXJ6qKCLla1Boz?*GN`zib#0J>6A z2LLp)W&U6hft;!lV;NMqa$g14QyiZe4EAe8qT0N@4yUte&*@I}0CuOlY`3R8Q9ozM z-K}dRYd27|M3VFb7+lgU;{5sZe>989k=5V3d$)XNU&frdrd8Cil)s4|9Fe0XjOS_a_wZDpovdnCXF!2v$9&ZZXHdF;$d@(elVQ>`S!n_ zBlPh*c<>bS@2LaMZU*v;Pi5w=@Lh~a4u%+o6;G%%`G*&i9*DZs;@yE+eGgy}n3dA> z=2RsnCpDXOr98;R%d{Ig^VY2y$3M4j+g8D0GMTxos31}ZxIY3*d3kv$gsrpav|vHz z%9(RHeN*t+*Ta3n2xL+u;9Yqt()#!A-`6KkPOm)DU6eM?>!`2se>@j?ASft?3SL^a zHb3f!+222dzgI}WnPH{KzlyHTqJ(c7IO;)XQRaG?qJN24UU4ga&drDoz7z8!NDccb zFIAS`YOS4vM1tP?&Hkja`70G|3*9r)(|b<3Y*YB4Qf|u6`a>jZ?+hly5fY3`c5ZqA z3lya_Rp&nGrZ3DHPSW}G42_c< zBH)luQCApUeyu{fg9gs5e{8ugf;qB|Q8tw`R3qx8@+oF~f{*~Ma`XWXyO#PG;ih1$ z{vHPs5~Dn<88?ahT0+q-q%o_?X^EqWE61yjA3T2C#;JyqZ)AN5RJ2B1kJXCrR;!ky zAevwC`hh{twMSD-E-c?ce9lLy-vR3ul(a?fRtORZMA6$RDO?6=3hKf@>!8 z_2+79k9J-M^JY+6{`&PA8Zs?Qn(}>G`}*2$vTG6@Qk^#$=n5xTwd++cU!HXTS+#Uk zIm!}gY}f677Sk6S(mb?fs)@j1u`!E2vR!l?UPi_F(+ST2Ak~o@CUokwZ(6yn`jRR{ z_6Z5th{f5}JGGM;k*&92lEDj&Oio)v?Q=2rwQ=^+A?dUN;LoRx%(tAmo!coI#(3?< zt8r5K$cruN*VdG)tp>x8n*WGJ7pEB@c=zwSB+SVkB~fF`Bxca>_G|pIOTZP_BBZZb z$Jk}07<$-*-pzjRH~G7KJ%N*!5PcziR1(7|^C>TnJdvX{KCkE(hc=b-_2kE|U}@Y24A=0b3MxODzJ`@Z?p2Z{`$K5=jZE<;!+BHe*D#I zV`G2azjsfM#-YTq_et`d*28Ddo*AYzl7)Ir5=MVzm3JO|Pjv&%X zuMOpoeNL!9eZfl+3MPodw5>N6$$n_KXkZ zN5%74f2>CCA=-&+*RFM}YD$+cK1&Sj>-t_3xI{W}BnFZooFg2vs?Y8x>PR0IrVuSV z_FnU$1PPa*-XYOe9Y{I4k+&fJ_Uo_(KO17*4QQXUVlW3x3|kPq&S@)9NfAT429q#k zd3zzVuO6q;pjP?}K=O{APCyb$F^+(fZdl>ZjJfni<55lsSN)&^#x8rUlQm@Dv_{8y zRn%XU4^d1i%ztvTxVZRr#FkGd!qwKlm?}UJB-MAbvho4{1cdn$uI*Et^$~c)(A9H2 zHzRX~VDNkN7`^t0jw($9P`SLcW;_C4FxLC0;<*vZ6M(VL5-uh;CK4ux77DvCF`scK z5h-K^#&Gc(Di?915b!P~!NFgxorL6Z=C#i`b4OWWQ@g4>*ri6Dif2jQO}bD}m{Igt z_b!G->zFj#ZfMlk~C0qEDTpjgoxV8Y~ z(IhaCu75Bu#%#hNO`}1gF$fD1jgA=XODswaTHZ7_d=t zwxgcWD!lF6Yutk~Q<}2SUp%LKSo7&|h5Pf)6oTJ~4?lsUFVzN5*!193-)CFD+lnIQ zwxf`AKS{j_wDngX5?Mqcy0`uOpWN$8CS*ag^)D=+nRRUJ+3W^Nj$D2o(vt|p&b@XRt;3Yp z`_X47!pWruMtpq=5IrTO2JoZhqSfIxPEO(<6qVUsQy%iRmuinMKQ5i}*fk`d*Cd3) zZnOwrEisdID%!JEOi_MST8i=%(Su>3;=9@kuJwbJ!@43JipSYKDTV;`4V*JC#bpx+PZxC*jVYgsY{1M8)@Q~aTQIS1#sQbD z5S4VHN1sF~)+Uzs6$YoUa0|&QM9nj}44Cm9M3)Rk;8Q?QGD8SrKwcm5^?=Wa#pkC2 zUoNt?w%)Fe`4gSF5fHXM-R&DZ_QV7ch444#XB=qjf5h|1^>|65p}piHOg%u_E+u8s zTd(2t4{bh-)-BfDrCQao=IIh<=nkTW2d!m-kluQ z;0V*ql<20);VXQXE6dh1#a9WkWQ^#Z4)Y9({y(b3BNfOLEte5z+xFQgB_$oed&b62 zh=z~? zd#U@+cwwHtu>=6W9?y+A2kA~#@YP1uSL6F7tjSrBkN!*Ab0j7tpg!~MS{_0Sg!i$2 z8e`8w+a&P_`A(xbC$INi|8Q5)nQkh&$exB2;k@C20qXXYunhzwY3E5{Lx4vbAm_ya zgz7U)apB(<{Y)ixy5MAy)ZGDQHa5CC2f0~c^#9$RosX$gFPxwJ{oPnfb{&}VxG#C? zZ)@m4Vj8tZnO+IG{pMGQwh%enneKIcIO(%I!A<~@czC?4OzVq2Ti8d@Obwup z&I+7hgr(v+WCwo7k4GWd7us|B+=HhH`Q+V|fFAZs9yWP~FFFZjQJ;m`0IW!?*3XFe zLr?_`-x`0EPl4(^ApkMEYhOhQQ|UQkkB^T54w1j?Da&AxPm?E1Qp#xu&I$g+z43dn zP)u+#6p^8WRj+y;we1oeH=}sVAL`>8`_1AoQ*6#l;X(?8G>({Ni@D9~;Wn2G zHoeW6DWG^@X@e1GJBhQ8?lza#6^FPPNVj`y*OHMqeA90(qR}DV#QxX-ah&W@_wnPU zuf+`?KPoC4lDv}}&CY*1emO2o@%keKWk!P6BLd>?K3_n^G{%dlGgMaHm&KL)@wZUv zC?YpxY9;XI0g6npbx{L=)hWOkLf?v6f^UQFgmue@%<>Nk8p)_QvN9;QSyVn!zmjQ2 zK(Ao~_&;Ni8esf&&qDb&pcvY&;TvO3hP@WhCSaU3OOgbM3cx}Ddsg|Z&u~ajF}5Jc zAn3fx#PXnAME!Z^mrW0x>}Jft@*B+l1~-v_MfLE>YgyCxsL@eVELhAp?XblRn%Y)D}boXzu1G1nhz}7nCc?dbkR}(s1qzy^)vRAKGCwi1JvF zPC0r65`e5uA9rk0`rTv0B9C>2nxM9nr%>Y36FwJ#rbu`1ej4|`Lnq^i%TTQuuO^Z8 z!nh?G=gMN6Z=r$~J%gxEobgIEEAbut7LReE3vmQ^O3%!UfXIf~gO12#UF1*cE1gEffRBZC|L;U*l-`~c)U(*f3Lg4TQ3}G5E z{`c3PIc+?wB@*pLfS#Q0A=IbhhQ+LF`WXEN2F0wC47tgDTA?iaAsFVnt=V-7P!5?! zqdJEWO2f(6U2+2L_p@9|#&hf*QdCjN%dh_HH%YcFx|L<2{JLcF{^Q3L;R13iSO`(SIy^Y+b9n3?U0`dOj$K+wP%{IjdR!UA~HYJP^dB(J) zW0TJ;zvIfcZcYAPB6+di^k>sgAd$$c{Y|LDzy1HNu&?RI`~O;G`2Wyn@slSp#H?$2 z_$3|75Q#{hrid3#8npsO6N15!ORt@yqxM%aF(k|j)nEPXLHjE93=7)NC5$R=N(OE7 zdD_0^^k>!0>9I)ObF{S=(jbv^DHxSkg5k1pu%o7&n~s-jAv zene3N6eEh_6lw(qEW9Z`OiRRK+yDJD0&|#9&i9?-;^QH5+g^#@`6sk=;hjUnhG+v! z#R)%JJaEpW7J=8}J!{NwIW(9;Xdc7LMihB_<5&OhkK->E*M15KNRB$*Z2f2aXO1#3 z_i;KgPA8YLz-r9tq1s?z1q?ei6xqaT9~5{EBz=Wg$xtx1>CbsLOSi++3s<0d1VX^D zg?0`obAurF>uX4WXW?SCe@kdc#zOpUmC_W(O}#*blb1o7@`#5$L>sI%0Q*OST6Wkh zMNfF7KGQ!$^u{+`n#xPI*>~1&{=z}H@H3ITzwrkY$eriUpGV3uVvq-Wf3)!`p11fp z-dQ&3Z@+H~zi)jKH22MlEeso}<@bcHfAkT|WNppgZe{wekbu+7IY9VdN$L=yFG@w^ zo!ftulbTf5J$)QzEUI)a{Dk}y*IL^ zvwR6d7)BN&l$En%0?_?PWiXrB=5O+EKZ`RO;brtSlv<+tfTTkJ6v*G5fga-E2877H zSb=(Kl=3P&-K^)&ucyj3j%=E6{MPalNb+h%m)B>C+5=UWKGZAK&Yd!iM~@zH5yj&! z4Vb=X6EEx!%TMM%trWsnRlZhh`jz~8*NCK~#{wgv#()5r&ovR~v>>8kS(g|Lqu#&= zRJ>$;p0W_$fFRp1DwNlz3Hr_Glwu6wluv=>y8Xg`PRrweJh|z*9i*-@DJozH0nk(T zi|iy+5s3CrBl@aj8Z+Ta!PtirL#fr@Ore0+rNDI3Dwbcy%hK`vPN`^6h`j9eW(I=I z9Gf1M7ZY3LTfD|MJBQ{)$p5M#F5w;ZMVX22E-|^WE>T#sP{9wsSirT86LWi|gdZn| zV)^a&rSf@??;>q#-0Z(M{lTdXts@6Khf7gm;({3tbGxf`J0F>{nFZC?kw&&j)eo$q zh<&}Qc-Wqi|A zH+pd6v!-95#;HYqf9(LYM?(9Qo}UiTA) z%vVeFZN64XE!4>b!s3#G+UWA~0|FtSf1Y20`M|x`S1!%hA1mA5DgSS|;N$v+v(0JQ z-$FMpgxt%+>xh?8oUgQGFq?K^cn?qjg$X}d(@PXTq2(M?nVyRd4W@4w6)FhL?S4@( zVQw9cOl|4;(zop#>CCs5Wn26Gy*{X!d?LJTDq4kn?1D}-US;z1CnT8Yvz8gnWT9_7 z*tsf4;gXGju|rqeZ&%ZcG>nc!jA1e=r5-_$rf16pRbvkx;cU)H4sH3IdTGRmL6Hmu z6ZXsNQ|nuW`uzRp(Eml>A)$Cjj{t;r#>tAaV_owL5pbBa$)#kL{v?;fh8bRTS!MF(% ziX(si^~)%ioCiX&W{ zCcgAoH=_LQ+X(RUnMBO7>cLo*qLM6HvzBP07HCfL9>vcQ#CDkZhSj5c^uCvs6(1Fo z7!q*FV?)!=e!ickYOCL>8it{X9CHzi1fYgT^io=x;8jl-62*-lS}u%Sh^nC%crQy* zW&v;W2EjQL=>v=MhSiKH^ROoBJLRixY8W=`?*Y}6+B!s~4J)MRy!_dAv*2M)T@h&~Bc`>475Pe+nHh8B*C2);U#(h}icbbMC}2E6d;9%~}`g{YBDf7^+( z4ud0KbSJbZt^X$E>$Z`gh25pS3UNn%D0+EYyW0XS) ze`={=W$ahXahOlJmY01~B zraX4Y=~tBtjtXTB+Dkc~lD2;?5wjd0K7STdhwaQhbE6fRp*bi70c_hYIrUA**T}+w^Of&#=c?h&8YOYKRpA<8NC5<}ndF{b=#><)_S4 zb)@oXz28Ott@tV*7~b*_m560M%eibnw0&^O6!j3wo@|8QZ}>+};;Rep@JBg9Jd>-l z5Y-tlav)R=6x9Md6pAUw*iYkea?!co&!{H^2!;5YdN(oP2VU6AN61cM0{#(OCqDIW zbqJ<^ZGCSUnN_)MOri6QXl}V&Mioi;gBpU0+;`u~oWqD`H%m%N|LZYiMhhj70TEV_)_Kxx2eZeG)=n_WzF57Kaaskv>RKluLpEb&E6&0=!m_D`VmZ z*P(7i{)OX**UIYzb4=L{wF=#fdgBe7 zx9jIg3-xtP*EgFn(^PkA=bdhSWGz;0bzgrpc+;vsmn>`fEY^Nw$A>pl9=eukC?B$a z?%FqPyjYp1iUsbTQ>O-AMF6bSw-B!Zh+2gV2rlC(hl7KIUsV+q88cRG$;B~_4ef;6 z5_sBKoqoB!f7I=Bza{vCQoy9!Z#FJoym(EDp{3;@go`7Ox{y&DVh%L1CHVc9-g`~l z4;ph@lzdGN9Y4MooUtV-=?3mK#uL@Xj_t;ZQ;jKylkBPR;>|t1EeQiye;5g;ZG#)D z0?=|vJDh`u%^=A9Uw^))YV(7$2MgmZY;3%UgO*S!)n*4D_)Q2WVXbleg|t{05)u02 zo!=otlCoq~OmyFN5=Pxyl){2rrL^q1%hR*@*3Ze$+mwltJl8mRQ>~2mQjmGgag+Wz z6Fo*JP%XS5JG;N^I5oA(lrl169b+(?A z4p~!d5taFz4LiyS7cM;dav?xnL!)g5(4%pzX2wTG%A^4rz3SjM`X87bXB-+6(~-7i zk++$t>3c!Dp40l*#|fI>OF!B5>qQG!+{RK0y6sIDdxG!&5MkQ*atiaVQW&~>)nRP0 zP+vc`nUb%;#1}3zxA+QenA#dsuVQaz@yY8eJ4X)M<-8IJ#_Dl zb-9vK*2k{JY;u5a@!FuH(*c`|++lr39l3V_^*dsUSI1TdbvaQi_eS^FNKMst?b@YX zNk}lPQoVfz%zt>rwof#MoXu`>fi575!VzTjR^GiZnf5H4-DDd6_g`i#3mUG_K@e5K zVwCo%3wCx0G+E-)lY#qYE5;nVTPG|p@z4_g7u~$r`GKNd*ykI&vBq~8ZrD1?tCua? zF4ay(w(@OUBAUBpFtuKtA5&lo3uR3sMF~B*hA_Ck{LHx>wc>B{FdB|EKgKsSGsS#|#3RH_D?rU4*4~6{&`Bm&eB%jKF{aFly2F3NB_%h@`r?nJzbdfVzxYjqjclgIu`n-9o1~Ik?Vh6Pi`7N zcmmceICae%{ovulLT4Y&M|$Dm!-vIb(zO29wIBF%bhy^gW!OD?#o`5|tFM1L|LeY1 z`OIXrq;&Vp*ZSAz|F%rmXWk9=wzepC4@2whx^DXP=A5b!S+=3w3yJ^dgXwZ$tE~Q@ zL)kQW>{-$q4SjbhqdX%sGc+kl?)Q3WCTX&=-~9W(>nr{{VHKyp5FL=67%lzA?N0?se3}yDbnS{P?8g?GAN(R0JXyKA4d{O_9Q0eSqwv?*ululKX z?G68V9j6MGleKL&WiBZ*jbK_s>}k9#zp>9q>si02CKy$n=7tg$H3Vs<3-4} zXIUs%c2TGa)j{_jJuolrp=fw6mDP9Mi+BDrq;y&M{occevuL0#Y;5j@$Q>J*XzGg! zQS6-+``|Zh7%e9!$F%r%&YC5w1jMF*6DLj-B{$v0@t+U7=thX#zg|F>p82Jks`oyP z#3l2ee>Buu=Ku4b?cRMmoLBZgr?JWZgf7=*ht)s-czgX{&hWr73jcrn!FQ8V+J*Q= zUAWK%mC6iP*NGoKeq6X{(H~?{l_?7z$I>JWM<|AjQs*t zfEF@dNh)^u9QoT19_-Xtkdk0lCHAh1g_|!bD~p1Lbc>H*ZL?TX77`*Ec%U^?jPfH! z%viXv3&)f+y1mCz*Derr?#2H;FP;{#W?FL@=XK1KpFa0&oHoIweJ6vrhaqNv``W9rY*i=ttUrOgAJ7w$D zfu~zau$r!TetinU2QRb>(!@b=yYT0Ys<@_=cK}dM7zx2OY{N2tgi?N^Twg}Cdl~&b z>g8>yetx|l8ojCL)3h=hQRrM*|MCyYq~)*gYP2+8n$yD5dPQe?=9a*9lja3a?t|z^ zR>NUURJm}FMJN;8Ou~HI=IZ!DZ?mBblXA}D1hY2$% z85tS5V9Lsk=ZxdqO2mFyMh`66YLS8E)9Wi&uJi{`RxZbih&iozJ!t#?{nJN}wt$MB z#im}bUM<%*GP-&9Zd<|bd787&EXpe=m^MNtG%T!T*F7yr zrspqSw64w3=o6tI`NcRlWaGyT`-10I)_pq|Mr24~%aRPQD6`$DtJ_3}7`0zc!YH3` zyaIHQY@?pruBlq-K>0OT_R{etA?d&}Fio*1h8mSByLHEoJC7Xc!GqzJcyNxv|638L zZ=ii;YkSGFA>9u2oHKXsbZoX16cqZ)>|MBYY3#=Pv?7n_51hh%cZZAArxA7T+$|4(dEDu-x;-P9Z9b3d2zb$}7h71Uzg`U}q+vDqhCZ`t&7+76!1b4t{^V zqhtP-z%n+I^MwB}=Poa^KZNYTY(ar{MJ{J|G2xPtU z?%g>^d?>G*i>F6nS$uxEwHTJhps_7A(oUL6$+qZdCDcW_1_o!wkCc~hO{T!5t1!{3 zmE%YTX|-0a9Aa%_vrtdZ6Y<~+8ac08w2SB$llv$R9+)$qWO>&&8T;O=GkO-Vs+ZoIQ@5~B= zjnML6&if{>0P|%lSC|Yu@I?37r(8NA)URuTmp4h>_fMFP%!F!x`QpVkn4q^;=YvkXvYAUD z_S34KJ@n8^So4M6o*o`?&$f={ar5mZ60pEQ*m6US?i_Jw-fiM=i&8w30}B$}8?TA; zne+A!-WNoCTS}ukrU%8w=(DQKfiEN^%5L&=F@hhSg;_MvvkzM%te)ze8^k@G zX=N3$oW(!x(S>+nZ)c&%a+5eJf70#y$s1ego12fkvdflz8Bn@`1<19!ddGK|9WY_K z`P_k(<2Bw*D2I-Ik0w>rK1U+;bj-Mxr4{_B9+oSbiKc!ctMk5y+lP#4g2N3 zD9l8ld{6~8&^Dvbki`f6{QiV|JfY#(mI1_Rps@oT@*k0wTZ^3%g;s$$uFGE|r5SFC z*3Ha*svf(91xx47opUf&go#qFk-xq`W#w%f?HPS$?dlMTpj{X?3tNN!o%8WCJZ!;) zr$-{M|5b3~kF%ras7w&87i|nLGVHT4M6BVDHy*6lfhTEgQ`NEL#lG1uNZxDKtm%B8 zWd6TJ%#72=VL(oj?}ScSu{Fg^i=cSbreQ??rp0snmR@fa~vut3r0Wy1~EnBr}C9dVdV_)CC>+@#! z?39i@^(VM~Q3k^i5nE8nQT8Y=rn9GOrm|SJF1CN|9Pwy`C?_S#@Q#6n`|rIGCHZ;= zDkUPWA{O1wHTBF}GmqTC;q@p;70Zqf+&w9P;Wf0 zkIz1*&O5O2=zVIux6{IyA9u%Sc<-8h^QX=C6){G<#WFyH7axxyJG_NKH4OR`)KL{Ix}}>xVXg z|2qgjVqKqTlB1*3F^PT8V4h@+b@dk1c-Y#Wj}G=S9BF!G8yePAd{PRJT`V;c%bF3U zMi(%zJd5s}`J8OT<5;`PZcKbS+u%^d-hKOec;ltJ%<4(KVj?|`_>J&q!c`P;aDOZ@ zIpVv0e9{S6#V40HxdOTRv_FhpwU(7mNX2U%r2@iZfi@DYEB>-w*bshc!QH*!&X`iBLmelAnpN^lZMIAf)d;g-B|@C^)g} zo*AqYkU=UF{UHS$k?nP@v201}Wp{PEa1RBm6gcR4_E3WcwTG~lRko0b5K|J91st1S^?AD!ZUEyIQ-Ae{ws=Y5?wE2jNq zS036E6%mn!nnubVS-l5wU(5s4zkAY6ta#S)mfcRqd;#1mb~APc-$!gO_I0xcc>ra5 z@!OtF)<_)lEsI%#tTK0tkLXEHpO&mC>bntCiV9%dY`3tC8#n&oOJ<7DC7?VwS4n`A z7A9~l#V(Oo(EIN?4>(6JmG(F-O|v{cFEHxr_tNTCRQXO*^>TJu%dSRK+=GJ0`OLelCDW$;Aqnpgi6Fi$ zb+IVY&NFl_mhXWNcitnco_dLWG9Nx{^gD8-`EOW=a{IE?&!pd2k$}CKM8AmxR>;wQ zlU>In5NkozN3+vRywjrfrKR}SbvHRXc^y2o*o~BCGc1?!Ds_LuY%=&w271=hI?oGG zX(M8e{Qefu{Rg3Fivpu%Z+GJ zIJsVRyqia)rs@8xb5IrSpcNdTsMwC_t%ZNi#jDIt3Q&DWV`gSqh7p*d$j+Ox-p zmsM2k#-`NY@O-C!li#WIo8zx)rx!jlJOQekLaUvyAQT%PC;?I@ok0)hZgJl}QdOaV7VSk08 zF-jz8fEfhT3gdn$Dk^SZ-x%&Sr>M~Z)Y-Di?bvnw`V@j181insp~ZtFZ^<$maHqEQ ziuOp$s5>;x&HGXp?&XCxUz2pik*Z$UOw+T{uveQ#lu_B}m(_nQbtfG8tLr@*YCtSt z7EPcWB7*4{|3;G#{&(l&wa@8JtQbVgNW8n9m1V$ym4=4+Hk-?itp=lddE02tf3*N% zpG)5=OqtS;Zk18x&QJG6#l~h*E=Xl^RC_>&c&OXh*bsiw?%k`}v+pE@6XpY6u>kLM z-QG;|M7|4p%_|fkTSLRRx=N=Xyz(vV1oUtCqc`7NlUSb3NK?A0n_G(3zUMN9IZq)E z#jXu$hsT#LUc7-^%oF1ZE59T1N=oTu=kC{YDLlP-$8u3C28NE!^4s5+crB1pl7IS% zz(8!0wzBR<-^2QDpZyw#sMvU5ZJvb$AX1QU)o$_)AQ9Rho7a-s7WZ<2)DmeV+aelzhd^DRybsa~q}q_zT!|oKM9i(NAum!dZ^+mC9)w9?-C<{g2!OV#<4V z9SS#d?x>W!w-fT&X=6by0~Nf53_#ee(+o~@{nDALSpa5Cql-0lhiZM#)I47j9Nj8E zFe+w`*Kqk!qcS-?oXOTa9V*b)=r4tENu%t@#tuG`TuE8C)) zk;=rMO)QNQ%fAF(!oRT$Cnn=5hIG~zJxsr ztTb*;*G46?9ZQ4x>+RH`6zRlo!Ht4BN(bH*SaVfw3kd*hDcB@s?s*gu46cu=kbO&S z%=Nb9Es0Mq03~n*iB6y4G>RteBjeNKRgBo^k^!FZ09R? z1)#g&EAxTFkj(0rVba!9f6%3TPStOiXZH+I$cfd)Ri`&>-Fhu#P_OLzGJaU$t1TN7 zd($PQb2UWd8~5C`8{M^Wp`A5@N0RX8pY%*bw}_75JTqC`e99#Esaje)@BB!g$#WHo z$v=1PvSdofdLI&!r7bK`2m1WFkxH?~V)r!U?b3V7%huWV(Izy{Ic}_1P+<0)z_1JU zJnbnR-+A;vK83nlOW2?RBKTM8tMp?c7*;E4V?R4!Yx&{B($eZIDSnyPgZtnT2xBtvDq7FenpWZ%?9Ye6s(`Fo?dfKAZ2Fxo_NmlGVYbw9l`Tu@Y7Rfy7t`} z1G@#Nvis>j=W+1*j#~lS$RO-vMPdhh|0(-ES2HYXeDnwpL{ zYShu>&?i)uyzXYl<`q0U(;7rSm8P0rGPjww2{ zu~ahkdPvbB6&01E^K`5E>0aBO5yFhmMG=gUA{jXBpxDj(Vy*{bAx3S-vwE@z1^%L_ zW$EZf!RbQ7!&`IfgodYf>*onReRCiCC5OB%T0f)Yfo*(z{L%3nmPZu_PVU>@X3d(Z zRBb}-@7O^sbow(r$onM>V-%)ZyoMK$^n!>60gwU?$!<>bRTnZ5bEOh?D(#im2E z2U|O)5G-CHD(Ug3osN>}%5?&k{^BzhB`V3fquv?XO}`j53MkKe!mC#G z=R*e%ilu>RXh1*4IeT*yYrtsvG8ICZkqKC{SQ-ObaQ_^r^ai{rlBzMPc}xIp~|T>!I9CjO#g z*!=#*_T}f^j$Da@pvV3ImV2&x7!>S(rZV09qb#>o7%!Cm8BE=N--YVJ~?cBM+H zh&znWFw^2%#U~W5Pd?6+YzEn7y%?Mv&j{q3Usp9C89TSg7id^g%biybmxZ4`E#RGs z!q|S!^%W;S{LFMOldP#XFQp0;JLu`}ywuGmR4?qdXGYGmA#veU{bP>Zf34v!*VfZ| zj@Yo!b?}lT2dk)N^q{BI{9~UM|h@OV}MCEY+GeRDnHry>tP65(;#fg<=I(1HF zqqfb3oa1@gv&Oq$LFjLrKz1hlFpgo^_86opDeCW7gLmHk)E#9t>2;HEj%~~5+UrGDR z)x)yun?1-AetgT_75lG9sJ~_Zjl(yGS-DTcqsSdU|ob`<9oLm0kb{ zziAocI}?Wt9U9rf81yRMyqrc!!M?1oR}+Ovj-jEU45B~PYmXWSX9arFq)8;WH?)tm zx=lCEMbD(35!)JBPI~Ixx%Oa*bYNFD*A1jz7>%0|ByilOM6dow1{}Lvuohx>k*k@p z@wZ#m>yC5KPZFr;FGbCxwfzRCNq#G#9Z6N_Igg?qBbb6F9-og!rXL=2+Ec~YtEU<) ziFL?qN)awDE3TNYmFN;eMnDo5Z$6As{*}IVr~b+?dB@#DLpS6ou6a30Rfe$s7MKT|p43yp^vqRX zIvdsohSYs!X@SQDwdEHR^>al{eQSALx0}Z~9v)#sDOzEJ z&Sq3i**b;3)#UTEJw?Y}J`c8^X)sS?S;kOU~4Em z?#rG@h#=_;&YR&i3mY{RmGC&F{PO0v2?_6+E_{jT_}k%vQy^gNGEgHGRC><5!~il# z5BkhGvCc)6TMoUHZ^6{rwgADMWBW($sBB+rnM?d!jQ2<8HUCMR!cz?pjrvq5BgK-tKqemS+Fcy}U zp^v~@TMRrfdvpTu)z@*hcla?vd{_v0-y zhDDNx%8Fwh+}^6D$TB>nsI(+pk{`1mKwv0xHyn(SbVk>V~U#)E-(5o=F0!x*ZEJ3X5-iD{SCL4U$8jmx-?vsr-;Ps7T{C% zufK!1(l7SORAjw!0f--)jZpA#LPY-y`&YyFD#UGRn-&NCNG&< zI)BT)QkU+>4$kkRk05;U#T9LYS~+BRPvHg{Xzb@SBcpBP@XLKb#cf9)n1n?iXoips zcpVPqgv(Cctjx&x+U%mB6;%2jbu?3?_^<{Ikb&KM_uiz1&^Ml~ICe+5&dx)AeuHHZ zn_I=lufGKF5+S+}?WlzFf5qb&W#^!j{)3OUtZJ>Nr#DE}rfblF*@Jm9HwZsi0FJEa zer7@c$WSVz@h+c6plgyEAjiw%S7lU~{1MqMGNL$MORw^mPBOkZADwru0i6|2tv}hF+CO{nQM`1bG7~<39C{PdshjsMJl7kRJ|>&J;-@HlWZ?6Z zc~jZ$v2M0@1(&rH{3&(Ylm&GU8V&-;V*o$u-Y1QA7E&+v;+bWu{TT}(I7B+^W+HRy zym{>@@biZ{hPOK~+^#ONgJXHXBHa#aDszNaT`5QQ3NqYo$x+R8UWzg6Q|u*M@8ID2 zY6F@jg=VK27Z239Gd#9=MM0mty1*(eCMBS+=0-K>B}#?a9j@GuD9yO{=$o)!vdBs%qeP3hHwY7YNb19*jMr-xMn^K{sXt>X|wmfYbMppm|a)0u#mRELKb0}y^5KF<4aQD0 z?)GWp=IR>vAPgBqKdRnnr{yAwc+T&sZ^DWP;dkFm!tP#A{3O{3nGhH?Mk#CAnCXo( zZ-R|U_1i|^Rc&hp z-`tGeZv56CkR_cbz#igzd$}uQs^u%Q*pV zO51N?W35M}lDquKJ>QUAseHEi`e3isN%C6;gb7e|O$Rr;Z`m8(k3K)>(dRkiq)8bP zi}&;Oe)ofoB=yQXnFJ-~8rxmW%qa(yZ5CW!!;8Nc|nN2YXw>n-uwCn#$p&wUvwRRJT+d zZDv(vQZ;Ca2`Dy0&Z%;2uU%ZN0k2}QdiB|y8PiHMqE~hf0JT_<9sLlzu=?SAuo*t6 zevMvKyU4e>3w26VO?4Ivz_4d)#1(7gsuuL-;x;Xx>dqGc39hZT$ z5ACtYrVJSr+>%-8^Fd`{dQ$nn&`rJc8TV`(FGcve{dl{;J*VO-k7PMQ#4i*a-liwt zuIU(wDyi4nECDAMNJ*IS5j%-nnmk|ZcX@5zke_CB-i&5-6g*Dhr+C`|`-`y#*^prq7+PSB%j6>3Rx8p_q8(=}$$TQ9MN_1b`x^YzBW`QsWD z6%<-9ZTH=&L#g&);qLm93r9j}l|i6-^bsr88AKk+!%mXumOIWx8w`>{`eR%eb#<>l zR1eqSY=g$mMTUk$TlXs}DoS`fd?ZnZ%1fw--!p~Z?%LUP$tF5bHW4w$jj5G{7L<&) z48f~ME&urKix(0$L;LhiK6PqZXZ^UiIQ=#Y+2Y+9g=wAX z6`GKB0GO*S2NI(}*^6=Wqpx0lx?;NZ)La*TeFcN>-!BiUIQnpo54AyQe4)7~@Q#3S zfv9{Q>MU3Qm3n=?-IcQNkdUd5%=NciUtxCV%|`zR3!v*}CMRsn_xrGGzFATw&ZS-H zk;U9%2ZO`fg3HG6tth$up;y(_O0qV|KdTs-Zc#ef?VAOY!On$E^wlfo2PA)zwO-xh z&HMYbd&?+v_s35-NCpG>eL(^5;j)@Og4dY}TcGp7yb=`swjr2V>3UW+_|$Q5OOMSyfhQ`qPsWzlNZO{%Jtf4#+zDaDDAi>ut|zKSdIbM zqOT0hj6F-qo-sj##k^DB*UHGshJPp$N!aC6rN{2OQ``|L$xTWy9ck1iG9tNU*1nRQ z2Bf0zc0;&IS=vssmWY}lFE7u6DrV=1sN73&aqqfoYU`~$>Qk)q7$J~ondo2y)Lf{* z-~>1)s_rvd{pJABJahYOM#H}xh7HH>BQYy4N0n8pBLEPB1V$1o$= zivo(NW1qObFUyN(fP8{1=eHaI)GBo{4er#Z?}XvQwa*kE`EcEyNQ>&nV&%&Gi#UN!#$@MY#>C62OBO}wooP;g#j)@I$3}d4yHwPsz&AB3d3om`MPAA60!+!Dg*DcnMX{;bOd%Lf| zKdz}p`c82193ZZMNzK(rUAm&g!F%BhCg*FYwuNR!!fYs|$_#ldcHuK=sK%k@tlh4Us9oPM^W!9%z)uyZbUR^3 zQms|rs}zO6mp|*&R~;NtR+sVX`^@94v(d-j?^*eJ4yOuHLB#DM>02T_s3K9}QWPQt z#D)+B*Uhhbgl**A3H?vJU;XuE5ghk-`v`z~Vt3~QF(4#vf6Lcd_P=c2d(?3b3#J6qtEt6 zD5q2%|6=uZ7EBBYN%G6kz+ZpGP5f`>aqrPO4`_I{ZIfu5=ESE_gAMNogq7|ta4e3v z`UNHF;pF5oc%QV*8qG1g1+@tU&XFZI$-*oxEmyI_uJ;{kZ+#{*{wH@hNFX6!*QC*2 zbxZM#I>soF4L0g-$yzp^kH3n#ke7GNV4(;Tb-$lO_CD6q@{5(v!*0n9$L;C2_1ppm zp0V+CzgKelN>?|Hu+#kzt)GYYnajQ1s> zN%oH)(_LCDM5>DtWU7OMaCzjwmHFLLV9+`aU-HGRm9_fP@AYuNU>2ox5sFQp!@B%c zQC)MvG1}b)JtleCuXzsOE3;i5eieCa(z2(|p8Za!1x0(Od{e8{*>~@I8;kx!`f1S6 z0vd{#^0dpJV8^nP=>JembTaALqld}Um#B~+gXPI#o0Y8Y8+11PH=O()8=^( zs$|aD_KX5v4~?z`ggG~6!1|9^W@3Q;F!tq9c=>m2R$ULLUZ`!eMw@>j3Rbthi$+Vw zQbNM~z%yhIKVZZvC$`%~mN%U(-%jl`Pw;ug+W<>_3JeU2e?jj;TfQjMj$w;~gR0Fg zL6MN*Vukbx8nHhB@fC)dJ1K68m*Bu{4B9ey^1jDX)-obh7emxA8 zOmOr6b4~kX&Py|E7Q4*aNgCnvkI75EA!bNc=)6zfX-%B+e|D}{3IIZ!ONJe-|1wAy z6Y{BF)SUf$_6Vo(0p3U3kIcO?tL>X(BQU_a8sy$dXY=)PzZo zu5C}foH%0~6`?1}xB=Q59cja)?iUW``w^awj-ia98OBh1^B}LZ`&weAAMF0aCh|1y z;32rFBMGqBNXjG+az~8(n4nmICy3aOV^^3t{e=pwt4t87vTHvL2A;dju~3Z`k+5!o zaNpA0d;@|NHP7z6hoK&05M){ssZ|d%bcpGA%*;>k9%7QL)R+Uz6z~puo5?VEsp}XS z!bs?m{DW=hX67-bYlCxsPh;cj6-PlNBVYS4kZ`t)jzv#l$JdAxB7Oo;9aA_(_iiH( zqm&HMeKQEn8RbcD#_#fscwNW2r{1(x;LI!|m}JDwgq~XwYaC!#pPxA(8nZ$1@;?yM z=ps_bGO`<1_*QfPOpm)Awz8+^@SJkSURa1f7vqfg!Tn*LWiB(7pk7@LZE)ql2WmiA z8O2?c_!hXN-`zB<;)bzuH5xH7?+uDKP;K42H-QGRq;_5Ai&0fQH9n7+f$dw+ps>2| z=55=txRJuk$e1JP1Nd4l7b z)u=ccIEP}kB=swR=p0n`&z?TLqJQK@gj_HEgk$$xc#vDt z>%?c;$m7Qt;4*U%^TS6qVZQDoYDctdzhMM~FUu4Y(zqJ04#8WfY3Ue<;&5?b^xGA3 z1Q|&YjiZh@j6tgEn6V_Uq6M#p0x5V{AN22pRWlijtMhzbHNKN^2lx1!W5#%ul!oD_ zE{7_ev5C8`wVkzo4G1-!U66lNvpoI<*oe3`Za_62bIUtNU&i~w~4ko}MW>A)D( zh(~&37%47Qd|W;|L8bvR${Q7jIPA%gL7cwNU%6rhc4N)Tid2uJ;y%AbCb_y+CM}7* zELmrtYOT!xvAQo!{({|~gA9><6mo865RZ)+At&8N3!vENWjzhhk*-UA2Sa+dv=Ut7 z2t|+ZPu+qR<=3(M$@8%YRJE@67eWAO3niR9D+EG4Wuk+(pP!6jWTnCg4x2;8~uF6!-j3=>@3bOI&3F~wTh=+v!C{z4Oeq>=Vt1Ca)bx0 z*RS7`s6NZhHQ%bP5gl8vSm1=X@fza;lp)KU@CjVaRxv_h6PSoM%p)|W`C<`=Ig1Pl zr8f&mKZKg~0J~d{QPenbsckG$9gAiD-aAkC7f%XgY%&~yo`Jz6@GO~ZA(Jd2SM5XV zgBk}R6ux5>iL_>`2kY^ zo@AOamxjH-IoLXAvftLN8@I1OJYW{zSh!Axr2|+;qa;wWFZVrYzxc_s;W?ovg#2d| zle`T;$3EyC5M;~p$GlnWH|d#Jc4WQwX?_oZYRu(jb{XN?=M4ZP@-!wvfp@rbOc-q^ zj1Hd4Qqa5KIz{D(^Li{T*o7JYT7m0jqJchp_3G8RP4_<%4|s~mb~kJ_PyhIQgf3=& zw}lfTMnosZ)O__>GYn+SX|xHt;?egLCu;R`fV?=I*?Rr3VMD8#>1mv1PV?gq)m~?Z z2JE!2Wq9ElhBRBn*A5#pM1~>S6>D8(t|eLf`aUSWnTFxdaU9;oa{2%H85y() z3?7N3c^8|3J11m!Zlajp@7R9JonP4egi^L$r%t)4d;j@mg~|qC_8T)Lo`@^HPFqiG zaogNTaj5-GE7gQu6(x#_`TzGdS60Todu+jtnNS!*xWc(h24Db3tsYroB&PsXtBg=4 zbe<(coBpke%nb==^5naURm#na+v*Klt+d1e^MJa&P0q!OgN9u^cP?>CuXZs}E4}`? zKFg$;vu4S$0=j!-aUXXI1^un(bkJzX&Nz8;6ib3byy=4-6MD+jZ1_7YCy0wvWN2(n zr3Hg*rVr%quk-vNiewBCqkMRYtp}2zCAn{OsN@vALPWoZuj0Y@xd{!w{ke1x$7ejY zraPpvWW!ETZrs%8b?WkbDn3zOxYtd48C4$U;T8smGxR~SlY5bAKQVAZNCxaz8%pTIL%l7cv_dq;h#r%LGg02q|T%~%o=f9O37SmO{v=H09;w54%^AsG>pr_uII!Y^4d`BWP@6giy5 z=KQ<2FK3Wq6lOGZ5Z2S@thokcph=iE0`4i#tuK;d0QzgBta02tQ?>nNY^gv-rKexaGtzZAK?{UV87#O8SN)ZgR&`NweRZrl6IPz97)PZ*1VqhRg_b$NEt5Ex3F(E69J11BjnhVl#8F& ze4fmWiYC@HEdZXH^Y+av(x15F49Oj~vT^vLcbPZTtw!uIp#$U>Y8nhY8EG`evZbkn z{c8!+radgAdABQ=UQ!XOVcXul)9x*wTj!8QtKZH}N9Gk@>$~Ka@;-RPpyDVXSC+93 zYCbi+`kGUU_kHu76M8N?GW*?(BPo_3?@(F0%A|PFmxCpqCEf=i%#XlOv20Cy*UNx{ zRTKtJfTB%(WIdA`=3@a?QQd!)H*;uyrIe^FdB1s9DNmof*)seYLm?*Z^Cd|z`Rhaz zt!j9g^LSP82G_VQ7GKPi5!){DhZi<6Z^?7-Sj%qoeu?unTwWFr&acV-)k@igEDFO8>lN>!fRJ+qhnSQ%-0^8A=jf4tTCND8UdI~-7^cn7W`{w!N5^S4mysmly z83^`GW2B6y!kH#(&iG3I7qsJz^PzDKETk=aBt~dA_oeb=wX_?0 z4d>bJ31LWp5K&eGCTZpKrG_AJ)DTlMCvVH8s-9`6ovVEF+Gf63wEkUtzl$}KBA?%? ze`}Ag4P#Zq#0-H7Y`gy^aYSKZ+LPHhpv5?sP84MJoM)Wh`|YAodCL=GKuT}#SZWc| zO}x7@9A7P|Vzv5C4F^0ZgE^MzU;0I3I;>f@F02#U9Y?@Uzw&17PLe(GLVnKQ@GTc> zmN%mQ)ntZn9G&1ZWZ1ArOd3p7VWeFK8lpdW+57jRuvuy}K!uHP0&KX<|AFV^VIL;< zon#dgZD!5E?d89MFRH@+%I)9Cm9-5$!m;

bMb$eTJeUtRaD%eKfyZwE2_1sW*~)W+1X8lgFW5dudwEg za*A6}P>?u4Wpcvm2oXTI2Pvb3Qk=Td$LE- zM2*Ho+gr8DE?00lf?B;VKV@}$p4+qt3jI}(xql}w<1u&tyM+d9$=M7ZkKU@ZSQ7MvpInCB}>Kxiu^~@ddEe;&pNHBGeO@ z)?3HqAkHrQ0$HeO6dJ{|7Oj`=!^F4*N7Dz6XK*Is!JctkKA&sX7NLFfK@r1lR1vTu zpsbku8L*AGBd>79ahYI(<;nL}@;H{J8PebZN|2$|3DHmC-q466^=? zxQnxs7m`bP=7?!Kkr3Ts7hZ@Mfqe-@AbJ*HR|7m>dEjRR(*>`iTSdlI$>aH*WvwAR z@}1W1Sg;k}1!?dOhZqC=kvIY_gOs>YVnoNK<`GZ1o7ggGHcfWn>Q z?(Wh^X!lJR85t3fERhDR7bN~uOx%^x)$;2G3%j+kJ*Lr zOiCnTwFQ387#|4*{l=NbaMR6!%LoxgRzU0#7w30Bftm>fmKvv|-7*kg^C3RkuJuh# zJ{Yijh1EcS-@i{p-A!j^K7<5Y+K05r|lIpu4UGc$qxNg3~3Y-d+ zK`P1Kcnp}dc-B@%7&tQiS0ubiQj!Ajv%g8;~q_@7+6pcar^2raI~<`Mf?WBjYL% z4t;}N;^OPDjg7nA}`kc5L3gr{neQnm#pW{i?RF@ z_KcFAEA*@@3!f$@ufPvT!}o^Ceru<6y5=1)xoCTe%zpW-ddlDWZR7Rvbp;1b%8e6I z4_)M*B*c74vbU2&?^ci6wzdsuDoPPc?!uNFk${Am9{{yzE5{K0yD*VZ1d#{v7`{6p z-LiCXOS>n!Zy|KY3Kxgg(V@+WQwq(owP{uGCKj1U6=y>JVPp2UDx#17q-(#-)e0B(6 z+jk(b4$e#$?d%o@lr4&E6^_hAiNUgNU1pQ?$eXo%2cC>Gqq-=DJjn~|)aYd4vh4xp zh@0QL1_2J|?G4sVr9m;K3;yQPiLgIq>X`>g{7dMtssf1h>(vUe8Dy?aWKGwvm@%fR5^Ye=$9 zQy?X=d(jW!$70+y&vRY|CrKFgcYeLS0|Rd*Q}vaVjqK#Ed3n*{{6!`uF(Ig&V9!iT zTZPJ*6yG4(;NocZ=|N&#cDA}<_+CAkkanjQy>(m%^Bsq|(Mh3NFZy670J~x=;$BAN zXGvpqL?Nqwt_;}g#=eH9(+KVr6HcKEF*$q2`aL4GkwY3VcWG@oK5b!QcLoEXN zTO}EIw6RO?e%Q+*6UK%C_<%4)dGolsK-Se*mFWTcKhc~H)K^=0k&(! zov*Mn1AqxT49w|10Uo$<>y|v?K9EIvBztmv_-dmP5|)#AK}rbpVf=0{Q&RlZVr6#h zSgM?m)y6ys%V8(z>d6MBw5BPa{l0t7XlVEr70K@r6I<`Xr}z2dK3GBY|Y14h3ec^}q-+`wf(dmdL=hBNg7Ga1WPuKYI2zKJn$K_b^8 z$>8=Ce8%SHH(^5@KmOt49e>8e+0VCkeMhk&S_z!E2pl>*F&l=)ppCjgGB<23=)5p7 ziGNO8 zyNYE29lVWU)*9a&aZTmO)-^mbG7+?os@!3fB}mt+R;}^^_?XB_2hw++{TG%hlt(Km zm-ca@iGEgC$SqqojDF2d0fMY# zwUS(*4H%hUjjz93OsoVA<8J6aQ2YtgB0+cGf@tQPiHRY`{fSA?N>3vi%D<)0Z93Ek z(nqg|u7`D>Uy%hcf}D$;3#~uoEVz96@*PfzH7M)>cPtDj!>VBpK7Rf{EQ7^CA$c0b zB40IiKQakCPfb${NIQvw#v6gJqOy|wL6o~jfD6g$N33as7urJjxd`ayq*W%G1z74# zo(IuGN-~%mlhr0p&d!~{LXN_R*T`sdMMcGT2=%7+)j+PK)!7Dp0=%g==zV{%%D#M( z8CX_T`2Kac3miim!vXf+n0}d?lfzE1h_c4z!t4x`Kx>YGrs z6ye?gR$%~$0Eo~KV51Y(AE-SnLwxql$PhuFpg!O`jBD3qd+^{QGzTs}Wk&%}^N5MD z!nN&Olf=G#@q6MvM&tM4GB9i_@}ohdJgUhbEG90#Wy_X>hI6nlZ8KInxm`-?@p20F z`ExuOu44z)N>8CS!Pc^>_N>_0ShBPLwre+ViuqP};?thRM}>-fX=_{C0y@;}uiz%o z*42I!nBNok$@OT5;y}cRt;IOVHxum?@mxgLCE||Hdl-w0p zj2w;tcNVRLKRc-iLO!~;wqW2O4e8LEAwo?}?Qh>}wCBs9D>Cnag zIvy-U(I0BIxNreR)dJ`x=CBg>I7RoW)(--fN%nl=%#^&ZNFs?-uGNKwyOiYx( zMCCh7G!AZB75Vh(LbAtL(mrv?fu4D+%{+pSvUSaJv`?@$dz+Xfaq*KZ(%)Gqjfm|n zI&ts}3+z~EudVFZ2t@k z8=JDG-?(=8Q~(t0ubP_Jyk=$g=4rq70nrHQBh#!BOFs^X>SKedmA$vR3TckXgf;!A;&UN}HxUkS1NIgH)ZF zFv8X}3ek?MiRRFO`Dtcoo)*|W{!S26xZ>rz`y+WkEbNaVbXMe?H(;$dt+|;7MunR~ znM3f0w>~%iz{eir#S0gZe;E>%Wn&2$fuXrX{Ps0BQPsWC7Kh$8G@|X%yGwFn(Cl;Z z$Dg;fybW4B-fqngfO)FR5*NI}OS6+y_|qXB+A!M5>#m{hicOJ#h*+(=K_@9-*8pmm z2SUq$HWUBi%uHfH@@a6hEIrpBEx_Pye9e&1&`uQ8D2`vl^KU}3qwGp;pD{>qfO{6w zsnOAkaSq?WFT=$x>F8E@TU!x0nUmEd1F8$`7IT=0=VOP$(}aYZ%Z1oQk=Uk-%r;X{tc`z|en3kS-?HK=$St!Wecr2Nw!9@;DiJ8<>Tw{|h8j}bd$RaDmE z(!odkp-`U*>Kq8IqT=J1Az)+Cstjetxzm8hE@9L+_F65(*y-3FA*7?=;^cG|iwa=g zvYYM`dhA!2Pw50?+?;)%5IB4O{F1<Z+=5w7CHl($nnjOs+3}miEy9#XpVNhD~q$D#*s#q)19~h^mUhH3vGKC;BNO}=rX2#uVB$O0g3cvdJ*4^ z1#402^hiJ>0b#z(E6db^C3Nh2t?pOU4haGoyh(EMu3fdfl}?p_4PRGQGUBELOxT+Y zUtW*=!4bvM5aCRLH8(?+90nni4nPISU36@05xyeKz+4_>7QS655q`~rERhqlV8LI+ zdtpMt#REiB_!)PfS4ikQ_am$ZMlRsypMkbK5_KzaL6|X$qfq!QACz@X+)8_R2jLbv z&1*pT{{;rIQW*pY!e$A=7IqtW!vOhNo)T~V`}PNW4XLD$dbW%Ls`I9VO)e&7t@|23 ze7J)zClZy2`ZToI0!263`LuS)IPwt2;_{dh-;%(O##0uTJz9AuW-e|6ms5{vZWNy$ z3j!{{5fGEq6C=Cn6|OJ&^L8tJmwU_O#+%!%5E2s$U7omrtC=p8o^}qCAFqE}OEyGn zdJ8=_v9a$gqF=UnA$X!go!h_Rd^e@vtn8WG_YpBY9qVb;w<#Di-X7lznJvDP*!Z5j z?*9H8#DC+oPqJNsXSv-k1LpomB@RqCC)an$8j5kDS}lX6Sh4mF7KRaIoGt)LPp+Jg z*?>v#QZRJ(Y&q)>ct}~I`1kxKoa6xa^^5yB1)!)Hm=YBcxd`*iraU4|7fVH5CTd8H zNIW{B4r%?X8>_c4Nw0XeHD5*LJdhR(U&WAinga$X^jXzsF)4`Bm|W>ks7(2uGxt}O zl+eS|-ZYUl0#25ddy61EkOzeUAD)h(`=|;ACfV->1zknO*p!DLo+xEHo%R%pG|ZJp zkB+&DN($K}r=+X|oM)=0H;(lUXrNO^*njDjByN zQElVkl|t$Fy2tjxT}-}7HS}1bFF}XQiq%4J>}znTLG(#7;SPwabABq0fL{Vbquod* z1mywnin;-1jVzYwB5$o;zy2l>McKbpQtNXl5NY+Hoj7@tRep1Ye%~cZLV_IDB+$KT zX~`|SWkMd6TGW||DOz3KD~3cZZEYhAYw`KAV8*g^@l$RYw`D?MmfzLyN^u9+U(UvX zP#pVF}bvbkZQO$W$?*qV3j!{@IovDHf(y^x_wP`xyFG#dr|kFnEhCIXyfeC zx)Dds!fHuxn%(U`r_J2_j2X`|Ugp&C?fAKHF=!YF-}Le0$0X(hqdYv$fIz|V%i|qD zRvIc3J9a=+R)Mh&s0T%C7Z`m_ZEYYn$;}$YTfUfESQMcQ&>Gxh?y94v7L#@=m|5RP zSzG%S(lVaXdLT$N-uUfZU0q~%*an+|2{z2nw4zRc>i!D$QL86uzU$E>zjK$t5iluX zg1p_u*7!6QRL{H26mykZkH@(-x{n7I=?Ot>uRgf37@qG8DC6Wo+TdzlfuY{(Pdg}o z7>rfUqpW=s>nj4ixw8shi((V|%lTjXQ52%G5D^s>#(N*y@#8+o#7}^M>AV0qRKWr; zhJgVh;rF1Gpd2MMGTJ%24M7lR$RY2>o_>-Nd|Q9CZ>34{T37JP{A51^%2cvS6gThP z`}fPP$a(>I!p0nWXdKuv*eO@LfsY~;eW7H9Nv*gKg92EOtEznTbfJlpBTiDimZGAW z))ecvpqNMruwuoEsyxVY>|1soS`Tib$rgLJU)RpR^{oVaKy4lF;lJI2&7f$&_@p)3 z{J9rik&PPlOKhI+_6)$&{+g~GA*E+i`H>e~3kZKA-<+94v6psF?Ly!etStm34cqyR zUY`pOHHzXf{Ft5HV6tBgoncOX{?uD{g=|kd){mhb2Oxw~<9MEx6`~e+GB{0efO|-^ z^a={jPl#hslypMimNbDoE?#%Xt_#p9on7-nmi`}~y88Re-7dxhbpg*F^>HXNv*Zp3N#>;k)O7+*FIW7_ShTh*)SxbX#`KQ zm>565Js`E~4+%|lITsz)P zAxeptFzJ@z+h2OtKWAZOHAJ_TWDndDD^d!yyTGn@K_AxC)A+wr6OJ;(xNt9wy$8Jm zfwU3UtLL`8Ugtm9N#ZHAWs`ZNEPD;6la3Bo)uP>Pxt-NDBrUQyBOb8f!JKK1*Zgk7OoN4fI}7*d<0qnwpEZ_spczC z7jwL{&NlJIi^tOs%ev(FLLK&}qNV(dodkgHijc5W6YC+FDrGQg!HNvKrC)CTq$Z=& zrADMOk9LZvwMFq2_t>%~){@0UJ$#y@WlQX*9^tX6QxNGDCN)h?A1Snm-_BzW{Pp8e zzR=IO3S__GOy+YcH7YXFi)xMGF1~<`XaTO%n7Bs2GG@(n+=`DI`xW)|$5X3$b_v_) zW5$&l=Nh(J9d|i0d#7`t@PYdQs^`21LDM)_E=BHrg{0>Vri9%^TRTuqx*@tk)lqWk zwP@u8`Sr)0F=KcsY@0irJRF<2MCk}PK#>MQxVMo;GsNJ5cKdor;(XEFipU)>`q@pH zMVoG>$}o>W$S|lnS9gvH@!Igh1wZ10yQ51BFBP*zhh1Iw!`qFea2{{C2=JQcY+Hv5 zCneul7FIJw;g8UOs5(bL#%=8Wp5AVw`m-+i;W4uMQuPEJaYNrdzD_vl-v`4k;j-q* zCu%e(Fo2X|!(W{JpuCr!cT7e`kQ!HlI5zpM1P9YOI7o4bTeTix0n3s%$|_9kF&Wwb z4U-a791ZICPk}qhUZY64@6QY(4eUADJ+x}wx~cd46$3^u1LFj~ zo@WY`lH3^f@ZrOYkn33U7XoAJ#MdcBN$LaMhP%wh%uI<_mISlVHctocIML}rHJ?oe zD^5H*c5l>JOH-531U-G|IWmbfLDj=rOKZJ~voi1t-JZU|r`{2ylu=bvqes}<5#c01 zFoo5Yn$2zi2tu>vzaH!3v(mF~;(4Pvk>yDciAiSMX>_>pd$NC~$GxOCQJj}U=`u1o zNs{k76l7>+P@I+2snoZk;}phN1W`;_od`S{eR0QOclUzQE(jR!QaL#|$aXkt8QN$c zO@XF;_jjyXmIjMK(m)VN;E%fp?}WK&3Ic8$V(ubcaj^vyIjtv#(#cAWG>qIZVRfge zu@F`-(cOLcutIzO)9XdR`N`5CZBIH02!kwJIyzof+U8f3&;J5dV$b&bJ-_K2BinGi zClc>v@A&hUT^@li>9utCp`By3u(MC^sHnI_wPNB~ZC_rq19gZVWmQmW>MkVSci49Y zw8VQjE;{-ZaABY@AU3++zC1mnt$DKa&6~xjmWje54WmV|3w<>ON|7B$@a8^)^U~Jq zQu581x|uDw-%l)QyJv(Xv^EIPq#efbqNeaVLhYcQ6I|SBX16x~=cPC*RgL0oJ_vjZ z4QEvb;5GH;o!yJbl3Bi`GXS#i*B+Rl3!|niMZptFwK6r0v#ooUo4Xz*ag~T~@tIle zxD>qU?k`_%0n*B-WQ!baE`dESEwPH%br)8kMz*b#y3YPAVln_+M3XHnUyjWW-#oNg z-Tn+fhUnp^E>|RDYnjIaRDjkHCKm$FaDU3z>sMm8u}!0IBA!PPll9vHo0MIyj@r63UBqNvcr5EC5=m&!UH5Q zSp*Ja)0QpQs2+eLz*st=rJ{1YL?Z!(X%cKi)fTZ7TY$6G$i1E`VyD|rjGU?^6`AS4 zl1pB=i8XNlyjAw#GBvG}Y%XKD+7bb6d7FHioG6s&T=9M13DmIx%W0#h{WG_IJT7 zEYJM|D&|*RBT!?#{;=8z85bAWn_2wL4>f)g;H^0K-@>kz4` zG9Z-HzH;;G)rCzNiE8h>{5t~O?kdwdc15!9Y3SC5NYBIo3j`{)Cr3w>^Mn19TtZ^o z_ClDlPDmGmjv_#;!hjO}N_h@71KQTCgzp1X85#q*6<0IzX67GHt~avcO*#^Sw?jwg z*Jk8!Scd&GQ2^>mVvhHmps%;LA!J?_0S%3fzll%`FiZQahLmbk=rLh%2n2&7It3Wn z!AkW8D$azt);DRG4A!h9hw7f?Pz^-FEKS64F4?xsT@4T+G|1k-&#(LVV2pb$^c$ss zQ<5j%mj!Gz6=dEhqno_<6&@eM4l%Lily{e+Y!tCNLNjjgrT3p8i$f4Jk<6-;-!q?T~2PUA!y5ho~x-DQ+G1_Hzq1)ziex1F_(%E0(m2#|AL*+^!t4I zQ>j!|bg-{cys9yT!{;P`D$wTHNc-@i>PylBI@E?10nd()@+`f;r^UD7IL(q9B`6GP z5p{H5^7HW-30~gEN%dgUvsy|>>!UJBu{G57>$Obwsuf?yK?hUA%gZZ^+k}eX!SKM~ z{5ttPm!Riq%8!S}{bfW}zGB>k6z0)zb1%g^s*SistwhmPqSvKZyQm->R9eQzDn+6~>M zCZ9_HOnCc6gI1iw4Q1~$pW{DU1li#<-fGr4Q)QuftSbnOWzN>PGkBQyyas* zN8ZT`;` zHy3xkKfw2!yOK-k##z;ay}9jJyU~)Jt)_u-8x~~u3r}*bu*+pdFeORhY4x?xC zuT{Tca@5qU3l)(i!FbD^J^RV{NpJ?65g40z5DSxF5VS^ zS5bq^>1YKHqNUHYvaw0M6p4t1vdPf!3Sh8!=~L$BJBjT6!{yqEx6F+q*u~1Bc1I)x za=flbHihqI2g}3yS7Hl<>mA@x-uCECGtRVMIe#=GcmvVhgX*LEt(A7somThz!oBFw z=0N$FvXq6X=*W>D+CR28Yyf`&wlB0O50gM&vILj@Te7S&vVu(NjDZ0&-3f0oJx~@` zPVqOCFI|qpbGH5DjnQ-Kh0+}CaU+^W@zQ9LOLQ@e)44G-g>MieQLD+U4tBaq(E{g=?b!UM zt{k_AZmFsy9qM{ysBrbW7|vf1R_P7lQcY>-fzu5QCEtF@Iy^H01BmEoh@qV479*p5 zG?!UF51avbT9U0wX1n(PqqN&_a#jFS8cowu7ixxfx~8Q6d@K!8K@KuUnSbzwxlKW!tu) z43TY4$($kctTHrfM&<@Y8B)fLy+er1k|C;3qhu&$OoTK~6f!hXq6`_jABTPJlYM^I zZ~g8+?zQf+^ZPU$0|$9?$3V2zKgewlN&eO-QgOS736(y~zZ#Sqzbu z*v+uCm~tq|A?aMbqxY#0#_!8+3AOf9r;nqy$2gnUuUm%=;nJ}knX$DzTxwjT5v^^D zC){LvsVMpE>Q>!61t1h1k{qr5arU(7z9ZGzw*958cX{IlNBjQD&{=uTES-;Qp60P- zb5^ucH8?!Rnx`vQzUk0fr%?-tC{JKOUSpV6ZLFNX|HH?R$IhH-PUSG5^x1$d*Vm(8 zOKku*jphsC4m^~Y2%bYxN zKMu0B-PsG>1)Qg-m(QO4ftgLooRZtQrtxuhkirxzB!>prhj?KsA5@(-1 z_><Ihjsg> zLaiD-A|xv(ConzYV9zRlMQJ$`|B&ZwFAL1?6x5ryy@?h=Ll@RZiAcnZ)?4X#Y%VNW->8aH0mGob^UTcDA^r_rO*IB&#Je)d z>6T+$ zAb;ZWS5b=N^}Z$A&ySX3w{hysX-x~(Kt^E3}_1D#*W=M=5cz8XnEL` zFrq_DotD=>(`sv0bfASs`|=HAXzR*tteaBZcU$0Qh72k1*N!>97nrTjj55|-IzJf5 zFtE=ZG}Tv^-5-8BbN`!$Gu@irVs0&zcjlZn5P=+ZTBWVt-ll&8(Gf+F=0o26<%={T z(oZOTFpq*iPix5Sr;c5MvITPXY%s!{i_5yp(EL-!ZHEmt=EU1!h@G4?^G?G%f?=KAn0vMeU}aJNtG?;q4LxYx#~Kn$0KnQ~z6$K5Rs2Gh+ztAycxJT?E|Kc`{UkTa(L zZ~^{|zcsyoQRPa9BaS6{Mc>-iyg}QRFUOVJJmDx=+`;Bv;Mub+L3F8`Ca6s|-Ult0 zkps^yqkNpdcXG$>j*ew^=?%`p!6dg#1o2g5`iGRsWt+P3V9m_=^S7YjGk1$7yhGf2 zlNkj@6Me*Q0?33)@>K{&U<1i!PVUN=p;cYIapRAmQ=M8WL#n1)4|`K~yWzYdo5uV)SM9i8!`$ScZ1)zZAAm_-f_-t<@Rg3lA%^$1J{<#7clH0fV&L|U2`7-wMGd%1Wb}^Ch(TEaWb88>HM?EpPs|vDrpTj_(E1#b~`&s2P1`UHhrs(B*dl z3I7Z+D}s+{NDm6|6naz0B4Qxg=Z-KW_(9QaTNqw?Fy`ygL_ep3OKsq-^0rTBE$bM! z&m+30*`tYd*DEm!(saxR*xEo76Z|<3&TayBSt*QT7*0cy1CKpwc08tcv5l|(zOE6^ ztyCV(65$pI!~3TX=Wzw~i%wm~L9!dDH#XfU8d{`(SlI_0bB) zcK@`y|D23%UZ6W=_7+86PxBnUW&gQ*Gl%bZU`fKjjVQ6_Y8V%E-3~j1Y02IMEW_34 z&2Pwvx9#~syN`5M!_(7FYfffXR=*kVH*baD5qbe$f>30Lrn?)-D6ta^^YFNLZJ~S2 zts1R!fT3jM-nnMx8B+b37pLUsw-+eu{*)GP66=FJGw(KiUNq92j z{ec}y+Rgwt-erq@g0Cj;`7M{`wQwHe=GCpL@CW+4Vn4ODEmoel{;IO_LNz-*gBCLK-fecjSetSNT-`FRUbi34V}qMkv+0qPFkN1Zm z7_w|m%rA=D!Cf&zZ;Ke@?!C0q;PCK7;S?|X2&!noLY)ak2Z2cT?_GZ+JO9!ulM@%) zY)#hQ1}CJd#_Gt}sW&~m0$9U&CFGtyM)5?#PHCOX2lHSek-@iW;@j?{TilodM^jfzMZPCdk@SG9;!-K1zSrLE*;LpiD%H>(B#zQ(MXH%blpG) zN9P@=4T6SM%8!@v^+3%isi}-39V|t)k5&x-|->MgF zl>L8$fnmNx%me0J^R@kmjFt%W>%3K1C5S@AD*S=%g?_lEglf*ZIrZKBOe{^^YL0nA z6%ASYLHGQeSIQ8NIqXjg3y&MtpB5jgp>*m^A%`{kuy%3JWCQ`1-f0#Qs1 zasS-5?I->%lms#^v7Ng6KVzz&Qx7C(&UbBNzWYc+HHac)#3c{}HYGJ14jOYE%dGvS z6Y*xb3YQdhpnvM&XAhbrkv_+ssNA+`)bC0uMww%es+^%+uxvFWGiY7)1;Ov(ap%N) z4uxmG|Mg$U+~5gr&$e6LWOuhI#uwxK`(otQ(Pa3vPP+nCesW&0U}oH-jT<*MsagH< zq27FC(#p0vF-!RvxZ&4T+OT26ZJt*b_7qhZYniym+jZKtK*hZ5;lfSVpJ@CPN8m8DvDvn6 z-MU4OmK~|LsqT9J2bT?KvwmnX&#t7VbTmizcjsR!|#7yo8=4G!|F4=Cxg>IwpB?n^5jQ1K@Rhp zopt#q8_D+K@@9Y8Z0WSL!o2 zsio2tv{(!bw=x$Ethyfq&CkZB9eXUcBmS&2YGRLfw7Z4kGORaVrw!HFXXHwA_hO|{ z31ivq<4VZp5WU)>Ji|+`#cO6urB1z0u6Tgz(j-6&cC?s>q$TQgX)d2em~2X}pRbEx zWkHo)X;}T4v^sRAnd<2lx63E__MQ?R3V-&=;fp! zPk?b3u@{`)Xkpi@(hpMxZ)M5DqQG|`@*DN2N8V(DBJBN96VJ<_d!*FRn|7%=K8 z{3?}RkAucqzE&zOc27yKKYn&MMKAF~j5EJg+U{KxD=96?Ce4NR?1C2#8x{tM5O7)l zI0&9-HFkjF%*OS{vy(+(dAT=Z%l>yPEiE-w=KTiepXAcw@$L?ZzhOd}tXP z8vWrmUemV|S_{$YainmLjBqV6_I>o8@GYXk7Us{EUYs;-TE&}6z!a%Sm(3c8;$Ll` zQ_A2zil$Jc=g05W54c-7>J-vls5%fkwS*_|Td5ECbGDgkRAl6OT>X;X7TYW|iof<7 zA`>n-G|8;lWf{Qa<_6hex-*tswE4`C90|dGOm@bs*he4xq6~*Sk!9uQ$35`h5(WhG zj%7$^zWxd#vSL-}B|*Khet^UU>oMf)ohBVU^-N7$I$n&u+?%*AUzD?b{V1@$jFf({X9#KDaB}XuPf>E)fTLUVL#ZjW&j-Mah=4|;PvY# zW6gWKOqNvb@wqbbXJzFLz)w&>VEXo1^ZA2%Z^H-BS8(JQ>|Ui2mCUg8{;;>(dadme zw>y7T=f({o3dYVoYy&>3kXLOoT5i{A^5r zSorV@?PxtEdBOWft{uU1TY@ZFhBZtkLJWJB%s@KS7|VfSEQ^#%oJwZ>rAxy+coE7g zRt(h9=?JHUDX9>B;Q@;$2sQppY_%HHH2UZA{g%m7W(aw3;qH}$=1^-r*q$w1A+np*f>9>KI~n1Luv`Kqr7J3%!2$={+t)=f2n;4V znyy_pEga9D#Pvk3euIt#C!y#@A9@qkOA5n#C@@rc+tddA3PL9HW#sWpz6=cwmHayw zGlt%_NU^j{66|RdYoeP|5&;?Yloyq+@OB7|6wYi|Gd({j{mz!oGD*){frOe5v>qJ; zKV5V{2&{uP2(1CL0^hnz#G7&a`1--Y!8;EfYK(4y(0DB)qvez-Q*NOpKpjJoi>J?4 zihSrk_6+cZ5x>s+_gi~M?_d+qoW$B%>BdXo{^mct}{TJLOYz1l2p02!-pu|@&y z4g`rj=VRf_Rix?RB1I6{Hf^d;#svzk3wzLa@gK!evve#C?j)Oj+(}TmYXzy;yrfzr zZ^SGa8#4e|$r8u?Dbp9buqYFrT2%RQ!f9&|wn&uO64@orf^9!$!bE?`Gw)Mp6e4ti zv@sPCevNC^o2a4UsKC149jq>FDB+IFA!=+EFkXWT00bc?Kq9`Vg%R0hd?GYFB|C6v zzmdwL9a~A8LZRKBK%R)*F(+L~>06FNa3NrvB6d)nncUvi{pS44PS(2U?*Sm4@?%tR zTVYa`nXqH-5~QIFN0j{cmCoVHQ7(>rjiXH=vIVrD*Kzzs4zdMjT@h+R8Aq8Qn&Gb> z@2{02TS<3}7d-I|%;N zjG;-9>QV|HMn&~R^>*OEw-9%a5&lqn&`kVAu%ukLf(n@@if~3q8GnrF$Y@AjGg&jZ{hPE_meKvuq)_43 zL#Qxm(xjQ*O{v@_|AsiWIW~l7<_46x?~MUXlS*2cg#ye^n>GIBq4PXh(g)XSxTSsS^q>OnIU~Luo_7}j zdKt^@LVp2M$->fd7Fq($`tEL#+MjfDzwSPG(2JuhdVVM-_;zge^;N<2qhs^G9zS`q zo;h$@v}#&7KlKXyZrVnrqxUDGUjnRX2s{40m4qaqVE2mM`6Ufwmd-rRZH_#z$*|I=bg3*}9Kgu~5m zy$ZR%Ia8Wq4}_{aUR5*cszm8H{9Jarj^%}E@i)4pxEMcvRQq+1tOq`M@?@Xk&lz@6 zY&|Y3YfRh_cb}6lE9pu4;sTHMQG_|nA4iWLpHQ1va3pcA*^Al^YoqRXuWKAODQv&D zcTkg4JAHkhKg+&rqZyTd^RBVMJ*Q;{_3oT)6B-$Q;9Slehk(F~KY0}{vg&4k@WPqD zj<0+9@bbvdx6eN~bmY^EEBDGiuB_Uh>~>_?k@H)g53YQ&3>QK}X1Atdg30{Ys`A0; zz&O^81){2vU1It!289G@Sw(T+dX3_<*@ddkQ0Q?_&93>_?mY3oa)J z(HYp)%bK%i>Oj9@T|EFsANF~gy?RaMxH#}IvyXE45uo1^X}a_y?GhhL>(*qCn;V|Ln(cgA3GgIwk$`mI_D%#iaO4DriAP) zFcBL??M9Vv-ivcf=u2E8^AKmUV=K72UZmsrDcOt9GYAZzp0dVRNOp|$vbz)r8iXO& z9Vkysvt_yUl=J3g_#;?$OegDh3Ce0&Zfd@K`8gT`Exm;CYUcnEf(ko~;-OvXcG=&m z8KNmuAYqrH6OLBr7ll%BWW_qaz-Ip#eUFtCXi{$B%S-`ib+{FF%t&AYF$D=&w8inP z{Ad0SWHWPiS+GRlBv3O3a<-7B-XIuTvSdkRa@$OBf&lP`1*mmpg&2x<-(9;pLagSF z$m(33wEb7xqNl3PzWmip2*g~6(+G{(1QSRURq6GcV`i|g7y?lxiutpd=9(tT%4tM8 z`Do+^J>$Q95x3~Cum@*P#eiGEcPS;9^WpYJMMW{W%>-F!erV5y3m0T@a?JC+*0Q-6 zF^e9GbLA#YLYEXxb{ux6M#RlfW(_%9Zu|wIQw<+(%QTO#l-Xp^L&L)R^VZcohE8n? zMGwnFS1^LYx>Fg?Vst+V;cN7V)u(B|1rEQsY(DGY%cw|~g8#Fjvo zY2`+Zg5J1utnow~P?qs_BHX6T@`8EV*~`rX71rz2alXV4k$=ixIGgLqpmty3+S<2A zYn^g>zw-R_>O5PHO)Fh6ZeV5ENS5ZM?Hm@pp6{&dTI`Qd6`Difh2mg;|4|%Fo?EEN z7N*g4X42wAYyw6H`C%r0#jQ9y6zfsVH)< zFMhFqayN9VQ+>o1dSm+EzNY@2i2J8_5_)M=9>F#tdw1_X!5~rh?e#KbhI-T24z_3| z#-Bb;Ru4@~O;3`#4PvZXXGYDRb|_yljB)yO&N{X5Y3oH71CkfzJ7_^`d-MMNJ`A~1 zjl|!uxlu?1J!9qx7%y5Mi_5E%a}y-|<%>av-=3rDBi2du8T_B^9-4*I*4FICn8EkR zkx&{VmKG=7yBC^PCa5a!0D@^&(}nej8_K`@5)y6Qd2bQEJo~yFT6BK7>=MOL)+Yl; zP`ZiSmv+zT+IQeEJUi!$nBbZ=ReLaoe-&%<(~7n;BQRD9V&}=yOk;ljOgS@m}1D-YnDQhkT@+*R38No3xiY7|mv6=}sm27vly>fbBj z)_H*>>?5^w(PQW^oyZ%IO^tw(e;*e%wEg{3O2iO)D{=fR)-YQdsyNu?(5u7Ex%QY^ z=^678bC-*&_o!&Ykj;hm(Uf~*mnl`}a^ari1ja5$YI+;ee-}N-Z_sYf z+#Zf4cqcuA{EK}B-yXACf`cLUQAD>%?>k@l9V?V<$ZfKYT)`N7LSoQ4Zn7rob)SsO zYuEnwES?dgdBWx-AF)0LJ)Zs{kC98#oPL>q*+*00mV#BrygK(_o42v-2HDevjRrY$ zzQ_;j+t1}N=Ws2Fa*Rj96fPBJvl%&kqC-^63ax6h{J zW{JLN0JSN8t&?Q86S={Sreoh~Th{MVqCT&vm{G5Xq8lMvc7TDP3|NrzJFhGkQ=+bV zwCn?Wn6o&cf}XTHAzHR1KK~SnT&;bg!}9WnJ@jO8zY+=SHd64c@Uo49ad?aI2;#;`ABFt%WPjP`wpQA}So58*YK1ON^?n_T9K~Y~>K53R*iUL+Ikn3=REhBynsqT)EMGOL&_M zm6nRkWlWtVmk^?zT=(eCLi>Odxw(_Zs*N}_1JM?LyByZ{~8P2yWi7pwEB@fh2^`}bRX?r>Yy8*fa{_$B^78x~eCr{t0w zB3K>b$}sUI;qG+xD|_+c)PqH_Q^>2KPxkn>U^N~%99UyYu|}qkuWwhq(t@~Hv+R5p zLa`?087gJuReNeG5eH^5T(Ecj$$#n-#|Cco6CA2-$k-tJ^If;;ig;X%m%($vE@0(r zl2BM(lEl-3S)@rl9X5jhkVQ!K-M8;F)2FY~hI{cynT=0?ierD^@^-82c5F%TL7{WP zJ>7D9MdNR8^-94P+BmFgb`dR4Qo5H$CaP;irrhBy;pyG?_gT-+I%X#q|7@Ph>OCTU zDaFLuyn|pc@`>;PlwyawVCn3vw9XB1pzTs;&#P}kAAI=GW@C|e(s7GWKWhw$Q=66U z!KiDT>^Vs7JF_8@;c9HH)Y@M!#ge;A!-frI^YBtQhPqR?KmPRhE``9RB#6%Rpt53^ z*OE!~e=loigED!HvB6!tb#qAYQX%nYaF%3qnj*A`QiM9Xa%xQlDIHKiCFbV!>tkto zB_6uO#Ox@tK3`eczrp?XzRzWwMf{pqSLXDSGMx(_v-G(adx-334p16pE8D_{S7(@1 zpHlq2gJ6_rb7~W~37YJOV-MIa;Mde$Gsh!4*xh9~s5yCW0NK*NCoiMl%U;I+Z~>Ms zS&|~&YQz<@;?ERYg@w!ZZ2#{E34r9eN1<@6Ec&y{kY~@I9|${Q$Ymv&pxBqdTACKH zb=IuEGvD9AMoG!d1ezH1RgQzzrB_;qF8{E8g1_VBZLjmsU;Xxf*WDx4{p+7Z4Dn)@ z#gBgmPK^AYZ3{0nSd8)GpPY6H4nO|?N=k=LSp2a5i~nxsvX=j|#o_)&C;~*84Od$_a^qW>E8d4d}gm|Wb0orl!A^NQLLMpry^*&>_R zc1Kn=B$(EuAN_h#RohDA_uof(dv{%%ixpk=l71Nz(C20maM({21nV`ec<^Q_#%~+x zwy3+yA{tp>D#-yJ%SC3yd`0%HUBJvg1OV~G(;v>XVOgqWlK|*&X-u!es5q?d(&#Ki zJ54vKSDAxDg+WjK2e>Cn4-2H3GoZKri>FUdP$IYNFrT%pJkWzfSA^xR0yEY*{7dvb z8b%oe$!d8YYJu+;?96Hk?BI@5cr? z(!1w%>8yz%oi)qaqFYz6P^!*CLp4Y*lXr}$35*f*wm|b4M+Uo$VOc>Y8~Ot20JgUu zN}4lbLEdybJ3m&OvwyZDVm1SekTl|ZX=xcSc@Cq}3q)<&_Ct3i&H1wX5$-x&P(R6r z%U;~=`|r;g*;Zc`VR-_$bWQ%Ymiq16{%S*M;+anx*zVANtAF`hX#bfVIYHd|UM?&@Uqj@83%z1R_E>g*mLB8*UCMlO%$+r} zAyjXNwi|`adlcqo!W7qJ)~sVB$CF^QWgvg)SW}(GT((J@em9EuZ|-|wa$9V?09Ntm zZzZc!o~s6b5l{YVao5-g#JqP8 zdr4?rRCbV~7J})?sb-L=v`()gEOdb%Zp z4FMHo@}p!^=eITPOP~D-Lc4hD{=nndE=N7(zhi^dE%_*viL&2-@^{iPt43kYtB0W< z%U^Q6iJ8@Px(6ZIC#)DSU2DdXJd<(j0flH8myOqxyPf8Xo=D$>42wQp`!%(PF~oa(i}bmwxzj z;vMYvT>H=bCN?sLE;s+-GnDGah7C(fPR@b@kU=^3h0*79w#k&oe_AxW_VZhJ#)6sW z7tf%VAj6HdVg~R2zC6?}&V3;z&V-5K(bQ1eyw^D}Zk3WTw>7!yKHO>NzGTbGB}gj5 z(%eBid%AdFNoi))^oL)*y3om0$$E%q%~@IFKvAxNs+hczd1OVwLYD#(;sE?1J>TWd z_c_Ur(CFE7GGI^|L)gv@8*Vx1ZKv?fbZZhl1F$03g(n8`H_Nc?m6d!lEf){EYs77V zRpvJ(C%VYj_uc+Oxw9@m@=mgY;0u5LnZjJJO1e5--IyC=bo68w3=3C+neDgQK{&gI zvxBVnx;-a2D95et{S%9}G6@?>6<9hHk8I;2m=IcVen*ea`0HtaO2QAtgW=JfxsiDQ ztwHclY-Q^Tvl1=Wd%`|vN_Swp>@g*UM~xr1=lYy}mQK_ED!oW^gP+-gffFLzmd3Kr zk8byia|kA$wgXjuc&5& ziCvI@6f2#;PiwFM4yFWgoEWNtwOBRa?f&h6zS9EyE(5Ui%Pz~0J->*!tUp(RBeu1n zH0%!8qCsL$=P~Wsy}L6?1J;X3YA5`e9v$JCae7(@xAKQHYG#V)VJlzzGi7muqg1$- z`a#y)S6&9#_F^UCg5`0y=NPjWhy}N9%(SI{*sQk5khxd&tz`TDTt{G;Xbq)6 zr${Sv^SwCnbtc@(O?KM7Rd=xfprnFmmLL}PrCO5{0xI-b3vzYzjmo?l&pPke88Smm zT_;c8CTdXdblE&aBvbx-QI4!41a6U^xp_`V$$OUE{PX8&u5Dx>UVD6#tdug-J37<=;X&wym*Egiwz)1Z?`oJJyd#`^9_zr|C}U( zmf&sC)o)qo{+FPaY*P;xLux%^5)q5$Erx&n@Kn&TC2RDg7Qx0jUHQl%41Jehg4G0c zJr#}005uxE*e&Pm24~lob=^UaqZ?MvTMc|!1`h~hc(pZqOk;ET1DTZM|+nz zoDQ7tw`u~ODSPP^otm|Y7{b%k=JSGprZW)8q~yqV%eHl3(B+#&1E=M3Ns5ZU8kt(~ z{6>6uT#tHWZ(d2oLFw1hGOss2v{fFXVz{NF46}e zqRb5<_ncysJDMbIe&5vWTll8#cy8B%rAZSzc$$)u%^?fPi>`i|D?4bcShOO3-|dGi z9mf@pa~@Mq=Zdt#pWd1AP84zGJfejCAS~mzOt_Lq9UvPoEu7wa$ckNA4^3%hOkqcz zu?MXE1g*Gp?wRq{=bX^Z?q|dN%&2W{C11V=#w!C~23x!W{ZG1myT^!lkB?{k*3>M_ zrvL*93{GiFEw>Y}ng^5{gIHF^HSY6HP_`QKMh2=4WosF~$E5HhwRsHnlYwpcrSj1X zH!>(M=A^z#p!xl_0nEF-wf&$=U~95&N0#_viDc>F2@zbZ{BRqylh&NxH0jc1SGS?@ zR8VCcW%(kOqw}i6oDW@u!G!v9ijV69=kp68>%~xEM04buZ;hvbsy=`D%M^}ck3rL> zOky7=g^kld^8!_WR9Krt>p@;EqjXI{NY>T%)!B1Po{WL9E07Oli!5TED`@?Lo+Ax0 z8Gd_T*QM}}gq7pQ{ZY4W-CrjCQG*)6K$5yV!*?rMl{g%GCVP$vt3jMR)oeSaBeES% z#9f>nO?oSGYeY`JytWS4KYh>t>neHR+MPRJw+{gz0LS3^zOAY<1>19jw;EMRPLiV3 z{I=zxvn-zk=IWI=%e1?$|M%?;$IiLHEM%y7AMiTNbq@bjEe5P^ygN7qYtHO{%Zx|_*{n?4;~t0nQr1^B zL6$AS8(xX^ygDn)GWY%~SPp61{>nkR((YW}sJ?WUJhC9;GDyKz+o%YqYgv1~=UGTc zraCfkIj{$J`OcTo)gAI()2GycGHc&_`}VDLT|hdgt~sJ;DkaQmxy1v(;EZ9oW=*{M z%E#*qJDqn7t1y2z5m1Inrp>0P92ryOx{y8Ht~VL9XJxs(Xk<|(QW;|tfxQ2-wtxpvK_*kIdbcwldhgq>ueH7{=W&Yk{P zJOtgiNWnNgsB<>X{8b}s-;PwZEud(sVoz8xVbq-Ik>*)*&YHoI2gG>l+0)>n#WQMa z_CZX!`+`YF7!2yYD?t9_bFttQwTf(z$V2-q)}H+4{#bF?{s5FTsK9nQ72F z?j)m*y%vGp^e9ZuwvL5{=it#e_jFgiIXR-H zxbxOZsXSt$x*IklWEe75{%aZVJALR<-|Tbd-LJ&__|<2sEzh)kB_K=ltqz*@3;|VV zvqxgu#q2n`9CupftvjZpR;BF?A_18t&FU?rg)f};uF0!builsUMA1oBOZtzl$7&Rd zyUqodEI9&_+k-E~cd=!ErjNe1y9Y~G0b6@%+g-|Wx`Z3l!9k1dqRXAR+)j}>YhKk9 zjad8DBkuLJ#_rTW_eSgpAH+S9-ElHbAloy1h%j#az^Ml|K4gH9%1?E(nGeZQU&h$8 z296+)if4R-fDOZAsrMkN7?yt4KL5~IOLuE#^^Z6GK}2k_LuNGu;Qj4mV`3b*=5y_K z@7p)uX#bw0NAJH|>@xzPf^m_=g84BQl?0!QR!3p368+u$KFUXz__wtKHNnZ}Txw^`j z!4+Pb;2#y2-g>iledg|mqikhW)K=%^alxvtF+INhwkw)VQjSg_J%6r-a8C#V|ZRF z2?^$Et9dTfu(5A5CiORsSRv)(vUseEboQKdefQ>vt2Wcq)%;T}4`qO$OOddl9f850 z+m+!M#dzVK{pa-!)g!FpV-{ovn{mgr-Qs6mhbS|V-(zru7hZ!mRIuj`IA;#wvtYTk z6ohNuY(BQ^mL!h__b@rNfXcOHa2;P)$Z{YG;~=^v%w_>XI^)1Q+KcK3Yf>jLW9t3( z@ENZQ4qkDWYQJ|n79BiP7qE(0>Xh$t^{`tCRU`~GsVBu_jjVe z6G)JN&RI@eJ-Z*vH1-94}E&AA7PTTS2HF;QFdddA;X zD;4ZgR1UyFLXoH9no>|u03er6tLX+H+}0|*$$(eX*fG+^=Wi6 z@Q-6Rr|n7ppATi%N`V}6A*duX_kFVDcLAZTZD0F8uHJq*mOTgeDrWUNkaYO+%1Zm_ ziYHH>n!M84w)5~|r*l(|b@3#o2o}p58{k>v%CJ|Xd-sWG-crPh9=)YRjFzEwx{5T( z&-0z7o)5#4_}Z_l(JuLj@wKR=tGo8*&Su5OA%s+s&XpI$XM zeWrts(X(%_+ynY8pNioJpK6KcbR9iRnx`{?m7rrO?GKB3!g$C@kbHbxv0weds4qk4 zOPv!MjItHKS{d0eJln$_V}bd_X1&-VaQ?SR;vdS9`+f|FWeQpt0VKZlisZB`+KUYE z`Hd#I`>tHM(ys{XHr`%URaL(tvbf!=)q{r)oxZwBXGmxV+Hd*e4QTPgWz){ar53zD%cs1gLE|Dj+Gki zuOJGj{j(s;ZS&r!5BDvF4J)Cbr1cv%bfj1m{!m6+K%#U?zaD>XHgkQMW5g6DMGElY zMZjG2p9`CuI&iWj>yWSH&U?Cd(%#R9LGpvx#}?J#?G*dVPql>F8ixk%h7i+KAzMaN5wv zKt4Vv@mR7P+Nrc7IaW5yNNpNa^qFuUgG0JwAK$J&1|ofv#a@{LQ^=-v@Kbm2Z5>n= z%C_g1KGzpmp1rtdUB-Np$*1H5$x$vNU}rGL*}G%MG~cYtBoJ=mdE+8x96dq9G(&B( z3DF1IpJ%Q?gnVwd;!BnjU3|~#hl?+NF{c;b>=gZiXTnG(l_& z0$~*%J40h*<$CxcUCo`i za_~jf+4Q*@l@H1DtV(l$`2T!SKx3O_C23uUQ%nn&f^poU_>c2&QcNkfm?wLHIV;ma zj`{V6l3w<*@I&=oMnfgEKZbRd#~e$q^(Wb-$Q~o6Dm_5|&;lB1+Jtcr31&x z7$fLS{WIRp@1h56n7$THN#Ro_Aq#;yrDdsG_gsPc?SHrcbuY|s!ZMW1Zvd9xu!}c7 z)^Ds@d|LuCZx4?De{ViwlGj{NVAl>F4oGEM@^& zHe*vaX=Fb(R9`s?Lbxe2ZmqKgGK67D(Y#1Fh)jR(K=iXZPJ~ef97|V%;!D0Kv25oH%A!52H3uJ~%*+VmTAGoc0T7 znIHgT!tGd$WwCaW=zFfvPZvuY4#oz z>-tlsMC(rsKXYyxD(7g@GQZgL)hE*gKA8QxL4~9o;ez5QC&F$J7JgWBO7rUbYm3fkK{xaSuJT7|CIBM2TxRf!z4$gS% znEpeyTvypnF2>pz%m0J4vMV=u4!1z4WDHG0&&`9g0%m1Z>H*TuXYx9~F80N)iR!6* zw~)Qp%_U5bgG?Q_Q$NIwX>Y?19zYJ3aY8_!@0#=tx!#i3on3LRmazx;aTmg&K6%~+sm9W8N2Ov zt)9Kh&u`JY$>499V4FSy;KMJGhw!p zG(7y=h!Z1qfprD_DSSv!UKrJDe)SW-Oj+tk^=yUvuj(>{Hi8spFs@@-YD2{)OnYp| z{V6kr$(m3^+%^*shEHkoWc9>x<1AdOdg?i^{Gm+>w50ap8T-IKa@pM^R@0nWhmc8K z+5f2sEdkOyR!zHf3YUde9fo41Ecl!Kp&JK#n#<4ae{lHfb0fZfn9VeKdWj$CaHe38 zf`nbG-`~fxl|tqdOa#_eMgp2MgQC1OJXS_;#mbsfASQY-R+k1c7}1*^+lZ=RGBA1? zq5?x>b`5ZlZ6vNNc zWL4>Z+>RSgiHZ4gf=OX?7UGZ50m+M*H3~lkK-UbPijB~7k3%n#bv6NZPay=-76o$M zC<=F$$CQO>x*1o#ejWJkXf{opRrZ4xKYC1Ob2Q|4(|Ov#GBLMK=&$4A$b)X0U;>` zEW*F)RVaTK7OMrLwcx7Nmx8x>{BZXA_}RLdkIi2}og%a>26ot@`GkE{5;Q~k*I)P2 zFkWrsc$osjhDI<|2y_g{!jeX&Jc@BsFnB+O{!S3tQkdid(2i&j5iE_us#ObVI^3V_ znz+r|M5}U}VS8#kLZunJETLKOk%O5V&>HMw{*~}i&K$IN%}Zj|~ws-8n{ zzh>W2W3!u|A7pZ z(^#l`n^|VRQ*t2>tWEoXtbcu&myLv|Hx_N;0d<>dXH_sd)!eBzImd6^XGEM#?>XBa)(O}|< zdP+LDzZr-vrSM;j0#$*17-u-}LuBL%rQ&&6nd9w zCpS6#FLN?+PCvmh6E-d7oy@$%SZb0;P zAy$AKIq>;)*|0@1GuySxe}A!3=D7JfInnsQNeQpzQqEh#vG!(bIlvm*>AX3#R)rsvx3UB7ixca%u+s?1l4y0c|7MZ!gM>BC8*LpWmWhp)i0y& zx81L-B2X{`Sv?+)h(L+1xgO-=OlS!p18w*G*5@YG$vyAu*RMCxHy%o!ybU{m4MP(q zem@e181A`J`$ew02eYl zj;;x!g{!v>o1xnvsj|}NAZ4&HMcpb2Z0`?$e^>@%4E;scI3e*ThYZfGc8s#|56tu{ z1#%?I=;Y|@U`$e(1lYZ&-PM3!&9Bf>$5bYrQA-B;Bt_Axr8YnUIXM}LhUf_Y8@FehsS&KhpL`t*s&7y{oZ6iEys zA7SsXMn?r52O@j5ljGdj1Aphig9pW}1G`8sB+7`^qe7cAcV7N5-g7k}uFakwSZ7Wr zU>cG)yLRnb0*D9Do)*Z5AIS@(lDZ{$n9&r?t89} zm?*?Sa+^?EWa5I%kI_Z8QvvErZepgKBMN}qm>T<^j5vin-MEOO;n;_36ksrITK2S& zU*-tA?EB$fJttdbC|@~6Febd>O7JzDPM=oI>>fZvf8>{{-LgF=?oE=G3?3l8NPQK( zAa#t813;;_dpA`Bmh7*~2lYA0lLeDF0Cn~Im%pN(>pVZ24=apXQz|bz%y_3@p$n5~ zUXd#^49cj>bZ(~^A}`>r=;Ga4fTjz+p%nyaE@r^!wRLlhEp4+A!RdWtKCvl7*m|QE ze*Z34mdi|!lLp@mKg9te6#;lWH~v^&2WjD?(?hxBl_(azBBM*XdpG2-TY?Y>=1 zZ7|7)10?Ub`dilec$pUTtlye$$TYcC{Tdv7`Th#{PTO`7fcuk(CcTtz|k>jeLMa1^B+I`d~!a3EeDByt<3CW!=|I;jx?hO6#xIAP1)HYA()=Y7 zJ=<8+SsFnDT-x_JbWe3+Y#pT~nE)2i@WsY%v)8$QV<>gkfBr17yEv=?!;VcpCvhG! z;4#w5AB9&1L+)~?3K3k7cY-fO(HWw$uoC?O*#X- zS<`<7u&9A;f0kLvtAaRd%p}r??~L$+UW`DXzVu zjVPROwKxudTvV$ElbQFQP#aHa=>B+p!(ZMXx;O6&9G@Ec7U2bQMZkb9m`#*IeTRe8 zE=h3`=M+BgYyyahucq0p#y;>9M;}xmTtXqkv&!oq&Rw zZ0|$#IO7>R=v;R*%_c*=nT@nj&N4<;23=-cxCuur6s$kTg9UtR!vXc*g)5U53n9p&>Vt}eIcz@2W=JTb6g^{(d^KErM}~lWzJvEZ8VGZ!imUj*)tS#d z>k?c5LE(u+n?Ep8`x{;qLI*ec?5)nO2G{?F=@t;(S<7s|m{7n>S!BEBOU*@{K}t(- z;o39HCfshgm=EN9x;rmQn67kl3mJqGVE1<6r0MmJtMY*i;EyGm&!pU0#Hcjkn2^)8 zm`2=kyj)YNrB~asTpDzi$YX6aY(-r9e{;sVvhf$S%z?Y5|AhnkiwoVieFgTOk$KHJ z_wT>kUdJMb$0?I%=($#Rg@_r0IKdSR3gpD9-=TYgc!<&jn7wQ5f;_CGi|P4!Q%?s# z3J`aLK7CT2U&OTeMQv@<5g$eg5rFm^xl{lQ8ljTPQFW!4(5rQgJ1}x>{MXNKrxD4s zoR8KD+%0cMT3_BzFisK^f$ zuCx%NRurr{t!W+-DA+Dm!7_6IX*KGn_n-$s6VyHe`|n`lX%LS;*!p}JE#I0v3MjOy z@8yetAPL~dCh7faFEieQHWUH`UkqO=jgM#h?5mH!Gswor69R-abL{s}f5pVR_l}Mn zS!?lNQGU!qre?x7l=+RKesc)0nE>A%m|TYEc-R2ey9gox@3)%p|7=A@384o&wQxjj zvLgiOIzpYuV|>4<(qWWJA>5?=-?YkqPNA6phQe$gR1hftnfSH0YTD`OAmbotBjt({G5ueK;C`ELxkQhg2Pv{vo;EmTI-v?AjYbp>`CD_*>aKzuFCaQL~V z6jjxb%Yw&jQcfs%PoE;(pUoDK{KjXu*waM#xU+W*pXV0FIm<)6HMVRjiH`w8OhWI1 zvG7!Bau3xOynJ}kMCLn?sfUbxh&d7uAcJ%Fu&IPrLc0jGNCV%(pxh!ckUz;H7@k>EusAA$Yl)WdKQq=-|$V1MtN+#)OC z>C#W#yEs@2Fi)mMw^D9s%$F|-{6t_8=e;IbnlJ0b8L zxouF(V1`4MEra#WwO?=79Melzc1){SwOr(Q>|oWj6;Uv#Z=AB%S-qVQ@rZ0YLzpffI_or!ZNg|GTYnd0-ysxN8F>;h{xATr+_C^}AEo6jeO)-BX zo4DwdZpG2kc3s4jUJ@08oN6x0iw;4nXioM+f_JDcpnJ#+GD$qu&SnGs^WSdwi*7~0>yXGVu{c9)`)iNE0v z_2D6QQ5d#fA3R_{M(8Gow2`YC{`AwW?Ck6lwS?CaktQ&Z&#!sZcpbEgtP{t%g0(Uh~4zECv^nC#{c={?a(M<*)Okcf0=HP7!kXhZK2c zTe5?X&^T_7nGI{P};-#n7*os(^Nh7-9<`BQcKYazhwVm z(1g)A7G03?ig~4|LHK`H9^2&HwFh^dGQYQLJUzF1s%6N(zxT@+2JOHsBKU;B?(=ns zz6+P?!dnpqh^mAv|d~wHEs07L`q4$J{ z&NY#gg+;T5dC!p}&dsJ77%VfZS#p9iH)h9x-6t7&42_$}RQcF4S!ar>ABM?`40RDr zsynVG6I=-0|2upuM3;u#IBo~=8iNgm+;XJAie)g(O5x}+$ey|4*ugGl zLB`_&<QXOoy#x$O% z+#fovh*0D)qA24pm=qgr*07d|bf+Q~t{*8lap7qmx3;KCow1&a`*xp!a(jx_e6E~G znRxrR)tpKL-L6VYE(*Gk1HvTAGO@W5f-eu?<>tHXR?y<0mswZMNWkTTgJd~!=}RKhz->cuJffyc7=Bx6T?>d2J?*7A zW(+F=8?ie={+JN2HtaUsD6hDiExa<5?)(_K6_@gltm__Va@w21^fZHqXNj`onCGK4 zWJqpUw1%nHT7b)~hfzT+b#J=3;&`oQp7BqH?WSooUy{Kw#$7{q>`QLXGLwNyj}J5W zx;_}LGDl3P>Ovdi2P=#N^kdL12$>N_tJv;FLtDQ0L-=AIpzo{S!_#@jAR>d{G=Ng36E% z#FZ*_t|cGgSNV+8cE3b|SH|{GQ(7`r3ZI93c_||bV?=EVpQ`U&*)%5^+F;nki7ltE zGqX7#nPzPFLnS@Ra(tv!9WluIlhNJ+Al5~<8uAG)Cwb2UVjO7_MjU=UR|}?Iy?&BRk4X%cyitnHn0qTeWjlI zqxGpy0||X4BpeYQm6W@#E1QZ&5bVSLPZhI}ZhF$_XqpDj{IKRPZ|`$fHfzm*s%RpM zZO$wg#wriVRgy}0E=pwjJ2Nj!%fcUm+J)&C8On1zMC>Gly0nprJw-;!4*s zqCjfB=Bi_3Jdd_o5Qx)19&-w|i;LkiJEuM=3x>@pv}vXi%NKlshap^!P08!ZIY{m3x3>Y=u#c!Bs zqFTM!hxQ4w=>zSFQy?-t9j~eY>WXa{73uv$lzD8Ibw5q(goxqoiNof+L))^?fIg~y zt=Amb_QsJLxG9+=Z~5hAt{|v%kmf}aSt*0JWcRuw;sGAHnmu-X5$?{=-o)dA!ZH{N zV8G7yNxKz^Cysx9oiX1kBFc(F&?~Mxyi3}*J~!yJO^U0@Cnj88(HKrNcGV~CX997L zDdO*znl?;g?!BcVPW1;s;fiOHS)|n_WtE=xs4RmmB9Gs5#I&@aFlV$L%!1g%NloTj z6Vh6yk&egA zJg}ZeeH{>-pme!>;XM(}f?zJj0ZWj=YLv!G$LeF5hjgDe-krbn<>D(7a|Av<*i zUJ+-%ZvV+AR|vBYZKLM&>t_;UpO%C%#M@r=qJn`fjRtLaG_|DY^&XaV-c>I;6k_y| zJfW%Zy~mg3f`eEuE=h?Y>FmmsigLGYodtmNA?}zbx>_g-ry+p^Rq1NzJ!j$m)idW9 zL`+`386Lc=C?YAT0HscleT$;B>hkVEk25?5GY(AX>{4D!mqN%F(mzASkW!K#_G;;q zp^4Azy(YJ$k7o?EwI@>J9$O0Xv{J!S=+!1+lC3}vG(kFzY^EL?@$~sCVLg-DtWQ6; zE6+_O?yEi5w#sr9ZXJhW9}}524LwGg=I@nO@Eb|Cu{+qzlgE$yKgoo{@E~gQH#3PH z^l#$G<~qHaY!Rx*Mk`pau4{g3Ks&nA{z_Sb@JR`M?cMgB<@0ywJRhUqU_;EPB9g%D%4nR$P4MBPoHf z<$_d~}Ug9#n^JtY5DQH=!!Z8u3qWYK44j>{{ZI`vN61)+> zs+*&bBWX`$^{SVC2~zI?x1n_SvZ;$7gk&uT8KTS3MsH%W7=5lT>i{X{ZX=N- z=|y0h9wt>Lo+N$vVPgjOh6x!!Hb^)8N_r|x(5 zE)aaX30G3jAk+UWlGM&LrC8R|%b|KjJ_@QJgL12sMb6{CRVw$r{!yY0J7JbrX4Apa zO==khJjX_8;NP~-KMKf`c>qcrZHx_S}!*>d1a?3j?xEs*s~_;F!f{lS$u$vv2^jaHXUr$` z9QxS)e0u{TyDp!70-U=SVov4e+&@7F0m^Y0lBPx3mcE)IBSCQh8+bZ7D;1u2*LLdO z{fM-12%VPow0n2S;s@ndux2IdiWO62q#lPNAXx6fH+8bvI@9#@_xho?#oga&7QvUAUh?7&->~bh|m3X{dOIF@D|{BN;8foT5lRwB2x>w zHQEQnTU_fn8?KRw!y2eby&EYeJ|NpSUH1wbi`@P{(ir9(KxRIJ{f_Fh#m5N$lT~jM zxp{LD!c#p&JVzrNY&>C} zJjNgpr4iqETRi2_|s0J{~rOib?Td3Ut6zaL8BT8^a6qxumR&`JrWS*w3YKx zN%aTpE7J25&KEVhGu)v021`#Df<0itlG>)5&2O!SmwEprqZ_3IcI4y@QC---iQ4;# zgn3MYE3S9_;fEiV*v~#Rt-ClXh-nmKBNP<0v_m=vrXK`yr(3cY#$5~m6reWhm!g}tvb2q zcIq6B7TV&$2+wPDx5Mxs;tQ(DsjNg45gK~;TAAix$h_k6#XXBg1D(^;3;lK&?Y$iMAjed2yT5g7Xe%Sh~Rr7s9{q16KN zps3v^JWg;Yt~4y-xN+dG+#XiG*u+)!)RS_A3y_LiUB}$pceV3m+Wr*eCssADWnmJ|LI5)-k~%nwvWB{ZFC zyIiT+XPqk|ENY7@Z$41_wI0JoUhR&*0%q!{?#XJGJN;9YtCgL>L6=_ibq(sQT;%pkA=?odN)

M}={l*^b)22) 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

*wu;G34Z~U?vG4m+^0f6JKLb}YNl3+d3W%vGC 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

gXgP;KKhHP;_{HSl16R?8loS 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%0A`hok_ zW@lNjG;QRZxWJgrCzNYPn7hgzRZh7cR%NvSF23Mr?`djw(XfeNwWL-y9DZd7lEmpn+wVw4O<+)~iEw~44T zw_iMrZ=p(n1#tFJ3S^C1)!?h{R0yi7s$w#&m~KizV5qsGB50Ok9PJcF1a=r<6b1R$ z>1m$Q*fy?f)?8dMJIB^YN~>Y#Ty!IXim=_)vi~FBkowjpFR<+v*|@s%vz`DA8j5+O zN%VO&iWH{{_~QwPPHU*u0#!YFG%Ew&Tqc?LrTQNyTZBFsW-C7?;Ia2(xb@%ezAl`@ z-dyZb__1Nn-6_(bW{a&twIu@I6k|QsRy_{C7z2x%#M%*;dmnJOh)_Jv}ktzsu>!9R097q<6_>fEx27 z-$7#%O!}!C^;5B!+DZ8Y+1*>-3g}8O8Oy_2Pv^Optf< z^BA|R!YS-1H=3H!x!tk9+h0H`8Im{@9szKrP<;vG*k0?}J6de&bLby~F|xNR;tn|YGk^8H^EcUkofT13)-2gBScPcFp9e=&|H z*hcpJ+H}T}9$Vg#btbOxndQ4Ak-4n8JNvCHA_HR}&)7v=Szh@3T1{fy|b$ zdWtAA#-x`_TdzEyQ8v#%bZH?)*20^dkZ#*Qynr^cbDYT#rR9)HjN`0#Cp=j|d$pHf zBmypV1E(8f^6c>ngXV41zfj*FsT!BKgSV=y|7aTOhS_D9y;aLqCqx=WN7Rj5mc_RA z%lPaaXsdi4&5V0(m?CzQ2se7-IX~9}LD7NpdHzC%uw&Jn;sKA+R0t;Z>mL@Rh zi{2Il=Oq;>@bjkAnOp0ZHDzJVMt66I#*T3|$ZPZFO~qoZgNTH*skVGAZFL}RAHp#fytcB>QIE4P+fhcM zAcu8h{cz=pz?aPJBb}yQa2vXPW5bIdI{bDq$Vn(P&c@zTRKN6kng&(MNK$smvZBh@ zLb<0_3BRQbzHk1Boe2%Hl+RB_9C?0qoUkSA_p2n+OP(wQOCD3Rit_vYj;w00UmPYa zA&q=A%s^L9FK42Tj?PXaGcpU`yL%M-?LPaVDw-O>H!pbFcrn);)K7r?w10;9 zTNFP&`2{c_XgYd06q}Op(Unzs7Y~drj4r5sd4rk zcr`mQhTN+06X>JRhOLfzspf@B1wf~J`TJ4y6DaOc@(z%{_hx+i|8W82CDg2H6<-a) z{>qZ|NMk&~?w`kOfQIW0=n)c$@ar2trJ=Q<4m(xgkG%Hp-#+2KYx&U%UPVBW`A6yV zlS_?fzFLrU;f8^i1G(}Y49s$$idkzq{{5mi^l_{sVVe;;wCiqB7lO4_Bkn5_ z4z`h;)8I?K%isO?P*s3II9jOx!764YE4OZjmNjy*&zVkUdqf|V&eD%1l;_|%GjTx> zkKjAo-@WgFmGf&>B=UIlipu4b6!!Lzb?5rCdoDV#oPmn&sT;V zGG9oQkV`uI?#1;N<$YG?C!2jE%#NSD9wT;dfB#Dp7yzSo)MIh9X$XD($scYspO?DA z@6=`YQm-Cs=}hFtSXc;u%9#(k06;N;XJz`HkKPWGDofe~d9|c&&SwOlroeIM*I+X1 zkl-!cX%g%nomrATy!9vVO&ve@fHW0XG}3T+))n%_JBLSSyyyi+`3)UB_{!idQ!m%K z(H;ExF>IH&_30ynl3BJ!Tc;*%X>>oAPAM6K{muSz53G)Hr#V`7nMG*_^yuy9m&dux zC2D$L!gNKqPs=&4x?cddHb8)}#Lt!9HyGCKk10U#V;UN1wsVLDiDo+q`irZpOScJH zIg4h*m@!7#lRo+6Dz3^%-n3X}?RxDH&hm=f#THrX#rw@(Uq$?->@dI);Mjvb0(A0~%7Wbkb87Ru9)1>Y16!uGy1&ij$qLqj|xz`lj>y}BzI%ikXT8HN4TK8`f+ zoc@6Z^+~4veVz! z(prsVGggqnJ~%Nsl9@jGB-!Bd2WeA8fJg`*Fv`(`_cm(L?LV1oQMHP*C|yMyOL!KB z@O&bP1x8MF&fx=Ri#dXWhd&*a(r3Bfsc~0$ul=@uck!TT4YU%ajJx~QnGb~7^io`+ zpVp9}RdSpC#-F=X6Se(XYFb)_5Fas=fYUOB?ADRa9Mh$2&%XrF(uI?5#0yf2Y)E3X zT~WNPe^x-J!IPl0awiDcM43bQFNaa4_-=0 z2rFEf2LkKV^dn_*%hKYqD=mIK+*4YrN3(SD{5f8Q4lIpsr!QG%Z!Z%LOjcqO!Rz6k9N`bmDcEB;m{X*P|bRX%ZwtXn2}{`RxewzX{cQRW;> zyuk5HPO@f;niNsw`2hX(f*T=5aYd(*P>pG16=W?9`6Y&vG7&&AR+EcqgwQ#fA{+BW^%nW5)B_{y}d-zD}Mi{ z&HH`CPf%s5QZxGEM{;}Rm}_t2zf3u?ZuL3RGr9dr7DpkJJO}hw<40bvcD9)_fBsmI zPiwULFFtBu0Hc9@*8bPe+TKlid16EoS#pUPIa%(HRTCS2yrmFNqhaLBoA13}xTw$Cg3cXm&}qCv)& zb|YsV9{8ZFN2XUcS&Z@zr>v}WFgI6^^UN*|CI(!YlAqjP+s2&TGclRYc%gSQ`u#qj zMKxuZ%8{DqK(+t!#%RB1RfWgstIW0rwB&>)9J+&k~syyVW#mmtI)^&NLV_ zI56f3`@^@OpkQSqyggshvZCRgFlm$NC1)oAnvN+;OG}fZno78HeA33aG-i+SA{Vwb4k)(|X46QE;_-eT+y zsJ!IlKnxY#IHG<&C1kmyQ|c>5blN;5V-)hj3sLn44j+EA<``bGk^RH6kBxsa+UMGJ zby0=uhc6sl#+9#6-wLfu2N=;8fT<^^r0aP9V=|W%&WhC`-oczeCHwIfL8>-BEYF@$ z22=#pNNQ;X2Af6-YOgT{>LM;9hiwf1?%CzoM;ziXif`FHKD%3=CRH$afsR*O*`MDp z{xS=>hUS@j$sD&<*%L17lDhcvrbj)kL2F_~U7|xJ6Vh5aepOzszgT@kPq^I@Bgusp zVgNC&yT^8yX4W+sT+=mJcDJxF< z@oBz3KI$~RJG_10|Ck0ck1*rdGXpZ+$^#ZhwY_$1x8dIRlTYaWeRd@&JDxt0xpGYJ zyMUU4LFRvZ{fIX;zE!e#V80~M_apDNm8*)IjQ+X2EeTToE&0hhc6^=cQ zB#Y*m!qWyvkmKcz_y~p*a5sKFM$|6?Y^0kV!r?SRMMb}ZocaRVN;yyp4P(rozb_yl zI)E2>dwcI6#0@VYOE6%vjB7x1*E<`F4-eGRzY*VYV;A5eTsA7ezVmHQdaQ`gCYus2 z3fePgRti5b{?icUp_i9eI19z$X<{_6(z6q0Q;#ox=IYh%g@w!%SbaHkh1LR5h@+9< z9>K#Un?D1pAI^<>@7jgtYR!Zdrlv>OTc^2{H>A-K6dckW``BrWJ*K~)`nSiActgkL z?m>NUo9{`-^|mN33iT20Y@}Jb``h1M0F*Q5;IPv2`wKdI(A(I5wpBkj`S~sc8k>#p zDk~!a#q7R)EO&o@<+VEJRDhzP;`|ylr{`Al+{#Rqh^o zBID$axM5+RBE}WNJj{B+dj*@T%De1GjUW_f?pU|E?UYf&B1euu&rQEcJj{-eBeGz+ zvj5z;Nr$RNgyl>BT7X(j!wExms3l~=Zw|RV`XaEz?1eCpKFZkAI}5CXpk7)pXE&1FMi!iTy_;5<}4eqB{o7EHl-(xU6DuC6V#T=t&iA-^C&AcbDa=DGbe+<5uV zYm9fVb$C}8KT~L%U6pJx^9d(~WStqN9PhSz$LswbOR#Nh=-V8vccN&)$7e>)@3 z7WQSu(@Qb;zMU#r?k5@6(JfEbS(G7!{7)BeMGYMtQ&skV+=SMUwz;00tQ&Q!lUBM0 ztaI!(yP_aA_<4%S4>@YEdUk`+JJC=F9?b=VIrM4j&D(vX7WDZLH6jV|Hp86^OD;7Y zJiOS%#Ys|KQXKE^*!}l%`LojvbBIRaIY-DZ zBf7k^deq*<&o+JGo_>8;YxAzP_4It8LMqIzVCddG7r`wg3C{XB<(zVSoU_>)qFZDl z6|(`|x_lb=OCOvY4J`^XZDo{b%g;qkW_~v#{_wyni=Vw}y+DSC_>A}&LZB;9lKC*? zRe;BmqtD73+Ffp5cjDhYW%6y~M5$Bq1X!Oz;iLf@P4%6yJP6Dma$q(ywRYTYV$y2_ zp7G(>V~{DD;YT1213|CC^nW&NnG5xQcq|`FSY{9Cl1Wp_Ty-hiDS&sXmB=lk{^H~= zKlB&L$H&@QLn3*tAhYNeUTfugM}@0Z7gx*4DOR`JMBkN%7xQ@faItP}|8ueWVbazj z)e6$%w$x`+!i-FB)2>~75|ps(xnBk+GKtiVfGtM?ia${_r?nz;7(~rDA|~k!J=|-@ zbfxS>|B|NH zBrCshX?>);rBAH$dYT-mCoA)Fclp&cww&a(DSOKK4!_>3XOtdmA;Vq%Q_MZw#2K-7 z4&%jD<KMX&1fg)_gVbK7rKJ;G zUhaTn8_hqyx;K8$#8<67U!J-!xpKj{|I5xBR?Pk}2-WKa@oYhubje8Xy?eWH*!}yF z;Tz@UFr|3j-v`v+|FGa9PgrZewQ5jQQ_J_Mef~Zle*WwEGYdL+4hknS??21`*~ok{ z10v18A0>*KtA+=P>m(pZxwzJ^#FLMs9%?tzl!l;sOVu$C#sug7=Wxnz?J%#P##| ztWTI7W@ZMenLt(OwR5MbAN{1;E2f%STb~7=3&v&FP2J-6N9J}yB=;trFp|nxIR@XN z*@UUKojTPSIcvoV+i~B%6Z4!KXm!+bF2AzUdGM{$;zxuKZIZ_q!+i`yV>dty{NXRhzrR<6sXxb&*k@4upY*b>9hS}F@c%gAHzKDGbrZma&Amwj@hu*%^$ zT$x^(8#xpU|7$fkDZNa5_WKW)OlA?UZpS(z3ol$*Ny`uwO?=-`CUIv^cziLrrqb8R zqwQ}$;dyI2i`?1e`&XCeS6tIqm{A~B`5jfW^%KqWbxNa0F94U&Cv|vsWZ-WQ$xIMBUIG>kHSPYf;<>PA?jq^=Jy|6H7w4{0nhUVq_(-_ zuF6=!KYc6FK9QW8GbnuXxBsWMGmnb$%+feBHc1aA=A;8k63}Q8LWz*5!N{tTFa||s zFA-b_Zb0DzBAYBp2kAjYMFkC;3(AgyP^?0X!BP>3BC-@{8%1PQ0ZEjA^LtI7?y_h8 zn$weWa*hY(tFPYgd++<)=XvhE117O>dASrt_0%0y{YAU^nk`mE)c8Jt6*UbR%AmOv z#?tr5e^#nrr9o-MwIRLG^OguKNw0%-|3^ zo)Am1F3c&e9wS}70oW+69v5SJwxzdEQ}s9gYHhv!$O$52zY{4EJ1XddId)E^1%6Xi z|5eNcjYhxoO-)iP5o?so-b;Ax!lUh~i>~%)8vo)Bo67~(s%`S5$Pw+G#~8=9IbC&2 zMb01@iu}mE)X$od5o}G@G43>yv@?_J5=RloW&9hfSKrq0B*R&TmtIOtFs9+hj!Tt) z6)ZL4-!{rBMq%JYHcM9%;;B>Rj$r3JcURzv3(l1rP)7lMtV+_eQ1-30rgk4LMJyr9FUFmclH2O8**C z;L?MWRG#eo1seb_E};~i{f{_eq%87DAi2rGzU56L0waY9@mTa2V_77#W;wvw2Cj~a z`WA|pM#@FZp1`W|nbdRKxN(iR;cE}i{2aDttGDgnlHDN_gpDjh>2h5W&PneIk-f=T z4HSa%2)VoGGr>HVg{|7q+g9qxvuGsbv$j9qI1EhMNbb_zkiHkLK|C8sjKna=iY#Ku zPstLqK4RA#^dC|2o$JLQM^EgdXL_6RP?_TV5N#Unv zxXEpC(ByQClAIeul3_k-ULP@U-n`efKba3VL1(Njc?18hvTXW)`NvVMS*z7Jt9TTe zxTq!J{p=Du^6E@t_+OrMRj{=%-nQJL>mqlx7}jhNwogtk!FTYAY-E=Sj(>9auO4B{ z5)Q6AnITMfLzGzcAV;>e!ZD|V%re2*-ATVUI9I}z4{J^!cpV0TyyPGSH@kVw} zdH!oOw|)wayUd~qU8AOlSK5a&ch+m4gR$8zF39Nt;*^K7iR-Vs&5LA!y@2!2k+Zrz z$^jx5&fvMS;|DAVzzdP-VB9Zn&%6$hz7$0`P zx0m@i88ob3uC+saMR9=PPQ4pi|t%_3TsjC*iq#yq%b;tGYW+GN^-o%MO zb7*}%BKg(3>z^$+L-A>#?Szpk!xZXI~+gxyq+y<-kd$^S@FiOsYu1r-(x)$tSiI+vSnV6PT9E8Xw) zBUwE*Fp2KN)!ijbxC0rTvKkDwrP85Uwz`A{XP<673Q^)joz9E!h1bok zb>vCUhQJ&MY`pqfoYvbcRo71)9%o)yMe^>Cc5Bc4p~Lqrx{GAMHFZ~x^fw?+wI{ir z|AmdGoZ)aFjSXe$oTg_}Z}CeK6Wx#W{VF&fI?kpK0&7pcG9k(0BPTUAO(UBx7C7l_ zEVjVwKrhoKm{&oo(VNvwT!yq*bY7jK#Hl>Q=!Ch+sUHAT9a*Ncu4#IgyQUf2Ob1X2 z?+7Pvz4wPDKg+P7e)oxom3*4}Hlx*>k-}UnH61ZEG=%#y8E7(P2 z_X1J5l!w7Wf})F;r2#cCw{`E7d7A=dC^^i>>D^}Z(o(G4Kf~!=F2fw0O!vLJ_U>(> z!!)^AAr?(RHjHc0au=mzFs;6FxF5H-^lUFVR|MV)R@@&x3SlP&0W@qC9f^y5;h0;` zc>I~I2aP>FeOEkU_gBS+{v&o9M*B8LkOGgcJ=+&GY1NjpFAcLn#yNnZgw`sYinyi~Hnudrx?lheV5tvHBs5i2jQPrSVL zO2F3DE_s?BS|9H8*a%7|#>-pAkWx!@Mlc7L*WM*-5@dbN*0Z3Q10Qq|ex(~XZj=wB zHa6>;k&+9|`olL=o>AmCkyb^|H^AhHUl9`Xp0{UTJgLot_-XOS=hDa{9T&efi+0(98}GhH9yeIvMHl1TeuhDQqG}N7*$ijd<|wPHncr?5u$&uyfN2J3P*0FFk*9E@cl{@6>-zf$E?&XhJN~djuI@x8}VWl=_c|M zE183pU?bI76UIwak+a78O$HV*`$U`oGltwhvO(~zWt#N6v1VCYV$B>M-SZV}c}e>^ zBA|P$uC{gt>ZHh|TUrIcL|Nmr3E0v+#bH~$=srY`ZOgCf*+6IeHQ!>ElY3`WIZc5x z2?0vq`kJJxP7u81zM^#$-wd?z#60-~QGej^R`)mBkZEGxQMt$Y`=z~7Cv@Lb$TJBj z-hK0wBhI8yf+X(?VtWq8t*WGh?uiS-0)I&=&wX>0t93{=okEu>{cx!ZT-pRB%uykM zI66#6%j$eSaA7*?;HGjj7oBXNNBbPa1pc{s>IouEaIDG=sM%?1_E;5>b)2I(JT8s1 zqDNRx94m-knVdZo+c}a!eahT3MuaGIGed3=26W|}+udqITMU}H(G@8_ulIA>a!

  • 10$K^w$-e!mgNl;+UG0STTVO0bZB@8`$9>yi0t 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 literal 0 HcmV?d00001 diff --git a/docs/src/graphics/saunders_simon_yip.png b/docs/src/graphics/saunders_simon_yip.png new file mode 100644 index 0000000000000000000000000000000000000000..c3acfd181e86129d042d1c7871ceb2b92ee16170 GIT binary patch literal 149026 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�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 F{|Ce<5Ly5L literal 0 HcmV?d00001 diff --git a/docs/src/processes.md b/docs/src/processes.md new file mode 100644 index 000000000..208824ae8 --- /dev/null +++ b/docs/src/processes.md @@ -0,0 +1,278 @@ +# [Krylov processes](@id krylov-processes) + +Krylov processes are the foundation of Krylov methods, they generate bases of Krylov subspaces. + +### Notation + +Define $V_k := \begin{bmatrix} v_1 & \ldots & v_k \end{bmatrix} \enspace$ and $\enspace U_k := \begin{bmatrix} u_1 & \ldots & u_k \end{bmatrix}$. + +For a matrix $C \in \mathbb{C}^{n \times n}$ and a vector $t \in \mathbb{C}^{n}$, the $k$-th Krylov subspace generated by $C$ and $t$ is +```math +\mathcal{K}_k(C, t) := +\left\{\sum_{i=0}^{k-1} \omega_i C^i t \, \middle \vert \, \omega_i \in \mathbb{C},~0 \le i \le k-1 \right\}. +``` + +For matrices $C \in \mathbb{C}^{n \times n} \enspace$ and $\enspace T \in \mathbb{C}^{n \times p}$, the $k$-th block Krylov subspace generated by $C$ and $T$ is +```math +\mathcal{K}_k^{\square}(C, T) := +\left\{\sum_{i=0}^{k-1} C^i T \, \Omega_i \, \middle \vert \, \Omega_i \in \mathbb{C}^{p \times p},~0 \le i \le k-1 \right\}. +``` + +## Hermitian Lanczos + +![hermitian_lanczos](./graphics/hermitian_lanczos.png) + +After $k$ iterations of the Hermitian Lanczos process, the situation may be summarized as +```math +\begin{align*} + A V_k &= V_k T_k + \beta_{k+1,k} v_{k+1} e_k^T = V_{k+1} T_{k+1,k}, \\ + V_k^H V_k &= I_k, +\end{align*} +``` +where $V_k$ is an orthonormal basis of the Krylov subspace $\mathcal{K}_k (A,b)$, +```math +T_k = +\begin{bmatrix} + \alpha_1 & \beta_2 & & \\ + \beta_2 & \alpha_2 & \ddots & \\ + & \ddots & \ddots & \beta_k \\ + & & \beta_k & \alpha_k +\end{bmatrix} +, \qquad +T_{k+1,k} = +\begin{bmatrix} + T_{k} \\ + \beta_{k+1} e_{k}^T +\end{bmatrix}. +``` +Note that $T_{k+1,k}$ is a real tridiagonal matrix even if $A$ is a complex matrix. + +The function [`hermitian_lanczos`](@ref hermitian_lanczos) returns $V_{k+1}$ and $T_{k+1,k}$. + +Related methods: [`SYMMLQ`](@ref symmlq), [`CG`](@ref cg), [`CR`](@ref cr), [`MINRES`](@ref minres), [`MINRES-QLP`](@ref minres_qlp), [`CGLS`](@ref cgls), [`CRLS`](@ref crls), [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`CG-LANCZOS`](@ref cg_lanczos) and [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift). + +```@docs +hermitian_lanczos +``` + +## Non-Hermitian Lanczos + +![nonhermitian_lanczos](./graphics/nonhermitian_lanczos.png) + +After $k$ iterations of the non-Hermitian Lanczos process (also named the Lanczos biorthogonalization process), the situation may be summarized as +```math +\begin{align*} + A V_k &= V_k T_k + \beta_{k+1} v_{k+1} e_k^T = V_{k+1} T_{k+1,k}, \\ + B U_k &= U_k T_k^H + \bar{\gamma}_{k+1} u_{k+1} e_k^T = U_{k+1} T_{k,k+1}^H, \\ + V_k^H U_k &= U_k^H V_k = I_k, +\end{align*} +``` +where $V_k$ and $U_k$ are bases of the Krylov subspaces $\mathcal{K}_k (A,b)$ and $\mathcal{K}_k (A^H,c)$, respectively, +```math +T_k = +\begin{bmatrix} + \alpha_1 & \gamma_2 & & \\ + \beta_2 & \alpha_2 & \ddots & \\ + & \ddots & \ddots & \gamma_k \\ + & & \beta_k & \alpha_k +\end{bmatrix} +, \qquad +T_{k+1,k} = +\begin{bmatrix} + T_{k} \\ + \beta_{k+1} e_{k}^T +\end{bmatrix} +, \qquad +T_{k,k+1} = +\begin{bmatrix} + T_{k} & \gamma_{k+1} e_k +\end{bmatrix}. +``` + +The function [`nonhermitian_lanczos`](@ref nonhermitian_lanczos) returns $V_{k+1}$, $T_{k+1,k}$, $U_{k+1}$ and $T_{k,k+1}^H$. + +Related methods: [`BiLQ`](@ref bilq), [`QMR`](@ref qmr), [`BiLQR`](@ref bilqr), [`CGS`](@ref cgs) and [`BICGSTAB`](@ref bicgstab). + +!!! note + The scaling factors used in our implementation are $\beta_k = |u_k^H v_k|^{\tfrac{1}{2}}$ and $\gamma_k = (u_k^H v_k) / \beta_k$. + +```@docs +nonhermitian_lanczos +``` + +## Arnoldi + +![arnoldi](./graphics/arnoldi.png) + +After $k$ iterations of the Arnoldi process, the situation may be summarized as +```math +\begin{align*} + A V_k &= V_k H_k + h_{k+1,k} v_{k+1} e_k^T = V_{k+1} H_{k+1,k}, \\ + V_k^H V_k &= I_k, +\end{align*} +``` +where $V_k$ is an orthonormal basis of the Krylov subspace $\mathcal{K}_k (A,b)$, +```math +H_k = +\begin{bmatrix} + h_{1,1}~ & h_{1,2}~ & \ldots & h_{1,k} \\ + h_{2,1}~ & \ddots~ & \ddots & \vdots \\ + & \ddots~ & \ddots & h_{k-1,k} \\ + & & h_{k,k-1} & h_{k,k} +\end{bmatrix} +, \qquad +H_{k+1,k} = +\begin{bmatrix} + H_{k} \\ + h_{k+1,k} e_{k}^T +\end{bmatrix}. +``` + +The function [`arnoldi`](@ref arnoldi) returns $V_{k+1}$ and $H_{k+1,k}$. + +Related methods: [`DIOM`](@ref diom), [`FOM`](@ref fom), [`DQGMRES`](@ref dqgmres) and [`GMRES`](@ref gmres). + + +```@docs +arnoldi +``` + +## Golub-Kahan + +![golub_kahan](./graphics/golub_kahan.png) + +After $k$ iterations of the Golub-Kahan bidiagonalization process, the situation may be summarized as +```math +\begin{align*} + A V_k &= U_{k+1} B_k, \\ + A^H U_{k+1} &= V_k B_k^H + \alpha_{k+1} v_{k+1} e_{k+1}^T = V_{k+1} L_{k+1}^H, \\ + V_k^H V_k &= U_k^H U_k = I_k, +\end{align*} +``` +where $V_k$ and $U_k$ are bases of the Krylov subspaces $\mathcal{K}_k (A^HA,A^Hb)$ and $\mathcal{K}_k (AA^H,b)$, respectively, +```math +L_k = +\begin{bmatrix} + \alpha_1 & & & \\ + \beta_2 & \alpha_2 & & \\ + & \ddots & \ddots & \\ + & & \beta_k & \alpha_k +\end{bmatrix} +, \qquad +B_k = +\begin{bmatrix} + \alpha_1 & & & \\ + \beta_2 & \alpha_2 & & \\ + & \ddots & \ddots & \\ + & & \beta_k & \alpha_k \\ + & & & \beta_{k+1} \\ +\end{bmatrix} += +\begin{bmatrix} + L_{k} \\ + \beta_{k+1} e_{k}^T +\end{bmatrix}. +``` +Note that $L_k$ is a real bidiagonal matrix even if $A$ is a complex matrix. + +The function [`golub_kahan`](@ref golub_kahan) returns $V_{k+1}$, $U_{k+1}$ and $L_{k+1}$. + +Related methods: [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig), [`CRAIGMR`](@ref craigmr), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). + +```@docs +golub_kahan +``` + +## Saunders-Simon-Yip + +![saunders_simon_yip](./graphics/saunders_simon_yip.png) + +After $k$ iterations of the Saunders-Simon-Yip process (also named the orthogonal tridiagonalization process), the situation may be summarized as +```math +\begin{align*} + A U_k &= V_k T_k + \beta_{k+1} v_{k+1} e_k^T = V_{k+1} T_{k+1,k}, \\ + B V_k &= U_k T_k^H + \gamma_{k+1} u_{k+1} e_k^T = U_{k+1} T_{k,k+1}^H, \\ + V_k^H V_k &= U_k^H U_k = I_k, +\end{align*} +``` +where $\begin{bmatrix} V_k & 0 \\ 0 & U_k \end{bmatrix}$ is an orthonormal basis of the block Krylov subspace $\mathcal{K}^{\square}_k \left(\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}, \begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}\right)$, +```math +T_k = +\begin{bmatrix} + \alpha_1 & \gamma_2 & & \\ + \beta_2 & \alpha_2 & \ddots & \\ + & \ddots & \ddots & \gamma_k \\ + & & \beta_k & \alpha_k +\end{bmatrix} +, \qquad +T_{k+1,k} = +\begin{bmatrix} + T_{k} \\ + \beta_{k+1} e_{k}^T +\end{bmatrix} +, \qquad +T_{k,k+1} = +\begin{bmatrix} + T_{k} & \gamma_{k+1} e_{k} +\end{bmatrix}. +``` + +The function [`saunders_simon_yip`](@ref saunders_simon_yip) returns $V_{k+1}$, $T_{k+1,k}$, $U_{k+1}$ and $T_{k,k+1}^H$. + +Related methods: [`USYMLQ`](@ref usymlq), [`USYMQR`](@ref usymqr), [`TriLQR`](@ref trilqr), [`TriCG`](@ref tricg) and [`TriMR`](@ref trimr). + +```@docs +saunders_simon_yip +``` + +## Montoison-Orban + +![montoison_orban](./graphics/montoison_orban.png) + +After $k$ iterations of the Montoison-Orban process (also named the orthogonal Hessenberg reduction process), the situation may be summarized as +```math +\begin{align*} + A U_k &= V_k H_k + h_{k+1,k} v_{k+1} e_k^T = V_{k+1} H_{k+1,k}, \\ + B V_k &= U_k F_k + f_{k+1,k} u_{k+1} e_k^T = U_{k+1} F_{k+1,k}, \\ + V_k^H V_k &= U_k^H U_k = I_k, +\end{align*} +``` +where $\begin{bmatrix} V_k & 0 \\ 0 & U_k \end{bmatrix}$ is an orthonormal basis of the block Krylov subspace $\mathcal{K}^{\square}_k \left(\begin{bmatrix} 0 & A \\ B & 0 \end{bmatrix}, \begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}\right)$, +```math +H_k = +\begin{bmatrix} + h_{1,1}~ & h_{1,2}~ & \ldots & h_{1,k} \\ + h_{2,1}~ & \ddots~ & \ddots & \vdots \\ + & \ddots~ & \ddots & h_{k-1,k} \\ + & & h_{k,k-1} & h_{k,k} +\end{bmatrix} +, \qquad +F_k = +\begin{bmatrix} + f_{1,1}~ & f_{1,2}~ & \ldots & f_{1,k} \\ + f_{2,1}~ & \ddots~ & \ddots & \vdots \\ + & \ddots~ & \ddots & f_{k-1,k} \\ + & & f_{k,k-1} & f_{k,k} +\end{bmatrix}, +``` +```math +H_{k+1,k} = +\begin{bmatrix} + H_{k} \\ + h_{k+1,k} e_{k}^T +\end{bmatrix} +, \qquad +F_{k+1,k} = +\begin{bmatrix} + F_{k} \\ + f_{k+1,k} e_{k}^T +\end{bmatrix}. +``` + +The function [`montoison_orban`](@ref montoison_orban) returns $V_{k+1}$, $H_{k+1,k}$, $U_{k+1}$ and $F_{k+1,k}$. + +Related methods: [`GPMR`](@ref gpmr). + +```@docs +montoison_orban +``` diff --git a/src/Krylov.jl b/src/Krylov.jl index e3903e124..aadde1575 100644 --- a/src/Krylov.jl +++ b/src/Krylov.jl @@ -5,6 +5,7 @@ using LinearAlgebra, SparseArrays, Printf include("krylov_utils.jl") include("krylov_stats.jl") include("krylov_solvers.jl") +include("krylov_processes.jl") include("cg.jl") include("cr.jl") diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl new file mode 100644 index 000000000..49a30f4b3 --- /dev/null +++ b/src/krylov_processes.jl @@ -0,0 +1,459 @@ +export hermitian_lanczos, nonhermitian_lanczos, arnoldi, golub_kahan, saunders_simon_yip, montoison_orban + +""" + V, T = hermitian_lanczos(A, b, k) + +#### Input arguments: + +* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `b`: a vector of length n. +* `k`: the number of iterations of the Hermitian Lanczos process. + +#### Output arguments: + +* `V`: a dense n × (k+1) matrix. +* `T`: a sparse (k+1) × k tridiagonal matrix. + +#### Reference + +* C. Lanczos, [*An Iteration Method for the Solution of the Eigenvalue Problem of Linear Differential and Integral Operators*](https://doi.org/10.6028/jres.045.026), Journal of Research of the National Bureau of Standards, 45(4), pp. 225--280, 1950. +""" +function hermitian_lanczos(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex + n, m = size(A) + R = real(FC) + S = ktypeof(b) + M = vector_to_matrix(S) + + colptr = zeros(Int, k+1) + rowval = zeros(Int, 3k-1) + nzval = zeros(R, 3k-1) + + colptr[1] = 1 + rowval[1] = 1 + rowval[2] = 2 + for i = 1:k + colptr[i+1] = 3i + if i ≥ 2 + pos = colptr[i] + rowval[pos] = i-1 + rowval[pos+1] = i + rowval[pos+2] = i+1 + end + end + + V = M(undef, n, k+1) + T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval) + + for i = 1:k + vᵢ = view(V,:,i) + vᵢ₊₁ = q = view(V,:,i+1) + if i == 1 + βᵢ = @knrm2(n, b) + vᵢ .= b ./ βᵢ + end + mul!(q, A, vᵢ) + αᵢ = @kdotr(n, vᵢ, q) + T[i,i] = αᵢ + @kaxpy!(n, -αᵢ, vᵢ, q) + if i ≥ 2 + vᵢ₋₁ = view(V,:,i-1) + βᵢ = T[i,i-1] + T[i-1,i] = βᵢ + @kaxpy!(n, -βᵢ, vᵢ₋₁, q) + end + βᵢ₊₁ = @knrm2(n, q) + T[i+1,i] = βᵢ₊₁ + vᵢ₊₁ .= q ./ βᵢ₊₁ + end + return V, T +end + +""" + V, T, U, Tᴴ = nonhermitian_lanczos(A, b, c, k) + +#### Input arguments: + +* `A`: a linear operator that models a square matrix of dimension n. +* `b`: a vector of length n. +* `c`: a vector of length n. +* `k`: the number of iterations of the non-Hermitian Lanczos process. + +#### Output arguments: + +* `V`: a dense n × (k+1) matrix. +* `T`: a sparse (k+1) × k tridiagonal matrix. +* `U`: a dense n × (k+1) matrix. +* `Tᴴ`: a sparse (k+1) × k tridiagonal matrix. + +#### Reference + +* C. Lanczos, [*An Iteration Method for the Solution of the Eigenvalue Problem of Linear Differential and Integral Operators*](https://doi.org/10.6028/jres.045.026), Journal of Research of the National Bureau of Standards, 45(4), pp. 225--280, 1950. +""" +function nonhermitian_lanczos(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex + n, m = size(A) + Aᴴ = A' + S = ktypeof(b) + M = vector_to_matrix(S) + + colptr = zeros(Int, k+1) + rowval = zeros(Int, 3k-1) + nzval_T = zeros(FC, 3k-1) + nzval_Tᴴ = zeros(FC, 3k-1) + + colptr[1] = 1 + rowval[1] = 1 + rowval[2] = 2 + for i = 1:k + colptr[i+1] = 3i + if i ≥ 2 + pos = colptr[i] + rowval[pos] = i-1 + rowval[pos+1] = i + rowval[pos+2] = i+1 + end + end + + V = M(undef, n, k+1) + U = M(undef, n, k+1) + T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_T) + Tᴴ = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_Tᴴ) + + for i = 1:k + vᵢ = view(V,:,i) + uᵢ = view(U,:,i) + vᵢ₊₁ = q = view(V,:,i+1) + uᵢ₊₁ = p = view(U,:,i+1) + if i == 1 + cᴴb = @kdot(n, c, b) + βᵢ = √(abs(cᴴb)) + γᵢ = cᴴb / βᵢ + vᵢ .= b ./ βᵢ + uᵢ .= c ./ conj(γᵢ) + end + mul!(q, A , vᵢ) + mul!(p, Aᴴ, uᵢ) + if i ≥ 2 + vᵢ₋₁ = view(V,:,i-1) + uᵢ₋₁ = view(U,:,i-1) + βᵢ = T[i,i-1] + γᵢ = T[i-1,i] + @kaxpy!(n, - γᵢ , vᵢ₋₁, q) + @kaxpy!(n, -conj(βᵢ), uᵢ₋₁, p) + end + αᵢ = @kdot(n, uᵢ, q) + T[i,i] = αᵢ + Tᴴ[i,i] = conj(αᵢ) + @kaxpy!(m, - αᵢ , vᵢ, q) + @kaxpy!(n, -conj(αᵢ), uᵢ, p) + pᴴq = @kdot(n, p, q) + βᵢ₊₁ = √(abs(pᴴq)) + γᵢ₊₁ = pᴴq / βᵢ₊₁ + vᵢ₊₁ .= q ./ βᵢ₊₁ + uᵢ₊₁ .= p ./ conj(γᵢ₊₁) + T[i+1,i] = βᵢ₊₁ + Tᴴ[i+1,i] = conj(γᵢ₊₁) + if i ≤ k-1 + T[i,i+1] = γᵢ₊₁ + Tᴴ[i,i+1] = conj(βᵢ₊₁) + end + end + return V, T, U, Tᴴ +end + +""" + V, H = arnoldi(A, b, k) + +#### Input arguments: + +* `A`: a linear operator that models a square matrix of dimension n. +* `b`: a vector of length n. +* `k`: the number of iterations of the Arnoldi process. + +#### Output arguments: + +* `V`: a dense n × (k+1) matrix. +* `H`: a sparse (k+1) × k upper Hessenberg matrix. + +#### Reference + +* W. E. Arnoldi, [*The principle of minimized iterations in the solution of the matrix eigenvalue problem*](https://doi.org/10.1090/qam/42792), Quarterly of Applied Mathematics, 9, pp. 17--29, 1951. +""" +function arnoldi(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex + n, m = size(A) + S = ktypeof(b) + M = vector_to_matrix(S) + + nnz = div(k*(k+1), 2) + k + colptr = zeros(Int, k+1) + rowval = zeros(Int, nnz) + nzval = zeros(FC, nnz) + + colptr[1] = 1 + for i = 1:k + pos = colptr[i] + colptr[i+1] = pos+i+1 + for j = 1:i+1 + rowval[pos+j-1] = j + end + end + + V = M(undef, n, k+1) + H = SparseMatrixCSC(k+1, k, colptr, rowval, nzval) + + for i = 1:k + vᵢ = view(V,:,i) + vᵢ₊₁ = q = view(V,:,i+1) + if i == 1 + β = @knrm2(n, b) + vᵢ .= b ./ β + end + mul!(q, A, vᵢ) + for j = 1:i + vⱼ = view(V,:,j) + H[j,i] = @kdot(n, vⱼ, q) + @kaxpy!(n, -H[j,i], vⱼ, q) + end + H[i+1,i] = @knrm2(n, q) + vᵢ₊₁ .= q ./ H[i+1,i] + end + return V, H +end + +""" + V, U, L = golub_kahan(A, b, k) + +#### Input arguments: + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `k`: the number of iterations of the Golub-Kahan process. + +#### Output arguments: + +* `V`: a dense m × (k+1) matrix. +* `U`: a dense n × (k+1) matrix. +* `L`: a sparse (k+1) × (k+1) lower bidiagonal matrix. + +#### Reference + +* G. H. Golub and W. Kahan, [*Calculating the Singular Values and Pseudo-Inverse of a Matrix*](https://doi.org/10.1137/0702016), SIAM Journal on Numerical Analysis, 2(2), pp. 225--224, 1965. +""" +function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex + n, m = size(A) + R = real(FC) + Aᴴ = A' + S = ktypeof(b) + M = vector_to_matrix(S) + + colptr = zeros(Int, k+2) + rowval = zeros(Int, 2k+1) + nzval = zeros(R, 2k+1) + + colptr[1] = 1 + for i = 1:k + pos = colptr[i] + colptr[i+1] = pos+2 + rowval[pos] = i + rowval[pos+1] = i+1 + end + rowval[2k+1] = k+1 + colptr[k+2] = 2k+2 + + V = M(undef, m, k+1) + U = M(undef, n, k+1) + L = SparseMatrixCSC(k+1, k+1, colptr, rowval, nzval) + + for i = 1:k + uᵢ = view(U,:,i) + vᵢ = view(V,:,i) + uᵢ₊₁ = q = view(U,:,i+1) + vᵢ₊₁ = p = view(V,:,i+1) + if i == 1 + wᵢ = vᵢ + βᵢ = @knrm2(n, b) + uᵢ .= b ./ βᵢ + mul!(wᵢ, Aᴴ, uᵢ) + αᵢ = @knrm2(m, wᵢ) + L[1,1] = αᵢ + vᵢ .= wᵢ ./ αᵢ + end + mul!(q, A, vᵢ) + αᵢ = L[i,i] + @kaxpy!(n, -αᵢ, uᵢ, q) + βᵢ₊₁ = @knrm2(n, q) + uᵢ₊₁ .= q ./ βᵢ₊₁ + mul!(p, Aᴴ, uᵢ₊₁) + @kaxpy!(m, -βᵢ₊₁, vᵢ, p) + αᵢ₊₁ = @knrm2(m, p) + vᵢ₊₁ .= p ./ αᵢ₊₁ + L[i+1,i] = βᵢ₊₁ + L[i+1,i+1] = αᵢ₊₁ + end + return V, U, L +end + +""" + V, T, U, Tᴴ = saunders_simon_yip(A, b, c, k) + +#### Input arguments: + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `c`: a vector of length m. +* `k`: the number of iterations of the Saunders-Simon-Yip process. + +#### Output arguments: + +* `V`: a dense n × (k+1) matrix. +* `T`: a sparse (k+1) × k tridiagonal matrix. +* `U`: a dense m × (k+1) matrix. +* `Tᴴ`: a sparse (k+1) × k tridiagonal matrix. + +#### Reference + +* M. A. Saunders, H. D. Simon, and E. L. Yip, [*Two Conjugate-Gradient-Type Methods for Unsymmetric Linear Equations*](https://doi.org/10.1137/0725052), SIAM Journal on Numerical Analysis, 25(4), pp. 927--940, 1988. +""" +function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex + n, m = size(A) + Aᴴ = A' + S = ktypeof(b) + M = vector_to_matrix(S) + + colptr = zeros(Int, k+1) + rowval = zeros(Int, 3k-1) + nzval_T = zeros(FC, 3k-1) + nzval_Tᴴ = zeros(FC, 3k-1) + + colptr[1] = 1 + rowval[1] = 1 + rowval[2] = 2 + for i = 1:k + colptr[i+1] = 3i + if i ≥ 2 + pos = colptr[i] + rowval[pos] = i-1 + rowval[pos+1] = i + rowval[pos+2] = i+1 + end + end + + V = M(undef, n, k+1) + U = M(undef, m, k+1) + T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_T) + Tᴴ = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_Tᴴ) + + for i = 1:k + vᵢ = view(V,:,i) + uᵢ = view(U,:,i) + vᵢ₊₁ = q = view(V,:,i+1) + uᵢ₊₁ = p = view(U,:,i+1) + if i == 1 + β = @knrm2(n, b) + γ = @knrm2(m, c) + vᵢ .= b ./ β + uᵢ .= c ./ γ + end + mul!(q, A , uᵢ) + mul!(p, Aᴴ, vᵢ) + if i ≥ 2 + vᵢ₋₁ = view(V,:,i-1) + uᵢ₋₁ = view(U,:,i-1) + βᵢ = T[i,i-1] + γᵢ = T[i-1,i] + @kaxpy!(n, -γᵢ, vᵢ₋₁, q) + @kaxpy!(m, -βᵢ, uᵢ₋₁, p) + end + αᵢ = @kdot(n, vᵢ, q) + T[i,i] = αᵢ + Tᴴ[i,i] = conj(αᵢ) + @kaxpy!(n, - αᵢ , vᵢ, q) + @kaxpy!(m, -conj(αᵢ), uᵢ, p) + βᵢ₊₁ = @knrm2(n, q) + γᵢ₊₁ = @knrm2(m, p) + vᵢ₊₁ .= q ./ βᵢ₊₁ + uᵢ₊₁ .= p ./ γᵢ₊₁ + T[i+1,i] = βᵢ₊₁ + Tᴴ[i+1,i] = conj(γᵢ₊₁) + if i ≤ k-1 + T[i,i+1] = γᵢ₊₁ + Tᴴ[i,i+1] = conj(βᵢ₊₁) + end + end + return V, T, U, Tᴴ +end + +""" + V, H, U, F = montoison_orban(A, B, b, c, k) + +#### Input arguments: + +* `A`: a linear operator that models a matrix of dimension n × m. +* `B`: a linear operator that models a matrix of dimension m × n. +* `b`: a vector of length n. +* `c`: a vector of length m. +* `k`: the number of iterations of the Montoison-Orban process. + +#### Output arguments: + +* `V`: a dense n × (k+1) matrix. +* `H`: a sparse (k+1) × k upper Hessenberg matrix. +* `U`: a dense m × (k+1) matrix. +* `F`: a sparse (k+1) × k upper Hessenberg matrix. + +#### Reference + +* A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://dx.doi.org/10.13140/RG.2.2.24069.68326), Cahier du GERAD G-2021-62, GERAD, Montréal, 2021. +""" +function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex + n, m = size(A) + S = ktypeof(b) + M = vector_to_matrix(S) + + nnz = div(k*(k+1), 2) + k + colptr = zeros(Int, k+1) + rowval = zeros(Int, nnz) + nzval_H = zeros(FC, nnz) + nzval_F = zeros(FC, nnz) + + colptr[1] = 1 + for i = 1:k + pos = colptr[i] + colptr[i+1] = pos+i+1 + for j = 1:i+1 + rowval[pos+j-1] = j + end + end + + V = M(undef, n, k+1) + U = M(undef, m, k+1) + H = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_H) + F = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_F) + + for i = 1:k + vᵢ = view(V,:,i) + uᵢ = view(U,:,i) + vᵢ₊₁ = q = view(V,:,i+1) + uᵢ₊₁ = p = view(U,:,i+1) + if i == 1 + β = @knrm2(n, b) + γ = @knrm2(m, c) + vᵢ .= b ./ β + uᵢ .= c ./ γ + end + mul!(q, A, uᵢ) + mul!(p, B, vᵢ) + for j = 1:i + vⱼ = view(V,:,j) + uⱼ = view(U,:,j) + H[j,i] = @kdot(n, vⱼ, q) + @kaxpy!(n, -H[j,i], vⱼ, q) + F[j,i] = @kdot(m, uⱼ, p) + @kaxpy!(m, -F[j,i], uⱼ, p) + end + H[i+1,i] = @knrm2(n, q) + vᵢ₊₁ .= q ./ H[i+1,i] + F[i+1,i] = @knrm2(m, p) + uᵢ₊₁ .= p ./ F[i+1,i] + end + return V, H, U, F +end diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index baad2bdcf..beec647b8 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -1,7 +1,6 @@ -using LinearAlgebra, SparseArrays, Test -using Krylov, AMDGPU +using AMDGPU -include("../test_utils.jl") +include("gpu.jl") @testset "AMD -- AMDGPU.jl" begin @@ -19,6 +18,7 @@ include("../test_utils.jl") for FC in (Float32, Float64, ComplexF32, ComplexF64) S = ROCVector{FC} + M = ROCMatrix{FC} T = real(FC) n = 10 x = rand(FC, n) @@ -70,8 +70,8 @@ include("../test_utils.jl") @testset "vector_to_matrix" begin S = ROCVector{FC} - M = Krylov.vector_to_matrix(S) - @test M == ROCMatrix{FC} + M2 = Krylov.vector_to_matrix(S) + @test M2 == M end ε = eps(T) @@ -93,5 +93,9 @@ include("../test_utils.jl") x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + + # @testset "processes -- $FC" begin + # test_processes(S, M) + # end end end diff --git a/test/gpu/gpu.jl b/test/gpu/gpu.jl new file mode 100644 index 000000000..e0ca265b7 --- /dev/null +++ b/test/gpu/gpu.jl @@ -0,0 +1,38 @@ +using LinearAlgebra, SparseArrays, Test +using Krylov + +include("../test_utils.jl") + +function test_processes(S, M) + n = 250 + m = 500 + k = 20 + FC = eltype(S) + + cpu_A, cpu_b = symmetric_indefinite(n, FC=FC) + gpu_A, gpu_b = M(cpu_A), S(cpu_b) + V, T = hermitian_lanczos(gpu_A, gpu_b, k) + + cpu_A, cpu_b = nonsymmetric_definite(n, FC=FC) + cpu_c = -cpu_b + gpu_A, gpu_b, gpu_c = M(cpu_A), S(cpu_b), S(cpu_c) + V, T, U, Tᴴ = nonhermitian_lanczos(gpu_A, gpu_b, gpu_c, k) + + cpu_A, cpu_b = nonsymmetric_indefinite(n, FC=FC) + gpu_A, gpu_b = M(cpu_A), S(cpu_b) + V, H = arnoldi(gpu_A, gpu_b, k) + + cpu_A, cpu_b = under_consistent(n, m, FC=FC) + gpu_A, gpu_b = M(cpu_A), S(cpu_b) + V, U, L = golub_kahan(gpu_A, gpu_b, k) + + cpu_A, cpu_b = under_consistent(n, m, FC=FC) + _, cpu_c = over_consistent(m, n, FC=FC) + gpu_A, gpu_b, gpu_c = M(cpu_A), S(cpu_b), S(cpu_c) + V, T, U, Tᴴ = saunders_simon_yip(gpu_A, gpu_b, gpu_c, k) + + cpu_A, cpu_b = under_consistent(n, m, FC=FC) + cpu_B, cpu_c = over_consistent(m, n, FC=FC) + gpu_A, gpu_B, gpu_b, gpu_c = M(cpu_A), M(cpu_B), S(cpu_b), S(cpu_c) + V, H, U, F = montoison_orban(gpu_A, gpu_B, gpu_b, gpu_c, k) +end diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index 67ad0a7d5..e65a2bf47 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -1,7 +1,6 @@ -using LinearAlgebra, SparseArrays, Test -using Krylov, oneAPI +using oneAPI -include("../test_utils.jl") +include("gpu.jl") import Krylov.kdot # https://github.com/JuliaGPU/GPUArrays.jl/pull/427 @@ -27,6 +26,7 @@ end for FC ∈ (Float32, ComplexF32) S = oneVector{FC} + M = oneMatrix{FC} T = real(FC) n = 10 x = rand(FC, n) @@ -78,8 +78,8 @@ end @testset "vector_to_matrix" begin S = oneVector{FC} - M = Krylov.vector_to_matrix(S) - @test M == oneMatrix{FC} + M2 = Krylov.vector_to_matrix(S) + @test M2 == M end ε = eps(T) @@ -101,5 +101,9 @@ end x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + + # @testset "processes -- $FC" begin + # test_processes(S, M) + # end end end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 35325c863..9fd24392a 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -1,7 +1,6 @@ -using LinearAlgebra, SparseArrays, Test -using Krylov, Metal +using Metal -include("../test_utils.jl") +include("gpu.jl") # https://github.com/JuliaGPU/Metal.jl/pull/48 const MtlVector{T} = MtlArray{T,1} @@ -31,6 +30,7 @@ end for FC in (Float32, ComplexF32) S = MtlVector{FC} + M = MtlMatrix{FC} T = real(FC) n = 10 x = rand(FC, n) @@ -82,8 +82,8 @@ end @testset "vector_to_matrix" begin S = MtlVector{FC} - M = Krylov.vector_to_matrix(S) - @test M == MtlMatrix{FC} + M2 = Krylov.vector_to_matrix(S) + @test M2 == M end ε = eps(T) @@ -105,5 +105,9 @@ end x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + + # @testset "processes -- $FC" begin + # test_processes(S, M) + # end end end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index c72c5c6ba..b27ee11d8 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -1,7 +1,6 @@ -using LinearAlgebra, SparseArrays, Test -using LinearOperators, Krylov, CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER +using LinearOperators, CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER -include("../test_utils.jl") +include("gpu.jl") @testset "Nvidia -- CUDA.jl" begin @@ -95,6 +94,7 @@ include("../test_utils.jl") for FC in (Float32, Float64, ComplexF32, ComplexF64) S = CuVector{FC} + M = CuMatrix{FC} T = real(FC) n = 10 x = rand(FC, n) @@ -146,8 +146,8 @@ include("../test_utils.jl") @testset "vector_to_matrix" begin S = CuVector{FC} - M = Krylov.vector_to_matrix(S) - @test M == CuMatrix{FC} + M2 = Krylov.vector_to_matrix(S) + @test M2 == M end ε = eps(T) @@ -169,5 +169,9 @@ include("../test_utils.jl") x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + + @testset "processes -- $FC" begin + test_processes(S, M) + end end end diff --git a/test/runtests.jl b/test/runtests.jl index 75e8f0941..b69865f61 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,7 @@ import Krylov.KRYLOV_SOLVERS include("test_utils.jl") include("test_aux.jl") include("test_stats.jl") +include("test_processes.jl") include("test_fgmres.jl") include("test_gpmr.jl") diff --git a/test/test_processes.jl b/test/test_processes.jl new file mode 100644 index 000000000..7411269bb --- /dev/null +++ b/test/test_processes.jl @@ -0,0 +1,148 @@ +""" + P = permutation_paige(k) + +Return the sparse (2k) × (2k) matrix + + [e₁ • eₖ ] + [ e₁ • eₖ] +""" +function permutation_paige(k) + P = spzeros(Float64, 2k, 2k) + for i = 1:k + P[i,2i-1] = 1.0 + P[i+k,2i] = 1.0 + end + return P +end + +@testset "processes" begin + n = 250 + m = 500 + k = 20 + + for FC in (Float64, ComplexF64) + R = real(FC) + nbits_FC = sizeof(FC) + nbits_R = sizeof(R) + nbits_I = sizeof(Int) + + @testset "Data Type: FC" begin + + @testset "Hermitian Lanczos" begin + A, b = symmetric_indefinite(n, FC=FC) + V, T = hermitian_lanczos(A, b, k) + + @test A * V[:,1:k] ≈ V * T + + storage_hermitian_lanczos_bytes(n, k) = 4k * nbits_I + (3k-1) * nbits_R + n*(k+1) * nbits_FC + + expected_hermitian_lanczos_bytes = storage_hermitian_lanczos_bytes(n, k) + actual_hermitian_lanczos_bytes = @allocated hermitian_lanczos(A, b, k) + @test expected_hermitian_lanczos_bytes ≤ actual_hermitian_lanczos_bytes ≤ 1.02 * expected_hermitian_lanczos_bytes + end + + @testset "Non-hermitian Lanczos" begin + A, b = nonsymmetric_definite(n, FC=FC) + c = -b + V, T, U, Tᴴ = nonhermitian_lanczos(A, b, c, k) + + @test T[1:k,1:k] ≈ Tᴴ[1:k,1:k]' + @test A * V[:,1:k] ≈ V * T + @test A' * U[:,1:k] ≈ U * Tᴴ + + storage_nonhermitian_lanczos_bytes(n, k) = 4k * nbits_I + (6k-2) * nbits_FC + 2*n*(k+1) * nbits_FC + + expected_nonhermitian_lanczos_bytes = storage_nonhermitian_lanczos_bytes(n, k) + actual_nonhermitian_lanczos_bytes = @allocated nonhermitian_lanczos(A, b, c, k) + @test expected_nonhermitian_lanczos_bytes ≤ actual_nonhermitian_lanczos_bytes ≤ 1.02 * expected_nonhermitian_lanczos_bytes + end + + @testset "Arnoldi" begin + A, b = nonsymmetric_indefinite(n, FC=FC) + V, H = arnoldi(A, b, k) + + @test A * V[:,1:k] ≈ V * H + + function storage_arnoldi_bytes(n, k) + nnz = div(k*(k+1), 2) + k + return (nnz + k+1) * nbits_I + nnz * nbits_FC + n*(k+1) * nbits_FC + end + + expected_arnoldi_bytes = storage_arnoldi_bytes(n, k) + actual_arnoldi_bytes = @allocated arnoldi(A, b, k) + @test expected_arnoldi_bytes ≤ actual_arnoldi_bytes ≤ 1.02 * expected_arnoldi_bytes + end + + @testset "Golub-Kahan" begin + A, b = under_consistent(n, m, FC=FC) + V, U, L = golub_kahan(A, b, k) + B = L[1:k+1,1:k] + + @test A * V[:,1:k] ≈ U * B + @test A' * U ≈ V * L' + @test A' * A * V[:,1:k] ≈ V * L' * B + @test A * A' * U[:,1:k] ≈ U * B * L[1:k,1:k]' + + storage_golub_kahan_bytes(n, m, k) = 3*(k+1) * nbits_I + (2k+1) * nbits_R + (n+m)*(k+1) * nbits_FC + + expected_golub_kahan_bytes = storage_golub_kahan_bytes(n, m, k) + actual_golub_kahan_bytes = @allocated golub_kahan(A, b, k) + @test expected_golub_kahan_bytes ≤ actual_golub_kahan_bytes ≤ 1.02 * expected_golub_kahan_bytes + end + + @testset "Saunders-Simon-Yip" begin + A, b = under_consistent(n, m, FC=FC) + _, c = over_consistent(m, n, FC=FC) + V, T, U, Tᴴ = saunders_simon_yip(A, b, c, k) + + @test T[1:k,1:k] ≈ Tᴴ[1:k,1:k]' + @test A * U[:,1:k] ≈ V * T + @test A' * V[:,1:k] ≈ U * Tᴴ + @test A' * A * U[:,1:k-1] ≈ U * Tᴴ * T[1:k,1:k-1] + @test A * A' * V[:,1:k-1] ≈ V * T * Tᴴ[1:k,1:k-1] + + K = [zeros(FC,n,n) A; A' zeros(FC,m,m)] + Pₖ = permutation_paige(k) + Wₖ = [V[:,1:k] zeros(FC,n,k); zeros(FC,m,k) U[:,1:k]] * Pₖ + Pₖ₊₁ = permutation_paige(k+1) + Wₖ₊₁ = [V zeros(FC,n,k+1); zeros(FC,m,k+1) U] * Pₖ₊₁ + G = Pₖ₊₁' * [zeros(FC,k+1,k) T; Tᴴ zeros(FC,k+1,k)] * Pₖ + @test K * Wₖ ≈ Wₖ₊₁ * G + + storage_saunders_simon_yip_bytes(n, m, k) = 4k * nbits_I + (6k-2) * nbits_FC + (n+m)*(k+1) * nbits_FC + + expected_saunders_simon_yip_bytes = storage_saunders_simon_yip_bytes(n, m, k) + actual_saunders_simon_yip_bytes = @allocated saunders_simon_yip(A, b, c, k) + @test expected_saunders_simon_yip_bytes ≤ actual_saunders_simon_yip_bytes ≤ 1.02 * expected_saunders_simon_yip_bytes + end + + @testset "Montoison-Orban" begin + A, b = under_consistent(n, m, FC=FC) + B, c = over_consistent(m, n, FC=FC) + V, H, U, F = montoison_orban(A, B, b, c, k) + + @test A * U[:,1:k] ≈ V * H + @test B * V[:,1:k] ≈ U * F + @test B * A * U[:,1:k-1] ≈ U * F * H[1:k,1:k-1] + @test A * B * V[:,1:k-1] ≈ V * H * F[1:k,1:k-1] + + K = [zeros(FC,n,n) A; B zeros(FC,m,m)] + Pₖ = permutation_paige(k) + Wₖ = [V[:,1:k] zeros(FC,n,k); zeros(FC,m,k) U[:,1:k]] * Pₖ + Pₖ₊₁ = permutation_paige(k+1) + Wₖ₊₁ = [V zeros(FC,n,k+1); zeros(FC,m,k+1) U] * Pₖ₊₁ + G = Pₖ₊₁' * [zeros(FC,k+1,k) H; F zeros(FC,k+1,k)] * Pₖ + @test K * Wₖ ≈ Wₖ₊₁ * G + + function storage_montoison_orban_bytes(n, m, k) + nnz = div(k*(k+1), 2) + k + return (nnz + k+1) * nbits_I + 2*nnz * nbits_FC + (n+m)*(k+1) * nbits_FC + end + + expected_montoison_orban_bytes = storage_montoison_orban_bytes(n, m, k) + actual_montoison_orban_bytes = @allocated montoison_orban(A, B, b, c, k) + @test expected_montoison_orban_bytes ≤ actual_montoison_orban_bytes ≤ 1.02 * expected_montoison_orban_bytes + end + end + end +end From ed8cd992b5cc729e3e893aa3e63c405f8fec6ac5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 28 Sep 2022 00:33:34 -0400 Subject: [PATCH 052/182] Add FGMRES in processes.md --- docs/src/processes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/processes.md b/docs/src/processes.md index 208824ae8..06845160b 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -130,7 +130,7 @@ H_{k+1,k} = The function [`arnoldi`](@ref arnoldi) returns $V_{k+1}$ and $H_{k+1,k}$. -Related methods: [`DIOM`](@ref diom), [`FOM`](@ref fom), [`DQGMRES`](@ref dqgmres) and [`GMRES`](@ref gmres). +Related methods: [`DIOM`](@ref diom), [`FOM`](@ref fom), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres) and [`FGMRES`](@ref fgmres). ```@docs From be01a41c70f8f07fe0891576df788d6fdd590027 Mon Sep 17 00:00:00 2001 From: tmigot Date: Thu, 29 Sep 2022 06:36:18 -0400 Subject: [PATCH 053/182] add discussion in readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index ced20f308..a34ba3b7d 100644 --- a/README.md +++ b/README.md @@ -121,3 +121,10 @@ julia> ] pkg> add Krylov pkg> test Krylov ``` + +# Bug reports and discussions + +If you think you found a bug, feel free to open an [issue](https://github.com/JuliaSmoothOptimizers/Krylov.jl/issues). +Focused suggestions and requests can also be opened as issues. Before opening a pull request, start an issue or a discussion on the topic, please. + +If you want to ask a question not suited for a bug report, feel free to start a discussion [here](https://github.com/JuliaSmoothOptimizers/Organization/discussions). This forum is for general discussion about this repository and the [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers), so questions about any of our packages are welcome. From 50273a7a637b8b2e879d62f9a1ff59a5a7a2b091 Mon Sep 17 00:00:00 2001 From: tmigot Date: Thu, 29 Sep 2022 06:37:07 -0400 Subject: [PATCH 054/182] add discussion in index --- docs/src/index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/src/index.md b/docs/src/index.md index 00694b4de..e7f080439 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -92,3 +92,10 @@ julia> ] pkg> add Krylov pkg> test Krylov ``` + +# Bug reports and discussions + +If you think you found a bug, feel free to open an [issue](https://github.com/JuliaSmoothOptimizers/Krylov.jl/issues). +Focused suggestions and requests can also be opened as issues. Before opening a pull request, start an issue or a discussion on the topic, please. + +If you want to ask a question not suited for a bug report, feel free to start a discussion [here](https://github.com/JuliaSmoothOptimizers/Organization/discussions). This forum is for general discussion about this repository and the [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers), so questions about any of our packages are welcome. From ce6704f435d81c9d53ab0563b43d977a52d93476 Mon Sep 17 00:00:00 2001 From: tmigot Date: Thu, 29 Sep 2022 09:01:18 -0400 Subject: [PATCH 055/182] Apply suggestions from code review Co-authored-by: Alexis <35051714+amontoison@users.noreply.github.com> --- README.md | 2 +- docs/src/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a34ba3b7d..6f22afd25 100644 --- a/README.md +++ b/README.md @@ -127,4 +127,4 @@ pkg> test Krylov If you think you found a bug, feel free to open an [issue](https://github.com/JuliaSmoothOptimizers/Krylov.jl/issues). Focused suggestions and requests can also be opened as issues. Before opening a pull request, start an issue or a discussion on the topic, please. -If you want to ask a question not suited for a bug report, feel free to start a discussion [here](https://github.com/JuliaSmoothOptimizers/Organization/discussions). This forum is for general discussion about this repository and the [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers), so questions about any of our packages are welcome. +If you want to ask a question not suited for a bug report, feel free to start a discussion [here](https://github.com/JuliaSmoothOptimizers/Organization/discussions). This forum is for general discussion about this repository and the [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers) organization, so questions about any of our packages are welcome. diff --git a/docs/src/index.md b/docs/src/index.md index e7f080439..1b61c48b0 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -98,4 +98,4 @@ pkg> test Krylov If you think you found a bug, feel free to open an [issue](https://github.com/JuliaSmoothOptimizers/Krylov.jl/issues). Focused suggestions and requests can also be opened as issues. Before opening a pull request, start an issue or a discussion on the topic, please. -If you want to ask a question not suited for a bug report, feel free to start a discussion [here](https://github.com/JuliaSmoothOptimizers/Organization/discussions). This forum is for general discussion about this repository and the [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers), so questions about any of our packages are welcome. +If you want to ask a question not suited for a bug report, feel free to start a discussion [here](https://github.com/JuliaSmoothOptimizers/Organization/discussions). This forum is for general discussion about this repository and the [JuliaSmoothOptimizers](https://github.com/JuliaSmoothOptimizers) organization, so questions about any of our packages are welcome. From 7dcc254bdf518b509efe360ee77de0b93efe424c Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Thu, 29 Sep 2022 12:12:37 -0400 Subject: [PATCH 056/182] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f22afd25..6c4c8863c 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ pkg> add Krylov pkg> test Krylov ``` -# Bug reports and discussions +## Bug reports and discussions If you think you found a bug, feel free to open an [issue](https://github.com/JuliaSmoothOptimizers/Krylov.jl/issues). Focused suggestions and requests can also be opened as issues. Before opening a pull request, start an issue or a discussion on the topic, please. From 226c50745dc2aed77ae54bd093ab65f01983fcbb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 29 Sep 2022 12:07:02 -0400 Subject: [PATCH 057/182] Add GPU support for to_boundary function --- src/krylov_utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 575179eec..b0a180dd3 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -344,7 +344,7 @@ If `flip` is set to `true`, `σ1` and `σ2` are computed such that ‖x - σi d‖ = radius, i = 1, 2. """ -function to_boundary(n :: Int, x :: Vector{T}, d :: Vector{T}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where T <: FloatOrComplex +function to_boundary(n :: Int, x :: AbstractVector{T}, d :: AbstractVector{T}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where T <: FloatOrComplex radius > 0 || error("radius must be positive") # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - Δ²). From 3fcb8eec548708051ff7035881f9b4a725f04115 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 5 Oct 2022 00:30:41 -0400 Subject: [PATCH 058/182] Add the workflow Invalidations.yml --- .github/workflows/Invalidations.yml | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/Invalidations.yml diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml new file mode 100644 index 000000000..b0c37e05f --- /dev/null +++ b/.github/workflows/Invalidations.yml @@ -0,0 +1,43 @@ +name: Invalidations +# Uses SnoopCompile to evaluate number of invalidations caused by `using` the package +# using https://github.com/julia-actions/julia-invalidations +# Based on https://github.com/julia-actions/julia-invalidations + +on: + pull_request: + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: always. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + evaluate: + # Only run on PRs to the default branch. + # In the PR trigger above branches can be specified only explicitly whereas this check should work for master, main, or any other default branch + if: github.base_ref == github.event.repository.default_branch + runs-on: ubuntu-latest + steps: + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + - uses: actions/checkout@v3 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-invalidations@v1 + id: invs_pr + + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.repository.default_branch }} + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-invalidations@v1 + id: invs_default + + - name: Report invalidation counts + run: | + echo "Invalidations on default branch: ${{ steps.invs_default.outputs.total }} (${{ steps.invs_default.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY + echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY + - name: Check if the PR does increase number of invalidations + if: steps.invs_pr.outputs.total > steps.invs_default.outputs.total + run: exit 1 From 8441c4de24c160590489d85f974a58e16d7bb501 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 5 Oct 2022 01:31:38 -0400 Subject: [PATCH 059/182] Use Aqua.jl in the tests --- Project.toml | 3 ++- test/aqua.jl | 4 ++++ test/runtests.jl | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/aqua.jl diff --git a/Project.toml b/Project.toml index 74005745f..de944a7f4 100644 --- a/Project.toml +++ b/Project.toml @@ -11,8 +11,9 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" julia = "^1.6.0" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Random", "Test"] +test = ["Aqua", "Random", "Test"] diff --git a/test/aqua.jl b/test/aqua.jl new file mode 100644 index 000000000..1ecd8f4a9 --- /dev/null +++ b/test/aqua.jl @@ -0,0 +1,4 @@ +@testset "Aqua" begin + import Aqua + Aqua.test_all(Krylov) +end diff --git a/test/runtests.jl b/test/runtests.jl index b69865f61..29a9e7ee6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,8 @@ using Krylov, LinearAlgebra, SparseArrays, Printf, Random, Test import Krylov.KRYLOV_SOLVERS +include("aqua.jl") + include("test_utils.jl") include("test_aux.jl") include("test_stats.jl") From a8eb8bb768743cadd40c23cc3317e199dcb450f9 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 5 Oct 2022 15:42:00 -0400 Subject: [PATCH 060/182] Add the support of CuSparseMatrixCOO in gpu.md --- docs/src/gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 69aef5c60..0e13b510c 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -26,7 +26,7 @@ b_gpu = CuVector(b_cpu) x, stats = bilq(A_gpu, b_gpu) ``` -Sparse matrices have a specific storage on Nvidia GPUs (`CuSparseMatrixCSC` or `CuSparseMatrixCSR`): +Sparse matrices have a specific storage on Nvidia GPUs (`CuSparseMatrixCSC`, `CuSparseMatrixCSR` or `CuSparseMatrixCOO`): ```julia using CUDA, Krylov From 25f852485aa5dc7a7e5e5a293ffc489f458ca2f1 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 3 Oct 2022 10:29:29 -0400 Subject: [PATCH 061/182] add imput and output arguments in all docstrings --- src/bicgstab.jl | 10 ++++++++++ src/bilq.jl | 10 ++++++++++ src/bilqr.jl | 12 ++++++++++++ src/cg.jl | 10 ++++++++++ src/cg_lanczos.jl | 10 ++++++++++ src/cg_lanczos_shift.jl | 16 ++++++++++++++++ src/cgls.jl | 10 ++++++++++ src/cgne.jl | 10 ++++++++++ src/cgs.jl | 10 ++++++++++ src/cr.jl | 10 ++++++++++ src/craig.jl | 11 +++++++++++ src/craigmr.jl | 11 +++++++++++ src/crls.jl | 10 ++++++++++ src/crmr.jl | 10 ++++++++++ src/diom.jl | 10 ++++++++++ src/dqgmres.jl | 10 ++++++++++ src/fgmres.jl | 10 ++++++++++ src/fom.jl | 10 ++++++++++ src/gmres.jl | 10 ++++++++++ src/gpmr.jl | 13 +++++++++++++ src/krylov_processes.jl | 24 ++++++++++++------------ src/lnlq.jl | 11 +++++++++++ src/lslq.jl | 24 +++++++++++++----------- src/lsmr.jl | 10 ++++++++++ src/lsqr.jl | 10 ++++++++++ src/minres.jl | 10 ++++++++++ src/minres_qlp.jl | 10 ++++++++++ src/qmr.jl | 10 ++++++++++ src/symmlq.jl | 10 ++++++++++ src/tricg.jl | 12 ++++++++++++ src/trilqr.jl | 12 ++++++++++++ src/trimr.jl | 12 ++++++++++++ src/usymlq.jl | 11 +++++++++++ src/usymqr.jl | 11 +++++++++++ 34 files changed, 367 insertions(+), 23 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 25abd01e6..00d05b591 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -51,6 +51,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * H. A. van der Vorst, [*Bi-CGSTAB: A fast and smoothly converging variant of Bi-CG for the solution of nonsymmetric linear systems*](https://doi.org/10.1137/0913035), SIAM Journal on Scientific and Statistical Computing, 13(2), pp. 631--644, 1992. diff --git a/src/bilq.jl b/src/bilq.jl index 8dbd46c51..0234af960 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -39,6 +39,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * A. Montoison and D. Orban, [*BiLQ: An Iterative Method for Nonsymmetric Linear Systems with a Quasi-Minimum Error Property*](https://doi.org/10.1137/19M1290991), SIAM Journal on Matrix Analysis and Applications, 41(3), pp. 1145--1166, 2020. diff --git a/src/bilqr.jl b/src/bilqr.jl index 479e01319..a695b06c7 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -42,6 +42,18 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. +* `c`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `y`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. + #### Reference * A. Montoison and D. Orban, [*BiLQ: An Iterative Method for Nonsymmetric Linear Systems with a Quasi-Minimum Error Property*](https://doi.org/10.1137/19M1290991), SIAM Journal on Matrix Analysis and Applications, 41(3), pp. 1145--1166, 2020. diff --git a/src/cg.jl b/src/cg.jl index 68a6e415d..9775d5dac 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -46,6 +46,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a Hermitian positive definite matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * M. R. Hestenes and E. Stiefel, [*Methods of conjugate gradients for solving linear systems*](https://doi.org/10.6028/jres.049.044), Journal of Research of the National Bureau of Standards, 49(6), pp. 409--436, 1952. diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 4e503f09a..b65d93578 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -41,6 +41,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`LanczosStats`](@ref) structure. + #### References * A. Frommer and P. Maass, [*Fast CG-Based Methods for Tikhonov-Phillips Regularization*](https://doi.org/10.1137/S1064827596313310), SIAM Journal on Scientific Computing, 20(5), pp. 1831--1850, 1999. diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index ff873e5b4..66f26572d 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -36,6 +36,22 @@ assumed to be hermitian and positive definite. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. + +#### Input arguments + +* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `b`: a vector of length n. +* `shifts`: a vector of length nshifts. + +#### Output arguments + +* `x`: a vector of nshifts dense vectors, each one of length n. +* `stats`: statistics collected on the run in a [`LanczosShiftStats`](@ref) structure. + +#### References + +* A. Frommer and P. Maass, [*Fast CG-Based Methods for Tikhonov-Phillips Regularization*](https://doi.org/10.1137/S1064827596313310), SIAM Journal on Scientific Computing, 20(5), pp. 1831--1850, 1999. +* C. C. Paige and M. A. Saunders, [*Solution of Sparse Indefinite Systems of Linear Equations*](https://doi.org/10.1137/0712047), SIAM Journal on Numerical Analysis, 12(4), pp. 617--629, 1975. """ function cg_lanczos_shift end diff --git a/src/cgls.jl b/src/cgls.jl index a8d6e3c94..628e61a53 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -56,6 +56,16 @@ but simpler to implement. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * M. R. Hestenes and E. Stiefel. [*Methods of conjugate gradients for solving linear systems*](https://doi.org/10.6028/jres.049.044), Journal of Research of the National Bureau of Standards, 49(6), pp. 409--436, 1952. diff --git a/src/cgne.jl b/src/cgne.jl index 68039d2de..2375d3a47 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -65,6 +65,16 @@ A preconditioner N may be provided in the form of a linear operator. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * J. E. Craig, [*The N-step iteration procedures*](https://doi.org/10.1002/sapm195534164), Journal of Mathematics and Physics, 34(1), pp. 64--73, 1955. diff --git a/src/cgs.jl b/src/cgs.jl index 37a1c4137..3a3c92e88 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -49,6 +49,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * P. Sonneveld, [*CGS, A Fast Lanczos-Type Solver for Nonsymmetric Linear systems*](https://doi.org/10.1137/0910004), SIAM Journal on Scientific and Statistical Computing, 10(1), pp. 36--52, 1989. diff --git a/src/cr.jl b/src/cr.jl index c18501b09..a9f1fa1b5 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -43,6 +43,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a Hermitian positive definite matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * M. R. Hestenes and E. Stiefel, [*Methods of conjugate gradients for solving linear systems*](https://doi.org/10.6028/jres.049.044), Journal of Research of the National Bureau of Standards, 49(6), pp. 409--436, 1952. diff --git a/src/craig.jl b/src/craig.jl index 5759e31df..82fe12269 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -89,6 +89,17 @@ In this implementation, both the x and y-parts of the solution are returned. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `y`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * C. C. Paige and M. A. Saunders, [*LSQR: An Algorithm for Sparse Linear Equations and Sparse Least Squares*](https://doi.org/10.1145/355984.355989), ACM Transactions on Mathematical Software, 8(1), pp. 43--71, 1982. diff --git a/src/craigmr.jl b/src/craigmr.jl index 854e3df98..52ece8bd8 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -85,6 +85,17 @@ returned. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `y`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * D. Orban and M. Arioli. [*Iterative Solution of Symmetric Quasi-Definite Linear Systems*](https://doi.org/10.1137/1.9781611974737), Volume 3 of Spotlights. SIAM, Philadelphia, PA, 2017. diff --git a/src/crls.jl b/src/crls.jl index 329b5a5fe..a5bc99bd9 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -48,6 +48,16 @@ but simpler to implement. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * D. C.-L. Fong, *Minimum-Residual Methods for Sparse, Least-Squares using Golubg-Kahan Bidiagonalization*, Ph.D. Thesis, Stanford University, 2011. diff --git a/src/crmr.jl b/src/crmr.jl index 3fff12b08..f23cb9667 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -63,6 +63,16 @@ A preconditioner N may be provided. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * D. Orban and M. Arioli, [*Iterative Solution of Symmetric Quasi-Definite Linear Systems*](https://doi.org/10.1137/1.9781611974737), Volume 3 of Spotlights. SIAM, Philadelphia, PA, 2017. diff --git a/src/diom.jl b/src/diom.jl index 77e73c414..a5e4b4dbc 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -43,6 +43,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * Y. Saad, [*Practical use of some krylov subspace methods for solving indefinite and nonsymmetric linear systems*](https://doi.org/10.1137/0905015), SIAM journal on scientific and statistical computing, 5(1), pp. 203--228, 1984. diff --git a/src/dqgmres.jl b/src/dqgmres.jl index aa6e245ba..3a45475b3 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -43,6 +43,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * Y. Saad and K. Wu, [*DQGMRES: a quasi minimal residual algorithm based on incomplete orthogonalization*](https://doi.org/10.1002/(SICI)1099-1506(199607/08)3:4%3C329::AID-NLA86%3E3.0.CO;2-8), Numerical Linear Algebra with Applications, Vol. 3(4), pp. 329--343, 1996. diff --git a/src/fgmres.jl b/src/fgmres.jl index 635e7241e..548cff4a4 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -46,6 +46,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * Y. Saad, [*A Flexible Inner-Outer Preconditioned GMRES Algorithm*](https://doi.org/10.1137/0914028), SIAM Journal on Scientific Computing, Vol. 14(2), pp. 461--469, 1993. diff --git a/src/fom.jl b/src/fom.jl index 95bcc97d1..b1c4d36ba 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -40,6 +40,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * Y. Saad, [*Krylov subspace methods for solving unsymmetric linear systems*](https://doi.org/10.1090/S0025-5718-1981-0616364-6), Mathematics of computation, Vol. 37(155), pp. 105--126, 1981. diff --git a/src/gmres.jl b/src/gmres.jl index b145b512b..c8f0d709c 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -40,6 +40,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * Y. Saad and M. H. Schultz, [*GMRES: A Generalized Minimal Residual Algorithm for Solving Nonsymmetric Linear Systems*](https://doi.org/10.1137/0907058), SIAM Journal on Scientific and Statistical Computing, Vol. 7(3), pp. 856--869, 1986. diff --git a/src/gpmr.jl b/src/gpmr.jl index 528bd522d..58c228921 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -67,6 +67,19 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `B`: a linear operator that models a matrix of dimension m × n. +* `b`: a vector of length n. +* `c`: a vector of length m. + +#### Output arguments + +* `x`: a dense vector of length n. +* `y`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://dx.doi.org/10.13140/RG.2.2.24069.68326), Cahier du GERAD G-2021-62, GERAD, Montréal, 2021. diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index 49a30f4b3..551e81a7c 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -3,13 +3,13 @@ export hermitian_lanczos, nonhermitian_lanczos, arnoldi, golub_kahan, saunders_s """ V, T = hermitian_lanczos(A, b, k) -#### Input arguments: +#### Input arguments * `A`: a linear operator that models a Hermitian matrix of dimension n. * `b`: a vector of length n. * `k`: the number of iterations of the Hermitian Lanczos process. -#### Output arguments: +#### Output arguments * `V`: a dense n × (k+1) matrix. * `T`: a sparse (k+1) × k tridiagonal matrix. @@ -71,14 +71,14 @@ end """ V, T, U, Tᴴ = nonhermitian_lanczos(A, b, c, k) -#### Input arguments: +#### Input arguments * `A`: a linear operator that models a square matrix of dimension n. * `b`: a vector of length n. * `c`: a vector of length n. * `k`: the number of iterations of the non-Hermitian Lanczos process. -#### Output arguments: +#### Output arguments * `V`: a dense n × (k+1) matrix. * `T`: a sparse (k+1) × k tridiagonal matrix. @@ -163,13 +163,13 @@ end """ V, H = arnoldi(A, b, k) -#### Input arguments: +#### Input arguments * `A`: a linear operator that models a square matrix of dimension n. * `b`: a vector of length n. * `k`: the number of iterations of the Arnoldi process. -#### Output arguments: +#### Output arguments * `V`: a dense n × (k+1) matrix. * `H`: a sparse (k+1) × k upper Hessenberg matrix. @@ -222,13 +222,13 @@ end """ V, U, L = golub_kahan(A, b, k) -#### Input arguments: +#### Input arguments * `A`: a linear operator that models a matrix of dimension n × m. * `b`: a vector of length n. * `k`: the number of iterations of the Golub-Kahan process. -#### Output arguments: +#### Output arguments * `V`: a dense m × (k+1) matrix. * `U`: a dense n × (k+1) matrix. @@ -295,14 +295,14 @@ end """ V, T, U, Tᴴ = saunders_simon_yip(A, b, c, k) -#### Input arguments: +#### Input arguments * `A`: a linear operator that models a matrix of dimension n × m. * `b`: a vector of length n. * `c`: a vector of length m. * `k`: the number of iterations of the Saunders-Simon-Yip process. -#### Output arguments: +#### Output arguments * `V`: a dense n × (k+1) matrix. * `T`: a sparse (k+1) × k tridiagonal matrix. @@ -385,7 +385,7 @@ end """ V, H, U, F = montoison_orban(A, B, b, c, k) -#### Input arguments: +#### Input arguments * `A`: a linear operator that models a matrix of dimension n × m. * `B`: a linear operator that models a matrix of dimension m × n. @@ -393,7 +393,7 @@ end * `c`: a vector of length m. * `k`: the number of iterations of the Montoison-Orban process. -#### Output arguments: +#### Output arguments * `V`: a dense n × (k+1) matrix. * `H`: a sparse (k+1) × k upper Hessenberg matrix. diff --git a/src/lnlq.jl b/src/lnlq.jl index db0a7c951..97316d04a 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -82,6 +82,17 @@ For instance σ:=(1-1e-7)σₘᵢₙ . The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `y`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`LNLQStats`](@ref) structure. + #### Reference * R. Estrin, D. Orban, M.A. Saunders, [*LNLQ: An Iterative Method for Least-Norm Problems with an Error Minimization Property*](https://doi.org/10.1137/18M1194948), SIAM Journal on Matrix Analysis and Applications, 40(3), pp. 1102--1124, 2019. diff --git a/src/lslq.jl b/src/lslq.jl index d43d4a089..ebc1afbd3 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -53,12 +53,6 @@ but is more stable. * it is possible to transition cheaply from the LSLQ iterate to the LSQR iterate if there is an advantage (there always is in terms of error) * if `A` is rank deficient, identify the minimum least-squares solution -#### Optional arguments - -* `M`: a symmetric and positive definite dual preconditioner -* `N`: a symmetric and positive definite primal preconditioner -* `sqd` indicates that we are solving a symmetric and quasi-definite system with `λ=1` - If `λ > 0`, we solve the symmetric and quasi-definite system [ E A ] [ r ] [ b ] @@ -87,6 +81,16 @@ The system above represents the optimality conditions of In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Keyword arguments + +* `M`: a symmetric and positive definite dual preconditioner +* `N`: a symmetric and positive definite primal preconditioner +* `sqd` indicates that we are solving a symmetric and quasi-definite system with `λ=1` * `λ` is a regularization parameter (see the problem statement above) * `σ` is an underestimate of the smallest nonzero singular value of `A`---setting `σ` too large will result in an error in the course of the iterations * `atol` is a stopping tolerance based on the residual @@ -99,12 +103,10 @@ In this case, `N` can still be specified and indicates the weighted norm in whic * `conlim` is the limit on the estimated condition number of `A` beyond which the solution will be abandoned * `verbose` determines verbosity. -#### Return values - -`lslq` returns the tuple `(x, stats)` where +#### Output arguments -* `x` is the LQ solution estimate -* `stats` collects other statistics on the run in a LSLQStats +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`LSLQStats`](@ref) structure. * `stats.err_lbnds` is a vector of lower bounds on the LQ error---the vector is empty if `window` is set to zero * `stats.err_ubnds_lq` is a vector of upper bounds on the LQ error---the vector is empty if `σ == 0` is left at zero diff --git a/src/lsmr.jl b/src/lsmr.jl index 79d2543fb..0edb8b037 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -88,6 +88,16 @@ In this case, `N` can still be specified and indicates the weighted norm in whic The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`LsmrStats`](@ref) structure. + #### Reference * D. C.-L. Fong and M. A. Saunders, [*LSMR: An Iterative Algorithm for Sparse Least Squares Problems*](https://doi.org/10.1137/10079687X), SIAM Journal on Scientific Computing, 33(5), pp. 2950--2971, 2011. diff --git a/src/lsqr.jl b/src/lsqr.jl index e4973bd38..d629d75d0 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -83,6 +83,16 @@ In this case, `N` can still be specified and indicates the weighted norm in whic The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * C. C. Paige and M. A. Saunders, [*LSQR: An Algorithm for Sparse Linear Equations and Sparse Least Squares*](https://doi.org/10.1145/355984.355989), ACM Transactions on Mathematical Software, 8(1), pp. 43--71, 1982. diff --git a/src/minres.jl b/src/minres.jl index c95048bbc..41d072cf5 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -64,6 +64,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * C. C. Paige and M. A. Saunders, [*Solution of Sparse Indefinite Systems of Linear Equations*](https://doi.org/10.1137/0712047), SIAM Journal on Numerical Analysis, 12(4), pp. 617--629, 1975. diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index cb70754b8..52a7553e4 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -43,6 +43,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * S.-C. T. Choi, *Iterative methods for singular linear equations and least-squares problems*, Ph.D. thesis, ICME, Stanford University, 2006. diff --git a/src/qmr.jl b/src/qmr.jl index fe0fab65c..3aa6a341e 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -44,6 +44,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * R. W. Freund and N. M. Nachtigal, [*QMR : a quasi-minimal residual method for non-Hermitian linear systems*](https://doi.org/10.1007/BF01385726), Numerische mathematik, Vol. 60(1), pp. 315--339, 1991. diff --git a/src/symmlq.jl b/src/symmlq.jl index efbd751aa..15c18eaae 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -44,6 +44,16 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `b`: a vector of length n. + +#### Output arguments + +* `x`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`SymmlqStats`](@ref) structure. + #### Reference * C. C. Paige and M. A. Saunders, [*Solution of Sparse Indefinite Systems of Linear Equations*](https://doi.org/10.1137/0712047), SIAM Journal on Numerical Analysis, 12(4), pp. 617--629, 1975. diff --git a/src/tricg.jl b/src/tricg.jl index 8d0a41ce3..6450cef79 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -62,6 +62,18 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `c`: a vector of length m. + +#### Output arguments + +* `x`: a dense vector of length n. +* `y`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * A. Montoison and D. Orban, [*TriCG and TriMR: Two Iterative Methods for Symmetric Quasi-Definite Systems*](https://doi.org/10.1137/20M1363030), SIAM Journal on Scientific Computing, 43(4), pp. 2502--2525, 2021. diff --git a/src/trilqr.jl b/src/trilqr.jl index 60663ff55..11d8815f4 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -41,6 +41,18 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `c`: a vector of length m. + +#### Output arguments + +* `x`: a dense vector of length m. +* `y`: a dense vector of length n. +* `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. + #### Reference * A. Montoison and D. Orban, [*BiLQ: An Iterative Method for Nonsymmetric Linear Systems with a Quasi-Minimum Error Property*](https://doi.org/10.1137/19M1290991), SIAM Journal on Matrix Analysis and Applications, 41(3), pp. 1145--1166, 2020. diff --git a/src/trimr.jl b/src/trimr.jl index 041a5ffff..4350340c8 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -62,6 +62,18 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `c`: a vector of length m. + +#### Output arguments + +* `x`: a dense vector of length n. +* `y`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### Reference * A. Montoison and D. Orban, [*TriCG and TriMR: Two Iterative Methods for Symmetric Quasi-Definite Systems*](https://doi.org/10.1137/20M1363030), SIAM Journal on Scientific Computing, 43(4), pp. 2502--2525, 2021. diff --git a/src/usymlq.jl b/src/usymlq.jl index acec8d77e..84f59c4d5 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -50,6 +50,17 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `c`: a vector of length m. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * M. A. Saunders, H. D. Simon, and E. L. Yip, [*Two Conjugate-Gradient-Type Methods for Unsymmetric Linear Equations*](https://doi.org/10.1137/0725052), SIAM Journal on Numerical Analysis, 25(4), pp. 927--940, 1988. diff --git a/src/usymqr.jl b/src/usymqr.jl index 13c19efa8..9838024dd 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -47,6 +47,17 @@ where `kwargs` are the same keyword arguments as above. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. +#### Input arguments + +* `A`: a linear operator that models a matrix of dimension n × m. +* `b`: a vector of length n. +* `c`: a vector of length m. + +#### Output arguments + +* `x`: a dense vector of length m. +* `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. + #### References * M. A. Saunders, H. D. Simon, and E. L. Yip, [*Two Conjugate-Gradient-Type Methods for Unsymmetric Linear Equations*](https://doi.org/10.1137/0725052), SIAM Journal on Numerical Analysis, 25(4), pp. 927--940, 1988. From 95d36a596407bb59f796c007c7b656878d206a9c Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 4 Oct 2022 10:46:21 -0400 Subject: [PATCH 062/182] Use semicolon --- src/bicgstab.jl | 4 +-- src/bilq.jl | 4 +-- src/bilqr.jl | 8 +++--- src/cg.jl | 4 +-- src/cg_lanczos.jl | 4 +-- src/cg_lanczos_shift.jl | 6 ++--- src/cgls.jl | 4 +-- src/cgne.jl | 4 +-- src/cgs.jl | 4 +-- src/cr.jl | 4 +-- src/craig.jl | 6 ++--- src/craigmr.jl | 6 ++--- src/crls.jl | 4 +-- src/crmr.jl | 4 +-- src/diom.jl | 4 +-- src/dqgmres.jl | 4 +-- src/fgmres.jl | 4 +-- src/fom.jl | 4 +-- src/gmres.jl | 4 +-- src/gpmr.jl | 10 +++---- src/krylov_processes.jl | 58 ++++++++++++++++++++--------------------- src/lnlq.jl | 6 ++--- src/lslq.jl | 30 ++++++++++----------- src/lsmr.jl | 4 +-- src/lsqr.jl | 4 +-- src/minres.jl | 4 +-- src/minres_qlp.jl | 4 +-- src/qmr.jl | 4 +-- src/symmlq.jl | 4 +-- src/tricg.jl | 8 +++--- src/trilqr.jl | 8 +++--- src/trimr.jl | 8 +++--- src/usymlq.jl | 6 ++--- src/usymqr.jl | 6 ++--- 34 files changed, 125 insertions(+), 125 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 00d05b591..2e6ae72b6 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -53,12 +53,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/bilq.jl b/src/bilq.jl index 0234af960..d2b88637c 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -41,12 +41,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/bilqr.jl b/src/bilqr.jl index a695b06c7..070f8f253 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -44,14 +44,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n; +* `b`: a vector of length n; * `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. -* `y`: a dense vector of length n. +* `x`: a dense vector of length n; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. #### Reference diff --git a/src/cg.jl b/src/cg.jl index 9775d5dac..3a07bc934 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -48,12 +48,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian positive definite matrix of dimension n. +* `A`: a linear operator that models a Hermitian positive definite matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index b65d93578..cb232f4b1 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -43,12 +43,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`LanczosStats`](@ref) structure. #### References diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 66f26572d..0559d1d5d 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -39,13 +39,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian matrix of dimension n. -* `b`: a vector of length n. +* `A`: a linear operator that models a Hermitian matrix of dimension n; +* `b`: a vector of length n; * `shifts`: a vector of length nshifts. #### Output arguments -* `x`: a vector of nshifts dense vectors, each one of length n. +* `x`: a vector of nshifts dense vectors, each one of length n; * `stats`: statistics collected on the run in a [`LanczosShiftStats`](@ref) structure. #### References diff --git a/src/cgls.jl b/src/cgls.jl index 628e61a53..437e4cd11 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -58,12 +58,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/cgne.jl b/src/cgne.jl index 2375d3a47..f09da2836 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -67,12 +67,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/cgs.jl b/src/cgs.jl index 3a3c92e88..f498cb37a 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -51,12 +51,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/cr.jl b/src/cr.jl index a9f1fa1b5..5c0eb0b95 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -45,12 +45,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian positive definite matrix of dimension n. +* `A`: a linear operator that models a Hermitian positive definite matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/craig.jl b/src/craig.jl index 82fe12269..dcdaa28d8 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -91,13 +91,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. -* `y`: a dense vector of length n. +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/craigmr.jl b/src/craigmr.jl index 52ece8bd8..a0f5b7477 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -87,13 +87,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. -* `y`: a dense vector of length n. +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/crls.jl b/src/crls.jl index a5bc99bd9..2bdaa7b4b 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -50,12 +50,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/crmr.jl b/src/crmr.jl index f23cb9667..cbd2fc696 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -65,12 +65,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/diom.jl b/src/diom.jl index a5e4b4dbc..5e4bb996b 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -45,12 +45,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 3a45475b3..ddc0c7123 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -45,12 +45,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/fgmres.jl b/src/fgmres.jl index 548cff4a4..0ddfcc87a 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -48,12 +48,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/fom.jl b/src/fom.jl index b1c4d36ba..069feb92d 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -42,12 +42,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/gmres.jl b/src/gmres.jl index c8f0d709c..b1fbe1f69 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -42,12 +42,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/gpmr.jl b/src/gpmr.jl index 58c228921..850e385ef 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -69,15 +69,15 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `B`: a linear operator that models a matrix of dimension m × n. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `B`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length n; * `c`: a vector of length m. #### Output arguments -* `x`: a dense vector of length n. -* `y`: a dense vector of length m. +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index 551e81a7c..c434a51b3 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -5,13 +5,13 @@ export hermitian_lanczos, nonhermitian_lanczos, arnoldi, golub_kahan, saunders_s #### Input arguments -* `A`: a linear operator that models a Hermitian matrix of dimension n. -* `b`: a vector of length n. +* `A`: a linear operator that models a Hermitian matrix of dimension n; +* `b`: a vector of length n; * `k`: the number of iterations of the Hermitian Lanczos process. #### Output arguments -* `V`: a dense n × (k+1) matrix. +* `V`: a dense n × (k+1) matrix; * `T`: a sparse (k+1) × k tridiagonal matrix. #### Reference @@ -73,16 +73,16 @@ end #### Input arguments -* `A`: a linear operator that models a square matrix of dimension n. -* `b`: a vector of length n. -* `c`: a vector of length n. +* `A`: a linear operator that models a square matrix of dimension n; +* `b`: a vector of length n; +* `c`: a vector of length n; * `k`: the number of iterations of the non-Hermitian Lanczos process. #### Output arguments -* `V`: a dense n × (k+1) matrix. -* `T`: a sparse (k+1) × k tridiagonal matrix. -* `U`: a dense n × (k+1) matrix. +* `V`: a dense n × (k+1) matrix; +* `T`: a sparse (k+1) × k tridiagonal matrix; +* `U`: a dense n × (k+1) matrix; * `Tᴴ`: a sparse (k+1) × k tridiagonal matrix. #### Reference @@ -165,13 +165,13 @@ end #### Input arguments -* `A`: a linear operator that models a square matrix of dimension n. -* `b`: a vector of length n. +* `A`: a linear operator that models a square matrix of dimension n; +* `b`: a vector of length n; * `k`: the number of iterations of the Arnoldi process. #### Output arguments -* `V`: a dense n × (k+1) matrix. +* `V`: a dense n × (k+1) matrix; * `H`: a sparse (k+1) × k upper Hessenberg matrix. #### Reference @@ -224,14 +224,14 @@ end #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; * `k`: the number of iterations of the Golub-Kahan process. #### Output arguments -* `V`: a dense m × (k+1) matrix. -* `U`: a dense n × (k+1) matrix. +* `V`: a dense m × (k+1) matrix; +* `U`: a dense n × (k+1) matrix; * `L`: a sparse (k+1) × (k+1) lower bidiagonal matrix. #### Reference @@ -297,16 +297,16 @@ end #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; +* `c`: a vector of length m; * `k`: the number of iterations of the Saunders-Simon-Yip process. #### Output arguments -* `V`: a dense n × (k+1) matrix. -* `T`: a sparse (k+1) × k tridiagonal matrix. -* `U`: a dense m × (k+1) matrix. +* `V`: a dense n × (k+1) matrix; +* `T`: a sparse (k+1) × k tridiagonal matrix; +* `U`: a dense m × (k+1) matrix; * `Tᴴ`: a sparse (k+1) × k tridiagonal matrix. #### Reference @@ -387,17 +387,17 @@ end #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `B`: a linear operator that models a matrix of dimension m × n. -* `b`: a vector of length n. -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension n × m; +* `B`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length n; +* `c`: a vector of length m; * `k`: the number of iterations of the Montoison-Orban process. #### Output arguments -* `V`: a dense n × (k+1) matrix. -* `H`: a sparse (k+1) × k upper Hessenberg matrix. -* `U`: a dense m × (k+1) matrix. +* `V`: a dense n × (k+1) matrix; +* `H`: a sparse (k+1) × k upper Hessenberg matrix; +* `U`: a dense m × (k+1) matrix; * `F`: a sparse (k+1) × k upper Hessenberg matrix. #### Reference diff --git a/src/lnlq.jl b/src/lnlq.jl index 97316d04a..9c956f17e 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -84,13 +84,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. -* `y`: a dense vector of length n. +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`LNLQStats`](@ref) structure. #### Reference diff --git a/src/lslq.jl b/src/lslq.jl index ebc1afbd3..7d304a89b 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -83,29 +83,29 @@ In this case, `N` can still be specified and indicates the weighted norm in whic #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Keyword arguments -* `M`: a symmetric and positive definite dual preconditioner -* `N`: a symmetric and positive definite primal preconditioner -* `sqd` indicates that we are solving a symmetric and quasi-definite system with `λ=1` -* `λ` is a regularization parameter (see the problem statement above) -* `σ` is an underestimate of the smallest nonzero singular value of `A`---setting `σ` too large will result in an error in the course of the iterations -* `atol` is a stopping tolerance based on the residual -* `btol` is a stopping tolerance used to detect zero-residual problems -* `etol` is a stopping tolerance based on the lower bound on the error -* `window` is the number of iterations used to accumulate a lower bound on the error -* `utol` is a stopping tolerance based on the upper bound on the error -* `transfer_to_lsqr` return the CG solution estimate (i.e., the LSQR point) instead of the LQ estimate -* `itmax` is the maximum number of iterations (0 means no imposed limit) -* `conlim` is the limit on the estimated condition number of `A` beyond which the solution will be abandoned +* `M`: a symmetric and positive definite dual preconditioner; +* `N`: a symmetric and positive definite primal preconditioner; +* `sqd` indicates that we are solving a symmetric and quasi-definite system with `λ=1`; +* `λ` is a regularization parameter (see the problem statement above); +* `σ` is an underestimate of the smallest nonzero singular value of `A`---setting `σ` too large will result in an error in the course of the iterations; +* `atol` is a stopping tolerance based on the residual; +* `btol` is a stopping tolerance used to detect zero-residual problems; +* `etol` is a stopping tolerance based on the lower bound on the error; +* `window` is the number of iterations used to accumulate a lower bound on the error; +* `utol` is a stopping tolerance based on the upper bound on the error; +* `transfer_to_lsqr` return the CG solution estimate (i.e., the LSQR point) instead of the LQ estimate; +* `itmax` is the maximum number of iterations (0 means no imposed limit); +* `conlim` is the limit on the estimated condition number of `A` beyond which the solution will be abandoned; * `verbose` determines verbosity. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`LSLQStats`](@ref) structure. * `stats.err_lbnds` is a vector of lower bounds on the LQ error---the vector is empty if `window` is set to zero diff --git a/src/lsmr.jl b/src/lsmr.jl index 0edb8b037..17f8022b2 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -90,12 +90,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`LsmrStats`](@ref) structure. #### Reference diff --git a/src/lsqr.jl b/src/lsqr.jl index d629d75d0..b3164757a 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -85,12 +85,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. +* `A`: a linear operator that models a matrix of dimension n × m; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/minres.jl b/src/minres.jl index 41d072cf5..570a507de 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -66,12 +66,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 52a7553e4..bc11f4c1a 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -45,12 +45,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/qmr.jl b/src/qmr.jl index 3aa6a341e..a346d2546 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -46,12 +46,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n. +* `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/symmlq.jl b/src/symmlq.jl index 15c18eaae..74c4e8e59 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -46,12 +46,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a Hermitian matrix of dimension n. +* `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n. +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SymmlqStats`](@ref) structure. #### Reference diff --git a/src/tricg.jl b/src/tricg.jl index 6450cef79..d2645a5ed 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -64,14 +64,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; * `c`: a vector of length m. #### Output arguments -* `x`: a dense vector of length n. -* `y`: a dense vector of length m. +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/trilqr.jl b/src/trilqr.jl index 11d8815f4..f0ff3a1b7 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -43,14 +43,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; * `c`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m. -* `y`: a dense vector of length n. +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. #### Reference diff --git a/src/trimr.jl b/src/trimr.jl index 4350340c8..1c693639c 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -64,14 +64,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; * `c`: a vector of length m. #### Output arguments -* `x`: a dense vector of length n. -* `y`: a dense vector of length m. +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/usymlq.jl b/src/usymlq.jl index 84f59c4d5..2d83aeb83 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -52,13 +52,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; * `c`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/usymqr.jl b/src/usymqr.jl index 9838024dd..f30aa3261 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -49,13 +49,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m. -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length n; * `c`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m. +* `x`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References From bf371217d2262bcc424f112ea9ae8b5bffd4b66a Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 4 Oct 2022 12:39:52 -0400 Subject: [PATCH 063/182] Add the size of each linear system in the docstring --- src/bicgstab.jl | 2 +- src/bilq.jl | 2 +- src/bilqr.jl | 4 ++-- src/cg.jl | 7 +++---- src/cg_lanczos.jl | 6 ++---- src/cg_lanczos_shift.jl | 10 +++++----- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 2 +- src/cr.jl | 10 +++++----- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 4 ++-- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 2 +- src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 8 ++++---- src/minres_qlp.jl | 4 ++-- src/qmr.jl | 2 +- src/symmlq.jl | 8 ++++---- src/tricg.jl | 2 +- src/trilqr.jl | 4 ++-- src/trimr.jl | 2 +- src/usymlq.jl | 2 +- src/usymqr.jl | 2 +- 33 files changed, 54 insertions(+), 57 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 2e6ae72b6..ed875827e 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -24,7 +24,7 @@ export bicgstab, bicgstab! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b using BICGSTAB. +Solve the square linear system Ax = b of size n using BICGSTAB. BICGSTAB requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. diff --git a/src/bilq.jl b/src/bilq.jl index d2b88637c..304c7a67e 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -21,7 +21,7 @@ export bilq, bilq! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b using BiLQ. +Solve the square linear system Ax = b of size n using BiLQ. BiLQ is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. diff --git a/src/bilqr.jl b/src/bilqr.jl index 070f8f253..7ce40ec2c 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -27,8 +27,8 @@ Combine BiLQ and QMR to solve adjoint systems. [Aᴴ 0] [x] [c] The relation `bᴴc ≠ 0` must be satisfied. -BiLQ is used for solving primal system `Ax = b`. -QMR is used for solving dual system `Aᴴy = c`. +BiLQ is used for solving primal system `Ax = b` of size n. +QMR is used for solving dual system `Aᴴy = c` of size n. An option gives the possibility of transferring from the BiLQ point to the BiCG point, when it exists. The transfer is based on the residual norm. diff --git a/src/cg.jl b/src/cg.jl index 3a07bc934..0bbe04535 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -26,16 +26,15 @@ export cg, cg! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -The conjugate gradient method to solve the symmetric linear system Ax = b. +The conjugate gradient method to solve the Hermitian linear system Ax = b of size n. The method does _not_ abort if A is not definite. A preconditioner M may be provided in the form of a linear operator and is -assumed to be symmetric and positive definite. +assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. -If `itmax=0`, the default number of iterations is set to `2 * n`, -with `n = length(b)`. +If `itmax=0`, the default number of iterations is set to `2 * n`. CG can be warm-started from an initial guess `x0` with diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index cb232f4b1..fc8f273de 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -23,14 +23,12 @@ export cg_lanczos, cg_lanczos! `FC` is `T` or `Complex{T}`. The Lanczos version of the conjugate gradient method to solve the -symmetric linear system - - Ax = b +Hermitian linear system Ax = b of size n. The method does _not_ abort if A is not definite. A preconditioner M may be provided in the form of a linear operator and is -assumed to be hermitian and positive definite. +assumed to be Hermitian and positive definite. CG-LANCZOS can be warm-started from an initial guess `x0` with diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 0559d1d5d..be51a8f31 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -27,12 +27,12 @@ export cg_lanczos_shift, cg_lanczos_shift! The Lanczos version of the conjugate gradient method to solve a family of shifted systems - (A + αI) x = b (α = α₁, ..., αₙ) + (A + αI) x = b (α = α₁, ..., αₚ) -The method does _not_ abort if A + αI is not definite. +of size n. The method does _not_ abort if A + αI is not definite. A preconditioner M may be provided in the form of a linear operator and is -assumed to be hermitian and positive definite. +assumed to be Hermitian and positive definite. The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -41,11 +41,11 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n; -* `shifts`: a vector of length nshifts. +* `shifts`: a vector of length p. #### Output arguments -* `x`: a vector of nshifts dense vectors, each one of length n; +* `x`: a vector of p dense vectors, each one of length n; * `stats`: statistics collected on the run in a [`LanczosShiftStats`](@ref) structure. #### References diff --git a/src/cgls.jl b/src/cgls.jl index 437e4cd11..b0c84c9a2 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -42,7 +42,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ‖x‖₂² -using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization +of size n × m using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CG to the normal equations (AᴴA + λI) x = Aᴴb diff --git a/src/cgne.jl b/src/cgne.jl index f09da2836..ca7a95565 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -42,7 +42,7 @@ Solve the consistent linear system Ax + √λs = b -using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization +of size n × m using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CG to the normal equations of the second kind diff --git a/src/cgs.jl b/src/cgs.jl index f498cb37a..78c96831e 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -19,7 +19,7 @@ export cgs, cgs! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the consistent linear system Ax = b using conjugate gradient squared algorithm. +Solve the consistent linear system Ax = b of size n using CGS. CGS requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. diff --git a/src/cr.jl b/src/cr.jl index 5c0eb0b95..d8e43ae18 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -23,16 +23,16 @@ export cr, cr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -A truncated version of Stiefel’s Conjugate Residual method to solve the symmetric linear system Ax = b or the least-squares problem min ‖b - Ax‖. -The matrix A must be positive semi-definite. +A truncated version of Stiefel’s Conjugate Residual method to solve the Hermitian linear system Ax = b +of size n or the least-squares problem min ‖b - Ax‖ if A is singular. +The matrix A must be Hermitian semi-definite. -A preconditioner M may be provided in the form of a linear operator and is assumed to be symmetric and positive definite. +A preconditioner M may be provided in the form of a linear operator and is assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. In a linesearch context, 'linesearch' must be set to 'true'. -If `itmax=0`, the default number of iterations is set to `2 * n`, -with `n = length(b)`. +If `itmax=0`, the default number of iterations is set to `2 * n`. CR can be warm-started from an initial guess `x0` with diff --git a/src/craig.jl b/src/craig.jl index dcdaa28d8..02ae8f8c6 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -47,7 +47,7 @@ Find the least-norm solution of the consistent linear system Ax + λ²y = b -using the Golub-Kahan implementation of Craig's method, where λ ≥ 0 is a +of size n × m using the Golub-Kahan implementation of Craig's method, where λ ≥ 0 is a regularization parameter. This method is equivalent to CGNE but is more stable. diff --git a/src/craigmr.jl b/src/craigmr.jl index a0f5b7477..57a499350 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -40,7 +40,7 @@ Solve the consistent linear system Ax + λ²y = b -using the CRAIGMR method, where λ ≥ 0 is a regularization parameter. +of size n × m using the CRAIGMR method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying the Conjugate Residuals method to the normal equations of the second kind diff --git a/src/crls.jl b/src/crls.jl index 2bdaa7b4b..c57e6a503 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -34,8 +34,8 @@ Solve the linear least-squares problem minimize ‖b - Ax‖₂² + λ‖x‖₂² -using the Conjugate Residuals (CR) method. This method is equivalent to -applying MINRES to the normal equations +of size n × m using the Conjugate Residuals (CR) method. +This method is equivalent to applying MINRES to the normal equations (AᴴA + λI) x = Aᴴb. diff --git a/src/crmr.jl b/src/crmr.jl index cbd2fc696..b624b8a53 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -40,7 +40,7 @@ Solve the consistent linear system Ax + √λs = b -using the Conjugate Residual (CR) method, where λ ≥ 0 is a regularization +of size n × m using the Conjugate Residual (CR) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CR to the normal equations of the second kind diff --git a/src/diom.jl b/src/diom.jl index 5e4bb996b..9089fa05c 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -20,7 +20,7 @@ export diom, diom! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the consistent linear system Ax = b using DIOM. +Solve the consistent linear system Ax = b of size n using DIOM. DIOM only orthogonalizes the new vectors of the Krylov basis against the `memory` most recent vectors. If CG is well defined on `Ax = b` and `memory = 2`, DIOM is theoretically equivalent to CG. diff --git a/src/dqgmres.jl b/src/dqgmres.jl index ddc0c7123..6d08d7292 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -20,7 +20,7 @@ export dqgmres, dqgmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the consistent linear system Ax = b using DQGMRES. +Solve the consistent linear system Ax = b of size n using DQGMRES. DQGMRES algorithm is based on the incomplete Arnoldi orthogonalization process and computes a sequence of approximate solutions with the quasi-minimal residual property. diff --git a/src/fgmres.jl b/src/fgmres.jl index 0ddfcc87a..c4f3a9fc7 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -20,7 +20,7 @@ export fgmres, fgmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using FGMRES. +Solve the linear system Ax = b of size n using FGMRES. FGMRES computes a sequence of approximate solutions with minimum residual. FGMRES is a variant of GMRES that allows changes in the right preconditioner at each iteration. diff --git a/src/fom.jl b/src/fom.jl index 069feb92d..9a102d456 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -20,7 +20,7 @@ export fom, fom! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using FOM. +Solve the linear system Ax = b of size n using FOM. FOM algorithm is based on the Arnoldi process and a Galerkin condition. diff --git a/src/gmres.jl b/src/gmres.jl index b1fbe1f69..9c6a5fd08 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -20,7 +20,7 @@ export gmres, gmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using GMRES. +Solve the linear system Ax = b of size n using GMRES. GMRES algorithm is based on the Arnoldi process and computes a sequence of approximate solutions with the minimum residual. diff --git a/src/gpmr.jl b/src/gpmr.jl index 850e385ef..a2990ab2a 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -27,7 +27,7 @@ GPMR solves the unsymmetric partitioned linear system [ λI A ] [ x ] = [ b ] [ B μI ] [ y ] [ c ], -where λ and μ are real or complex numbers. +of size (n+m) × (n+m) where λ and μ are real or complex numbers. `A` can have any shape and `B` has the shape of `Aᴴ`. `A`, `B`, `b` and `c` must be all nonzero. diff --git a/src/lnlq.jl b/src/lnlq.jl index 9c956f17e..8a5742b82 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -38,7 +38,7 @@ Find the least-norm solution of the consistent linear system Ax + λ²y = b -using the LNLQ method, where λ ≥ 0 is a regularization parameter. +of size n × m using the LNLQ method, where λ ≥ 0 is a regularization parameter. For a system in the form Ax = b, LNLQ method is equivalent to applying SYMMLQ to AAᴴy = b and recovering x = Aᴴy but is more stable. diff --git a/src/lslq.jl b/src/lslq.jl index 7d304a89b..a3aa6994e 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -38,7 +38,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ²‖x‖₂² -using the LSLQ method, where λ ≥ 0 is a regularization parameter. +of size n × m using the LSLQ method, where λ ≥ 0 is a regularization parameter. LSLQ is formally equivalent to applying SYMMLQ to the normal equations (AᴴA + λ²I) x = Aᴴb diff --git a/src/lsmr.jl b/src/lsmr.jl index 17f8022b2..930443808 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -43,7 +43,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ²‖x‖₂² -using the LSMR method, where λ ≥ 0 is a regularization parameter. +of size n × m using the LSMR method, where λ ≥ 0 is a regularization parameter. LSMR is formally equivalent to applying MINRES to the normal equations (AᴴA + λ²I) x = Aᴴb diff --git a/src/lsqr.jl b/src/lsqr.jl index b3164757a..80ca8003c 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -42,7 +42,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ²‖x‖₂² -using the LSQR method, where λ ≥ 0 is a regularization parameter. +of size n × m using the LSQR method, where λ ≥ 0 is a regularization parameter. LSQR is formally equivalent to applying CG to the normal equations (AᴴA + λ²I) x = Aᴴb diff --git a/src/minres.jl b/src/minres.jl index 570a507de..1abe7160f 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -3,7 +3,7 @@ # # minimize ‖Ax - b‖₂ # -# where A is square and symmetric. +# where A is Hermitian. # # MINRES is formally equivalent to applying the conjugate residuals method # to Ax = b when A is positive definite, but is more general and also applies @@ -43,8 +43,8 @@ or the shifted linear system (A + λI) x = b -using the MINRES method, where λ ≥ 0 is a shift parameter, -where A is square and symmetric. +of size n using the MINRES method, where λ ≥ 0 is a shift parameter, +where A is Hermitian. MINRES is formally equivalent to applying CR to Ax=b when A is positive definite, but is typically more stable and also applies to the case where @@ -53,7 +53,7 @@ A is indefinite. MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr‖₂. A preconditioner M may be provided in the form of a linear operator and is -assumed to be symmetric and positive definite. +assumed to be Hermitian and positive definite. MINRES can be warm-started from an initial guess `x0` with diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index bc11f4c1a..59ac6c77f 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -27,11 +27,11 @@ export minres_qlp, minres_qlp! `FC` is `T` or `Complex{T}`. MINRES-QLP is the only method based on the Lanczos process that returns the minimum-norm -solution on singular inconsistent systems (A + λI)x = b, where λ is a shift parameter. +solution on singular inconsistent systems (A + λI)x = b of size n, where λ is a shift parameter. It is significantly more complex but can be more reliable than MINRES when A is ill-conditioned. A preconditioner M may be provided in the form of a linear operator and is -assumed to be symmetric and positive definite. +assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. MINRES-QLP can be warm-started from an initial guess `x0` with diff --git a/src/qmr.jl b/src/qmr.jl index a346d2546..c6eed1aa6 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -29,7 +29,7 @@ export qmr, qmr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b using QMR. +Solve the square linear system Ax = b of size n using QMR. QMR is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. diff --git a/src/symmlq.jl b/src/symmlq.jl index 74c4e8e59..a7b984b2b 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -1,5 +1,5 @@ # An implementation of SYMMLQ for the solution of the -# linear system Ax = b, where A is square and symmetric. +# linear system Ax = b, where A is Hermitian. # # This implementation follows the original implementation by # Michael Saunders described in @@ -27,13 +27,13 @@ Solve the shifted linear system (A + λI) x = b -using the SYMMLQ method, where λ is a shift parameter, -and A is square and symmetric. +of size n using the SYMMLQ method, where λ is a shift parameter, +and A is Hermitian. SYMMLQ produces monotonic errors ‖x* - x‖₂. A preconditioner M may be provided in the form of a linear operator and is -assumed to be symmetric and positive definite. +assumed to be Hermitian and positive definite. SYMMLQ can be warm-started from an initial guess `x0` with diff --git a/src/tricg.jl b/src/tricg.jl index d2645a5ed..6788a0026 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -27,7 +27,7 @@ TriCG solves the symmetric linear system [ τE A ] [ x ] = [ b ] [ Aᴴ νF ] [ y ] [ c ], -where τ and ν are real numbers, E = M⁻¹ ≻ 0 and F = N⁻¹ ≻ 0. +of size (n+m) × (n+m) where τ and ν are real numbers, E = M⁻¹ ≻ 0 and F = N⁻¹ ≻ 0. `b` and `c` must both be nonzero. TriCG could breakdown if `τ = 0` or `ν = 0`. It's recommended to use TriMR in these cases. diff --git a/src/trilqr.jl b/src/trilqr.jl index f0ff3a1b7..bb279e947 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -26,8 +26,8 @@ Combine USYMLQ and USYMQR to solve adjoint systems. [0 A] [y] = [b] [Aᴴ 0] [x] [c] -USYMLQ is used for solving primal system `Ax = b`. -USYMQR is used for solving dual system `Aᴴy = c`. +USYMLQ is used for solving primal system `Ax = b` of size n. +USYMQR is used for solving dual system `Aᴴy = c` of size m. An option gives the possibility of transferring from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm. diff --git a/src/trimr.jl b/src/trimr.jl index 1c693639c..90ee54387 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -27,7 +27,7 @@ TriMR solves the symmetric linear system [ τE A ] [ x ] = [ b ] [ Aᴴ νF ] [ y ] [ c ], -where τ and ν are real numbers, E = M⁻¹ ≻ 0, F = N⁻¹ ≻ 0. +of size (n+m) × (n+m) where τ and ν are real numbers, E = M⁻¹ ≻ 0, F = N⁻¹ ≻ 0. `b` and `c` must both be nonzero. TriMR handles saddle-point systems (`τ = 0` or `ν = 0`) and adjoint systems (`τ = 0` and `ν = 0`) without any risk of breakdown. diff --git a/src/usymlq.jl b/src/usymlq.jl index 2d83aeb83..e89a400c2 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -28,7 +28,7 @@ export usymlq, usymlq! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using the USYMLQ method. +Solve the linear system Ax = b of size n × m using the USYMLQ method. USYMLQ is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. diff --git a/src/usymqr.jl b/src/usymqr.jl index f30aa3261..6dfef55fb 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -28,7 +28,7 @@ export usymqr, usymqr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b using USYMQR. +Solve the linear system Ax = b of size n × m using USYMQR. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. From ff0da247109df475f4607953c44288e21d30e3eb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 6 Oct 2022 10:33:40 -0400 Subject: [PATCH 064/182] Update the dimension of A --- src/cgls.jl | 8 ++++---- src/cgne.jl | 8 ++++---- src/craig.jl | 10 +++++----- src/craigmr.jl | 10 +++++----- src/crls.jl | 8 ++++---- src/crmr.jl | 8 ++++---- src/gpmr.jl | 17 +++++++++-------- src/krylov_processes.jl | 30 +++++++++++++++--------------- src/lnlq.jl | 10 +++++----- src/lslq.jl | 8 ++++---- src/lsmr.jl | 8 ++++---- src/lsqr.jl | 8 ++++---- src/tricg.jl | 12 ++++++------ src/trilqr.jl | 14 +++++++------- src/trimr.jl | 12 ++++++------ src/usymlq.jl | 10 +++++----- src/usymqr.jl | 10 +++++----- 17 files changed, 96 insertions(+), 95 deletions(-) diff --git a/src/cgls.jl b/src/cgls.jl index b0c84c9a2..78b1632e6 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -42,7 +42,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ‖x‖₂² -of size n × m using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization +of size m × n using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CG to the normal equations (AᴴA + λI) x = Aᴴb @@ -58,12 +58,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/cgne.jl b/src/cgne.jl index ca7a95565..f1e61481d 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -42,7 +42,7 @@ Solve the consistent linear system Ax + √λs = b -of size n × m using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization +of size m × n using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CG to the normal equations of the second kind @@ -67,12 +67,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/craig.jl b/src/craig.jl index 02ae8f8c6..756d311a4 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -47,7 +47,7 @@ Find the least-norm solution of the consistent linear system Ax + λ²y = b -of size n × m using the Golub-Kahan implementation of Craig's method, where λ ≥ 0 is a +of size m × n using the Golub-Kahan implementation of Craig's method, where λ ≥ 0 is a regularization parameter. This method is equivalent to CGNE but is more stable. @@ -91,13 +91,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; -* `y`: a dense vector of length n; +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/craigmr.jl b/src/craigmr.jl index 57a499350..fc0a38e89 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -40,7 +40,7 @@ Solve the consistent linear system Ax + λ²y = b -of size n × m using the CRAIGMR method, where λ ≥ 0 is a regularization parameter. +of size m × n using the CRAIGMR method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying the Conjugate Residuals method to the normal equations of the second kind @@ -87,13 +87,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; -* `y`: a dense vector of length n; +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/crls.jl b/src/crls.jl index c57e6a503..bbfd116cb 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -34,7 +34,7 @@ Solve the linear least-squares problem minimize ‖b - Ax‖₂² + λ‖x‖₂² -of size n × m using the Conjugate Residuals (CR) method. +of size m × n using the Conjugate Residuals (CR) method. This method is equivalent to applying MINRES to the normal equations (AᴴA + λI) x = Aᴴb. @@ -50,12 +50,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/crmr.jl b/src/crmr.jl index b624b8a53..b7e236950 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -40,7 +40,7 @@ Solve the consistent linear system Ax + √λs = b -of size n × m using the Conjugate Residual (CR) method, where λ ≥ 0 is a regularization +of size m × n using the Conjugate Residual (CR) method, where λ ≥ 0 is a regularization parameter. This method is equivalent to applying CR to the normal equations of the second kind @@ -65,12 +65,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/gpmr.jl b/src/gpmr.jl index a2990ab2a..139643c85 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -22,10 +22,11 @@ export gpmr, gpmr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. +Given matrices `A` of dimension m × n and `B` of dimension n × m, GPMR solves the unsymmetric partitioned linear system - [ λI A ] [ x ] = [ b ] - [ B μI ] [ y ] [ c ], + [ λIₘ A ] [ x ] = [ b ] + [ B μIₙ ] [ y ] [ c ], of size (n+m) × (n+m) where λ and μ are real or complex numbers. `A` can have any shape and `B` has the shape of `Aᴴ`. @@ -69,15 +70,15 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `B`: a linear operator that models a matrix of dimension m × n; -* `b`: a vector of length n; -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension m × n; +* `B`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length m; +* `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n; -* `y`: a dense vector of length m; +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index c434a51b3..deaf6abba 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -224,14 +224,14 @@ end #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; * `k`: the number of iterations of the Golub-Kahan process. #### Output arguments -* `V`: a dense m × (k+1) matrix; -* `U`: a dense n × (k+1) matrix; +* `V`: a dense n × (k+1) matrix; +* `U`: a dense m × (k+1) matrix; * `L`: a sparse (k+1) × (k+1) lower bidiagonal matrix. #### Reference @@ -297,16 +297,16 @@ end #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; -* `c`: a vector of length m; +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; +* `c`: a vector of length n; * `k`: the number of iterations of the Saunders-Simon-Yip process. #### Output arguments -* `V`: a dense n × (k+1) matrix; +* `V`: a dense m × (k+1) matrix; * `T`: a sparse (k+1) × k tridiagonal matrix; -* `U`: a dense m × (k+1) matrix; +* `U`: a dense n × (k+1) matrix; * `Tᴴ`: a sparse (k+1) × k tridiagonal matrix. #### Reference @@ -387,17 +387,17 @@ end #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `B`: a linear operator that models a matrix of dimension m × n; -* `b`: a vector of length n; -* `c`: a vector of length m; +* `A`: a linear operator that models a matrix of dimension m × n; +* `B`: a linear operator that models a matrix of dimension n × m; +* `b`: a vector of length m; +* `c`: a vector of length n; * `k`: the number of iterations of the Montoison-Orban process. #### Output arguments -* `V`: a dense n × (k+1) matrix; +* `V`: a dense m × (k+1) matrix; * `H`: a sparse (k+1) × k upper Hessenberg matrix; -* `U`: a dense m × (k+1) matrix; +* `U`: a dense n × (k+1) matrix; * `F`: a sparse (k+1) × k upper Hessenberg matrix. #### Reference diff --git a/src/lnlq.jl b/src/lnlq.jl index 8a5742b82..0611712e2 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -38,7 +38,7 @@ Find the least-norm solution of the consistent linear system Ax + λ²y = b -of size n × m using the LNLQ method, where λ ≥ 0 is a regularization parameter. +of size m × n using the LNLQ method, where λ ≥ 0 is a regularization parameter. For a system in the form Ax = b, LNLQ method is equivalent to applying SYMMLQ to AAᴴy = b and recovering x = Aᴴy but is more stable. @@ -84,13 +84,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; -* `y`: a dense vector of length n; +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`LNLQStats`](@ref) structure. #### Reference diff --git a/src/lslq.jl b/src/lslq.jl index a3aa6994e..1caebdd48 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -38,7 +38,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ²‖x‖₂² -of size n × m using the LSLQ method, where λ ≥ 0 is a regularization parameter. +of size m × n using the LSLQ method, where λ ≥ 0 is a regularization parameter. LSLQ is formally equivalent to applying SYMMLQ to the normal equations (AᴴA + λ²I) x = Aᴴb @@ -83,8 +83,8 @@ In this case, `N` can still be specified and indicates the weighted norm in whic #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Keyword arguments @@ -105,7 +105,7 @@ In this case, `N` can still be specified and indicates the weighted norm in whic #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`LSLQStats`](@ref) structure. * `stats.err_lbnds` is a vector of lower bounds on the LQ error---the vector is empty if `window` is set to zero diff --git a/src/lsmr.jl b/src/lsmr.jl index 930443808..39bbf3367 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -43,7 +43,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ²‖x‖₂² -of size n × m using the LSMR method, where λ ≥ 0 is a regularization parameter. +of size m × n using the LSMR method, where λ ≥ 0 is a regularization parameter. LSMR is formally equivalent to applying MINRES to the normal equations (AᴴA + λ²I) x = Aᴴb @@ -90,12 +90,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`LsmrStats`](@ref) structure. #### Reference diff --git a/src/lsqr.jl b/src/lsqr.jl index 80ca8003c..7dad61896 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -42,7 +42,7 @@ Solve the regularized linear least-squares problem minimize ‖b - Ax‖₂² + λ²‖x‖₂² -of size n × m using the LSQR method, where λ ≥ 0 is a regularization parameter. +of size m × n using the LSQR method, where λ ≥ 0 is a regularization parameter. LSQR is formally equivalent to applying CG to the normal equations (AᴴA + λ²I) x = Aᴴb @@ -85,12 +85,12 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/tricg.jl b/src/tricg.jl index 6788a0026..578c7d07e 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -22,7 +22,7 @@ export tricg, tricg! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -TriCG solves the symmetric linear system +Given a matrix `A` of dimension m × n, TriCG solves the symmetric linear system [ τE A ] [ x ] = [ b ] [ Aᴴ νF ] [ y ] [ c ], @@ -64,14 +64,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; +* `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n; -* `y`: a dense vector of length m; +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/trilqr.jl b/src/trilqr.jl index bb279e947..ab231e42f 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -26,8 +26,8 @@ Combine USYMLQ and USYMQR to solve adjoint systems. [0 A] [y] = [b] [Aᴴ 0] [x] [c] -USYMLQ is used for solving primal system `Ax = b` of size n. -USYMQR is used for solving dual system `Aᴴy = c` of size m. +USYMLQ is used for solving primal system `Ax = b` of size m × n. +USYMQR is used for solving dual system `Aᴴy = c` of size n × m. An option gives the possibility of transferring from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm. @@ -43,14 +43,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; +* `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m; -* `y`: a dense vector of length n; +* `x`: a dense vector of length n; +* `y`: a dense vector of length m; * `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. #### Reference diff --git a/src/trimr.jl b/src/trimr.jl index 90ee54387..82e22b6cf 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -22,7 +22,7 @@ export trimr, trimr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -TriMR solves the symmetric linear system +Given a matrix `A` of dimension m × n, TriMR solves the symmetric linear system [ τE A ] [ x ] = [ b ] [ Aᴴ νF ] [ y ] [ c ], @@ -64,14 +64,14 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; +* `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length n; -* `y`: a dense vector of length m; +* `x`: a dense vector of length m; +* `y`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### Reference diff --git a/src/usymlq.jl b/src/usymlq.jl index e89a400c2..357498973 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -28,7 +28,7 @@ export usymlq, usymlq! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b of size n × m using the USYMLQ method. +Solve the linear system Ax = b of size m × n using the USYMLQ method. USYMLQ is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. @@ -52,13 +52,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; +* `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References diff --git a/src/usymqr.jl b/src/usymqr.jl index 6dfef55fb..7705d0d7f 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -28,7 +28,7 @@ export usymqr, usymqr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the linear system Ax = b of size n × m using USYMQR. +Solve the linear system Ax = b of size m × n using USYMQR. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. @@ -49,13 +49,13 @@ and `false` otherwise. #### Input arguments -* `A`: a linear operator that models a matrix of dimension n × m; -* `b`: a vector of length n; -* `c`: a vector of length m. +* `A`: a linear operator that models a matrix of dimension m × n; +* `b`: a vector of length m; +* `c`: a vector of length n. #### Output arguments -* `x`: a dense vector of length m; +* `x`: a dense vector of length n; * `stats`: statistics collected on the run in a [`SimpleStats`](@ref) structure. #### References From 01b570ee810560dc30a628d585c30f7ac4a860b7 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 6 Oct 2022 22:56:19 -0400 Subject: [PATCH 065/182] Add m and n in all Krylov solvers --- src/bicgstab.jl | 2 +- src/bilq.jl | 2 +- src/bilqr.jl | 2 +- src/cg.jl | 2 +- src/cg_lanczos.jl | 2 +- src/cg_lanczos_shift.jl | 2 +- src/cr.jl | 3 +- src/krylov_processes.jl | 66 ++-- src/krylov_solvers.jl | 699 ++++++++++++++++++++++------------------ src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 2 +- test/test_processes.jl | 38 +-- test/test_solvers.jl | 2 +- 14 files changed, 450 insertions(+), 376 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index ed875827e..789aacced 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -101,7 +101,7 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax :: Int=0, verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("BICGSTAB: system of size %d\n", n) diff --git a/src/bilq.jl b/src/bilq.jl index 304c7a67e..2bb25340a 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -89,7 +89,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab itmax :: Int=0, verbose :: Int=0, history :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("BILQ: system of size %d\n", n) diff --git a/src/bilqr.jl b/src/bilqr.jl index 7ce40ec2c..4afe76f70 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -94,7 +94,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: itmax :: Int=0, verbose :: Int=0, history :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("Systems must be square") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") diff --git a/src/cg.jl b/src/cg.jl index 0bbe04535..e2195d388 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -97,7 +97,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf("CG: system of %d equations in %d variables\n", n, n) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index fc8f273de..7bdb11f82 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -89,7 +89,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf("CG Lanczos: system of %d equations in %d variables\n", n, n) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index be51a8f31..5aa7f94ef 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -77,7 +77,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") diff --git a/src/cr.jl b/src/cr.jl index d8e43ae18..e10af4425 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -95,7 +95,8 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") - n, m = size(A) + + m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf("CR: system of %d equations in %d variables\n", n, n) diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index deaf6abba..ea3663eb4 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -19,7 +19,7 @@ export hermitian_lanczos, nonhermitian_lanczos, arnoldi, golub_kahan, saunders_s * C. Lanczos, [*An Iteration Method for the Solution of the Eigenvalue Problem of Linear Differential and Integral Operators*](https://doi.org/10.6028/jres.045.026), Journal of Research of the National Bureau of Standards, 45(4), pp. 225--280, 1950. """ function hermitian_lanczos(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex - n, m = size(A) + m, n = size(A) R = real(FC) S = ktypeof(b) M = vector_to_matrix(S) @@ -90,7 +90,7 @@ end * C. Lanczos, [*An Iteration Method for the Solution of the Eigenvalue Problem of Linear Differential and Integral Operators*](https://doi.org/10.6028/jres.045.026), Journal of Research of the National Bureau of Standards, 45(4), pp. 225--280, 1950. """ function nonhermitian_lanczos(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex - n, m = size(A) + m, n = size(A) Aᴴ = A' S = ktypeof(b) M = vector_to_matrix(S) @@ -179,7 +179,7 @@ end * W. E. Arnoldi, [*The principle of minimized iterations in the solution of the matrix eigenvalue problem*](https://doi.org/10.1090/qam/42792), Quarterly of Applied Mathematics, 9, pp. 17--29, 1951. """ function arnoldi(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex - n, m = size(A) + m, n = size(A) S = ktypeof(b) M = vector_to_matrix(S) @@ -239,7 +239,7 @@ end * G. H. Golub and W. Kahan, [*Calculating the Singular Values and Pseudo-Inverse of a Matrix*](https://doi.org/10.1137/0702016), SIAM Journal on Numerical Analysis, 2(2), pp. 225--224, 1965. """ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex - n, m = size(A) + m, n = size(A) R = real(FC) Aᴴ = A' S = ktypeof(b) @@ -259,8 +259,8 @@ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComple rowval[2k+1] = k+1 colptr[k+2] = 2k+2 - V = M(undef, m, k+1) - U = M(undef, n, k+1) + V = M(undef, n, k+1) + U = M(undef, m, k+1) L = SparseMatrixCSC(k+1, k+1, colptr, rowval, nzval) for i = 1:k @@ -270,21 +270,21 @@ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComple vᵢ₊₁ = p = view(V,:,i+1) if i == 1 wᵢ = vᵢ - βᵢ = @knrm2(n, b) + βᵢ = @knrm2(m, b) uᵢ .= b ./ βᵢ mul!(wᵢ, Aᴴ, uᵢ) - αᵢ = @knrm2(m, wᵢ) + αᵢ = @knrm2(n, wᵢ) L[1,1] = αᵢ vᵢ .= wᵢ ./ αᵢ end mul!(q, A, vᵢ) αᵢ = L[i,i] - @kaxpy!(n, -αᵢ, uᵢ, q) - βᵢ₊₁ = @knrm2(n, q) + @kaxpy!(m, -αᵢ, uᵢ, q) + βᵢ₊₁ = @knrm2(m, q) uᵢ₊₁ .= q ./ βᵢ₊₁ mul!(p, Aᴴ, uᵢ₊₁) - @kaxpy!(m, -βᵢ₊₁, vᵢ, p) - αᵢ₊₁ = @knrm2(m, p) + @kaxpy!(n, -βᵢ₊₁, vᵢ, p) + αᵢ₊₁ = @knrm2(n, p) vᵢ₊₁ .= p ./ αᵢ₊₁ L[i+1,i] = βᵢ₊₁ L[i+1,i+1] = αᵢ₊₁ @@ -314,7 +314,7 @@ end * M. A. Saunders, H. D. Simon, and E. L. Yip, [*Two Conjugate-Gradient-Type Methods for Unsymmetric Linear Equations*](https://doi.org/10.1137/0725052), SIAM Journal on Numerical Analysis, 25(4), pp. 927--940, 1988. """ function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex - n, m = size(A) + m, n = size(A) Aᴴ = A' S = ktypeof(b) M = vector_to_matrix(S) @@ -337,8 +337,8 @@ function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: end end - V = M(undef, n, k+1) - U = M(undef, m, k+1) + V = M(undef, m, k+1) + U = M(undef, n, k+1) T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_T) Tᴴ = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_Tᴴ) @@ -348,8 +348,8 @@ function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: vᵢ₊₁ = q = view(V,:,i+1) uᵢ₊₁ = p = view(U,:,i+1) if i == 1 - β = @knrm2(n, b) - γ = @knrm2(m, c) + β = @knrm2(m, b) + γ = @knrm2(n, c) vᵢ .= b ./ β uᵢ .= c ./ γ end @@ -360,16 +360,16 @@ function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: uᵢ₋₁ = view(U,:,i-1) βᵢ = T[i,i-1] γᵢ = T[i-1,i] - @kaxpy!(n, -γᵢ, vᵢ₋₁, q) - @kaxpy!(m, -βᵢ, uᵢ₋₁, p) + @kaxpy!(m, -γᵢ, vᵢ₋₁, q) + @kaxpy!(n, -βᵢ, uᵢ₋₁, p) end - αᵢ = @kdot(n, vᵢ, q) + αᵢ = @kdot(m, vᵢ, q) T[i,i] = αᵢ Tᴴ[i,i] = conj(αᵢ) - @kaxpy!(n, - αᵢ , vᵢ, q) - @kaxpy!(m, -conj(αᵢ), uᵢ, p) - βᵢ₊₁ = @knrm2(n, q) - γᵢ₊₁ = @knrm2(m, p) + @kaxpy!(m, - αᵢ , vᵢ, q) + @kaxpy!(n, -conj(αᵢ), uᵢ, p) + βᵢ₊₁ = @knrm2(m, q) + γᵢ₊₁ = @knrm2(n, p) vᵢ₊₁ .= q ./ βᵢ₊₁ uᵢ₊₁ .= p ./ γᵢ₊₁ T[i+1,i] = βᵢ₊₁ @@ -405,7 +405,7 @@ end * A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://dx.doi.org/10.13140/RG.2.2.24069.68326), Cahier du GERAD G-2021-62, GERAD, Montréal, 2021. """ function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex - n, m = size(A) + m, n = size(A) S = ktypeof(b) M = vector_to_matrix(S) @@ -424,8 +424,8 @@ function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: end end - V = M(undef, n, k+1) - U = M(undef, m, k+1) + V = M(undef, m, k+1) + U = M(undef, n, k+1) H = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_H) F = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_F) @@ -435,8 +435,8 @@ function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: vᵢ₊₁ = q = view(V,:,i+1) uᵢ₊₁ = p = view(U,:,i+1) if i == 1 - β = @knrm2(n, b) - γ = @knrm2(m, c) + β = @knrm2(m, b) + γ = @knrm2(n, c) vᵢ .= b ./ β uᵢ .= c ./ γ end @@ -445,14 +445,14 @@ function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: for j = 1:i vⱼ = view(V,:,j) uⱼ = view(U,:,j) - H[j,i] = @kdot(n, vⱼ, q) + H[j,i] = @kdot(m, vⱼ, q) @kaxpy!(n, -H[j,i], vⱼ, q) - F[j,i] = @kdot(m, uⱼ, p) + F[j,i] = @kdot(n, uⱼ, p) @kaxpy!(m, -F[j,i], uⱼ, p) end - H[i+1,i] = @knrm2(n, q) + H[i+1,i] = @knrm2(m, q) vᵢ₊₁ .= q ./ H[i+1,i] - F[i+1,i] = @knrm2(m, p) + F[i+1,i] = @knrm2(n, p) uᵢ₊₁ .= p ./ F[i+1,i] end return V, H, U, F diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index f94efd2f9..2cc3197f5 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -8,6 +8,8 @@ GmresSolver, FomSolver, GpmrSolver, FgmresSolver export solve!, solution, nsolution, statistics, issolved, issolved_primal, issolved_dual, niterations, Aprod, Atprod, Bprod, warm_start! +import Base.size + const KRYLOV_SOLVERS = Dict( :cg => :CgSolver , :cr => :CrSolver , @@ -52,12 +54,14 @@ Type for storing the vectors required by the in-place version of MINRES. The outer constructors - solver = MinresSolver(n, m, S; window :: Int=5) + solver = MinresSolver(m, n, S; window :: Int=5) solver = MinresSolver(A, b; window :: Int=5) may be used in order to create these vectors. """ mutable struct MinresSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S r1 :: S @@ -71,7 +75,7 @@ mutable struct MinresSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function MinresSolver(n, m, S; window :: Int=5) +function MinresSolver(m, n, S; window :: Int=5) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -84,14 +88,14 @@ function MinresSolver(n, m, S; window :: Int=5) v = S(undef, 0) err_vec = zeros(T, window) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = MinresSolver{T,FC,S}(Δx, x, r1, r2, w1, w2, y, v, err_vec, false, stats) + solver = MinresSolver{T,FC,S}(m, n, Δx, x, r1, r2, w1, w2, y, v, err_vec, false, stats) return solver end function MinresSolver(A, b; window :: Int=5) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - MinresSolver(n, m, S, window=window) + MinresSolver(m, n, S, window=window) end """ @@ -99,12 +103,14 @@ Type for storing the vectors required by the in-place version of CG. The outer constructors - solver = CgSolver(n, m, S) + solver = CgSolver(m, n, S) solver = CgSolver(A, b) may be used in order to create these vectors. """ mutable struct CgSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S r :: S @@ -115,7 +121,7 @@ mutable struct CgSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CgSolver(n, m, S) +function CgSolver(m, n, S) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -125,14 +131,14 @@ function CgSolver(n, m, S) Ap = S(undef, n) z = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CgSolver{T,FC,S}(Δx, x, r, p, Ap, z, false, stats) + solver = CgSolver{T,FC,S}(m, n, Δx, x, r, p, Ap, z, false, stats) return solver end function CgSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CgSolver(n, m, S) + CgSolver(m, n, S) end """ @@ -140,12 +146,14 @@ Type for storing the vectors required by the in-place version of CR. The outer constructors - solver = CrSolver(n, m, S) + solver = CrSolver(m, n, S) solver = CrSolver(A, b) may be used in order to create these vectors. """ mutable struct CrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S r :: S @@ -157,7 +165,7 @@ mutable struct CrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CrSolver(n, m, S) +function CrSolver(m, n, S) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -168,14 +176,14 @@ function CrSolver(n, m, S) Ar = S(undef, n) Mq = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CrSolver{T,FC,S}(Δx, x, r, p, q, Ar, Mq, false, stats) + solver = CrSolver{T,FC,S}(m, n, Δx, x, r, p, q, Ar, Mq, false, stats) return solver end function CrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CrSolver(n, m, S) + CrSolver(m, n, S) end """ @@ -183,12 +191,14 @@ Type for storing the vectors required by the in-place version of SYMMLQ. The outer constructors - solver = SymmlqSolver(n, m, S) + solver = SymmlqSolver(m, n, S) solver = SymmlqSolver(A, b) may be used in order to create these vectors. """ mutable struct SymmlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S Mvold :: S @@ -203,7 +213,7 @@ mutable struct SymmlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SymmlqStats{T} end -function SymmlqSolver(n, m, S; window :: Int=5) +function SymmlqSolver(m, n, S; window :: Int=5) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -217,14 +227,14 @@ function SymmlqSolver(n, m, S; window :: Int=5) zlist = zeros(T, window) sprod = ones(T, window) stats = SymmlqStats(0, false, T[], Union{T, Missing}[], T[], Union{T, Missing}[], T(NaN), T(NaN), "unknown") - solver = SymmlqSolver{T,FC,S}(Δx, x, Mvold, Mv, Mv_next, w̅, v, clist, zlist, sprod, false, stats) + solver = SymmlqSolver{T,FC,S}(m, n, Δx, x, Mvold, Mv, Mv_next, w̅, v, clist, zlist, sprod, false, stats) return solver end function SymmlqSolver(A, b; window :: Int=5) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - SymmlqSolver(n, m, S, window=window) + SymmlqSolver(m, n, S, window=window) end """ @@ -232,12 +242,14 @@ Type for storing the vectors required by the in-place version of CG-LANCZOS. The outer constructors - solver = CgLanczosSolver(n, m, S) + solver = CgLanczosSolver(m, n, S) solver = CgLanczosSolver(A, b) may be used in order to create these vectors. """ mutable struct CgLanczosSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S Mv :: S @@ -249,7 +261,7 @@ mutable struct CgLanczosSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: LanczosStats{T} end -function CgLanczosSolver(n, m, S) +function CgLanczosSolver(m, n, S) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -260,14 +272,14 @@ function CgLanczosSolver(n, m, S) Mv_next = S(undef, n) v = S(undef, 0) stats = LanczosStats(0, false, T[], false, T(NaN), T(NaN), "unknown") - solver = CgLanczosSolver{T,FC,S}(Δx, x, Mv, Mv_prev, p, Mv_next, v, false, stats) + solver = CgLanczosSolver{T,FC,S}(m, n, Δx, x, Mv, Mv_prev, p, Mv_next, v, false, stats) return solver end function CgLanczosSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CgLanczosSolver(n, m, S) + CgLanczosSolver(m, n, S) end """ @@ -275,12 +287,14 @@ Type for storing the vectors required by the in-place version of CG-LANCZOS-SHIF The outer constructors - solver = CgLanczosShiftSolver(n, m, nshifts, S) + solver = CgLanczosShiftSolver(m, n, nshifts, S) solver = CgLanczosShiftSolver(A, b, nshifts) may be used in order to create these vectors. """ mutable struct CgLanczosShiftSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Mv :: S Mv_prev :: S Mv_next :: S @@ -297,7 +311,7 @@ mutable struct CgLanczosShiftSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: LanczosShiftStats{T} end -function CgLanczosShiftSolver(n, m, nshifts, S) +function CgLanczosShiftSolver(m, n, nshifts, S) FC = eltype(S) T = real(FC) Mv = S(undef, n) @@ -315,14 +329,14 @@ function CgLanczosShiftSolver(n, m, nshifts, S) converged = BitVector(undef, nshifts) not_cv = BitVector(undef, nshifts) stats = LanczosShiftStats(0, false, [T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") - solver = CgLanczosShiftSolver{T,FC,S}(Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) + solver = CgLanczosShiftSolver{T,FC,S}(m, n, Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) return solver end function CgLanczosShiftSolver(A, b, nshifts) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CgLanczosShiftSolver(n, m, nshifts, S) + CgLanczosShiftSolver(m, n, nshifts, S) end """ @@ -330,12 +344,14 @@ Type for storing the vectors required by the in-place version of MINRES-QLP. The outer constructors - solver = MinresQlpSolver(n, m, S) + solver = MinresQlpSolver(m, n, S) solver = MinresQlpSolver(A, b) may be used in order to create these vectors. """ mutable struct MinresQlpSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S wₖ₋₁ :: S wₖ :: S @@ -348,7 +364,7 @@ mutable struct MinresQlpSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function MinresQlpSolver(n, m, S) +function MinresQlpSolver(m, n, S) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -360,14 +376,14 @@ function MinresQlpSolver(n, m, S) p = S(undef, n) vₖ = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = MinresQlpSolver{T,FC,S}(Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats) + solver = MinresQlpSolver{T,FC,S}(m, n, Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats) return solver end function MinresQlpSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - MinresQlpSolver(n, m, S) + MinresQlpSolver(m, n, S) end """ @@ -375,13 +391,15 @@ Type for storing the vectors required by the in-place version of DQGMRES. The outer constructors - solver = DqgmresSolver(n, m, memory, S) + solver = DqgmresSolver(m, n, memory, S) solver = DqgmresSolver(A, b, memory = 20) may be used in order to create these vectors. `memory` is set to `n` if the value given is larger than `n`. """ mutable struct DqgmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S t :: S @@ -396,8 +414,8 @@ mutable struct DqgmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function DqgmresSolver(n, m, memory, S) - memory = min(n, memory) +function DqgmresSolver(m, n, memory, S) + memory = min(m, memory) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -411,14 +429,14 @@ function DqgmresSolver(n, m, memory, S) s = Vector{FC}(undef, memory) H = Vector{FC}(undef, memory+1) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = DqgmresSolver{T,FC,S}(Δx, x, t, z, w, P, V, c, s, H, false, stats) + solver = DqgmresSolver{T,FC,S}(m, n, Δx, x, t, z, w, P, V, c, s, H, false, stats) return solver end function DqgmresSolver(A, b, memory = 20) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - DqgmresSolver(n, m, memory, S) + DqgmresSolver(m, n, memory, S) end """ @@ -426,13 +444,15 @@ Type for storing the vectors required by the in-place version of DIOM. The outer constructors - solver = DiomSolver(n, m, memory, S) + solver = DiomSolver(m, n, memory, S) solver = DiomSolver(A, b, memory = 20) may be used in order to create these vectors. `memory` is set to `n` if the value given is larger than `n`. """ mutable struct DiomSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S t :: S @@ -446,8 +466,8 @@ mutable struct DiomSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function DiomSolver(n, m, memory, S) - memory = min(n, memory) +function DiomSolver(m, n, memory, S) + memory = min(m, memory) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -460,14 +480,14 @@ function DiomSolver(n, m, memory, S) L = Vector{FC}(undef, memory-1) H = Vector{FC}(undef, memory) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = DiomSolver{T,FC,S}(Δx, x, t, z, w, P, V, L, H, false, stats) + solver = DiomSolver{T,FC,S}(m, n, Δx, x, t, z, w, P, V, L, H, false, stats) return solver end function DiomSolver(A, b, memory = 20) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - DiomSolver(n, m, memory, S) + DiomSolver(m, n, memory, S) end """ @@ -475,12 +495,14 @@ Type for storing the vectors required by the in-place version of USYMLQ. The outer constructors - solver = UsymlqSolver(n, m, S) + solver = UsymlqSolver(m, n, S) solver = UsymlqSolver(A, b) may be used in order to create these vectors. """ mutable struct UsymlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int uₖ₋₁ :: S uₖ :: S p :: S @@ -494,27 +516,27 @@ mutable struct UsymlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function UsymlqSolver(n, m, S) +function UsymlqSolver(m, n, S) FC = eltype(S) T = real(FC) - uₖ₋₁ = S(undef, m) - uₖ = S(undef, m) - p = S(undef, m) + uₖ₋₁ = S(undef, n) + uₖ = S(undef, n) + p = S(undef, n) Δx = S(undef, 0) - x = S(undef, m) - d̅ = S(undef, m) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - q = S(undef, n) + x = S(undef, n) + d̅ = S(undef, n) + vₖ₋₁ = S(undef, m) + vₖ = S(undef, m) + q = S(undef, m) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = UsymlqSolver{T,FC,S}(uₖ₋₁, uₖ, p, Δx, x, d̅, vₖ₋₁, vₖ, q, false, stats) + solver = UsymlqSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, p, Δx, x, d̅, vₖ₋₁, vₖ, q, false, stats) return solver end function UsymlqSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - UsymlqSolver(n, m, S) + UsymlqSolver(m, n, S) end """ @@ -522,12 +544,14 @@ Type for storing the vectors required by the in-place version of USYMQR. The outer constructors - solver = UsymqrSolver(n, m, S) + solver = UsymqrSolver(m, n, S) solver = UsymqrSolver(A, b) may be used in order to create these vectors. """ mutable struct UsymqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int vₖ₋₁ :: S vₖ :: S q :: S @@ -542,28 +566,28 @@ mutable struct UsymqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function UsymqrSolver(n, m, S) +function UsymqrSolver(m, n, S) FC = eltype(S) T = real(FC) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - q = S(undef, n) + vₖ₋₁ = S(undef, m) + vₖ = S(undef, m) + q = S(undef, m) Δx = S(undef, 0) - x = S(undef, m) - wₖ₋₂ = S(undef, m) - wₖ₋₁ = S(undef, m) - uₖ₋₁ = S(undef, m) - uₖ = S(undef, m) - p = S(undef, m) + x = S(undef, n) + wₖ₋₂ = S(undef, n) + wₖ₋₁ = S(undef, n) + uₖ₋₁ = S(undef, n) + uₖ = S(undef, n) + p = S(undef, n) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = UsymqrSolver{T,FC,S}(vₖ₋₁, vₖ, q, Δx, x, wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, p, false, stats) + solver = UsymqrSolver{T,FC,S}(m, n, vₖ₋₁, vₖ, q, Δx, x, wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, p, false, stats) return solver end function UsymqrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - UsymqrSolver(n, m, S) + UsymqrSolver(m, n, S) end """ @@ -571,12 +595,14 @@ Type for storing the vectors required by the in-place version of TRICG. The outer constructors - solver = TricgSolver(n, m, S) + solver = TricgSolver(m, n, S) solver = TricgSolver(A, b) may be used in order to create these vectors. """ mutable struct TricgSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int y :: S N⁻¹uₖ₋₁ :: S N⁻¹uₖ :: S @@ -597,34 +623,34 @@ mutable struct TricgSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function TricgSolver(n, m, S) +function TricgSolver(m, n, S) FC = eltype(S) T = real(FC) - y = S(undef, m) - N⁻¹uₖ₋₁ = S(undef, m) - N⁻¹uₖ = S(undef, m) - p = S(undef, m) - gy₂ₖ₋₁ = S(undef, m) - gy₂ₖ = S(undef, m) - x = S(undef, n) - M⁻¹vₖ₋₁ = S(undef, n) - M⁻¹vₖ = S(undef, n) - q = S(undef, n) - gx₂ₖ₋₁ = S(undef, n) - gx₂ₖ = S(undef, n) + y = S(undef, n) + N⁻¹uₖ₋₁ = S(undef, n) + N⁻¹uₖ = S(undef, n) + p = S(undef, n) + gy₂ₖ₋₁ = S(undef, n) + gy₂ₖ = S(undef, n) + x = S(undef, m) + M⁻¹vₖ₋₁ = S(undef, m) + M⁻¹vₖ = S(undef, m) + q = S(undef, m) + gx₂ₖ₋₁ = S(undef, m) + gx₂ₖ = S(undef, m) Δx = S(undef, 0) Δy = S(undef, 0) uₖ = S(undef, 0) vₖ = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = TricgSolver{T,FC,S}(y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) + solver = TricgSolver{T,FC,S}(m, n, y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) return solver end function TricgSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - TricgSolver(n, m, S) + TricgSolver(m, n, S) end """ @@ -632,12 +658,14 @@ Type for storing the vectors required by the in-place version of TRIMR. The outer constructors - solver = TrimrSolver(n, m, S) + solver = TrimrSolver(m, n, S) solver = TrimrSolver(A, b) may be used in order to create these vectors. """ mutable struct TrimrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int y :: S N⁻¹uₖ₋₁ :: S N⁻¹uₖ :: S @@ -662,38 +690,38 @@ mutable struct TrimrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function TrimrSolver(n, m, S) +function TrimrSolver(m, n, S) FC = eltype(S) T = real(FC) - y = S(undef, m) - N⁻¹uₖ₋₁ = S(undef, m) - N⁻¹uₖ = S(undef, m) - p = S(undef, m) - gy₂ₖ₋₃ = S(undef, m) - gy₂ₖ₋₂ = S(undef, m) - gy₂ₖ₋₁ = S(undef, m) - gy₂ₖ = S(undef, m) - x = S(undef, n) - M⁻¹vₖ₋₁ = S(undef, n) - M⁻¹vₖ = S(undef, n) - q = S(undef, n) - gx₂ₖ₋₃ = S(undef, n) - gx₂ₖ₋₂ = S(undef, n) - gx₂ₖ₋₁ = S(undef, n) - gx₂ₖ = S(undef, n) + y = S(undef, n) + N⁻¹uₖ₋₁ = S(undef, n) + N⁻¹uₖ = S(undef, n) + p = S(undef, n) + gy₂ₖ₋₃ = S(undef, n) + gy₂ₖ₋₂ = S(undef, n) + gy₂ₖ₋₁ = S(undef, n) + gy₂ₖ = S(undef, n) + x = S(undef, m) + M⁻¹vₖ₋₁ = S(undef, m) + M⁻¹vₖ = S(undef, m) + q = S(undef, m) + gx₂ₖ₋₃ = S(undef, m) + gx₂ₖ₋₂ = S(undef, m) + gx₂ₖ₋₁ = S(undef, m) + gx₂ₖ = S(undef, m) Δx = S(undef, 0) Δy = S(undef, 0) uₖ = S(undef, 0) vₖ = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = TrimrSolver{T,FC,S}(y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) + solver = TrimrSolver{T,FC,S}(m, n, y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) return solver end function TrimrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - TrimrSolver(n, m, S) + TrimrSolver(m, n, S) end """ @@ -701,12 +729,14 @@ Type for storing the vectors required by the in-place version of TRILQR. The outer constructors - solver = TrilqrSolver(n, m, S) + solver = TrilqrSolver(m, n, S) solver = TrilqrSolver(A, b) may be used in order to create these vectors. """ mutable struct TrilqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int uₖ₋₁ :: S uₖ :: S p :: S @@ -724,31 +754,31 @@ mutable struct TrilqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: AdjointStats{T} end -function TrilqrSolver(n, m, S) +function TrilqrSolver(m, n, S) FC = eltype(S) T = real(FC) - uₖ₋₁ = S(undef, m) - uₖ = S(undef, m) - p = S(undef, m) - d̅ = S(undef, m) + uₖ₋₁ = S(undef, n) + uₖ = S(undef, n) + p = S(undef, n) + d̅ = S(undef, n) Δx = S(undef, 0) - x = S(undef, m) - vₖ₋₁ = S(undef, n) - vₖ = S(undef, n) - q = S(undef, n) + x = S(undef, n) + vₖ₋₁ = S(undef, m) + vₖ = S(undef, m) + q = S(undef, m) Δy = S(undef, 0) - y = S(undef, n) - wₖ₋₃ = S(undef, n) - wₖ₋₂ = S(undef, n) + y = S(undef, m) + wₖ₋₃ = S(undef, m) + wₖ₋₂ = S(undef, m) stats = AdjointStats(0, false, false, T[], T[], "unknown") - solver = TrilqrSolver{T,FC,S}(uₖ₋₁, uₖ, p, d̅, Δx, x, vₖ₋₁, vₖ, q, Δy, y, wₖ₋₃, wₖ₋₂, false, stats) + solver = TrilqrSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, p, d̅, Δx, x, vₖ₋₁, vₖ, q, Δy, y, wₖ₋₃, wₖ₋₂, false, stats) return solver end function TrilqrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - TrilqrSolver(n, m, S) + TrilqrSolver(m, n, S) end """ @@ -756,12 +786,14 @@ Type for storing the vectors required by the in-place version of CGS. The outer constructorss - solver = CgsSolver(n, m, S) + solver = CgsSolver(m, n, S) solver = CgsSolver(A, b) may be used in order to create these vectors. """ mutable struct CgsSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S r :: S @@ -775,7 +807,7 @@ mutable struct CgsSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CgsSolver(n, m, S) +function CgsSolver(m, n, S) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -788,14 +820,14 @@ function CgsSolver(n, m, S) yz = S(undef, 0) vw = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CgsSolver{T,FC,S}(Δx, x, r, u, p, q, ts, yz, vw, false, stats) + solver = CgsSolver{T,FC,S}(m, n, Δx, x, r, u, p, q, ts, yz, vw, false, stats) return solver end function CgsSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CgsSolver(n, m, S) + CgsSolver(m, n, S) end """ @@ -803,12 +835,14 @@ Type for storing the vectors required by the in-place version of BICGSTAB. The outer constructors - solver = BicgstabSolver(n, m, S) + solver = BicgstabSolver(m, n, S) solver = BicgstabSolver(A, b) may be used in order to create these vectors. """ mutable struct BicgstabSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S r :: S @@ -822,7 +856,7 @@ mutable struct BicgstabSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function BicgstabSolver(n, m, S) +function BicgstabSolver(m, n, S) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -835,14 +869,14 @@ function BicgstabSolver(n, m, S) yz = S(undef, 0) t = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = BicgstabSolver{T,FC,S}(Δx, x, r, p, v, s, qd, yz, t, false, stats) + solver = BicgstabSolver{T,FC,S}(m, n, Δx, x, r, p, v, s, qd, yz, t, false, stats) return solver end function BicgstabSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - BicgstabSolver(n, m, S) + BicgstabSolver(m, n, S) end """ @@ -850,12 +884,14 @@ Type for storing the vectors required by the in-place version of BILQ. The outer constructors - solver = BilqSolver(n, m, S) + solver = BilqSolver(m, n, S) solver = BilqSolver(A, b) may be used in order to create these vectors. """ mutable struct BilqSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int uₖ₋₁ :: S uₖ :: S q :: S @@ -869,7 +905,7 @@ mutable struct BilqSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function BilqSolver(n, m, S) +function BilqSolver(m, n, S) FC = eltype(S) T = real(FC) uₖ₋₁ = S(undef, n) @@ -882,14 +918,14 @@ function BilqSolver(n, m, S) x = S(undef, n) d̅ = S(undef, n) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = BilqSolver{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, d̅, false, stats) + solver = BilqSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, d̅, false, stats) return solver end function BilqSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - BilqSolver(n, m, S) + BilqSolver(m, n, S) end """ @@ -897,12 +933,14 @@ Type for storing the vectors required by the in-place version of QMR. The outer constructors - solver = QmrSolver(n, m, S) + solver = QmrSolver(m, n, S) solver = QmrSolver(A, b) may be used in order to create these vectors. """ mutable struct QmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int uₖ₋₁ :: S uₖ :: S q :: S @@ -917,7 +955,7 @@ mutable struct QmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function QmrSolver(n, m, S) +function QmrSolver(m, n, S) FC = eltype(S) T = real(FC) uₖ₋₁ = S(undef, n) @@ -931,14 +969,14 @@ function QmrSolver(n, m, S) wₖ₋₂ = S(undef, n) wₖ₋₁ = S(undef, n) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = QmrSolver{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, wₖ₋₂, wₖ₋₁, false, stats) + solver = QmrSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, wₖ₋₂, wₖ₋₁, false, stats) return solver end function QmrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - QmrSolver(n, m, S) + QmrSolver(m, n, S) end """ @@ -946,12 +984,14 @@ Type for storing the vectors required by the in-place version of BILQR. The outer constructors - solver = BilqrSolver(n, m, S) + solver = BilqrSolver(m, n, S) solver = BilqrSolver(A, b) may be used in order to create these vectors. """ mutable struct BilqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int uₖ₋₁ :: S uₖ :: S q :: S @@ -969,7 +1009,7 @@ mutable struct BilqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: AdjointStats{T} end -function BilqrSolver(n, m, S) +function BilqrSolver(m, n, S) FC = eltype(S) T = real(FC) uₖ₋₁ = S(undef, n) @@ -986,14 +1026,14 @@ function BilqrSolver(n, m, S) wₖ₋₃ = S(undef, n) wₖ₋₂ = S(undef, n) stats = AdjointStats(0, false, false, T[], T[], "unknown") - solver = BilqrSolver{T,FC,S}(uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, Δy, y, d̅, wₖ₋₃, wₖ₋₂, false, stats) + solver = BilqrSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, Δy, y, d̅, wₖ₋₃, wₖ₋₂, false, stats) return solver end function BilqrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - BilqrSolver(n, m, S) + BilqrSolver(m, n, S) end """ @@ -1001,12 +1041,14 @@ Type for storing the vectors required by the in-place version of CGLS. The outer constructors - solver = CglsSolver(n, m, S) + solver = CglsSolver(m, n, S) solver = CglsSolver(A, b) may be used in order to create these vectors. """ mutable struct CglsSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S p :: S s :: S @@ -1016,24 +1058,24 @@ mutable struct CglsSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CglsSolver(n, m, S) +function CglsSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - p = S(undef, m) - s = S(undef, m) - r = S(undef, n) - q = S(undef, n) + x = S(undef, n) + p = S(undef, n) + s = S(undef, n) + r = S(undef, m) + q = S(undef, m) Mr = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CglsSolver{T,FC,S}(x, p, s, r, q, Mr, stats) + solver = CglsSolver{T,FC,S}(m, n, x, p, s, r, q, Mr, stats) return solver end function CglsSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CglsSolver(n, m, S) + CglsSolver(m, n, S) end """ @@ -1041,12 +1083,14 @@ Type for storing the vectors required by the in-place version of CRLS. The outer constructors - solver = CrlsSolver(n, m, S) + solver = CrlsSolver(m, n, S) solver = CrlsSolver(A, b) may be used in order to create these vectors. """ mutable struct CrlsSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S p :: S Ar :: S @@ -1058,26 +1102,26 @@ mutable struct CrlsSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CrlsSolver(n, m, S) +function CrlsSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - p = S(undef, m) - Ar = S(undef, m) - q = S(undef, m) - r = S(undef, n) - Ap = S(undef, n) - s = S(undef, n) + x = S(undef, n) + p = S(undef, n) + Ar = S(undef, n) + q = S(undef, n) + r = S(undef, m) + Ap = S(undef, m) + s = S(undef, m) Ms = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CrlsSolver{T,FC,S}(x, p, Ar, q, r, Ap, s, Ms, stats) + solver = CrlsSolver{T,FC,S}(m, n, x, p, Ar, q, r, Ap, s, Ms, stats) return solver end function CrlsSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CrlsSolver(n, m, S) + CrlsSolver(m, n, S) end """ @@ -1085,12 +1129,14 @@ Type for storing the vectors required by the in-place version of CGNE. The outer constructors - solver = CgneSolver(n, m, S) + solver = CgneSolver(m, n, S) solver = CgneSolver(A, b) may be used in order to create these vectors. """ mutable struct CgneSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S p :: S Aᴴz :: S @@ -1101,25 +1147,25 @@ mutable struct CgneSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CgneSolver(n, m, S) +function CgneSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - p = S(undef, m) - Aᴴz = S(undef, m) - r = S(undef, n) - q = S(undef, n) + x = S(undef, n) + p = S(undef, n) + Aᴴz = S(undef, n) + r = S(undef, m) + q = S(undef, m) s = S(undef, 0) z = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CgneSolver{T,FC,S}(x, p, Aᴴz, r, q, s, z, stats) + solver = CgneSolver{T,FC,S}(m, n, x, p, Aᴴz, r, q, s, z, stats) return solver end function CgneSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CgneSolver(n, m, S) + CgneSolver(m, n, S) end """ @@ -1127,12 +1173,14 @@ Type for storing the vectors required by the in-place version of CRMR. The outer constructors - solver = CrmrSolver(n, m, S) + solver = CrmrSolver(m, n, S) solver = CrmrSolver(A, b) may be used in order to create these vectors. """ mutable struct CrmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S p :: S Aᴴr :: S @@ -1143,25 +1191,25 @@ mutable struct CrmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CrmrSolver(n, m, S) +function CrmrSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - p = S(undef, m) - Aᴴr = S(undef, m) - r = S(undef, n) - q = S(undef, n) + x = S(undef, n) + p = S(undef, n) + Aᴴr = S(undef, n) + r = S(undef, m) + q = S(undef, m) Nq = S(undef, 0) s = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CrmrSolver{T,FC,S}(x, p, Aᴴr, r, q, Nq, s, stats) + solver = CrmrSolver{T,FC,S}(m, n, x, p, Aᴴr, r, q, Nq, s, stats) return solver end function CrmrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CrmrSolver(n, m, S) + CrmrSolver(m, n, S) end """ @@ -1169,12 +1217,14 @@ Type for storing the vectors required by the in-place version of LSLQ. The outer constructors - solver = LslqSolver(n, m, S) + solver = LslqSolver(m, n, S) solver = LslqSolver(A, b) may be used in order to create these vectors. """ mutable struct LslqSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S Nv :: S Aᴴu :: S @@ -1187,27 +1237,27 @@ mutable struct LslqSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: LSLQStats{T} end -function LslqSolver(n, m, S; window :: Int=5) +function LslqSolver(m, n, S; window :: Int=5) FC = eltype(S) T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᴴu = S(undef, m) - w̄ = S(undef, m) - Mu = S(undef, n) - Av = S(undef, n) + x = S(undef, n) + Nv = S(undef, n) + Aᴴu = S(undef, n) + w̄ = S(undef, n) + Mu = S(undef, m) + Av = S(undef, m) u = S(undef, 0) v = S(undef, 0) err_vec = zeros(T, window) stats = LSLQStats(0, false, false, T[], T[], T[], false, T[], T[], "unknown") - solver = LslqSolver{T,FC,S}(x, Nv, Aᴴu, w̄, Mu, Av, u, v, err_vec, stats) + solver = LslqSolver{T,FC,S}(m, n, x, Nv, Aᴴu, w̄, Mu, Av, u, v, err_vec, stats) return solver end function LslqSolver(A, b; window :: Int=5) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - LslqSolver(n, m, S, window=window) + LslqSolver(m, n, S, window=window) end """ @@ -1215,12 +1265,14 @@ Type for storing the vectors required by the in-place version of LSQR. The outer constructors - solver = LsqrSolver(n, m, S) + solver = LsqrSolver(m, n, S) solver = LsqrSolver(A, b) may be used in order to create these vectors. """ mutable struct LsqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S Nv :: S Aᴴu :: S @@ -1233,27 +1285,27 @@ mutable struct LsqrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function LsqrSolver(n, m, S; window :: Int=5) +function LsqrSolver(m, n, S; window :: Int=5) FC = eltype(S) T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᴴu = S(undef, m) - w = S(undef, m) - Mu = S(undef, n) - Av = S(undef, n) + x = S(undef, n) + Nv = S(undef, n) + Aᴴu = S(undef, n) + w = S(undef, n) + Mu = S(undef, m) + Av = S(undef, m) u = S(undef, 0) v = S(undef, 0) err_vec = zeros(T, window) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = LsqrSolver{T,FC,S}(x, Nv, Aᴴu, w, Mu, Av, u, v, err_vec, stats) + solver = LsqrSolver{T,FC,S}(m, n, x, Nv, Aᴴu, w, Mu, Av, u, v, err_vec, stats) return solver end function LsqrSolver(A, b; window :: Int=5) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - LsqrSolver(n, m, S, window=window) + LsqrSolver(m, n, S, window=window) end """ @@ -1261,12 +1313,14 @@ Type for storing the vectors required by the in-place version of LSMR. The outer constructors - solver = LsmrSolver(n, m, S) + solver = LsmrSolver(m, n, S) solver = LsmrSolver(A, b) may be used in order to create these vectors. """ mutable struct LsmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S Nv :: S Aᴴu :: S @@ -1280,28 +1334,28 @@ mutable struct LsmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: LsmrStats{T} end -function LsmrSolver(n, m, S; window :: Int=5) +function LsmrSolver(m, n, S; window :: Int=5) FC = eltype(S) T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᴴu = S(undef, m) - h = S(undef, m) - hbar = S(undef, m) - Mu = S(undef, n) - Av = S(undef, n) + x = S(undef, n) + Nv = S(undef, n) + Aᴴu = S(undef, n) + h = S(undef, n) + hbar = S(undef, n) + Mu = S(undef, m) + Av = S(undef, m) u = S(undef, 0) v = S(undef, 0) err_vec = zeros(T, window) stats = LsmrStats(0, false, false, T[], T[], zero(T), zero(T), zero(T), zero(T), zero(T), "unknown") - solver = LsmrSolver{T,FC,S}(x, Nv, Aᴴu, h, hbar, Mu, Av, u, v, err_vec, stats) + solver = LsmrSolver{T,FC,S}(m, n, x, Nv, Aᴴu, h, hbar, Mu, Av, u, v, err_vec, stats) return solver end function LsmrSolver(A, b; window :: Int=5) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - LsmrSolver(n, m, S, window=window) + LsmrSolver(m, n, S, window=window) end """ @@ -1309,12 +1363,14 @@ Type for storing the vectors required by the in-place version of LNLQ. The outer constructors - solver = LnlqSolver(n, m, S) + solver = LnlqSolver(m, n, S) solver = LnlqSolver(A, b) may be used in order to create these vectors. """ mutable struct LnlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S Nv :: S Aᴴu :: S @@ -1328,28 +1384,28 @@ mutable struct LnlqSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: LNLQStats{T} end -function LnlqSolver(n, m, S) +function LnlqSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᴴu = S(undef, m) - y = S(undef, n) - w̄ = S(undef, n) - Mu = S(undef, n) - Av = S(undef, n) + x = S(undef, n) + Nv = S(undef, n) + Aᴴu = S(undef, n) + y = S(undef, m) + w̄ = S(undef, m) + Mu = S(undef, m) + Av = S(undef, m) u = S(undef, 0) v = S(undef, 0) q = S(undef, 0) stats = LNLQStats(0, false, T[], false, T[], T[], "unknown") - solver = LnlqSolver{T,FC,S}(x, Nv, Aᴴu, y, w̄, Mu, Av, u, v, q, stats) + solver = LnlqSolver{T,FC,S}(m, n, x, Nv, Aᴴu, y, w̄, Mu, Av, u, v, q, stats) return solver end function LnlqSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - LnlqSolver(n, m, S) + LnlqSolver(m, n, S) end """ @@ -1357,12 +1413,14 @@ Type for storing the vectors required by the in-place version of CRAIG. The outer constructors - solver = CraigSolver(n, m, S) + solver = CraigSolver(m, n, S) solver = CraigSolver(A, b) may be used in order to create these vectors. """ mutable struct CraigSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S Nv :: S Aᴴu :: S @@ -1376,28 +1434,28 @@ mutable struct CraigSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CraigSolver(n, m, S) +function CraigSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᴴu = S(undef, m) - y = S(undef, n) - w = S(undef, n) - Mu = S(undef, n) - Av = S(undef, n) + x = S(undef, n) + Nv = S(undef, n) + Aᴴu = S(undef, n) + y = S(undef, m) + w = S(undef, m) + Mu = S(undef, m) + Av = S(undef, m) u = S(undef, 0) v = S(undef, 0) w2 = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CraigSolver{T,FC,S}(x, Nv, Aᴴu, y, w, Mu, Av, u, v, w2, stats) + solver = CraigSolver{T,FC,S}(m, n, x, Nv, Aᴴu, y, w, Mu, Av, u, v, w2, stats) return solver end function CraigSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CraigSolver(n, m, S) + CraigSolver(m, n, S) end """ @@ -1405,12 +1463,14 @@ Type for storing the vectors required by the in-place version of CRAIGMR. The outer constructors - solver = CraigmrSolver(n, m, S) + solver = CraigmrSolver(m, n, S) solver = CraigmrSolver(A, b) may be used in order to create these vectors. """ mutable struct CraigmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int x :: S Nv :: S Aᴴu :: S @@ -1426,30 +1486,30 @@ mutable struct CraigmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function CraigmrSolver(n, m, S) +function CraigmrSolver(m, n, S) FC = eltype(S) T = real(FC) - x = S(undef, m) - Nv = S(undef, m) - Aᴴu = S(undef, m) - d = S(undef, m) - y = S(undef, n) - Mu = S(undef, n) - w = S(undef, n) - wbar = S(undef, n) - Av = S(undef, n) + x = S(undef, n) + Nv = S(undef, n) + Aᴴu = S(undef, n) + d = S(undef, n) + y = S(undef, m) + Mu = S(undef, m) + w = S(undef, m) + wbar = S(undef, m) + Av = S(undef, m) u = S(undef, 0) v = S(undef, 0) q = S(undef, 0) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = CraigmrSolver{T,FC,S}(x, Nv, Aᴴu, d, y, Mu, w, wbar, Av, u, v, q, stats) + solver = CraigmrSolver{T,FC,S}(m, n, x, Nv, Aᴴu, d, y, Mu, w, wbar, Av, u, v, q, stats) return solver end function CraigmrSolver(A, b) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - CraigmrSolver(n, m, S) + CraigmrSolver(m, n, S) end """ @@ -1457,13 +1517,15 @@ Type for storing the vectors required by the in-place version of GMRES. The outer constructors - solver = GmresSolver(n, m, memory, S) + solver = GmresSolver(m, n, memory, S) solver = GmresSolver(A, b, memory = 20) may be used in order to create these vectors. `memory` is set to `n` if the value given is larger than `n`. """ mutable struct GmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S w :: S @@ -1479,8 +1541,8 @@ mutable struct GmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function GmresSolver(n, m, memory, S) - memory = min(n, memory) +function GmresSolver(m, n, memory, S) + memory = min(m, memory) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -1494,14 +1556,14 @@ function GmresSolver(n, m, memory, S) z = Vector{FC}(undef, memory) R = Vector{FC}(undef, div(memory * (memory+1), 2)) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = GmresSolver{T,FC,S}(Δx, x, w, p, q, V, c, s, z, R, false, 0, stats) + solver = GmresSolver{T,FC,S}(m, n, Δx, x, w, p, q, V, c, s, z, R, false, 0, stats) return solver end function GmresSolver(A, b, memory = 20) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - GmresSolver(n, m, memory, S) + GmresSolver(m, n, memory, S) end """ @@ -1509,13 +1571,15 @@ Type for storing the vectors required by the in-place version of FGMRES. The outer constructors - solver = FgmresSolver(n, m, memory, S) + solver = FgmresSolver(m, n, memory, S) solver = FgmresSolver(A, b, memory = 20) may be used in order to create these vectors. `memory` is set to `n` if the value given is larger than `n`. """ mutable struct FgmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S w :: S @@ -1531,8 +1595,8 @@ mutable struct FgmresSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function FgmresSolver(n, m, memory, S) - memory = min(n, memory) +function FgmresSolver(m, n, memory, S) + memory = min(m, memory) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -1546,14 +1610,14 @@ function FgmresSolver(n, m, memory, S) z = Vector{FC}(undef, memory) R = Vector{FC}(undef, div(memory * (memory+1), 2)) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = FgmresSolver{T,FC,S}(Δx, x, w, q, V, Z, c, s, z, R, false, 0, stats) + solver = FgmresSolver{T,FC,S}(m, n, Δx, x, w, q, V, Z, c, s, z, R, false, 0, stats) return solver end function FgmresSolver(A, b, memory = 20) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - FgmresSolver(n, m, memory, S) + FgmresSolver(m, n, memory, S) end """ @@ -1561,13 +1625,15 @@ Type for storing the vectors required by the in-place version of FOM. The outer constructors - solver = FomSolver(n, m, memory, S) + solver = FomSolver(m, n, memory, S) solver = FomSolver(A, b, memory = 20) may be used in order to create these vectors. `memory` is set to `n` if the value given is larger than `n`. """ mutable struct FomSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int Δx :: S x :: S w :: S @@ -1581,8 +1647,8 @@ mutable struct FomSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function FomSolver(n, m, memory, S) - memory = min(n, memory) +function FomSolver(m, n, memory, S) + memory = min(m, memory) FC = eltype(S) T = real(FC) Δx = S(undef, 0) @@ -1595,14 +1661,14 @@ function FomSolver(n, m, memory, S) z = Vector{FC}(undef, memory) U = Vector{FC}(undef, div(memory * (memory+1), 2)) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = FomSolver{T,FC,S}(Δx, x, w, p, q, V, l, z, U, false, stats) + solver = FomSolver{T,FC,S}(m, n, Δx, x, w, p, q, V, l, z, U, false, stats) return solver end function FomSolver(A, b, memory = 20) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - FomSolver(n, m, memory, S) + FomSolver(m, n, memory, S) end """ @@ -1610,13 +1676,15 @@ Type for storing the vectors required by the in-place version of GPMR. The outer constructors - solver = GpmrSolver(n, m, memory, S) + solver = GpmrSolver(m, n, memory, S) solver = GpmrSolver(A, b, memory = 20) may be used in order to create these vectors. `memory` is set to `n + m` if the value given is larger than `n + m`. """ mutable struct GpmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} + m :: Int + n :: Int wA :: S wB :: S dA :: S @@ -1637,35 +1705,35 @@ mutable struct GpmrSolver{T,FC,S} <: KrylovSolver{T,FC,S} stats :: SimpleStats{T} end -function GpmrSolver(n, m, memory, S) +function GpmrSolver(m, n, memory, S) memory = min(n + m, memory) FC = eltype(S) T = real(FC) wA = S(undef, 0) wB = S(undef, 0) - dA = S(undef, n) - dB = S(undef, m) + dA = S(undef, m) + dB = S(undef, n) Δx = S(undef, 0) Δy = S(undef, 0) - x = S(undef, n) - y = S(undef, m) + x = S(undef, m) + y = S(undef, n) q = S(undef, 0) p = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] - U = [S(undef, m) for i = 1 : memory] + V = [S(undef, m) for i = 1 : memory] + U = [S(undef, n) for i = 1 : memory] gs = Vector{FC}(undef, 4 * memory) gc = Vector{T}(undef, 4 * memory) zt = Vector{FC}(undef, 2 * memory) - R = Vector{FC}(undef, memory * (2memory + 1)) + R = Vector{FC}(undef, memory * (2 * memory + 1)) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") - solver = GpmrSolver{T,FC,S}(wA, wB, dA, dB, Δx, Δy, x, y, q, p, V, U, gs, gc, zt, R, false, stats) + solver = GpmrSolver{T,FC,S}(m, n, wA, wB, dA, dB, Δx, Δy, x, y, q, p, V, U, gs, gc, zt, R, false, stats) return solver end function GpmrSolver(A, b, memory = 20) - n, m = size(A) + m, n = size(A) S = ktypeof(b) - GpmrSolver(n, m, memory, S) + GpmrSolver(m, n, memory, S) end """ @@ -1762,25 +1830,30 @@ for (KS, fun, nsol, nA, nAt, warm_start) in [ (GpmrSolver , :gpmr! , 2, 1, 0, true ) ] @eval begin - @inline solve!(solver :: $KS, args...; kwargs...) = $(fun)(solver, args...; kwargs...) - @inline statistics(solver :: $KS) = solver.stats - @inline niterations(solver :: $KS) = solver.stats.niter - @inline Aprod(solver :: $KS) = $nA * solver.stats.niter - @inline Atprod(solver :: $KS) = $nAt * solver.stats.niter + size(solver :: $KS) = solver.m, solver.n + solve!(solver :: $KS, args...; kwargs...) = $(fun)(solver, args...; kwargs...) + statistics(solver :: $KS) = solver.stats + niterations(solver :: $KS) = solver.stats.niter + Aprod(solver :: $KS) = $nA * solver.stats.niter + Atprod(solver :: $KS) = $nAt * solver.stats.niter if $KS == GpmrSolver - @inline Bprod(solver :: $KS) = solver.stats.niter + Bprod(solver :: $KS) = solver.stats.niter + end + nsolution(solver :: $KS) = $nsol + if $nsol == 1 + solution(solver :: $KS) = solver.x + solution(solver :: $KS, p :: Integer) = (p == 1) ? solution(solver) : error("solution(solver) has only one output.") + end + if $nsol == 2 + solution(solver :: $KS) = solver.x, solver.y + solution(solver :: $KS, p :: Integer) = (1 ≤ p ≤ 2) ? solution(solver)[p] : error("solution(solver) has only two outputs.") end - @inline nsolution(solver :: $KS) = $nsol - ($nsol == 1) && @inline solution(solver :: $KS) = solver.x - ($nsol == 2) && @inline solution(solver :: $KS) = solver.x, solver.y - ($nsol == 1) && @inline solution(solver :: $KS, p :: Integer) = (p == 1) ? solution(solver) : error("solution(solver) has only one output.") - ($nsol == 2) && @inline solution(solver :: $KS, p :: Integer) = (1 ≤ p ≤ 2) ? solution(solver)[p] : error("solution(solver) has only two outputs.") if $KS ∈ (BilqrSolver, TrilqrSolver) - @inline issolved_primal(solver :: $KS) = solver.stats.solved_primal - @inline issolved_dual(solver :: $KS) = solver.stats.solved_dual - @inline issolved(solver :: $KS) = issolved_primal(solver) && issolved_dual(solver) + issolved_primal(solver :: $KS) = solver.stats.solved_primal + issolved_dual(solver :: $KS) = solver.stats.solved_dual + issolved(solver :: $KS) = issolved_primal(solver) && issolved_dual(solver) else - @inline issolved(solver :: $KS) = solver.stats.solved + issolved(solver :: $KS) = solver.stats.solved end if $warm_start if $KS in (BilqrSolver, TrilqrSolver, TricgSolver, TrimrSolver, GpmrSolver) @@ -1830,7 +1903,7 @@ function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^18) Printf.format(io, format, "Attribute", "Type", "Size") @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^18) - for i=1:fieldcount(workspace)-1 # show stats seperately + for i=3:fieldcount(workspace)-1 # show m, n and stats seperately type_i = fieldtype(workspace, i) name_i = fieldname(workspace, i) len = if type_i <: AbstractVector diff --git a/src/minres.jl b/src/minres.jl index 1abe7160f..9887fe886 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -114,7 +114,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax :: Int=0, conlim :: T=1/√eps(T), verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf("MINRES: system of size %d\n", n) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 59ac6c77f..2866d9d52 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -95,7 +95,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("MINRES-QLP: system of size %d\n", n) diff --git a/src/qmr.jl b/src/qmr.jl index c6eed1aa6..05e52f8fb 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -95,7 +95,7 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst itmax :: Int=0, verbose :: Int=0, history :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} - n, m = size(A) + m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf("QMR: system of size %d\n", n) diff --git a/test/test_processes.jl b/test/test_processes.jl index 7411269bb..40825b29a 100644 --- a/test/test_processes.jl +++ b/test/test_processes.jl @@ -16,8 +16,8 @@ function permutation_paige(k) end @testset "processes" begin - n = 250 - m = 500 + m = 250 + n = 500 k = 20 for FC in (Float64, ComplexF64) @@ -74,7 +74,7 @@ end end @testset "Golub-Kahan" begin - A, b = under_consistent(n, m, FC=FC) + A, b = under_consistent(m, n, FC=FC) V, U, L = golub_kahan(A, b, k) B = L[1:k+1,1:k] @@ -83,16 +83,16 @@ end @test A' * A * V[:,1:k] ≈ V * L' * B @test A * A' * U[:,1:k] ≈ U * B * L[1:k,1:k]' - storage_golub_kahan_bytes(n, m, k) = 3*(k+1) * nbits_I + (2k+1) * nbits_R + (n+m)*(k+1) * nbits_FC + storage_golub_kahan_bytes(m, n, k) = 3*(k+1) * nbits_I + (2k+1) * nbits_R + (n+m)*(k+1) * nbits_FC - expected_golub_kahan_bytes = storage_golub_kahan_bytes(n, m, k) + expected_golub_kahan_bytes = storage_golub_kahan_bytes(m, n, k) actual_golub_kahan_bytes = @allocated golub_kahan(A, b, k) @test expected_golub_kahan_bytes ≤ actual_golub_kahan_bytes ≤ 1.02 * expected_golub_kahan_bytes end @testset "Saunders-Simon-Yip" begin - A, b = under_consistent(n, m, FC=FC) - _, c = over_consistent(m, n, FC=FC) + A, b = under_consistent(m, n, FC=FC) + _, c = over_consistent(n, m, FC=FC) V, T, U, Tᴴ = saunders_simon_yip(A, b, c, k) @test T[1:k,1:k] ≈ Tᴴ[1:k,1:k]' @@ -101,24 +101,24 @@ end @test A' * A * U[:,1:k-1] ≈ U * Tᴴ * T[1:k,1:k-1] @test A * A' * V[:,1:k-1] ≈ V * T * Tᴴ[1:k,1:k-1] - K = [zeros(FC,n,n) A; A' zeros(FC,m,m)] + K = [zeros(FC,m,m) A; A' zeros(FC,n,n)] Pₖ = permutation_paige(k) - Wₖ = [V[:,1:k] zeros(FC,n,k); zeros(FC,m,k) U[:,1:k]] * Pₖ + Wₖ = [V[:,1:k] zeros(FC,m,k); zeros(FC,n,k) U[:,1:k]] * Pₖ Pₖ₊₁ = permutation_paige(k+1) - Wₖ₊₁ = [V zeros(FC,n,k+1); zeros(FC,m,k+1) U] * Pₖ₊₁ + Wₖ₊₁ = [V zeros(FC,m,k+1); zeros(FC,n,k+1) U] * Pₖ₊₁ G = Pₖ₊₁' * [zeros(FC,k+1,k) T; Tᴴ zeros(FC,k+1,k)] * Pₖ @test K * Wₖ ≈ Wₖ₊₁ * G - storage_saunders_simon_yip_bytes(n, m, k) = 4k * nbits_I + (6k-2) * nbits_FC + (n+m)*(k+1) * nbits_FC + storage_saunders_simon_yip_bytes(m, n, k) = 4k * nbits_I + (6k-2) * nbits_FC + (n+m)*(k+1) * nbits_FC - expected_saunders_simon_yip_bytes = storage_saunders_simon_yip_bytes(n, m, k) + expected_saunders_simon_yip_bytes = storage_saunders_simon_yip_bytes(m, n, k) actual_saunders_simon_yip_bytes = @allocated saunders_simon_yip(A, b, c, k) @test expected_saunders_simon_yip_bytes ≤ actual_saunders_simon_yip_bytes ≤ 1.02 * expected_saunders_simon_yip_bytes end @testset "Montoison-Orban" begin - A, b = under_consistent(n, m, FC=FC) - B, c = over_consistent(m, n, FC=FC) + A, b = under_consistent(m, n, FC=FC) + B, c = over_consistent(n, m, FC=FC) V, H, U, F = montoison_orban(A, B, b, c, k) @test A * U[:,1:k] ≈ V * H @@ -126,20 +126,20 @@ end @test B * A * U[:,1:k-1] ≈ U * F * H[1:k,1:k-1] @test A * B * V[:,1:k-1] ≈ V * H * F[1:k,1:k-1] - K = [zeros(FC,n,n) A; B zeros(FC,m,m)] + K = [zeros(FC,m,m) A; B zeros(FC,n,n)] Pₖ = permutation_paige(k) - Wₖ = [V[:,1:k] zeros(FC,n,k); zeros(FC,m,k) U[:,1:k]] * Pₖ + Wₖ = [V[:,1:k] zeros(FC,m,k); zeros(FC,n,k) U[:,1:k]] * Pₖ Pₖ₊₁ = permutation_paige(k+1) - Wₖ₊₁ = [V zeros(FC,n,k+1); zeros(FC,m,k+1) U] * Pₖ₊₁ + Wₖ₊₁ = [V zeros(FC,m,k+1); zeros(FC,n,k+1) U] * Pₖ₊₁ G = Pₖ₊₁' * [zeros(FC,k+1,k) H; F zeros(FC,k+1,k)] * Pₖ @test K * Wₖ ≈ Wₖ₊₁ * G - function storage_montoison_orban_bytes(n, m, k) + function storage_montoison_orban_bytes(m, n, k) nnz = div(k*(k+1), 2) + k return (nnz + k+1) * nbits_I + 2*nnz * nbits_FC + (n+m)*(k+1) * nbits_FC end - expected_montoison_orban_bytes = storage_montoison_orban_bytes(n, m, k) + expected_montoison_orban_bytes = storage_montoison_orban_bytes(m, n, k) actual_montoison_orban_bytes = @allocated montoison_orban(A, B, b, c, k) @test expected_montoison_orban_bytes ≤ actual_montoison_orban_bytes ≤ 1.02 * expected_montoison_orban_bytes end diff --git a/test/test_solvers.jl b/test/test_solvers.jl index a706cf3d0..7f36af586 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -45,7 +45,7 @@ function test_solvers(FC) tricg_solver = $(KRYLOV_SOLVERS[:tricg])($m, $n, $S) trimr_solver = $(KRYLOV_SOLVERS[:trimr])($m, $n, $S) gpmr_solver = $(KRYLOV_SOLVERS[:gpmr])($n, $m, $mem, $S) - cg_lanczos_shift_solver = $(KRYLOV_SOLVERS[:cg_lanczos_shift])($n, $m, $nshifts, $S) + cg_lanczos_shift_solver = $(KRYLOV_SOLVERS[:cg_lanczos_shift])($n, $n, $nshifts, $S) end for i = 1 : 3 From ffc9329eadd71005dd82515cae50a657a8a6f197 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 7 Oct 2022 17:41:56 -0400 Subject: [PATCH 066/182] [KrylovSolvers] Improve GPU support --- src/bicgstab.jl | 4 ++-- src/bilq.jl | 4 ++-- src/bilqr.jl | 4 ++-- src/cg.jl | 2 +- src/cg_lanczos.jl | 2 +- src/cg_lanczos_shift.jl | 2 +- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 4 ++-- src/cr.jl | 2 +- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 4 ++-- src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 4 ++-- src/symmlq.jl | 2 +- src/tricg.jl | 4 ++-- src/trilqr.jl | 4 ++-- src/trimr.jl | 4 ++-- src/usymlq.jl | 4 ++-- src/usymqr.jl | 4 ++-- test/gpu/amd.jl | 4 ++++ test/gpu/gpu.jl | 23 ++++++++++++++++------- test/gpu/intel.jl | 4 ++++ test/gpu/metal.jl | 4 ++++ test/gpu/nvidia.jl | 4 ++++ 38 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 789aacced..4d234234e 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -112,8 +112,8 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :t , S, n) diff --git a/src/bilq.jl b/src/bilq.jl index 2bb25340a..c26431426 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -96,8 +96,8 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/bilqr.jl b/src/bilqr.jl index 4afe76f70..592c5a982 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -102,8 +102,8 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/cg.jl b/src/cg.jl index e2195d388..4d42c8215 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -107,7 +107,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :z, S, n) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 7bdb11f82..a91d52d79 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -99,7 +99,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $T") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 5aa7f94ef..7d6371ddb 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -89,7 +89,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/cgls.jl b/src/cgls.jl index 78b1632e6..fd8d1c82d 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -102,7 +102,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/cgne.jl b/src/cgne.jl index f1e61481d..f70f6967d 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -111,7 +111,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/cgs.jl b/src/cgs.jl index 78c96831e..f9988f911 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -109,8 +109,8 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :vw, S, n) diff --git a/src/cr.jl b/src/cr.jl index e10af4425..88dd1d390 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -106,7 +106,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace allocate_if(!MisI, solver, :Mq, S, n) diff --git a/src/craig.jl b/src/craig.jl index 756d311a4..5ed395199 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -142,7 +142,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/craigmr.jl b/src/craigmr.jl index fc0a38e89..377beb388 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -137,7 +137,7 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/crls.jl b/src/crls.jl index bbfd116cb..71d8be16f 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -93,7 +93,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/crmr.jl b/src/crmr.jl index b7e236950..9f8e7a155 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -109,7 +109,7 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/diom.jl b/src/diom.jl index 9089fa05c..8a18cb26c 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -107,7 +107,7 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :w, S, n) diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 6d08d7292..dd828797c 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -107,7 +107,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :w, S, n) diff --git a/src/fgmres.jl b/src/fgmres.jl index c4f3a9fc7..6d8a839f8 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -109,7 +109,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI , solver, :q , S, n) diff --git a/src/fom.jl b/src/fom.jl index 9a102d456..120a2a37b 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -104,7 +104,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI , solver, :q , S, n) diff --git a/src/gmres.jl b/src/gmres.jl index 9c6a5fd08..bb1f53fac 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -104,7 +104,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI , solver, :q , S, n) diff --git a/src/gpmr.jl b/src/gpmr.jl index 139643c85..c8987a95c 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -143,8 +143,8 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") eltype(B) == FC || error("eltype(B) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Determine λ and μ associated to generalized saddle point systems. gsp && (λ = one(FC) ; μ = zero(FC)) diff --git a/src/lnlq.jl b/src/lnlq.jl index 0611712e2..f6fc5a8e4 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -134,7 +134,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/lslq.jl b/src/lslq.jl index 1caebdd48..6ae9ed3aa 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -176,7 +176,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/lsmr.jl b/src/lsmr.jl index 39bbf3367..49ba609ab 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -141,7 +141,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/lsqr.jl b/src/lsqr.jl index 7dad61896..afd726af5 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -136,7 +136,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/minres.jl b/src/minres.jl index 9887fe886..e775ee6e7 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -124,7 +124,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 2866d9d52..d0e9ca39f 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -105,7 +105,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :vₖ, S, n) diff --git a/src/qmr.jl b/src/qmr.jl index 05e52f8fb..6ac2d61d5 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -102,8 +102,8 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/symmlq.jl b/src/symmlq.jl index a7b984b2b..982b1c0fd 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -105,7 +105,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/tricg.jl b/src/tricg.jl index 578c7d07e..512eacf0b 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -132,8 +132,8 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Determine τ and ν associated to SQD, SPD or SND systems. flip && (τ = -one(T) ; ν = one(T)) diff --git a/src/trilqr.jl b/src/trilqr.jl index ab231e42f..a31e0a950 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -101,8 +101,8 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/trimr.jl b/src/trimr.jl index 82e22b6cf..dcc64cbbe 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -135,8 +135,8 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Determine τ and ν associated to SQD, SPD or SND systems. flip && (τ = -one(T) ; ν = one(T)) diff --git a/src/usymlq.jl b/src/usymlq.jl index 357498973..842e5cb0c 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -110,8 +110,8 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/usymqr.jl b/src/usymqr.jl index 7705d0d7f..49a1e000f 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -107,8 +107,8 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) == S || error("ktypeof(b) ≠ $S") - ktypeof(c) == S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(c) <: S || error("ktypeof(c) ≠ $S") # Compute the adjoint of A Aᴴ = A' diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index beec647b8..a5852c434 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -97,5 +97,9 @@ include("gpu.jl") # @testset "processes -- $FC" begin # test_processes(S, M) # end + + @testset "solver -- $FC" begin + test_solver(S, M) + end end end diff --git a/test/gpu/gpu.jl b/test/gpu/gpu.jl index e0ca265b7..5d62de982 100644 --- a/test/gpu/gpu.jl +++ b/test/gpu/gpu.jl @@ -4,8 +4,8 @@ using Krylov include("../test_utils.jl") function test_processes(S, M) - n = 250 - m = 500 + m = 250 + n = 500 k = 20 FC = eltype(S) @@ -22,17 +22,26 @@ function test_processes(S, M) gpu_A, gpu_b = M(cpu_A), S(cpu_b) V, H = arnoldi(gpu_A, gpu_b, k) - cpu_A, cpu_b = under_consistent(n, m, FC=FC) + cpu_A, cpu_b = under_consistent(m, n, FC=FC) gpu_A, gpu_b = M(cpu_A), S(cpu_b) V, U, L = golub_kahan(gpu_A, gpu_b, k) - cpu_A, cpu_b = under_consistent(n, m, FC=FC) - _, cpu_c = over_consistent(m, n, FC=FC) + cpu_A, cpu_b = under_consistent(m, n, FC=FC) + _, cpu_c = over_consistent(n, m, FC=FC) gpu_A, gpu_b, gpu_c = M(cpu_A), S(cpu_b), S(cpu_c) V, T, U, Tᴴ = saunders_simon_yip(gpu_A, gpu_b, gpu_c, k) - cpu_A, cpu_b = under_consistent(n, m, FC=FC) - cpu_B, cpu_c = over_consistent(m, n, FC=FC) + cpu_A, cpu_b = under_consistent(m, n, FC=FC) + cpu_B, cpu_c = over_consistent(n, m, FC=FC) gpu_A, gpu_B, gpu_b, gpu_c = M(cpu_A), M(cpu_B), S(cpu_b), S(cpu_c) V, H, U, F = montoison_orban(gpu_A, gpu_B, gpu_b, gpu_c, k) end + +function test_solver(S, M) + n = 10 + A = M(undef, n, n) + b = S(undef, n) + FC = eltype(S) + solver = GmresSolver(n, n, S) + solve!(solver, A, b) # Test that we don't have errors +end diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index e65a2bf47..5b8e88209 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -105,5 +105,9 @@ end # @testset "processes -- $FC" begin # test_processes(S, M) # end + + @testset "solver -- $FC" begin + test_solver(S, M) + end end end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 9fd24392a..15720bbfc 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -109,5 +109,9 @@ end # @testset "processes -- $FC" begin # test_processes(S, M) # end + + @testset "solver -- $FC" begin + test_solver(S, M) + end end end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index b27ee11d8..32c1dd519 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -173,5 +173,9 @@ include("gpu.jl") @testset "processes -- $FC" begin test_processes(S, M) end + + @testset "solver -- $FC" begin + test_solver(S, M) + end end end From c7902ed0a35b7ddd3381b7113c86c7adccdc1a09 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 7 Oct 2022 18:09:20 -0400 Subject: [PATCH 067/182] Update gpu.jl --- test/gpu/gpu.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/gpu/gpu.jl b/test/gpu/gpu.jl index 5d62de982..4d934e9e5 100644 --- a/test/gpu/gpu.jl +++ b/test/gpu/gpu.jl @@ -39,9 +39,9 @@ end function test_solver(S, M) n = 10 + memory = 5 A = M(undef, n, n) b = S(undef, n) - FC = eltype(S) - solver = GmresSolver(n, n, S) + solver = GmresSolver(n, n, memory, S) solve!(solver, A, b) # Test that we don't have errors end From c88e3e2a341cc7de3d2a19bbaa5d8dc12ea72652 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 9 Oct 2022 22:41:23 -0400 Subject: [PATCH 068/182] [GPU] Update the error message --- src/bicgstab.jl | 4 ++-- src/bilq.jl | 4 ++-- src/bilqr.jl | 4 ++-- src/cg.jl | 2 +- src/cg_lanczos.jl | 2 +- src/cg_lanczos_shift.jl | 2 +- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 4 ++-- src/cr.jl | 2 +- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 4 ++-- src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 4 ++-- src/symmlq.jl | 2 +- src/tricg.jl | 4 ++-- src/trilqr.jl | 4 ++-- src/trimr.jl | 4 ++-- src/usymlq.jl | 4 ++-- src/usymqr.jl | 4 ++-- 33 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 4d234234e..588f64838 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -112,8 +112,8 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :t , S, n) diff --git a/src/bilq.jl b/src/bilq.jl index c26431426..002858943 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -96,8 +96,8 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/bilqr.jl b/src/bilqr.jl index 592c5a982..4677e9e9d 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -102,8 +102,8 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/cg.jl b/src/cg.jl index 4d42c8215..09b91e64c 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -107,7 +107,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :z, S, n) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index a91d52d79..b49cdd726 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -99,7 +99,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $T") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 7d6371ddb..a548fe2aa 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -89,7 +89,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/cgls.jl b/src/cgls.jl index fd8d1c82d..ac4bb9b8d 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -102,7 +102,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/cgne.jl b/src/cgne.jl index f70f6967d..50155057c 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -111,7 +111,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/cgs.jl b/src/cgs.jl index f9988f911..9b4eb695f 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -109,8 +109,8 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :vw, S, n) diff --git a/src/cr.jl b/src/cr.jl index 88dd1d390..79f2cd289 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -106,7 +106,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace allocate_if(!MisI, solver, :Mq, S, n) diff --git a/src/craig.jl b/src/craig.jl index 5ed395199..7f87d0861 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -142,7 +142,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/craigmr.jl b/src/craigmr.jl index 377beb388..776a25558 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -137,7 +137,7 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/crls.jl b/src/crls.jl index 71d8be16f..da9471fe2 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -93,7 +93,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/crmr.jl b/src/crmr.jl index 9f8e7a155..b4445ef81 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -109,7 +109,7 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/diom.jl b/src/diom.jl index 8a18cb26c..58986ab47 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -107,7 +107,7 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :w, S, n) diff --git a/src/dqgmres.jl b/src/dqgmres.jl index dd828797c..2dd5b0843 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -107,7 +107,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :w, S, n) diff --git a/src/fgmres.jl b/src/fgmres.jl index 6d8a839f8..b79c197ba 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -109,7 +109,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI , solver, :q , S, n) diff --git a/src/fom.jl b/src/fom.jl index 120a2a37b..4ea3fce92 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -104,7 +104,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI , solver, :q , S, n) diff --git a/src/gmres.jl b/src/gmres.jl index bb1f53fac..ac425054f 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -104,7 +104,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI , solver, :q , S, n) diff --git a/src/gpmr.jl b/src/gpmr.jl index c8987a95c..f94f6e4ac 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -143,8 +143,8 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") eltype(B) == FC || error("eltype(B) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Determine λ and μ associated to generalized saddle point systems. gsp && (λ = one(FC) ; μ = zero(FC)) diff --git a/src/lnlq.jl b/src/lnlq.jl index f6fc5a8e4..b39241a30 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -134,7 +134,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/lslq.jl b/src/lslq.jl index 6ae9ed3aa..7ddc6f5cb 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -176,7 +176,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/lsmr.jl b/src/lsmr.jl index 49ba609ab..bf2df3c8c 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -141,7 +141,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/lsqr.jl b/src/lsqr.jl index afd726af5..bcff1e6e7 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -136,7 +136,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/minres.jl b/src/minres.jl index e775ee6e7..c1d5d5751 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -124,7 +124,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index d0e9ca39f..a1c9fc01b 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -105,7 +105,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :vₖ, S, n) diff --git a/src/qmr.jl b/src/qmr.jl index 6ac2d61d5..cf3328649 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -102,8 +102,8 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/symmlq.jl b/src/symmlq.jl index 982b1c0fd..10d903049 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -105,7 +105,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. allocate_if(!MisI, solver, :v, S, n) diff --git a/src/tricg.jl b/src/tricg.jl index 512eacf0b..f4a3c35f2 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -132,8 +132,8 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Determine τ and ν associated to SQD, SPD or SND systems. flip && (τ = -one(T) ; ν = one(T)) diff --git a/src/trilqr.jl b/src/trilqr.jl index a31e0a950..7aa90d1c9 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -101,8 +101,8 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/trimr.jl b/src/trimr.jl index dcc64cbbe..0905f351e 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -135,8 +135,8 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Determine τ and ν associated to SQD, SPD or SND systems. flip && (τ = -one(T) ; ν = one(T)) diff --git a/src/usymlq.jl b/src/usymlq.jl index 842e5cb0c..d61fc10b0 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -110,8 +110,8 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' diff --git a/src/usymqr.jl b/src/usymqr.jl index 49a1e000f..2cef2db0d 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -107,8 +107,8 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) ≠ $S") - ktypeof(c) <: S || error("ktypeof(c) ≠ $S") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") # Compute the adjoint of A Aᴴ = A' From 4416fa11966b5583b6ea0a6caa41a4c179895938 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 10 Oct 2022 23:19:32 -0400 Subject: [PATCH 069/182] Create a Aqua workflow --- .github/workflows/Aqua.yml | 22 ++++++++++++++++++++++ .github/workflows/Documentation.yml | 2 +- Project.toml | 3 +-- test/aqua.jl | 4 ---- test/runtests.jl | 2 -- 5 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/Aqua.yml delete mode 100644 test/aqua.jl diff --git a/.github/workflows/Aqua.yml b/.github/workflows/Aqua.yml new file mode 100644 index 000000000..d4c00e964 --- /dev/null +++ b/.github/workflows/Aqua.yml @@ -0,0 +1,22 @@ +name: Aqua +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: julia-actions/setup-julia@latest + with: + version: '1' + - name: Aqua.jl + run: julia --color=yes -e ' + using Pkg + Pkg.add("Aqua") + Pkg.develop(path=".") + using Aqua, Krylov + Aqua.test_all(Krylov)' diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index fef36054d..406f15e0d 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -15,7 +15,7 @@ jobs: with: version: '1' - name: Install dependencies - run: julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + run: julia --project=docs --color=yes -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Project.toml b/Project.toml index de944a7f4..74005745f 100644 --- a/Project.toml +++ b/Project.toml @@ -11,9 +11,8 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" julia = "^1.6.0" [extras] -Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "Random", "Test"] +test = ["Random", "Test"] diff --git a/test/aqua.jl b/test/aqua.jl deleted file mode 100644 index 1ecd8f4a9..000000000 --- a/test/aqua.jl +++ /dev/null @@ -1,4 +0,0 @@ -@testset "Aqua" begin - import Aqua - Aqua.test_all(Krylov) -end diff --git a/test/runtests.jl b/test/runtests.jl index 29a9e7ee6..b69865f61 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,6 @@ using Krylov, LinearAlgebra, SparseArrays, Printf, Random, Test import Krylov.KRYLOV_SOLVERS -include("aqua.jl") - include("test_utils.jl") include("test_aux.jl") include("test_stats.jl") From 1072e5b54406fd09d34c5f674c9025627b6103e4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 10 Oct 2022 23:22:32 -0400 Subject: [PATCH 070/182] Fix Aqua workflow --- .github/workflows/Aqua.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Aqua.yml b/.github/workflows/Aqua.yml index d4c00e964..8b1a70487 100644 --- a/.github/workflows/Aqua.yml +++ b/.github/workflows/Aqua.yml @@ -14,7 +14,8 @@ jobs: with: version: '1' - name: Aqua.jl - run: julia --color=yes -e ' + run: | + julia --color=yes -e ' using Pkg Pkg.add("Aqua") Pkg.develop(path=".") From de4b630bee0a53a960c55e40ced4a1f50a94692a Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 10 Oct 2022 23:26:49 -0400 Subject: [PATCH 071/182] Fix again the Aqua workflow --- .github/workflows/Aqua.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/Aqua.yml b/.github/workflows/Aqua.yml index 8b1a70487..da872e225 100644 --- a/.github/workflows/Aqua.yml +++ b/.github/workflows/Aqua.yml @@ -14,10 +14,4 @@ jobs: with: version: '1' - name: Aqua.jl - run: | - julia --color=yes -e ' - using Pkg - Pkg.add("Aqua") - Pkg.develop(path=".") - using Aqua, Krylov - Aqua.test_all(Krylov)' + run: julia --color=yes -e 'using Pkg; Pkg.add("Aqua"); Pkg.develop(path="."); using Aqua, Krylov; Aqua.test_all(Krylov)' From 38bfc22cf6805480eca766231eb751fc287e0b7f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 7 Oct 2022 01:31:38 -0400 Subject: [PATCH 072/182] Better display of Krylov solvers --- src/krylov_solvers.jl | 99 +++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 2cc3197f5..37be1373d 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -8,7 +8,7 @@ GmresSolver, FomSolver, GpmrSolver, FgmresSolver export solve!, solution, nsolution, statistics, issolved, issolved_primal, issolved_dual, niterations, Aprod, Atprod, Bprod, warm_start! -import Base.size +import Base.size, Base.sizeof const KRYLOV_SOLVERS = Dict( :cg => :CgSolver , @@ -1885,6 +1885,50 @@ for (KS, fun, nsol, nA, nAt, warm_start) in [ end end +function ksizeof(attribute) + if isa(attribute, AbstractVector) && !isempty(attribute) + # All vectors inside a vector have the same size in Krylov.jl + size_attribute = length(attribute) * ksizeof(attribute[1]) + else + size_attribute = sizeof(attribute) + end + return size_attribute +end + +function sizeof(solver :: KrylovSolver) + workspace = typeof(solver) + nfields = fieldcount(workspace) + storage = 0 + for i = 1:nfields-1 + field_i = getfield(solver, i) + size_i = ksizeof(field_i) + storage += size_i + end + return storage +end + +function val_metric(val::Int) + metric = "bytes" + if val ≥ 1024 + val /= 1024 + metric = "KB" + if val ≥ 1024 + val /= 1024 + metric = "MB" + if val ≥ 1024 + val /= 1024 + metric = "GB" + if val ≥ 1024 + val /= 1024 + metric = "TB" + end + end + end + val = round(val, digits=2) + end + return string(val, " ", metric) +end + """ show(io, solver; show_stats=true) @@ -1892,38 +1936,37 @@ Statistics of `solver` are displayed if `show_stats` is set to true. """ function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} workspace = typeof(solver) - name_solver = workspace.name.wrapper - l1 = max(length(string(name_solver)), 10) # length("warm_start") = 10 - l2 = length(string(S)) + 8 # length("Vector{}") = 8 + name_solver = string(workspace.name.name) + nbytes = sizeof(solver) + storage = val_metric(nbytes) architecture = S <: Vector ? "CPU" : "GPU" - format = Printf.Format("│%$(l1)s│%$(l2)s│%18s│\n") - format2 = Printf.Format("│%$(l1+1)s│%$(l2)s│%18s│\n") - @printf(io, "┌%s┬%s┬%s┐\n", "─"^l1, "─"^l2, "─"^18) - Printf.format(io, format, name_solver, "Precision: $FC", "Architecture: $architecture") - @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^18) - Printf.format(io, format, "Attribute", "Type", "Size") - @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^18) - for i=3:fieldcount(workspace)-1 # show m, n and stats seperately - type_i = fieldtype(workspace, i) + l1 = max(length(name_solver) + 8, length(string(FC)) + 11) # length("solver: ") = 8 and length("Precision: ") = 11 + l2 = max(ndigits(solver.m) + 7, length(architecture) + 14, length(string(S)) + 8) # length("Vector{}") = 8, # length("Architecture: ") = 14 and length("nrows: ") = 7 + l3 = max(ndigits(solver.n) + 7, length(storage) + 9, 13) # length("Size in bytes") = 13, length("Storage: ") = 9 and length("cols: ") = 7 + format = Printf.Format("│%$(l1)s│%$(l2)s│%$(l3)s│\n") + format2 = Printf.Format("│%$(l1+1)s│%$(l2)s│%$(l3)s│\n") + @printf(io, "┌%s┬%s┬%s┐\n", "─"^l1, "─"^l2, "─"^l3) + Printf.format(io, format, "Solver: $(name_solver)", "nrows: $(solver.m)", "ncols: $(solver.n)") + @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^l3) + Printf.format(io, format, "Precision: $FC", "Architecture: $architecture","Storage: $storage") + @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^l3) + Printf.format(io, format, "Attribute", "Type", "Size in bytes") + @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^l3) + for i=1:fieldcount(workspace)-1 # show stats seperately name_i = fieldname(workspace, i) - len = if type_i <: AbstractVector - field_i = getfield(solver, name_i) - ni = length(field_i) - if eltype(type_i) <: AbstractVector - "$(ni) x $(length(field_i[1]))" - else - length(field_i) - end - else - 0 - end + type_i = fieldtype(workspace, i) + field_i = getfield(solver, name_i) + size_i = ksizeof(field_i) if (name_i in [:w̅, :w̄, :d̅]) && (VERSION < v"1.8.0-DEV") - Printf.format(io, format2, string(name_i), type_i, len) + Printf.format(io, format2, string(name_i), type_i, size_i) else - Printf.format(io, format, string(name_i), type_i, len) + Printf.format(io, format, string(name_i), type_i, size_i) end end - @printf(io, "└%s┴%s┴%s┘\n","─"^l1,"─"^l2,"─"^18) - show_stats && show(io, solver.stats) + @printf(io, "└%s┴%s┴%s┘\n","─"^l1,"─"^l2,"─"^l3) + if show_stats + @printf(io, "\n") + show(io, solver.stats) + end return nothing end From 8a2978b6205f20f632eba2d645a993fdf16a95c9 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 7 Oct 2022 17:23:10 -0400 Subject: [PATCH 073/182] Update test_solvers.jl --- src/krylov_solvers.jl | 52 +- src/krylov_stats.jl | 3 + src/minres.jl | 2 +- test/test_solvers.jl | 1292 ++++------------------------------------- 4 files changed, 148 insertions(+), 1201 deletions(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 37be1373d..c86ecef2d 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -8,7 +8,7 @@ GmresSolver, FomSolver, GpmrSolver, FgmresSolver export solve!, solution, nsolution, statistics, issolved, issolved_primal, issolved_dual, niterations, Aprod, Atprod, Bprod, warm_start! -import Base.size, Base.sizeof +import Base.size, Base.sizeof, Base.format_bytes const KRYLOV_SOLVERS = Dict( :cg => :CgSolver , @@ -1895,40 +1895,18 @@ function ksizeof(attribute) return size_attribute end -function sizeof(solver :: KrylovSolver) - workspace = typeof(solver) - nfields = fieldcount(workspace) +function sizeof(stats_solver :: Union{KrylovStats, KrylovSolver}) + type = typeof(stats_solver) + nfields = fieldcount(type) storage = 0 - for i = 1:nfields-1 - field_i = getfield(solver, i) + for i = 1:nfields + field_i = getfield(stats_solver, i) size_i = ksizeof(field_i) storage += size_i end return storage end -function val_metric(val::Int) - metric = "bytes" - if val ≥ 1024 - val /= 1024 - metric = "KB" - if val ≥ 1024 - val /= 1024 - metric = "MB" - if val ≥ 1024 - val /= 1024 - metric = "GB" - if val ≥ 1024 - val /= 1024 - metric = "TB" - end - end - end - val = round(val, digits=2) - end - return string(val, " ", metric) -end - """ show(io, solver; show_stats=true) @@ -1937,30 +1915,32 @@ Statistics of `solver` are displayed if `show_stats` is set to true. function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} workspace = typeof(solver) name_solver = string(workspace.name.name) + name_stats = string(typeof(solver.stats).name.name) nbytes = sizeof(solver) - storage = val_metric(nbytes) + storage = format_bytes(nbytes) architecture = S <: Vector ? "CPU" : "GPU" - l1 = max(length(name_solver) + 8, length(string(FC)) + 11) # length("solver: ") = 8 and length("Precision: ") = 11 + l1 = max(length(name_solver), length(string(FC)) + 11) # length("Precision: ") = 11 l2 = max(ndigits(solver.m) + 7, length(architecture) + 14, length(string(S)) + 8) # length("Vector{}") = 8, # length("Architecture: ") = 14 and length("nrows: ") = 7 - l3 = max(ndigits(solver.n) + 7, length(storage) + 9, 13) # length("Size in bytes") = 13, length("Storage: ") = 9 and length("cols: ") = 7 + l2 = max(l2, length(name_stats) + 2 + length(string(T))) # length("{}") = 2 + l3 = max(ndigits(solver.n) + 7, length(storage) + 9) # length("Storage: ") = 9 and length("cols: ") = 7 format = Printf.Format("│%$(l1)s│%$(l2)s│%$(l3)s│\n") format2 = Printf.Format("│%$(l1+1)s│%$(l2)s│%$(l3)s│\n") @printf(io, "┌%s┬%s┬%s┐\n", "─"^l1, "─"^l2, "─"^l3) - Printf.format(io, format, "Solver: $(name_solver)", "nrows: $(solver.m)", "ncols: $(solver.n)") + Printf.format(io, format, "$(name_solver)", "nrows: $(solver.m)", "ncols: $(solver.n)") @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^l3) Printf.format(io, format, "Precision: $FC", "Architecture: $architecture","Storage: $storage") @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^l3) - Printf.format(io, format, "Attribute", "Type", "Size in bytes") + Printf.format(io, format, "Attribute", "Type", "Size") @printf(io, "├%s┼%s┼%s┤\n", "─"^l1, "─"^l2, "─"^l3) - for i=1:fieldcount(workspace)-1 # show stats seperately + for i=1:fieldcount(workspace) name_i = fieldname(workspace, i) type_i = fieldtype(workspace, i) field_i = getfield(solver, name_i) size_i = ksizeof(field_i) if (name_i in [:w̅, :w̄, :d̅]) && (VERSION < v"1.8.0-DEV") - Printf.format(io, format2, string(name_i), type_i, size_i) + Printf.format(io, format2, string(name_i), type_i, format_bytes(size_i)) else - Printf.format(io, format, string(name_i), type_i, size_i) + Printf.format(io, format, string(name_i), type_i, format_bytes(size_i)) end end @printf(io, "└%s┴%s┴%s┘\n","─"^l1,"─"^l2,"─"^l3) diff --git a/src/krylov_stats.jl b/src/krylov_stats.jl index a662fa0a0..f99c7863b 100644 --- a/src/krylov_stats.jl +++ b/src/krylov_stats.jl @@ -1,3 +1,6 @@ +export KrylovStats, SimpleStats, LsmrStats, LanczosStats, LanczosShiftStats, +SymmlqStats, AdjointStats, LNLQStats, LSLQStats + "Abstract type for statistics returned by a solver" abstract type KrylovStats{T} end diff --git a/src/minres.jl b/src/minres.jl index c1d5d5751..f2814403c 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -306,7 +306,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; if iter == 1 && β / β₁ ≤ 10 * ϵM # Aᴴb = 0 so x = 0 is a minimum least-squares solution - stats.niter = 0 + stats.niter = 1 stats.solved, stats.inconsistent = true, true stats.status = "x is a minimum least-squares solution" solver.warm_start = false diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 7f36af586..8c1e2975d 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -11,1175 +11,139 @@ function test_solvers(FC) nshifts = 5 T = real(FC) S = Vector{FC} + solvers = Dict{Symbol, KrylovSolver}() @eval begin - cg_solver = $(KRYLOV_SOLVERS[:cg])($n, $n, $S) - symmlq_solver = $(KRYLOV_SOLVERS[:symmlq])($n, $n, $S) - minres_solver = $(KRYLOV_SOLVERS[:minres])($n, $n, $S) - cg_lanczos_solver = $(KRYLOV_SOLVERS[:cg_lanczos])($n, $n, $S) - diom_solver = $(KRYLOV_SOLVERS[:diom])($n, $n, $mem, $S) - fom_solver = $(KRYLOV_SOLVERS[:fom])($n, $n, $mem, $S) - dqgmres_solver = $(KRYLOV_SOLVERS[:dqgmres])($n, $n, $mem, $S) - gmres_solver = $(KRYLOV_SOLVERS[:gmres])($n, $n, $mem, $S) - fgmres_solver = $(KRYLOV_SOLVERS[:fgmres])($n, $n, $mem, $S) - cr_solver = $(KRYLOV_SOLVERS[:cr])($n, $n, $S) - crmr_solver = $(KRYLOV_SOLVERS[:crmr])($m, $n, $S) - cgs_solver = $(KRYLOV_SOLVERS[:cgs])($n, $n, $S) - bicgstab_solver = $(KRYLOV_SOLVERS[:bicgstab])($n, $n, $S) - craigmr_solver = $(KRYLOV_SOLVERS[:craigmr])($m, $n, $S) - cgne_solver = $(KRYLOV_SOLVERS[:cgne])($m, $n, $S) - lnlq_solver = $(KRYLOV_SOLVERS[:lnlq])($m, $n, $S) - craig_solver = $(KRYLOV_SOLVERS[:craig])($m, $n, $S) - lslq_solver = $(KRYLOV_SOLVERS[:lslq])($n, $m, $S) - cgls_solver = $(KRYLOV_SOLVERS[:cgls])($n, $m, $S) - lsqr_solver = $(KRYLOV_SOLVERS[:lsqr])($n, $m, $S) - crls_solver = $(KRYLOV_SOLVERS[:crls])($n, $m, $S) - lsmr_solver = $(KRYLOV_SOLVERS[:lsmr])($n, $m, $S) - usymqr_solver = $(KRYLOV_SOLVERS[:usymqr])($n, $m, $S) - trilqr_solver = $(KRYLOV_SOLVERS[:trilqr])($n, $n, $S) - bilq_solver = $(KRYLOV_SOLVERS[:bilq])($n, $n, $S) - bilqr_solver = $(KRYLOV_SOLVERS[:bilqr])($n, $n, $S) - minres_qlp_solver = $(KRYLOV_SOLVERS[:minres_qlp])($n, $n, $S) - qmr_solver = $(KRYLOV_SOLVERS[:qmr])($n, $n, $S) - usymlq_solver = $(KRYLOV_SOLVERS[:usymlq])($m, $n, $S) - tricg_solver = $(KRYLOV_SOLVERS[:tricg])($m, $n, $S) - trimr_solver = $(KRYLOV_SOLVERS[:trimr])($m, $n, $S) - gpmr_solver = $(KRYLOV_SOLVERS[:gpmr])($n, $m, $mem, $S) - cg_lanczos_shift_solver = $(KRYLOV_SOLVERS[:cg_lanczos_shift])($n, $n, $nshifts, $S) + $solvers[:cg] = $(KRYLOV_SOLVERS[:cg])($n, $n, $S) + $solvers[:symmlq] = $(KRYLOV_SOLVERS[:symmlq])($n, $n, $S) + $solvers[:minres] = $(KRYLOV_SOLVERS[:minres])($n, $n, $S) + $solvers[:cg_lanczos] = $(KRYLOV_SOLVERS[:cg_lanczos])($n, $n, $S) + $solvers[:cg_lanczos_shift] = $(KRYLOV_SOLVERS[:cg_lanczos_shift])($n, $n, $nshifts, $S) + $solvers[:diom] = $(KRYLOV_SOLVERS[:diom])($n, $n, $mem, $S) + $solvers[:fom] = $(KRYLOV_SOLVERS[:fom])($n, $n, $mem, $S) + $solvers[:dqgmres] = $(KRYLOV_SOLVERS[:dqgmres])($n, $n, $mem, $S) + $solvers[:gmres] = $(KRYLOV_SOLVERS[:gmres])($n, $n, $mem, $S) + $solvers[:fgmres] = $(KRYLOV_SOLVERS[:fgmres])($n, $n, $mem, $S) + $solvers[:cr] = $(KRYLOV_SOLVERS[:cr])($n, $n, $S) + $solvers[:crmr] = $(KRYLOV_SOLVERS[:crmr])($m, $n, $S) + $solvers[:cgs] = $(KRYLOV_SOLVERS[:cgs])($n, $n, $S) + $solvers[:bicgstab] = $(KRYLOV_SOLVERS[:bicgstab])($n, $n, $S) + $solvers[:craigmr] = $(KRYLOV_SOLVERS[:craigmr])($m, $n, $S) + $solvers[:cgne] = $(KRYLOV_SOLVERS[:cgne])($m, $n, $S) + $solvers[:lnlq] = $(KRYLOV_SOLVERS[:lnlq])($m, $n, $S) + $solvers[:craig] = $(KRYLOV_SOLVERS[:craig])($m, $n, $S) + $solvers[:lslq] = $(KRYLOV_SOLVERS[:lslq])($n, $m, $S) + $solvers[:cgls] = $(KRYLOV_SOLVERS[:cgls])($n, $m, $S) + $solvers[:lsqr] = $(KRYLOV_SOLVERS[:lsqr])($n, $m, $S) + $solvers[:crls] = $(KRYLOV_SOLVERS[:crls])($n, $m, $S) + $solvers[:lsmr] = $(KRYLOV_SOLVERS[:lsmr])($n, $m, $S) + $solvers[:usymqr] = $(KRYLOV_SOLVERS[:usymqr])($n, $m, $S) + $solvers[:trilqr] = $(KRYLOV_SOLVERS[:trilqr])($n, $n, $S) + $solvers[:bilq] = $(KRYLOV_SOLVERS[:bilq])($n, $n, $S) + $solvers[:bilqr] = $(KRYLOV_SOLVERS[:bilqr])($n, $n, $S) + $solvers[:minres_qlp] = $(KRYLOV_SOLVERS[:minres_qlp])($n, $n, $S) + $solvers[:qmr] = $(KRYLOV_SOLVERS[:qmr])($n, $n, $S) + $solvers[:usymlq] = $(KRYLOV_SOLVERS[:usymlq])($m, $n, $S) + $solvers[:tricg] = $(KRYLOV_SOLVERS[:tricg])($m, $n, $S) + $solvers[:trimr] = $(KRYLOV_SOLVERS[:trimr])($m, $n, $S) + $solvers[:gpmr] = $(KRYLOV_SOLVERS[:gpmr])($n, $m, $mem, $S) + $solvers[:cg_lanczos_shift] = $(KRYLOV_SOLVERS[:cg_lanczos_shift])($n, $n, $nshifts, $S) end - for i = 1 : 3 - A = i * A - Au = i * Au - Ao = i * Ao - b = 5 * b - c = 3 * c - - solver = solve!(cg_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(symmlq_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(minres_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(cg_lanczos_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(cg_lanczos_shift_solver, A, b, shifts) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(diom_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(fom_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(dqgmres_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(gmres_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(fgmres_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(cr_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(crmr_solver, Au, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(cgs_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == 2 * niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(bicgstab_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == 2 * niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(craigmr_solver, Au, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 2 - @test issolved(solver) - - solver = solve!(cgne_solver, Au, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(lnlq_solver, Au, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved(solver) - - solver = solve!(craig_solver, Au, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved(solver) - - solver = solve!(lslq_solver, Ao, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(cgls_solver, Ao, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(lsqr_solver, Ao, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(crls_solver, Ao, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(lsmr_solver, Ao, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(usymqr_solver, Ao, b, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(trilqr_solver, A, b, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved_primal(solver) - @test issolved_dual(solver) - @test issolved(solver) - - solver = solve!(bilq_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(bilqr_solver, A, b, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved_primal(solver) - @test issolved_dual(solver) - @test issolved(solver) - - solver = solve!(minres_qlp_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(qmr_solver, A, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(usymlq_solver, Au, c, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test nsolution(solver) == 1 - @test issolved(solver) - - solver = solve!(tricg_solver, Au, c, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved(solver) - - solver = solve!(trimr_solver, Au, c, b) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved(solver) - - solver = solve!(gpmr_solver, Ao, Au, b, c) - niter = niterations(solver) - @test niter > 0 - @test Aprod(solver) == niter - @test Atprod(solver) == 0 - @test Bprod(solver) == niter - @test statistics(solver) === solver.stats - @test solution(solver, 1) === solver.x - @test solution(solver, 2) === solver.y - @test nsolution(solver) == 2 - @test issolved(solver) + for (method, solver) in solvers + @testset "$(method)" begin + for i = 1 : 3 + A = i * A + Au = i * Au + Ao = i * Ao + b = 5 * b + c = 3 * c + + if method ∈ (:cg, :cr, :symmlq, :minres, :minres_qlp, :cg_lanczos, :diom, :fom, + :dqgmres, :gmres, :fgmres, :cgs, :bicgstab, :bilq, :qmr, :cg_lanczos_shift) + method == :cg_lanczos_shift ? solve!(solver, A, b, shifts) : solve!(solver, A, b) + niter = niterations(solver) + @test Aprod(solver) == (method ∈ (:cgs, :bicgstab) ? 2 * niter : niter) + @test Atprod(solver) == (method ∈ (:bilq, :qmr) ? niter : 0) + @test solution(solver) === solver.x + @test nsolution(solver) == 1 + end + + if method ∈ (:cgne, :crmr, :lnlq, :craig, :craigmr) + solve!(solver, Au, c) + niter = niterations(solver) + @test Aprod(solver) == niter + @test Atprod(solver) == niter + @test solution(solver, 1) === solver.x + @test nsolution(solver) == (method ∈ (:cgne, :crmr) ? 1 : 2) + (nsolution == 2) && (@test solution(solver, 2) == solver.y) + end + + if method ∈ (:cgls, :crls, :lslq, :lsqr, :lsmr) + solve!(solver, Ao, b) + niter = niterations(solver) + @test Aprod(solver) == niter + @test Atprod(solver) == niter + @test solution(solver) === solver.x + @test nsolution(solver) == 1 + end + + if method ∈ (:bilqr, :trilqr) + solve!(solver, A, b, b) + niter = niterations(solver) + @test Aprod(solver) == niter + @test Atprod(solver) == niter + @test solution(solver, 1) === solver.x + @test solution(solver, 2) === solver.y + @test nsolution(solver) == 2 + @test issolved_primal(solver) + @test issolved_dual(solver) + end + + if method ∈ (:tricg, :trimr, :gpmr) + method == :gpmr ? solve!(solver, Ao, Au, b, c) : solve!(solver, Au, c, b) + niter = niterations(solver) + @test Aprod(solver) == niter + method != :gpmr && (@test Atprod(solver) == niter) + method == :gpmr && (@test Bprod(solver) == niter) + @test solution(solver, 1) === solver.x + @test solution(solver, 2) === solver.y + @test nsolution(solver) == 2 + end + + if method ∈ (:usymlq, :usymqr) + method == :usymlq ? solve!(solver, Au, c, b) : solve!(solver, Ao, b, c) + niter = niterations(solver) + @test Aprod(solver) == niter + @test Atprod(solver) == niter + @test solution(solver) === solver.x + @test nsolution(solver) == 1 + end + + @test niter > 0 + @test statistics(solver) === solver.stats + @test issolved(solver) + end + + io = IOBuffer() + show(io, solver, show_stats=false) + showed = String(take!(io)) + + # Test that the lines have the same length + str = split(showed, "\n", keepempty=false) + len_row = length(str[1]) + @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_row, &, str) + + # Test that the columns have the same length + str2 = split(showed, ['│','┌','┬','┐','├','┼','┤','└','┴','┴','┘','\n'], keepempty=false) + len_col1 = length(str2[1]) + len_col2 = length(str2[2]) + len_col3 = length(str2[3]) + @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_col1, &, str2[1:3:end-2]) + @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_col2, &, str2[2:3:end-1]) + @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_col3, &, str2[3:3:end]) + end end - - io = IOBuffer() - show(io, cg_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │ CgSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ r│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Ap│ Vector{$FC}│ 64│ - │ z│ Vector{$FC}│ 0│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, symmlq_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────┬───────────────┬─────────────────┐ - │SymmlqSolver│Precision: $FC │Architecture: CPU│ - ├────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ Mvold│ Vector{$FC}│ 64│ - │ Mv│ Vector{$FC}│ 64│ - │ Mv_next│ Vector{$FC}│ 64│ - │ w̅│ Vector{$FC}│ 64│ - │ v│ Vector{$FC}│ 0│ - │ clist│ Vector{$T}│ 5│ - │ zlist│ Vector{$T}│ 5│ - │ sprod│ Vector{$T}│ 5│ - │ warm_start│ Bool│ 0│ - └────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, minres_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────┬───────────────┬─────────────────┐ - │MinresSolver│Precision: $FC │Architecture: CPU│ - ├────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ r1│ Vector{$FC}│ 64│ - │ r2│ Vector{$FC}│ 64│ - │ w1│ Vector{$FC}│ 64│ - │ w2│ Vector{$FC}│ 64│ - │ y│ Vector{$FC}│ 64│ - │ v│ Vector{$FC}│ 0│ - │ err_vec│ Vector{$T}│ 5│ - │ warm_start│ Bool│ 0│ - └────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, cg_lanczos_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────────┬───────────────┬─────────────────┐ - │CgLanczosSolver│Precision: $FC │Architecture: CPU│ - ├───────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ Mv│ Vector{$FC}│ 64│ - │ Mv_prev│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Mv_next│ Vector{$FC}│ 64│ - │ v│ Vector{$FC}│ 0│ - │ warm_start│ Bool│ 0│ - └───────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, cg_lanczos_shift_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────────────┬───────────────────┬─────────────────┐ - │CgLanczosShiftSolver│ Precision: $FC │Architecture: CPU│ - ├────────────────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────────────┼───────────────────┼─────────────────┤ - │ Mv│ Vector{$FC}│ 64│ - │ Mv_prev│ Vector{$FC}│ 64│ - │ Mv_next│ Vector{$FC}│ 64│ - │ v│ Vector{$FC}│ 0│ - │ x│Vector{Vector{$FC}}│ 5 x 64│ - │ p│Vector{Vector{$FC}}│ 5 x 64│ - │ σ│ Vector{$T}│ 5│ - │ δhat│ Vector{$T}│ 5│ - │ ω│ Vector{$T}│ 5│ - │ γ│ Vector{$T}│ 5│ - │ rNorms│ Vector{$T}│ 5│ - │ converged│ BitVector│ 5│ - │ not_cv│ BitVector│ 5│ - └────────────────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, diom_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────────┬─────────────────┐ - │DiomSolver│ Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ t│ Vector{$FC}│ 64│ - │ z│ Vector{$FC}│ 0│ - │ w│ Vector{$FC}│ 0│ - │ P│Vector{Vector{$FC}}│ 9 x 64│ - │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ L│ Vector{$FC}│ 9│ - │ H│ Vector{$FC}│ 10│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, fom_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────────┬─────────────────┐ - │ FomSolver│ Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ w│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 0│ - │ q│ Vector{$FC}│ 0│ - │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ l│ Vector{$FC}│ 10│ - │ z│ Vector{$FC}│ 10│ - │ U│ Vector{$FC}│ 55│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, dqgmres_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌─────────────┬───────────────────┬─────────────────┐ - │DqgmresSolver│ Precision: $FC │Architecture: CPU│ - ├─────────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├─────────────┼───────────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ t│ Vector{$FC}│ 64│ - │ z│ Vector{$FC}│ 0│ - │ w│ Vector{$FC}│ 0│ - │ P│Vector{Vector{$FC}}│ 10 x 64│ - │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ c│ Vector{$T}│ 10│ - │ s│ Vector{$FC}│ 10│ - │ H│ Vector{$FC}│ 11│ - │ warm_start│ Bool│ 0│ - └─────────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, gmres_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────┬───────────────────┬─────────────────┐ - │GmresSolver│ Precision: $FC │Architecture: CPU│ - ├───────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────┼───────────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ w│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 0│ - │ q│ Vector{$FC}│ 0│ - │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ c│ Vector{$T}│ 10│ - │ s│ Vector{$FC}│ 10│ - │ z│ Vector{$FC}│ 10│ - │ R│ Vector{$FC}│ 55│ - │ warm_start│ Bool│ 0│ - │ inner_iter│ Int64│ 0│ - └───────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, fgmres_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────┬───────────────────┬─────────────────┐ - │FgmresSolver│ Precision: $FC │Architecture: CPU│ - ├────────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────┼───────────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ w│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 0│ - │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ Z│Vector{Vector{$FC}}│ 10 x 64│ - │ c│ Vector{$T}│ 10│ - │ s│ Vector{$FC}│ 10│ - │ z│ Vector{$FC}│ 10│ - │ R│ Vector{$FC}│ 55│ - │ warm_start│ Bool│ 0│ - │ inner_iter│ Int64│ 0│ - └────────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, cr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │ CrSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ r│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ Ar│ Vector{$FC}│ 64│ - │ Mq│ Vector{$FC}│ 0│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, crmr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │CrmrSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Aᴴr│ Vector{$FC}│ 64│ - │ r│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 32│ - │ Nq│ Vector{$FC}│ 0│ - │ s│ Vector{$FC}│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, cgs_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │ CgsSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │Attribute │ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ r│ Vector{$FC}│ 64│ - │ u│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ ts│ Vector{$FC}│ 64│ - │ yz│ Vector{$FC}│ 0│ - │ vw│ Vector{$FC}│ 0│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, bicgstab_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────────┬───────────────┬─────────────────┐ - │BicgstabSolver│Precision: $FC │Architecture: CPU│ - ├──────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ r│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ v│ Vector{$FC}│ 64│ - │ s│ Vector{$FC}│ 64│ - │ qd│ Vector{$FC}│ 64│ - │ yz│ Vector{$FC}│ 0│ - │ t│ Vector{$FC}│ 0│ - │ warm_start│ Bool│ 0│ - └──────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, craigmr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌─────────────┬───────────────┬─────────────────┐ - │CraigmrSolver│Precision: $FC │Architecture: CPU│ - ├─────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├─────────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 64│ - │ Nv│ Vector{$FC}│ 64│ - │ Aᴴu│ Vector{$FC}│ 64│ - │ d│ Vector{$FC}│ 64│ - │ y│ Vector{$FC}│ 32│ - │ Mu│ Vector{$FC}│ 32│ - │ w│ Vector{$FC}│ 32│ - │ wbar│ Vector{$FC}│ 32│ - │ Av│ Vector{$FC}│ 32│ - │ u│ Vector{$FC}│ 0│ - │ v│ Vector{$FC}│ 0│ - │ q│ Vector{$FC}│ 0│ - └─────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, cgne_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │CgneSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Aᴴz│ Vector{$FC}│ 64│ - │ r│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 32│ - │ s│ Vector{$FC}│ 0│ - │ z│ Vector{$FC}│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, lnlq_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │LnlqSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 64│ - │ Nv│ Vector{$FC}│ 64│ - │ Aᴴu│ Vector{$FC}│ 64│ - │ y│ Vector{$FC}│ 32│ - │ w̄│ Vector{$FC}│ 32│ - │ Mu│ Vector{$FC}│ 32│ - │ Av│ Vector{$FC}│ 32│ - │ u│ Vector{$FC}│ 0│ - │ v│ Vector{$FC}│ 0│ - │ q│ Vector{$FC}│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, craig_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────┬───────────────┬─────────────────┐ - │CraigSolver│Precision: $FC │Architecture: CPU│ - ├───────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 64│ - │ Nv│ Vector{$FC}│ 64│ - │ Aᴴu│ Vector{$FC}│ 64│ - │ y│ Vector{$FC}│ 32│ - │ w│ Vector{$FC}│ 32│ - │ Mu│ Vector{$FC}│ 32│ - │ Av│ Vector{$FC}│ 32│ - │ u│ Vector{$FC}│ 0│ - │ v│ Vector{$FC}│ 0│ - │ w2│ Vector{$FC}│ 0│ - └───────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, lslq_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │LslqSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 32│ - │ Nv│ Vector{$FC}│ 32│ - │ Aᴴu│ Vector{$FC}│ 32│ - │ w̄│ Vector{$FC}│ 32│ - │ Mu│ Vector{$FC}│ 64│ - │ Av│ Vector{$FC}│ 64│ - │ u│ Vector{$FC}│ 0│ - │ v│ Vector{$FC}│ 0│ - │ err_vec│ Vector{$T}│ 5│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, cgls_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │CglsSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 32│ - │ p│ Vector{$FC}│ 32│ - │ s│ Vector{$FC}│ 32│ - │ r│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ Mr│ Vector{$FC}│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, lsqr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │LsqrSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 32│ - │ Nv│ Vector{$FC}│ 32│ - │ Aᴴu│ Vector{$FC}│ 32│ - │ w│ Vector{$FC}│ 32│ - │ Mu│ Vector{$FC}│ 64│ - │ Av│ Vector{$FC}│ 64│ - │ u│ Vector{$FC}│ 0│ - │ v│ Vector{$FC}│ 0│ - │ err_vec│ Vector{$T}│ 5│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, crls_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │CrlsSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 32│ - │ p│ Vector{$FC}│ 32│ - │ Ar│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 32│ - │ r│ Vector{$FC}│ 64│ - │ Ap│ Vector{$FC}│ 64│ - │ s│ Vector{$FC}│ 64│ - │ Ms│ Vector{$FC}│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, lsmr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │LsmrSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ x│ Vector{$FC}│ 32│ - │ Nv│ Vector{$FC}│ 32│ - │ Aᴴu│ Vector{$FC}│ 32│ - │ h│ Vector{$FC}│ 32│ - │ hbar│ Vector{$FC}│ 32│ - │ Mu│ Vector{$FC}│ 64│ - │ Av│ Vector{$FC}│ 64│ - │ u│ Vector{$FC}│ 0│ - │ v│ Vector{$FC}│ 0│ - │ err_vec│ Vector{$T}│ 5│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, usymqr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────┬───────────────┬─────────────────┐ - │UsymqrSolver│Precision: $FC │Architecture: CPU│ - ├────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────┼───────────────┼─────────────────┤ - │ vₖ₋₁│ Vector{$FC}│ 64│ - │ vₖ│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 32│ - │ wₖ₋₂│ Vector{$FC}│ 32│ - │ wₖ₋₁│ Vector{$FC}│ 32│ - │ uₖ₋₁│ Vector{$FC}│ 32│ - │ uₖ│ Vector{$FC}│ 32│ - │ p│ Vector{$FC}│ 32│ - │ warm_start│ Bool│ 0│ - └────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, trilqr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────┬───────────────┬─────────────────┐ - │TrilqrSolver│Precision: $FC │Architecture: CPU│ - ├────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────┼───────────────┼─────────────────┤ - │ uₖ₋₁│ Vector{$FC}│ 64│ - │ uₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ d̅│ Vector{$FC}│ 64│ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ vₖ₋₁│ Vector{$FC}│ 64│ - │ vₖ│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ Δy│ Vector{$FC}│ 0│ - │ y│ Vector{$FC}│ 64│ - │ wₖ₋₃│ Vector{$FC}│ 64│ - │ wₖ₋₂│ Vector{$FC}│ 64│ - │ warm_start│ Bool│ 0│ - └────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, bilq_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │BilqSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ uₖ₋₁│ Vector{$FC}│ 64│ - │ uₖ│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ vₖ₋₁│ Vector{$FC}│ 64│ - │ vₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ d̅│ Vector{$FC}│ 64│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, bilqr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────┬───────────────┬─────────────────┐ - │BilqrSolver│Precision: $FC │Architecture: CPU│ - ├───────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────┼───────────────┼─────────────────┤ - │ uₖ₋₁│ Vector{$FC}│ 64│ - │ uₖ│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ vₖ₋₁│ Vector{$FC}│ 64│ - │ vₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ Δy│ Vector{$FC}│ 0│ - │ y│ Vector{$FC}│ 64│ - │ d̅│ Vector{$FC}│ 64│ - │ wₖ₋₃│ Vector{$FC}│ 64│ - │ wₖ₋₂│ Vector{$FC}│ 64│ - │ warm_start│ Bool│ 0│ - └───────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, minres_qlp_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────────┬───────────────┬─────────────────┐ - │MinresQlpSolver│Precision: $FC │Architecture: CPU│ - ├───────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────────┼───────────────┼─────────────────┤ - │ Δx│ Vector{$FC}│ 0│ - │ wₖ₋₁│ Vector{$FC}│ 64│ - │ wₖ│ Vector{$FC}│ 64│ - │ M⁻¹vₖ₋₁│ Vector{$FC}│ 64│ - │ M⁻¹vₖ│ Vector{$FC}│ 64│ - │ x│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ vₖ│ Vector{$FC}│ 0│ - │ warm_start│ Bool│ 0│ - └───────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, qmr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────┬─────────────────┐ - │ QmrSolver│Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────┼─────────────────┤ - │ uₖ₋₁│ Vector{$FC}│ 64│ - │ uₖ│ Vector{$FC}│ 64│ - │ q│ Vector{$FC}│ 64│ - │ vₖ₋₁│ Vector{$FC}│ 64│ - │ vₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ wₖ₋₂│ Vector{$FC}│ 64│ - │ wₖ₋₁│ Vector{$FC}│ 64│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, usymlq_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌────────────┬───────────────┬─────────────────┐ - │UsymlqSolver│Precision: $FC │Architecture: CPU│ - ├────────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├────────────┼───────────────┼─────────────────┤ - │ uₖ₋₁│ Vector{$FC}│ 64│ - │ uₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ Δx│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ d̅│ Vector{$FC}│ 64│ - │ vₖ₋₁│ Vector{$FC}│ 32│ - │ vₖ│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 32│ - │ warm_start│ Bool│ 0│ - └────────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, tricg_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────┬───────────────┬─────────────────┐ - │TricgSolver│Precision: $FC │Architecture: CPU│ - ├───────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────┼───────────────┼─────────────────┤ - │ y│ Vector{$FC}│ 64│ - │ N⁻¹uₖ₋₁│ Vector{$FC}│ 64│ - │ N⁻¹uₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ gy₂ₖ₋₁│ Vector{$FC}│ 64│ - │ gy₂ₖ│ Vector{$FC}│ 64│ - │ x│ Vector{$FC}│ 32│ - │ M⁻¹vₖ₋₁│ Vector{$FC}│ 32│ - │ M⁻¹vₖ│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 32│ - │ gx₂ₖ₋₁│ Vector{$FC}│ 32│ - │ gx₂ₖ│ Vector{$FC}│ 32│ - │ Δx│ Vector{$FC}│ 0│ - │ Δy│ Vector{$FC}│ 0│ - │ uₖ│ Vector{$FC}│ 0│ - │ vₖ│ Vector{$FC}│ 0│ - │ warm_start│ Bool│ 0│ - └───────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, trimr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌───────────┬───────────────┬─────────────────┐ - │TrimrSolver│Precision: $FC │Architecture: CPU│ - ├───────────┼───────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├───────────┼───────────────┼─────────────────┤ - │ y│ Vector{$FC}│ 64│ - │ N⁻¹uₖ₋₁│ Vector{$FC}│ 64│ - │ N⁻¹uₖ│ Vector{$FC}│ 64│ - │ p│ Vector{$FC}│ 64│ - │ gy₂ₖ₋₃│ Vector{$FC}│ 64│ - │ gy₂ₖ₋₂│ Vector{$FC}│ 64│ - │ gy₂ₖ₋₁│ Vector{$FC}│ 64│ - │ gy₂ₖ│ Vector{$FC}│ 64│ - │ x│ Vector{$FC}│ 32│ - │ M⁻¹vₖ₋₁│ Vector{$FC}│ 32│ - │ M⁻¹vₖ│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 32│ - │ gx₂ₖ₋₃│ Vector{$FC}│ 32│ - │ gx₂ₖ₋₂│ Vector{$FC}│ 32│ - │ gx₂ₖ₋₁│ Vector{$FC}│ 32│ - │ gx₂ₖ│ Vector{$FC}│ 32│ - │ Δx│ Vector{$FC}│ 0│ - │ Δy│ Vector{$FC}│ 0│ - │ uₖ│ Vector{$FC}│ 0│ - │ vₖ│ Vector{$FC}│ 0│ - │ warm_start│ Bool│ 0│ - └───────────┴───────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) - - io = IOBuffer() - show(io, gpmr_solver, show_stats=false) - showed = String(take!(io)) - expected = """ - ┌──────────┬───────────────────┬─────────────────┐ - │GpmrSolver│ Precision: $FC │Architecture: CPU│ - ├──────────┼───────────────────┼─────────────────┤ - │ Attribute│ Type│ Size│ - ├──────────┼───────────────────┼─────────────────┤ - │ wA│ Vector{$FC}│ 0│ - │ wB│ Vector{$FC}│ 0│ - │ dA│ Vector{$FC}│ 64│ - │ dB│ Vector{$FC}│ 32│ - │ Δx│ Vector{$FC}│ 0│ - │ Δy│ Vector{$FC}│ 0│ - │ x│ Vector{$FC}│ 64│ - │ y│ Vector{$FC}│ 32│ - │ q│ Vector{$FC}│ 0│ - │ p│ Vector{$FC}│ 0│ - │ V│Vector{Vector{$FC}}│ 10 x 64│ - │ U│Vector{Vector{$FC}}│ 10 x 32│ - │ gs│ Vector{$FC}│ 40│ - │ gc│ Vector{$T}│ 40│ - │ zt│ Vector{$FC}│ 20│ - │ R│ Vector{$FC}│ 210│ - │warm_start│ Bool│ 0│ - └──────────┴───────────────────┴─────────────────┘ - """ - @test reduce(replace, [" " => "", "\n" => "", "─" => ""], init=showed) == reduce(replace, [" " => "", "\n" => "", "─" => ""], init=expected) end @testset "solvers" begin From ea736058c65604f8f1f46b7c4041b33ba8935e30 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 10 Oct 2022 23:05:35 -0400 Subject: [PATCH 074/182] Fix the sizeof of a vector of vector --- src/krylov_solvers.jl | 9 +++++---- test/test_solvers.jl | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index c86ecef2d..2c6b5aa4e 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1886,9 +1886,10 @@ for (KS, fun, nsol, nA, nAt, warm_start) in [ end function ksizeof(attribute) - if isa(attribute, AbstractVector) && !isempty(attribute) + if isa(attribute, Vector{<:AbstractVector}) && !isempty(attribute) + # A vector of vector is a vector of pointers in Julia. # All vectors inside a vector have the same size in Krylov.jl - size_attribute = length(attribute) * ksizeof(attribute[1]) + size_attribute = sizeof(attribute) + length(attribute) * ksizeof(attribute[1]) else size_attribute = sizeof(attribute) end @@ -1938,9 +1939,9 @@ function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) field_i = getfield(solver, name_i) size_i = ksizeof(field_i) if (name_i in [:w̅, :w̄, :d̅]) && (VERSION < v"1.8.0-DEV") - Printf.format(io, format2, string(name_i), type_i, format_bytes(size_i)) + (size_i ≠ 0) && Printf.format(io, format2, string(name_i), type_i, format_bytes(size_i)) else - Printf.format(io, format, string(name_i), type_i, format_bytes(size_i)) + (size_i ≠ 0) && Printf.format(io, format, string(name_i), type_i, format_bytes(size_i)) end end @printf(io, "└%s┴%s┴%s┘\n","─"^l1,"─"^l2,"─"^l3) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 8c1e2975d..2c98dc795 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -142,6 +142,9 @@ function test_solvers(FC) @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_col1, &, str2[1:3:end-2]) @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_col2, &, str2[2:3:end-1]) @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_col3, &, str2[3:3:end]) + + # Code coverage + show(io, solver, show_stats=true) end end end From bc93b5486a37710457fcf07d9cd2652b43714fe1 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Mon, 10 Oct 2022 23:36:08 -0400 Subject: [PATCH 075/182] Update src/krylov_solvers.jl --- src/krylov_solvers.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 2c6b5aa4e..48b8d774a 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1887,7 +1887,7 @@ end function ksizeof(attribute) if isa(attribute, Vector{<:AbstractVector}) && !isempty(attribute) - # A vector of vector is a vector of pointers in Julia. + # A vector of vectors is a vector of pointers in Julia. # All vectors inside a vector have the same size in Krylov.jl size_attribute = sizeof(attribute) + length(attribute) * ksizeof(attribute[1]) else From 3a1134796aef3cb0e7d1339d25f4f0d0eb61c4a2 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 11 Oct 2022 02:08:51 -0400 Subject: [PATCH 076/182] Improve allocation tests --- test/test_allocations.jl | 188 ++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 110 deletions(-) diff --git a/test/test_allocations.jl b/test/test_allocations.jl index 308da1597..a12c49db0 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -1,26 +1,27 @@ @testset "allocations" begin - for FC in (Float64, ComplexF64) + for FC in (Float32, Float64, ComplexF32, ComplexF64) @testset "Data Type: $FC" begin - A = FC.(get_div_grad(16, 16, 16)) # Dimension n x n - n = size(A, 1) - m = div(n, 2) - Au = A[1:m,:] # Dimension m x n - Ao = A[:,1:m] # Dimension n x m - b = Ao * ones(FC, m) # Dimension n - c = Au * ones(FC, n) # Dimension m + A = FC.(get_div_grad(16, 16, 16)) # Dimension m x n + m,n = size(A) + k = div(n, 2) + Au = A[1:k,:] # Dimension k x n + Ao = A[:,1:k] # Dimension m x k + b = Ao * ones(FC, k) # Dimension m + c = Au * ones(FC, n) # Dimension k mem = 200 - shifts = [1.0; 2.0; 3.0; 4.0; 5.0] + T = real(FC) + shifts = T[1; 2; 3; 4; 5] nshifts = 5 - nbits = sizeof(FC) # 8 bits for Float64 and 16 bits for ComplexF64 + nbits_FC = sizeof(FC) # 8 bits for ComplexF32 and 16 bits for ComplexF64 + nbits_T = sizeof(T) # 4 bits for Float32 and 8 bits for Float64 @testset "SYMMLQ" begin # SYMMLQ needs: # 5 n-vectors: x, Mvold, Mv, Mv_next, w̅ - storage_symmlq(n) = 5 * n - storage_symmlq_bytes(n) = nbits * storage_symmlq(n) + storage_symmlq_bytes(n) = nbits_FC * 5 * n expected_symmlq_bytes = storage_symmlq_bytes(n) symmlq(A, b) # warmup @@ -36,8 +37,7 @@ @testset "CG" begin # CG needs: # 4 n-vectors: x, r, p, Ap - storage_cg(n) = 4 * n - storage_cg_bytes(n) = nbits * storage_cg(n) + storage_cg_bytes(n) = nbits_FC * 4 * n expected_cg_bytes = storage_cg_bytes(n) cg(A, b) # warmup @@ -53,8 +53,7 @@ @testset "CG-LANCZOS" begin # CG-LANCZOS needs: # 5 n-vectors: x, Mv, Mv_prev, p, Mv_next - storage_cg_lanczos(n) = 5 * n - storage_cg_lanczos_bytes(n) = nbits * storage_cg_lanczos(n) + storage_cg_lanczos_bytes(n) = nbits_FC * 5 * n expected_cg_lanczos_bytes = storage_cg_lanczos_bytes(n) cg_lanczos(A, b) # warmup @@ -73,9 +72,7 @@ # - 2 (n*nshifts)-matrices: x, p # - 5 nshifts-vectors: σ, δhat, ω, γ, rNorms # - 3 nshifts-bitVector: indefinite, converged, not_cv - storage_cg_lanczos_shift(n, nshifts) = (3 * n) + (2 * n * nshifts) + (5 * nshifts) + (3 * nshifts / 64) - storage_cg_lanczos_shift_bytes(n, nshifts) = nbits * storage_cg_lanczos_shift(n, nshifts) - + storage_cg_lanczos_shift_bytes(n, nshifts) = nbits_FC * ((3 * n) + (2 * n * nshifts)) + nbits_T * (5 * nshifts) + (3 * nshifts) expected_cg_lanczos_shift_bytes = storage_cg_lanczos_shift_bytes(n, nshifts) cg_lanczos_shift(A, b, shifts) # warmup actual_cg_lanczos_shift_bytes = @allocated cg_lanczos_shift(A, b, shifts) @@ -90,8 +87,7 @@ @testset "CR" begin # CR needs: # 5 n-vectors: x, r, p, q, Ar - storage_cr(n) = 5 * n - storage_cr_bytes(n) = nbits * storage_cr(n) + storage_cr_bytes(n) = nbits_FC * 5 * n expected_cr_bytes = storage_cr_bytes(n) cr(A, b) # warmup @@ -107,8 +103,7 @@ @testset "MINRES" begin # MINRES needs: # 6 n-vectors: x, r1, r2, w1, w2, y - storage_minres(n) = 6 * n - storage_minres_bytes(n) = nbits * storage_minres(n) + storage_minres_bytes(n) = nbits_FC * 6 * n expected_minres_bytes = storage_minres_bytes(n) minres(A, b) # warmup @@ -124,8 +119,7 @@ @testset "MINRES-QLP" begin # MINRES-QLP needs: # - 6 n-vectors: wₖ₋₁, wₖ, vₖ₋₁, vₖ, x, p - storage_minres_qlp(n) = 6 * n - storage_minres_qlp_bytes(n) = nbits * storage_minres_qlp(n) + storage_minres_qlp_bytes(n) = nbits_FC * 6 * n expected_minres_qlp_bytes = storage_minres_qlp_bytes(n) minres_qlp(A, b) # warmup @@ -145,8 +139,7 @@ # - 1 n*(mem-1)-matrix: P # - 1 (mem-1)-vector: L # - 1 mem-vector: H - storage_diom(mem, n) = (2 * n) + (n * mem) + (n * (mem-1)) + (mem-1) + (mem) - storage_diom_bytes(mem, n) = nbits * storage_diom(mem, n) + storage_diom_bytes(mem, n) = nbits_FC * ((2 * n) + (n * mem) + (n * (mem-1)) + (mem-1) + (mem)) expected_diom_bytes = storage_diom_bytes(mem, n) diom(A, b, memory=mem) # warmup @@ -165,8 +158,7 @@ # - 1 (n*mem)-matrix: V # - 2 mem-vectors: l, z # - 1 (mem*(mem+1)/2)-vector: U - storage_fom(mem, n) = (2 * n) + (n * mem) + (2 * mem) + (mem * (mem+1) / 2) - storage_fom_bytes(mem, n) = nbits * storage_fom(mem, n) + storage_fom_bytes(mem, n) = nbits_FC * ((2 * n) + (n * mem) + (2 * mem) + (mem * (mem+1) / 2)) expected_fom_bytes = storage_fom_bytes(mem, n) fom(A, b, memory=mem) # warmup @@ -185,8 +177,7 @@ # - 2 (n*mem)-matrices: P, V # - 2 mem-vectors: c, s # - 1 (mem+1)-vector: H - storage_dqgmres(mem, n) = (2 * n) + (2 * n * mem) + (2 * mem) + (mem + 1) - storage_dqgmres_bytes(mem, n) = nbits * storage_dqgmres(mem, n) + storage_dqgmres_bytes(mem, n) = nbits_FC * ((2 * n) + (2 * n * mem) + mem + (mem + 1)) + nbits_T * mem expected_dqgmres_bytes = storage_dqgmres_bytes(mem, n) dqgmres(A, b, memory=mem) # warmup @@ -205,8 +196,7 @@ # - 1 n*(mem)-matrix: V # - 3 mem-vectors: c, s, z # - 1 (mem*(mem+1)/2)-vector: R - storage_gmres(mem, n) = (2 * n) + (n * mem) + (3 * mem) + (mem * (mem+1) / 2) - storage_gmres_bytes(mem, n) = nbits * storage_gmres(mem, n) + storage_gmres_bytes(mem, n) = nbits_FC * ((2 * n) + (n * mem) + (2 * mem) + (mem * (mem+1) / 2)) + nbits_T * mem expected_gmres_bytes = storage_gmres_bytes(mem, n) gmres(A, b, memory=mem) # warmup @@ -225,8 +215,7 @@ # - 2 n*(mem)-matrix: V, Z # - 3 mem-vectors: c, s, z # - 1 (mem*(mem+1)/2)-vector: R - storage_fgmres(mem, n) = (2 * n) + (2 * n * mem) + (3 * mem) + (mem * (mem+1) / 2) - storage_fgmres_bytes(mem, n) = nbits * storage_fgmres(mem, n) + storage_fgmres_bytes(mem, n) = nbits_FC * ((2 * n) + (2 * n * mem) + (2 * mem) + (mem * (mem+1) / 2)) + nbits_T * mem expected_fgmres_bytes = storage_fgmres_bytes(mem, n) fgmres(A, b, memory=mem) # warmup @@ -242,8 +231,7 @@ @testset "CGS" begin # CGS needs: # 6 n-vectors: x, r, u, p, q, ts - storage_cgs(n) = 6 * n - storage_cgs_bytes(n) = nbits * storage_cgs(n) + storage_cgs_bytes(n) = nbits_FC * 6 * n expected_cgs_bytes = storage_cgs_bytes(n) cgs(A, b) # warmup @@ -259,8 +247,7 @@ @testset "BICGSTAB" begin # BICGSTAB needs: # 6 n-vectors: x, r, p, v, s, qd - storage_bicgstab(n) = 6 * n - storage_bicgstab_bytes(n) = nbits * storage_bicgstab(n) + storage_bicgstab_bytes(n) = nbits_FC * 6 * n expected_bicgstab_bytes = storage_bicgstab_bytes(n) bicgstab(A, b) # warmup @@ -277,10 +264,9 @@ # CGNE needs: # - 3 n-vectors: x, p, Aᴴz # - 2 m-vectors: r, q - storage_cgne(n, m) = 3 * n + 2 * m - storage_cgne_bytes(n, m) = nbits * storage_cgne(n, m) + storage_cgne_bytes(m, n) = nbits_FC * (3 * n + 2 * m) - expected_cgne_bytes = storage_cgne_bytes(n, m) + expected_cgne_bytes = storage_cgne_bytes(k, n) (x, stats) = cgne(Au, c) # warmup actual_cgne_bytes = @allocated cgne(Au, c) @test expected_cgne_bytes ≤ actual_cgne_bytes ≤ 1.02 * expected_cgne_bytes @@ -295,10 +281,9 @@ # CRMR needs: # - 3 n-vectors: x, p, Aᴴr # - 2 m-vectors: r, q - storage_crmr(n, m) = 3 * n + 2 * m - storage_crmr_bytes(n, m) = nbits * storage_crmr(n, m) + storage_crmr_bytes(m, n) = nbits_FC * (3 * n + 2 * m) - expected_crmr_bytes = storage_crmr_bytes(n, m) + expected_crmr_bytes = storage_crmr_bytes(k, n) (x, stats) = crmr(Au, c) # warmup actual_crmr_bytes = @allocated crmr(Au, c) @test expected_crmr_bytes ≤ actual_crmr_bytes ≤ 1.02 * expected_crmr_bytes @@ -313,10 +298,9 @@ # LNLQ needs: # - 3 n-vectors: x, v, Aᴴu # - 4 m-vectors: y, w̄, u, Av - storage_lnlq(n, m) = 3 * n + 4 * m - storage_lnlq_bytes(n, m) = nbits * storage_lnlq(n, m) + storage_lnlq_bytes(m, n) = nbits_FC * (3 * n + 4 * m) - expected_lnlq_bytes = storage_lnlq_bytes(n, m) + expected_lnlq_bytes = storage_lnlq_bytes(k, n) lnlq(Au, c) # warmup actual_lnlq_bytes = @allocated lnlq(Au, c) @test expected_lnlq_bytes ≤ actual_lnlq_bytes ≤ 1.02 * expected_lnlq_bytes @@ -331,10 +315,9 @@ # CRAIG needs: # - 3 n-vectors: x, v, Aᴴu # - 4 m-vectors: y, w, u, Av - storage_craig(n, m) = 3 * n + 4 * m - storage_craig_bytes(n, m) = nbits * storage_craig(n, m) + storage_craig_bytes(m, n) = nbits_FC * (3 * n + 4 * m) - expected_craig_bytes = storage_craig_bytes(n, m) + expected_craig_bytes = storage_craig_bytes(k, n) craig(Au, c) # warmup actual_craig_bytes = @allocated craig(Au, c) @test expected_craig_bytes ≤ actual_craig_bytes ≤ 1.02 * expected_craig_bytes @@ -349,10 +332,9 @@ # CRAIGMR needs: # - 4 n-vectors: x, v, Aᴴu, d # - 5 m-vectors: y, u, w, wbar, Av - storage_craigmr(n, m) = 4 * n + 5 * m - storage_craigmr_bytes(n, m) = nbits * storage_craigmr(n, m) + storage_craigmr_bytes(m, n) = nbits_FC * (4 * n + 5 * m) - expected_craigmr_bytes = storage_craigmr_bytes(n, m) + expected_craigmr_bytes = storage_craigmr_bytes(k, n) craigmr(Au, c) # warmup actual_craigmr_bytes = @allocated craigmr(Au, c) @test expected_craigmr_bytes ≤ actual_craigmr_bytes ≤ 1.02 * expected_craigmr_bytes @@ -365,12 +347,11 @@ @testset "CGLS" begin # CGLS needs: - # - 3 m-vectors: x, p, s - # - 2 n-vectors: r, q - storage_cgls(n, m) = 3 * m + 2 * n - storage_cgls_bytes(n, m) = nbits * storage_cgls(n, m) + # - 3 n-vectors: x, p, s + # - 2 m-vectors: r, q + storage_cgls_bytes(m, n) = nbits_FC * (3 * n + 2 * m) - expected_cgls_bytes = storage_cgls_bytes(n, m) + expected_cgls_bytes = storage_cgls_bytes(m, k) (x, stats) = cgls(Ao, b) # warmup actual_cgls_bytes = @allocated cgls(Ao, b) @test expected_cgls_bytes ≤ actual_cgls_bytes ≤ 1.02 * expected_cgls_bytes @@ -383,15 +364,14 @@ @testset "LSLQ" begin # LSLQ needs: - # - 4 m-vectors: x_lq, v, Aᴴu, w̄ (= x_cg) - # - 2 n-vectors: u, Av - storage_lslq(n, m) = 4 * m + 2 * n - storage_lslq_bytes(n, m) = nbits * storage_lslq(n, m) + # - 4 n-vectors: x_lq, v, Aᴴu, w̄ (= x_cg) + # - 2 m-vectors: u, Av + storage_lslq_bytes(m, n) = nbits_FC * (4 * n + 2 * m) - expected_lslq_bytes = storage_lslq_bytes(n, m) + expected_lslq_bytes = storage_lslq_bytes(m, k) (x, stats) = lslq(Ao, b) # warmup actual_lslq_bytes = @allocated lslq(Ao, b) - @test expected_lslq_bytes ≤ actual_lslq_bytes ≤ 1.02 * expected_lslq_bytes + @test expected_lslq_bytes ≤ actual_lslq_bytes ≤ 1.03 * expected_lslq_bytes solver = LslqSolver(Ao, b) lslq!(solver, Ao, b) # warmup @@ -401,12 +381,11 @@ @testset "CRLS" begin # CRLS needs: - # - 4 m-vectors: x, p, Ar, q - # - 3 n-vectors: r, Ap, s - storage_crls(n, m) = 4 * m + 3 * n - storage_crls_bytes(n, m) = nbits * storage_crls(n, m) + # - 4 n-vectors: x, p, Ar, q + # - 3 m-vectors: r, Ap, s + storage_crls_bytes(m, n) = nbits_FC * (4 * n + 3 * m) - expected_crls_bytes = storage_crls_bytes(n, m) + expected_crls_bytes = storage_crls_bytes(m, k) (x, stats) = crls(Ao, b) # warmup actual_crls_bytes = @allocated crls(Ao, b) @test expected_crls_bytes ≤ actual_crls_bytes ≤ 1.02 * expected_crls_bytes @@ -419,15 +398,14 @@ @testset "LSQR" begin # LSQR needs: - # - 4 m-vectors: x, v, w, Aᴴu - # - 2 n-vectors: u, Av - storage_lsqr(n, m) = 4 * m + 2 * n - storage_lsqr_bytes(n, m) = nbits * storage_lsqr(n, m) + # - 4 n-vectors: x, v, w, Aᴴu + # - 2 m-vectors: u, Av + storage_lsqr_bytes(m, n) = nbits_FC * (4 * n + 2 * m) - expected_lsqr_bytes = storage_lsqr_bytes(n, m) + expected_lsqr_bytes = storage_lsqr_bytes(m, k) (x, stats) = lsqr(Ao, b) # warmup actual_lsqr_bytes = @allocated lsqr(Ao, b) - @test expected_lsqr_bytes ≤ actual_lsqr_bytes ≤ 1.02 * expected_lsqr_bytes + @test expected_lsqr_bytes ≤ actual_lsqr_bytes ≤ 1.03 * expected_lsqr_bytes solver = LsqrSolver(Ao, b) lsqr!(solver, Ao, b) # warmup @@ -437,12 +415,11 @@ @testset "LSMR" begin # LSMR needs: - # - 5 m-vectors: x, v, h, hbar, Aᴴu - # - 2 n-vectors: u, Av - storage_lsmr(n, m) = 5 * m + 2 * n - storage_lsmr_bytes(n, m) = nbits * storage_lsmr(n, m) + # - 5 n-vectors: x, v, h, hbar, Aᴴu + # - 2 m-vectors: u, Av + storage_lsmr_bytes(m, n) = nbits_FC * (5 * n + 2 * m) - expected_lsmr_bytes = storage_lsmr_bytes(n, m) + expected_lsmr_bytes = storage_lsmr_bytes(m, k) (x, stats) = lsmr(Ao, b) # warmup actual_lsmr_bytes = @allocated lsmr(Ao, b) @test expected_lsmr_bytes ≤ actual_lsmr_bytes ≤ 1.02 * expected_lsmr_bytes @@ -456,8 +433,7 @@ @testset "BiLQ" begin # BILQ needs: # - 8 n-vectors: uₖ₋₁, uₖ, vₖ₋₁, vₖ, x, d̅, p, q - storage_bilq(n) = 8 * n - storage_bilq_bytes(n) = nbits * storage_bilq(n) + storage_bilq_bytes(n) = nbits_FC * 8 * n expected_bilq_bytes = storage_bilq_bytes(n) bilq(A, b) # warmup @@ -473,8 +449,7 @@ @testset "QMR" begin # QMR needs: # - 9 n-vectors: uₖ₋₁, uₖ, vₖ₋₁, vₖ, x, wₖ₋₁, wₖ, p, q - storage_qmr(n) = 9 * n - storage_qmr_bytes(n) = nbits * storage_qmr(n) + storage_qmr_bytes(n) = nbits_FC * 9 * n expected_qmr_bytes = storage_qmr_bytes(n) qmr(A, b) # warmup @@ -490,8 +465,7 @@ @testset "BiLQR" begin # BILQR needs: # - 11 n-vectors: uₖ₋₁, uₖ, vₖ₋₁, vₖ, x, t, d̅, wₖ₋₁, wₖ, p, q - storage_bilqr(n) = 11 * n - storage_bilqr_bytes(n) = nbits * storage_bilqr(n) + storage_bilqr_bytes(n) = nbits_FC * 11 * n expected_bilqr_bytes = storage_bilqr_bytes(n) bilqr(A, b, b) # warmup @@ -508,10 +482,9 @@ # USYMLQ needs: # - 5 n-vectors: uₖ₋₁, uₖ, x, d̅, p # - 3 m-vectors: vₖ₋₁, vₖ, q - storage_usymlq(n, m) = 5 * n + 3 * m - storage_usymlq_bytes(n, m) = nbits * storage_usymlq(n, m) + storage_usymlq_bytes(m, n) = nbits_FC * (5 * n + 3 * m) - expected_usymlq_bytes = storage_usymlq_bytes(n, m) + expected_usymlq_bytes = storage_usymlq_bytes(k, n) usymlq(Au, c, b) # warmup actual_usymlq_bytes = @allocated usymlq(Au, c, b) @test expected_usymlq_bytes ≤ actual_usymlq_bytes ≤ 1.02 * expected_usymlq_bytes @@ -524,12 +497,11 @@ @testset "USYMQR" begin # USYMQR needs: - # - 6 m-vectors: vₖ₋₁, vₖ, x, wₖ₋₁, wₖ, p - # - 3 n-vectors: uₖ₋₁, uₖ, q - storage_usymqr(n, m) = 6 * m + 3 * n - storage_usymqr_bytes(n, m) = nbits * storage_usymqr(n, m) + # - 6 n-vectors: vₖ₋₁, vₖ, x, wₖ₋₁, wₖ, p + # - 3 m-vectors: uₖ₋₁, uₖ, q + storage_usymqr_bytes(m, n) = nbits_FC * (6 * n + 3 * m) - expected_usymqr_bytes = storage_usymqr_bytes(n, m) + expected_usymqr_bytes = storage_usymqr_bytes(m, k) (x, stats) = usymqr(Ao, b, c) # warmup actual_usymqr_bytes = @allocated usymqr(Ao, b, c) @test expected_usymqr_bytes ≤ actual_usymqr_bytes ≤ 1.02 * expected_usymqr_bytes @@ -544,8 +516,7 @@ # TRILQR needs: # - 6 m-vectors: vₖ₋₁, vₖ, t, wₖ₋₁, wₖ, q # - 5 n-vectors: uₖ₋₁, uₖ, x, d̅, p - storage_trilqr(n, m) = 6 * m + 5 * n - storage_trilqr_bytes(n, m) = nbits * storage_trilqr(n, m) + storage_trilqr_bytes(m, n) = nbits_FC * (6 * m + 5 * n) expected_trilqr_bytes = storage_trilqr_bytes(n, n) trilqr(A, b, b) # warmup @@ -562,10 +533,9 @@ # TriCG needs: # - 6 n-vectors: yₖ, uₖ₋₁, uₖ, gy₂ₖ₋₁, gy₂ₖ, p # - 6 m-vectors: xₖ, vₖ₋₁, vₖ, gx₂ₖ₋₁, gx₂ₖ, q - storage_tricg(n, m) = 6 * n + 6 * m - storage_tricg_bytes(n, m) = nbits * storage_tricg(n, m) + storage_tricg_bytes(m, n) = nbits_FC * (6 * n + 6 * m) - expected_tricg_bytes = storage_tricg_bytes(n, m) + expected_tricg_bytes = storage_tricg_bytes(k, n) tricg(Au, c, b) # warmup actual_tricg_bytes = @allocated tricg(Au, c, b) @test expected_tricg_bytes ≤ actual_tricg_bytes ≤ 1.02 * expected_tricg_bytes @@ -580,10 +550,9 @@ # TriMR needs: # - 8 n-vectors: yₖ, uₖ₋₁, uₖ, gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ, p # - 8 m-vectors: xₖ, vₖ₋₁, vₖ, gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ, q - storage_trimr(n, m) = 8 * n + 8 * m - storage_trimr_bytes(n, m) = nbits * storage_trimr(n, m) + storage_trimr_bytes(m, n) = nbits_FC * (8 * n + 8 * m) - expected_trimr_bytes = storage_trimr_bytes(n, m) + expected_trimr_bytes = storage_trimr_bytes(k, n) trimr(Au, c, b) # warmup actual_trimr_bytes = @allocated trimr(Au, c, b) @test expected_trimr_bytes ≤ actual_trimr_bytes ≤ 1.02 * expected_trimr_bytes @@ -596,17 +565,16 @@ @testset "GPMR" begin # GPMR needs: - # - 2 n-vectors: x, q - # - 2 m-vectors: y, p - # - 1 (n*mem)-matrix: V - # - 1 (m*mem)-matrix: U + # - 2 m-vectors: x, q + # - 2 n-vectors: y, p + # - 1 (m*mem)-matrix: V + # - 1 (n*mem)-matrix: U # - 1 (2*mem)-vector: zt # - 2 (4*mem)-vectors: gc, gs # - 1 (mem*(2mem+1))-vector: R - storage_gpmr(mem, n, m) = (mem + 2) * (n + m) + mem * (2 * mem + 11) - storage_gpmr_bytes(mem, n, m) = nbits * storage_gpmr(mem, n, m) + storage_gpmr_bytes(mem, m, n) = nbits_FC * ((mem + 2) * (n + m) + mem * (2 * mem + 7)) + nbits_T * 4 * mem - expected_gpmr_bytes = storage_gpmr_bytes(mem, n, m) + expected_gpmr_bytes = storage_gpmr_bytes(mem, m, k) gpmr(Ao, Au, b, c, memory=mem, itmax=mem) # warmup actual_gpmr_bytes = @allocated gpmr(Ao, Au, b, c, memory=mem, itmax=mem) @test expected_gpmr_bytes ≤ actual_gpmr_bytes ≤ 1.02 * expected_gpmr_bytes From ae3fc2d65482baa00c97e2043e65d713630347aa Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 11 Oct 2022 10:18:04 -0400 Subject: [PATCH 077/182] Update tolerances for LSMR and SYMMLQ in allocation tests --- test/test_allocations.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_allocations.jl b/test/test_allocations.jl index a12c49db0..5f122b33e 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -26,7 +26,7 @@ expected_symmlq_bytes = storage_symmlq_bytes(n) symmlq(A, b) # warmup actual_symmlq_bytes = @allocated symmlq(A, b) - @test expected_symmlq_bytes ≤ actual_symmlq_bytes ≤ 1.02 * expected_symmlq_bytes + @test expected_symmlq_bytes ≤ actual_symmlq_bytes ≤ 1.03 * expected_symmlq_bytes solver = SymmlqSolver(A, b) symmlq!(solver, A, b) # warmup @@ -422,7 +422,7 @@ expected_lsmr_bytes = storage_lsmr_bytes(m, k) (x, stats) = lsmr(Ao, b) # warmup actual_lsmr_bytes = @allocated lsmr(Ao, b) - @test expected_lsmr_bytes ≤ actual_lsmr_bytes ≤ 1.02 * expected_lsmr_bytes + @test expected_lsmr_bytes ≤ actual_lsmr_bytes ≤ 1.03 * expected_lsmr_bytes solver = LsmrSolver(Ao, b) lsmr!(solver, Ao, b) # warmup From b6f6ae9573be51d7a07d886b31a5476fcb1ad579 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 11 Oct 2022 16:36:29 -0400 Subject: [PATCH 078/182] Add optional argument(s) in the docstrings --- src/bicgstab.jl | 21 ++++++++++++--------- src/bilq.jl | 24 +++++++++++++----------- src/bilqr.jl | 17 ++++++++++------- src/cg.jl | 14 ++++++++------ src/cg_lanczos.jl | 14 ++++++++------ src/cgs.jl | 22 ++++++++++++---------- src/cr.jl | 19 +++++++++++-------- src/diom.jl | 22 ++++++++++++---------- src/dqgmres.jl | 22 ++++++++++++---------- src/fgmres.jl | 18 ++++++++++-------- src/fom.jl | 18 ++++++++++-------- src/gmres.jl | 18 ++++++++++-------- src/gpmr.jl | 27 +++++++++++++++------------ src/minres.jl | 14 ++++++++------ src/minres_qlp.jl | 14 ++++++++------ src/qmr.jl | 22 ++++++++++++---------- src/symmlq.jl | 18 ++++++++++-------- src/tricg.jl | 15 +++++++++------ src/trilqr.jl | 17 ++++++++++------- src/trimr.jl | 15 +++++++++------ src/usymlq.jl | 20 +++++++++++--------- src/usymqr.jl | 19 ++++++++++--------- 22 files changed, 230 insertions(+), 180 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 588f64838..b2421f1f7 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -16,14 +16,19 @@ export bicgstab, bicgstab! """ - (x, stats) = bicgstab(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, + (x, stats) = bicgstab(A, b::AbstractVector{FC}; + c::AbstractVector{FC}=b, M=I, N=I, + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = bicgstab(A, b, x0::AbstractVector; kwargs...) + +BICGSTAB can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the square linear system Ax = b of size n using BICGSTAB. BICGSTAB requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. @@ -42,12 +47,6 @@ Information will be displayed every `verbose` iterations. This implementation allows a left preconditioner `M` and a right preconditioner `N`. -BICGSTAB can be warm-started from an initial guess `x0` with - - (x, stats) = bicgstab(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -56,6 +55,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/bilq.jl b/src/bilq.jl index 002858943..163b6339d 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -13,16 +13,20 @@ export bilq, bilq! """ - (x, stats) = bilq(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, - atol::T=√eps(T), rtol::T=√eps(T), transfer_to_bicg::Bool=true, - itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false) + (x, stats) = bilq(A, b::AbstractVector{FC}; + c::AbstractVector{FC}=b, atol::T=√eps(T), + rtol::T=√eps(T), transfer_to_bicg::Bool=true, + itmax::Int=0, verbose::Int=0, + history::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. -Solve the square linear system Ax = b of size n using BiLQ. + (x, stats) = bilq(A, b, x0::AbstractVector; kwargs...) + +BiLQ can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. +Solve the square linear system Ax = b of size n using BiLQ. BiLQ is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. When `A` is symmetric and `b = c`, BiLQ is equivalent to SYMMLQ. @@ -30,12 +34,6 @@ When `A` is symmetric and `b = c`, BiLQ is equivalent to SYMMLQ. An option gives the possibility of transferring to the BiCG point, when it exists. The transfer is based on the residual norm. -BiLQ can be warm-started from an initial guess `x0` with - - (x, stats) = bilq(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -44,6 +42,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/bilqr.jl b/src/bilqr.jl index 4677e9e9d..5e45cb372 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -21,6 +21,10 @@ export bilqr, bilqr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, y, stats) = bilqr(A, b, c, x0::AbstractVector, y0::AbstractVector; kwargs...) + +BiLQR can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. + Combine BiLQ and QMR to solve adjoint systems. [0 A] [y] = [b] @@ -33,12 +37,6 @@ QMR is used for solving dual system `Aᴴy = c` of size n. An option gives the possibility of transferring from the BiLQ point to the BiCG point, when it exists. The transfer is based on the residual norm. -BiLQR can be warm-started from initial guesses `x0` and `y0` with - - (x, y, stats) = bilqr(A, b, c, x0, y0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -48,11 +46,16 @@ and `false` otherwise. * `b`: a vector of length n; * `c`: a vector of length n. +#### Optional arguments + +* `x0`: a vector of length n that represents an initial guess of the solution x; +* `y0`: a vector of length n that represents an initial guess of the solution y. + #### Output arguments * `x`: a dense vector of length n; * `y`: a dense vector of length n; -* `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. +* `stats`: statistics collected on the run in an [`AdjointStats`](@ref) structure. #### Reference diff --git a/src/cg.jl b/src/cg.jl index 09b91e64c..1c4b39cb5 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -26,6 +26,10 @@ export cg, cg! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = cg(A, b, x0::AbstractVector; kwargs...) + +CG can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + The conjugate gradient method to solve the Hermitian linear system Ax = b of size n. The method does _not_ abort if A is not definite. @@ -36,12 +40,6 @@ M also indicates the weighted norm in which residuals are measured. If `itmax=0`, the default number of iterations is set to `2 * n`. -CG can be warm-started from an initial guess `x0` with - - (x, stats) = cg(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -50,6 +48,10 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian positive definite matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index b49cdd726..3efdcd90e 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -22,6 +22,10 @@ export cg_lanczos, cg_lanczos! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = cg_lanczos(A, b, x0::AbstractVector; kwargs...) + +CG-LANCZOS can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + The Lanczos version of the conjugate gradient method to solve the Hermitian linear system Ax = b of size n. @@ -30,12 +34,6 @@ The method does _not_ abort if A is not definite. A preconditioner M may be provided in the form of a linear operator and is assumed to be Hermitian and positive definite. -CG-LANCZOS can be warm-started from an initial guess `x0` with - - (x, stats) = cg_lanczos(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -44,6 +42,10 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/cgs.jl b/src/cgs.jl index 9b4eb695f..4f770c5f3 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -11,14 +11,18 @@ export cgs, cgs! """ - (x, stats) = cgs(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + (x, stats) = cgs(A, b::AbstractVector{FC}; + c::AbstractVector{FC}=b, M=I, N=I, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, + history::Bool=false, ldiv::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = cgs(A, b, x0::AbstractVector; kwargs...) + +CGS can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the consistent linear system Ax = b of size n using CGS. CGS requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. @@ -40,12 +44,6 @@ TFQMR and BICGSTAB were developed to remedy this difficulty.» This implementation allows a left preconditioner M and a right preconditioner N. -CGS can be warm-started from an initial guess `x0` with - - (x, stats) = cgs(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -54,6 +52,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/cr.jl b/src/cr.jl index 79f2cd289..4bb104534 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -16,13 +16,18 @@ export cr, cr! """ (x, stats) = cr(A, b::AbstractVector{FC}; - M=I, atol::T=√eps(T), rtol::T=√eps(T), γ::T=√eps(T), itmax::Int=0, - radius::T=zero(T), verbose::Int=0, linesearch::Bool=false, history::Bool=false, + M=I, atol::T=√eps(T), rtol::T=√eps(T), γ::T=√eps(T), + itmax::Int=0, radius::T=zero(T), verbose::Int=0, + linesearch::Bool=false, history::Bool=false, ldiv::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = cr(A, b, x0::AbstractVector; kwargs...) + +CR can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + A truncated version of Stiefel’s Conjugate Residual method to solve the Hermitian linear system Ax = b of size n or the least-squares problem min ‖b - Ax‖ if A is singular. The matrix A must be Hermitian semi-definite. @@ -34,12 +39,6 @@ In a linesearch context, 'linesearch' must be set to 'true'. If `itmax=0`, the default number of iterations is set to `2 * n`. -CR can be warm-started from an initial guess `x0` with - - (x, stats) = cr(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -48,6 +47,10 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian positive definite matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/diom.jl b/src/diom.jl index 58986ab47..23b6d48ca 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -11,15 +11,19 @@ export diom, diom! """ - (x, stats) = diom(A, b::AbstractVector{FC}; memory::Int=20, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - reorthogonalization::Bool=false, itmax::Int=0, - verbose::Int=0, history::Bool=false, + (x, stats) = diom(A, b::AbstractVector{FC}; + memory::Int=20, M=I, N=I, atol::T=√eps(T), + rtol::T=√eps(T), reorthogonalization::Bool=false, + itmax::Int=0, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = diom(A, b, x0::AbstractVector; kwargs...) + +DIOM can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the consistent linear system Ax = b of size n using DIOM. DIOM only orthogonalizes the new vectors of the Krylov basis against the `memory` most recent vectors. @@ -34,12 +38,6 @@ and indefinite systems of linear equations can be handled by this single algorit This implementation allows a left preconditioner M and a right preconditioner N. -DIOM can be warm-started from an initial guess `x0` with - - (x, stats) = diom(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -48,6 +46,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 2dd5b0843..2d428b87c 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -11,15 +11,19 @@ export dqgmres, dqgmres! """ - (x, stats) = dqgmres(A, b::AbstractVector{FC}; memory::Int=20, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - reorthogonalization::Bool=false, itmax::Int=0, - verbose::Int=0, history::Bool=false, + (x, stats) = dqgmres(A, b::AbstractVector{FC}; + memory::Int=20, M=I, N=I, atol::T=√eps(T), + rtol::T=√eps(T), reorthogonalization::Bool=false, + itmax::Int=0, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = dqgmres(A, b, x0::AbstractVector; kwargs...) + +DQGMRES can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the consistent linear system Ax = b of size n using DQGMRES. DQGMRES algorithm is based on the incomplete Arnoldi orthogonalization process @@ -34,12 +38,6 @@ Partial reorthogonalization is available with the `reorthogonalization` option. This implementation allows a left preconditioner M and a right preconditioner N. -DQGMRES can be warm-started from an initial guess `x0` with - - (x, stats) = dqgmres(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -48,6 +46,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/fgmres.jl b/src/fgmres.jl index b79c197ba..9fc53408a 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -11,8 +11,8 @@ export fgmres, fgmres! """ - (x, stats) = fgmres(A, b::AbstractVector{FC}; memory::Int=20, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), + (x, stats) = fgmres(A, b::AbstractVector{FC}; + memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) @@ -20,6 +20,10 @@ export fgmres, fgmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = fgmres(A, b, x0::AbstractVector; kwargs...) + +FGMRES can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the linear system Ax = b of size n using FGMRES. FGMRES computes a sequence of approximate solutions with minimum residual. @@ -37,12 +41,6 @@ If `restart = true`, the restarted version FGMRES(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. More storage will be allocated only if the number of iterations exceeds `memory`. -FGMRES can be warm-started from an initial guess `x0` with - - (x, stats) = fgmres(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -51,6 +49,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/fom.jl b/src/fom.jl index 4ea3fce92..fdd99708b 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -11,8 +11,8 @@ export fom, fom! """ - (x, stats) = fom(A, b::AbstractVector{FC}; memory::Int=20, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), + (x, stats) = fom(A, b::AbstractVector{FC}; + memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) @@ -20,6 +20,10 @@ export fom, fom! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = fom(A, b, x0::AbstractVector; kwargs...) + +FOM can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the linear system Ax = b of size n using FOM. FOM algorithm is based on the Arnoldi process and a Galerkin condition. @@ -31,12 +35,6 @@ If `restart = true`, the restarted version FOM(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. More storage will be allocated only if the number of iterations exceeds `memory`. -FOM can be warm-started from an initial guess `x0` with - - (x, stats) = fom(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -45,6 +43,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/gmres.jl b/src/gmres.jl index ac425054f..1af93328b 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -11,8 +11,8 @@ export gmres, gmres! """ - (x, stats) = gmres(A, b::AbstractVector{FC}; memory::Int=20, - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), + (x, stats) = gmres(A, b::AbstractVector{FC}; + memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) @@ -20,6 +20,10 @@ export gmres, gmres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = gmres(A, b, x0::AbstractVector; kwargs...) + +GMRES can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the linear system Ax = b of size n using GMRES. GMRES algorithm is based on the Arnoldi process and computes a sequence of approximate solutions with the minimum residual. @@ -31,12 +35,6 @@ If `restart = true`, the restarted version GMRES(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. More storage will be allocated only if the number of iterations exceeds `memory`. -GMRES can be warm-started from an initial guess `x0` with - - (x, stats) = gmres(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -45,6 +43,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/gpmr.jl b/src/gpmr.jl index f94f6e4ac..fac2270ba 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -12,16 +12,20 @@ export gpmr, gpmr! """ - (x, y, stats) = gpmr(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}; memory::Int=20, - C=I, D=I, E=I, F=I, atol::T=√eps(T), rtol::T=√eps(T), - gsp::Bool=false, reorthogonalization::Bool=false, - itmax::Int=0, λ::FC=one(FC), μ::FC=one(FC), - verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + (x, y, stats) = gpmr(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}; + memory::Int=20, C=I, D=I, E=I, F=I, + atol::T=√eps(T), rtol::T=√eps(T), gsp::Bool=false, + reorthogonalization::Bool=false, itmax::Int=0, + λ::FC=one(FC), μ::FC=one(FC), verbose::Int=0, + history::Bool=false, ldiv::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, y, stats) = gpmr(A, B, b, c, x0::AbstractVector, y0::AbstractVector; kwargs...) + +GPMR can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. + Given matrices `A` of dimension m × n and `B` of dimension n × m, GPMR solves the unsymmetric partitioned linear system @@ -59,12 +63,6 @@ Full reorthogonalization is available with the `reorthogonalization` option. Additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations. -GPMR can be warm-started from initial guesses `x0` and `y0` with - - (x, y, stats) = gpmr(A, B, b, c, x0, y0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -75,6 +73,11 @@ and `false` otherwise. * `b`: a vector of length m; * `c`: a vector of length n. +#### Optional arguments + +* `x0`: a vector of length m that represents an initial guess of the solution x; +* `y0`: a vector of length n that represents an initial guess of the solution y. + #### Output arguments * `x`: a dense vector of length m; diff --git a/src/minres.jl b/src/minres.jl index f2814403c..c63312f77 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -35,6 +35,10 @@ export minres, minres! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = minres(A, b, x0::AbstractVector; kwargs...) + +MINRES can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the shifted linear least-squares problem minimize ‖b - (A + λI)x‖₂² @@ -55,12 +59,6 @@ MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr A preconditioner M may be provided in the form of a linear operator and is assumed to be Hermitian and positive definite. -MINRES can be warm-started from an initial guess `x0` with - - (x, stats) = minres(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -69,6 +67,10 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index a1c9fc01b..ba27efb44 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -26,6 +26,10 @@ export minres_qlp, minres_qlp! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = minres_qlp(A, b, x0::AbstractVector; kwargs...) + +MINRES-QLP can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + MINRES-QLP is the only method based on the Lanczos process that returns the minimum-norm solution on singular inconsistent systems (A + λI)x = b of size n, where λ is a shift parameter. It is significantly more complex but can be more reliable than MINRES when A is ill-conditioned. @@ -34,12 +38,6 @@ A preconditioner M may be provided in the form of a linear operator and is assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. -MINRES-QLP can be warm-started from an initial guess `x0` with - - (x, stats) = minres_qlp(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -48,6 +46,10 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/qmr.jl b/src/qmr.jl index cf3328649..a8db7e978 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -21,26 +21,24 @@ export qmr, qmr! """ - (x, stats) = qmr(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, - atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false) + (x, stats) = qmr(A, b::AbstractVector{FC}; + c::AbstractVector{FC}=b, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, + history::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = qmr(A, b, x0::AbstractVector; kwargs...) + +QMR can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the square linear system Ax = b of size n using QMR. QMR is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. When `A` is symmetric and `b = c`, QMR is equivalent to MINRES. -QMR can be warm-started from an initial guess `x0` with - - (x, stats) = qmr(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -49,6 +47,10 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/symmlq.jl b/src/symmlq.jl index 10d903049..9d28dc07b 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -13,8 +13,8 @@ export symmlq, symmlq! """ - (x, stats) = symmlq(A, b::AbstractVector{FC}; window::Int=0, - M=I, λ::T=zero(T), transfer_to_cg::Bool=true, + (x, stats) = symmlq(A, b::AbstractVector{FC}; + window::Int=0, M=I, λ::T=zero(T), transfer_to_cg::Bool=true, λest::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), etol::T=√eps(T), itmax::Int=0, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, @@ -23,6 +23,10 @@ export symmlq, symmlq! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = symmlq(A, b, x0::AbstractVector; kwargs...) + +SYMMLQ can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above + Solve the shifted linear system (A + λI) x = b @@ -35,12 +39,6 @@ SYMMLQ produces monotonic errors ‖x* - x‖₂. A preconditioner M may be provided in the form of a linear operator and is assumed to be Hermitian and positive definite. -SYMMLQ can be warm-started from an initial guess `x0` with - - (x, stats) = symmlq(A, b, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -49,6 +47,10 @@ and `false` otherwise. * `A`: a linear operator that models a Hermitian matrix of dimension n; * `b`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/tricg.jl b/src/tricg.jl index f4a3c35f2..1860d8492 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -22,6 +22,10 @@ export tricg, tricg! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, y, stats) = tricg(A, b, c, x0::AbstractVector, y0::AbstractVector; kwargs...) + +TriCG can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. + Given a matrix `A` of dimension m × n, TriCG solves the symmetric linear system [ τE A ] [ x ] = [ b ] @@ -53,12 +57,6 @@ TriCG stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + Additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations. -TriCG can be warm-started from initial guesses `x0` and `y0` with - - (x, y, stats) = tricg(A, b, c, x0, y0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -68,6 +66,11 @@ and `false` otherwise. * `b`: a vector of length m; * `c`: a vector of length n. +#### Optional arguments + +* `x0`: a vector of length m that represents an initial guess of the solution x; +* `y0`: a vector of length n that represents an initial guess of the solution y. + #### Output arguments * `x`: a dense vector of length m; diff --git a/src/trilqr.jl b/src/trilqr.jl index 7aa90d1c9..81e3f3bf3 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -21,6 +21,10 @@ export trilqr, trilqr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, y, stats) = trilqr(A, b, c, x0::AbstractVector, y0::AbstractVector; kwargs...) + +TriLQR can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. + Combine USYMLQ and USYMQR to solve adjoint systems. [0 A] [y] = [b] @@ -32,12 +36,6 @@ USYMQR is used for solving dual system `Aᴴy = c` of size n × m. An option gives the possibility of transferring from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm. -TriLQR can be warm-started from initial guesses `x0` and `y0` with - - (x, y, stats) = trilqr(A, b, c, x0, y0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -47,11 +45,16 @@ and `false` otherwise. * `b`: a vector of length m; * `c`: a vector of length n. +#### Optional arguments + +* `x0`: a vector of length n that represents an initial guess of the solution x; +* `y0`: a vector of length m that represents an initial guess of the solution y. + #### Output arguments * `x`: a dense vector of length n; * `y`: a dense vector of length m; -* `stats`: statistics collected on the run in a [`AdjointStats`](@ref) structure. +* `stats`: statistics collected on the run in an [`AdjointStats`](@ref) structure. #### Reference diff --git a/src/trimr.jl b/src/trimr.jl index 0905f351e..52bc1b8e9 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -22,6 +22,10 @@ export trimr, trimr! `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, y, stats) = trimr(A, b, c, x0::AbstractVector, y0::AbstractVector; kwargs...) + +TriMR can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. + Given a matrix `A` of dimension m × n, TriMR solves the symmetric linear system [ τE A ] [ x ] = [ b ] @@ -53,12 +57,6 @@ TriMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + Additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations. -TriMR can be warm-started from initial guesses `x0` and `y0` with - - (x, y, stats) = trimr(A, b, c, x0, y0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -68,6 +66,11 @@ and `false` otherwise. * `b`: a vector of length m; * `c`: a vector of length n. +#### Optional arguments + +* `x0`: a vector of length m that represents an initial guess of the solution x; +* `y0`: a vector of length n that represents an initial guess of the solution y. + #### Output arguments * `x`: a dense vector of length m; diff --git a/src/usymlq.jl b/src/usymlq.jl index d61fc10b0..1d6d3e1d8 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -21,13 +21,17 @@ export usymlq, usymlq! """ (x, stats) = usymlq(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - atol::T=√eps(T), rtol::T=√eps(T), transfer_to_usymcg::Bool=true, - itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false) + atol::T=√eps(T), rtol::T=√eps(T), + transfer_to_usymcg::Bool=true, itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = usymlq(A, b, c, x0::AbstractVector; kwargs...) + +USYMLQ can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the linear system Ax = b of size m × n using the USYMLQ method. USYMLQ is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. @@ -41,12 +45,6 @@ In all cases, problems must be consistent. An option gives the possibility of transferring to the USYMCG point, when it exists. The transfer is based on the residual norm. -USYMLQ can be warm-started from an initial guess `x0` with - - (x, stats) = usymlq(A, b, c, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -56,6 +54,10 @@ and `false` otherwise. * `b`: a vector of length m; * `c`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/usymqr.jl b/src/usymqr.jl index 2cef2db0d..003a46dc5 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -21,13 +21,16 @@ export usymqr, usymqr! """ (x, stats) = usymqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false) + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. + (x, stats) = usymqr(A, b, c, x0::AbstractVector; kwargs...) + +USYMQR can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. + Solve the linear system Ax = b of size m × n using USYMQR. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. @@ -38,12 +41,6 @@ It's considered as a generalization of MINRES. It can also be applied to under-determined and over-determined problems. USYMQR finds the minimum-norm solution if problems are inconsistent. -USYMQR can be warm-started from an initial guess `x0` with - - (x, stats) = usymqr(A, b, c, x0; kwargs...) - -where `kwargs` are the same keyword arguments as above. - The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, and `false` otherwise. @@ -53,6 +50,10 @@ and `false` otherwise. * `b`: a vector of length m; * `c`: a vector of length n. +#### Optional argument + +* `x0`: a vector of length n that represents an initial guess of the solution x. + #### Output arguments * `x`: a dense vector of length n; From a42db8e18a251af99df08ca1c1a748909c9132cc Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 12 Oct 2022 14:22:12 -0400 Subject: [PATCH 079/182] [LNLQ] Use utolx and utoly instead of etolx and etoly --- src/lnlq.jl | 15 ++++++--------- test/test_lnlq.jl | 6 +++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/lnlq.jl b/src/lnlq.jl index b39241a30..208c888d5 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -27,7 +27,7 @@ export lnlq, lnlq! """ (x, y, stats) = lnlq(A, b::AbstractVector{FC}; M=I, N=I, sqd::Bool=false, λ::T=zero(T), σ::T=zero(T), - atol::T=√eps(T), rtol::T=√eps(T), etolx::T=√eps(T), etoly::T=√eps(T), itmax::Int=0, + atol::T=√eps(T), rtol::T=√eps(T), utolx::T=√eps(T), utoly::T=√eps(T), itmax::Int=0, transfer_to_craig::Bool=true, verbose::Int=0, history::Bool=false, ldiv::Bool=false, callback=solver->false) @@ -75,7 +75,7 @@ In this case, `M` can still be specified and indicates the weighted norm in whic In this implementation, both the x and y-parts of the solution are returned. -`etolx` and `etoly` are tolerances on the upper bound of the distance to the solution ‖x-xₛ‖ and ‖y-yₛ‖, respectively. +`utolx` and `utoly` are tolerances on the upper bound of the distance to the solution ‖x-x*‖ and ‖y-y*‖, respectively. The bound is valid if λ>0 or σ>0 where σ should be strictly smaller than the smallest positive singular value. For instance σ:=(1-1e-7)σₘᵢₙ . @@ -116,7 +116,7 @@ function lnlq! end function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), - atol :: T=√eps(T), rtol :: T=√eps(T), etolx :: T=√eps(T), etoly :: T=√eps(T), itmax :: Int=0, + atol :: T=√eps(T), rtol :: T=√eps(T), utolx :: T=√eps(T), utoly :: T=√eps(T), itmax :: Int=0, transfer_to_craig :: Bool=true, verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} @@ -258,7 +258,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; err_x = τtildeₖ err_y = ζtildeₖ - solved_lq = err_x ≤ etolx || err_y ≤ etoly + solved_lq = err_x ≤ utolx || err_y ≤ utoly history && push!(xNorms, err_x) history && push!(yNorms, err_y) @@ -449,11 +449,8 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved_lq = rNorm_lq ≤ ε solved_cg = transfer_to_craig && rNorm_cg ≤ ε if σₑₛₜ > 0 - if transfer_to_craig - solved_cg = solved_cg || err_x ≤ etolx || err_y ≤ etoly - else - solved_lq = solved_lq || err_x ≤ etolx || err_y ≤ etoly - end + solved_lq = solved_lq || err_x ≤ utolx || err_y ≤ utoly + solved_cg = transfer_to_craig && (solved_cg || err_x ≤ utolx || err_y ≤ utoly) end kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm_lq) diff --git a/test/test_lnlq.jl b/test/test_lnlq.jl index 888119db8..b308609fa 100644 --- a/test/test_lnlq.jl +++ b/test/test_lnlq.jl @@ -1,5 +1,5 @@ function test_lnlq(A, b,transfer_to_craig) - (x, y, stats) = lnlq(A, b, transfer_to_craig=transfer_to_craig, etolx=0.0, etoly=0.0) + (x, y, stats) = lnlq(A, b, transfer_to_craig=transfer_to_craig, utolx=0.0, utoly=0.0) r = b - A * x resid = norm(r) / norm(b) return (x, y, stats, resid) @@ -61,8 +61,8 @@ end # Test regularization A, b, λ = regularization(FC=FC) - (x, y, stats) = lnlq(A, b, λ=λ, transfer_to_craig=transfer_to_craig, etolx=0.0, etoly=0.0) - (xₛ, yₛ, stats) = lnlq(A, b, transfer_to_craig=transfer_to_craig, atol=0.0, rtol=0.0, etolx=1e-10, etoly=1e-10, λ=λ) + (x, y, stats) = lnlq(A, b, λ=λ, transfer_to_craig=transfer_to_craig, utolx=0.0, utoly=0.0) + (xₛ, yₛ, stats) = lnlq(A, b, transfer_to_craig=transfer_to_craig, atol=0.0, rtol=0.0, utolx=1e-10, utoly=1e-10, λ=λ) for (x, y) in ((x, y), (xₛ, yₛ)) s = λ * y r = b - (A * x + λ * s) From 3c580a2cfd69f0e610e23b12854b9780ee4bcb3b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 13 Oct 2022 23:21:25 -0400 Subject: [PATCH 080/182] [documentation] Replace symmetric by Hermitian --- README.md | 4 ++-- docs/make.jl | 10 +++++----- docs/src/index.md | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6c4c8863c..55476e684 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Overdetermined sytems are less common but also occur. where **_A_** can have any shape. -5. Saddle-point and symmetric quasi-definite (SQD) systems +5. Saddle-point and Hermitian quasi-definite systems

    [M     A]  [x] = [b] @@ -86,7 +86,7 @@ where **_A_** can have any shape. where **_A_** can have any shape. -6. Generalized saddle-point and unsymmetric partitioned systems +6. Generalized saddle-point and non-Hermitian partitioned systems

    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�Q`>PAPe!W5gWAdzL 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 literal 135734 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 T5C1$)euMUL{U7p=+F$)|8gO!b diff --git a/docs/src/processes.md b/docs/src/processes.md index da6087017..12dc7b309 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -4,6 +4,10 @@ Krylov processes are the foundation of Krylov methods, they generate bases of Kr ### Notation +For a matrix $A$, $A^H$ denotes the conjugate transpose of $A$. + +It coincides with $A^T$, the transpose of $A$, for real matrices. + Define $V_k := \begin{bmatrix} v_1 & \ldots & v_k \end{bmatrix} \enspace$ and $\enspace U_k := \begin{bmatrix} u_1 & \ldots & u_k \end{bmatrix}$. For a matrix $C \in \mathbb{C}^{n \times n}$ and a vector $t \in \mathbb{C}^{n}$, the $k$-th Krylov subspace generated by $C$ and $t$ is @@ -95,6 +99,7 @@ Related methods: [`BiLQ`](@ref bilq), [`QMR`](@ref qmr), [`BiLQR`](@ref bilqr), !!! note The scaling factors used in our implementation are $\beta_k = |u_k^H v_k|^{\tfrac{1}{2}}$ and $\gamma_k = (u_k^H v_k) / \beta_k$. + With these scaling factors, the non-Hermitian Lanczos process coincides with the Hermitian Lanczos process when $A = A^H$ and $b = c$. ```@docs nonhermitian_lanczos From 53125529fb2f93f6e63e03bf817d0f997d79a036 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 4 Nov 2022 20:02:17 -0400 Subject: [PATCH 089/182] [documentation] Add a section storage requirements --- docs/make.jl | 1 + docs/src/factorization-free.md | 29 +++++++ docs/src/storage.md | 152 +++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 docs/src/storage.md diff --git a/docs/make.jl b/docs/make.jl index 1b38a9a3e..a699f81a5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,6 +23,7 @@ makedocs( "Generalized saddle-point and non-Hermitian partitioned systems" => "solvers/gsp.md"], "In-place methods" => "inplace.md", "Preconditioners" => "preconditioners.md", + "Storage requirements" => "storage.md", "GPU support" => "gpu.md", "Warm start" => "warm_start.md", "Factorization-free operators" => "factorization-free.md", diff --git a/docs/src/factorization-free.md b/docs/src/factorization-free.md index 81f995810..b97108b99 100644 --- a/docs/src/factorization-free.md +++ b/docs/src/factorization-free.md @@ -1,3 +1,32 @@ +```@raw html + +``` + ## [Factorization-free operators](@id factorization-free) All methods are factorization-free, which means that you only need to provide operator-vector products. diff --git a/docs/src/storage.md b/docs/src/storage.md new file mode 100644 index 000000000..1f7796452 --- /dev/null +++ b/docs/src/storage.md @@ -0,0 +1,152 @@ +```@meta +# Thanks Morten Piibeleht for the hack with the tables! +``` + +```@raw html + +``` + +# [Storage requirements](@id storage-requirements) + +This section provides the storage requirements of all Krylov methods available in Krylov.jl. + +### Notation + +We denote by $m$ and $n$ the number of rows and columns of the linear problem. +The memory parameter of DIOM, FOM, DQGMRES, GMRES, FGMRES and GPMR is $k$. +The numbers of shifts of CG-LANCZOS-SHIFT is $p$. + +## Theoretical storage requirements + +The following tables provide the number of coefficients that must be allocated for each Krylov method. +The coefficients have the same type as those that composed the linear problem we seek to solve. +Each table summarizes the storage requirements a Krylov methods recommended to a specific linear problem. + +#### Hermitian positive definite linear systems + +| Methods | [`CG`](@ref cg) | [`CR`](@ref cr) | [`CG-LANCZOS`](@ref cg_lanczos) | [`CG-LANCZOS-SHIFT`](@ref cg_lanczos_shift) | +|:-------:|:---------------:|:---------------:|:-------------------------------:|:-------------------------------------------:| + Storage | $4n$ | $5n$ | $5n$ | $3n + 2np + 5p$ | + +#### Hermitian indefinite linear systems + +| Methods | [`SYMMLQ`](@ref symmlq) | [`MINRES`](@ref minres) | [`MINRES-QLP`](@ref minres_qlp) | +|:-------:|:-----------------------:|:-----------------------:|:-------------------------------:| +| Storage | $5n$ | $6n$ | $6n$ | + +#### Non-Hermitian linear systems + +| Methods | [`CGS`](@ref cgs) | [`BICGSTAB`](@ref bicgstab) | [`BiLQ`](@ref bilq) | [`QMR`](@ref qmr) | +|:-------:|:-----------------:|:---------------------------:|:-------------------:|:-----------------:| +| Storage | $6n$ | $6n$ | $8n$ | $9n$ | + +| Methods | [`DIOM`](@ref diom) | [`DQGMRES`](@ref dqgmres) | +|:-------:|:-------------------:|:-------------------------:| +| Storage | $n(2k+1) + 2k - 1$ | $n(2k+2) + 3k + 1$ | + +| Methods | [`FOM`](@ref fom) | [`GMRES`](@ref gmres) | [`FGMRES`](@ref fgmres) | +|:-------:|:--------------------------------------------------:|:---------------------------------------:|:----------------------------------------:| +| Storage$\dfrac{}{}$ | $\!n(2+k) +2k + \dfrac{k(k + 1)}{2}\!$ | $\!n(2+k) + 3k + \dfrac{k(k + 1)}{2}\!$ | $\!n(2+2k) + 3k + \dfrac{k(k + 1)}{2}\!$ | + +#### Least-norm problems + +| Methods | [`USYMLQ`](@ref usymlq) | [`CGNE`](@ref cgne) | [`CRMR`](@ref crmr) | [`LNLQ`](@ref lnlq) | [`CRAIG`](@ref craig) | [`CRAIGMR`](@ref craigmr) | +|:-------:|:-----------------------:|:-------------------:|:-------------------:|:-------------------:|:---------------------:|:-------------------------:| +| Storage | $5n + 3m$ | $3n + 2m$ | $3n + 2m$ | $3n + 4m$ | $3n + 4m$ | $4n + 5m$ | + +#### Least-squares problems + +| Methods | [`USYMQR`](@ref usymqr) | [`CGLS`](@ref cgls) | [`CRLS`](@ref crls) | [`LSLQ`](@ref lslq) | [`LSQR`](@ref lsqr) | [`LSMR`](@ref lsmr) | +|:-------:|:-----------------------:|:-------------------:|:-------------------:|:-------------------:|:-------------------:|:-------------------:| +| Storage | $6n + 3m$ | $3n + 2m$ | $4n + 3m$ | $4n + 2m$ | $4n + 2m$ | $5n + 2m$ | + +#### Adjoint systems + +| Methods | [`BiLQR`](@ref bilqr) | [`TriLQR`](@ref trilqr) | +|:-------:|:---------------------:|:-----------------------:| +| Storage | $11n$ | $6m + 5n$ | + +#### Saddle-point and Hermitian quasi-definite systems + +| Methods | [`TriCG`](@ref tricg) | [`TriMR`](@ref trimr) | +|:--------:|:---------------------:|:---------------------:| +| Storage | $6n + 6m$ | $8n + 8m$ | + +#### Generalized saddle-point and non-Hermitian partitioned systems + +| Method | [`GPMR`](@ref gpmr) | +|:-------:|:-------------------------:| +| Storage | $(2+k)(n+m) + 2k^2 + 11k$ | + +## Practical storage requirements + +Each method has its own `KrylovSolver` that contains all the storage needed by the method. +In the REPL, the size in bytes of each attribute and the total amount of memory allocated by the solver are displayed when we show a `KrylovSolver`. + +```@example storage +using Krylov + +m = 5000 +n = 12000 +A = rand(Float64, m, n) +b = rand(Float64, m) +solver = LsmrSolver(A, b) +show(stdout, solver, show_stats=false) +``` + +If we want the total number of bytes used by the solver, we can call `nbytes = sizeof(solver)`. + +```@example storage +nbytes = sizeof(solver) +``` + +Thereafter, we can use `Base.format_bytes(nbytes)` to recover what is displayed in the REPL. + +```@example storage +Base.format_bytes(nbytes) +``` + +To verify that we match the theoretical results, we just need to multiply the storage requirement of a method by the number of bytes associated to the precision of the linear problem. +For instance, we need 4 bytes for the precision `Float32` , 8 bytes for precisions `Float64` and `ComplexF32`, and 16 bytes for the precision `ComplexF64`. + +```@example storage +FC = Float64 # precision of the least-squares problem +ncoefs_lsmr = 5*n + 2*m # number of coefficients +nbytes_lsmr = sizeof(FC) * ncoefs_lsmr # number of bytes +``` + +Therefore, you can check that you have enough memory in RAM to allocate a `KrylovSolver`. + +```@example storage +free_nbytes = Sys.free_memory() +Base.format_bytes(free_nbytes) # Total free memory in RAM in bytes. +``` + +!!! note + - Beyond having faster operations, using low precisions, such as simple precision, allows to store more coefficients in RAM and solve larger linear problems. + - In the file [test_allocations.jl](https://github.com/JuliaSmoothOptimizers/Krylov.jl/blob/main/test/test_allocations.jl), we use the macro `@allocated` to test that we match the expected storage requirement of each method with a tolerance of 2%. From 60e5b6501e380a9fe472652f5686474019ef7a71 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 5 Nov 2022 13:06:09 -0400 Subject: [PATCH 090/182] Fix two typos --- docs/src/storage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/storage.md b/docs/src/storage.md index 1f7796452..8390792f9 100644 --- a/docs/src/storage.md +++ b/docs/src/storage.md @@ -44,8 +44,8 @@ The numbers of shifts of CG-LANCZOS-SHIFT is $p$. ## Theoretical storage requirements The following tables provide the number of coefficients that must be allocated for each Krylov method. -The coefficients have the same type as those that composed the linear problem we seek to solve. -Each table summarizes the storage requirements a Krylov methods recommended to a specific linear problem. +The coefficients have the same type as those that compose the linear problem we seek to solve. +Each table summarizes the storage requirements of Krylov methods recommended to a specific linear problem. #### Hermitian positive definite linear systems From aa8b6acc6ce6e767ac0a0a9e6ff2cec12d3f0b3b Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Sat, 5 Nov 2022 18:17:05 -0400 Subject: [PATCH 091/182] Update docs/src/storage.md Co-authored-by: Dominique --- docs/src/storage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/storage.md b/docs/src/storage.md index 8390792f9..a60b30a53 100644 --- a/docs/src/storage.md +++ b/docs/src/storage.md @@ -59,7 +59,7 @@ Each table summarizes the storage requirements of Krylov methods recommended to |:-------:|:-----------------------:|:-----------------------:|:-------------------------------:| | Storage | $5n$ | $6n$ | $6n$ | -#### Non-Hermitian linear systems +#### Non-Hermitian square linear systems | Methods | [`CGS`](@ref cgs) | [`BICGSTAB`](@ref bicgstab) | [`BiLQ`](@ref bilq) | [`QMR`](@ref qmr) | |:-------:|:-----------------:|:---------------------------:|:-------------------:|:-----------------:| From 823f734527b5ffd1eca86dab71d31baa4daed2bf Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Sat, 5 Nov 2022 18:19:14 -0400 Subject: [PATCH 092/182] Update docs/src/storage.md --- docs/src/storage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/storage.md b/docs/src/storage.md index a60b30a53..903cc0558 100644 --- a/docs/src/storage.md +++ b/docs/src/storage.md @@ -132,7 +132,7 @@ Base.format_bytes(nbytes) ``` To verify that we match the theoretical results, we just need to multiply the storage requirement of a method by the number of bytes associated to the precision of the linear problem. -For instance, we need 4 bytes for the precision `Float32` , 8 bytes for precisions `Float64` and `ComplexF32`, and 16 bytes for the precision `ComplexF64`. +For instance, we need 4 bytes for the precision `Float32`, 8 bytes for precisions `Float64` and `ComplexF32`, and 16 bytes for the precision `ComplexF64`. ```@example storage FC = Float64 # precision of the least-squares problem From 135d47ff24939af1c5653d8ca33bcb91d9460375 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 6 Nov 2022 16:34:47 -0500 Subject: [PATCH 093/182] Use the same tolerance for all allocation tests --- docs/make.jl | 2 +- docs/src/solvers/gsp.md | 2 +- docs/src/solvers/sid.md | 2 +- docs/src/solvers/sp_sqd.md | 2 +- docs/src/solvers/spd.md | 2 +- docs/src/solvers/unsymmetric.md | 2 +- test/test_allocations.jl | 10 +++++----- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index a699f81a5..210052165 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,7 +15,7 @@ makedocs( "Krylov processes" => "processes.md", "Krylov methods" => ["Hermitian positive definite linear systems" => "solvers/spd.md", "Hermitian indefinite linear systems" => "solvers/sid.md", - "Non-Hermitian linear systems" => "solvers/unsymmetric.md", + "Non-Hermitian square linear systems" => "solvers/unsymmetric.md", "Least-norm problems" => "solvers/ln.md", "Least-squares problems" => "solvers/ls.md", "Adjoint systems" => "solvers/as.md", diff --git a/docs/src/solvers/gsp.md b/docs/src/solvers/gsp.md index 10aaccbe0..33c580b8a 100644 --- a/docs/src/solvers/gsp.md +++ b/docs/src/solvers/gsp.md @@ -1,5 +1,5 @@ ```@meta -# Generalized saddle-point and unsymmetric partitioned systems +# Generalized saddle-point and non-Hermitian partitioned systems ``` ## GPMR diff --git a/docs/src/solvers/sid.md b/docs/src/solvers/sid.md index 1bd459cd2..e911681be 100644 --- a/docs/src/solvers/sid.md +++ b/docs/src/solvers/sid.md @@ -1,5 +1,5 @@ ```@meta -# Symmetric indefinite linear systems +# Hermitian indefinite linear systems ``` ## SYMMLQ diff --git a/docs/src/solvers/sp_sqd.md b/docs/src/solvers/sp_sqd.md index 518684b5b..4ee4ab09b 100644 --- a/docs/src/solvers/sp_sqd.md +++ b/docs/src/solvers/sp_sqd.md @@ -1,5 +1,5 @@ ```@meta -# Saddle-point and symmetric quasi-definite systems +# Saddle-point and Hermitian quasi-definite systems ``` ## TriCG diff --git a/docs/src/solvers/spd.md b/docs/src/solvers/spd.md index 79bb6e9e8..aebda285b 100644 --- a/docs/src/solvers/spd.md +++ b/docs/src/solvers/spd.md @@ -1,5 +1,5 @@ ```@meta -# Symmetric positive definite linear systems +# Hermitian positive definite linear systems ``` ## CG diff --git a/docs/src/solvers/unsymmetric.md b/docs/src/solvers/unsymmetric.md index 2c596361a..c9e77f787 100644 --- a/docs/src/solvers/unsymmetric.md +++ b/docs/src/solvers/unsymmetric.md @@ -1,5 +1,5 @@ ```@meta -# Unsymmetric linear systems +# Non-Hermitian square linear systems ``` ## BiLQ diff --git a/test/test_allocations.jl b/test/test_allocations.jl index 5f122b33e..174d0ae55 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -3,7 +3,7 @@ for FC in (Float32, Float64, ComplexF32, ComplexF64) @testset "Data Type: $FC" begin - A = FC.(get_div_grad(16, 16, 16)) # Dimension m x n + A = FC.(get_div_grad(18, 18, 18)) # Dimension m x n m,n = size(A) k = div(n, 2) Au = A[1:k,:] # Dimension k x n @@ -26,7 +26,7 @@ expected_symmlq_bytes = storage_symmlq_bytes(n) symmlq(A, b) # warmup actual_symmlq_bytes = @allocated symmlq(A, b) - @test expected_symmlq_bytes ≤ actual_symmlq_bytes ≤ 1.03 * expected_symmlq_bytes + @test expected_symmlq_bytes ≤ actual_symmlq_bytes ≤ 1.02 * expected_symmlq_bytes solver = SymmlqSolver(A, b) symmlq!(solver, A, b) # warmup @@ -371,7 +371,7 @@ expected_lslq_bytes = storage_lslq_bytes(m, k) (x, stats) = lslq(Ao, b) # warmup actual_lslq_bytes = @allocated lslq(Ao, b) - @test expected_lslq_bytes ≤ actual_lslq_bytes ≤ 1.03 * expected_lslq_bytes + @test expected_lslq_bytes ≤ actual_lslq_bytes ≤ 1.02 * expected_lslq_bytes solver = LslqSolver(Ao, b) lslq!(solver, Ao, b) # warmup @@ -405,7 +405,7 @@ expected_lsqr_bytes = storage_lsqr_bytes(m, k) (x, stats) = lsqr(Ao, b) # warmup actual_lsqr_bytes = @allocated lsqr(Ao, b) - @test expected_lsqr_bytes ≤ actual_lsqr_bytes ≤ 1.03 * expected_lsqr_bytes + @test expected_lsqr_bytes ≤ actual_lsqr_bytes ≤ 1.02 * expected_lsqr_bytes solver = LsqrSolver(Ao, b) lsqr!(solver, Ao, b) # warmup @@ -422,7 +422,7 @@ expected_lsmr_bytes = storage_lsmr_bytes(m, k) (x, stats) = lsmr(Ao, b) # warmup actual_lsmr_bytes = @allocated lsmr(Ao, b) - @test expected_lsmr_bytes ≤ actual_lsmr_bytes ≤ 1.03 * expected_lsmr_bytes + @test expected_lsmr_bytes ≤ actual_lsmr_bytes ≤ 1.02 * expected_lsmr_bytes solver = LsmrSolver(Ao, b) lsmr!(solver, Ao, b) # warmup From 630faacfa28f4d7503b2076285691c78a175a0d6 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 10 Nov 2022 10:31:01 -0500 Subject: [PATCH 094/182] Update the show function for Krylov solvers --- src/krylov_solvers.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 48b8d774a..a427cf63b 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1921,7 +1921,8 @@ function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) storage = format_bytes(nbytes) architecture = S <: Vector ? "CPU" : "GPU" l1 = max(length(name_solver), length(string(FC)) + 11) # length("Precision: ") = 11 - l2 = max(ndigits(solver.m) + 7, length(architecture) + 14, length(string(S)) + 8) # length("Vector{}") = 8, # length("Architecture: ") = 14 and length("nrows: ") = 7 + nchar = workspace <: Union{CgLanczosShiftSolver, FomSolver, DiomSolver, DqgmresSolver, GmresSolver, FgmresSolver, GpmrSolver} ? 8 : 0 # length("Vector{}") = 8 + l2 = max(ndigits(solver.m) + 7, length(architecture) + 14, length(string(S)) + nchar) # length("nrows: ") = 7 and length("Architecture: ") = 14 l2 = max(l2, length(name_stats) + 2 + length(string(T))) # length("{}") = 2 l3 = max(ndigits(solver.n) + 7, length(storage) + 9) # length("Storage: ") = 9 and length("cols: ") = 7 format = Printf.Format("│%$(l1)s│%$(l2)s│%$(l3)s│\n") From f152a01c3a501564a0911d2ac2aa98827292e221 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 8 Nov 2022 17:06:34 -0500 Subject: [PATCH 095/182] Add a keyword argument iostream for all Krylov methods --- src/bicgstab.jl | 14 +++++----- src/bilq.jl | 14 +++++----- src/bilqr.jl | 18 ++++++------- src/cg.jl | 16 +++++------ src/cg_lanczos.jl | 14 +++++----- src/cg_lanczos_shift.jl | 8 +++--- src/cgls.jl | 14 +++++----- src/cgne.jl | 14 +++++----- src/cgs.jl | 14 +++++----- src/cr.jl | 60 ++++++++++++++++++++--------------------- src/craig.jl | 14 +++++----- src/craigmr.jl | 14 +++++----- src/crls.jl | 14 +++++----- src/crmr.jl | 14 +++++----- src/diom.jl | 14 +++++----- src/dqgmres.jl | 14 +++++----- src/fgmres.jl | 14 +++++----- src/fom.jl | 14 +++++----- src/gmres.jl | 14 +++++----- src/gpmr.jl | 14 +++++----- src/krylov_solvers.jl | 2 +- src/lnlq.jl | 14 +++++----- src/lslq.jl | 14 +++++----- src/lsmr.jl | 14 +++++----- src/lsqr.jl | 14 +++++----- src/minres.jl | 14 +++++----- src/minres_qlp.jl | 14 +++++----- src/qmr.jl | 14 +++++----- src/symmlq.jl | 14 +++++----- src/tricg.jl | 14 +++++----- src/trilqr.jl | 20 +++++++------- src/trimr.jl | 14 +++++----- src/usymlq.jl | 14 +++++----- src/usymqr.jl | 14 +++++----- 34 files changed, 258 insertions(+), 258 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index b2421f1f7..6ea05a5c6 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -20,7 +20,7 @@ export bicgstab, bicgstab! c::AbstractVector{FC}=b, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -102,12 +102,12 @@ end function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("BICGSTAB: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "BICGSTAB: system of size %d\n", n) # Check M = Iₙ and N = Iₙ MisI = (M === I) @@ -163,8 +163,8 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s %8s %8s\n", "k", "‖rₖ‖", "|αₖ|", "|ωₖ|") - kdisplay(iter, verbose) && @printf("%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) + (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s\n", "k", "‖rₖ‖", "|αₖ|", "|ωₖ|") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) next_ρ = @kdot(n, c, r) # ρ₁ = ⟨r̅₀,r₀⟩ if next_ρ == 0 @@ -220,9 +220,9 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax breakdown = (α == 0 || isnan(α)) - kdisplay(iter, verbose) && @printf("%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") breakdown && (status = "breakdown αₖ == 0") diff --git a/src/bilq.jl b/src/bilq.jl index 163b6339d..e90c1933a 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -17,7 +17,7 @@ export bilq, bilq! c::AbstractVector{FC}=b, atol::T=√eps(T), rtol::T=√eps(T), transfer_to_bicg::Bool=true, itmax::Int=0, verbose::Int=0, - history::Bool=false, callback=solver->false) + history::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -89,12 +89,12 @@ end function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_bicg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("BILQ: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "BILQ: system of size %d\n", n) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -135,8 +135,8 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab itmax == 0 && (itmax = 2*n) ε = atol + rtol * bNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, bNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) # Initialize the Lanczos biorthogonalization process. cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ @@ -313,9 +313,9 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Ab solved_cg = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) tired = iter ≥ itmax breakdown = !solved_lq && !solved_cg && (pᴴq == 0) - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm_lq) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Compute BICG point # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ diff --git a/src/bilqr.jl b/src/bilqr.jl index 5e45cb372..df2b7b2d9 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -16,7 +16,7 @@ export bilqr, bilqr! (x, y, stats) = bilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), transfer_to_bicg::Bool=true, itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false) + callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,13 +95,13 @@ end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_bicg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("Systems must be square") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("BILQR: systems of size %d\n", n) + (verbose > 0) && @printf(iostream, "BILQR: systems of size %d\n", n) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -143,8 +143,8 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: history && push!(sNorms, cNorm) εL = atol + rtol * bNorm εQ = atol + rtol * cNorm - (verbose > 0) && @printf("%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e\n", iter, bNorm, cNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) # Initialize the Lanczos biorthogonalization process. cᴴb = @kdot(n, s₀, r₀) # ⟨s₀,r₀⟩ = ⟨c - Aᴴy₀,b - Ax₀⟩ @@ -409,11 +409,11 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: tired = iter ≥ itmax breakdown = !solved_lq && !solved_cg && (pᴴq == 0) - kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf("%5d %7s %7.1e\n", iter, "", sNorm) - kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf("%5d %7.1e %7s\n", iter, rNorm_lq, "") - kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf("%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) + kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) + kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") + kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Compute BICG point # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ diff --git a/src/cg.jl b/src/cg.jl index 1c4b39cb5..370692453 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -21,7 +21,7 @@ export cg, cg! M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, radius::T=zero(T), linesearch::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,14 +95,14 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, radius :: T=zero(T), linesearch :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("CG: system of %d equations in %d variables\n", n, n) + (verbose > 0) && @printf(iostream, "CG: system of %d equations in %d variables\n", n, n) # Tests M = Iₙ MisI = (M === I) @@ -145,8 +145,8 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; pAp = zero(T) pNorm² = γ ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s %8s %8s %8s\n", "k", "‖r‖", "pAp", "α", "σ") - kdisplay(iter, verbose) && @printf("%5d %7.1e ", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %8s\n", "k", "‖r‖", "pAp", "α", "σ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) solved = rNorm ≤ ε tired = iter ≥ itmax @@ -177,7 +177,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Compute step size to boundary if applicable. σ = radius > 0 ? maximum(to_boundary(n, x, p, radius, dNorm2=pNorm²)) : α - kdisplay(iter, verbose) && @printf("%8.1e %8.1e %8.1e\n", pAp, α, σ) + kdisplay(iter, verbose) && @printf(iostream, "%8.1e %8.1e %8.1e\n", pAp, α, σ) # Move along p from x to the boundary if either # the next step leads outside the trust region or @@ -212,9 +212,9 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 tired = iter ≥ itmax user_requested_exit = callback(solver) :: Bool - kdisplay(iter, verbose) && @printf("%5d %7.1e ", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") solved && on_boundary && (status = "on trust-region boundary") solved && linesearch && (pAp ≤ 0) && (status = "nonpositive curvature detected") diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 3efdcd90e..44e53e162 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -17,7 +17,7 @@ export cg_lanczos, cg_lanczos! (x, stats) = cg_lanczos(A, b::AbstractVector{FC}; M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, check_curvature::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -89,12 +89,12 @@ end function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("CG Lanczos: system of %d equations in %d variables\n", n, n) + (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables\n", n, n) # Tests M = Iₙ MisI = (M === I) @@ -153,8 +153,8 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F # Define stopping tolerance. ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) indefinite = false solved = rNorm ≤ ε @@ -197,7 +197,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F rNorm = abs(σ) # ‖rₖ₊₁‖_M = |σₖ₊₁| because rₖ₊₁ = σₖ₊₁ * vₖ₊₁ and ‖vₖ₊₁‖_M = 1 history && push!(rNorms, rNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. @@ -208,7 +208,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") (check_curvature & indefinite) && (status = "negative curvature") diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index a548fe2aa..fa45f77e2 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -19,7 +19,7 @@ export cg_lanczos_shift, cg_lanczos_shift! M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, check_curvature::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -75,14 +75,14 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") nshifts = length(shifts) - (verbose > 0) && @printf("CG Lanczos: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) + (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) # Tests M = Iₙ MisI = (M === I) @@ -230,7 +230,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr solved = sum(not_cv) == 0 tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") diff --git a/src/cgls.jl b/src/cgls.jl index ac4bb9b8d..5bcd0083c 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -33,7 +33,7 @@ export cgls, cgls! (x, stats) = cgls(A, b::AbstractVector{FC}; M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), radius::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -91,11 +91,11 @@ function cgls! end function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), radius :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CGLS: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "CGLS: system of %d equations in %d variables\n", m, n) # Tests M = Iₙ MisI = (M === I) @@ -138,8 +138,8 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) ε = atol + rtol * ArNorm - (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) status = "unknown" on_boundary = false @@ -175,12 +175,12 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) user_requested_exit = callback(solver) :: Bool solved = (ArNorm ≤ ε) | on_boundary tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") diff --git a/src/cgne.jl b/src/cgne.jl index 50155057c..c07d2dbb6 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -33,7 +33,7 @@ export cgne, cgne! (x, stats) = cgne(A, b::AbstractVector{FC}; N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -100,11 +100,11 @@ function cgne! end function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CGNE: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "CGNE: system of %d equations in %d variables\n", m, n) # Tests N = Iₙ NisI = (N === I) @@ -151,8 +151,8 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol + rtol * pNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf("%5s %8s\n", "k", "‖r‖") - kdisplay(iter, verbose) && @printf("%5d %8.2e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s\n", "k", "‖r‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) status = "unknown" solved = rNorm ≤ ɛ_c @@ -181,7 +181,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; rNorm = sqrt(γ_next) history && push!(rNorms, rNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf("%5d %8.2e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. @@ -193,7 +193,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = (rNorm > 100 * ɛ_c) && (pNorm ≤ ɛ_i) tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") inconsistent && (status = "system probably inconsistent") diff --git a/src/cgs.jl b/src/cgs.jl index 4f770c5f3..1e0b7185a 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -14,7 +14,7 @@ export cgs, cgs! (x, stats) = cgs(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, - history::Bool=false, ldiv::Bool=false, callback=solver->false) + history::Bool=false, ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -98,12 +98,12 @@ end function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CGS: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "CGS: system of size %d\n", n) # Check M = Iₙ and N = Iₙ MisI = (M === I) @@ -163,8 +163,8 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) u .= r # u₀ p .= r # p₀ @@ -219,9 +219,9 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax breakdown = (α == 0 || isnan(α)) - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") breakdown && (status = "breakdown αₖ == 0") diff --git a/src/cr.jl b/src/cr.jl index 4bb104534..09f7dc3e2 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -19,7 +19,7 @@ export cr, cr! M=I, atol::T=√eps(T), rtol::T=√eps(T), γ::T=√eps(T), itmax::Int=0, radius::T=zero(T), verbose::Int=0, linesearch::Bool=false, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,14 +95,14 @@ end function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, atol :: T=√eps(T), rtol :: T=√eps(T), γ :: T=√eps(T), itmax :: Int=0, radius :: T=zero(T), verbose :: Int=0, linesearch :: Bool=false, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("CR: system of %d equations in %d variables\n", n, n) + (verbose > 0) && @printf(iostream, "CR: system of %d equations in %d variables\n", n, n) # Tests M = Iₙ MisI = (M === I) @@ -160,8 +160,8 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ArNorm = @knrm2(n, Ar) # ‖Ar‖ history && push!(ArNorms, ArNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") - kdisplay(iter, verbose) && @printf(" %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") + kdisplay(iter, verbose) && @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) descent = pr > 0 # pᴴr > 0 means p is a descent direction solved = rNorm ≤ ε @@ -175,7 +175,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; if linesearch if (pAp ≤ γ * pNorm²) || (ρ ≤ γ * rNorm²) npcurv = true - (verbose > 0) && @printf("nonpositive curvature detected: pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) + (verbose > 0) && @printf(iostream, "nonpositive curvature detected: pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) stats.solved = solved stats.inconsistent = false stats.status = "nonpositive curvature" @@ -187,30 +187,30 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; MisI || mulorldiv!(Mq, M, q, ldiv) if radius > 0 - (verbose > 0) && @printf("radius = %8.1e > 0 and ‖x‖ = %8.1e\n", radius, xNorm) + (verbose > 0) && @printf(iostream, "radius = %8.1e > 0 and ‖x‖ = %8.1e\n", radius, xNorm) # find t1 > 0 and t2 < 0 such that ‖x + ti * p‖² = radius² (i = 1, 2) xNorm² = xNorm * xNorm t = to_boundary(n, x, p, radius; flip = false, xNorm2 = xNorm², dNorm2 = pNorm²) t1 = maximum(t) # > 0 t2 = minimum(t) # < 0 tr = maximum(to_boundary(n, x, r, radius; flip = false, xNorm2 = xNorm², dNorm2 = rNorm²)) - (verbose > 0) && @printf("t1 = %8.1e, t2 = %8.1e and tr = %8.1e\n", t1, t2, tr) + (verbose > 0) && @printf(iostream, "t1 = %8.1e, t2 = %8.1e and tr = %8.1e\n", t1, t2, tr) if abspAp ≤ γ * pNorm * @knrm2(n, q) # pᴴAp ≃ 0 npcurv = true # nonpositive curvature - (verbose > 0) && @printf("pᴴAp = %8.1e ≃ 0\n", pAp) + (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e ≃ 0\n", pAp) if abspr ≤ γ * pNorm * rNorm # pᴴr ≃ 0 - (verbose > 0) && @printf("pᴴr = %8.1e ≃ 0, redefining p := r\n", pr) + (verbose > 0) && @printf(iostream, "pᴴr = %8.1e ≃ 0, redefining p := r\n", pr) p = r # - ∇q(x) q = Ar # q(x + αr) = q(x) - α ‖r‖² + ½ α² rᴴAr # 1) if rᴴAr > 0, the quadratic decreases from α = 0 to α = ‖r‖² / rᴴAr # 2) if rᴴAr ≤ 0, the quadratic decreases to -∞ in the direction r if ρ > 0 # case 1 - (verbose > 0) && @printf("quadratic is convex in direction r, curv = %8.1e\n", ρ) + (verbose > 0) && @printf(iostream, "quadratic is convex in direction r, curv = %8.1e\n", ρ) α = min(tr, rNorm² / ρ) else # case 2 - (verbose > 0) && @printf("r is a direction of nonpositive curvature: %8.1e\n", ρ) + (verbose > 0) && @printf(iostream, "r is a direction of nonpositive curvature: %8.1e\n", ρ) α = tr end else @@ -221,18 +221,18 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ρ > 0 && (tr = min(tr, rNorm² / ρ)) Δ = -α * pr + tr * rNorm² - (tr)^2 * ρ / 2 # as pᴴAp = 0 if Δ > 0 # direction r engenders a better decrease - (verbose > 0) && @printf("direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf("redefining p := r\n") + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") p = r q = Ar α = tr else - (verbose > 0) && @printf("direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) end end elseif pAp > 0 && ρ > 0 # no negative curvature - (verbose > 0) && @printf("positive curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) + (verbose > 0) && @printf(iostream, "positive curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) α = ρ / @kdotr(n, q, Mq) if α ≥ t1 α = t1 @@ -241,49 +241,49 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; elseif pAp > 0 && ρ < 0 npcurv = true - (verbose > 0) && @printf("pᴴAp = %8.1e > 0 and rᴴAr = %8.1e < 0\n", pAp, ρ) + (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e > 0 and rᴴAr = %8.1e < 0\n", pAp, ρ) # q_p is minimal for α_p = rᴴp / pᴴAp α = descent ? min(t1, pr / pAp) : max(t2, pr / pAp) Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 if Δ > 0 - (verbose > 0) && @printf("direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf("redefining p := r\n") + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") p = r q = Ar α = tr else - (verbose > 0) && @printf("direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) end elseif pAp < 0 && ρ > 0 npcurv = true - (verbose > 0) && @printf("pᴴAp = %8.1e < 0 and rᴴAr = %8.1e > 0\n", pAp, ρ) + (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e < 0 and rᴴAr = %8.1e > 0\n", pAp, ρ) α = descent ? t1 : t2 tr = min(tr, rNorm² / ρ) Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 if Δ > 0 - (verbose > 0) && @printf("direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf("redefining p := r\n") + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") p = r q = Ar α = tr else - (verbose > 0) && @printf("direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) end elseif pAp < 0 && ρ < 0 npcurv = true - (verbose > 0) && @printf("negative curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) + (verbose > 0) && @printf(iostream, "negative curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) α = descent ? t1 : t2 Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 if Δ > 0 - (verbose > 0) && @printf("direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf("redefining p := r\n") + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") p = r q = Ar α = tr else - (verbose > 0) && @printf("direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) end end @@ -311,7 +311,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 if kdisplay(iter, verbose) m = m - α * pr + α^2 * pAp / 2 - @printf(" %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) end # Stopping conditions that do not depend on user input. @@ -351,7 +351,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; descent = pr > 0 end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") on_boundary && (status = "on trust-region boundary") diff --git a/src/craig.jl b/src/craig.jl index 7f87d0861..56245ac6b 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -38,7 +38,7 @@ export craig, craig! M=I, N=I, sqd::Bool=false, λ::T=zero(T), atol::T=√eps(T), btol::T=√eps(T), rtol::T=√eps(T), conlim::T=1/√eps(T), itmax::Int=0, verbose::Int=0, transfer_to_lsqr::Bool=false, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -126,11 +126,11 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), btol :: T=√eps(T), rtol :: T=√eps(T), conlim :: T=1/√eps(T), itmax :: Int=0, verbose :: Int=0, transfer_to_lsqr :: Bool=false, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CRAIG: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "CRAIG: system of %d equations in %d variables\n", m, n) # Check sqd and λ parameters sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") @@ -202,8 +202,8 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol # Stopping tolerance for inconsistent systems. ctol = conlim > 0 ? 1/conlim : zero(T) # Stopping tolerance for ill-conditioned operators. - (verbose > 0) && @printf("%5s %8s %8s %8s %8s %8s %7s\n", "k", "‖r‖", "‖x‖", "‖A‖", "κ(A)", "α", "β") - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e %8.2e %8.2e\n", iter, rNorm, xNorm, Anorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s %8s %8s %7s\n", "k", "‖r‖", "‖x‖", "‖A‖", "κ(A)", "α", "β") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e\n", iter, rNorm, xNorm, Anorm, Acond) bkwerr = one(T) # initial value of the backward error ‖r‖ / √(‖b‖² + ‖A‖² ‖x‖²) @@ -307,7 +307,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; ρ_prev = ρ # Only differs from α if λ > 0. - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e %8.2e %8.2e %8.1e %7.1e\n", iter, rNorm, xNorm, Anorm, Acond, α, β) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e %8.1e %7.1e\n", iter, rNorm, xNorm, Anorm, Acond, α, β) solved_lim = bkwerr ≤ btol solved_mach = one(T) + bkwerr ≤ one(T) @@ -323,7 +323,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = false tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # transfer to LSQR point if requested if λ > 0 && transfer_to_lsqr diff --git a/src/craigmr.jl b/src/craigmr.jl index 776a25558..eb6d5f958 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -31,7 +31,7 @@ export craigmr, craigmr! (x, y, stats) = craigmr(A, b::AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -121,11 +121,11 @@ function craigmr! end function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CRAIGMR: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "CRAIGMR: system of %d equations in %d variables\n", m, n) # Check sqd and λ parameters sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") @@ -182,8 +182,8 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β, α, β, α, 0, 1, Anorm²) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β, α, β, α, 0, 1, Anorm²) # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 @@ -308,7 +308,7 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; ArNorm = α * β * abs(ζ/ρ) history && push!(ArNorms, ArNorm) - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) if λ > 0 (cdₖ, sdₖ, λₖ₊₁) = sym_givens(λ, λₐᵤₓ) @@ -331,7 +331,7 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "found approximate minimum-norm solution") diff --git a/src/crls.jl b/src/crls.jl index da9471fe2..244160d2c 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -25,7 +25,7 @@ export crls, crls! (x, stats) = crls(A, b::AbstractVector{FC}; M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), radius::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -82,11 +82,11 @@ function crls! end function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), radius :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CRLS: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "CRLS: system of %d equations in %d variables\n", m, n) # Tests M = Iₙ MisI = (M === I) @@ -138,8 +138,8 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; λ > 0 && (γ += λ * ArNorm * ArNorm) history && push!(ArNorms, ArNorm) ε = atol + rtol * ArNorm - (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) status = "unknown" on_boundary = false @@ -199,12 +199,12 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) user_requested_exit = callback(solver) :: Bool solved = (ArNorm ≤ ε) || on_boundary tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") diff --git a/src/crmr.jl b/src/crmr.jl index b4445ef81..e5709c243 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -31,7 +31,7 @@ export crmr, crmr! (x, stats) = crmr(A, b::AbstractVector{FC}; N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -98,11 +98,11 @@ function crmr! end function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("CRMR: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "CRMR: system of %d equations in %d variables\n", m, n) # Tests N = Iₙ NisI = (N === I) @@ -147,8 +147,8 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(ArNorms, ArNorm) ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf("%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) status = "unknown" solved = rNorm ≤ ɛ_c @@ -179,13 +179,13 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf("%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) user_requested_exit = callback(solver) :: Bool solved = rNorm ≤ ɛ_c inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") diff --git a/src/diom.jl b/src/diom.jl index 23b6d48ca..7afcb8c7d 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -15,7 +15,7 @@ export diom, diom! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -96,12 +96,12 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("DIOM: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "DIOM: system of size %d\n", n) # Check M = Iₙ and N = Iₙ MisI = (M === I) @@ -145,8 +145,8 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) mem = length(V) # Memory for i = 1 : mem @@ -271,9 +271,9 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease_lim = rNorm ≤ ε solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 2d428b87c..faf601b83 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -15,7 +15,7 @@ export dqgmres, dqgmres! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -96,12 +96,12 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("DQGMRES: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "DQGMRES: system of size %d\n", n) # Check M = Iₙ and N = Iₙ MisI = (M === I) @@ -145,8 +145,8 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) # Set up workspace. mem = length(V) # Memory. @@ -273,9 +273,9 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease_lim = rNorm ≤ ε solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") solved && (status = "solution good enough given atol and rtol") tired && (status = "maximum number of iterations exceeded") user_requested_exit && (status = "user-requested exit") diff --git a/src/fgmres.jl b/src/fgmres.jl index 9fc53408a..d17686fa7 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -15,7 +15,7 @@ export fgmres, fgmres! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -99,12 +99,12 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("FGMRES: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "FGMRES: system of size %d\n", n) # Check M = Iₙ MisI = (M === I) @@ -160,8 +160,8 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax == 0 && (itmax = 2*n) inner_itmax = itmax - (verbose > 0) && @printf("%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf("%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -281,7 +281,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = resid_decrease_lim || resid_decrease_mach inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax solver.inner_iter = inner_iter - kdisplay(iter+inner_iter, verbose) && @printf("%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) # Compute vₖ₊₁ if !(solved || inner_tired || breakdown) @@ -324,7 +324,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + inner_iter tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") diff --git a/src/fom.jl b/src/fom.jl index fdd99708b..ba1985d97 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -15,7 +15,7 @@ export fom, fom! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -93,12 +93,12 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("FOM: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "FOM: system of size %d\n", n) # Check M = Iₙ and N = Iₙ MisI = (M === I) @@ -156,8 +156,8 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax == 0 && (itmax = 2*n) inner_itmax = itmax - (verbose > 0) && @printf("%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf("%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -265,7 +265,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; breakdown = Hbis ≤ btol solved = resid_decrease_lim || resid_decrease_mach inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax - kdisplay(iter+inner_iter, verbose) && @printf("%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) # Compute vₖ₊₁. if !(solved || inner_tired || breakdown) @@ -303,7 +303,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + inner_iter tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") breakdown && (status = "inconsistent linear system") diff --git a/src/gmres.jl b/src/gmres.jl index 1af93328b..a183e01e4 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -15,7 +15,7 @@ export gmres, gmres! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -93,12 +93,12 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("GMRES: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "GMRES: system of size %d\n", n) # Check M = Iₙ and N = Iₙ MisI = (M === I) @@ -156,8 +156,8 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; itmax == 0 && (itmax = 2*n) inner_itmax = itmax - (verbose > 0) && @printf("%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf("%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -275,7 +275,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = resid_decrease_lim || resid_decrease_mach inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax solver.inner_iter = inner_iter - kdisplay(iter+inner_iter, verbose) && @printf("%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) # Compute vₖ₊₁ if !(solved || inner_tired || breakdown) @@ -322,7 +322,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + inner_iter tired = iter ≥ itmax end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") diff --git a/src/gpmr.jl b/src/gpmr.jl index fac2270ba..c41fdebdc 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -17,7 +17,7 @@ export gpmr, gpmr! atol::T=√eps(T), rtol::T=√eps(T), gsp::Bool=false, reorthogonalization::Bool=false, itmax::Int=0, λ::FC=one(FC), μ::FC=one(FC), verbose::Int=0, - history::Bool=false, ldiv::Bool=false, callback=solver->false) + history::Bool=false, ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -127,7 +127,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: gsp :: Bool=false, reorthogonalization :: Bool=false, itmax :: Int=0, λ :: FC=one(FC), μ :: FC=one(FC), verbose :: Int=0, history::Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) s, t = size(B) @@ -135,7 +135,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: s == n || error("Inconsistent problem size") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("GPMR: system of %d equations in %d variables\n", m+n, m+n) + (verbose > 0) && @printf(iostream, "GPMR: system of %d equations in %d variables\n", m+n, m+n) # Check C = E = Iₘ and D = F = Iₙ CisI = (C === I) @@ -230,8 +230,8 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: zt[1] = β zt[2] = γ - (verbose > 0) && @printf("%5s %7s %7s %7s\n", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "fₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7s %7s\n", iter, rNorm, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "fₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7s\n", iter, rNorm, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗") # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -417,7 +417,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: breakdown = Faux ≤ btol && Haux ≤ btol solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e\n", iter, rNorm, Haux, Faux) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, Haux, Faux) # Compute vₖ₊₁ and uₖ₊₁ if !(solved || tired || breakdown || user_requested_exit) @@ -447,7 +447,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: zt[2k+2] = τbar₂ₖ₊₂ end end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Compute zₖ = (ζ₁, ..., ζ₂ₖ) by solving Rₖzₖ = tₖ with backward substitution. for i = 2iter : -1 : 1 diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index a427cf63b..87c4b1ba0 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1913,7 +1913,7 @@ end Statistics of `solver` are displayed if `show_stats` is set to true. """ -function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} workspace = typeof(solver) name_solver = string(workspace.name.name) name_stats = string(typeof(solver.stats).name.name) diff --git a/src/lnlq.jl b/src/lnlq.jl index 208c888d5..084638804 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -29,7 +29,7 @@ export lnlq, lnlq! M=I, N=I, sqd::Bool=false, λ::T=zero(T), σ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), utolx::T=√eps(T), utoly::T=√eps(T), itmax::Int=0, transfer_to_craig::Bool=true, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -118,11 +118,11 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), utolx :: T=√eps(T), utoly :: T=√eps(T), itmax :: Int=0, transfer_to_craig :: Bool=true, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("LNLQ: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "LNLQ: system of %d equations in %d variables\n", m, n) # Check sqd and λ parameters sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") @@ -174,8 +174,8 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, bNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) # Update iteration index iter = iter + 1 @@ -452,12 +452,12 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved_lq = solved_lq || err_x ≤ utolx || err_y ≤ utoly solved_cg = transfer_to_craig && (solved_cg || err_x ≤ utolx || err_y ≤ utoly) end - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm_lq) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) # Update iteration index. iter = iter + 1 end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") if solved_cg if λ > 0 diff --git a/src/lslq.jl b/src/lslq.jl index 7ddc6f5cb..ba065f9fa 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -29,7 +29,7 @@ export lslq, lslq! window::Int=5, utol::T=√eps(T), itmax::Int=0, σ::T=zero(T), transfer_to_lsqr::Bool=false, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -160,11 +160,11 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; utol :: T=√eps(T), itmax :: Int=0, σ :: T=zero(T), transfer_to_lsqr :: Bool=false, conlim :: T=1/√eps(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("LSLQ: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "LSLQ: system of %d equations in %d variables\n", m, n) # Check sqd and λ parameters sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") @@ -276,8 +276,8 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) status = "unknown" solved = solved_mach = solved_lim = (rNorm ≤ atol) @@ -441,9 +441,9 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = solved_mach || solved_lim || zero_resid || fwd_err_lbnd || fwd_err_ubnd iter = iter + 1 - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") if transfer_to_lsqr # compute LSQR point @kaxpy!(n, ζ̄ , w̄, x) diff --git a/src/lsmr.jl b/src/lsmr.jl index bf2df3c8c..5ae86e4ee 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -34,7 +34,7 @@ export lsmr, lsmr! itmax::Int=0, conlim::T=1/√eps(T), radius::T=zero(T), verbose::Int=0, history::Bool=false, ldiv::Bool=false, - callback=solver->false) + callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -125,11 +125,11 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; atol :: T=zero(T), rtol :: T=zero(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), radius :: T=zero(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("LSMR: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "LSMR: system of %d equations in %d variables\n", m, n) # Check sqd and λ parameters sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") @@ -220,8 +220,8 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm²) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm²) # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 @@ -346,7 +346,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; t1 = test1 / (one(T) + Anorm * xNorm / β₁) rNormtol = btol + axtol * Anorm * xNorm / β₁ - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. @@ -367,7 +367,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid = zero_resid_mach | zero_resid_lim solved = solved_mach | solved_lim | solved_opt | zero_resid | fwd_err | on_boundary end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") diff --git a/src/lsqr.jl b/src/lsqr.jl index bcff1e6e7..f7a3c6a4b 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -33,7 +33,7 @@ export lsqr, lsqr! etol::T=√eps(T), window::Int=5, itmax::Int=0, conlim::T=1/√eps(T), radius::T=zero(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -120,11 +120,11 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; atol :: T=zero(T), rtol :: T=zero(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), radius :: T=zero(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("LSQR: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "LSQR: system of %d equations in %d variables\n", m, n) # Check sqd and λ parameters sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") @@ -194,8 +194,8 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond) rNorm = β₁ r1Norm = rNorm @@ -335,7 +335,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; t1 = test1 / (one(T) + Anorm * xNorm / β₁) rNormtol = btol + axtol * Anorm * xNorm / β₁ - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, α, β, rNorm, ArNorm, test1, test2, Anorm, Acond) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, α, β, rNorm, ArNorm, test1, test2, Anorm, Acond) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. @@ -356,7 +356,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid = zero_resid_mach | zero_resid_lim solved = solved_mach | solved_lim | solved_opt | zero_resid | fwd_err | on_boundary end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") diff --git a/src/minres.jl b/src/minres.jl index c63312f77..21cf6f2b6 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -30,7 +30,7 @@ export minres, minres! window::Int=5, itmax::Int=0, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, ldiv::Bool=false, - callback=solver->false) + callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -114,12 +114,12 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, λ :: T=zero(T), atol :: T=√eps(T)/100, rtol :: T=√eps(T)/100, ratol :: T=zero(T), rrtol :: T=zero(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), verbose :: Int=0, - history :: Bool=false, ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + history :: Bool=false, ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("MINRES: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "MINRES: system of size %d\n", n) # Tests M = Iₙ MisI = (M === I) @@ -201,8 +201,8 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = 2*n) - (verbose > 0) && @printf("%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) tol = atol + rtol * β₁ rNormtol = ratol + rrtol * β₁ @@ -304,7 +304,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; Acond = γmax / γmin history && push!(Aconds, Acond) - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2) if iter == 1 && β / β₁ ≤ 10 * ϵM # Aᴴb = 0 so x = 0 is a minimum least-squares solution @@ -337,7 +337,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; ill_cond = ill_cond_mach | ill_cond_lim solved = solved_mach | solved_lim | zero_resid | fwd_err | resid_decrease end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index ba27efb44..2a5f4a9f0 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -21,7 +21,7 @@ export minres_qlp, minres_qlp! M=I, atol::T=√eps(T), rtol::T=√eps(T), ctol::T=√eps(T), λ::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,12 +95,12 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F M=I, atol :: T=√eps(T), rtol :: T=√eps(T), ctol :: T=√eps(T), λ ::T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("MINRES-QLP: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "MINRES-QLP: system of size %d\n", n) # Tests M = Iₙ MisI = (M === I) @@ -159,8 +159,8 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F ε = atol + rtol * rNorm κ = zero(T) - (verbose > 0) && @printf("%5s %7s %7s %7s %7s %8s %7s %8s %7s\n", "k", "‖rₖ‖", "‖Arₖ₋₁‖", "βₖ₊₁", "Rₖ.ₖ", "Lₖ.ₖ", "‖A‖", "κ(A)", "backward") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7s %7.1e %7s %8s %7.1e %7.1e %8s\n", iter, rNorm, "✗ ✗ ✗ ✗", βₖ, "✗ ✗ ✗ ✗", " ✗ ✗ ✗ ✗", ANorm, Acond, " ✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %7s %8s %7s\n", "k", "‖rₖ‖", "‖Arₖ₋₁‖", "βₖ₊₁", "Rₖ.ₖ", "Lₖ.ₖ", "‖A‖", "κ(A)", "backward") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7.1e %7s %8s %7.1e %7.1e %8s\n", iter, rNorm, "✗ ✗ ✗ ✗", βₖ, "✗ ✗ ✗ ✗", " ✗ ✗ ✗ ✗", ANorm, Acond, " ✗ ✗ ✗ ✗") # Set up workspace. M⁻¹vₖ₋₁ .= zero(FC) @@ -417,9 +417,9 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F μbarₖ₋₁ = μbarₖ ζbarₖ = ζbarₖ₊₁ βₖ = βₖ₊₁ - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e %7.1e %8.1e %7.1e %7.1e %8.1e\n", iter, rNorm, ArNorm, βₖ₊₁, λₖ, μbarₖ, ANorm, Acond, backward) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %7.1e %7.1e %8.1e\n", iter, rNorm, ArNorm, βₖ₊₁, λₖ, μbarₖ, ANorm, Acond, backward) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Finalize the update of x if iter ≥ 2 diff --git a/src/qmr.jl b/src/qmr.jl index a8db7e978..80b51889e 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -24,7 +24,7 @@ export qmr, qmr! (x, stats) = qmr(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, - history::Bool=false, callback=solver->false) + history::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,12 +95,12 @@ end function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("QMR: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "QMR: system of size %d\n", n) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -141,8 +141,8 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) # Initialize the Lanczos biorthogonalization process. cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ @@ -316,9 +316,9 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax breakdown = !solved && (pᴴq == 0) - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") diff --git a/src/symmlq.jl b/src/symmlq.jl index 9d28dc07b..581b6eba4 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -18,7 +18,7 @@ export symmlq, symmlq! λest::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), etol::T=√eps(T), itmax::Int=0, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,12 +95,12 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; λest :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf("SYMMLQ: system of size %d\n", n) + (verbose > 0) && @printf(iostream, "SYMMLQ: system of size %d\n", n) # Tests M = Iₙ MisI = (M === I) @@ -225,8 +225,8 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = 0 itmax == 0 && (itmax = 2 * n) - (verbose > 0) && @printf("%5s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, β, cold, sold, ANorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, β, cold, sold, ANorm, Acond) tol = atol + rtol * β₁ status = "unknown" @@ -357,7 +357,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ANorm = sqrt(ANorm²) test1 = rNorm / (ANorm * xNorm) - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, β, c, s, ANorm, Acond, test1) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, β, c, s, ANorm, Acond, test1) # Reset variables ϵold = ϵ @@ -384,7 +384,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ill_cond = ill_cond_mach || ill_cond_lim solved = solved_mach || zero_resid || zero_resid_mach || zero_resid_lim || fwd_err || resid_decrease_mach end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Compute CG point # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * w̅ₖ diff --git a/src/tricg.jl b/src/tricg.jl index 1860d8492..92449c563 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -17,7 +17,7 @@ export tricg, tricg! spd::Bool=false, snd::Bool=false, flip::Bool=false, τ::T=one(T), ν::T=-one(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -117,12 +117,12 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: spd :: Bool=false, snd :: Bool=false, flip :: Bool=false, τ :: T=one(T), ν :: T=-one(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("TriCG: system of %d equations in %d variables\n", m+n, m+n) + (verbose > 0) && @printf(iostream, "TriCG: system of %d equations in %d variables\n", m+n, m+n) # Check flip, spd and snd parameters spd && flip && error("The matrix cannot be SPD and SQD") @@ -222,8 +222,8 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: history && push!(rNorms, rNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) # Set up workspace. d₂ₖ₋₃ = d₂ₖ₋₂ = zero(T) @@ -403,9 +403,9 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") breakdown && (status = "inconsistent linear system") diff --git a/src/trilqr.jl b/src/trilqr.jl index 81e3f3bf3..51541bc00 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -16,7 +16,7 @@ export trilqr, trilqr! (x, y, stats) = trilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), transfer_to_usymcg::Bool=true, itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false) + callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -94,13 +94,13 @@ end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_usymcg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("TRILQR: primal system of %d equations in %d variables\n", m, n) - (verbose > 0) && @printf("TRILQR: dual system of %d equations in %d variables\n", n, m) + (verbose > 0) && @printf(iostream, "TRILQR: primal system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "TRILQR: dual system of %d equations in %d variables\n", n, m) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -142,8 +142,8 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : εL = atol + rtol * bNorm εQ = atol + rtol * cNorm ξ = zero(T) - (verbose > 0) && @printf("%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e\n", iter, bNorm, cNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) # Set up workspace. βₖ = @knrm2(m, r₀) # β₁ = ‖r₀‖ = ‖v₁‖ @@ -389,11 +389,11 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : user_requested_exit = callback(solver) :: Bool tired = iter ≥ itmax - kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf("%5d %7s %7.1e\n", iter, "", sNorm) - kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf("%5d %7.1e %7s\n", iter, rNorm_lq, "") - kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf("%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) + kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) + kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") + kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Compute USYMCG point # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ diff --git a/src/trimr.jl b/src/trimr.jl index 52bc1b8e9..991d8f48c 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -17,7 +17,7 @@ export trimr, trimr! spd::Bool=false, snd::Bool=false, flip::Bool=false, sp::Bool=false, τ::T=one(T), ν::T=-one(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false) + ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -117,12 +117,12 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: spd :: Bool=false, snd :: Bool=false, flip :: Bool=false, sp :: Bool=false, τ :: T=one(T), ν :: T=-one(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("TriMR: system of %d equations in %d variables\n", m+n, m+n) + (verbose > 0) && @printf(iostream, "TriMR: system of %d equations in %d variables\n", m+n, m+n) # Check flip, sp, spd and snd parameters spd && flip && error("The matrix cannot be symmetric positive definite and symmetric quasi-definite !") @@ -231,8 +231,8 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: history && push!(rNorms, rNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf("%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) # Set up workspace. old_c₁ₖ = old_c₂ₖ = old_c₃ₖ = old_c₄ₖ = zero(T) @@ -505,9 +505,9 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") breakdown && (status = "inconsistent linear system") diff --git a/src/usymlq.jl b/src/usymlq.jl index adb4f52e2..ea96e8b2a 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -23,7 +23,7 @@ export usymlq, usymlq! (x, stats) = usymlq(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), transfer_to_usymcg::Bool=true, itmax::Int=0, - verbose::Int=0, history::Bool=false, callback=solver->false) + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -103,12 +103,12 @@ end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_usymcg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("USYMLQ: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "USYMLQ: system of %d equations in %d variables\n", m, n) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -148,8 +148,8 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : itmax == 0 && (itmax = m+n) ε = atol + rtol * bNorm - (verbose > 0) && @printf("%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, bNorm) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ @@ -307,9 +307,9 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : solved_lq = rNorm_lq ≤ ε solved_cg = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e\n", iter, rNorm_lq) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") # Compute USYMCG point # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ diff --git a/src/usymqr.jl b/src/usymqr.jl index 5d9caa525..2c8a02a32 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -22,7 +22,7 @@ export usymqr, usymqr! """ (x, stats) = usymqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, callback=solver->false) + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=stdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -100,12 +100,12 @@ end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf("USYMQR: system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "USYMQR: system of %d equations in %d variables\n", m, n) # Check type consistency eltype(A) == FC || error("eltype(A) ≠ $FC") @@ -146,8 +146,8 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : ε = atol + rtol * rNorm κ = zero(T) - (verbose > 0) && @printf("%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") - kdisplay(iter, verbose) && @printf("%5d %7.1e %7s\n", iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm, "✗ ✗ ✗ ✗") βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ @@ -304,9 +304,9 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : solved = rNorm ≤ ε inconsistent = !solved && AᴴrNorm ≤ κ tired = iter ≥ itmax - kdisplay(iter, verbose) && @printf("%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) end - (verbose > 0) && @printf("\n") + (verbose > 0) && @printf(iostream, "\n") tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") From 3dcf774830b2ed1cd5f7c1889f2bbc2ba27ca2f3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 8 Nov 2022 19:03:36 -0500 Subject: [PATCH 096/182] Hack with kstdout --- src/bicgstab.jl | 2 +- src/bilq.jl | 2 +- src/bilqr.jl | 2 +- src/cg.jl | 2 +- src/cg_lanczos.jl | 2 +- src/cg_lanczos_shift.jl | 2 +- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 2 +- src/cr.jl | 2 +- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 2 +- src/krylov_solvers.jl | 2 +- src/krylov_utils.jl | 3 +++ src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 2 +- src/symmlq.jl | 2 +- src/tricg.jl | 2 +- src/trilqr.jl | 2 +- src/trimr.jl | 2 +- src/usymlq.jl | 2 +- src/usymqr.jl | 2 +- 35 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 6ea05a5c6..e4d77d51a 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -102,7 +102,7 @@ end function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/bilq.jl b/src/bilq.jl index e90c1933a..2f06e500d 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -89,7 +89,7 @@ end function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_bicg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/bilqr.jl b/src/bilqr.jl index df2b7b2d9..fa2dbad69 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -95,7 +95,7 @@ end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_bicg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("Systems must be square") diff --git a/src/cg.jl b/src/cg.jl index 370692453..2a55783c0 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -95,7 +95,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, radius :: T=zero(T), linesearch :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 44e53e162..8d48c520a 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -89,7 +89,7 @@ end function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index fa45f77e2..bf34a415c 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -75,7 +75,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cgls.jl b/src/cgls.jl index 5bcd0083c..260e44261 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -91,7 +91,7 @@ function cgls! end function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), radius :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/cgne.jl b/src/cgne.jl index c07d2dbb6..ec42f3954 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -100,7 +100,7 @@ function cgne! end function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/cgs.jl b/src/cgs.jl index 1e0b7185a..721dc9e5e 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -98,7 +98,7 @@ end function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cr.jl b/src/cr.jl index 09f7dc3e2..b2a0ad83a 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -95,7 +95,7 @@ end function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, atol :: T=√eps(T), rtol :: T=√eps(T), γ :: T=√eps(T), itmax :: Int=0, radius :: T=zero(T), verbose :: Int=0, linesearch :: Bool=false, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") diff --git a/src/craig.jl b/src/craig.jl index 56245ac6b..3bad05dd5 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -126,7 +126,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), btol :: T=√eps(T), rtol :: T=√eps(T), conlim :: T=1/√eps(T), itmax :: Int=0, verbose :: Int=0, transfer_to_lsqr :: Bool=false, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/craigmr.jl b/src/craigmr.jl index eb6d5f958..aafe85e18 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -121,7 +121,7 @@ function craigmr! end function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/crls.jl b/src/crls.jl index 244160d2c..f73ccfa37 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -82,7 +82,7 @@ function crls! end function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), radius :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/crmr.jl b/src/crmr.jl index e5709c243..c6db26c36 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -98,7 +98,7 @@ function crmr! end function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/diom.jl b/src/diom.jl index 7afcb8c7d..a4e1d72af 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -96,7 +96,7 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/dqgmres.jl b/src/dqgmres.jl index faf601b83..d3c21254d 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -96,7 +96,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/fgmres.jl b/src/fgmres.jl index d17686fa7..0b49b1f75 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -99,7 +99,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/fom.jl b/src/fom.jl index ba1985d97..da1ba6697 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -93,7 +93,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/gmres.jl b/src/gmres.jl index a183e01e4..ccbcf6b1b 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -93,7 +93,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), reorthogonalization :: Bool=false, itmax :: Int=0, restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/gpmr.jl b/src/gpmr.jl index c41fdebdc..d5ba1eca9 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -127,7 +127,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: gsp :: Bool=false, reorthogonalization :: Bool=false, itmax :: Int=0, λ :: FC=one(FC), μ :: FC=one(FC), verbose :: Int=0, history::Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) s, t = size(B) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 87c4b1ba0..0484cf073 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1913,7 +1913,7 @@ end Statistics of `solver` are displayed if `show_stats` is set to true. """ -function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} workspace = typeof(solver) name_solver = string(workspace.name.name) name_stats = string(typeof(solver.stats).name.name) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 0c0b5bf71..71e3876fc 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -1,3 +1,6 @@ +"Default I/O stream for all Krylov methods." +const kstdout = stdout + """ FloatOrComplex{T} Union type of `T` and `Complex{T}` where T is an `AbstractFloat`. diff --git a/src/lnlq.jl b/src/lnlq.jl index 084638804..26ad45366 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -118,7 +118,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), utolx :: T=√eps(T), utoly :: T=√eps(T), itmax :: Int=0, transfer_to_craig :: Bool=true, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lslq.jl b/src/lslq.jl index ba065f9fa..28ea25223 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -160,7 +160,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; utol :: T=√eps(T), itmax :: Int=0, σ :: T=zero(T), transfer_to_lsqr :: Bool=false, conlim :: T=1/√eps(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lsmr.jl b/src/lsmr.jl index 5ae86e4ee..ff4634cbf 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -125,7 +125,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; atol :: T=zero(T), rtol :: T=zero(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), radius :: T=zero(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lsqr.jl b/src/lsqr.jl index f7a3c6a4b..be8551dd1 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -120,7 +120,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; atol :: T=zero(T), rtol :: T=zero(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), radius :: T=zero(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/minres.jl b/src/minres.jl index 21cf6f2b6..996677b56 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -114,7 +114,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, λ :: T=zero(T), atol :: T=√eps(T)/100, rtol :: T=√eps(T)/100, ratol :: T=zero(T), rrtol :: T=zero(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), verbose :: Int=0, - history :: Bool=false, ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + history :: Bool=false, ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 2a5f4a9f0..caf3ee335 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -95,7 +95,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F M=I, atol :: T=√eps(T), rtol :: T=√eps(T), ctol :: T=√eps(T), λ ::T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/qmr.jl b/src/qmr.jl index 80b51889e..22a929c1a 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -95,7 +95,7 @@ end function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/symmlq.jl b/src/symmlq.jl index 581b6eba4..4c9d8647d 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -95,7 +95,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; λest :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/tricg.jl b/src/tricg.jl index 92449c563..27885ed59 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -117,7 +117,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: spd :: Bool=false, snd :: Bool=false, flip :: Bool=false, τ :: T=one(T), ν :: T=-one(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/trilqr.jl b/src/trilqr.jl index 51541bc00..019493ad6 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -94,7 +94,7 @@ end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_usymcg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/trimr.jl b/src/trimr.jl index 991d8f48c..f1fcc26af 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -117,7 +117,7 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: spd :: Bool=false, snd :: Bool=false, flip :: Bool=false, sp :: Bool=false, τ :: T=one(T), ν :: T=-one(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/usymlq.jl b/src/usymlq.jl index ea96e8b2a..5c990d4f2 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -103,7 +103,7 @@ end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_usymcg :: Bool=true, itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/usymqr.jl b/src/usymqr.jl index 2c8a02a32..5fc3a758b 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -100,7 +100,7 @@ end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=stdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") From 76e1390b449548672cb5f92b4eeb2955120a7b47 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 9 Nov 2022 17:13:48 -0500 Subject: [PATCH 097/182] Use kstdout in all docstrings --- docs/src/reference.md | 1 + src/bicgstab.jl | 2 +- src/bilq.jl | 2 +- src/bilqr.jl | 2 +- src/cg.jl | 2 +- src/cg_lanczos.jl | 2 +- src/cg_lanczos_shift.jl | 6 ++++-- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 12 +++++++----- src/cr.jl | 2 +- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 7 ++++--- src/krylov_utils.jl | 2 ++ src/lnlq.jl | 14 ++++++++------ src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 2 +- src/symmlq.jl | 2 +- src/tricg.jl | 2 +- src/trilqr.jl | 2 +- src/trimr.jl | 2 +- src/usymlq.jl | 8 +++++--- src/usymqr.jl | 7 ++++--- 35 files changed, 62 insertions(+), 49 deletions(-) diff --git a/docs/src/reference.md b/docs/src/reference.md index 0896e1639..f73e10043 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -6,6 +6,7 @@ ``` ```@docs +Krylov.kstdout Krylov.FloatOrComplex Krylov.niterations Krylov.Aprod diff --git a/src/bicgstab.jl b/src/bicgstab.jl index e4d77d51a..695f2ab82 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -20,7 +20,7 @@ export bicgstab, bicgstab! c::AbstractVector{FC}=b, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/bilq.jl b/src/bilq.jl index 2f06e500d..1940551e4 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -17,7 +17,7 @@ export bilq, bilq! c::AbstractVector{FC}=b, atol::T=√eps(T), rtol::T=√eps(T), transfer_to_bicg::Bool=true, itmax::Int=0, verbose::Int=0, - history::Bool=false, callback=solver->false, iostream::IO=stdout) + history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/bilqr.jl b/src/bilqr.jl index fa2dbad69..4c1f915da 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -16,7 +16,7 @@ export bilqr, bilqr! (x, y, stats) = bilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), transfer_to_bicg::Bool=true, itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false, iostream::IO=stdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/cg.jl b/src/cg.jl index 2a55783c0..bd077d4aa 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -21,7 +21,7 @@ export cg, cg! M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, radius::T=zero(T), linesearch::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 8d48c520a..754a097a1 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -17,7 +17,7 @@ export cg_lanczos, cg_lanczos! (x, stats) = cg_lanczos(A, b::AbstractVector{FC}; M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, check_curvature::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index bf34a415c..3354b3c2c 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -19,7 +19,8 @@ export cg_lanczos_shift, cg_lanczos_shift! M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, check_curvature::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, + iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -75,7 +76,8 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + ldiv :: Bool=false, callback = solver -> false, + iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cgls.jl b/src/cgls.jl index 260e44261..030211b56 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -33,7 +33,7 @@ export cgls, cgls! (x, stats) = cgls(A, b::AbstractVector{FC}; M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), radius::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/cgne.jl b/src/cgne.jl index ec42f3954..d16ee22f8 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -33,7 +33,7 @@ export cgne, cgne! (x, stats) = cgne(A, b::AbstractVector{FC}; N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/cgs.jl b/src/cgs.jl index 721dc9e5e..d9e9a7374 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -14,7 +14,8 @@ export cgs, cgs! (x, stats) = cgs(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, - history::Bool=false, ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + history::Bool=false, ldiv::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -95,10 +96,11 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: Abs return solver end -function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; + c :: AbstractVector{FC}=b, M=I, N=I, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, + history :: Bool=false, ldiv :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cr.jl b/src/cr.jl index b2a0ad83a..5ca20991f 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -19,7 +19,7 @@ export cr, cr! M=I, atol::T=√eps(T), rtol::T=√eps(T), γ::T=√eps(T), itmax::Int=0, radius::T=zero(T), verbose::Int=0, linesearch::Bool=false, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/craig.jl b/src/craig.jl index 3bad05dd5..f71d2722a 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -38,7 +38,7 @@ export craig, craig! M=I, N=I, sqd::Bool=false, λ::T=zero(T), atol::T=√eps(T), btol::T=√eps(T), rtol::T=√eps(T), conlim::T=1/√eps(T), itmax::Int=0, verbose::Int=0, transfer_to_lsqr::Bool=false, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/craigmr.jl b/src/craigmr.jl index aafe85e18..081fd5f61 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -31,7 +31,7 @@ export craigmr, craigmr! (x, y, stats) = craigmr(A, b::AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/crls.jl b/src/crls.jl index f73ccfa37..8b71cee37 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -25,7 +25,7 @@ export crls, crls! (x, stats) = crls(A, b::AbstractVector{FC}; M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), radius::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/crmr.jl b/src/crmr.jl index c6db26c36..91e216fbb 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -31,7 +31,7 @@ export crmr, crmr! (x, stats) = crmr(A, b::AbstractVector{FC}; N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/diom.jl b/src/diom.jl index a4e1d72af..4114d2f4b 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -15,7 +15,7 @@ export diom, diom! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/dqgmres.jl b/src/dqgmres.jl index d3c21254d..7987efcf5 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -15,7 +15,7 @@ export dqgmres, dqgmres! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/fgmres.jl b/src/fgmres.jl index 0b49b1f75..04d46d353 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -15,7 +15,7 @@ export fgmres, fgmres! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/fom.jl b/src/fom.jl index da1ba6697..02d8b2422 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -15,7 +15,7 @@ export fom, fom! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/gmres.jl b/src/gmres.jl index ccbcf6b1b..1824c7cc2 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -15,7 +15,7 @@ export gmres, gmres! memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), reorthogonalization::Bool=false, itmax::Int=0, restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/gpmr.jl b/src/gpmr.jl index d5ba1eca9..76b46d42e 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -17,7 +17,8 @@ export gpmr, gpmr! atol::T=√eps(T), rtol::T=√eps(T), gsp::Bool=false, reorthogonalization::Bool=false, itmax::Int=0, λ::FC=one(FC), μ::FC=one(FC), verbose::Int=0, - history::Bool=false, ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + history::Bool=false, ldiv::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -126,8 +127,8 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: C=I, D=I, E=I, F=I, atol :: T=√eps(T), rtol :: T=√eps(T), gsp :: Bool=false, reorthogonalization :: Bool=false, itmax :: Int=0, λ :: FC=one(FC), μ :: FC=one(FC), - verbose :: Int=0, history::Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + verbose :: Int=0, history::Bool=false, ldiv :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) s, t = size(B) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 71e3876fc..b702ac3ae 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -1,3 +1,5 @@ +export kstdout + "Default I/O stream for all Krylov methods." const kstdout = stdout diff --git a/src/lnlq.jl b/src/lnlq.jl index 26ad45366..66feb7de2 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -27,9 +27,10 @@ export lnlq, lnlq! """ (x, y, stats) = lnlq(A, b::AbstractVector{FC}; M=I, N=I, sqd::Bool=false, λ::T=zero(T), σ::T=zero(T), - atol::T=√eps(T), rtol::T=√eps(T), utolx::T=√eps(T), utoly::T=√eps(T), itmax::Int=0, - transfer_to_craig::Bool=true, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + atol::T=√eps(T), rtol::T=√eps(T), utolx::T=√eps(T), + utoly::T=√eps(T), itmax::Int=0, transfer_to_craig::Bool=true, + verbose::Int=0, history::Bool=false, ldiv::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -116,9 +117,10 @@ function lnlq! end function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), - atol :: T=√eps(T), rtol :: T=√eps(T), utolx :: T=√eps(T), utoly :: T=√eps(T), itmax :: Int=0, - transfer_to_craig :: Bool=true, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + atol :: T=√eps(T), rtol :: T=√eps(T), utolx :: T=√eps(T), + utoly :: T=√eps(T), itmax :: Int=0, transfer_to_craig :: Bool=true, + verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lslq.jl b/src/lslq.jl index 28ea25223..677db64f1 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -29,7 +29,7 @@ export lslq, lslq! window::Int=5, utol::T=√eps(T), itmax::Int=0, σ::T=zero(T), transfer_to_lsqr::Bool=false, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/lsmr.jl b/src/lsmr.jl index ff4634cbf..e864bb6b9 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -34,7 +34,7 @@ export lsmr, lsmr! itmax::Int=0, conlim::T=1/√eps(T), radius::T=zero(T), verbose::Int=0, history::Bool=false, ldiv::Bool=false, - callback=solver->false, iostream::IO=stdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/lsqr.jl b/src/lsqr.jl index be8551dd1..eca1b2447 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -33,7 +33,7 @@ export lsqr, lsqr! etol::T=√eps(T), window::Int=5, itmax::Int=0, conlim::T=1/√eps(T), radius::T=zero(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/minres.jl b/src/minres.jl index 996677b56..f5f35c7db 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -30,7 +30,7 @@ export minres, minres! window::Int=5, itmax::Int=0, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, ldiv::Bool=false, - callback=solver->false, iostream::IO=stdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index caf3ee335..48111ee26 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -21,7 +21,7 @@ export minres_qlp, minres_qlp! M=I, atol::T=√eps(T), rtol::T=√eps(T), ctol::T=√eps(T), λ::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/qmr.jl b/src/qmr.jl index 22a929c1a..f6f346a8e 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -24,7 +24,7 @@ export qmr, qmr! (x, stats) = qmr(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, - history::Bool=false, callback=solver->false, iostream::IO=stdout) + history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/symmlq.jl b/src/symmlq.jl index 4c9d8647d..acd96e154 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -18,7 +18,7 @@ export symmlq, symmlq! λest::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), etol::T=√eps(T), itmax::Int=0, conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/tricg.jl b/src/tricg.jl index 27885ed59..3af12358f 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -17,7 +17,7 @@ export tricg, tricg! spd::Bool=false, snd::Bool=false, flip::Bool=false, τ::T=one(T), ν::T=-one(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/trilqr.jl b/src/trilqr.jl index 019493ad6..d46e45b33 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -16,7 +16,7 @@ export trilqr, trilqr! (x, y, stats) = trilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), transfer_to_usymcg::Bool=true, itmax::Int=0, verbose::Int=0, history::Bool=false, - callback=solver->false, iostream::IO=stdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/trimr.jl b/src/trimr.jl index f1fcc26af..7a416cefb 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -17,7 +17,7 @@ export trimr, trimr! spd::Bool=false, snd::Bool=false, flip::Bool=false, sp::Bool=false, τ::T=one(T), ν::T=-one(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=stdout) + ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. diff --git a/src/usymlq.jl b/src/usymlq.jl index 5c990d4f2..6f2752718 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -23,7 +23,8 @@ export usymlq, usymlq! (x, stats) = usymlq(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), transfer_to_usymcg::Bool=true, itmax::Int=0, - verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=stdout) + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -101,8 +102,9 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_usymcg :: Bool=true, - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + atol :: T=√eps(T), rtol :: T=√eps(T), + transfer_to_usymcg :: Bool=true, itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/usymqr.jl b/src/usymqr.jl index 5fc3a758b..a932f9b0d 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -22,7 +22,8 @@ export usymqr, usymqr! """ (x, stats) = usymqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=stdout) + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -98,8 +99,8 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) From e232f9744e439c88516788b7d10c8860c7a0b06b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 9 Nov 2022 18:19:13 -0500 Subject: [PATCH 098/182] Use Core.stdout --- src/krylov_utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index b702ac3ae..199dd544e 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -1,7 +1,7 @@ export kstdout "Default I/O stream for all Krylov methods." -const kstdout = stdout +const kstdout = Core.stdout """ FloatOrComplex{T} From c98beb42e2340a6bdac5fc04a4371ddcdc0f43e5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 11 Nov 2022 14:20:03 -0500 Subject: [PATCH 099/182] Fix verbose mode in CG-LANCZOS-SHIFT --- src/cg_lanczos_shift.jl | 10 +++------- src/krylov_solvers.jl | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 3354b3c2c..1e2ff6f21 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -158,12 +158,8 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr itmax == 0 && (itmax = 2 * n) # Build format strings for printing. - if kdisplay(iter, verbose) - fmt = "%5d" * repeat(" %8.1e", nshifts) * "\n" - # precompile printf for our particular format - local_printf(data...) = Core.eval(Main, :(@printf($fmt, $(data)...))) - local_printf(iter, rNorms...) - end + (verbose > 0) && (fmt = Printf.Format("%5d" * repeat(" %8.1e", nshifts) * "\n")) + kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) solved = sum(not_cv) == 0 tired = iter ≥ itmax @@ -226,7 +222,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr not_cv[i] = check_curvature ? !(converged[i] || indefinite[i]) : !converged[i] end iter = iter + 1 - kdisplay(iter, verbose) && local_printf(iter, rNorms...) + kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) user_requested_exit = callback(solver) :: Bool solved = sum(not_cv) == 0 diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 0484cf073..a427cf63b 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1913,7 +1913,7 @@ end Statistics of `solver` are displayed if `show_stats` is set to true. """ -function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} workspace = typeof(solver) name_solver = string(workspace.name.name) name_stats = string(typeof(solver.stats).name.name) From 66a3a188ab998f6503b71bc91a96c73bf8f29df9 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 18:35:54 -0500 Subject: [PATCH 100/182] [documentation] Remove the subsection restarted methods --- docs/make.jl | 2 +- docs/src/{warm_start.md => warm-start.md} | 42 ++++++++++++----------- 2 files changed, 23 insertions(+), 21 deletions(-) rename docs/src/{warm_start.md => warm-start.md} (61%) diff --git a/docs/make.jl b/docs/make.jl index 210052165..da7312965 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -25,7 +25,7 @@ makedocs( "Preconditioners" => "preconditioners.md", "Storage requirements" => "storage.md", "GPU support" => "gpu.md", - "Warm start" => "warm_start.md", + "Warm start" => "warm-start.md", "Factorization-free operators" => "factorization-free.md", "Callbacks" => "callbacks.md", "Performance tips" => "tips.md", diff --git a/docs/src/warm_start.md b/docs/src/warm-start.md similarity index 61% rename from docs/src/warm_start.md rename to docs/src/warm-start.md index e1d680efd..d926db183 100644 --- a/docs/src/warm_start.md +++ b/docs/src/warm-start.md @@ -1,9 +1,10 @@ -## Warm Start +# [Warm-start](@id warm-start) -Most Krylov methods in this module accept a starting point as argument. The starting point is used as initial approximation to a solution. +Most Krylov methods in this module accept a starting point as argument. +The starting point is used as initial approximation to a solution. ```julia -solver = CgSolver(n, n, S) +solver = CgSolver(A, b) cg!(solver, A, b, itmax=100) if !issolved(solver) cg!(solver, A, b, solver.x, itmax=100) # cg! uses the approximate solution `solver.x` as starting point @@ -28,7 +29,7 @@ If a Krylov method doesn't have the option to warm start, it can still be done e We provide an example with `cg_lanczos!`. ```julia -solver = CgLanczosSolver(n, n, S) +solver = CgLanczosSolver(A, b) cg_lanczos!(solver, A, b) x₀ = solver.x # Ax₀ ≈ b r = b - A * x₀ # r = b - Ax₀ @@ -54,20 +55,21 @@ c₀ = c - Aᴴx₀ - Fy x = x₀ + Δx y = y₀ + Δy ``` - -## Restarted methods - -The storage requierements of Krylov methods based on the Arnoldi process, such as FOM and GMRES, increase as the iteration progresses. -For very large problems, the storage costs become prohibitive after only few iterations and restarted variants FOM(k) and GMRES(k) are prefered. -In this section, we show how to use warm starts to implement GMRES(k) and FOM(k). - -```julia -k = 50 -solver = GmresSolver(A, b, k) # FomSolver(A, b, k) -solver.x .= 0 # solver.x .= x₀ -nrestart = 0 -while !issolved(solver) || nrestart ≤ 10 - solve!(solver, A, b, solver.x, itmax=k) - nrestart += 1 -end +```@meta +# ## Restarted methods +# +# The storage requierements of Krylov methods based on the Arnoldi process, such as FOM and GMRES, increase as the iteration progresses. +# For very large problems, the storage costs become prohibitive after only few iterations and restarted variants FOM(k) and GMRES(k) are prefered. +# In this section, we show how to use warm starts to implement GMRES(k) and FOM(k). +# +# ```julia +# k = 50 +# solver = GmresSolver(A, b, k) # FomSolver(A, b, k) +# solver.x .= 0 # solver.x .= x₀ +# nrestart = 0 +# while !issolved(solver) || nrestart ≤ 10 +# solve!(solver, A, b, solver.x, itmax=k) +# nrestart += 1 +# end +# ``` ``` From 9d4f7b6271bfe8686c4a9873b4f55b167f4ea604 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Mon, 14 Nov 2022 20:05:14 -0500 Subject: [PATCH 101/182] Update docs/make.jl --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index da7312965..441ddb3ee 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -25,7 +25,7 @@ makedocs( "Preconditioners" => "preconditioners.md", "Storage requirements" => "storage.md", "GPU support" => "gpu.md", - "Warm start" => "warm-start.md", + "Warm-start" => "warm-start.md", "Factorization-free operators" => "factorization-free.md", "Callbacks" => "callbacks.md", "Performance tips" => "tips.md", From bce609273d76604cd879662fb1ed68e37b369180 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 20:55:58 -0500 Subject: [PATCH 102/182] [documentation] Add notes about relations between processes --- docs/src/processes.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/src/processes.md b/docs/src/processes.md index 12dc7b309..5d0b1529d 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -137,6 +137,8 @@ The function [`arnoldi`](@ref arnoldi) returns $V_{k+1}$ and $H_{k+1,k}$. Related methods: [`DIOM`](@ref diom), [`FOM`](@ref fom), [`DQGMRES`](@ref dqgmres), [`GMRES`](@ref gmres) and [`FGMRES`](@ref fgmres). +!!! note + The Arnoldi process coincides with the Hermitian Lanczos process when $A$ is Hermitian. ```@docs arnoldi @@ -184,6 +186,9 @@ The function [`golub_kahan`](@ref golub_kahan) returns $V_{k+1}$, $U_{k+1}$ and Related methods: [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig), [`CRAIGMR`](@ref craigmr), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). +!!! note + The Golub-Kahan process coincides with the Hermitian Lanczos process applied on the normal equations $A^HA = A^Hb$ and $AA^H = b$. + ```@docs golub_kahan ``` @@ -230,6 +235,9 @@ Related methods: [`USYMLQ`](@ref usymlq), [`USYMQR`](@ref usymqr), [`TriLQR`](@r saunders_simon_yip ``` +!!! note + The Saunders-Simon-Yip is equivalent to the block-Lanczos process applied on $\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}$ with the initial matrix $\begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}$. + ## Montoison-Orban ![montoison_orban](./graphics/montoison_orban.png) @@ -278,6 +286,10 @@ The function [`montoison_orban`](@ref montoison_orban) returns $V_{k+1}$, $H_{k+ Related methods: [`GPMR`](@ref gpmr). +!!! note + The Montoison-Orban is equivalent to the block-Arnoldi process applied on $\begin{bmatrix} 0 & A \\ B & 0 \end{bmatrix}$ with the initial matrix $\begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}$. + It also coincides with the Saunders-Simon-Yip process when $B = A^H$. + ```@docs montoison_orban ``` From f15aedce80ebf64b08bf37a983109414bf9480cb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 22:04:49 -0500 Subject: [PATCH 103/182] Add a table to summarize the most relevant processes for each linear problem --- docs/src/processes.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/src/processes.md b/docs/src/processes.md index 5d0b1529d..cc75c65b1 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -1,6 +1,45 @@ +```@raw html + +``` + # [Krylov processes](@id krylov-processes) Krylov processes are the foundation of Krylov methods, they generate bases of Krylov subspaces. +The following table summarizes the most relevant processes for each linear problem. + +| Linear problems | Processes | +|:--------------------------------------------------------------:|:---------------------------------:| +| Hermitian linear systems | Hermitian Lanczos | +| Square Non-Hermitian linear systems | Non-Hermitian Lanczos -- Arnoldi | +| Least-squares problems | Golub-Kahan -- Saunders-Simon-Yip | +| Least-norm problems | Golub-Kahan -- Saunders-Simon-Yip | +| Saddle-point and Hermitian quasi-definite systems | Golub-Kahan -- Saunders-Simon-Yip | +| Generalized saddle-point and non-Hermitian partitioned systems | Montoison-Orban | ### Notation From 45d762cc020351ccdfc69a2063f0c1ccc2f50c2e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 19 Nov 2022 10:22:22 -0500 Subject: [PATCH 104/182] Add a second reference for the Golub-Kahan process --- src/krylov_processes.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index 858822b02..2be66b1c5 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -224,9 +224,10 @@ end * `U`: a dense m × (k+1) matrix; * `L`: a sparse (k+1) × (k+1) lower bidiagonal matrix. -#### Reference +#### References * G. H. Golub and W. Kahan, [*Calculating the Singular Values and Pseudo-Inverse of a Matrix*](https://doi.org/10.1137/0702016), SIAM Journal on Numerical Analysis, 2(2), pp. 225--224, 1965. +* C. C. Paige, [*Bidiagonalization of Matrices and Solution of Linear Equations*](https://doi.org/10.1137/0711019), SIAM Journal on Numerical Analysis, 11(1), pp. 197--209, 1974. """ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex m, n = size(A) From 211f38a328a977503e9c344f100008aeba7602bc Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 19 Nov 2022 17:10:01 -0500 Subject: [PATCH 105/182] Add another note for the Golub-Kahan process --- docs/src/processes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/processes.md b/docs/src/processes.md index cc75c65b1..bfb459f8d 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -30,6 +30,7 @@ html.theme--documenter-dark .content table th:last-child { # [Krylov processes](@id krylov-processes) Krylov processes are the foundation of Krylov methods, they generate bases of Krylov subspaces. +Depending on the Krylov subspaces generated, Krylov processes are more or less specialized for a subset of linear problems. The following table summarizes the most relevant processes for each linear problem. | Linear problems | Processes | @@ -44,9 +45,7 @@ The following table summarizes the most relevant processes for each linear probl ### Notation For a matrix $A$, $A^H$ denotes the conjugate transpose of $A$. - It coincides with $A^T$, the transpose of $A$, for real matrices. - Define $V_k := \begin{bmatrix} v_1 & \ldots & v_k \end{bmatrix} \enspace$ and $\enspace U_k := \begin{bmatrix} u_1 & \ldots & u_k \end{bmatrix}$. For a matrix $C \in \mathbb{C}^{n \times n}$ and a vector $t \in \mathbb{C}^{n}$, the $k$-th Krylov subspace generated by $C$ and $t$ is @@ -227,6 +226,7 @@ Related methods: [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig), [`CRAIGMR`](@ref cr !!! note The Golub-Kahan process coincides with the Hermitian Lanczos process applied on the normal equations $A^HA = A^Hb$ and $AA^H = b$. + It is also equivalent to the Hermitian Lanczos process applied on $\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}$ with the initial vector $\begin{bmatrix} b \\ 0 \end{bmatrix}$. ```@docs golub_kahan From 2ff14890c4b5a5a3b0acb5fadc5962a9d1ded41f Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Sun, 20 Nov 2022 13:09:58 -0500 Subject: [PATCH 106/182] Apply suggestions from code review Co-authored-by: Dominique --- docs/src/processes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/processes.md b/docs/src/processes.md index bfb459f8d..e9d4066d2 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -225,8 +225,8 @@ The function [`golub_kahan`](@ref golub_kahan) returns $V_{k+1}$, $U_{k+1}$ and Related methods: [`LNLQ`](@ref lnlq), [`CRAIG`](@ref craig), [`CRAIGMR`](@ref craigmr), [`LSLQ`](@ref lslq), [`LSQR`](@ref lsqr) and [`LSMR`](@ref lsmr). !!! note - The Golub-Kahan process coincides with the Hermitian Lanczos process applied on the normal equations $A^HA = A^Hb$ and $AA^H = b$. - It is also equivalent to the Hermitian Lanczos process applied on $\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}$ with the initial vector $\begin{bmatrix} b \\ 0 \end{bmatrix}$. + The Golub-Kahan process coincides with the Hermitian Lanczos process applied to the normal equations $A^HA x = A^Hb$ and $AA^H x = b$. + It is also related to the Hermitian Lanczos process applied to $\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}$ with initial vector $\begin{bmatrix} b \\ 0 \end{bmatrix}$. ```@docs golub_kahan @@ -275,7 +275,7 @@ saunders_simon_yip ``` !!! note - The Saunders-Simon-Yip is equivalent to the block-Lanczos process applied on $\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}$ with the initial matrix $\begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}$. + The Saunders-Simon-Yip is equivalent to the block-Lanczos process applied to $\begin{bmatrix} 0 & A \\ A^H & 0 \end{bmatrix}$ with initial matrix $\begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}$. ## Montoison-Orban @@ -326,7 +326,7 @@ The function [`montoison_orban`](@ref montoison_orban) returns $V_{k+1}$, $H_{k+ Related methods: [`GPMR`](@ref gpmr). !!! note - The Montoison-Orban is equivalent to the block-Arnoldi process applied on $\begin{bmatrix} 0 & A \\ B & 0 \end{bmatrix}$ with the initial matrix $\begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}$. + The Montoison-Orban is equivalent to the block-Arnoldi process applied to $\begin{bmatrix} 0 & A \\ B & 0 \end{bmatrix}$ with initial matrix $\begin{bmatrix} b & 0 \\ 0 & c \end{bmatrix}$. It also coincides with the Saunders-Simon-Yip process when $B = A^H$. ```@docs From 18691a895462b0eb46dbd22438e9a87f4d5a3bfb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 13 Nov 2022 13:35:56 -0500 Subject: [PATCH 107/182] Add the list of keyword arguments for all Krylov methods --- src/bicgstab.jl | 29 ++++++++++++++---- src/bilq.jl | 27 ++++++++++++----- src/bilqr.jl | 21 ++++++++++--- src/cg.jl | 29 +++++++++++++----- src/cg_lanczos.jl | 30 ++++++++++++++----- src/cg_lanczos_shift.jl | 30 +++++++++++++------ src/cgls.jl | 29 +++++++++++++----- src/cgne.jl | 30 ++++++++++++++----- src/cgs.jl | 28 ++++++++++++++---- src/cr.jl | 32 +++++++++++++++----- src/craig.jl | 41 ++++++++++++++++++++------ src/craigmr.jl | 32 +++++++++++++++----- src/crls.jl | 29 +++++++++++++----- src/crmr.jl | 30 ++++++++++++++----- src/diom.jl | 31 +++++++++++++++----- src/dqgmres.jl | 31 +++++++++++++++----- src/fgmres.jl | 34 ++++++++++++++++----- src/fom.jl | 34 ++++++++++++++++----- src/gmres.jl | 34 ++++++++++++++++----- src/gpmr.jl | 39 ++++++++++++++++++++----- src/lnlq.jl | 41 +++++++++++++++++++++----- src/lslq.jl | 65 +++++++++++++++++++++++------------------ src/lsmr.jl | 46 +++++++++++++++++++++-------- src/lsqr.jl | 47 +++++++++++++++++++++-------- src/minres.jl | 40 ++++++++++++++++++------- src/minres_qlp.jl | 28 ++++++++++++++---- src/qmr.jl | 11 +++++++ src/symmlq.jl | 39 +++++++++++++++++++------ src/tricg.jl | 38 +++++++++++++++++++----- src/trilqr.jl | 21 ++++++++++--- src/trimr.jl | 39 ++++++++++++++++++++----- src/usymlq.jl | 19 +++++++++--- src/usymqr.jl | 10 +++++++ 33 files changed, 811 insertions(+), 253 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 695f2ab82..269317102 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -18,9 +18,10 @@ export bicgstab, bicgstab! """ (x, stats) = bicgstab(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, M=I, N=I, - atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + ldiv::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -59,6 +60,20 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `c`: +* `M`: +* `N`: +* `ldiv`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -99,10 +114,12 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, return solver end -function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; + c :: AbstractVector{FC}=b, M=I, N=I, + ldiv :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/bilq.jl b/src/bilq.jl index 1940551e4..d7110408c 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -14,10 +14,10 @@ export bilq, bilq! """ (x, stats) = bilq(A, b::AbstractVector{FC}; - c::AbstractVector{FC}=b, atol::T=√eps(T), - rtol::T=√eps(T), transfer_to_bicg::Bool=true, - itmax::Int=0, verbose::Int=0, - history::Bool=false, callback=solver->false, iostream::IO=kstdout) + c::AbstractVector{FC}=b, transfer_to_bicg::Bool=true, + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -46,6 +46,18 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `c`: +* `transfer_to_bicg`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -86,9 +98,10 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: A return solver end -function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, - atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_bicg :: Bool=true, - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, +function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; + c :: AbstractVector{FC}=b, transfer_to_bicg :: Bool=true, + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/bilqr.jl b/src/bilqr.jl index 4c1f915da..07641ff90 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -14,8 +14,9 @@ export bilqr, bilqr! """ (x, y, stats) = bilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - atol::T=√eps(T), rtol::T=√eps(T), transfer_to_bicg::Bool=true, - itmax::Int=0, verbose::Int=0, history::Bool=false, + transfer_to_bicg::Bool=true, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -51,6 +52,17 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x; * `y0`: a vector of length n that represents an initial guess of the solution y. +#### Keyword arguments + +* `transfer_to_bicg`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -93,8 +105,9 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_bicg :: Bool=true, - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + transfer_to_bicg :: Bool=true, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/cg.jl b/src/cg.jl index bd077d4aa..4042488a2 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -15,13 +15,13 @@ export cg, cg! - """ (x, stats) = cg(A, b::AbstractVector{FC}; - M=I, atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, radius::T=zero(T), linesearch::Bool=false, + M=I, ldiv::Bool=false, radius::T=zero(T), + linesearch::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -52,6 +52,20 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `radius`: +* `linesearch`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -92,10 +106,11 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: Abstr end function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, radius :: T=zero(T), linesearch :: Bool=false, + M=I, ldiv :: Bool=false, radius :: T=zero(T), + linesearch :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 754a097a1..f09a4ca29 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -12,12 +12,13 @@ export cg_lanczos, cg_lanczos! - """ (x, stats) = cg_lanczos(A, b::AbstractVector{FC}; - M=I, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - check_curvature::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, ldiv::Bool=false, + check_curvature::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -46,6 +47,19 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `check_curvature`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -87,9 +101,11 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F end function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - check_curvature :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, ldiv :: Bool=false, + check_curvature :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 1e2ff6f21..a647b9114 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -13,14 +13,13 @@ export cg_lanczos_shift, cg_lanczos_shift! - """ (x, stats) = cg_lanczos_shift(A, b::AbstractVector{FC}, shifts::AbstractVector{T}; - M=I, atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, check_curvature::Bool=false, + M=I, ldiv::Bool=false, + check_curvature::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, - iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -44,6 +43,19 @@ and `false` otherwise. * `b`: a vector of length n; * `shifts`: a vector of length p. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `check_curvature`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a vector of p dense vectors, each one of length n; @@ -73,11 +85,11 @@ See [`CgLanczosShiftSolver`](@ref) for more details about the `solver`. function cg_lanczos_shift! end function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; - M=I, atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, check_curvature :: Bool=false, + M=I, ldiv :: Bool=false, + check_curvature :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, - iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cgls.jl b/src/cgls.jl index 030211b56..ebf54ace1 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -28,12 +28,12 @@ export cgls, cgls! - """ (x, stats) = cgls(A, b::AbstractVector{FC}; - M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - radius::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, ldiv::Bool=false, radius::T=zero(T), + λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), + itmax::Int=0, verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -61,6 +61,20 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `radius`: +* `λ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -89,9 +103,10 @@ See [`CglsSolver`](@ref) for more details about the `solver`. function cgls! end function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - radius :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, ldiv :: Bool=false, radius :: T=zero(T), + λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), + itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/cgne.jl b/src/cgne.jl index d16ee22f8..2750d2a5f 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -28,12 +28,13 @@ export cgne, cgne! - """ (x, stats) = cgne(A, b::AbstractVector{FC}; - N=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + N=I, ldiv::Bool=false, + λ::T=zero(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -70,6 +71,19 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `N`: +* `ldiv`: +* `λ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -98,9 +112,11 @@ See [`CgneSolver`](@ref) for more details about the `solver`. function cgne! end function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; - N=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + N=I, ldiv :: Bool=false, + λ :: T=zero(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/cgs.jl b/src/cgs.jl index d9e9a7374..2f166c4ce 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -12,9 +12,10 @@ export cgs, cgs! """ (x, stats) = cgs(A, b::AbstractVector{FC}; - c::AbstractVector{FC}=b, M=I, N=I, atol::T=√eps(T), - rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, - history::Bool=false, ldiv::Bool=false, + c::AbstractVector{FC}=b, M=I, N=I, + ldiv::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -57,6 +58,20 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `c`: +* `M`: +* `N`: +* `ldiv`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -97,9 +112,10 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: Abs end function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; - c :: AbstractVector{FC}=b, M=I, N=I, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, - history :: Bool=false, ldiv :: Bool=false, + c :: AbstractVector{FC}=b, M=I, N=I, + ldiv :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/cr.jl b/src/cr.jl index 5ca20991f..415c10a3a 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -16,10 +16,11 @@ export cr, cr! """ (x, stats) = cr(A, b::AbstractVector{FC}; - M=I, atol::T=√eps(T), rtol::T=√eps(T), γ::T=√eps(T), - itmax::Int=0, radius::T=zero(T), verbose::Int=0, - linesearch::Bool=false, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, ldiv::Bool=false, radius::T=zero(T), + linesearch::Bool=false, γ::T=√eps(T), + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -51,6 +52,21 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `radius`: +* `linesearch`: +* `γ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -93,9 +109,11 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: Abstr end function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, atol :: T=√eps(T), rtol :: T=√eps(T), γ :: T=√eps(T), itmax :: Int=0, - radius :: T=zero(T), verbose :: Int=0, linesearch :: Bool=false, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, ldiv :: Bool=false, radius :: T=zero(T), + linesearch :: Bool=false, γ :: T=√eps(T), + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") diff --git a/src/craig.jl b/src/craig.jl index f71d2722a..443c26570 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -32,13 +32,15 @@ export craig, craig! - """ (x, y, stats) = craig(A, b::AbstractVector{FC}; - M=I, N=I, sqd::Bool=false, λ::T=zero(T), atol::T=√eps(T), - btol::T=√eps(T), rtol::T=√eps(T), conlim::T=1/√eps(T), itmax::Int=0, - verbose::Int=0, transfer_to_lsqr::Bool=false, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, N=I, ldiv::Bool=false, + transfer_to_lsqr::Bool=false, sqd::Bool=false, + λ::T=zero(T), btol::T=√eps(T), + conlim::T=1/√eps(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -94,6 +96,24 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `transfer_to_lsqr`: +* `sqd`: +* `λ`: +* `btol`: +* `conlim`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -123,10 +143,13 @@ See [`CraigSolver`](@ref) for more details about the `solver`. function craig! end function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), - btol :: T=√eps(T), rtol :: T=√eps(T), conlim :: T=1/√eps(T), itmax :: Int=0, - verbose :: Int=0, transfer_to_lsqr :: Bool=false, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, N=I, ldiv :: Bool=false, + transfer_to_lsqr :: Bool=false, sqd :: Bool=false, + λ :: T=zero(T), btol :: T=√eps(T), + conlim :: T=1/√eps(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/craigmr.jl b/src/craigmr.jl index 081fd5f61..66ec8a360 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -26,12 +26,13 @@ export craigmr, craigmr! - """ (x, y, stats) = craigmr(A, b::AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), - rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, N=I, ldiv::Bool=false, + sqd::Bool=false, λ::T=zero(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -90,6 +91,21 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `sqd`: +* `λ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -119,9 +135,11 @@ See [`CraigmrSolver`](@ref) for more details about the `solver`. function craigmr! end function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, N=I, ldiv :: Bool=false, + sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/crls.jl b/src/crls.jl index 8b71cee37..2ce904edf 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -20,12 +20,12 @@ export crls, crls! - """ (x, stats) = crls(A, b::AbstractVector{FC}; - M=I, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - radius::T=zero(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, ldiv::Bool=false, radius::T=zero(T), + λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), + itmax::Int=0, verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -53,6 +53,20 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `radius`: +* `λ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -80,9 +94,10 @@ See [`CrlsSolver`](@ref) for more details about the `solver`. function crls! end function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - radius :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, ldiv :: Bool=false, radius :: T=zero(T), + λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), + itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/crmr.jl b/src/crmr.jl index 91e216fbb..e9dd9a675 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -26,12 +26,13 @@ export crmr, crmr! - """ (x, stats) = crmr(A, b::AbstractVector{FC}; - N=I, λ::T=zero(T), atol::T=√eps(T), - rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + N=I, ldiv::Bool=false, + λ::T=zero(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -68,6 +69,19 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `N`: +* `ldiv`: +* `λ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -96,9 +110,11 @@ See [`CrmrSolver`](@ref) for more details about the `solver`. function crmr! end function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - N=I, λ :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + N=I, ldiv :: Bool=false, + λ :: T=zero(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/diom.jl b/src/diom.jl index 4114d2f4b..f745e17f1 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -12,10 +12,11 @@ export diom, diom! """ (x, stats) = diom(A, b::AbstractVector{FC}; - memory::Int=20, M=I, N=I, atol::T=√eps(T), - rtol::T=√eps(T), reorthogonalization::Bool=false, - itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + memory::Int=20, M=I, N=I, ldiv::Bool=false, + reorthogonalization::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -50,6 +51,21 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `memory`: +* `M`: +* `N`: +* `ldiv`: +* `reorthogonalization`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -93,10 +109,11 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: A end function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - reorthogonalization :: Bool=false, itmax :: Int=0, + M=I, N=I, ldiv :: Bool=false, + reorthogonalization :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 7987efcf5..f2efbf391 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -12,10 +12,11 @@ export dqgmres, dqgmres! """ (x, stats) = dqgmres(A, b::AbstractVector{FC}; - memory::Int=20, M=I, N=I, atol::T=√eps(T), - rtol::T=√eps(T), reorthogonalization::Bool=false, - itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + memory::Int=20, M=I, N=I, ldiv::Bool=false, + reorthogonalization::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -50,6 +51,21 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `memory`: +* `M`: +* `N`: +* `reorthogonalization`: +* `ldiv`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -93,10 +109,11 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x end function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - reorthogonalization :: Bool=false, itmax :: Int=0, + M=I, N=I, ldiv :: Bool=false, + reorthogonalization :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/fgmres.jl b/src/fgmres.jl index 04d46d353..b00c24d04 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -12,10 +12,11 @@ export fgmres, fgmres! """ (x, stats) = fgmres(A, b::AbstractVector{FC}; - memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - reorthogonalization::Bool=false, itmax::Int=0, - restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + memory::Int=20, M=I, N=I, ldiv::Bool=false, + restart::Bool=false, reorthogonalization::Bool=false, + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -53,6 +54,22 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `memory`: +* `M`: +* `N`: +* `ldiv`: +* `restart`: +* `reorthogonalization`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -96,10 +113,11 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 end function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - reorthogonalization :: Bool=false, itmax :: Int=0, - restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, N=I, ldiv :: Bool=false, + restart :: Bool=false, reorthogonalization :: Bool=false, + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/fom.jl b/src/fom.jl index 02d8b2422..177c02d3d 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -12,10 +12,11 @@ export fom, fom! """ (x, stats) = fom(A, b::AbstractVector{FC}; - memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - reorthogonalization::Bool=false, itmax::Int=0, - restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + memory::Int=20, M=I, N=I, ldiv::Bool=false, + restart::Bool=false, reorthogonalization::Bool=false, + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -47,6 +48,22 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `memory`: +* `M`: +* `N`: +* `ldiv`: +* `restart`: +* `reorthogonalization`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -90,10 +107,11 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: Abs end function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - reorthogonalization :: Bool=false, itmax :: Int=0, - restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, N=I, ldiv :: Bool=false, + restart :: Bool=false, reorthogonalization :: Bool=false, + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/gmres.jl b/src/gmres.jl index 1824c7cc2..8d66f23cd 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -12,10 +12,11 @@ export gmres, gmres! """ (x, stats) = gmres(A, b::AbstractVector{FC}; - memory::Int=20, M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - reorthogonalization::Bool=false, itmax::Int=0, - restart::Bool=false, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + memory::Int=20, M=I, N=I, ldiv::Bool=false, + restart::Bool=false, reorthogonalization::Bool=false, + atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -47,6 +48,22 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `memory`: +* `M`: +* `N`: +* `ldiv`: +* `restart`: +* `reorthogonalization`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -90,10 +107,11 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: end function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - reorthogonalization :: Bool=false, itmax :: Int=0, - restart :: Bool=false, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, N=I, ldiv :: Bool=false, + restart :: Bool=false, reorthogonalization :: Bool=false, + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/gpmr.jl b/src/gpmr.jl index 76b46d42e..52108c82a 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -14,10 +14,11 @@ export gpmr, gpmr! """ (x, y, stats) = gpmr(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}; memory::Int=20, C=I, D=I, E=I, F=I, - atol::T=√eps(T), rtol::T=√eps(T), gsp::Bool=false, - reorthogonalization::Bool=false, itmax::Int=0, - λ::FC=one(FC), μ::FC=one(FC), verbose::Int=0, - history::Bool=false, ldiv::Bool=false, + ldiv::Bool=false, gsp::Bool=false, + λ::FC=one(FC), μ::FC=one(FC), + reorthogonalization::Bool=false, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -79,6 +80,26 @@ and `false` otherwise. * `x0`: a vector of length m that represents an initial guess of the solution x; * `y0`: a vector of length n that represents an initial guess of the solution y. +#### Keyword arguments + +* `memory`: +* `C`: +* `D`: +* `E`: +* `F`: +* `ldiv`: +* `gsp`: +* `λ`: +* `μ`: +* `reorthogonalization`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length m; @@ -124,10 +145,12 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: end function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - C=I, D=I, E=I, F=I, atol :: T=√eps(T), rtol :: T=√eps(T), - gsp :: Bool=false, reorthogonalization :: Bool=false, - itmax :: Int=0, λ :: FC=one(FC), μ :: FC=one(FC), - verbose :: Int=0, history::Bool=false, ldiv :: Bool=false, + C=I, D=I, E=I, F=I, + ldiv :: Bool=false, gsp :: Bool=false, + λ :: FC=one(FC), μ :: FC=one(FC), + reorthogonalization :: Bool=false, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history::Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/lnlq.jl b/src/lnlq.jl index 66feb7de2..e231cc6ee 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -26,10 +26,13 @@ export lnlq, lnlq! """ (x, y, stats) = lnlq(A, b::AbstractVector{FC}; - M=I, N=I, sqd::Bool=false, λ::T=zero(T), σ::T=zero(T), - atol::T=√eps(T), rtol::T=√eps(T), utolx::T=√eps(T), - utoly::T=√eps(T), itmax::Int=0, transfer_to_craig::Bool=true, - verbose::Int=0, history::Bool=false, ldiv::Bool=false, + M=I, N=I, ldiv::Bool=false, + transfer_to_craig::Bool=true, + sqd::Bool=false, λ::T=zero(T), + σ::T=zero(T), utolx::T=√eps(T), + utoly::T=√eps(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -88,6 +91,25 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `transfer_to_craig`: +* `sqd`: +* `λ`: +* `σ`: +* `utolx`: +* `utoly`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -116,10 +138,13 @@ See [`LnlqSolver`](@ref) for more details about the `solver`. function lnlq! end function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), - atol :: T=√eps(T), rtol :: T=√eps(T), utolx :: T=√eps(T), - utoly :: T=√eps(T), itmax :: Int=0, transfer_to_craig :: Bool=true, - verbose :: Int=0, history :: Bool=false, ldiv :: Bool=false, + M=I, N=I, ldiv :: Bool=false, + transfer_to_craig :: Bool=true, + sqd :: Bool=false, λ :: T=zero(T), + σ :: T=zero(T), utolx :: T=√eps(T), + utoly :: T=√eps(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/lslq.jl b/src/lslq.jl index 677db64f1..9da54edf8 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -21,15 +21,16 @@ export lslq, lslq! - """ (x, stats) = lslq(A, b::AbstractVector{FC}; - M=I, N=I, sqd::Bool=false, λ::T=zero(T), - atol::T=√eps(T), btol::T=√eps(T), etol::T=√eps(T), - window::Int=5, utol::T=√eps(T), itmax::Int=0, - σ::T=zero(T), transfer_to_lsqr::Bool=false, - conlim::T=1/√eps(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + M=I, N=I, ldiv::Bool=false, + window::Int=5, transfer_to_lsqr::Bool=false, + sqd::Bool=false, λ::T=zero(T), σ::T=zero(T), + etol::T=√eps(T), utol::T=√eps(T), + conlim::T=1/√eps(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -88,20 +89,24 @@ In this case, `N` can still be specified and indicates the weighted norm in whic #### Keyword arguments -* `M`: a symmetric and positive definite dual preconditioner; -* `N`: a symmetric and positive definite primal preconditioner; -* `sqd` indicates that we are solving a symmetric and quasi-definite system with `λ=1`; -* `λ` is a regularization parameter (see the problem statement above); -* `σ` is an underestimate of the smallest nonzero singular value of `A`---setting `σ` too large will result in an error in the course of the iterations; -* `atol` is a stopping tolerance based on the residual; -* `btol` is a stopping tolerance used to detect zero-residual problems; -* `etol` is a stopping tolerance based on the lower bound on the error; -* `window` is the number of iterations used to accumulate a lower bound on the error; -* `utol` is a stopping tolerance based on the upper bound on the error; -* `transfer_to_lsqr` return the CG solution estimate (i.e., the LSQR point) instead of the LQ estimate; -* `itmax` is the maximum number of iterations (0 means no imposed limit); -* `conlim` is the limit on the estimated condition number of `A` beyond which the solution will be abandoned; -* `verbose` determines verbosity. +* `M`: +* `N`: +* `ldiv`: +* `window`: +* `transfer_to_lsqr`: +* `sqd`: +* `λ`: +* `σ`: +* `etol`: +* `utol`: +* `conlim`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: #### Output arguments @@ -121,7 +126,7 @@ The iterations stop as soon as one of the following conditions holds true: * ‖Aᴴr‖ / (‖A‖ ‖r‖) ≤ atol, or * 1 + ‖Aᴴr‖ / (‖A‖ ‖r‖) ≤ 1 * an approximate zero-residual solution has been found (`stats.status = "found approximate zero-residual solution"`) in the sense that either - * ‖r‖ / ‖b‖ ≤ btol + atol ‖A‖ * ‖xᴸ‖ / ‖b‖, or + * ‖r‖ / ‖b‖ ≤ rtol + atol ‖A‖ * ‖xᴸ‖ / ‖b‖, or * 1 + ‖r‖ / ‖b‖ ≤ 1 * the estimated condition number of `A` is too large in the sense that either * 1/cond(A) ≤ 1/conlim (`stats.status = "condition number exceeds tolerance"`), or @@ -155,12 +160,14 @@ See [`LslqSolver`](@ref) for more details about the `solver`. function lslq! end function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), - atol :: T=√eps(T), btol :: T=√eps(T), etol :: T=√eps(T), - utol :: T=√eps(T), itmax :: Int=0, σ :: T=zero(T), - transfer_to_lsqr :: Bool=false, conlim :: T=1/√eps(T), + M=I, N=I, ldiv :: Bool=false, + transfer_to_lsqr :: Bool=false, + sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), + etol :: T=√eps(T), utol :: T=√eps(T), + conlim :: T=1/√eps(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback=solver->false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") @@ -394,7 +401,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; test2 = ArNorm / (Anorm * rNorm) test3 = 1 / Acond t1 = test1 / (one(T) + Anorm * xlqNorm / β₁) - rtol = btol + atol * Anorm * xlqNorm / β₁ + tol = rtol + atol * Anorm * xlqNorm / β₁ # update LSLQ point for next iteration @kaxpy!(n, c * ζ, w̄, x) @@ -434,7 +441,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax ill_cond_lim = (test3 ≤ ctol) solved_lim = (test2 ≤ atol) - zero_resid_lim = (test1 ≤ rtol) + zero_resid_lim = (test1 ≤ tol) ill_cond = ill_cond_mach || ill_cond_lim zero_resid = zero_resid_mach || zero_resid_lim diff --git a/src/lsmr.jl b/src/lsmr.jl index e864bb6b9..0d3127566 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -24,16 +24,15 @@ export lsmr, lsmr! - """ (x, stats) = lsmr(A, b::AbstractVector{FC}; - M=I, N=I, sqd::Bool=false, λ::T=zero(T), + M=I, N=I, ldiv::Bool=false, + window::Int=5, sqd::Bool=false, λ::T=zero(T), + radius::T=zero(T), etol::T=√eps(T), axtol::T=√eps(T), btol::T=√eps(T), - atol::T=zero(T), rtol::T=zero(T), - etol::T=√eps(T), window::Int=5, - itmax::Int=0, conlim::T=1/√eps(T), - radius::T=zero(T), verbose::Int=0, - history::Bool=false, ldiv::Bool=false, + conlim::T=1/√eps(T), atol::T=zero(T), + rtol::T=zero(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -93,6 +92,27 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `window`: +* `sqd`: +* `λ`: +* `radius`: +* `etol`: +* `axtol`: +* `btol`: +* `conlim`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -120,12 +140,14 @@ See [`LsmrSolver`](@ref) for more details about the `solver`. function lsmr! end function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), + M=I, N=I, ldiv :: Bool=false, + sqd :: Bool=false, λ :: T=zero(T), + radius :: T=zero(T), etol :: T=√eps(T), axtol :: T=√eps(T), btol :: T=√eps(T), - atol :: T=zero(T), rtol :: T=zero(T), - etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), - radius :: T=zero(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + conlim :: T=1/√eps(T), atol :: T=zero(T), + rtol :: T=zero(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lsqr.jl b/src/lsqr.jl index eca1b2447..6ac4153d1 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -24,16 +24,16 @@ export lsqr, lsqr! - """ (x, stats) = lsqr(A, b::AbstractVector{FC}; - M=I, N=I, sqd::Bool=false, λ::T=zero(T), + M=I, N=I, ldiv::Bool=false, + window::Int=5, sqd::Bool=false, λ::T=zero(T), + radius::T=zero(T), etol::T=√eps(T), axtol::T=√eps(T), btol::T=√eps(T), - atol::T=zero(T), rtol::T=zero(T), - etol::T=√eps(T), window::Int=5, - itmax::Int=0, conlim::T=1/√eps(T), - radius::T=zero(T), verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + conlim::T=1/√eps(T), atol::T=zero(T), + rtol::T=zero(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -88,6 +88,27 @@ and `false` otherwise. * `A`: a linear operator that models a matrix of dimension m × n; * `b`: a vector of length m. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `window`: +* `sqd`: +* `λ`: +* `radius`: +* `etol`: +* `axtol`: +* `btol`: +* `conlim`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -115,12 +136,14 @@ See [`LsqrSolver`](@ref) for more details about the `solver`. function lsqr! end function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, sqd :: Bool=false, λ :: T=zero(T), + M=I, N=I, ldiv :: Bool=false, + sqd :: Bool=false, λ :: T=zero(T), + radius :: T=zero(T), etol :: T=√eps(T), axtol :: T=√eps(T), btol :: T=√eps(T), - atol :: T=zero(T), rtol :: T=zero(T), - etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), - radius :: T=zero(T), verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + conlim :: T=1/√eps(T), atol :: T=zero(T), + rtol :: T=zero(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/minres.jl b/src/minres.jl index f5f35c7db..f19f78ac1 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -21,15 +21,14 @@ export minres, minres! - """ (x, stats) = minres(A, b::AbstractVector{FC}; - M=I, λ::T=zero(T), atol::T=√eps(T)/100, - rtol::T=√eps(T)/100, ratol :: T=zero(T), + M=I, ldiv::Bool=false, window::Int=5, + λ::T=zero(T), atol::T=√eps(T)/100, + rtol::T=√eps(T)/100, ratol :: T=zero(T), rrtol :: T=zero(T), etol::T=√eps(T), - window::Int=5, itmax::Int=0, - conlim::T=1/√eps(T), verbose::Int=0, - history::Bool=false, ldiv::Bool=false, + conlim::T=1/√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -71,6 +70,24 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `window`: +* `λ`: +* `atol`: +* `rtol`: +* `ratol`: +* `rrtol`: +* `etol`: +* `conlim`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -111,10 +128,13 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 end function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, λ :: T=zero(T), atol :: T=√eps(T)/100, rtol :: T=√eps(T)/100, - ratol :: T=zero(T), rrtol :: T=zero(T), etol :: T=√eps(T), - itmax :: Int=0, conlim :: T=1/√eps(T), verbose :: Int=0, - history :: Bool=false, ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + M=I, ldiv :: Bool=false, + λ :: T=zero(T), atol :: T=√eps(T)/100, + rtol :: T=√eps(T)/100, ratol :: T=zero(T), + rrtol :: T=zero(T), etol :: T=√eps(T), + conlim :: T=1/√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 48111ee26..d482019a7 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -18,10 +18,11 @@ export minres_qlp, minres_qlp! """ (x, stats) = minres_qlp(A, b::AbstractVector{FC}; - M=I, atol::T=√eps(T), rtol::T=√eps(T), - ctol::T=√eps(T), λ::T=zero(T), itmax::Int=0, + M=I, ldiv::Bool=false, ctol::T=√eps(T), + λ::T=zero(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -50,6 +51,20 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `ctol`: +* `λ`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -92,10 +107,11 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F end function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, atol :: T=√eps(T), rtol :: T=√eps(T), - ctol :: T=√eps(T), λ ::T=zero(T), itmax :: Int=0, + M=I, ldiv :: Bool=false, ctol :: T=√eps(T), + λ ::T=zero(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/qmr.jl b/src/qmr.jl index f6f346a8e..eb6c82dbd 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -51,6 +51,17 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `c`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; diff --git a/src/symmlq.jl b/src/symmlq.jl index acd96e154..25db217e2 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -11,14 +11,15 @@ export symmlq, symmlq! - """ (x, stats) = symmlq(A, b::AbstractVector{FC}; - window::Int=0, M=I, λ::T=zero(T), transfer_to_cg::Bool=true, - λest::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - etol::T=√eps(T), itmax::Int=0, conlim::T=1/√eps(T), + M=I, ldiv::Bool=false, window::Int=0, + transfer_to_cg::Bool=true, λ::T=zero(T), + λest::T=zero(T), etol::T=√eps(T), + conlim::T=1/√eps(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -51,6 +52,24 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `M`: +* `ldiv`: +* `window`: +* `transfer_to_cg`: +* `λ`: +* `λest`: +* `etol`: +* `conlim`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -91,11 +110,13 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 end function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, λ :: T=zero(T), transfer_to_cg :: Bool=true, - λest :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - etol :: T=√eps(T), itmax :: Int=0, conlim :: T=1/√eps(T), + M=I, ldiv :: Bool=false, + transfer_to_cg :: Bool=true, λ :: T=zero(T), + λest :: T=zero(T), etol :: T=√eps(T), + conlim :: T=1/√eps(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/tricg.jl b/src/tricg.jl index 3af12358f..7cedc03ef 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -13,11 +13,13 @@ export tricg, tricg! """ (x, y, stats) = tricg(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - spd::Bool=false, snd::Bool=false, flip::Bool=false, - τ::T=one(T), ν::T=-one(T), itmax::Int=0, + M=I, N=I, ldiv::Bool=false, + spd::Bool=false, snd::Bool=false, + flip::Bool=false, τ::T=one(T), + ν::T=-one(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -71,6 +73,24 @@ and `false` otherwise. * `x0`: a vector of length m that represents an initial guess of the solution x; * `y0`: a vector of length n that represents an initial guess of the solution y. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `spd`: +* `snd`: +* `flip`: +* `τ`: +* `ν`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length m; @@ -113,11 +133,13 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: end function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - spd :: Bool=false, snd :: Bool=false, flip :: Bool=false, - τ :: T=one(T), ν :: T=-one(T), itmax :: Int=0, + M=I, N=I, ldiv :: Bool=false, + spd :: Bool=false, snd :: Bool=false, + flip :: Bool=false, τ :: T=one(T), + ν :: T=-one(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/trilqr.jl b/src/trilqr.jl index d46e45b33..2f051ae52 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -14,8 +14,9 @@ export trilqr, trilqr! """ (x, y, stats) = trilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - atol::T=√eps(T), rtol::T=√eps(T), transfer_to_usymcg::Bool=true, - itmax::Int=0, verbose::Int=0, history::Bool=false, + transfer_to_usymcg::Bool=true, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -50,6 +51,17 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x; * `y0`: a vector of length m that represents an initial guess of the solution y. +#### Keyword arguments + +* `transfer_to_usymcg`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -92,8 +104,9 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - atol :: T=√eps(T), rtol :: T=√eps(T), transfer_to_usymcg :: Bool=true, - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) diff --git a/src/trimr.jl b/src/trimr.jl index 7a416cefb..8e5ff0050 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -13,11 +13,13 @@ export trimr, trimr! """ (x, y, stats) = trimr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - M=I, N=I, atol::T=√eps(T), rtol::T=√eps(T), - spd::Bool=false, snd::Bool=false, flip::Bool=false, sp::Bool=false, - τ::T=one(T), ν::T=-one(T), itmax::Int=0, + M=I, N=I, ldiv::Bool=false, + spd::Bool=false, snd::Bool=false, + flip::Bool=false, sp::Bool=false, + τ::T=one(T), ν::T=-one(T), atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, - ldiv::Bool=false, callback=solver->false, iostream::IO=kstdout) + callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. `FC` is `T` or `Complex{T}`. @@ -71,6 +73,25 @@ and `false` otherwise. * `x0`: a vector of length m that represents an initial guess of the solution x; * `y0`: a vector of length n that represents an initial guess of the solution y. +#### Keyword arguments + +* `M`: +* `N`: +* `ldiv`: +* `spd`: +* `snd`: +* `flip`: +* `sp`: +* `τ`: +* `ν`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length m; @@ -113,11 +134,13 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: end function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - M=I, N=I, atol :: T=√eps(T), rtol :: T=√eps(T), - spd :: Bool=false, snd :: Bool=false, flip :: Bool=false, sp :: Bool=false, - τ :: T=one(T), ν :: T=-one(T), itmax :: Int=0, + M=I, N=I, ldiv :: Bool=false, + spd :: Bool=false, snd :: Bool=false, + flip :: Bool=false, sp :: Bool=false, + τ :: T=one(T), ν :: T=-one(T), atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - ldiv :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/usymlq.jl b/src/usymlq.jl index 6f2752718..b7c823eaa 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -21,8 +21,8 @@ export usymlq, usymlq! """ (x, stats) = usymlq(A, b::AbstractVector{FC}, c::AbstractVector{FC}; - atol::T=√eps(T), rtol::T=√eps(T), - transfer_to_usymcg::Bool=true, itmax::Int=0, + transfer_to_usymcg::Bool=true, atol::T=√eps(T), + rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) @@ -59,6 +59,17 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `transfer_to_usymcg`: +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; @@ -102,8 +113,8 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - atol :: T=√eps(T), rtol :: T=√eps(T), - transfer_to_usymcg :: Bool=true, itmax :: Int=0, + transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} diff --git a/src/usymqr.jl b/src/usymqr.jl index a932f9b0d..f1783cdee 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -56,6 +56,16 @@ and `false` otherwise. * `x0`: a vector of length n that represents an initial guess of the solution x. +#### Keyword arguments + +* `atol`: +* `rtol`: +* `itmax`: +* `verbose`: +* `history`: +* `callback`: +* `iostream`: + #### Output arguments * `x`: a dense vector of length n; From 1e31e3ffd533bf0cde3b7efd2aefa672233e4bef Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 13 Nov 2022 18:02:30 -0500 Subject: [PATCH 108/182] Regroup diagonal scaling factors of TriCG, TriMR and GPMR --- src/gpmr.jl | 3 +-- src/tricg.jl | 3 +-- src/trimr.jl | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/gpmr.jl b/src/gpmr.jl index 52108c82a..94db27476 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -89,8 +89,7 @@ and `false` otherwise. * `F`: * `ldiv`: * `gsp`: -* `λ`: -* `μ`: +* `λ` and `μ`: * `reorthogonalization`: * `atol`: * `rtol`: diff --git a/src/tricg.jl b/src/tricg.jl index 7cedc03ef..84a869670 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -81,8 +81,7 @@ and `false` otherwise. * `spd`: * `snd`: * `flip`: -* `τ`: -* `ν`: +* `τ` and `ν`: * `atol`: * `rtol`: * `itmax`: diff --git a/src/trimr.jl b/src/trimr.jl index 8e5ff0050..2e0429513 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -82,8 +82,7 @@ and `false` otherwise. * `snd`: * `flip`: * `sp`: -* `τ`: -* `ν`: +* `τ` and `ν`: * `atol`: * `rtol`: * `itmax`: From 37219496a01561d08ed33e6bdb70c92ff9dc2503 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 02:47:22 -0500 Subject: [PATCH 109/182] Replace ctol by Artol in MINRES-QLP --- src/minres_qlp.jl | 10 +++++----- test/test_minres_qlp.jl | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index d482019a7..c6a6173de 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -18,7 +18,7 @@ export minres_qlp, minres_qlp! """ (x, stats) = minres_qlp(A, b::AbstractVector{FC}; - M=I, ldiv::Bool=false, ctol::T=√eps(T), + M=I, ldiv::Bool=false, Artol::T=√eps(T), λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, @@ -55,7 +55,7 @@ and `false` otherwise. * `M`: * `ldiv`: -* `ctol`: +* `Artol`: * `λ`: * `atol`: * `rtol`: @@ -107,7 +107,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F end function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, ctol :: T=√eps(T), + M=I, ldiv :: Bool=false, Artol :: T=√eps(T), λ ::T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, @@ -380,7 +380,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F # Update ‖Arₖ₋₁‖ estimate # ‖ Arₖ₋₁ ‖ = |ζbarₖ| * √(|λbarₖ|² + |γbarₖ|²) ArNorm = abs(ζbarₖ) * √(abs2(λbarₖ) + abs2(cₖ₋₁ * βₖ₊₁)) - iter == 1 && (κ = atol + ctol * ArNorm) + iter == 1 && (κ = atol + Artol * ArNorm) history && push!(ArNorms, ArNorm) ANorm = sqrt(ANorm²) @@ -418,7 +418,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F zero_resid = zero_resid_mach | zero_resid_lim resid_decrease = resid_decrease_mach | resid_decrease_lim solved = resid_decrease | zero_resid - inconsistent = (ArNorm ≤ κ && abs(μbarₖ) ≤ ctol) || (breakdown && !solved) + inconsistent = (ArNorm ≤ κ && abs(μbarₖ) ≤ Artol) || (breakdown && !solved) # Update variables if iter ≥ 2 diff --git a/test/test_minres_qlp.jl b/test/test_minres_qlp.jl index 6e983e49a..0b4d2046d 100644 --- a/test/test_minres_qlp.jl +++ b/test/test_minres_qlp.jl @@ -80,7 +80,7 @@ solver = MinresQlpSolver(A, b) tol = 1.0 cb_n2 = TestCallbackN2(A, b, tol = tol) - minres_qlp!(solver, A, b, atol = 0.0, rtol = 0.0, ctol = 0.0, callback = cb_n2) + minres_qlp!(solver, A, b, atol = 0.0, rtol = 0.0, Artol = 0.0, callback = cb_n2) @test solver.stats.status == "user-requested exit" @test cb_n2(solver) From 676fbda9b1dd4c8d989627593e8b79475f2a86f7 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 02:47:55 -0500 Subject: [PATCH 110/182] window=5 by default in SYMMLQ --- src/symmlq.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/symmlq.jl b/src/symmlq.jl index 25db217e2..2b738f49c 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -13,7 +13,7 @@ export symmlq, symmlq! """ (x, stats) = symmlq(A, b::AbstractVector{FC}; - M=I, ldiv::Bool=false, window::Int=0, + M=I, ldiv::Bool=false, window::Int=5, transfer_to_cg::Bool=true, λ::T=zero(T), λest::T=zero(T), etol::T=√eps(T), conlim::T=1/√eps(T), atol::T=√eps(T), From 1dbe269ff7921ad8d6a9d6591e801f23e9d3ce8d Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 02:49:03 -0500 Subject: [PATCH 111/182] Remove duplicate tolerances in MINRES --- src/minres.jl | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/minres.jl b/src/minres.jl index f19f78ac1..cc94b4548 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -24,9 +24,8 @@ export minres, minres! """ (x, stats) = minres(A, b::AbstractVector{FC}; M=I, ldiv::Bool=false, window::Int=5, - λ::T=zero(T), atol::T=√eps(T)/100, - rtol::T=√eps(T)/100, ratol :: T=zero(T), - rrtol :: T=zero(T), etol::T=√eps(T), + λ::T=zero(T), atol::T=√eps(T), + rtol::T=√eps(T), etol::T=√eps(T), conlim::T=1/√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) @@ -78,8 +77,6 @@ and `false` otherwise. * `λ`: * `atol`: * `rtol`: -* `ratol`: -* `rrtol`: * `etol`: * `conlim`: * `itmax`: @@ -129,9 +126,8 @@ end function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, - λ :: T=zero(T), atol :: T=√eps(T)/100, - rtol :: T=√eps(T)/100, ratol :: T=zero(T), - rrtol :: T=zero(T), etol :: T=√eps(T), + λ :: T=zero(T), atol :: T=√eps(T), + rtol :: T=√eps(T), etol :: T=√eps(T), conlim :: T=1/√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} @@ -224,13 +220,12 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) - tol = atol + rtol * β₁ - rNormtol = ratol + rrtol * β₁ + ε = atol + rtol * β₁ stats.status = "unknown" solved = solved_mach = solved_lim = (rNorm ≤ rtol) tired = iter ≥ itmax ill_cond = ill_cond_mach = ill_cond_lim = false - zero_resid = zero_resid_mach = zero_resid_lim = (rNorm ≤ tol) + zero_resid = zero_resid_mach = zero_resid_lim = (rNorm ≤ ε) fwd_err = false user_requested_exit = false @@ -346,16 +341,18 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Stopping conditions based on user-provided tolerances. tired = iter ≥ itmax ill_cond_lim = (one(T) / Acond ≤ ctol) - solved_lim = (test2 ≤ tol) - zero_resid_lim = (test1 ≤ tol) - resid_decrease_lim = (rNorm ≤ rNormtol) + # We must check that these stopping conditions work with preconditioners + # before we reuse them as stopping conditions. + # solved_lim = (test2 ≤ ε) + # zero_resid_lim = (test1 ≤ ε) + resid_decrease_lim = (rNorm ≤ ε) iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) user_requested_exit = callback(solver) :: Bool - zero_resid = zero_resid_mach | zero_resid_lim - resid_decrease = resid_decrease_mach | resid_decrease_lim - ill_cond = ill_cond_mach | ill_cond_lim - solved = solved_mach | solved_lim | zero_resid | fwd_err | resid_decrease + zero_resid = zero_resid_mach || zero_resid_lim + resid_decrease = resid_decrease_mach || resid_decrease_lim + ill_cond = ill_cond_mach || ill_cond_lim + solved = solved_mach || solved_lim || zero_resid || fwd_err || resid_decrease end (verbose > 0) && @printf(iostream, "\n") From 4dfbe1552678c4134b8672d5d33b4b7ca170082b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 03:12:14 -0500 Subject: [PATCH 112/182] Add rtol in LSLQ --- src/lslq.jl | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lslq.jl b/src/lslq.jl index 9da54edf8..6e956e513 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -25,8 +25,9 @@ export lslq, lslq! (x, stats) = lslq(A, b::AbstractVector{FC}; M=I, N=I, ldiv::Bool=false, window::Int=5, transfer_to_lsqr::Bool=false, - sqd::Bool=false, λ::T=zero(T), σ::T=zero(T), - etol::T=√eps(T), utol::T=√eps(T), + sqd::Bool=false, λ::T=zero(T), + σ::T=zero(T), etol::T=√eps(T), + utol::T=√eps(T), btol::T=√eps(T), conlim::T=1/√eps(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, history::Bool=false, @@ -99,6 +100,7 @@ In this case, `N` can still be specified and indicates the weighted norm in whic * `σ`: * `etol`: * `utol`: +* `btol`: * `conlim`: * `atol`: * `rtol`: @@ -126,7 +128,7 @@ The iterations stop as soon as one of the following conditions holds true: * ‖Aᴴr‖ / (‖A‖ ‖r‖) ≤ atol, or * 1 + ‖Aᴴr‖ / (‖A‖ ‖r‖) ≤ 1 * an approximate zero-residual solution has been found (`stats.status = "found approximate zero-residual solution"`) in the sense that either - * ‖r‖ / ‖b‖ ≤ rtol + atol ‖A‖ * ‖xᴸ‖ / ‖b‖, or + * ‖r‖ / ‖b‖ ≤ btol + atol ‖A‖ * ‖xᴸ‖ / ‖b‖, or * 1 + ‖r‖ / ‖b‖ ≤ 1 * the estimated condition number of `A` is too large in the sense that either * 1/cond(A) ≤ 1/conlim (`stats.status = "condition number exceeds tolerance"`), or @@ -162,8 +164,9 @@ function lslq! end function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, transfer_to_lsqr :: Bool=false, - sqd :: Bool=false, λ :: T=zero(T), σ :: T=zero(T), - etol :: T=√eps(T), utol :: T=√eps(T), + sqd :: Bool=false, λ :: T=zero(T), + σ :: T=zero(T), etol :: T=√eps(T), + utol :: T=√eps(T), btol :: T=√eps(T), conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, @@ -287,7 +290,8 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) status = "unknown" - solved = solved_mach = solved_lim = (rNorm ≤ atol) + ε = atol + rtol * β₁ + solved = solved_mach = solved_lim = (rNorm ≤ ε) tired = iter ≥ itmax ill_cond = ill_cond_mach = ill_cond_lim = false zero_resid = zero_resid_mach = zero_resid_lim = false @@ -397,11 +401,11 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; end end - test1 = rNorm / β₁ + test1 = rNorm test2 = ArNorm / (Anorm * rNorm) test3 = 1 / Acond - t1 = test1 / (one(T) + Anorm * xlqNorm / β₁) - tol = rtol + atol * Anorm * xlqNorm / β₁ + t1 = test1 / (one(T) + Anorm * xlqNorm) + tol = btol + atol * Anorm * xlqNorm / β₁ # update LSLQ point for next iteration @kaxpy!(n, c * ζ, w̄, x) @@ -441,7 +445,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax ill_cond_lim = (test3 ≤ ctol) solved_lim = (test2 ≤ atol) - zero_resid_lim = (test1 ≤ tol) + zero_resid_lim = (test1 ≤ ε) ill_cond = ill_cond_mach || ill_cond_lim zero_resid = zero_resid_mach || zero_resid_lim From 92bc8d6b81377667ae43e8d0c989f73dbf9bcc7e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 14 Nov 2022 08:28:40 -0500 Subject: [PATCH 113/182] Add documentation for all Keyword arguments --- src/bicgstab.jl | 22 +++++++++++----------- src/bilq.jl | 18 +++++++++--------- src/bilqr.jl | 16 ++++++++-------- src/cg.jl | 22 +++++++++++----------- src/cg_lanczos.jl | 20 ++++++++++---------- src/cg_lanczos_shift.jl | 20 ++++++++++---------- src/cgls.jl | 22 +++++++++++----------- src/cgne.jl | 18 +++++++++--------- src/cgs.jl | 22 +++++++++++----------- src/cr.jl | 24 ++++++++++++------------ src/craig.jl | 30 +++++++++++++++--------------- src/craigmr.jl | 24 ++++++++++++------------ src/crls.jl | 22 +++++++++++----------- src/crmr.jl | 18 +++++++++--------- src/diom.jl | 24 ++++++++++++------------ src/dqgmres.jl | 24 ++++++++++++------------ src/fgmres.jl | 26 +++++++++++++------------- src/fom.jl | 26 +++++++++++++------------- src/gmres.jl | 26 +++++++++++++------------- src/gpmr.jl | 32 ++++++++++++++++---------------- src/lnlq.jl | 32 ++++++++++++++++---------------- src/lslq.jl | 38 +++++++++++++++++++------------------- src/lsmr.jl | 36 ++++++++++++++++++------------------ src/lsqr.jl | 36 ++++++++++++++++++------------------ src/minres.jl | 26 +++++++++++++------------- src/minres_qlp.jl | 22 +++++++++++----------- src/qmr.jl | 16 ++++++++-------- src/symmlq.jl | 30 +++++++++++++++--------------- src/tricg.jl | 28 ++++++++++++++-------------- src/trilqr.jl | 16 ++++++++-------- src/trimr.jl | 30 +++++++++++++++--------------- src/usymlq.jl | 16 ++++++++-------- src/usymqr.jl | 14 +++++++------- 33 files changed, 398 insertions(+), 398 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 269317102..ab09098bf 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -62,17 +62,17 @@ and `false` otherwise. #### Keyword arguments -* `c`: -* `M`: -* `N`: -* `ldiv`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `c`: the second initial vector of length `n` required by the Lanczos biorthogonalization process; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/bilq.jl b/src/bilq.jl index d7110408c..aa1766217 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -48,15 +48,15 @@ and `false` otherwise. #### Keyword arguments -* `c`: -* `transfer_to_bicg`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `c`: the second initial vector of length `n` required by the Lanczos biorthogonalization process; +* `transfer_to_bicg`: transfer from the BiLQ point to the BiCG point, when it exists. The transfer is based on the residual norm; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/bilqr.jl b/src/bilqr.jl index 07641ff90..0d6e2d168 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -54,14 +54,14 @@ and `false` otherwise. #### Keyword arguments -* `transfer_to_bicg`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `transfer_to_bicg`: transfer from the BiLQ point to the BiCG point, when it exists. The transfer is based on the residual norm; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cg.jl b/src/cg.jl index 4042488a2..cf155561a 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -54,17 +54,17 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `radius`: -* `linesearch`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; +* `linesearch`: if `true`, indicate that the solution is to be used in an inexact Newton method with linesearch. If negative curvature is detected at iteration k > 0, the solution of iteration k-1 is returned. If negative curvature is detected at iteration 0, the right-hand side is returned (i.e., the negative gradient); +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index f09a4ca29..eac44e11d 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -49,16 +49,16 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `check_curvature`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `check_curvature`: if `true`, check that the curvature of the quadratic along the search direction is positive, and abort if not, unless `linesearch` is also `true`; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index a647b9114..5b9833484 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -45,16 +45,16 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `check_curvature`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `check_curvature`: if `true`, check that the curvature of the quadratic along the search direction is positive, and abort if not, unless `linesearch` is also `true`; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cgls.jl b/src/cgls.jl index ebf54ace1..5fe6449cb 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -63,17 +63,17 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `radius`: -* `λ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cgne.jl b/src/cgne.jl index 2750d2a5f..e4aff6ddf 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -74,15 +74,15 @@ and `false` otherwise. #### Keyword arguments * `N`: -* `ldiv`: -* `λ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cgs.jl b/src/cgs.jl index 2f166c4ce..336f80e10 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -60,17 +60,17 @@ and `false` otherwise. #### Keyword arguments -* `c`: -* `M`: -* `N`: -* `ldiv`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `c`: the second initial vector of length `n` required by the Lanczos biorthogonalization process; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/cr.jl b/src/cr.jl index 415c10a3a..83e1bc10e 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -54,18 +54,18 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `radius`: -* `linesearch`: -* `γ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; +* `linesearch`: if `true`, indicate that the solution is to be used in an inexact Newton method with linesearch. If negative curvature is detected at iteration k > 0, the solution of iteration k-1 is returned. If negative curvature is detected at iteration 0, the right-hand side is returned (i.e., the negative gradient); +* `γ`: tolerance to determine that the curvature of the quadratic model is nonpositive; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/craig.jl b/src/craig.jl index 443c26570..72b803724 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -98,21 +98,21 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `transfer_to_lsqr`: -* `sqd`: -* `λ`: -* `btol`: -* `conlim`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the augmented system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the augmented system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `transfer_to_lsqr`: transfer from the LSLQ point to the LSQR point, when it exists. The transfer is based on the residual norm; +* `sqd`: if `true`, set `λ=1` for Hermitian quasi-definite systems; +* `λ`: regularization parameter; +* `btol`: stopping tolerance used to detect zero-residual problems; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/craigmr.jl b/src/craigmr.jl index 66ec8a360..ce5087de7 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -93,18 +93,18 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `sqd`: -* `λ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the augmented system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the augmented system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `sqd`: if `true`, set `λ=1` for Hermitian quasi-definite systems; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/crls.jl b/src/crls.jl index 2ce904edf..cc98fb1be 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -55,17 +55,17 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `radius`: -* `λ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/crmr.jl b/src/crmr.jl index e9dd9a675..bd4fd81f0 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -72,15 +72,15 @@ and `false` otherwise. #### Keyword arguments * `N`: -* `ldiv`: -* `λ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/diom.jl b/src/diom.jl index f745e17f1..9c61e513b 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -53,18 +53,18 @@ and `false` otherwise. #### Keyword arguments -* `memory`: -* `M`: -* `N`: -* `ldiv`: -* `reorthogonalization`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `memory`: the number of most recent vectors of the Krylov basis against which to orthogonalize a new vector; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `reorthogonalization`: reorthogonalize the new vectors of the Krylov basis against the `memory` most recent vectors; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/dqgmres.jl b/src/dqgmres.jl index f2efbf391..d0fe6ffaf 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -53,18 +53,18 @@ and `false` otherwise. #### Keyword arguments -* `memory`: -* `M`: -* `N`: -* `reorthogonalization`: -* `ldiv`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `memory`: the number of most recent vectors of the Krylov basis against which to orthogonalize a new vector; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `reorthogonalization`: reorthogonalize the new vectors of the Krylov basis against the `memory` most recent vectors; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/fgmres.jl b/src/fgmres.jl index b00c24d04..b25131736 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -56,19 +56,19 @@ and `false` otherwise. #### Keyword arguments -* `memory`: -* `M`: -* `N`: -* `ldiv`: -* `restart`: -* `reorthogonalization`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `memory`: if `restart = true`, the restarted version FGMRES(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. Additional storage will be allocated if the number of iterations exceeds `memory`; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `restart`: restart the method after `memory` iterations; +* `reorthogonalization`: reorthogonalize the new vectors of the Krylov basis against all previous vectors; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/fom.jl b/src/fom.jl index 177c02d3d..b6d057998 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -50,19 +50,19 @@ and `false` otherwise. #### Keyword arguments -* `memory`: -* `M`: -* `N`: -* `ldiv`: -* `restart`: -* `reorthogonalization`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `memory`: if `restart = true`, the restarted version FOM(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. Additional storage will be allocated if the number of iterations exceeds `memory`; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `restart`: restart the method after `memory` iterations; +* `reorthogonalization`: reorthogonalize the new vectors of the Krylov basis against all previous vectors; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/gmres.jl b/src/gmres.jl index 8d66f23cd..4a238f2a6 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -50,19 +50,19 @@ and `false` otherwise. #### Keyword arguments -* `memory`: -* `M`: -* `N`: -* `ldiv`: -* `restart`: -* `reorthogonalization`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `memory`: if `restart = true`, the restarted version GMRES(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. Additional storage will be allocated if the number of iterations exceeds `memory`; +* `M`: linear operator that models a nonsingular matrix of size `n` used for left preconditioning; +* `N`: linear operator that models a nonsingular matrix of size `n` used for right preconditioning; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `restart`: restart the method after `memory` iterations; +* `reorthogonalization`: reorthogonalize the new vectors of the Krylov basis against all previous vectors; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/gpmr.jl b/src/gpmr.jl index 94db27476..75cbcac26 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -82,22 +82,22 @@ and `false` otherwise. #### Keyword arguments -* `memory`: -* `C`: -* `D`: -* `E`: -* `F`: -* `ldiv`: -* `gsp`: -* `λ` and `μ`: -* `reorthogonalization`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `memory`: if `restart = true`, the restarted version GPMR(k) is used with `k = memory`. If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. Additional storage will be allocated if the number of iterations exceeds `memory`; +* `C`: linear operator that models a nonsingular matrix of size `m`, and represents the first term of the block-diagonal left preconditioner; +* `D`: linear operator that models a nonsingular matrix of size `n`, and represents the second term of the block-diagonal left preconditioner; +* `E`: linear operator that models a nonsingular matrix of size `m`, and represents the first term of the block-diagonal right preconditioner; +* `F`: linear operator that models a nonsingular matrix of size `n`, and represents the second term of the block-diagonal right preconditioner; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `gsp`: if `true`, set `λ = 1` and `μ = 0` for generalized saddle-point systems; +* `λ` and `μ`: diagonal scaling factors of the partitioned linear system; +* `reorthogonalization`: reorthogonalize the new vectors of the Krylov basis against all previous vectors; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/lnlq.jl b/src/lnlq.jl index e231cc6ee..e2d4f89bc 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -93,22 +93,22 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `transfer_to_craig`: -* `sqd`: -* `λ`: -* `σ`: -* `utolx`: -* `utoly`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the augmented system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the augmented system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `transfer_to_craig`: transfer from the LNLQ point to the CRAIG point, when it exists. The transfer is based on the residual norm; +* `sqd`: if `true`, set `λ=1` for Hermitian quasi-definite systems; +* `λ`: regularization parameter; +* `σ`: strict lower bound on the smallest positive singular value `σₘᵢₙ` such as `σ = (1-10⁻⁷)σₘᵢₙ`; +* `utolx`: tolerance on the upper bound on the distance to the solution `‖x-x*‖`; +* `utoly`: tolerance on the upper bound on the distance to the solution `‖y-y*‖`; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/lslq.jl b/src/lslq.jl index 6e956e513..a3b4307b2 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -90,25 +90,25 @@ In this case, `N` can still be specified and indicates the weighted norm in whic #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `window`: -* `transfer_to_lsqr`: -* `sqd`: -* `λ`: -* `σ`: -* `etol`: -* `utol`: -* `btol`: -* `conlim`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the augmented system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the augmented system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `window`: number of iterations used to accumulate a lower bound on the error; +* `transfer_to_lsqr`: transfer from the LSLQ point to the LSQR point, when it exists. The transfer is based on the residual norm; +* `sqd`: if `true`, set `λ=1` for Hermitian quasi-definite systems; +* `λ`: regularization parameter; +* `σ`: strict lower bound on the smallest positive singular value `σₘᵢₙ` such as `σ = (1-10⁻⁷)σₘᵢₙ`; +* `etol`: stopping tolerance based on the lower bound on the error; +* `utol`: stopping tolerance based on the upper bound on the error; +* `btol`: stopping tolerance used to detect zero-residual problems; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/lsmr.jl b/src/lsmr.jl index 0d3127566..b9efa6237 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -94,24 +94,24 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `window`: -* `sqd`: -* `λ`: -* `radius`: -* `etol`: -* `axtol`: -* `btol`: -* `conlim`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the augmented system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the augmented system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `window`: number of iterations used to accumulate a lower bound on the error; +* `sqd`: if `true`, set `λ=1` for Hermitian quasi-definite systems; +* `λ`: regularization parameter; +* `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; +* `etol`: stopping tolerance based on the lower bound on the error; +* `axtol`: tolerance on the backward error; +* `btol`: stopping tolerance used to detect zero-residual problems; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/lsqr.jl b/src/lsqr.jl index 6ac4153d1..39a044deb 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -90,24 +90,24 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `window`: -* `sqd`: -* `λ`: -* `radius`: -* `etol`: -* `axtol`: -* `btol`: -* `conlim`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the augmented system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the augmented system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `window`: number of iterations used to accumulate a lower bound on the error; +* `sqd`: if `true`, set `λ=1` for Hermitian quasi-definite systems; +* `λ`: regularization parameter; +* `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; +* `etol`: stopping tolerance based on the lower bound on the error; +* `axtol`: tolerance on the backward error; +* `btol`: stopping tolerance used to detect zero-residual problems; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/minres.jl b/src/minres.jl index cc94b4548..ba24ee184 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -71,19 +71,19 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `window`: -* `λ`: -* `atol`: -* `rtol`: -* `etol`: -* `conlim`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `window`: number of iterations used to accumulate a lower bound on the error; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `etol`: stopping tolerance based on the lower bound on the error; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index c6a6173de..09c32c5bf 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -53,17 +53,17 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `Artol`: -* `λ`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `Artol`: relative stopping tolerance based on the Aᴴ-residual norm; +* `λ`: regularization parameter; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/qmr.jl b/src/qmr.jl index eb6c82dbd..c23a829f5 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -53,14 +53,14 @@ and `false` otherwise. #### Keyword arguments -* `c`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `c`: the second initial vector of length `n` required by the Lanczos biorthogonalization process; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/symmlq.jl b/src/symmlq.jl index 2b738f49c..175082268 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -54,21 +54,21 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `ldiv`: -* `window`: -* `transfer_to_cg`: -* `λ`: -* `λest`: -* `etol`: -* `conlim`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; +* `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; +* `window`: number of iterations used to accumulate a lower bound on the error; +* `transfer_to_cg`: transfer from the SYMMLQ point to the CG point, when it exists. The transfer is based on the residual norm; +* `λ`: regularization parameter; +* `λest`: positive strict lower bound on the smallest eigenvalue `λₘᵢₙ` when solving a positive-definite system, such as `λest = (1-10⁻⁷)λₘᵢₙ`; +* `etol`: stopping tolerance based on the lower bound on the error; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/tricg.jl b/src/tricg.jl index 84a869670..61f3389c8 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -75,20 +75,20 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `spd`: -* `snd`: -* `flip`: -* `τ` and `ν`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the partitioned system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the partitioned system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `spd`: if `true`, set `τ = 1` and `ν = 1` for Hermitian and positive-definite linear system; +* `snd`: if `true`, set `τ = -1` and `ν = -1` for Hermitian and negative-definite linear systems; +* `flip`: if `true`, set `τ = -1` and `ν = 1` for another known variant of Hermitian quasi-definite systems; +* `τ` and `ν`: diagonal scaling factors of the partitioned Hermitian linear system; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/trilqr.jl b/src/trilqr.jl index 2f051ae52..4fb2d6cce 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -53,14 +53,14 @@ and `false` otherwise. #### Keyword arguments -* `transfer_to_usymcg`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `transfer_to_usymcg`: transfer from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/trimr.jl b/src/trimr.jl index 2e0429513..4be1eb444 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -75,21 +75,21 @@ and `false` otherwise. #### Keyword arguments -* `M`: -* `N`: -* `ldiv`: -* `spd`: -* `snd`: -* `flip`: -* `sp`: -* `τ` and `ν`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `M`: linear operator that models a Hermitian positive-definite matrix of size `m` used for centered preconditioning of the partitioned system; +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning of the partitioned system; +* `ldiv`: define whether the preconditioners use `ldiv!` or `mul!`; +* `spd`: if `true`, set `τ = 1` and `ν = 1` for Hermitian and positive-definite linear system; +* `snd`: if `true`, set `τ = -1` and `ν = -1` for Hermitian and negative-definite linear systems; +* `flip`: if `true`, set `τ = -1` and `ν = 1` for another known variant of Hermitian quasi-definite systems; +* `sp`: if `true`, set `τ = 1` and `ν = 0` for saddle-point systems; +* `τ` and `ν`: diagonal scaling factors of the partitioned Hermitian linear system; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/usymlq.jl b/src/usymlq.jl index b7c823eaa..db092c5a2 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -61,14 +61,14 @@ and `false` otherwise. #### Keyword arguments -* `transfer_to_usymcg`: -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `transfer_to_usymcg`: transfer from the USYMLQ point to the USYMCG point, when it exists. The transfer is based on the residual norm; +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments diff --git a/src/usymqr.jl b/src/usymqr.jl index f1783cdee..471fa5313 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -58,13 +58,13 @@ and `false` otherwise. #### Keyword arguments -* `atol`: -* `rtol`: -* `itmax`: -* `verbose`: -* `history`: -* `callback`: -* `iostream`: +* `atol`: absolute stopping tolerance based on the residual norm; +* `rtol`: relative stopping tolerance based on the residual norm; +* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; +* `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; +* `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; +* `iostream`: stream to which output is logged. #### Output arguments From 243425e98889fec6817a50616b298eac61be5051 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 24 Nov 2022 23:13:09 -0500 Subject: [PATCH 114/182] Update MINRES and MINRES-QLP --- src/minres.jl | 6 ++---- src/minres_qlp.jl | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/minres.jl b/src/minres.jl index ba24ee184..7f4dcd7b9 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -341,10 +341,8 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Stopping conditions based on user-provided tolerances. tired = iter ≥ itmax ill_cond_lim = (one(T) / Acond ≤ ctol) - # We must check that these stopping conditions work with preconditioners - # before we reuse them as stopping conditions. - # solved_lim = (test2 ≤ ε) - # zero_resid_lim = (test1 ≤ ε) + solved_lim = (test2 ≤ ε) + zero_resid_lim = MisI && (test1 ≤ ε) resid_decrease_lim = (rNorm ≤ ε) iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 09c32c5bf..5f96a9616 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -411,7 +411,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F # Stopping conditions based on user-provided tolerances. tired = iter ≥ itmax resid_decrease_lim = (rNorm ≤ ε) - zero_resid_lim = (backward ≤ ε) + zero_resid_lim = MisI && (backward ≤ ε) breakdown = βₖ₊₁ ≤ btol user_requested_exit = callback(solver) :: Bool From 566f23e5e21b949306c9b32e6d2e46a8b5f6dee1 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 25 Nov 2022 00:00:37 -0500 Subject: [PATCH 115/182] Check all docstrings --- src/bicgstab.jl | 9 --------- src/bilq.jl | 8 +------- src/bilqr.jl | 6 ------ src/cg.jl | 8 -------- src/cg_lanczos.jl | 6 ------ src/cg_lanczos_shift.jl | 6 ------ src/cgls.jl | 3 --- src/cgne.jl | 5 ----- src/cgs.jl | 5 ----- src/cr.jl | 9 --------- src/craig.jl | 3 --- src/craigmr.jl | 3 --- src/crls.jl | 3 --- src/crmr.jl | 5 ----- src/diom.jl | 9 +-------- src/dqgmres.jl | 7 ------- src/fgmres.jl | 9 --------- src/fom.jl | 10 ---------- src/gmres.jl | 10 ---------- src/gpmr.jl | 12 +----------- src/lnlq.jl | 3 --- src/lslq.jl | 19 ++++++++----------- src/lsmr.jl | 3 --- src/lsqr.jl | 3 --- src/minres.jl | 6 ------ src/minres_qlp.jl | 5 ----- src/qmr.jl | 5 +---- src/symmlq.jl | 9 +-------- src/tricg.jl | 14 ++------------ src/trilqr.jl | 6 ------ src/trimr.jl | 11 ----------- src/usymlq.jl | 6 ------ src/usymqr.jl | 3 --- 33 files changed, 15 insertions(+), 214 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index ab09098bf..c4f16595e 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -41,15 +41,6 @@ convergence than CGS. If BICGSTAB stagnates, we recommend DQGMRES and BiLQ as alternative methods for unsymmetric square systems. BICGSTAB stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + ‖b‖ * rtol`. -`atol` is an absolute tolerance and `rtol` is a relative tolerance. - -Additional details can be displayed if verbose mode is enabled (verbose > 0). -Information will be displayed every `verbose` iterations. - -This implementation allows a left preconditioner `M` and a right preconditioner `N`. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. #### Input arguments diff --git a/src/bilq.jl b/src/bilq.jl index aa1766217..12ee40652 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -29,13 +29,7 @@ BiLQ can be warm-started from an initial guess `x0` where `kwargs` are the same Solve the square linear system Ax = b of size n using BiLQ. BiLQ is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. -When `A` is symmetric and `b = c`, BiLQ is equivalent to SYMMLQ. - -An option gives the possibility of transferring to the BiCG point, -when it exists. The transfer is based on the residual norm. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. +When `A` is Hermitian and `b = c`, BiLQ is equivalent to SYMMLQ. #### Input arguments diff --git a/src/bilqr.jl b/src/bilqr.jl index 0d6e2d168..5666f0863 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -35,12 +35,6 @@ The relation `bᴴc ≠ 0` must be satisfied. BiLQ is used for solving primal system `Ax = b` of size n. QMR is used for solving dual system `Aᴴy = c` of size n. -An option gives the possibility of transferring from the BiLQ point to the -BiCG point, when it exists. The transfer is based on the residual norm. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/cg.jl b/src/cg.jl index cf155561a..ed9d88cfa 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -33,16 +33,8 @@ CG can be warm-started from an initial guess `x0` where `kwargs` are the same ke The conjugate gradient method to solve the Hermitian linear system Ax = b of size n. The method does _not_ abort if A is not definite. - -A preconditioner M may be provided in the form of a linear operator and is -assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. -If `itmax=0`, the default number of iterations is set to `2 * n`. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian positive definite matrix of dimension n; diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index eac44e11d..f648eb2a8 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -32,12 +32,6 @@ Hermitian linear system Ax = b of size n. The method does _not_ abort if A is not definite. -A preconditioner M may be provided in the form of a linear operator and is -assumed to be Hermitian and positive definite. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian matrix of dimension n; diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 5b9833484..38001d7e7 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -31,12 +31,6 @@ of shifted systems of size n. The method does _not_ abort if A + αI is not definite. -A preconditioner M may be provided in the form of a linear operator and is -assumed to be Hermitian and positive definite. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian matrix of dimension n; diff --git a/src/cgls.jl b/src/cgls.jl index 5fe6449cb..55fe6d0ec 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -53,9 +53,6 @@ CGLS produces monotonic residuals ‖r‖₂ but not optimality residuals ‖A It is formally equivalent to LSQR, though can be slightly less accurate, but simpler to implement. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/cgne.jl b/src/cgne.jl index e4aff6ddf..f85af32be 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -61,11 +61,6 @@ CGNE produces monotonic errors ‖x-x*‖₂ but not residuals ‖r‖₂. It is formally equivalent to CRAIG, though can be slightly less accurate, but simpler to implement. Only the x-part of the solution is returned. -A preconditioner N may be provided in the form of a linear operator. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/cgs.jl b/src/cgs.jl index 336f80e10..cbb3db13b 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -44,11 +44,6 @@ to become inaccurate. TFQMR and BICGSTAB were developed to remedy this difficulty.» -This implementation allows a left preconditioner M and a right preconditioner N. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/cr.jl b/src/cr.jl index 83e1bc10e..26f317385 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -32,17 +32,8 @@ CR can be warm-started from an initial guess `x0` where `kwargs` are the same ke A truncated version of Stiefel’s Conjugate Residual method to solve the Hermitian linear system Ax = b of size n or the least-squares problem min ‖b - Ax‖ if A is singular. The matrix A must be Hermitian semi-definite. - -A preconditioner M may be provided in the form of a linear operator and is assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. -In a linesearch context, 'linesearch' must be set to 'true'. - -If `itmax=0`, the default number of iterations is set to `2 * n`. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian positive definite matrix of dimension n; diff --git a/src/craig.jl b/src/craig.jl index 72b803724..76afe9d51 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -88,9 +88,6 @@ In this case, `M` can still be specified and indicates the weighted norm in whic In this implementation, both the x and y-parts of the solution are returned. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/craigmr.jl b/src/craigmr.jl index ce5087de7..3b64829d6 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -83,9 +83,6 @@ It is formally equivalent to CRMR, though can be slightly more accurate, and intricate to implement. Both the x- and y-parts of the solution are returned. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/crls.jl b/src/crls.jl index cc98fb1be..78615fad6 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -45,9 +45,6 @@ CRLS produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr It is formally equivalent to LSMR, though can be substantially less accurate, but simpler to implement. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/crmr.jl b/src/crmr.jl index bd4fd81f0..621ba5ef3 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -59,11 +59,6 @@ CRMR produces monotonic residuals ‖r‖₂. It is formally equivalent to CRAIG-MR, though can be slightly less accurate, but simpler to implement. Only the x-part of the solution is returned. -A preconditioner N may be provided. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/diom.jl b/src/diom.jl index 9c61e513b..7bf23e355 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -32,16 +32,9 @@ If CG is well defined on `Ax = b` and `memory = 2`, DIOM is theoretically equiva If `k ≤ memory` where `k` is the number of iterations, DIOM is theoretically equivalent to FOM. Otherwise, DIOM interpolates between CG and FOM and is similar to CG with partial reorthogonalization. -Partial reorthogonalization is available with the `reorthogonalization` option. - -An advantage of DIOM is that nonsymmetric or symmetric indefinite or both nonsymmetric +An advantage of DIOM is that non-Hermitian or Hermitian indefinite or both non-Hermitian and indefinite systems of linear equations can be handled by this single algorithm. -This implementation allows a left preconditioner M and a right preconditioner N. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/dqgmres.jl b/src/dqgmres.jl index d0fe6ffaf..025016304 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -35,13 +35,6 @@ If MINRES is well defined on `Ax = b` and `memory = 2`, DQGMRES is theoretically If `k ≤ memory` where `k` is the number of iterations, DQGMRES is theoretically equivalent to GMRES. Otherwise, DQGMRES interpolates between MINRES and GMRES and is similar to MINRES with partial reorthogonalization. -Partial reorthogonalization is available with the `reorthogonalization` option. - -This implementation allows a left preconditioner M and a right preconditioner N. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/fgmres.jl b/src/fgmres.jl index b25131736..fa536af23 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -36,15 +36,6 @@ a Chebyshev iteration or another Krylov subspace method is used as a preconditio Compared to GMRES, there is no additional cost incurred in the arithmetic but the memory requirement almost doubles. Thus, GMRES is recommended if the right preconditioner N is constant. -Full reorthogonalization is available with the `reorthogonalization` option. - -If `restart = true`, the restarted version FGMRES(k) is used with `k = memory`. -If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. -More storage will be allocated only if the number of iterations exceeds `memory`. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/fom.jl b/src/fom.jl index b6d057998..6aabb33f5 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -29,16 +29,6 @@ Solve the linear system Ax = b of size n using FOM. FOM algorithm is based on the Arnoldi process and a Galerkin condition. -This implementation allows a left preconditioner M and a right preconditioner N. -Full reorthogonalization is available with the `reorthogonalization` option. - -If `restart = true`, the restarted version FOM(k) is used with `k = memory`. -If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. -More storage will be allocated only if the number of iterations exceeds `memory`. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/gmres.jl b/src/gmres.jl index 4a238f2a6..d475198b5 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -29,16 +29,6 @@ Solve the linear system Ax = b of size n using GMRES. GMRES algorithm is based on the Arnoldi process and computes a sequence of approximate solutions with the minimum residual. -This implementation allows a left preconditioner M and a right preconditioner N. -Full reorthogonalization is available with the `reorthogonalization` option. - -If `restart = true`, the restarted version GMRES(k) is used with `k = memory`. -If `restart = false`, the parameter `memory` should be used as a hint of the number of iterations to limit dynamic memory allocations. -More storage will be allocated only if the number of iterations exceeds `memory`. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension n; diff --git a/src/gpmr.jl b/src/gpmr.jl index 75cbcac26..958d2977c 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -29,7 +29,7 @@ export gpmr, gpmr! GPMR can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. Given matrices `A` of dimension m × n and `B` of dimension n × m, -GPMR solves the unsymmetric partitioned linear system +GPMR solves the non-Hermitian partitioned linear system [ λIₘ A ] [ x ] = [ b ] [ B μIₙ ] [ y ] [ c ], @@ -51,8 +51,6 @@ and can solve when `CE = M⁻¹` and `DF = N⁻¹`. By default, GPMR solves unsymmetric linear systems with `λ = 1` and `μ = 1`. -If `gsp = true`, `λ = 1`, `μ = 0` and the associated generalized saddle point system is solved. -`λ` and `μ` are also keyword arguments that can be directly modified for more specific problems. GPMR is based on the orthogonal Hessenberg reduction process and its relations with the block-Arnoldi process. The residual norm ‖rₖ‖ is monotonically decreasing in GPMR. @@ -60,14 +58,6 @@ The residual norm ‖rₖ‖ is monotonically decreasing in GPMR. GPMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + ‖r₀‖ * rtol`. `atol` is an absolute tolerance and `rtol` is a relative tolerance. -Full reorthogonalization is available with the `reorthogonalization` option. - -Additional details can be displayed if verbose mode is enabled (verbose > 0). -Information will be displayed every `verbose` iterations. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/lnlq.jl b/src/lnlq.jl index e2d4f89bc..deda7336f 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -83,9 +83,6 @@ In this implementation, both the x and y-parts of the solution are returned. The bound is valid if λ>0 or σ>0 where σ should be strictly smaller than the smallest positive singular value. For instance σ:=(1-1e-7)σₘᵢₙ . -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/lslq.jl b/src/lslq.jl index a3b4307b2..420c8a1cb 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -47,14 +47,6 @@ LSLQ is formally equivalent to applying SYMMLQ to the normal equations but is more stable. -#### Main features - -* the solution estimate is updated along orthogonal directions -* the norm of the solution estimate ‖xᴸₖ‖₂ is increasing -* the error ‖eₖ‖₂ := ‖xᴸₖ - x*‖₂ is decreasing -* it is possible to transition cheaply from the LSLQ iterate to the LSQR iterate if there is an advantage (there always is in terms of error) -* if `A` is rank deficient, identify the minimum least-squares solution - If `λ > 0`, we solve the symmetric and quasi-definite system [ E A ] [ r ] [ b ] @@ -83,6 +75,14 @@ The system above represents the optimality conditions of In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. +#### Main features + +* the solution estimate is updated along orthogonal directions +* the norm of the solution estimate ‖xᴸₖ‖₂ is increasing +* the error ‖eₖ‖₂ := ‖xᴸₖ - x*‖₂ is decreasing +* it is possible to transition cheaply from the LSLQ iterate to the LSQR iterate if there is an advantage (there always is in terms of error) +* if `A` is rank deficient, identify the minimum least-squares solution + #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; @@ -136,9 +136,6 @@ The iterations stop as soon as one of the following conditions holds true: * the lower bound on the LQ forward error is less than etol * ‖xᴸ‖ * the upper bound on the CG forward error is less than utol * ‖xᶜ‖ -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### References * R. Estrin, D. Orban and M. A. Saunders, [*Euclidean-norm error bounds for SYMMLQ and CG*](https://doi.org/10.1137/16M1094816), SIAM Journal on Matrix Analysis and Applications, 40(1), pp. 235--253, 2019. diff --git a/src/lsmr.jl b/src/lsmr.jl index b9efa6237..781d9448a 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -84,9 +84,6 @@ The system above represents the optimality conditions of In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/lsqr.jl b/src/lsqr.jl index 39a044deb..36e5a8ef9 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -80,9 +80,6 @@ The system above represents the optimality conditions of In this case, `N` can still be specified and indicates the weighted norm in which `x` and `Aᴴr` should be measured. `r` can be recovered by computing `E⁻¹(b - Ax)`. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/minres.jl b/src/minres.jl index 7f4dcd7b9..718a754be 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -54,12 +54,6 @@ A is indefinite. MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr‖₂. -A preconditioner M may be provided in the form of a linear operator and is -assumed to be Hermitian and positive definite. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian matrix of dimension n; diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 5f96a9616..d4d63266f 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -35,13 +35,8 @@ MINRES-QLP is the only method based on the Lanczos process that returns the mini solution on singular inconsistent systems (A + λI)x = b of size n, where λ is a shift parameter. It is significantly more complex but can be more reliable than MINRES when A is ill-conditioned. -A preconditioner M may be provided in the form of a linear operator and is -assumed to be Hermitian and positive definite. M also indicates the weighted norm in which residuals are measured. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian matrix of dimension n; diff --git a/src/qmr.jl b/src/qmr.jl index c23a829f5..e24fba79a 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -37,10 +37,7 @@ Solve the square linear system Ax = b of size n using QMR. QMR is based on the Lanczos biorthogonalization process and requires two initial vectors `b` and `c`. The relation `bᴴc ≠ 0` must be satisfied and by default `c = b`. -When `A` is symmetric and `b = c`, QMR is equivalent to MINRES. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. +When `A` is Hermitian and `b = c`, QMR is equivalent to MINRES. #### Input arguments diff --git a/src/symmlq.jl b/src/symmlq.jl index 175082268..1da2c02f6 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -32,17 +32,10 @@ Solve the shifted linear system (A + λI) x = b -of size n using the SYMMLQ method, where λ is a shift parameter, -and A is Hermitian. +of size n using the SYMMLQ method, where λ is a shift parameter, and A is Hermitian. SYMMLQ produces monotonic errors ‖x* - x‖₂. -A preconditioner M may be provided in the form of a linear operator and is -assumed to be Hermitian and positive definite. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a Hermitian matrix of dimension n; diff --git a/src/tricg.jl b/src/tricg.jl index 61f3389c8..4096a9ffe 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -28,7 +28,7 @@ export tricg, tricg! TriCG can be warm-started from initial guesses `x0` and `y0` where `kwargs` are the same keyword arguments as above. -Given a matrix `A` of dimension m × n, TriCG solves the symmetric linear system +Given a matrix `A` of dimension m × n, TriCG solves the Hermitian linear system [ τE A ] [ x ] = [ b ] [ Aᴴ νF ] [ y ] [ c ], @@ -38,11 +38,7 @@ of size (n+m) × (n+m) where τ and ν are real numbers, E = M⁻¹ ≻ 0 and F TriCG could breakdown if `τ = 0` or `ν = 0`. It's recommended to use TriMR in these cases. -By default, TriCG solves symmetric and quasi-definite linear systems with τ = 1 and ν = -1. -If `flip = true`, TriCG solves another known variant of SQD systems where τ = -1 and ν = 1. -If `spd = true`, τ = ν = 1 and the associated symmetric and positive definite linear system is solved. -If `snd = true`, τ = ν = -1 and the associated symmetric and negative definite linear system is solved. -`τ` and `ν` are also keyword arguments that can be directly modified for more specific problems. +By default, TriCG solves Hermitian and quasi-definite linear systems with τ = 1 and ν = -1. TriCG is based on the preconditioned orthogonal tridiagonalization process and its relation with the preconditioned block-Lanczos process. @@ -56,12 +52,6 @@ It's the Euclidean norm when `M` and `N` are identity operators. TriCG stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + ‖r₀‖ * rtol`. `atol` is an absolute tolerance and `rtol` is a relative tolerance. -Additional details can be displayed if verbose mode is enabled (verbose > 0). -Information will be displayed every `verbose` iterations. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/trilqr.jl b/src/trilqr.jl index 4fb2d6cce..e11a8a6c6 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -34,12 +34,6 @@ Combine USYMLQ and USYMQR to solve adjoint systems. USYMLQ is used for solving primal system `Ax = b` of size m × n. USYMQR is used for solving dual system `Aᴴy = c` of size n × m. -An option gives the possibility of transferring from the USYMLQ point to the -USYMCG point, when it exists. The transfer is based on the residual norm. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/trimr.jl b/src/trimr.jl index 4be1eb444..9da4dfa92 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -38,11 +38,6 @@ of size (n+m) × (n+m) where τ and ν are real numbers, E = M⁻¹ ≻ 0, F = N TriMR handles saddle-point systems (`τ = 0` or `ν = 0`) and adjoint systems (`τ = 0` and `ν = 0`) without any risk of breakdown. By default, TriMR solves symmetric and quasi-definite linear systems with τ = 1 and ν = -1. -If `flip = true`, TriMR solves another known variant of SQD systems where τ = -1 and ν = 1. -If `spd = true`, τ = ν = 1 and the associated symmetric and positive definite linear system is solved. -If `snd = true`, τ = ν = -1 and the associated symmetric and negative definite linear system is solved. -If `sp = true`, τ = 1, ν = 0 and the associated saddle-point linear system is solved. -`τ` and `ν` are also keyword arguments that can be directly modified for more specific problems. TriMR is based on the preconditioned orthogonal tridiagonalization process and its relation with the preconditioned block-Lanczos process. @@ -56,12 +51,6 @@ It's the Euclidean norm when `M` and `N` are identity operators. TriMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + ‖r₀‖ * rtol`. `atol` is an absolute tolerance and `rtol` is a relative tolerance. -Additional details can be displayed if verbose mode is enabled (verbose > 0). -Information will be displayed every `verbose` iterations. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/usymlq.jl b/src/usymlq.jl index db092c5a2..53aef51a3 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -43,12 +43,6 @@ It's considered as a generalization of SYMMLQ. It can also be applied to under-determined and over-determined problems. In all cases, problems must be consistent. -An option gives the possibility of transferring to the USYMCG point, -when it exists. The transfer is based on the residual norm. - -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; diff --git a/src/usymqr.jl b/src/usymqr.jl index 471fa5313..3876499b5 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -43,9 +43,6 @@ It's considered as a generalization of MINRES. It can also be applied to under-determined and over-determined problems. USYMQR finds the minimum-norm solution if problems are inconsistent. -The callback is called as `callback(solver)` and should return `true` if the main loop should terminate, -and `false` otherwise. - #### Input arguments * `A`: a linear operator that models a matrix of dimension m × n; From 338dbc4506b1a4ce8b87d07ef19afe84486b83b8 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 25 Nov 2022 00:36:25 -0500 Subject: [PATCH 116/182] Release 0.9.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 74005745f..6249e13f4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Krylov" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.8.4" +version = "0.9.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 37581327cc9c09311f6041ce77c0f6b47dd30492 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 29 Nov 2022 02:09:04 -0500 Subject: [PATCH 117/182] Update CI for aarch64 Mac -- Apple M1 --- .github/workflows/CI_M1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI_M1.yml b/.github/workflows/CI_M1.yml index 6f9aa720b..590de6b5c 100644 --- a/.github/workflows/CI_M1.yml +++ b/.github/workflows/CI_M1.yml @@ -8,7 +8,7 @@ on: jobs: test: name: Julia ${{ matrix.version }} - macOS - ${{ matrix.arch }} - ${{ github.event_name }} - runs-on: self-hosted + runs-on: [self-hosted, macOS] strategy: fail-fast: false matrix: From e965ad599e9ce926b88e6820da1e49075cce9b73 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 30 Nov 2022 02:32:20 -0500 Subject: [PATCH 118/182] Add an example with BasicLU.jl in preconditioners.md --- docs/src/preconditioners.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/src/preconditioners.md b/docs/src/preconditioners.md index 60258868b..fd203dddb 100644 --- a/docs/src/preconditioners.md +++ b/docs/src/preconditioners.md @@ -135,6 +135,7 @@ Methods concerned: [`CGNE`](@ref cgne), [`CRMR`](@ref crmr), [`LNLQ`](@ref lnlq) - [LimitedLDLFactorizations.jl](https://github.com/JuliaSmoothOptimizers/LimitedLDLFactorizations.jl) for limited-memory LDLᵀ factorization of symmetric matrices. - [AlgebraicMultigrid.jl](https://github.com/JuliaLinearAlgebra/AlgebraicMultigrid.jl) provides two algebraic multigrid (AMG) preconditioners. - [RandomizedPreconditioners.jl](https://github.com/tjdiamandis/RandomizedPreconditioners.jl) uses randomized numerical linear algebra to construct approximate inverses of matrices. +- [BasicLU.jl](https://github.com/JuliaSmoothOptimizers/BasicLU.jl) uses a sparse LU factorization to compute a maximum volume basis that can be used as a preconditioner for least-norm and least-squares problems. ## Examples @@ -207,3 +208,30 @@ C = lu(M) # [B 0] [y] [c] x, y, stats = gpmr(A, B, b, c, C=C, gsp=true, ldiv=true) ``` + +```julia +import BasicLU +using LinearOperators, Krylov + +# Least-squares problem +m, n = size(A) +Aᴴ = sparse(A') +basis, B = BasicLU.maxvolbasis(Aᴴ) +opA = LinearOperator(A) +B⁻ᴴ = LinearOperator(Float64, n, n, false, false, (y, v) -> (y .= v ; BasicLU.solve!(B, y, 'T')), + (y, v) -> (y .= v ; BasicLU.solve!(B, y, 'N')), + (y, v) -> (y .= v ; BasicLU.solve!(B, y, 'N'))) + +d, stats = lsmr(opA * B⁻ᴴ, b) # min ‖AB⁻ᴴd - b‖₂ +x = B⁻ᴴ * d # recover the solution of min ‖Ax - b‖₂ + +# Least-norm problem +m, n = size(A) +basis, B = maxvolbasis(A) +opA = LinearOperator(A) +B⁻¹ = LinearOperator(Float64, m, m, false, false, (y, v) -> (y .= v ; BasicLU.solve!(B, y, 'N')), + (y, v) -> (y .= v ; BasicLU.solve!(B, y, 'T')), + (y, v) -> (y .= v ; BasicLU.solve!(B, y, 'T'))) + +x, y, stats = craigmr(B⁻¹ * opA, B⁻¹ * b) # min ‖x‖₂ s.t. B⁻¹Ax = B⁻¹b +``` From 12b4ab6642466fa5a613584d7c905687bb605ef5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Wed, 21 Dec 2022 21:04:45 +0100 Subject: [PATCH 119/182] Use Cirrus to test Krylov.jl with Apple M1 --- .cirrus.yml | 48 ++++++++++++++++++++++++++++--------- .github/workflows/CI_M1.yml | 31 ------------------------ 2 files changed, 37 insertions(+), 42 deletions(-) delete mode 100644 .github/workflows/CI_M1.yml diff --git a/.cirrus.yml b/.cirrus.yml index d559cf609..f51d815a3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,15 +1,41 @@ -freebsd_instance: - image: freebsd-13-0-release-amd64 task: - name: FreeBSD - env: - matrix: - - JULIA_VERSION: 1.6 - - JULIA_VERSION: 1 - - JULIA_VERSION: nightly - allow_failures: $JULIA_VERSION == 'nightly' - install_script: - - sh -c "$(fetch https://raw.githubusercontent.com/ararslan/CirrusCI.jl/master/bin/install.sh -o -)" + matrix: + - name: FreeBSD + freebsd_instance: + image_family: freebsd-13-1 + env: + matrix: + - JULIA_VERSION: 1.6 + - JULIA_VERSION: 1 + - name: Linux ARMv8 + arm_container: + image: ubuntu:latest + env: + - JULIA_VERSION: 1 + - name: musl Linux + container: + image: alpine:3.14 + env: + - JULIA_VERSION: 1 + - name: MacOS M1 + macos_instance: + image: ghcr.io/cirruslabs/macos-monterey-base:latest + env: + - JULIA_VERSION: 1 + install_script: | + URL="https://raw.githubusercontent.com/ararslan/CirrusCI.jl/master/bin/install.sh" + set -x + if [ "$(uname -s)" = "Linux" ] && command -v apt; then + apt update + apt install -y curl + fi + if command -v curl; then + sh -c "$(curl ${URL})" + elif command -v wget; then + sh -c "$(wget ${URL} -q -O-)" + elif command -v fetch; then + sh -c "$(fetch ${URL} -o -)" + fi build_script: - cirrusjl build test_script: diff --git a/.github/workflows/CI_M1.yml b/.github/workflows/CI_M1.yml deleted file mode 100644 index 590de6b5c..000000000 --- a/.github/workflows/CI_M1.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: CI_M1 -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened] -jobs: - test: - name: Julia ${{ matrix.version }} - macOS - ${{ matrix.arch }} - ${{ github.event_name }} - runs-on: [self-hosted, macOS] - strategy: - fail-fast: false - matrix: - version: - - '1' - arch: - - aarch64 - steps: - - uses: actions/checkout@v3 - - uses: julia-actions/setup-julia@v1 - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - name: Version Info - shell: julia --color=yes {0} - run: | - using InteractiveUtils - versioninfo() - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-runtest@v1 From 5291cafa6ce220db1620c664917495a429457b4c Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 23 Dec 2022 15:33:36 +0100 Subject: [PATCH 120/182] Switch Metal.jl CI to the juliaecosystem pipeline. --- .buildkite/pipeline.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 73121253c..59fdd3033 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -55,8 +55,9 @@ steps: - JuliaCI/julia#v1: version: 1.8 agents: - queue: "juliagpu" - metal: "*" + queue: "juliaecosystem" + os: "macos" + arch: "aarch64" command: | julia --color=yes --project -e ' using Pkg From 0e3de13924498c92344af6759ddf048e7f6ee659 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 2 Jan 2023 01:17:20 +0100 Subject: [PATCH 121/182] Use JET to fix bugs and type instabilities --- src/cg_lanczos_shift.jl | 4 +- src/krylov_solvers.jl | 28 ++++++------ src/krylov_stats.jl | 96 +++++++++++++++++++++++++---------------- src/krylov_utils.jl | 12 +++--- src/lslq.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/symmlq.jl | 7 ++- test/callback_utils.jl | 2 +- test/test_stats.jl | 42 ++++++++++-------- 10 files changed, 114 insertions(+), 83 deletions(-) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 38001d7e7..bf883649d 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -167,7 +167,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr (verbose > 0) && (fmt = Printf.Format("%5d" * repeat(" %8.1e", nshifts) * "\n")) kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) - solved = sum(not_cv) == 0 + solved = !reduce(|, not_cv) tired = iter ≥ itmax status = "unknown" user_requested_exit = false @@ -231,7 +231,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) user_requested_exit = callback(solver) :: Bool - solved = sum(not_cv) == 0 + solved = !reduce(|, not_cv) tired = iter ≥ itmax end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index a427cf63b..d6aece6a5 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -318,8 +318,8 @@ function CgLanczosShiftSolver(m, n, nshifts, S) Mv_prev = S(undef, n) Mv_next = S(undef, n) v = S(undef, 0) - x = [S(undef, n) for i = 1 : nshifts] - p = [S(undef, n) for i = 1 : nshifts] + x = S[S(undef, n) for i = 1 : nshifts] + p = S[S(undef, n) for i = 1 : nshifts] σ = Vector{T}(undef, nshifts) δhat = Vector{T}(undef, nshifts) ω = Vector{T}(undef, nshifts) @@ -328,7 +328,7 @@ function CgLanczosShiftSolver(m, n, nshifts, S) indefinite = BitVector(undef, nshifts) converged = BitVector(undef, nshifts) not_cv = BitVector(undef, nshifts) - stats = LanczosShiftStats(0, false, [T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") + stats = LanczosShiftStats(0, false, Vector{T}[T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") solver = CgLanczosShiftSolver{T,FC,S}(m, n, Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) return solver end @@ -423,8 +423,8 @@ function DqgmresSolver(m, n, memory, S) t = S(undef, n) z = S(undef, 0) w = S(undef, 0) - P = [S(undef, n) for i = 1 : memory] - V = [S(undef, n) for i = 1 : memory] + P = S[S(undef, n) for i = 1 : memory] + V = S[S(undef, n) for i = 1 : memory] c = Vector{T}(undef, memory) s = Vector{FC}(undef, memory) H = Vector{FC}(undef, memory+1) @@ -475,8 +475,8 @@ function DiomSolver(m, n, memory, S) t = S(undef, n) z = S(undef, 0) w = S(undef, 0) - P = [S(undef, n) for i = 1 : memory-1] - V = [S(undef, n) for i = 1 : memory] + P = S[S(undef, n) for i = 1 : memory-1] + V = S[S(undef, n) for i = 1 : memory] L = Vector{FC}(undef, memory-1) H = Vector{FC}(undef, memory) stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") @@ -1550,7 +1550,7 @@ function GmresSolver(m, n, memory, S) w = S(undef, n) p = S(undef, 0) q = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] + V = S[S(undef, n) for i = 1 : memory] c = Vector{T}(undef, memory) s = Vector{FC}(undef, memory) z = Vector{FC}(undef, memory) @@ -1603,8 +1603,8 @@ function FgmresSolver(m, n, memory, S) x = S(undef, n) w = S(undef, n) q = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] - Z = [S(undef, n) for i = 1 : memory] + V = S[S(undef, n) for i = 1 : memory] + Z = S[S(undef, n) for i = 1 : memory] c = Vector{T}(undef, memory) s = Vector{FC}(undef, memory) z = Vector{FC}(undef, memory) @@ -1656,7 +1656,7 @@ function FomSolver(m, n, memory, S) w = S(undef, n) p = S(undef, 0) q = S(undef, 0) - V = [S(undef, n) for i = 1 : memory] + V = S[S(undef, n) for i = 1 : memory] l = Vector{FC}(undef, memory) z = Vector{FC}(undef, memory) U = Vector{FC}(undef, div(memory * (memory+1), 2)) @@ -1719,8 +1719,8 @@ function GpmrSolver(m, n, memory, S) y = S(undef, n) q = S(undef, 0) p = S(undef, 0) - V = [S(undef, m) for i = 1 : memory] - U = [S(undef, n) for i = 1 : memory] + V = S[S(undef, m) for i = 1 : memory] + U = S[S(undef, n) for i = 1 : memory] gs = Vector{FC}(undef, 4 * memory) gc = Vector{T}(undef, 4 * memory) zt = Vector{FC}(undef, 2 * memory) @@ -1939,7 +1939,7 @@ function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) type_i = fieldtype(workspace, i) field_i = getfield(solver, name_i) size_i = ksizeof(field_i) - if (name_i in [:w̅, :w̄, :d̅]) && (VERSION < v"1.8.0-DEV") + if (name_i::Symbol in [:w̅, :w̄, :d̅]) && (VERSION < v"1.8.0-DEV") (size_i ≠ 0) && Printf.format(io, format2, string(name_i), type_i, format_bytes(size_i)) else (size_i ≠ 0) && Printf.format(io, format, string(name_i), type_i, format_bytes(size_i)) diff --git a/src/krylov_stats.jl b/src/krylov_stats.jl index f99c7863b..6fb10df56 100644 --- a/src/krylov_stats.jl +++ b/src/krylov_stats.jl @@ -24,6 +24,12 @@ mutable struct SimpleStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: SimpleStats) + empty!(stats.residuals) + empty!(stats.Aresiduals) + empty!(stats.Acond) +end + """ Type for statistics returned by LSMR. The attributes are: - niter @@ -50,6 +56,11 @@ mutable struct LsmrStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: LsmrStats) + empty!(stats.residuals) + empty!(stats.Aresiduals) +end + """ Type for statistics returned by CG-LANCZOS, the attributes are: - niter @@ -70,6 +81,10 @@ mutable struct LanczosStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: LanczosStats) + empty!(stats.residuals) +end + """ Type for statistics returned by CG-LANCZOS with shifts, the attributes are: - niter @@ -120,6 +135,13 @@ mutable struct SymmlqStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: SymmlqStats) + empty!(stats.residuals) + empty!(stats.residualscg) + empty!(stats.errors) + empty!(stats.errorscg) +end + """ Type for statistics returned by adjoint systems solvers BiLQR and TriLQR, the attributes are: - niter @@ -138,6 +160,11 @@ mutable struct AdjointStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: AdjointStats) + empty!(stats.residuals_primal) + empty!(stats.residuals_dual) +end + """ Type for statistics returned by the LNLQ method, the attributes are: - niter @@ -158,6 +185,12 @@ mutable struct LNLQStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: LNLQStats) + empty!(stats.residuals) + empty!(stats.error_bnd_x) + empty!(stats.error_bnd_y) +end + """ Type for statistics returned by the LSLQ method, the attributes are: - niter @@ -184,6 +217,14 @@ mutable struct LSLQStats{T} <: KrylovStats{T} status :: String end +function reset!(stats :: LSLQStats) + empty!(stats.residuals) + empty!(stats.Aresiduals) + empty!(stats.err_lbnds) + empty!(stats.err_ubnds_lq) + empty!(stats.err_ubnds_cg) +end + import Base.show special_fields = Dict( @@ -195,45 +236,24 @@ special_fields = Dict( :err_ubnds_cg => "error bound CG", ) -for f in ["Simple", "Lsmr", "Adjoint", "LNLQ", "LSLQ", "Lanczos", "Symmlq"] - T = Meta.parse("Krylov." * f * "Stats{S}") - - @eval function empty_field!(stats :: $T, i, ::Type{Vector{Si}}) where {S, Si} - statfield = getfield(stats, i) - empty!(statfield) - end - @eval empty_field!(stats :: $T, i, type) where S = stats - - @eval function reset!(stats :: $T) where S - nfield = length($T.types) - for i = 1 : nfield - type = fieldtype($T, i) - empty_field!(stats, i, type) +function show(io :: IO, stats :: KrylovStats) + kst = typeof(stats) + s = string(kst.name.name) * "\n" + nfield = fieldcount(kst) + for i = 1 : nfield + field = fieldname(kst, i) + field_name = if field ∈ keys(special_fields) + special_fields[field] + else + replace(string(field), "_" => " ") end - end -end - -for f in ["Simple", "Lsmr", "Lanczos", "LanczosShift", "Symmlq", "Adjoint", "LNLQ", "LSLQ"] - T = Meta.parse("Krylov." * f * "Stats{S}") - - @eval function show(io :: IO, stats :: $T) where S - s = $f * " stats\n" - nfield = length($T.types) - for i = 1 : nfield - field = fieldname($T, i) - field_name = if field ∈ keys(special_fields) - special_fields[field] - else - replace(string(field), "_" => " ") - end - s *= " " * field_name * ":" - statfield = getfield(stats, field) - if isa(statfield, AbstractVector) && eltype(statfield) <: Union{Missing, AbstractFloat} - s *= @sprintf " %s\n" vec2str(statfield) - else - s *= @sprintf " %s\n" statfield - end + s *= " " * field_name * ":" + statfield = getfield(stats, field) + if isa(statfield, AbstractVector) && eltype(statfield) <: Union{Missing, AbstractFloat} + s *= @sprintf " %s\n" vec2str(statfield) + else + s *= @sprintf " %s\n" statfield end - print(io, s) end + print(io, s) end diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 199dd544e..6049f9c28 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -272,18 +272,18 @@ end """ v = kzeros(S, n) -Create an AbstractVector of storage type `S` of length `n` only composed of zero. +Create a vector of storage type `S` of length `n` only composed of zero. """ kzeros(S, n) = fill!(S(undef, n), zero(eltype(S))) """ v = kones(S, n) -Create an AbstractVector of storage type `S` of length `n` only composed of one. +Create a vector of storage type `S` of length `n` only composed of one. """ kones(S, n) = fill!(S(undef, n), one(eltype(S))) -allocate_if(bool, solver, v, S, n) = bool && isempty(solver.:($v)) && (solver.:($v) = S(undef, n)) +allocate_if(bool, solver, v, S, n) = bool && isempty(solver.:($v)::S) && (solver.:($v)::S = S(undef, n)) kdisplay(iter, verbose) = (verbose > 0) && (mod(iter, verbose) == 0) @@ -373,15 +373,15 @@ If `flip` is set to `true`, `σ1` and `σ2` are computed such that ‖x - σi d‖ = radius, i = 1, 2. """ -function to_boundary(n :: Int, x :: AbstractVector{T}, d :: AbstractVector{T}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where T <: FloatOrComplex +function to_boundary(n :: Int, x :: AbstractVector{FC}, d :: AbstractVector{FC}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} radius > 0 || error("radius must be positive") # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - Δ²). rxd = @kdotr(n, x, d) flip && (rxd = -rxd) - dNorm2 == zero(T) && (dNorm2 = @kdot(n, d, d)) + dNorm2 == zero(T) && (dNorm2 = @kdotr(n, d, d)) dNorm2 == zero(T) && error("zero direction") - xNorm2 == zero(T) && (xNorm2 = @kdot(n, x, x)) + xNorm2 == zero(T) && (xNorm2 = @kdotr(n, x, x)) radius2 = radius * radius (xNorm2 ≤ radius2) || error(@sprintf("outside of the trust region: ‖x‖²=%7.1e, Δ²=%7.1e", xNorm2, radius2)) diff --git a/src/lslq.jl b/src/lslq.jl index 420c8a1cb..4e26fb67a 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -417,7 +417,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; # check stopping condition based on forward error lower bound err_vec[mod(iter, window) + 1] = ζ if iter ≥ window - err_lbnd = norm(err_vec) + err_lbnd = @knrm2(window, err_vec) history && push!(err_lbnds, err_lbnd) fwd_err_lbnd = err_lbnd ≤ etol * xlqNorm end diff --git a/src/lsqr.jl b/src/lsqr.jl index 36e5a8ef9..0351b75e1 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -302,7 +302,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; xENorm² = xENorm² + ϕ * ϕ err_vec[mod(iter, window) + 1] = ϕ - iter ≥ window && (err_lbnd = norm(err_vec)) + iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) τ = s * ϕ θ = s * α diff --git a/src/minres.jl b/src/minres.jl index 718a754be..6098d4f33 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -287,7 +287,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; # Compute lower bound on forward error. err_vec[mod(iter, window) + 1] = ϕ - iter ≥ window && (err_lbnd = norm(err_vec)) + iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) γmax = max(γmax, γ) γmin = min(γmin, γ) diff --git a/src/symmlq.jl b/src/symmlq.jl index 1da2c02f6..81477fc66 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -327,8 +327,11 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; zetabark = zlist[jx] / clist[jx] if γbar ≠ 0 - theta = abs(sum(clist[i] * sprod[i] * zlist[i] for i = 1 : window)) - theta = zetabark * theta + abs(zetabark * ζbar * sprod[ix] * s) - zetabark^2 + theta = zero(T) + for i = 1 : window + theta += clist[i] * sprod[i] * zlist[i] + end + theta = zetabark * abs(theta) + abs(zetabark * ζbar * sprod[ix] * s) - zetabark^2 history && (errorscg[iter-window+1] = sqrt(abs(errorscg[iter-window+1]^2 - 2*theta))) else history && (errorscg[iter-window+1] = missing) diff --git a/test/callback_utils.jl b/test/callback_utils.jl index c5993c2a3..f88f01848 100644 --- a/test/callback_utils.jl +++ b/test/callback_utils.jl @@ -120,7 +120,7 @@ TestCallbackN2LN(A, b, λ; tol = 0.1) = TestCallbackN2LN(A, b, λ, similar(b), t function (cb_n2::TestCallbackN2LN)(solver) mul!(cb_n2.storage_vec, cb_n2.A, solver.x) cb_n2.storage_vec .-= cb_n2.b - cb_n2.λ != 0 && (cb_n2.storage_vec .+= sqrt(cb_n2.λ) .* solver.s) + cb_n2.λ != 0 && (cb_n2.storage_vec .+= cb_n2.λ .* solver.x) return norm(cb_n2.storage_vec) ≤ cb_n2.tol end diff --git a/test/test_stats.jl b/test/test_stats.jl index 4289a78a3..186c56c20 100644 --- a/test/test_stats.jl +++ b/test/test_stats.jl @@ -4,7 +4,7 @@ show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """Simple stats + expected = """SimpleStats niter: 0 solved: true inconsistent: true @@ -15,14 +15,15 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.LsmrStats(0, true, true, Float64[1.0], Float64[2.0], Float64(3.0), Float64(4.0), Float64(5.0), Float64(6.0), Float64(7.0), "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """Lsmr stats + expected = """LsmrStats niter: 0 solved: true inconsistent: true @@ -37,14 +38,15 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.LanczosStats(0, true, Float64[3.0], true, NaN, NaN, "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """Lanczos stats + expected = """LanczosStats niter: 0 solved: true residuals: [ 3.0e+00 ] @@ -55,14 +57,15 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.LanczosShiftStats(0, true, [Float64[0.9, 0.5], Float64[0.6, 0.4, 0.1]], BitVector([false, true]), NaN, NaN, "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """LanczosShift stats + expected = """LanczosShiftStats niter: 0 solved: true residuals: [[0.9, 0.5], [0.6, 0.4, 0.1]] @@ -70,16 +73,17 @@ ‖A‖F: NaN κ₂(A): NaN status: t""" - @test (VERSION < v"1.5") || strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) + @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.SymmlqStats(0, true, Float64[4.0], Union{Float64,Missing}[5.0, missing], Float64[6.0], Union{Float64,Missing}[7.0, missing], NaN, NaN, "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """Symmlq stats + expected = """SymmlqStats niter: 0 solved: true residuals: [ 4.0e+00 ] @@ -92,14 +96,15 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.AdjointStats(0, true, true, Float64[8.0], Float64[9.0], "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """Adjoint stats + expected = """AdjointStats niter: 0 solved primal: true solved dual: true @@ -109,14 +114,15 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.LNLQStats(0, true, Float64[10.0], false, Float64[11.0], Float64[12.0], "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """LNLQ stats + expected = """LNLQStats niter: 0 solved: true residuals: [ 1.0e+01 ] @@ -127,14 +133,15 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 stats = Krylov.LSLQStats(0, true, false, Float64[13.0], Float64[14.0], Float64[15.0], false, Float64[16.0], Float64[17.0], "t") io = IOBuffer() show(io, stats) showed = String(take!(io)) storage_type = typeof(stats) - expected = """LSLQ stats + expected = """LSLQStats niter: 0 solved: true inconsistent: false @@ -148,5 +155,6 @@ @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) - @test (VERSION < v"1.5") || (@allocated Krylov.reset!(stats)) == 0 + nbytes_allocated = @allocated Krylov.reset!(stats) + @test nbytes_allocated == 0 end From 962811e23d021d578c059636a0b7b2fdbc280b63 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 22 Jan 2023 23:40:17 +0100 Subject: [PATCH 122/182] Add new tests for GPUs --- docs/src/gpu.md | 4 ++-- test/gpu/amd.jl | 22 +++++++++++++++------- test/gpu/intel.jl | 28 +++++++++++++++------------- test/gpu/metal.jl | 34 ++++++++++++++++------------------ test/gpu/nvidia.jl | 16 ++++++++++++---- 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 0e13b510c..378f4f5d3 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -145,7 +145,7 @@ x, stats = minres(A_gpu, b_gpu) ## Intel GPUs -All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) and allow computations on Intel GPUs. +All solvers in Krylov.jl can be used with [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) and allow computations on Intel GPUs. Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to the related GPU format (`oneMatrix` and `oneVector`). ```julia @@ -172,7 +172,7 @@ x, stats = lsqr(A_gpu, b_gpu) ## Apple M1 GPUs -All solvers in Krylov.jl, except [`MINRES-QLP`](@ref minres_qlp), can be used with [Metal.jl](https://github.com/JuliaGPU/Metal.jl) and allow computations on Apple M1 GPUs. +All solvers in Krylov.jl can be used with [Metal.jl](https://github.com/JuliaGPU/Metal.jl) and allow computations on Apple M1 GPUs. Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to the related GPU format (`MtlMatrix` and `MtlVector`). ```julia diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index 1708722b2..9fb6cdffd 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -64,9 +64,9 @@ include("gpu.jl") Krylov.@kswap(x, y) end - # @testset "kref! -- $FC" begin - # Krylov.@kref!(n, x, y, c, s) - # end + @testset "kref! -- $FC" begin + Krylov.@kref!(n, x, y, c, s) + end @testset "conversion -- $FC" begin test_conversion(S, M) @@ -78,20 +78,28 @@ include("gpu.jl") @testset "GMRES -- $FC" begin A, b = nonsymmetric_indefinite(FC=FC) - A = ROCMatrix{FC}(A) - b = ROCVector{FC}(b) + A = M(A) + b = S(b) x, stats = gmres(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin A, b = symmetric_definite(FC=FC) - A = ROCMatrix{FC}(A) - b = ROCVector{FC}(b) + A = M(A) + b = S(b) x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + @testset "MINRES-QLP -- $FC" begin + A, b = symmetric_indefinite(FC=FC) + A = M(A) + b = S(b) + x, stats = minres_qlp(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) + end + # @testset "processes -- $FC" begin # test_processes(S, M) # end diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index 2e2812553..f03176199 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -2,12 +2,6 @@ using oneAPI include("gpu.jl") -import Krylov.kdot -# https://github.com/JuliaGPU/GPUArrays.jl/pull/427 -function kdot(n :: Integer, x :: oneVector{T}, dx :: Integer, y :: oneVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex - return mapreduce(dot, +, x, y) -end - @testset "Intel -- oneAPI.jl" begin @test oneAPI.functional() @@ -72,9 +66,9 @@ end Krylov.@kswap(x, y) end - # @testset "kref! -- $FC" begin - # Krylov.@kref!(n, x, y, c, s) - # end + @testset "kref! -- $FC" begin + Krylov.@kref!(n, x, y, c, s) + end @testset "conversion -- $FC" begin test_conversion(S, M) @@ -86,20 +80,28 @@ end @testset "GMRES -- $FC" begin A, b = nonsymmetric_indefinite(FC=FC) - A = oneMatrix{FC}(A) - b = oneVector{FC}(b) + A = M(A) + b = S(b) x, stats = gmres(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin A, b = symmetric_definite(FC=FC) - A = oneMatrix{FC}(A) - b = oneVector{FC}(b) + A = M(A) + b = S(b) x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + @testset "MINRES-QLP -- $FC" begin + A, b = symmetric_indefinite(FC=FC) + A = M(A) + b = S(b) + x, stats = minres_qlp(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) + end + # @testset "processes -- $FC" begin # test_processes(S, M) # end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 3b83ab921..2e684e21f 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -2,16 +2,6 @@ using Metal include("gpu.jl") -# https://github.com/JuliaGPU/Metal.jl/pull/48 -const MtlVector{T} = MtlArray{T,1} -const MtlMatrix{T} = MtlArray{T,2} - -# https://github.com/JuliaGPU/GPUArrays.jl/pull/427 -import Krylov.kdot -function kdot(n :: Integer, x :: MtlVector{T}, dx :: Integer, y :: MtlVector{T}, dy :: Integer) where T <: Krylov.FloatOrComplex - return mapreduce(dot, +, x, y) -end - @testset "Apple M1 GPUs -- Metal.jl" begin # @test Metal.functional() @@ -76,9 +66,9 @@ end Krylov.@kswap(x, y) end - # @testset "kref! -- $FC" begin - # Krylov.@kref!(n, x, y, c, s) - # end + @testset "kref! -- $FC" begin + Krylov.@kref!(n, x, y, c, s) + end @testset "conversion -- $FC" begin test_conversion(S, M) @@ -90,20 +80,28 @@ end @testset "GMRES -- $FC" begin A, b = nonsymmetric_indefinite(FC=FC) - A = MtlMatrix{FC}(A) - b = MtlVector{FC}(b) + A = M(A) + b = S(b) x, stats = gmres(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin - A, b = symmetric_definite(FC=FC) - A = MtlMatrix{FC}(A) - b = MtlVector{FC}(b) + A, b = symmetric_definite(FC=FC) + A = M(A) + b = S(b) x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + @testset "MINRES-QLP -- $FC" begin + A, b = symmetric_indefinite(FC=FC) + A = M(A) + b = S(b) + x, stats = minres_qlp(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) + end + # @testset "processes -- $FC" begin # test_processes(S, M) # end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 286e0c99d..908a2819c 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -155,20 +155,28 @@ include("gpu.jl") @testset "GMRES -- $FC" begin A, b = nonsymmetric_indefinite(FC=FC) - A = CuMatrix{FC}(A) - b = CuVector{FC}(b) + A = M(A) + b = S(b) x, stats = gmres(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end @testset "CG -- $FC" begin A, b = symmetric_definite(FC=FC) - A = CuMatrix{FC}(A) - b = CuVector{FC}(b) + A = M(A) + b = S(b) x, stats = cg(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end + @testset "MINRES-QLP -- $FC" begin + A, b = symmetric_indefinite(FC=FC) + A = M(A) + b = S(b) + x, stats = minres_qlp(A, b) + @test norm(b - A * x) ≤ atol + rtol * norm(b) + end + @testset "processes -- $FC" begin test_processes(S, M) end From 9e4b6da6d476ce2aefbd512ea10b3f8894a0bdc2 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 22 Jan 2023 23:55:10 +0100 Subject: [PATCH 123/182] Use sparse_laplacian instead of symmetric_indefinite for GPU tests --- test/gpu/amd.jl | 2 +- test/gpu/intel.jl | 2 +- test/gpu/metal.jl | 2 +- test/gpu/nvidia.jl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index 9fb6cdffd..3245240a1 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -93,7 +93,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_indefinite(FC=FC) + A, b = sparse_laplacian(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index f03176199..dcd380098 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -95,7 +95,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_indefinite(FC=FC) + A, b = sparse_laplacian(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 2e684e21f..39bb4d7ee 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -95,7 +95,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_indefinite(FC=FC) + A, b = sparse_laplacian(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 908a2819c..4e2ab4e32 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -170,7 +170,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_indefinite(FC=FC) + A, b = sparse_laplacian(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) From 03ee0a8a1864ae8fd371842bd0f48d4db440ee7d Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 22 Jan 2023 23:55:41 +0100 Subject: [PATCH 124/182] [buildkite] Use Julia 1.9 for AMD GPUs --- .buildkite/pipeline.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 59fdd3033..67daaca40 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -18,7 +18,7 @@ steps: - label: "AMD GPUs -- AMDGPU.jl" plugins: - JuliaCI/julia#v1: - version: 1.8 + version: 1.9-nightly agents: queue: "juliagpu" rocm: "*" @@ -26,7 +26,6 @@ steps: env: JULIA_AMDGPU_CORE_MUST_LOAD: "1" JULIA_AMDGPU_HIP_MUST_LOAD: "1" - JULIA_AMDGPU_DISABLE_ARTIFACTS: "1" command: | julia --color=yes --project -e ' using Pkg From baa45b39313e71b90be3a65427d8fa0f176390e3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 23 Jan 2023 00:00:44 +0100 Subject: [PATCH 125/182] Use symmetric_definite instead of sparse_laplacian for GPU tests --- test/gpu/amd.jl | 2 +- test/gpu/intel.jl | 2 +- test/gpu/metal.jl | 2 +- test/gpu/nvidia.jl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index 3245240a1..66689495a 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -93,7 +93,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = sparse_laplacian(FC=FC) + A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index dcd380098..c1e1bf29d 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -95,7 +95,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = sparse_laplacian(FC=FC) + A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 39bb4d7ee..fef84a789 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -95,7 +95,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = sparse_laplacian(FC=FC) + A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 4e2ab4e32..cd7a59e5b 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -170,7 +170,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = sparse_laplacian(FC=FC) + A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) From 67dbbee54b782df2cf07d4e0d7a489eca22875bf Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 23 Jan 2023 00:04:54 +0100 Subject: [PATCH 126/182] Debug minres_qlp with verbose mode --- test/gpu/amd.jl | 2 +- test/gpu/intel.jl | 2 +- test/gpu/metal.jl | 2 +- test/gpu/nvidia.jl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index 66689495a..861a9e136 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -93,7 +93,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_definite(FC=FC) + A, b = symmetric_definite(FC=FC, verbose=1) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index c1e1bf29d..8f5e2f755 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -98,7 +98,7 @@ include("gpu.jl") A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) - x, stats = minres_qlp(A, b) + x, stats = minres_qlp(A, b, verbose=1) @test norm(b - A * x) ≤ atol + rtol * norm(b) end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index fef84a789..cdb8c0507 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -98,7 +98,7 @@ include("gpu.jl") A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) - x, stats = minres_qlp(A, b) + x, stats = minres_qlp(A, b, verbose=1) @test norm(b - A * x) ≤ atol + rtol * norm(b) end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index cd7a59e5b..6ed2f45f1 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -173,7 +173,7 @@ include("gpu.jl") A, b = symmetric_definite(FC=FC) A = M(A) b = S(b) - x, stats = minres_qlp(A, b) + x, stats = minres_qlp(A, b, verbose=1) @test norm(b - A * x) ≤ atol + rtol * norm(b) end From 18ef839fcd7247dfbf3c3db2e6e0c2c31ee395fd Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 23 Jan 2023 00:37:45 +0100 Subject: [PATCH 127/182] Relax the tolerance of the stopping condition based on the backward error --- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minres.jl b/src/minres.jl index 6098d4f33..f82bbc350 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -336,7 +336,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax ill_cond_lim = (one(T) / Acond ≤ ctol) solved_lim = (test2 ≤ ε) - zero_resid_lim = MisI && (test1 ≤ ε) + zero_resid_lim = MisI && (test1 ≤ eps(T)) resid_decrease_lim = (rNorm ≤ ε) iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index d4d63266f..72662f97e 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -406,7 +406,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F # Stopping conditions based on user-provided tolerances. tired = iter ≥ itmax resid_decrease_lim = (rNorm ≤ ε) - zero_resid_lim = MisI && (backward ≤ ε) + zero_resid_lim = MisI && (backward ≤ eps(T)) breakdown = βₖ₊₁ ≤ btol user_requested_exit = callback(solver) :: Bool From 391da90ed3370bac97387e8a22837e5276e19a50 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 23 Jan 2023 00:46:27 +0100 Subject: [PATCH 128/182] Use master branch of AMDGPU.jl --- .buildkite/pipeline.yml | 3 ++- test/gpu/amd.jl | 2 +- test/gpu/intel.jl | 4 ++-- test/gpu/metal.jl | 4 ++-- test/gpu/nvidia.jl | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 67daaca40..d2cbb0258 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -29,7 +29,8 @@ steps: command: | julia --color=yes --project -e ' using Pkg - Pkg.add("AMDGPU") + # Pkg.add("AMDGPU") + Pkg.add(url="https://github.com/JuliaGPU/AMDGPU.jl", rev="master") Pkg.instantiate() include("test/gpu/amd.jl")' timeout_in_minutes: 30 diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index 861a9e136..9fb6cdffd 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -93,7 +93,7 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_definite(FC=FC, verbose=1) + A, b = symmetric_indefinite(FC=FC) A = M(A) b = S(b) x, stats = minres_qlp(A, b) diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index 8f5e2f755..f03176199 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -95,10 +95,10 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_definite(FC=FC) + A, b = symmetric_indefinite(FC=FC) A = M(A) b = S(b) - x, stats = minres_qlp(A, b, verbose=1) + x, stats = minres_qlp(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index cdb8c0507..2e684e21f 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -95,10 +95,10 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_definite(FC=FC) + A, b = symmetric_indefinite(FC=FC) A = M(A) b = S(b) - x, stats = minres_qlp(A, b, verbose=1) + x, stats = minres_qlp(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 6ed2f45f1..908a2819c 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -170,10 +170,10 @@ include("gpu.jl") end @testset "MINRES-QLP -- $FC" begin - A, b = symmetric_definite(FC=FC) + A, b = symmetric_indefinite(FC=FC) A = M(A) b = S(b) - x, stats = minres_qlp(A, b, verbose=1) + x, stats = minres_qlp(A, b) @test norm(b - A * x) ≤ atol + rtol * norm(b) end From c2148736d5b1551e273425280191c607a6be6318 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 9 Feb 2023 12:33:47 -0500 Subject: [PATCH 129/182] Allow more generic storage type S --- src/bicgstab.jl | 4 ++-- src/bilq.jl | 4 ++-- src/bilqr.jl | 4 ++-- src/cg.jl | 4 ++-- src/cg_lanczos.jl | 4 ++-- src/cg_lanczos_shift.jl | 2 +- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 4 ++-- src/cr.jl | 4 ++-- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 4 ++-- src/dqgmres.jl | 4 ++-- src/fgmres.jl | 4 ++-- src/fom.jl | 4 ++-- src/gmres.jl | 4 ++-- src/gpmr.jl | 4 ++-- src/krylov_solvers.jl | 2 +- src/krylov_utils.jl | 11 +++++------ src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 4 ++-- src/minres_qlp.jl | 4 ++-- src/qmr.jl | 4 ++-- src/symmlq.jl | 4 ++-- src/tricg.jl | 4 ++-- src/trilqr.jl | 4 ++-- src/trimr.jl | 4 ++-- src/usymlq.jl | 4 ++-- src/usymqr.jl | 4 ++-- 35 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index c4f16595e..4eac41142 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -99,7 +99,7 @@ See [`BicgstabSolver`](@ref) for more details about the `solver`. """ function bicgstab! end -function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) bicgstab!(solver, A, b; kwargs...) return solver @@ -110,7 +110,7 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; ldiv :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/bilq.jl b/src/bilq.jl index 12ee40652..f30d7f3f9 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -86,7 +86,7 @@ See [`BilqSolver`](@ref) for more details about the `solver`. """ function bilq! end -function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) bilq!(solver, A, b; kwargs...) return solver @@ -96,7 +96,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, transfer_to_bicg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/bilqr.jl b/src/bilqr.jl index 5666f0863..6699247de 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -92,7 +92,7 @@ See [`BilqrSolver`](@ref) for more details about the `solver`. function bilqr! end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) bilqr!(solver, A, b, c; kwargs...) return solver @@ -102,7 +102,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: transfer_to_bicg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("Systems must be square") diff --git a/src/cg.jl b/src/cg.jl index ed9d88cfa..f441f85c9 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -91,7 +91,7 @@ See [`CgSolver`](@ref) for more details about the `solver`. """ function cg! end -function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) cg!(solver, A, b; kwargs...) return solver @@ -102,7 +102,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; linesearch :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index f648eb2a8..dfef7cbba 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -88,7 +88,7 @@ See [`CgLanczosSolver`](@ref) for more details about the `solver`. """ function cg_lanczos! end -function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) cg_lanczos!(solver, A, b; kwargs...) return solver @@ -99,7 +99,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F check_curvature :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index bf883649d..55d95be12 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -83,7 +83,7 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr check_curvature :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cgls.jl b/src/cgls.jl index 55fe6d0ec..3dda31258 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -103,7 +103,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/cgne.jl b/src/cgne.jl index f85af32be..c414b699c 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -111,7 +111,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/cgs.jl b/src/cgs.jl index cbb3db13b..49de75f25 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -100,7 +100,7 @@ See [`CgsSolver`](@ref) for more details about the `solver`. """ function cgs! end -function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) cgs!(solver, A, b; kwargs...) return solver @@ -111,7 +111,7 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; ldiv :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/cr.jl b/src/cr.jl index 26f317385..f1e0235c1 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -93,7 +93,7 @@ See [`CrSolver`](@ref) for more details about the `solver`. """ function cr! end -function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) cr!(solver, A, b; kwargs...) return solver @@ -104,7 +104,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; linesearch :: Bool=false, γ :: T=√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") diff --git a/src/craig.jl b/src/craig.jl index 76afe9d51..b948d61b1 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -146,7 +146,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/craigmr.jl b/src/craigmr.jl index 3b64829d6..f6899c4f8 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -136,7 +136,7 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/crls.jl b/src/crls.jl index 78615fad6..e21274ddc 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -94,7 +94,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/crmr.jl b/src/crmr.jl index 621ba5ef3..6f603b953 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -109,7 +109,7 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/diom.jl b/src/diom.jl index 7bf23e355..c15e548fa 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -95,7 +95,7 @@ See [`DiomSolver`](@ref) for more details about the `solver`. """ function diom! end -function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) diom!(solver, A, b; kwargs...) return solver @@ -106,7 +106,7 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 025016304..762a54da2 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -95,7 +95,7 @@ See [`DqgmresSolver`](@ref) for more details about the `solver`. """ function dqgmres! end -function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) dqgmres!(solver, A, b; kwargs...) return solver @@ -106,7 +106,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/fgmres.jl b/src/fgmres.jl index fa536af23..995ea09b3 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -97,7 +97,7 @@ See [`FgmresSolver`](@ref) for more details about the `solver`. """ function fgmres! end -function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) fgmres!(solver, A, b; kwargs...) return solver @@ -108,7 +108,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; restart :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/fom.jl b/src/fom.jl index 6aabb33f5..50ca5c4be 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -90,7 +90,7 @@ See [`FomSolver`](@ref) for more details about the `solver`. """ function fom! end -function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) fom!(solver, A, b; kwargs...) return solver @@ -101,7 +101,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; restart :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/gmres.jl b/src/gmres.jl index d475198b5..8cf0384f1 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -90,7 +90,7 @@ See [`GmresSolver`](@ref) for more details about the `solver`. """ function gmres! end -function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) gmres!(solver, A, b; kwargs...) return solver @@ -101,7 +101,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; restart :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/gpmr.jl b/src/gpmr.jl index 958d2977c..e98e5e432 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -127,7 +127,7 @@ See [`GpmrSolver`](@ref) for more details about the `solver`. function gpmr! end function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) gpmr!(solver, A, B, b, c; kwargs...) return solver @@ -140,7 +140,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history::Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) s, t = size(B) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index d6aece6a5..2e013c50b 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1913,7 +1913,7 @@ end Statistics of `solver` are displayed if `show_stats` is set to true. """ -function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function show(io :: IO, solver :: KrylovSolver{T,FC,S}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} workspace = typeof(solver) name_solver = string(workspace.name.name) name_stats = string(typeof(solver.stats).name.name) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 6049f9c28..dbdd51227 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -201,10 +201,14 @@ end """ S = ktypeof(v) -Return a dense storage type `S` based on the type of `v`. +Return the most relevant storage type `S` based on the type of `v`. """ function ktypeof end +function ktypeof(v::S) where S <: AbstractVector + return S # BlockArrays, FillArrays, PartitionedArrays, etc... +end + function ktypeof(v::S) where S <: DenseVector return S end @@ -218,11 +222,6 @@ function ktypeof(v::S) where S <: AbstractSparseVector return S.types[2] # return `CuVector` for a `CuSparseVector` end -function ktypeof(v::S) where S <: AbstractVector - T = eltype(S) - return Vector{T} # BlockArrays, FillArrays, etc... -end - function ktypeof(v::S) where S <: SubArray vp = v.parent if isa(vp, DenseMatrix) diff --git a/src/lnlq.jl b/src/lnlq.jl index deda7336f..b84fe21d9 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -142,7 +142,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; utoly :: T=√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lslq.jl b/src/lslq.jl index 4e26fb67a..df47de091 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -167,7 +167,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback=solver->false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback=solver->false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lsmr.jl b/src/lsmr.jl index 781d9448a..d49c8e4f7 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -144,7 +144,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; conlim :: T=1/√eps(T), atol :: T=zero(T), rtol :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/lsqr.jl b/src/lsqr.jl index 0351b75e1..20129c166 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -140,7 +140,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; conlim :: T=1/√eps(T), atol :: T=zero(T), rtol :: T=zero(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/minres.jl b/src/minres.jl index f82bbc350..d2c79bd04 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -112,7 +112,7 @@ See [`MinresSolver`](@ref) for more details about the `solver`. """ function minres! end -function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) minres!(solver, A, b; kwargs...) return solver @@ -124,7 +124,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; rtol :: T=√eps(T), etol :: T=√eps(T), conlim :: T=1/√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 72662f97e..7b88b437c 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -95,7 +95,7 @@ See [`MinresQlpSolver`](@ref) for more details about the `solver`. """ function minres_qlp! end -function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) minres_qlp!(solver, A, b; kwargs...) return solver @@ -106,7 +106,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F λ ::T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/qmr.jl b/src/qmr.jl index e24fba79a..3f491b765 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -94,7 +94,7 @@ See [`QmrSolver`](@ref) for more details about the `solver`. """ function qmr! end -function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) qmr!(solver, A, b; kwargs...) return solver @@ -103,7 +103,7 @@ end function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/symmlq.jl b/src/symmlq.jl index 81477fc66..2d1f097a7 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -96,7 +96,7 @@ See [`SymmlqSolver`](@ref) for more details about the `solver`. """ function symmlq! end -function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} +function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) symmlq!(solver, A, b; kwargs...) return solver @@ -109,7 +109,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) m == n || error("System must be square") diff --git a/src/tricg.jl b/src/tricg.jl index 4096a9ffe..0f55f2a54 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -115,7 +115,7 @@ See [`TricgSolver`](@ref) for more details about the `solver`. function tricg! end function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) tricg!(solver, A, b, c; kwargs...) return solver @@ -128,7 +128,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: ν :: T=-one(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/trilqr.jl b/src/trilqr.jl index e11a8a6c6..0d57e1555 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -91,7 +91,7 @@ See [`TrilqrSolver`](@ref) for more details about the `solver`. function trilqr! end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) trilqr!(solver, A, b, c; kwargs...) return solver @@ -101,7 +101,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/trimr.jl b/src/trimr.jl index 9da4dfa92..c6cf83208 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -115,7 +115,7 @@ See [`TrimrSolver`](@ref) for more details about the `solver`. function trimr! end function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) trimr!(solver, A, b, c; kwargs...) return solver @@ -128,7 +128,7 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: τ :: T=one(T), ν :: T=-one(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/usymlq.jl b/src/usymlq.jl index 53aef51a3..d3eeace65 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -100,7 +100,7 @@ See [`UsymlqSolver`](@ref) for more details about the `solver`. function usymlq! end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) usymlq!(solver, A, b, c; kwargs...) return solver @@ -110,7 +110,7 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") diff --git a/src/usymqr.jl b/src/usymqr.jl index 3876499b5..270d4d085 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -99,7 +99,7 @@ See [`UsymqrSolver`](@ref) for more details about the `solver`. function usymqr! end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) usymqr!(solver, A, b, c; kwargs...) return solver @@ -108,7 +108,7 @@ end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: DenseVector{FC}} + callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) length(b) == m || error("Inconsistent problem size") From b83491ca9f6cec9f1c6f297ae8ce06caf1d0f332 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 9 Feb 2023 12:59:10 -0500 Subject: [PATCH 130/182] Small modification for FillArrays package --- src/krylov_utils.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index dbdd51227..ebacc7a74 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -205,14 +205,19 @@ Return the most relevant storage type `S` based on the type of `v`. """ function ktypeof end -function ktypeof(v::S) where S <: AbstractVector - return S # BlockArrays, FillArrays, PartitionedArrays, etc... -end - function ktypeof(v::S) where S <: DenseVector return S end +function ktypeof(v::S) where S <: AbstractVector + if S.name.name == :Zeros || S.name.name == :Ones + T = eltype(S) + return Vector{T} # FillArrays + else + return S # BlockArrays, PartitionedArrays, etc... + end +end + function ktypeof(v::S) where S <: SparseVector T = eltype(S) return Vector{T} From 414e0b2594d61e1897401e08f5f0c407daa9acf8 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 12 Feb 2023 19:55:04 -0500 Subject: [PATCH 131/182] [buildkite] Remove the build with AMDGPU.jl] --- .buildkite/pipeline.yml | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index d2cbb0258..0943f7c21 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -15,25 +15,24 @@ steps: include("test/gpu/nvidia.jl")' timeout_in_minutes: 30 - - label: "AMD GPUs -- AMDGPU.jl" - plugins: - - JuliaCI/julia#v1: - version: 1.9-nightly - agents: - queue: "juliagpu" - rocm: "*" - rocmgpu: "*" - env: - JULIA_AMDGPU_CORE_MUST_LOAD: "1" - JULIA_AMDGPU_HIP_MUST_LOAD: "1" - command: | - julia --color=yes --project -e ' - using Pkg - # Pkg.add("AMDGPU") - Pkg.add(url="https://github.com/JuliaGPU/AMDGPU.jl", rev="master") - Pkg.instantiate() - include("test/gpu/amd.jl")' - timeout_in_minutes: 30 + # - label: "AMD GPUs -- AMDGPU.jl" + # plugins: + # - JuliaCI/julia#v1: + # version: 1.8 + # agents: + # queue: "juliagpu" + # rocm: "*" + # rocmgpu: "*" + # env: + # JULIA_AMDGPU_CORE_MUST_LOAD: "1" + # JULIA_AMDGPU_HIP_MUST_LOAD: "1" + # command: | + # julia --color=yes --project -e ' + # using Pkg + # Pkg.add("AMDGPU") + # Pkg.instantiate() + # include("test/gpu/amd.jl")' + # timeout_in_minutes: 30 - label: "Intel GPUs -- oneAPI.jl" plugins: From c8c08363580f1531d15a6c9a8f0f251dcd76a722 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 11 Feb 2023 15:24:25 -0500 Subject: [PATCH 132/182] Check that the KrylovSolvers are compatible with the dimension of the linear problems --- src/bicgstab.jl | 1 + src/bilq.jl | 1 + src/bilqr.jl | 1 + src/cg.jl | 1 + src/cg_lanczos.jl | 1 + src/cg_lanczos_shift.jl | 2 ++ src/cgls.jl | 1 + src/cgne.jl | 1 + src/cgs.jl | 1 + src/cr.jl | 1 + src/craig.jl | 1 + src/craigmr.jl | 1 + src/crls.jl | 1 + src/crmr.jl | 1 + src/diom.jl | 1 + src/dqgmres.jl | 1 + src/fgmres.jl | 1 + src/fom.jl | 1 + src/gmres.jl | 1 + src/gpmr.jl | 1 + src/krylov_solvers.jl | 3 ++- src/lnlq.jl | 1 + src/lslq.jl | 1 + src/lsmr.jl | 1 + src/lsqr.jl | 1 + src/minres.jl | 1 + src/minres_qlp.jl | 1 + src/qmr.jl | 1 + src/symmlq.jl | 1 + src/tricg.jl | 1 + src/trilqr.jl | 1 + src/trimr.jl | 1 + src/usymlq.jl | 1 + src/usymqr.jl | 1 + 34 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 4eac41142..c3b6c8b53 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -113,6 +113,7 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "BICGSTAB: system of size %d\n", n) diff --git a/src/bilq.jl b/src/bilq.jl index f30d7f3f9..327ca8b0e 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -99,6 +99,7 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "BILQ: system of size %d\n", n) diff --git a/src/bilqr.jl b/src/bilqr.jl index 6699247de..111eef567 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -105,6 +105,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("Systems must be square") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") diff --git a/src/cg.jl b/src/cg.jl index f441f85c9..5ee6d2cda 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -107,6 +107,7 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CG: system of %d equations in %d variables\n", n, n) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index dfef7cbba..9bab1563e 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -102,6 +102,7 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables\n", n, n) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 55d95be12..f579a79c8 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -86,10 +86,12 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") nshifts = length(shifts) + nshifts == solver.nshifts || error("solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $nshifts") (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) # Tests M = Iₙ diff --git a/src/cgls.jl b/src/cgls.jl index 3dda31258..bad84fd5c 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -106,6 +106,7 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CGLS: system of %d equations in %d variables\n", m, n) diff --git a/src/cgne.jl b/src/cgne.jl index c414b699c..ac005906d 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -114,6 +114,7 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CGNE: system of %d equations in %d variables\n", m, n) diff --git a/src/cgs.jl b/src/cgs.jl index 49de75f25..db27b08c4 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -114,6 +114,7 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CGS: system of size %d\n", n) diff --git a/src/cr.jl b/src/cr.jl index f1e0235c1..3063a428f 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -109,6 +109,7 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CR: system of %d equations in %d variables\n", n, n) diff --git a/src/craig.jl b/src/craig.jl index b948d61b1..4cefa3fc4 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -149,6 +149,7 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CRAIG: system of %d equations in %d variables\n", m, n) diff --git a/src/craigmr.jl b/src/craigmr.jl index f6899c4f8..00833251f 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -139,6 +139,7 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CRAIGMR: system of %d equations in %d variables\n", m, n) diff --git a/src/crls.jl b/src/crls.jl index e21274ddc..e204b5f79 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -97,6 +97,7 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CRLS: system of %d equations in %d variables\n", m, n) diff --git a/src/crmr.jl b/src/crmr.jl index 6f603b953..56224ca1a 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -112,6 +112,7 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "CRMR: system of %d equations in %d variables\n", m, n) diff --git a/src/diom.jl b/src/diom.jl index c15e548fa..a65054296 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -109,6 +109,7 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "DIOM: system of size %d\n", n) diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 762a54da2..5a2bd1b2e 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -109,6 +109,7 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "DQGMRES: system of size %d\n", n) diff --git a/src/fgmres.jl b/src/fgmres.jl index 995ea09b3..4ccea2b2c 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -111,6 +111,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "FGMRES: system of size %d\n", n) diff --git a/src/fom.jl b/src/fom.jl index 50ca5c4be..8aa5d0be7 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -104,6 +104,7 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "FOM: system of size %d\n", n) diff --git a/src/gmres.jl b/src/gmres.jl index 8cf0384f1..f6455f386 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -104,6 +104,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "GMRES: system of size %d\n", n) diff --git a/src/gpmr.jl b/src/gpmr.jl index e98e5e432..3cc234700 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -144,6 +144,7 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: m, n = size(A) s, t = size(B) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == t || error("Inconsistent problem size") s == n || error("Inconsistent problem size") length(b) == m || error("Inconsistent problem size") diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 2e013c50b..3582c6c52 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -295,6 +295,7 @@ may be used in order to create these vectors. mutable struct CgLanczosShiftSolver{T,FC,S} <: KrylovSolver{T,FC,S} m :: Int n :: Int + nshifts :: Int Mv :: S Mv_prev :: S Mv_next :: S @@ -329,7 +330,7 @@ function CgLanczosShiftSolver(m, n, nshifts, S) converged = BitVector(undef, nshifts) not_cv = BitVector(undef, nshifts) stats = LanczosShiftStats(0, false, Vector{T}[T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") - solver = CgLanczosShiftSolver{T,FC,S}(m, n, Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) + solver = CgLanczosShiftSolver{T,FC,S}(m, n, nshifts, Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) return solver end diff --git a/src/lnlq.jl b/src/lnlq.jl index b84fe21d9..e0b5bfc97 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -145,6 +145,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "LNLQ: system of %d equations in %d variables\n", m, n) diff --git a/src/lslq.jl b/src/lslq.jl index df47de091..62f16c251 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -170,6 +170,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback=solver->false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "LSLQ: system of %d equations in %d variables\n", m, n) diff --git a/src/lsmr.jl b/src/lsmr.jl index d49c8e4f7..f1b1f52c3 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -147,6 +147,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "LSMR: system of %d equations in %d variables\n", m, n) diff --git a/src/lsqr.jl b/src/lsqr.jl index 20129c166..bfc6b09c8 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -143,6 +143,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "LSQR: system of %d equations in %d variables\n", m, n) diff --git a/src/minres.jl b/src/minres.jl index d2c79bd04..06864d325 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -127,6 +127,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "MINRES: system of size %d\n", n) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 7b88b437c..3fda13f5b 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -109,6 +109,7 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "MINRES-QLP: system of size %d\n", n) diff --git a/src/qmr.jl b/src/qmr.jl index 3f491b765..50499d761 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -106,6 +106,7 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "QMR: system of size %d\n", n) diff --git a/src/symmlq.jl b/src/symmlq.jl index 2d1f097a7..22288a50d 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -112,6 +112,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == m || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "SYMMLQ: system of size %d\n", n) diff --git a/src/tricg.jl b/src/tricg.jl index 0f55f2a54..43bcead47 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -131,6 +131,7 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "TriCG: system of %d equations in %d variables\n", m+n, m+n) diff --git a/src/trilqr.jl b/src/trilqr.jl index 0d57e1555..80eea3c15 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -104,6 +104,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "TRILQR: primal system of %d equations in %d variables\n", m, n) diff --git a/src/trimr.jl b/src/trimr.jl index c6cf83208..353e5b29a 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -131,6 +131,7 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "TriMR: system of %d equations in %d variables\n", m+n, m+n) diff --git a/src/usymlq.jl b/src/usymlq.jl index d3eeace65..3f7baf9da 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -113,6 +113,7 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "USYMLQ: system of %d equations in %d variables\n", m, n) diff --git a/src/usymqr.jl b/src/usymqr.jl index 270d4d085..eb545718a 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -111,6 +111,7 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") length(c) == n || error("Inconsistent problem size") (verbose > 0) && @printf(iostream, "USYMQR: system of %d equations in %d variables\n", m, n) From 7fa1b0601ae0965dff96997e9929e9c36031c823 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 12 Feb 2023 19:07:26 -0500 Subject: [PATCH 133/182] Add tests for compatibility between KrylovSolvers and the dimension of the linear problems --- test/test_solvers.jl | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 2c98dc795..605085007 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -50,6 +50,33 @@ function test_solvers(FC) $solvers[:cg_lanczos_shift] = $(KRYLOV_SOLVERS[:cg_lanczos_shift])($n, $n, $nshifts, $S) end + @testset "Check compatibility between KrylovSolvers and the dimension of the linear problems" begin + A2 = FC.(get_div_grad(2, 2, 2)) + n2 = size(A2, 1) + m2 = div(n2, 2) + Au2 = A2[1:m2,:] + Ao2 = A2[:,1:m2] + b2 = Ao2 * ones(FC, m2) + c2 = Au2 * ones(FC, n2) + shifts2 = [1.0; 2.0; 3.0; 4.0; 5.0; 6.0] + T = real(FC) + S = Vector{FC} + for (method, solver) in solvers + if method ∈ (:cg, :cr, :symmlq, :minres, :minres_qlp, :cg_lanczos, :diom, :fom, :dqgmres, :gmres, :fgmres, :cgs, :bicgstab, :bilq, :qmr) + @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)" solve!(solver, A2, b2) + end + method == :cg_lanczos_shift && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)" solve!(solver, A2, b2, shifts2) + method == :cg_lanczos_shift && @test_throws "solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $(length(shifts2))" solve!(solver, A, b, shifts2) + method ∈ (:cgne, :crmr, :lnlq, :craig, :craigmr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m2, $n2)" solve!(solver, Au2, c2) + method ∈ (:cgls, :crls, :lslq, :lsqr, :lsmr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, b2) + method ∈ (:bilqr, :trilqr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)" solve!(solver, A2, b2, b2) + method == :gpmr && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, Au2, b2, c2) + method ∈ (:tricg, :trimr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, b2, c2) + method == :usymlq && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m2, $n2)" solve!(solver, Au2, c2, b2) + method == :usymqr && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, b2, c2) + end + end + for (method, solver) in solvers @testset "$(method)" begin for i = 1 : 3 From 49bdbec5ac27e98e12b42b5ae20aa95b09e91be6 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 12 Feb 2023 19:40:46 -0500 Subject: [PATCH 134/182] Update solver tests for Julia 1.6 --- test/test_solvers.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 605085007..1daee0cac 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -63,17 +63,17 @@ function test_solvers(FC) S = Vector{FC} for (method, solver) in solvers if method ∈ (:cg, :cr, :symmlq, :minres, :minres_qlp, :cg_lanczos, :diom, :fom, :dqgmres, :gmres, :fgmres, :cgs, :bicgstab, :bilq, :qmr) - @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)" solve!(solver, A2, b2) + @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)") solve!(solver, A2, b2) end - method == :cg_lanczos_shift && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)" solve!(solver, A2, b2, shifts2) - method == :cg_lanczos_shift && @test_throws "solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $(length(shifts2))" solve!(solver, A, b, shifts2) - method ∈ (:cgne, :crmr, :lnlq, :craig, :craigmr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m2, $n2)" solve!(solver, Au2, c2) - method ∈ (:cgls, :crls, :lslq, :lsqr, :lsmr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, b2) - method ∈ (:bilqr, :trilqr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)" solve!(solver, A2, b2, b2) - method == :gpmr && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, Au2, b2, c2) - method ∈ (:tricg, :trimr) && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, b2, c2) - method == :usymlq && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m2, $n2)" solve!(solver, Au2, c2, b2) - method == :usymqr && @test_throws "(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)" solve!(solver, Ao2, b2, c2) + method == :cg_lanczos_shift && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)") solve!(solver, A2, b2, shifts2) + method == :cg_lanczos_shift && @test_throws ErrorException("solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $(length(shifts2))") solve!(solver, A, b, shifts2) + method ∈ (:cgne, :crmr, :lnlq, :craig, :craigmr) && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m2, $n2)") solve!(solver, Au2, c2) + method ∈ (:cgls, :crls, :lslq, :lsqr, :lsmr) && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)") solve!(solver, Ao2, b2) + method ∈ (:bilqr, :trilqr) && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)") solve!(solver, A2, b2, b2) + method == :gpmr && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)") solve!(solver, Ao2, Au2, b2, c2) + method ∈ (:tricg, :trimr) && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)") solve!(solver, Ao2, b2, c2) + method == :usymlq && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m2, $n2)") solve!(solver, Au2, c2, b2) + method == :usymqr && @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $m2)") solve!(solver, Ao2, b2, c2) end end From 407cbaec75b6e1522963b8908e7d888aa1f879ff Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 16 Feb 2023 12:33:51 -0500 Subject: [PATCH 135/182] [documentation] Update the page related to the GPU support --- docs/src/gpu.md | 63 ++++++++++++++++++++++++++++------------------ test/gpu/nvidia.jl | 42 ++++++++++++++++--------------- 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 378f4f5d3..4b93fcffd 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -56,33 +56,38 @@ using SparseArrays, Krylov, LinearOperators using CUDA, CUDA.CUSPARSE # Transfer the linear system from the CPU to the GPU -A_gpu = CuSparseMatrixCSC(A_cpu) # A_gpu = CuSparseMatrixCSR(A_cpu) +A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) -# LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices +# Incomplete decomposition LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ic02(A_gpu, 'O') +# Additional vector required for solving triangular systems +n = length(b_gpu) +T = eltype(b_gpu) +z = similar(CuVector{T}, n) + # Solve Py = x -function ldiv_ic0!(y, P, x) - copyto!(y, x) # Variant for CuSparseMatrixCSR - sv2!('T', 'U', 'N', 1.0, P, y, 'O') # sv2!('N', 'L', 'N', 1.0, P, y, 'O') - sv2!('N', 'U', 'N', 1.0, P, y, 'O') # sv2!('T', 'L', 'N', 1.0, P, y, 'O') +function ldiv_ic0!(P::CuSparseMatrixCSR, x, y, z) + ldiv!(z, LowerTriangular(P), x) # Forward substitution with L + ldiv!(y, LowerTriangular(P)', z) # Backward substitution with Lᴴ + return y +end + +function ldiv_ic0!(P::CuSparseMatrixCSC, x, y, z) + ldiv!(z, UpperTriangular(P)', x) # Forward substitution with L + ldiv!(y, UpperTriangular(P), z) # Backward substitution with Lᴴ return y end # Operator that model P⁻¹ -n = length(b_gpu) -T = eltype(b_gpu) symmetric = hermitian = true -opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(y, P, x)) +opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) -# Solve a symmetric positive definite system with an incomplete Cholesky preconditioner on GPU +# Solve an Hermitian positive definite system with an incomplete Cholesky preconditioner on GPU x, stats = cg(A_gpu, b_gpu, M=opM) ``` -!!! note - You need to replace `'T'` by `'C'` in `ldiv_ic0!` if `A_gpu` is a complex matrix. - ### Example with a general square system ```julia @@ -96,27 +101,35 @@ A_cpu = A_cpu[p,:] b_cpu = b_cpu[p] # Transfer the linear system from the CPU to the GPU -A_gpu = CuSparseMatrixCSC(A_cpu) # A_gpu = CuSparseMatrixCSR(A_cpu) +A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) -# LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices +# Incomplete decomposition LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ilu02(A_gpu, 'O') +# Additional vector required for solving triangular systems +n = length(b_gpu) +T = eltype(b_gpu) +z = similar(CuVector{T}, n) + # Solve Py = x -function ldiv_ilu0!(y, P, x) - copyto!(y, x) # Variant for CuSparseMatrixCSR - sv2!('N', 'L', 'N', 1.0, P, y, 'O') # sv2!('N', 'L', 'U', 1.0, P, y, 'O') - sv2!('N', 'U', 'U', 1.0, P, y, 'O') # sv2!('N', 'U', 'N', 1.0, P, y, 'O') +function ldiv_ilu0!(P::CuSparseMatrixCSR, x, y, z) + ldiv!(z, UnitLowerTriangular(P), x) # Forward substitution with L + ldiv!(y, UpperTriangular(P), z) # Backward substitution with U + return y +end + +function ldiv_ilu0!(P::CuSparseMatrixCSC, x, y, z) + ldiv!(z, LowerTriangular(P), x) # Forward substitution with L + ldiv!(y, UnitUpperTriangular(P), z) # Backward substitution with U return y end # Operator that model P⁻¹ -n = length(b_gpu) -T = eltype(b_gpu) symmetric = hermitian = false -opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(y, P, x)) +opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) -# Solve an unsymmetric system with an incomplete LU preconditioner on GPU +# Solve a non-Hermitian system with an incomplete LU preconditioner on GPU x, stats = bicgstab(A_gpu, b_gpu, M=opM) ``` @@ -167,8 +180,8 @@ b_gpu = oneVector(b_cpu) x, stats = lsqr(A_gpu, b_gpu) ``` -!!! warning - The library `oneMKL` is not interfaced yet in oneAPI.jl and all BLAS routines (dot, norm, mul!, etc.) dispatch to generic fallbacks. +!!! note + The library `oneMKL` is interfaced in oneAPI.jl and accelerate linear algebra operations on Intel GPUs. Only dense linear systems are supported because sparse linear algebra routines are not interfaced yet. ## Apple M1 GPUs diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 908a2819c..4a2428b82 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -26,31 +26,32 @@ include("gpu.jl") b_gpu = CuVector(b_cpu) n = length(b_gpu) T = eltype(b_gpu) + z = similar(CuVector{T}, n) symmetric = hermitian = true A_gpu = CuSparseMatrixCSC(A_cpu) P = ic02(A_gpu, 'O') - function ldiv_csc_ic0!(y, P, x) - copyto!(y, x) - sv2!('T', 'U', 'N', 1.0, P, y, 'O') - sv2!('N', 'U', 'N', 1.0, P, y, 'O') + function ldiv_ic0!(P::CuSparseMatrixCSC, x, y, z) + ldiv!(z, UpperTriangular(P)', x) + ldiv!(y, UpperTriangular(P), z) return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csc_ic0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) x, stats = cg(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + @test stats.niter ≤ 38 A_gpu = CuSparseMatrixCSR(A_cpu) P = ic02(A_gpu, 'O') - function ldiv_csr_ic0!(y, P, x) - copyto!(y, x) - sv2!('N', 'L', 'N', 1.0, P, y, 'O') - sv2!('T', 'L', 'N', 1.0, P, y, 'O') + function ldiv_ic0!(P::CuSparseMatrixCSR, x, y, z) + ldiv!(z, LowerTriangular(P), x) + ldiv!(y, LowerTriangular(P)', z) return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csr_ic0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) x, stats = cg(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + @test stats.niter ≤ 38 end @testset "ilu0" begin @@ -64,31 +65,32 @@ include("gpu.jl") b_gpu = CuVector(b_cpu) n = length(b_gpu) T = eltype(b_gpu) + z = similar(CuVector{T}, n) symmetric = hermitian = false A_gpu = CuSparseMatrixCSC(A_cpu) P = ilu02(A_gpu, 'O') - function ldiv_csc_ilu0!(y, P, x) - copyto!(y, x) - sv2!('N', 'L', 'N', 1.0, P, y, 'O') - sv2!('N', 'U', 'U', 1.0, P, y, 'O') + function ldiv_ilu0!(P::CuSparseMatrixCSC, x, y, z) + ldiv!(z, LowerTriangular(P), x) + ldiv!(y, UnitUpperTriangular(P), z) return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csc_ilu0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) x, stats = bicgstab(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + @test stats.niter ≤ 1659 A_gpu = CuSparseMatrixCSR(A_cpu) P = ilu02(A_gpu, 'O') - function ldiv_csr_ilu0!(y, P, x) - copyto!(y, x) - sv2!('N', 'L', 'U', 1.0, P, y, 'O') - sv2!('N', 'U', 'N', 1.0, P, y, 'O') + function ldiv_ilu0!(P::CuSparseMatrixCSR, x, y, z) + ldiv!(z, UnitLowerTriangular(P), x) + ldiv!(y, UpperTriangular(P), z) return y end - opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_csr_ilu0!(y, P, x)) + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) x, stats = bicgstab(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 + @test stats.niter ≤ 1659 end end From f2410b8a303223015653c4fcbbb5d012608ec318 Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:40:57 -0500 Subject: [PATCH 136/182] Update docs/src/gpu.md Co-authored-by: Paul Raynaud --- docs/src/gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 4b93fcffd..3ed5178e3 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -181,7 +181,7 @@ x, stats = lsqr(A_gpu, b_gpu) ``` !!! note - The library `oneMKL` is interfaced in oneAPI.jl and accelerate linear algebra operations on Intel GPUs. Only dense linear systems are supported because sparse linear algebra routines are not interfaced yet. + The library `oneMKL` is interfaced in oneAPI.jl and accelerates linear algebra operations on Intel GPUs. Only dense linear systems are supported for the time being because sparse linear algebra routines are not interfaced yet. ## Apple M1 GPUs From 1cf4063a6c09befef15d0cc59eb061e87e778094 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 6 Mar 2023 15:16:11 -0500 Subject: [PATCH 137/182] Update LICENSE.md --- LICENSE.md | 136 ++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 1533671ce..befba1c4d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ -Copyright (c) 2015-2019: Dominique Orban +Copyright (c) 2015-present: Alexis Montoison, Dominique Orban, and other contributors -Krylov.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2.0/). +[Krylov.jl](https://github.com/JuliaSmoothOptimizers/Krylov.jl) is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2.0/). ## License @@ -11,83 +11,83 @@ Krylov.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2. -------------- 1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. 1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" - means Covered Software of a particular Contributor. + means Covered Software of a particular Contributor. 1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. 1.5. "Incompatible With Secondary Licenses" - means + means - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. 1.6. "Executable Form" - means any form of the work other than Source Code Form. + means any form of the work other than Source Code Form. 1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. 1.8. "License" - means this document. + means this document. 1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. 1.10. "Modifications" - means any of the following: + means any of the following: - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or - (b) any new file in Source Code Form that contains any Covered - Software. + (b) any new file in Source Code Form that contains any Covered + Software. 1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. 1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. 1.13. "Source Code Form" - means the form of the work preferred for making modifications. + means the form of the work preferred for making modifications. 1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. 2. License Grants and Conditions -------------------------------- @@ -98,14 +98,14 @@ Krylov.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2. non-exclusive license: (a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. 2.2. Effective Date @@ -122,15 +122,15 @@ Krylov.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2. Contributor: (a) for any code that a Contributor has removed from Covered Software; - or + or (b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or (c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. + its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with @@ -178,15 +178,15 @@ Krylov.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2. If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work @@ -363,7 +363,7 @@ Krylov.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. + file, You can obtain one at https://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE From fe3047da594b8576d600d846a48b96c2a069d87f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 6 Mar 2023 17:38:54 -0500 Subject: [PATCH 138/182] [documentation] Update the GPU section --- docs/src/gpu.md | 214 +++++++++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 101 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 3ed5178e3..4415fba97 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -14,16 +14,18 @@ Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to ```julia using CUDA, Krylov -# CPU Arrays -A_cpu = rand(20, 20) -b_cpu = rand(20) +if CUDA.functional() + # CPU Arrays + A_cpu = rand(20, 20) + b_cpu = rand(20) -# GPU Arrays -A_gpu = CuMatrix(A_cpu) -b_gpu = CuVector(b_cpu) + # GPU Arrays + A_gpu = CuMatrix(A_cpu) + b_gpu = CuVector(b_cpu) -# Solve a square and dense system on an Nivida GPU -x, stats = bilq(A_gpu, b_gpu) + # Solve a square and dense system on an Nivida GPU + x, stats = bilq(A_gpu, b_gpu) +end ``` Sparse matrices have a specific storage on Nvidia GPUs (`CuSparseMatrixCSC`, `CuSparseMatrixCSR` or `CuSparseMatrixCOO`): @@ -32,16 +34,18 @@ Sparse matrices have a specific storage on Nvidia GPUs (`CuSparseMatrixCSC`, `Cu using CUDA, Krylov using CUDA.CUSPARSE, SparseArrays -# CPU Arrays -A_cpu = sprand(200, 100, 0.3) -b_cpu = rand(200) +if CUDA.functional() + # CPU Arrays + A_cpu = sprand(200, 100, 0.3) + b_cpu = rand(200) -# GPU Arrays -A_gpu = CuSparseMatrixCSC(A_cpu) -b_gpu = CuVector(b_cpu) + # GPU Arrays + A_gpu = CuSparseMatrixCSC(A_cpu) + b_gpu = CuVector(b_cpu) -# Solve a rectangular and sparse system on an Nvidia GPU -x, stats = lsmr(A_gpu, b_gpu) + # Solve a rectangular and sparse system on an Nvidia GPU + x, stats = lsmr(A_gpu, b_gpu) +end ``` Optimized operator-vector products that exploit GPU features can be also used by means of linear operators. @@ -55,37 +59,39 @@ can be applied directly on GPU thanks to efficient operators that take advantage using SparseArrays, Krylov, LinearOperators using CUDA, CUDA.CUSPARSE -# Transfer the linear system from the CPU to the GPU -A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) -b_gpu = CuVector(b_cpu) - -# Incomplete decomposition LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices -P = ic02(A_gpu, 'O') - -# Additional vector required for solving triangular systems -n = length(b_gpu) -T = eltype(b_gpu) -z = similar(CuVector{T}, n) - -# Solve Py = x -function ldiv_ic0!(P::CuSparseMatrixCSR, x, y, z) - ldiv!(z, LowerTriangular(P), x) # Forward substitution with L - ldiv!(y, LowerTriangular(P)', z) # Backward substitution with Lᴴ - return y +if CUDA.functional() + # Transfer the linear system from the CPU to the GPU + A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) + b_gpu = CuVector(b_cpu) + + # Incomplete decomposition LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices + P = ic02(A_gpu, 'O') + + # Additional vector required for solving triangular systems + n = length(b_gpu) + T = eltype(b_gpu) + z = similar(CuVector{T}, n) + + # Solve Py = x + function ldiv_ic0!(P::CuSparseMatrixCSR, x, y, z) + ldiv!(z, LowerTriangular(P), x) # Forward substitution with L + ldiv!(y, LowerTriangular(P)', z) # Backward substitution with Lᴴ + return y + end + + function ldiv_ic0!(P::CuSparseMatrixCSC, x, y, z) + ldiv!(z, UpperTriangular(P)', x) # Forward substitution with L + ldiv!(y, UpperTriangular(P), z) # Backward substitution with Lᴴ + return y + end + + # Operator that model P⁻¹ + symmetric = hermitian = true + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) + + # Solve an Hermitian positive definite system with an incomplete Cholesky preconditioner on GPU + x, stats = cg(A_gpu, b_gpu, M=opM) end - -function ldiv_ic0!(P::CuSparseMatrixCSC, x, y, z) - ldiv!(z, UpperTriangular(P)', x) # Forward substitution with L - ldiv!(y, UpperTriangular(P), z) # Backward substitution with Lᴴ - return y -end - -# Operator that model P⁻¹ -symmetric = hermitian = true -opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) - -# Solve an Hermitian positive definite system with an incomplete Cholesky preconditioner on GPU -x, stats = cg(A_gpu, b_gpu, M=opM) ``` ### Example with a general square system @@ -94,43 +100,45 @@ x, stats = cg(A_gpu, b_gpu, M=opM) using SparseArrays, Krylov, LinearOperators using CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER -# Optional -- Compute a permutation vector p such that A[p,:] has no zero diagonal -p = zfd(A_cpu, 'O') -p .+= 1 -A_cpu = A_cpu[p,:] -b_cpu = b_cpu[p] - -# Transfer the linear system from the CPU to the GPU -A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) -b_gpu = CuVector(b_cpu) - -# Incomplete decomposition LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices -P = ilu02(A_gpu, 'O') - -# Additional vector required for solving triangular systems -n = length(b_gpu) -T = eltype(b_gpu) -z = similar(CuVector{T}, n) - -# Solve Py = x -function ldiv_ilu0!(P::CuSparseMatrixCSR, x, y, z) - ldiv!(z, UnitLowerTriangular(P), x) # Forward substitution with L - ldiv!(y, UpperTriangular(P), z) # Backward substitution with U - return y -end - -function ldiv_ilu0!(P::CuSparseMatrixCSC, x, y, z) - ldiv!(z, LowerTriangular(P), x) # Forward substitution with L - ldiv!(y, UnitUpperTriangular(P), z) # Backward substitution with U - return y +if CUDA.functional() + # Optional -- Compute a permutation vector p such that A[p,:] has no zero diagonal + p = zfd(A_cpu, 'O') + p .+= 1 + A_cpu = A_cpu[p,:] + b_cpu = b_cpu[p] + + # Transfer the linear system from the CPU to the GPU + A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) + b_gpu = CuVector(b_cpu) + + # Incomplete decomposition LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices + P = ilu02(A_gpu, 'O') + + # Additional vector required for solving triangular systems + n = length(b_gpu) + T = eltype(b_gpu) + z = similar(CuVector{T}, n) + + # Solve Py = x + function ldiv_ilu0!(P::CuSparseMatrixCSR, x, y, z) + ldiv!(z, UnitLowerTriangular(P), x) # Forward substitution with L + ldiv!(y, UpperTriangular(P), z) # Backward substitution with U + return y + end + + function ldiv_ilu0!(P::CuSparseMatrixCSC, x, y, z) + ldiv!(z, LowerTriangular(P), x) # Forward substitution with L + ldiv!(y, UnitUpperTriangular(P), z) # Backward substitution with U + return y + end + + # Operator that model P⁻¹ + symmetric = hermitian = false + opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) + + # Solve a non-Hermitian system with an incomplete LU preconditioner on GPU + x, stats = bicgstab(A_gpu, b_gpu, M=opM) end - -# Operator that model P⁻¹ -symmetric = hermitian = false -opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) - -# Solve a non-Hermitian system with an incomplete LU preconditioner on GPU -x, stats = bicgstab(A_gpu, b_gpu, M=opM) ``` ## AMD GPUs @@ -141,16 +149,18 @@ Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to ```julia using Krylov, AMDGPU -# CPU Arrays -A_cpu = rand(ComplexF64, 20, 20) -A_cpu = A_cpu + A_cpu' -b_cpu = rand(ComplexF64, 20) +if AMDGPU.functional() + # CPU Arrays + A_cpu = rand(ComplexF64, 20, 20) + A_cpu = A_cpu + A_cpu' + b_cpu = rand(ComplexF64, 20) -A_gpu = ROCMatrix(A_cpu) -b_gpu = ROCVector(b_cpu) + A_gpu = ROCMatrix(A_cpu) + b_gpu = ROCVector(b_cpu) -# Solve a dense Hermitian system on an AMD GPU -x, stats = minres(A_gpu, b_gpu) + # Solve a dense Hermitian system on an AMD GPU + x, stats = minres(A_gpu, b_gpu) +end ``` !!! info @@ -164,20 +174,22 @@ Problems stored in CPU format (`Matrix` and `Vector`) must first be converted to ```julia using Krylov, oneAPI -T = Float32 # oneAPI.jl also works with ComplexF32 -m = 20 -n = 10 +if oneAPI.functional() + T = Float32 # oneAPI.jl also works with ComplexF32 + m = 20 + n = 10 -# CPU Arrays -A_cpu = rand(T, m, n) -b_cpu = rand(T, m) + # CPU Arrays + A_cpu = rand(T, m, n) + b_cpu = rand(T, m) -# GPU Arrays -A_gpu = oneMatrix(A_cpu) -b_gpu = oneVector(b_cpu) + # GPU Arrays + A_gpu = oneMatrix(A_cpu) + b_gpu = oneVector(b_cpu) -# Solve a dense least-squares problem on an Intel GPU -x, stats = lsqr(A_gpu, b_gpu) + # Solve a dense least-squares problem on an Intel GPU + x, stats = lsqr(A_gpu, b_gpu) +end ``` !!! note From 886200f0f2373c4d32423de1b7215bd3138b1eb3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 6 Mar 2023 18:22:38 -0500 Subject: [PATCH 139/182] [documentation] Use ILU(0) and IC(0) in the comments --- docs/src/gpu.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 4415fba97..8e95eb84f 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -64,7 +64,7 @@ if CUDA.functional() A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) - # Incomplete decomposition LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices + # IC(0) decomposition LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ic02(A_gpu, 'O') # Additional vector required for solving triangular systems @@ -89,7 +89,7 @@ if CUDA.functional() symmetric = hermitian = true opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) - # Solve an Hermitian positive definite system with an incomplete Cholesky preconditioner on GPU + # Solve an Hermitian positive definite system with an IC(0) preconditioner on GPU x, stats = cg(A_gpu, b_gpu, M=opM) end ``` @@ -111,7 +111,7 @@ if CUDA.functional() A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) - # Incomplete decomposition LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices + # ILU(0) decomposition LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices P = ilu02(A_gpu, 'O') # Additional vector required for solving triangular systems @@ -136,7 +136,7 @@ if CUDA.functional() symmetric = hermitian = false opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) - # Solve a non-Hermitian system with an incomplete LU preconditioner on GPU + # Solve a non-Hermitian system with an ILU(0) preconditioner on GPU x, stats = bicgstab(A_gpu, b_gpu, M=opM) end ``` From aaf83e5ff87fdbd730af388299112415ceb6b504 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 10 Mar 2023 16:14:40 -0500 Subject: [PATCH 140/182] Update the reference of the GPMR paper --- src/gpmr.jl | 6 +++--- src/krylov_processes.jl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gpmr.jl b/src/gpmr.jl index 3cc234700..fec86250e 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -3,8 +3,8 @@ # This method is described in # # A. Montoison and D. Orban -# GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems -# Cahier du GERAD G-2021-62. +# GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems. +# SIAM Journal on Matrix Analysis and Applications, 44(1), pp. 293--311, 2023. # # Alexis Montoison, # Montréal, August 2021. @@ -97,7 +97,7 @@ GPMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + #### Reference -* A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://dx.doi.org/10.13140/RG.2.2.24069.68326), Cahier du GERAD G-2021-62, GERAD, Montréal, 2021. +* A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://doi.org/10.1137/21M1459265), SIAM Journal on Matrix Analysis and Applications, 44(1), pp. 293--311, 2023. """ function gpmr end diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index 2be66b1c5..5c9cad24d 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -397,7 +397,7 @@ end #### Reference -* A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://dx.doi.org/10.13140/RG.2.2.24069.68326), Cahier du GERAD G-2021-62, GERAD, Montréal, 2021. +* A. Montoison and D. Orban, [*GPMR: An Iterative Method for Unsymmetric Partitioned Linear Systems*](https://doi.org/10.1137/21M1459265), SIAM Journal on Matrix Analysis and Applications, 44(1), pp. 293--311, 2023. """ function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex m, n = size(A) From fcbf1894916506cc3e0bc07d04977bb75207ea67 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 24 Mar 2023 14:08:02 -0400 Subject: [PATCH 141/182] Fix the documentation for CUDA.jl 4.1 --- .buildkite/pipeline.yml | 2 ++ docs/src/gpu.md | 20 +++++++++++------ test/gpu/nvidia.jl | 49 ++++++++++++++++++++++++----------------- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 0943f7c21..6e28f623d 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -12,6 +12,8 @@ steps: Pkg.add("CUDA") Pkg.add("LinearOperators") Pkg.instantiate() + using CUDA + CUDA.set_runtime_version!(v"11.8") include("test/gpu/nvidia.jl")' timeout_in_minutes: 30 diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 8e95eb84f..3307790b1 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -65,7 +65,7 @@ if CUDA.functional() b_gpu = CuVector(b_cpu) # IC(0) decomposition LLᴴ ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices - P = ic02(A_gpu, 'O') + P = ic02(A_gpu) # Additional vector required for solving triangular systems n = length(b_gpu) @@ -101,18 +101,17 @@ using SparseArrays, Krylov, LinearOperators using CUDA, CUDA.CUSPARSE, CUDA.CUSOLVER if CUDA.functional() - # Optional -- Compute a permutation vector p such that A[p,:] has no zero diagonal - p = zfd(A_cpu, 'O') + # Optional -- Compute a permutation vector p such that A[:,p] has no zero diagonal + p = zfd(A_cpu) p .+= 1 - A_cpu = A_cpu[p,:] - b_cpu = b_cpu[p] + A_cpu = A_cpu[:,p] # Transfer the linear system from the CPU to the GPU A_gpu = CuSparseMatrixCSR(A_cpu) # A_gpu = CuSparseMatrixCSC(A_cpu) b_gpu = CuVector(b_cpu) # ILU(0) decomposition LU ≈ A for CuSparseMatrixCSC or CuSparseMatrixCSR matrices - P = ilu02(A_gpu, 'O') + P = ilu02(A_gpu) # Additional vector required for solving triangular systems n = length(b_gpu) @@ -137,10 +136,17 @@ if CUDA.functional() opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) # Solve a non-Hermitian system with an ILU(0) preconditioner on GPU - x, stats = bicgstab(A_gpu, b_gpu, M=opM) + x̄, stats = bicgstab(A_gpu, b_gpu, M=opM) + + # Recover the solution of Ax = b with the solution of A[:,p]x̄ = b + invp = invperm(p) + x = x̄[invp] end ``` +!!! note + The `CUSPARSE` library of CUDA toolkits `v"12.x"` has some bugs. We recommend to use an older CUDA toolkit with `CUDA.set_runtime_version!(v"11.8")`. + ## AMD GPUs All solvers in Krylov.jl can be used with [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) and allow computations on AMD GPUs. diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 4a2428b82..b03dcd6ab 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -22,6 +22,7 @@ include("gpu.jl") @testset "ic0" begin A_cpu, b_cpu = sparse_laplacian() + @test mapreduce(Aᵢᵢ -> Aᵢᵢ != 0, &, diag(A_cpu)) == true b_gpu = CuVector(b_cpu) n = length(b_gpu) @@ -30,7 +31,7 @@ include("gpu.jl") symmetric = hermitian = true A_gpu = CuSparseMatrixCSC(A_cpu) - P = ic02(A_gpu, 'O') + P = ic02(A_gpu) function ldiv_ic0!(P::CuSparseMatrixCSC, x, y, z) ldiv!(z, UpperTriangular(P)', x) ldiv!(y, UpperTriangular(P), z) @@ -39,10 +40,10 @@ include("gpu.jl") opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) x, stats = cg(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 - @test stats.niter ≤ 38 + @test stats.niter ≤ 19 - A_gpu = CuSparseMatrixCSR(A_cpu) - P = ic02(A_gpu, 'O') + A_gpu = CuSparseMatrixCSR(A_gpu) + P = ic02(A_gpu) function ldiv_ic0!(P::CuSparseMatrixCSR, x, y, z) ldiv!(z, LowerTriangular(P), x) ldiv!(y, LowerTriangular(P)', z) @@ -51,16 +52,22 @@ include("gpu.jl") opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ic0!(P, x, y, z)) x, stats = cg(A_gpu, b_gpu, M=opM) @test norm(b_gpu - A_gpu * x) ≤ 1e-6 - @test stats.niter ≤ 38 + @test stats.niter ≤ 19 end @testset "ilu0" begin - A_cpu, b_cpu = polar_poisson() - - p = zfd(A_cpu, 'O') + A_cpu = Float64[1 0 0 4; + 0 0 7 8; + 9 0 0 12; + 0 14 0 16] + A_cpu = sparse(A_cpu) + b_cpu = ones(4) + @test mapreduce(Aᵢᵢ -> Aᵢᵢ != 0, &, diag(A_cpu)) == false + + p = zfd(A_cpu) p .+= 1 - A_cpu = A_cpu[p,:] - b_cpu = b_cpu[p] + invp = invperm(p) + @test reduce(&, invp .== p) == false b_gpu = CuVector(b_cpu) n = length(b_gpu) @@ -68,29 +75,31 @@ include("gpu.jl") z = similar(CuVector{T}, n) symmetric = hermitian = false - A_gpu = CuSparseMatrixCSC(A_cpu) - P = ilu02(A_gpu, 'O') + A_gpu = CuSparseMatrixCSC(A_cpu[:,p]) + P = ilu02(A_gpu) function ldiv_ilu0!(P::CuSparseMatrixCSC, x, y, z) ldiv!(z, LowerTriangular(P), x) ldiv!(y, UnitUpperTriangular(P), z) return y end opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) - x, stats = bicgstab(A_gpu, b_gpu, M=opM) - @test norm(b_gpu - A_gpu * x) ≤ 1e-6 - @test stats.niter ≤ 1659 + x̄, stats = gmres(A_gpu, b_gpu, M=opM) + x = Vector(x̄)[invp] + @test norm(b_gpu - A_gpu * x̄) ≤ 1e-6 + @test norm(b_cpu - A_cpu * x) ≤ 1e-6 - A_gpu = CuSparseMatrixCSR(A_cpu) - P = ilu02(A_gpu, 'O') + A_gpu = CuSparseMatrixCSR(A_cpu[:,p]) + P = ilu02(A_gpu) function ldiv_ilu0!(P::CuSparseMatrixCSR, x, y, z) ldiv!(z, UnitLowerTriangular(P), x) ldiv!(y, UpperTriangular(P), z) return y end opM = LinearOperator(T, n, n, symmetric, hermitian, (y, x) -> ldiv_ilu0!(P, x, y, z)) - x, stats = bicgstab(A_gpu, b_gpu, M=opM) - @test norm(b_gpu - A_gpu * x) ≤ 1e-6 - @test stats.niter ≤ 1659 + x̄, stats = gmres(A_gpu, b_gpu, M=opM) + x = Vector(x̄)[invp] + @test norm(b_gpu - A_gpu * x̄) ≤ 1e-6 + @test norm(b_cpu - A_cpu * x) ≤ 1e-6 end end From f903b841456c382618c987bd663532bb198fe8bb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 24 Mar 2023 14:24:23 -0400 Subject: [PATCH 142/182] Update .buildkite again --- .buildkite/pipeline.yml | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 6e28f623d..3c8a8420e 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -8,13 +8,15 @@ steps: cuda: "*" command: | julia --color=yes --project -e ' - using Pkg - Pkg.add("CUDA") - Pkg.add("LinearOperators") - Pkg.instantiate() - using CUDA - CUDA.set_runtime_version!(v"11.8") - include("test/gpu/nvidia.jl")' + using Pkg + Pkg.add("CUDA") + Pkg.add("LinearOperators") + Pkg.instantiate() + using CUDA + CUDA.set_runtime_version!(v"11.8")' + + julia --color=yes --project -e ' + include("test/gpu/nvidia.jl")' timeout_in_minutes: 30 # - label: "AMD GPUs -- AMDGPU.jl" @@ -31,9 +33,9 @@ steps: # command: | # julia --color=yes --project -e ' # using Pkg - # Pkg.add("AMDGPU") - # Pkg.instantiate() - # include("test/gpu/amd.jl")' + # Pkg.add("AMDGPU") + # Pkg.instantiate() + # include("test/gpu/amd.jl")' # timeout_in_minutes: 30 - label: "Intel GPUs -- oneAPI.jl" @@ -45,10 +47,10 @@ steps: intel: "*" command: | julia --color=yes --project -e ' - using Pkg - Pkg.add("oneAPI") - Pkg.instantiate() - include("test/gpu/intel.jl")' + using Pkg + Pkg.add("oneAPI") + Pkg.instantiate() + include("test/gpu/intel.jl")' timeout_in_minutes: 30 - label: "Apple M1 GPUs -- Metal.jl" @@ -61,8 +63,8 @@ steps: arch: "aarch64" command: | julia --color=yes --project -e ' - using Pkg - Pkg.add("Metal") - Pkg.instantiate() - include("test/gpu/metal.jl")' + using Pkg + Pkg.add("Metal") + Pkg.instantiate() + include("test/gpu/metal.jl")' timeout_in_minutes: 30 From b6ed790c0e6bc045fe1eef02a922c8922d44e971 Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:51:17 -0400 Subject: [PATCH 143/182] Update docs/src/gpu.md Co-authored-by: tmigot --- docs/src/gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index 3307790b1..d88771748 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -145,7 +145,7 @@ end ``` !!! note - The `CUSPARSE` library of CUDA toolkits `v"12.x"` has some bugs. We recommend to use an older CUDA toolkit with `CUDA.set_runtime_version!(v"11.8")`. + The `CUSPARSE` library of CUDA toolkits `v"12.x"` is unstable. We recommend using CUDA toolkit v"11.8" with `CUDA.set_runtime_version!(v"11.8")`. ## AMD GPUs From d98a9279b3d8e536ad72c804a5ebfbd0892c5db6 Mon Sep 17 00:00:00 2001 From: Alexander Seiler Date: Wed, 29 Mar 2023 04:08:57 +0200 Subject: [PATCH 144/182] Fix some typos Signed-off-by: Alexander Seiler --- README.md | 6 +++--- docs/src/factorization-free.md | 4 ++-- docs/src/index.md | 6 +++--- docs/src/tips.md | 2 +- docs/src/warm-start.md | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 55476e684..57bcd1d81 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ should be solved when **_b_** is not in the range of **_A_** (inconsistent syste * **_A_** is square and singular, * **_A_** is tall and thin. -Underdetermined sytems are less common but also occur. +Underdetermined systems are less common but also occur. If there are infinitely many such **_x_** (because **_A_** is column rank-deficient), one with minimum norm is identified @@ -61,12 +61,12 @@ If there are infinitely many such **_x_** (because **_A_** is column rank-defici minimize ‖x‖   subject to   Ax = b

    -sould be solved when **_A_** is column rank-deficient but **_b_** is in the range of **_A_** (consistent systems), regardless of the shape of **_A_**. +should be solved when **_A_** is column rank-deficient but **_b_** is in the range of **_A_** (consistent systems), regardless of the shape of **_A_**. This situation mainly occurs when * **_A_** is square and singular, * **_A_** is short and wide. -Overdetermined sytems are less common but also occur. +Overdetermined systems are less common but also occur. 4. Adjoint systems diff --git a/docs/src/factorization-free.md b/docs/src/factorization-free.md index b97108b99..0bff49d4c 100644 --- a/docs/src/factorization-free.md +++ b/docs/src/factorization-free.md @@ -59,9 +59,9 @@ where * `type` is the operator element type; * `nrow` and `ncol` are its dimensions; * `symmetric` and `hermitian` should be set to `true` or `false`; -* `prod(y, v)`, `tprod(y, w)` and `ctprod(u, w)` are called when writing `mul!(y, A, v)`, `mul!(y, tranpose(A), w)`, and `mul!(y, A', u)`, respectively. +* `prod(y, v)`, `tprod(y, w)` and `ctprod(u, w)` are called when writing `mul!(y, A, v)`, `mul!(y, transpose(A), w)`, and `mul!(y, A', u)`, respectively. -See the [tutorial](https://juliasmoothoptimizers.github.io/tutorials/introduction-to-linear-operators/) and the detailed [documentation](https://juliasmoothoptimizers.github.io/LinearOperators.jl/dev/) for more informations on `LinearOperators.jl`. +See the [tutorial](https://juliasmoothoptimizers.github.io/tutorials/introduction-to-linear-operators/) and the detailed [documentation](https://juliasmoothoptimizers.github.io/LinearOperators.jl/dev/) for more information on `LinearOperators.jl`. ## Examples diff --git a/docs/src/index.md b/docs/src/index.md index 1a18e2315..1cc2c3302 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -22,7 +22,7 @@ should be solved when **_b_** is not in the range of **_A_** (inconsistent syste * **_A_** is square and singular, * **_A_** is tall and thin. -Underdetermined sytems are less common but also occur. +Underdetermined systems are less common but also occur. If there are infinitely many such **_x_** (because **_A_** is column rank-deficient), one with minimum norm is identified @@ -36,12 +36,12 @@ If there are infinitely many such **_x_** (because **_A_** is column rank-defici \min \|x\| \quad \text{subject to} \quad Ax = b ``` -sould be solved when **_A_** is column rank-deficient but **_b_** is in the range of **_A_** (consistent systems), regardless of the shape of **_A_**. +should be solved when **_A_** is column rank-deficient but **_b_** is in the range of **_A_** (consistent systems), regardless of the shape of **_A_**. This situation mainly occurs when * **_A_** is square and singular, * **_A_** is short and wide. -Overdetermined sytems are less common but also occur. +Overdetermined systems are less common but also occur. 4 - Adjoint systems diff --git a/docs/src/tips.md b/docs/src/tips.md index ca3d927bd..e08567ae1 100644 --- a/docs/src/tips.md +++ b/docs/src/tips.md @@ -16,7 +16,7 @@ If you don't know the maximum number of threads available on your computer, you NMAX = Sys.CPU_THREADS ``` -and define the number of OpenBLAS/MKL threads at runtine with +and define the number of OpenBLAS/MKL threads at runtime with ```julia BLAS.set_num_threads(N) # 1 ≤ N ≤ NMAX diff --git a/docs/src/warm-start.md b/docs/src/warm-start.md index d926db183..6b830bff3 100644 --- a/docs/src/warm-start.md +++ b/docs/src/warm-start.md @@ -58,8 +58,8 @@ y = y₀ + Δy ```@meta # ## Restarted methods # -# The storage requierements of Krylov methods based on the Arnoldi process, such as FOM and GMRES, increase as the iteration progresses. -# For very large problems, the storage costs become prohibitive after only few iterations and restarted variants FOM(k) and GMRES(k) are prefered. +# The storage requirements of Krylov methods based on the Arnoldi process, such as FOM and GMRES, increase as the iteration progresses. +# For very large problems, the storage costs become prohibitive after only few iterations and restarted variants FOM(k) and GMRES(k) are preferred. # In this section, we show how to use warm starts to implement GMRES(k) and FOM(k). # # ```julia From c1be2706d4a197fce9dca2a203bbd1797ebaa4c0 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 24 Mar 2023 17:57:34 -0400 Subject: [PATCH 145/182] Add a threshold for codecov --- .github/codecov.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..c3a812781 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + project: + default: + # Drops on the order 0.01% are typical even when no change occurs + # Having this threshold set a little higher (0.5%) than that makes it + # a little more tolerant to fluctuations + threshold: 0.5% From 19542fa207c2579cb618942e5651375facdccac4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 31 Mar 2023 00:28:39 -0400 Subject: [PATCH 146/182] [documentation] Use CUDA.zeros instead of similar --- docs/src/gpu.md | 4 ++-- test/gpu/nvidia.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index d88771748..b03f8f5e3 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -70,7 +70,7 @@ if CUDA.functional() # Additional vector required for solving triangular systems n = length(b_gpu) T = eltype(b_gpu) - z = similar(CuVector{T}, n) + z = CUDA.zeros(T, n) # Solve Py = x function ldiv_ic0!(P::CuSparseMatrixCSR, x, y, z) @@ -116,7 +116,7 @@ if CUDA.functional() # Additional vector required for solving triangular systems n = length(b_gpu) T = eltype(b_gpu) - z = similar(CuVector{T}, n) + z = CUDA.zeros(T, n) # Solve Py = x function ldiv_ilu0!(P::CuSparseMatrixCSR, x, y, z) diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index b03dcd6ab..8cb44136d 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -27,7 +27,7 @@ include("gpu.jl") b_gpu = CuVector(b_cpu) n = length(b_gpu) T = eltype(b_gpu) - z = similar(CuVector{T}, n) + z = CUDA.zeros(T, n) symmetric = hermitian = true A_gpu = CuSparseMatrixCSC(A_cpu) @@ -72,7 +72,7 @@ include("gpu.jl") b_gpu = CuVector(b_cpu) n = length(b_gpu) T = eltype(b_gpu) - z = similar(CuVector{T}, n) + z = CUDA.zeros(T, n) symmetric = hermitian = false A_gpu = CuSparseMatrixCSC(A_cpu[:,p]) From 73a4152ccdfa8e92126fc245720e38a10c3998bb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 1 May 2023 17:42:37 -0400 Subject: [PATCH 147/182] Add a reference for CR --- src/cr.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cr.jl b/src/cr.jl index 3063a428f..744083d5b 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -6,6 +6,9 @@ # E. Stiefel, Relaxationsmethoden bester Strategie zur Losung linearer Gleichungssysteme. # Commentarii Mathematici Helvetici, 29(1), pp. 157--179, 1955. # +# D. G. Luenberger, The conjugate residual method for constrained minimization problems. +# SIAM Journal on Numerical Analysis, 7(3), pp. 390--398, 1970. +# # M-A. Dahito and D. Orban, The Conjugate Residual Method in Linesearch and Trust-Region Methods. # SIAM Journal on Optimization, 29(3), pp. 1988--2025, 2019. # @@ -67,6 +70,7 @@ M also indicates the weighted norm in which residuals are measured. * M. R. Hestenes and E. Stiefel, [*Methods of conjugate gradients for solving linear systems*](https://doi.org/10.6028/jres.049.044), Journal of Research of the National Bureau of Standards, 49(6), pp. 409--436, 1952. * E. Stiefel, [*Relaxationsmethoden bester Strategie zur Losung linearer Gleichungssysteme*](https://doi.org/10.1007/BF02564277), Commentarii Mathematici Helvetici, 29(1), pp. 157--179, 1955. +* D. G. Luenberger, [*The conjugate residual method for constrained minimization problems*](https://doi.org/10.1137/0707032), SIAM Journal on Numerical Analysis, 7(3), pp. 390--398, 1970. * M-A. Dahito and D. Orban, [*The Conjugate Residual Method in Linesearch and Trust-Region Methods*](https://doi.org/10.1137/18M1204255), SIAM Journal on Optimization, 29(3), pp. 1988--2025, 2019. """ function cr end From 07e28333988c2798b590534fd0fbb526d4a68cb7 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 7 May 2023 22:40:25 -0400 Subject: [PATCH 148/182] Test Krylov.jl with CUDA 12.1.1 --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 3c8a8420e..169198903 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -13,7 +13,7 @@ steps: Pkg.add("LinearOperators") Pkg.instantiate() using CUDA - CUDA.set_runtime_version!(v"11.8")' + # CUDA.set_runtime_version!(v"11.8")' julia --color=yes --project -e ' include("test/gpu/nvidia.jl")' From 5945a5df6dcfbe5e3dc4c0e83ae2b2277e765db4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 7 May 2023 22:46:51 -0400 Subject: [PATCH 149/182] [documentation] Remove the comment about unstable CUDA toolkits --- docs/src/gpu.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/src/gpu.md b/docs/src/gpu.md index b03f8f5e3..33b76b421 100644 --- a/docs/src/gpu.md +++ b/docs/src/gpu.md @@ -144,9 +144,6 @@ if CUDA.functional() end ``` -!!! note - The `CUSPARSE` library of CUDA toolkits `v"12.x"` is unstable. We recommend using CUDA toolkit v"11.8" with `CUDA.set_runtime_version!(v"11.8")`. - ## AMD GPUs All solvers in Krylov.jl can be used with [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) and allow computations on AMD GPUs. From baa4941a7cee4465aad150e72f258687fd9e4962 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 8 May 2023 01:53:11 -0400 Subject: [PATCH 150/182] Improve test_mp.jl --- test/test_mp.jl | 98 ++++++++++++++++++++++---------------------- test/test_solvers.jl | 2 +- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/test/test_mp.jl b/test/test_mp.jl index 6b6d58450..96300bea6 100644 --- a/test/test_mp.jl +++ b/test/test_mp.jl @@ -4,54 +4,56 @@ :lslq, :lsqr, :lsmr, :lnlq, :craig, :bicgstab, :craigmr, :crls, :symmlq, :minres, :bilq, :minres_qlp, :qmr, :usymlq, :tricg, :trimr, :trilqr, :bilqr, :gmres, :fom, :fgmres, :cg_lanczos_shift) - for T in (Float16, Float32, Float64, BigFloat) - for FC in (T, Complex{T}) - A = spdiagm(-1 => -ones(FC,n-1), 0 => 3*ones(FC,n), 1 => -ones(FC,n-1)) - B = spdiagm(-1 => -ones(FC,n-1), 0 => 5*ones(FC,n), 1 => -ones(FC,n-1)) - b = ones(FC, n) - c = -ones(FC, n) - shifts = [-one(T), one(T)] - if fn in (:usymlq, :usymqr) - x, _ = @eval $fn($A, $b, $c) - elseif fn in (:trilqr, :bilqr) - x, t, _ = @eval $fn($A, $b, $c) - elseif fn in (:tricg, :trimr) - x, y, _ = @eval $fn($A, $b, $c) - elseif fn == :gpmr - x, y, _ = @eval $fn($A, $B, $b, $c) - elseif fn in (:lnlq, :craig, :craigmr) - x, y, _ = @eval $fn($A, $b) - elseif fn == :cg_lanczos_shift - x, _ = @eval $fn($A, $b, $shifts) - else - x, _ = @eval $fn($A, $b) - end - atol = √eps(T) - rtol = √eps(T) - Κ = (T == Float16 ? 10 : 1) - if fn in (:tricg, :trimr) - @test norm(x + A * y - b) ≤ Κ * (atol + norm([b; c]) * rtol) - @test norm(A' * x - y - c) ≤ Κ * (atol + norm([b; c]) * rtol) - @test eltype(y) == FC - elseif fn == :gpmr - @test norm(x + A * y - b) ≤ Κ * (atol + norm([b; c]) * rtol) - @test norm(B * x + y - c) ≤ Κ * (atol + norm([b; c]) * rtol) - @test eltype(y) == FC - elseif fn == :cg_lanczos_shift - @test norm((A - I) * x[1] - b) ≤ Κ * (atol + norm(b) * rtol) - @test norm((A + I) * x[2] - b) ≤ Κ * (atol + norm(b) * rtol) - @test eltype(x) == Vector{FC} - else - @test norm(A * x - b) ≤ Κ * (atol + norm(b) * rtol) - @test eltype(x) == FC - end - if fn in (:trilqr, :bilqr) - @test norm(A' * t - c) ≤ Κ * (atol + norm(c) * rtol) - @test eltype(t) == FC - end - if fn in (:lnlq, :craig, :craigmr) - @test norm(A * A' * y - b) ≤ Κ * (atol + norm(b) * rtol) - @test eltype(y) == FC + @testset "$fn" begin + for T in (Float16, Float32, Float64, BigFloat) + for FC in (T, Complex{T}) + A = spdiagm(-1 => -ones(FC,n-1), 0 => 3*ones(FC,n), 1 => -ones(FC,n-1)) + B = spdiagm(-1 => -ones(FC,n-1), 0 => 5*ones(FC,n), 1 => -ones(FC,n-1)) + b = ones(FC, n) + c = -ones(FC, n) + shifts = [-one(T), one(T)] + if fn in (:usymlq, :usymqr) + x, _ = @eval $fn($A, $b, $c) + elseif fn in (:trilqr, :bilqr) + x, t, _ = @eval $fn($A, $b, $c) + elseif fn in (:tricg, :trimr) + x, y, _ = @eval $fn($A, $b, $c) + elseif fn == :gpmr + x, y, _ = @eval $fn($A, $B, $b, $c) + elseif fn in (:lnlq, :craig, :craigmr) + x, y, _ = @eval $fn($A, $b) + elseif fn == :cg_lanczos_shift + x, _ = @eval $fn($A, $b, $shifts) + else + x, _ = @eval $fn($A, $b) + end + atol = √eps(T) + rtol = √eps(T) + Κ = (T == Float16 ? 10 : 1) + if fn in (:tricg, :trimr) + @test norm(x + A * y - b) ≤ Κ * (atol + norm([b; c]) * rtol) + @test norm(A' * x - y - c) ≤ Κ * (atol + norm([b; c]) * rtol) + @test eltype(y) == FC + elseif fn == :gpmr + @test norm(x + A * y - b) ≤ Κ * (atol + norm([b; c]) * rtol) + @test norm(B * x + y - c) ≤ Κ * (atol + norm([b; c]) * rtol) + @test eltype(y) == FC + elseif fn == :cg_lanczos_shift + @test norm((A - I) * x[1] - b) ≤ Κ * (atol + norm(b) * rtol) + @test norm((A + I) * x[2] - b) ≤ Κ * (atol + norm(b) * rtol) + @test eltype(x) == Vector{FC} + else + @test norm(A * x - b) ≤ Κ * (atol + norm(b) * rtol) + @test eltype(x) == FC + end + if fn in (:trilqr, :bilqr) + @test norm(A' * t - c) ≤ Κ * (atol + norm(c) * rtol) + @test eltype(t) == FC + end + if fn in (:lnlq, :craig, :craigmr) + @test norm(A * A' * y - b) ≤ Κ * (atol + norm(b) * rtol) + @test eltype(y) == FC + end end end end diff --git a/test/test_solvers.jl b/test/test_solvers.jl index 1daee0cac..f69e90ad5 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -157,7 +157,7 @@ function test_solvers(FC) showed = String(take!(io)) # Test that the lines have the same length - str = split(showed, "\n", keepempty=false) + str = split(showed, '\n', keepempty=false) len_row = length(str[1]) @test mapreduce(x -> length(x) - mapreduce(y -> occursin(y, x), |, ["w̅","w̄","d̅"]) == len_row, &, str) From cec1dd814292a012080b63c30665e89f839a04bc Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Mon, 8 May 2023 11:23:46 -0400 Subject: [PATCH 151/182] Add a keyword argument timemax for all Krylov methods --- src/bicgstab.jl | 14 +++++++++++--- src/bilq.jl | 14 +++++++++++--- src/bilqr.jl | 14 +++++++++++--- src/cg.jl | 27 +++++++++++++++++---------- src/cg_lanczos.jl | 14 +++++++++++--- src/cg_lanczos_shift.jl | 14 +++++++++++--- src/cgls.jl | 18 +++++++++++++----- src/cgne.jl | 14 +++++++++++--- src/cgs.jl | 14 +++++++++++--- src/cr.jl | 19 +++++++++++++------ src/craig.jl | 14 +++++++++++--- src/craigmr.jl | 18 +++++++++++++----- src/crls.jl | 14 +++++++++++--- src/crmr.jl | 14 +++++++++++--- src/diom.jl | 15 ++++++++++++--- src/dqgmres.jl | 15 ++++++++++++--- src/fgmres.jl | 24 ++++++++++++++++-------- src/fom.jl | 20 +++++++++++++++----- src/gmres.jl | 28 ++++++++++++++++++---------- src/gpmr.jl | 16 ++++++++++++---- src/lnlq.jl | 14 +++++++++++--- src/lslq.jl | 14 +++++++++++--- src/lsmr.jl | 20 ++++++++++++++------ src/lsqr.jl | 20 ++++++++++++++------ src/minres.jl | 14 +++++++++++--- src/minres_qlp.jl | 14 +++++++++++--- src/qmr.jl | 15 ++++++++++++--- src/symmlq.jl | 14 +++++++++++--- src/tricg.jl | 14 +++++++++++--- src/trilqr.jl | 14 +++++++++++--- src/trimr.jl | 14 +++++++++++--- src/usymlq.jl | 14 +++++++++++--- src/usymqr.jl | 15 ++++++++++++--- test/test_fom.jl | 7 ------- test/test_solvers.jl | 18 ++++++++++++++++-- 35 files changed, 418 insertions(+), 143 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index c3b6c8b53..108b337b1 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -20,7 +20,7 @@ export bicgstab, bicgstab! c::AbstractVector{FC}=b, M=I, N=I, ldiv::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -60,6 +60,7 @@ BICGSTAB stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -109,9 +110,11 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, M=I, N=I, ldiv :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -190,8 +193,9 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; breakdown = false status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Update iteration index and ρ. iter = iter + 1 ρ = next_ρ @@ -229,14 +233,18 @@ function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax breakdown = (α == 0 || isnan(α)) + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "breakdown αₖ == 0") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/bilq.jl b/src/bilq.jl index 327ca8b0e..e87bac7ba 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -16,7 +16,7 @@ export bilq, bilq! (x, stats) = bilq(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, transfer_to_bicg::Bool=true, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -47,6 +47,7 @@ When `A` is Hermitian and `b = c`, BiLQ is equivalent to SYMMLQ. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -95,9 +96,11 @@ end function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, transfer_to_bicg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -178,8 +181,9 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved_lq || solved_cg || tired || breakdown || user_requested_exit) + while !(solved_lq || solved_cg || tired || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -321,6 +325,8 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved_cg = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) tired = iter ≥ itmax breakdown = !solved_lq && !solved_cg && (pᴴq == 0) + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) end (verbose > 0) && @printf(iostream, "\n") @@ -331,11 +337,13 @@ function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kaxpy!(n, ζbarₖ, d̅, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") solved_lq && (status = "solution xᴸ good enough given atol and rtol") solved_cg && (status = "solution xᶜ good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/bilqr.jl b/src/bilqr.jl index 111eef567..adb4ce921 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -16,7 +16,7 @@ export bilqr, bilqr! (x, y, stats) = bilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; transfer_to_bicg::Bool=true, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -52,6 +52,7 @@ QMR is used for solving dual system `Aᴴy = c` of size n. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -101,9 +102,11 @@ end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; transfer_to_bicg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("Systems must be square") @@ -196,8 +199,9 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: breakdown = false status = "unknown" user_requested_exit = false + overtimed = false - while !((solved_primal && solved_dual) || tired || breakdown || user_requested_exit) + while !((solved_primal && solved_dual) || tired || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -416,6 +420,8 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: user_requested_exit = callback(solver) :: Bool tired = iter ≥ itmax breakdown = !solved_lq && !solved_cg && (pᴴq == 0) + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") @@ -429,6 +435,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: @kaxpy!(n, ζbarₖ, d̅, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") solved_lq_tol && !solved_dual && (status = "Only the primal solution xᴸ is good enough given atol and rtol") @@ -446,6 +453,7 @@ function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: solved_lq_tol && solved_qr_mach && (status = "Found a primal solution xᴸ good enough given atol and rtol and an approximate zero-residual dual solutions t") solved_cg_tol && solved_qr_mach && (status = "Found a primal solution xᶜ good enough given atol and rtol and an approximate zero-residual dual solutions t") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x and y warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/cg.jl b/src/cg.jl index 5ee6d2cda..cd49c2b25 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -20,7 +20,7 @@ export cg, cg! M=I, ldiv::Bool=false, radius::T=zero(T), linesearch::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -53,6 +53,7 @@ M also indicates the weighted norm in which residuals are measured. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -101,15 +102,16 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), linesearch :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") - + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") + linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") (verbose > 0) && @printf(iostream, "CG: system of %d equations in %d variables\n", n, n) # Tests M = Iₙ @@ -162,10 +164,11 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; on_boundary = false zero_curvature = false user_requested_exit = false + overtimed = false status = "unknown" - while !(solved || tired || zero_curvature || user_requested_exit) + while !(solved || tired || zero_curvature || user_requested_exit || overtimed) mul!(Ap, A, p) pAp = @kdotr(n, p, Ap) if (pAp ≤ eps(T) * pNorm²) && (radius == 0) @@ -220,16 +223,20 @@ function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 tired = iter ≥ itmax user_requested_exit = callback(solver) :: Bool + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) end (verbose > 0) && @printf(iostream, "\n") - solved && on_boundary && (status = "on trust-region boundary") + # Termination status + solved && on_boundary && (status = "on trust-region boundary") solved && linesearch && (pAp ≤ 0) && (status = "nonpositive curvature detected") - solved && (status == "unknown") && (status = "solution good enough given atol and rtol") - zero_curvature && (status = "zero curvature detected") - tired && (status = "maximum number of iterations exceeded") - user_requested_exit && (status = "user-requested exit") + solved && (status == "unknown") && (status = "solution good enough given atol and rtol") + zero_curvature && (status = "zero curvature detected") + tired && (status = "maximum number of iterations exceeded") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 9bab1563e..7a2b6b80b 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -17,7 +17,7 @@ export cg_lanczos, cg_lanczos! M=I, ldiv::Bool=false, check_curvature::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -49,6 +49,7 @@ The method does _not_ abort if A is not definite. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -98,9 +99,11 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F M=I, ldiv :: Bool=false, check_curvature :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -172,9 +175,10 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false # Main loop. - while ! (solved || tired || (check_curvature & indefinite) || user_requested_exit) + while ! (solved || tired || (check_curvature & indefinite) || user_requested_exit || overtimed) # Form next Lanczos vector. # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ @@ -218,13 +222,17 @@ function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{F resid_decrease_lim = rNorm ≤ ε solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") (check_curvature & indefinite) && (status = "negative curvature") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index f579a79c8..cbe8d6bb5 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -18,7 +18,7 @@ export cg_lanczos_shift, cg_lanczos_shift! M=I, ldiv::Bool=false, check_curvature::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -45,6 +45,7 @@ of size n. The method does _not_ abort if A + αI is not definite. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -82,9 +83,11 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr M=I, ldiv :: Bool=false, check_curvature :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -173,9 +176,10 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false # Main loop. - while ! (solved || tired || user_requested_exit) + while ! (solved || tired || user_requested_exit || overtimed) # Form next Lanczos vector. # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ @@ -235,12 +239,16 @@ function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: Abstr user_requested_exit = callback(solver) :: Bool solved = !reduce(|, not_cv) tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats. TODO: Estimate Anorm and Acond. stats.niter = iter diff --git a/src/cgls.jl b/src/cgls.jl index bad84fd5c..07da838ab 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -32,7 +32,8 @@ export cgls, cgls! (x, stats) = cgls(A, b::AbstractVector{FC}; M=I, ldiv::Bool=false, radius::T=zero(T), λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, + itmax::Int=0, timemax::Float64=Inf, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -67,6 +68,7 @@ but simpler to implement. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -102,9 +104,11 @@ function cgls! end function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -159,8 +163,9 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = ArNorm ≤ ε tired = iter ≥ itmax user_requested_exit = false + overtimed = false - while ! (solved || tired || user_requested_exit) + while ! (solved || tired || user_requested_exit || overtimed) mul!(q, A, p) MisI || mulorldiv!(Mq, M, q, ldiv) δ = @kdotr(m, q, Mq) # δ = qᴴMq @@ -190,16 +195,19 @@ function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; iter = iter + 1 kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) user_requested_exit = callback(solver) :: Bool - solved = (ArNorm ≤ ε) | on_boundary + solved = (ArNorm ≤ ε) || on_boundary tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") on_boundary && (status = "on trust-region boundary") user_requested_exit && (status = "user-requested exit") - + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/cgne.jl b/src/cgne.jl index ac005906d..9245e04a1 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -33,7 +33,7 @@ export cgne, cgne! N=I, ldiv::Bool=false, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -74,6 +74,7 @@ but simpler to implement. Only the x-part of the solution is returned. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -110,9 +111,11 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, ldiv :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -171,8 +174,9 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = (rNorm > 100 * ɛ_c) && (pNorm ≤ ɛ_i) tired = iter ≥ itmax user_requested_exit = false + overtimed = false - while ! (solved || inconsistent || tired || user_requested_exit) + while ! (solved || inconsistent || tired || user_requested_exit || overtimed) mul!(q, A, p) λ > 0 && @kaxpy!(m, λ, s, q) δ = @kdotr(n, p, p) # Faster than dot(p, p) @@ -204,13 +208,17 @@ function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = resid_decrease_lim || resid_decrease_mach inconsistent = (rNorm > 100 * ɛ_c) && (pNorm ≤ ɛ_i) tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") inconsistent && (status = "system probably inconsistent") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/cgs.jl b/src/cgs.jl index db27b08c4..508f37cfc 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -15,7 +15,7 @@ export cgs, cgs! c::AbstractVector{FC}=b, M=I, N=I, ldiv::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -62,6 +62,7 @@ TFQMR and BICGSTAB were developed to remedy this difficulty.» * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -110,9 +111,11 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, M=I, N=I, ldiv :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -190,8 +193,9 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; breakdown = false status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) NisI || mulorldiv!(y, N, p, ldiv) # yₖ = N⁻¹pₖ mul!(t, A, y) # tₖ = Ayₖ @@ -233,14 +237,18 @@ function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax breakdown = (α == 0 || isnan(α)) + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "breakdown αₖ == 0") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/cr.jl b/src/cr.jl index 744083d5b..7180f9201 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -22,7 +22,7 @@ export cr, cr! M=I, ldiv::Bool=false, radius::T=zero(T), linesearch::Bool=false, γ::T=√eps(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -56,6 +56,7 @@ M also indicates the weighted norm in which residuals are measured. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -107,15 +108,16 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), linesearch :: Bool=false, γ :: T=√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") - + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") + linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") (verbose > 0) && @printf(iostream, "CR: system of %d equations in %d variables\n", n, n) # Tests M = Iₙ @@ -184,8 +186,9 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; npcurv = false status = "unknown" user_requested_exit = false + overtimed = false - while ! (solved || tired || user_requested_exit) + while ! (solved || tired || user_requested_exit || overtimed) if linesearch if (pAp ≤ γ * pNorm²) || (ρ ≤ γ * rNorm²) npcurv = true @@ -337,8 +340,10 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease = resid_decrease_lim || resid_decrease_mach solved = resid_decrease || npcurv || on_boundary tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns - (solved || tired || user_requested_exit) && continue + (solved || tired || user_requested_exit || overtimed) && continue ρbar = ρ ρ = @kdotr(n, r, Ar) β = ρ / ρbar # step for the direction computation @@ -367,11 +372,13 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") on_boundary && (status = "on trust-region boundary") npcurv && (status = "nonpositive curvature") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/craig.jl b/src/craig.jl index 4cefa3fc4..2ee23b971 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -39,7 +39,7 @@ export craig, craig! λ::T=zero(T), btol::T=√eps(T), conlim::T=1/√eps(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -106,6 +106,7 @@ In this implementation, both the x and y-parts of the solution are returned. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -145,9 +146,11 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; λ :: T=zero(T), btol :: T=√eps(T), conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -241,8 +244,9 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = false tired = iter ≥ itmax user_requested_exit = false + overtimed = false - while ! (solved || inconsistent || ill_cond || tired || user_requested_exit) + while ! (solved || inconsistent || ill_cond || tired || user_requested_exit || overtimed) # Generate the next Golub-Kahan vectors # 1. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ mul!(Aᴴu, Aᴴ, u) @@ -343,6 +347,8 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; user_requested_exit = callback(solver) :: Bool inconsistent = false tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") @@ -353,12 +359,14 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; # TODO: update y end + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough for the tolerances given") ill_cond_mach && (status = "condition number seems too large for this machine") ill_cond_lim && (status = "condition number exceeds tolerance") inconsistent && (status = "system may be inconsistent") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/craigmr.jl b/src/craigmr.jl index 00833251f..b95813d5b 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -31,7 +31,7 @@ export craigmr, craigmr! M=I, N=I, ldiv::Bool=false, sqd::Bool=false, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -98,6 +98,7 @@ returned. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -135,9 +136,11 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -248,8 +251,9 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) tired = iter ≥ itmax user_requested_exit = false + overtimed = false - while ! (solved || inconsistent || tired || user_requested_exit) + while ! (solved || inconsistent || tired || user_requested_exit || overtimed) iter = iter + 1 # Generate next Golub-Kahan vectors. @@ -345,14 +349,18 @@ function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; user_requested_exit = callback(solver) :: Bool solved = rNorm ≤ ɛ_c inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) - tired = iter ≥ itmax + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") - + + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "found approximate minimum-norm solution") !tired && !solved && (status = "found approximate minimum least-squares solution") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/crls.jl b/src/crls.jl index e204b5f79..e2061e84c 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -24,7 +24,7 @@ export crls, crls! (x, stats) = crls(A, b::AbstractVector{FC}; M=I, ldiv::Bool=false, radius::T=zero(T), λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, verbose::Int=0, history::Bool=false, + itmax::Int=0, timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -59,6 +59,7 @@ but simpler to implement. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -93,9 +94,11 @@ function crls! end function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -160,8 +163,9 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax psd = false user_requested_exit = false + overtimed = false - while ! (solved || tired || user_requested_exit) + while ! (solved || tired || user_requested_exit || overtimed) qNorm² = @kdotr(n, q, q) # dot(q, q) α = γ / qNorm² @@ -216,14 +220,18 @@ function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; user_requested_exit = callback(solver) :: Bool solved = (ArNorm ≤ ε) || on_boundary tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") psd && (status = "zero-curvature encountered") on_boundary && (status = "on trust-region boundary") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/crmr.jl b/src/crmr.jl index 56224ca1a..8addc803c 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -31,7 +31,7 @@ export crmr, crmr! N=I, ldiv::Bool=false, λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -72,6 +72,7 @@ but simpler to implement. Only the x-part of the solution is returned. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -108,9 +109,11 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, ldiv :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -167,8 +170,9 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) tired = iter ≥ itmax user_requested_exit = false + overtimed = false - while ! (solved || inconsistent || tired || user_requested_exit) + while ! (solved || inconsistent || tired || user_requested_exit || overtimed) mul!(q, A, p) λ > 0 && @kaxpy!(m, λ, s, q) # q = q + λ * s NisI || mulorldiv!(Nq, N, q, ldiv) @@ -196,13 +200,17 @@ function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = rNorm ≤ ɛ_c inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") inconsistent && (status = "system probably inconsistent but least squares/norm solution found") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/diom.jl b/src/diom.jl index a65054296..c6ce56eb1 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -15,7 +15,7 @@ export diom, diom! memory::Int=20, M=I, N=I, ldiv::Bool=false, reorthogonalization::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -54,6 +54,7 @@ and indefinite systems of linear equations can be handled by this single algorit * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -105,9 +106,11 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -182,8 +185,9 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || user_requested_exit) + while !(solved || tired || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -282,12 +286,17 @@ function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease_lim = rNorm ≤ ε solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end (verbose > 0) && @printf(iostream, "\n") + + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 5a2bd1b2e..785324c96 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -15,7 +15,7 @@ export dqgmres, dqgmres! memory::Int=20, M=I, N=I, ldiv::Bool=false, reorthogonalization::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -54,6 +54,7 @@ Otherwise, DQGMRES interpolates between MINRES and GMRES and is similar to MINRE * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -105,9 +106,11 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -184,8 +187,9 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || user_requested_exit) + while !(solved || tired || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -284,12 +288,17 @@ function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease_lim = rNorm ≤ ε solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end (verbose > 0) && @printf(iostream, "\n") + + # Termination status solved && (status = "solution good enough given atol and rtol") tired && (status = "maximum number of iterations exceeded") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/fgmres.jl b/src/fgmres.jl index 4ccea2b2c..ce4a5df82 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -15,7 +15,7 @@ export fgmres, fgmres! memory::Int=20, M=I, N=I, ldiv::Bool=false, restart::Bool=false, reorthogonalization::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -56,6 +56,7 @@ Thus, GMRES is recommended if the right preconditioner N is constant. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -107,9 +108,11 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, restart :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -184,8 +187,9 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; inner_tired = inner_iter ≥ inner_itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Initialize workspace. nr = 0 # Number of coefficients stored in Rₖ. @@ -216,7 +220,7 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; solver.inner_iter = 0 inner_tired = false - while !(solved || inner_tired || breakdown || user_requested_exit) + while !(solved || inner_tired || breakdown || user_requested_exit || overtimed) # Update iteration index solver.inner_iter = solver.inner_iter + 1 @@ -286,15 +290,17 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease_mach = (rNorm + one(T) ≤ one(T)) # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool resid_decrease_lim = rNorm ≤ ε breakdown = Hbis ≤ btol solved = resid_decrease_lim || resid_decrease_mach inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax - solver.inner_iter = inner_iter + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) # Compute vₖ₊₁ - if !(solved || inner_tired || breakdown) + if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) if !restart && (inner_iter ≥ mem) push!(V, S(undef, n)) push!(z, zero(FC)) @@ -302,8 +308,6 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q z[inner_iter+1] = ζₖ₊₁ end - - user_requested_exit = callback(solver) :: Bool end # Compute y by solving Ry = z with backward substitution. @@ -333,13 +337,17 @@ function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; inner_itmax = inner_itmax - inner_iter iter = iter + inner_iter tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") inconsistent && (status = "found approximate least-squares solution") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/fom.jl b/src/fom.jl index 8aa5d0be7..c87774ae3 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -15,7 +15,7 @@ export fom, fom! memory::Int=20, M=I, N=I, ldiv::Bool=false, restart::Bool=false, reorthogonalization::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -49,6 +49,7 @@ FOM algorithm is based on the Arnoldi process and a Galerkin condition. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -100,9 +101,11 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, restart :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -178,8 +181,9 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; inner_tired = inner_iter ≥ inner_itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Initialize workspace. nr = 0 # Number of coefficients stored in Uₖ. @@ -274,10 +278,12 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; breakdown = Hbis ≤ btol solved = resid_decrease_lim || resid_decrease_mach inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) # Compute vₖ₊₁. - if !(solved || inner_tired || breakdown) + if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) if !restart && (inner_iter ≥ mem) push!(V, S(undef, n)) end @@ -307,17 +313,21 @@ function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; end restart && @kaxpy!(n, one(FC), xr, x) - # Update inner_itmax, iter and tired variables. + # Update inner_itmax, iter, tired and overtimed variables. inner_itmax = inner_itmax - inner_iter iter = iter + inner_iter tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "inconsistent linear system") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/gmres.jl b/src/gmres.jl index f6455f386..5653b5b43 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -15,7 +15,7 @@ export gmres, gmres! memory::Int=20, M=I, N=I, ldiv::Bool=false, restart::Bool=false, reorthogonalization::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -49,6 +49,7 @@ GMRES algorithm is based on the Arnoldi process and computes a sequence of appro * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -100,9 +101,11 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, restart :: Bool=false, reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -179,8 +182,9 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; inner_tired = inner_iter ≥ inner_itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Initialize workspace. nr = 0 # Number of coefficients stored in Rₖ. @@ -210,7 +214,7 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; solver.inner_iter = 0 inner_tired = false - while !(solved || inner_tired || breakdown || user_requested_exit) + while !(solved || inner_tired || breakdown || user_requested_exit || overtimed) # Update iteration index solver.inner_iter = solver.inner_iter + 1 @@ -279,15 +283,17 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease_mach = (rNorm + one(T) ≤ one(T)) # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool resid_decrease_lim = rNorm ≤ ε breakdown = Hbis ≤ btol solved = resid_decrease_lim || resid_decrease_mach inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax - solver.inner_iter = inner_iter + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) - # Compute vₖ₊₁ - if !(solved || inner_tired || breakdown) + # Compute vₖ₊₁. + if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) if !restart && (inner_iter ≥ mem) push!(V, S(undef, n)) push!(z, zero(FC)) @@ -295,8 +301,6 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q z[inner_iter+1] = ζₖ₊₁ end - - user_requested_exit = callback(solver) :: Bool end # Compute yₖ by solving Rₖyₖ = zₖ with backward substitution. @@ -326,17 +330,21 @@ function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; end restart && @kaxpy!(n, one(FC), xr, x) - # Update inner_itmax, iter and tired variables. + # Update inner_itmax, iter, tired and overtimed variables. inner_itmax = inner_itmax - inner_iter iter = iter + inner_iter tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") inconsistent && (status = "found approximate least-squares solution") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/gpmr.jl b/src/gpmr.jl index fec86250e..42d4c36cb 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -18,7 +18,7 @@ export gpmr, gpmr! λ::FC=one(FC), μ::FC=one(FC), reorthogonalization::Bool=false, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -84,6 +84,7 @@ GPMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -139,9 +140,11 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: λ :: FC=one(FC), μ :: FC=one(FC), reorthogonalization :: Bool=false, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history::Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history::Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) s, t = size(B) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") @@ -257,8 +260,9 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -431,10 +435,12 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: breakdown = Faux ≤ btol && Haux ≤ btol solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, Haux, Faux) # Compute vₖ₊₁ and uₖ₊₁ - if !(solved || tired || breakdown || user_requested_exit) + if !(solved || tired || breakdown || user_requested_exit || overtimed) if iter ≥ mem push!(V, S(undef, m)) push!(U, S(undef, n)) @@ -496,10 +502,12 @@ function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: warm_start && @kaxpy!(n, one(FC), Δy, y) solver.warm_start = false + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") inconsistent && (status = "found approximate least-squares solution") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/lnlq.jl b/src/lnlq.jl index e0b5bfc97..bfeb26b70 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -32,7 +32,7 @@ export lnlq, lnlq! σ::T=zero(T), utolx::T=√eps(T), utoly::T=√eps(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -102,6 +102,7 @@ For instance σ:=(1-1e-7)σₘᵢₙ . * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -141,9 +142,11 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; σ :: T=zero(T), utolx :: T=√eps(T), utoly :: T=√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -276,6 +279,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; tired = false status = "unknown" user_requested_exit = false + overtimed = false if σₑₛₜ > 0 τtildeₖ = βₖ / σₑₛₜ @@ -291,7 +295,7 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; csig = -one(T) end - while !(solved_lq || solved_cg || tired || user_requested_exit) + while !(solved_lq || solved_cg || tired || user_requested_exit || overtimed) # Update of (xᵃᵘˣ)ₖ = Vₖtₖ if λ > 0 @@ -477,6 +481,8 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved_lq = solved_lq || err_x ≤ utolx || err_y ≤ utoly solved_cg = transfer_to_craig && (solved_cg || err_x ≤ utolx || err_y ≤ utoly) end + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) # Update iteration index. @@ -510,10 +516,12 @@ function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; end end + # Termination status tired && (status = "maximum number of iterations exceeded") solved_lq && (status = "solutions (xᴸ, yᴸ) good enough for the tolerances given") solved_cg && (status = "solutions (xᶜ, yᶜ) good enough for the tolerances given") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/lslq.jl b/src/lslq.jl index 62f16c251..5aa887536 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -30,7 +30,7 @@ export lslq, lslq! utol::T=√eps(T), btol::T=√eps(T), conlim::T=1/√eps(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -105,6 +105,7 @@ In this case, `N` can still be specified and indicates the weighted norm in whic * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -166,9 +167,11 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; utol :: T=√eps(T), btol :: T=√eps(T), conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback=solver->false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -296,8 +299,9 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; fwd_err_lbnd = false fwd_err_ubnd = false user_requested_exit = false + overtimed = false - while ! (solved || tired || ill_cond || user_requested_exit) + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) # Generate next Golub-Kahan vectors. # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ @@ -448,6 +452,8 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; ill_cond = ill_cond_mach || ill_cond_lim zero_resid = zero_resid_mach || zero_resid_lim solved = solved_mach || solved_lim || zero_resid || fwd_err_lbnd || fwd_err_ubnd + timer = time_ns() - start_time + overtimed = timer > timemax_ns iter = iter + 1 kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm) @@ -458,6 +464,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kaxpy!(n, ζ̄ , w̄, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") ill_cond_lim && (status = "condition number exceeds tolerance") @@ -466,6 +473,7 @@ function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; fwd_err_lbnd && (status = "forward error lower bound small enough") fwd_err_ubnd && (status = "forward error upper bound small enough") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/lsmr.jl b/src/lsmr.jl index f1b1f52c3..459a7d855 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -32,7 +32,7 @@ export lsmr, lsmr! axtol::T=√eps(T), btol::T=√eps(T), conlim::T=1/√eps(T), atol::T=zero(T), rtol::T=zero(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -105,6 +105,7 @@ In this case, `N` can still be specified and indicates the weighted norm in whic * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -143,9 +144,11 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; axtol :: T=√eps(T), btol :: T=√eps(T), conlim :: T=1/√eps(T), atol :: T=zero(T), rtol :: T=zero(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -264,8 +267,9 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid = zero_resid_mach = zero_resid_lim = false fwd_err = false user_requested_exit = false + overtimed = false - while ! (solved || tired || ill_cond || user_requested_exit) + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) iter = iter + 1 # Generate next Golub-Kahan vectors. @@ -383,12 +387,15 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid_lim = (test1 ≤ rNormtol) iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) - ill_cond = ill_cond_mach | ill_cond_lim - zero_resid = zero_resid_mach | zero_resid_lim - solved = solved_mach | solved_lim | solved_opt | zero_resid | fwd_err | on_boundary + ill_cond = ill_cond_mach || ill_cond_lim + zero_resid = zero_resid_mach || zero_resid_lim + solved = solved_mach || solved_lim || solved_opt || zero_resid || fwd_err || on_boundary + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") ill_cond_lim && (status = "condition number exceeds tolerance") @@ -397,6 +404,7 @@ function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; fwd_err && (status = "truncated forward error small enough") on_boundary && (status = "on trust-region boundary") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.residual = rNorm diff --git a/src/lsqr.jl b/src/lsqr.jl index bfc6b09c8..f98e1d80e 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -32,7 +32,7 @@ export lsqr, lsqr! axtol::T=√eps(T), btol::T=√eps(T), conlim::T=1/√eps(T), atol::T=zero(T), rtol::T=zero(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -101,6 +101,7 @@ In this case, `N` can still be specified and indicates the weighted norm in whic * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -139,9 +140,11 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; axtol :: T=√eps(T), btol :: T=√eps(T), conlim :: T=1/√eps(T), atol :: T=zero(T), rtol :: T=zero(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -252,8 +255,9 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid = zero_resid_mach | zero_resid_lim fwd_err = false user_requested_exit = false + overtimed = false - while ! (solved || tired || ill_cond || user_requested_exit) + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) iter = iter + 1 # Generate next Golub-Kahan vectors. @@ -373,12 +377,15 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid_lim = (test1 ≤ rNormtol) iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) - ill_cond = ill_cond_mach | ill_cond_lim - zero_resid = zero_resid_mach | zero_resid_lim - solved = solved_mach | solved_lim | solved_opt | zero_resid | fwd_err | on_boundary + ill_cond = ill_cond_mach || ill_cond_lim + zero_resid = zero_resid_mach || zero_resid_lim + solved = solved_mach || solved_lim || solved_opt || zero_resid || fwd_err || on_boundary + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") ill_cond_lim && (status = "condition number exceeds tolerance") @@ -387,6 +394,7 @@ function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; fwd_err && (status = "truncated forward error small enough") on_boundary && (status = "on trust-region boundary") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update stats stats.niter = iter diff --git a/src/minres.jl b/src/minres.jl index 06864d325..91041ef47 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -27,7 +27,7 @@ export minres, minres! λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), etol::T=√eps(T), conlim::T=1/√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -74,6 +74,7 @@ MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr * `etol`: stopping tolerance based on the lower bound on the error; * `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -123,9 +124,11 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), etol :: T=√eps(T), conlim :: T=1/√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -223,8 +226,9 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid = zero_resid_mach = zero_resid_lim = (rNorm ≤ ε) fwd_err = false user_requested_exit = false + overtimed = false - while !(solved || tired || ill_cond || user_requested_exit) + while !(solved || tired || ill_cond || user_requested_exit || overtimed) iter = iter + 1 # Generate next Lanczos vector. @@ -346,9 +350,12 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; resid_decrease = resid_decrease_mach || resid_decrease_lim ill_cond = ill_cond_mach || ill_cond_lim solved = solved_mach || solved_lim || zero_resid || fwd_err || resid_decrease + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") ill_cond_lim && (status = "condition number exceeds tolerance") @@ -356,6 +363,7 @@ function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid && (status = "found approximate zero-residual solution") fwd_err && (status = "truncated forward error small enough") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 3fda13f5b..ca40d0629 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -21,7 +21,7 @@ export minres_qlp, minres_qlp! M=I, ldiv::Bool=false, Artol::T=√eps(T), λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -55,6 +55,7 @@ M also indicates the weighted norm in which residuals are measured. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -105,9 +106,11 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F M=I, ldiv :: Bool=false, Artol :: T=√eps(T), λ ::T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -198,8 +201,9 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || inconsistent || ill_cond_mach || breakdown || user_requested_exit) + while !(solved || tired || inconsistent || ill_cond_mach || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -415,6 +419,8 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F resid_decrease = resid_decrease_mach | resid_decrease_lim solved = resid_decrease | zero_resid inconsistent = (ArNorm ≤ κ && abs(μbarₖ) ≤ Artol) || (breakdown && !solved) + timer = time_ns() - start_time + overtimed = timer > timemax_ns # Update variables if iter ≥ 2 @@ -441,12 +447,14 @@ function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{F @kaxpy!(n, τₖ, wₖ, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") inconsistent && (status = "found approximate minimum least-squares solution") zero_resid && (status = "found approximate zero-residual solution") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/qmr.jl b/src/qmr.jl index 50499d761..24d2c1fde 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -23,7 +23,7 @@ export qmr, qmr! """ (x, stats) = qmr(A, b::AbstractVector{FC}; c::AbstractVector{FC}=b, atol::T=√eps(T), - rtol::T=√eps(T), itmax::Int=0, verbose::Int=0, + rtol::T=√eps(T), itmax::Int=0, timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -54,6 +54,7 @@ When `A` is Hermitian and `b = c`, QMR is equivalent to MINRES. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -102,9 +103,12 @@ end function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, verbose :: Int=0, history :: Bool=false, + itmax :: Int=0, timemax :: Float64=Inf, + verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -183,8 +187,9 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -325,14 +330,18 @@ function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: Abst solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax breakdown = !solved && (pᴴq == 0) + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/symmlq.jl b/src/symmlq.jl index 22288a50d..b85f39adb 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -18,7 +18,7 @@ export symmlq, symmlq! λest::T=zero(T), etol::T=√eps(T), conlim::T=1/√eps(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -58,6 +58,7 @@ SYMMLQ produces monotonic errors ‖x* - x‖₂. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -108,9 +109,11 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; λest :: T=zero(T), etol :: T=√eps(T), conlim :: T=1/√eps(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") @@ -252,8 +255,9 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved = zero_resid = solved_lq || solved_cg fwd_err = false user_requested_exit = false + overtimed = false - while ! (solved || tired || ill_cond || user_requested_exit) + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) iter = iter + 1 # Continue QR factorization @@ -401,6 +405,8 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; zero_resid = solved_lq || solved_cg ill_cond = ill_cond_mach || ill_cond_lim solved = solved_mach || zero_resid || zero_resid_mach || zero_resid_lim || fwd_err || resid_decrease_mach + timer = time_ns() - start_time + overtimed = timer > timemax_ns end (verbose > 0) && @printf(iostream, "\n") @@ -410,6 +416,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kaxpy!(m, ζbar, w̅, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") ill_cond_mach && (status = "condition number seems too large for this machine") ill_cond_lim && (status = "condition number exceeds tolerance") @@ -417,6 +424,7 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved_lq && (status = "solution xᴸ good enough given atol and rtol") solved_cg && (status = "solution xᶜ good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/tricg.jl b/src/tricg.jl index 43bcead47..93769da44 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -18,7 +18,7 @@ export tricg, tricg! flip::Bool=false, τ::T=one(T), ν::T=-one(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -75,6 +75,7 @@ TriCG stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -127,9 +128,11 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: flip :: Bool=false, τ :: T=one(T), ν :: T=-one(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -251,8 +254,9 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -415,14 +419,18 @@ function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "inconsistent linear system") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x and y warm_start && @kaxpy!(m, one(FC), Δx, xₖ) diff --git a/src/trilqr.jl b/src/trilqr.jl index 80eea3c15..8be5ac68f 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -16,7 +16,7 @@ export trilqr, trilqr! (x, y, stats) = trilqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; transfer_to_usymcg::Bool=true, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -51,6 +51,7 @@ USYMQR is used for solving dual system `Aᴴy = c` of size n × m. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -100,9 +101,11 @@ end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -182,8 +185,9 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !((solved_primal && solved_dual) || tired || user_requested_exit) + while !((solved_primal && solved_dual) || tired || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -396,6 +400,8 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : user_requested_exit = callback(solver) :: Bool tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") @@ -409,6 +415,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : @kaxpy!(n, ζbarₖ, d̅, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") solved_lq_tol && !solved_dual && (status = "Only the primal solution xᴸ is good enough given atol and rtol") solved_cg_tol && !solved_dual && (status = "Only the primal solution xᶜ is good enough given atol and rtol") @@ -425,6 +432,7 @@ function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : solved_lq_tol && solved_qr_mach && (status = "Found a primal solution xᴸ good enough given atol and rtol and an approximate zero-residual dual solutions t") solved_cg_tol && solved_qr_mach && (status = "Found a primal solution xᶜ good enough given atol and rtol and an approximate zero-residual dual solutions t") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x and y warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/trimr.jl b/src/trimr.jl index 353e5b29a..42e17733d 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -18,7 +18,7 @@ export trimr, trimr! flip::Bool=false, sp::Bool=false, τ::T=one(T), ν::T=-one(T), atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -75,6 +75,7 @@ TriMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -127,9 +128,11 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: flip :: Bool=false, sp :: Bool=false, τ :: T=one(T), ν :: T=-one(T), atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -262,10 +265,11 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false θbarₖ = δbar₂ₖ₋₁ = δbar₂ₖ = σbar₂ₖ₋₁ = σbar₂ₖ = λbar₂ₖ₋₁ = ηbar₂ₖ₋₁ = zero(FC) - while !(solved || tired || breakdown || user_requested_exit) + while !(solved || tired || breakdown || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -517,14 +521,18 @@ function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol solved = resid_decrease_lim || resid_decrease_mach tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) end (verbose > 0) && @printf(iostream, "\n") + # Termination status tired && (status = "maximum number of iterations exceeded") breakdown && (status = "inconsistent linear system") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x and y warm_start && @kaxpy!(m, one(FC), Δx, xₖ) diff --git a/src/usymlq.jl b/src/usymlq.jl index 3f7baf9da..2421eecd7 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -23,7 +23,7 @@ export usymlq, usymlq! (x, stats) = usymlq(A, b::AbstractVector{FC}, c::AbstractVector{FC}; transfer_to_usymcg::Bool=true, atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -59,6 +59,7 @@ In all cases, problems must be consistent. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -109,9 +110,11 @@ end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -178,8 +181,9 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved_lq || solved_cg || tired || user_requested_exit) + while !(solved_lq || solved_cg || tired || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -315,6 +319,8 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : solved_lq = rNorm_lq ≤ ε solved_cg = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) end (verbose > 0) && @printf(iostream, "\n") @@ -325,10 +331,12 @@ function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : @kaxpy!(n, ζbarₖ, d̅, x) end + # Termination status tired && (status = "maximum number of iterations exceeded") solved_lq && (status = "solution xᴸ good enough given atol and rtol") solved_cg && (status = "solution xᶜ good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/src/usymqr.jl b/src/usymqr.jl index eb545718a..5b170d170 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -22,7 +22,7 @@ export usymqr, usymqr! """ (x, stats) = usymqr(A, b::AbstractVector{FC}, c::AbstractVector{FC}; atol::T=√eps(T), rtol::T=√eps(T), itmax::Int=0, - verbose::Int=0, history::Bool=false, + timemax::Float64=Inf, verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -58,6 +58,7 @@ USYMQR finds the minimum-norm solution if problems are inconsistent. * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `m+n`; +* `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; * `history`: collect additional statistics on the run such as residual norms, or Aᴴ-residual norms; * `callback`: function or functor called as `callback(solver)` that returns `true` if the Krylov method should terminate, and `false` otherwise; @@ -107,9 +108,11 @@ end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - verbose :: Int=0, history :: Bool=false, + timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + timemax_ns = 1e9 * timemax m, n = size(A) (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") length(b) == m || error("Inconsistent problem size") @@ -176,8 +179,9 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : tired = iter ≥ itmax status = "unknown" user_requested_exit = false + overtimed = false - while !(solved || tired || inconsistent || user_requested_exit) + while !(solved || tired || inconsistent || user_requested_exit || overtimed) # Update iteration index. iter = iter + 1 @@ -313,12 +317,17 @@ function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c : solved = rNorm ≤ ε inconsistent = !solved && AᴴrNorm ≤ κ tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) end (verbose > 0) && @printf(iostream, "\n") + + # Termination status tired && (status = "maximum number of iterations exceeded") solved && (status = "solution good enough given atol and rtol") user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") # Update x warm_start && @kaxpy!(n, one(FC), Δx, x) diff --git a/test/test_fom.jl b/test/test_fom.jl index 9469b6b9c..0500d139f 100644 --- a/test/test_fom.jl +++ b/test/test_fom.jl @@ -126,13 +126,6 @@ end # test callback function - solver = FomSolver(A, b) - tol = 1.0e-1 - cb_n2 = TestCallbackN2(A, b, tol = tol) - fom!(solver, A, b, restart = true, callback = cb_n2) - @test solver.stats.status == "user-requested exit" - @test cb_n2(solver) - @test_throws TypeError fom(A, b, restart = true, callback = solver -> "string", history = true) end end diff --git a/test/test_solvers.jl b/test/test_solvers.jl index f69e90ad5..71885029f 100644 --- a/test/test_solvers.jl +++ b/test/test_solvers.jl @@ -59,8 +59,6 @@ function test_solvers(FC) b2 = Ao2 * ones(FC, m2) c2 = Au2 * ones(FC, n2) shifts2 = [1.0; 2.0; 3.0; 4.0; 5.0; 6.0] - T = real(FC) - S = Vector{FC} for (method, solver) in solvers if method ∈ (:cg, :cr, :symmlq, :minres, :minres_qlp, :cg_lanczos, :diom, :fom, :dqgmres, :gmres, :fgmres, :cgs, :bicgstab, :bilq, :qmr) @test_throws ErrorException("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($n2, $n2)") solve!(solver, A2, b2) @@ -77,6 +75,22 @@ function test_solvers(FC) end end + @testset "Test the keyword argument timemax" begin + timemax = 0.0 + for (method, solver) in solvers + method ∈ (:cg, :cr, :symmlq, :minres, :minres_qlp, :cg_lanczos, :diom, :fom, :dqgmres, :gmres, :fgmres, :cgs, :bicgstab, :bilq, :qmr) && solve!(solver, A, b, timemax=timemax) + method == :cg_lanczos_shift && solve!(solver, A, b, shifts, timemax=timemax) + method ∈ (:cgne, :crmr, :lnlq, :craig, :craigmr) && solve!(solver, Au, c, timemax=timemax) + method ∈ (:cgls, :crls, :lslq, :lsqr, :lsmr) && solve!(solver, Ao, b, timemax=timemax) + method ∈ (:bilqr, :trilqr) && solve!(solver, A, b, b, timemax=timemax) + method == :gpmr && solve!(solver, Ao, Au, b, c, timemax=timemax) + method ∈ (:tricg, :trimr) && solve!(solver, Au, c, b, timemax=timemax) + method == :usymlq && solve!(solver, Au, c, b, timemax=timemax) + method == :usymqr && solve!(solver, Ao, b, c, timemax=timemax) + @test solver.stats.status == "time limit exceeded" + end + end + for (method, solver) in solvers @testset "$(method)" begin for i = 1 : 3 From fdddcaf303dc0c6b2f4938ef29a022a211a06711 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 9 May 2023 20:03:26 -0400 Subject: [PATCH 152/182] Update Breakage.yml for LinearSolve.jl --- .github/workflows/Breakage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Breakage.yml b/.github/workflows/Breakage.yml index 8fd92afdd..4a907d631 100644 --- a/.github/workflows/Breakage.yml +++ b/.github/workflows/Breakage.yml @@ -19,7 +19,8 @@ jobs: "JuliaSmoothOptimizers/JSOSolvers.jl", "JuliaSmoothOptimizers/LLSModels.jl", "JuliaSmoothOptimizers/Percival.jl", - "JuliaSmoothOptimizers/RipQP.jl" + "JuliaSmoothOptimizers/RipQP.jl", + "SciML/LinearSolve.jl" ] pkgversion: [latest, stable] From b87e4d6005a5e5820bd039ffb4a7ba5a181ba4db Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 9 May 2023 01:33:32 -0400 Subject: [PATCH 153/182] Use metaprogramming to define the kwargs of all Krylov methods --- src/bicgstab.jl | 51 +++++++++++++++++++++----------- src/bilq.jl | 49 ++++++++++++++++++++----------- src/bilqr.jl | 50 +++++++++++++++++++++----------- src/cg.jl | 51 +++++++++++++++++++++----------- src/cg_lanczos.jl | 50 +++++++++++++++++++++----------- src/cg_lanczos_shift.jl | 32 ++++++++++++++++----- src/cgls.jl | 33 ++++++++++++++++----- src/cgne.jl | 32 ++++++++++++++++----- src/cgs.jl | 51 +++++++++++++++++++++----------- src/cr.jl | 54 +++++++++++++++++++++++----------- src/craig.jl | 35 ++++++++++++++++++---- src/craigmr.jl | 32 +++++++++++++++++---- src/crls.jl | 36 +++++++++++++++++------ src/crmr.jl | 32 ++++++++++++++++----- src/diom.jl | 51 +++++++++++++++++++++----------- src/dqgmres.jl | 51 +++++++++++++++++++++----------- src/fgmres.jl | 52 ++++++++++++++++++++++----------- src/fom.jl | 52 ++++++++++++++++++++++----------- src/gmres.jl | 52 ++++++++++++++++++++++----------- src/gpmr.jl | 58 ++++++++++++++++++++++++++----------- src/lnlq.jl | 36 +++++++++++++++++++---- src/lslq.jl | 38 ++++++++++++++++++++---- src/lsmr.jl | 37 ++++++++++++++++++++---- src/lsqr.jl | 37 ++++++++++++++++++++---- src/minres.jl | 52 ++++++++++++++++++++++----------- src/minres_qlp.jl | 58 ++++++++++++++++++++++++------------- src/qmr.jl | 54 ++++++++++++++++++++++------------ src/symmlq.jl | 64 +++++++++++++++++++++++++++-------------- src/tricg.jl | 57 +++++++++++++++++++++++++----------- src/trilqr.jl | 50 +++++++++++++++++++++----------- src/trimr.jl | 58 ++++++++++++++++++++++++++----------- src/usymlq.jl | 50 +++++++++++++++++++++----------- src/usymqr.jl | 49 ++++++++++++++++++++----------- 33 files changed, 1100 insertions(+), 444 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 108b337b1..abf76ac88 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -78,18 +78,6 @@ BICGSTAB stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol """ function bicgstab end -function bicgstab(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = BicgstabSolver(A, b) - bicgstab!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function bicgstab(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = BicgstabSolver(A, b) - bicgstab!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = bicgstab!(solver::BicgstabSolver, A, b; kwargs...) solver = bicgstab!(solver::BicgstabSolver, A, b, x0; kwargs...) @@ -100,10 +88,41 @@ See [`BicgstabSolver`](@ref) for more details about the `solver`. """ function bicgstab! end -function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - bicgstab!(solver, A, b; kwargs...) - return solver +def_kwargs_bicgstab = (:(; c::AbstractVector{FC} = b ), + :(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_bicgstab = reduce(vcat, kw.args[1].args for kw in def_kwargs_bicgstab) + +kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function bicgstab(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = BicgstabSolver(A, b) + bicgstab!(solver, A, b, x0; $(kwargs_bicgstab...)) + return (solver.x, solver.stats) + end + + function bicgstab(A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = BicgstabSolver(A, b) + bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + return (solver.x, solver.stats) + end + + function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + return solver + end end function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/bilq.jl b/src/bilq.jl index e87bac7ba..d54720fad 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -65,18 +65,6 @@ When `A` is Hermitian and `b = c`, BiLQ is equivalent to SYMMLQ. """ function bilq end -function bilq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = BilqSolver(A, b) - bilq!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function bilq(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = BilqSolver(A, b) - bilq!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = bilq!(solver::BilqSolver, A, b; kwargs...) solver = bilq!(solver::BilqSolver, A, b, x0; kwargs...) @@ -87,10 +75,39 @@ See [`BilqSolver`](@ref) for more details about the `solver`. """ function bilq! end -function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - bilq!(solver, A, b; kwargs...) - return solver +def_kwargs_bilq = (:(; c::AbstractVector{FC} = b ), + :(; transfer_to_bicg::Bool = true), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_bilq = reduce(vcat, kw.args[1].args for kw in def_kwargs_bilq) + +kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function bilq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = BilqSolver(A, b) + bilq!(solver, A, b, x0; $(kwargs_bilq...)) + return (solver.x, solver.stats) + end + + function bilq(A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = BilqSolver(A, b) + bilq!(solver, A, b; $(kwargs_bilq...)) + return (solver.x, solver.stats) + end + + function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + bilq!(solver, A, b; $(kwargs_bilq...)) + return solver + end end function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/bilqr.jl b/src/bilqr.jl index adb4ce921..c2942dbea 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -70,18 +70,6 @@ QMR is used for solving dual system `Aᴴy = c` of size n. """ function bilqr end -function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = BilqrSolver(A, b) - bilqr!(solver, A, b, c, x0, y0; kwargs...) - return (solver.x, solver.y, solver.stats) -end - -function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = BilqrSolver(A, b) - bilqr!(solver, A, b, c; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = bilqr!(solver::BilqrSolver, A, b, c; kwargs...) solver = bilqr!(solver::BilqrSolver, A, b, c, x0, y0; kwargs...) @@ -92,11 +80,39 @@ See [`BilqrSolver`](@ref) for more details about the `solver`. """ function bilqr! end -function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0, y0) - bilqr!(solver, A, b, c; kwargs...) - return solver +def_kwargs_bilqr = (:(; transfer_to_bicg::Bool = true), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_bilqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_bilqr) + +kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = BilqrSolver(A, b) + bilqr!(solver, A, b, c, x0, y0; $(kwargs_bilqr...)) + return (solver.x, solver.y, solver.stats) + end + + function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = BilqrSolver(A, b) + bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + return (solver.x, solver.y, solver.stats) + end + + function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0, y0) + bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + return solver + end end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; diff --git a/src/cg.jl b/src/cg.jl index cd49c2b25..bed93fe58 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -70,18 +70,6 @@ M also indicates the weighted norm in which residuals are measured. """ function cg end -function cg(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = CgSolver(A, b) - cg!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function cg(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CgSolver(A, b) - cg!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cg!(solver::CgSolver, A, b; kwargs...) solver = cg!(solver::CgSolver, A, b, x0; kwargs...) @@ -92,10 +80,41 @@ See [`CgSolver`](@ref) for more details about the `solver`. """ function cg! end -function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - cg!(solver, A, b; kwargs...) - return solver +def_kwargs_cg = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; radius::T = zero(T) ), + :(; linesearch::Bool = false ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_cg = reduce(vcat, kw.args[1].args for kw in def_kwargs_cg) + +kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cg(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgSolver(A, b) + cg!(solver, A, b, x0; $(kwargs_cg...)) + return (solver.x, solver.stats) + end + + function cg(A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgSolver(A, b) + cg!(solver, A, b; $(kwargs_cg...)) + return (solver.x, solver.stats) + end + + function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + cg!(solver, A, b; $(kwargs_cg...)) + return solver + end end function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 7a2b6b80b..f2cd5afc6 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -67,18 +67,6 @@ The method does _not_ abort if A is not definite. """ function cg_lanczos end -function cg_lanczos(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = CgLanczosSolver(A, b) - cg_lanczos!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function cg_lanczos(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CgLanczosSolver(A, b) - cg_lanczos!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cg_lanczos!(solver::CgLanczosSolver, A, b; kwargs...) solver = cg_lanczos!(solver::CgLanczosSolver, A, b, x0; kwargs...) @@ -89,10 +77,40 @@ See [`CgLanczosSolver`](@ref) for more details about the `solver`. """ function cg_lanczos! end -function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - cg_lanczos!(solver, A, b; kwargs...) - return solver +def_kwargs_cg_lanczos = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; check_curvature::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_cg_lanczos = reduce(vcat, kw.args[1].args for kw in def_kwargs_cg_lanczos) + +kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cg_lanczos(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgLanczosSolver(A, b) + cg_lanczos!(solver, A, b, x0; $(kwargs_cg_lanczos...)) + return (solver.x, solver.stats) + end + + function cg_lanczos(A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgLanczosSolver(A, b) + cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + return (solver.x, solver.stats) + end + + function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + return solver + end end function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index cbe8d6bb5..4d96cce77 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -63,13 +63,6 @@ of size n. The method does _not_ abort if A + αI is not definite. """ function cg_lanczos_shift end -function cg_lanczos_shift(A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - nshifts = length(shifts) - solver = CgLanczosShiftSolver(A, b, nshifts) - cg_lanczos_shift!(solver, A, b, shifts; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cg_lanczos!(solver::CgLanczosShiftSolver, A, b, shifts; kwargs...) @@ -79,6 +72,31 @@ See [`CgLanczosShiftSolver`](@ref) for more details about the `solver`. """ function cg_lanczos_shift! end +def_kwargs_cg_lanczos_shift = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; check_curvature::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_cg_lanczos_shift = reduce(vcat, kw.args[1].args for kw in def_kwargs_cg_lanczos_shift) + +kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cg_lanczos_shift(A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + nshifts = length(shifts) + solver = CgLanczosShiftSolver(A, b, nshifts) + cg_lanczos_shift!(solver, A, b, shifts; $(kwargs_cg_lanczos_shift...)) + return (solver.x, solver.stats) + end +end + function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; M=I, ldiv :: Bool=false, check_curvature :: Bool=false, atol :: T=√eps(T), diff --git a/src/cgls.jl b/src/cgls.jl index 07da838ab..9d8a7604f 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -61,7 +61,7 @@ but simpler to implement. #### Keyword arguments -* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; * `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; * `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; * `λ`: regularization parameter; @@ -86,12 +86,6 @@ but simpler to implement. """ function cgls end -function cgls(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CglsSolver(A, b) - cgls!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cgls!(solver::CglsSolver, A, b; kwargs...) @@ -101,6 +95,31 @@ See [`CglsSolver`](@ref) for more details about the `solver`. """ function cgls! end +def_kwargs_cgls = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; radius::T = zero(T) ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_cgls = reduce(vcat, kw.args[1].args for kw in def_kwargs_cgls) + +kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cgls(A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CglsSolver(A, b) + cgls!(solver, A, b; $(kwargs_cgls...)) + return (solver.x, solver.stats) + end +end + function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), diff --git a/src/cgne.jl b/src/cgne.jl index 9245e04a1..5523f2c98 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -68,7 +68,7 @@ but simpler to implement. Only the x-part of the solution is returned. #### Keyword arguments -* `N`: +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; * `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; * `λ`: regularization parameter; * `atol`: absolute stopping tolerance based on the residual norm; @@ -92,12 +92,6 @@ but simpler to implement. Only the x-part of the solution is returned. """ function cgne end -function cgne(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CgneSolver(A, b) - cgne!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cgne!(solver::CgneSolver, A, b; kwargs...) @@ -107,6 +101,30 @@ See [`CgneSolver`](@ref) for more details about the `solver`. """ function cgne! end +def_kwargs_cgne = (:(; N = I ), + :(; ldiv::Bool = false ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_cgne = reduce(vcat, kw.args[1].args for kw in def_kwargs_cgne) + +kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cgne(A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgneSolver(A, b) + cgne!(solver, A, b; $(kwargs_cgne...)) + return (solver.x, solver.stats) + end +end + function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, ldiv :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), diff --git a/src/cgs.jl b/src/cgs.jl index 508f37cfc..8523d5be0 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -79,18 +79,6 @@ TFQMR and BICGSTAB were developed to remedy this difficulty.» """ function cgs end -function cgs(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = CgsSolver(A, b) - cgs!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function cgs(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CgsSolver(A, b) - cgs!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cgs!(solver::CgsSolver, A, b; kwargs...) solver = cgs!(solver::CgsSolver, A, b, x0; kwargs...) @@ -101,10 +89,41 @@ See [`CgsSolver`](@ref) for more details about the `solver`. """ function cgs! end -function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - cgs!(solver, A, b; kwargs...) - return solver +def_kwargs_cgs = (:(; c::AbstractVector{FC} = b ), + :(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_cgs = reduce(vcat, kw.args[1].args for kw in def_kwargs_cgs) + +kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cgs(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgsSolver(A, b) + cgs!(solver, A, b, x0; $(kwargs_cgs...)) + return (solver.x, solver.stats) + end + + function cgs(A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CgsSolver(A, b) + cgs!(solver, A, b; $(kwargs_cgs...)) + return (solver.x, solver.stats) + end + + function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + cgs!(solver, A, b; $(kwargs_cgs...)) + return solver + end end function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/cr.jl b/src/cr.jl index 7180f9201..38c2a712b 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -76,18 +76,6 @@ M also indicates the weighted norm in which residuals are measured. """ function cr end -function cr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = CrSolver(A, b) - cr!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function cr(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CrSolver(A, b) - cr!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = cr!(solver::CrSolver, A, b; kwargs...) solver = cr!(solver::CrSolver, A, b, x0; kwargs...) @@ -98,16 +86,48 @@ See [`CrSolver`](@ref) for more details about the `solver`. """ function cr! end -function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - cr!(solver, A, b; kwargs...) - return solver +def_kwargs_cr = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; radius::T = zero(T) ), + :(; linesearch::Bool = false ), + :(; γ::T = √eps(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_cr = reduce(vcat, kw.args[1].args for kw in def_kwargs_cr) + +kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function cr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CrSolver(A, b) + cr!(solver, A, b, x0; $(kwargs_cr...)) + return (solver.x, solver.stats) + end + + function cr(A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CrSolver(A, b) + cr!(solver, A, b; $(kwargs_cr...)) + return (solver.x, solver.stats) + end + + function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + cr!(solver, A, b; $(kwargs_cr...)) + return solver + end end function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), linesearch :: Bool=false, γ :: T=√eps(T), - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, + atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} diff --git a/src/craig.jl b/src/craig.jl index 2ee23b971..2ca8be2e0 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -125,12 +125,6 @@ In this implementation, both the x and y-parts of the solution are returned. """ function craig end -function craig(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CraigSolver(A, b) - craig!(solver, A, b; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = craig!(solver::CraigSolver, A, b; kwargs...) @@ -140,6 +134,35 @@ See [`CraigSolver`](@ref) for more details about the `solver`. """ function craig! end +def_kwargs_craig = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; transfer_to_lsqr::Bool = false), + :(; sqd::Bool = false ), + :(; λ::T = zero(T) ), + :(; btol::T = √eps(T) ), + :(; conlim::T = 1/√eps(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_craig = reduce(vcat, kw.args[1].args for kw in def_kwargs_craig) + +kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function craig(A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CraigSolver(A, b) + craig!(solver, A, b; $(kwargs_craig...)) + return (solver.x, solver.y, solver.stats) + end +end + function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, transfer_to_lsqr :: Bool=false, sqd :: Bool=false, diff --git a/src/craigmr.jl b/src/craigmr.jl index b95813d5b..87f0bb27a 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -117,12 +117,6 @@ returned. """ function craigmr end -function craigmr(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CraigmrSolver(A, b) - craigmr!(solver, A, b; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = craigmr!(solver::CraigmrSolver, A, b; kwargs...) @@ -132,6 +126,32 @@ See [`CraigmrSolver`](@ref) for more details about the `solver`. """ function craigmr! end +def_kwargs_craigmr = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; sqd::Bool = false ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_craigmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_craigmr) + +kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function craigmr(A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CraigmrSolver(A, b) + craigmr!(solver, A, b; $(kwargs_craigmr...)) + return (solver.x, solver.y, solver.stats) + end +end + function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), diff --git a/src/crls.jl b/src/crls.jl index e2061e84c..149baef6d 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -24,7 +24,8 @@ export crls, crls! (x, stats) = crls(A, b::AbstractVector{FC}; M=I, ldiv::Bool=false, radius::T=zero(T), λ::T=zero(T), atol::T=√eps(T), rtol::T=√eps(T), - itmax::Int=0, timemax::Float64=Inf, verbose::Int=0, history::Bool=false, + itmax::Int=0, timemax::Float64=Inf, + verbose::Int=0, history::Bool=false, callback=solver->false, iostream::IO=kstdout) `T` is an `AbstractFloat` such as `Float32`, `Float64` or `BigFloat`. @@ -52,7 +53,7 @@ but simpler to implement. #### Keyword arguments -* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; +* `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; * `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; * `radius`: add the trust-region constraint ‖x‖ ≤ `radius` if `radius > 0`. Useful to compute a step in a trust-region method for optimization; * `λ`: regularization parameter; @@ -76,12 +77,6 @@ but simpler to implement. """ function crls end -function crls(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CrlsSolver(A, b) - crls!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = crls!(solver::CrlsSolver, A, b; kwargs...) @@ -91,6 +86,31 @@ See [`CrlsSolver`](@ref) for more details about the `solver`. """ function crls! end +def_kwargs_crls = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; radius::T = zero(T) ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_crls = reduce(vcat, kw.args[1].args for kw in def_kwargs_crls) + +kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function crls(A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CrlsSolver(A, b) + crls!(solver, A, b; $(kwargs_crls...)) + return (solver.x, solver.stats) + end +end + function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, radius :: T=zero(T), λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), diff --git a/src/crmr.jl b/src/crmr.jl index 8addc803c..06c252b04 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -66,7 +66,7 @@ but simpler to implement. Only the x-part of the solution is returned. #### Keyword arguments -* `N`: +* `N`: linear operator that models a Hermitian positive-definite matrix of size `n` used for preconditioning; * `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; * `λ`: regularization parameter; * `atol`: absolute stopping tolerance based on the residual norm; @@ -90,12 +90,6 @@ but simpler to implement. Only the x-part of the solution is returned. """ function crmr end -function crmr(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = CrmrSolver(A, b) - crmr!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = crmr!(solver::CrmrSolver, A, b; kwargs...) @@ -105,6 +99,30 @@ See [`CrmrSolver`](@ref) for more details about the `solver`. """ function crmr! end +def_kwargs_crmr = (:(; N = I ), + :(; ldiv::Bool = false ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_crmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_crmr) + +kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function crmr(A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = CrmrSolver(A, b) + crmr!(solver, A, b; $(kwargs_crmr...)) + return (solver.x, solver.stats) + end +end + function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; N=I, ldiv :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), diff --git a/src/diom.jl b/src/diom.jl index c6ce56eb1..8c5eff703 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -71,18 +71,6 @@ and indefinite systems of linear equations can be handled by this single algorit """ function diom end -function diom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = DiomSolver(A, b, memory) - diom!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function diom(A, b :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = DiomSolver(A, b, memory) - diom!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = diom!(solver::DiomSolver, A, b; kwargs...) solver = diom!(solver::DiomSolver, A, b, x0; kwargs...) @@ -96,10 +84,41 @@ See [`DiomSolver`](@ref) for more details about the `solver`. """ function diom! end -function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - diom!(solver, A, b; kwargs...) - return solver +def_kwargs_diom = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; reorthogonalization::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_diom = reduce(vcat, kw.args[1].args for kw in def_kwargs_diom) + +kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function diom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = DiomSolver(A, b, memory) + diom!(solver, A, b, x0; $(kwargs_diom...)) + return (solver.x, solver.stats) + end + + function diom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = DiomSolver(A, b, memory) + diom!(solver, A, b; $(kwargs_diom...)) + return (solver.x, solver.stats) + end + + function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + diom!(solver, A, b; $(kwargs_diom...)) + return solver + end end function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 785324c96..a76c3bac3 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -71,18 +71,6 @@ Otherwise, DQGMRES interpolates between MINRES and GMRES and is similar to MINRE """ function dqgmres end -function dqgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = DqgmresSolver(A, b, memory) - dqgmres!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function dqgmres(A, b :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = DqgmresSolver(A, b, memory) - dqgmres!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = dqgmres!(solver::DqgmresSolver, A, b; kwargs...) solver = dqgmres!(solver::DqgmresSolver, A, b, x0; kwargs...) @@ -96,10 +84,41 @@ See [`DqgmresSolver`](@ref) for more details about the `solver`. """ function dqgmres! end -function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - dqgmres!(solver, A, b; kwargs...) - return solver +def_kwargs_dqgmres = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; reorthogonalization::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_dqgmres = reduce(vcat, kw.args[1].args for kw in def_kwargs_dqgmres) + +kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function dqgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = DqgmresSolver(A, b, memory) + dqgmres!(solver, A, b, x0; $(kwargs_dqgmres...)) + return (solver.x, solver.stats) + end + + function dqgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = DqgmresSolver(A, b, memory) + dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + return (solver.x, solver.stats) + end + + function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + return solver + end end function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/fgmres.jl b/src/fgmres.jl index ce4a5df82..d571007a5 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -73,18 +73,6 @@ Thus, GMRES is recommended if the right preconditioner N is constant. """ function fgmres end -function fgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = FgmresSolver(A, b, memory) - fgmres!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = FgmresSolver(A, b, memory) - fgmres!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = fgmres!(solver::FgmresSolver, A, b; kwargs...) solver = fgmres!(solver::FgmresSolver, A, b, x0; kwargs...) @@ -98,10 +86,42 @@ See [`FgmresSolver`](@ref) for more details about the `solver`. """ function fgmres! end -function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - fgmres!(solver, A, b; kwargs...) - return solver +def_kwargs_fgmres = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; restart::Bool = false ), + :(; reorthogonalization::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_fgmres = reduce(vcat, kw.args[1].args for kw in def_kwargs_fgmres) + +kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function fgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = FgmresSolver(A, b, memory) + fgmres!(solver, A, b, x0; $(kwargs_fgmres...)) + return (solver.x, solver.stats) + end + + function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = FgmresSolver(A, b, memory) + fgmres!(solver, A, b; $(kwargs_fgmres...)) + return (solver.x, solver.stats) + end + + function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + fgmres!(solver, A, b; $(kwargs_fgmres...)) + return solver + end end function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/fom.jl b/src/fom.jl index c87774ae3..c4d8540d9 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -66,18 +66,6 @@ FOM algorithm is based on the Arnoldi process and a Galerkin condition. """ function fom end -function fom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = FomSolver(A, b, memory) - fom!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function fom(A, b :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = FomSolver(A, b, memory) - fom!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = fom!(solver::FomSolver, A, b; kwargs...) solver = fom!(solver::FomSolver, A, b, x0; kwargs...) @@ -91,10 +79,42 @@ See [`FomSolver`](@ref) for more details about the `solver`. """ function fom! end -function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - fom!(solver, A, b; kwargs...) - return solver +def_kwargs_fom = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; restart::Bool = false ), + :(; reorthogonalization::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_fom = reduce(vcat, kw.args[1].args for kw in def_kwargs_fom) + +kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function fom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = FomSolver(A, b, memory) + fom!(solver, A, b, x0; $(kwargs_fom...)) + return (solver.x, solver.stats) + end + + function fom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = FomSolver(A, b, memory) + fom!(solver, A, b; $(kwargs_fom...)) + return (solver.x, solver.stats) + end + + function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + fom!(solver, A, b; $(kwargs_fom...)) + return solver + end end function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/gmres.jl b/src/gmres.jl index 5653b5b43..ecaf0a80e 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -66,18 +66,6 @@ GMRES algorithm is based on the Arnoldi process and computes a sequence of appro """ function gmres end -function gmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = GmresSolver(A, b, memory) - gmres!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function gmres(A, b :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = GmresSolver(A, b, memory) - gmres!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = gmres!(solver::GmresSolver, A, b; kwargs...) solver = gmres!(solver::GmresSolver, A, b, x0; kwargs...) @@ -91,10 +79,42 @@ See [`GmresSolver`](@ref) for more details about the `solver`. """ function gmres! end -function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - gmres!(solver, A, b; kwargs...) - return solver +def_kwargs_gmres = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; restart::Bool = false ), + :(; reorthogonalization::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_gmres = reduce(vcat, kw.args[1].args for kw in def_kwargs_gmres) + +kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function gmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = GmresSolver(A, b, memory) + gmres!(solver, A, b, x0; $(kwargs_gmres...)) + return (solver.x, solver.stats) + end + + function gmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = GmresSolver(A, b, memory) + gmres!(solver, A, b; $(kwargs_gmres...)) + return (solver.x, solver.stats) + end + + function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + gmres!(solver, A, b; $(kwargs_gmres...)) + return solver + end end function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/gpmr.jl b/src/gpmr.jl index 42d4c36cb..5dc0bd5fe 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -102,18 +102,6 @@ GPMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + """ function gpmr end -function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = GpmrSolver(A, b, memory) - gpmr!(solver, A, B, b, c, x0, y0; kwargs...) - return (solver.x, solver.y, solver.stats) -end - -function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; memory :: Int=20, kwargs...) where FC <: FloatOrComplex - solver = GpmrSolver(A, b, memory) - gpmr!(solver, A, B, b, c; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = gpmr!(solver::GpmrSolver, A, B, b, c; kwargs...) solver = gpmr!(solver::GpmrSolver, A, B, b, c, x0, y0; kwargs...) @@ -127,11 +115,47 @@ See [`GpmrSolver`](@ref) for more details about the `solver`. """ function gpmr! end -function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0, y0) - gpmr!(solver, A, B, b, c; kwargs...) - return solver +def_kwargs_gpmr = (:(; C = I ), + :(; D = I ), + :(; E = I ), + :(; F = I ), + :(; ldiv::Bool = false ), + :(; gsp::Bool = false ), + :(; λ::FC = one(FC) ), + :(; μ::FC = one(FC) ), + :(; reorthogonalization::Bool = false), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_gpmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_gpmr) + +kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = GpmrSolver(A, b, memory) + gpmr!(solver, A, B, b, c, x0, y0; $(kwargs_gpmr...)) + return (solver.x, solver.y, solver.stats) + end + + function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = GpmrSolver(A, b, memory) + gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + return (solver.x, solver.y, solver.stats) + end + + function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0, y0) + gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + return solver + end end function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; diff --git a/src/lnlq.jl b/src/lnlq.jl index bfeb26b70..ab05b942b 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -120,12 +120,6 @@ For instance σ:=(1-1e-7)σₘᵢₙ . """ function lnlq end -function lnlq(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = LnlqSolver(A, b) - lnlq!(solver, A, b; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = lnlq!(solver::LnlqSolver, A, b; kwargs...) @@ -135,6 +129,36 @@ See [`LnlqSolver`](@ref) for more details about the `solver`. """ function lnlq! end +def_kwargs_lnlq = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; transfer_to_craig::Bool = true), + :(; sqd::Bool = false ), + :(; λ::T = zero(T) ), + :(; σ::T = zero(T) ), + :(; utolx::T = √eps(T) ), + :(; utoly::T = √eps(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_lnlq = reduce(vcat, kw.args[1].args for kw in def_kwargs_lnlq) + +kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function lnlq(A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = LnlqSolver(A, b) + lnlq!(solver, A, b; $(kwargs_lnlq...)) + return (solver.x, solver.y, solver.stats) + end +end + function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, transfer_to_craig :: Bool=true, diff --git a/src/lslq.jl b/src/lslq.jl index 5aa887536..c5f509eb2 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -144,12 +144,6 @@ The iterations stop as soon as one of the following conditions holds true: """ function lslq end -function lslq(A, b :: AbstractVector{FC}; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = LslqSolver(A, b, window=window) - lslq!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = lslq!(solver::LslqSolver, A, b; kwargs...) @@ -159,6 +153,38 @@ See [`LslqSolver`](@ref) for more details about the `solver`. """ function lslq! end +def_kwargs_lslq = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; transfer_to_lsqr::Bool = false), + :(; sqd::Bool = false ), + :(; λ::T = zero(T) ), + :(; σ::T = zero(T) ), + :(; etol::T = √eps(T) ), + :(; utol::T = √eps(T) ), + :(; btol::T = √eps(T) ), + :(; conlim::T = 1/√eps(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_lslq = reduce(vcat, kw.args[1].args for kw in def_kwargs_lslq) + +kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = LslqSolver(A, b, window=window) + lslq!(solver, A, b; $(kwargs_lslq...)) + return (solver.x, solver.stats) + end +end + function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, transfer_to_lsqr :: Bool=false, diff --git a/src/lsmr.jl b/src/lsmr.jl index 459a7d855..fb995888c 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -122,12 +122,6 @@ In this case, `N` can still be specified and indicates the weighted norm in whic """ function lsmr end -function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = LsmrSolver(A, b, window=window) - lsmr!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = lsmr!(solver::LsmrSolver, A, b; kwargs...) @@ -137,6 +131,37 @@ See [`LsmrSolver`](@ref) for more details about the `solver`. """ function lsmr! end +def_kwargs_lsmr = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; sqd::Bool = false ), + :(; λ::T = zero(T) ), + :(; radius::T = zero(T) ), + :(; etol::T = √eps(T) ), + :(; axtol::T = √eps(T) ), + :(; btol::T = √eps(T) ), + :(; conlim::T = 1/√eps(T) ), + :(; atol::T = zero(T) ), + :(; rtol::T = zero(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_lsmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_lsmr) + +kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = LsmrSolver(A, b, window=window) + lsmr!(solver, A, b; $(kwargs_lsmr...)) + return (solver.x, solver.stats) + end +end + function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, sqd :: Bool=false, λ :: T=zero(T), diff --git a/src/lsqr.jl b/src/lsqr.jl index f98e1d80e..de3ff1c9b 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -118,12 +118,6 @@ In this case, `N` can still be specified and indicates the weighted norm in whic """ function lsqr end -function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = LsqrSolver(A, b, window=window) - lsqr!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = lsqr!(solver::LsqrSolver, A, b; kwargs...) @@ -133,6 +127,37 @@ See [`LsqrSolver`](@ref) for more details about the `solver`. """ function lsqr! end +def_kwargs_lsqr = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; sqd::Bool = false ), + :(; λ::T = zero(T) ), + :(; radius::T = zero(T) ), + :(; etol::T = √eps(T) ), + :(; axtol::T = √eps(T) ), + :(; btol::T = √eps(T) ), + :(; conlim::T = 1/√eps(T) ), + :(; atol::T = zero(T) ), + :(; rtol::T = zero(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_lsqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_lsqr) + +kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = LsqrSolver(A, b, window=window) + lsqr!(solver, A, b; $(kwargs_lsqr...)) + return (solver.x, solver.stats) + end +end + function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, N=I, ldiv :: Bool=false, sqd :: Bool=false, λ :: T=zero(T), diff --git a/src/minres.jl b/src/minres.jl index 91041ef47..3ed91defe 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -91,18 +91,6 @@ MINRES produces monotonic residuals ‖r‖₂ and optimality residuals ‖Aᴴr """ function minres end -function minres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = MinresSolver(A, b, window=window) - minres!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function minres(A, b :: AbstractVector{FC}; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = MinresSolver(A, b, window=window) - minres!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = minres!(solver::MinresSolver, A, b; kwargs...) solver = minres!(solver::MinresSolver, A, b, x0; kwargs...) @@ -113,10 +101,42 @@ See [`MinresSolver`](@ref) for more details about the `solver`. """ function minres! end -function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - minres!(solver, A, b; kwargs...) - return solver +def_kwargs_minres = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; etol::T = √eps(T) ), + :(; conlim::T = 1/√eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_minres = reduce(vcat, kw.args[1].args for kw in def_kwargs_minres) + +kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function minres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = MinresSolver(A, b, window=window) + minres!(solver, A, b, x0; $(kwargs_minres...)) + return (solver.x, solver.stats) + end + + function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = MinresSolver(A, b, window=window) + minres!(solver, A, b; $(kwargs_minres...)) + return (solver.x, solver.stats) + end + + function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + minres!(solver, A, b; $(kwargs_minres...)) + return solver + end end function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index ca40d0629..da0e60089 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -50,10 +50,10 @@ M also indicates the weighted norm in which residuals are measured. * `M`: linear operator that models a Hermitian positive-definite matrix of size `n` used for centered preconditioning; * `ldiv`: define whether the preconditioner uses `ldiv!` or `mul!`; -* `Artol`: relative stopping tolerance based on the Aᴴ-residual norm; * `λ`: regularization parameter; * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; +* `Artol`: relative stopping tolerance based on the Aᴴ-residual norm; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; * `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; @@ -74,18 +74,6 @@ M also indicates the weighted norm in which residuals are measured. """ function minres_qlp end -function minres_qlp(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = MinresQlpSolver(A, b) - minres_qlp!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function minres_qlp(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = MinresQlpSolver(A, b) - minres_qlp!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = minres_qlp!(solver::MinresQlpSolver, A, b; kwargs...) solver = minres_qlp!(solver::MinresQlpSolver, A, b, x0; kwargs...) @@ -96,16 +84,46 @@ See [`MinresQlpSolver`](@ref) for more details about the `solver`. """ function minres_qlp! end -function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - minres_qlp!(solver, A, b; kwargs...) - return solver +def_kwargs_minres_qlp = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; λ::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; Artol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_minres_qlp = reduce(vcat, kw.args[1].args for kw in def_kwargs_minres_qlp) + +kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function minres_qlp(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = MinresQlpSolver(A, b) + minres_qlp!(solver, A, b, x0; $(kwargs_minres_qlp...)) + return (solver.x, solver.stats) + end + + function minres_qlp(A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = MinresQlpSolver(A, b) + minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + return (solver.x, solver.stats) + end + + function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + return solver + end end function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, Artol :: T=√eps(T), - λ ::T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, + M=I, ldiv :: Bool=false, λ :: T=zero(T),atol :: T=√eps(T), + rtol :: T=√eps(T), Artol :: T=√eps(T), itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} diff --git a/src/qmr.jl b/src/qmr.jl index 24d2c1fde..eca87aa62 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -73,18 +73,6 @@ When `A` is Hermitian and `b = c`, QMR is equivalent to MINRES. """ function qmr end -function qmr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = QmrSolver(A, b) - qmr!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function qmr(A, b :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = QmrSolver(A, b) - qmr!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = qmr!(solver::QmrSolver, A, b; kwargs...) solver = qmr!(solver::QmrSolver, A, b, x0; kwargs...) @@ -95,15 +83,43 @@ See [`QmrSolver`](@ref) for more details about the `solver`. """ function qmr! end -function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - qmr!(solver, A, b; kwargs...) - return solver +def_kwargs_qmr = (:(; c::AbstractVector{FC} = b ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_qmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_qmr) + +kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function qmr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = QmrSolver(A, b) + qmr!(solver, A, b, x0; $(kwargs_qmr...)) + return (solver.x, solver.stats) + end + + function qmr(A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = QmrSolver(A, b) + qmr!(solver, A, b; $(kwargs_qmr...)) + return (solver.x, solver.stats) + end + + function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + qmr!(solver, A, b; $(kwargs_qmr...)) + return solver + end end -function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; c :: AbstractVector{FC}=b, - atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, timemax :: Float64=Inf, +function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; + c :: AbstractVector{FC}=b, atol :: T=√eps(T), + rtol :: T=√eps(T), itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} diff --git a/src/symmlq.jl b/src/symmlq.jl index b85f39adb..9b762a643 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -53,10 +53,10 @@ SYMMLQ produces monotonic errors ‖x* - x‖₂. * `transfer_to_cg`: transfer from the SYMMLQ point to the CG point, when it exists. The transfer is based on the residual norm; * `λ`: regularization parameter; * `λest`: positive strict lower bound on the smallest eigenvalue `λₘᵢₙ` when solving a positive-definite system, such as `λest = (1-10⁻⁷)λₘᵢₙ`; -* `etol`: stopping tolerance based on the lower bound on the error; -* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; * `atol`: absolute stopping tolerance based on the residual norm; * `rtol`: relative stopping tolerance based on the residual norm; +* `etol`: stopping tolerance based on the lower bound on the error; +* `conlim`: limit on the estimated condition number of `A` beyond which the solution will be abandoned; * `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`; * `timemax`: the time limit in seconds; * `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations; @@ -75,18 +75,6 @@ SYMMLQ produces monotonic errors ‖x* - x‖₂. """ function symmlq end -function symmlq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = SymmlqSolver(A, b, window=window) - symmlq!(solver, A, b, x0; kwargs...) - return (solver.x, solver.stats) -end - -function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, kwargs...) where FC <: FloatOrComplex - solver = SymmlqSolver(A, b, window=window) - symmlq!(solver, A, b; kwargs...) - return (solver.x, solver.stats) -end - """ solver = symmlq!(solver::SymmlqSolver, A, b; kwargs...) solver = symmlq!(solver::SymmlqSolver, A, b, x0; kwargs...) @@ -97,18 +85,52 @@ See [`SymmlqSolver`](@ref) for more details about the `solver`. """ function symmlq! end -function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - symmlq!(solver, A, b; kwargs...) - return solver +def_kwargs_symmlq = (:(; M = I ), + :(; ldiv::Bool = false ), + :(; transfer_to_cg::Bool = true), + :(; λ::T = zero(T) ), + :(; λest::T = zero(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; etol::T = √eps(T) ), + :(; conlim::T = 1/√eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_symmlq = reduce(vcat, kw.args[1].args for kw in def_kwargs_symmlq) + +kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function symmlq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = SymmlqSolver(A, b, window=window) + symmlq!(solver, A, b, x0; $(kwargs_symmlq...)) + return (solver.x, solver.stats) + end + + function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = SymmlqSolver(A, b, window=window) + symmlq!(solver, A, b; $(kwargs_symmlq...)) + return (solver.x, solver.stats) + end + + function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + symmlq!(solver, A, b; $(kwargs_symmlq...)) + return solver + end end function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; M=I, ldiv :: Bool=false, transfer_to_cg :: Bool=true, λ :: T=zero(T), - λest :: T=zero(T), etol :: T=√eps(T), - conlim :: T=1/√eps(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, + λest :: T=zero(T), atol :: T=√eps(T), + rtol :: T=√eps(T), etol :: T=√eps(T), + conlim :: T=1/√eps(T), itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} diff --git a/src/tricg.jl b/src/tricg.jl index 93769da44..4d7264b20 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -93,18 +93,6 @@ TriCG stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + """ function tricg end -function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = TricgSolver(A, b) - tricg!(solver, A, b, c, x0, y0; kwargs...) - return (solver.x, solver.y, solver.stats) -end - -function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = TricgSolver(A, b) - tricg!(solver, A, b, c; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = tricg!(solver::TricgSolver, A, b, c; kwargs...) solver = tricg!(solver::TricgSolver, A, b, c, x0, y0; kwargs...) @@ -115,11 +103,46 @@ See [`TricgSolver`](@ref) for more details about the `solver`. """ function tricg! end -function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0, y0) - tricg!(solver, A, b, c; kwargs...) - return solver +def_kwargs_tricg = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; spd::Bool = false ), + :(; snd::Bool = false ), + :(; flip::Bool = false ), + :(; τ::T = one(T) ), + :(; ν::T = -one(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_tricg = reduce(vcat, kw.args[1].args for kw in def_kwargs_tricg) + +kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = TricgSolver(A, b) + tricg!(solver, A, b, c, x0, y0; $(kwargs_tricg...)) + return (solver.x, solver.y, solver.stats) + end + + function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = TricgSolver(A, b) + tricg!(solver, A, b, c; $(kwargs_tricg...)) + return (solver.x, solver.y, solver.stats) + end + + function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0, y0) + tricg!(solver, A, b, c; $(kwargs_tricg...)) + return solver + end end function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; diff --git a/src/trilqr.jl b/src/trilqr.jl index 8be5ac68f..921a38607 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -69,18 +69,6 @@ USYMQR is used for solving dual system `Aᴴy = c` of size n × m. """ function trilqr end -function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = TrilqrSolver(A, b) - trilqr!(solver, A, b, c, x0, y0; kwargs...) - return (solver.x, solver.y, solver.stats) -end - -function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = TrilqrSolver(A, b) - trilqr!(solver, A, b, c; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = trilqr!(solver::TrilqrSolver, A, b, c; kwargs...) solver = trilqr!(solver::TrilqrSolver, A, b, c, x0, y0; kwargs...) @@ -91,11 +79,39 @@ See [`TrilqrSolver`](@ref) for more details about the `solver`. """ function trilqr! end -function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0, y0) - trilqr!(solver, A, b, c; kwargs...) - return solver +def_kwargs_trilqr = (:(; transfer_to_usymcg::Bool = true), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_trilqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_trilqr) + +kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = TrilqrSolver(A, b) + trilqr!(solver, A, b, c, x0, y0; $(kwargs_trilqr...)) + return (solver.x, solver.y, solver.stats) + end + + function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = TrilqrSolver(A, b) + trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + return (solver.x, solver.y, solver.stats) + end + + function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0, y0) + trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + return solver + end end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; diff --git a/src/trimr.jl b/src/trimr.jl index 42e17733d..bd831ce36 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -93,18 +93,6 @@ TriMR stops when `itmax` iterations are reached or when `‖rₖ‖ ≤ atol + """ function trimr end -function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = TrimrSolver(A, b) - trimr!(solver, A, b, c, x0, y0; kwargs...) - return (solver.x, solver.y, solver.stats) -end - -function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = TrimrSolver(A, b) - trimr!(solver, A, b, c; kwargs...) - return (solver.x, solver.y, solver.stats) -end - """ solver = trimr!(solver::TrimrSolver, A, b, c; kwargs...) solver = trimr!(solver::TrimrSolver, A, b, c, x0, y0; kwargs...) @@ -115,11 +103,47 @@ See [`TrimrSolver`](@ref) for more details about the `solver`. """ function trimr! end -function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0, y0) - trimr!(solver, A, b, c; kwargs...) - return solver +def_kwargs_trimr = (:(; M = I ), + :(; N = I ), + :(; ldiv::Bool = false ), + :(; spd::Bool = false ), + :(; snd::Bool = false ), + :(; flip::Bool = false ), + :(; sp::Bool = false ), + :(; τ::T = one(T) ), + :(; ν::T = -one(T) ), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_trimr = reduce(vcat, kw.args[1].args for kw in def_kwargs_trimr) + +kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = TrimrSolver(A, b) + trimr!(solver, A, b, c, x0, y0; $(kwargs_trimr...)) + return (solver.x, solver.y, solver.stats) + end + + function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = TrimrSolver(A, b) + trimr!(solver, A, b, c; $(kwargs_trimr...)) + return (solver.x, solver.y, solver.stats) + end + + function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0, y0) + trimr!(solver, A, b, c; $(kwargs_trimr...)) + return solver + end end function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; diff --git a/src/usymlq.jl b/src/usymlq.jl index 2421eecd7..4c659601a 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -78,18 +78,6 @@ In all cases, problems must be consistent. """ function usymlq end -function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = UsymlqSolver(A, b) - usymlq!(solver, A, b, c, x0; kwargs...) - return (solver.x, solver.stats) -end - -function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = UsymlqSolver(A, b) - usymlq!(solver, A, b, c; kwargs...) - return (solver.x, solver.stats) -end - """ solver = usymlq!(solver::UsymlqSolver, A, b, c; kwargs...) solver = usymlq!(solver::UsymlqSolver, A, b, c, x0; kwargs...) @@ -100,11 +88,39 @@ See [`UsymlqSolver`](@ref) for more details about the `solver`. """ function usymlq! end -function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - usymlq!(solver, A, b, c; kwargs...) - return solver +def_kwargs_usymlq = (:(; transfer_to_usymcg::Bool = true), + :(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false ), + :(; iostream::IO = kstdout )) + +def_kwargs_usymlq = reduce(vcat, kw.args[1].args for kw in def_kwargs_usymlq) + +kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = UsymlqSolver(A, b) + usymlq!(solver, A, b, c, x0; $(kwargs_usymlq...)) + return (solver.x, solver.stats) + end + + function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = UsymlqSolver(A, b) + usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + return (solver.x, solver.stats) + end + + function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + return solver + end end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; diff --git a/src/usymqr.jl b/src/usymqr.jl index 5b170d170..048f9b347 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -77,18 +77,6 @@ USYMQR finds the minimum-norm solution if problems are inconsistent. """ function usymqr end -function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; kwargs...) where FC <: FloatOrComplex - solver = UsymqrSolver(A, b) - usymqr!(solver, A, b, c, x0; kwargs...) - return (solver.x, solver.stats) -end - -function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; kwargs...) where FC <: FloatOrComplex - solver = UsymqrSolver(A, b) - usymqr!(solver, A, b, c; kwargs...) - return (solver.x, solver.stats) -end - """ solver = usymqr!(solver::UsymqrSolver, A, b, c; kwargs...) solver = usymqr!(solver::UsymqrSolver, A, b, c, x0; kwargs...) @@ -99,11 +87,38 @@ See [`UsymqrSolver`](@ref) for more details about the `solver`. """ function usymqr! end -function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector; kwargs...) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - warm_start!(solver, x0) - usymqr!(solver, A, b, c; kwargs...) - return solver +def_kwargs_usymqr = (:(; atol::T = √eps(T) ), + :(; rtol::T = √eps(T) ), + :(; itmax::Int = 0 ), + :(; timemax::Float64 = Inf ), + :(; verbose::Int = 0 ), + :(; history::Bool = false ), + :(; callback = solver -> false), + :(; iostream::IO = kstdout )) + +def_kwargs_usymqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_usymqr) + +kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) + +@eval begin + function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = UsymqrSolver(A, b) + usymqr!(solver, A, b, c, x0; $(kwargs_usymqr...)) + return (solver.x, solver.stats) + end + + function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + solver = UsymqrSolver(A, b) + usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + return (solver.x, solver.stats) + end + + function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, + x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + warm_start!(solver, x0) + usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + return solver + end end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; From 2f53b6e48044a5955a3459b45192909d6e94fd02 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 9 May 2023 20:00:34 -0400 Subject: [PATCH 154/182] Add an extract_parameters function --- src/bicgstab.jl | 2 +- src/bilq.jl | 2 +- src/bilqr.jl | 2 +- src/cg.jl | 2 +- src/cg_lanczos.jl | 2 +- src/cg_lanczos_shift.jl | 2 +- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 2 +- src/cr.jl | 2 +- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 2 +- src/krylov_utils.jl | 13 +++++++++++++ src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 2 +- src/symmlq.jl | 2 +- src/tricg.jl | 2 +- src/trilqr.jl | 2 +- src/trimr.jl | 2 +- src/usymlq.jl | 2 +- src/usymqr.jl | 2 +- 34 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index abf76ac88..333396625 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -101,7 +101,7 @@ def_kwargs_bicgstab = (:(; c::AbstractVector{FC} = b ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_bicgstab = reduce(vcat, kw.args[1].args for kw in def_kwargs_bicgstab) +def_kwargs_bicgstab = mapreduce(extract_parameters, vcat, def_kwargs_bicgstab) kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/bilq.jl b/src/bilq.jl index d54720fad..727651904 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -86,7 +86,7 @@ def_kwargs_bilq = (:(; c::AbstractVector{FC} = b ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_bilq = reduce(vcat, kw.args[1].args for kw in def_kwargs_bilq) +def_kwargs_bilq = mapreduce(extract_parameters, vcat, def_kwargs_bilq) kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/bilqr.jl b/src/bilqr.jl index c2942dbea..57b9ff206 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -90,7 +90,7 @@ def_kwargs_bilqr = (:(; transfer_to_bicg::Bool = true), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_bilqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_bilqr) +def_kwargs_bilqr = mapreduce(extract_parameters, vcat, def_kwargs_bilqr) kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cg.jl b/src/cg.jl index bed93fe58..0b02aae99 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -93,7 +93,7 @@ def_kwargs_cg = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_cg = reduce(vcat, kw.args[1].args for kw in def_kwargs_cg) +def_kwargs_cg = mapreduce(extract_parameters, vcat, def_kwargs_cg) kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index f2cd5afc6..8f1f98ae9 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -89,7 +89,7 @@ def_kwargs_cg_lanczos = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_cg_lanczos = reduce(vcat, kw.args[1].args for kw in def_kwargs_cg_lanczos) +def_kwargs_cg_lanczos = mapreduce(extract_parameters, vcat, def_kwargs_cg_lanczos) kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 4d96cce77..228fe705c 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -84,7 +84,7 @@ def_kwargs_cg_lanczos_shift = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_cg_lanczos_shift = reduce(vcat, kw.args[1].args for kw in def_kwargs_cg_lanczos_shift) +def_kwargs_cg_lanczos_shift = mapreduce(extract_parameters, vcat, def_kwargs_cg_lanczos_shift) kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cgls.jl b/src/cgls.jl index 9d8a7604f..223389cd8 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -108,7 +108,7 @@ def_kwargs_cgls = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_cgls = reduce(vcat, kw.args[1].args for kw in def_kwargs_cgls) +def_kwargs_cgls = mapreduce(extract_parameters, vcat, def_kwargs_cgls) kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cgne.jl b/src/cgne.jl index 5523f2c98..0f80b2698 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -113,7 +113,7 @@ def_kwargs_cgne = (:(; N = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_cgne = reduce(vcat, kw.args[1].args for kw in def_kwargs_cgne) +def_kwargs_cgne = mapreduce(extract_parameters, vcat, def_kwargs_cgne) kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cgs.jl b/src/cgs.jl index 8523d5be0..d5e937f9d 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -102,7 +102,7 @@ def_kwargs_cgs = (:(; c::AbstractVector{FC} = b ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_cgs = reduce(vcat, kw.args[1].args for kw in def_kwargs_cgs) +def_kwargs_cgs = mapreduce(extract_parameters, vcat, def_kwargs_cgs) kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/cr.jl b/src/cr.jl index 38c2a712b..bd4493d31 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -100,7 +100,7 @@ def_kwargs_cr = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_cr = reduce(vcat, kw.args[1].args for kw in def_kwargs_cr) +def_kwargs_cr = mapreduce(extract_parameters, vcat, def_kwargs_cr) kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/craig.jl b/src/craig.jl index 2ca8be2e0..9a1aa3276 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -151,7 +151,7 @@ def_kwargs_craig = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_craig = reduce(vcat, kw.args[1].args for kw in def_kwargs_craig) +def_kwargs_craig = mapreduce(extract_parameters, vcat, def_kwargs_craig) kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/craigmr.jl b/src/craigmr.jl index 87f0bb27a..03b9c1b1a 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -140,7 +140,7 @@ def_kwargs_craigmr = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_craigmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_craigmr) +def_kwargs_craigmr = mapreduce(extract_parameters, vcat, def_kwargs_craigmr) kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/crls.jl b/src/crls.jl index 149baef6d..9d9b90306 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -99,7 +99,7 @@ def_kwargs_crls = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_crls = reduce(vcat, kw.args[1].args for kw in def_kwargs_crls) +def_kwargs_crls = mapreduce(extract_parameters, vcat, def_kwargs_crls) kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/crmr.jl b/src/crmr.jl index 06c252b04..6fca81afd 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -111,7 +111,7 @@ def_kwargs_crmr = (:(; N = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_crmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_crmr) +def_kwargs_crmr = mapreduce(extract_parameters, vcat, def_kwargs_crmr) kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/diom.jl b/src/diom.jl index 8c5eff703..481345cdd 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -97,7 +97,7 @@ def_kwargs_diom = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_diom = reduce(vcat, kw.args[1].args for kw in def_kwargs_diom) +def_kwargs_diom = mapreduce(extract_parameters, vcat, def_kwargs_diom) kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/dqgmres.jl b/src/dqgmres.jl index a76c3bac3..7f7fff622 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -97,7 +97,7 @@ def_kwargs_dqgmres = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_dqgmres = reduce(vcat, kw.args[1].args for kw in def_kwargs_dqgmres) +def_kwargs_dqgmres = mapreduce(extract_parameters, vcat, def_kwargs_dqgmres) kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/fgmres.jl b/src/fgmres.jl index d571007a5..a990e2636 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -100,7 +100,7 @@ def_kwargs_fgmres = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_fgmres = reduce(vcat, kw.args[1].args for kw in def_kwargs_fgmres) +def_kwargs_fgmres = mapreduce(extract_parameters, vcat, def_kwargs_fgmres) kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/fom.jl b/src/fom.jl index c4d8540d9..8836f7d8b 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -93,7 +93,7 @@ def_kwargs_fom = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_fom = reduce(vcat, kw.args[1].args for kw in def_kwargs_fom) +def_kwargs_fom = mapreduce(extract_parameters, vcat, def_kwargs_fom) kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/gmres.jl b/src/gmres.jl index ecaf0a80e..0eb4c55e6 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -93,7 +93,7 @@ def_kwargs_gmres = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_gmres = reduce(vcat, kw.args[1].args for kw in def_kwargs_gmres) +def_kwargs_gmres = mapreduce(extract_parameters, vcat, def_kwargs_gmres) kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/gpmr.jl b/src/gpmr.jl index 5dc0bd5fe..3726709cf 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -133,7 +133,7 @@ def_kwargs_gpmr = (:(; C = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_gpmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_gpmr) +def_kwargs_gpmr = mapreduce(extract_parameters, vcat, def_kwargs_gpmr) kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index ebacc7a74..54497d2da 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -394,3 +394,16 @@ function to_boundary(n :: Int, x :: AbstractVector{FC}, d :: AbstractVector{FC}, roots = roots_quadratic(dNorm2, 2 * rxd, xNorm2 - radius2) return roots # `σ1` and `σ2` end + +""" + arguments = extract_parameters(ex::Expr) + +Extract the arguments of an expression that is keyword parameter tuple. +Implementation suggested by Mitchell J. O'Sullivan (@mosullivan93). +""" +function extract_parameters(ex::Expr) + Meta.isexpr(ex, :tuple, 1) && + Meta.isexpr((@inbounds p = ex.args[1]), :parameters) && + all(Base.Docs.validcall, p.args) || throw(ArgumentError("Given expression is not a kw parameter tuple [e.g. :(; x)]: $ex")) + return p.args +end diff --git a/src/lnlq.jl b/src/lnlq.jl index ab05b942b..047ad82aa 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -147,7 +147,7 @@ def_kwargs_lnlq = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_lnlq = reduce(vcat, kw.args[1].args for kw in def_kwargs_lnlq) +def_kwargs_lnlq = mapreduce(extract_parameters, vcat, def_kwargs_lnlq) kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/lslq.jl b/src/lslq.jl index c5f509eb2..e596102f6 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -173,7 +173,7 @@ def_kwargs_lslq = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_lslq = reduce(vcat, kw.args[1].args for kw in def_kwargs_lslq) +def_kwargs_lslq = mapreduce(extract_parameters, vcat, def_kwargs_lslq) kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/lsmr.jl b/src/lsmr.jl index fb995888c..fc612a8f9 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -150,7 +150,7 @@ def_kwargs_lsmr = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_lsmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_lsmr) +def_kwargs_lsmr = mapreduce(extract_parameters, vcat, def_kwargs_lsmr) kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/lsqr.jl b/src/lsqr.jl index de3ff1c9b..7a9294449 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -146,7 +146,7 @@ def_kwargs_lsqr = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_lsqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_lsqr) +def_kwargs_lsqr = mapreduce(extract_parameters, vcat, def_kwargs_lsqr) kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/minres.jl b/src/minres.jl index 3ed91defe..7a49571c2 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -115,7 +115,7 @@ def_kwargs_minres = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_minres = reduce(vcat, kw.args[1].args for kw in def_kwargs_minres) +def_kwargs_minres = mapreduce(extract_parameters, vcat, def_kwargs_minres) kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index da0e60089..1929242cb 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -97,7 +97,7 @@ def_kwargs_minres_qlp = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_minres_qlp = reduce(vcat, kw.args[1].args for kw in def_kwargs_minres_qlp) +def_kwargs_minres_qlp = mapreduce(extract_parameters, vcat, def_kwargs_minres_qlp) kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/qmr.jl b/src/qmr.jl index eca87aa62..3cf471252 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -93,7 +93,7 @@ def_kwargs_qmr = (:(; c::AbstractVector{FC} = b ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_qmr = reduce(vcat, kw.args[1].args for kw in def_kwargs_qmr) +def_kwargs_qmr = mapreduce(extract_parameters, vcat, def_kwargs_qmr) kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/symmlq.jl b/src/symmlq.jl index 9b762a643..04def5e74 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -101,7 +101,7 @@ def_kwargs_symmlq = (:(; M = I ), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_symmlq = reduce(vcat, kw.args[1].args for kw in def_kwargs_symmlq) +def_kwargs_symmlq = mapreduce(extract_parameters, vcat, def_kwargs_symmlq) kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/tricg.jl b/src/tricg.jl index 4d7264b20..ae93b69b3 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -120,7 +120,7 @@ def_kwargs_tricg = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_tricg = reduce(vcat, kw.args[1].args for kw in def_kwargs_tricg) +def_kwargs_tricg = mapreduce(extract_parameters, vcat, def_kwargs_tricg) kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/trilqr.jl b/src/trilqr.jl index 921a38607..b3d1774eb 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -89,7 +89,7 @@ def_kwargs_trilqr = (:(; transfer_to_usymcg::Bool = true), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_trilqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_trilqr) +def_kwargs_trilqr = mapreduce(extract_parameters, vcat, def_kwargs_trilqr) kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/trimr.jl b/src/trimr.jl index bd831ce36..710367975 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -121,7 +121,7 @@ def_kwargs_trimr = (:(; M = I ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_trimr = reduce(vcat, kw.args[1].args for kw in def_kwargs_trimr) +def_kwargs_trimr = mapreduce(extract_parameters, vcat, def_kwargs_trimr) kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/usymlq.jl b/src/usymlq.jl index 4c659601a..ebd05163c 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -98,7 +98,7 @@ def_kwargs_usymlq = (:(; transfer_to_usymcg::Bool = true), :(; callback = solver -> false ), :(; iostream::IO = kstdout )) -def_kwargs_usymlq = reduce(vcat, kw.args[1].args for kw in def_kwargs_usymlq) +def_kwargs_usymlq = mapreduce(extract_parameters, vcat, def_kwargs_usymlq) kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) diff --git a/src/usymqr.jl b/src/usymqr.jl index 048f9b347..aa62f9c54 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -96,7 +96,7 @@ def_kwargs_usymqr = (:(; atol::T = √eps(T) ), :(; callback = solver -> false), :(; iostream::IO = kstdout )) -def_kwargs_usymqr = reduce(vcat, kw.args[1].args for kw in def_kwargs_usymqr) +def_kwargs_usymqr = mapreduce(extract_parameters, vcat, def_kwargs_usymqr) kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) From de52dfe57bf12b779c2da75913ecd69d227c4be5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 9 May 2023 20:07:52 -0400 Subject: [PATCH 155/182] [documentation] Update reference.md --- docs/src/reference.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/reference.md b/docs/src/reference.md index f73e10043..be0ac5288 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -6,10 +6,11 @@ ``` ```@docs -Krylov.kstdout Krylov.FloatOrComplex Krylov.niterations Krylov.Aprod Krylov.Atprod +Krylov.kstdout +Krylov.extract_parameters Base.show ``` From 88629bc869f3a7805c4a8dfbf2b2ad778b16b9b5 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 11:17:12 -0400 Subject: [PATCH 156/182] Update the kwargs of the in-place methods --- src/bicgstab.jl | 289 +++++++------- src/bilq.jl | 498 ++++++++++++------------ src/bilqr.jl | 679 +++++++++++++++++---------------- src/cg.jl | 275 +++++++------- src/cg_lanczos.jl | 287 +++++++------- src/cg_lanczos_shift.jl | 315 ++++++++-------- src/cgls.jl | 210 +++++------ src/cgne.jl | 223 ++++++----- src/cgs.jl | 293 +++++++-------- src/cr.jl | 527 +++++++++++++------------- src/craig.jl | 417 ++++++++++---------- src/craigmr.jl | 425 +++++++++++---------- src/crls.jl | 276 +++++++------- src/crmr.jl | 207 +++++----- src/diom.jl | 377 +++++++++---------- src/dqgmres.jl | 381 ++++++++++--------- src/fgmres.jl | 455 +++++++++++----------- src/fom.jl | 421 ++++++++++----------- src/gmres.jl | 455 +++++++++++----------- src/gpmr.jl | 722 ++++++++++++++++++----------------- src/lnlq.jl | 700 +++++++++++++++++----------------- src/lslq.jl | 595 +++++++++++++++-------------- src/lsmr.jl | 524 +++++++++++++------------- src/lsqr.jl | 518 +++++++++++++------------ src/minres.jl | 482 ++++++++++++------------ src/minres_qlp.jl | 700 +++++++++++++++++----------------- src/qmr.jl | 478 ++++++++++++----------- src/symmlq.jl | 577 ++++++++++++++-------------- src/tricg.jl | 612 +++++++++++++++--------------- src/trilqr.jl | 649 ++++++++++++++++---------------- src/trimr.jl | 816 ++++++++++++++++++++-------------------- src/usymlq.jl | 463 +++++++++++------------ src/usymqr.jl | 452 +++++++++++----------- 33 files changed, 7589 insertions(+), 7709 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 333396625..2c7b7535e 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -123,156 +123,153 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return solver end -end - -function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; - c :: AbstractVector{FC}=b, M=I, N=I, - ldiv :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "BICGSTAB: system of size %d\n", n) - - # Check M = Iₙ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :t , S, n) - allocate_if(!NisI, solver, :yz, S, n) - Δx, x, r, p, v, s, qd, stats = solver.Δx, solver.x, solver.r, solver.p, solver.v, solver.s, solver.qd, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - q = d = solver.qd - t = MisI ? d : solver.t - y = NisI ? p : solver.yz - z = NisI ? s : solver.yz - r₀ = MisI ? r : solver.qd - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - else - r₀ .= b - end - - x .= zero(FC) # x₀ - s .= zero(FC) # s₀ - v .= zero(FC) # v₀ - MisI || mulorldiv!(r, M, r₀, ldiv) # r₀ - p .= r # p₁ - - α = one(FC) # α₀ - ω = one(FC) # ω₀ - ρ = one(FC) # ρ₀ - - # Compute residual norm ‖r₀‖₂. - rNorm = @knrm2(n, r) - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - - iter = 0 - itmax == 0 && (itmax = 2*n) - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s\n", "k", "‖rₖ‖", "|αₖ|", "|ωₖ|") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) - - next_ρ = @kdot(n, c, r) # ρ₁ = ⟨r̅₀,r₀⟩ - if next_ρ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = false, false - stats.status = "Breakdown bᴴc = 0" - solver.warm_start = false - return solver - end - - # Stopping criterion. - solved = rNorm ≤ ε - tired = iter ≥ itmax - breakdown = false - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || breakdown || user_requested_exit || overtimed) - # Update iteration index and ρ. - iter = iter + 1 - ρ = next_ρ - - NisI || mulorldiv!(y, N, p, ldiv) # yₖ = N⁻¹pₖ - mul!(q, A, y) # qₖ = Ayₖ - mulorldiv!(v, M, q, ldiv) # vₖ = M⁻¹qₖ - α = ρ / @kdot(n, c, v) # αₖ = ⟨r̅₀,rₖ₋₁⟩ / ⟨r̅₀,vₖ⟩ - @kcopy!(n, r, s) # sₖ = rₖ₋₁ - @kaxpy!(n, -α, v, s) # sₖ = sₖ - αₖvₖ - @kaxpy!(n, α, y, x) # xₐᵤₓ = xₖ₋₁ + αₖyₖ - NisI || mulorldiv!(z, N, s, ldiv) # zₖ = N⁻¹sₖ - mul!(d, A, z) # dₖ = Azₖ - MisI || mulorldiv!(t, M, d, ldiv) # tₖ = M⁻¹dₖ - ω = @kdot(n, t, s) / @kdot(n, t, t) # ⟨tₖ,sₖ⟩ / ⟨tₖ,tₖ⟩ - @kaxpy!(n, ω, z, x) # xₖ = xₐᵤₓ + ωₖzₖ - @kcopy!(n, s, r) # rₖ = sₖ - @kaxpy!(n, -ω, t, r) # rₖ = rₖ - ωₖtₖ - next_ρ = @kdot(n, c, r) # ρₖ₊₁ = ⟨r̅₀,rₖ⟩ - β = (next_ρ / ρ) * (α / ω) # βₖ₊₁ = (ρₖ₊₁ / ρₖ) * (αₖ / ωₖ) - @kaxpy!(n, -ω, v, p) # pₐᵤₓ = pₖ - ωₖvₖ - @kaxpby!(n, one(FC), r, β, p) # pₖ₊₁ = rₖ₊₁ + βₖ₊₁pₐᵤₓ - - # Compute residual norm ‖rₖ‖₂. + function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "BICGSTAB: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :t , S, n) + allocate_if(!NisI, solver, :yz, S, n) + Δx, x, r, p, v, s, qd, stats = solver.Δx, solver.x, solver.r, solver.p, solver.v, solver.s, solver.qd, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + q = d = solver.qd + t = MisI ? d : solver.t + y = NisI ? p : solver.yz + z = NisI ? s : solver.yz + r₀ = MisI ? r : solver.qd + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) + else + r₀ .= b + end + + x .= zero(FC) # x₀ + s .= zero(FC) # s₀ + v .= zero(FC) # v₀ + MisI || mulorldiv!(r, M, r₀, ldiv) # r₀ + p .= r # p₁ + + α = one(FC) # α₀ + ω = one(FC) # ω₀ + ρ = one(FC) # ρ₀ + + # Compute residual norm ‖r₀‖₂. rNorm = @knrm2(n, r) history && push!(rNorms, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end + + iter = 0 + itmax == 0 && (itmax = 2*n) + + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s\n", "k", "‖rₖ‖", "|αₖ|", "|ωₖ|") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - solved = resid_decrease_lim || resid_decrease_mach + next_ρ = @kdot(n, c, r) # ρ₁ = ⟨r̅₀,r₀⟩ + if next_ρ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = false, false + stats.status = "Breakdown bᴴc = 0" + solver.warm_start = false + return solver + end + + # Stopping criterion. + solved = rNorm ≤ ε tired = iter ≥ itmax - breakdown = (α == 0 || isnan(α)) - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) + breakdown = false + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || breakdown || user_requested_exit || overtimed) + # Update iteration index and ρ. + iter = iter + 1 + ρ = next_ρ + + NisI || mulorldiv!(y, N, p, ldiv) # yₖ = N⁻¹pₖ + mul!(q, A, y) # qₖ = Ayₖ + mulorldiv!(v, M, q, ldiv) # vₖ = M⁻¹qₖ + α = ρ / @kdot(n, c, v) # αₖ = ⟨r̅₀,rₖ₋₁⟩ / ⟨r̅₀,vₖ⟩ + @kcopy!(n, r, s) # sₖ = rₖ₋₁ + @kaxpy!(n, -α, v, s) # sₖ = sₖ - αₖvₖ + @kaxpy!(n, α, y, x) # xₐᵤₓ = xₖ₋₁ + αₖyₖ + NisI || mulorldiv!(z, N, s, ldiv) # zₖ = N⁻¹sₖ + mul!(d, A, z) # dₖ = Azₖ + MisI || mulorldiv!(t, M, d, ldiv) # tₖ = M⁻¹dₖ + ω = @kdot(n, t, s) / @kdot(n, t, t) # ⟨tₖ,sₖ⟩ / ⟨tₖ,tₖ⟩ + @kaxpy!(n, ω, z, x) # xₖ = xₐᵤₓ + ωₖzₖ + @kcopy!(n, s, r) # rₖ = sₖ + @kaxpy!(n, -ω, t, r) # rₖ = rₖ - ωₖtₖ + next_ρ = @kdot(n, c, r) # ρₖ₊₁ = ⟨r̅₀,rₖ⟩ + β = (next_ρ / ρ) * (α / ω) # βₖ₊₁ = (ρₖ₊₁ / ρₖ) * (αₖ / ωₖ) + @kaxpy!(n, -ω, v, p) # pₐᵤₓ = pₖ - ωₖvₖ + @kaxpby!(n, one(FC), r, β, p) # pₖ₊₁ = rₖ₊₁ + βₖ₊₁pₐᵤₓ + + # Compute residual norm ‖rₖ‖₂. + rNorm = @knrm2(n, r) + history && push!(rNorms, rNorm) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + breakdown = (α == 0 || isnan(α)) + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "breakdown αₖ == 0") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "breakdown αₖ == 0") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/bilq.jl b/src/bilq.jl index 727651904..6c9e6be69 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -108,268 +108,266 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, bilq!(solver, A, b; $(kwargs_bilq...)) return solver end -end - -function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - c :: AbstractVector{FC}=b, transfer_to_bicg :: Bool=true, - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "BILQ: system of size %d\n", n) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - uₖ₋₁, uₖ, q, vₖ₋₁, vₖ = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ - p, Δx, x, d̅, stats = solver.p, solver.Δx, solver.x, solver.d̅, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - r₀ = warm_start ? q : b - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - end - - # Initial solution x₀ and residual norm ‖r₀‖. - x .= zero(FC) - bNorm = @knrm2(n, r₀) # ‖r₀‖ = ‖b₀ - Ax₀‖ - - history && push!(rNorms, bNorm) - if bNorm == 0 - stats.niter = 0 - stats.solved = true - stats.inconsistent = false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - - iter = 0 - itmax == 0 && (itmax = 2*n) - - ε = atol + rtol * bNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) - - # Initialize the Lanczos biorthogonalization process. - cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ - if cᴴb == 0 - stats.niter = 0 - stats.solved = false - stats.inconsistent = false - stats.status = "Breakdown bᴴc = 0" - solver.warm_start = false - return solver - end - βₖ = √(abs(cᴴb)) # β₁γ₁ = cᴴ(b - Ax₀) - γₖ = cᴴb / βₖ # β₁γ₁ = cᴴ(b - Ax₀) - vₖ₋₁ .= zero(FC) # v₀ = 0 - uₖ₋₁ .= zero(FC) # u₀ = 0 - vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= c ./ conj(γₖ) # u₁ = c / γ̄₁ - cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ - sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᴴ - ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ - ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ - δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations - norm_vₖ = bNorm / βₖ # ‖vₖ‖ is used for residual norm estimates - - # Stopping criterion. - solved_lq = bNorm ≤ ε - solved_cg = false - breakdown = false - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved_lq || solved_cg || tired || breakdown || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the Lanczos biorthogonalization process. - # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ - mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ - - @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ - @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ - - αₖ = @kdot(n, uₖ, q) # αₖ = ⟨uₖ,q⟩ - - @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ - @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - - pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ - βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) - γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ - - # Update the LQ factorization of Tₖ = L̅ₖQₖ. - # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] - # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] - # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] - # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ - # [ • • • • • 0 ] [ • • • • • • • ] - # [ • • • • γₖ] [ • • • • • 0 ] - # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] - - if iter == 1 - δbarₖ = αₖ - elseif iter == 2 - # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] - # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - λₖ₋₁ = cₖ * βₖ + sₖ * αₖ - δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ - else - # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] - # [sₖ₋₁ -cₖ₋₁ 0] - # [ 0 0 1] - # - # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] - # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] - # [0 sₖ -cₖ] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - ϵₖ₋₂ = sₖ₋₁ * βₖ - λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ - δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "BILQ: system of size %d\n", n) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + uₖ₋₁, uₖ, q, vₖ₋₁, vₖ = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ + p, Δx, x, d̅, stats = solver.p, solver.Δx, solver.x, solver.d̅, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + r₀ = warm_start ? q : b + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) end - # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ - # [δbar₁] [ζbar₁] = [β₁] - if iter == 1 - ηₖ = βₖ - end - # [δ₁ 0 ] [ ζ₁ ] = [β₁] - # [λ₁ δbar₂] [ζbar₂] [0 ] - if iter == 2 - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -λₖ₋₁ * ζₖ₋₁ - end - # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] - # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] - # [ζbarₖ] - if iter ≥ 3 - ζₖ₋₂ = ζₖ₋₁ - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ + # Initial solution x₀ and residual norm ‖r₀‖. + x .= zero(FC) + bNorm = @knrm2(n, r₀) # ‖r₀‖ = ‖b₀ - Ax₀‖ + + history && push!(rNorms, bNorm) + if bNorm == 0 + stats.niter = 0 + stats.solved = true + stats.inconsistent = false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver end - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᴴ. - # [d̅ₖ₋₁ vₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * vₖ - # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ - if iter ≥ 2 - # Compute solution xₖ. - # (xᴸ)ₖ₋₁ ← (xᴸ)ₖ₋₂ + ζₖ₋₁ * dₖ₋₁ - @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) - @kaxpy!(n, ζₖ₋₁ * sₖ, vₖ, x) + iter = 0 + itmax == 0 && (itmax = 2*n) + + ε = atol + rtol * bNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) + + # Initialize the Lanczos biorthogonalization process. + cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ + if cᴴb == 0 + stats.niter = 0 + stats.solved = false + stats.inconsistent = false + stats.status = "Breakdown bᴴc = 0" + solver.warm_start = false + return solver end - # Compute d̅ₖ. - if iter == 1 - # d̅₁ = v₁ - @. d̅ = vₖ - else - # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ - @kaxpby!(n, -cₖ, vₖ, conj(sₖ), d̅) + βₖ = √(abs(cᴴb)) # β₁γ₁ = cᴴ(b - Ax₀) + γₖ = cᴴb / βₖ # β₁γ₁ = cᴴ(b - Ax₀) + vₖ₋₁ .= zero(FC) # v₀ = 0 + uₖ₋₁ .= zero(FC) # u₀ = 0 + vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ + uₖ .= c ./ conj(γₖ) # u₁ = c / γ̄₁ + cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ + sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ + d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᴴ + ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ + ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ + δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations + norm_vₖ = bNorm / βₖ # ‖vₖ‖ is used for residual norm estimates + + # Stopping criterion. + solved_lq = bNorm ≤ ε + solved_cg = false + breakdown = false + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved_lq || solved_cg || tired || breakdown || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 + + # Continue the Lanczos biorthogonalization process. + # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ + # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ + + mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ + mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ + + @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ + @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ + + αₖ = @kdot(n, uₖ, q) # αₖ = ⟨uₖ,q⟩ + + @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ + @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ + + pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ + βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) + γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ + + # Update the LQ factorization of Tₖ = L̅ₖQₖ. + # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] + # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] + # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] + # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ + # [ • • • • • 0 ] [ • • • • • • • ] + # [ • • • • γₖ] [ • • • • • 0 ] + # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] + + if iter == 1 + δbarₖ = αₖ + elseif iter == 2 + # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] + # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + λₖ₋₁ = cₖ * βₖ + sₖ * αₖ + δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ + else + # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] + # [sₖ₋₁ -cₖ₋₁ 0] + # [ 0 0 1] + # + # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] + # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] + # [0 sₖ -cₖ] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + ϵₖ₋₂ = sₖ₋₁ * βₖ + λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ + δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + end + + # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ + # [δbar₁] [ζbar₁] = [β₁] + if iter == 1 + ηₖ = βₖ + end + # [δ₁ 0 ] [ ζ₁ ] = [β₁] + # [λ₁ δbar₂] [ζbar₂] [0 ] + if iter == 2 + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -λₖ₋₁ * ζₖ₋₁ + end + # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] + # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] + # [ζbarₖ] + if iter ≥ 3 + ζₖ₋₂ = ζₖ₋₁ + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ + end + + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᴴ. + # [d̅ₖ₋₁ vₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * vₖ + # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ + if iter ≥ 2 + # Compute solution xₖ. + # (xᴸ)ₖ₋₁ ← (xᴸ)ₖ₋₂ + ζₖ₋₁ * dₖ₋₁ + @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) + @kaxpy!(n, ζₖ₋₁ * sₖ, vₖ, x) + end + + # Compute d̅ₖ. + if iter == 1 + # d̅₁ = v₁ + @. d̅ = vₖ + else + # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ + @kaxpby!(n, -cₖ, vₖ, conj(sₖ), d̅) + end + + # Compute vₖ₊₁ and uₖ₊₁. + @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ + @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + + if pᴴq ≠ 0 + @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q + @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p + end + + # Compute ⟨vₖ,vₖ₊₁⟩ and ‖vₖ₊₁‖ + vₖᴴvₖ₊₁ = @kdot(n, vₖ₋₁, vₖ) + norm_vₖ₊₁ = @knrm2(n, vₖ) + + # Compute BiLQ residual norm + # ‖rₖ‖ = √(|μₖ|²‖vₖ‖² + |ωₖ|²‖vₖ₊₁‖² + μ̄ₖωₖ⟨vₖ,vₖ₊₁⟩ + μₖω̄ₖ⟨vₖ₊₁,vₖ⟩) + if iter == 1 + rNorm_lq = bNorm + else + μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ + ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ + θₖ = conj(μₖ) * ωₖ * vₖᴴvₖ₊₁ + rNorm_lq = sqrt(abs2(μₖ) * norm_vₖ^2 + abs2(ωₖ) * norm_vₖ₊₁^2 + 2 * real(θₖ)) + end + history && push!(rNorms, rNorm_lq) + + # Compute BiCG residual norm + # ‖rₖ‖ = |ρₖ| * ‖vₖ₊₁‖ + if transfer_to_bicg && (abs(δbarₖ) > eps(T)) + ζbarₖ = ηₖ / δbarₖ + ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) + rNorm_cg = abs(ρₖ) * norm_vₖ₊₁ + end + + # Update sₖ₋₁, cₖ₋₁, γₖ, βₖ, δbarₖ₋₁ and norm_vₖ. + sₖ₋₁ = sₖ + cₖ₋₁ = cₖ + γₖ = γₖ₊₁ + βₖ = βₖ₊₁ + δbarₖ₋₁ = δbarₖ + norm_vₖ = norm_vₖ₊₁ + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + solved_lq = rNorm_lq ≤ ε + solved_cg = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) + tired = iter ≥ itmax + breakdown = !solved_lq && !solved_cg && (pᴴq == 0) + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) end + (verbose > 0) && @printf(iostream, "\n") - # Compute vₖ₊₁ and uₖ₊₁. - @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ - @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - - if pᴴq ≠ 0 - @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q - @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p + # Compute BICG point + # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ + if solved_cg + @kaxpy!(n, ζbarₖ, d̅, x) end - # Compute ⟨vₖ,vₖ₊₁⟩ and ‖vₖ₊₁‖ - vₖᴴvₖ₊₁ = @kdot(n, vₖ₋₁, vₖ) - norm_vₖ₊₁ = @knrm2(n, vₖ) - - # Compute BiLQ residual norm - # ‖rₖ‖ = √(|μₖ|²‖vₖ‖² + |ωₖ|²‖vₖ₊₁‖² + μ̄ₖωₖ⟨vₖ,vₖ₊₁⟩ + μₖω̄ₖ⟨vₖ₊₁,vₖ⟩) - if iter == 1 - rNorm_lq = bNorm - else - μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ - ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ - θₖ = conj(μₖ) * ωₖ * vₖᴴvₖ₊₁ - rNorm_lq = sqrt(abs2(μₖ) * norm_vₖ^2 + abs2(ωₖ) * norm_vₖ₊₁^2 + 2 * real(θₖ)) - end - history && push!(rNorms, rNorm_lq) - - # Compute BiCG residual norm - # ‖rₖ‖ = |ρₖ| * ‖vₖ₊₁‖ - if transfer_to_bicg && (abs(δbarₖ) > eps(T)) - ζbarₖ = ηₖ / δbarₖ - ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) - rNorm_cg = abs(ρₖ) * norm_vₖ₊₁ - end + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") + solved_lq && (status = "solution xᴸ good enough given atol and rtol") + solved_cg && (status = "solution xᶜ good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - # Update sₖ₋₁, cₖ₋₁, γₖ, βₖ, δbarₖ₋₁ and norm_vₖ. - sₖ₋₁ = sₖ - cₖ₋₁ = cₖ - γₖ = γₖ₊₁ - βₖ = βₖ₊₁ - δbarₖ₋₁ = δbarₖ - norm_vₖ = norm_vₖ₊₁ - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - solved_lq = rNorm_lq ≤ ε - solved_cg = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) - tired = iter ≥ itmax - breakdown = !solved_lq && !solved_cg && (pᴴq == 0) - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) - end - (verbose > 0) && @printf(iostream, "\n") + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Compute BICG point - # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ - if solved_cg - @kaxpy!(n, ζbarₖ, d̅, x) + # Update stats + stats.niter = iter + stats.solved = solved_lq || solved_cg + stats.inconsistent = false + stats.status = status + return solver end - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") - solved_lq && (status = "solution xᴸ good enough given atol and rtol") - solved_cg && (status = "solution xᶜ good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved_lq || solved_cg - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/bilqr.jl b/src/bilqr.jl index 57b9ff206..387219b23 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -107,379 +107,376 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi return (solver.x, solver.y, solver.stats) end - function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return solver end -end - -function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - transfer_to_bicg :: Bool=true, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("Systems must be square") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "BILQR: systems of size %d\n", n) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - uₖ₋₁, uₖ, q, vₖ₋₁, vₖ = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ - p, Δx, Δy, x, t = solver.p, solver.Δx, solver.Δy, solver.x, solver.y - d̅, wₖ₋₃, wₖ₋₂, stats = solver.d̅, solver.wₖ₋₃, solver.wₖ₋₂, solver.stats - warm_start = solver.warm_start - rNorms, sNorms = stats.residuals_primal, stats.residuals_dual - reset!(stats) - r₀ = warm_start ? q : b - s₀ = warm_start ? p : c - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - mul!(s₀, Aᴴ, Δy) - @kaxpby!(n, one(FC), c, -one(FC), s₀) - end - # Initial solution x₀ and residual norm ‖r₀‖ = ‖b - Ax₀‖. - x .= zero(FC) # x₀ - bNorm = @knrm2(n, r₀) # rNorm = ‖r₀‖ - - # Initial solution t₀ and residual norm ‖s₀‖ = ‖c - Aᴴy₀‖. - t .= zero(FC) # t₀ - cNorm = @knrm2(n, s₀) # sNorm = ‖s₀‖ - - iter = 0 - itmax == 0 && (itmax = 2*n) - - history && push!(rNorms, bNorm) - history && push!(sNorms, cNorm) - εL = atol + rtol * bNorm - εQ = atol + rtol * cNorm - (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) - - # Initialize the Lanczos biorthogonalization process. - cᴴb = @kdot(n, s₀, r₀) # ⟨s₀,r₀⟩ = ⟨c - Aᴴy₀,b - Ax₀⟩ - if cᴴb == 0 - stats.niter = 0 - stats.solved_primal = false - stats.solved_dual = false - stats.status = "Breakdown bᴴc = 0" - solver.warm_start = false - return solver - end + function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("Systems must be square") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "BILQR: systems of size %d\n", n) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + uₖ₋₁, uₖ, q, vₖ₋₁, vₖ = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ + p, Δx, Δy, x, t = solver.p, solver.Δx, solver.Δy, solver.x, solver.y + d̅, wₖ₋₃, wₖ₋₂, stats = solver.d̅, solver.wₖ₋₃, solver.wₖ₋₂, solver.stats + warm_start = solver.warm_start + rNorms, sNorms = stats.residuals_primal, stats.residuals_dual + reset!(stats) + r₀ = warm_start ? q : b + s₀ = warm_start ? p : c + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) + mul!(s₀, Aᴴ, Δy) + @kaxpby!(n, one(FC), c, -one(FC), s₀) + end - # Set up workspace. - βₖ = √(abs(cᴴb)) # β₁γ₁ = (c - Aᴴy₀)ᴴ(b - Ax₀) - γₖ = cᴴb / βₖ # β₁γ₁ = (c - Aᴴy₀)ᴴ(b - Ax₀) - vₖ₋₁ .= zero(FC) # v₀ = 0 - uₖ₋₁ .= zero(FC) # u₀ = 0 - vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= s₀ ./ conj(γₖ) # u₁ = (c - Aᴴy₀) / γ̄₁ - cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ - sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᴴ - ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ - ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ - δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations - ψbarₖ₋₁ = ψₖ₋₁ = zero(FC) # ψₖ₋₁ and ψbarₖ are the last components of h̅ₖ = Qₖγ̄₁e₁ - norm_vₖ = bNorm / βₖ # ‖vₖ‖ is used for residual norm estimates - ϵₖ₋₃ = λₖ₋₂ = zero(FC) # Components of Lₖ₋₁ - wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Uₖ(Lₖ)⁻ᴴ - wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Lₖ)⁻ᴴ - τₖ = zero(T) # τₖ is used for the dual residual norm estimate - - # Stopping criterion. - solved_lq = bNorm == 0 - solved_lq_tol = solved_lq_mach = false - solved_cg = solved_cg_tol = solved_cg_mach = false - solved_primal = solved_lq || solved_cg - solved_qr_tol = solved_qr_mach = false - solved_dual = cNorm == 0 - tired = iter ≥ itmax - breakdown = false - status = "unknown" - user_requested_exit = false - overtimed = false - - while !((solved_primal && solved_dual) || tired || breakdown || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the Lanczos biorthogonalization process. - # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ - mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ - - @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ - @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ - - αₖ = @kdot(n, uₖ, q) # αₖ = ⟨uₖ,q⟩ - - @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ - @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - - pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ - βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) - γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ - - # Update the LQ factorization of Tₖ = L̅ₖQₖ. - # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] - # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] - # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] - # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ - # [ • • • • • 0 ] [ • • • • • • • ] - # [ • • • • γₖ] [ • • • λₖ₋₂ δₖ₋₁ 0 ] - # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] - - if iter == 1 - δbarₖ = αₖ - elseif iter == 2 - # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] - # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - λₖ₋₁ = cₖ * βₖ + sₖ * αₖ - δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ - else - # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] - # [sₖ₋₁ -cₖ₋₁ 0] - # [ 0 0 1] - # - # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] - # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] - # [0 sₖ -cₖ] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - ϵₖ₋₂ = sₖ₋₁ * βₖ - λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ - δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + # Initial solution x₀ and residual norm ‖r₀‖ = ‖b - Ax₀‖. + x .= zero(FC) # x₀ + bNorm = @knrm2(n, r₀) # rNorm = ‖r₀‖ + + # Initial solution t₀ and residual norm ‖s₀‖ = ‖c - Aᴴy₀‖. + t .= zero(FC) # t₀ + cNorm = @knrm2(n, s₀) # sNorm = ‖s₀‖ + + iter = 0 + itmax == 0 && (itmax = 2*n) + + history && push!(rNorms, bNorm) + history && push!(sNorms, cNorm) + εL = atol + rtol * bNorm + εQ = atol + rtol * cNorm + (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) + + # Initialize the Lanczos biorthogonalization process. + cᴴb = @kdot(n, s₀, r₀) # ⟨s₀,r₀⟩ = ⟨c - Aᴴy₀,b - Ax₀⟩ + if cᴴb == 0 + stats.niter = 0 + stats.solved_primal = false + stats.solved_dual = false + stats.status = "Breakdown bᴴc = 0" + solver.warm_start = false + return solver end - if !solved_primal - # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ - # [δbar₁] [ζbar₁] = [β₁] - if iter == 1 - ηₖ = βₖ - end - # [δ₁ 0 ] [ ζ₁ ] = [β₁] - # [λ₁ δbar₂] [ζbar₂] [0 ] - if iter == 2 - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -λₖ₋₁ * ζₖ₋₁ - end - # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] - # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] - # [ζbarₖ] - if iter ≥ 3 - ζₖ₋₂ = ζₖ₋₁ - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ - end + # Set up workspace. + βₖ = √(abs(cᴴb)) # β₁γ₁ = (c - Aᴴy₀)ᴴ(b - Ax₀) + γₖ = cᴴb / βₖ # β₁γ₁ = (c - Aᴴy₀)ᴴ(b - Ax₀) + vₖ₋₁ .= zero(FC) # v₀ = 0 + uₖ₋₁ .= zero(FC) # u₀ = 0 + vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ + uₖ .= s₀ ./ conj(γₖ) # u₁ = (c - Aᴴy₀) / γ̄₁ + cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ + sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ + d̅ .= zero(FC) # Last column of D̅ₖ = Vₖ(Qₖ)ᴴ + ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ + ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ + δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations + ψbarₖ₋₁ = ψₖ₋₁ = zero(FC) # ψₖ₋₁ and ψbarₖ are the last components of h̅ₖ = Qₖγ̄₁e₁ + norm_vₖ = bNorm / βₖ # ‖vₖ‖ is used for residual norm estimates + ϵₖ₋₃ = λₖ₋₂ = zero(FC) # Components of Lₖ₋₁ + wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Uₖ(Lₖ)⁻ᴴ + wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Lₖ)⁻ᴴ + τₖ = zero(T) # τₖ is used for the dual residual norm estimate + + # Stopping criterion. + solved_lq = bNorm == 0 + solved_lq_tol = solved_lq_mach = false + solved_cg = solved_cg_tol = solved_cg_mach = false + solved_primal = solved_lq || solved_cg + solved_qr_tol = solved_qr_mach = false + solved_dual = cNorm == 0 + tired = iter ≥ itmax + breakdown = false + status = "unknown" + user_requested_exit = false + overtimed = false - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᴴ. - # [d̅ₖ₋₁ vₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * vₖ - # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ - if iter ≥ 2 - # Compute solution xₖ. - # (xᴸ)ₖ ← (xᴸ)ₖ₋₁ + ζₖ₋₁ * dₖ₋₁ - @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) - @kaxpy!(n, ζₖ₋₁ * sₖ, vₖ, x) - end + while !((solved_primal && solved_dual) || tired || breakdown || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 - # Compute d̅ₖ. - if iter == 1 - # d̅₁ = v₁ - @. d̅ = vₖ - else - # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ - @kaxpby!(n, -cₖ, vₖ, conj(sₖ), d̅) - end + # Continue the Lanczos biorthogonalization process. + # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ + # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - # Compute ⟨vₖ,vₖ₊₁⟩ and ‖vₖ₊₁‖ - vₖᴴvₖ₊₁ = @kdot(n, vₖ, q) / βₖ₊₁ - norm_vₖ₊₁ = @knrm2(n, q) / βₖ₊₁ + mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ + mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ - # Compute BiLQ residual norm - # ‖rₖ‖ = √(|μₖ|²‖vₖ‖² + |ωₖ|²‖vₖ₊₁‖² + μ̄ₖωₖ⟨vₖ,vₖ₊₁⟩ + μₖω̄ₖ⟨vₖ₊₁,vₖ⟩) - if iter == 1 - rNorm_lq = bNorm - else - μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ - ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ - θₖ = conj(μₖ) * ωₖ * vₖᴴvₖ₊₁ - rNorm_lq = sqrt(abs2(μₖ) * norm_vₖ^2 + abs2(ωₖ) * norm_vₖ₊₁^2 + 2 * real(θₖ)) - end - history && push!(rNorms, rNorm_lq) + @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ + @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ - # Update ‖vₖ‖ - norm_vₖ = norm_vₖ₊₁ + αₖ = @kdot(n, uₖ, q) # αₖ = ⟨uₖ,q⟩ - # Compute BiCG residual norm - # ‖rₖ‖ = |ρₖ| * ‖vₖ₊₁‖ - if transfer_to_bicg && (abs(δbarₖ) > eps(T)) - ζbarₖ = ηₖ / δbarₖ - ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) - rNorm_cg = abs(ρₖ) * norm_vₖ₊₁ - end + @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ + @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - # Update primal stopping criterion - solved_lq_tol = rNorm_lq ≤ εL - solved_lq_mach = rNorm_lq + 1 ≤ 1 - solved_lq = solved_lq_tol || solved_lq_mach - solved_cg_tol = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ εL) - solved_cg_mach = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg + 1 ≤ 1) - solved_cg = solved_cg_tol || solved_cg_mach - solved_primal = solved_lq || solved_cg - end + pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ + βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) + γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ + + # Update the LQ factorization of Tₖ = L̅ₖQₖ. + # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] + # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] + # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] + # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ + # [ • • • • • 0 ] [ • • • • • • • ] + # [ • • • • γₖ] [ • • • λₖ₋₂ δₖ₋₁ 0 ] + # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] - if !solved_dual - # Compute ψₖ₋₁ and ψbarₖ the last coefficients of h̅ₖ = Qₖγ̄₁e₁. if iter == 1 - ψbarₖ = conj(γₖ) + δbarₖ = αₖ + elseif iter == 2 + # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] + # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + λₖ₋₁ = cₖ * βₖ + sₖ * αₖ + δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ else - # [cₖ s̄ₖ] [ψbarₖ₋₁] = [ ψₖ₋₁ ] - # [sₖ -cₖ] [ 0 ] [ ψbarₖ] - ψₖ₋₁ = cₖ * ψbarₖ₋₁ - ψbarₖ = sₖ * ψbarₖ₋₁ + # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] + # [sₖ₋₁ -cₖ₋₁ 0] + # [ 0 0 1] + # + # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] + # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] + # [0 sₖ -cₖ] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + ϵₖ₋₂ = sₖ₋₁ * βₖ + λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ + δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ end - # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Uₖ₋₁)(Lₖ₋₁)⁻ᴴ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Uₖ₋₁)ᵀ. - # w₁ = u₁ / δ̄₁ - if iter == 2 - wₖ₋₁ = wₖ₋₂ - @kaxpy!(n, one(FC), uₖ₋₁, wₖ₋₁) - @. wₖ₋₁ = uₖ₋₁ / conj(δₖ₋₁) + if !solved_primal + # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ + # [δbar₁] [ζbar₁] = [β₁] + if iter == 1 + ηₖ = βₖ + end + # [δ₁ 0 ] [ ζ₁ ] = [β₁] + # [λ₁ δbar₂] [ζbar₂] [0 ] + if iter == 2 + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -λₖ₋₁ * ζₖ₋₁ + end + # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] + # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] + # [ζbarₖ] + if iter ≥ 3 + ζₖ₋₂ = ζₖ₋₁ + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ + end + + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Vₖ(Qₖ)ᴴ. + # [d̅ₖ₋₁ vₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * vₖ + # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ + if iter ≥ 2 + # Compute solution xₖ. + # (xᴸ)ₖ ← (xᴸ)ₖ₋₁ + ζₖ₋₁ * dₖ₋₁ + @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) + @kaxpy!(n, ζₖ₋₁ * sₖ, vₖ, x) + end + + # Compute d̅ₖ. + if iter == 1 + # d̅₁ = v₁ + @. d̅ = vₖ + else + # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * vₖ + @kaxpby!(n, -cₖ, vₖ, conj(sₖ), d̅) + end + + # Compute ⟨vₖ,vₖ₊₁⟩ and ‖vₖ₊₁‖ + vₖᴴvₖ₊₁ = @kdot(n, vₖ, q) / βₖ₊₁ + norm_vₖ₊₁ = @knrm2(n, q) / βₖ₊₁ + + # Compute BiLQ residual norm + # ‖rₖ‖ = √(|μₖ|²‖vₖ‖² + |ωₖ|²‖vₖ₊₁‖² + μ̄ₖωₖ⟨vₖ,vₖ₊₁⟩ + μₖω̄ₖ⟨vₖ₊₁,vₖ⟩) + if iter == 1 + rNorm_lq = bNorm + else + μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ + ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ + θₖ = conj(μₖ) * ωₖ * vₖᴴvₖ₊₁ + rNorm_lq = sqrt(abs2(μₖ) * norm_vₖ^2 + abs2(ωₖ) * norm_vₖ₊₁^2 + 2 * real(θₖ)) + end + history && push!(rNorms, rNorm_lq) + + # Update ‖vₖ‖ + norm_vₖ = norm_vₖ₊₁ + + # Compute BiCG residual norm + # ‖rₖ‖ = |ρₖ| * ‖vₖ₊₁‖ + if transfer_to_bicg && (abs(δbarₖ) > eps(T)) + ζbarₖ = ηₖ / δbarₖ + ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) + rNorm_cg = abs(ρₖ) * norm_vₖ₊₁ + end + + # Update primal stopping criterion + solved_lq_tol = rNorm_lq ≤ εL + solved_lq_mach = rNorm_lq + 1 ≤ 1 + solved_lq = solved_lq_tol || solved_lq_mach + solved_cg_tol = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ εL) + solved_cg_mach = transfer_to_bicg && (abs(δbarₖ) > eps(T)) && (rNorm_cg + 1 ≤ 1) + solved_cg = solved_cg_tol || solved_cg_mach + solved_primal = solved_lq || solved_cg end - # w₂ = (u₂ - λ̄₁w₁) / δ̄₂ - if iter == 3 - wₖ₋₁ = wₖ₋₃ - @kaxpy!(n, one(FC), uₖ₋₁, wₖ₋₁) - @kaxpy!(n, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) - @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + + if !solved_dual + # Compute ψₖ₋₁ and ψbarₖ the last coefficients of h̅ₖ = Qₖγ̄₁e₁. + if iter == 1 + ψbarₖ = conj(γₖ) + else + # [cₖ s̄ₖ] [ψbarₖ₋₁] = [ ψₖ₋₁ ] + # [sₖ -cₖ] [ 0 ] [ ψbarₖ] + ψₖ₋₁ = cₖ * ψbarₖ₋₁ + ψbarₖ = sₖ * ψbarₖ₋₁ + end + + # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Uₖ₋₁)(Lₖ₋₁)⁻ᴴ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Uₖ₋₁)ᵀ. + # w₁ = u₁ / δ̄₁ + if iter == 2 + wₖ₋₁ = wₖ₋₂ + @kaxpy!(n, one(FC), uₖ₋₁, wₖ₋₁) + @. wₖ₋₁ = uₖ₋₁ / conj(δₖ₋₁) + end + # w₂ = (u₂ - λ̄₁w₁) / δ̄₂ + if iter == 3 + wₖ₋₁ = wₖ₋₃ + @kaxpy!(n, one(FC), uₖ₋₁, wₖ₋₁) + @kaxpy!(n, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) + @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + end + # wₖ₋₁ = (uₖ₋₁ - λ̄ₖ₋₂wₖ₋₂ - ϵ̄ₖ₋₃wₖ₋₃) / δ̄ₖ₋₁ + if iter ≥ 4 + @kscal!(n, -conj(ϵₖ₋₃), wₖ₋₃) + wₖ₋₁ = wₖ₋₃ + @kaxpy!(n, one(FC), uₖ₋₁, wₖ₋₁) + @kaxpy!(n, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) + @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + end + + if iter ≥ 3 + # Swap pointers. + @kswap(wₖ₋₃, wₖ₋₂) + end + + if iter ≥ 2 + # Compute solution tₖ₋₁. + # tₖ₋₁ ← tₖ₋₂ + ψₖ₋₁ * wₖ₋₁ + @kaxpy!(n, ψₖ₋₁, wₖ₋₁, t) + end + + # Update ψbarₖ₋₁ + ψbarₖ₋₁ = ψbarₖ + + # Compute τₖ = τₖ₋₁ + ‖uₖ‖² + τₖ += @kdotr(n, uₖ, uₖ) + + # Compute QMR residual norm ‖sₖ₋₁‖ ≤ |ψbarₖ| * √τₖ + sNorm = abs(ψbarₖ) * √τₖ + history && push!(sNorms, sNorm) + + # Update dual stopping criterion + solved_qr_tol = sNorm ≤ εQ + solved_qr_mach = sNorm + 1 ≤ 1 + solved_dual = solved_qr_tol || solved_qr_mach end - # wₖ₋₁ = (uₖ₋₁ - λ̄ₖ₋₂wₖ₋₂ - ϵ̄ₖ₋₃wₖ₋₃) / δ̄ₖ₋₁ - if iter ≥ 4 - @kscal!(n, -conj(ϵₖ₋₃), wₖ₋₃) - wₖ₋₁ = wₖ₋₃ - @kaxpy!(n, one(FC), uₖ₋₁, wₖ₋₁) - @kaxpy!(n, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) - @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + + # Compute vₖ₊₁ and uₖ₊₁. + @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ + @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + + if pᴴq ≠ zero(FC) + @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q + @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p end + # Update ϵₖ₋₃, λₖ₋₂, δbarₖ₋₁, cₖ₋₁, sₖ₋₁, γₖ and βₖ. if iter ≥ 3 - # Swap pointers. - @kswap(wₖ₋₃, wₖ₋₂) + ϵₖ₋₃ = ϵₖ₋₂ end - if iter ≥ 2 - # Compute solution tₖ₋₁. - # tₖ₋₁ ← tₖ₋₂ + ψₖ₋₁ * wₖ₋₁ - @kaxpy!(n, ψₖ₋₁, wₖ₋₁, t) + λₖ₋₂ = λₖ₋₁ end - - # Update ψbarₖ₋₁ - ψbarₖ₋₁ = ψbarₖ - - # Compute τₖ = τₖ₋₁ + ‖uₖ‖² - τₖ += @kdotr(n, uₖ, uₖ) - - # Compute QMR residual norm ‖sₖ₋₁‖ ≤ |ψbarₖ| * √τₖ - sNorm = abs(ψbarₖ) * √τₖ - history && push!(sNorms, sNorm) - - # Update dual stopping criterion - solved_qr_tol = sNorm ≤ εQ - solved_qr_mach = sNorm + 1 ≤ 1 - solved_dual = solved_qr_tol || solved_qr_mach + δbarₖ₋₁ = δbarₖ + cₖ₋₁ = cₖ + sₖ₋₁ = sₖ + γₖ = γₖ₊₁ + βₖ = βₖ₊₁ + + user_requested_exit = callback(solver) :: Bool + tired = iter ≥ itmax + breakdown = !solved_lq && !solved_cg && (pᴴq == 0) + timer = time_ns() - start_time + overtimed = timer > timemax_ns + + kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) + kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") + kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) end + (verbose > 0) && @printf(iostream, "\n") - # Compute vₖ₊₁ and uₖ₊₁. - @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ - @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - - if pᴴq ≠ zero(FC) - @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q - @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p - end - - # Update ϵₖ₋₃, λₖ₋₂, δbarₖ₋₁, cₖ₋₁, sₖ₋₁, γₖ and βₖ. - if iter ≥ 3 - ϵₖ₋₃ = ϵₖ₋₂ + # Compute BICG point + # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ + if solved_cg + @kaxpy!(n, ζbarₖ, d̅, x) end - if iter ≥ 2 - λₖ₋₂ = λₖ₋₁ - end - δbarₖ₋₁ = δbarₖ - cₖ₋₁ = cₖ - sₖ₋₁ = sₖ - γₖ = γₖ₊₁ - βₖ = βₖ₊₁ - - user_requested_exit = callback(solver) :: Bool - tired = iter ≥ itmax - breakdown = !solved_lq && !solved_cg && (pᴴq == 0) - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) - kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") - kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) - end - (verbose > 0) && @printf(iostream, "\n") + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") + solved_lq_tol && !solved_dual && (status = "Only the primal solution xᴸ is good enough given atol and rtol") + solved_cg_tol && !solved_dual && (status = "Only the primal solution xᶜ is good enough given atol and rtol") + !solved_primal && solved_qr_tol && (status = "Only the dual solution t is good enough given atol and rtol") + solved_lq_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᴸ, t) are good enough given atol and rtol") + solved_cg_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᶜ, t) are good enough given atol and rtol") + solved_lq_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᴸ") + solved_cg_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᶜ") + !solved_primal && solved_qr_mach && (status = "Only found approximate zero-residual dual solution t") + solved_lq_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᴸ, t)") + solved_cg_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᶜ, t)") + solved_lq_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᴸ and a dual solution t good enough given atol and rtol") + solved_cg_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᶜ and a dual solution t good enough given atol and rtol") + solved_lq_tol && solved_qr_mach && (status = "Found a primal solution xᴸ good enough given atol and rtol and an approximate zero-residual dual solutions t") + solved_cg_tol && solved_qr_mach && (status = "Found a primal solution xᶜ good enough given atol and rtol and an approximate zero-residual dual solutions t") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x and y + warm_start && @kaxpy!(n, one(FC), Δx, x) + warm_start && @kaxpy!(n, one(FC), Δy, t) + solver.warm_start = false - # Compute BICG point - # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ - if solved_cg - @kaxpy!(n, ζbarₖ, d̅, x) + # Update stats + stats.niter = iter + stats.status = status + stats.solved_primal = solved_primal + stats.solved_dual = solved_dual + return solver end - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") - solved_lq_tol && !solved_dual && (status = "Only the primal solution xᴸ is good enough given atol and rtol") - solved_cg_tol && !solved_dual && (status = "Only the primal solution xᶜ is good enough given atol and rtol") - !solved_primal && solved_qr_tol && (status = "Only the dual solution t is good enough given atol and rtol") - solved_lq_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᴸ, t) are good enough given atol and rtol") - solved_cg_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᶜ, t) are good enough given atol and rtol") - solved_lq_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᴸ") - solved_cg_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᶜ") - !solved_primal && solved_qr_mach && (status = "Only found approximate zero-residual dual solution t") - solved_lq_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᴸ, t)") - solved_cg_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᶜ, t)") - solved_lq_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᴸ and a dual solution t good enough given atol and rtol") - solved_cg_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᶜ and a dual solution t good enough given atol and rtol") - solved_lq_tol && solved_qr_mach && (status = "Found a primal solution xᴸ good enough given atol and rtol and an approximate zero-residual dual solutions t") - solved_cg_tol && solved_qr_mach && (status = "Found a primal solution xᶜ good enough given atol and rtol and an approximate zero-residual dual solutions t") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x and y - warm_start && @kaxpy!(n, one(FC), Δx, x) - warm_start && @kaxpy!(n, one(FC), Δy, t) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.status = status - stats.solved_primal = solved_primal - stats.solved_dual = solved_dual - return solver end diff --git a/src/cg.jl b/src/cg.jl index 0b02aae99..5501dba07 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -115,156 +115,153 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v cg!(solver, A, b; $(kwargs_cg...)) return solver end -end - -function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, radius :: T=zero(T), - linesearch :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == n || error("Inconsistent problem size") - linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") - (verbose > 0) && @printf(iostream, "CG: system of %d equations in %d variables\n", n, n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :z, S, n) - Δx, x, r, p, Ap, stats = solver.Δx, solver.x, solver.r, solver.p, solver.Ap, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - z = MisI ? r : solver.z - - x .= zero(FC) - if warm_start - mul!(r, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r) - else - r .= b - end - MisI || mulorldiv!(z, M, r, ldiv) - p .= z - γ = @kdotr(n, r, z) - rNorm = sqrt(γ) - history && push!(rNorms, rNorm) - if γ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - iter = 0 - itmax == 0 && (itmax = 2 * n) - - pAp = zero(T) - pNorm² = γ - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %8s\n", "k", "‖r‖", "pAp", "α", "σ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) - - solved = rNorm ≤ ε - tired = iter ≥ itmax - inconsistent = false - on_boundary = false - zero_curvature = false - user_requested_exit = false - overtimed = false - - status = "unknown" - - while !(solved || tired || zero_curvature || user_requested_exit || overtimed) - mul!(Ap, A, p) - pAp = @kdotr(n, p, Ap) - if (pAp ≤ eps(T) * pNorm²) && (radius == 0) - if abs(pAp) ≤ eps(T) * pNorm² - zero_curvature = true - inconsistent = !linesearch - end - if linesearch - iter == 0 && (x .= b) - solved = true - end + function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == n || error("Inconsistent problem size") + linesearch && (radius > 0) && error("`linesearch` set to `true` but trust-region radius > 0") + (verbose > 0) && @printf(iostream, "CG: system of %d equations in %d variables\n", n, n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :z, S, n) + Δx, x, r, p, Ap, stats = solver.Δx, solver.x, solver.r, solver.p, solver.Ap, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + z = MisI ? r : solver.z + + x .= zero(FC) + if warm_start + mul!(r, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r) + else + r .= b + end + MisI || mulorldiv!(z, M, r, ldiv) + p .= z + γ = @kdotr(n, r, z) + rNorm = sqrt(γ) + history && push!(rNorms, rNorm) + if γ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver end - (zero_curvature || solved) && continue - α = γ / pAp + iter = 0 + itmax == 0 && (itmax = 2 * n) + + pAp = zero(T) + pNorm² = γ + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %8s\n", "k", "‖r‖", "pAp", "α", "σ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) - # Compute step size to boundary if applicable. - σ = radius > 0 ? maximum(to_boundary(n, x, p, radius, dNorm2=pNorm²)) : α + solved = rNorm ≤ ε + tired = iter ≥ itmax + inconsistent = false + on_boundary = false + zero_curvature = false + user_requested_exit = false + overtimed = false + + status = "unknown" + + while !(solved || tired || zero_curvature || user_requested_exit || overtimed) + mul!(Ap, A, p) + pAp = @kdotr(n, p, Ap) + if (pAp ≤ eps(T) * pNorm²) && (radius == 0) + if abs(pAp) ≤ eps(T) * pNorm² + zero_curvature = true + inconsistent = !linesearch + end + if linesearch + iter == 0 && (x .= b) + solved = true + end + end + (zero_curvature || solved) && continue - kdisplay(iter, verbose) && @printf(iostream, "%8.1e %8.1e %8.1e\n", pAp, α, σ) + α = γ / pAp - # Move along p from x to the boundary if either - # the next step leads outside the trust region or - # we have nonpositive curvature. - if (radius > 0) && ((pAp ≤ 0) || (α > σ)) - α = σ - on_boundary = true - end + # Compute step size to boundary if applicable. + σ = radius > 0 ? maximum(to_boundary(n, x, p, radius, dNorm2=pNorm²)) : α - @kaxpy!(n, α, p, x) - @kaxpy!(n, -α, Ap, r) - MisI || mulorldiv!(z, M, r, ldiv) - γ_next = @kdotr(n, r, z) - rNorm = sqrt(γ_next) - history && push!(rNorms, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%8.1e %8.1e %8.1e\n", pAp, α, σ) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + # Move along p from x to the boundary if either + # the next step leads outside the trust region or + # we have nonpositive curvature. + if (radius > 0) && ((pAp ≤ 0) || (α > σ)) + α = σ + on_boundary = true + end - resid_decrease_lim = rNorm ≤ ε - resid_decrease = resid_decrease_lim || resid_decrease_mach - solved = resid_decrease || on_boundary + @kaxpy!(n, α, p, x) + @kaxpy!(n, -α, Ap, r) + MisI || mulorldiv!(z, M, r, ldiv) + γ_next = @kdotr(n, r, z) + rNorm = sqrt(γ_next) + history && push!(rNorms, rNorm) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + resid_decrease_lim = rNorm ≤ ε + resid_decrease = resid_decrease_lim || resid_decrease_mach + solved = resid_decrease || on_boundary + + if !solved + β = γ_next / γ + pNorm² = γ_next + β^2 * pNorm² + γ = γ_next + @kaxpby!(n, one(FC), z, β, p) + end - if !solved - β = γ_next / γ - pNorm² = γ_next + β^2 * pNorm² - γ = γ_next - @kaxpby!(n, one(FC), z, β, p) + iter = iter + 1 + tired = iter ≥ itmax + user_requested_exit = callback(solver) :: Bool + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + solved && on_boundary && (status = "on trust-region boundary") + solved && linesearch && (pAp ≤ 0) && (status = "nonpositive curvature detected") + solved && (status == "unknown") && (status = "solution good enough given atol and rtol") + zero_curvature && (status = "zero curvature detected") + tired && (status = "maximum number of iterations exceeded") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - iter = iter + 1 - tired = iter ≥ itmax - user_requested_exit = callback(solver) :: Bool - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - solved && on_boundary && (status = "on trust-region boundary") - solved && linesearch && (pAp ≤ 0) && (status = "nonpositive curvature detected") - solved && (status == "unknown") && (status = "solution good enough given atol and rtol") - zero_curvature && (status = "zero curvature detected") - tired && (status = "maximum number of iterations exceeded") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 8f1f98ae9..c1010a37f 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -111,156 +111,153 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return solver end -end -function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, - check_curvature :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables\n", n, n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $T") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :v, S, n) - Δx, x, Mv, Mv_prev = solver.Δx, solver.x, solver.Mv, solver.Mv_prev - p, Mv_next, stats = solver.p, solver.Mv_next, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - v = MisI ? Mv : solver.v - - # Initial state. - x .= zero(FC) - if warm_start - mul!(Mv, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), Mv) - else - Mv .= b - end - MisI || mulorldiv!(v, M, Mv, ldiv) # v₁ = M⁻¹r₀ - β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᴴ M v₁ - σ = β - rNorm = σ - history && push!(rNorms, rNorm) - if β == 0 - stats.niter = 0 - stats.solved = true - stats.Anorm = zero(T) - stats.indefinite = false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - p .= v - - # Initialize Lanczos process. - # β₁Mv₁ = b - @kscal!(n, one(FC) / β, v) # v₁ ← v₁ / β₁ - MisI || @kscal!(n, one(FC) / β, Mv) # Mv₁ ← Mv₁ / β₁ - Mv_prev .= Mv - - iter = 0 - itmax == 0 && (itmax = 2 * n) - - # Initialize some constants used in recursions below. - ω = zero(T) - γ = one(T) - Anorm2 = zero(T) - β_prev = zero(T) - - # Define stopping tolerance. - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - - indefinite = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - # Main loop. - while ! (solved || tired || (check_curvature & indefinite) || user_requested_exit || overtimed) - # Form next Lanczos vector. - # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ - mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ - δ = @kdotr(n, v, Mv_next) # δₖ = vₖᴴ A vₖ - - # Check curvature. Exit fast if requested. - # It is possible to show that σₖ² (δₖ - ωₖ₋₁ / γₖ₋₁) = pₖᴴ A pₖ. - γ = one(T) / (δ - ω / γ) # γₖ = 1 / (δₖ - ωₖ₋₁ / γₖ₋₁) - indefinite |= (γ ≤ 0) - (check_curvature & indefinite) && continue - - @kaxpy!(n, -δ, Mv, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - δₖMvₖ - if iter > 0 - @kaxpy!(n, -β, Mv_prev, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - βₖMvₖ₋₁ - @. Mv_prev = Mv # Mvₖ₋₁ ← Mvₖ + function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables\n", n, n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $T") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :v, S, n) + Δx, x, Mv, Mv_prev = solver.Δx, solver.x, solver.Mv, solver.Mv_prev + p, Mv_next, stats = solver.p, solver.Mv_next, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + v = MisI ? Mv : solver.v + + # Initial state. + x .= zero(FC) + if warm_start + mul!(Mv, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), Mv) + else + Mv .= b end - @. Mv = Mv_next # Mvₖ ← Mvₖ₊₁ - MisI || mulorldiv!(v, M, Mv, ldiv) # vₖ₊₁ = M⁻¹ * Mvₖ₊₁ - β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᴴ M vₖ₊₁ - @kscal!(n, one(FC) / β, v) # vₖ₊₁ ← vₖ₊₁ / βₖ₊₁ - MisI || @kscal!(n, one(FC) / β, Mv) # Mvₖ₊₁ ← Mvₖ₊₁ / βₖ₊₁ - Anorm2 += β_prev^2 + β^2 + δ^2 # Use ‖Tₖ₊₁‖₂ as increasing approximation of ‖A‖₂. - β_prev = β - - # Compute next CG iterate. - @kaxpy!(n, γ, p, x) # xₖ₊₁ = xₖ + γₖ * pₖ - ω = β * γ - σ = -ω * σ # σₖ₊₁ = - βₖ₊₁ * γₖ * σₖ - ω = ω * ω # ωₖ = (βₖ₊₁ * γₖ)² - @kaxpby!(n, σ, v, ω, p) # pₖ₊₁ = σₖ₊₁ * vₖ₊₁ + ωₖ * pₖ - rNorm = abs(σ) # ‖rₖ₊₁‖_M = |σₖ₊₁| because rₖ₊₁ = σₖ₊₁ * vₖ₊₁ and ‖vₖ₊₁‖_M = 1 + MisI || mulorldiv!(v, M, Mv, ldiv) # v₁ = M⁻¹r₀ + β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᴴ M v₁ + σ = β + rNorm = σ history && push!(rNorms, rNorm) - iter = iter + 1 + if β == 0 + stats.niter = 0 + stats.solved = true + stats.Anorm = zero(T) + stats.indefinite = false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end + p .= v + + # Initialize Lanczos process. + # β₁Mv₁ = b + @kscal!(n, one(FC) / β, v) # v₁ ← v₁ / β₁ + MisI || @kscal!(n, one(FC) / β, Mv) # Mv₁ ← Mv₁ / β₁ + Mv_prev .= Mv + + iter = 0 + itmax == 0 && (itmax = 2 * n) + + # Initialize some constants used in recursions below. + ω = zero(T) + γ = one(T) + Anorm2 = zero(T) + β_prev = zero(T) + + # Define stopping tolerance. + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - solved = resid_decrease_lim || resid_decrease_mach + indefinite = false + solved = rNorm ≤ ε tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + status = "unknown" + user_requested_exit = false + overtimed = false + + # Main loop. + while ! (solved || tired || (check_curvature & indefinite) || user_requested_exit || overtimed) + # Form next Lanczos vector. + # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ + mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ + δ = @kdotr(n, v, Mv_next) # δₖ = vₖᴴ A vₖ + + # Check curvature. Exit fast if requested. + # It is possible to show that σₖ² (δₖ - ωₖ₋₁ / γₖ₋₁) = pₖᴴ A pₖ. + γ = one(T) / (δ - ω / γ) # γₖ = 1 / (δₖ - ωₖ₋₁ / γₖ₋₁) + indefinite |= (γ ≤ 0) + (check_curvature & indefinite) && continue + + @kaxpy!(n, -δ, Mv, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - δₖMvₖ + if iter > 0 + @kaxpy!(n, -β, Mv_prev, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - βₖMvₖ₋₁ + @. Mv_prev = Mv # Mvₖ₋₁ ← Mvₖ + end + @. Mv = Mv_next # Mvₖ ← Mvₖ₊₁ + MisI || mulorldiv!(v, M, Mv, ldiv) # vₖ₊₁ = M⁻¹ * Mvₖ₊₁ + β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᴴ M vₖ₊₁ + @kscal!(n, one(FC) / β, v) # vₖ₊₁ ← vₖ₊₁ / βₖ₊₁ + MisI || @kscal!(n, one(FC) / β, Mv) # Mvₖ₊₁ ← Mvₖ₊₁ / βₖ₊₁ + Anorm2 += β_prev^2 + β^2 + δ^2 # Use ‖Tₖ₊₁‖₂ as increasing approximation of ‖A‖₂. + β_prev = β + + # Compute next CG iterate. + @kaxpy!(n, γ, p, x) # xₖ₊₁ = xₖ + γₖ * pₖ + ω = β * γ + σ = -ω * σ # σₖ₊₁ = - βₖ₊₁ * γₖ * σₖ + ω = ω * ω # ωₖ = (βₖ₊₁ * γₖ)² + @kaxpby!(n, σ, v, ω, p) # pₖ₊₁ = σₖ₊₁ * vₖ₊₁ + ωₖ * pₖ + rNorm = abs(σ) # ‖rₖ₊₁‖_M = |σₖ₊₁| because rₖ₊₁ = σₖ₊₁ * vₖ₊₁ and ‖vₖ₊₁‖_M = 1 + history && push!(rNorms, rNorm) + iter = iter + 1 + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + (check_curvature & indefinite) && (status = "negative curvature") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats. TODO: Estimate Acond. + stats.niter = iter + stats.solved = solved + stats.Anorm = sqrt(Anorm2) + stats.indefinite = indefinite + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - (check_curvature & indefinite) && (status = "negative curvature") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats. TODO: Estimate Acond. - stats.niter = iter - stats.solved = solved - stats.Anorm = sqrt(Anorm2) - stats.indefinite = indefinite - stats.status = status - return solver end diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 228fe705c..7ce03fade 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -95,182 +95,179 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t cg_lanczos_shift!(solver, A, b, shifts; $(kwargs_cg_lanczos_shift...)) return (solver.x, solver.stats) end -end - -function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; - M=I, ldiv :: Bool=false, - check_curvature :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == n || error("Inconsistent problem size") - - nshifts = length(shifts) - nshifts == solver.nshifts || error("solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $nshifts") - (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :v, S, n) - Mv, Mv_prev, Mv_next = solver.Mv, solver.Mv_prev, solver.Mv_next - x, p, σ, δhat = solver.x, solver.p, solver.σ, solver.δhat - ω, γ, rNorms, converged = solver.ω, solver.γ, solver.rNorms, solver.converged - not_cv, stats = solver.not_cv, solver.stats - rNorms_history, indefinite = stats.residuals, stats.indefinite - reset!(stats) - v = MisI ? Mv : solver.v - - # Initial state. - ## Distribute x similarly to shifts. - for i = 1 : nshifts - x[i] .= zero(FC) # x₀ - end - Mv .= b # Mv₁ ← b - MisI || mulorldiv!(v, M, Mv, ldiv) # v₁ = M⁻¹ * Mv₁ - β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᴴ M v₁ - rNorms .= β - if history - for i = 1 : nshifts - push!(rNorms_history[i], rNorms[i]) - end - end - # Keep track of shifted systems with negative curvature if required. - indefinite .= false + function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - if β == 0 - stats.niter = 0 - stats.solved = true - stats.status = "x = 0 is a zero-residual solution" - return solver - end + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax - # Initialize each p to v. - for i = 1 : nshifts - p[i] .= v - end + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == n || error("Inconsistent problem size") - # Initialize Lanczos process. - # β₁Mv₁ = b - @kscal!(n, one(FC) / β, v) # v₁ ← v₁ / β₁ - MisI || @kscal!(n, one(FC) / β, Mv) # Mv₁ ← Mv₁ / β₁ - Mv_prev .= Mv - - # Initialize some constants used in recursions below. - ρ = one(T) - σ .= β - δhat .= zero(T) - ω .= zero(T) - γ .= one(T) - - # Define stopping tolerance. - ε = atol + rtol * β - - # Keep track of shifted systems that have converged. - for i = 1 : nshifts - converged[i] = rNorms[i] ≤ ε - not_cv[i] = !converged[i] - end - iter = 0 - itmax == 0 && (itmax = 2 * n) - - # Build format strings for printing. - (verbose > 0) && (fmt = Printf.Format("%5d" * repeat(" %8.1e", nshifts) * "\n")) - kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) - - solved = !reduce(|, not_cv) - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - # Main loop. - while ! (solved || tired || user_requested_exit || overtimed) - # Form next Lanczos vector. - # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ - mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ - δ = @kdotr(n, v, Mv_next) # δₖ = vₖᴴ A vₖ - @kaxpy!(n, -δ, Mv, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - δₖMvₖ - if iter > 0 - @kaxpy!(n, -β, Mv_prev, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - βₖMvₖ₋₁ - @. Mv_prev = Mv # Mvₖ₋₁ ← Mvₖ - end - @. Mv = Mv_next # Mvₖ ← Mvₖ₊₁ - MisI || mulorldiv!(v, M, Mv, ldiv) # vₖ₊₁ = M⁻¹ * Mvₖ₊₁ - β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᴴ M vₖ₊₁ - @kscal!(n, one(FC) / β, v) # vₖ₊₁ ← vₖ₊₁ / βₖ₊₁ - MisI || @kscal!(n, one(FC) / β, Mv) # Mvₖ₊₁ ← Mvₖ₊₁ / βₖ₊₁ - - # Check curvature: vₖᴴ(A + sᵢI)vₖ = vₖᴴAvₖ + sᵢ‖vₖ‖² = δₖ + ρₖ * sᵢ with ρₖ = ‖vₖ‖². - # It is possible to show that σₖ² (δₖ + ρₖ * sᵢ - ωₖ₋₁ / γₖ₋₁) = pₖᴴ (A + sᵢ I) pₖ. - MisI || (ρ = @kdotr(n, v, v)) + nshifts = length(shifts) + nshifts == solver.nshifts || error("solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $nshifts") + (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :v, S, n) + Mv, Mv_prev, Mv_next = solver.Mv, solver.Mv_prev, solver.Mv_next + x, p, σ, δhat = solver.x, solver.p, solver.σ, solver.δhat + ω, γ, rNorms, converged = solver.ω, solver.γ, solver.rNorms, solver.converged + not_cv, stats = solver.not_cv, solver.stats + rNorms_history, indefinite = stats.residuals, stats.indefinite + reset!(stats) + v = MisI ? Mv : solver.v + + # Initial state. + ## Distribute x similarly to shifts. for i = 1 : nshifts - δhat[i] = δ + ρ * shifts[i] - γ[i] = 1 / (δhat[i] - ω[i] / γ[i]) + x[i] .= zero(FC) # x₀ end - for i = 1 : nshifts - indefinite[i] |= γ[i] ≤ 0 + Mv .= b # Mv₁ ← b + MisI || mulorldiv!(v, M, Mv, ldiv) # v₁ = M⁻¹ * Mv₁ + β = sqrt(@kdotr(n, v, Mv)) # β₁ = v₁ᴴ M v₁ + rNorms .= β + if history + for i = 1 : nshifts + push!(rNorms_history[i], rNorms[i]) + end end - # Compute next CG iterate for each shifted system that has not yet converged. - # Stop iterating on indefinite problems if requested. - for i = 1 : nshifts - not_cv[i] = check_curvature ? !(converged[i] || indefinite[i]) : !converged[i] - if not_cv[i] - @kaxpy!(n, γ[i], p[i], x[i]) - ω[i] = β * γ[i] - σ[i] *= -ω[i] - ω[i] *= ω[i] - @kaxpby!(n, σ[i], v, ω[i], p[i]) - - # Update list of systems that have not converged. - rNorms[i] = abs(σ[i]) - converged[i] = rNorms[i] ≤ ε - end + # Keep track of shifted systems with negative curvature if required. + indefinite .= false + + if β == 0 + stats.niter = 0 + stats.solved = true + stats.status = "x = 0 is a zero-residual solution" + return solver end - if length(not_cv) > 0 && history - for i = 1 : nshifts - not_cv[i] && push!(rNorms_history[i], rNorms[i]) - end + # Initialize each p to v. + for i = 1 : nshifts + p[i] .= v end - # Is there a better way than to update this array twice per iteration? + # Initialize Lanczos process. + # β₁Mv₁ = b + @kscal!(n, one(FC) / β, v) # v₁ ← v₁ / β₁ + MisI || @kscal!(n, one(FC) / β, Mv) # Mv₁ ← Mv₁ / β₁ + Mv_prev .= Mv + + # Initialize some constants used in recursions below. + ρ = one(T) + σ .= β + δhat .= zero(T) + ω .= zero(T) + γ .= one(T) + + # Define stopping tolerance. + ε = atol + rtol * β + + # Keep track of shifted systems that have converged. for i = 1 : nshifts - not_cv[i] = check_curvature ? !(converged[i] || indefinite[i]) : !converged[i] + converged[i] = rNorms[i] ≤ ε + not_cv[i] = !converged[i] end - iter = iter + 1 + iter = 0 + itmax == 0 && (itmax = 2 * n) + + # Build format strings for printing. + (verbose > 0) && (fmt = Printf.Format("%5d" * repeat(" %8.1e", nshifts) * "\n")) kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) - user_requested_exit = callback(solver) :: Bool solved = !reduce(|, not_cv) tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + status = "unknown" + user_requested_exit = false + overtimed = false + + # Main loop. + while ! (solved || tired || user_requested_exit || overtimed) + # Form next Lanczos vector. + # βₖ₊₁Mvₖ₊₁ = Avₖ - δₖMvₖ - βₖMvₖ₋₁ + mul!(Mv_next, A, v) # Mvₖ₊₁ ← Avₖ + δ = @kdotr(n, v, Mv_next) # δₖ = vₖᴴ A vₖ + @kaxpy!(n, -δ, Mv, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - δₖMvₖ + if iter > 0 + @kaxpy!(n, -β, Mv_prev, Mv_next) # Mvₖ₊₁ ← Mvₖ₊₁ - βₖMvₖ₋₁ + @. Mv_prev = Mv # Mvₖ₋₁ ← Mvₖ + end + @. Mv = Mv_next # Mvₖ ← Mvₖ₊₁ + MisI || mulorldiv!(v, M, Mv, ldiv) # vₖ₊₁ = M⁻¹ * Mvₖ₊₁ + β = sqrt(@kdotr(n, v, Mv)) # βₖ₊₁ = vₖ₊₁ᴴ M vₖ₊₁ + @kscal!(n, one(FC) / β, v) # vₖ₊₁ ← vₖ₊₁ / βₖ₊₁ + MisI || @kscal!(n, one(FC) / β, Mv) # Mvₖ₊₁ ← Mvₖ₊₁ / βₖ₊₁ + + # Check curvature: vₖᴴ(A + sᵢI)vₖ = vₖᴴAvₖ + sᵢ‖vₖ‖² = δₖ + ρₖ * sᵢ with ρₖ = ‖vₖ‖². + # It is possible to show that σₖ² (δₖ + ρₖ * sᵢ - ωₖ₋₁ / γₖ₋₁) = pₖᴴ (A + sᵢ I) pₖ. + MisI || (ρ = @kdotr(n, v, v)) + for i = 1 : nshifts + δhat[i] = δ + ρ * shifts[i] + γ[i] = 1 / (δhat[i] - ω[i] / γ[i]) + end + for i = 1 : nshifts + indefinite[i] |= γ[i] ≤ 0 + end + + # Compute next CG iterate for each shifted system that has not yet converged. + # Stop iterating on indefinite problems if requested. + for i = 1 : nshifts + not_cv[i] = check_curvature ? !(converged[i] || indefinite[i]) : !converged[i] + if not_cv[i] + @kaxpy!(n, γ[i], p[i], x[i]) + ω[i] = β * γ[i] + σ[i] *= -ω[i] + ω[i] *= ω[i] + @kaxpby!(n, σ[i], v, ω[i], p[i]) + + # Update list of systems that have not converged. + rNorms[i] = abs(σ[i]) + converged[i] = rNorms[i] ≤ ε + end + end + + if length(not_cv) > 0 && history + for i = 1 : nshifts + not_cv[i] && push!(rNorms_history[i], rNorms[i]) + end + end + + # Is there a better way than to update this array twice per iteration? + for i = 1 : nshifts + not_cv[i] = check_curvature ? !(converged[i] || indefinite[i]) : !converged[i] + end + iter = iter + 1 + kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) + + user_requested_exit = callback(solver) :: Bool + solved = !reduce(|, not_cv) + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats. TODO: Estimate Anorm and Acond. + stats.niter = iter + stats.solved = solved + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats. TODO: Estimate Anorm and Acond. - stats.niter = iter - stats.solved = solved - stats.status = status - return solver end diff --git a/src/cgls.jl b/src/cgls.jl index 223389cd8..850bedfa0 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -118,120 +118,118 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose cgls!(solver, A, b; $(kwargs_cgls...)) return (solver.x, solver.stats) end -end -function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, radius :: T=zero(T), - λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CGLS: system of %d equations in %d variables\n", m, n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :Mr, S, m) - x, p, s, r, q, stats = solver.x, solver.p, solver.s, solver.r, solver.q, solver.stats - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - Mr = MisI ? r : solver.Mr - Mq = MisI ? q : solver.Mr - - x .= zero(FC) - r .= b - bNorm = @knrm2(m, r) # Marginally faster than norm(b) - if bNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(rNorms, zero(T)) - history && push!(ArNorms, zero(T)) - return solver - end - MisI || mulorldiv!(Mr, M, r, ldiv) - mul!(s, Aᴴ, Mr) - p .= s - γ = @kdotr(n, s, s) # γ = sᴴs - iter = 0 - itmax == 0 && (itmax = m + n) - - rNorm = bNorm - ArNorm = sqrt(γ) - history && push!(rNorms, rNorm) - history && push!(ArNorms, ArNorm) - ε = atol + rtol * ArNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) - - status = "unknown" - on_boundary = false - solved = ArNorm ≤ ε - tired = iter ≥ itmax - user_requested_exit = false - overtimed = false - - while ! (solved || tired || user_requested_exit || overtimed) - mul!(q, A, p) - MisI || mulorldiv!(Mq, M, q, ldiv) - δ = @kdotr(m, q, Mq) # δ = qᴴMq - λ > 0 && (δ += λ * @kdotr(n, p, p)) # δ = δ + pᴴp - α = γ / δ - - # if a trust-region constraint is give, compute step to the boundary - σ = radius > 0 ? maximum(to_boundary(n, x, p, radius)) : α - if (radius > 0) & (α > σ) - α = σ - on_boundary = true + function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CGLS: system of %d equations in %d variables\n", m, n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :Mr, S, m) + x, p, s, r, q, stats = solver.x, solver.p, solver.s, solver.r, solver.q, solver.stats + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + Mr = MisI ? r : solver.Mr + Mq = MisI ? q : solver.Mr + + x .= zero(FC) + r .= b + bNorm = @knrm2(m, r) # Marginally faster than norm(b) + if bNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(rNorms, zero(T)) + history && push!(ArNorms, zero(T)) + return solver end - - @kaxpy!(n, α, p, x) # Faster than x = x + α * p - @kaxpy!(m, -α, q, r) # Faster than r = r - α * q MisI || mulorldiv!(Mr, M, r, ldiv) mul!(s, Aᴴ, Mr) - λ > 0 && @kaxpy!(n, -λ, x, s) # s = A' * r - λ * x - γ_next = @kdotr(n, s, s) # γ_next = sᴴs - β = γ_next / γ - @kaxpby!(n, one(FC), s, β, p) # p = s + βp - γ = γ_next - rNorm = @knrm2(m, r) # Marginally faster than norm(r) + p .= s + γ = @kdotr(n, s, s) # γ = sᴴs + iter = 0 + itmax == 0 && (itmax = m + n) + + rNorm = bNorm ArNorm = sqrt(γ) history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) - iter = iter + 1 + ε = atol + rtol * ArNorm + (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) - user_requested_exit = callback(solver) :: Bool - solved = (ArNorm ≤ ε) || on_boundary + + status = "unknown" + on_boundary = false + solved = ArNorm ≤ ε tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + user_requested_exit = false + overtimed = false + + while ! (solved || tired || user_requested_exit || overtimed) + mul!(q, A, p) + MisI || mulorldiv!(Mq, M, q, ldiv) + δ = @kdotr(m, q, Mq) # δ = qᴴMq + λ > 0 && (δ += λ * @kdotr(n, p, p)) # δ = δ + pᴴp + α = γ / δ + + # if a trust-region constraint is give, compute step to the boundary + σ = radius > 0 ? maximum(to_boundary(n, x, p, radius)) : α + if (radius > 0) & (α > σ) + α = σ + on_boundary = true + end + + @kaxpy!(n, α, p, x) # Faster than x = x + α * p + @kaxpy!(m, -α, q, r) # Faster than r = r - α * q + MisI || mulorldiv!(Mr, M, r, ldiv) + mul!(s, Aᴴ, Mr) + λ > 0 && @kaxpy!(n, -λ, x, s) # s = A' * r - λ * x + γ_next = @kdotr(n, s, s) # γ_next = sᴴs + β = γ_next / γ + @kaxpby!(n, one(FC), s, β, p) # p = s + βp + γ = γ_next + rNorm = @knrm2(m, r) # Marginally faster than norm(r) + ArNorm = sqrt(γ) + history && push!(rNorms, rNorm) + history && push!(ArNorms, ArNorm) + iter = iter + 1 + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + user_requested_exit = callback(solver) :: Bool + solved = (ArNorm ≤ ε) || on_boundary + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + on_boundary && (status = "on trust-region boundary") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - on_boundary && (status = "on trust-region boundary") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/cgne.jl b/src/cgne.jl index 0f80b2698..3c98a8988 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -123,125 +123,122 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor cgne!(solver, A, b; $(kwargs_cgne...)) return (solver.x, solver.stats) end -end -function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; - N=I, ldiv :: Bool=false, - λ :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CGNE: system of %d equations in %d variables\n", m, n) - - # Tests N = Iₙ - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!NisI, solver, :z, S, m) - allocate_if(λ > 0, solver, :s, S, m) - x, p, Aᴴz, r, q, s, stats = solver.x, solver.p, solver.Aᴴz, solver.r, solver.q, solver.s, solver.stats - rNorms = stats.residuals - reset!(stats) - z = NisI ? r : solver.z - - x .= zero(FC) - r .= b - NisI || mulorldiv!(z, N, r, ldiv) - rNorm = @knrm2(m, r) # Marginally faster than norm(r) - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - return solver - end - λ > 0 && (s .= r) - mul!(p, Aᴴ, z) - - # Use ‖p‖ to detect inconsistent system. - # An inconsistent system will necessarily have AA' singular. - # Because CGNE is equivalent to CG applied to AA'y = b, there will be a - # conjugate direction u such that u'AA'u = 0, i.e., A'u = 0. In this - # implementation, p is a substitute for A'u. - pNorm = @knrm2(n, p) - - γ = @kdotr(m, r, z) # Faster than γ = dot(r, z) - iter = 0 - itmax == 0 && (itmax = m + n) - - ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. - ɛ_i = atol + rtol * pNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf(iostream, "%5s %8s\n", "k", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) - - status = "unknown" - solved = rNorm ≤ ɛ_c - inconsistent = (rNorm > 100 * ɛ_c) && (pNorm ≤ ɛ_i) - tired = iter ≥ itmax - user_requested_exit = false - overtimed = false - - while ! (solved || inconsistent || tired || user_requested_exit || overtimed) - mul!(q, A, p) - λ > 0 && @kaxpy!(m, λ, s, q) - δ = @kdotr(n, p, p) # Faster than dot(p, p) - λ > 0 && (δ += λ * @kdotr(m, s, s)) - α = γ / δ - @kaxpy!(n, α, p, x) # Faster than x = x + α * p - @kaxpy!(m, -α, q, r) # Faster than r = r - α * q + function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CGNE: system of %d equations in %d variables\n", m, n) + + # Tests N = Iₙ + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!NisI, solver, :z, S, m) + allocate_if(λ > 0, solver, :s, S, m) + x, p, Aᴴz, r, q, s, stats = solver.x, solver.p, solver.Aᴴz, solver.r, solver.q, solver.s, solver.stats + rNorms = stats.residuals + reset!(stats) + z = NisI ? r : solver.z + + x .= zero(FC) + r .= b NisI || mulorldiv!(z, N, r, ldiv) - γ_next = @kdotr(m, r, z) # Faster than γ_next = dot(r, z) - β = γ_next / γ - mul!(Aᴴz, Aᴴ, z) - @kaxpby!(n, one(FC), Aᴴz, β, p) # Faster than p = Aᴴz + β * p - pNorm = @knrm2(n, p) - if λ > 0 - @kaxpby!(m, one(FC), r, β, s) # s = r + β * s - end - γ = γ_next - rNorm = sqrt(γ_next) + rNorm = @knrm2(m, r) # Marginally faster than norm(r) history && push!(rNorms, rNorm) - iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + return solver + end + λ > 0 && (s .= r) + mul!(p, Aᴴ, z) + + # Use ‖p‖ to detect inconsistent system. + # An inconsistent system will necessarily have AA' singular. + # Because CGNE is equivalent to CG applied to AA'y = b, there will be a + # conjugate direction u such that u'AA'u = 0, i.e., A'u = 0. In this + # implementation, p is a substitute for A'u. + pNorm = @knrm2(n, p) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + γ = @kdotr(m, r, z) # Faster than γ = dot(r, z) + iter = 0 + itmax == 0 && (itmax = m + n) - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ɛ_c - solved = resid_decrease_lim || resid_decrease_mach + ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. + ɛ_i = atol + rtol * pNorm # Stopping tolerance for inconsistent systems. + (verbose > 0) && @printf(iostream, "%5s %8s\n", "k", "‖r‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) + + status = "unknown" + solved = rNorm ≤ ɛ_c inconsistent = (rNorm > 100 * ɛ_c) && (pNorm ≤ ɛ_i) tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + user_requested_exit = false + overtimed = false + + while ! (solved || inconsistent || tired || user_requested_exit || overtimed) + mul!(q, A, p) + λ > 0 && @kaxpy!(m, λ, s, q) + δ = @kdotr(n, p, p) # Faster than dot(p, p) + λ > 0 && (δ += λ * @kdotr(m, s, s)) + α = γ / δ + @kaxpy!(n, α, p, x) # Faster than x = x + α * p + @kaxpy!(m, -α, q, r) # Faster than r = r - α * q + NisI || mulorldiv!(z, N, r, ldiv) + γ_next = @kdotr(m, r, z) # Faster than γ_next = dot(r, z) + β = γ_next / γ + mul!(Aᴴz, Aᴴ, z) + @kaxpby!(n, one(FC), Aᴴz, β, p) # Faster than p = Aᴴz + β * p + pNorm = @knrm2(n, p) + if λ > 0 + @kaxpby!(m, one(FC), r, β, s) # s = r + β * s + end + γ = γ_next + rNorm = sqrt(γ_next) + history && push!(rNorms, rNorm) + iter = iter + 1 + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ɛ_c + solved = resid_decrease_lim || resid_decrease_mach + inconsistent = (rNorm > 100 * ɛ_c) && (pNorm ≤ ɛ_i) + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + inconsistent && (status = "system probably inconsistent") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - inconsistent && (status = "system probably inconsistent") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/cgs.jl b/src/cgs.jl index d5e937f9d..3d7ab95f7 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -124,159 +124,156 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist cgs!(solver, A, b; $(kwargs_cgs...)) return solver end -end - -function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; - c :: AbstractVector{FC}=b, M=I, N=I, - ldiv :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CGS: system of size %d\n", n) - - # Check M = Iₙ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :vw, S, n) - allocate_if(!NisI, solver, :yz, S, n) - Δx, x, r, u, p, q, ts, stats = solver.Δx, solver.x, solver.r, solver.u, solver.p, solver.q, solver.ts, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - t = s = solver.ts - v = MisI ? t : solver.vw - w = MisI ? s : solver.vw - y = NisI ? p : solver.yz - z = NisI ? u : solver.yz - r₀ = MisI ? r : solver.ts - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - else - r₀ .= b - end - - x .= zero(FC) # x₀ - MisI || mulorldiv!(r, M, r₀, ldiv) # r₀ - - # Compute residual norm ‖r₀‖₂. - rNorm = @knrm2(n, r) - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - # Compute ρ₀ = ⟨ r̅₀,r₀ ⟩ - ρ = @kdot(n, c, r) - if ρ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = false, false - stats.status = "Breakdown bᴴc = 0" - solver.warm_start =false - return solver - end - - iter = 0 - itmax == 0 && (itmax = 2*n) - - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - - u .= r # u₀ - p .= r # p₀ - q .= zero(FC) # q₋₁ - - # Stopping criterion. - solved = rNorm ≤ ε - tired = iter ≥ itmax - breakdown = false - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || breakdown || user_requested_exit || overtimed) - - NisI || mulorldiv!(y, N, p, ldiv) # yₖ = N⁻¹pₖ - mul!(t, A, y) # tₖ = Ayₖ - MisI || mulorldiv!(v, M, t, ldiv) # vₖ = M⁻¹tₖ - σ = @kdot(n, c, v) # σₖ = ⟨ r̅₀,M⁻¹AN⁻¹pₖ ⟩ - α = ρ / σ # αₖ = ρₖ / σₖ - @kcopy!(n, u, q) # qₖ = uₖ - @kaxpy!(n, -α, v, q) # qₖ = qₖ - αₖ * M⁻¹AN⁻¹pₖ - @kaxpy!(n, one(FC), q, u) # uₖ₊½ = uₖ + qₖ - NisI || mulorldiv!(z, N, u, ldiv) # zₖ = N⁻¹uₖ₊½ - @kaxpy!(n, α, z, x) # xₖ₊₁ = xₖ + αₖ * N⁻¹(uₖ + qₖ) - mul!(s, A, z) # sₖ = Azₖ - MisI || mulorldiv!(w, M, s, ldiv) # wₖ = M⁻¹sₖ - @kaxpy!(n, -α, w, r) # rₖ₊₁ = rₖ - αₖ * M⁻¹AN⁻¹(uₖ + qₖ) - ρ_next = @kdot(n, c, r) # ρₖ₊₁ = ⟨ r̅₀,rₖ₊₁ ⟩ - β = ρ_next / ρ # βₖ = ρₖ₊₁ / ρₖ - @kcopy!(n, r, u) # uₖ₊₁ = rₖ₊₁ - @kaxpy!(n, β, q, u) # uₖ₊₁ = uₖ₊₁ + βₖ * qₖ - @kaxpby!(n, one(FC), q, β, p) # pₐᵤₓ = qₖ + βₖ * pₖ - @kaxpby!(n, one(FC), u, β, p) # pₖ₊₁ = uₖ₊₁ + βₖ * pₐᵤₓ - - # Update ρ. - ρ = ρ_next # ρₖ ← ρₖ₊₁ - - # Update iteration index. - iter = iter + 1 - - # Compute residual norm ‖rₖ‖₂. + function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CGS: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :vw, S, n) + allocate_if(!NisI, solver, :yz, S, n) + Δx, x, r, u, p, q, ts, stats = solver.Δx, solver.x, solver.r, solver.u, solver.p, solver.q, solver.ts, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + t = s = solver.ts + v = MisI ? t : solver.vw + w = MisI ? s : solver.vw + y = NisI ? p : solver.yz + z = NisI ? u : solver.yz + r₀ = MisI ? r : solver.ts + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) + else + r₀ .= b + end + + x .= zero(FC) # x₀ + MisI || mulorldiv!(r, M, r₀, ldiv) # r₀ + + # Compute residual norm ‖r₀‖₂. rNorm = @knrm2(n, r) history && push!(rNorms, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end + + # Compute ρ₀ = ⟨ r̅₀,r₀ ⟩ + ρ = @kdot(n, c, r) + if ρ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = false, false + stats.status = "Breakdown bᴴc = 0" + solver.warm_start =false + return solver + end + + iter = 0 + itmax == 0 && (itmax = 2*n) + + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + u .= r # u₀ + p .= r # p₀ + q .= zero(FC) # q₋₁ - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - solved = resid_decrease_lim || resid_decrease_mach + # Stopping criterion. + solved = rNorm ≤ ε tired = iter ≥ itmax - breakdown = (α == 0 || isnan(α)) - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + breakdown = false + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || breakdown || user_requested_exit || overtimed) + + NisI || mulorldiv!(y, N, p, ldiv) # yₖ = N⁻¹pₖ + mul!(t, A, y) # tₖ = Ayₖ + MisI || mulorldiv!(v, M, t, ldiv) # vₖ = M⁻¹tₖ + σ = @kdot(n, c, v) # σₖ = ⟨ r̅₀,M⁻¹AN⁻¹pₖ ⟩ + α = ρ / σ # αₖ = ρₖ / σₖ + @kcopy!(n, u, q) # qₖ = uₖ + @kaxpy!(n, -α, v, q) # qₖ = qₖ - αₖ * M⁻¹AN⁻¹pₖ + @kaxpy!(n, one(FC), q, u) # uₖ₊½ = uₖ + qₖ + NisI || mulorldiv!(z, N, u, ldiv) # zₖ = N⁻¹uₖ₊½ + @kaxpy!(n, α, z, x) # xₖ₊₁ = xₖ + αₖ * N⁻¹(uₖ + qₖ) + mul!(s, A, z) # sₖ = Azₖ + MisI || mulorldiv!(w, M, s, ldiv) # wₖ = M⁻¹sₖ + @kaxpy!(n, -α, w, r) # rₖ₊₁ = rₖ - αₖ * M⁻¹AN⁻¹(uₖ + qₖ) + ρ_next = @kdot(n, c, r) # ρₖ₊₁ = ⟨ r̅₀,rₖ₊₁ ⟩ + β = ρ_next / ρ # βₖ = ρₖ₊₁ / ρₖ + @kcopy!(n, r, u) # uₖ₊₁ = rₖ₊₁ + @kaxpy!(n, β, q, u) # uₖ₊₁ = uₖ₊₁ + βₖ * qₖ + @kaxpby!(n, one(FC), q, β, p) # pₐᵤₓ = qₖ + βₖ * pₖ + @kaxpby!(n, one(FC), u, β, p) # pₖ₊₁ = uₖ₊₁ + βₖ * pₐᵤₓ + + # Update ρ. + ρ = ρ_next # ρₖ ← ρₖ₊₁ + + # Update iteration index. + iter = iter + 1 + + # Compute residual norm ‖rₖ‖₂. + rNorm = @knrm2(n, r) + history && push!(rNorms, rNorm) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + breakdown = (α == 0 || isnan(α)) + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "breakdown αₖ == 0") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "breakdown αₖ == 0") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/cr.jl b/src/cr.jl index bd4493d31..19246c197 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -122,142 +122,179 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema cr!(solver, A, b; $(kwargs_cr...)) return solver end -end -function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, radius :: T=zero(T), - linesearch :: Bool=false, γ :: T=√eps(T), - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == n || error("Inconsistent problem size") - linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") - (verbose > 0) && @printf(iostream, "CR: system of %d equations in %d variables\n", n, n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace - allocate_if(!MisI, solver, :Mq, S, n) - Δx, x, r, p, q, Ar, stats = solver.Δx, solver.x, solver.r, solver.p, solver.q, solver.Ar, solver.stats - warm_start = solver.warm_start - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - Mq = MisI ? q : solver.Mq - - # Initial state. - x .= zero(FC) - if warm_start - mul!(p, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), p) - else - p .= b - end - mulorldiv!(r, M, p, ldiv) - mul!(Ar, A, r) - ρ = @kdotr(n, r, Ar) - - rNorm = sqrt(@kdotr(n, r, p)) # ‖r‖ - history && push!(rNorms, rNorm) # Values of ‖r‖ - - if ρ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(ArNorms, zero(T)) - solver.warm_start = false - return solver - end - p .= r - q .= Ar - (verbose > 0) && (m = zero(T)) # quadratic model - - iter = 0 - itmax == 0 && (itmax = 2 * n) - - rNorm² = rNorm * rNorm - pNorm = rNorm - pNorm² = rNorm² - pr = rNorm² - abspr = pr - pAp = ρ - abspAp = abs(pAp) - xNorm = zero(T) - ArNorm = @knrm2(n, Ar) # ‖Ar‖ - history && push!(ArNorms, ArNorm) - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") - kdisplay(iter, verbose) && @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) - - descent = pr > 0 # pᴴr > 0 means p is a descent direction - solved = rNorm ≤ ε - tired = iter ≥ itmax - on_boundary = false - npcurv = false - status = "unknown" - user_requested_exit = false - overtimed = false - - while ! (solved || tired || user_requested_exit || overtimed) - if linesearch - if (pAp ≤ γ * pNorm²) || (ρ ≤ γ * rNorm²) - npcurv = true - (verbose > 0) && @printf(iostream, "nonpositive curvature detected: pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) - stats.solved = solved - stats.inconsistent = false - stats.status = "nonpositive curvature" - return solver - end - elseif pAp ≤ 0 && radius == 0 - error("Indefinite system and no trust region") + function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == n || error("Inconsistent problem size") + linesearch && (radius > 0) && error("'linesearch' set to 'true' but radius > 0") + (verbose > 0) && @printf(iostream, "CR: system of %d equations in %d variables\n", n, n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace + allocate_if(!MisI, solver, :Mq, S, n) + Δx, x, r, p, q, Ar, stats = solver.Δx, solver.x, solver.r, solver.p, solver.q, solver.Ar, solver.stats + warm_start = solver.warm_start + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + Mq = MisI ? q : solver.Mq + + # Initial state. + x .= zero(FC) + if warm_start + mul!(p, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), p) + else + p .= b end - MisI || mulorldiv!(Mq, M, q, ldiv) - - if radius > 0 - (verbose > 0) && @printf(iostream, "radius = %8.1e > 0 and ‖x‖ = %8.1e\n", radius, xNorm) - # find t1 > 0 and t2 < 0 such that ‖x + ti * p‖² = radius² (i = 1, 2) - xNorm² = xNorm * xNorm - t = to_boundary(n, x, p, radius; flip = false, xNorm2 = xNorm², dNorm2 = pNorm²) - t1 = maximum(t) # > 0 - t2 = minimum(t) # < 0 - tr = maximum(to_boundary(n, x, r, radius; flip = false, xNorm2 = xNorm², dNorm2 = rNorm²)) - (verbose > 0) && @printf(iostream, "t1 = %8.1e, t2 = %8.1e and tr = %8.1e\n", t1, t2, tr) - - if abspAp ≤ γ * pNorm * @knrm2(n, q) # pᴴAp ≃ 0 - npcurv = true # nonpositive curvature - (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e ≃ 0\n", pAp) - if abspr ≤ γ * pNorm * rNorm # pᴴr ≃ 0 - (verbose > 0) && @printf(iostream, "pᴴr = %8.1e ≃ 0, redefining p := r\n", pr) - p = r # - ∇q(x) - q = Ar - # q(x + αr) = q(x) - α ‖r‖² + ½ α² rᴴAr - # 1) if rᴴAr > 0, the quadratic decreases from α = 0 to α = ‖r‖² / rᴴAr - # 2) if rᴴAr ≤ 0, the quadratic decreases to -∞ in the direction r - if ρ > 0 # case 1 - (verbose > 0) && @printf(iostream, "quadratic is convex in direction r, curv = %8.1e\n", ρ) - α = min(tr, rNorm² / ρ) - else # case 2 - (verbose > 0) && @printf(iostream, "r is a direction of nonpositive curvature: %8.1e\n", ρ) + mulorldiv!(r, M, p, ldiv) + mul!(Ar, A, r) + ρ = @kdotr(n, r, Ar) + + rNorm = sqrt(@kdotr(n, r, p)) # ‖r‖ + history && push!(rNorms, rNorm) # Values of ‖r‖ + + if ρ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(ArNorms, zero(T)) + solver.warm_start = false + return solver + end + p .= r + q .= Ar + (verbose > 0) && (m = zero(T)) # quadratic model + + iter = 0 + itmax == 0 && (itmax = 2 * n) + + rNorm² = rNorm * rNorm + pNorm = rNorm + pNorm² = rNorm² + pr = rNorm² + abspr = pr + pAp = ρ + abspAp = abs(pAp) + xNorm = zero(T) + ArNorm = @knrm2(n, Ar) # ‖Ar‖ + history && push!(ArNorms, ArNorm) + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") + kdisplay(iter, verbose) && @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + + descent = pr > 0 # pᴴr > 0 means p is a descent direction + solved = rNorm ≤ ε + tired = iter ≥ itmax + on_boundary = false + npcurv = false + status = "unknown" + user_requested_exit = false + overtimed = false + + while ! (solved || tired || user_requested_exit || overtimed) + if linesearch + if (pAp ≤ γ * pNorm²) || (ρ ≤ γ * rNorm²) + npcurv = true + (verbose > 0) && @printf(iostream, "nonpositive curvature detected: pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) + stats.solved = solved + stats.inconsistent = false + stats.status = "nonpositive curvature" + return solver + end + elseif pAp ≤ 0 && radius == 0 + error("Indefinite system and no trust region") + end + MisI || mulorldiv!(Mq, M, q, ldiv) + + if radius > 0 + (verbose > 0) && @printf(iostream, "radius = %8.1e > 0 and ‖x‖ = %8.1e\n", radius, xNorm) + # find t1 > 0 and t2 < 0 such that ‖x + ti * p‖² = radius² (i = 1, 2) + xNorm² = xNorm * xNorm + t = to_boundary(n, x, p, radius; flip = false, xNorm2 = xNorm², dNorm2 = pNorm²) + t1 = maximum(t) # > 0 + t2 = minimum(t) # < 0 + tr = maximum(to_boundary(n, x, r, radius; flip = false, xNorm2 = xNorm², dNorm2 = rNorm²)) + (verbose > 0) && @printf(iostream, "t1 = %8.1e, t2 = %8.1e and tr = %8.1e\n", t1, t2, tr) + + if abspAp ≤ γ * pNorm * @knrm2(n, q) # pᴴAp ≃ 0 + npcurv = true # nonpositive curvature + (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e ≃ 0\n", pAp) + if abspr ≤ γ * pNorm * rNorm # pᴴr ≃ 0 + (verbose > 0) && @printf(iostream, "pᴴr = %8.1e ≃ 0, redefining p := r\n", pr) + p = r # - ∇q(x) + q = Ar + # q(x + αr) = q(x) - α ‖r‖² + ½ α² rᴴAr + # 1) if rᴴAr > 0, the quadratic decreases from α = 0 to α = ‖r‖² / rᴴAr + # 2) if rᴴAr ≤ 0, the quadratic decreases to -∞ in the direction r + if ρ > 0 # case 1 + (verbose > 0) && @printf(iostream, "quadratic is convex in direction r, curv = %8.1e\n", ρ) + α = min(tr, rNorm² / ρ) + else # case 2 + (verbose > 0) && @printf(iostream, "r is a direction of nonpositive curvature: %8.1e\n", ρ) + α = tr + end + else + # q_p = q(x + α_p * p) - q(x) = -α_p * rᴴp + ½ (α_p)² * pᴴAp + # q_r = q(x + α_r * r) - q(x) = -α_r * ‖r‖² + ½ (α_r)² * rᴴAr + # Δ = q_p - q_r. If Δ > 0, r is followed, else p is followed + α = descent ? t1 : t2 + ρ > 0 && (tr = min(tr, rNorm² / ρ)) + Δ = -α * pr + tr * rNorm² - (tr)^2 * ρ / 2 # as pᴴAp = 0 + if Δ > 0 # direction r engenders a better decrease + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") + p = r + q = Ar + α = tr + else + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + end + end + + elseif pAp > 0 && ρ > 0 # no negative curvature + (verbose > 0) && @printf(iostream, "positive curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) + α = ρ / @kdotr(n, q, Mq) + if α ≥ t1 + α = t1 + on_boundary = true + end + + elseif pAp > 0 && ρ < 0 + npcurv = true + (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e > 0 and rᴴAr = %8.1e < 0\n", pAp, ρ) + # q_p is minimal for α_p = rᴴp / pᴴAp + α = descent ? min(t1, pr / pAp) : max(t2, pr / pAp) + Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 + if Δ > 0 + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") + p = r + q = Ar α = tr + else + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) end - else - # q_p = q(x + α_p * p) - q(x) = -α_p * rᴴp + ½ (α_p)² * pᴴAp - # q_r = q(x + α_r * r) - q(x) = -α_r * ‖r‖² + ½ (α_r)² * rᴴAr - # Δ = q_p - q_r. If Δ > 0, r is followed, else p is followed + + elseif pAp < 0 && ρ > 0 + npcurv = true + (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e < 0 and rᴴAr = %8.1e > 0\n", pAp, ρ) α = descent ? t1 : t2 - ρ > 0 && (tr = min(tr, rNorm² / ρ)) - Δ = -α * pr + tr * rNorm² - (tr)^2 * ρ / 2 # as pᴴAp = 0 - if Δ > 0 # direction r engenders a better decrease + tr = min(tr, rNorm² / ρ) + Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 + if Δ > 0 (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) (verbose > 0) && @printf(iostream, "redefining p := r\n") p = r @@ -266,148 +303,108 @@ function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; else (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) end - end - elseif pAp > 0 && ρ > 0 # no negative curvature - (verbose > 0) && @printf(iostream, "positive curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) - α = ρ / @kdotr(n, q, Mq) - if α ≥ t1 - α = t1 - on_boundary = true - end - - elseif pAp > 0 && ρ < 0 - npcurv = true - (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e > 0 and rᴴAr = %8.1e < 0\n", pAp, ρ) - # q_p is minimal for α_p = rᴴp / pᴴAp - α = descent ? min(t1, pr / pAp) : max(t2, pr / pAp) - Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 - if Δ > 0 - (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf(iostream, "redefining p := r\n") - p = r - q = Ar - α = tr - else - (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) - end - - elseif pAp < 0 && ρ > 0 - npcurv = true - (verbose > 0) && @printf(iostream, "pᴴAp = %8.1e < 0 and rᴴAr = %8.1e > 0\n", pAp, ρ) - α = descent ? t1 : t2 - tr = min(tr, rNorm² / ρ) - Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 - if Δ > 0 - (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf(iostream, "redefining p := r\n") - p = r - q = Ar - α = tr - else - (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + elseif pAp < 0 && ρ < 0 + npcurv = true + (verbose > 0) && @printf(iostream, "negative curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) + α = descent ? t1 : t2 + Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 + if Δ > 0 + (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) + (verbose > 0) && @printf(iostream, "redefining p := r\n") + p = r + q = Ar + α = tr + else + (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) + end end - elseif pAp < 0 && ρ < 0 - npcurv = true - (verbose > 0) && @printf(iostream, "negative curvatures along p and r. pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) - α = descent ? t1 : t2 - Δ = -α * pr + tr * rNorm² + (α^2 * pAp - (tr)^2 * ρ) / 2 - if Δ > 0 - (verbose > 0) && @printf(iostream, "direction r engenders a bigger decrease. q_p - q_r = %8.1e > 0\n", Δ) - (verbose > 0) && @printf(iostream, "redefining p := r\n") - p = r - q = Ar - α = tr - else - (verbose > 0) && @printf(iostream, "direction p engenders an equal or a bigger decrease. q_p - q_r = %8.1e ≤ 0\n", Δ) - end + elseif radius == 0 + α = ρ / @kdotr(n, q, Mq) # step end - elseif radius == 0 - α = ρ / @kdotr(n, q, Mq) # step - end - - @kaxpy!(n, α, p, x) - xNorm = @knrm2(n, x) - xNorm ≈ radius && (on_boundary = true) - @kaxpy!(n, -α, Mq, r) # residual - if MisI - rNorm² = @kdotr(n, r, r) - rNorm = sqrt(rNorm²) - else - ω = sqrt(α) * sqrt(ρ) - rNorm = sqrt(abs(rNorm + ω)) * sqrt(abs(rNorm - ω)) - rNorm² = rNorm * rNorm # rNorm² = rNorm² - α * ρ - end - history && push!(rNorms, rNorm) - mul!(Ar, A, r) - ArNorm = @knrm2(n, Ar) - history && push!(ArNorms, ArNorm) - - iter = iter + 1 - if kdisplay(iter, verbose) - m = m - α * pr + α^2 * pAp / 2 - @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) - end - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + @kaxpy!(n, α, p, x) + xNorm = @knrm2(n, x) + xNorm ≈ radius && (on_boundary = true) + @kaxpy!(n, -α, Mq, r) # residual + if MisI + rNorm² = @kdotr(n, r, r) + rNorm = sqrt(rNorm²) + else + ω = sqrt(α) * sqrt(ρ) + rNorm = sqrt(abs(rNorm + ω)) * sqrt(abs(rNorm - ω)) + rNorm² = rNorm * rNorm # rNorm² = rNorm² - α * ρ + end + history && push!(rNorms, rNorm) + mul!(Ar, A, r) + ArNorm = @knrm2(n, Ar) + history && push!(ArNorms, ArNorm) + + iter = iter + 1 + if kdisplay(iter, verbose) + m = m - α * pr + α^2 * pAp / 2 + @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + end - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - resid_decrease = resid_decrease_lim || resid_decrease_mach - solved = resid_decrease || npcurv || on_boundary - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + resid_decrease = resid_decrease_lim || resid_decrease_mach + solved = resid_decrease || npcurv || on_boundary + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + + (solved || tired || user_requested_exit || overtimed) && continue + ρbar = ρ + ρ = @kdotr(n, r, Ar) + β = ρ / ρbar # step for the direction computation + @kaxpby!(n, one(FC), r, β, p) + @kaxpby!(n, one(FC), Ar, β, q) + + pNorm² = rNorm² + 2 * β * pr - 2 * β * α * pAp + β^2 * pNorm² + if pNorm² > sqrt(eps(T)) + pNorm = sqrt(pNorm²) + elseif abs(pNorm²) ≤ sqrt(eps(T)) + pNorm = zero(T) + else + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = "solver encountered numerical issues" + solver.warm_start = false + return solver + end + pr = rNorm² + β * pr - β * α * pAp # pᴴr + abspr = abs(pr) + pAp = ρ + β^2 * pAp # pᴴq + abspAp = abs(pAp) + descent = pr > 0 - (solved || tired || user_requested_exit || overtimed) && continue - ρbar = ρ - ρ = @kdotr(n, r, Ar) - β = ρ / ρbar # step for the direction computation - @kaxpby!(n, one(FC), r, β, p) - @kaxpby!(n, one(FC), Ar, β, q) - - pNorm² = rNorm² + 2 * β * pr - 2 * β * α * pAp + β^2 * pNorm² - if pNorm² > sqrt(eps(T)) - pNorm = sqrt(pNorm²) - elseif abs(pNorm²) ≤ sqrt(eps(T)) - pNorm = zero(T) - else - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = "solver encountered numerical issues" - solver.warm_start = false - return solver end - pr = rNorm² + β * pr - β * α * pAp # pᴴr - abspr = abs(pr) - pAp = ρ + β^2 * pAp # pᴴq - abspAp = abs(pAp) - descent = pr > 0 + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + on_boundary && (status = "on trust-region boundary") + npcurv && (status = "nonpositive curvature") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - on_boundary && (status = "on trust-region boundary") - npcurv && (status = "nonpositive curvature") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/craig.jl b/src/craig.jl index 9a1aa3276..54e3b8f7e 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -161,201 +161,94 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at craig!(solver, A, b; $(kwargs_craig...)) return (solver.x, solver.y, solver.stats) end -end -function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - transfer_to_lsqr :: Bool=false, sqd :: Bool=false, - λ :: T=zero(T), btol :: T=√eps(T), - conlim :: T=1/√eps(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CRAIG: system of %d equations in %d variables\n", m, n) - - # Check sqd and λ parameters - sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") - sqd && (λ = one(T)) - - # Tests M = Iₘ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :u , S, m) - allocate_if(!NisI, solver, :v , S, n) - allocate_if(λ > 0, solver, :w2, S, n) - x, Nv, Aᴴu, y, w = solver.x, solver.Nv, solver.Aᴴu, solver.y, solver.w - Mu, Av, w2, stats = solver.Mu, solver.Av, solver.w2, solver.stats - rNorms = stats.residuals - reset!(stats) - u = MisI ? Mu : solver.u - v = NisI ? Nv : solver.v - - x .= zero(FC) - y .= zero(FC) - - Mu .= b - MisI || mulorldiv!(u, M, Mu, ldiv) - β₁ = sqrt(@kdotr(m, u, Mu)) - rNorm = β₁ - history && push!(rNorms, rNorm) - if β₁ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - return solver - end - β₁² = β₁^2 - β = β₁ - θ = β₁ # θ will differ from β when there is regularization (λ > 0). - ξ = -one(T) # Most recent component of x in Range(V). - δ = λ - ρ_prev = one(T) - - # Initialize Golub-Kahan process. - # β₁Mu₁ = b. - @kscal!(m, one(FC) / β₁, u) - MisI || @kscal!(m, one(FC) / β₁, Mu) - - Nv .= zero(FC) - w .= zero(FC) # Used to update y. - - λ > 0 && (w2 .= zero(FC)) - - Anorm² = zero(T) # Estimate of ‖A‖²_F. - Anorm = zero(T) - Dnorm² = zero(T) # Estimate of ‖(AᴴA)⁻¹‖². - Acond = zero(T) # Estimate of cond(A). - xNorm² = zero(T) # Estimate of ‖x‖². - xNorm = zero(T) - - iter = 0 - itmax == 0 && (itmax = m + n) - - ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. - ɛ_i = atol # Stopping tolerance for inconsistent systems. - ctol = conlim > 0 ? 1/conlim : zero(T) # Stopping tolerance for ill-conditioned operators. - (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s %8s %8s %7s\n", "k", "‖r‖", "‖x‖", "‖A‖", "κ(A)", "α", "β") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e\n", iter, rNorm, xNorm, Anorm, Acond) - - bkwerr = one(T) # initial value of the backward error ‖r‖ / √(‖b‖² + ‖A‖² ‖x‖²) - - status = "unknown" - - solved_lim = bkwerr ≤ btol - solved_mach = one(T) + bkwerr ≤ one(T) - solved_resid_tol = rNorm ≤ ɛ_c - solved_resid_lim = rNorm ≤ btol + atol * Anorm * xNorm / β₁ - solved = solved_mach | solved_lim | solved_resid_tol | solved_resid_lim - - ill_cond = ill_cond_mach = ill_cond_lim = false - - inconsistent = false - tired = iter ≥ itmax - user_requested_exit = false - overtimed = false - - while ! (solved || inconsistent || ill_cond || tired || user_requested_exit || overtimed) - # Generate the next Golub-Kahan vectors - # 1. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᴴu, Aᴴ, u) - @kaxpby!(n, one(FC), Aᴴu, -β, Nv) - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - if α == 0 - inconsistent = true - continue - end - @kscal!(n, one(FC) / α, v) - NisI || @kscal!(n, one(FC) / α, Nv) - - Anorm² += α * α + λ * λ - - if λ > 0 - # Givens rotation to zero out the δ in position (k, 2k): - # k-1 k 2k k 2k k-1 k 2k - # k [ θ α δ ] [ c₁ s₁ ] = [ θ ρ ] - # k+1 [ β ] [ s₁ -c₁ ] [ θ+ γ ] - (c₁, s₁, ρ) = sym_givens(α, δ) - else - ρ = α - end + function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - ξ = -θ / ρ * ξ - - if λ > 0 - # w1 = c₁ * v + s₁ * w2 - # w2 = s₁ * v - c₁ * w2 - # x = x + ξ * w1 - @kaxpy!(n, ξ * c₁, v, x) - @kaxpy!(n, ξ * s₁, w2, x) - @kaxpby!(n, s₁, v, -c₁, w2) - else - @kaxpy!(n, ξ, v, x) # x = x + ξ * v - end + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax - # Recur y. - @kaxpby!(m, one(FC), u, -θ/ρ_prev, w) # w = u - θ/ρ_prev * w - @kaxpy!(m, ξ/ρ, w, y) # y = y + ξ/ρ * w + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CRAIG: system of %d equations in %d variables\n", m, n) - Dnorm² += @knrm2(m, w) + # Check sqd and λ parameters + sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") + sqd && (λ = one(T)) - # 2. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ - mul!(Av, A, v) - @kaxpby!(m, one(FC), Av, -α, Mu) + # Tests M = Iₘ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :u , S, m) + allocate_if(!NisI, solver, :v , S, n) + allocate_if(λ > 0, solver, :w2, S, n) + x, Nv, Aᴴu, y, w = solver.x, solver.Nv, solver.Aᴴu, solver.y, solver.w + Mu, Av, w2, stats = solver.Mu, solver.Av, solver.w2, solver.stats + rNorms = stats.residuals + reset!(stats) + u = MisI ? Mu : solver.u + v = NisI ? Nv : solver.v + + x .= zero(FC) + y .= zero(FC) + + Mu .= b MisI || mulorldiv!(u, M, Mu, ldiv) - β = sqrt(@kdotr(m, u, Mu)) - if β ≠ 0 - @kscal!(m, one(FC) / β, u) - MisI || @kscal!(m, one(FC) / β, Mu) + β₁ = sqrt(@kdotr(m, u, Mu)) + rNorm = β₁ + history && push!(rNorms, rNorm) + if β₁ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + return solver end + β₁² = β₁^2 + β = β₁ + θ = β₁ # θ will differ from β when there is regularization (λ > 0). + ξ = -one(T) # Most recent component of x in Range(V). + δ = λ + ρ_prev = one(T) - # Finish updates from the first Givens rotation. - if λ > 0 - θ = β * c₁ - γ = β * s₁ - else - θ = β - end + # Initialize Golub-Kahan process. + # β₁Mu₁ = b. + @kscal!(m, one(FC) / β₁, u) + MisI || @kscal!(m, one(FC) / β₁, Mu) - if λ > 0 - # Givens rotation to zero out the γ in position (k+1, 2k) - # 2k 2k+1 2k 2k+1 2k 2k+1 - # k+1 [ γ λ ] [ -c₂ s₂ ] = [ 0 δ ] - # k+2 [ 0 0 ] [ s₂ c₂ ] [ 0 0 ] - c₂, s₂, δ = sym_givens(λ, γ) - @kscal!(n, s₂, w2) - end + Nv .= zero(FC) + w .= zero(FC) # Used to update y. - Anorm² += β * β - Anorm = sqrt(Anorm²) - Acond = Anorm * sqrt(Dnorm²) - xNorm² += ξ * ξ - xNorm = sqrt(xNorm²) - rNorm = β * abs(ξ) # r = - β * ξ * u - λ > 0 && (rNorm *= abs(c₁)) # r = -c₁ * β * ξ * u when λ > 0. - history && push!(rNorms, rNorm) - iter = iter + 1 + λ > 0 && (w2 .= zero(FC)) - bkwerr = rNorm / sqrt(β₁² + Anorm² * xNorm²) + Anorm² = zero(T) # Estimate of ‖A‖²_F. + Anorm = zero(T) + Dnorm² = zero(T) # Estimate of ‖(AᴴA)⁻¹‖². + Acond = zero(T) # Estimate of cond(A). + xNorm² = zero(T) # Estimate of ‖x‖². + xNorm = zero(T) - ρ_prev = ρ # Only differs from α if λ > 0. + iter = 0 + itmax == 0 && (itmax = m + n) - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e %8.1e %7.1e\n", iter, rNorm, xNorm, Anorm, Acond, α, β) + ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. + ɛ_i = atol # Stopping tolerance for inconsistent systems. + ctol = conlim > 0 ? 1/conlim : zero(T) # Stopping tolerance for ill-conditioned operators. + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s %8s %8s %7s\n", "k", "‖r‖", "‖x‖", "‖A‖", "κ(A)", "α", "β") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e\n", iter, rNorm, xNorm, Anorm, Acond) + + bkwerr = one(T) # initial value of the backward error ‖r‖ / √(‖b‖² + ‖A‖² ‖x‖²) + + status = "unknown" solved_lim = bkwerr ≤ btol solved_mach = one(T) + bkwerr ≤ one(T) @@ -363,38 +256,140 @@ function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; solved_resid_lim = rNorm ≤ btol + atol * Anorm * xNorm / β₁ solved = solved_mach | solved_lim | solved_resid_tol | solved_resid_lim - ill_cond_mach = one(T) + one(T) / Acond ≤ one(T) - ill_cond_lim = 1 / Acond ≤ ctol - ill_cond = ill_cond_mach | ill_cond_lim + ill_cond = ill_cond_mach = ill_cond_lim = false - user_requested_exit = callback(solver) :: Bool inconsistent = false tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - end - (verbose > 0) && @printf(iostream, "\n") + user_requested_exit = false + overtimed = false + + while ! (solved || inconsistent || ill_cond || tired || user_requested_exit || overtimed) + # Generate the next Golub-Kahan vectors + # 1. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + if α == 0 + inconsistent = true + continue + end + @kscal!(n, one(FC) / α, v) + NisI || @kscal!(n, one(FC) / α, Nv) + + Anorm² += α * α + λ * λ + + if λ > 0 + # Givens rotation to zero out the δ in position (k, 2k): + # k-1 k 2k k 2k k-1 k 2k + # k [ θ α δ ] [ c₁ s₁ ] = [ θ ρ ] + # k+1 [ β ] [ s₁ -c₁ ] [ θ+ γ ] + (c₁, s₁, ρ) = sym_givens(α, δ) + else + ρ = α + end + + ξ = -θ / ρ * ξ + + if λ > 0 + # w1 = c₁ * v + s₁ * w2 + # w2 = s₁ * v - c₁ * w2 + # x = x + ξ * w1 + @kaxpy!(n, ξ * c₁, v, x) + @kaxpy!(n, ξ * s₁, w2, x) + @kaxpby!(n, s₁, v, -c₁, w2) + else + @kaxpy!(n, ξ, v, x) # x = x + ξ * v + end + + # Recur y. + @kaxpby!(m, one(FC), u, -θ/ρ_prev, w) # w = u - θ/ρ_prev * w + @kaxpy!(m, ξ/ρ, w, y) # y = y + ξ/ρ * w + + Dnorm² += @knrm2(m, w) + + # 2. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ + mul!(Av, A, v) + @kaxpby!(m, one(FC), Av, -α, Mu) + MisI || mulorldiv!(u, M, Mu, ldiv) + β = sqrt(@kdotr(m, u, Mu)) + if β ≠ 0 + @kscal!(m, one(FC) / β, u) + MisI || @kscal!(m, one(FC) / β, Mu) + end + + # Finish updates from the first Givens rotation. + if λ > 0 + θ = β * c₁ + γ = β * s₁ + else + θ = β + end + + if λ > 0 + # Givens rotation to zero out the γ in position (k+1, 2k) + # 2k 2k+1 2k 2k+1 2k 2k+1 + # k+1 [ γ λ ] [ -c₂ s₂ ] = [ 0 δ ] + # k+2 [ 0 0 ] [ s₂ c₂ ] [ 0 0 ] + c₂, s₂, δ = sym_givens(λ, γ) + @kscal!(n, s₂, w2) + end + + Anorm² += β * β + Anorm = sqrt(Anorm²) + Acond = Anorm * sqrt(Dnorm²) + xNorm² += ξ * ξ + xNorm = sqrt(xNorm²) + rNorm = β * abs(ξ) # r = - β * ξ * u + λ > 0 && (rNorm *= abs(c₁)) # r = -c₁ * β * ξ * u when λ > 0. + history && push!(rNorms, rNorm) + iter = iter + 1 + + bkwerr = rNorm / sqrt(β₁² + Anorm² * xNorm²) + + ρ_prev = ρ # Only differs from α if λ > 0. + + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e %8.1e %7.1e\n", iter, rNorm, xNorm, Anorm, Acond, α, β) + + solved_lim = bkwerr ≤ btol + solved_mach = one(T) + bkwerr ≤ one(T) + solved_resid_tol = rNorm ≤ ɛ_c + solved_resid_lim = rNorm ≤ btol + atol * Anorm * xNorm / β₁ + solved = solved_mach | solved_lim | solved_resid_tol | solved_resid_lim + + ill_cond_mach = one(T) + one(T) / Acond ≤ one(T) + ill_cond_lim = 1 / Acond ≤ ctol + ill_cond = ill_cond_mach | ill_cond_lim + + user_requested_exit = callback(solver) :: Bool + inconsistent = false + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") - # transfer to LSQR point if requested - if λ > 0 && transfer_to_lsqr - ξ *= -θ / δ - @kaxpy!(n, ξ, w2, x) - # TODO: update y - end + # transfer to LSQR point if requested + if λ > 0 && transfer_to_lsqr + ξ *= -θ / δ + @kaxpy!(n, ξ, w2, x) + # TODO: update y + end - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough for the tolerances given") - ill_cond_mach && (status = "condition number seems too large for this machine") - ill_cond_lim && (status = "condition number exceeds tolerance") - inconsistent && (status = "system may be inconsistent") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough for the tolerances given") + ill_cond_mach && (status = "condition number seems too large for this machine") + ill_cond_lim && (status = "condition number exceeds tolerance") + inconsistent && (status = "system may be inconsistent") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver + end end diff --git a/src/craigmr.jl b/src/craigmr.jl index 03b9c1b1a..ad5515447 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -150,242 +150,239 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver craigmr!(solver, A, b; $(kwargs_craigmr...)) return (solver.x, solver.y, solver.stats) end -end - -function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - sqd :: Bool=false, λ :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CRAIGMR: system of %d equations in %d variables\n", m, n) - - # Check sqd and λ parameters - sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") - sqd && (λ = one(T)) - - # Tests M = Iₘ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :u, S, m) - allocate_if(!NisI, solver, :v, S, n) - allocate_if(λ > 0, solver, :q, S, n) - x, Nv, Aᴴu, d, y, Mu = solver.x, solver.Nv, solver.Aᴴu, solver.d, solver.y, solver.Mu - w, wbar, Av, q, stats = solver.w, solver.wbar, solver.Av, solver.q, solver.stats - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - u = MisI ? Mu : solver.u - v = NisI ? Nv : solver.v - - # Compute y such that AAᴴy = b. Then recover x = Aᴴy. - x .= zero(FC) - y .= zero(FC) - Mu .= b - MisI || mulorldiv!(u, M, Mu, ldiv) - β = sqrt(@kdotr(m, u, Mu)) - if β == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - history && push!(rNorms, β) - history && push!(ArNorms, zero(T)) - stats.status = "x = 0 is a zero-residual solution" - return solver - end - - # Initialize Golub-Kahan process. - # β₁Mu₁ = b. - @kscal!(m, one(FC)/β, u) - MisI || @kscal!(m, one(FC)/β, Mu) - # α₁Nv₁ = Aᴴu₁. - mul!(Aᴴu, Aᴴ, u) - Nv .= Aᴴu - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - Anorm² = α * α - - iter = 0 - itmax == 0 && (itmax = m + n) - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β, α, β, α, 0, 1, Anorm²) - - # Aᴴb = 0 so x = 0 is a minimum least-squares solution - if α == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - history && push!(rNorms, β) - history && push!(ArNorms, zero(T)) - stats.status = "x = 0 is a minimum least-squares solution" - return solver - end - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - - # Regularization. - λₖ = λ # λ₁ = λ - cpₖ = spₖ = one(T) # Givens sines and cosines used to zero out λₖ - cdₖ = sdₖ = one(T) # Givens sines and cosines used to define λₖ₊₁ - λ > 0 && (q .= v) # Additional vector needed to update x, by definition q₀ = 0 - - if λ > 0 - (cpₖ, spₖ, αhat) = sym_givens(α, λₖ) - @kscal!(n, spₖ, q) # q̄₁ = sp₁ * v₁ - else - αhat = α - end - # Initialize other constants. - ζbar = β - ρbar = αhat - θ = zero(T) - rNorm = ζbar - history && push!(rNorms, rNorm) - ArNorm = α - history && push!(ArNorms, ArNorm) - - ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. - ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. - - wbar .= u - @kscal!(m, one(FC)/αhat, wbar) - w .= zero(FC) - d .= zero(FC) - - status = "unknown" - solved = rNorm ≤ ɛ_c - inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) - tired = iter ≥ itmax - user_requested_exit = false - overtimed = false - - while ! (solved || inconsistent || tired || user_requested_exit || overtimed) - iter = iter + 1 - - # Generate next Golub-Kahan vectors. - # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ - mul!(Av, A, v) - @kaxpby!(m, one(FC), Av, -α, Mu) + function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CRAIGMR: system of %d equations in %d variables\n", m, n) + + # Check sqd and λ parameters + sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") + sqd && (λ = one(T)) + + # Tests M = Iₘ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :u, S, m) + allocate_if(!NisI, solver, :v, S, n) + allocate_if(λ > 0, solver, :q, S, n) + x, Nv, Aᴴu, d, y, Mu = solver.x, solver.Nv, solver.Aᴴu, solver.d, solver.y, solver.Mu + w, wbar, Av, q, stats = solver.w, solver.wbar, solver.Av, solver.q, solver.stats + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + u = MisI ? Mu : solver.u + v = NisI ? Nv : solver.v + + # Compute y such that AAᴴy = b. Then recover x = Aᴴy. + x .= zero(FC) + y .= zero(FC) + Mu .= b MisI || mulorldiv!(u, M, Mu, ldiv) β = sqrt(@kdotr(m, u, Mu)) - if β ≠ 0 - @kscal!(m, one(FC)/β, u) - MisI || @kscal!(m, one(FC)/β, Mu) + if β == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + history && push!(rNorms, β) + history && push!(ArNorms, zero(T)) + stats.status = "x = 0 is a zero-residual solution" + return solver + end + + # Initialize Golub-Kahan process. + # β₁Mu₁ = b. + @kscal!(m, one(FC)/β, u) + MisI || @kscal!(m, one(FC)/β, Mu) + # α₁Nv₁ = Aᴴu₁. + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + Anorm² = α * α + + iter = 0 + itmax == 0 && (itmax = m + n) + + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β, α, β, α, 0, 1, Anorm²) + + # Aᴴb = 0 so x = 0 is a minimum least-squares solution + if α == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + history && push!(rNorms, β) + history && push!(ArNorms, zero(T)) + stats.status = "x = 0 is a minimum least-squares solution" + return solver end + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) - Anorm² = Anorm² + β * β # = ‖B_{k-1}‖² + # Regularization. + λₖ = λ # λ₁ = λ + cpₖ = spₖ = one(T) # Givens sines and cosines used to zero out λₖ + cdₖ = sdₖ = one(T) # Givens sines and cosines used to define λₖ₊₁ + λ > 0 && (q .= v) # Additional vector needed to update x, by definition q₀ = 0 if λ > 0 - βhat = cpₖ * β - λₐᵤₓ = spₖ * β + (cpₖ, spₖ, αhat) = sym_givens(α, λₖ) + @kscal!(n, spₖ, q) # q̄₁ = sp₁ * v₁ else - βhat = β + αhat = α end - # Continue QR factorization - # - # Q [ Lₖ β₁ e₁ ] = [ Rₖ zₖ ] : - # [ β 0 ] [ 0 ζbar ] - # - # k k+1 k k+1 k k+1 - # k [ c s ] [ ρbar ] = [ ρ θ⁺ ] - # k+1 [ s -c ] [ β α⁺ ] [ ρbar⁺ ] - # - # so that we obtain - # - # [ c s ] [ ζbar ] = [ ζ ] - # [ s -c ] [ 0 ] [ ζbar⁺ ] - (c, s, ρ) = sym_givens(ρbar, βhat) - ζ = c * ζbar - ζbar = s * ζbar - rNorm = abs(ζbar) + # Initialize other constants. + ζbar = β + ρbar = αhat + θ = zero(T) + rNorm = ζbar history && push!(rNorms, rNorm) + ArNorm = α + history && push!(ArNorms, ArNorm) - @kaxpby!(m, one(FC)/ρ, wbar, -θ/ρ, w) # w = (wbar - θ * w) / ρ - @kaxpy!(m, ζ, w, y) # y = y + ζ * w + ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. + ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. - if λ > 0 - # DₖRₖ = V̅ₖ with v̅ₖ = cpₖvₖ + spₖqₖ₋₁ - if iter == 1 - @kaxpy!(n, one(FC)/ρ, cpₖ * v, d) + wbar .= u + @kscal!(m, one(FC)/αhat, wbar) + w .= zero(FC) + d .= zero(FC) + + status = "unknown" + solved = rNorm ≤ ɛ_c + inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) + tired = iter ≥ itmax + user_requested_exit = false + overtimed = false + + while ! (solved || inconsistent || tired || user_requested_exit || overtimed) + iter = iter + 1 + + # Generate next Golub-Kahan vectors. + # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ + mul!(Av, A, v) + @kaxpby!(m, one(FC), Av, -α, Mu) + MisI || mulorldiv!(u, M, Mu, ldiv) + β = sqrt(@kdotr(m, u, Mu)) + if β ≠ 0 + @kscal!(m, one(FC)/β, u) + MisI || @kscal!(m, one(FC)/β, Mu) + end + + Anorm² = Anorm² + β * β # = ‖B_{k-1}‖² + + if λ > 0 + βhat = cpₖ * β + λₐᵤₓ = spₖ * β else - @kaxpby!(n, one(FC)/ρ, cpₖ * v, -θ/ρ, d) - @kaxpy!(n, one(FC)/ρ, spₖ * q, d) - @kaxpby!(n, spₖ, v, -cpₖ, q) # q̄ₖ ← spₖ * vₖ - cpₖ * qₖ₋₁ + βhat = β end - else - # DₖRₖ = Vₖ - if iter == 1 - @kaxpy!(n, one(FC)/ρ, v, d) + + # Continue QR factorization + # + # Q [ Lₖ β₁ e₁ ] = [ Rₖ zₖ ] : + # [ β 0 ] [ 0 ζbar ] + # + # k k+1 k k+1 k k+1 + # k [ c s ] [ ρbar ] = [ ρ θ⁺ ] + # k+1 [ s -c ] [ β α⁺ ] [ ρbar⁺ ] + # + # so that we obtain + # + # [ c s ] [ ζbar ] = [ ζ ] + # [ s -c ] [ 0 ] [ ζbar⁺ ] + (c, s, ρ) = sym_givens(ρbar, βhat) + ζ = c * ζbar + ζbar = s * ζbar + rNorm = abs(ζbar) + history && push!(rNorms, rNorm) + + @kaxpby!(m, one(FC)/ρ, wbar, -θ/ρ, w) # w = (wbar - θ * w) / ρ + @kaxpy!(m, ζ, w, y) # y = y + ζ * w + + if λ > 0 + # DₖRₖ = V̅ₖ with v̅ₖ = cpₖvₖ + spₖqₖ₋₁ + if iter == 1 + @kaxpy!(n, one(FC)/ρ, cpₖ * v, d) + else + @kaxpby!(n, one(FC)/ρ, cpₖ * v, -θ/ρ, d) + @kaxpy!(n, one(FC)/ρ, spₖ * q, d) + @kaxpby!(n, spₖ, v, -cpₖ, q) # q̄ₖ ← spₖ * vₖ - cpₖ * qₖ₋₁ + end else - @kaxpby!(n, one(FC)/ρ, v, -θ/ρ, d) + # DₖRₖ = Vₖ + if iter == 1 + @kaxpy!(n, one(FC)/ρ, v, d) + else + @kaxpby!(n, one(FC)/ρ, v, -θ/ρ, d) + end end - end - # xₖ = Dₖzₖ - @kaxpy!(n, ζ, d, x) + # xₖ = Dₖzₖ + @kaxpy!(n, ζ, d, x) - # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᴴu, Aᴴ, u) - @kaxpby!(n, one(FC), Aᴴu, -β, Nv) - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - Anorm² = Anorm² + α * α # = ‖Lₖ‖ - ArNorm = α * β * abs(ζ/ρ) - history && push!(ArNorms, ArNorm) + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + Anorm² = Anorm² + α * α # = ‖Lₖ‖ + ArNorm = α * β * abs(ζ/ρ) + history && push!(ArNorms, ArNorm) - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) - if λ > 0 - (cdₖ, sdₖ, λₖ₊₁) = sym_givens(λ, λₐᵤₓ) - @kscal!(n, sdₖ, q) # qₖ ← sdₖ * q̄ₖ - (cpₖ, spₖ, αhat) = sym_givens(α, λₖ₊₁) - else - αhat = α - end + if λ > 0 + (cdₖ, sdₖ, λₖ₊₁) = sym_givens(λ, λₐᵤₓ) + @kscal!(n, sdₖ, q) # qₖ ← sdₖ * q̄ₖ + (cpₖ, spₖ, αhat) = sym_givens(α, λₖ₊₁) + else + αhat = α + end - if α ≠ 0 - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - @kaxpby!(m, one(T)/αhat, u, -βhat / αhat, wbar) # wbar = (u - beta * wbar) / alpha + if α ≠ 0 + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) + @kaxpby!(m, one(T)/αhat, u, -βhat / αhat, wbar) # wbar = (u - beta * wbar) / alpha + end + θ = s * αhat + ρbar = -c * αhat + + user_requested_exit = callback(solver) :: Bool + solved = rNorm ≤ ɛ_c + inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end - θ = s * αhat - ρbar = -c * αhat - - user_requested_exit = callback(solver) :: Bool - solved = rNorm ≤ ɛ_c - inconsistent = (rNorm > 100 * ɛ_c) & (ArNorm ≤ ɛ_i) - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "found approximate minimum-norm solution") + !tired && !solved && (status = "found approximate minimum least-squares solution") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "found approximate minimum-norm solution") - !tired && !solved && (status = "found approximate minimum least-squares solution") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/crls.jl b/src/crls.jl index 9d9b90306..f53a94f01 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -109,154 +109,152 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose crls!(solver, A, b; $(kwargs_crls...)) return (solver.x, solver.stats) end -end -function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, radius :: T=zero(T), - λ :: T=zero(T), atol :: T=√eps(T), rtol :: T=√eps(T), - itmax :: Int=0, timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CRLS: system of %d equations in %d variables\n", m, n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :Ms, S, m) - x, p, Ar, q = solver.x, solver.p, solver.Ar, solver.q - r, Ap, s, stats = solver.r, solver.Ap, solver.s, solver.stats - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - Ms = MisI ? s : solver.Ms - Mr = MisI ? r : solver.Ms - MAp = MisI ? Ap : solver.Ms - - x .= zero(FC) - r .= b - bNorm = @knrm2(m, r) # norm(b - A * x0) if x0 ≠ 0. - rNorm = bNorm # + λ * ‖x0‖ if x0 ≠ 0 and λ > 0. - history && push!(rNorms, rNorm) - if bNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(ArNorms, zero(T)) - return solver - end - - MisI || mulorldiv!(Mr, M, r, ldiv) - mul!(Ar, Aᴴ, Mr) # - λ * x0 if x0 ≠ 0. - mul!(s, A, Ar) - MisI || mulorldiv!(Ms, M, s, ldiv) - - p .= Ar - Ap .= s - mul!(q, Aᴴ, Ms) # Ap - λ > 0 && @kaxpy!(n, λ, p, q) # q = q + λ * p - γ = @kdotr(m, s, Ms) # Faster than γ = dot(s, Ms) - iter = 0 - itmax == 0 && (itmax = m + n) - - ArNorm = @knrm2(n, Ar) # Marginally faster than norm(Ar) - λ > 0 && (γ += λ * ArNorm * ArNorm) - history && push!(ArNorms, ArNorm) - ε = atol + rtol * ArNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) - - status = "unknown" - on_boundary = false - solved = ArNorm ≤ ε - tired = iter ≥ itmax - psd = false - user_requested_exit = false - overtimed = false - - while ! (solved || tired || user_requested_exit || overtimed) - qNorm² = @kdotr(n, q, q) # dot(q, q) - α = γ / qNorm² - - # if a trust-region constraint is give, compute step to the boundary - # (note that α > 0 in CRLS) - if radius > 0 - pNorm = @knrm2(n, p) - if @kdotr(m, Ap, Ap) ≤ ε * sqrt(qNorm²) * pNorm # the quadratic is constant in the direction p - psd = true # det(AᴴA) = 0 - p = Ar # p = Aᴴr - pNorm² = ArNorm * ArNorm - mul!(q, Aᴴ, s) - α = min(ArNorm^2 / γ, maximum(to_boundary(n, x, p, radius, flip = false, dNorm2 = pNorm²))) # the quadratic is minimal in the direction Aᴴr for α = ‖Ar‖²/γ - else - pNorm² = pNorm * pNorm - σ = maximum(to_boundary(n, x, p, radius, flip = false, dNorm2 = pNorm²)) - if α ≥ σ - α = σ - on_boundary = true - end - end + function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CRLS: system of %d equations in %d variables\n", m, n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :Ms, S, m) + x, p, Ar, q = solver.x, solver.p, solver.Ar, solver.q + r, Ap, s, stats = solver.r, solver.Ap, solver.s, solver.stats + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + Ms = MisI ? s : solver.Ms + Mr = MisI ? r : solver.Ms + MAp = MisI ? Ap : solver.Ms + + x .= zero(FC) + r .= b + bNorm = @knrm2(m, r) # norm(b - A * x0) if x0 ≠ 0. + rNorm = bNorm # + λ * ‖x0‖ if x0 ≠ 0 and λ > 0. + history && push!(rNorms, rNorm) + if bNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(ArNorms, zero(T)) + return solver end - @kaxpy!(n, α, p, x) # Faster than x = x + α * p - @kaxpy!(n, -α, q, Ar) # Faster than Ar = Ar - α * q - ArNorm = @knrm2(n, Ar) - solved = psd || on_boundary - solved && continue - @kaxpy!(m, -α, Ap, r) # Faster than r = r - α * Ap + MisI || mulorldiv!(Mr, M, r, ldiv) + mul!(Ar, Aᴴ, Mr) # - λ * x0 if x0 ≠ 0. mul!(s, A, Ar) MisI || mulorldiv!(Ms, M, s, ldiv) - γ_next = @kdotr(m, s, Ms) # Faster than γ_next = dot(s, s) - λ > 0 && (γ_next += λ * ArNorm * ArNorm) - β = γ_next / γ - - @kaxpby!(n, one(FC), Ar, β, p) # Faster than p = Ar + β * p - @kaxpby!(m, one(FC), s, β, Ap) # Faster than Ap = s + β * Ap - MisI || mulorldiv!(MAp, M, Ap, ldiv) - mul!(q, Aᴴ, MAp) + + p .= Ar + Ap .= s + mul!(q, Aᴴ, Ms) # Ap λ > 0 && @kaxpy!(n, λ, p, q) # q = q + λ * p + γ = @kdotr(m, s, Ms) # Faster than γ = dot(s, Ms) + iter = 0 + itmax == 0 && (itmax = m + n) - γ = γ_next - if λ > 0 - rNorm = sqrt(@kdotr(m, r, r) + λ * @kdotr(n, x, x)) - else - rNorm = @knrm2(m, r) # norm(r) - end - history && push!(rNorms, rNorm) + ArNorm = @knrm2(n, Ar) # Marginally faster than norm(Ar) + λ > 0 && (γ += λ * ArNorm * ArNorm) history && push!(ArNorms, ArNorm) - iter = iter + 1 + ε = atol + rtol * ArNorm + (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) - user_requested_exit = callback(solver) :: Bool - solved = (ArNorm ≤ ε) || on_boundary + + status = "unknown" + on_boundary = false + solved = ArNorm ≤ ε tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + psd = false + user_requested_exit = false + overtimed = false + + while ! (solved || tired || user_requested_exit || overtimed) + qNorm² = @kdotr(n, q, q) # dot(q, q) + α = γ / qNorm² + + # if a trust-region constraint is give, compute step to the boundary + # (note that α > 0 in CRLS) + if radius > 0 + pNorm = @knrm2(n, p) + if @kdotr(m, Ap, Ap) ≤ ε * sqrt(qNorm²) * pNorm # the quadratic is constant in the direction p + psd = true # det(AᴴA) = 0 + p = Ar # p = Aᴴr + pNorm² = ArNorm * ArNorm + mul!(q, Aᴴ, s) + α = min(ArNorm^2 / γ, maximum(to_boundary(n, x, p, radius, flip = false, dNorm2 = pNorm²))) # the quadratic is minimal in the direction Aᴴr for α = ‖Ar‖²/γ + else + pNorm² = pNorm * pNorm + σ = maximum(to_boundary(n, x, p, radius, flip = false, dNorm2 = pNorm²)) + if α ≥ σ + α = σ + on_boundary = true + end + end + end + + @kaxpy!(n, α, p, x) # Faster than x = x + α * p + @kaxpy!(n, -α, q, Ar) # Faster than Ar = Ar - α * q + ArNorm = @knrm2(n, Ar) + solved = psd || on_boundary + solved && continue + @kaxpy!(m, -α, Ap, r) # Faster than r = r - α * Ap + mul!(s, A, Ar) + MisI || mulorldiv!(Ms, M, s, ldiv) + γ_next = @kdotr(m, s, Ms) # Faster than γ_next = dot(s, s) + λ > 0 && (γ_next += λ * ArNorm * ArNorm) + β = γ_next / γ + + @kaxpby!(n, one(FC), Ar, β, p) # Faster than p = Ar + β * p + @kaxpby!(m, one(FC), s, β, Ap) # Faster than Ap = s + β * Ap + MisI || mulorldiv!(MAp, M, Ap, ldiv) + mul!(q, Aᴴ, MAp) + λ > 0 && @kaxpy!(n, λ, p, q) # q = q + λ * p + + γ = γ_next + if λ > 0 + rNorm = sqrt(@kdotr(m, r, r) + λ * @kdotr(n, x, x)) + else + rNorm = @knrm2(m, r) # norm(r) + end + history && push!(rNorms, rNorm) + history && push!(ArNorms, ArNorm) + iter = iter + 1 + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + user_requested_exit = callback(solver) :: Bool + solved = (ArNorm ≤ ε) || on_boundary + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + psd && (status = "zero-curvature encountered") + on_boundary && (status = "on trust-region boundary") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - psd && (status = "zero-curvature encountered") - on_boundary && (status = "on trust-region boundary") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/crmr.jl b/src/crmr.jl index 6fca81afd..4790f02f9 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -121,119 +121,116 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor crmr!(solver, A, b; $(kwargs_crmr...)) return (solver.x, solver.stats) end -end -function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - N=I, ldiv :: Bool=false, - λ :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CRMR: system of %d equations in %d variables\n", m, n) - - # Tests N = Iₙ - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!NisI, solver, :Nq, S, m) - allocate_if(λ > 0, solver, :s , S, m) - x, p, Aᴴr, r = solver.x, solver.p, solver.Aᴴr, solver.r - q, s, stats = solver.q, solver.s, solver.stats - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - Nq = NisI ? q : solver.Nq - - x .= zero(FC) # initial estimation x = 0 - mulorldiv!(r, N, b, ldiv) # initial residual r = N * (b - Ax) = N * b - bNorm = @knrm2(m, r) # norm(b - A * x0) if x0 ≠ 0. - rNorm = bNorm # + λ * ‖x0‖ if x0 ≠ 0 and λ > 0. - history && push!(rNorms, rNorm) - if bNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(ArNorms, zero(T)) - return solver - end - λ > 0 && (s .= r) - mul!(Aᴴr, Aᴴ, r) # - λ * x0 if x0 ≠ 0. - p .= Aᴴr - γ = @kdotr(n, Aᴴr, Aᴴr) # Faster than γ = dot(Aᴴr, Aᴴr) - λ > 0 && (γ += λ * rNorm * rNorm) - iter = 0 - itmax == 0 && (itmax = m + n) - - ArNorm = sqrt(γ) - history && push!(ArNorms, ArNorm) - ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. - ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) - - status = "unknown" - solved = rNorm ≤ ɛ_c - inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) - tired = iter ≥ itmax - user_requested_exit = false - overtimed = false - - while ! (solved || inconsistent || tired || user_requested_exit || overtimed) - mul!(q, A, p) - λ > 0 && @kaxpy!(m, λ, s, q) # q = q + λ * s - NisI || mulorldiv!(Nq, N, q, ldiv) - α = γ / @kdotr(m, q, Nq) # Compute qᴴ * N * q - @kaxpy!(n, α, p, x) # Faster than x = x + α * p - @kaxpy!(m, -α, Nq, r) # Faster than r = r - α * Nq - rNorm = @knrm2(m, r) # norm(r) - mul!(Aᴴr, Aᴴ, r) - γ_next = @kdotr(n, Aᴴr, Aᴴr) # Faster than γ_next = dot(Aᴴr, Aᴴr) - λ > 0 && (γ_next += λ * rNorm * rNorm) - β = γ_next / γ - - @kaxpby!(n, one(FC), Aᴴr, β, p) # Faster than p = Aᴴr + β * p - if λ > 0 - @kaxpby!(m, one(FC), r, β, s) # s = r + β * s + function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "CRMR: system of %d equations in %d variables\n", m, n) + + # Tests N = Iₙ + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!NisI, solver, :Nq, S, m) + allocate_if(λ > 0, solver, :s , S, m) + x, p, Aᴴr, r = solver.x, solver.p, solver.Aᴴr, solver.r + q, s, stats = solver.q, solver.s, solver.stats + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + Nq = NisI ? q : solver.Nq + + x .= zero(FC) # initial estimation x = 0 + mulorldiv!(r, N, b, ldiv) # initial residual r = N * (b - Ax) = N * b + bNorm = @knrm2(m, r) # norm(b - A * x0) if x0 ≠ 0. + rNorm = bNorm # + λ * ‖x0‖ if x0 ≠ 0 and λ > 0. + history && push!(rNorms, rNorm) + if bNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(ArNorms, zero(T)) + return solver end + λ > 0 && (s .= r) + mul!(Aᴴr, Aᴴ, r) # - λ * x0 if x0 ≠ 0. + p .= Aᴴr + γ = @kdotr(n, Aᴴr, Aᴴr) # Faster than γ = dot(Aᴴr, Aᴴr) + λ > 0 && (γ += λ * rNorm * rNorm) + iter = 0 + itmax == 0 && (itmax = m + n) - γ = γ_next ArNorm = sqrt(γ) - history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) - iter = iter + 1 + ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. + ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. + (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) - user_requested_exit = callback(solver) :: Bool + + status = "unknown" solved = rNorm ≤ ɛ_c inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + user_requested_exit = false + overtimed = false + + while ! (solved || inconsistent || tired || user_requested_exit || overtimed) + mul!(q, A, p) + λ > 0 && @kaxpy!(m, λ, s, q) # q = q + λ * s + NisI || mulorldiv!(Nq, N, q, ldiv) + α = γ / @kdotr(m, q, Nq) # Compute qᴴ * N * q + @kaxpy!(n, α, p, x) # Faster than x = x + α * p + @kaxpy!(m, -α, Nq, r) # Faster than r = r - α * Nq + rNorm = @knrm2(m, r) # norm(r) + mul!(Aᴴr, Aᴴ, r) + γ_next = @kdotr(n, Aᴴr, Aᴴr) # Faster than γ_next = dot(Aᴴr, Aᴴr) + λ > 0 && (γ_next += λ * rNorm * rNorm) + β = γ_next / γ + + @kaxpby!(n, one(FC), Aᴴr, β, p) # Faster than p = Aᴴr + β * p + if λ > 0 + @kaxpby!(m, one(FC), r, β, s) # s = r + β * s + end + + γ = γ_next + ArNorm = sqrt(γ) + history && push!(rNorms, rNorm) + history && push!(ArNorms, ArNorm) + iter = iter + 1 + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + user_requested_exit = callback(solver) :: Bool + solved = rNorm ≤ ɛ_c + inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + inconsistent && (status = "system probably inconsistent but least squares/norm solution found") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - inconsistent && (status = "system probably inconsistent but least squares/norm solution found") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/diom.jl b/src/diom.jl index 481345cdd..7c21dc167 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -119,212 +119,209 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem diom!(solver, A, b; $(kwargs_diom...)) return solver end -end -function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - reorthogonalization :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "DIOM: system of size %d\n", n) - - # Check M = Iₙ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :w, S, n) - allocate_if(!NisI, solver, :z, S, n) - Δx, x, t, P, V = solver.Δx, solver.x, solver.t, solver.P, solver.V - L, H, stats = solver.L, solver.H, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - w = MisI ? t : solver.w - r₀ = MisI ? t : solver.w - - # Initial solution x₀ and residual r₀. - x .= zero(FC) # x₀ - if warm_start - mul!(t, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), t) - else - t .= b - end - MisI || mulorldiv!(r₀, M, t, ldiv) # M(b - Ax₀) - rNorm = @knrm2(n, r₀) # β = ‖r₀‖₂ - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end + function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "DIOM: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :w, S, n) + allocate_if(!NisI, solver, :z, S, n) + Δx, x, t, P, V = solver.Δx, solver.x, solver.t, solver.P, solver.V + L, H, stats = solver.L, solver.H, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + w = MisI ? t : solver.w + r₀ = MisI ? t : solver.w + + # Initial solution x₀ and residual r₀. + x .= zero(FC) # x₀ + if warm_start + mul!(t, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), t) + else + t .= b + end + MisI || mulorldiv!(r₀, M, t, ldiv) # M(b - Ax₀) + rNorm = @knrm2(n, r₀) # β = ‖r₀‖₂ + history && push!(rNorms, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end - iter = 0 - itmax == 0 && (itmax = 2*n) + iter = 0 + itmax == 0 && (itmax = 2*n) - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - mem = length(V) # Memory - for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). - end - for i = 1 : mem-1 - P[i] .= zero(FC) # Directions Pₖ = NVₖ(Uₖ)⁻¹. - end - H .= zero(FC) # Last column of the band hessenberg matrix Hₖ = LₖUₖ. - # Each column has at most mem + 1 nonzero elements. - # hᵢ.ₖ is stored as H[k-i+1], i ≤ k. hₖ₊₁.ₖ is not stored in H. - # k-i+1 represents the indice of the diagonal where hᵢ.ₖ is located. - # In addition of that, the last column of Uₖ is stored in H. - L .= zero(FC) # Last mem-1 pivots of Lₖ. - - # Initial ξ₁ and V₁. - ξ = rNorm - V[1] .= r₀ ./ rNorm - - # Stopping criterion. - solved = rNorm ≤ ε - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || user_requested_exit || overtimed) - - # Update iteration index. - iter = iter + 1 - - # Set position in circulars stacks. - pos = mod(iter-1, mem) + 1 # Position corresponding to vₖ in the circular stack V. - next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. - - # Incomplete Arnoldi procedure. - z = NisI ? V[pos] : solver.z - NisI || mulorldiv!(z, N, V[pos], ldiv) # Nvₖ, forms pₖ - mul!(t, A, z) # ANvₖ - MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ - for i = max(1, iter-mem+1) : iter - ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. - diag = iter - i + 1 - H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ, vᵢ⟩ - @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ + mem = length(V) # Memory + for i = 1 : mem + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). end + for i = 1 : mem-1 + P[i] .= zero(FC) # Directions Pₖ = NVₖ(Uₖ)⁻¹. + end + H .= zero(FC) # Last column of the band hessenberg matrix Hₖ = LₖUₖ. + # Each column has at most mem + 1 nonzero elements. + # hᵢ.ₖ is stored as H[k-i+1], i ≤ k. hₖ₊₁.ₖ is not stored in H. + # k-i+1 represents the indice of the diagonal where hᵢ.ₖ is located. + # In addition of that, the last column of Uₖ is stored in H. + L .= zero(FC) # Last mem-1 pivots of Lₖ. + + # Initial ξ₁ and V₁. + ξ = rNorm + V[1] .= r₀ ./ rNorm + + # Stopping criterion. + solved = rNorm ≤ ε + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false - # Partial reorthogonalization of the Krylov basis. - if reorthogonalization + while !(solved || tired || user_requested_exit || overtimed) + + # Update iteration index. + iter = iter + 1 + + # Set position in circulars stacks. + pos = mod(iter-1, mem) + 1 # Position corresponding to vₖ in the circular stack V. + next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. + + # Incomplete Arnoldi procedure. + z = NisI ? V[pos] : solver.z + NisI || mulorldiv!(z, N, V[pos], ldiv) # Nvₖ, forms pₖ + mul!(t, A, z) # ANvₖ + MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ for i = max(1, iter-mem+1) : iter - ipos = mod(i-1, mem) + 1 + ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. diag = iter - i + 1 - Htmp = @kdot(n, w, V[ipos]) - H[diag] += Htmp - @kaxpy!(n, -Htmp, V[ipos], w) + H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ, vᵢ⟩ + @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ end - end - # Compute hₖ₊₁.ₖ and vₖ₊₁. - Haux = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ - if Haux ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" - V[next_pos] .= w ./ Haux # vₖ₊₁ = w / hₖ₊₁.ₖ - end + # Partial reorthogonalization of the Krylov basis. + if reorthogonalization + for i = max(1, iter-mem+1) : iter + ipos = mod(i-1, mem) + 1 + diag = iter - i + 1 + Htmp = @kdot(n, w, V[ipos]) + H[diag] += Htmp + @kaxpy!(n, -Htmp, V[ipos], w) + end + end - # Update the LU factorization of Hₖ. - # Compute the last column of Uₖ. - if iter ≥ 2 - # u₁.ₖ ← h₁.ₖ if iter ≤ mem - # uₖ₋ₘₑₘ₊₁.ₖ ← hₖ₋ₘₑₘ₊₁.ₖ if iter ≥ mem + 1 - for i = max(2,iter-mem+2) : iter - lpos = mod(i-1, mem-1) + 1 # Position corresponding to lᵢ.ᵢ₋₁ in the circular stack L. - diag = iter - i + 1 - next_diag = diag + 1 - # uᵢ.ₖ ← hᵢ.ₖ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₖ - H[diag] = H[diag] - L[lpos] * H[next_diag] - if i == iter - # Compute ξₖ the last component of zₖ = β(Lₖ)⁻¹e₁. - # ξₖ = -lₖ.ₖ₋₁ * ξₖ₋₁ - ξ = - L[lpos] * ξ + # Compute hₖ₊₁.ₖ and vₖ₊₁. + Haux = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + if Haux ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" + V[next_pos] .= w ./ Haux # vₖ₊₁ = w / hₖ₊₁.ₖ + end + + # Update the LU factorization of Hₖ. + # Compute the last column of Uₖ. + if iter ≥ 2 + # u₁.ₖ ← h₁.ₖ if iter ≤ mem + # uₖ₋ₘₑₘ₊₁.ₖ ← hₖ₋ₘₑₘ₊₁.ₖ if iter ≥ mem + 1 + for i = max(2,iter-mem+2) : iter + lpos = mod(i-1, mem-1) + 1 # Position corresponding to lᵢ.ᵢ₋₁ in the circular stack L. + diag = iter - i + 1 + next_diag = diag + 1 + # uᵢ.ₖ ← hᵢ.ₖ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₖ + H[diag] = H[diag] - L[lpos] * H[next_diag] + if i == iter + # Compute ξₖ the last component of zₖ = β(Lₖ)⁻¹e₁. + # ξₖ = -lₖ.ₖ₋₁ * ξₖ₋₁ + ξ = - L[lpos] * ξ + end end end - end - # Compute next pivot lₖ₊₁.ₖ = hₖ₊₁.ₖ / uₖ.ₖ - next_lpos = mod(iter, mem-1) + 1 - L[next_lpos] = Haux / H[1] - - ppos = mod(iter-1, mem-1) + 1 # Position corresponding to pₖ in the circular stack P. - - # Compute the direction pₖ, the last column of Pₖ = NVₖ(Uₖ)⁻¹. - # u₁.ₖp₁ + ... + uₖ.ₖpₖ = Nvₖ if k ≤ mem - # uₖ₋ₘₑₘ₊₁.ₖpₖ₋ₘₑₘ₊₁ + ... + uₖ.ₖpₖ = Nvₖ if k ≥ mem + 1 - for i = max(1,iter-mem+1) : iter-1 - ipos = mod(i-1, mem-1) + 1 # Position corresponding to pᵢ in the circular stack P. - diag = iter - i + 1 - if ipos == ppos - # pₖ ← -uₖ₋ₘₑₘ₊₁.ₖ * pₖ₋ₘₑₘ₊₁ - @kscal!(n, -H[diag], P[ppos]) - else - # pₖ ← pₖ - uᵢ.ₖ * pᵢ - @kaxpy!(n, -H[diag], P[ipos], P[ppos]) + # Compute next pivot lₖ₊₁.ₖ = hₖ₊₁.ₖ / uₖ.ₖ + next_lpos = mod(iter, mem-1) + 1 + L[next_lpos] = Haux / H[1] + + ppos = mod(iter-1, mem-1) + 1 # Position corresponding to pₖ in the circular stack P. + + # Compute the direction pₖ, the last column of Pₖ = NVₖ(Uₖ)⁻¹. + # u₁.ₖp₁ + ... + uₖ.ₖpₖ = Nvₖ if k ≤ mem + # uₖ₋ₘₑₘ₊₁.ₖpₖ₋ₘₑₘ₊₁ + ... + uₖ.ₖpₖ = Nvₖ if k ≥ mem + 1 + for i = max(1,iter-mem+1) : iter-1 + ipos = mod(i-1, mem-1) + 1 # Position corresponding to pᵢ in the circular stack P. + diag = iter - i + 1 + if ipos == ppos + # pₖ ← -uₖ₋ₘₑₘ₊₁.ₖ * pₖ₋ₘₑₘ₊₁ + @kscal!(n, -H[diag], P[ppos]) + else + # pₖ ← pₖ - uᵢ.ₖ * pᵢ + @kaxpy!(n, -H[diag], P[ipos], P[ppos]) + end end + # pₐᵤₓ ← pₐᵤₓ + Nvₖ + @kaxpy!(n, one(FC), z, P[ppos]) + # pₖ = pₐᵤₓ / uₖ.ₖ + P[ppos] .= P[ppos] ./ H[1] + + # Update solution xₖ. + # xₖ = xₖ₋₁ + ξₖ * pₖ + @kaxpy!(n, ξ, P[ppos], x) + + # Compute residual norm. + # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ξₖ / uₖ.ₖ| + rNorm = Haux * abs(ξ / H[1]) + history && push!(rNorms, rNorm) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end - # pₐᵤₓ ← pₐᵤₓ + Nvₖ - @kaxpy!(n, one(FC), z, P[ppos]) - # pₖ = pₐᵤₓ / uₖ.ₖ - P[ppos] .= P[ppos] ./ H[1] - - # Update solution xₖ. - # xₖ = xₖ₋₁ + ξₖ * pₖ - @kaxpy!(n, ξ, P[ppos], x) - - # Compute residual norm. - # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ξₖ / uₖ.ₖ| - rNorm = Haux * abs(ξ / H[1]) - history && push!(rNorms, rNorm) + (verbose > 0) && @printf(iostream, "\n") - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - solved = resid_decrease_lim || resid_decrease_mach - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 7f7fff622..eb3a96935 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -119,214 +119,211 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return solver end -end -function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - reorthogonalization :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "DQGMRES: system of size %d\n", n) - - # Check M = Iₙ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :w, S, n) - allocate_if(!NisI, solver, :z, S, n) - Δx, x, t, P, V = solver.Δx, solver.x, solver.t, solver.P, solver.V - c, s, H, stats = solver.c, solver.s, solver.H, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - w = MisI ? t : solver.w - r₀ = MisI ? t : solver.w - - # Initial solution x₀ and residual r₀. - x .= zero(FC) # x₀ - if warm_start - mul!(t, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), t) - else - t .= b - end - MisI || mulorldiv!(r₀, M, t, ldiv) # M(b - Ax₀) - rNorm = @knrm2(n, r₀) # β = ‖r₀‖₂ - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end + function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "DQGMRES: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :w, S, n) + allocate_if(!NisI, solver, :z, S, n) + Δx, x, t, P, V = solver.Δx, solver.x, solver.t, solver.P, solver.V + c, s, H, stats = solver.c, solver.s, solver.H, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + w = MisI ? t : solver.w + r₀ = MisI ? t : solver.w + + # Initial solution x₀ and residual r₀. + x .= zero(FC) # x₀ + if warm_start + mul!(t, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), t) + else + t .= b + end + MisI || mulorldiv!(r₀, M, t, ldiv) # M(b - Ax₀) + rNorm = @knrm2(n, r₀) # β = ‖r₀‖₂ + history && push!(rNorms, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end - iter = 0 - itmax == 0 && (itmax = 2*n) + iter = 0 + itmax == 0 && (itmax = 2*n) - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - # Set up workspace. - mem = length(V) # Memory. - for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). - P[i] .= zero(FC) # Directions for x : Pₖ = NVₖ(Rₖ)⁻¹. - end - c .= zero(T) # Last mem Givens cosines used for the factorization QₖRₖ = Hₖ. - s .= zero(FC) # Last mem Givens sines used for the factorization QₖRₖ = Hₖ. - H .= zero(FC) # Last column of the band hessenberg matrix Hₖ. - # Each column has at most mem + 1 nonzero elements. - # hᵢ.ₖ is stored as H[k-i+1], i ≤ k. hₖ₊₁.ₖ is not stored in H. - # k-i+1 represents the indice of the diagonal where hᵢ.ₖ is located. - # In addition of that, the last column of Rₖ is also stored in H. - - # Initial γ₁ and V₁. - γₖ = rNorm # γₖ and γₖ₊₁ are the last components of gₖ, right-hand of the least squares problem min ‖ Hₖyₖ - gₖ ‖₂. - V[1] .= r₀ ./ rNorm - - # The following stopping criterion compensates for the lag in the - # residual, but usually increases the number of iterations. - # solved = sqrt(max(1, iter-mem+1)) * |γₖ₊₁| ≤ ε - solved = rNorm ≤ ε # less accurate, but acceptable. - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || user_requested_exit || overtimed) - - # Update iteration index. - iter = iter + 1 - - # Set position in circulars stacks. - pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. - next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. - - # Incomplete Arnoldi procedure. - z = NisI ? V[pos] : solver.z - NisI || mulorldiv!(z, N, V[pos], ldiv) # Nvₖ, forms pₖ - mul!(t, A, z) # ANvₖ - MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ - for i = max(1, iter-mem+1) : iter - ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. - diag = iter - i + 1 - H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ, vᵢ⟩ - @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ + # Set up workspace. + mem = length(V) # Memory. + for i = 1 : mem + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). + P[i] .= zero(FC) # Directions for x : Pₖ = NVₖ(Rₖ)⁻¹. end + c .= zero(T) # Last mem Givens cosines used for the factorization QₖRₖ = Hₖ. + s .= zero(FC) # Last mem Givens sines used for the factorization QₖRₖ = Hₖ. + H .= zero(FC) # Last column of the band hessenberg matrix Hₖ. + # Each column has at most mem + 1 nonzero elements. + # hᵢ.ₖ is stored as H[k-i+1], i ≤ k. hₖ₊₁.ₖ is not stored in H. + # k-i+1 represents the indice of the diagonal where hᵢ.ₖ is located. + # In addition of that, the last column of Rₖ is also stored in H. + + # Initial γ₁ and V₁. + γₖ = rNorm # γₖ and γₖ₊₁ are the last components of gₖ, right-hand of the least squares problem min ‖ Hₖyₖ - gₖ ‖₂. + V[1] .= r₀ ./ rNorm + + # The following stopping criterion compensates for the lag in the + # residual, but usually increases the number of iterations. + # solved = sqrt(max(1, iter-mem+1)) * |γₖ₊₁| ≤ ε + solved = rNorm ≤ ε # less accurate, but acceptable. + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || user_requested_exit || overtimed) - # Partial reorthogonalization of the Krylov basis. - if reorthogonalization + # Update iteration index. + iter = iter + 1 + + # Set position in circulars stacks. + pos = mod(iter-1, mem) + 1 # Position corresponding to pₖ and vₖ in circular stacks P and V. + next_pos = mod(iter, mem) + 1 # Position corresponding to vₖ₊₁ in the circular stack V. + + # Incomplete Arnoldi procedure. + z = NisI ? V[pos] : solver.z + NisI || mulorldiv!(z, N, V[pos], ldiv) # Nvₖ, forms pₖ + mul!(t, A, z) # ANvₖ + MisI || mulorldiv!(w, M, t, ldiv) # MANvₖ, forms vₖ₊₁ for i = max(1, iter-mem+1) : iter - ipos = mod(i-1, mem) + 1 + ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. diag = iter - i + 1 - Htmp = @kdot(n, w, V[ipos]) - H[diag] += Htmp - @kaxpy!(n, -Htmp, V[ipos], w) + H[diag] = @kdot(n, w, V[ipos]) # hᵢ.ₖ = ⟨MANvₖ, vᵢ⟩ + @kaxpy!(n, -H[diag], V[ipos], w) # w ← w - hᵢ.ₖvᵢ end - end - # Compute hₖ₊₁.ₖ and vₖ₊₁. - Haux = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ - if Haux ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" - V[next_pos] .= w ./ Haux # vₖ₊₁ = w / hₖ₊₁.ₖ - end - # rₖ₋ₘₑₘ.ₖ ≠ 0 when k ≥ mem + 1 - # We don't want to use rₖ₋₁₋ₘₑₘ.ₖ₋₁ when we compute rₖ₋ₘₑₘ.ₖ - if iter ≥ mem + 2 - H[mem+1] = zero(FC) # rₖ₋ₘₑₘ.ₖ = 0 - end + # Partial reorthogonalization of the Krylov basis. + if reorthogonalization + for i = max(1, iter-mem+1) : iter + ipos = mod(i-1, mem) + 1 + diag = iter - i + 1 + Htmp = @kdot(n, w, V[ipos]) + H[diag] += Htmp + @kaxpy!(n, -Htmp, V[ipos], w) + end + end - # Update the QR factorization of Hₖ. - # Apply mem previous Givens reflections Ωᵢ. - for i = max(1,iter-mem) : iter-1 - irot_pos = mod(i-1, mem) + 1 # Position corresponding to cᵢ and sᵢ in circular stacks c and s. - diag = iter - i - next_diag = diag + 1 - Htmp = c[irot_pos] * H[next_diag] + s[irot_pos] * H[diag] - H[diag] = conj(s[irot_pos]) * H[next_diag] - c[irot_pos] * H[diag] - H[next_diag] = Htmp - end + # Compute hₖ₊₁.ₖ and vₖ₊₁. + Haux = @knrm2(n, w) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + if Haux ≠ 0 # hₖ₊₁.ₖ = 0 ⇒ "lucky breakdown" + V[next_pos] .= w ./ Haux # vₖ₊₁ = w / hₖ₊₁.ₖ + end + # rₖ₋ₘₑₘ.ₖ ≠ 0 when k ≥ mem + 1 + # We don't want to use rₖ₋₁₋ₘₑₘ.ₖ₋₁ when we compute rₖ₋ₘₑₘ.ₖ + if iter ≥ mem + 2 + H[mem+1] = zero(FC) # rₖ₋ₘₑₘ.ₖ = 0 + end - # Compute and apply current Givens reflection Ωₖ. - # [cₖ sₖ] [ hₖ.ₖ ] = [ρₖ] - # [sₖ -cₖ] [hₖ₊₁.ₖ] [0 ] - (c[pos], s[pos], H[1]) = sym_givens(H[1], Haux) - γₖ₊₁ = conj(s[pos]) * γₖ - γₖ = c[pos] * γₖ - - # Compute the direction pₖ, the last column of Pₖ = NVₖ(Rₖ)⁻¹. - for i = max(1,iter-mem) : iter-1 - ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. - diag = iter - i + 1 - if ipos == pos - # pₐᵤₓ ← -hₖ₋ₘₑₘ.ₖ * pₖ₋ₘₑₘ - @kscal!(n, -H[diag], P[pos]) - else - # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₖ * pᵢ - @kaxpy!(n, -H[diag], P[ipos], P[pos]) + # Update the QR factorization of Hₖ. + # Apply mem previous Givens reflections Ωᵢ. + for i = max(1,iter-mem) : iter-1 + irot_pos = mod(i-1, mem) + 1 # Position corresponding to cᵢ and sᵢ in circular stacks c and s. + diag = iter - i + next_diag = diag + 1 + Htmp = c[irot_pos] * H[next_diag] + s[irot_pos] * H[diag] + H[diag] = conj(s[irot_pos]) * H[next_diag] - c[irot_pos] * H[diag] + H[next_diag] = Htmp end + + # Compute and apply current Givens reflection Ωₖ. + # [cₖ sₖ] [ hₖ.ₖ ] = [ρₖ] + # [sₖ -cₖ] [hₖ₊₁.ₖ] [0 ] + (c[pos], s[pos], H[1]) = sym_givens(H[1], Haux) + γₖ₊₁ = conj(s[pos]) * γₖ + γₖ = c[pos] * γₖ + + # Compute the direction pₖ, the last column of Pₖ = NVₖ(Rₖ)⁻¹. + for i = max(1,iter-mem) : iter-1 + ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. + diag = iter - i + 1 + if ipos == pos + # pₐᵤₓ ← -hₖ₋ₘₑₘ.ₖ * pₖ₋ₘₑₘ + @kscal!(n, -H[diag], P[pos]) + else + # pₐᵤₓ ← pₐᵤₓ - hᵢ.ₖ * pᵢ + @kaxpy!(n, -H[diag], P[ipos], P[pos]) + end + end + # pₐᵤₓ ← pₐᵤₓ + Nvₖ + @kaxpy!(n, one(FC), z, P[pos]) + # pₖ = pₐᵤₓ / hₖ.ₖ + P[pos] .= P[pos] ./ H[1] + + # Compute solution xₖ. + # xₖ ← xₖ₋₁ + γₖ * pₖ + @kaxpy!(n, γₖ, P[pos], x) + + # Update residual norm estimate. + # ‖ M(b - Axₖ) ‖₂ ≈ |γₖ₊₁| + rNorm = abs(γₖ₊₁) + history && push!(rNorms, rNorm) + + # Update γₖ. + γₖ = γₖ₊₁ + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) end - # pₐᵤₓ ← pₐᵤₓ + Nvₖ - @kaxpy!(n, one(FC), z, P[pos]) - # pₖ = pₐᵤₓ / hₖ.ₖ - P[pos] .= P[pos] ./ H[1] - - # Compute solution xₖ. - # xₖ ← xₖ₋₁ + γₖ * pₖ - @kaxpy!(n, γₖ, P[pos], x) - - # Update residual norm estimate. - # ‖ M(b - Axₖ) ‖₂ ≈ |γₖ₊₁| - rNorm = abs(γₖ₊₁) - history && push!(rNorms, rNorm) + (verbose > 0) && @printf(iostream, "\n") - # Update γₖ. - γₖ = γₖ₊₁ + # Termination status + solved && (status = "solution good enough given atol and rtol") + tired && (status = "maximum number of iterations exceeded") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - solved = resid_decrease_lim || resid_decrease_mach - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - solved && (status = "solution good enough given atol and rtol") - tired && (status = "maximum number of iterations exceeded") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/fgmres.jl b/src/fgmres.jl index a990e2636..632daac1b 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -122,261 +122,258 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i fgmres!(solver, A, b; $(kwargs_fgmres...)) return solver end -end - -function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - restart :: Bool=false, reorthogonalization :: Bool=false, - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "FGMRES: system of size %d\n", n) - - # Check M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI , solver, :q , S, n) - allocate_if(restart, solver, :Δx, S, n) - Δx, x, w, V, Z = solver.Δx, solver.x, solver.w, solver.V, solver.Z - z, c, s, R, stats = solver.z, solver.c, solver.s, solver.R, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - q = MisI ? w : solver.q - r₀ = MisI ? w : solver.q - xr = restart ? Δx : x - - # Initial solution x₀. - x .= zero(FC) - - # Initial residual r₀. - if warm_start - mul!(w, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), w) - restart && @kaxpy!(n, one(FC), Δx, x) - else - w .= b - end - MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) - β = @knrm2(n, r₀) # β = ‖r₀‖₂ - - rNorm = β - history && push!(rNorms, β) - ε = atol + rtol * rNorm - - if β == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - mem = length(c) # Memory - npass = 0 # Number of pass - - iter = 0 # Cumulative number of iterations - inner_iter = 0 # Number of iterations in a pass + function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "FGMRES: system of size %d\n", n) + + # Check M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI , solver, :q , S, n) + allocate_if(restart, solver, :Δx, S, n) + Δx, x, w, V, Z = solver.Δx, solver.x, solver.w, solver.V, solver.Z + z, c, s, R, stats = solver.z, solver.c, solver.s, solver.R, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + q = MisI ? w : solver.q + r₀ = MisI ? w : solver.q + xr = restart ? Δx : x + + # Initial solution x₀. + x .= zero(FC) + + # Initial residual r₀. + if warm_start + mul!(w, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), w) + restart && @kaxpy!(n, one(FC), Δx, x) + else + w .= b + end + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) + β = @knrm2(n, r₀) # β = ‖r₀‖₂ + + rNorm = β + history && push!(rNorms, β) + ε = atol + rtol * rNorm + + if β == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end - itmax == 0 && (itmax = 2*n) - inner_itmax = itmax + mem = length(c) # Memory + npass = 0 # Number of pass - (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + iter = 0 # Cumulative number of iterations + inner_iter = 0 # Number of iterations in a pass - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) + itmax == 0 && (itmax = 2*n) + inner_itmax = itmax - # Stopping criterion - breakdown = false - inconsistent = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - inner_tired = inner_iter ≥ inner_itmax - status = "unknown" - user_requested_exit = false - overtimed = false + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") - while !(solved || tired || breakdown || user_requested_exit || overtimed) + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) - # Initialize workspace. - nr = 0 # Number of coefficients stored in Rₖ. - for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of {Mr₀, MANₖr₀, ..., (MANₖ)ᵏ⁻¹r₀}. - Z[i] .= zero(FC) # Zₖ = [N₁v₁, ..., Nₖvₖ] - end - s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. - c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. - R .= zero(FC) # Upper triangular matrix Rₖ. - z .= zero(FC) # Right-hand of the least squares problem min ‖Hₖ₊₁.ₖyₖ - βe₁‖₂. - - if restart - xr .= zero(FC) # xr === Δx when restart is set to true - if npass ≥ 1 - mul!(w, A, x) - @kaxpby!(n, one(FC), b, -one(FC), w) - MisI || mulorldiv!(r₀, M, w, ldiv) + # Stopping criterion + breakdown = false + inconsistent = false + solved = rNorm ≤ ε + tired = iter ≥ itmax + inner_tired = inner_iter ≥ inner_itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || breakdown || user_requested_exit || overtimed) + + # Initialize workspace. + nr = 0 # Number of coefficients stored in Rₖ. + for i = 1 : mem + V[i] .= zero(FC) # Orthogonal basis of {Mr₀, MANₖr₀, ..., (MANₖ)ᵏ⁻¹r₀}. + Z[i] .= zero(FC) # Zₖ = [N₁v₁, ..., Nₖvₖ] + end + s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. + c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. + R .= zero(FC) # Upper triangular matrix Rₖ. + z .= zero(FC) # Right-hand of the least squares problem min ‖Hₖ₊₁.ₖyₖ - βe₁‖₂. + + if restart + xr .= zero(FC) # xr === Δx when restart is set to true + if npass ≥ 1 + mul!(w, A, x) + @kaxpby!(n, one(FC), b, -one(FC), w) + MisI || mulorldiv!(r₀, M, w, ldiv) + end end - end - - # Initial ζ₁ and V₁ - β = @knrm2(n, r₀) - z[1] = β - @. V[1] = r₀ / rNorm - npass = npass + 1 - solver.inner_iter = 0 - inner_tired = false + # Initial ζ₁ and V₁ + β = @knrm2(n, r₀) + z[1] = β + @. V[1] = r₀ / rNorm + + npass = npass + 1 + solver.inner_iter = 0 + inner_tired = false + + while !(solved || inner_tired || breakdown || user_requested_exit || overtimed) + + # Update iteration index + solver.inner_iter = solver.inner_iter + 1 + inner_iter = solver.inner_iter + + # Update workspace if more storage is required and restart is set to false + if !restart && (inner_iter > mem) + for i = 1 : inner_iter + push!(R, zero(FC)) + end + push!(s, zero(FC)) + push!(c, zero(T)) + push!(Z, S(undef, n)) + end - while !(solved || inner_tired || breakdown || user_requested_exit || overtimed) + # Continue the process. + # MAZₖ = Vₖ₊₁Hₖ₊₁.ₖ + mulorldiv!(Z[inner_iter], N, V[inner_iter], ldiv) # zₖ ← Nₖvₖ + mul!(w, A, Z[inner_iter]) # w ← Azₖ + MisI || mulorldiv!(q, M, w, ldiv) # q ← MAzₖ + for i = 1 : inner_iter + R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq + @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ + end - # Update iteration index - solver.inner_iter = solver.inner_iter + 1 - inner_iter = solver.inner_iter + # Reorthogonalization of the basis. + if reorthogonalization + for i = 1 : inner_iter + Htmp = @kdot(n, V[i], q) + R[nr+i] += Htmp + @kaxpy!(n, -Htmp, V[i], q) + end + end - # Update workspace if more storage is required and restart is set to false - if !restart && (inner_iter > mem) - for i = 1 : inner_iter - push!(R, zero(FC)) + # Compute hₖ₊₁.ₖ + Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + + # Update the QR factorization of Hₖ₊₁.ₖ. + # Apply previous Givens reflections Ωᵢ. + # [cᵢ sᵢ] [ r̄ᵢ.ₖ ] = [ rᵢ.ₖ ] + # [s̄ᵢ -cᵢ] [rᵢ₊₁.ₖ] [r̄ᵢ₊₁.ₖ] + for i = 1 : inner_iter-1 + Rtmp = c[i] * R[nr+i] + s[i] * R[nr+i+1] + R[nr+i+1] = conj(s[i]) * R[nr+i] - c[i] * R[nr+i+1] + R[nr+i] = Rtmp end - push!(s, zero(FC)) - push!(c, zero(T)) - push!(Z, S(undef, n)) - end - # Continue the process. - # MAZₖ = Vₖ₊₁Hₖ₊₁.ₖ - mulorldiv!(Z[inner_iter], N, V[inner_iter], ldiv) # zₖ ← Nₖvₖ - mul!(w, A, Z[inner_iter]) # w ← Azₖ - MisI || mulorldiv!(q, M, w, ldiv) # q ← MAzₖ - for i = 1 : inner_iter - R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq - @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ + # Compute and apply current Givens reflection Ωₖ. + # [cₖ sₖ] [ r̄ₖ.ₖ ] = [rₖ.ₖ] + # [s̄ₖ -cₖ] [hₖ₊₁.ₖ] [ 0 ] + (c[inner_iter], s[inner_iter], R[nr+inner_iter]) = sym_givens(R[nr+inner_iter], Hbis) + + # Update zₖ = (Qₖ)ᴴβe₁ + ζₖ₊₁ = conj(s[inner_iter]) * z[inner_iter] + z[inner_iter] = c[inner_iter] * z[inner_iter] + + # Update residual norm estimate. + # ‖ M⁻¹(b - Axₖ) ‖₂ = |ζₖ₊₁| + rNorm = abs(ζₖ₊₁) + history && push!(rNorms, rNorm) + + # Update the number of coefficients in Rₖ + nr = nr + inner_iter + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + breakdown = Hbis ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + + # Compute vₖ₊₁ + if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) + if !restart && (inner_iter ≥ mem) + push!(V, S(undef, n)) + push!(z, zero(FC)) + end + @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q + z[inner_iter+1] = ζₖ₊₁ + end end - # Reorthogonalization of the basis. - if reorthogonalization - for i = 1 : inner_iter - Htmp = @kdot(n, V[i], q) - R[nr+i] += Htmp - @kaxpy!(n, -Htmp, V[i], q) + # Compute y by solving Ry = z with backward substitution. + y = z # yᵢ = ζᵢ + for i = inner_iter : -1 : 1 + pos = nr + i - inner_iter # position of rᵢ.ₖ + for j = inner_iter : -1 : i+1 + y[i] = y[i] - R[pos] * y[j] # yᵢ ← yᵢ - rᵢⱼyⱼ + pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + end + # Rₖ can be singular if the system is inconsistent + if abs(R[pos]) ≤ btol + y[i] = zero(FC) + inconsistent = true + else + y[i] = y[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ end end - # Compute hₖ₊₁.ₖ - Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ - - # Update the QR factorization of Hₖ₊₁.ₖ. - # Apply previous Givens reflections Ωᵢ. - # [cᵢ sᵢ] [ r̄ᵢ.ₖ ] = [ rᵢ.ₖ ] - # [s̄ᵢ -cᵢ] [rᵢ₊₁.ₖ] [r̄ᵢ₊₁.ₖ] - for i = 1 : inner_iter-1 - Rtmp = c[i] * R[nr+i] + s[i] * R[nr+i+1] - R[nr+i+1] = conj(s[i]) * R[nr+i] - c[i] * R[nr+i+1] - R[nr+i] = Rtmp + # Form xₖ = N₁v₁y₁ + ... + Nₖvₖyₖ = z₁y₁ + ... + zₖyₖ + for i = 1 : inner_iter + @kaxpy!(n, y[i], Z[i], xr) end + restart && @kaxpy!(n, one(FC), xr, x) - # Compute and apply current Givens reflection Ωₖ. - # [cₖ sₖ] [ r̄ₖ.ₖ ] = [rₖ.ₖ] - # [s̄ₖ -cₖ] [hₖ₊₁.ₖ] [ 0 ] - (c[inner_iter], s[inner_iter], R[nr+inner_iter]) = sym_givens(R[nr+inner_iter], Hbis) - - # Update zₖ = (Qₖ)ᴴβe₁ - ζₖ₊₁ = conj(s[inner_iter]) * z[inner_iter] - z[inner_iter] = c[inner_iter] * z[inner_iter] - - # Update residual norm estimate. - # ‖ M⁻¹(b - Axₖ) ‖₂ = |ζₖ₊₁| - rNorm = abs(ζₖ₊₁) - history && push!(rNorms, rNorm) - - # Update the number of coefficients in Rₖ - nr = nr + inner_iter - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - breakdown = Hbis ≤ btol - solved = resid_decrease_lim || resid_decrease_mach - inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax + # Update inner_itmax, iter and tired variables. + inner_itmax = inner_itmax - inner_iter + iter = iter + inner_iter + tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) - - # Compute vₖ₊₁ - if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) - if !restart && (inner_iter ≥ mem) - push!(V, S(undef, n)) - push!(z, zero(FC)) - end - @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q - z[inner_iter+1] = ζₖ₊₁ - end end + (verbose > 0) && @printf(iostream, "\n") - # Compute y by solving Ry = z with backward substitution. - y = z # yᵢ = ζᵢ - for i = inner_iter : -1 : 1 - pos = nr + i - inner_iter # position of rᵢ.ₖ - for j = inner_iter : -1 : i+1 - y[i] = y[i] - R[pos] * y[j] # yᵢ ← yᵢ - rᵢⱼyⱼ - pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ - end - # Rₖ can be singular if the system is inconsistent - if abs(R[pos]) ≤ btol - y[i] = zero(FC) - inconsistent = true - else - y[i] = y[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ - end - end + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + inconsistent && (status = "found approximate least-squares solution") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - # Form xₖ = N₁v₁y₁ + ... + Nₖvₖyₖ = z₁y₁ + ... + zₖyₖ - for i = 1 : inner_iter - @kaxpy!(n, y[i], Z[i], xr) - end - restart && @kaxpy!(n, one(FC), xr, x) + # Update x + warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Update inner_itmax, iter and tired variables. - inner_itmax = inner_itmax - inner_iter - iter = iter + inner_iter - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - inconsistent && (status = "found approximate least-squares solution") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/fom.jl b/src/fom.jl index 8836f7d8b..85733116c 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -115,248 +115,245 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma fom!(solver, A, b; $(kwargs_fom...)) return solver end -end - -function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - restart :: Bool=false, reorthogonalization :: Bool=false, - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "FOM: system of size %d\n", n) - - # Check M = Iₙ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI , solver, :q , S, n) - allocate_if(!NisI , solver, :p , S, n) - allocate_if(restart, solver, :Δx, S, n) - Δx, x, w, V, z = solver.Δx, solver.x, solver.w, solver.V, solver.z - l, U, stats = solver.l, solver.U, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - q = MisI ? w : solver.q - r₀ = MisI ? w : solver.q - xr = restart ? Δx : x - - # Initial solution x₀. - x .= zero(FC) - - # Initial residual r₀. - if warm_start - mul!(w, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), w) - restart && @kaxpy!(n, one(FC), Δx, x) - else - w .= b - end - MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) - β = @knrm2(n, r₀) # β = ‖r₀‖₂ - rNorm = β - history && push!(rNorms, β) - ε = atol + rtol * rNorm - - if β == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end + function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "FOM: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI , solver, :q , S, n) + allocate_if(!NisI , solver, :p , S, n) + allocate_if(restart, solver, :Δx, S, n) + Δx, x, w, V, z = solver.Δx, solver.x, solver.w, solver.V, solver.z + l, U, stats = solver.l, solver.U, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + q = MisI ? w : solver.q + r₀ = MisI ? w : solver.q + xr = restart ? Δx : x + + # Initial solution x₀. + x .= zero(FC) + + # Initial residual r₀. + if warm_start + mul!(w, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), w) + restart && @kaxpy!(n, one(FC), Δx, x) + else + w .= b + end + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) + β = @knrm2(n, r₀) # β = ‖r₀‖₂ + + rNorm = β + history && push!(rNorms, β) + ε = atol + rtol * rNorm + + if β == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end - mem = length(l) # Memory - npass = 0 # Number of pass + mem = length(l) # Memory + npass = 0 # Number of pass - iter = 0 # Cumulative number of iterations - inner_iter = 0 # Number of iterations in a pass + iter = 0 # Cumulative number of iterations + inner_iter = 0 # Number of iterations in a pass - itmax == 0 && (itmax = 2*n) - inner_itmax = itmax + itmax == 0 && (itmax = 2*n) + inner_itmax = itmax - (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) - # Stopping criterion - breakdown = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - inner_tired = inner_iter ≥ inner_itmax - status = "unknown" - user_requested_exit = false - overtimed = false + # Stopping criterion + breakdown = false + solved = rNorm ≤ ε + tired = iter ≥ itmax + inner_tired = inner_iter ≥ inner_itmax + status = "unknown" + user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit || overtimed) + while !(solved || tired || breakdown || user_requested_exit || overtimed) - # Initialize workspace. - nr = 0 # Number of coefficients stored in Uₖ. - for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). - end - l .= zero(FC) # Lower unit triangular matrix Lₖ. - U .= zero(FC) # Upper triangular matrix Uₖ. - z .= zero(FC) # Solution of Lₖzₖ = βe₁. - - if restart - xr .= zero(FC) # xr === Δx when restart is set to true - if npass ≥ 1 - mul!(w, A, x) - @kaxpby!(n, one(FC), b, -one(FC), w) - MisI || mulorldiv!(r₀, M, w, ldiv) + # Initialize workspace. + nr = 0 # Number of coefficients stored in Uₖ. + for i = 1 : mem + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). + end + l .= zero(FC) # Lower unit triangular matrix Lₖ. + U .= zero(FC) # Upper triangular matrix Uₖ. + z .= zero(FC) # Solution of Lₖzₖ = βe₁. + + if restart + xr .= zero(FC) # xr === Δx when restart is set to true + if npass ≥ 1 + mul!(w, A, x) + @kaxpby!(n, one(FC), b, -one(FC), w) + MisI || mulorldiv!(r₀, M, w, ldiv) + end end - end - # Initial ζ₁ and V₁ - β = @knrm2(n, r₀) - z[1] = β - @. V[1] = r₀ / rNorm + # Initial ζ₁ and V₁ + β = @knrm2(n, r₀) + z[1] = β + @. V[1] = r₀ / rNorm - npass = npass + 1 - inner_iter = 0 - inner_tired = false + npass = npass + 1 + inner_iter = 0 + inner_tired = false - while !(solved || inner_tired || breakdown) + while !(solved || inner_tired || breakdown) - # Update iteration index - inner_iter = inner_iter + 1 + # Update iteration index + inner_iter = inner_iter + 1 - # Update workspace if more storage is required and restart is set to false - if !restart && (inner_iter > mem) - for i = 1 : inner_iter - push!(U, zero(FC)) + # Update workspace if more storage is required and restart is set to false + if !restart && (inner_iter > mem) + for i = 1 : inner_iter + push!(U, zero(FC)) + end + push!(l, zero(FC)) + push!(z, zero(FC)) end - push!(l, zero(FC)) - push!(z, zero(FC)) - end - - # Continue the Arnoldi process. - p = NisI ? V[inner_iter] : solver.p - NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← Nvₖ - mul!(w, A, p) # w ← ANvₖ - MisI || mulorldiv!(q, M, w, ldiv) # q ← MANvₖ - for i = 1 : inner_iter - U[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq - @kaxpy!(n, -U[nr+i], V[i], q) # q ← q - hᵢₖvᵢ - end - # Reorthogonalization of the Krylov basis. - if reorthogonalization + # Continue the Arnoldi process. + p = NisI ? V[inner_iter] : solver.p + NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← Nvₖ + mul!(w, A, p) # w ← ANvₖ + MisI || mulorldiv!(q, M, w, ldiv) # q ← MANvₖ for i = 1 : inner_iter - Htmp = @kdot(n, V[i], q) - U[nr+i] += Htmp - @kaxpy!(n, -Htmp, V[i], q) + U[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq + @kaxpy!(n, -U[nr+i], V[i], q) # q ← q - hᵢₖvᵢ end - end - # Compute hₖ₊₁.ₖ - Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + # Reorthogonalization of the Krylov basis. + if reorthogonalization + for i = 1 : inner_iter + Htmp = @kdot(n, V[i], q) + U[nr+i] += Htmp + @kaxpy!(n, -Htmp, V[i], q) + end + end - # Update the LU factorization of Hₖ. - if inner_iter ≥ 2 - for i = 2 : inner_iter - # uᵢ.ₖ ← hᵢ.ₖ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₖ - U[nr+i] = U[nr+i] - l[i-1] * U[nr+i-1] + # Compute hₖ₊₁.ₖ + Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + + # Update the LU factorization of Hₖ. + if inner_iter ≥ 2 + for i = 2 : inner_iter + # uᵢ.ₖ ← hᵢ.ₖ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₖ + U[nr+i] = U[nr+i] - l[i-1] * U[nr+i-1] + end + # ζₖ = -lₖ.ₖ₋₁ * ζₖ₋₁ + z[inner_iter] = - l[inner_iter-1] * z[inner_iter-1] + end + # lₖ₊₁.ₖ = hₖ₊₁.ₖ / uₖ.ₖ + l[inner_iter] = Hbis / U[nr+inner_iter] + + # Update residual norm estimate. + # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ζₖ / uₖ.ₖ| + rNorm = Hbis * abs(z[inner_iter] / U[nr+inner_iter]) + history && push!(rNorms, rNorm) + + # Update the number of coefficients in Uₖ + nr = nr + inner_iter + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + breakdown = Hbis ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + + # Compute vₖ₊₁. + if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) + if !restart && (inner_iter ≥ mem) + push!(V, S(undef, n)) + end + @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q end - # ζₖ = -lₖ.ₖ₋₁ * ζₖ₋₁ - z[inner_iter] = - l[inner_iter-1] * z[inner_iter-1] end - # lₖ₊₁.ₖ = hₖ₊₁.ₖ / uₖ.ₖ - l[inner_iter] = Hbis / U[nr+inner_iter] - - # Update residual norm estimate. - # ‖ M(b - Axₖ) ‖₂ = hₖ₊₁.ₖ * |ζₖ / uₖ.ₖ| - rNorm = Hbis * abs(z[inner_iter] / U[nr+inner_iter]) - history && push!(rNorms, rNorm) - - # Update the number of coefficients in Uₖ - nr = nr + inner_iter - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - breakdown = Hbis ≤ btol - solved = resid_decrease_lim || resid_decrease_mach - inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) - # Compute vₖ₊₁. - if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) - if !restart && (inner_iter ≥ mem) - push!(V, S(undef, n)) + # Hₖyₖ = βe₁ ⟺ LₖUₖyₖ = βe₁ ⟺ Uₖyₖ = zₖ. + # Compute yₖ by solving Uₖyₖ = zₖ with backward substitution. + y = z # yᵢ = zᵢ + for i = inner_iter : -1 : 1 + pos = nr + i - inner_iter # position of rᵢ.ₖ + for j = inner_iter : -1 : i+1 + y[i] = y[i] - U[pos] * y[j] # yᵢ ← yᵢ - uᵢⱼyⱼ + pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ end - @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q + y[i] = y[i] / U[pos] # yᵢ ← yᵢ / rᵢᵢ end - end - # Hₖyₖ = βe₁ ⟺ LₖUₖyₖ = βe₁ ⟺ Uₖyₖ = zₖ. - # Compute yₖ by solving Uₖyₖ = zₖ with backward substitution. - y = z # yᵢ = zᵢ - for i = inner_iter : -1 : 1 - pos = nr + i - inner_iter # position of rᵢ.ₖ - for j = inner_iter : -1 : i+1 - y[i] = y[i] - U[pos] * y[j] # yᵢ ← yᵢ - uᵢⱼyⱼ - pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + # Form xₖ = NVₖyₖ + for i = 1 : inner_iter + @kaxpy!(n, y[i], V[i], xr) end - y[i] = y[i] / U[pos] # yᵢ ← yᵢ / rᵢᵢ - end + if !NisI + solver.p .= xr + mulorldiv!(xr, N, solver.p, ldiv) + end + restart && @kaxpy!(n, one(FC), xr, x) - # Form xₖ = NVₖyₖ - for i = 1 : inner_iter - @kaxpy!(n, y[i], V[i], xr) - end - if !NisI - solver.p .= xr - mulorldiv!(xr, N, solver.p, ldiv) + # Update inner_itmax, iter, tired and overtimed variables. + inner_itmax = inner_itmax - inner_iter + iter = iter + inner_iter + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end - restart && @kaxpy!(n, one(FC), xr, x) + (verbose > 0) && @printf(iostream, "\n") - # Update inner_itmax, iter, tired and overtimed variables. - inner_itmax = inner_itmax - inner_iter - iter = iter + inner_iter - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "inconsistent linear system") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = !solved && breakdown + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "inconsistent linear system") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = !solved && breakdown - stats.status = status - return solver end diff --git a/src/gmres.jl b/src/gmres.jl index 0eb4c55e6..dc227bc21 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -115,265 +115,262 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it gmres!(solver, A, b; $(kwargs_gmres...)) return solver end -end - -function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - restart :: Bool=false, reorthogonalization :: Bool=false, - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "GMRES: system of size %d\n", n) - - # Check M = Iₙ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI , solver, :q , S, n) - allocate_if(!NisI , solver, :p , S, n) - allocate_if(restart, solver, :Δx, S, n) - Δx, x, w, V, z = solver.Δx, solver.x, solver.w, solver.V, solver.z - c, s, R, stats = solver.c, solver.s, solver.R, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - q = MisI ? w : solver.q - r₀ = MisI ? w : solver.q - xr = restart ? Δx : x - - # Initial solution x₀. - x .= zero(FC) - - # Initial residual r₀. - if warm_start - mul!(w, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), w) - restart && @kaxpy!(n, one(FC), Δx, x) - else - w .= b - end - MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) - β = @knrm2(n, r₀) # β = ‖r₀‖₂ - rNorm = β - history && push!(rNorms, β) - ε = atol + rtol * rNorm - - if β == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end + function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "GMRES: system of size %d\n", n) + + # Check M = Iₙ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI , solver, :q , S, n) + allocate_if(!NisI , solver, :p , S, n) + allocate_if(restart, solver, :Δx, S, n) + Δx, x, w, V, z = solver.Δx, solver.x, solver.w, solver.V, solver.z + c, s, R, stats = solver.c, solver.s, solver.R, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + q = MisI ? w : solver.q + r₀ = MisI ? w : solver.q + xr = restart ? Δx : x + + # Initial solution x₀. + x .= zero(FC) + + # Initial residual r₀. + if warm_start + mul!(w, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), w) + restart && @kaxpy!(n, one(FC), Δx, x) + else + w .= b + end + MisI || mulorldiv!(r₀, M, w, ldiv) # r₀ = M(b - Ax₀) + β = @knrm2(n, r₀) # β = ‖r₀‖₂ + + rNorm = β + history && push!(rNorms, β) + ε = atol + rtol * rNorm + + if β == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end - mem = length(c) # Memory - npass = 0 # Number of pass + mem = length(c) # Memory + npass = 0 # Number of pass - iter = 0 # Cumulative number of iterations - inner_iter = 0 # Number of iterations in a pass + iter = 0 # Cumulative number of iterations + inner_iter = 0 # Number of iterations in a pass - itmax == 0 && (itmax = 2*n) - inner_itmax = itmax + itmax == 0 && (itmax = 2*n) + inner_itmax = itmax - (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) - # Stopping criterion - breakdown = false - inconsistent = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - inner_tired = inner_iter ≥ inner_itmax - status = "unknown" - user_requested_exit = false - overtimed = false + # Stopping criterion + breakdown = false + inconsistent = false + solved = rNorm ≤ ε + tired = iter ≥ itmax + inner_tired = inner_iter ≥ inner_itmax + status = "unknown" + user_requested_exit = false + overtimed = false - while !(solved || tired || breakdown || user_requested_exit || overtimed) + while !(solved || tired || breakdown || user_requested_exit || overtimed) - # Initialize workspace. - nr = 0 # Number of coefficients stored in Rₖ. - for i = 1 : mem - V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). - end - s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. - c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. - R .= zero(FC) # Upper triangular matrix Rₖ. - z .= zero(FC) # Right-hand of the least squares problem min ‖Hₖ₊₁.ₖyₖ - βe₁‖₂. - - if restart - xr .= zero(FC) # xr === Δx when restart is set to true - if npass ≥ 1 - mul!(w, A, x) - @kaxpby!(n, one(FC), b, -one(FC), w) - MisI || mulorldiv!(r₀, M, w, ldiv) + # Initialize workspace. + nr = 0 # Number of coefficients stored in Rₖ. + for i = 1 : mem + V[i] .= zero(FC) # Orthogonal basis of Kₖ(MAN, Mr₀). + end + s .= zero(FC) # Givens sines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. + c .= zero(T) # Givens cosines used for the factorization QₖRₖ = Hₖ₊₁.ₖ. + R .= zero(FC) # Upper triangular matrix Rₖ. + z .= zero(FC) # Right-hand of the least squares problem min ‖Hₖ₊₁.ₖyₖ - βe₁‖₂. + + if restart + xr .= zero(FC) # xr === Δx when restart is set to true + if npass ≥ 1 + mul!(w, A, x) + @kaxpby!(n, one(FC), b, -one(FC), w) + MisI || mulorldiv!(r₀, M, w, ldiv) + end end - end - # Initial ζ₁ and V₁ - β = @knrm2(n, r₀) - z[1] = β - @. V[1] = r₀ / rNorm + # Initial ζ₁ and V₁ + β = @knrm2(n, r₀) + z[1] = β + @. V[1] = r₀ / rNorm - npass = npass + 1 - solver.inner_iter = 0 - inner_tired = false + npass = npass + 1 + solver.inner_iter = 0 + inner_tired = false - while !(solved || inner_tired || breakdown || user_requested_exit || overtimed) + while !(solved || inner_tired || breakdown || user_requested_exit || overtimed) - # Update iteration index - solver.inner_iter = solver.inner_iter + 1 - inner_iter = solver.inner_iter + # Update iteration index + solver.inner_iter = solver.inner_iter + 1 + inner_iter = solver.inner_iter - # Update workspace if more storage is required and restart is set to false - if !restart && (inner_iter > mem) + # Update workspace if more storage is required and restart is set to false + if !restart && (inner_iter > mem) + for i = 1 : inner_iter + push!(R, zero(FC)) + end + push!(s, zero(FC)) + push!(c, zero(T)) + end + + # Continue the Arnoldi process. + p = NisI ? V[inner_iter] : solver.p + NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← Nvₖ + mul!(w, A, p) # w ← ANvₖ + MisI || mulorldiv!(q, M, w, ldiv) # q ← MANvₖ for i = 1 : inner_iter - push!(R, zero(FC)) + R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq + @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ end - push!(s, zero(FC)) - push!(c, zero(T)) - end - # Continue the Arnoldi process. - p = NisI ? V[inner_iter] : solver.p - NisI || mulorldiv!(p, N, V[inner_iter], ldiv) # p ← Nvₖ - mul!(w, A, p) # w ← ANvₖ - MisI || mulorldiv!(q, M, w, ldiv) # q ← MANvₖ - for i = 1 : inner_iter - R[nr+i] = @kdot(n, V[i], q) # hᵢₖ = (vᵢ)ᴴq - @kaxpy!(n, -R[nr+i], V[i], q) # q ← q - hᵢₖvᵢ - end + # Reorthogonalization of the Krylov basis. + if reorthogonalization + for i = 1 : inner_iter + Htmp = @kdot(n, V[i], q) + R[nr+i] += Htmp + @kaxpy!(n, -Htmp, V[i], q) + end + end - # Reorthogonalization of the Krylov basis. - if reorthogonalization - for i = 1 : inner_iter - Htmp = @kdot(n, V[i], q) - R[nr+i] += Htmp - @kaxpy!(n, -Htmp, V[i], q) + # Compute hₖ₊₁.ₖ + Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ + + # Update the QR factorization of Hₖ₊₁.ₖ. + # Apply previous Givens reflections Ωᵢ. + # [cᵢ sᵢ] [ r̄ᵢ.ₖ ] = [ rᵢ.ₖ ] + # [s̄ᵢ -cᵢ] [rᵢ₊₁.ₖ] [r̄ᵢ₊₁.ₖ] + for i = 1 : inner_iter-1 + Rtmp = c[i] * R[nr+i] + s[i] * R[nr+i+1] + R[nr+i+1] = conj(s[i]) * R[nr+i] - c[i] * R[nr+i+1] + R[nr+i] = Rtmp end - end - # Compute hₖ₊₁.ₖ - Hbis = @knrm2(n, q) # hₖ₊₁.ₖ = ‖vₖ₊₁‖₂ - - # Update the QR factorization of Hₖ₊₁.ₖ. - # Apply previous Givens reflections Ωᵢ. - # [cᵢ sᵢ] [ r̄ᵢ.ₖ ] = [ rᵢ.ₖ ] - # [s̄ᵢ -cᵢ] [rᵢ₊₁.ₖ] [r̄ᵢ₊₁.ₖ] - for i = 1 : inner_iter-1 - Rtmp = c[i] * R[nr+i] + s[i] * R[nr+i+1] - R[nr+i+1] = conj(s[i]) * R[nr+i] - c[i] * R[nr+i+1] - R[nr+i] = Rtmp + # Compute and apply current Givens reflection Ωₖ. + # [cₖ sₖ] [ r̄ₖ.ₖ ] = [rₖ.ₖ] + # [s̄ₖ -cₖ] [hₖ₊₁.ₖ] [ 0 ] + (c[inner_iter], s[inner_iter], R[nr+inner_iter]) = sym_givens(R[nr+inner_iter], Hbis) + + # Update zₖ = (Qₖ)ᴴβe₁ + ζₖ₊₁ = conj(s[inner_iter]) * z[inner_iter] + z[inner_iter] = c[inner_iter] * z[inner_iter] + + # Update residual norm estimate. + # ‖ M(b - Axₖ) ‖₂ = |ζₖ₊₁| + rNorm = abs(ζₖ₊₁) + history && push!(rNorms, rNorm) + + # Update the number of coefficients in Rₖ + nr = nr + inner_iter + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + breakdown = Hbis ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + + # Compute vₖ₊₁. + if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) + if !restart && (inner_iter ≥ mem) + push!(V, S(undef, n)) + push!(z, zero(FC)) + end + @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q + z[inner_iter+1] = ζₖ₊₁ + end end - # Compute and apply current Givens reflection Ωₖ. - # [cₖ sₖ] [ r̄ₖ.ₖ ] = [rₖ.ₖ] - # [s̄ₖ -cₖ] [hₖ₊₁.ₖ] [ 0 ] - (c[inner_iter], s[inner_iter], R[nr+inner_iter]) = sym_givens(R[nr+inner_iter], Hbis) - - # Update zₖ = (Qₖ)ᴴβe₁ - ζₖ₊₁ = conj(s[inner_iter]) * z[inner_iter] - z[inner_iter] = c[inner_iter] * z[inner_iter] - - # Update residual norm estimate. - # ‖ M(b - Axₖ) ‖₂ = |ζₖ₊₁| - rNorm = abs(ζₖ₊₁) - history && push!(rNorms, rNorm) - - # Update the number of coefficients in Rₖ - nr = nr + inner_iter - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - breakdown = Hbis ≤ btol - solved = resid_decrease_lim || resid_decrease_mach - inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) - - # Compute vₖ₊₁. - if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) - if !restart && (inner_iter ≥ mem) - push!(V, S(undef, n)) - push!(z, zero(FC)) + # Compute yₖ by solving Rₖyₖ = zₖ with backward substitution. + y = z # yᵢ = zᵢ + for i = inner_iter : -1 : 1 + pos = nr + i - inner_iter # position of rᵢ.ₖ + for j = inner_iter : -1 : i+1 + y[i] = y[i] - R[pos] * y[j] # yᵢ ← yᵢ - rᵢⱼyⱼ + pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + end + # Rₖ can be singular if the system is inconsistent + if abs(R[pos]) ≤ btol + y[i] = zero(FC) + inconsistent = true + else + y[i] = y[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ end - @. V[inner_iter+1] = q / Hbis # hₖ₊₁.ₖvₖ₊₁ = q - z[inner_iter+1] = ζₖ₊₁ end - end - # Compute yₖ by solving Rₖyₖ = zₖ with backward substitution. - y = z # yᵢ = zᵢ - for i = inner_iter : -1 : 1 - pos = nr + i - inner_iter # position of rᵢ.ₖ - for j = inner_iter : -1 : i+1 - y[i] = y[i] - R[pos] * y[j] # yᵢ ← yᵢ - rᵢⱼyⱼ - pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + # Form xₖ = NVₖyₖ + for i = 1 : inner_iter + @kaxpy!(n, y[i], V[i], xr) end - # Rₖ can be singular if the system is inconsistent - if abs(R[pos]) ≤ btol - y[i] = zero(FC) - inconsistent = true - else - y[i] = y[i] / R[pos] # yᵢ ← yᵢ / rᵢᵢ + if !NisI + solver.p .= xr + mulorldiv!(xr, N, solver.p, ldiv) end - end + restart && @kaxpy!(n, one(FC), xr, x) - # Form xₖ = NVₖyₖ - for i = 1 : inner_iter - @kaxpy!(n, y[i], V[i], xr) - end - if !NisI - solver.p .= xr - mulorldiv!(xr, N, solver.p, ldiv) + # Update inner_itmax, iter, tired and overtimed variables. + inner_itmax = inner_itmax - inner_iter + iter = iter + inner_iter + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns end - restart && @kaxpy!(n, one(FC), xr, x) + (verbose > 0) && @printf(iostream, "\n") - # Update inner_itmax, iter, tired and overtimed variables. - inner_itmax = inner_itmax - inner_iter - iter = iter + inner_iter - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + inconsistent && (status = "found approximate least-squares solution") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - inconsistent && (status = "found approximate least-squares solution") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && !restart && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/gpmr.jl b/src/gpmr.jl index 3726709cf..a4165a465 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -150,393 +150,387 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato return (solver.x, solver.y, solver.stats) end - function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return solver end -end -function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - C=I, D=I, E=I, F=I, - ldiv :: Bool=false, gsp :: Bool=false, - λ :: FC=one(FC), μ :: FC=one(FC), - reorthogonalization :: Bool=false, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history::Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - s, t = size(B) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == t || error("Inconsistent problem size") - s == n || error("Inconsistent problem size") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "GPMR: system of %d equations in %d variables\n", m+n, m+n) - - # Check C = E = Iₘ and D = F = Iₙ - CisI = (C === I) - DisI = (D === I) - EisI = (E === I) - FisI = (F === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - eltype(B) == FC || error("eltype(B) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Determine λ and μ associated to generalized saddle point systems. - gsp && (λ = one(FC) ; μ = zero(FC)) - - warm_start = solver.warm_start - warm_start && (λ ≠ 0) && !EisI && error("Warm-start with right preconditioners is not supported.") - warm_start && (μ ≠ 0) && !FisI && error("Warm-start with right preconditioners is not supported.") - - # Set up workspace. - allocate_if(!CisI, solver, :q , S, m) - allocate_if(!DisI, solver, :p , S, n) - allocate_if(!EisI, solver, :wB, S, m) - allocate_if(!FisI, solver, :wA, S, n) - wA, wB, dA, dB, Δx, Δy = solver.wA, solver.wB, solver.dA, solver.dB, solver.Δx, solver.Δy - x, y, V, U, gs, gc = solver.x, solver.y, solver.V, solver.U, solver.gs, solver.gc - zt, R, stats = solver.zt, solver.R, solver.stats - rNorms = stats.residuals - reset!(stats) - b₀ = warm_start ? dA : b - c₀ = warm_start ? dB : c - q = CisI ? dA : solver.q - p = DisI ? dB : solver.p - - # Initial solutions x₀ and y₀. - x .= zero(FC) - y .= zero(FC) - - iter = 0 - itmax == 0 && (itmax = m+n) - - # Initialize workspace. - nr = 0 # Number of coefficients stored in Rₖ - mem = length(V) # Memory - ωₖ = zero(FC) # Auxiliary variable to store fₖₖ - for i = 1 : mem - V[i] .= zero(FC) - U[i] .= zero(FC) - end - gs .= zero(FC) # Givens sines used for the factorization QₖRₖ = Sₖ₊₁.ₖ. - gc .= zero(T) # Givens cosines used for the factorization QₖRₖ = Sₖ₊₁.ₖ. - R .= zero(FC) # Upper triangular matrix Rₖ. - zt .= zero(FC) # Rₖzₖ = tₖ with (tₖ, τbar₂ₖ₊₁, τbar₂ₖ₊₂) = (Qₖ)ᴴ(βe₁ + γe₂). - - # Warm-start - # If λ ≠ 0, Cb₀ = Cb - CAΔy - λΔx because CM = Iₘ and E = Iₘ - # E ≠ Iₘ is only allowed when λ = 0 because E⁻¹Δx can't be computed to use CME = Iₘ - # Compute C(b - AΔy) - λΔx - warm_start && mul!(b₀, A, Δy) - warm_start && @kaxpby!(m, one(FC), b, -one(FC), b₀) - !CisI && mulorldiv!(q, C, b₀, ldiv) - !CisI && (b₀ = q) - warm_start && (λ ≠ 0) && @kaxpy!(m, -λ, Δx, b₀) - - # If μ ≠ 0, Dc₀ = Dc - DBΔx - μΔy because DN = Iₙ and F = Iₙ - # F ≠ Iₙ is only allowed when μ = 0 because F⁻¹Δy can't be computed to use DNF = Iₘ - # Compute D(c - BΔx) - μΔy - warm_start && mul!(c₀, B, Δx) - warm_start && @kaxpby!(n, one(FC), c, -one(FC), c₀) - !DisI && mulorldiv!(p, D, c₀, ldiv) - !DisI && (c₀ = p) - warm_start && (μ ≠ 0) && @kaxpy!(n, -μ, Δy, c₀) - - # Initialize the orthogonal Hessenberg reduction process. - # βv₁ = Cb - β = @knrm2(m, b₀) - β ≠ 0 || error("b must be nonzero") - @. V[1] = b₀ / β - - # γu₁ = Dc - γ = @knrm2(n, c₀) - γ ≠ 0 || error("c must be nonzero") - @. U[1] = c₀ / γ - - # Compute ‖r₀‖² = γ² + β² - rNorm = sqrt(γ^2 + β^2) - history && push!(rNorms, rNorm) - ε = atol + rtol * rNorm - - # Initialize t̄₀ - zt[1] = β - zt[2] = γ - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "fₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7s\n", iter, rNorm, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗") - - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) - - # Stopping criterion. - breakdown = false - inconsistent = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || breakdown || user_requested_exit || overtimed) - - # Update iteration index. - iter = iter + 1 - k = iter - nr₂ₖ₋₁ = nr # Position of the column 2k-1 in Rₖ. - nr₂ₖ = nr + 2k-1 # Position of the column 2k in Rₖ. - - # Update workspace if more storage is required - if iter > mem - for i = 1 : 4k-1 - push!(R, zero(FC)) - end - for i = 1 : 4 - push!(gs, zero(FC)) - push!(gc, zero(T)) - end + function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + s, t = size(B) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == t || error("Inconsistent problem size") + s == n || error("Inconsistent problem size") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "GPMR: system of %d equations in %d variables\n", m+n, m+n) + + # Check C = E = Iₘ and D = F = Iₙ + CisI = (C === I) + DisI = (D === I) + EisI = (E === I) + FisI = (F === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(B) == FC || error("eltype(B) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Determine λ and μ associated to generalized saddle point systems. + gsp && (λ = one(FC) ; μ = zero(FC)) + + warm_start = solver.warm_start + warm_start && (λ ≠ 0) && !EisI && error("Warm-start with right preconditioners is not supported.") + warm_start && (μ ≠ 0) && !FisI && error("Warm-start with right preconditioners is not supported.") + + # Set up workspace. + allocate_if(!CisI, solver, :q , S, m) + allocate_if(!DisI, solver, :p , S, n) + allocate_if(!EisI, solver, :wB, S, m) + allocate_if(!FisI, solver, :wA, S, n) + wA, wB, dA, dB, Δx, Δy = solver.wA, solver.wB, solver.dA, solver.dB, solver.Δx, solver.Δy + x, y, V, U, gs, gc = solver.x, solver.y, solver.V, solver.U, solver.gs, solver.gc + zt, R, stats = solver.zt, solver.R, solver.stats + rNorms = stats.residuals + reset!(stats) + b₀ = warm_start ? dA : b + c₀ = warm_start ? dB : c + q = CisI ? dA : solver.q + p = DisI ? dB : solver.p + + # Initial solutions x₀ and y₀. + x .= zero(FC) + y .= zero(FC) + + iter = 0 + itmax == 0 && (itmax = m+n) + + # Initialize workspace. + nr = 0 # Number of coefficients stored in Rₖ + mem = length(V) # Memory + ωₖ = zero(FC) # Auxiliary variable to store fₖₖ + for i = 1 : mem + V[i] .= zero(FC) + U[i] .= zero(FC) end + gs .= zero(FC) # Givens sines used for the factorization QₖRₖ = Sₖ₊₁.ₖ. + gc .= zero(T) # Givens cosines used for the factorization QₖRₖ = Sₖ₊₁.ₖ. + R .= zero(FC) # Upper triangular matrix Rₖ. + zt .= zero(FC) # Rₖzₖ = tₖ with (tₖ, τbar₂ₖ₊₁, τbar₂ₖ₊₂) = (Qₖ)ᴴ(βe₁ + γe₂). + + # Warm-start + # If λ ≠ 0, Cb₀ = Cb - CAΔy - λΔx because CM = Iₘ and E = Iₘ + # E ≠ Iₘ is only allowed when λ = 0 because E⁻¹Δx can't be computed to use CME = Iₘ + # Compute C(b - AΔy) - λΔx + warm_start && mul!(b₀, A, Δy) + warm_start && @kaxpby!(m, one(FC), b, -one(FC), b₀) + !CisI && mulorldiv!(q, C, b₀, ldiv) + !CisI && (b₀ = q) + warm_start && (λ ≠ 0) && @kaxpy!(m, -λ, Δx, b₀) + + # If μ ≠ 0, Dc₀ = Dc - DBΔx - μΔy because DN = Iₙ and F = Iₙ + # F ≠ Iₙ is only allowed when μ = 0 because F⁻¹Δy can't be computed to use DNF = Iₘ + # Compute D(c - BΔx) - μΔy + warm_start && mul!(c₀, B, Δx) + warm_start && @kaxpby!(n, one(FC), c, -one(FC), c₀) + !DisI && mulorldiv!(p, D, c₀, ldiv) + !DisI && (c₀ = p) + warm_start && (μ ≠ 0) && @kaxpy!(n, -μ, Δy, c₀) + + # Initialize the orthogonal Hessenberg reduction process. + # βv₁ = Cb + β = @knrm2(m, b₀) + β ≠ 0 || error("b must be nonzero") + @. V[1] = b₀ / β + + # γu₁ = Dc + γ = @knrm2(n, c₀) + γ ≠ 0 || error("c must be nonzero") + @. U[1] = c₀ / γ + + # Compute ‖r₀‖² = γ² + β² + rNorm = sqrt(γ^2 + β^2) + history && push!(rNorms, rNorm) + ε = atol + rtol * rNorm - # Continue the orthogonal Hessenberg reduction process. - # CAFUₖ = VₖHₖ + hₖ₊₁.ₖ * vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Hₖ₊₁.ₖ - # DBEVₖ = UₖFₖ + fₖ₊₁.ₖ * uₖ₊₁(eₖ)ᵀ = Uₖ₊₁Fₖ₊₁.ₖ - wA = FisI ? U[iter] : solver.wA - wB = EisI ? V[iter] : solver.wB - FisI || mulorldiv!(wA, F, U[iter], ldiv) # wA = Fuₖ - EisI || mulorldiv!(wB, E, V[iter], ldiv) # wB = Evₖ - mul!(dA, A, wA) # dA = AFuₖ - mul!(dB, B, wB) # dB = BEvₖ - CisI || mulorldiv!(q, C, dA, ldiv) # q = CAFuₖ - DisI || mulorldiv!(p, D, dB, ldiv) # p = DBEvₖ + # Initialize t̄₀ + zt[1] = β + zt[2] = γ - for i = 1 : iter - hᵢₖ = @kdot(m, V[i], q) # hᵢ.ₖ = (vᵢ)ᴴq - fᵢₖ = @kdot(n, U[i], p) # fᵢ.ₖ = (uᵢ)ᴴp - @kaxpy!(m, -hᵢₖ, V[i], q) # q ← q - hᵢ.ₖvᵢ - @kaxpy!(n, -fᵢₖ, U[i], p) # p ← p - fᵢ.ₖuᵢ - R[nr₂ₖ + 2i-1] = hᵢₖ - (i < iter) ? R[nr₂ₖ₋₁ + 2i] = fᵢₖ : ωₖ = fᵢₖ - end + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "fₖ₊₁.ₖ") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7s\n", iter, rNorm, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗") - # Reorthogonalization of the Krylov basis. - if reorthogonalization - for i = 1 : iter - Htmp = @kdot(m, V[i], q) # hₜₘₚ = (vᵢ)ᴴq - Ftmp = @kdot(n, U[i], p) # fₜₘₚ = (uᵢ)ᴴp - @kaxpy!(m, -Htmp, V[i], q) # q ← q - hₜₘₚvᵢ - @kaxpy!(n, -Ftmp, U[i], p) # p ← p - fₜₘₚuᵢ - R[nr₂ₖ + 2i-1] += Htmp # hᵢ.ₖ = hᵢ.ₖ + hₜₘₚ - (i < iter) ? R[nr₂ₖ₋₁ + 2i] += Ftmp : ωₖ += Ftmp # fᵢ.ₖ = fᵢ.ₖ + fₜₘₚ - end - end + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) - Haux = @knrm2(m, q) # hₖ₊₁.ₖ = ‖q‖₂ - Faux = @knrm2(n, p) # fₖ₊₁.ₖ = ‖p‖₂ - - # Add regularization terms. - R[nr₂ₖ₋₁ + 2k-1] = λ # S₂ₖ₋₁.₂ₖ₋₁ = λ - R[nr₂ₖ + 2k] = μ # S₂ₖ.₂ₖ = μ - - # Notations : Wₖ = [w₁ ••• wₖ] = [v₁ 0 ••• vₖ 0 ] - # [0 u₁ ••• 0 uₖ] - # - # rₖ = [ b ] - [ λI A ] [ xₖ ] = [ b ] - [ λI A ] Wₖzₖ - # [ c ] [ B μI ] [ yₖ ] [ c ] [ B μI ] - # - # block-Arnoldi formulation : [ λI A ] Wₖ = Wₖ₊₁Sₖ₊₁.ₖ - # [ B μI ] - # - # GPMR subproblem : min ‖ rₖ ‖ ↔ min ‖ Sₖ₊₁.ₖzₖ - βe₁ - γe₂ ‖ - # - # Update the QR factorization of Sₖ₊₁.ₖ = Qₖ [ Rₖ ]. - # [ Oᵀ ] - # - # Apply previous givens reflections when k ≥ 2 - # [ 1 ][ 1 ][ c₂.ᵢ s₂.ᵢ ][ c₁.ᵢ s₁.ᵢ ] [ r̄₂ᵢ₋₁.₂ₖ₋₁ r̄₂ᵢ₋₁.₂ₖ ] [ r₂ᵢ₋₁.₂ₖ₋₁ r₂ᵢ₋₁.₂ₖ ] - # [ c₄.ᵢ s₄.ᵢ ][ c₃.ᵢ s₃.ᵢ ][ s̄₂.ᵢ -c₂.ᵢ ][ 1 ] [ r̄₂ᵢ.₂ₖ₋₁ r̄₂ᵢ.₂ₖ ] = [ r₂ᵢ.₂ₖ₋₁ r₂ᵢ.₂ₖ ] - # [ s̄₄.ᵢ -c₄.ᵢ ][ 1 ][ 1 ][ 1 ] [ ρ hᵢ₊₁.ₖ ] [ r̄₂ᵢ₊₁.₂ₖ₋₁ r̄₂ᵢ₊₁.₂ₖ ] - # [ 1 ][ s̄₃.ᵢ -c₃.ᵢ ][ 1 ][ s̄₁.ᵢ -c₁.ᵢ ] [ fᵢ₊₁.ₖ δ ] [ r̄₂ᵢ₊₂.₂ₖ₋₁ r̄₂ᵢ₊₂.₂ₖ ] - # - # r̄₁.₂ₖ₋₁ = 0, r̄₁.₂ₖ = h₁.ₖ, r̄₂.₂ₖ₋₁ = f₁.ₖ and r̄₂.₂ₖ = 0. - # (ρ, δ) = (λ, μ) if i == k-1, (ρ, δ) = (0, 0) otherwise. - for i = 1 : iter-1 - for nrcol ∈ (nr₂ₖ₋₁, nr₂ₖ) - flag = (i == iter-1 && nrcol == nr₂ₖ₋₁) - αₖ = flag ? ωₖ : R[nrcol + 2i+2] - - c₁ᵢ = gc[4i-3] - s₁ᵢ = gs[4i-3] - rtmp = c₁ᵢ * R[nrcol + 2i-1] + s₁ᵢ * αₖ - αₖ = conj(s₁ᵢ) * R[nrcol + 2i-1] - c₁ᵢ * αₖ - R[nrcol + 2i-1] = rtmp - - c₂ᵢ = gc[4i-2] - s₂ᵢ = gs[4i-2] - rtmp = c₂ᵢ * R[nrcol + 2i-1] + s₂ᵢ * R[nrcol + 2i] - R[nrcol + 2i] = conj(s₂ᵢ) * R[nrcol + 2i-1] - c₂ᵢ * R[nrcol + 2i] - R[nrcol + 2i-1] = rtmp - - c₃ᵢ = gc[4i-1] - s₃ᵢ = gs[4i-1] - rtmp = c₃ᵢ * R[nrcol + 2i] + s₃ᵢ * αₖ - αₖ = conj(s₃ᵢ) * R[nrcol + 2i] - c₃ᵢ * αₖ - R[nrcol + 2i] = rtmp - - c₄ᵢ = gc[4i] - s₄ᵢ = gs[4i] - rtmp = c₄ᵢ * R[nrcol + 2i] + s₄ᵢ * R[nrcol + 2i+1] - R[nrcol + 2i+1] = conj(s₄ᵢ) * R[nrcol + 2i] - c₄ᵢ * R[nrcol + 2i+1] - R[nrcol + 2i] = rtmp - - flag ? ωₖ = αₖ : R[nrcol + 2i+2] = αₖ + # Stopping criterion. + breakdown = false + inconsistent = false + solved = rNorm ≤ ε + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || breakdown || user_requested_exit || overtimed) + + # Update iteration index. + iter = iter + 1 + k = iter + nr₂ₖ₋₁ = nr # Position of the column 2k-1 in Rₖ. + nr₂ₖ = nr + 2k-1 # Position of the column 2k in Rₖ. + + # Update workspace if more storage is required + if iter > mem + for i = 1 : 4k-1 + push!(R, zero(FC)) + end + for i = 1 : 4 + push!(gs, zero(FC)) + push!(gc, zero(T)) + end end - end - # Compute and apply current givens reflections - # [ 1 ][ 1 ][ c₂.ₖ s₂.ₖ ][ c₁.ₖ s₁.ₖ ] [ r̄₂ₖ₋₁.₂ₖ₋₁ r̄₂ₖ₋₁.₂ₖ ] [ r₂ₖ₋₁.₂ₖ₋₁ r₂ₖ₋₁.₂ₖ ] - # [ c₄.ₖ s₄.ₖ ][ c₃.ₖ s₃.ₖ ][ s̄₂.ₖ -c₂.ₖ ][ 1 ] [ r̄₂ₖ.₂ₖ₋₁ r̄₂ₖ.₂ₖ ] = [ r₂ₖ.₂ₖ ] - # [ s̄₄.ₖ -c₄.ₖ ][ 1 ][ 1 ][ 1 ] [ hₖ₊₁.ₖ ] [ ] - # [ 1 ][ s̄₃.ₖ -c₃.ₖ ][ 1 ][ s̄₁.ₖ -c₁.ₖ ] [ fₖ₊₁.ₖ ] [ ] - (c₁ₖ, s₁ₖ, R[nr₂ₖ₋₁ + 2k-1]) = sym_givens(R[nr₂ₖ₋₁ + 2k-1], Faux) # annihilate fₖ₊₁.ₖ - θₖ = conj(s₁ₖ) * R[nr₂ₖ + 2k-1] - R[nr₂ₖ + 2k-1] = c₁ₖ * R[nr₂ₖ + 2k-1] - - (c₂ₖ, s₂ₖ, R[nr₂ₖ₋₁ + 2k-1]) = sym_givens(R[nr₂ₖ₋₁ + 2k-1], ωₖ) # annihilate ωₖ = r̄₂ₖ.₂ₖ₋₁ - rtmp = c₂ₖ * R[nr₂ₖ + 2k-1] + s₂ₖ * R[nr₂ₖ + 2k] - R[nr₂ₖ + 2k] = conj(s₂ₖ) * R[nr₂ₖ + 2k-1] - c₂ₖ * R[nr₂ₖ + 2k] - R[nr₂ₖ + 2k-1] = rtmp - - (c₃ₖ, s₃ₖ, R[nr₂ₖ + 2k]) = sym_givens(R[nr₂ₖ + 2k], θₖ) # annihilate Θₖ = r̄₂ₖ₊₂.₂ₖ - - (c₄ₖ, s₄ₖ, R[nr₂ₖ + 2k]) = sym_givens(R[nr₂ₖ + 2k], Haux) # annihilate hₖ₊₁.ₖ - - # Update t̄ₖ = (τ₁, ..., τ₂ₖ, τbar₂ₖ₊₁, τbar₂ₖ₊₂). - # - # [ 1 ][ 1 ][ c₂.ₖ s₂.ₖ ][ c₁.ₖ s₁.ₖ ] [ τbar₂ₖ₋₁ ] [ τ₂ₖ₋₁ ] - # [ c₄.ₖ s₄.ₖ ][ c₃.ₖ s₃.ₖ ][ s̄₂.ₖ -c₂.ₖ ][ 1 ] [ τbar₂ₖ ] = [ τ₂ₖ ] - # [ s̄₄.ₖ -c₄.ₖ ][ 1 ][ 1 ][ 1 ] [ ] [ τbar₂ₖ₊₁ ] - # [ 1 ][ s̄₃.ₖ -c₃.ₖ ][ 1 ][ s̄₁.ₖ -c₁.ₖ ] [ ] [ τbar₂ₖ₊₂ ] - τbar₂ₖ₊₂ = conj(s₁ₖ) * zt[2k-1] - zt[2k-1] = c₁ₖ * zt[2k-1] - - τtmp = c₂ₖ * zt[2k-1] + s₂ₖ * zt[2k] - zt[2k] = conj(s₂ₖ) * zt[2k-1] - c₂ₖ * zt[2k] - zt[2k-1] = τtmp - - τtmp = c₃ₖ * zt[2k] + s₃ₖ * τbar₂ₖ₊₂ - τbar₂ₖ₊₂ = conj(s₃ₖ) * zt[2k] - c₃ₖ * τbar₂ₖ₊₂ - zt[2k] = τtmp - - τbar₂ₖ₊₁ = conj(s₄ₖ) * zt[2k] - zt[2k] = c₄ₖ * zt[2k] - - # Update gc and gs vectors - gc[4k-3], gc[4k-2], gc[4k-1], gc[4k] = c₁ₖ, c₂ₖ, c₃ₖ, c₄ₖ - gs[4k-3], gs[4k-2], gs[4k-1], gs[4k] = s₁ₖ, s₂ₖ, s₃ₖ, s₄ₖ - - # Compute ‖rₖ‖² = |τbar₂ₖ₊₁|² + |τbar₂ₖ₊₂|² - rNorm = sqrt(abs2(τbar₂ₖ₊₁) + abs2(τbar₂ₖ₊₂)) - history && push!(rNorms, rNorm) - - # Update the number of coefficients in Rₖ. - nr = nr + 4k-1 + # Continue the orthogonal Hessenberg reduction process. + # CAFUₖ = VₖHₖ + hₖ₊₁.ₖ * vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Hₖ₊₁.ₖ + # DBEVₖ = UₖFₖ + fₖ₊₁.ₖ * uₖ₊₁(eₖ)ᵀ = Uₖ₊₁Fₖ₊₁.ₖ + wA = FisI ? U[iter] : solver.wA + wB = EisI ? V[iter] : solver.wB + FisI || mulorldiv!(wA, F, U[iter], ldiv) # wA = Fuₖ + EisI || mulorldiv!(wB, E, V[iter], ldiv) # wB = Evₖ + mul!(dA, A, wA) # dA = AFuₖ + mul!(dB, B, wB) # dB = BEvₖ + CisI || mulorldiv!(q, C, dA, ldiv) # q = CAFuₖ + DisI || mulorldiv!(p, D, dB, ldiv) # p = DBEvₖ - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + for i = 1 : iter + hᵢₖ = @kdot(m, V[i], q) # hᵢ.ₖ = (vᵢ)ᴴq + fᵢₖ = @kdot(n, U[i], p) # fᵢ.ₖ = (uᵢ)ᴴp + @kaxpy!(m, -hᵢₖ, V[i], q) # q ← q - hᵢ.ₖvᵢ + @kaxpy!(n, -fᵢₖ, U[i], p) # p ← p - fᵢ.ₖuᵢ + R[nr₂ₖ + 2i-1] = hᵢₖ + (i < iter) ? R[nr₂ₖ₋₁ + 2i] = fᵢₖ : ωₖ = fᵢₖ + end - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - breakdown = Faux ≤ btol && Haux ≤ btol - solved = resid_decrease_lim || resid_decrease_mach - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, Haux, Faux) - - # Compute vₖ₊₁ and uₖ₊₁ - if !(solved || tired || breakdown || user_requested_exit || overtimed) - if iter ≥ mem - push!(V, S(undef, m)) - push!(U, S(undef, n)) - push!(zt, zero(FC), zero(FC)) + # Reorthogonalization of the Krylov basis. + if reorthogonalization + for i = 1 : iter + Htmp = @kdot(m, V[i], q) # hₜₘₚ = (vᵢ)ᴴq + Ftmp = @kdot(n, U[i], p) # fₜₘₚ = (uᵢ)ᴴp + @kaxpy!(m, -Htmp, V[i], q) # q ← q - hₜₘₚvᵢ + @kaxpy!(n, -Ftmp, U[i], p) # p ← p - fₜₘₚuᵢ + R[nr₂ₖ + 2i-1] += Htmp # hᵢ.ₖ = hᵢ.ₖ + hₜₘₚ + (i < iter) ? R[nr₂ₖ₋₁ + 2i] += Ftmp : ωₖ += Ftmp # fᵢ.ₖ = fᵢ.ₖ + fₜₘₚ + end end - # hₖ₊₁.ₖ ≠ 0 - if Haux > btol - @. V[k+1] = q / Haux # hₖ₊₁.ₖvₖ₊₁ = q - else - # Breakdown -- hₖ₊₁.ₖ = ‖q‖₂ = 0 and Auₖ ∈ Span{v₁, ..., vₖ} - V[k+1] .= zero(FC) # vₖ₊₁ = 0 such that vₖ₊₁ ⊥ Span{v₁, ..., vₖ} + Haux = @knrm2(m, q) # hₖ₊₁.ₖ = ‖q‖₂ + Faux = @knrm2(n, p) # fₖ₊₁.ₖ = ‖p‖₂ + + # Add regularization terms. + R[nr₂ₖ₋₁ + 2k-1] = λ # S₂ₖ₋₁.₂ₖ₋₁ = λ + R[nr₂ₖ + 2k] = μ # S₂ₖ.₂ₖ = μ + + # Notations : Wₖ = [w₁ ••• wₖ] = [v₁ 0 ••• vₖ 0 ] + # [0 u₁ ••• 0 uₖ] + # + # rₖ = [ b ] - [ λI A ] [ xₖ ] = [ b ] - [ λI A ] Wₖzₖ + # [ c ] [ B μI ] [ yₖ ] [ c ] [ B μI ] + # + # block-Arnoldi formulation : [ λI A ] Wₖ = Wₖ₊₁Sₖ₊₁.ₖ + # [ B μI ] + # + # GPMR subproblem : min ‖ rₖ ‖ ↔ min ‖ Sₖ₊₁.ₖzₖ - βe₁ - γe₂ ‖ + # + # Update the QR factorization of Sₖ₊₁.ₖ = Qₖ [ Rₖ ]. + # [ Oᵀ ] + # + # Apply previous givens reflections when k ≥ 2 + # [ 1 ][ 1 ][ c₂.ᵢ s₂.ᵢ ][ c₁.ᵢ s₁.ᵢ ] [ r̄₂ᵢ₋₁.₂ₖ₋₁ r̄₂ᵢ₋₁.₂ₖ ] [ r₂ᵢ₋₁.₂ₖ₋₁ r₂ᵢ₋₁.₂ₖ ] + # [ c₄.ᵢ s₄.ᵢ ][ c₃.ᵢ s₃.ᵢ ][ s̄₂.ᵢ -c₂.ᵢ ][ 1 ] [ r̄₂ᵢ.₂ₖ₋₁ r̄₂ᵢ.₂ₖ ] = [ r₂ᵢ.₂ₖ₋₁ r₂ᵢ.₂ₖ ] + # [ s̄₄.ᵢ -c₄.ᵢ ][ 1 ][ 1 ][ 1 ] [ ρ hᵢ₊₁.ₖ ] [ r̄₂ᵢ₊₁.₂ₖ₋₁ r̄₂ᵢ₊₁.₂ₖ ] + # [ 1 ][ s̄₃.ᵢ -c₃.ᵢ ][ 1 ][ s̄₁.ᵢ -c₁.ᵢ ] [ fᵢ₊₁.ₖ δ ] [ r̄₂ᵢ₊₂.₂ₖ₋₁ r̄₂ᵢ₊₂.₂ₖ ] + # + # r̄₁.₂ₖ₋₁ = 0, r̄₁.₂ₖ = h₁.ₖ, r̄₂.₂ₖ₋₁ = f₁.ₖ and r̄₂.₂ₖ = 0. + # (ρ, δ) = (λ, μ) if i == k-1, (ρ, δ) = (0, 0) otherwise. + for i = 1 : iter-1 + for nrcol ∈ (nr₂ₖ₋₁, nr₂ₖ) + flag = (i == iter-1 && nrcol == nr₂ₖ₋₁) + αₖ = flag ? ωₖ : R[nrcol + 2i+2] + + c₁ᵢ = gc[4i-3] + s₁ᵢ = gs[4i-3] + rtmp = c₁ᵢ * R[nrcol + 2i-1] + s₁ᵢ * αₖ + αₖ = conj(s₁ᵢ) * R[nrcol + 2i-1] - c₁ᵢ * αₖ + R[nrcol + 2i-1] = rtmp + + c₂ᵢ = gc[4i-2] + s₂ᵢ = gs[4i-2] + rtmp = c₂ᵢ * R[nrcol + 2i-1] + s₂ᵢ * R[nrcol + 2i] + R[nrcol + 2i] = conj(s₂ᵢ) * R[nrcol + 2i-1] - c₂ᵢ * R[nrcol + 2i] + R[nrcol + 2i-1] = rtmp + + c₃ᵢ = gc[4i-1] + s₃ᵢ = gs[4i-1] + rtmp = c₃ᵢ * R[nrcol + 2i] + s₃ᵢ * αₖ + αₖ = conj(s₃ᵢ) * R[nrcol + 2i] - c₃ᵢ * αₖ + R[nrcol + 2i] = rtmp + + c₄ᵢ = gc[4i] + s₄ᵢ = gs[4i] + rtmp = c₄ᵢ * R[nrcol + 2i] + s₄ᵢ * R[nrcol + 2i+1] + R[nrcol + 2i+1] = conj(s₄ᵢ) * R[nrcol + 2i] - c₄ᵢ * R[nrcol + 2i+1] + R[nrcol + 2i] = rtmp + + flag ? ωₖ = αₖ : R[nrcol + 2i+2] = αₖ + end end - # fₖ₊₁.ₖ ≠ 0 - if Faux > btol - @. U[k+1] = p / Faux # fₖ₊₁.ₖuₖ₊₁ = p + # Compute and apply current givens reflections + # [ 1 ][ 1 ][ c₂.ₖ s₂.ₖ ][ c₁.ₖ s₁.ₖ ] [ r̄₂ₖ₋₁.₂ₖ₋₁ r̄₂ₖ₋₁.₂ₖ ] [ r₂ₖ₋₁.₂ₖ₋₁ r₂ₖ₋₁.₂ₖ ] + # [ c₄.ₖ s₄.ₖ ][ c₃.ₖ s₃.ₖ ][ s̄₂.ₖ -c₂.ₖ ][ 1 ] [ r̄₂ₖ.₂ₖ₋₁ r̄₂ₖ.₂ₖ ] = [ r₂ₖ.₂ₖ ] + # [ s̄₄.ₖ -c₄.ₖ ][ 1 ][ 1 ][ 1 ] [ hₖ₊₁.ₖ ] [ ] + # [ 1 ][ s̄₃.ₖ -c₃.ₖ ][ 1 ][ s̄₁.ₖ -c₁.ₖ ] [ fₖ₊₁.ₖ ] [ ] + (c₁ₖ, s₁ₖ, R[nr₂ₖ₋₁ + 2k-1]) = sym_givens(R[nr₂ₖ₋₁ + 2k-1], Faux) # annihilate fₖ₊₁.ₖ + θₖ = conj(s₁ₖ) * R[nr₂ₖ + 2k-1] + R[nr₂ₖ + 2k-1] = c₁ₖ * R[nr₂ₖ + 2k-1] + + (c₂ₖ, s₂ₖ, R[nr₂ₖ₋₁ + 2k-1]) = sym_givens(R[nr₂ₖ₋₁ + 2k-1], ωₖ) # annihilate ωₖ = r̄₂ₖ.₂ₖ₋₁ + rtmp = c₂ₖ * R[nr₂ₖ + 2k-1] + s₂ₖ * R[nr₂ₖ + 2k] + R[nr₂ₖ + 2k] = conj(s₂ₖ) * R[nr₂ₖ + 2k-1] - c₂ₖ * R[nr₂ₖ + 2k] + R[nr₂ₖ + 2k-1] = rtmp + + (c₃ₖ, s₃ₖ, R[nr₂ₖ + 2k]) = sym_givens(R[nr₂ₖ + 2k], θₖ) # annihilate Θₖ = r̄₂ₖ₊₂.₂ₖ + + (c₄ₖ, s₄ₖ, R[nr₂ₖ + 2k]) = sym_givens(R[nr₂ₖ + 2k], Haux) # annihilate hₖ₊₁.ₖ + + # Update t̄ₖ = (τ₁, ..., τ₂ₖ, τbar₂ₖ₊₁, τbar₂ₖ₊₂). + # + # [ 1 ][ 1 ][ c₂.ₖ s₂.ₖ ][ c₁.ₖ s₁.ₖ ] [ τbar₂ₖ₋₁ ] [ τ₂ₖ₋₁ ] + # [ c₄.ₖ s₄.ₖ ][ c₃.ₖ s₃.ₖ ][ s̄₂.ₖ -c₂.ₖ ][ 1 ] [ τbar₂ₖ ] = [ τ₂ₖ ] + # [ s̄₄.ₖ -c₄.ₖ ][ 1 ][ 1 ][ 1 ] [ ] [ τbar₂ₖ₊₁ ] + # [ 1 ][ s̄₃.ₖ -c₃.ₖ ][ 1 ][ s̄₁.ₖ -c₁.ₖ ] [ ] [ τbar₂ₖ₊₂ ] + τbar₂ₖ₊₂ = conj(s₁ₖ) * zt[2k-1] + zt[2k-1] = c₁ₖ * zt[2k-1] + + τtmp = c₂ₖ * zt[2k-1] + s₂ₖ * zt[2k] + zt[2k] = conj(s₂ₖ) * zt[2k-1] - c₂ₖ * zt[2k] + zt[2k-1] = τtmp + + τtmp = c₃ₖ * zt[2k] + s₃ₖ * τbar₂ₖ₊₂ + τbar₂ₖ₊₂ = conj(s₃ₖ) * zt[2k] - c₃ₖ * τbar₂ₖ₊₂ + zt[2k] = τtmp + + τbar₂ₖ₊₁ = conj(s₄ₖ) * zt[2k] + zt[2k] = c₄ₖ * zt[2k] + + # Update gc and gs vectors + gc[4k-3], gc[4k-2], gc[4k-1], gc[4k] = c₁ₖ, c₂ₖ, c₃ₖ, c₄ₖ + gs[4k-3], gs[4k-2], gs[4k-1], gs[4k] = s₁ₖ, s₂ₖ, s₃ₖ, s₄ₖ + + # Compute ‖rₖ‖² = |τbar₂ₖ₊₁|² + |τbar₂ₖ₊₂|² + rNorm = sqrt(abs2(τbar₂ₖ₊₁) + abs2(τbar₂ₖ₊₂)) + history && push!(rNorms, rNorm) + + # Update the number of coefficients in Rₖ. + nr = nr + 4k-1 + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + breakdown = Faux ≤ btol && Haux ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, Haux, Faux) + + # Compute vₖ₊₁ and uₖ₊₁ + if !(solved || tired || breakdown || user_requested_exit || overtimed) + if iter ≥ mem + push!(V, S(undef, m)) + push!(U, S(undef, n)) + push!(zt, zero(FC), zero(FC)) + end + + # hₖ₊₁.ₖ ≠ 0 + if Haux > btol + @. V[k+1] = q / Haux # hₖ₊₁.ₖvₖ₊₁ = q + else + # Breakdown -- hₖ₊₁.ₖ = ‖q‖₂ = 0 and Auₖ ∈ Span{v₁, ..., vₖ} + V[k+1] .= zero(FC) # vₖ₊₁ = 0 such that vₖ₊₁ ⊥ Span{v₁, ..., vₖ} + end + + # fₖ₊₁.ₖ ≠ 0 + if Faux > btol + @. U[k+1] = p / Faux # fₖ₊₁.ₖuₖ₊₁ = p + else + # Breakdown -- fₖ₊₁.ₖ = ‖p‖₂ = 0 and Bvₖ ∈ Span{u₁, ..., uₖ} + U[k+1] .= zero(FC) # uₖ₊₁ = 0 such that uₖ₊₁ ⊥ Span{u₁, ..., uₖ} + end + + zt[2k+1] = τbar₂ₖ₊₁ + zt[2k+2] = τbar₂ₖ₊₂ + end + end + (verbose > 0) && @printf(iostream, "\n") + + # Compute zₖ = (ζ₁, ..., ζ₂ₖ) by solving Rₖzₖ = tₖ with backward substitution. + for i = 2iter : -1 : 1 + pos = nr + i - 2iter # position of rᵢ.ₖ + for j = 2iter : -1 : i+1 + zt[i] = zt[i] - R[pos] * zt[j] # ζᵢ ← ζᵢ - rᵢ.ⱼζⱼ + pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + end + # Rₖ can be singular if the system is inconsistent + if abs(R[pos]) ≤ btol + zt[i] = zero(FC) + inconsistent = true else - # Breakdown -- fₖ₊₁.ₖ = ‖p‖₂ = 0 and Bvₖ ∈ Span{u₁, ..., uₖ} - U[k+1] .= zero(FC) # uₖ₊₁ = 0 such that uₖ₊₁ ⊥ Span{u₁, ..., uₖ} + zt[i] = zt[i] / R[pos] # ζᵢ ← ζᵢ / rᵢ.ᵢ end + end - zt[2k+1] = τbar₂ₖ₊₁ - zt[2k+2] = τbar₂ₖ₊₂ + # Compute xₖ and yₖ + for i = 1 : iter + @kaxpy!(m, zt[2i-1], V[i], x) # xₖ = ζ₁v₁ + ζ₃v₂ + ••• + ζ₂ₖ₋₁vₖ + @kaxpy!(n, zt[2i] , U[i], y) # xₖ = ζ₂u₁ + ζ₄u₂ + ••• + ζ₂ₖuₖ end - end - (verbose > 0) && @printf(iostream, "\n") - - # Compute zₖ = (ζ₁, ..., ζ₂ₖ) by solving Rₖzₖ = tₖ with backward substitution. - for i = 2iter : -1 : 1 - pos = nr + i - 2iter # position of rᵢ.ₖ - for j = 2iter : -1 : i+1 - zt[i] = zt[i] - R[pos] * zt[j] # ζᵢ ← ζᵢ - rᵢ.ⱼζⱼ - pos = pos - j + 1 # position of rᵢ.ⱼ₋₁ + if !EisI + wB .= x + mulorldiv!(x, E, wB, ldiv) end - # Rₖ can be singular if the system is inconsistent - if abs(R[pos]) ≤ btol - zt[i] = zero(FC) - inconsistent = true - else - zt[i] = zt[i] / R[pos] # ζᵢ ← ζᵢ / rᵢ.ᵢ + if !FisI + wA .= y + mulorldiv!(y, F, wA, ldiv) end + warm_start && @kaxpy!(m, one(FC), Δx, x) + warm_start && @kaxpy!(n, one(FC), Δy, y) + solver.warm_start = false + + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + inconsistent && (status = "found approximate least-squares solution") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - - # Compute xₖ and yₖ - for i = 1 : iter - @kaxpy!(m, zt[2i-1], V[i], x) # xₖ = ζ₁v₁ + ζ₃v₂ + ••• + ζ₂ₖ₋₁vₖ - @kaxpy!(n, zt[2i] , U[i], y) # xₖ = ζ₂u₁ + ζ₄u₂ + ••• + ζ₂ₖuₖ - end - if !EisI - wB .= x - mulorldiv!(x, E, wB, ldiv) - end - if !FisI - wA .= y - mulorldiv!(y, F, wA, ldiv) - end - warm_start && @kaxpy!(m, one(FC), Δx, x) - warm_start && @kaxpy!(n, one(FC), Δy, y) - solver.warm_start = false - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - inconsistent && (status = "found approximate least-squares solution") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end diff --git a/src/lnlq.jl b/src/lnlq.jl index 047ad82aa..f7eb3377c 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -157,400 +157,394 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly lnlq!(solver, A, b; $(kwargs_lnlq...)) return (solver.x, solver.y, solver.stats) end -end - -function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - transfer_to_craig :: Bool=true, - sqd :: Bool=false, λ :: T=zero(T), - σ :: T=zero(T), utolx :: T=√eps(T), - utoly :: T=√eps(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "LNLQ: system of %d equations in %d variables\n", m, n) - - # Check sqd and λ parameters - sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") - sqd && (λ = one(T)) - - # Tests M = Iₘ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :u, S, m) - allocate_if(!NisI, solver, :v, S, n) - allocate_if(λ > 0, solver, :q, S, n) - x, Nv, Aᴴu, y, w̄ = solver.x, solver.Nv, solver.Aᴴu, solver.y, solver.w̄ - Mu, Av, q, stats = solver.Mu, solver.Av, solver.q, solver.stats - rNorms, xNorms, yNorms = stats.residuals, stats.error_bnd_x, stats.error_bnd_y - reset!(stats) - u = MisI ? Mu : solver.u - v = NisI ? Nv : solver.v - - # Set up parameter σₑₛₜ for the error estimate on x and y - σₑₛₜ = √(σ^2 + λ^2) - complex_error_bnd = false - - # Initial solutions (x₀, y₀) and residual norm ‖r₀‖. - x .= zero(FC) - y .= zero(FC) - - bNorm = @knrm2(m, b) - if bNorm == 0 - stats.niter = 0 - stats.solved = true - stats.error_with_bnd = false - history && push!(rNorms, bNorm) - stats.status = "x = 0 is a zero-residual solution" - return solver - end - - history && push!(rNorms, bNorm) - ε = atol + rtol * bNorm - - iter = 0 - itmax == 0 && (itmax = m + n) - - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) - - # Update iteration index - iter = iter + 1 - # Initialize generalized Golub-Kahan bidiagonalization. - # β₁Mu₁ = b. - Mu .= b - MisI || mulorldiv!(u, M, Mu, ldiv) # u₁ = M⁻¹ * Mu₁ - βₖ = sqrt(@kdotr(m, u, Mu)) # β₁ = ‖u₁‖_M - if βₖ ≠ 0 - @kscal!(m, one(FC) / βₖ, u) - MisI || @kscal!(m, one(FC) / βₖ, Mu) - end - - # α₁Nv₁ = Aᴴu₁. - mul!(Aᴴu, Aᴴ, u) - Nv .= Aᴴu - NisI || mulorldiv!(v, N, Nv, ldiv) # v₁ = N⁻¹ * Nv₁ - αₖ = sqrt(@kdotr(n, v, Nv)) # α₁ = ‖v₁‖_N - if αₖ ≠ 0 - @kscal!(n, one(FC) / αₖ, v) - NisI || @kscal!(n, one(FC) / αₖ, Nv) - end + function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "LNLQ: system of %d equations in %d variables\n", m, n) + + # Check sqd and λ parameters + sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") + sqd && (λ = one(T)) + + # Tests M = Iₘ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :u, S, m) + allocate_if(!NisI, solver, :v, S, n) + allocate_if(λ > 0, solver, :q, S, n) + x, Nv, Aᴴu, y, w̄ = solver.x, solver.Nv, solver.Aᴴu, solver.y, solver.w̄ + Mu, Av, q, stats = solver.Mu, solver.Av, solver.q, solver.stats + rNorms, xNorms, yNorms = stats.residuals, stats.error_bnd_x, stats.error_bnd_y + reset!(stats) + u = MisI ? Mu : solver.u + v = NisI ? Nv : solver.v + + # Set up parameter σₑₛₜ for the error estimate on x and y + σₑₛₜ = √(σ^2 + λ^2) + complex_error_bnd = false + + # Initial solutions (x₀, y₀) and residual norm ‖r₀‖. + x .= zero(FC) + y .= zero(FC) + + bNorm = @knrm2(m, b) + if bNorm == 0 + stats.niter = 0 + stats.solved = true + stats.error_with_bnd = false + history && push!(rNorms, bNorm) + stats.status = "x = 0 is a zero-residual solution" + return solver + end - w̄ .= u # Direction w̄₁ - cₖ = zero(T) # Givens cosines used for the LQ factorization of (Lₖ)ᴴ - sₖ = zero(FC) # Givens sines used for the LQ factorization of (Lₖ)ᴴ - ζₖ₋₁ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ - ηₖ = zero(FC) # Coefficient of M̅ₖ - - # Variable used for the regularization. - λₖ = λ # λ₁ = λ - cpₖ = spₖ = one(T) # Givens sines and cosines used to zero out λₖ - cdₖ = sdₖ = one(FC) # Givens sines and cosines used to define λₖ₊₁ - λ > 0 && (q .= v) # Additional vector needed to update x, by definition q₀ = 0 - - # Initialize the regularization. - if λ > 0 - # k 2k k 2k k 2k - # k [ αₖ λₖ ] [ cpₖ spₖ ] = [ αhatₖ 0 ] - # k+1 [ βₖ₊₁ 0 ] [ spₖ -cpₖ ] [ βhatₖ₊₁ θₖ₊₁ ] - (cpₖ, spₖ, αhatₖ) = sym_givens(αₖ, λₖ) - - # q̄₁ = sp₁ * v₁ - @kscal!(n, spₖ, q) - else - αhatₖ = αₖ - end + history && push!(rNorms, bNorm) + ε = atol + rtol * bNorm - # Begin the LQ factorization of (Lₖ)ᴴ = M̅ₖQₖ. - # [ α₁ β₂ 0 • • • 0 ] [ ϵ₁ 0 • • • • 0 ] - # [ 0 α₂ • • • ] [ η₂ ϵ₂ • • ] - # [ • • • • • • ] [ 0 • • • • ] - # [ • • • • • • ] = [ • • • • • • ] Qₖ - # [ • • • • 0 ] [ • • • • • • ] - # [ • • • βₖ] [ • • • • 0 ] - # [ 0 • • • • 0 αₖ] [ 0 • • • 0 ηₖ ϵbarₖ] - - ϵbarₖ = αhatₖ # ϵbar₁ = αhat₁ - - # Hₖ = Bₖ(Lₖ)ᴴ = [ Lₖ(Lₖ)ᴴ ] ⟹ (Hₖ₋₁)ᴴ = [Lₖ₋₁Mₖ₋₁ 0] Qₖ - # [ αₖβₖ₊₁(eₖ)ᵀ ] - # - # Solve Lₖtₖ = β₁e₁ and M̅ₖz̅ₖ = tₖ - # tₖ = (τ₁, •••, τₖ) - # z̅ₖ = (zₖ₋₁, ζbarₖ) = (ζ₁, •••, ζₖ₋₁, ζbarₖ) - - τₖ = βₖ / αhatₖ # τ₁ = β₁ / αhat₁ - ζbarₖ = τₖ / ϵbarₖ # ζbar₁ = τ₁ / ϵbar₁ - - # Stopping criterion. - solved_lq = solved_cg = false - tired = false - status = "unknown" - user_requested_exit = false - overtimed = false - - if σₑₛₜ > 0 - τtildeₖ = βₖ / σₑₛₜ - ζtildeₖ = τtildeₖ / σₑₛₜ - err_x = τtildeₖ - err_y = ζtildeₖ - - solved_lq = err_x ≤ utolx || err_y ≤ utoly - history && push!(xNorms, err_x) - history && push!(yNorms, err_y) - - ρbar = -σₑₛₜ - csig = -one(T) - end + iter = 0 + itmax == 0 && (itmax = m + n) - while !(solved_lq || solved_cg || tired || user_requested_exit || overtimed) + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) - # Update of (xᵃᵘˣ)ₖ = Vₖtₖ - if λ > 0 - # (xᵃᵘˣ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * (cpₖvₖ + spₖqₖ₋₁) - @kaxpy!(n, τₖ * cpₖ, v, x) - if iter ≥ 2 - @kaxpy!(n, τₖ * spₖ, q, x) - # q̄ₖ ← spₖ * vₖ - cpₖ * qₖ₋₁ - @kaxpby!(n, spₖ, v, -cpₖ, q) - end - else - # (xᵃᵘˣ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * vₖ - @kaxpy!(n, τₖ, v, x) - end + # Update iteration index + iter = iter + 1 - # Continue the generalized Golub-Kahan bidiagonalization. - # AVₖ = MUₖ₊₁Bₖ - # AᴴUₖ₊₁ = NVₖ(Bₖ)ᴴ + αₖ₊₁Nvₖ₊₁(eₖ₊₁)ᴴ = NVₖ₊₁(Lₖ₊₁)ᴴ - # - # [ α₁ 0 • • • • 0 ] - # [ β₂ α₂ • • ] - # [ 0 • • • • ] - # Lₖ = [ • • • • • • ] - # [ • • • • • • ] - # [ • • • • 0 ] - # [ 0 • • • 0 βₖ αₖ] - # - # Bₖ = [ Lₖ ] - # [ βₖ₊₁(eₖ)ᵀ ] - - # βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ - mul!(Av, A, v) - @kaxpby!(m, one(FC), Av, -αₖ, Mu) - MisI || mulorldiv!(u, M, Mu, ldiv) # uₖ₊₁ = M⁻¹ * Muₖ₊₁ - βₖ₊₁ = sqrt(@kdotr(m, u, Mu)) # βₖ₊₁ = ‖uₖ₊₁‖_M - if βₖ₊₁ ≠ 0 - @kscal!(m, one(FC) / βₖ₊₁, u) - MisI || @kscal!(m, one(FC) / βₖ₊₁, Mu) + # Initialize generalized Golub-Kahan bidiagonalization. + # β₁Mu₁ = b. + Mu .= b + MisI || mulorldiv!(u, M, Mu, ldiv) # u₁ = M⁻¹ * Mu₁ + βₖ = sqrt(@kdotr(m, u, Mu)) # β₁ = ‖u₁‖_M + if βₖ ≠ 0 + @kscal!(m, one(FC) / βₖ, u) + MisI || @kscal!(m, one(FC) / βₖ, Mu) end - # αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + # α₁Nv₁ = Aᴴu₁. mul!(Aᴴu, Aᴴ, u) - @kaxpby!(n, one(FC), Aᴴu, -βₖ₊₁, Nv) - NisI || mulorldiv!(v, N, Nv, ldiv) # vₖ₊₁ = N⁻¹ * Nvₖ₊₁ - αₖ₊₁ = sqrt(@kdotr(n, v, Nv)) # αₖ₊₁ = ‖vₖ₊₁‖_N - if αₖ₊₁ ≠ 0 - @kscal!(n, one(FC) / αₖ₊₁, v) - NisI || @kscal!(n, one(FC) / αₖ₊₁, Nv) + Nv .= Aᴴu + NisI || mulorldiv!(v, N, Nv, ldiv) # v₁ = N⁻¹ * Nv₁ + αₖ = sqrt(@kdotr(n, v, Nv)) # α₁ = ‖v₁‖_N + if αₖ ≠ 0 + @kscal!(n, one(FC) / αₖ, v) + NisI || @kscal!(n, one(FC) / αₖ, Nv) end - # Continue the regularization. + w̄ .= u # Direction w̄₁ + cₖ = zero(T) # Givens cosines used for the LQ factorization of (Lₖ)ᴴ + sₖ = zero(FC) # Givens sines used for the LQ factorization of (Lₖ)ᴴ + ζₖ₋₁ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ + ηₖ = zero(FC) # Coefficient of M̅ₖ + + # Variable used for the regularization. + λₖ = λ # λ₁ = λ + cpₖ = spₖ = one(T) # Givens sines and cosines used to zero out λₖ + cdₖ = sdₖ = one(FC) # Givens sines and cosines used to define λₖ₊₁ + λ > 0 && (q .= v) # Additional vector needed to update x, by definition q₀ = 0 + + # Initialize the regularization. if λ > 0 # k 2k k 2k k 2k # k [ αₖ λₖ ] [ cpₖ spₖ ] = [ αhatₖ 0 ] # k+1 [ βₖ₊₁ 0 ] [ spₖ -cpₖ ] [ βhatₖ₊₁ θₖ₊₁ ] - βhatₖ₊₁ = cpₖ * βₖ₊₁ - θₖ₊₁ = spₖ * βₖ₊₁ - - # 2k 2k+1 2k 2k+1 2k 2k+1 - # k [ 0 0 ] [ -cdₖ sdₖ ] = [ 0 0 ] - # k+1 [ θₖ₊₁ λ ] [ sdₖ cdₖ ] [ 0 λₖ₊₁ ] - (cdₖ, sdₖ, λₖ₊₁) = sym_givens(λ, θₖ₊₁) - - # qₖ ← sdₖ * q̄ₖ - @kscal!(n, sdₖ, q) + (cpₖ, spₖ, αhatₖ) = sym_givens(αₖ, λₖ) - # k+1 2k+1 k+1 2k+1 k+1 2k+1 - # k+1 [ αₖ₊₁ λₖ₊₁ ] [ cpₖ₊₁ spₖ₊₁ ] = [ αhatₖ₊₁ 0 ] - # k+2 [ βₖ₊₂ 0 ] [ spₖ₊₁ -cpₖ₊₁ ] [ γₖ₊₂ θₖ₊₂ ] - (cpₖ₊₁, spₖ₊₁, αhatₖ₊₁) = sym_givens(αₖ₊₁, λₖ₊₁) + # q̄₁ = sp₁ * v₁ + @kscal!(n, spₖ, q) else - βhatₖ₊₁ = βₖ₊₁ - αhatₖ₊₁ = αₖ₊₁ + αhatₖ = αₖ end - if σₑₛₜ > 0 && !complex_error_bnd - μbar = -csig * αhatₖ - ρ = √(ρbar^2 + αhatₖ^2) - csig = ρbar / ρ - ssig = αhatₖ / ρ - ρbar = ssig * μbar + csig * σₑₛₜ - μbar = -csig * βhatₖ₊₁ - θ = βhatₖ₊₁ * csig / ρbar - ωdisc = σₑₛₜ^2 - σₑₛₜ * βhatₖ₊₁ * θ - if ωdisc < 0 - complex_error_bnd = true - else - ω = √ωdisc - τtildeₖ = - τₖ * βhatₖ₊₁ / ω - end + # Begin the LQ factorization of (Lₖ)ᴴ = M̅ₖQₖ. + # [ α₁ β₂ 0 • • • 0 ] [ ϵ₁ 0 • • • • 0 ] + # [ 0 α₂ • • • ] [ η₂ ϵ₂ • • ] + # [ • • • • • • ] [ 0 • • • • ] + # [ • • • • • • ] = [ • • • • • • ] Qₖ + # [ • • • • 0 ] [ • • • • • • ] + # [ • • • βₖ] [ • • • • 0 ] + # [ 0 • • • • 0 αₖ] [ 0 • • • 0 ηₖ ϵbarₖ] - ρ = √(ρbar^2 + βhatₖ₊₁^2) - csig = ρbar / ρ - ssig = βhatₖ₊₁ / ρ - ρbar = ssig * μbar + csig * σₑₛₜ - end + ϵbarₖ = αhatₖ # ϵbar₁ = αhat₁ - # Continue the LQ factorization of (Lₖ₊₁)ᴴ. - # [ηₖ ϵbarₖ βₖ₊₁] [1 0 0 ] = [ηₖ ϵₖ 0 ] - # [0 0 αₖ₊₁] [0 cₖ₊₁ sₖ₊₁] [0 ηₖ₊₁ ϵbarₖ₊₁] - # [0 sₖ₊₁ -cₖ₊₁] + # Hₖ = Bₖ(Lₖ)ᴴ = [ Lₖ(Lₖ)ᴴ ] ⟹ (Hₖ₋₁)ᴴ = [Lₖ₋₁Mₖ₋₁ 0] Qₖ + # [ αₖβₖ₊₁(eₖ)ᵀ ] + # + # Solve Lₖtₖ = β₁e₁ and M̅ₖz̅ₖ = tₖ + # tₖ = (τ₁, •••, τₖ) + # z̅ₖ = (zₖ₋₁, ζbarₖ) = (ζ₁, •••, ζₖ₋₁, ζbarₖ) - (cₖ₊₁, sₖ₊₁, ϵₖ) = sym_givens(ϵbarₖ, βhatₖ₊₁) - ηₖ₊₁ = αhatₖ₊₁ * sₖ₊₁ - ϵbarₖ₊₁ = - αhatₖ₊₁ * cₖ₊₁ + τₖ = βₖ / αhatₖ # τ₁ = β₁ / αhat₁ + ζbarₖ = τₖ / ϵbarₖ # ζbar₁ = τ₁ / ϵbar₁ - # Update solutions of Lₖ₊₁tₖ₊₁ = β₁e₁ and M̅ₖ₊₁z̅ₖ₊₁ = tₖ₊₁. - τₖ₊₁ = - βhatₖ₊₁ * τₖ / αhatₖ₊₁ - ζₖ = cₖ₊₁ * ζbarₖ - ζbarₖ₊₁ = (τₖ₊₁ - ηₖ₊₁ * ζₖ) / ϵbarₖ₊₁ + # Stopping criterion. + solved_lq = solved_cg = false + tired = false + status = "unknown" + user_requested_exit = false + overtimed = false - # Relations for the directions wₖ and w̄ₖ₊₁ - # [w̄ₖ uₖ₊₁] [cₖ₊₁ sₖ₊₁] = [wₖ w̄ₖ₊₁] → wₖ = cₖ₊₁ * w̄ₖ + sₖ₊₁ * uₖ₊₁ - # [sₖ₊₁ -cₖ₊₁] → w̄ₖ₊₁ = sₖ₊₁ * w̄ₖ - cₖ₊₁ * uₖ₊₁ + if σₑₛₜ > 0 + τtildeₖ = βₖ / σₑₛₜ + ζtildeₖ = τtildeₖ / σₑₛₜ + err_x = τtildeₖ + err_y = ζtildeₖ - # (yᴸ)ₖ₊₁ ← (yᴸ)ₖ + ζₖ * wₖ - @kaxpy!(m, ζₖ * cₖ₊₁, w̄, y) - @kaxpy!(m, ζₖ * sₖ₊₁, u, y) + solved_lq = err_x ≤ utolx || err_y ≤ utoly + history && push!(xNorms, err_x) + history && push!(yNorms, err_y) - # Compute w̄ₖ₊₁ - @kaxpby!(m, -cₖ₊₁, u, sₖ₊₁, w̄) + ρbar = -σₑₛₜ + csig = -one(T) + end - if σₑₛₜ > 0 && !complex_error_bnd - if transfer_to_craig - disc_x = τtildeₖ^2 - τₖ₊₁^2 - disc_x < 0 ? complex_error_bnd = true : err_x = √disc_x + while !(solved_lq || solved_cg || tired || user_requested_exit || overtimed) + + # Update of (xᵃᵘˣ)ₖ = Vₖtₖ + if λ > 0 + # (xᵃᵘˣ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * (cpₖvₖ + spₖqₖ₋₁) + @kaxpy!(n, τₖ * cpₖ, v, x) + if iter ≥ 2 + @kaxpy!(n, τₖ * spₖ, q, x) + # q̄ₖ ← spₖ * vₖ - cpₖ * qₖ₋₁ + @kaxpby!(n, spₖ, v, -cpₖ, q) + end else - disc_xL = τtildeₖ^2 - τₖ₊₁^2 + (τₖ₊₁ - ηₖ₊₁ * ζₖ)^2 - disc_xL < 0 ? complex_error_bnd = true : err_x = √disc_xL + # (xᵃᵘˣ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * vₖ + @kaxpy!(n, τₖ, v, x) end - ηtildeₖ = ω * sₖ₊₁ - ϵtildeₖ = -ω * cₖ₊₁ - ζtildeₖ = (τtildeₖ - ηtildeₖ * ζₖ) / ϵtildeₖ - - if transfer_to_craig - disc_y = ζtildeₖ^2 - ζbarₖ₊₁^2 - disc_y < 0 ? complex_error_bnd = true : err_y = √disc_y - else - err_y = abs(ζtildeₖ) + + # Continue the generalized Golub-Kahan bidiagonalization. + # AVₖ = MUₖ₊₁Bₖ + # AᴴUₖ₊₁ = NVₖ(Bₖ)ᴴ + αₖ₊₁Nvₖ₊₁(eₖ₊₁)ᴴ = NVₖ₊₁(Lₖ₊₁)ᴴ + # + # [ α₁ 0 • • • • 0 ] + # [ β₂ α₂ • • ] + # [ 0 • • • • ] + # Lₖ = [ • • • • • • ] + # [ • • • • • • ] + # [ • • • • 0 ] + # [ 0 • • • 0 βₖ αₖ] + # + # Bₖ = [ Lₖ ] + # [ βₖ₊₁(eₖ)ᵀ ] + + # βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ + mul!(Av, A, v) + @kaxpby!(m, one(FC), Av, -αₖ, Mu) + MisI || mulorldiv!(u, M, Mu, ldiv) # uₖ₊₁ = M⁻¹ * Muₖ₊₁ + βₖ₊₁ = sqrt(@kdotr(m, u, Mu)) # βₖ₊₁ = ‖uₖ₊₁‖_M + if βₖ₊₁ ≠ 0 + @kscal!(m, one(FC) / βₖ₊₁, u) + MisI || @kscal!(m, one(FC) / βₖ₊₁, Mu) end - history && push!(xNorms, err_x) - history && push!(yNorms, err_y) - end + # αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -βₖ₊₁, Nv) + NisI || mulorldiv!(v, N, Nv, ldiv) # vₖ₊₁ = N⁻¹ * Nvₖ₊₁ + αₖ₊₁ = sqrt(@kdotr(n, v, Nv)) # αₖ₊₁ = ‖vₖ₊₁‖_N + if αₖ₊₁ ≠ 0 + @kscal!(n, one(FC) / αₖ₊₁, v) + NisI || @kscal!(n, one(FC) / αₖ₊₁, Nv) + end - # Compute residual norm ‖(rᴸ)ₖ‖ = |αₖ| * √(|ϵbarₖζbarₖ|² + |βₖ₊₁sₖζₖ₋₁|²) - if iter == 1 - rNorm_lq = bNorm - else - rNorm_lq = abs(αhatₖ) * √(abs2(ϵbarₖ * ζbarₖ) + abs2(βhatₖ₊₁ * sₖ * ζₖ₋₁)) - end - history && push!(rNorms, rNorm_lq) + # Continue the regularization. + if λ > 0 + # k 2k k 2k k 2k + # k [ αₖ λₖ ] [ cpₖ spₖ ] = [ αhatₖ 0 ] + # k+1 [ βₖ₊₁ 0 ] [ spₖ -cpₖ ] [ βhatₖ₊₁ θₖ₊₁ ] + βhatₖ₊₁ = cpₖ * βₖ₊₁ + θₖ₊₁ = spₖ * βₖ₊₁ + + # 2k 2k+1 2k 2k+1 2k 2k+1 + # k [ 0 0 ] [ -cdₖ sdₖ ] = [ 0 0 ] + # k+1 [ θₖ₊₁ λ ] [ sdₖ cdₖ ] [ 0 λₖ₊₁ ] + (cdₖ, sdₖ, λₖ₊₁) = sym_givens(λ, θₖ₊₁) + + # qₖ ← sdₖ * q̄ₖ + @kscal!(n, sdₖ, q) + + # k+1 2k+1 k+1 2k+1 k+1 2k+1 + # k+1 [ αₖ₊₁ λₖ₊₁ ] [ cpₖ₊₁ spₖ₊₁ ] = [ αhatₖ₊₁ 0 ] + # k+2 [ βₖ₊₂ 0 ] [ spₖ₊₁ -cpₖ₊₁ ] [ γₖ₊₂ θₖ₊₂ ] + (cpₖ₊₁, spₖ₊₁, αhatₖ₊₁) = sym_givens(αₖ₊₁, λₖ₊₁) + else + βhatₖ₊₁ = βₖ₊₁ + αhatₖ₊₁ = αₖ₊₁ + end - # Compute residual norm ‖(rᶜ)ₖ‖ = |βₖ₊₁ * τₖ| - if transfer_to_craig - rNorm_cg = abs(βhatₖ₊₁ * τₖ) - end + if σₑₛₜ > 0 && !complex_error_bnd + μbar = -csig * αhatₖ + ρ = √(ρbar^2 + αhatₖ^2) + csig = ρbar / ρ + ssig = αhatₖ / ρ + ρbar = ssig * μbar + csig * σₑₛₜ + μbar = -csig * βhatₖ₊₁ + θ = βhatₖ₊₁ * csig / ρbar + ωdisc = σₑₛₜ^2 - σₑₛₜ * βhatₖ₊₁ * θ + if ωdisc < 0 + complex_error_bnd = true + else + ω = √ωdisc + τtildeₖ = - τₖ * βhatₖ₊₁ / ω + end + + ρ = √(ρbar^2 + βhatₖ₊₁^2) + csig = ρbar / ρ + ssig = βhatₖ₊₁ / ρ + ρbar = ssig * μbar + csig * σₑₛₜ + end - # Update sₖ, cₖ, αₖ, βₖ, ηₖ, ϵbarₖ, τₖ, ζₖ₋₁ and ζbarₖ. - cₖ = cₖ₊₁ - sₖ = sₖ₊₁ - αₖ = αₖ₊₁ - αhatₖ = αhatₖ₊₁ - βₖ = βₖ₊₁ - ηₖ = ηₖ₊₁ - ϵbarₖ = ϵbarₖ₊₁ - τₖ = τₖ₊₁ - ζₖ₋₁ = ζₖ - ζbarₖ = ζbarₖ₊₁ - - # Update regularization variables. - if λ > 0 - cpₖ = cpₖ₊₁ - spₖ = spₖ₊₁ - end + # Continue the LQ factorization of (Lₖ₊₁)ᴴ. + # [ηₖ ϵbarₖ βₖ₊₁] [1 0 0 ] = [ηₖ ϵₖ 0 ] + # [0 0 αₖ₊₁] [0 cₖ₊₁ sₖ₊₁] [0 ηₖ₊₁ ϵbarₖ₊₁] + # [0 sₖ₊₁ -cₖ₊₁] + + (cₖ₊₁, sₖ₊₁, ϵₖ) = sym_givens(ϵbarₖ, βhatₖ₊₁) + ηₖ₊₁ = αhatₖ₊₁ * sₖ₊₁ + ϵbarₖ₊₁ = - αhatₖ₊₁ * cₖ₊₁ + + # Update solutions of Lₖ₊₁tₖ₊₁ = β₁e₁ and M̅ₖ₊₁z̅ₖ₊₁ = tₖ₊₁. + τₖ₊₁ = - βhatₖ₊₁ * τₖ / αhatₖ₊₁ + ζₖ = cₖ₊₁ * ζbarₖ + ζbarₖ₊₁ = (τₖ₊₁ - ηₖ₊₁ * ζₖ) / ϵbarₖ₊₁ + + # Relations for the directions wₖ and w̄ₖ₊₁ + # [w̄ₖ uₖ₊₁] [cₖ₊₁ sₖ₊₁] = [wₖ w̄ₖ₊₁] → wₖ = cₖ₊₁ * w̄ₖ + sₖ₊₁ * uₖ₊₁ + # [sₖ₊₁ -cₖ₊₁] → w̄ₖ₊₁ = sₖ₊₁ * w̄ₖ - cₖ₊₁ * uₖ₊₁ + + # (yᴸ)ₖ₊₁ ← (yᴸ)ₖ + ζₖ * wₖ + @kaxpy!(m, ζₖ * cₖ₊₁, w̄, y) + @kaxpy!(m, ζₖ * sₖ₊₁, u, y) + + # Compute w̄ₖ₊₁ + @kaxpby!(m, -cₖ₊₁, u, sₖ₊₁, w̄) + + if σₑₛₜ > 0 && !complex_error_bnd + if transfer_to_craig + disc_x = τtildeₖ^2 - τₖ₊₁^2 + disc_x < 0 ? complex_error_bnd = true : err_x = √disc_x + else + disc_xL = τtildeₖ^2 - τₖ₊₁^2 + (τₖ₊₁ - ηₖ₊₁ * ζₖ)^2 + disc_xL < 0 ? complex_error_bnd = true : err_x = √disc_xL + end + ηtildeₖ = ω * sₖ₊₁ + ϵtildeₖ = -ω * cₖ₊₁ + ζtildeₖ = (τtildeₖ - ηtildeₖ * ζₖ) / ϵtildeₖ + + if transfer_to_craig + disc_y = ζtildeₖ^2 - ζbarₖ₊₁^2 + disc_y < 0 ? complex_error_bnd = true : err_y = √disc_y + else + err_y = abs(ζtildeₖ) + end + + history && push!(xNorms, err_x) + history && push!(yNorms, err_y) + end - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - tired = iter ≥ itmax - solved_lq = rNorm_lq ≤ ε - solved_cg = transfer_to_craig && rNorm_cg ≤ ε - if σₑₛₜ > 0 - solved_lq = solved_lq || err_x ≤ utolx || err_y ≤ utoly - solved_cg = transfer_to_craig && (solved_cg || err_x ≤ utolx || err_y ≤ utoly) - end - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) + # Compute residual norm ‖(rᴸ)ₖ‖ = |αₖ| * √(|ϵbarₖζbarₖ|² + |βₖ₊₁sₖζₖ₋₁|²) + if iter == 1 + rNorm_lq = bNorm + else + rNorm_lq = abs(αhatₖ) * √(abs2(ϵbarₖ * ζbarₖ) + abs2(βhatₖ₊₁ * sₖ * ζₖ₋₁)) + end + history && push!(rNorms, rNorm_lq) - # Update iteration index. - iter = iter + 1 - end - (verbose > 0) && @printf(iostream, "\n") + # Compute residual norm ‖(rᶜ)ₖ‖ = |βₖ₊₁ * τₖ| + if transfer_to_craig + rNorm_cg = abs(βhatₖ₊₁ * τₖ) + end - if solved_cg - if λ > 0 - # (xᶜ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * (cpₖvₖ + spₖqₖ₋₁) - @kaxpy!(n, τₖ * cpₖ, v, x) - if iter ≥ 2 - @kaxpy!(n, τₖ * spₖ, q, x) + # Update sₖ, cₖ, αₖ, βₖ, ηₖ, ϵbarₖ, τₖ, ζₖ₋₁ and ζbarₖ. + cₖ = cₖ₊₁ + sₖ = sₖ₊₁ + αₖ = αₖ₊₁ + αhatₖ = αhatₖ₊₁ + βₖ = βₖ₊₁ + ηₖ = ηₖ₊₁ + ϵbarₖ = ϵbarₖ₊₁ + τₖ = τₖ₊₁ + ζₖ₋₁ = ζₖ + ζbarₖ = ζbarₖ₊₁ + + # Update regularization variables. + if λ > 0 + cpₖ = cpₖ₊₁ + spₖ = spₖ₊₁ end - else - # (xᶜ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * vₖ - @kaxpy!(n, τₖ, v, x) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + tired = iter ≥ itmax + solved_lq = rNorm_lq ≤ ε + solved_cg = transfer_to_craig && rNorm_cg ≤ ε + if σₑₛₜ > 0 + solved_lq = solved_lq || err_x ≤ utolx || err_y ≤ utoly + solved_cg = transfer_to_craig && (solved_cg || err_x ≤ utolx || err_y ≤ utoly) + end + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) + + # Update iteration index. + iter = iter + 1 end - # (yᶜ)ₖ ← (yᴸ)ₖ₋₁ + ζbarₖ * w̄ₖ - @kaxpy!(m, ζbarₖ, w̄, y) - else - if λ > 0 - # (xᴸ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + ηₖζₖ₋₁ * (cpₖvₖ + spₖqₖ₋₁) - @kaxpy!(n, ηₖ * ζₖ₋₁ * cpₖ, v, x) - if iter ≥ 2 - @kaxpy!(n, ηₖ * ζₖ₋₁ * spₖ, q, x) + (verbose > 0) && @printf(iostream, "\n") + + if solved_cg + if λ > 0 + # (xᶜ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * (cpₖvₖ + spₖqₖ₋₁) + @kaxpy!(n, τₖ * cpₖ, v, x) + if iter ≥ 2 + @kaxpy!(n, τₖ * spₖ, q, x) + end + else + # (xᶜ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + τₖ * vₖ + @kaxpy!(n, τₖ, v, x) end + # (yᶜ)ₖ ← (yᴸ)ₖ₋₁ + ζbarₖ * w̄ₖ + @kaxpy!(m, ζbarₖ, w̄, y) else - # (xᴸ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + ηₖζₖ₋₁ * vₖ - @kaxpy!(n, ηₖ * ζₖ₋₁, v, x) + if λ > 0 + # (xᴸ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + ηₖζₖ₋₁ * (cpₖvₖ + spₖqₖ₋₁) + @kaxpy!(n, ηₖ * ζₖ₋₁ * cpₖ, v, x) + if iter ≥ 2 + @kaxpy!(n, ηₖ * ζₖ₋₁ * spₖ, q, x) + end + else + # (xᴸ)ₖ ← (xᵃᵘˣ)ₖ₋₁ + ηₖζₖ₋₁ * vₖ + @kaxpy!(n, ηₖ * ζₖ₋₁, v, x) + end end - end - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved_lq && (status = "solutions (xᴸ, yᴸ) good enough for the tolerances given") - solved_cg && (status = "solutions (xᶜ, yᶜ) good enough for the tolerances given") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved_lq || solved_cg - stats.error_with_bnd = complex_error_bnd - stats.status = status - return solver + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved_lq && (status = "solutions (xᴸ, yᴸ) good enough for the tolerances given") + solved_cg && (status = "solutions (xᶜ, yᶜ) good enough for the tolerances given") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved_lq || solved_cg + stats.error_with_bnd = complex_error_bnd + stats.status = status + return solver + end end diff --git a/src/lslq.jl b/src/lslq.jl index e596102f6..e5e7fef6d 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -183,329 +183,322 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : lslq!(solver, A, b; $(kwargs_lslq...)) return (solver.x, solver.stats) end -end -function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - transfer_to_lsqr :: Bool=false, - sqd :: Bool=false, λ :: T=zero(T), - σ :: T=zero(T), etol :: T=√eps(T), - utol :: T=√eps(T), btol :: T=√eps(T), - conlim :: T=1/√eps(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback=solver->false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "LSLQ: system of %d equations in %d variables\n", m, n) - - # Check sqd and λ parameters - sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") - sqd && (λ = one(T)) - - # Tests M = Iₙ and N = Iₘ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :u, S, m) - allocate_if(!NisI, solver, :v, S, n) - x, Nv, Aᴴu, w̄ = solver.x, solver.Nv, solver.Aᴴu, solver.w̄ - Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats - rNorms, ArNorms, err_lbnds = stats.residuals, stats.Aresiduals, stats.err_lbnds - err_ubnds_lq, err_ubnds_cg = stats.err_ubnds_lq, stats.err_ubnds_cg - reset!(stats) - u = MisI ? Mu : solver.u - v = NisI ? Nv : solver.v - - λ² = λ * λ - ctol = conlim > 0 ? 1/conlim : zero(T) - - x .= zero(FC) # LSLQ point - - # Initialize Golub-Kahan process. - # β₁ M u₁ = b. - Mu .= b - MisI || mulorldiv!(u, M, Mu, ldiv) - β₁ = sqrt(@kdotr(m, u, Mu)) - if β₁ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.error_with_bnd = false - history && push!(rNorms, zero(T)) - history && push!(ArNorms, zero(T)) - stats.status = "x = 0 is a zero-residual solution" - return solver - end - β = β₁ - - @kscal!(m, one(FC)/β₁, u) - MisI || @kscal!(m, one(FC)/β₁, Mu) - mul!(Aᴴu, Aᴴ, u) - Nv .= Aᴴu - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) # = α₁ - - # Aᴴb = 0 so x = 0 is a minimum least-squares solution - if α == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.error_with_bnd = false - history && push!(rNorms, β₁) - history && push!(ArNorms, zero(T)) - stats.status = "x = 0 is a minimum least-squares solution" - return solver - end - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - - Anorm = α - Anorm² = α * α - - # condition number estimate - σmax = zero(T) - σmin = Inf - Acond = zero(T) - - xlqNorm = zero(T) - xlqNorm² = zero(T) - xcgNorm = zero(T) - xcgNorm² = zero(T) - - w̄ .= v # w̄₁ = v₁ - - err_lbnd = zero(T) - window = length(err_vec) - err_vec .= zero(T) - complex_error_bnd = false - - # Initialize other constants. - αL = α - βL = β - ρ̄ = -σ - γ̄ = α - ψ = β₁ - c = -one(T) - s = zero(T) - δ = -one(T) - τ = α * β₁ - ζ = zero(T) - ζ̄ = zero(T) - ζ̃ = zero(T) - csig = -one(T) - - rNorm = β₁ - history && push!(rNorms, rNorm) - ArNorm = α * β - history && push!(ArNorms, ArNorm) - - iter = 0 - itmax == 0 && (itmax = m + n) - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) - - status = "unknown" - ε = atol + rtol * β₁ - solved = solved_mach = solved_lim = (rNorm ≤ ε) - tired = iter ≥ itmax - ill_cond = ill_cond_mach = ill_cond_lim = false - zero_resid = zero_resid_mach = zero_resid_lim = false - fwd_err_lbnd = false - fwd_err_ubnd = false - user_requested_exit = false - overtimed = false - - while ! (solved || tired || ill_cond || user_requested_exit || overtimed) - - # Generate next Golub-Kahan vectors. - # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ - mul!(Av, A, v) - @kaxpby!(m, one(FC), Av, -α, Mu) - MisI || mulorldiv!(u, M, Mu, ldiv) - β = sqrt(@kdotr(m, u, Mu)) - if β ≠ 0 - @kscal!(m, one(FC)/β, u) - MisI || @kscal!(m, one(FC)/β, Mu) - - # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᴴu, Aᴴ, u) - @kaxpby!(n, one(FC), Aᴴu, -β, Nv) - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - if α ≠ 0 - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - end + function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - # rotate out regularization term if present - αL = α - βL = β - if λ ≠ 0 - (cL, sL, βL) = sym_givens(β, λ) - αL = cL * α + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax - # the rotation updates the next regularization parameter - λ = sqrt(λ² + (sL * α)^2) - end - Anorm² = Anorm² + αL * αL + βL * βL # = ‖Lₖ‖² - Anorm = sqrt(Anorm²) - end + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "LSLQ: system of %d equations in %d variables\n", m, n) - # Continue QR factorization of Bₖ - # - # k k+1 k k+1 k k+1 - # k [ c' s' ] [ γ̄ ] = [ γ δ ] - # k+1 [ s' -c' ] [ β α⁺ ] [ γ̄ ] - (cp, sp, γ) = sym_givens(γ̄, βL) - τ = -τ * δ / γ # forward substitution for t - δ = sp * αL - γ̄ = -cp * αL - - if σ > 0 && !complex_error_bnd - # Continue QR factorization for error estimate - μ̄ = -csig * γ - (csig, ssig, ρ) = sym_givens(ρ̄, γ) - ρ̄ = ssig * μ̄ + csig * σ - μ̄ = -csig * δ - - # determine component of eigenvector and Gauss-Radau parameter - h = δ * csig / ρ̄ - disc = σ * (σ - δ * h) - disc < 0 ? complex_error_bnd = true : ω = sqrt(disc) - (csig, ssig, ρ) = sym_givens(ρ̄, δ) - ρ̄ = ssig * μ̄ + csig * σ + # Check sqd and λ parameters + sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") + sqd && (λ = one(T)) + + # Tests M = Iₙ and N = Iₘ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :u, S, m) + allocate_if(!NisI, solver, :v, S, n) + x, Nv, Aᴴu, w̄ = solver.x, solver.Nv, solver.Aᴴu, solver.w̄ + Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats + rNorms, ArNorms, err_lbnds = stats.residuals, stats.Aresiduals, stats.err_lbnds + err_ubnds_lq, err_ubnds_cg = stats.err_ubnds_lq, stats.err_ubnds_cg + reset!(stats) + u = MisI ? Mu : solver.u + v = NisI ? Nv : solver.v + + λ² = λ * λ + ctol = conlim > 0 ? 1/conlim : zero(T) + + x .= zero(FC) # LSLQ point + + # Initialize Golub-Kahan process. + # β₁ M u₁ = b. + Mu .= b + MisI || mulorldiv!(u, M, Mu, ldiv) + β₁ = sqrt(@kdotr(m, u, Mu)) + if β₁ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.error_with_bnd = false + history && push!(rNorms, zero(T)) + history && push!(ArNorms, zero(T)) + stats.status = "x = 0 is a zero-residual solution" + return solver + end + β = β₁ + + @kscal!(m, one(FC)/β₁, u) + MisI || @kscal!(m, one(FC)/β₁, Mu) + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) # = α₁ + + # Aᴴb = 0 so x = 0 is a minimum least-squares solution + if α == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.error_with_bnd = false + history && push!(rNorms, β₁) + history && push!(ArNorms, zero(T)) + stats.status = "x = 0 is a minimum least-squares solution" + return solver end + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) - # Continue LQ factorization of Rₖ - ϵ̄ = -γ * c - η = γ * s - (c, s, ϵ) = sym_givens(ϵ̄, δ) + Anorm = α + Anorm² = α * α # condition number estimate - # the QLP factorization suggests that the diagonal of M̄ approximates - # the singular values of B. - σmax = max(σmax, ϵ, abs(ϵ̄)) - σmin = min(σmin, ϵ, abs(ϵ̄)) - Acond = σmax / σmin - - # forward substitution for z, ζ̄ - ζold = ζ - ζ = (τ - ζ * η) / ϵ - ζ̄ = ζ / c - - # residual norm estimate - rNorm = sqrt((ψ * cp - ζold * η)^2 + (ψ * sp)^2) + σmax = zero(T) + σmin = Inf + Acond = zero(T) + + xlqNorm = zero(T) + xlqNorm² = zero(T) + xcgNorm = zero(T) + xcgNorm² = zero(T) + + w̄ .= v # w̄₁ = v₁ + + err_lbnd = zero(T) + window = length(err_vec) + err_vec .= zero(T) + complex_error_bnd = false + + # Initialize other constants. + αL = α + βL = β + ρ̄ = -σ + γ̄ = α + ψ = β₁ + c = -one(T) + s = zero(T) + δ = -one(T) + τ = α * β₁ + ζ = zero(T) + ζ̄ = zero(T) + ζ̃ = zero(T) + csig = -one(T) + + rNorm = β₁ history && push!(rNorms, rNorm) - - ArNorm = sqrt((γ * ϵ * ζ)^2 + (δ * η * ζold)^2) + ArNorm = α * β history && push!(ArNorms, ArNorm) - # Compute ψₖ - ψ = ψ * sp + iter = 0 + itmax == 0 && (itmax = m + n) - # Compute ‖x_cg‖₂ - xcgNorm² = xlqNorm² + ζ̄ * ζ̄ + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) - if σ > 0 && iter > 0 && !complex_error_bnd - disc = ζ̃ * ζ̃ - ζ̄ * ζ̄ - if disc < 0 - complex_error_bnd = true - else - err_ubnd_cg = sqrt(disc) - history && push!(err_ubnds_cg, err_ubnd_cg) - fwd_err_ubnd = err_ubnd_cg ≤ utol * sqrt(xcgNorm²) + status = "unknown" + ε = atol + rtol * β₁ + solved = solved_mach = solved_lim = (rNorm ≤ ε) + tired = iter ≥ itmax + ill_cond = ill_cond_mach = ill_cond_lim = false + zero_resid = zero_resid_mach = zero_resid_lim = false + fwd_err_lbnd = false + fwd_err_ubnd = false + user_requested_exit = false + overtimed = false + + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) + + # Generate next Golub-Kahan vectors. + # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ + mul!(Av, A, v) + @kaxpby!(m, one(FC), Av, -α, Mu) + MisI || mulorldiv!(u, M, Mu, ldiv) + β = sqrt(@kdotr(m, u, Mu)) + if β ≠ 0 + @kscal!(m, one(FC)/β, u) + MisI || @kscal!(m, one(FC)/β, Mu) + + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + if α ≠ 0 + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) + end + + # rotate out regularization term if present + αL = α + βL = β + if λ ≠ 0 + (cL, sL, βL) = sym_givens(β, λ) + αL = cL * α + + # the rotation updates the next regularization parameter + λ = sqrt(λ² + (sL * α)^2) + end + Anorm² = Anorm² + αL * αL + βL * βL # = ‖Lₖ‖² + Anorm = sqrt(Anorm²) end - end - test1 = rNorm - test2 = ArNorm / (Anorm * rNorm) - test3 = 1 / Acond - t1 = test1 / (one(T) + Anorm * xlqNorm) - tol = btol + atol * Anorm * xlqNorm / β₁ + # Continue QR factorization of Bₖ + # + # k k+1 k k+1 k k+1 + # k [ c' s' ] [ γ̄ ] = [ γ δ ] + # k+1 [ s' -c' ] [ β α⁺ ] [ γ̄ ] + (cp, sp, γ) = sym_givens(γ̄, βL) + τ = -τ * δ / γ # forward substitution for t + δ = sp * αL + γ̄ = -cp * αL + + if σ > 0 && !complex_error_bnd + # Continue QR factorization for error estimate + μ̄ = -csig * γ + (csig, ssig, ρ) = sym_givens(ρ̄, γ) + ρ̄ = ssig * μ̄ + csig * σ + μ̄ = -csig * δ + + # determine component of eigenvector and Gauss-Radau parameter + h = δ * csig / ρ̄ + disc = σ * (σ - δ * h) + disc < 0 ? complex_error_bnd = true : ω = sqrt(disc) + (csig, ssig, ρ) = sym_givens(ρ̄, δ) + ρ̄ = ssig * μ̄ + csig * σ + end - # update LSLQ point for next iteration - @kaxpy!(n, c * ζ, w̄, x) - @kaxpy!(n, s * ζ, v, x) + # Continue LQ factorization of Rₖ + ϵ̄ = -γ * c + η = γ * s + (c, s, ϵ) = sym_givens(ϵ̄, δ) + + # condition number estimate + # the QLP factorization suggests that the diagonal of M̄ approximates + # the singular values of B. + σmax = max(σmax, ϵ, abs(ϵ̄)) + σmin = min(σmin, ϵ, abs(ϵ̄)) + Acond = σmax / σmin + + # forward substitution for z, ζ̄ + ζold = ζ + ζ = (τ - ζ * η) / ϵ + ζ̄ = ζ / c + + # residual norm estimate + rNorm = sqrt((ψ * cp - ζold * η)^2 + (ψ * sp)^2) + history && push!(rNorms, rNorm) + + ArNorm = sqrt((γ * ϵ * ζ)^2 + (δ * η * ζold)^2) + history && push!(ArNorms, ArNorm) + + # Compute ψₖ + ψ = ψ * sp + + # Compute ‖x_cg‖₂ + xcgNorm² = xlqNorm² + ζ̄ * ζ̄ + + if σ > 0 && iter > 0 && !complex_error_bnd + disc = ζ̃ * ζ̃ - ζ̄ * ζ̄ + if disc < 0 + complex_error_bnd = true + else + err_ubnd_cg = sqrt(disc) + history && push!(err_ubnds_cg, err_ubnd_cg) + fwd_err_ubnd = err_ubnd_cg ≤ utol * sqrt(xcgNorm²) + end + end - # compute w̄ - @kaxpby!(n, -c, v, s, w̄) + test1 = rNorm + test2 = ArNorm / (Anorm * rNorm) + test3 = 1 / Acond + t1 = test1 / (one(T) + Anorm * xlqNorm) + tol = btol + atol * Anorm * xlqNorm / β₁ - xlqNorm² += ζ * ζ - xlqNorm = sqrt(xlqNorm²) + # update LSLQ point for next iteration + @kaxpy!(n, c * ζ, w̄, x) + @kaxpy!(n, s * ζ, v, x) - # check stopping condition based on forward error lower bound - err_vec[mod(iter, window) + 1] = ζ - if iter ≥ window - err_lbnd = @knrm2(window, err_vec) - history && push!(err_lbnds, err_lbnd) - fwd_err_lbnd = err_lbnd ≤ etol * xlqNorm - end + # compute w̄ + @kaxpby!(n, -c, v, s, w̄) - # compute LQ forward error upper bound - if σ > 0 && !complex_error_bnd - η̃ = ω * s - ϵ̃ = -ω * c - τ̃ = -τ * δ / ω - ζ̃ = (τ̃ - ζ * η̃) / ϵ̃ - history && push!(err_ubnds_lq, abs(ζ̃ )) - end + xlqNorm² += ζ * ζ + xlqNorm = sqrt(xlqNorm²) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - ill_cond_mach = (one(T) + test3 ≤ one(T)) - solved_mach = (one(T) + test2 ≤ one(T)) - zero_resid_mach = (one(T) + t1 ≤ one(T)) + # check stopping condition based on forward error lower bound + err_vec[mod(iter, window) + 1] = ζ + if iter ≥ window + err_lbnd = @knrm2(window, err_vec) + history && push!(err_lbnds, err_lbnd) + fwd_err_lbnd = err_lbnd ≤ etol * xlqNorm + end - # Stopping conditions based on user-provided tolerances. - user_requested_exit = callback(solver) :: Bool - tired = iter ≥ itmax - ill_cond_lim = (test3 ≤ ctol) - solved_lim = (test2 ≤ atol) - zero_resid_lim = (test1 ≤ ε) - - ill_cond = ill_cond_mach || ill_cond_lim - zero_resid = zero_resid_mach || zero_resid_lim - solved = solved_mach || solved_lim || zero_resid || fwd_err_lbnd || fwd_err_ubnd - timer = time_ns() - start_time - overtimed = timer > timemax_ns - - iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm) - end - (verbose > 0) && @printf(iostream, "\n") + # compute LQ forward error upper bound + if σ > 0 && !complex_error_bnd + η̃ = ω * s + ϵ̃ = -ω * c + τ̃ = -τ * δ / ω + ζ̃ = (τ̃ - ζ * η̃) / ϵ̃ + history && push!(err_ubnds_lq, abs(ζ̃ )) + end - if transfer_to_lsqr # compute LSQR point - @kaxpy!(n, ζ̄ , w̄, x) - end + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + ill_cond_mach = (one(T) + test3 ≤ one(T)) + solved_mach = (one(T) + test2 ≤ one(T)) + zero_resid_mach = (one(T) + t1 ≤ one(T)) + + # Stopping conditions based on user-provided tolerances. + user_requested_exit = callback(solver) :: Bool + tired = iter ≥ itmax + ill_cond_lim = (test3 ≤ ctol) + solved_lim = (test2 ≤ atol) + zero_resid_lim = (test1 ≤ ε) + + ill_cond = ill_cond_mach || ill_cond_lim + zero_resid = zero_resid_mach || zero_resid_lim + solved = solved_mach || solved_lim || zero_resid || fwd_err_lbnd || fwd_err_ubnd + timer = time_ns() - start_time + overtimed = timer > timemax_ns + + iter = iter + 1 + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm) + end + (verbose > 0) && @printf(iostream, "\n") - # Termination status - tired && (status = "maximum number of iterations exceeded") - ill_cond_mach && (status = "condition number seems too large for this machine") - ill_cond_lim && (status = "condition number exceeds tolerance") - solved && (status = "found approximate minimum least-squares solution") - zero_resid && (status = "found approximate zero-residual solution") - fwd_err_lbnd && (status = "forward error lower bound small enough") - fwd_err_ubnd && (status = "forward error upper bound small enough") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = !zero_resid - stats.error_with_bnd = complex_error_bnd - stats.status = status - return solver + if transfer_to_lsqr # compute LSQR point + @kaxpy!(n, ζ̄ , w̄, x) + end + + # Termination status + tired && (status = "maximum number of iterations exceeded") + ill_cond_mach && (status = "condition number seems too large for this machine") + ill_cond_lim && (status = "condition number exceeds tolerance") + solved && (status = "found approximate minimum least-squares solution") + zero_resid && (status = "found approximate zero-residual solution") + fwd_err_lbnd && (status = "forward error lower bound small enough") + fwd_err_ubnd && (status = "forward error upper bound small enough") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = !zero_resid + stats.error_with_bnd = complex_error_bnd + stats.status = status + return solver + end end diff --git a/src/lsmr.jl b/src/lsmr.jl index fc612a8f9..3d133b95d 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -160,286 +160,280 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, lsmr!(solver, A, b; $(kwargs_lsmr...)) return (solver.x, solver.stats) end -end -function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - sqd :: Bool=false, λ :: T=zero(T), - radius :: T=zero(T), etol :: T=√eps(T), - axtol :: T=√eps(T), btol :: T=√eps(T), - conlim :: T=1/√eps(T), atol :: T=zero(T), - rtol :: T=zero(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "LSMR: system of %d equations in %d variables\n", m, n) - - # Check sqd and λ parameters - sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") - sqd && (λ = one(T)) - - # Tests M = Iₙ and N = Iₘ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :u, S, m) - allocate_if(!NisI, solver, :v, S, n) - x, Nv, Aᴴu, h, hbar = solver.x, solver.Nv, solver.Aᴴu, solver.h, solver.hbar - Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - u = MisI ? Mu : solver.u - v = NisI ? Nv : solver.v - - ctol = conlim > 0 ? 1/conlim : zero(T) - x .= zero(FC) - - # Initialize Golub-Kahan process. - # β₁ M u₁ = b. - Mu .= b - MisI || mulorldiv!(u, M, Mu, ldiv) - β₁ = sqrt(@kdotr(m, u, Mu)) - if β₁ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(rNorms, zero(T)) - history && push!(ArNorms, zero(T)) - return solver - end - β = β₁ - - @kscal!(m, one(FC)/β₁, u) - MisI || @kscal!(m, one(FC)/β₁, Mu) - mul!(Aᴴu, Aᴴ, u) - Nv .= Aᴴu - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - - ζbar = α * β - αbar = α - ρ = one(T) - ρbar = one(T) - cbar = one(T) - sbar = zero(T) - - # Initialize variables for estimation of ‖r‖. - βdd = β - βd = zero(T) - ρdold = one(T) - τtildeold = zero(T) - θtilde = zero(T) - ζ = zero(T) - d = zero(T) - - # Initialize variables for estimation of ‖A‖, cond(A) and xNorm. - Anorm² = α * α - maxrbar = zero(T) - minrbar = min(floatmax(T), T(1.0e+100)) - Acond = maxrbar / minrbar - Anorm = sqrt(Anorm²) - xNorm = zero(T) - - # Items for use in stopping rules. - ctol = conlim > 0 ? 1 / conlim : zero(T) - rNorm = β - history && push!(rNorms, rNorm) - ArNorm = ArNorm0 = α * β - history && push!(ArNorms, ArNorm) - - xENorm² = zero(T) - err_lbnd = zero(T) - window = length(err_vec) - err_vec .= zero(T) - - iter = 0 - itmax == 0 && (itmax = m + n) - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm²) - - # Aᴴb = 0 so x = 0 is a minimum least-squares solution - if α == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a minimum least-squares solution" - return solver - end - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - - h .= v - hbar .= zero(FC) - - status = "unknown" - on_boundary = false - solved = solved_mach = solved_lim = (rNorm ≤ axtol) - tired = iter ≥ itmax - ill_cond = ill_cond_mach = ill_cond_lim = false - zero_resid = zero_resid_mach = zero_resid_lim = false - fwd_err = false - user_requested_exit = false - overtimed = false - - while ! (solved || tired || ill_cond || user_requested_exit || overtimed) - iter = iter + 1 - - # Generate next Golub-Kahan vectors. - # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ - mul!(Av, A, v) - @kaxpby!(m, one(FC), Av, -α, Mu) - MisI || mulorldiv!(u, M, Mu, ldiv) - β = sqrt(@kdotr(m, u, Mu)) - if β ≠ 0 - @kscal!(m, one(FC)/β, u) - MisI || @kscal!(m, one(FC)/β, Mu) - - # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᴴu, Aᴴ, u) - @kaxpby!(n, one(FC), Aᴴu, -β, Nv) - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - if α ≠ 0 - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - end - end + function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - # Continue QR factorization - (chat, shat, αhat) = sym_givens(αbar, λ) - - ρold = ρ - (c, s, ρ) = sym_givens(αhat, β) - θnew = s * α - αbar = c * α - - ρbarold = ρbar - ζold = ζ - θbar = sbar * ρ - ρtemp = cbar * ρ - (cbar, sbar, ρbar) = sym_givens(ρtemp, θnew) - ζ = cbar * ζbar - ζbar = -sbar * ζbar - - xENorm² = xENorm² + ζ * ζ - err_vec[mod(iter, window) + 1] = ζ - iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) - - # Update h, hbar and x. - δ = θbar * ρ / (ρold * ρbarold) # δₖ = θbarₖ * ρₖ / (ρₖ₋₁ * ρbarₖ₋₁) - @kaxpby!(n, one(FC), h, -δ, hbar) # ĥₖ = hₖ - δₖ * ĥₖ₋₁ - - # if a trust-region constraint is given, compute step to the boundary - # the step ϕ/ρ is not necessarily positive - σ = ζ / (ρ * ρbar) - if radius > 0 - t1, t2 = to_boundary(n, x, hbar, radius) - tmax, tmin = max(t1, t2), min(t1, t2) - on_boundary = σ > tmax || σ < tmin - σ = σ > 0 ? min(σ, tmax) : max(σ, tmin) - end + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax - @kaxpy!(n, σ, hbar, x) # xₖ = xₖ₋₁ + σₖ * ĥₖ - @kaxpby!(n, one(FC), v, -θnew / ρ, h) # hₖ₊₁ = vₖ₊₁ - (θₖ₊₁/ρₖ) * hₖ + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "LSMR: system of %d equations in %d variables\n", m, n) - # Estimate ‖r‖. - βacute = chat * βdd - βcheck = -shat * βdd + # Check sqd and λ parameters + sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") + sqd && (λ = one(T)) - βhat = c * βacute - βdd = -s * βacute + # Tests M = Iₙ and N = Iₘ + MisI = (M === I) + NisI = (N === I) - θtildeold = θtilde - (ctildeold, stildeold, ρtildeold) = sym_givens(ρdold, θbar) - θtilde = stildeold * ρbar - ρdold = ctildeold * ρbar - βd = -stildeold * βd + ctildeold * βhat + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - τtildeold = (ζold - θtildeold * τtildeold) / ρtildeold - τd = (ζ - θtilde * τtildeold) / ρdold - d = d + βcheck * βcheck - rNorm = sqrt(d + (βd - τd)^2 + βdd * βdd) - history && push!(rNorms, rNorm) + # Compute the adjoint of A + Aᴴ = A' - # Estimate ‖A‖. - Anorm² += β * β - Anorm = sqrt(Anorm²) - Anorm² += α * α + # Set up workspace. + allocate_if(!MisI, solver, :u, S, m) + allocate_if(!NisI, solver, :v, S, n) + x, Nv, Aᴴu, h, hbar = solver.x, solver.Nv, solver.Aᴴu, solver.h, solver.hbar + Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + u = MisI ? Mu : solver.u + v = NisI ? Nv : solver.v - # Estimate cond(A). - maxrbar = max(maxrbar, ρbarold) - iter > 1 && (minrbar = min(minrbar, ρbarold)) - Acond = max(maxrbar, ρtemp) / min(minrbar, ρtemp) + ctol = conlim > 0 ? 1/conlim : zero(T) + x .= zero(FC) - # Test for convergence. - ArNorm = abs(ζbar) + # Initialize Golub-Kahan process. + # β₁ M u₁ = b. + Mu .= b + MisI || mulorldiv!(u, M, Mu, ldiv) + β₁ = sqrt(@kdotr(m, u, Mu)) + if β₁ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(rNorms, zero(T)) + history && push!(ArNorms, zero(T)) + return solver + end + β = β₁ + + @kscal!(m, one(FC)/β₁, u) + MisI || @kscal!(m, one(FC)/β₁, Mu) + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + + ζbar = α * β + αbar = α + ρ = one(T) + ρbar = one(T) + cbar = one(T) + sbar = zero(T) + + # Initialize variables for estimation of ‖r‖. + βdd = β + βd = zero(T) + ρdold = one(T) + τtildeold = zero(T) + θtilde = zero(T) + ζ = zero(T) + d = zero(T) + + # Initialize variables for estimation of ‖A‖, cond(A) and xNorm. + Anorm² = α * α + maxrbar = zero(T) + minrbar = min(floatmax(T), T(1.0e+100)) + Acond = maxrbar / minrbar + Anorm = sqrt(Anorm²) + xNorm = zero(T) + + # Items for use in stopping rules. + ctol = conlim > 0 ? 1 / conlim : zero(T) + rNorm = β + history && push!(rNorms, rNorm) + ArNorm = ArNorm0 = α * β history && push!(ArNorms, ArNorm) - xNorm = @knrm2(n, x) - test1 = rNorm / β₁ - test2 = ArNorm / (Anorm * rNorm) - test3 = 1 / Acond - t1 = test1 / (one(T) + Anorm * xNorm / β₁) - rNormtol = btol + axtol * Anorm * xNorm / β₁ + xENorm² = zero(T) + err_lbnd = zero(T) + window = length(err_vec) + err_vec .= zero(T) + + iter = 0 + itmax == 0 && (itmax = m + n) - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm²) - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - ill_cond_mach = (one(T) + test3 ≤ one(T)) - solved_mach = (one(T) + test2 ≤ one(T)) - zero_resid_mach = (one(T) + t1 ≤ one(T)) + # Aᴴb = 0 so x = 0 is a minimum least-squares solution + if α == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a minimum least-squares solution" + return solver + end + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) + + h .= v + hbar .= zero(FC) - # Stopping conditions based on user-provided tolerances. - user_requested_exit = callback(solver) :: Bool + status = "unknown" + on_boundary = false + solved = solved_mach = solved_lim = (rNorm ≤ axtol) tired = iter ≥ itmax - ill_cond_lim = (test3 ≤ ctol) - solved_lim = (test2 ≤ axtol) - solved_opt = ArNorm ≤ atol + rtol * ArNorm0 - zero_resid_lim = (test1 ≤ rNormtol) - iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) - - ill_cond = ill_cond_mach || ill_cond_lim - zero_resid = zero_resid_mach || zero_resid_lim - solved = solved_mach || solved_lim || solved_opt || zero_resid || fwd_err || on_boundary - timer = time_ns() - start_time - overtimed = timer > timemax_ns + ill_cond = ill_cond_mach = ill_cond_lim = false + zero_resid = zero_resid_mach = zero_resid_lim = false + fwd_err = false + user_requested_exit = false + overtimed = false + + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) + iter = iter + 1 + + # Generate next Golub-Kahan vectors. + # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ + mul!(Av, A, v) + @kaxpby!(m, one(FC), Av, -α, Mu) + MisI || mulorldiv!(u, M, Mu, ldiv) + β = sqrt(@kdotr(m, u, Mu)) + if β ≠ 0 + @kscal!(m, one(FC)/β, u) + MisI || @kscal!(m, one(FC)/β, Mu) + + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + if α ≠ 0 + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) + end + end + + # Continue QR factorization + (chat, shat, αhat) = sym_givens(αbar, λ) + + ρold = ρ + (c, s, ρ) = sym_givens(αhat, β) + θnew = s * α + αbar = c * α + + ρbarold = ρbar + ζold = ζ + θbar = sbar * ρ + ρtemp = cbar * ρ + (cbar, sbar, ρbar) = sym_givens(ρtemp, θnew) + ζ = cbar * ζbar + ζbar = -sbar * ζbar + + xENorm² = xENorm² + ζ * ζ + err_vec[mod(iter, window) + 1] = ζ + iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) + + # Update h, hbar and x. + δ = θbar * ρ / (ρold * ρbarold) # δₖ = θbarₖ * ρₖ / (ρₖ₋₁ * ρbarₖ₋₁) + @kaxpby!(n, one(FC), h, -δ, hbar) # ĥₖ = hₖ - δₖ * ĥₖ₋₁ + + # if a trust-region constraint is given, compute step to the boundary + # the step ϕ/ρ is not necessarily positive + σ = ζ / (ρ * ρbar) + if radius > 0 + t1, t2 = to_boundary(n, x, hbar, radius) + tmax, tmin = max(t1, t2), min(t1, t2) + on_boundary = σ > tmax || σ < tmin + σ = σ > 0 ? min(σ, tmax) : max(σ, tmin) + end + + @kaxpy!(n, σ, hbar, x) # xₖ = xₖ₋₁ + σₖ * ĥₖ + @kaxpby!(n, one(FC), v, -θnew / ρ, h) # hₖ₊₁ = vₖ₊₁ - (θₖ₊₁/ρₖ) * hₖ + + # Estimate ‖r‖. + βacute = chat * βdd + βcheck = -shat * βdd + + βhat = c * βacute + βdd = -s * βacute + + θtildeold = θtilde + (ctildeold, stildeold, ρtildeold) = sym_givens(ρdold, θbar) + θtilde = stildeold * ρbar + ρdold = ctildeold * ρbar + βd = -stildeold * βd + ctildeold * βhat + + τtildeold = (ζold - θtildeold * τtildeold) / ρtildeold + τd = (ζ - θtilde * τtildeold) / ρdold + d = d + βcheck * βcheck + rNorm = sqrt(d + (βd - τd)^2 + βdd * βdd) + history && push!(rNorms, rNorm) + + # Estimate ‖A‖. + Anorm² += β * β + Anorm = sqrt(Anorm²) + Anorm² += α * α + + # Estimate cond(A). + maxrbar = max(maxrbar, ρbarold) + iter > 1 && (minrbar = min(minrbar, ρbarold)) + Acond = max(maxrbar, ρtemp) / min(minrbar, ρtemp) + + # Test for convergence. + ArNorm = abs(ζbar) + history && push!(ArNorms, ArNorm) + xNorm = @knrm2(n, x) + + test1 = rNorm / β₁ + test2 = ArNorm / (Anorm * rNorm) + test3 = 1 / Acond + t1 = test1 / (one(T) + Anorm * xNorm / β₁) + rNormtol = btol + axtol * Anorm * xNorm / β₁ + + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + ill_cond_mach = (one(T) + test3 ≤ one(T)) + solved_mach = (one(T) + test2 ≤ one(T)) + zero_resid_mach = (one(T) + t1 ≤ one(T)) + + # Stopping conditions based on user-provided tolerances. + user_requested_exit = callback(solver) :: Bool + tired = iter ≥ itmax + ill_cond_lim = (test3 ≤ ctol) + solved_lim = (test2 ≤ axtol) + solved_opt = ArNorm ≤ atol + rtol * ArNorm0 + zero_resid_lim = (test1 ≤ rNormtol) + iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) + + ill_cond = ill_cond_mach || ill_cond_lim + zero_resid = zero_resid_mach || zero_resid_lim + solved = solved_mach || solved_lim || solved_opt || zero_resid || fwd_err || on_boundary + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + ill_cond_mach && (status = "condition number seems too large for this machine") + ill_cond_lim && (status = "condition number exceeds tolerance") + solved && (status = "found approximate minimum least-squares solution") + zero_resid && (status = "found approximate zero-residual solution") + fwd_err && (status = "truncated forward error small enough") + on_boundary && (status = "on trust-region boundary") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.residual = rNorm + stats.Aresidual = ArNorm + stats.Acond = Acond + stats.Anorm = Anorm + stats.xNorm = xNorm + stats.niter = iter + stats.solved = solved + stats.inconsistent = !zero_resid + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - ill_cond_mach && (status = "condition number seems too large for this machine") - ill_cond_lim && (status = "condition number exceeds tolerance") - solved && (status = "found approximate minimum least-squares solution") - zero_resid && (status = "found approximate zero-residual solution") - fwd_err && (status = "truncated forward error small enough") - on_boundary && (status = "on trust-region boundary") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.residual = rNorm - stats.Aresidual = ArNorm - stats.Acond = Acond - stats.Anorm = Anorm - stats.xNorm = xNorm - stats.niter = iter - stats.solved = solved - stats.inconsistent = !zero_resid - stats.status = status - return solver end diff --git a/src/lsqr.jl b/src/lsqr.jl index 7a9294449..c8cf8357a 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -156,275 +156,269 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, lsqr!(solver, A, b; $(kwargs_lsqr...)) return (solver.x, solver.stats) end -end -function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - sqd :: Bool=false, λ :: T=zero(T), - radius :: T=zero(T), etol :: T=√eps(T), - axtol :: T=√eps(T), btol :: T=√eps(T), - conlim :: T=1/√eps(T), atol :: T=zero(T), - rtol :: T=zero(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "LSQR: system of %d equations in %d variables\n", m, n) - - # Check sqd and λ parameters - sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") - sqd && (λ = one(T)) - - # Tests M = Iₙ and N = Iₘ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :u, S, m) - allocate_if(!NisI, solver, :v, S, n) - x, Nv, Aᴴu, w = solver.x, solver.Nv, solver.Aᴴu, solver.w - Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats - rNorms, ArNorms = stats.residuals, stats.Aresiduals - reset!(stats) - u = MisI ? Mu : solver.u - v = NisI ? Nv : solver.v - - λ² = λ * λ - ctol = conlim > 0 ? 1/conlim : zero(T) - x .= zero(FC) - - # Initialize Golub-Kahan process. - # β₁ M u₁ = b. - Mu .= b - MisI || mulorldiv!(u, M, Mu, ldiv) - β₁ = sqrt(@kdotr(m, u, Mu)) - if β₁ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(rNorms, zero(T)) - history && push!(ArNorms, zero(T)) - return solver - end - β = β₁ - - @kscal!(m, one(FC)/β₁, u) - MisI || @kscal!(m, one(FC)/β₁, Mu) - mul!(Aᴴu, Aᴴ, u) - Nv .= Aᴴu - NisI || mulorldiv!(v, N, Nv, ldiv) - Anorm² = @kdotr(n, v, Nv) - Anorm = sqrt(Anorm²) - α = Anorm - Acond = zero(T) - xNorm = zero(T) - xNorm² = zero(T) - dNorm² = zero(T) - c2 = -one(T) - s2 = zero(T) - z = zero(T) - - xENorm² = zero(T) - err_lbnd = zero(T) - window = length(err_vec) - err_vec .= zero(T) - - iter = 0 - itmax == 0 && (itmax = m + n) - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond) - - rNorm = β₁ - r1Norm = rNorm - r2Norm = rNorm - res2 = zero(T) - history && push!(rNorms, r2Norm) - ArNorm = ArNorm0 = α * β - history && push!(ArNorms, ArNorm) - # Aᴴb = 0 so x = 0 is a minimum least-squares solution - if α == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a minimum least-squares solution" - return solver - end - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - w .= v - - # Initialize other constants. - ϕbar = β₁ - ρbar = α - - status = "unknown" - on_boundary = false - solved_lim = ArNorm / (Anorm * rNorm) ≤ axtol - solved_mach = one(T) + ArNorm / (Anorm * rNorm) ≤ one(T) - solved = solved_mach | solved_lim - tired = iter ≥ itmax - ill_cond = ill_cond_mach = ill_cond_lim = false - zero_resid_lim = rNorm / β₁ ≤ axtol - zero_resid_mach = one(T) + rNorm / β₁ ≤ one(T) - zero_resid = zero_resid_mach | zero_resid_lim - fwd_err = false - user_requested_exit = false - overtimed = false - - while ! (solved || tired || ill_cond || user_requested_exit || overtimed) - iter = iter + 1 - - # Generate next Golub-Kahan vectors. - # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ - mul!(Av, A, v) - @kaxpby!(m, one(FC), Av, -α, Mu) + function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "LSQR: system of %d equations in %d variables\n", m, n) + + # Check sqd and λ parameters + sqd && (λ ≠ 0) && error("sqd cannot be set to true if λ ≠ 0 !") + sqd && (λ = one(T)) + + # Tests M = Iₙ and N = Iₘ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :u, S, m) + allocate_if(!NisI, solver, :v, S, n) + x, Nv, Aᴴu, w = solver.x, solver.Nv, solver.Aᴴu, solver.w + Mu, Av, err_vec, stats = solver.Mu, solver.Av, solver.err_vec, solver.stats + rNorms, ArNorms = stats.residuals, stats.Aresiduals + reset!(stats) + u = MisI ? Mu : solver.u + v = NisI ? Nv : solver.v + + λ² = λ * λ + ctol = conlim > 0 ? 1/conlim : zero(T) + x .= zero(FC) + + # Initialize Golub-Kahan process. + # β₁ M u₁ = b. + Mu .= b MisI || mulorldiv!(u, M, Mu, ldiv) - β = sqrt(@kdotr(m, u, Mu)) - if β ≠ 0 - @kscal!(m, one(FC)/β, u) - MisI || @kscal!(m, one(FC)/β, Mu) - Anorm² = Anorm² + α * α + β * β # = ‖B_{k-1}‖² - λ > 0 && (Anorm² += λ²) - - # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ - mul!(Aᴴu, Aᴴ, u) - @kaxpby!(n, one(FC), Aᴴu, -β, Nv) - NisI || mulorldiv!(v, N, Nv, ldiv) - α = sqrt(@kdotr(n, v, Nv)) - if α ≠ 0 - @kscal!(n, one(FC)/α, v) - NisI || @kscal!(n, one(FC)/α, Nv) - end + β₁ = sqrt(@kdotr(m, u, Mu)) + if β₁ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(rNorms, zero(T)) + history && push!(ArNorms, zero(T)) + return solver end - - # Continue QR factorization - # 1. Eliminate the regularization parameter. - (c1, s1, ρbar1) = sym_givens(ρbar, λ) - ψ = s1 * ϕbar - ϕbar = c1 * ϕbar - - # 2. Eliminate β. - # Q [ Lₖ β₁ e₁ ] = [ Rₖ zₖ ] : - # [ β 0 ] [ 0 ζbar ] - # - # k k+1 k k+1 k k+1 - # k [ c s ] [ ρbar ] = [ ρ θ⁺ ] - # k+1 [ s -c ] [ β α⁺ ] [ ρbar⁺ ] - # - # so that we obtain - # - # [ c s ] [ ζbar ] = [ ζ ] - # [ s -c ] [ 0 ] [ ζbar⁺ ] - (c, s, ρ) = sym_givens(ρbar1, β) - ϕ = c * ϕbar - ϕbar = s * ϕbar - - xENorm² = xENorm² + ϕ * ϕ - err_vec[mod(iter, window) + 1] = ϕ - iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) - - τ = s * ϕ - θ = s * α - ρbar = -c * α - dNorm² += @kdotr(n, w, w) / ρ^2 - - # if a trust-region constraint is give, compute step to the boundary - # the step ϕ/ρ is not necessarily positive - σ = ϕ / ρ - if radius > 0 - t1, t2 = to_boundary(n, x, w, radius) - tmax, tmin = max(t1, t2), min(t1, t2) - on_boundary = σ > tmax || σ < tmin - σ = σ > 0 ? min(σ, tmax) : max(σ, tmin) - end - - @kaxpy!(n, σ, w, x) # x = x + ϕ / ρ * w - @kaxpby!(n, one(FC), v, -θ/ρ, w) # w = v - θ / ρ * w - - # Use a plane rotation on the right to eliminate the super-diagonal - # element (θ) of the upper-bidiagonal matrix. - # Use the result to estimate norm(x). - δ = s2 * ρ - γbar = -c2 * ρ - rhs = ϕ - δ * z - zbar = rhs / γbar - xNorm = sqrt(xNorm² + zbar * zbar) - (c2, s2, γ) = sym_givens(γbar, θ) - z = rhs / γ - xNorm² += z * z - + β = β₁ + + @kscal!(m, one(FC)/β₁, u) + MisI || @kscal!(m, one(FC)/β₁, Mu) + mul!(Aᴴu, Aᴴ, u) + Nv .= Aᴴu + NisI || mulorldiv!(v, N, Nv, ldiv) + Anorm² = @kdotr(n, v, Nv) Anorm = sqrt(Anorm²) - Acond = Anorm * sqrt(dNorm²) - res1 = ϕbar * ϕbar - res2 += ψ * ψ - rNorm = sqrt(res1 + res2) - - ArNorm = α * abs(τ) - history && push!(ArNorms, ArNorm) - - r1sq = rNorm * rNorm - λ² * xNorm² - r1Norm = sqrt(abs(r1sq)) - r1sq < 0 && (r1Norm = -r1Norm) + α = Anorm + Acond = zero(T) + xNorm = zero(T) + xNorm² = zero(T) + dNorm² = zero(T) + c2 = -one(T) + s2 = zero(T) + z = zero(T) + + xENorm² = zero(T) + err_lbnd = zero(T) + window = length(err_vec) + err_vec .= zero(T) + + iter = 0 + itmax == 0 && (itmax = m + n) + + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond) + + rNorm = β₁ + r1Norm = rNorm r2Norm = rNorm + res2 = zero(T) history && push!(rNorms, r2Norm) + ArNorm = ArNorm0 = α * β + history && push!(ArNorms, ArNorm) + # Aᴴb = 0 so x = 0 is a minimum least-squares solution + if α == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a minimum least-squares solution" + return solver + end + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) + w .= v + + # Initialize other constants. + ϕbar = β₁ + ρbar = α + + status = "unknown" + on_boundary = false + solved_lim = ArNorm / (Anorm * rNorm) ≤ axtol + solved_mach = one(T) + ArNorm / (Anorm * rNorm) ≤ one(T) + solved = solved_mach | solved_lim + tired = iter ≥ itmax + ill_cond = ill_cond_mach = ill_cond_lim = false + zero_resid_lim = rNorm / β₁ ≤ axtol + zero_resid_mach = one(T) + rNorm / β₁ ≤ one(T) + zero_resid = zero_resid_mach | zero_resid_lim + fwd_err = false + user_requested_exit = false + overtimed = false + + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) + iter = iter + 1 + + # Generate next Golub-Kahan vectors. + # 1. βₖ₊₁Muₖ₊₁ = Avₖ - αₖMuₖ + mul!(Av, A, v) + @kaxpby!(m, one(FC), Av, -α, Mu) + MisI || mulorldiv!(u, M, Mu, ldiv) + β = sqrt(@kdotr(m, u, Mu)) + if β ≠ 0 + @kscal!(m, one(FC)/β, u) + MisI || @kscal!(m, one(FC)/β, Mu) + Anorm² = Anorm² + α * α + β * β # = ‖B_{k-1}‖² + λ > 0 && (Anorm² += λ²) + + # 2. αₖ₊₁Nvₖ₊₁ = Aᴴuₖ₊₁ - βₖ₊₁Nvₖ + mul!(Aᴴu, Aᴴ, u) + @kaxpby!(n, one(FC), Aᴴu, -β, Nv) + NisI || mulorldiv!(v, N, Nv, ldiv) + α = sqrt(@kdotr(n, v, Nv)) + if α ≠ 0 + @kscal!(n, one(FC)/α, v) + NisI || @kscal!(n, one(FC)/α, Nv) + end + end - test1 = rNorm / β₁ - test2 = ArNorm / (Anorm * rNorm) - test3 = 1 / Acond - t1 = test1 / (one(T) + Anorm * xNorm / β₁) - rNormtol = btol + axtol * Anorm * xNorm / β₁ - - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, α, β, rNorm, ArNorm, test1, test2, Anorm, Acond) - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - ill_cond_mach = (one(T) + test3 ≤ one(T)) - solved_mach = (one(T) + test2 ≤ one(T)) - zero_resid_mach = (one(T) + t1 ≤ one(T)) + # Continue QR factorization + # 1. Eliminate the regularization parameter. + (c1, s1, ρbar1) = sym_givens(ρbar, λ) + ψ = s1 * ϕbar + ϕbar = c1 * ϕbar + + # 2. Eliminate β. + # Q [ Lₖ β₁ e₁ ] = [ Rₖ zₖ ] : + # [ β 0 ] [ 0 ζbar ] + # + # k k+1 k k+1 k k+1 + # k [ c s ] [ ρbar ] = [ ρ θ⁺ ] + # k+1 [ s -c ] [ β α⁺ ] [ ρbar⁺ ] + # + # so that we obtain + # + # [ c s ] [ ζbar ] = [ ζ ] + # [ s -c ] [ 0 ] [ ζbar⁺ ] + (c, s, ρ) = sym_givens(ρbar1, β) + ϕ = c * ϕbar + ϕbar = s * ϕbar + + xENorm² = xENorm² + ϕ * ϕ + err_vec[mod(iter, window) + 1] = ϕ + iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) + + τ = s * ϕ + θ = s * α + ρbar = -c * α + dNorm² += @kdotr(n, w, w) / ρ^2 + + # if a trust-region constraint is give, compute step to the boundary + # the step ϕ/ρ is not necessarily positive + σ = ϕ / ρ + if radius > 0 + t1, t2 = to_boundary(n, x, w, radius) + tmax, tmin = max(t1, t2), min(t1, t2) + on_boundary = σ > tmax || σ < tmin + σ = σ > 0 ? min(σ, tmax) : max(σ, tmin) + end - # Stopping conditions based on user-provided tolerances. - user_requested_exit = callback(solver) :: Bool - tired = iter ≥ itmax - ill_cond_lim = (test3 ≤ ctol) - solved_lim = (test2 ≤ axtol) - solved_opt = ArNorm ≤ atol + rtol * ArNorm0 - zero_resid_lim = (test1 ≤ rNormtol) - iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) - - ill_cond = ill_cond_mach || ill_cond_lim - zero_resid = zero_resid_mach || zero_resid_lim - solved = solved_mach || solved_lim || solved_opt || zero_resid || fwd_err || on_boundary - timer = time_ns() - start_time - overtimed = timer > timemax_ns + @kaxpy!(n, σ, w, x) # x = x + ϕ / ρ * w + @kaxpby!(n, one(FC), v, -θ/ρ, w) # w = v - θ / ρ * w + + # Use a plane rotation on the right to eliminate the super-diagonal + # element (θ) of the upper-bidiagonal matrix. + # Use the result to estimate norm(x). + δ = s2 * ρ + γbar = -c2 * ρ + rhs = ϕ - δ * z + zbar = rhs / γbar + xNorm = sqrt(xNorm² + zbar * zbar) + (c2, s2, γ) = sym_givens(γbar, θ) + z = rhs / γ + xNorm² += z * z + + Anorm = sqrt(Anorm²) + Acond = Anorm * sqrt(dNorm²) + res1 = ϕbar * ϕbar + res2 += ψ * ψ + rNorm = sqrt(res1 + res2) + + ArNorm = α * abs(τ) + history && push!(ArNorms, ArNorm) + + r1sq = rNorm * rNorm - λ² * xNorm² + r1Norm = sqrt(abs(r1sq)) + r1sq < 0 && (r1Norm = -r1Norm) + r2Norm = rNorm + history && push!(rNorms, r2Norm) + + test1 = rNorm / β₁ + test2 = ArNorm / (Anorm * rNorm) + test3 = 1 / Acond + t1 = test1 / (one(T) + Anorm * xNorm / β₁) + rNormtol = btol + axtol * Anorm * xNorm / β₁ + + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, α, β, rNorm, ArNorm, test1, test2, Anorm, Acond) + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + ill_cond_mach = (one(T) + test3 ≤ one(T)) + solved_mach = (one(T) + test2 ≤ one(T)) + zero_resid_mach = (one(T) + t1 ≤ one(T)) + + # Stopping conditions based on user-provided tolerances. + user_requested_exit = callback(solver) :: Bool + tired = iter ≥ itmax + ill_cond_lim = (test3 ≤ ctol) + solved_lim = (test2 ≤ axtol) + solved_opt = ArNorm ≤ atol + rtol * ArNorm0 + zero_resid_lim = (test1 ≤ rNormtol) + iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) + + ill_cond = ill_cond_mach || ill_cond_lim + zero_resid = zero_resid_mach || zero_resid_lim + solved = solved_mach || solved_lim || solved_opt || zero_resid || fwd_err || on_boundary + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + ill_cond_mach && (status = "condition number seems too large for this machine") + ill_cond_lim && (status = "condition number exceeds tolerance") + solved && (status = "found approximate minimum least-squares solution") + zero_resid && (status = "found approximate zero-residual solution") + fwd_err && (status = "truncated forward error small enough") + on_boundary && (status = "on trust-region boundary") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = !zero_resid + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - ill_cond_mach && (status = "condition number seems too large for this machine") - ill_cond_lim && (status = "condition number exceeds tolerance") - solved && (status = "found approximate minimum least-squares solution") - zero_resid && (status = "found approximate zero-residual solution") - fwd_err && (status = "truncated forward error small enough") - on_boundary && (status = "on trust-region boundary") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = !zero_resid - stats.status = status - return solver end diff --git a/src/minres.jl b/src/minres.jl index 7a49571c2..92553f987 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -137,262 +137,258 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, minres!(solver, A, b; $(kwargs_minres...)) return solver end -end - -function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, - λ :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), etol :: T=√eps(T), - conlim :: T=1/√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "MINRES: system of size %d\n", n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :v, S, n) - Δx, x, r1, r2, w1, w2, y = solver.Δx, solver.x, solver.r1, solver.r2, solver.w1, solver.w2, solver.y - err_vec, stats = solver.err_vec, solver.stats - warm_start = solver.warm_start - rNorms, ArNorms, Aconds = stats.residuals, stats.Aresiduals, stats.Acond - reset!(stats) - v = MisI ? r2 : solver.v - - ϵM = eps(T) - ctol = conlim > 0 ? 1 / conlim : zero(T) - - # Initial solution x₀ - x .= zero(FC) - - if warm_start - mul!(r1, A, Δx) - (λ ≠ 0) && @kaxpy!(n, λ, Δx, r1) - @kaxpby!(n, one(FC), b, -one(FC), r1) - else - r1 .= b - end - - # Initialize Lanczos process. - # β₁ M v₁ = b. - r2 .= r1 - MisI || mulorldiv!(v, M, r1, ldiv) - β₁ = @kdotr(m, r1, v) - β₁ < 0 && error("Preconditioner is not positive definite") - if β₁ == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - history && push!(rNorms, β₁) - history && push!(ArNorms, zero(T)) - history && push!(Aconds, zero(T)) - solver.warm_start = false - return solver - end - β₁ = sqrt(β₁) - β = β₁ - - oldβ = zero(T) - δbar = zero(T) - ϵ = zero(T) - rNorm = β₁ - history && push!(rNorms, β₁) - ϕbar = β₁ - rhs1 = β₁ - rhs2 = zero(T) - γmax = zero(T) - γmin = T(Inf) - cs = -one(T) - sn = zero(T) - w1 .= zero(FC) - w2 .= zero(FC) - - ANorm² = zero(T) - ANorm = zero(T) - Acond = zero(T) - history && push!(Aconds, Acond) - ArNorm = zero(T) - history && push!(ArNorms, ArNorm) - xNorm = zero(T) - - xENorm² = zero(T) - err_lbnd = zero(T) - window = length(err_vec) - err_vec .= zero(T) - - iter = 0 - itmax == 0 && (itmax = 2*n) - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) - - ε = atol + rtol * β₁ - stats.status = "unknown" - solved = solved_mach = solved_lim = (rNorm ≤ rtol) - tired = iter ≥ itmax - ill_cond = ill_cond_mach = ill_cond_lim = false - zero_resid = zero_resid_mach = zero_resid_lim = (rNorm ≤ ε) - fwd_err = false - user_requested_exit = false - overtimed = false - - while !(solved || tired || ill_cond || user_requested_exit || overtimed) - iter = iter + 1 - - # Generate next Lanczos vector. - mul!(y, A, v) - λ ≠ 0 && @kaxpy!(n, λ, v, y) # (y = y + λ * v) - @kscal!(n, one(FC) / β, y) - iter ≥ 2 && @kaxpy!(n, -β / oldβ, r1, y) # (y = y - β / oldβ * r1) - - α = real((@kdot(n, v, y) / β)) - @kaxpy!(n, -α / β, r2, y) # y = y - α / β * r2 - - # Compute w. - δ = cs * δbar + sn * α - if iter == 1 - w = w2 - else - iter ≥ 3 && @kscal!(n, -ϵ, w1) - w = w1 - @kaxpy!(n, -δ, w2, w) - end - @kaxpy!(n, one(FC) / β, v, w) - - @. r1 = r2 - @. r2 = y - MisI || mulorldiv!(v, M, r2, ldiv) - oldβ = β - β = @kdotr(n, r2, v) - β < 0 && error("Preconditioner is not positive definite") - β = sqrt(β) - ANorm² = ANorm² + α * α + oldβ * oldβ + β * β - - # Apply rotation to obtain - # [ δₖ ϵₖ₊₁ ] = [ cs sn ] [ δbarₖ 0 ] - # [ γbar δbarₖ₊₁ ] [ sn -cs ] [ αₖ βₖ₊₁ ] - γbar = sn * δbar - cs * α - ϵ = sn * β - δbar = -cs * β - root = sqrt(γbar * γbar + δbar * δbar) - ArNorm = ϕbar * root # = ‖Aᴴrₖ₋₁‖ - history && push!(ArNorms, ArNorm) - - # Compute the next plane rotation. - γ = sqrt(γbar * γbar + β * β) - γ = max(γ, ϵM) - cs = γbar / γ - sn = β / γ - ϕ = cs * ϕbar - ϕbar = sn * ϕbar - # Final update of w. - @kscal!(n, one(FC) / γ, w) + function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - # Update x. - @kaxpy!(n, ϕ, w, x) # x = x + ϕ * w - xENorm² = xENorm² + ϕ * ϕ + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax - # Update directions for x. - if iter ≥ 2 - @kswap(w1, w2) - end - - # Compute lower bound on forward error. - err_vec[mod(iter, window) + 1] = ϕ - iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "MINRES: system of size %d\n", n) - γmax = max(γmax, γ) - γmin = min(γmin, γ) - ζ = rhs1 / γ - rhs1 = rhs2 - δ * ζ - rhs2 = -ϵ * ζ + # Tests M = Iₙ + MisI = (M === I) - # Estimate various norms. - ANorm = sqrt(ANorm²) - xNorm = @knrm2(n, x) - ϵA = ANorm * ϵM - ϵx = ANorm * xNorm * ϵM - ϵr = ANorm * xNorm * rtol - d = γbar - d == 0 && (d = ϵA) + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - rNorm = ϕbar + # Set up workspace. + allocate_if(!MisI, solver, :v, S, n) + Δx, x, r1, r2, w1, w2, y = solver.Δx, solver.x, solver.r1, solver.r2, solver.w1, solver.w2, solver.y + err_vec, stats = solver.err_vec, solver.stats + warm_start = solver.warm_start + rNorms, ArNorms, Aconds = stats.residuals, stats.Aresiduals, stats.Acond + reset!(stats) + v = MisI ? r2 : solver.v - test1 = rNorm / (ANorm * xNorm) - test2 = root / ANorm - history && push!(rNorms, rNorm) + ϵM = eps(T) + ctol = conlim > 0 ? 1 / conlim : zero(T) - Acond = γmax / γmin - history && push!(Aconds, Acond) + # Initial solution x₀ + x .= zero(FC) - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2) + if warm_start + mul!(r1, A, Δx) + (λ ≠ 0) && @kaxpy!(n, λ, Δx, r1) + @kaxpby!(n, one(FC), b, -one(FC), r1) + else + r1 .= b + end - if iter == 1 && β / β₁ ≤ 10 * ϵM - # Aᴴb = 0 so x = 0 is a minimum least-squares solution - stats.niter = 1 - stats.solved, stats.inconsistent = true, true - stats.status = "x is a minimum least-squares solution" + # Initialize Lanczos process. + # β₁ M v₁ = b. + r2 .= r1 + MisI || mulorldiv!(v, M, r1, ldiv) + β₁ = @kdotr(m, r1, v) + β₁ < 0 && error("Preconditioner is not positive definite") + if β₁ == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + history && push!(rNorms, β₁) + history && push!(ArNorms, zero(T)) + history && push!(Aconds, zero(T)) solver.warm_start = false return solver end + β₁ = sqrt(β₁) + β = β₁ + + oldβ = zero(T) + δbar = zero(T) + ϵ = zero(T) + rNorm = β₁ + history && push!(rNorms, β₁) + ϕbar = β₁ + rhs1 = β₁ + rhs2 = zero(T) + γmax = zero(T) + γmin = T(Inf) + cs = -one(T) + sn = zero(T) + w1 .= zero(FC) + w2 .= zero(FC) + + ANorm² = zero(T) + ANorm = zero(T) + Acond = zero(T) + history && push!(Aconds, Acond) + ArNorm = zero(T) + history && push!(ArNorms, ArNorm) + xNorm = zero(T) + + xENorm² = zero(T) + err_lbnd = zero(T) + window = length(err_vec) + err_vec .= zero(T) + + iter = 0 + itmax == 0 && (itmax = 2*n) + + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) + + ε = atol + rtol * β₁ + stats.status = "unknown" + solved = solved_mach = solved_lim = (rNorm ≤ rtol) + tired = iter ≥ itmax + ill_cond = ill_cond_mach = ill_cond_lim = false + zero_resid = zero_resid_mach = zero_resid_lim = (rNorm ≤ ε) + fwd_err = false + user_requested_exit = false + overtimed = false + + while !(solved || tired || ill_cond || user_requested_exit || overtimed) + iter = iter + 1 + + # Generate next Lanczos vector. + mul!(y, A, v) + λ ≠ 0 && @kaxpy!(n, λ, v, y) # (y = y + λ * v) + @kscal!(n, one(FC) / β, y) + iter ≥ 2 && @kaxpy!(n, -β / oldβ, r1, y) # (y = y - β / oldβ * r1) + + α = real((@kdot(n, v, y) / β)) + @kaxpy!(n, -α / β, r2, y) # y = y - α / β * r2 + + # Compute w. + δ = cs * δbar + sn * α + if iter == 1 + w = w2 + else + iter ≥ 3 && @kscal!(n, -ϵ, w1) + w = w1 + @kaxpy!(n, -δ, w2, w) + end + @kaxpy!(n, one(FC) / β, v, w) + + @. r1 = r2 + @. r2 = y + MisI || mulorldiv!(v, M, r2, ldiv) + oldβ = β + β = @kdotr(n, r2, v) + β < 0 && error("Preconditioner is not positive definite") + β = sqrt(β) + ANorm² = ANorm² + α * α + oldβ * oldβ + β * β + + # Apply rotation to obtain + # [ δₖ ϵₖ₊₁ ] = [ cs sn ] [ δbarₖ 0 ] + # [ γbar δbarₖ₊₁ ] [ sn -cs ] [ αₖ βₖ₊₁ ] + γbar = sn * δbar - cs * α + ϵ = sn * β + δbar = -cs * β + root = sqrt(γbar * γbar + δbar * δbar) + ArNorm = ϕbar * root # = ‖Aᴴrₖ₋₁‖ + history && push!(ArNorms, ArNorm) + + # Compute the next plane rotation. + γ = sqrt(γbar * γbar + β * β) + γ = max(γ, ϵM) + cs = γbar / γ + sn = β / γ + ϕ = cs * ϕbar + ϕbar = sn * ϕbar + + # Final update of w. + @kscal!(n, one(FC) / γ, w) + + # Update x. + @kaxpy!(n, ϕ, w, x) # x = x + ϕ * w + xENorm² = xENorm² + ϕ * ϕ + + # Update directions for x. + if iter ≥ 2 + @kswap(w1, w2) + end + + # Compute lower bound on forward error. + err_vec[mod(iter, window) + 1] = ϕ + iter ≥ window && (err_lbnd = @knrm2(window, err_vec)) + + γmax = max(γmax, γ) + γmin = min(γmin, γ) + ζ = rhs1 / γ + rhs1 = rhs2 - δ * ζ + rhs2 = -ϵ * ζ + + # Estimate various norms. + ANorm = sqrt(ANorm²) + xNorm = @knrm2(n, x) + ϵA = ANorm * ϵM + ϵx = ANorm * xNorm * ϵM + ϵr = ANorm * xNorm * rtol + d = γbar + d == 0 && (d = ϵA) + + rNorm = ϕbar + + test1 = rNorm / (ANorm * xNorm) + test2 = root / ANorm + history && push!(rNorms, rNorm) + + Acond = γmax / γmin + history && push!(Aconds, Acond) + + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2) + + if iter == 1 && β / β₁ ≤ 10 * ϵM + # Aᴴb = 0 so x = 0 is a minimum least-squares solution + stats.niter = 1 + stats.solved, stats.inconsistent = true, true + stats.status = "x is a minimum least-squares solution" + solver.warm_start = false + return solver + end + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + ill_cond_mach = (one(T) + one(T) / Acond ≤ one(T)) + solved_mach = (one(T) + test2 ≤ one(T)) + zero_resid_mach = (one(T) + test1 ≤ one(T)) + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + # solved_mach = (ϵx ≥ β₁) + + # Stopping conditions based on user-provided tolerances. + tired = iter ≥ itmax + ill_cond_lim = (one(T) / Acond ≤ ctol) + solved_lim = (test2 ≤ ε) + zero_resid_lim = MisI && (test1 ≤ eps(T)) + resid_decrease_lim = (rNorm ≤ ε) + iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) + + user_requested_exit = callback(solver) :: Bool + zero_resid = zero_resid_mach || zero_resid_lim + resid_decrease = resid_decrease_mach || resid_decrease_lim + ill_cond = ill_cond_mach || ill_cond_lim + solved = solved_mach || solved_lim || zero_resid || fwd_err || resid_decrease + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + ill_cond_mach && (status = "condition number seems too large for this machine") + ill_cond_lim && (status = "condition number exceeds tolerance") + solved && (status = "found approximate minimum least-squares solution") + zero_resid && (status = "found approximate zero-residual solution") + fwd_err && (status = "truncated forward error small enough") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - ill_cond_mach = (one(T) + one(T) / Acond ≤ one(T)) - solved_mach = (one(T) + test2 ≤ one(T)) - zero_resid_mach = (one(T) + test1 ≤ one(T)) - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - # solved_mach = (ϵx ≥ β₁) - - # Stopping conditions based on user-provided tolerances. - tired = iter ≥ itmax - ill_cond_lim = (one(T) / Acond ≤ ctol) - solved_lim = (test2 ≤ ε) - zero_resid_lim = MisI && (test1 ≤ eps(T)) - resid_decrease_lim = (rNorm ≤ ε) - iter ≥ window && (fwd_err = err_lbnd ≤ etol * sqrt(xENorm²)) - - user_requested_exit = callback(solver) :: Bool - zero_resid = zero_resid_mach || zero_resid_lim - resid_decrease = resid_decrease_mach || resid_decrease_lim - ill_cond = ill_cond_mach || ill_cond_lim - solved = solved_mach || solved_lim || zero_resid || fwd_err || resid_decrease - timer = time_ns() - start_time - overtimed = timer > timemax_ns + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = !zero_resid + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - ill_cond_mach && (status = "condition number seems too large for this machine") - ill_cond_lim && (status = "condition number exceeds tolerance") - solved && (status = "found approximate minimum least-squares solution") - zero_resid && (status = "found approximate zero-residual solution") - fwd_err && (status = "truncated forward error small enough") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = !zero_resid - stats.status = status - return solver end diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 1929242cb..ffdbaf56b 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -119,369 +119,367 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return solver end -end - -function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, λ :: T=zero(T),atol :: T=√eps(T), - rtol :: T=√eps(T), Artol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "MINRES-QLP: system of size %d\n", n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :vₖ, S, n) - wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ = solver.wₖ₋₁, solver.wₖ, solver.M⁻¹vₖ₋₁, solver.M⁻¹vₖ - Δx, x, p, stats = solver.Δx, solver.x, solver.p, solver.stats - warm_start = solver.warm_start - rNorms, ArNorms, Aconds = stats.residuals, stats.Aresiduals, stats.Acond - reset!(stats) - vₖ = MisI ? M⁻¹vₖ : solver.vₖ - vₖ₊₁ = MisI ? p : M⁻¹vₖ₋₁ - - # Initial solution x₀ - x .= zero(FC) - - if warm_start - mul!(M⁻¹vₖ, A, Δx) - (λ ≠ 0) && @kaxpy!(n, λ, Δx, M⁻¹vₖ) - @kaxpby!(n, one(FC), b, -one(FC), M⁻¹vₖ) - else - M⁻¹vₖ .= b - end - - # β₁v₁ = Mb - MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv) - βₖ = sqrt(@kdotr(n, vₖ, M⁻¹vₖ)) - if βₖ ≠ 0 - @kscal!(n, one(FC) / βₖ, M⁻¹vₖ) - MisI || @kscal!(n, one(FC) / βₖ, vₖ) - end - - rNorm = βₖ - ANorm² = zero(T) - ANorm = zero(T) - μmin = zero(T) - μmax = zero(T) - Acond = zero(T) - history && push!(rNorms, rNorm) - history && push!(Aconds, Acond) - if rNorm == 0 - stats.niter = 0 - stats.solved, stats.inconsistent = true, false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - - iter = 0 - itmax == 0 && (itmax = 2*n) - - ε = atol + rtol * rNorm - κ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %7s %8s %7s\n", "k", "‖rₖ‖", "‖Arₖ₋₁‖", "βₖ₊₁", "Rₖ.ₖ", "Lₖ.ₖ", "‖A‖", "κ(A)", "backward") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7.1e %7s %8s %7.1e %7.1e %8s\n", iter, rNorm, "✗ ✗ ✗ ✗", βₖ, "✗ ✗ ✗ ✗", " ✗ ✗ ✗ ✗", ANorm, Acond, " ✗ ✗ ✗ ✗") - - # Set up workspace. - M⁻¹vₖ₋₁ .= zero(FC) - ζbarₖ = βₖ - ξₖ₋₁ = zero(T) - τₖ₋₂ = τₖ₋₁ = τₖ = zero(T) - ψbarₖ₋₂ = zero(T) - μbisₖ₋₂ = μbarₖ₋₁ = zero(T) - wₖ₋₁ .= zero(FC) - wₖ .= zero(FC) - cₖ₋₂ = cₖ₋₁ = cₖ = one(T) # Givens cosines used for the QR factorization of Tₖ₊₁.ₖ - sₖ₋₂ = sₖ₋₁ = sₖ = zero(T) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ - - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) - - # Stopping criterion. - breakdown = false - solved = zero_resid = zero_resid_lim = rNorm ≤ ε - zero_resid_mach = false - inconsistent = false - ill_cond_mach = false - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || inconsistent || ill_cond_mach || breakdown || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the preconditioned Lanczos process. - # M(A + λI)Vₖ = Vₖ₊₁Tₖ₊₁.ₖ - # βₖ₊₁vₖ₊₁ = M(A + λI)vₖ - αₖvₖ - βₖvₖ₋₁ - - mul!(p, A, vₖ) # p ← Avₖ - if λ ≠ 0 - @kaxpy!(n, λ, vₖ, p) # p ← p + λvₖ - end - - if iter ≥ 2 - @kaxpy!(n, -βₖ, M⁻¹vₖ₋₁, p) # p ← p - βₖ * M⁻¹vₖ₋₁ - end - - αₖ = @kdotr(n, vₖ, p) # αₖ = ⟨vₖ,p⟩ - - @kaxpy!(n, -αₖ, M⁻¹vₖ, p) # p ← p - αₖM⁻¹vₖ - - MisI || mulorldiv!(vₖ₊₁, M, p, ldiv) # βₖ₊₁vₖ₊₁ = MAvₖ - γₖvₖ₋₁ - αₖvₖ - - βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, p)) - - # βₖ₊₁.ₖ ≠ 0 - if βₖ₊₁ > btol - @kscal!(m, one(FC) / βₖ₊₁, vₖ₊₁) - MisI || @kscal!(m, one(FC) / βₖ₊₁, p) - end - - ANorm² = ANorm² + αₖ * αₖ + βₖ * βₖ + βₖ₊₁ * βₖ₊₁ - - # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. - # [ Oᵀ ] - # - # [ α₁ β₂ 0 • • • 0 ] [ λ₁ γ₁ ϵ₁ 0 • • 0 ] - # [ β₂ α₂ β₃ • • ] [ 0 λ₂ γ₂ • • • ] - # [ 0 • • • • • ] [ • • λ₃ • • • • ] - # [ • • • • • • • ] = Qₖ [ • • • • • 0 ] - # [ • • • • • 0 ] [ • • • • ϵₖ₋₂] - # [ • • • • βₖ ] [ • • • γₖ₋₁] - # [ • • βₖ αₖ ] [ 0 • • • • 0 λₖ ] - # [ 0 • • • • 0 βₖ₊₁] [ 0 • • • • • 0 ] - # - # If k = 1, we don't have any previous reflexion. - # If k = 2, we apply the last reflexion. - # If k ≥ 3, we only apply the two previous reflexions. - - # Apply previous Givens reflections Qₖ₋₂.ₖ₋₁ - if iter ≥ 3 - # [cₖ₋₂ sₖ₋₂] [0 ] = [ ϵₖ₋₂ ] - # [sₖ₋₂ -cₖ₋₂] [βₖ] [γbarₖ₋₁] - ϵₖ₋₂ = sₖ₋₂ * βₖ - γbarₖ₋₁ = -cₖ₋₂ * βₖ - end - # Apply previous Givens reflections Qₖ₋₁.ₖ - if iter ≥ 2 - iter == 2 && (γbarₖ₋₁ = βₖ) - # [cₖ₋₁ sₖ₋₁] [γbarₖ₋₁] = [γₖ₋₁ ] - # [sₖ₋₁ -cₖ₋₁] [ αₖ ] [λbarₖ] - γₖ₋₁ = cₖ₋₁ * γbarₖ₋₁ + sₖ₋₁ * αₖ - λbarₖ = sₖ₋₁ * γbarₖ₋₁ - cₖ₋₁ * αₖ - end - iter == 1 && (λbarₖ = αₖ) - - # Compute and apply current Givens reflection Qₖ.ₖ₊₁ - # [cₖ sₖ] [λbarₖ] = [λₖ] - # [sₖ -cₖ] [βₖ₊₁ ] [0 ] - (cₖ, sₖ, λₖ) = sym_givens(λbarₖ, βₖ₊₁) - - # Compute [ zₖ ] = (Qₖ)ᴴβ₁e₁ - # [ζbarₖ₊₁] - # - # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] - # [sₖ -cₖ] [ 0 ] [ζbarₖ₊₁] - ζₖ = cₖ * ζbarₖ - ζbarₖ₊₁ = sₖ * ζbarₖ - - # Update the LQ factorization of Rₖ = LₖPₖ. - # [ λ₁ γ₁ ϵ₁ 0 • • 0 ] [ μ₁ 0 • • • • 0 ] - # [ 0 λ₂ γ₂ • • • ] [ ψ₁ μ₂ • • ] - # [ • • λ₃ • • • • ] [ ρ₁ ψ₂ μ₃ • • ] - # [ • • • • • 0 ] = [ 0 • • • • • ] Pₖ - # [ • • • • ϵₖ₋₂] [ • • • • μₖ₋₂ • • ] - # [ • • • γₖ₋₁] [ • • • ψₖ₋₂ μbisₖ₋₁ 0 ] - # [ 0 • • • • 0 λₖ ] [ 0 • • 0 ρₖ₋₂ ψbarₖ₋₁ μbarₖ] - - if iter == 1 - μbarₖ = λₖ - elseif iter == 2 - # [μbar₁ γ₁] [cp₂ sp₂] = [μbis₁ 0 ] - # [ 0 λ₂] [sp₂ -cp₂] [ψbar₁ μbar₂] - (cpₖ, spₖ, μbisₖ₋₁) = sym_givens(μbarₖ₋₁, γₖ₋₁) - ψbarₖ₋₁ = spₖ * λₖ - μbarₖ = -cpₖ * λₖ - else - # [μbisₖ₋₂ 0 ϵₖ₋₂] [cpₖ 0 spₖ] [μₖ₋₂ 0 0 ] - # [ψbarₖ₋₂ μbarₖ₋₁ γₖ₋₁] [ 0 1 0 ] = [ψₖ₋₂ μbarₖ₋₁ θₖ] - # [ 0 0 λₖ ] [spₖ 0 -cpₖ] [ρₖ₋₂ 0 ηₖ] - (cpₖ, spₖ, μₖ₋₂) = sym_givens(μbisₖ₋₂, ϵₖ₋₂) - ψₖ₋₂ = cpₖ * ψbarₖ₋₂ + spₖ * γₖ₋₁ - θₖ = spₖ * ψbarₖ₋₂ - cpₖ * γₖ₋₁ - ρₖ₋₂ = spₖ * λₖ - ηₖ = -cpₖ * λₖ - - # [μₖ₋₂ 0 0 ] [1 0 0 ] [μₖ₋₂ 0 0 ] - # [ψₖ₋₂ μbarₖ₋₁ θₖ] [0 cdₖ sdₖ] = [ψₖ₋₂ μbisₖ₋₁ 0 ] - # [ρₖ₋₂ 0 ηₖ] [0 sdₖ -cdₖ] [ρₖ₋₂ ψbarₖ₋₁ μbarₖ] - (cdₖ, sdₖ, μbisₖ₋₁) = sym_givens(μbarₖ₋₁, θₖ) - ψbarₖ₋₁ = sdₖ * ηₖ - μbarₖ = -cdₖ * ηₖ - end - # Compute Lₖtₖ = zₖ - # [ μ₁ 0 • • • • 0 ] [τ₁] [ζ₁] - # [ ψ₁ μ₂ • • ] [τ₂] [ζ₂] - # [ ρ₁ ψ₂ μ₃ • • ] [τ₃] [ζ₃] - # [ 0 • • • • • ] [••] = [••] - # [ • • • • μₖ₋₂ • • ] [••] [••] - # [ • • • ψₖ₋₂ μbisₖ₋₁ 0 ] [••] [••] - # [ 0 • • 0 ρₖ₋₂ ψbarₖ₋₁ μbarₖ] [τₖ] [ζₖ] - if iter == 1 - τₖ = ζₖ / μbarₖ - elseif iter == 2 - τₖ₋₁ = τₖ - τₖ₋₁ = τₖ₋₁ * μbarₖ₋₁ / μbisₖ₋₁ - ξₖ = ζₖ - τₖ = (ξₖ - ψbarₖ₋₁ * τₖ₋₁) / μbarₖ + function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "MINRES-QLP: system of size %d\n", n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :vₖ, S, n) + wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ = solver.wₖ₋₁, solver.wₖ, solver.M⁻¹vₖ₋₁, solver.M⁻¹vₖ + Δx, x, p, stats = solver.Δx, solver.x, solver.p, solver.stats + warm_start = solver.warm_start + rNorms, ArNorms, Aconds = stats.residuals, stats.Aresiduals, stats.Acond + reset!(stats) + vₖ = MisI ? M⁻¹vₖ : solver.vₖ + vₖ₊₁ = MisI ? p : M⁻¹vₖ₋₁ + + # Initial solution x₀ + x .= zero(FC) + + if warm_start + mul!(M⁻¹vₖ, A, Δx) + (λ ≠ 0) && @kaxpy!(n, λ, Δx, M⁻¹vₖ) + @kaxpby!(n, one(FC), b, -one(FC), M⁻¹vₖ) else - τₖ₋₂ = τₖ₋₁ - τₖ₋₂ = τₖ₋₂ * μbisₖ₋₂ / μₖ₋₂ - τₖ₋₁ = (ξₖ₋₁ - ψₖ₋₂ * τₖ₋₂) / μbisₖ₋₁ - ξₖ = ζₖ - ρₖ₋₂ * τₖ₋₂ - τₖ = (ξₖ - ψbarₖ₋₁ * τₖ₋₁) / μbarₖ + M⁻¹vₖ .= b end - # Compute directions wₖ₋₂, ẘₖ₋₁ and w̄ₖ, last columns of Wₖ = Vₖ(Pₖ)ᴴ - if iter == 1 - # w̅₁ = v₁ - @. wₖ = vₖ - elseif iter == 2 - # [w̅ₖ₋₁ vₖ] [cpₖ spₖ] = [ẘₖ₋₁ w̅ₖ] ⟷ ẘₖ₋₁ = cpₖ * w̅ₖ₋₁ + spₖ * vₖ - # [spₖ -cpₖ] ⟷ w̅ₖ = spₖ * w̅ₖ₋₁ - cpₖ * vₖ - @kswap(wₖ₋₁, wₖ) - @. wₖ = spₖ * wₖ₋₁ - cpₖ * vₖ - @kaxpby!(n, spₖ, vₖ, cpₖ, wₖ₋₁) - else - # [ẘₖ₋₂ w̄ₖ₋₁ vₖ] [cpₖ 0 spₖ] [1 0 0 ] = [wₖ₋₂ ẘₖ₋₁ w̄ₖ] ⟷ wₖ₋₂ = cpₖ * ẘₖ₋₂ + spₖ * vₖ - # [ 0 1 0 ] [0 cdₖ sdₖ] ⟷ ẘₖ₋₁ = cdₖ * w̄ₖ₋₁ + sdₖ * (spₖ * ẘₖ₋₂ - cpₖ * vₖ) - # [spₖ 0 -cpₖ] [0 sdₖ -cdₖ] ⟷ w̄ₖ = sdₖ * w̄ₖ₋₁ - cdₖ * (spₖ * ẘₖ₋₂ - cpₖ * vₖ) - ẘₖ₋₂ = wₖ₋₁ - w̄ₖ₋₁ = wₖ - # Update the solution x - @kaxpy!(n, cpₖ * τₖ₋₂, ẘₖ₋₂, x) - @kaxpy!(n, spₖ * τₖ₋₂, vₖ, x) - # Compute wₐᵤₓ = spₖ * ẘₖ₋₂ - cpₖ * vₖ - @kaxpby!(n, -cpₖ, vₖ, spₖ, ẘₖ₋₂) - wₐᵤₓ = ẘₖ₋₂ - # Compute ẘₖ₋₁ and w̄ₖ - @kref!(n, w̄ₖ₋₁, wₐᵤₓ, cdₖ, sdₖ) - @kswap(wₖ₋₁, wₖ) + # β₁v₁ = Mb + MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv) + βₖ = sqrt(@kdotr(n, vₖ, M⁻¹vₖ)) + if βₖ ≠ 0 + @kscal!(n, one(FC) / βₖ, M⁻¹vₖ) + MisI || @kscal!(n, one(FC) / βₖ, vₖ) end - # Update vₖ, M⁻¹vₖ₋₁, M⁻¹vₖ - MisI || (vₖ .= vₖ₊₁) - M⁻¹vₖ₋₁ .= M⁻¹vₖ - M⁻¹vₖ .= p - - # Update ‖rₖ‖ estimate - # ‖ rₖ ‖ = |ζbarₖ₊₁| - rNorm = abs(ζbarₖ₊₁) + rNorm = βₖ + ANorm² = zero(T) + ANorm = zero(T) + μmin = zero(T) + μmax = zero(T) + Acond = zero(T) history && push!(rNorms, rNorm) - - # Update ‖Arₖ₋₁‖ estimate - # ‖ Arₖ₋₁ ‖ = |ζbarₖ| * √(|λbarₖ|² + |γbarₖ|²) - ArNorm = abs(ζbarₖ) * √(abs2(λbarₖ) + abs2(cₖ₋₁ * βₖ₊₁)) - iter == 1 && (κ = atol + Artol * ArNorm) - history && push!(ArNorms, ArNorm) - - ANorm = sqrt(ANorm²) - # estimate A condition number - abs_μbarₖ = abs(μbarₖ) - if iter == 1 - μmin = abs_μbarₖ - μmax = abs_μbarₖ - elseif iter == 2 - μmax = max(μmax, μbisₖ₋₁, abs_μbarₖ) - μmin = min(μmin, μbisₖ₋₁, abs_μbarₖ) - else - μmax = max(μmax, μₖ₋₂, μbisₖ₋₁, abs_μbarₖ) - μmin = min(μmin, μₖ₋₂, μbisₖ₋₁, abs_μbarₖ) - end - Acond = μmax / μmin history && push!(Aconds, Acond) - xNorm = @knrm2(n, x) - backward = rNorm / (ANorm * xNorm) - - # Update stopping criterion. - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - ill_cond_mach = (one(T) + one(T) / Acond ≤ one(T)) - resid_decrease_mach = (one(T) + rNorm ≤ one(T)) - zero_resid_mach = (one(T) + backward ≤ one(T)) + if rNorm == 0 + stats.niter = 0 + stats.solved, stats.inconsistent = true, false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end - # Stopping conditions based on user-provided tolerances. + iter = 0 + itmax == 0 && (itmax = 2*n) + + ε = atol + rtol * rNorm + κ = zero(T) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %7s %8s %7s\n", "k", "‖rₖ‖", "‖Arₖ₋₁‖", "βₖ₊₁", "Rₖ.ₖ", "Lₖ.ₖ", "‖A‖", "κ(A)", "backward") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7.1e %7s %8s %7.1e %7.1e %8s\n", iter, rNorm, "✗ ✗ ✗ ✗", βₖ, "✗ ✗ ✗ ✗", " ✗ ✗ ✗ ✗", ANorm, Acond, " ✗ ✗ ✗ ✗") + + # Set up workspace. + M⁻¹vₖ₋₁ .= zero(FC) + ζbarₖ = βₖ + ξₖ₋₁ = zero(T) + τₖ₋₂ = τₖ₋₁ = τₖ = zero(T) + ψbarₖ₋₂ = zero(T) + μbisₖ₋₂ = μbarₖ₋₁ = zero(T) + wₖ₋₁ .= zero(FC) + wₖ .= zero(FC) + cₖ₋₂ = cₖ₋₁ = cₖ = one(T) # Givens cosines used for the QR factorization of Tₖ₊₁.ₖ + sₖ₋₂ = sₖ₋₁ = sₖ = zero(T) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ + + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) + + # Stopping criterion. + breakdown = false + solved = zero_resid = zero_resid_lim = rNorm ≤ ε + zero_resid_mach = false + inconsistent = false + ill_cond_mach = false tired = iter ≥ itmax - resid_decrease_lim = (rNorm ≤ ε) - zero_resid_lim = MisI && (backward ≤ eps(T)) - breakdown = βₖ₊₁ ≤ btol - - user_requested_exit = callback(solver) :: Bool - zero_resid = zero_resid_mach | zero_resid_lim - resid_decrease = resid_decrease_mach | resid_decrease_lim - solved = resid_decrease | zero_resid - inconsistent = (ArNorm ≤ κ && abs(μbarₖ) ≤ Artol) || (breakdown && !solved) - timer = time_ns() - start_time - overtimed = timer > timemax_ns - - # Update variables + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || inconsistent || ill_cond_mach || breakdown || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 + + # Continue the preconditioned Lanczos process. + # M(A + λI)Vₖ = Vₖ₊₁Tₖ₊₁.ₖ + # βₖ₊₁vₖ₊₁ = M(A + λI)vₖ - αₖvₖ - βₖvₖ₋₁ + + mul!(p, A, vₖ) # p ← Avₖ + if λ ≠ 0 + @kaxpy!(n, λ, vₖ, p) # p ← p + λvₖ + end + + if iter ≥ 2 + @kaxpy!(n, -βₖ, M⁻¹vₖ₋₁, p) # p ← p - βₖ * M⁻¹vₖ₋₁ + end + + αₖ = @kdotr(n, vₖ, p) # αₖ = ⟨vₖ,p⟩ + + @kaxpy!(n, -αₖ, M⁻¹vₖ, p) # p ← p - αₖM⁻¹vₖ + + MisI || mulorldiv!(vₖ₊₁, M, p, ldiv) # βₖ₊₁vₖ₊₁ = MAvₖ - γₖvₖ₋₁ - αₖvₖ + + βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, p)) + + # βₖ₊₁.ₖ ≠ 0 + if βₖ₊₁ > btol + @kscal!(m, one(FC) / βₖ₊₁, vₖ₊₁) + MisI || @kscal!(m, one(FC) / βₖ₊₁, p) + end + + ANorm² = ANorm² + αₖ * αₖ + βₖ * βₖ + βₖ₊₁ * βₖ₊₁ + + # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. + # [ Oᵀ ] + # + # [ α₁ β₂ 0 • • • 0 ] [ λ₁ γ₁ ϵ₁ 0 • • 0 ] + # [ β₂ α₂ β₃ • • ] [ 0 λ₂ γ₂ • • • ] + # [ 0 • • • • • ] [ • • λ₃ • • • • ] + # [ • • • • • • • ] = Qₖ [ • • • • • 0 ] + # [ • • • • • 0 ] [ • • • • ϵₖ₋₂] + # [ • • • • βₖ ] [ • • • γₖ₋₁] + # [ • • βₖ αₖ ] [ 0 • • • • 0 λₖ ] + # [ 0 • • • • 0 βₖ₊₁] [ 0 • • • • • 0 ] + # + # If k = 1, we don't have any previous reflexion. + # If k = 2, we apply the last reflexion. + # If k ≥ 3, we only apply the two previous reflexions. + + # Apply previous Givens reflections Qₖ₋₂.ₖ₋₁ + if iter ≥ 3 + # [cₖ₋₂ sₖ₋₂] [0 ] = [ ϵₖ₋₂ ] + # [sₖ₋₂ -cₖ₋₂] [βₖ] [γbarₖ₋₁] + ϵₖ₋₂ = sₖ₋₂ * βₖ + γbarₖ₋₁ = -cₖ₋₂ * βₖ + end + # Apply previous Givens reflections Qₖ₋₁.ₖ + if iter ≥ 2 + iter == 2 && (γbarₖ₋₁ = βₖ) + # [cₖ₋₁ sₖ₋₁] [γbarₖ₋₁] = [γₖ₋₁ ] + # [sₖ₋₁ -cₖ₋₁] [ αₖ ] [λbarₖ] + γₖ₋₁ = cₖ₋₁ * γbarₖ₋₁ + sₖ₋₁ * αₖ + λbarₖ = sₖ₋₁ * γbarₖ₋₁ - cₖ₋₁ * αₖ + end + iter == 1 && (λbarₖ = αₖ) + + # Compute and apply current Givens reflection Qₖ.ₖ₊₁ + # [cₖ sₖ] [λbarₖ] = [λₖ] + # [sₖ -cₖ] [βₖ₊₁ ] [0 ] + (cₖ, sₖ, λₖ) = sym_givens(λbarₖ, βₖ₊₁) + + # Compute [ zₖ ] = (Qₖ)ᴴβ₁e₁ + # [ζbarₖ₊₁] + # + # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] + # [sₖ -cₖ] [ 0 ] [ζbarₖ₊₁] + ζₖ = cₖ * ζbarₖ + ζbarₖ₊₁ = sₖ * ζbarₖ + + # Update the LQ factorization of Rₖ = LₖPₖ. + # [ λ₁ γ₁ ϵ₁ 0 • • 0 ] [ μ₁ 0 • • • • 0 ] + # [ 0 λ₂ γ₂ • • • ] [ ψ₁ μ₂ • • ] + # [ • • λ₃ • • • • ] [ ρ₁ ψ₂ μ₃ • • ] + # [ • • • • • 0 ] = [ 0 • • • • • ] Pₖ + # [ • • • • ϵₖ₋₂] [ • • • • μₖ₋₂ • • ] + # [ • • • γₖ₋₁] [ • • • ψₖ₋₂ μbisₖ₋₁ 0 ] + # [ 0 • • • • 0 λₖ ] [ 0 • • 0 ρₖ₋₂ ψbarₖ₋₁ μbarₖ] + + if iter == 1 + μbarₖ = λₖ + elseif iter == 2 + # [μbar₁ γ₁] [cp₂ sp₂] = [μbis₁ 0 ] + # [ 0 λ₂] [sp₂ -cp₂] [ψbar₁ μbar₂] + (cpₖ, spₖ, μbisₖ₋₁) = sym_givens(μbarₖ₋₁, γₖ₋₁) + ψbarₖ₋₁ = spₖ * λₖ + μbarₖ = -cpₖ * λₖ + else + # [μbisₖ₋₂ 0 ϵₖ₋₂] [cpₖ 0 spₖ] [μₖ₋₂ 0 0 ] + # [ψbarₖ₋₂ μbarₖ₋₁ γₖ₋₁] [ 0 1 0 ] = [ψₖ₋₂ μbarₖ₋₁ θₖ] + # [ 0 0 λₖ ] [spₖ 0 -cpₖ] [ρₖ₋₂ 0 ηₖ] + (cpₖ, spₖ, μₖ₋₂) = sym_givens(μbisₖ₋₂, ϵₖ₋₂) + ψₖ₋₂ = cpₖ * ψbarₖ₋₂ + spₖ * γₖ₋₁ + θₖ = spₖ * ψbarₖ₋₂ - cpₖ * γₖ₋₁ + ρₖ₋₂ = spₖ * λₖ + ηₖ = -cpₖ * λₖ + + # [μₖ₋₂ 0 0 ] [1 0 0 ] [μₖ₋₂ 0 0 ] + # [ψₖ₋₂ μbarₖ₋₁ θₖ] [0 cdₖ sdₖ] = [ψₖ₋₂ μbisₖ₋₁ 0 ] + # [ρₖ₋₂ 0 ηₖ] [0 sdₖ -cdₖ] [ρₖ₋₂ ψbarₖ₋₁ μbarₖ] + (cdₖ, sdₖ, μbisₖ₋₁) = sym_givens(μbarₖ₋₁, θₖ) + ψbarₖ₋₁ = sdₖ * ηₖ + μbarₖ = -cdₖ * ηₖ + end + + # Compute Lₖtₖ = zₖ + # [ μ₁ 0 • • • • 0 ] [τ₁] [ζ₁] + # [ ψ₁ μ₂ • • ] [τ₂] [ζ₂] + # [ ρ₁ ψ₂ μ₃ • • ] [τ₃] [ζ₃] + # [ 0 • • • • • ] [••] = [••] + # [ • • • • μₖ₋₂ • • ] [••] [••] + # [ • • • ψₖ₋₂ μbisₖ₋₁ 0 ] [••] [••] + # [ 0 • • 0 ρₖ₋₂ ψbarₖ₋₁ μbarₖ] [τₖ] [ζₖ] + if iter == 1 + τₖ = ζₖ / μbarₖ + elseif iter == 2 + τₖ₋₁ = τₖ + τₖ₋₁ = τₖ₋₁ * μbarₖ₋₁ / μbisₖ₋₁ + ξₖ = ζₖ + τₖ = (ξₖ - ψbarₖ₋₁ * τₖ₋₁) / μbarₖ + else + τₖ₋₂ = τₖ₋₁ + τₖ₋₂ = τₖ₋₂ * μbisₖ₋₂ / μₖ₋₂ + τₖ₋₁ = (ξₖ₋₁ - ψₖ₋₂ * τₖ₋₂) / μbisₖ₋₁ + ξₖ = ζₖ - ρₖ₋₂ * τₖ₋₂ + τₖ = (ξₖ - ψbarₖ₋₁ * τₖ₋₁) / μbarₖ + end + + # Compute directions wₖ₋₂, ẘₖ₋₁ and w̄ₖ, last columns of Wₖ = Vₖ(Pₖ)ᴴ + if iter == 1 + # w̅₁ = v₁ + @. wₖ = vₖ + elseif iter == 2 + # [w̅ₖ₋₁ vₖ] [cpₖ spₖ] = [ẘₖ₋₁ w̅ₖ] ⟷ ẘₖ₋₁ = cpₖ * w̅ₖ₋₁ + spₖ * vₖ + # [spₖ -cpₖ] ⟷ w̅ₖ = spₖ * w̅ₖ₋₁ - cpₖ * vₖ + @kswap(wₖ₋₁, wₖ) + @. wₖ = spₖ * wₖ₋₁ - cpₖ * vₖ + @kaxpby!(n, spₖ, vₖ, cpₖ, wₖ₋₁) + else + # [ẘₖ₋₂ w̄ₖ₋₁ vₖ] [cpₖ 0 spₖ] [1 0 0 ] = [wₖ₋₂ ẘₖ₋₁ w̄ₖ] ⟷ wₖ₋₂ = cpₖ * ẘₖ₋₂ + spₖ * vₖ + # [ 0 1 0 ] [0 cdₖ sdₖ] ⟷ ẘₖ₋₁ = cdₖ * w̄ₖ₋₁ + sdₖ * (spₖ * ẘₖ₋₂ - cpₖ * vₖ) + # [spₖ 0 -cpₖ] [0 sdₖ -cdₖ] ⟷ w̄ₖ = sdₖ * w̄ₖ₋₁ - cdₖ * (spₖ * ẘₖ₋₂ - cpₖ * vₖ) + ẘₖ₋₂ = wₖ₋₁ + w̄ₖ₋₁ = wₖ + # Update the solution x + @kaxpy!(n, cpₖ * τₖ₋₂, ẘₖ₋₂, x) + @kaxpy!(n, spₖ * τₖ₋₂, vₖ, x) + # Compute wₐᵤₓ = spₖ * ẘₖ₋₂ - cpₖ * vₖ + @kaxpby!(n, -cpₖ, vₖ, spₖ, ẘₖ₋₂) + wₐᵤₓ = ẘₖ₋₂ + # Compute ẘₖ₋₁ and w̄ₖ + @kref!(n, w̄ₖ₋₁, wₐᵤₓ, cdₖ, sdₖ) + @kswap(wₖ₋₁, wₖ) + end + + # Update vₖ, M⁻¹vₖ₋₁, M⁻¹vₖ + MisI || (vₖ .= vₖ₊₁) + M⁻¹vₖ₋₁ .= M⁻¹vₖ + M⁻¹vₖ .= p + + # Update ‖rₖ‖ estimate + # ‖ rₖ ‖ = |ζbarₖ₊₁| + rNorm = abs(ζbarₖ₊₁) + history && push!(rNorms, rNorm) + + # Update ‖Arₖ₋₁‖ estimate + # ‖ Arₖ₋₁ ‖ = |ζbarₖ| * √(|λbarₖ|² + |γbarₖ|²) + ArNorm = abs(ζbarₖ) * √(abs2(λbarₖ) + abs2(cₖ₋₁ * βₖ₊₁)) + iter == 1 && (κ = atol + Artol * ArNorm) + history && push!(ArNorms, ArNorm) + + ANorm = sqrt(ANorm²) + # estimate A condition number + abs_μbarₖ = abs(μbarₖ) + if iter == 1 + μmin = abs_μbarₖ + μmax = abs_μbarₖ + elseif iter == 2 + μmax = max(μmax, μbisₖ₋₁, abs_μbarₖ) + μmin = min(μmin, μbisₖ₋₁, abs_μbarₖ) + else + μmax = max(μmax, μₖ₋₂, μbisₖ₋₁, abs_μbarₖ) + μmin = min(μmin, μₖ₋₂, μbisₖ₋₁, abs_μbarₖ) + end + Acond = μmax / μmin + history && push!(Aconds, Acond) + xNorm = @knrm2(n, x) + backward = rNorm / (ANorm * xNorm) + + # Update stopping criterion. + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + ill_cond_mach = (one(T) + one(T) / Acond ≤ one(T)) + resid_decrease_mach = (one(T) + rNorm ≤ one(T)) + zero_resid_mach = (one(T) + backward ≤ one(T)) + + # Stopping conditions based on user-provided tolerances. + tired = iter ≥ itmax + resid_decrease_lim = (rNorm ≤ ε) + zero_resid_lim = MisI && (backward ≤ eps(T)) + breakdown = βₖ₊₁ ≤ btol + + user_requested_exit = callback(solver) :: Bool + zero_resid = zero_resid_mach | zero_resid_lim + resid_decrease = resid_decrease_mach | resid_decrease_lim + solved = resid_decrease | zero_resid + inconsistent = (ArNorm ≤ κ && abs(μbarₖ) ≤ Artol) || (breakdown && !solved) + timer = time_ns() - start_time + overtimed = timer > timemax_ns + + # Update variables + if iter ≥ 2 + sₖ₋₂ = sₖ₋₁ + cₖ₋₂ = cₖ₋₁ + ξₖ₋₁ = ξₖ + μbisₖ₋₂ = μbisₖ₋₁ + ψbarₖ₋₂ = ψbarₖ₋₁ + end + sₖ₋₁ = sₖ + cₖ₋₁ = cₖ + μbarₖ₋₁ = μbarₖ + ζbarₖ = ζbarₖ₊₁ + βₖ = βₖ₊₁ + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %7.1e %7.1e %8.1e\n", iter, rNorm, ArNorm, βₖ₊₁, λₖ, μbarₖ, ANorm, Acond, backward) + end + (verbose > 0) && @printf(iostream, "\n") + + # Finalize the update of x if iter ≥ 2 - sₖ₋₂ = sₖ₋₁ - cₖ₋₂ = cₖ₋₁ - ξₖ₋₁ = ξₖ - μbisₖ₋₂ = μbisₖ₋₁ - ψbarₖ₋₂ = ψbarₖ₋₁ + @kaxpy!(n, τₖ₋₁, wₖ₋₁, x) + end + if !inconsistent + @kaxpy!(n, τₖ, wₖ, x) end - sₖ₋₁ = sₖ - cₖ₋₁ = cₖ - μbarₖ₋₁ = μbarₖ - ζbarₖ = ζbarₖ₊₁ - βₖ = βₖ₊₁ - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %7.1e %7.1e %8.1e\n", iter, rNorm, ArNorm, βₖ₊₁, λₖ, μbarₖ, ANorm, Acond, backward) - end - (verbose > 0) && @printf(iostream, "\n") - # Finalize the update of x - if iter ≥ 2 - @kaxpy!(n, τₖ₋₁, wₖ₋₁, x) - end - if !inconsistent - @kaxpy!(n, τₖ, wₖ, x) - end + # Termination status + tired && (status = "maximum number of iterations exceeded") + ill_cond_mach && (status = "condition number seems too large for this machine") + inconsistent && (status = "found approximate minimum least-squares solution") + zero_resid && (status = "found approximate zero-residual solution") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Termination status - tired && (status = "maximum number of iterations exceeded") - ill_cond_mach && (status = "condition number seems too large for this machine") - inconsistent && (status = "found approximate minimum least-squares solution") - zero_resid && (status = "found approximate zero-residual solution") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver + end end diff --git a/src/qmr.jl b/src/qmr.jl index 3cf471252..fe7a7f7c9 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -115,258 +115,256 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, qmr!(solver, A, b; $(kwargs_qmr...)) return solver end -end - -function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; - c :: AbstractVector{FC}=b, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, timemax :: Float64=Inf, - verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "QMR: system of size %d\n", n) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ, solver.p - Δx, x, wₖ₋₂, wₖ₋₁, stats = solver.Δx, solver.x, solver.wₖ₋₂, solver.wₖ₋₁, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - r₀ = warm_start ? q : b - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - end - - # Initial solution x₀ and residual norm ‖r₀‖. - x .= zero(FC) - rNorm = @knrm2(n, r₀) # ‖r₀‖ = ‖b₀ - Ax₀‖ - - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved = true - stats.inconsistent = false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - - iter = 0 - itmax == 0 && (itmax = 2*n) - - ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - - # Initialize the Lanczos biorthogonalization process. - cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ - if cᴴb == 0 - stats.niter = 0 - stats.solved = false - stats.inconsistent = false - stats.status = "Breakdown bᴴc = 0" - solver.warm_start = false - return solver - end - βₖ = √(abs(cᴴb)) # β₁γ₁ = cᴴ(b - Ax₀) - γₖ = cᴴb / βₖ # β₁γ₁ = cᴴ(b - Ax₀) - vₖ₋₁ .= zero(FC) # v₀ = 0 - uₖ₋₁ .= zero(FC) # u₀ = 0 - vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= c ./ conj(γₖ) # u₁ = c / γ̄₁ - cₖ₋₂ = cₖ₋₁ = cₖ = zero(T) # Givens cosines used for the QR factorization of Tₖ₊₁.ₖ - sₖ₋₂ = sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ - wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Rₖ)⁻¹ - wₖ₋₁ .= zero(FC) # Column k-1 of Wₖ = Vₖ(Rₖ)⁻¹ - ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᴴβ₁e₁ - τₖ = @kdotr(n, vₖ, vₖ) # τₖ is used for the residual norm estimate - - # Stopping criterion. - solved = rNorm ≤ ε - breakdown = false - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || breakdown || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the Lanczos biorthogonalization process. - # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ - mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ - - @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ - @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ - - αₖ = @kdot(n, uₖ, q) # αₖ = ⟨uₖ,q⟩ - - @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ - @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - - pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ - βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) - γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ - - # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. - # [ Oᵀ ] - # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ λ₁ ϵ₁ 0 • • 0 ] - # [ β₂ α₂ γ₃ • • ] [ 0 δ₂ λ₂ • • • ] - # [ 0 • • • • • ] [ • • δ₃ • • • • ] - # [ • • • • • • • ] = Qₖ [ • • • • • 0 ] - # [ • • • • • 0 ] [ • • • • ϵₖ₋₂] - # [ • • • • γₖ ] [ • • • λₖ₋₁] - # [ • • βₖ αₖ ] [ • • δₖ ] - # [ 0 • • • • 0 βₖ₊₁] [ 0 • • • • • 0 ] - # - # If k = 1, we don't have any previous reflexion. - # If k = 2, we apply the last reflexion. - # If k ≥ 3, we only apply the two previous reflexions. - - # Apply previous Givens reflections Qₖ₋₂.ₖ₋₁ - if iter ≥ 3 - # [cₖ₋₂ sₖ₋₂] [0 ] = [ ϵₖ₋₂ ] - # [s̄ₖ₋₂ -cₖ₋₂] [γₖ] [λbarₖ₋₁] - ϵₖ₋₂ = sₖ₋₂ * γₖ - λbarₖ₋₁ = -cₖ₋₂ * γₖ + function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "QMR: system of size %d\n", n) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p = solver.uₖ₋₁, solver.uₖ, solver.q, solver.vₖ₋₁, solver.vₖ, solver.p + Δx, x, wₖ₋₂, wₖ₋₁, stats = solver.Δx, solver.x, solver.wₖ₋₂, solver.wₖ₋₁, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + r₀ = warm_start ? q : b + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) end - # Apply previous Givens reflections Qₖ₋₁.ₖ - if iter ≥ 2 - iter == 2 && (λbarₖ₋₁ = γₖ) - # [cₖ₋₁ sₖ₋₁] [λbarₖ₋₁] = [λₖ₋₁ ] - # [s̄ₖ₋₁ -cₖ₋₁] [ αₖ ] [δbarₖ] - λₖ₋₁ = cₖ₋₁ * λbarₖ₋₁ + sₖ₋₁ * αₖ - δbarₖ = conj(sₖ₋₁) * λbarₖ₋₁ - cₖ₋₁ * αₖ - - # Update sₖ₋₂ and cₖ₋₂. - sₖ₋₂ = sₖ₋₁ - cₖ₋₂ = cₖ₋₁ - end + # Initial solution x₀ and residual norm ‖r₀‖. + x .= zero(FC) + rNorm = @knrm2(n, r₀) # ‖r₀‖ = ‖b₀ - Ax₀‖ - # Compute and apply current Givens reflection Qₖ.ₖ₊₁ - iter == 1 && (δbarₖ = αₖ) - # [cₖ sₖ] [δbarₖ] = [δₖ] - # [s̄ₖ -cₖ] [βₖ₊₁ ] [0 ] - (cₖ, sₖ, δₖ) = sym_givens(δbarₖ, βₖ₊₁) - - # Update z̅ₖ₊₁ = Qₖ.ₖ₊₁ [ z̄ₖ ] - # [ 0 ] - # - # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] - # [s̄ₖ -cₖ] [ 0 ] [ζbarₖ₊₁] - ζₖ = cₖ * ζbarₖ - ζbarₖ₊₁ = conj(sₖ) * ζbarₖ - - # Update sₖ₋₁ and cₖ₋₁. - sₖ₋₁ = sₖ - cₖ₋₁ = cₖ - - # Compute the direction wₖ, the last column of Wₖ = Vₖ(Rₖ)⁻¹ ⟷ (Rₖ)ᵀ(Wₖ)ᵀ = (Vₖ)ᵀ. - # w₁ = v₁ / δ₁ - if iter == 1 - wₖ = wₖ₋₁ - @kaxpy!(n, one(FC), vₖ, wₖ) - @. wₖ = wₖ / δₖ - end - # w₂ = (v₂ - λ₁w₁) / δ₂ - if iter == 2 - wₖ = wₖ₋₂ - @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) - @kaxpy!(n, one(FC), vₖ, wₖ) - @. wₖ = wₖ / δₖ - end - # wₖ = (vₖ - λₖ₋₁wₖ₋₁ - ϵₖ₋₂wₖ₋₂) / δₖ - if iter ≥ 3 - @kscal!(n, -ϵₖ₋₂, wₖ₋₂) - wₖ = wₖ₋₂ - @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) - @kaxpy!(n, one(FC), vₖ, wₖ) - @. wₖ = wₖ / δₖ + history && push!(rNorms, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved = true + stats.inconsistent = false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver end - # Compute solution xₖ. - # xₖ ← xₖ₋₁ + ζₖ * wₖ - @kaxpy!(n, ζₖ, wₖ, x) + iter = 0 + itmax == 0 && (itmax = 2*n) - # Compute vₖ₊₁ and uₖ₊₁. - @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ - @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + ε = atol + rtol * rNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) - if pᴴq ≠ zero(FC) - @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q - @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p + # Initialize the Lanczos biorthogonalization process. + cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ + if cᴴb == 0 + stats.niter = 0 + stats.solved = false + stats.inconsistent = false + stats.status = "Breakdown bᴴc = 0" + solver.warm_start = false + return solver end - # Compute τₖ₊₁ = τₖ + ‖vₖ₊₁‖² - τₖ₊₁ = τₖ + @kdotr(n, vₖ, vₖ) + βₖ = √(abs(cᴴb)) # β₁γ₁ = cᴴ(b - Ax₀) + γₖ = cᴴb / βₖ # β₁γ₁ = cᴴ(b - Ax₀) + vₖ₋₁ .= zero(FC) # v₀ = 0 + uₖ₋₁ .= zero(FC) # u₀ = 0 + vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ + uₖ .= c ./ conj(γₖ) # u₁ = c / γ̄₁ + cₖ₋₂ = cₖ₋₁ = cₖ = zero(T) # Givens cosines used for the QR factorization of Tₖ₊₁.ₖ + sₖ₋₂ = sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ + wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Rₖ)⁻¹ + wₖ₋₁ .= zero(FC) # Column k-1 of Wₖ = Vₖ(Rₖ)⁻¹ + ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᴴβ₁e₁ + τₖ = @kdotr(n, vₖ, vₖ) # τₖ is used for the residual norm estimate + + # Stopping criterion. + solved = rNorm ≤ ε + breakdown = false + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || breakdown || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 + + # Continue the Lanczos biorthogonalization process. + # AVₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ + # AᴴUₖ = Uₖ(Tₖ)ᴴ + γ̄ₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ + + mul!(q, A , vₖ) # Forms vₖ₊₁ : q ← Avₖ + mul!(p, Aᴴ, uₖ) # Forms uₖ₊₁ : p ← Aᴴuₖ + + @kaxpy!(n, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ + @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - β̄ₖ * uₖ₋₁ + + αₖ = @kdot(n, uₖ, q) # αₖ = ⟨uₖ,q⟩ + + @kaxpy!(n, - αₖ , vₖ, q) # q ← q - αₖ * vₖ + @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ + + pᴴq = @kdot(n, p, q) # pᴴq = ⟨p,q⟩ + βₖ₊₁ = √(abs(pᴴq)) # βₖ₊₁ = √(|pᴴq|) + γₖ₊₁ = pᴴq / βₖ₊₁ # γₖ₊₁ = pᴴq / βₖ₊₁ + + # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. + # [ Oᵀ ] + # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ λ₁ ϵ₁ 0 • • 0 ] + # [ β₂ α₂ γ₃ • • ] [ 0 δ₂ λ₂ • • • ] + # [ 0 • • • • • ] [ • • δ₃ • • • • ] + # [ • • • • • • • ] = Qₖ [ • • • • • 0 ] + # [ • • • • • 0 ] [ • • • • ϵₖ₋₂] + # [ • • • • γₖ ] [ • • • λₖ₋₁] + # [ • • βₖ αₖ ] [ • • δₖ ] + # [ 0 • • • • 0 βₖ₊₁] [ 0 • • • • • 0 ] + # + # If k = 1, we don't have any previous reflexion. + # If k = 2, we apply the last reflexion. + # If k ≥ 3, we only apply the two previous reflexions. + + # Apply previous Givens reflections Qₖ₋₂.ₖ₋₁ + if iter ≥ 3 + # [cₖ₋₂ sₖ₋₂] [0 ] = [ ϵₖ₋₂ ] + # [s̄ₖ₋₂ -cₖ₋₂] [γₖ] [λbarₖ₋₁] + ϵₖ₋₂ = sₖ₋₂ * γₖ + λbarₖ₋₁ = -cₖ₋₂ * γₖ + end + + # Apply previous Givens reflections Qₖ₋₁.ₖ + if iter ≥ 2 + iter == 2 && (λbarₖ₋₁ = γₖ) + # [cₖ₋₁ sₖ₋₁] [λbarₖ₋₁] = [λₖ₋₁ ] + # [s̄ₖ₋₁ -cₖ₋₁] [ αₖ ] [δbarₖ] + λₖ₋₁ = cₖ₋₁ * λbarₖ₋₁ + sₖ₋₁ * αₖ + δbarₖ = conj(sₖ₋₁) * λbarₖ₋₁ - cₖ₋₁ * αₖ + + # Update sₖ₋₂ and cₖ₋₂. + sₖ₋₂ = sₖ₋₁ + cₖ₋₂ = cₖ₋₁ + end + + # Compute and apply current Givens reflection Qₖ.ₖ₊₁ + iter == 1 && (δbarₖ = αₖ) + # [cₖ sₖ] [δbarₖ] = [δₖ] + # [s̄ₖ -cₖ] [βₖ₊₁ ] [0 ] + (cₖ, sₖ, δₖ) = sym_givens(δbarₖ, βₖ₊₁) + + # Update z̅ₖ₊₁ = Qₖ.ₖ₊₁ [ z̄ₖ ] + # [ 0 ] + # + # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] + # [s̄ₖ -cₖ] [ 0 ] [ζbarₖ₊₁] + ζₖ = cₖ * ζbarₖ + ζbarₖ₊₁ = conj(sₖ) * ζbarₖ + + # Update sₖ₋₁ and cₖ₋₁. + sₖ₋₁ = sₖ + cₖ₋₁ = cₖ + + # Compute the direction wₖ, the last column of Wₖ = Vₖ(Rₖ)⁻¹ ⟷ (Rₖ)ᵀ(Wₖ)ᵀ = (Vₖ)ᵀ. + # w₁ = v₁ / δ₁ + if iter == 1 + wₖ = wₖ₋₁ + @kaxpy!(n, one(FC), vₖ, wₖ) + @. wₖ = wₖ / δₖ + end + # w₂ = (v₂ - λ₁w₁) / δ₂ + if iter == 2 + wₖ = wₖ₋₂ + @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) + @kaxpy!(n, one(FC), vₖ, wₖ) + @. wₖ = wₖ / δₖ + end + # wₖ = (vₖ - λₖ₋₁wₖ₋₁ - ϵₖ₋₂wₖ₋₂) / δₖ + if iter ≥ 3 + @kscal!(n, -ϵₖ₋₂, wₖ₋₂) + wₖ = wₖ₋₂ + @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) + @kaxpy!(n, one(FC), vₖ, wₖ) + @. wₖ = wₖ / δₖ + end + + # Compute solution xₖ. + # xₖ ← xₖ₋₁ + ζₖ * wₖ + @kaxpy!(n, ζₖ, wₖ, x) + + # Compute vₖ₊₁ and uₖ₊₁. + @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ + @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + + if pᴴq ≠ zero(FC) + @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q + @. uₖ = p / conj(γₖ₊₁) # γ̄ₖ₊₁uₖ₊₁ = p + end + + # Compute τₖ₊₁ = τₖ + ‖vₖ₊₁‖² + τₖ₊₁ = τₖ + @kdotr(n, vₖ, vₖ) + + # Compute ‖rₖ‖ ≤ |ζbarₖ₊₁|√τₖ₊₁ + rNorm = abs(ζbarₖ₊₁) * √τₖ₊₁ + history && push!(rNorms, rNorm) + + # Update directions for x. + if iter ≥ 2 + @kswap(wₖ₋₂, wₖ₋₁) + end + + # Update ζbarₖ, βₖ, γₖ and τₖ. + ζbarₖ = ζbarₖ₊₁ + βₖ = βₖ₊₁ + γₖ = γₖ₊₁ + τₖ = τₖ₊₁ + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + breakdown = !solved && (pᴴq == 0) + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + end + (verbose > 0) && @printf(iostream, "\n") - # Compute ‖rₖ‖ ≤ |ζbarₖ₊₁|√τₖ₊₁ - rNorm = abs(ζbarₖ₊₁) * √τₖ₊₁ - history && push!(rNorms, rNorm) + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - # Update directions for x. - if iter ≥ 2 - @kswap(wₖ₋₂, wₖ₋₁) - end + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Update ζbarₖ, βₖ, γₖ and τₖ. - ζbarₖ = ζbarₖ₊₁ - βₖ = βₖ₊₁ - γₖ = γₖ₊₁ - τₖ = τₖ₊₁ - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - solved = resid_decrease_lim || resid_decrease_mach - tired = iter ≥ itmax - breakdown = !solved && (pᴴq == 0) - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = false + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "Breakdown ⟨uₖ₊₁,vₖ₊₁⟩ = 0") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/symmlq.jl b/src/symmlq.jl index 04def5e74..c72ea8f82 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -123,184 +123,76 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : symmlq!(solver, A, b; $(kwargs_symmlq...)) return solver end -end - -function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; - M=I, ldiv :: Bool=false, - transfer_to_cg :: Bool=true, λ :: T=zero(T), - λest :: T=zero(T), atol :: T=√eps(T), - rtol :: T=√eps(T), etol :: T=√eps(T), - conlim :: T=1/√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - m == n || error("System must be square") - length(b) == m || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "SYMMLQ: system of size %d\n", n) - - # Tests M = Iₙ - MisI = (M === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - - # Set up workspace. - allocate_if(!MisI, solver, :v, S, n) - x, Mvold, Mv, Mv_next, w̅ = solver.x, solver.Mvold, solver.Mv, solver.Mv_next, solver.w̅ - Δx, clist, zlist, sprod, stats = solver.Δx, solver.clist, solver.zlist, solver.sprod, solver.stats - warm_start = solver.warm_start - rNorms, rcgNorms = stats.residuals, stats.residualscg - errors, errorscg = stats.errors, stats.errorscg - reset!(stats) - v = MisI ? Mv : solver.v - vold = MisI ? Mvold : solver.v - - ϵM = eps(T) - ctol = conlim > 0 ? 1 / conlim : zero(T) - - # Initial solution x₀ - x .= zero(FC) - - if warm_start - mul!(Mvold, A, Δx) - (λ ≠ 0) && @kaxpy!(n, λ, Δx, Mvold) - @kaxpby!(n, one(FC), b, -one(FC), Mvold) - else - Mvold .= b - end - - # Initialize Lanczos process. - # β₁ M v₁ = b. - MisI || mulorldiv!(vold, M, Mvold, ldiv) - β₁ = @kdotr(m, vold, Mvold) - if β₁ == 0 - stats.niter = 0 - stats.solved = true - stats.Anorm = T(NaN) - stats.Acond = T(NaN) - history && push!(rNorms, zero(T)) - history && push!(rcgNorms, zero(T)) - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - β₁ = sqrt(β₁) - β = β₁ - @kscal!(m, one(FC) / β, vold) - MisI || @kscal!(m, one(FC) / β, Mvold) - - w̅ .= vold - - mul!(Mv, A, vold) - α = @kdotr(m, vold, Mv) + λ - @kaxpy!(m, -α, Mvold, Mv) # Mv = Mv - α * Mvold - MisI || mulorldiv!(v, M, Mv, ldiv) - β = @kdotr(m, v, Mv) - β < 0 && error("Preconditioner is not positive definite") - β = sqrt(β) - @kscal!(m, one(FC) / β, v) - MisI || @kscal!(m, one(FC) / β, Mv) - - # Start QR factorization - γbar = α - δbar = β - ϵold = zero(T) - cold = one(T) - sold = zero(T) - - ηold = zero(T) - η = β₁ - ζold = zero(T) - - ANorm² = α * α + β * β - - γmax = T(-Inf) - γmin = T(Inf) - ANorm = zero(T) - Acond = zero(T) - - xNorm = zero(T) - rNorm = β₁ - history && push!(rNorms, rNorm) - - if γbar ≠ 0 - ζbar = η / γbar - xcgNorm = abs(ζbar) - rcgNorm = β₁ * abs(ζbar) - history && push!(rcgNorms, rcgNorm) - else - history && push!(rcgNorms, missing) - end - err = T(Inf) - errcg = T(Inf) - - window = length(clist) - clist .= zero(T) - zlist .= zero(T) - sprod .= one(T) - - if λest ≠ 0 - # Start QR factorization of Tₖ - λest I - ρbar = α - λest - σbar = β - ρ = sqrt(ρbar * ρbar + β * β) - cwold = -one(T) - cw = ρbar / ρ - sw = β / ρ - - history && push!(errors, abs(β₁/λest)) - if γbar ≠ 0 - history && push!(errorscg, sqrt(errors[1]^2 - ζbar^2)) + function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + m == n || error("System must be square") + length(b) == m || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "SYMMLQ: system of size %d\n", n) + + # Tests M = Iₙ + MisI = (M === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + + # Set up workspace. + allocate_if(!MisI, solver, :v, S, n) + x, Mvold, Mv, Mv_next, w̅ = solver.x, solver.Mvold, solver.Mv, solver.Mv_next, solver.w̅ + Δx, clist, zlist, sprod, stats = solver.Δx, solver.clist, solver.zlist, solver.sprod, solver.stats + warm_start = solver.warm_start + rNorms, rcgNorms = stats.residuals, stats.residualscg + errors, errorscg = stats.errors, stats.errorscg + reset!(stats) + v = MisI ? Mv : solver.v + vold = MisI ? Mvold : solver.v + + ϵM = eps(T) + ctol = conlim > 0 ? 1 / conlim : zero(T) + + # Initial solution x₀ + x .= zero(FC) + + if warm_start + mul!(Mvold, A, Δx) + (λ ≠ 0) && @kaxpy!(n, λ, Δx, Mvold) + @kaxpby!(n, one(FC), b, -one(FC), Mvold) else - history && push!(errorscg, missing) + Mvold .= b end - end - iter = 0 - itmax == 0 && (itmax = 2 * n) - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, β, cold, sold, ANorm, Acond) - - tol = atol + rtol * β₁ - status = "unknown" - solved_lq = solved_mach = solved_lim = (rNorm ≤ tol) - solved_cg = (γbar ≠ 0) && transfer_to_cg && rcgNorm ≤ tol - tired = iter ≥ itmax - ill_cond = ill_cond_mach = ill_cond_lim = false - solved = zero_resid = solved_lq || solved_cg - fwd_err = false - user_requested_exit = false - overtimed = false - - while ! (solved || tired || ill_cond || user_requested_exit || overtimed) - iter = iter + 1 - - # Continue QR factorization - (c, s, γ) = sym_givens(γbar, β) - - # Update SYMMLQ point - ηold = η - ζ = ηold / γ - @kaxpy!(n, c * ζ, w̅, x) - @kaxpy!(n, s * ζ, v, x) - # Update w̅ - @kaxpby!(n, -c, v, s, w̅) - - # Generate next Lanczos vector - oldβ = β - mul!(Mv_next, A, v) - α = @kdotr(m, v, Mv_next) + λ - @kaxpy!(m, -oldβ, Mvold, Mv_next) - @. Mvold = Mv - @kaxpy!(m, -α, Mv, Mv_next) - @. Mv = Mv_next + # Initialize Lanczos process. + # β₁ M v₁ = b. + MisI || mulorldiv!(vold, M, Mvold, ldiv) + β₁ = @kdotr(m, vold, Mvold) + if β₁ == 0 + stats.niter = 0 + stats.solved = true + stats.Anorm = T(NaN) + stats.Acond = T(NaN) + history && push!(rNorms, zero(T)) + history && push!(rcgNorms, zero(T)) + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver + end + β₁ = sqrt(β₁) + β = β₁ + @kscal!(m, one(FC) / β, vold) + MisI || @kscal!(m, one(FC) / β, Mvold) + + w̅ .= vold + + mul!(Mv, A, vold) + α = @kdotr(m, vold, Mv) + λ + @kaxpy!(m, -α, Mvold, Mv) # Mv = Mv - α * Mvold MisI || mulorldiv!(v, M, Mv, ldiv) β = @kdotr(m, v, Mv) β < 0 && error("Preconditioner is not positive definite") @@ -308,155 +200,258 @@ function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; @kscal!(m, one(FC) / β, v) MisI || @kscal!(m, one(FC) / β, Mv) - # Continue A norm estimate - ANorm² = ANorm² + α * α + oldβ * oldβ + β * β + # Start QR factorization + γbar = α + δbar = β + ϵold = zero(T) + cold = one(T) + sold = zero(T) - if λest ≠ 0 - η = -oldβ * oldβ * cwold / ρbar - ω = λest + η - ψ = c * δbar + s * ω - ωbar = s * δbar - c * ω - end + ηold = zero(T) + η = β₁ + ζold = zero(T) + + ANorm² = α * α + β * β - # Continue QR factorization - δ = δbar * c + α * s - γbar = δbar * s - α * c - ϵ = β * s - δbar = -β * c - η = -ϵold * ζold - δ * ζ + γmax = T(-Inf) + γmin = T(Inf) + ANorm = zero(T) + Acond = zero(T) - rNorm = sqrt(γ * γ * ζ * ζ + ϵold * ϵold * ζold * ζold) - xNorm = xNorm + ζ * ζ + xNorm = zero(T) + rNorm = β₁ history && push!(rNorms, rNorm) if γbar ≠ 0 ζbar = η / γbar - rcgNorm = β * abs(s * ζ - c * ζbar) - xcgNorm = xNorm + ζbar * ζbar + xcgNorm = abs(ζbar) + rcgNorm = β₁ * abs(ζbar) history && push!(rcgNorms, rcgNorm) else history && push!(rcgNorms, missing) end - if window > 0 && λest ≠ 0 - if iter < window && window > 1 - for i = iter+1 : window - sprod[i] = s * sprod[i] - end - end + err = T(Inf) + errcg = T(Inf) - ix = ((iter-1) % window) + 1 - clist[ix] = c - zlist[ix] = ζ + window = length(clist) + clist .= zero(T) + zlist .= zero(T) + sprod .= one(T) - if iter ≥ window - jx = mod(iter, window) + 1 - zetabark = zlist[jx] / clist[jx] + if λest ≠ 0 + # Start QR factorization of Tₖ - λest I + ρbar = α - λest + σbar = β + ρ = sqrt(ρbar * ρbar + β * β) + cwold = -one(T) + cw = ρbar / ρ + sw = β / ρ - if γbar ≠ 0 - theta = zero(T) - for i = 1 : window - theta += clist[i] * sprod[i] * zlist[i] - end - theta = zetabark * abs(theta) + abs(zetabark * ζbar * sprod[ix] * s) - zetabark^2 - history && (errorscg[iter-window+1] = sqrt(abs(errorscg[iter-window+1]^2 - 2*theta))) - else - history && (errorscg[iter-window+1] = missing) - end + history && push!(errors, abs(β₁/λest)) + if γbar ≠ 0 + history && push!(errorscg, sqrt(errors[1]^2 - ζbar^2)) + else + history && push!(errorscg, missing) end + end + + iter = 0 + itmax == 0 && (itmax = 2 * n) - ix = (iter % window) + 1 - if iter ≥ window && window > 1 - sprod .= sprod ./ sprod[(ix % window) + 1] - sprod[ix] = sprod[mod(ix-2, window)+1] * s + (verbose > 0) && @printf(iostream, "%5s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, β, cold, sold, ANorm, Acond) + + tol = atol + rtol * β₁ + status = "unknown" + solved_lq = solved_mach = solved_lim = (rNorm ≤ tol) + solved_cg = (γbar ≠ 0) && transfer_to_cg && rcgNorm ≤ tol + tired = iter ≥ itmax + ill_cond = ill_cond_mach = ill_cond_lim = false + solved = zero_resid = solved_lq || solved_cg + fwd_err = false + user_requested_exit = false + overtimed = false + + while ! (solved || tired || ill_cond || user_requested_exit || overtimed) + iter = iter + 1 + + # Continue QR factorization + (c, s, γ) = sym_givens(γbar, β) + + # Update SYMMLQ point + ηold = η + ζ = ηold / γ + @kaxpy!(n, c * ζ, w̅, x) + @kaxpy!(n, s * ζ, v, x) + # Update w̅ + @kaxpby!(n, -c, v, s, w̅) + + # Generate next Lanczos vector + oldβ = β + mul!(Mv_next, A, v) + α = @kdotr(m, v, Mv_next) + λ + @kaxpy!(m, -oldβ, Mvold, Mv_next) + @. Mvold = Mv + @kaxpy!(m, -α, Mv, Mv_next) + @. Mv = Mv_next + MisI || mulorldiv!(v, M, Mv, ldiv) + β = @kdotr(m, v, Mv) + β < 0 && error("Preconditioner is not positive definite") + β = sqrt(β) + @kscal!(m, one(FC) / β, v) + MisI || @kscal!(m, one(FC) / β, Mv) + + # Continue A norm estimate + ANorm² = ANorm² + α * α + oldβ * oldβ + β * β + + if λest ≠ 0 + η = -oldβ * oldβ * cwold / ρbar + ω = λest + η + ψ = c * δbar + s * ω + ωbar = s * δbar - c * ω end - end - if λest ≠ 0 - err = abs((ϵold * ζold + ψ * ζ) / ωbar) - history && push!(errors, err) + # Continue QR factorization + δ = δbar * c + α * s + γbar = δbar * s - α * c + ϵ = β * s + δbar = -β * c + η = -ϵold * ζold - δ * ζ + + rNorm = sqrt(γ * γ * ζ * ζ + ϵold * ϵold * ζold * ζold) + xNorm = xNorm + ζ * ζ + history && push!(rNorms, rNorm) if γbar ≠ 0 - errcg = sqrt(abs(err * err - ζbar * ζbar)) - history && push!(errorscg, errcg) + ζbar = η / γbar + rcgNorm = β * abs(s * ζ - c * ζbar) + xcgNorm = xNorm + ζbar * ζbar + history && push!(rcgNorms, rcgNorm) else - history && push!(errorscg, missing) + history && push!(rcgNorms, missing) end - ρbar = sw * σbar - cw * (α - λest) - σbar = -cw * β - ρ = sqrt(ρbar * ρbar + β * β) + if window > 0 && λest ≠ 0 + if iter < window && window > 1 + for i = iter+1 : window + sprod[i] = s * sprod[i] + end + end - cwold = cw + ix = ((iter-1) % window) + 1 + clist[ix] = c + zlist[ix] = ζ + + if iter ≥ window + jx = mod(iter, window) + 1 + zetabark = zlist[jx] / clist[jx] + + if γbar ≠ 0 + theta = zero(T) + for i = 1 : window + theta += clist[i] * sprod[i] * zlist[i] + end + theta = zetabark * abs(theta) + abs(zetabark * ζbar * sprod[ix] * s) - zetabark^2 + history && (errorscg[iter-window+1] = sqrt(abs(errorscg[iter-window+1]^2 - 2*theta))) + else + history && (errorscg[iter-window+1] = missing) + end + end - cw = ρbar / ρ - sw = β / ρ - end + ix = (iter % window) + 1 + if iter ≥ window && window > 1 + sprod .= sprod ./ sprod[(ix % window) + 1] + sprod[ix] = sprod[mod(ix-2, window)+1] * s + end + end - # TODO: Use γ or γbar? - γmax = max(γmax, γ) - γmin = min(γmin, γ) + if λest ≠ 0 + err = abs((ϵold * ζold + ψ * ζ) / ωbar) + history && push!(errors, err) - Acond = γmax / γmin - ANorm = sqrt(ANorm²) - test1 = rNorm / (ANorm * xNorm) + if γbar ≠ 0 + errcg = sqrt(abs(err * err - ζbar * ζbar)) + history && push!(errorscg, errcg) + else + history && push!(errorscg, missing) + end - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, β, c, s, ANorm, Acond, test1) + ρbar = sw * σbar - cw * (α - λest) + σbar = -cw * β + ρ = sqrt(ρbar * ρbar + β * β) - # Reset variables - ϵold = ϵ - ζold = ζ - cold = c + cwold = cw - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (one(T) + rNorm ≤ one(T)) - ill_cond_mach = (one(T) + one(T) / Acond ≤ one(T)) - zero_resid_mach = (one(T) + test1 ≤ one(T)) - # solved_mach = (ϵx ≥ β₁) + cw = ρbar / ρ + sw = β / ρ + end - # Stopping conditions based on user-provided tolerances. - tired = iter ≥ itmax - ill_cond_lim = (one(T) / Acond ≤ ctol) - zero_resid_lim = (test1 ≤ tol) - fwd_err = (err ≤ etol) || ((γbar ≠ 0) && (errcg ≤ etol)) - solved_lq = rNorm ≤ tol - solved_cg = transfer_to_cg && (γbar ≠ 0) && rcgNorm ≤ tol - - user_requested_exit = callback(solver) :: Bool - zero_resid = solved_lq || solved_cg - ill_cond = ill_cond_mach || ill_cond_lim - solved = solved_mach || zero_resid || zero_resid_mach || zero_resid_lim || fwd_err || resid_decrease_mach - timer = time_ns() - start_time - overtimed = timer > timemax_ns - end - (verbose > 0) && @printf(iostream, "\n") + # TODO: Use γ or γbar? + γmax = max(γmax, γ) + γmin = min(γmin, γ) + + Acond = γmax / γmin + ANorm = sqrt(ANorm²) + test1 = rNorm / (ANorm * xNorm) + + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, β, c, s, ANorm, Acond, test1) + + # Reset variables + ϵold = ϵ + ζold = ζ + cold = c + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (one(T) + rNorm ≤ one(T)) + ill_cond_mach = (one(T) + one(T) / Acond ≤ one(T)) + zero_resid_mach = (one(T) + test1 ≤ one(T)) + # solved_mach = (ϵx ≥ β₁) + + # Stopping conditions based on user-provided tolerances. + tired = iter ≥ itmax + ill_cond_lim = (one(T) / Acond ≤ ctol) + zero_resid_lim = (test1 ≤ tol) + fwd_err = (err ≤ etol) || ((γbar ≠ 0) && (errcg ≤ etol)) + solved_lq = rNorm ≤ tol + solved_cg = transfer_to_cg && (γbar ≠ 0) && rcgNorm ≤ tol + + user_requested_exit = callback(solver) :: Bool + zero_resid = solved_lq || solved_cg + ill_cond = ill_cond_mach || ill_cond_lim + solved = solved_mach || zero_resid || zero_resid_mach || zero_resid_lim || fwd_err || resid_decrease_mach + timer = time_ns() - start_time + overtimed = timer > timemax_ns + end + (verbose > 0) && @printf(iostream, "\n") - # Compute CG point - # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * w̅ₖ - if solved_cg - @kaxpy!(m, ζbar, w̅, x) - end + # Compute CG point + # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * w̅ₖ + if solved_cg + @kaxpy!(m, ζbar, w̅, x) + end - # Termination status - tired && (status = "maximum number of iterations exceeded") - ill_cond_mach && (status = "condition number seems too large for this machine") - ill_cond_lim && (status = "condition number exceeds tolerance") - solved && (status = "found approximate solution") - solved_lq && (status = "solution xᴸ good enough given atol and rtol") - solved_cg && (status = "solution xᶜ good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.Anorm = ANorm - stats.Acond = Acond - stats.status = status - return solver + # Termination status + tired && (status = "maximum number of iterations exceeded") + ill_cond_mach && (status = "condition number seems too large for this machine") + ill_cond_lim && (status = "condition number exceeds tolerance") + solved && (status = "found approximate solution") + solved_lq && (status = "solution xᴸ good enough given atol and rtol") + solved_cg && (status = "solution xᶜ good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.Anorm = ANorm + stats.Acond = Acond + stats.status = status + return solver + end end diff --git a/src/tricg.jl b/src/tricg.jl index ae93b69b3..145855f9d 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -137,333 +137,327 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax return (solver.x, solver.y, solver.stats) end - function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) tricg!(solver, A, b, c; $(kwargs_tricg...)) return solver end -end - -function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - spd :: Bool=false, snd :: Bool=false, - flip :: Bool=false, τ :: T=one(T), - ν :: T=-one(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "TriCG: system of %d equations in %d variables\n", m+n, m+n) - - # Check flip, spd and snd parameters - spd && flip && error("The matrix cannot be SPD and SQD") - snd && flip && error("The matrix cannot be SND and SQD") - spd && snd && error("The matrix cannot be SPD and SND") - - # Check M = Iₘ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Determine τ and ν associated to SQD, SPD or SND systems. - flip && (τ = -one(T) ; ν = one(T)) - spd && (τ = one(T) ; ν = one(T)) - snd && (τ = -one(T) ; ν = -one(T)) - - warm_start = solver.warm_start - warm_start && (τ ≠ 0) && !MisI && error("Warm-start with preconditioners is not supported.") - warm_start && (ν ≠ 0) && !NisI && error("Warm-start with preconditioners is not supported.") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :vₖ, S, m) - allocate_if(!NisI, solver, :uₖ, S, n) - Δy, yₖ, N⁻¹uₖ₋₁, N⁻¹uₖ, p = solver.Δy, solver.y, solver.N⁻¹uₖ₋₁, solver.N⁻¹uₖ, solver.p - Δx, xₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, q = solver.Δx, solver.x, solver.M⁻¹vₖ₋₁, solver.M⁻¹vₖ, solver.q - gy₂ₖ₋₁, gy₂ₖ, gx₂ₖ₋₁, gx₂ₖ = solver.gy₂ₖ₋₁, solver.gy₂ₖ, solver.gx₂ₖ₋₁, solver.gx₂ₖ - vₖ = MisI ? M⁻¹vₖ : solver.vₖ - uₖ = NisI ? N⁻¹uₖ : solver.uₖ - vₖ₊₁ = MisI ? q : vₖ - uₖ₊₁ = NisI ? p : uₖ - b₀ = warm_start ? q : b - c₀ = warm_start ? p : c - - stats = solver.stats - rNorms = stats.residuals - reset!(stats) - - # Initial solutions x₀ and y₀. - xₖ .= zero(FC) - yₖ .= zero(FC) - - iter = 0 - itmax == 0 && (itmax = m+n) - - # Initialize preconditioned orthogonal tridiagonalization process. - M⁻¹vₖ₋₁ .= zero(FC) # v₀ = 0 - N⁻¹uₖ₋₁ .= zero(FC) # u₀ = 0 - - # [ τI A ] [ xₖ ] = [ b - τΔx - AΔy ] = [ b₀ ] - # [ Aᴴ νI ] [ yₖ ] [ c - AᴴΔx - νΔy ] [ c₀ ] - if warm_start - mul!(b₀, A, Δy) - (τ ≠ 0) && @kaxpy!(m, τ, Δx, b₀) - @kaxpby!(m, one(FC), b, -one(FC), b₀) - mul!(c₀, Aᴴ, Δx) - (ν ≠ 0) && @kaxpy!(n, ν, Δy, c₀) - @kaxpby!(n, one(FC), c, -one(FC), c₀) - end - - # β₁Ev₁ = b ↔ β₁v₁ = Mb - M⁻¹vₖ .= b₀ - MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv) - βₖ = sqrt(@kdotr(m, vₖ, M⁻¹vₖ)) # β₁ = ‖v₁‖_E - if βₖ ≠ 0 - @kscal!(m, one(FC) / βₖ, M⁻¹vₖ) - MisI || @kscal!(m, one(FC) / βₖ, vₖ) - else - error("b must be nonzero") - end - # γ₁Fu₁ = c ↔ γ₁u₁ = Nc - N⁻¹uₖ .= c₀ - NisI || mulorldiv!(uₖ, N, N⁻¹uₖ, ldiv) - γₖ = sqrt(@kdotr(n, uₖ, N⁻¹uₖ)) # γ₁ = ‖u₁‖_F - if γₖ ≠ 0 - @kscal!(n, one(FC) / γₖ, N⁻¹uₖ) - NisI || @kscal!(n, one(FC) / γₖ, uₖ) - else - error("c must be nonzero") - end - - # Initialize directions Gₖ such that L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ - gx₂ₖ₋₁ .= zero(FC) - gy₂ₖ₋₁ .= zero(FC) - gx₂ₖ .= zero(FC) - gy₂ₖ .= zero(FC) - - # Compute ‖r₀‖² = (γ₁)² + (β₁)² - rNorm = sqrt(γₖ^2 + βₖ^2) - history && push!(rNorms, rNorm) - ε = atol + rtol * rNorm - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) - - # Set up workspace. - d₂ₖ₋₃ = d₂ₖ₋₂ = zero(T) - π₂ₖ₋₃ = π₂ₖ₋₂ = zero(FC) - δₖ₋₁ = zero(FC) - - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) - - # Stopping criterion. - breakdown = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || breakdown || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the orthogonal tridiagonalization process. - # AUₖ = EVₖTₖ + βₖ₊₁Evₖ₊₁(eₖ)ᵀ = EVₖ₊₁Tₖ₊₁.ₖ - # AᴴVₖ = FUₖ(Tₖ)ᴴ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , uₖ) # Forms Evₖ₊₁ : q ← Auₖ - mul!(p, Aᴴ, vₖ) # Forms Fuₖ₊₁ : p ← Aᴴvₖ - - if iter ≥ 2 - @kaxpy!(m, -γₖ, M⁻¹vₖ₋₁, q) # q ← q - γₖ * M⁻¹vₖ₋₁ - @kaxpy!(n, -βₖ, N⁻¹uₖ₋₁, p) # p ← p - βₖ * N⁻¹uₖ₋₁ - end - - αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ - - @kaxpy!(m, - αₖ , M⁻¹vₖ, q) # q ← q - αₖ * M⁻¹vₖ - @kaxpy!(n, -conj(αₖ), N⁻¹uₖ, p) # p ← p - ᾱₖ * N⁻¹uₖ - - # Update M⁻¹vₖ₋₁ and N⁻¹uₖ₋₁ - M⁻¹vₖ₋₁ .= M⁻¹vₖ - N⁻¹uₖ₋₁ .= N⁻¹uₖ - - # Notations : Wₖ = [w₁ ••• wₖ] = [v₁ 0 ••• vₖ 0 ] - # [0 u₁ ••• 0 uₖ] - # - # rₖ = [ b ] - [ τE A ] [ xₖ ] = [ b ] - [ τE A ] Wₖzₖ - # [ c ] [ Aᴴ νF ] [ yₖ ] [ c ] [ Aᴴ νF ] - # - # block-Lanczos formulation : [ τE A ] Wₖ = [ E 0 ] Wₖ₊₁Sₖ₊₁.ₖ - # [ Aᴴ νF ] [ 0 F ] - # - # TriCG subproblem : (Wₖ)ᴴ * rₖ = 0 ↔ Sₖ.ₖzₖ = β₁e₁ + γ₁e₂ - # - # Update the LDLᴴ factorization of Sₖ.ₖ. - # - # [ τ α₁ γ₂ 0 • • • • 0 ] - # [ ᾱ₁ ν β₂ • • ] - # [ β₂ τ α₂ γ₃ • • ] - # [ γ₂ ᾱ₂ ν β₃ • • ] - # [ 0 β₃ • • • • • ] - # [ • • γ₃ • • • 0 ] - # [ • • • • • γₖ ] - # [ • • • • • βₖ ] - # [ • • βₖ τ αₖ ] - # [ 0 • • • • 0 γₖ ᾱₖ ν ] - if iter == 1 - d₂ₖ₋₁ = τ - δₖ = conj(αₖ) / d₂ₖ₋₁ - d₂ₖ = ν - abs2(δₖ) * d₂ₖ₋₁ - else - σₖ = βₖ / d₂ₖ₋₂ - ηₖ = γₖ / d₂ₖ₋₃ - λₖ = -(ηₖ * conj(δₖ₋₁) * d₂ₖ₋₃) / d₂ₖ₋₂ - d₂ₖ₋₁ = τ - abs2(σₖ) * d₂ₖ₋₂ - δₖ = (conj(αₖ) - λₖ * conj(σₖ) * d₂ₖ₋₂) / d₂ₖ₋₁ - d₂ₖ = ν - abs2(ηₖ) * d₂ₖ₋₃ - abs2(λₖ) * d₂ₖ₋₂ - abs2(δₖ) * d₂ₖ₋₁ + function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "TriCG: system of %d equations in %d variables\n", m+n, m+n) + + # Check flip, spd and snd parameters + spd && flip && error("The matrix cannot be SPD and SQD") + snd && flip && error("The matrix cannot be SND and SQD") + spd && snd && error("The matrix cannot be SPD and SND") + + # Check M = Iₘ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Determine τ and ν associated to SQD, SPD or SND systems. + flip && (τ = -one(T) ; ν = one(T)) + spd && (τ = one(T) ; ν = one(T)) + snd && (τ = -one(T) ; ν = -one(T)) + + warm_start = solver.warm_start + warm_start && (τ ≠ 0) && !MisI && error("Warm-start with preconditioners is not supported.") + warm_start && (ν ≠ 0) && !NisI && error("Warm-start with preconditioners is not supported.") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :vₖ, S, m) + allocate_if(!NisI, solver, :uₖ, S, n) + Δy, yₖ, N⁻¹uₖ₋₁, N⁻¹uₖ, p = solver.Δy, solver.y, solver.N⁻¹uₖ₋₁, solver.N⁻¹uₖ, solver.p + Δx, xₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, q = solver.Δx, solver.x, solver.M⁻¹vₖ₋₁, solver.M⁻¹vₖ, solver.q + gy₂ₖ₋₁, gy₂ₖ, gx₂ₖ₋₁, gx₂ₖ = solver.gy₂ₖ₋₁, solver.gy₂ₖ, solver.gx₂ₖ₋₁, solver.gx₂ₖ + vₖ = MisI ? M⁻¹vₖ : solver.vₖ + uₖ = NisI ? N⁻¹uₖ : solver.uₖ + vₖ₊₁ = MisI ? q : vₖ + uₖ₊₁ = NisI ? p : uₖ + b₀ = warm_start ? q : b + c₀ = warm_start ? p : c + + stats = solver.stats + rNorms = stats.residuals + reset!(stats) + + # Initial solutions x₀ and y₀. + xₖ .= zero(FC) + yₖ .= zero(FC) + + iter = 0 + itmax == 0 && (itmax = m+n) + + # Initialize preconditioned orthogonal tridiagonalization process. + M⁻¹vₖ₋₁ .= zero(FC) # v₀ = 0 + N⁻¹uₖ₋₁ .= zero(FC) # u₀ = 0 + + # [ τI A ] [ xₖ ] = [ b - τΔx - AΔy ] = [ b₀ ] + # [ Aᴴ νI ] [ yₖ ] [ c - AᴴΔx - νΔy ] [ c₀ ] + if warm_start + mul!(b₀, A, Δy) + (τ ≠ 0) && @kaxpy!(m, τ, Δx, b₀) + @kaxpby!(m, one(FC), b, -one(FC), b₀) + mul!(c₀, Aᴴ, Δx) + (ν ≠ 0) && @kaxpy!(n, ν, Δy, c₀) + @kaxpby!(n, one(FC), c, -one(FC), c₀) end - # Solve LₖDₖpₖ = (β₁e₁ + γ₁e₂) - # - # [ 1 0 • • • • • • • 0 ] [ d₁ ] [ β₁ ] - # [ δ₁ 1 • • ] [ d₂ ] [ γ₁ ] - # [ σ₂ 1 • • ] [ • ] [ 0 ] - # [ η₂ λ₂ δ₂ 1 • • ] [ • ] [ • ] - # [ 0 σ₃ 1 • • ] [ • ] zₖ = [ • ] - # [ • • η₃ λ₃ δ₃ 1 • • ] [ • ] [ • ] - # [ • • • • • • ] [ • ] [ • ] - # [ • • • • • • • • ] [ • ] [ • ] - # [ • • σₖ 1 0 ] [ d₂ₖ₋₁ ] [ • ] - # [ 0 • • • • 0 ηₖ λₖ δₖ 1 ] [ d₂ₖ] [ 0 ] - if iter == 1 - π₂ₖ₋₁ = βₖ / d₂ₖ₋₁ - π₂ₖ = (γₖ - δₖ * βₖ) / d₂ₖ + # β₁Ev₁ = b ↔ β₁v₁ = Mb + M⁻¹vₖ .= b₀ + MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv) + βₖ = sqrt(@kdotr(m, vₖ, M⁻¹vₖ)) # β₁ = ‖v₁‖_E + if βₖ ≠ 0 + @kscal!(m, one(FC) / βₖ, M⁻¹vₖ) + MisI || @kscal!(m, one(FC) / βₖ, vₖ) else - π₂ₖ₋₁ = -(σₖ * d₂ₖ₋₂ * π₂ₖ₋₂) / d₂ₖ₋₁ - π₂ₖ = -(δₖ * d₂ₖ₋₁ * π₂ₖ₋₁ + λₖ * d₂ₖ₋₂ * π₂ₖ₋₂ + ηₖ * d₂ₖ₋₃ * π₂ₖ₋₃) / d₂ₖ + error("b must be nonzero") end - # Solve Gₖ = Wₖ(Lₖ)⁻ᴴ ⟷ L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ. - if iter == 1 - # [ 1 0 ] [ gx₁ gy₁ ] = [ v₁ 0 ] - # [ δ̄₁ 1 ] [ gx₂ gy₂ ] [ 0 u₁ ] - @. gx₂ₖ₋₁ = vₖ - @. gx₂ₖ = - conj(δₖ) * gx₂ₖ₋₁ - @. gy₂ₖ = uₖ + # γ₁Fu₁ = c ↔ γ₁u₁ = Nc + N⁻¹uₖ .= c₀ + NisI || mulorldiv!(uₖ, N, N⁻¹uₖ, ldiv) + γₖ = sqrt(@kdotr(n, uₖ, N⁻¹uₖ)) # γ₁ = ‖u₁‖_F + if γₖ ≠ 0 + @kscal!(n, one(FC) / γₖ, N⁻¹uₖ) + NisI || @kscal!(n, one(FC) / γₖ, uₖ) else - # [ 0 σ̄ₖ 1 0 ] [ gx₂ₖ₋₃ gy₂ₖ₋₃ ] = [ vₖ 0 ] - # [ η̄ₖ λ̄ₖ δ̄ₖ 1 ] [ gx₂ₖ₋₂ gy₂ₖ₋₂ ] [ 0 uₖ ] - # [ gx₂ₖ₋₁ gy₂ₖ₋₁ ] - # [ gx₂ₖ gy₂ₖ ] - @. gx₂ₖ₋₁ = conj(ηₖ) * gx₂ₖ₋₁ + conj(λₖ) * gx₂ₖ - @. gy₂ₖ₋₁ = conj(ηₖ) * gy₂ₖ₋₁ + conj(λₖ) * gy₂ₖ - - @. gx₂ₖ = vₖ - conj(σₖ) * gx₂ₖ - @. gy₂ₖ = - conj(σₖ) * gy₂ₖ - - @. gx₂ₖ₋₁ = - gx₂ₖ₋₁ - conj(δₖ) * gx₂ₖ - @. gy₂ₖ₋₁ = uₖ - gy₂ₖ₋₁ - conj(δₖ) * gy₂ₖ - - # g₂ₖ₋₃ == g₂ₖ and g₂ₖ₋₂ == g₂ₖ₋₁ - @kswap(gx₂ₖ₋₁, gx₂ₖ) - @kswap(gy₂ₖ₋₁, gy₂ₖ) + error("c must be nonzero") end - # Update xₖ = Gxₖ * pₖ - @kaxpy!(m, π₂ₖ₋₁, gx₂ₖ₋₁, xₖ) - @kaxpy!(m, π₂ₖ , gx₂ₖ , xₖ) + # Initialize directions Gₖ such that L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ + gx₂ₖ₋₁ .= zero(FC) + gy₂ₖ₋₁ .= zero(FC) + gx₂ₖ .= zero(FC) + gy₂ₖ .= zero(FC) - # Update yₖ = Gyₖ * pₖ - @kaxpy!(n, π₂ₖ₋₁, gy₂ₖ₋₁, yₖ) - @kaxpy!(n, π₂ₖ , gy₂ₖ , yₖ) - - # Compute vₖ₊₁ and uₖ₊₁ - MisI || mulorldiv!(vₖ₊₁, M, q, ldiv) # βₖ₊₁vₖ₊₁ = MAuₖ - γₖvₖ₋₁ - αₖvₖ - NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᴴvₖ - βₖuₖ₋₁ - ᾱₖuₖ + # Compute ‖r₀‖² = (γ₁)² + (β₁)² + rNorm = sqrt(γₖ^2 + βₖ^2) + history && push!(rNorms, rNorm) + ε = atol + rtol * rNorm - βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, q)) # βₖ₊₁ = ‖vₖ₊₁‖_E - γₖ₊₁ = sqrt(@kdotr(n, uₖ₊₁, p)) # γₖ₊₁ = ‖uₖ₊₁‖_F + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) - # βₖ₊₁ ≠ 0 - if βₖ₊₁ > btol - @kscal!(m, one(FC) / βₖ₊₁, q) - MisI || @kscal!(m, one(FC) / βₖ₊₁, vₖ₊₁) - end - - # γₖ₊₁ ≠ 0 - if γₖ₊₁ > btol - @kscal!(n, one(FC) / γₖ₊₁, p) - NisI || @kscal!(n, one(FC) / γₖ₊₁, uₖ₊₁) - end + # Set up workspace. + d₂ₖ₋₃ = d₂ₖ₋₂ = zero(T) + π₂ₖ₋₃ = π₂ₖ₋₂ = zero(FC) + δₖ₋₁ = zero(FC) - # Update M⁻¹vₖ and N⁻¹uₖ - M⁻¹vₖ .= q - N⁻¹uₖ .= p + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) - # Compute ‖rₖ‖² = |γₖ₊₁ζ₂ₖ₋₁|² + |βₖ₊₁ζ₂ₖ|² - ζ₂ₖ₋₁ = π₂ₖ₋₁ - conj(δₖ) * π₂ₖ - ζ₂ₖ = π₂ₖ - rNorm = sqrt(abs2(γₖ₊₁ * ζ₂ₖ₋₁) + abs2(βₖ₊₁ * ζ₂ₖ)) - history && push!(rNorms, rNorm) - - # Update βₖ, γₖ, π₂ₖ₋₃, π₂ₖ₋₂, d₂ₖ₋₃, d₂ₖ₋₂, δₖ₋₁, vₖ, uₖ. - βₖ = βₖ₊₁ - γₖ = γₖ₊₁ - π₂ₖ₋₃ = π₂ₖ₋₁ - π₂ₖ₋₂ = π₂ₖ - d₂ₖ₋₃ = d₂ₖ₋₁ - d₂ₖ₋₂ = d₂ₖ - δₖ₋₁ = δₖ - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol - solved = resid_decrease_lim || resid_decrease_mach + # Stopping criterion. + breakdown = false + solved = rNorm ≤ ε tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || breakdown || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 + + # Continue the orthogonal tridiagonalization process. + # AUₖ = EVₖTₖ + βₖ₊₁Evₖ₊₁(eₖ)ᵀ = EVₖ₊₁Tₖ₊₁.ₖ + # AᴴVₖ = FUₖ(Tₖ)ᴴ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᴴ + + mul!(q, A , uₖ) # Forms Evₖ₊₁ : q ← Auₖ + mul!(p, Aᴴ, vₖ) # Forms Fuₖ₊₁ : p ← Aᴴvₖ + + if iter ≥ 2 + @kaxpy!(m, -γₖ, M⁻¹vₖ₋₁, q) # q ← q - γₖ * M⁻¹vₖ₋₁ + @kaxpy!(n, -βₖ, N⁻¹uₖ₋₁, p) # p ← p - βₖ * N⁻¹uₖ₋₁ + end + + αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ + + @kaxpy!(m, - αₖ , M⁻¹vₖ, q) # q ← q - αₖ * M⁻¹vₖ + @kaxpy!(n, -conj(αₖ), N⁻¹uₖ, p) # p ← p - ᾱₖ * N⁻¹uₖ + + # Update M⁻¹vₖ₋₁ and N⁻¹uₖ₋₁ + M⁻¹vₖ₋₁ .= M⁻¹vₖ + N⁻¹uₖ₋₁ .= N⁻¹uₖ + + # Notations : Wₖ = [w₁ ••• wₖ] = [v₁ 0 ••• vₖ 0 ] + # [0 u₁ ••• 0 uₖ] + # + # rₖ = [ b ] - [ τE A ] [ xₖ ] = [ b ] - [ τE A ] Wₖzₖ + # [ c ] [ Aᴴ νF ] [ yₖ ] [ c ] [ Aᴴ νF ] + # + # block-Lanczos formulation : [ τE A ] Wₖ = [ E 0 ] Wₖ₊₁Sₖ₊₁.ₖ + # [ Aᴴ νF ] [ 0 F ] + # + # TriCG subproblem : (Wₖ)ᴴ * rₖ = 0 ↔ Sₖ.ₖzₖ = β₁e₁ + γ₁e₂ + # + # Update the LDLᴴ factorization of Sₖ.ₖ. + # + # [ τ α₁ γ₂ 0 • • • • 0 ] + # [ ᾱ₁ ν β₂ • • ] + # [ β₂ τ α₂ γ₃ • • ] + # [ γ₂ ᾱ₂ ν β₃ • • ] + # [ 0 β₃ • • • • • ] + # [ • • γ₃ • • • 0 ] + # [ • • • • • γₖ ] + # [ • • • • • βₖ ] + # [ • • βₖ τ αₖ ] + # [ 0 • • • • 0 γₖ ᾱₖ ν ] + if iter == 1 + d₂ₖ₋₁ = τ + δₖ = conj(αₖ) / d₂ₖ₋₁ + d₂ₖ = ν - abs2(δₖ) * d₂ₖ₋₁ + else + σₖ = βₖ / d₂ₖ₋₂ + ηₖ = γₖ / d₂ₖ₋₃ + λₖ = -(ηₖ * conj(δₖ₋₁) * d₂ₖ₋₃) / d₂ₖ₋₂ + d₂ₖ₋₁ = τ - abs2(σₖ) * d₂ₖ₋₂ + δₖ = (conj(αₖ) - λₖ * conj(σₖ) * d₂ₖ₋₂) / d₂ₖ₋₁ + d₂ₖ = ν - abs2(ηₖ) * d₂ₖ₋₃ - abs2(λₖ) * d₂ₖ₋₂ - abs2(δₖ) * d₂ₖ₋₁ + end + + # Solve LₖDₖpₖ = (β₁e₁ + γ₁e₂) + # + # [ 1 0 • • • • • • • 0 ] [ d₁ ] [ β₁ ] + # [ δ₁ 1 • • ] [ d₂ ] [ γ₁ ] + # [ σ₂ 1 • • ] [ • ] [ 0 ] + # [ η₂ λ₂ δ₂ 1 • • ] [ • ] [ • ] + # [ 0 σ₃ 1 • • ] [ • ] zₖ = [ • ] + # [ • • η₃ λ₃ δ₃ 1 • • ] [ • ] [ • ] + # [ • • • • • • ] [ • ] [ • ] + # [ • • • • • • • • ] [ • ] [ • ] + # [ • • σₖ 1 0 ] [ d₂ₖ₋₁ ] [ • ] + # [ 0 • • • • 0 ηₖ λₖ δₖ 1 ] [ d₂ₖ] [ 0 ] + if iter == 1 + π₂ₖ₋₁ = βₖ / d₂ₖ₋₁ + π₂ₖ = (γₖ - δₖ * βₖ) / d₂ₖ + else + π₂ₖ₋₁ = -(σₖ * d₂ₖ₋₂ * π₂ₖ₋₂) / d₂ₖ₋₁ + π₂ₖ = -(δₖ * d₂ₖ₋₁ * π₂ₖ₋₁ + λₖ * d₂ₖ₋₂ * π₂ₖ₋₂ + ηₖ * d₂ₖ₋₃ * π₂ₖ₋₃) / d₂ₖ + end + + # Solve Gₖ = Wₖ(Lₖ)⁻ᴴ ⟷ L̄ₖ(Gₖ)ᵀ = (Wₖ)ᵀ. + if iter == 1 + # [ 1 0 ] [ gx₁ gy₁ ] = [ v₁ 0 ] + # [ δ̄₁ 1 ] [ gx₂ gy₂ ] [ 0 u₁ ] + @. gx₂ₖ₋₁ = vₖ + @. gx₂ₖ = - conj(δₖ) * gx₂ₖ₋₁ + @. gy₂ₖ = uₖ + else + # [ 0 σ̄ₖ 1 0 ] [ gx₂ₖ₋₃ gy₂ₖ₋₃ ] = [ vₖ 0 ] + # [ η̄ₖ λ̄ₖ δ̄ₖ 1 ] [ gx₂ₖ₋₂ gy₂ₖ₋₂ ] [ 0 uₖ ] + # [ gx₂ₖ₋₁ gy₂ₖ₋₁ ] + # [ gx₂ₖ gy₂ₖ ] + @. gx₂ₖ₋₁ = conj(ηₖ) * gx₂ₖ₋₁ + conj(λₖ) * gx₂ₖ + @. gy₂ₖ₋₁ = conj(ηₖ) * gy₂ₖ₋₁ + conj(λₖ) * gy₂ₖ + + @. gx₂ₖ = vₖ - conj(σₖ) * gx₂ₖ + @. gy₂ₖ = - conj(σₖ) * gy₂ₖ + + @. gx₂ₖ₋₁ = - gx₂ₖ₋₁ - conj(δₖ) * gx₂ₖ + @. gy₂ₖ₋₁ = uₖ - gy₂ₖ₋₁ - conj(δₖ) * gy₂ₖ + + # g₂ₖ₋₃ == g₂ₖ and g₂ₖ₋₂ == g₂ₖ₋₁ + @kswap(gx₂ₖ₋₁, gx₂ₖ) + @kswap(gy₂ₖ₋₁, gy₂ₖ) + end + + # Update xₖ = Gxₖ * pₖ + @kaxpy!(m, π₂ₖ₋₁, gx₂ₖ₋₁, xₖ) + @kaxpy!(m, π₂ₖ , gx₂ₖ , xₖ) + + # Update yₖ = Gyₖ * pₖ + @kaxpy!(n, π₂ₖ₋₁, gy₂ₖ₋₁, yₖ) + @kaxpy!(n, π₂ₖ , gy₂ₖ , yₖ) + + # Compute vₖ₊₁ and uₖ₊₁ + MisI || mulorldiv!(vₖ₊₁, M, q, ldiv) # βₖ₊₁vₖ₊₁ = MAuₖ - γₖvₖ₋₁ - αₖvₖ + NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᴴvₖ - βₖuₖ₋₁ - ᾱₖuₖ + + βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, q)) # βₖ₊₁ = ‖vₖ₊₁‖_E + γₖ₊₁ = sqrt(@kdotr(n, uₖ₊₁, p)) # γₖ₊₁ = ‖uₖ₊₁‖_F + + # βₖ₊₁ ≠ 0 + if βₖ₊₁ > btol + @kscal!(m, one(FC) / βₖ₊₁, q) + MisI || @kscal!(m, one(FC) / βₖ₊₁, vₖ₊₁) + end + + # γₖ₊₁ ≠ 0 + if γₖ₊₁ > btol + @kscal!(n, one(FC) / γₖ₊₁, p) + NisI || @kscal!(n, one(FC) / γₖ₊₁, uₖ₊₁) + end + + # Update M⁻¹vₖ and N⁻¹uₖ + M⁻¹vₖ .= q + N⁻¹uₖ .= p + + # Compute ‖rₖ‖² = |γₖ₊₁ζ₂ₖ₋₁|² + |βₖ₊₁ζ₂ₖ|² + ζ₂ₖ₋₁ = π₂ₖ₋₁ - conj(δₖ) * π₂ₖ + ζ₂ₖ = π₂ₖ + rNorm = sqrt(abs2(γₖ₊₁ * ζ₂ₖ₋₁) + abs2(βₖ₊₁ * ζ₂ₖ)) + history && push!(rNorms, rNorm) + + # Update βₖ, γₖ, π₂ₖ₋₃, π₂ₖ₋₂, d₂ₖ₋₃, d₂ₖ₋₂, δₖ₋₁, vₖ, uₖ. + βₖ = βₖ₊₁ + γₖ = γₖ₊₁ + π₂ₖ₋₃ = π₂ₖ₋₁ + π₂ₖ₋₂ = π₂ₖ + d₂ₖ₋₃ = d₂ₖ₋₁ + d₂ₖ₋₂ = d₂ₖ + δₖ₋₁ = δₖ + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + end + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "inconsistent linear system") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x and y + warm_start && @kaxpy!(m, one(FC), Δx, xₖ) + warm_start && @kaxpy!(n, one(FC), Δy, yₖ) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = !solved && breakdown + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "inconsistent linear system") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x and y - warm_start && @kaxpy!(m, one(FC), Δx, xₖ) - warm_start && @kaxpy!(n, one(FC), Δy, yₖ) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = !solved && breakdown - stats.status = status - return solver end diff --git a/src/trilqr.jl b/src/trilqr.jl index b3d1774eb..5c1d5b35c 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -106,359 +106,356 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, return (solver.x, solver.y, solver.stats) end - function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return solver end -end - -function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "TRILQR: primal system of %d equations in %d variables\n", m, n) - (verbose > 0) && @printf(iostream, "TRILQR: dual system of %d equations in %d variables\n", n, m) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - uₖ₋₁, uₖ, p, d̅, x, stats = solver.uₖ₋₁, solver.uₖ, solver.p, solver.d̅, solver.x, solver.stats - vₖ₋₁, vₖ, q, t, wₖ₋₃, wₖ₋₂ = solver.vₖ₋₁, solver.vₖ, solver.q, solver.y, solver.wₖ₋₃, solver.wₖ₋₂ - Δx, Δy, warm_start = solver.Δx, solver.Δy, solver.warm_start - rNorms, sNorms = stats.residuals_primal, stats.residuals_dual - reset!(stats) - r₀ = warm_start ? q : b - s₀ = warm_start ? p : c - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - mul!(s₀, Aᴴ, Δy) - @kaxpby!(n, one(FC), c, -one(FC), s₀) - end - # Initial solution x₀ and residual r₀ = b - Ax₀. - x .= zero(FC) # x₀ - bNorm = @knrm2(m, r₀) # rNorm = ‖r₀‖ - - # Initial solution y₀ and residual s₀ = c - Aᴴy₀. - t .= zero(FC) # t₀ - cNorm = @knrm2(n, s₀) # sNorm = ‖s₀‖ - - iter = 0 - itmax == 0 && (itmax = m+n) - - history && push!(rNorms, bNorm) - history && push!(sNorms, cNorm) - εL = atol + rtol * bNorm - εQ = atol + rtol * cNorm - ξ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) - - # Set up workspace. - βₖ = @knrm2(m, r₀) # β₁ = ‖r₀‖ = ‖v₁‖ - γₖ = @knrm2(n, s₀) # γ₁ = ‖s₀‖ = ‖u₁‖ - vₖ₋₁ .= zero(FC) # v₀ = 0 - uₖ₋₁ .= zero(FC) # u₀ = 0 - vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= s₀ ./ γₖ # u₁ = (c - Aᴴy₀) / γ₁ - cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ - sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᴴ - ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ - ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ - δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations - ψbarₖ₋₁ = ψₖ₋₁ = zero(FC) # ψₖ₋₁ and ψbarₖ are the last components of h̅ₖ = Qₖγ₁e₁ - ϵₖ₋₃ = λₖ₋₂ = zero(FC) # Components of Lₖ₋₁ - wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Vₖ(Lₖ)⁻ᴴ - wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Lₖ)⁻ᴴ - - # Stopping criterion. - inconsistent = false - solved_lq = bNorm == 0 - solved_lq_tol = solved_lq_mach = false - solved_cg = solved_cg_tol = solved_cg_mach = false - solved_primal = solved_lq || solved_cg - solved_qr_tol = solved_qr_mach = false - solved_dual = cNorm == 0 - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !((solved_primal && solved_dual) || tired || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the SSY tridiagonalization process. - # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ - mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ - - @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ - @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ - - αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ - - @kaxpy!(m, - αₖ , vₖ, q) # q ← q - αₖ * vₖ - @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - - βₖ₊₁ = @knrm2(m, q) # βₖ₊₁ = ‖q‖ - γₖ₊₁ = @knrm2(n, p) # γₖ₊₁ = ‖p‖ - - # Update the LQ factorization of Tₖ = L̅ₖQₖ. - # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] - # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] - # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] - # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ - # [ • • • • • 0 ] [ • • • • • • • ] - # [ • • • • γₖ] [ • • • λₖ₋₂ δₖ₋₁ 0 ] - # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] - - if iter == 1 - δbarₖ = αₖ - elseif iter == 2 - # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] - # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - λₖ₋₁ = cₖ * βₖ + sₖ * αₖ - δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ - else - # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] - # [sₖ₋₁ -cₖ₋₁ 0] - # [ 0 0 1] - # - # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] - # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] - # [0 sₖ -cₖ] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - ϵₖ₋₂ = sₖ₋₁ * βₖ - λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ - δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "TRILQR: primal system of %d equations in %d variables\n", m, n) + (verbose > 0) && @printf(iostream, "TRILQR: dual system of %d equations in %d variables\n", n, m) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + uₖ₋₁, uₖ, p, d̅, x, stats = solver.uₖ₋₁, solver.uₖ, solver.p, solver.d̅, solver.x, solver.stats + vₖ₋₁, vₖ, q, t, wₖ₋₃, wₖ₋₂ = solver.vₖ₋₁, solver.vₖ, solver.q, solver.y, solver.wₖ₋₃, solver.wₖ₋₂ + Δx, Δy, warm_start = solver.Δx, solver.Δy, solver.warm_start + rNorms, sNorms = stats.residuals_primal, stats.residuals_dual + reset!(stats) + r₀ = warm_start ? q : b + s₀ = warm_start ? p : c + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) + mul!(s₀, Aᴴ, Δy) + @kaxpby!(n, one(FC), c, -one(FC), s₀) end - if !solved_primal - # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ - # [δbar₁] [ζbar₁] = [β₁] - if iter == 1 - ηₖ = βₖ - end - # [δ₁ 0 ] [ ζ₁ ] = [β₁] - # [λ₁ δbar₂] [ζbar₂] [0 ] - if iter == 2 - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -λₖ₋₁ * ζₖ₋₁ - end - # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] - # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] - # [ζbarₖ] - if iter ≥ 3 - ζₖ₋₂ = ζₖ₋₁ - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ - end + # Initial solution x₀ and residual r₀ = b - Ax₀. + x .= zero(FC) # x₀ + bNorm = @knrm2(m, r₀) # rNorm = ‖r₀‖ + + # Initial solution y₀ and residual s₀ = c - Aᴴy₀. + t .= zero(FC) # t₀ + cNorm = @knrm2(n, s₀) # sNorm = ‖s₀‖ + + iter = 0 + itmax == 0 && (itmax = m+n) + + history && push!(rNorms, bNorm) + history && push!(sNorms, cNorm) + εL = atol + rtol * bNorm + εQ = atol + rtol * cNorm + ξ = zero(T) + (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) + + # Set up workspace. + βₖ = @knrm2(m, r₀) # β₁ = ‖r₀‖ = ‖v₁‖ + γₖ = @knrm2(n, s₀) # γ₁ = ‖s₀‖ = ‖u₁‖ + vₖ₋₁ .= zero(FC) # v₀ = 0 + uₖ₋₁ .= zero(FC) # u₀ = 0 + vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ + uₖ .= s₀ ./ γₖ # u₁ = (c - Aᴴy₀) / γ₁ + cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ + sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ + d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᴴ + ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ + ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ + δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and L̅ₖ modified over the course of two iterations + ψbarₖ₋₁ = ψₖ₋₁ = zero(FC) # ψₖ₋₁ and ψbarₖ are the last components of h̅ₖ = Qₖγ₁e₁ + ϵₖ₋₃ = λₖ₋₂ = zero(FC) # Components of Lₖ₋₁ + wₖ₋₃ .= zero(FC) # Column k-3 of Wₖ = Vₖ(Lₖ)⁻ᴴ + wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Vₖ(Lₖ)⁻ᴴ + + # Stopping criterion. + inconsistent = false + solved_lq = bNorm == 0 + solved_lq_tol = solved_lq_mach = false + solved_cg = solved_cg_tol = solved_cg_mach = false + solved_primal = solved_lq || solved_cg + solved_qr_tol = solved_qr_mach = false + solved_dual = cNorm == 0 + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᴴ. - # [d̅ₖ₋₁ uₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * uₖ - # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ - if iter ≥ 2 - # Compute solution xₖ. - # (xᴸ)ₖ ← (xᴸ)ₖ₋₁ + ζₖ₋₁ * dₖ₋₁ - @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) - @kaxpy!(n, ζₖ₋₁ * sₖ, uₖ, x) - end + while !((solved_primal && solved_dual) || tired || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 - # Compute d̅ₖ. - if iter == 1 - # d̅₁ = u₁ - @. d̅ = uₖ - else - # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ - @kaxpby!(n, -cₖ, uₖ, conj(sₖ), d̅) - end + # Continue the SSY tridiagonalization process. + # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ + # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - # Compute USYMLQ residual norm - # ‖rₖ‖ = √(|μₖ|² + |ωₖ|²) - if iter == 1 - rNorm_lq = bNorm - else - μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ - ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ - rNorm_lq = sqrt(abs2(μₖ) + abs2(ωₖ)) - end - history && push!(rNorms, rNorm_lq) - - # Compute USYMCG residual norm - # ‖rₖ‖ = |ρₖ| - if transfer_to_usymcg && (abs(δbarₖ) > eps(T)) - ζbarₖ = ηₖ / δbarₖ - ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) - rNorm_cg = abs(ρₖ) - end + mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ + mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ - # Update primal stopping criterion - solved_lq_tol = rNorm_lq ≤ εL - solved_lq_mach = rNorm_lq + 1 ≤ 1 - solved_lq = solved_lq_tol || solved_lq_mach - solved_cg_tol = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ εL) - solved_cg_mach = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg + 1 ≤ 1) - solved_cg = solved_cg_tol || solved_cg_mach - solved_primal = solved_lq || solved_cg - end + @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ + @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ + + αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ + + @kaxpy!(m, - αₖ , vₖ, q) # q ← q - αₖ * vₖ + @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ + + βₖ₊₁ = @knrm2(m, q) # βₖ₊₁ = ‖q‖ + γₖ₊₁ = @knrm2(n, p) # γₖ₊₁ = ‖p‖ + + # Update the LQ factorization of Tₖ = L̅ₖQₖ. + # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] + # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] + # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] + # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ + # [ • • • • • 0 ] [ • • • • • • • ] + # [ • • • • γₖ] [ • • • λₖ₋₂ δₖ₋₁ 0 ] + # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] - if !solved_dual - # Compute ψₖ₋₁ and ψbarₖ the last coefficients of h̅ₖ = Qₖγ₁e₁. if iter == 1 - ψbarₖ = γₖ + δbarₖ = αₖ + elseif iter == 2 + # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] + # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + λₖ₋₁ = cₖ * βₖ + sₖ * αₖ + δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ else - # [cₖ s̄ₖ] [ψbarₖ₋₁] = [ ψₖ₋₁ ] - # [sₖ -cₖ] [ 0 ] [ ψbarₖ] - ψₖ₋₁ = cₖ * ψbarₖ₋₁ - ψbarₖ = sₖ * ψbarₖ₋₁ + # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] + # [sₖ₋₁ -cₖ₋₁ 0] + # [ 0 0 1] + # + # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] + # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] + # [0 sₖ -cₖ] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + ϵₖ₋₂ = sₖ₋₁ * βₖ + λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ + δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + end + + if !solved_primal + # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ + # [δbar₁] [ζbar₁] = [β₁] + if iter == 1 + ηₖ = βₖ + end + # [δ₁ 0 ] [ ζ₁ ] = [β₁] + # [λ₁ δbar₂] [ζbar₂] [0 ] + if iter == 2 + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -λₖ₋₁ * ζₖ₋₁ + end + # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] + # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] + # [ζbarₖ] + if iter ≥ 3 + ζₖ₋₂ = ζₖ₋₁ + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ + end + + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᴴ. + # [d̅ₖ₋₁ uₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * uₖ + # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ + if iter ≥ 2 + # Compute solution xₖ. + # (xᴸ)ₖ ← (xᴸ)ₖ₋₁ + ζₖ₋₁ * dₖ₋₁ + @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) + @kaxpy!(n, ζₖ₋₁ * sₖ, uₖ, x) + end + + # Compute d̅ₖ. + if iter == 1 + # d̅₁ = u₁ + @. d̅ = uₖ + else + # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ + @kaxpby!(n, -cₖ, uₖ, conj(sₖ), d̅) + end + + # Compute USYMLQ residual norm + # ‖rₖ‖ = √(|μₖ|² + |ωₖ|²) + if iter == 1 + rNorm_lq = bNorm + else + μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ + ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ + rNorm_lq = sqrt(abs2(μₖ) + abs2(ωₖ)) + end + history && push!(rNorms, rNorm_lq) + + # Compute USYMCG residual norm + # ‖rₖ‖ = |ρₖ| + if transfer_to_usymcg && (abs(δbarₖ) > eps(T)) + ζbarₖ = ηₖ / δbarₖ + ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) + rNorm_cg = abs(ρₖ) + end + + # Update primal stopping criterion + solved_lq_tol = rNorm_lq ≤ εL + solved_lq_mach = rNorm_lq + 1 ≤ 1 + solved_lq = solved_lq_tol || solved_lq_mach + solved_cg_tol = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ εL) + solved_cg_mach = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg + 1 ≤ 1) + solved_cg = solved_cg_tol || solved_cg_mach + solved_primal = solved_lq || solved_cg end - # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Vₖ₋₁)(Lₖ₋₁)⁻ᴴ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Vₖ₋₁)ᵀ. - # w₁ = v₁ / δ̄₁ - if iter == 2 - wₖ₋₁ = wₖ₋₂ - @kaxpy!(m, one(FC), vₖ₋₁, wₖ₋₁) - @. wₖ₋₁ = vₖ₋₁ / conj(δₖ₋₁) + if !solved_dual + # Compute ψₖ₋₁ and ψbarₖ the last coefficients of h̅ₖ = Qₖγ₁e₁. + if iter == 1 + ψbarₖ = γₖ + else + # [cₖ s̄ₖ] [ψbarₖ₋₁] = [ ψₖ₋₁ ] + # [sₖ -cₖ] [ 0 ] [ ψbarₖ] + ψₖ₋₁ = cₖ * ψbarₖ₋₁ + ψbarₖ = sₖ * ψbarₖ₋₁ + end + + # Compute the direction wₖ₋₁, the last column of Wₖ₋₁ = (Vₖ₋₁)(Lₖ₋₁)⁻ᴴ ⟷ (L̄ₖ₋₁)(Wₖ₋₁)ᵀ = (Vₖ₋₁)ᵀ. + # w₁ = v₁ / δ̄₁ + if iter == 2 + wₖ₋₁ = wₖ₋₂ + @kaxpy!(m, one(FC), vₖ₋₁, wₖ₋₁) + @. wₖ₋₁ = vₖ₋₁ / conj(δₖ₋₁) + end + # w₂ = (v₂ - λ̄₁w₁) / δ̄₂ + if iter == 3 + wₖ₋₁ = wₖ₋₃ + @kaxpy!(m, one(FC), vₖ₋₁, wₖ₋₁) + @kaxpy!(m, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) + @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + end + # wₖ₋₁ = (vₖ₋₁ - λ̄ₖ₋₂wₖ₋₂ - ϵ̄ₖ₋₃wₖ₋₃) / δ̄ₖ₋₁ + if iter ≥ 4 + @kscal!(m, -conj(ϵₖ₋₃), wₖ₋₃) + wₖ₋₁ = wₖ₋₃ + @kaxpy!(m, one(FC), vₖ₋₁, wₖ₋₁) + @kaxpy!(m, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) + @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + end + + if iter ≥ 3 + # Swap pointers. + @kswap(wₖ₋₃, wₖ₋₂) + end + + if iter ≥ 2 + # Compute solution tₖ₋₁. + # tₖ₋₁ ← tₖ₋₂ + ψₖ₋₁ * wₖ₋₁ + @kaxpy!(m, ψₖ₋₁, wₖ₋₁, t) + end + + # Update ψbarₖ₋₁ + ψbarₖ₋₁ = ψbarₖ + + # Compute USYMQR residual norm ‖sₖ₋₁‖ = |ψbarₖ|. + sNorm = abs(ψbarₖ) + history && push!(sNorms, sNorm) + + # Compute ‖Asₖ₋₁‖ = |ψbarₖ| * √(|δbarₖ|² + |λbarₖ|²). + AsNorm = abs(ψbarₖ) * √(abs2(δbarₖ) + abs2(cₖ * βₖ₊₁)) + + # Update dual stopping criterion + iter == 1 && (ξ = atol + rtol * AsNorm) + solved_qr_tol = sNorm ≤ εQ + solved_qr_mach = sNorm + 1 ≤ 1 + inconsistent = AsNorm ≤ ξ + solved_dual = solved_qr_tol || solved_qr_mach || inconsistent end - # w₂ = (v₂ - λ̄₁w₁) / δ̄₂ - if iter == 3 - wₖ₋₁ = wₖ₋₃ - @kaxpy!(m, one(FC), vₖ₋₁, wₖ₋₁) - @kaxpy!(m, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) - @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + + # Compute uₖ₊₁ and uₖ₊₁. + @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ + @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + + if βₖ₊₁ ≠ zero(T) + @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q end - # wₖ₋₁ = (vₖ₋₁ - λ̄ₖ₋₂wₖ₋₂ - ϵ̄ₖ₋₃wₖ₋₃) / δ̄ₖ₋₁ - if iter ≥ 4 - @kscal!(m, -conj(ϵₖ₋₃), wₖ₋₃) - wₖ₋₁ = wₖ₋₃ - @kaxpy!(m, one(FC), vₖ₋₁, wₖ₋₁) - @kaxpy!(m, -conj(λₖ₋₂), wₖ₋₂, wₖ₋₁) - @. wₖ₋₁ = wₖ₋₁ / conj(δₖ₋₁) + if γₖ₊₁ ≠ zero(T) + @. uₖ = p / γₖ₊₁ # γₖ₊₁uₖ₊₁ = p end + # Update ϵₖ₋₃, λₖ₋₂, δbarₖ₋₁, cₖ₋₁, sₖ₋₁, γₖ and βₖ. if iter ≥ 3 - # Swap pointers. - @kswap(wₖ₋₃, wₖ₋₂) + ϵₖ₋₃ = ϵₖ₋₂ end - if iter ≥ 2 - # Compute solution tₖ₋₁. - # tₖ₋₁ ← tₖ₋₂ + ψₖ₋₁ * wₖ₋₁ - @kaxpy!(m, ψₖ₋₁, wₖ₋₁, t) + λₖ₋₂ = λₖ₋₁ end - - # Update ψbarₖ₋₁ - ψbarₖ₋₁ = ψbarₖ - - # Compute USYMQR residual norm ‖sₖ₋₁‖ = |ψbarₖ|. - sNorm = abs(ψbarₖ) - history && push!(sNorms, sNorm) - - # Compute ‖Asₖ₋₁‖ = |ψbarₖ| * √(|δbarₖ|² + |λbarₖ|²). - AsNorm = abs(ψbarₖ) * √(abs2(δbarₖ) + abs2(cₖ * βₖ₊₁)) - - # Update dual stopping criterion - iter == 1 && (ξ = atol + rtol * AsNorm) - solved_qr_tol = sNorm ≤ εQ - solved_qr_mach = sNorm + 1 ≤ 1 - inconsistent = AsNorm ≤ ξ - solved_dual = solved_qr_tol || solved_qr_mach || inconsistent + δbarₖ₋₁ = δbarₖ + cₖ₋₁ = cₖ + sₖ₋₁ = sₖ + γₖ = γₖ₊₁ + βₖ = βₖ₊₁ + + user_requested_exit = callback(solver) :: Bool + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + + kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) + kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") + kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) end + (verbose > 0) && @printf(iostream, "\n") - # Compute uₖ₊₁ and uₖ₊₁. - @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ - @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - - if βₖ₊₁ ≠ zero(T) - @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q + # Compute USYMCG point + # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ + if solved_cg + @kaxpy!(n, ζbarₖ, d̅, x) end - if γₖ₊₁ ≠ zero(T) - @. uₖ = p / γₖ₊₁ # γₖ₊₁uₖ₊₁ = p - end - - # Update ϵₖ₋₃, λₖ₋₂, δbarₖ₋₁, cₖ₋₁, sₖ₋₁, γₖ and βₖ. - if iter ≥ 3 - ϵₖ₋₃ = ϵₖ₋₂ - end - if iter ≥ 2 - λₖ₋₂ = λₖ₋₁ - end - δbarₖ₋₁ = δbarₖ - cₖ₋₁ = cₖ - sₖ₋₁ = sₖ - γₖ = γₖ₊₁ - βₖ = βₖ₊₁ - - user_requested_exit = callback(solver) :: Bool - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - - kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) - kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") - kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) - end - (verbose > 0) && @printf(iostream, "\n") - # Compute USYMCG point - # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ - if solved_cg - @kaxpy!(n, ζbarₖ, d̅, x) + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved_lq_tol && !solved_dual && (status = "Only the primal solution xᴸ is good enough given atol and rtol") + solved_cg_tol && !solved_dual && (status = "Only the primal solution xᶜ is good enough given atol and rtol") + !solved_primal && solved_qr_tol && (status = "Only the dual solution t is good enough given atol and rtol") + solved_lq_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᴸ, t) are good enough given atol and rtol") + solved_cg_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᶜ, t) are good enough given atol and rtol") + solved_lq_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᴸ") + solved_cg_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᶜ") + !solved_primal && solved_qr_mach && (status = "Only found approximate zero-residual dual solution t") + solved_lq_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᴸ, t)") + solved_cg_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᶜ, t)") + solved_lq_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᴸ and a dual solution t good enough given atol and rtol") + solved_cg_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᶜ and a dual solution t good enough given atol and rtol") + solved_lq_tol && solved_qr_mach && (status = "Found a primal solution xᴸ good enough given atol and rtol and an approximate zero-residual dual solutions t") + solved_cg_tol && solved_qr_mach && (status = "Found a primal solution xᶜ good enough given atol and rtol and an approximate zero-residual dual solutions t") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x and y + warm_start && @kaxpy!(n, one(FC), Δx, x) + warm_start && @kaxpy!(m, one(FC), Δy, t) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.status = status + stats.solved_primal = solved_primal + stats.solved_dual = solved_dual + return solver end - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved_lq_tol && !solved_dual && (status = "Only the primal solution xᴸ is good enough given atol and rtol") - solved_cg_tol && !solved_dual && (status = "Only the primal solution xᶜ is good enough given atol and rtol") - !solved_primal && solved_qr_tol && (status = "Only the dual solution t is good enough given atol and rtol") - solved_lq_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᴸ, t) are good enough given atol and rtol") - solved_cg_tol && solved_qr_tol && (status = "Both primal and dual solutions (xᶜ, t) are good enough given atol and rtol") - solved_lq_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᴸ") - solved_cg_mach && !solved_dual && (status = "Only found approximate zero-residual primal solution xᶜ") - !solved_primal && solved_qr_mach && (status = "Only found approximate zero-residual dual solution t") - solved_lq_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᴸ, t)") - solved_cg_mach && solved_qr_mach && (status = "Found approximate zero-residual primal and dual solutions (xᶜ, t)") - solved_lq_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᴸ and a dual solution t good enough given atol and rtol") - solved_cg_mach && solved_qr_tol && (status = "Found approximate zero-residual primal solutions xᶜ and a dual solution t good enough given atol and rtol") - solved_lq_tol && solved_qr_mach && (status = "Found a primal solution xᴸ good enough given atol and rtol and an approximate zero-residual dual solutions t") - solved_cg_tol && solved_qr_mach && (status = "Found a primal solution xᶜ good enough given atol and rtol and an approximate zero-residual dual solutions t") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x and y - warm_start && @kaxpy!(n, one(FC), Δx, x) - warm_start && @kaxpy!(m, one(FC), Δy, t) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.status = status - stats.solved_primal = solved_primal - stats.solved_dual = solved_dual - return solver end diff --git a/src/trimr.jl b/src/trimr.jl index 710367975..c1eab7b5d 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -138,435 +138,429 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : return (solver.x, solver.y, solver.stats) end - function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0, y0) trimr!(solver, A, b, c; $(kwargs_trimr...)) return solver end -end - -function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - M=I, N=I, ldiv :: Bool=false, - spd :: Bool=false, snd :: Bool=false, - flip :: Bool=false, sp :: Bool=false, - τ :: T=one(T), ν :: T=-one(T), atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "TriMR: system of %d equations in %d variables\n", m+n, m+n) - - # Check flip, sp, spd and snd parameters - spd && flip && error("The matrix cannot be symmetric positive definite and symmetric quasi-definite !") - spd && snd && error("The matrix cannot be symmetric positive definite and symmetric negative definite !") - spd && sp && error("The matrix cannot be symmetric positive definite and a saddle-point !") - snd && flip && error("The matrix cannot be symmetric negative definite and symmetric quasi-definite !") - snd && sp && error("The matrix cannot be symmetric negative definite and a saddle-point !") - sp && flip && error("The matrix cannot be symmetric quasi-definite and a saddle-point !") - - # Check M = Iₘ and N = Iₙ - MisI = (M === I) - NisI = (N === I) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Determine τ and ν associated to SQD, SPD or SND systems. - flip && (τ = -one(T) ; ν = one(T)) - spd && (τ = one(T) ; ν = one(T)) - snd && (τ = -one(T) ; ν = -one(T)) - sp && (τ = one(T) ; ν = zero(T)) - - warm_start = solver.warm_start - warm_start && (τ ≠ 0) && !MisI && error("Warm-start with preconditioners is not supported.") - warm_start && (ν ≠ 0) && !NisI && error("Warm-start with preconditioners is not supported.") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - allocate_if(!MisI, solver, :vₖ, S, m) - allocate_if(!NisI, solver, :uₖ, S, n) - Δy, yₖ, N⁻¹uₖ₋₁, N⁻¹uₖ, p = solver.Δy, solver.y, solver.N⁻¹uₖ₋₁, solver.N⁻¹uₖ, solver.p - Δx, xₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, q = solver.Δx, solver.x, solver.M⁻¹vₖ₋₁, solver.M⁻¹vₖ, solver.q - gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ = solver.gy₂ₖ₋₃, solver.gy₂ₖ₋₂, solver.gy₂ₖ₋₁, solver.gy₂ₖ - gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ = solver.gx₂ₖ₋₃, solver.gx₂ₖ₋₂, solver.gx₂ₖ₋₁, solver.gx₂ₖ - vₖ = MisI ? M⁻¹vₖ : solver.vₖ - uₖ = NisI ? N⁻¹uₖ : solver.uₖ - vₖ₊₁ = MisI ? q : M⁻¹vₖ₋₁ - uₖ₊₁ = NisI ? p : N⁻¹uₖ₋₁ - b₀ = warm_start ? q : b - c₀ = warm_start ? p : c - - stats = solver.stats - rNorms = stats.residuals - reset!(stats) - - # Initial solutions x₀ and y₀. - xₖ .= zero(FC) - yₖ .= zero(FC) - - iter = 0 - itmax == 0 && (itmax = m+n) - - # Initialize preconditioned orthogonal tridiagonalization process. - M⁻¹vₖ₋₁ .= zero(FC) # v₀ = 0 - N⁻¹uₖ₋₁ .= zero(FC) # u₀ = 0 - - # [ τI A ] [ xₖ ] = [ b - τΔx - AΔy ] = [ b₀ ] - # [ Aᴴ νI ] [ yₖ ] [ c - AᴴΔx - νΔy ] [ c₀ ] - if warm_start - mul!(b₀, A, Δy) - (τ ≠ 0) && @kaxpy!(m, τ, Δx, b₀) - @kaxpby!(m, one(FC), b, -one(FC), b₀) - mul!(c₀, Aᴴ, Δx) - (ν ≠ 0) && @kaxpy!(n, ν, Δy, c₀) - @kaxpby!(n, one(FC), c, -one(FC), c₀) - end - # β₁Ev₁ = b ↔ β₁v₁ = Mb - M⁻¹vₖ .= b₀ - MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv) - βₖ = sqrt(@kdotr(m, vₖ, M⁻¹vₖ)) # β₁ = ‖v₁‖_E - if βₖ ≠ 0 - @kscal!(m, one(FC) / βₖ, M⁻¹vₖ) - MisI || @kscal!(m, one(FC) / βₖ, vₖ) - else - error("b must be nonzero") - end + function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "TriMR: system of %d equations in %d variables\n", m+n, m+n) + + # Check flip, sp, spd and snd parameters + spd && flip && error("The matrix cannot be symmetric positive definite and symmetric quasi-definite !") + spd && snd && error("The matrix cannot be symmetric positive definite and symmetric negative definite !") + spd && sp && error("The matrix cannot be symmetric positive definite and a saddle-point !") + snd && flip && error("The matrix cannot be symmetric negative definite and symmetric quasi-definite !") + snd && sp && error("The matrix cannot be symmetric negative definite and a saddle-point !") + sp && flip && error("The matrix cannot be symmetric quasi-definite and a saddle-point !") + + # Check M = Iₘ and N = Iₙ + MisI = (M === I) + NisI = (N === I) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Determine τ and ν associated to SQD, SPD or SND systems. + flip && (τ = -one(T) ; ν = one(T)) + spd && (τ = one(T) ; ν = one(T)) + snd && (τ = -one(T) ; ν = -one(T)) + sp && (τ = one(T) ; ν = zero(T)) + + warm_start = solver.warm_start + warm_start && (τ ≠ 0) && !MisI && error("Warm-start with preconditioners is not supported.") + warm_start && (ν ≠ 0) && !NisI && error("Warm-start with preconditioners is not supported.") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + allocate_if(!MisI, solver, :vₖ, S, m) + allocate_if(!NisI, solver, :uₖ, S, n) + Δy, yₖ, N⁻¹uₖ₋₁, N⁻¹uₖ, p = solver.Δy, solver.y, solver.N⁻¹uₖ₋₁, solver.N⁻¹uₖ, solver.p + Δx, xₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, q = solver.Δx, solver.x, solver.M⁻¹vₖ₋₁, solver.M⁻¹vₖ, solver.q + gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ = solver.gy₂ₖ₋₃, solver.gy₂ₖ₋₂, solver.gy₂ₖ₋₁, solver.gy₂ₖ + gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ = solver.gx₂ₖ₋₃, solver.gx₂ₖ₋₂, solver.gx₂ₖ₋₁, solver.gx₂ₖ + vₖ = MisI ? M⁻¹vₖ : solver.vₖ + uₖ = NisI ? N⁻¹uₖ : solver.uₖ + vₖ₊₁ = MisI ? q : M⁻¹vₖ₋₁ + uₖ₊₁ = NisI ? p : N⁻¹uₖ₋₁ + b₀ = warm_start ? q : b + c₀ = warm_start ? p : c + + stats = solver.stats + rNorms = stats.residuals + reset!(stats) + + # Initial solutions x₀ and y₀. + xₖ .= zero(FC) + yₖ .= zero(FC) + + iter = 0 + itmax == 0 && (itmax = m+n) + + # Initialize preconditioned orthogonal tridiagonalization process. + M⁻¹vₖ₋₁ .= zero(FC) # v₀ = 0 + N⁻¹uₖ₋₁ .= zero(FC) # u₀ = 0 + + # [ τI A ] [ xₖ ] = [ b - τΔx - AΔy ] = [ b₀ ] + # [ Aᴴ νI ] [ yₖ ] [ c - AᴴΔx - νΔy ] [ c₀ ] + if warm_start + mul!(b₀, A, Δy) + (τ ≠ 0) && @kaxpy!(m, τ, Δx, b₀) + @kaxpby!(m, one(FC), b, -one(FC), b₀) + mul!(c₀, Aᴴ, Δx) + (ν ≠ 0) && @kaxpy!(n, ν, Δy, c₀) + @kaxpby!(n, one(FC), c, -one(FC), c₀) + end - # γ₁Fu₁ = c ↔ γ₁u₁ = Nc - N⁻¹uₖ .= c₀ - NisI || mulorldiv!(uₖ, N, N⁻¹uₖ, ldiv) - γₖ = sqrt(@kdotr(n, uₖ, N⁻¹uₖ)) # γ₁ = ‖u₁‖_F - if γₖ ≠ 0 - @kscal!(n, one(FC) / γₖ, N⁻¹uₖ) - NisI || @kscal!(n, one(FC) / γₖ, uₖ) - else - error("c must be nonzero") - end + # β₁Ev₁ = b ↔ β₁v₁ = Mb + M⁻¹vₖ .= b₀ + MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv) + βₖ = sqrt(@kdotr(m, vₖ, M⁻¹vₖ)) # β₁ = ‖v₁‖_E + if βₖ ≠ 0 + @kscal!(m, one(FC) / βₖ, M⁻¹vₖ) + MisI || @kscal!(m, one(FC) / βₖ, vₖ) + else + error("b must be nonzero") + end - # Initialize directions Gₖ such that (GₖRₖ)ᵀ = (Wₖ)ᵀ. - gx₂ₖ₋₃ .= zero(FC) - gy₂ₖ₋₃ .= zero(FC) - gx₂ₖ₋₂ .= zero(FC) - gy₂ₖ₋₂ .= zero(FC) - gx₂ₖ₋₁ .= zero(FC) - gy₂ₖ₋₁ .= zero(FC) - gx₂ₖ .= zero(FC) - gy₂ₖ .= zero(FC) - - # Compute ‖r₀‖² = (γ₁)² + (β₁)² - rNorm = sqrt(γₖ^2 + βₖ^2) - history && push!(rNorms, rNorm) - ε = atol + rtol * rNorm - - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) - - # Set up workspace. - old_c₁ₖ = old_c₂ₖ = old_c₃ₖ = old_c₄ₖ = zero(T) - old_s₁ₖ = old_s₂ₖ = old_s₃ₖ = old_s₄ₖ = zero(FC) - σbar₂ₖ₋₂ = ηbar₂ₖ₋₃ = λbar₂ₖ₋₃ = μ₂ₖ₋₅ = λ₂ₖ₋₄ = μ₂ₖ₋₄ = zero(FC) - πbar₂ₖ₋₁ = βₖ - πbar₂ₖ = γₖ - - # Tolerance for breakdown detection. - btol = eps(T)^(3/4) - - # Stopping criterion. - breakdown = false - solved = rNorm ≤ ε - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - θbarₖ = δbar₂ₖ₋₁ = δbar₂ₖ = σbar₂ₖ₋₁ = σbar₂ₖ = λbar₂ₖ₋₁ = ηbar₂ₖ₋₁ = zero(FC) - - while !(solved || tired || breakdown || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the orthogonal tridiagonalization process. - # AUₖ = EVₖTₖ + βₖ₊₁Evₖ₊₁(eₖ)ᵀ = EVₖ₊₁Tₖ₊₁.ₖ - # AᴴVₖ = FUₖ(Tₖ)ᴴ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , uₖ) # Forms Evₖ₊₁ : q ← Auₖ - mul!(p, Aᴴ, vₖ) # Forms Fuₖ₊₁ : p ← Aᴴvₖ - - if iter ≥ 2 - @kaxpy!(m, -γₖ, M⁻¹vₖ₋₁, q) # q ← q - γₖ * M⁻¹vₖ₋₁ - @kaxpy!(n, -βₖ, N⁻¹uₖ₋₁, p) # p ← p - βₖ * N⁻¹uₖ₋₁ + # γ₁Fu₁ = c ↔ γ₁u₁ = Nc + N⁻¹uₖ .= c₀ + NisI || mulorldiv!(uₖ, N, N⁻¹uₖ, ldiv) + γₖ = sqrt(@kdotr(n, uₖ, N⁻¹uₖ)) # γ₁ = ‖u₁‖_F + if γₖ ≠ 0 + @kscal!(n, one(FC) / γₖ, N⁻¹uₖ) + NisI || @kscal!(n, one(FC) / γₖ, uₖ) + else + error("c must be nonzero") end - αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ + # Initialize directions Gₖ such that (GₖRₖ)ᵀ = (Wₖ)ᵀ. + gx₂ₖ₋₃ .= zero(FC) + gy₂ₖ₋₃ .= zero(FC) + gx₂ₖ₋₂ .= zero(FC) + gy₂ₖ₋₂ .= zero(FC) + gx₂ₖ₋₁ .= zero(FC) + gy₂ₖ₋₁ .= zero(FC) + gx₂ₖ .= zero(FC) + gy₂ₖ .= zero(FC) + + # Compute ‖r₀‖² = (γ₁)² + (β₁)² + rNorm = sqrt(γₖ^2 + βₖ^2) + history && push!(rNorms, rNorm) + ε = atol + rtol * rNorm - @kaxpy!(m, - αₖ , M⁻¹vₖ, q) # q ← q - αₖ * M⁻¹vₖ - @kaxpy!(n, -conj(αₖ), N⁻¹uₖ, p) # p ← p - ᾱₖ * N⁻¹uₖ + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) - # Compute vₖ₊₁ and uₖ₊₁ - MisI || mulorldiv!(vₖ₊₁, M, q, ldiv) # βₖ₊₁vₖ₊₁ = MAuₖ - γₖvₖ₋₁ - αₖvₖ - NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᴴvₖ - βₖuₖ₋₁ - ᾱₖuₖ + # Set up workspace. + old_c₁ₖ = old_c₂ₖ = old_c₃ₖ = old_c₄ₖ = zero(T) + old_s₁ₖ = old_s₂ₖ = old_s₃ₖ = old_s₄ₖ = zero(FC) + σbar₂ₖ₋₂ = ηbar₂ₖ₋₃ = λbar₂ₖ₋₃ = μ₂ₖ₋₅ = λ₂ₖ₋₄ = μ₂ₖ₋₄ = zero(FC) + πbar₂ₖ₋₁ = βₖ + πbar₂ₖ = γₖ - βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, q)) # βₖ₊₁ = ‖vₖ₊₁‖_E - γₖ₊₁ = sqrt(@kdotr(n, uₖ₊₁, p)) # γₖ₊₁ = ‖uₖ₊₁‖_F + # Tolerance for breakdown detection. + btol = eps(T)^(3/4) - # βₖ₊₁ ≠ 0 - if βₖ₊₁ > btol - @kscal!(m, one(FC) / βₖ₊₁, q) - MisI || @kscal!(m, one(FC) / βₖ₊₁, vₖ₊₁) - end + # Stopping criterion. + breakdown = false + solved = rNorm ≤ ε + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false - # γₖ₊₁ ≠ 0 - if γₖ₊₁ > btol - @kscal!(n, one(FC) / γₖ₊₁, p) - NisI || @kscal!(n, one(FC) / γₖ₊₁, uₖ₊₁) - end + θbarₖ = δbar₂ₖ₋₁ = δbar₂ₖ = σbar₂ₖ₋₁ = σbar₂ₖ = λbar₂ₖ₋₁ = ηbar₂ₖ₋₁ = zero(FC) - # Notations : Wₖ = [w₁ ••• wₖ] = [v₁ 0 ••• vₖ 0 ] - # [0 u₁ ••• 0 uₖ] - # - # rₖ = [ b ] - [ τE A ] [ xₖ ] = [ b ] - [ τE A ] Wₖzₖ - # [ c ] [ Aᴴ νF ] [ yₖ ] [ c ] [ Aᴴ νF ] - # - # block-Lanczos formulation : [ τE A ] Wₖ = [ E 0 ] Wₖ₊₁Sₖ₊₁.ₖ - # [ Aᴴ νF ] [ 0 F ] - # - # TriMR subproblem : min ‖ rₖ ‖ ↔ min ‖ Sₖ₊₁.ₖzₖ - β₁e₁ - γ₁e₂ ‖ - # - # Update the QR factorization of Sₖ₊₁.ₖ = Qₖ [ Rₖ ]. - # [ Oᵀ ] - if iter == 1 - θbarₖ = conj(αₖ) - δbar₂ₖ₋₁ = τ - δbar₂ₖ = ν - σbar₂ₖ₋₁ = αₖ - σbar₂ₖ = βₖ₊₁ - λbar₂ₖ₋₁ = γₖ₊₁ - ηbar₂ₖ₋₁ = zero(FC) - else - # Apply previous reflections - # [ 1 ][ 1 ][ c₂.ₖ₋₁ s₂.ₖ₋₁ ][ 1 ] - # Ζₖ₋₁ = [ c₄.ₖ₋₁ s₄.ₖ₋₁ ][ c₃.ₖ₋₁ s₃.ₖ₋₁ ][ s̄₂.ₖ₋₁ -c₂.ₖ₋₁ ][ c₁.ₖ₋₁ s₁.ₖ₋₁ ] - # [ s̄₄.ₖ₋₁ -c₄.ₖ₋₁ ][ 1 ][ 1 ][ 1 ] - # [ 1 ][ s̄₃.ₖ₋₁ -c₃.ₖ₋₁ ][ 1 ][ s̄₁.ₖ₋₁ -c₁.ₖ₋₁ ] - # - # [ δbar₂ₖ₋₃ σbar₂ₖ₋₃ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] [ δ₂ₖ₋₃ σ₂ₖ₋₃ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] - # Ζₖ₋₁ * [ θbarₖ₋₁ δbar₂ₖ₋₂ σbar₂ₖ₋₂ 0 0 0 ] = [ 0 δ₂ₖ₋₂ σ₂ₖ₋₂ η₂ₖ₋₂ λ₂ₖ₋₂ μ₂ₖ₋₂ ] - # [ 0 βₖ τ αₖ 0 γₖ₊₁ ] [ 0 0 δbar₂ₖ₋₁ σbar₂ₖ₋₁ ηbar₂ₖ₋₁ λbar₂ₖ₋₁ ] - # [ γₖ 0 ᾱₖ ν βₖ₊₁ 0 ] [ 0 0 θbarₖ δbar₂ₖ σbar₂ₖ 0 ] - # - # [ 1 ] [ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] [ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] - # [ c₁.ₖ₋₁ s₁.ₖ₋₁ ] [ σbar₂ₖ₋₂ 0 0 0 ] = [ σbis₂ₖ₋₂ ηbis₂ₖ₋₂ λbis₂ₖ₋₂ 0 ] - # [ 1 ] [ τ αₖ 0 γₖ₊₁ ] [ τ αₖ 0 γₖ₊₁ ] - # [ s̄₁.ₖ₋₁ -c₁.ₖ₋₁ ] [ ᾱₖ ν βₖ₊₁ 0 ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] - σbis₂ₖ₋₂ = old_c₁ₖ * σbar₂ₖ₋₂ + old_s₁ₖ * conj(αₖ) - ηbis₂ₖ₋₂ = old_s₁ₖ * ν - λbis₂ₖ₋₂ = old_s₁ₖ * βₖ₊₁ - θbisₖ = conj(old_s₁ₖ) * σbar₂ₖ₋₂ - old_c₁ₖ * conj(αₖ) - δbis₂ₖ = - old_c₁ₖ * ν - σbis₂ₖ = - old_c₁ₖ * βₖ₊₁ - # [ c₂.ₖ₋₁ s₂.ₖ₋₁ ] [ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] - # [ s̄₂.ₖ₋₁ -c₂.ₖ₋₁ ] [ σbis₂ₖ₋₂ ηbis₂ₖ₋₂ λbis₂ₖ₋₂ 0 ] = [ σhat₂ₖ₋₂ ηhat₂ₖ₋₂ λhat₂ₖ₋₂ 0 ] - # [ 1 ] [ τ αₖ 0 γₖ₊₁ ] [ τ αₖ 0 γₖ₊₁ ] - # [ 1 ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] - η₂ₖ₋₃ = old_c₂ₖ * ηbar₂ₖ₋₃ + old_s₂ₖ * σbis₂ₖ₋₂ - λ₂ₖ₋₃ = old_c₂ₖ * λbar₂ₖ₋₃ + old_s₂ₖ * ηbis₂ₖ₋₂ - μ₂ₖ₋₃ = old_s₂ₖ * λbis₂ₖ₋₂ - σhat₂ₖ₋₂ = conj(old_s₂ₖ) * ηbar₂ₖ₋₃ - old_c₂ₖ * σbis₂ₖ₋₂ - ηhat₂ₖ₋₂ = conj(old_s₂ₖ) * λbar₂ₖ₋₃ - old_c₂ₖ * ηbis₂ₖ₋₂ - λhat₂ₖ₋₂ = - old_c₂ₖ * λbis₂ₖ₋₂ - # [ 1 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] - # [ c₃.ₖ₋₁ s₃.ₖ₋₁ ] [ σhat₂ₖ₋₂ ηhat₂ₖ₋₂ λhat₂ₖ₋₂ 0 ] = [ σtmp₂ₖ₋₂ ηtmp₂ₖ₋₂ λtmp₂ₖ₋₂ 0 ] - # [ 1 ] [ τ αₖ 0 γₖ₊₁ ] [ τ αₖ 0 γₖ₊₁ ] - # [ s̄₃.ₖ₋₁ -c₃.ₖ₋₁ ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] [ θbarₖ δbar₂ₖ σbar₂ₖ 0 ] - σtmp₂ₖ₋₂ = old_c₃ₖ * σhat₂ₖ₋₂ + old_s₃ₖ * θbisₖ - ηtmp₂ₖ₋₂ = old_c₃ₖ * ηhat₂ₖ₋₂ + old_s₃ₖ * δbis₂ₖ - λtmp₂ₖ₋₂ = old_c₃ₖ * λhat₂ₖ₋₂ + old_s₃ₖ * σbis₂ₖ - θbarₖ = conj(old_s₃ₖ) * σhat₂ₖ₋₂ - old_c₃ₖ * θbisₖ - δbar₂ₖ = conj(old_s₃ₖ) * ηhat₂ₖ₋₂ - old_c₃ₖ * δbis₂ₖ - σbar₂ₖ = conj(old_s₃ₖ) * λhat₂ₖ₋₂ - old_c₃ₖ * σbis₂ₖ - # [ 1 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] - # [ c₄.ₖ₋₁ s₄.ₖ₋₁ ] [ σtmp₂ₖ₋₂ ηtmp₂ₖ₋₂ λtmp₂ₖ₋₂ 0 ] = [ σ₂ₖ₋₂ η₂ₖ₋₂ λ₂ₖ₋₂ μ₂ₖ₋₂ ] - # [ s̄₄.ₖ₋₁ -c₄.ₖ₋₁ ] [ τ αₖ 0 γₖ₊₁ ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ηbar₂ₖ₋₁ λbar₂ₖ₋₁ ] - # [ 1 ] [ θbarₖ δbar₂ₖ σbar₂ₖ 0 ] [ θbarₖ δbar₂ₖ σbar₂ₖ 0 ] - σ₂ₖ₋₂ = old_c₄ₖ * σtmp₂ₖ₋₂ + old_s₄ₖ * τ - η₂ₖ₋₂ = old_c₄ₖ * ηtmp₂ₖ₋₂ + old_s₄ₖ * αₖ - λ₂ₖ₋₂ = old_c₄ₖ * λtmp₂ₖ₋₂ - μ₂ₖ₋₂ = old_s₄ₖ * γₖ₊₁ - δbar₂ₖ₋₁ = conj(old_s₄ₖ) * σtmp₂ₖ₋₂ - old_c₄ₖ * τ - σbar₂ₖ₋₁ = conj(old_s₄ₖ) * ηtmp₂ₖ₋₂ - old_c₄ₖ * αₖ - ηbar₂ₖ₋₁ = conj(old_s₄ₖ) * λtmp₂ₖ₋₂ - λbar₂ₖ₋₁ = - old_c₄ₖ * γₖ₊₁ - end + while !(solved || tired || breakdown || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 - # [ 1 ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ] - # [ c₁.ₖ s₁.ₖ ] [ θbarₖ δbar₂ₖ ] = [ θₖ δbar₂ₖ ] - # [ 1 ] [ 0 βₖ₊₁ ] [ 0 βₖ₊₁ ] - # [ s̄₁.ₖ -c₁.ₖ ] [ γₖ₊₁ 0 ] [ 0 gₖ ] - (c₁ₖ, s₁ₖ, θₖ) = sym_givens(θbarₖ, γₖ₊₁) - gₖ = conj(s₁ₖ) * δbar₂ₖ - δbar₂ₖ = c₁ₖ * δbar₂ₖ - - # [ c₂.ₖ s₂.ₖ ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] - # [ s̄₂.ₖ -c₂.ₖ ] [ θₖ δbar₂ₖ ] = [ 0 δbis₂ₖ ] - # [ 1 ] [ 0 βₖ₊₁ ] [ 0 βₖ₊₁ ] - # [ 1 ] [ 0 gₖ ] [ 0 gₖ ] - (c₂ₖ, s₂ₖ, δ₂ₖ₋₁) = sym_givens(δbar₂ₖ₋₁, θₖ) - σ₂ₖ₋₁ = c₂ₖ * σbar₂ₖ₋₁ + s₂ₖ * δbar₂ₖ - δbis₂ₖ = conj(s₂ₖ) * σbar₂ₖ₋₁ - c₂ₖ * δbar₂ₖ - - # [ 1 ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] - # [ c₃.ₖ s₃.ₖ ] [ 0 δbis₂ₖ ] = [ 0 δhat₂ₖ ] - # [ 1 ] [ 0 βₖ₊₁ ] [ 0 βₖ₊₁ ] - # [ s̄₃.ₖ -c₃.ₖ ] [ 0 gₖ ] [ 0 0 ] - (c₃ₖ, s₃ₖ, δhat₂ₖ) = sym_givens(δbis₂ₖ, gₖ) - - # [ 1 ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] - # [ c₄.ₖ s₄.ₖ ] [ 0 δhat₂ₖ ] = [ 0 δ₂ₖ ] - # [ s̄₄.ₖ -c₄.ₖ ] [ 0 βₖ₊₁ ] [ 0 0 ] - # [ 1 ] [ 0 0 ] [ 0 0 ] - (c₄ₖ, s₄ₖ, δ₂ₖ) = sym_givens(δhat₂ₖ, βₖ₊₁) - - # Solve Gₖ = Wₖ(Rₖ)⁻¹ ⟷ (Rₖ)ᵀ(Gₖ)ᵀ = (Wₖ)ᵀ. - if iter == 1 - # [ δ₁ 0 ] [ gx₁ gy₁ ] = [ v₁ 0 ] - # [ σ₁ δ₂ ] [ gx₂ gy₂ ] [ 0 u₁ ] - @. gx₂ₖ₋₁ = vₖ / δ₂ₖ₋₁ - @. gx₂ₖ = - σ₂ₖ₋₁ / δ₂ₖ * gx₂ₖ₋₁ - @. gy₂ₖ = uₖ / δ₂ₖ - elseif iter == 2 - # [ η₁ σ₂ δ₃ 0 ] [ gx₁ gy₁ ] = [ v₂ 0 ] - # [ λ₁ η₂ σ₃ δ₄ ] [ gx₂ gy₂ ] [ 0 u₂ ] - # [ gx₃ gy₃ ] - # [ gx₄ gy₄ ] - @kswap(gx₂ₖ₋₃, gx₂ₖ₋₁) - @kswap(gx₂ₖ₋₂, gx₂ₖ) - @kswap(gy₂ₖ₋₂, gy₂ₖ) - @. gx₂ₖ₋₁ = (vₖ - η₂ₖ₋₃ * gx₂ₖ₋₃ - σ₂ₖ₋₂ * gx₂ₖ₋₂ ) / δ₂ₖ₋₁ - @. gx₂ₖ = ( - λ₂ₖ₋₃ * gx₂ₖ₋₃ - η₂ₖ₋₂ * gx₂ₖ₋₂ - σ₂ₖ₋₁ * gx₂ₖ₋₁) / δ₂ₖ - @. gy₂ₖ₋₁ = ( - η₂ₖ₋₃ * gy₂ₖ₋₃ - σ₂ₖ₋₂ * gy₂ₖ₋₂ ) / δ₂ₖ₋₁ - @. gy₂ₖ = (uₖ - λ₂ₖ₋₃ * gy₂ₖ₋₃ - η₂ₖ₋₂ * gy₂ₖ₋₂ - σ₂ₖ₋₁ * gy₂ₖ₋₁) / δ₂ₖ - else - # μ₂ₖ₋₅ * gx₂ₖ₋₅ + λ₂ₖ₋₄ * gx₂ₖ₋₄ + η₂ₖ₋₃ * gx₂ₖ₋₃ + σ₂ₖ₋₂ * gx₂ₖ₋₂ + δ₂ₖ₋₁ * gx₂ₖ₋₁ = vₖ - # μ₂ₖ₋₄ * gx₂ₖ₋₄ + λ₂ₖ₋₃ * gx₂ₖ₋₃ + η₂ₖ₋₂ * gx₂ₖ₋₂ + σ₂ₖ₋₁ * gx₂ₖ₋₁ + δ₂ₖ * gx₂ₖ = 0 - g₂ₖ₋₁ = g₂ₖ₋₅ = gx₂ₖ₋₃; g₂ₖ = g₂ₖ₋₄ = gx₂ₖ₋₂; g₂ₖ₋₃ = gx₂ₖ₋₁; g₂ₖ₋₂ = gx₂ₖ - @. g₂ₖ₋₁ = (vₖ - μ₂ₖ₋₅ * g₂ₖ₋₅ - λ₂ₖ₋₄ * g₂ₖ₋₄ - η₂ₖ₋₃ * g₂ₖ₋₃ - σ₂ₖ₋₂ * g₂ₖ₋₂ ) / δ₂ₖ₋₁ - @. g₂ₖ = ( - μ₂ₖ₋₄ * g₂ₖ₋₄ - λ₂ₖ₋₃ * g₂ₖ₋₃ - η₂ₖ₋₂ * g₂ₖ₋₂ - σ₂ₖ₋₁ * g₂ₖ₋₁) / δ₂ₖ - @kswap(gx₂ₖ₋₃, gx₂ₖ₋₁) - @kswap(gx₂ₖ₋₂, gx₂ₖ) - # μ₂ₖ₋₅ * gy₂ₖ₋₅ + λ₂ₖ₋₄ * gy₂ₖ₋₄ + η₂ₖ₋₃ * gy₂ₖ₋₃ + σ₂ₖ₋₂ * gy₂ₖ₋₂ + δ₂ₖ₋₁ * gy₂ₖ₋₁ = 0 - # μ₂ₖ₋₄ * gy₂ₖ₋₄ + λ₂ₖ₋₃ * gy₂ₖ₋₃ + η₂ₖ₋₂ * gy₂ₖ₋₂ + σ₂ₖ₋₁ * gy₂ₖ₋₁ + δ₂ₖ * gy₂ₖ = uₖ - g₂ₖ₋₁ = g₂ₖ₋₅ = gy₂ₖ₋₃; g₂ₖ = g₂ₖ₋₄ = gy₂ₖ₋₂; g₂ₖ₋₃ = gy₂ₖ₋₁; g₂ₖ₋₂ = gy₂ₖ - @. g₂ₖ₋₁ = ( - μ₂ₖ₋₅ * g₂ₖ₋₅ - λ₂ₖ₋₄ * g₂ₖ₋₄ - η₂ₖ₋₃ * g₂ₖ₋₃ - σ₂ₖ₋₂ * g₂ₖ₋₂ ) / δ₂ₖ₋₁ - @. g₂ₖ = (uₖ - μ₂ₖ₋₄ * g₂ₖ₋₄ - λ₂ₖ₋₃ * g₂ₖ₋₃ - η₂ₖ₋₂ * g₂ₖ₋₂ - σ₂ₖ₋₁ * g₂ₖ₋₁) / δ₂ₖ - @kswap(gy₂ₖ₋₃, gy₂ₖ₋₁) - @kswap(gy₂ₖ₋₂, gy₂ₖ) - end + # Continue the orthogonal tridiagonalization process. + # AUₖ = EVₖTₖ + βₖ₊₁Evₖ₊₁(eₖ)ᵀ = EVₖ₊₁Tₖ₊₁.ₖ + # AᴴVₖ = FUₖ(Tₖ)ᴴ + γₖ₊₁Fuₖ₊₁(eₖ)ᵀ = FUₖ₊₁(Tₖ.ₖ₊₁)ᴴ - # Update p̅ₖ = (Qₖ)ᴴ * (β₁e₁ + γ₁e₂) - πbis₂ₖ = c₁ₖ * πbar₂ₖ - πbis₂ₖ₊₂ = conj(s₁ₖ) * πbar₂ₖ - # - π₂ₖ₋₁ = c₂ₖ * πbar₂ₖ₋₁ + s₂ₖ * πbis₂ₖ - πhat₂ₖ = conj(s₂ₖ) * πbar₂ₖ₋₁ - c₂ₖ * πbis₂ₖ - # - πtmp₂ₖ = c₃ₖ * πhat₂ₖ + s₃ₖ * πbis₂ₖ₊₂ - πbar₂ₖ₊₂ = conj(s₃ₖ) * πhat₂ₖ - c₃ₖ * πbis₂ₖ₊₂ - # - π₂ₖ = c₄ₖ * πtmp₂ₖ - πbar₂ₖ₊₁ = conj(s₄ₖ) * πtmp₂ₖ - - # Update xₖ = Gxₖ * pₖ - @kaxpy!(m, π₂ₖ₋₁, gx₂ₖ₋₁, xₖ) - @kaxpy!(m, π₂ₖ , gx₂ₖ , xₖ) - - # Update yₖ = Gyₖ * pₖ - @kaxpy!(n, π₂ₖ₋₁, gy₂ₖ₋₁, yₖ) - @kaxpy!(n, π₂ₖ , gy₂ₖ , yₖ) - - # Compute ‖rₖ‖² = |πbar₂ₖ₊₁|² + |πbar₂ₖ₊₂|² - rNorm = sqrt(abs2(πbar₂ₖ₊₁) + abs2(πbar₂ₖ₊₂)) - history && push!(rNorms, rNorm) + mul!(q, A , uₖ) # Forms Evₖ₊₁ : q ← Auₖ + mul!(p, Aᴴ, vₖ) # Forms Fuₖ₊₁ : p ← Aᴴvₖ + + if iter ≥ 2 + @kaxpy!(m, -γₖ, M⁻¹vₖ₋₁, q) # q ← q - γₖ * M⁻¹vₖ₋₁ + @kaxpy!(n, -βₖ, N⁻¹uₖ₋₁, p) # p ← p - βₖ * N⁻¹uₖ₋₁ + end + + αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ + + @kaxpy!(m, - αₖ , M⁻¹vₖ, q) # q ← q - αₖ * M⁻¹vₖ + @kaxpy!(n, -conj(αₖ), N⁻¹uₖ, p) # p ← p - ᾱₖ * N⁻¹uₖ + + # Compute vₖ₊₁ and uₖ₊₁ + MisI || mulorldiv!(vₖ₊₁, M, q, ldiv) # βₖ₊₁vₖ₊₁ = MAuₖ - γₖvₖ₋₁ - αₖvₖ + NisI || mulorldiv!(uₖ₊₁, N, p, ldiv) # γₖ₊₁uₖ₊₁ = NAᴴvₖ - βₖuₖ₋₁ - ᾱₖuₖ + + βₖ₊₁ = sqrt(@kdotr(m, vₖ₊₁, q)) # βₖ₊₁ = ‖vₖ₊₁‖_E + γₖ₊₁ = sqrt(@kdotr(n, uₖ₊₁, p)) # γₖ₊₁ = ‖uₖ₊₁‖_F + + # βₖ₊₁ ≠ 0 + if βₖ₊₁ > btol + @kscal!(m, one(FC) / βₖ₊₁, q) + MisI || @kscal!(m, one(FC) / βₖ₊₁, vₖ₊₁) + end + + # γₖ₊₁ ≠ 0 + if γₖ₊₁ > btol + @kscal!(n, one(FC) / γₖ₊₁, p) + NisI || @kscal!(n, one(FC) / γₖ₊₁, uₖ₊₁) + end - # Update vₖ and uₖ - MisI || (vₖ .= vₖ₊₁) - NisI || (uₖ .= uₖ₊₁) - - # Update M⁻¹vₖ₋₁ and N⁻¹uₖ₋₁ - M⁻¹vₖ₋₁ .= M⁻¹vₖ - N⁻¹uₖ₋₁ .= N⁻¹uₖ - - # Update M⁻¹vₖ and N⁻¹uₖ - M⁻¹vₖ .= q - N⁻¹uₖ .= p - - # Update cosines and sines - old_s₁ₖ = s₁ₖ - old_s₂ₖ = s₂ₖ - old_s₃ₖ = s₃ₖ - old_s₄ₖ = s₄ₖ - old_c₁ₖ = c₁ₖ - old_c₂ₖ = c₂ₖ - old_c₃ₖ = c₃ₖ - old_c₄ₖ = c₄ₖ - - # Update workspace - βₖ = βₖ₊₁ - γₖ = γₖ₊₁ - σbar₂ₖ₋₂ = σbar₂ₖ - ηbar₂ₖ₋₃ = ηbar₂ₖ₋₁ - λbar₂ₖ₋₃ = λbar₂ₖ₋₁ - if iter ≥ 2 - μ₂ₖ₋₅ = μ₂ₖ₋₃ - μ₂ₖ₋₄ = μ₂ₖ₋₂ - λ₂ₖ₋₄ = λ₂ₖ₋₂ + # Notations : Wₖ = [w₁ ••• wₖ] = [v₁ 0 ••• vₖ 0 ] + # [0 u₁ ••• 0 uₖ] + # + # rₖ = [ b ] - [ τE A ] [ xₖ ] = [ b ] - [ τE A ] Wₖzₖ + # [ c ] [ Aᴴ νF ] [ yₖ ] [ c ] [ Aᴴ νF ] + # + # block-Lanczos formulation : [ τE A ] Wₖ = [ E 0 ] Wₖ₊₁Sₖ₊₁.ₖ + # [ Aᴴ νF ] [ 0 F ] + # + # TriMR subproblem : min ‖ rₖ ‖ ↔ min ‖ Sₖ₊₁.ₖzₖ - β₁e₁ - γ₁e₂ ‖ + # + # Update the QR factorization of Sₖ₊₁.ₖ = Qₖ [ Rₖ ]. + # [ Oᵀ ] + if iter == 1 + θbarₖ = conj(αₖ) + δbar₂ₖ₋₁ = τ + δbar₂ₖ = ν + σbar₂ₖ₋₁ = αₖ + σbar₂ₖ = βₖ₊₁ + λbar₂ₖ₋₁ = γₖ₊₁ + ηbar₂ₖ₋₁ = zero(FC) + else + # Apply previous reflections + # [ 1 ][ 1 ][ c₂.ₖ₋₁ s₂.ₖ₋₁ ][ 1 ] + # Ζₖ₋₁ = [ c₄.ₖ₋₁ s₄.ₖ₋₁ ][ c₃.ₖ₋₁ s₃.ₖ₋₁ ][ s̄₂.ₖ₋₁ -c₂.ₖ₋₁ ][ c₁.ₖ₋₁ s₁.ₖ₋₁ ] + # [ s̄₄.ₖ₋₁ -c₄.ₖ₋₁ ][ 1 ][ 1 ][ 1 ] + # [ 1 ][ s̄₃.ₖ₋₁ -c₃.ₖ₋₁ ][ 1 ][ s̄₁.ₖ₋₁ -c₁.ₖ₋₁ ] + # + # [ δbar₂ₖ₋₃ σbar₂ₖ₋₃ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] [ δ₂ₖ₋₃ σ₂ₖ₋₃ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] + # Ζₖ₋₁ * [ θbarₖ₋₁ δbar₂ₖ₋₂ σbar₂ₖ₋₂ 0 0 0 ] = [ 0 δ₂ₖ₋₂ σ₂ₖ₋₂ η₂ₖ₋₂ λ₂ₖ₋₂ μ₂ₖ₋₂ ] + # [ 0 βₖ τ αₖ 0 γₖ₊₁ ] [ 0 0 δbar₂ₖ₋₁ σbar₂ₖ₋₁ ηbar₂ₖ₋₁ λbar₂ₖ₋₁ ] + # [ γₖ 0 ᾱₖ ν βₖ₊₁ 0 ] [ 0 0 θbarₖ δbar₂ₖ σbar₂ₖ 0 ] + # + # [ 1 ] [ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] [ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] + # [ c₁.ₖ₋₁ s₁.ₖ₋₁ ] [ σbar₂ₖ₋₂ 0 0 0 ] = [ σbis₂ₖ₋₂ ηbis₂ₖ₋₂ λbis₂ₖ₋₂ 0 ] + # [ 1 ] [ τ αₖ 0 γₖ₊₁ ] [ τ αₖ 0 γₖ₊₁ ] + # [ s̄₁.ₖ₋₁ -c₁.ₖ₋₁ ] [ ᾱₖ ν βₖ₊₁ 0 ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] + σbis₂ₖ₋₂ = old_c₁ₖ * σbar₂ₖ₋₂ + old_s₁ₖ * conj(αₖ) + ηbis₂ₖ₋₂ = old_s₁ₖ * ν + λbis₂ₖ₋₂ = old_s₁ₖ * βₖ₊₁ + θbisₖ = conj(old_s₁ₖ) * σbar₂ₖ₋₂ - old_c₁ₖ * conj(αₖ) + δbis₂ₖ = - old_c₁ₖ * ν + σbis₂ₖ = - old_c₁ₖ * βₖ₊₁ + # [ c₂.ₖ₋₁ s₂.ₖ₋₁ ] [ ηbar₂ₖ₋₃ λbar₂ₖ₋₃ 0 0 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] + # [ s̄₂.ₖ₋₁ -c₂.ₖ₋₁ ] [ σbis₂ₖ₋₂ ηbis₂ₖ₋₂ λbis₂ₖ₋₂ 0 ] = [ σhat₂ₖ₋₂ ηhat₂ₖ₋₂ λhat₂ₖ₋₂ 0 ] + # [ 1 ] [ τ αₖ 0 γₖ₊₁ ] [ τ αₖ 0 γₖ₊₁ ] + # [ 1 ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] + η₂ₖ₋₃ = old_c₂ₖ * ηbar₂ₖ₋₃ + old_s₂ₖ * σbis₂ₖ₋₂ + λ₂ₖ₋₃ = old_c₂ₖ * λbar₂ₖ₋₃ + old_s₂ₖ * ηbis₂ₖ₋₂ + μ₂ₖ₋₃ = old_s₂ₖ * λbis₂ₖ₋₂ + σhat₂ₖ₋₂ = conj(old_s₂ₖ) * ηbar₂ₖ₋₃ - old_c₂ₖ * σbis₂ₖ₋₂ + ηhat₂ₖ₋₂ = conj(old_s₂ₖ) * λbar₂ₖ₋₃ - old_c₂ₖ * ηbis₂ₖ₋₂ + λhat₂ₖ₋₂ = - old_c₂ₖ * λbis₂ₖ₋₂ + # [ 1 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] + # [ c₃.ₖ₋₁ s₃.ₖ₋₁ ] [ σhat₂ₖ₋₂ ηhat₂ₖ₋₂ λhat₂ₖ₋₂ 0 ] = [ σtmp₂ₖ₋₂ ηtmp₂ₖ₋₂ λtmp₂ₖ₋₂ 0 ] + # [ 1 ] [ τ αₖ 0 γₖ₊₁ ] [ τ αₖ 0 γₖ₊₁ ] + # [ s̄₃.ₖ₋₁ -c₃.ₖ₋₁ ] [ θbisₖ δbis₂ₖ σbis₂ₖ 0 ] [ θbarₖ δbar₂ₖ σbar₂ₖ 0 ] + σtmp₂ₖ₋₂ = old_c₃ₖ * σhat₂ₖ₋₂ + old_s₃ₖ * θbisₖ + ηtmp₂ₖ₋₂ = old_c₃ₖ * ηhat₂ₖ₋₂ + old_s₃ₖ * δbis₂ₖ + λtmp₂ₖ₋₂ = old_c₃ₖ * λhat₂ₖ₋₂ + old_s₃ₖ * σbis₂ₖ + θbarₖ = conj(old_s₃ₖ) * σhat₂ₖ₋₂ - old_c₃ₖ * θbisₖ + δbar₂ₖ = conj(old_s₃ₖ) * ηhat₂ₖ₋₂ - old_c₃ₖ * δbis₂ₖ + σbar₂ₖ = conj(old_s₃ₖ) * λhat₂ₖ₋₂ - old_c₃ₖ * σbis₂ₖ + # [ 1 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] [ η₂ₖ₋₃ λ₂ₖ₋₃ μ₂ₖ₋₃ 0 ] + # [ c₄.ₖ₋₁ s₄.ₖ₋₁ ] [ σtmp₂ₖ₋₂ ηtmp₂ₖ₋₂ λtmp₂ₖ₋₂ 0 ] = [ σ₂ₖ₋₂ η₂ₖ₋₂ λ₂ₖ₋₂ μ₂ₖ₋₂ ] + # [ s̄₄.ₖ₋₁ -c₄.ₖ₋₁ ] [ τ αₖ 0 γₖ₊₁ ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ηbar₂ₖ₋₁ λbar₂ₖ₋₁ ] + # [ 1 ] [ θbarₖ δbar₂ₖ σbar₂ₖ 0 ] [ θbarₖ δbar₂ₖ σbar₂ₖ 0 ] + σ₂ₖ₋₂ = old_c₄ₖ * σtmp₂ₖ₋₂ + old_s₄ₖ * τ + η₂ₖ₋₂ = old_c₄ₖ * ηtmp₂ₖ₋₂ + old_s₄ₖ * αₖ + λ₂ₖ₋₂ = old_c₄ₖ * λtmp₂ₖ₋₂ + μ₂ₖ₋₂ = old_s₄ₖ * γₖ₊₁ + δbar₂ₖ₋₁ = conj(old_s₄ₖ) * σtmp₂ₖ₋₂ - old_c₄ₖ * τ + σbar₂ₖ₋₁ = conj(old_s₄ₖ) * ηtmp₂ₖ₋₂ - old_c₄ₖ * αₖ + ηbar₂ₖ₋₁ = conj(old_s₄ₖ) * λtmp₂ₖ₋₂ + λbar₂ₖ₋₁ = - old_c₄ₖ * γₖ₊₁ + end + + # [ 1 ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ] + # [ c₁.ₖ s₁.ₖ ] [ θbarₖ δbar₂ₖ ] = [ θₖ δbar₂ₖ ] + # [ 1 ] [ 0 βₖ₊₁ ] [ 0 βₖ₊₁ ] + # [ s̄₁.ₖ -c₁.ₖ ] [ γₖ₊₁ 0 ] [ 0 gₖ ] + (c₁ₖ, s₁ₖ, θₖ) = sym_givens(θbarₖ, γₖ₊₁) + gₖ = conj(s₁ₖ) * δbar₂ₖ + δbar₂ₖ = c₁ₖ * δbar₂ₖ + + # [ c₂.ₖ s₂.ₖ ] [ δbar₂ₖ₋₁ σbar₂ₖ₋₁ ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] + # [ s̄₂.ₖ -c₂.ₖ ] [ θₖ δbar₂ₖ ] = [ 0 δbis₂ₖ ] + # [ 1 ] [ 0 βₖ₊₁ ] [ 0 βₖ₊₁ ] + # [ 1 ] [ 0 gₖ ] [ 0 gₖ ] + (c₂ₖ, s₂ₖ, δ₂ₖ₋₁) = sym_givens(δbar₂ₖ₋₁, θₖ) + σ₂ₖ₋₁ = c₂ₖ * σbar₂ₖ₋₁ + s₂ₖ * δbar₂ₖ + δbis₂ₖ = conj(s₂ₖ) * σbar₂ₖ₋₁ - c₂ₖ * δbar₂ₖ + + # [ 1 ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] + # [ c₃.ₖ s₃.ₖ ] [ 0 δbis₂ₖ ] = [ 0 δhat₂ₖ ] + # [ 1 ] [ 0 βₖ₊₁ ] [ 0 βₖ₊₁ ] + # [ s̄₃.ₖ -c₃.ₖ ] [ 0 gₖ ] [ 0 0 ] + (c₃ₖ, s₃ₖ, δhat₂ₖ) = sym_givens(δbis₂ₖ, gₖ) + + # [ 1 ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] [ δ₂ₖ₋₁ σ₂ₖ₋₁ ] + # [ c₄.ₖ s₄.ₖ ] [ 0 δhat₂ₖ ] = [ 0 δ₂ₖ ] + # [ s̄₄.ₖ -c₄.ₖ ] [ 0 βₖ₊₁ ] [ 0 0 ] + # [ 1 ] [ 0 0 ] [ 0 0 ] + (c₄ₖ, s₄ₖ, δ₂ₖ) = sym_givens(δhat₂ₖ, βₖ₊₁) + + # Solve Gₖ = Wₖ(Rₖ)⁻¹ ⟷ (Rₖ)ᵀ(Gₖ)ᵀ = (Wₖ)ᵀ. + if iter == 1 + # [ δ₁ 0 ] [ gx₁ gy₁ ] = [ v₁ 0 ] + # [ σ₁ δ₂ ] [ gx₂ gy₂ ] [ 0 u₁ ] + @. gx₂ₖ₋₁ = vₖ / δ₂ₖ₋₁ + @. gx₂ₖ = - σ₂ₖ₋₁ / δ₂ₖ * gx₂ₖ₋₁ + @. gy₂ₖ = uₖ / δ₂ₖ + elseif iter == 2 + # [ η₁ σ₂ δ₃ 0 ] [ gx₁ gy₁ ] = [ v₂ 0 ] + # [ λ₁ η₂ σ₃ δ₄ ] [ gx₂ gy₂ ] [ 0 u₂ ] + # [ gx₃ gy₃ ] + # [ gx₄ gy₄ ] + @kswap(gx₂ₖ₋₃, gx₂ₖ₋₁) + @kswap(gx₂ₖ₋₂, gx₂ₖ) + @kswap(gy₂ₖ₋₂, gy₂ₖ) + @. gx₂ₖ₋₁ = (vₖ - η₂ₖ₋₃ * gx₂ₖ₋₃ - σ₂ₖ₋₂ * gx₂ₖ₋₂ ) / δ₂ₖ₋₁ + @. gx₂ₖ = ( - λ₂ₖ₋₃ * gx₂ₖ₋₃ - η₂ₖ₋₂ * gx₂ₖ₋₂ - σ₂ₖ₋₁ * gx₂ₖ₋₁) / δ₂ₖ + @. gy₂ₖ₋₁ = ( - η₂ₖ₋₃ * gy₂ₖ₋₃ - σ₂ₖ₋₂ * gy₂ₖ₋₂ ) / δ₂ₖ₋₁ + @. gy₂ₖ = (uₖ - λ₂ₖ₋₃ * gy₂ₖ₋₃ - η₂ₖ₋₂ * gy₂ₖ₋₂ - σ₂ₖ₋₁ * gy₂ₖ₋₁) / δ₂ₖ + else + # μ₂ₖ₋₅ * gx₂ₖ₋₅ + λ₂ₖ₋₄ * gx₂ₖ₋₄ + η₂ₖ₋₃ * gx₂ₖ₋₃ + σ₂ₖ₋₂ * gx₂ₖ₋₂ + δ₂ₖ₋₁ * gx₂ₖ₋₁ = vₖ + # μ₂ₖ₋₄ * gx₂ₖ₋₄ + λ₂ₖ₋₃ * gx₂ₖ₋₃ + η₂ₖ₋₂ * gx₂ₖ₋₂ + σ₂ₖ₋₁ * gx₂ₖ₋₁ + δ₂ₖ * gx₂ₖ = 0 + g₂ₖ₋₁ = g₂ₖ₋₅ = gx₂ₖ₋₃; g₂ₖ = g₂ₖ₋₄ = gx₂ₖ₋₂; g₂ₖ₋₃ = gx₂ₖ₋₁; g₂ₖ₋₂ = gx₂ₖ + @. g₂ₖ₋₁ = (vₖ - μ₂ₖ₋₅ * g₂ₖ₋₅ - λ₂ₖ₋₄ * g₂ₖ₋₄ - η₂ₖ₋₃ * g₂ₖ₋₃ - σ₂ₖ₋₂ * g₂ₖ₋₂ ) / δ₂ₖ₋₁ + @. g₂ₖ = ( - μ₂ₖ₋₄ * g₂ₖ₋₄ - λ₂ₖ₋₃ * g₂ₖ₋₃ - η₂ₖ₋₂ * g₂ₖ₋₂ - σ₂ₖ₋₁ * g₂ₖ₋₁) / δ₂ₖ + @kswap(gx₂ₖ₋₃, gx₂ₖ₋₁) + @kswap(gx₂ₖ₋₂, gx₂ₖ) + # μ₂ₖ₋₅ * gy₂ₖ₋₅ + λ₂ₖ₋₄ * gy₂ₖ₋₄ + η₂ₖ₋₃ * gy₂ₖ₋₃ + σ₂ₖ₋₂ * gy₂ₖ₋₂ + δ₂ₖ₋₁ * gy₂ₖ₋₁ = 0 + # μ₂ₖ₋₄ * gy₂ₖ₋₄ + λ₂ₖ₋₃ * gy₂ₖ₋₃ + η₂ₖ₋₂ * gy₂ₖ₋₂ + σ₂ₖ₋₁ * gy₂ₖ₋₁ + δ₂ₖ * gy₂ₖ = uₖ + g₂ₖ₋₁ = g₂ₖ₋₅ = gy₂ₖ₋₃; g₂ₖ = g₂ₖ₋₄ = gy₂ₖ₋₂; g₂ₖ₋₃ = gy₂ₖ₋₁; g₂ₖ₋₂ = gy₂ₖ + @. g₂ₖ₋₁ = ( - μ₂ₖ₋₅ * g₂ₖ₋₅ - λ₂ₖ₋₄ * g₂ₖ₋₄ - η₂ₖ₋₃ * g₂ₖ₋₃ - σ₂ₖ₋₂ * g₂ₖ₋₂ ) / δ₂ₖ₋₁ + @. g₂ₖ = (uₖ - μ₂ₖ₋₄ * g₂ₖ₋₄ - λ₂ₖ₋₃ * g₂ₖ₋₃ - η₂ₖ₋₂ * g₂ₖ₋₂ - σ₂ₖ₋₁ * g₂ₖ₋₁) / δ₂ₖ + @kswap(gy₂ₖ₋₃, gy₂ₖ₋₁) + @kswap(gy₂ₖ₋₂, gy₂ₖ) + end + + # Update p̅ₖ = (Qₖ)ᴴ * (β₁e₁ + γ₁e₂) + πbis₂ₖ = c₁ₖ * πbar₂ₖ + πbis₂ₖ₊₂ = conj(s₁ₖ) * πbar₂ₖ + # + π₂ₖ₋₁ = c₂ₖ * πbar₂ₖ₋₁ + s₂ₖ * πbis₂ₖ + πhat₂ₖ = conj(s₂ₖ) * πbar₂ₖ₋₁ - c₂ₖ * πbis₂ₖ + # + πtmp₂ₖ = c₃ₖ * πhat₂ₖ + s₃ₖ * πbis₂ₖ₊₂ + πbar₂ₖ₊₂ = conj(s₃ₖ) * πhat₂ₖ - c₃ₖ * πbis₂ₖ₊₂ + # + π₂ₖ = c₄ₖ * πtmp₂ₖ + πbar₂ₖ₊₁ = conj(s₄ₖ) * πtmp₂ₖ + + # Update xₖ = Gxₖ * pₖ + @kaxpy!(m, π₂ₖ₋₁, gx₂ₖ₋₁, xₖ) + @kaxpy!(m, π₂ₖ , gx₂ₖ , xₖ) + + # Update yₖ = Gyₖ * pₖ + @kaxpy!(n, π₂ₖ₋₁, gy₂ₖ₋₁, yₖ) + @kaxpy!(n, π₂ₖ , gy₂ₖ , yₖ) + + # Compute ‖rₖ‖² = |πbar₂ₖ₊₁|² + |πbar₂ₖ₊₂|² + rNorm = sqrt(abs2(πbar₂ₖ₊₁) + abs2(πbar₂ₖ₊₂)) + history && push!(rNorms, rNorm) + + # Update vₖ and uₖ + MisI || (vₖ .= vₖ₊₁) + NisI || (uₖ .= uₖ₊₁) + + # Update M⁻¹vₖ₋₁ and N⁻¹uₖ₋₁ + M⁻¹vₖ₋₁ .= M⁻¹vₖ + N⁻¹uₖ₋₁ .= N⁻¹uₖ + + # Update M⁻¹vₖ and N⁻¹uₖ + M⁻¹vₖ .= q + N⁻¹uₖ .= p + + # Update cosines and sines + old_s₁ₖ = s₁ₖ + old_s₂ₖ = s₂ₖ + old_s₃ₖ = s₃ₖ + old_s₄ₖ = s₄ₖ + old_c₁ₖ = c₁ₖ + old_c₂ₖ = c₂ₖ + old_c₃ₖ = c₃ₖ + old_c₄ₖ = c₄ₖ + + # Update workspace + βₖ = βₖ₊₁ + γₖ = γₖ₊₁ + σbar₂ₖ₋₂ = σbar₂ₖ + ηbar₂ₖ₋₃ = ηbar₂ₖ₋₁ + λbar₂ₖ₋₃ = λbar₂ₖ₋₁ + if iter ≥ 2 + μ₂ₖ₋₅ = μ₂ₖ₋₃ + μ₂ₖ₋₄ = μ₂ₖ₋₂ + λ₂ₖ₋₄ = λ₂ₖ₋₂ + end + πbar₂ₖ₋₁ = πbar₂ₖ₊₁ + πbar₂ₖ = πbar₂ₖ₊₂ + + # Stopping conditions that do not depend on user input. + # This is to guard against tolerances that are unreasonably small. + resid_decrease_mach = (rNorm + one(T) ≤ one(T)) + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + resid_decrease_lim = rNorm ≤ ε + breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol + solved = resid_decrease_lim || resid_decrease_mach + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) end - πbar₂ₖ₋₁ = πbar₂ₖ₊₁ - πbar₂ₖ = πbar₂ₖ₊₂ - - # Stopping conditions that do not depend on user input. - # This is to guard against tolerances that are unreasonably small. - resid_decrease_mach = (rNorm + one(T) ≤ one(T)) - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - resid_decrease_lim = rNorm ≤ ε - breakdown = βₖ₊₁ ≤ btol && γₖ₊₁ ≤ btol - solved = resid_decrease_lim || resid_decrease_mach - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + (verbose > 0) && @printf(iostream, "\n") + + # Termination status + tired && (status = "maximum number of iterations exceeded") + breakdown && (status = "inconsistent linear system") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") + + # Update x and y + warm_start && @kaxpy!(m, one(FC), Δx, xₖ) + warm_start && @kaxpy!(n, one(FC), Δy, yₖ) + solver.warm_start = false + + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = !solved && breakdown + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - breakdown && (status = "inconsistent linear system") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x and y - warm_start && @kaxpy!(m, one(FC), Δx, xₖ) - warm_start && @kaxpy!(n, one(FC), Δy, yₖ) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = !solved && breakdown - stats.status = status - return solver end diff --git a/src/usymlq.jl b/src/usymlq.jl index ebd05163c..bc90a07f2 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -115,253 +115,250 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, return (solver.x, solver.stats) end - function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return solver end -end - -function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - transfer_to_usymcg :: Bool=true, atol :: T=√eps(T), - rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "USYMLQ: system of %d equations in %d variables\n", m, n) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - uₖ₋₁, uₖ, p, Δx, x = solver.uₖ₋₁, solver.uₖ, solver.p, solver.Δx, solver.x - vₖ₋₁, vₖ, q, d̅, stats = solver.vₖ₋₁, solver.vₖ, solver.q, solver.d̅, solver.stats - warm_start = solver.warm_start - rNorms = stats.residuals - reset!(stats) - r₀ = warm_start ? q : b - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - end - # Initial solution x₀ and residual norm ‖r₀‖. - x .= zero(FC) - bNorm = @knrm2(m, r₀) - history && push!(rNorms, bNorm) - if bNorm == 0 - stats.niter = 0 - stats.solved = true - stats.inconsistent = false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - - iter = 0 - itmax == 0 && (itmax = m+n) - - ε = atol + rtol * bNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) - - βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ - γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ - vₖ₋₁ .= zero(FC) # v₀ = 0 - uₖ₋₁ .= zero(FC) # u₀ = 0 - vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= c ./ γₖ # u₁ = c / γ₁ - cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ - sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ - d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᴴ - ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ - ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ - δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and Lₖ modified over the course of two iterations - - # Stopping criterion. - solved_lq = bNorm ≤ ε - solved_cg = false - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved_lq || solved_cg || tired || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the SSY tridiagonalization process. - # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ - mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ - - @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ - @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ - - αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ - - @kaxpy!(m, - αₖ , vₖ, q) # q ← q - αₖ * vₖ - @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - - βₖ₊₁ = @knrm2(m, q) # βₖ₊₁ = ‖q‖ - γₖ₊₁ = @knrm2(n, p) # γₖ₊₁ = ‖p‖ - - # Update the LQ factorization of Tₖ = L̅ₖQₖ. - # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] - # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] - # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] - # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ - # [ • • • • • 0 ] [ • • • • • • • ] - # [ • • • • γₖ] [ • • • • • 0 ] - # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] - - if iter == 1 - δbarₖ = αₖ - elseif iter == 2 - # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] - # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - λₖ₋₁ = cₖ * βₖ + sₖ * αₖ - δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ - else - # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] - # [sₖ₋₁ -cₖ₋₁ 0] - # [ 0 0 1] - # - # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] - # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] - # [0 sₖ -cₖ] - (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) - ϵₖ₋₂ = sₖ₋₁ * βₖ - λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ - δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "USYMLQ: system of %d equations in %d variables\n", m, n) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + uₖ₋₁, uₖ, p, Δx, x = solver.uₖ₋₁, solver.uₖ, solver.p, solver.Δx, solver.x + vₖ₋₁, vₖ, q, d̅, stats = solver.vₖ₋₁, solver.vₖ, solver.q, solver.d̅, solver.stats + warm_start = solver.warm_start + rNorms = stats.residuals + reset!(stats) + r₀ = warm_start ? q : b + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) end - # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ - # [δbar₁] [ζbar₁] = [β₁] - if iter == 1 - ηₖ = βₖ - end - # [δ₁ 0 ] [ ζ₁ ] = [β₁] - # [λ₁ δbar₂] [ζbar₂] [0 ] - if iter == 2 - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -λₖ₋₁ * ζₖ₋₁ - end - # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] - # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] - # [ζbarₖ] - if iter ≥ 3 - ζₖ₋₂ = ζₖ₋₁ - ηₖ₋₁ = ηₖ - ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ - ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ + # Initial solution x₀ and residual norm ‖r₀‖. + x .= zero(FC) + bNorm = @knrm2(m, r₀) + history && push!(rNorms, bNorm) + if bNorm == 0 + stats.niter = 0 + stats.solved = true + stats.inconsistent = false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver end - # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᴴ. - # [d̅ₖ₋₁ uₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * uₖ - # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ - if iter ≥ 2 - # Compute solution xₖ. - # (xᴸ)ₖ₋₁ ← (xᴸ)ₖ₋₂ + ζₖ₋₁ * dₖ₋₁ - @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) - @kaxpy!(n, ζₖ₋₁ * sₖ, uₖ, x) + iter = 0 + itmax == 0 && (itmax = m+n) + + ε = atol + rtol * bNorm + (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) + + βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ + γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ + vₖ₋₁ .= zero(FC) # v₀ = 0 + uₖ₋₁ .= zero(FC) # u₀ = 0 + vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ + uₖ .= c ./ γₖ # u₁ = c / γ₁ + cₖ₋₁ = cₖ = -one(T) # Givens cosines used for the LQ factorization of Tₖ + sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the LQ factorization of Tₖ + d̅ .= zero(FC) # Last column of D̅ₖ = Uₖ(Qₖ)ᴴ + ζₖ₋₁ = ζbarₖ = zero(FC) # ζₖ₋₁ and ζbarₖ are the last components of z̅ₖ = (L̅ₖ)⁻¹β₁e₁ + ζₖ₋₂ = ηₖ = zero(FC) # ζₖ₋₂ and ηₖ are used to update ζₖ₋₁ and ζbarₖ + δbarₖ₋₁ = δbarₖ = zero(FC) # Coefficients of Lₖ₋₁ and Lₖ modified over the course of two iterations + + # Stopping criterion. + solved_lq = bNorm ≤ ε + solved_cg = false + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved_lq || solved_cg || tired || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 + + # Continue the SSY tridiagonalization process. + # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ + # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ + + mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ + mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ + + @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ + @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ + + αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ + + @kaxpy!(m, - αₖ , vₖ, q) # q ← q - αₖ * vₖ + @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ + + βₖ₊₁ = @knrm2(m, q) # βₖ₊₁ = ‖q‖ + γₖ₊₁ = @knrm2(n, p) # γₖ₊₁ = ‖p‖ + + # Update the LQ factorization of Tₖ = L̅ₖQₖ. + # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ 0 • • • • 0 ] + # [ β₂ α₂ γ₃ • • ] [ λ₁ δ₂ • • ] + # [ 0 • • • • • ] [ ϵ₁ λ₂ δ₃ • • ] + # [ • • • • • • • ] = [ 0 • • • • • ] Qₖ + # [ • • • • • 0 ] [ • • • • • • • ] + # [ • • • • γₖ] [ • • • • • 0 ] + # [ 0 • • • 0 βₖ αₖ] [ • • • 0 ϵₖ₋₂ λₖ₋₁ δbarₖ] + + if iter == 1 + δbarₖ = αₖ + elseif iter == 2 + # [δbar₁ γ₂] [c₂ s̄₂] = [δ₁ 0 ] + # [ β₂ α₂] [s₂ -c₂] [λ₁ δbar₂] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + λₖ₋₁ = cₖ * βₖ + sₖ * αₖ + δbarₖ = conj(sₖ) * βₖ - cₖ * αₖ + else + # [0 βₖ αₖ] [cₖ₋₁ s̄ₖ₋₁ 0] = [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] + # [sₖ₋₁ -cₖ₋₁ 0] + # [ 0 0 1] + # + # [ λₖ₋₂ δbarₖ₋₁ γₖ] [1 0 0 ] = [λₖ₋₂ δₖ₋₁ 0 ] + # [sₖ₋₁βₖ -cₖ₋₁βₖ αₖ] [0 cₖ s̄ₖ] [ϵₖ₋₂ λₖ₋₁ δbarₖ] + # [0 sₖ -cₖ] + (cₖ, sₖ, δₖ₋₁) = sym_givens(δbarₖ₋₁, γₖ) + ϵₖ₋₂ = sₖ₋₁ * βₖ + λₖ₋₁ = -cₖ₋₁ * cₖ * βₖ + sₖ * αₖ + δbarₖ = -cₖ₋₁ * conj(sₖ) * βₖ - cₖ * αₖ + end + + # Compute ζₖ₋₁ and ζbarₖ, last components of the solution of L̅ₖz̅ₖ = β₁e₁ + # [δbar₁] [ζbar₁] = [β₁] + if iter == 1 + ηₖ = βₖ + end + # [δ₁ 0 ] [ ζ₁ ] = [β₁] + # [λ₁ δbar₂] [ζbar₂] [0 ] + if iter == 2 + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -λₖ₋₁ * ζₖ₋₁ + end + # [λₖ₋₂ δₖ₋₁ 0 ] [ζₖ₋₂ ] = [0] + # [ϵₖ₋₂ λₖ₋₁ δbarₖ] [ζₖ₋₁ ] [0] + # [ζbarₖ] + if iter ≥ 3 + ζₖ₋₂ = ζₖ₋₁ + ηₖ₋₁ = ηₖ + ζₖ₋₁ = ηₖ₋₁ / δₖ₋₁ + ηₖ = -ϵₖ₋₂ * ζₖ₋₂ - λₖ₋₁ * ζₖ₋₁ + end + + # Relations for the directions dₖ₋₁ and d̅ₖ, the last two columns of D̅ₖ = Uₖ(Qₖ)ᴴ. + # [d̅ₖ₋₁ uₖ] [cₖ s̄ₖ] = [dₖ₋₁ d̅ₖ] ⟷ dₖ₋₁ = cₖ * d̅ₖ₋₁ + sₖ * uₖ + # [sₖ -cₖ] ⟷ d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ + if iter ≥ 2 + # Compute solution xₖ. + # (xᴸ)ₖ₋₁ ← (xᴸ)ₖ₋₂ + ζₖ₋₁ * dₖ₋₁ + @kaxpy!(n, ζₖ₋₁ * cₖ, d̅, x) + @kaxpy!(n, ζₖ₋₁ * sₖ, uₖ, x) + end + + # Compute d̅ₖ. + if iter == 1 + # d̅₁ = u₁ + @. d̅ = uₖ + else + # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ + @kaxpby!(n, -cₖ, uₖ, conj(sₖ), d̅) + end + + # Compute uₖ₊₁ and uₖ₊₁. + @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ + @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + + if βₖ₊₁ ≠ zero(T) + @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q + end + if γₖ₊₁ ≠ zero(T) + @. uₖ = p / γₖ₊₁ # γₖ₊₁uₖ₊₁ = p + end + + # Compute USYMLQ residual norm + # ‖rₖ‖ = √(|μₖ|² + |ωₖ|²) + if iter == 1 + rNorm_lq = bNorm + else + μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ + ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ + rNorm_lq = sqrt(abs2(μₖ) + abs2(ωₖ)) + end + history && push!(rNorms, rNorm_lq) + + # Compute USYMCG residual norm + # ‖rₖ‖ = |ρₖ| + if transfer_to_usymcg && (abs(δbarₖ) > eps(T)) + ζbarₖ = ηₖ / δbarₖ + ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) + rNorm_cg = abs(ρₖ) + end + + # Update sₖ₋₁, cₖ₋₁, γₖ, βₖ and δbarₖ₋₁. + sₖ₋₁ = sₖ + cₖ₋₁ = cₖ + γₖ = γₖ₊₁ + βₖ = βₖ₊₁ + δbarₖ₋₁ = δbarₖ + + # Update stopping criterion. + user_requested_exit = callback(solver) :: Bool + solved_lq = rNorm_lq ≤ ε + solved_cg = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) end + (verbose > 0) && @printf(iostream, "\n") - # Compute d̅ₖ. - if iter == 1 - # d̅₁ = u₁ - @. d̅ = uₖ - else - # d̅ₖ = s̄ₖ * d̅ₖ₋₁ - cₖ * uₖ - @kaxpby!(n, -cₖ, uₖ, conj(sₖ), d̅) + # Compute USYMCG point + # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ + if solved_cg + @kaxpy!(n, ζbarₖ, d̅, x) end - # Compute uₖ₊₁ and uₖ₊₁. - @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ - @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved_lq && (status = "solution xᴸ good enough given atol and rtol") + solved_cg && (status = "solution xᶜ good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - if βₖ₊₁ ≠ zero(T) - @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q - end - if γₖ₊₁ ≠ zero(T) - @. uₖ = p / γₖ₊₁ # γₖ₊₁uₖ₊₁ = p - end - - # Compute USYMLQ residual norm - # ‖rₖ‖ = √(|μₖ|² + |ωₖ|²) - if iter == 1 - rNorm_lq = bNorm - else - μₖ = βₖ * (sₖ₋₁ * ζₖ₋₂ - cₖ₋₁ * cₖ * ζₖ₋₁) + αₖ * sₖ * ζₖ₋₁ - ωₖ = βₖ₊₁ * sₖ * ζₖ₋₁ - rNorm_lq = sqrt(abs2(μₖ) + abs2(ωₖ)) - end - history && push!(rNorms, rNorm_lq) - - # Compute USYMCG residual norm - # ‖rₖ‖ = |ρₖ| - if transfer_to_usymcg && (abs(δbarₖ) > eps(T)) - ζbarₖ = ηₖ / δbarₖ - ρₖ = βₖ₊₁ * (sₖ * ζₖ₋₁ - cₖ * ζbarₖ) - rNorm_cg = abs(ρₖ) - end - - # Update sₖ₋₁, cₖ₋₁, γₖ, βₖ and δbarₖ₋₁. - sₖ₋₁ = sₖ - cₖ₋₁ = cₖ - γₖ = γₖ₊₁ - βₖ = βₖ₊₁ - δbarₖ₋₁ = δbarₖ - - # Update stopping criterion. - user_requested_exit = callback(solver) :: Bool - solved_lq = rNorm_lq ≤ ε - solved_cg = transfer_to_usymcg && (abs(δbarₖ) > eps(T)) && (rNorm_cg ≤ ε) - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) - end - (verbose > 0) && @printf(iostream, "\n") + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Compute USYMCG point - # (xᶜ)ₖ ← (xᴸ)ₖ₋₁ + ζbarₖ * d̅ₖ - if solved_cg - @kaxpy!(n, ζbarₖ, d̅, x) + # Update stats + stats.niter = iter + stats.solved = solved_lq || solved_cg + stats.inconsistent = false + stats.status = status + return solver end - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved_lq && (status = "solution xᴸ good enough given atol and rtol") - solved_cg && (status = "solution xᶜ good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved_lq || solved_cg - stats.inconsistent = false - stats.status = status - return solver end diff --git a/src/usymqr.jl b/src/usymqr.jl index aa62f9c54..bc9dc5d03 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -113,245 +113,243 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, return (solver.x, solver.stats) end - function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, - x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} warm_start!(solver, x0) usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return solver end -end - -function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; - atol :: T=√eps(T), rtol :: T=√eps(T), itmax :: Int=0, - timemax :: Float64=Inf, verbose :: Int=0, history :: Bool=false, - callback = solver -> false, iostream :: IO=kstdout) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - - start_time = time_ns() - timemax_ns = 1e9 * timemax - m, n = size(A) - (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") - length(b) == m || error("Inconsistent problem size") - length(c) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "USYMQR: system of %d equations in %d variables\n", m, n) - - # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") - ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") - - # Compute the adjoint of A - Aᴴ = A' - - # Set up workspace. - vₖ₋₁, vₖ, q, Δx, x, p = solver.vₖ₋₁, solver.vₖ, solver.q, solver.Δx, solver.x, solver.p - wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, stats = solver.wₖ₋₂, solver.wₖ₋₁, solver.uₖ₋₁, solver.uₖ, solver.stats - warm_start = solver.warm_start - rNorms, AᴴrNorms = stats.residuals, stats.Aresiduals - reset!(stats) - r₀ = warm_start ? q : b - - if warm_start - mul!(r₀, A, Δx) - @kaxpby!(n, one(FC), b, -one(FC), r₀) - end - - # Initial solution x₀ and residual norm ‖r₀‖. - x .= zero(FC) - rNorm = @knrm2(m, r₀) - history && push!(rNorms, rNorm) - if rNorm == 0 - stats.niter = 0 - stats.solved = true - stats.inconsistent = false - stats.status = "x = 0 is a zero-residual solution" - solver.warm_start = false - return solver - end - iter = 0 - itmax == 0 && (itmax = m+n) - - ε = atol + rtol * rNorm - κ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm, "✗ ✗ ✗ ✗") - - βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ - γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ - vₖ₋₁ .= zero(FC) # v₀ = 0 - uₖ₋₁ .= zero(FC) # u₀ = 0 - vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ - uₖ .= c ./ γₖ # u₁ = c / γ₁ - cₖ₋₂ = cₖ₋₁ = cₖ = one(T) # Givens cosines used for the QR factorization of Tₖ₊₁.ₖ - sₖ₋₂ = sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ - wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Rₖ)⁻¹ - wₖ₋₁ .= zero(FC) # Column k-1 of Wₖ = Uₖ(Rₖ)⁻¹ - ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᴴβ₁e₁ - - # Stopping criterion. - solved = rNorm ≤ ε - inconsistent = false - tired = iter ≥ itmax - status = "unknown" - user_requested_exit = false - overtimed = false - - while !(solved || tired || inconsistent || user_requested_exit || overtimed) - # Update iteration index. - iter = iter + 1 - - # Continue the SSY tridiagonalization process. - # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ - # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ - - mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ - mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ - - @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ - @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ - - αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ - - @kaxpy!(m, - αₖ , vₖ, q) # q ← q - αₖ * vₖ - @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ - - βₖ₊₁ = @knrm2(m, q) # βₖ₊₁ = ‖q‖ - γₖ₊₁ = @knrm2(n, p) # γₖ₊₁ = ‖p‖ - - # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. - # [ Oᵀ ] - # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ λ₁ ϵ₁ 0 • • 0 ] - # [ β₂ α₂ γ₃ • • ] [ 0 δ₂ λ₂ • • • ] - # [ 0 • • • • • ] [ • • δ₃ • • • • ] - # [ • • • • • • • ] = Qₖ [ • • • • • 0 ] - # [ • • • • • 0 ] [ • • • • ϵₖ₋₂] - # [ • • • • γₖ ] [ • • • λₖ₋₁] - # [ • • βₖ αₖ ] [ 0 • • • • 0 δₖ ] - # [ 0 • • • • 0 βₖ₊₁] [ 0 • • • • • 0 ] - # - # If k = 1, we don't have any previous reflexion. - # If k = 2, we apply the last reflexion. - # If k ≥ 3, we only apply the two previous reflexions. - - # Apply previous Givens reflections Qₖ₋₂.ₖ₋₁ - if iter ≥ 3 - # [cₖ₋₂ sₖ₋₂] [0 ] = [ ϵₖ₋₂ ] - # [s̄ₖ₋₂ -cₖ₋₂] [γₖ] [λbarₖ₋₁] - ϵₖ₋₂ = sₖ₋₂ * γₖ - λbarₖ₋₁ = -cₖ₋₂ * γₖ + function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + + # Timer + start_time = time_ns() + timemax_ns = 1e9 * timemax + + m, n = size(A) + (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") + length(b) == m || error("Inconsistent problem size") + length(c) == n || error("Inconsistent problem size") + (verbose > 0) && @printf(iostream, "USYMQR: system of %d equations in %d variables\n", m, n) + + # Check type consistency + eltype(A) == FC || error("eltype(A) ≠ $FC") + ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") + ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") + + # Compute the adjoint of A + Aᴴ = A' + + # Set up workspace. + vₖ₋₁, vₖ, q, Δx, x, p = solver.vₖ₋₁, solver.vₖ, solver.q, solver.Δx, solver.x, solver.p + wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, stats = solver.wₖ₋₂, solver.wₖ₋₁, solver.uₖ₋₁, solver.uₖ, solver.stats + warm_start = solver.warm_start + rNorms, AᴴrNorms = stats.residuals, stats.Aresiduals + reset!(stats) + r₀ = warm_start ? q : b + + if warm_start + mul!(r₀, A, Δx) + @kaxpby!(n, one(FC), b, -one(FC), r₀) end - # Apply previous Givens reflections Qₖ₋₁.ₖ - if iter ≥ 2 - iter == 2 && (λbarₖ₋₁ = γₖ) - # [cₖ₋₁ sₖ₋₁] [λbarₖ₋₁] = [λₖ₋₁ ] - # [s̄ₖ₋₁ -cₖ₋₁] [ αₖ ] [δbarₖ] - λₖ₋₁ = cₖ₋₁ * λbarₖ₋₁ + sₖ₋₁ * αₖ - δbarₖ = conj(sₖ₋₁) * λbarₖ₋₁ - cₖ₋₁ * αₖ + # Initial solution x₀ and residual norm ‖r₀‖. + x .= zero(FC) + rNorm = @knrm2(m, r₀) + history && push!(rNorms, rNorm) + if rNorm == 0 + stats.niter = 0 + stats.solved = true + stats.inconsistent = false + stats.status = "x = 0 is a zero-residual solution" + solver.warm_start = false + return solver end - # Compute and apply current Givens reflection Qₖ.ₖ₊₁ - iter == 1 && (δbarₖ = αₖ) - # [cₖ sₖ] [δbarₖ] = [δₖ] - # [s̄ₖ -cₖ] [βₖ₊₁ ] [0 ] - (cₖ, sₖ, δₖ) = sym_givens(δbarₖ, βₖ₊₁) - - # Update z̅ₖ₊₁ = Qₖ.ₖ₊₁ [ z̄ₖ ] - # [ 0 ] - # - # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] - # [s̄ₖ -cₖ] [ 0 ] [ζbarₖ₊₁] - ζₖ = cₖ * ζbarₖ - ζbarₖ₊₁ = conj(sₖ) * ζbarₖ - - # Compute the direction wₖ, the last column of Wₖ = Uₖ(Rₖ)⁻¹ ⟷ (Rₖ)ᵀ(Wₖ)ᵀ = (Uₖ)ᵀ. - # w₁ = u₁ / δ₁ - if iter == 1 - wₖ = wₖ₋₁ - @kaxpy!(n, one(FC), uₖ, wₖ) - @. wₖ = wₖ / δₖ - end - # w₂ = (u₂ - λ₁w₁) / δ₂ - if iter == 2 - wₖ = wₖ₋₂ - @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) - @kaxpy!(n, one(FC), uₖ, wₖ) - @. wₖ = wₖ / δₖ - end - # wₖ = (uₖ - λₖ₋₁wₖ₋₁ - ϵₖ₋₂wₖ₋₂) / δₖ - if iter ≥ 3 - @kscal!(n, -ϵₖ₋₂, wₖ₋₂) - wₖ = wₖ₋₂ - @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) - @kaxpy!(n, one(FC), uₖ, wₖ) - @. wₖ = wₖ / δₖ + iter = 0 + itmax == 0 && (itmax = m+n) + + ε = atol + rtol * rNorm + κ = zero(T) + (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm, "✗ ✗ ✗ ✗") + + βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ + γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ + vₖ₋₁ .= zero(FC) # v₀ = 0 + uₖ₋₁ .= zero(FC) # u₀ = 0 + vₖ .= r₀ ./ βₖ # v₁ = (b - Ax₀) / β₁ + uₖ .= c ./ γₖ # u₁ = c / γ₁ + cₖ₋₂ = cₖ₋₁ = cₖ = one(T) # Givens cosines used for the QR factorization of Tₖ₊₁.ₖ + sₖ₋₂ = sₖ₋₁ = sₖ = zero(FC) # Givens sines used for the QR factorization of Tₖ₊₁.ₖ + wₖ₋₂ .= zero(FC) # Column k-2 of Wₖ = Uₖ(Rₖ)⁻¹ + wₖ₋₁ .= zero(FC) # Column k-1 of Wₖ = Uₖ(Rₖ)⁻¹ + ζbarₖ = βₖ # ζbarₖ is the last component of z̅ₖ = (Qₖ)ᴴβ₁e₁ + + # Stopping criterion. + solved = rNorm ≤ ε + inconsistent = false + tired = iter ≥ itmax + status = "unknown" + user_requested_exit = false + overtimed = false + + while !(solved || tired || inconsistent || user_requested_exit || overtimed) + # Update iteration index. + iter = iter + 1 + + # Continue the SSY tridiagonalization process. + # AUₖ = VₖTₖ + βₖ₊₁vₖ₊₁(eₖ)ᵀ = Vₖ₊₁Tₖ₊₁.ₖ + # AᴴVₖ = Uₖ(Tₖ)ᴴ + γₖ₊₁uₖ₊₁(eₖ)ᵀ = Uₖ₊₁(Tₖ.ₖ₊₁)ᴴ + + mul!(q, A , uₖ) # Forms vₖ₊₁ : q ← Auₖ + mul!(p, Aᴴ, vₖ) # Forms uₖ₊₁ : p ← Aᴴvₖ + + @kaxpy!(m, -γₖ, vₖ₋₁, q) # q ← q - γₖ * vₖ₋₁ + @kaxpy!(n, -βₖ, uₖ₋₁, p) # p ← p - βₖ * uₖ₋₁ + + αₖ = @kdot(m, vₖ, q) # αₖ = ⟨vₖ,q⟩ + + @kaxpy!(m, - αₖ , vₖ, q) # q ← q - αₖ * vₖ + @kaxpy!(n, -conj(αₖ), uₖ, p) # p ← p - ᾱₖ * uₖ + + βₖ₊₁ = @knrm2(m, q) # βₖ₊₁ = ‖q‖ + γₖ₊₁ = @knrm2(n, p) # γₖ₊₁ = ‖p‖ + + # Update the QR factorization of Tₖ₊₁.ₖ = Qₖ [ Rₖ ]. + # [ Oᵀ ] + # [ α₁ γ₂ 0 • • • 0 ] [ δ₁ λ₁ ϵ₁ 0 • • 0 ] + # [ β₂ α₂ γ₃ • • ] [ 0 δ₂ λ₂ • • • ] + # [ 0 • • • • • ] [ • • δ₃ • • • • ] + # [ • • • • • • • ] = Qₖ [ • • • • • 0 ] + # [ • • • • • 0 ] [ • • • • ϵₖ₋₂] + # [ • • • • γₖ ] [ • • • λₖ₋₁] + # [ • • βₖ αₖ ] [ 0 • • • • 0 δₖ ] + # [ 0 • • • • 0 βₖ₊₁] [ 0 • • • • • 0 ] + # + # If k = 1, we don't have any previous reflexion. + # If k = 2, we apply the last reflexion. + # If k ≥ 3, we only apply the two previous reflexions. + + # Apply previous Givens reflections Qₖ₋₂.ₖ₋₁ + if iter ≥ 3 + # [cₖ₋₂ sₖ₋₂] [0 ] = [ ϵₖ₋₂ ] + # [s̄ₖ₋₂ -cₖ₋₂] [γₖ] [λbarₖ₋₁] + ϵₖ₋₂ = sₖ₋₂ * γₖ + λbarₖ₋₁ = -cₖ₋₂ * γₖ + end + + # Apply previous Givens reflections Qₖ₋₁.ₖ + if iter ≥ 2 + iter == 2 && (λbarₖ₋₁ = γₖ) + # [cₖ₋₁ sₖ₋₁] [λbarₖ₋₁] = [λₖ₋₁ ] + # [s̄ₖ₋₁ -cₖ₋₁] [ αₖ ] [δbarₖ] + λₖ₋₁ = cₖ₋₁ * λbarₖ₋₁ + sₖ₋₁ * αₖ + δbarₖ = conj(sₖ₋₁) * λbarₖ₋₁ - cₖ₋₁ * αₖ + end + + # Compute and apply current Givens reflection Qₖ.ₖ₊₁ + iter == 1 && (δbarₖ = αₖ) + # [cₖ sₖ] [δbarₖ] = [δₖ] + # [s̄ₖ -cₖ] [βₖ₊₁ ] [0 ] + (cₖ, sₖ, δₖ) = sym_givens(δbarₖ, βₖ₊₁) + + # Update z̅ₖ₊₁ = Qₖ.ₖ₊₁ [ z̄ₖ ] + # [ 0 ] + # + # [cₖ sₖ] [ζbarₖ] = [ ζₖ ] + # [s̄ₖ -cₖ] [ 0 ] [ζbarₖ₊₁] + ζₖ = cₖ * ζbarₖ + ζbarₖ₊₁ = conj(sₖ) * ζbarₖ + + # Compute the direction wₖ, the last column of Wₖ = Uₖ(Rₖ)⁻¹ ⟷ (Rₖ)ᵀ(Wₖ)ᵀ = (Uₖ)ᵀ. + # w₁ = u₁ / δ₁ + if iter == 1 + wₖ = wₖ₋₁ + @kaxpy!(n, one(FC), uₖ, wₖ) + @. wₖ = wₖ / δₖ + end + # w₂ = (u₂ - λ₁w₁) / δ₂ + if iter == 2 + wₖ = wₖ₋₂ + @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) + @kaxpy!(n, one(FC), uₖ, wₖ) + @. wₖ = wₖ / δₖ + end + # wₖ = (uₖ - λₖ₋₁wₖ₋₁ - ϵₖ₋₂wₖ₋₂) / δₖ + if iter ≥ 3 + @kscal!(n, -ϵₖ₋₂, wₖ₋₂) + wₖ = wₖ₋₂ + @kaxpy!(n, -λₖ₋₁, wₖ₋₁, wₖ) + @kaxpy!(n, one(FC), uₖ, wₖ) + @. wₖ = wₖ / δₖ + end + + # Compute solution xₖ. + # xₖ ← xₖ₋₁ + ζₖ * wₖ + @kaxpy!(n, ζₖ, wₖ, x) + + # Compute ‖rₖ‖ = |ζbarₖ₊₁|. + rNorm = abs(ζbarₖ₊₁) + history && push!(rNorms, rNorm) + + # Compute ‖Aᴴrₖ₋₁‖ = |ζbarₖ| * √(|δbarₖ|² + |λbarₖ|²). + AᴴrNorm = abs(ζbarₖ) * √(abs2(δbarₖ) + abs2(cₖ₋₁ * γₖ₊₁)) + history && push!(AᴴrNorms, AᴴrNorm) + + # Compute uₖ₊₁ and uₖ₊₁. + @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ + @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ + + if βₖ₊₁ ≠ zero(T) + @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q + end + if γₖ₊₁ ≠ zero(T) + @. uₖ = p / γₖ₊₁ # γₖ₊₁uₖ₊₁ = p + end + + # Update directions for x. + if iter ≥ 2 + @kswap(wₖ₋₂, wₖ₋₁) + end + + # Update sₖ₋₂, cₖ₋₂, sₖ₋₁, cₖ₋₁, ζbarₖ, γₖ, βₖ. + if iter ≥ 2 + sₖ₋₂ = sₖ₋₁ + cₖ₋₂ = cₖ₋₁ + end + sₖ₋₁ = sₖ + cₖ₋₁ = cₖ + ζbarₖ = ζbarₖ₊₁ + γₖ = γₖ₊₁ + βₖ = βₖ₊₁ + + # Update stopping criterion. + iter == 1 && (κ = atol + rtol * AᴴrNorm) + user_requested_exit = callback(solver) :: Bool + solved = rNorm ≤ ε + inconsistent = !solved && AᴴrNorm ≤ κ + tired = iter ≥ itmax + timer = time_ns() - start_time + overtimed = timer > timemax_ns + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) end + (verbose > 0) && @printf(iostream, "\n") - # Compute solution xₖ. - # xₖ ← xₖ₋₁ + ζₖ * wₖ - @kaxpy!(n, ζₖ, wₖ, x) + # Termination status + tired && (status = "maximum number of iterations exceeded") + solved && (status = "solution good enough given atol and rtol") + user_requested_exit && (status = "user-requested exit") + overtimed && (status = "time limit exceeded") - # Compute ‖rₖ‖ = |ζbarₖ₊₁|. - rNorm = abs(ζbarₖ₊₁) - history && push!(rNorms, rNorm) - - # Compute ‖Aᴴrₖ₋₁‖ = |ζbarₖ| * √(|δbarₖ|² + |λbarₖ|²). - AᴴrNorm = abs(ζbarₖ) * √(abs2(δbarₖ) + abs2(cₖ₋₁ * γₖ₊₁)) - history && push!(AᴴrNorms, AᴴrNorm) - - # Compute uₖ₊₁ and uₖ₊₁. - @. vₖ₋₁ = vₖ # vₖ₋₁ ← vₖ - @. uₖ₋₁ = uₖ # uₖ₋₁ ← uₖ - - if βₖ₊₁ ≠ zero(T) - @. vₖ = q / βₖ₊₁ # βₖ₊₁vₖ₊₁ = q - end - if γₖ₊₁ ≠ zero(T) - @. uₖ = p / γₖ₊₁ # γₖ₊₁uₖ₊₁ = p - end - - # Update directions for x. - if iter ≥ 2 - @kswap(wₖ₋₂, wₖ₋₁) - end + # Update x + warm_start && @kaxpy!(n, one(FC), Δx, x) + solver.warm_start = false - # Update sₖ₋₂, cₖ₋₂, sₖ₋₁, cₖ₋₁, ζbarₖ, γₖ, βₖ. - if iter ≥ 2 - sₖ₋₂ = sₖ₋₁ - cₖ₋₂ = cₖ₋₁ - end - sₖ₋₁ = sₖ - cₖ₋₁ = cₖ - ζbarₖ = ζbarₖ₊₁ - γₖ = γₖ₊₁ - βₖ = βₖ₊₁ - - # Update stopping criterion. - iter == 1 && (κ = atol + rtol * AᴴrNorm) - user_requested_exit = callback(solver) :: Bool - solved = rNorm ≤ ε - inconsistent = !solved && AᴴrNorm ≤ κ - tired = iter ≥ itmax - timer = time_ns() - start_time - overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) + # Update stats + stats.niter = iter + stats.solved = solved + stats.inconsistent = inconsistent + stats.status = status + return solver end - (verbose > 0) && @printf(iostream, "\n") - - # Termination status - tired && (status = "maximum number of iterations exceeded") - solved && (status = "solution good enough given atol and rtol") - user_requested_exit && (status = "user-requested exit") - overtimed && (status = "time limit exceeded") - - # Update x - warm_start && @kaxpy!(n, one(FC), Δx, x) - solver.warm_start = false - - # Update stats - stats.niter = iter - stats.solved = solved - stats.inconsistent = inconsistent - stats.status = status - return solver end From 0ff88a26fc330379f31500feedc164b6d84dd663 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 17:38:45 -0400 Subject: [PATCH 157/182] Update codecov.yml --- .github/codecov.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index c3a812781..e3469746f 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,8 +1,11 @@ +# Drops on the order 0.01% are typical even when no change occurs +# Having the threshold set a little higher (0.5%) than that makes it +# a little more tolerant to fluctuations coverage: status: project: default: - # Drops on the order 0.01% are typical even when no change occurs - # Having this threshold set a little higher (0.5%) than that makes it - # a little more tolerant to fluctuations + threshold: 0.5% + patch: + default: threshold: 0.5% From b901092cf37167a2b73e72eb18b50b0d08550d78 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 17:26:47 -0400 Subject: [PATCH 158/182] Display a warning when the operator and the right-hand side don't have the same type --- src/bicgstab.jl | 2 +- src/bilq.jl | 2 +- src/bilqr.jl | 2 +- src/cg.jl | 2 +- src/cg_lanczos_shift.jl | 2 +- src/cgls.jl | 2 +- src/cgne.jl | 2 +- src/cgs.jl | 2 +- src/cr.jl | 2 +- src/craig.jl | 2 +- src/craigmr.jl | 2 +- src/crls.jl | 2 +- src/crmr.jl | 2 +- src/diom.jl | 2 +- src/dqgmres.jl | 2 +- src/fgmres.jl | 2 +- src/fom.jl | 2 +- src/gmres.jl | 2 +- src/gpmr.jl | 4 ++-- src/lnlq.jl | 2 +- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 2 +- src/minres_qlp.jl | 2 +- src/qmr.jl | 2 +- src/symmlq.jl | 2 +- src/tricg.jl | 2 +- src/trilqr.jl | 2 +- src/trimr.jl | 2 +- src/usymlq.jl | 2 +- src/usymqr.jl | 2 +- 32 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 2c7b7535e..6b39bf847 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -141,7 +141,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/bilq.jl b/src/bilq.jl index 6c9e6be69..d21480fac 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -122,7 +122,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, (verbose > 0) && @printf(iostream, "BILQ: system of size %d\n", n) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/bilqr.jl b/src/bilqr.jl index 387219b23..cb6752b7b 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -127,7 +127,7 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi (verbose > 0) && @printf(iostream, "BILQR: systems of size %d\n", n) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/cg.jl b/src/cg.jl index 5501dba07..d8c0dd5d5 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -133,7 +133,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 7ce03fade..aac7baee3 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -115,7 +115,7 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/cgls.jl b/src/cgls.jl index 850bedfa0..ae7ff4fa7 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -134,7 +134,7 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/cgne.jl b/src/cgne.jl index 3c98a8988..eb7a83814 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -139,7 +139,7 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/cgs.jl b/src/cgs.jl index 3d7ab95f7..1097c7615 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -142,7 +142,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/cr.jl b/src/cr.jl index 19246c197..f6aa2c83f 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -140,7 +140,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace diff --git a/src/craig.jl b/src/craig.jl index 54e3b8f7e..a3ed1b2fa 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -182,7 +182,7 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/craigmr.jl b/src/craigmr.jl index ad5515447..3d0eecf2e 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -171,7 +171,7 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/crls.jl b/src/crls.jl index f53a94f01..1692fafb3 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -125,7 +125,7 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/crmr.jl b/src/crmr.jl index 4790f02f9..cdf259e13 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -137,7 +137,7 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/diom.jl b/src/diom.jl index 7c21dc167..f6b101b44 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -137,7 +137,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/dqgmres.jl b/src/dqgmres.jl index eb3a96935..3ada26fc5 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -137,7 +137,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/fgmres.jl b/src/fgmres.jl index 632daac1b..980fd7172 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -139,7 +139,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/fom.jl b/src/fom.jl index 85733116c..04b351dce 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -133,7 +133,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/gmres.jl b/src/gmres.jl index dc227bc21..23e4798d0 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -133,7 +133,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/gpmr.jl b/src/gpmr.jl index a4165a465..e93d540eb 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -178,8 +178,8 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato FisI = (F === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") - eltype(B) == FC || error("eltype(B) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." + eltype(B) == FC || @warn "eltype(B) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/lnlq.jl b/src/lnlq.jl index f7eb3377c..f4eb1932b 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -178,7 +178,7 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/lslq.jl b/src/lslq.jl index e5e7fef6d..a9a10025a 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -204,7 +204,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/lsmr.jl b/src/lsmr.jl index 3d133b95d..f3e722a54 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -181,7 +181,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/lsqr.jl b/src/lsqr.jl index c8cf8357a..49e4e25ed 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -177,7 +177,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Compute the adjoint of A diff --git a/src/minres.jl b/src/minres.jl index 92553f987..3efd93d29 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -154,7 +154,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index ffdbaf56b..e3779f0f4 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -136,7 +136,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/qmr.jl b/src/qmr.jl index fe7a7f7c9..d493efa22 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -129,7 +129,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, (verbose > 0) && @printf(iostream, "QMR: system of size %d\n", n) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/symmlq.jl b/src/symmlq.jl index c72ea8f82..32b845044 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -140,7 +140,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. diff --git a/src/tricg.jl b/src/tricg.jl index 145855f9d..c13e0a069 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -165,7 +165,7 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/trilqr.jl b/src/trilqr.jl index 5c1d5b35c..b2378d3f4 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -126,7 +126,7 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, (verbose > 0) && @printf(iostream, "TRILQR: dual system of %d equations in %d variables\n", n, m) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/trimr.jl b/src/trimr.jl index c1eab7b5d..83deb17e2 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -169,7 +169,7 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : NisI = (N === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/usymlq.jl b/src/usymlq.jl index bc90a07f2..d5ae04335 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -134,7 +134,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, (verbose > 0) && @printf(iostream, "USYMLQ: system of %d equations in %d variables\n", m, n) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") diff --git a/src/usymqr.jl b/src/usymqr.jl index bc9dc5d03..1d8bf205b 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -132,7 +132,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, (verbose > 0) && @printf(iostream, "USYMQR: system of %d equations in %d variables\n", m, n) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $FC") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") ktypeof(c) <: S || error("ktypeof(c) is not a subtype of $S") From c29e8f61b5d09f80dbf00788a6d530809ccf49c3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 17:29:39 -0400 Subject: [PATCH 159/182] Fix a typo in CG-LANCZOS --- src/cg_lanczos.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index c1010a37f..2f329ee78 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -128,7 +128,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax MisI = (M === I) # Check type consistency - eltype(A) == FC || error("eltype(A) ≠ $T") + eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products." ktypeof(b) <: S || error("ktypeof(b) is not a subtype of $S") # Set up workspace. From bbbad21582c653f78dbae3d31647c5d557736a33 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 18:56:18 -0400 Subject: [PATCH 160/182] Combine the allocation of the KrylovSolver with the warm-start --- src/bicgstab.jl | 3 ++- src/bilq.jl | 3 ++- src/bilqr.jl | 3 ++- src/cg.jl | 3 ++- src/cg_lanczos.jl | 3 ++- src/cgs.jl | 3 ++- src/cr.jl | 3 ++- src/diom.jl | 3 ++- src/dqgmres.jl | 3 ++- src/fgmres.jl | 3 ++- src/fom.jl | 3 ++- src/gmres.jl | 3 ++- src/gpmr.jl | 3 ++- src/krylov_solvers.jl | 10 +++++----- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 7 ++++--- src/minres_qlp.jl | 3 ++- src/qmr.jl | 3 ++- src/symmlq.jl | 7 ++++--- src/tricg.jl | 3 ++- src/trilqr.jl | 3 ++- src/trimr.jl | 3 ++- src/usymlq.jl | 3 ++- src/usymqr.jl | 3 ++- 26 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 6b39bf847..727deb7cb 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -108,7 +108,8 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function bicgstab(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = BicgstabSolver(A, b) - bicgstab!(solver, A, b, x0; $(kwargs_bicgstab...)) + warm_start!(solver, x0) + bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return (solver.x, solver.stats) end diff --git a/src/bilq.jl b/src/bilq.jl index d21480fac..9045d1927 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -93,7 +93,8 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function bilq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = BilqSolver(A, b) - bilq!(solver, A, b, x0; $(kwargs_bilq...)) + warm_start!(solver, x0) + bilq!(solver, A, b; $(kwargs_bilq...)) return (solver.x, solver.stats) end diff --git a/src/bilqr.jl b/src/bilqr.jl index cb6752b7b..d77b9dd87 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -97,7 +97,8 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi @eval begin function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = BilqrSolver(A, b) - bilqr!(solver, A, b, c, x0, y0; $(kwargs_bilqr...)) + warm_start!(solver, x0, y0) + bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/cg.jl b/src/cg.jl index d8c0dd5d5..734356928 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -100,7 +100,8 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v @eval begin function cg(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = CgSolver(A, b) - cg!(solver, A, b, x0; $(kwargs_cg...)) + warm_start!(solver, x0) + cg!(solver, A, b; $(kwargs_cg...)) return (solver.x, solver.stats) end diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 2f329ee78..37ce9135f 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -96,7 +96,8 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax @eval begin function cg_lanczos(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = CgLanczosSolver(A, b) - cg_lanczos!(solver, A, b, x0; $(kwargs_cg_lanczos...)) + warm_start!(solver, x0) + cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return (solver.x, solver.stats) end diff --git a/src/cgs.jl b/src/cgs.jl index 1097c7615..ba9ce6940 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -109,7 +109,8 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist @eval begin function cgs(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = CgsSolver(A, b) - cgs!(solver, A, b, x0; $(kwargs_cgs...)) + warm_start!(solver, x0) + cgs!(solver, A, b; $(kwargs_cgs...)) return (solver.x, solver.stats) end diff --git a/src/cr.jl b/src/cr.jl index f6aa2c83f..275d5adff 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -107,7 +107,8 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema @eval begin function cr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = CrSolver(A, b) - cr!(solver, A, b, x0; $(kwargs_cr...)) + warm_start!(solver, x0) + cr!(solver, A, b; $(kwargs_cr...)) return (solver.x, solver.stats) end diff --git a/src/diom.jl b/src/diom.jl index f6b101b44..d6995f4cd 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -104,7 +104,8 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem @eval begin function diom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = DiomSolver(A, b, memory) - diom!(solver, A, b, x0; $(kwargs_diom...)) + warm_start!(solver, x0) + diom!(solver, A, b; $(kwargs_diom...)) return (solver.x, solver.stats) end diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 3ada26fc5..2323547e9 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -104,7 +104,8 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti @eval begin function dqgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = DqgmresSolver(A, b, memory) - dqgmres!(solver, A, b, x0; $(kwargs_dqgmres...)) + warm_start!(solver, x0) + dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return (solver.x, solver.stats) end diff --git a/src/fgmres.jl b/src/fgmres.jl index 980fd7172..5824ff2ad 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -107,7 +107,8 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i @eval begin function fgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = FgmresSolver(A, b, memory) - fgmres!(solver, A, b, x0; $(kwargs_fgmres...)) + warm_start!(solver, x0) + fgmres!(solver, A, b; $(kwargs_fgmres...)) return (solver.x, solver.stats) end diff --git a/src/fom.jl b/src/fom.jl index 04b351dce..e0019ae26 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -100,7 +100,8 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma @eval begin function fom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = FomSolver(A, b, memory) - fom!(solver, A, b, x0; $(kwargs_fom...)) + warm_start!(solver, x0) + fom!(solver, A, b; $(kwargs_fom...)) return (solver.x, solver.stats) end diff --git a/src/gmres.jl b/src/gmres.jl index 23e4798d0..5b3914aca 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -100,7 +100,8 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it @eval begin function gmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = GmresSolver(A, b, memory) - gmres!(solver, A, b, x0; $(kwargs_gmres...)) + warm_start!(solver, x0) + gmres!(solver, A, b; $(kwargs_gmres...)) return (solver.x, solver.stats) end diff --git a/src/gpmr.jl b/src/gpmr.jl index e93d540eb..c843d17ff 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -140,7 +140,8 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato @eval begin function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = GpmrSolver(A, b, memory) - gpmr!(solver, A, B, b, c, x0, y0; $(kwargs_gpmr...)) + warm_start!(solver, x0, y0) + gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 3582c6c52..65a227549 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -95,7 +95,7 @@ end function MinresSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - MinresSolver(m, n, S, window=window) + MinresSolver(m, n, S; windows) end """ @@ -234,7 +234,7 @@ end function SymmlqSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - SymmlqSolver(m, n, S, window=window) + SymmlqSolver(m, n, S; windows) end """ @@ -1258,7 +1258,7 @@ end function LslqSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - LslqSolver(m, n, S, window=window) + LslqSolver(m, n, S; windows) end """ @@ -1306,7 +1306,7 @@ end function LsqrSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - LsqrSolver(m, n, S, window=window) + LsqrSolver(m, n, S; windows) end """ @@ -1356,7 +1356,7 @@ end function LsmrSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - LsmrSolver(m, n, S, window=window) + LsmrSolver(m, n, S; windows) end """ diff --git a/src/lslq.jl b/src/lslq.jl index a9a10025a..ab046897d 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -179,7 +179,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : @eval begin function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = LslqSolver(A, b, window=window) + solver = LslqSolver(A, b; windows) lslq!(solver, A, b; $(kwargs_lslq...)) return (solver.x, solver.stats) end diff --git a/src/lsmr.jl b/src/lsmr.jl index f3e722a54..779896315 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -156,7 +156,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, @eval begin function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = LsmrSolver(A, b, window=window) + solver = LsmrSolver(A, b; windows) lsmr!(solver, A, b; $(kwargs_lsmr...)) return (solver.x, solver.stats) end diff --git a/src/lsqr.jl b/src/lsqr.jl index 49e4e25ed..11c9d3366 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -152,7 +152,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, @eval begin function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = LsqrSolver(A, b, window=window) + solver = LsqrSolver(A, b; windows) lsqr!(solver, A, b; $(kwargs_lsqr...)) return (solver.x, solver.stats) end diff --git a/src/minres.jl b/src/minres.jl index 3efd93d29..04487b4a2 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -121,13 +121,14 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, @eval begin function minres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = MinresSolver(A, b, window=window) - minres!(solver, A, b, x0; $(kwargs_minres...)) + solver = MinresSolver(A, b; windows) + warm_start!(solver, x0) + minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = MinresSolver(A, b, window=window) + solver = MinresSolver(A, b; windows) minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index e3779f0f4..021d3557d 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -104,7 +104,8 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve @eval begin function minres_qlp(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = MinresQlpSolver(A, b) - minres_qlp!(solver, A, b, x0; $(kwargs_minres_qlp...)) + warm_start!(solver, x0) + minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return (solver.x, solver.stats) end diff --git a/src/qmr.jl b/src/qmr.jl index d493efa22..a5c9f8486 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -100,7 +100,8 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, @eval begin function qmr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = QmrSolver(A, b) - qmr!(solver, A, b, x0; $(kwargs_qmr...)) + warm_start!(solver, x0) + qmr!(solver, A, b; $(kwargs_qmr...)) return (solver.x, solver.stats) end diff --git a/src/symmlq.jl b/src/symmlq.jl index 32b845044..a97e847b9 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -107,13 +107,14 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : @eval begin function symmlq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = SymmlqSolver(A, b, window=window) - symmlq!(solver, A, b, x0; $(kwargs_symmlq...)) + solver = SymmlqSolver(A, b; windows) + warm_start!(solver, x0) + symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = SymmlqSolver(A, b, window=window) + solver = SymmlqSolver(A, b; windows) symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end diff --git a/src/tricg.jl b/src/tricg.jl index c13e0a069..abc7b1836 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -127,7 +127,8 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax @eval begin function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = TricgSolver(A, b) - tricg!(solver, A, b, c, x0, y0; $(kwargs_tricg...)) + warm_start!(solver, x0, y0) + tricg!(solver, A, b, c; $(kwargs_tricg...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/trilqr.jl b/src/trilqr.jl index b2378d3f4..f98d61106 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -96,7 +96,8 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = TrilqrSolver(A, b) - trilqr!(solver, A, b, c, x0, y0; $(kwargs_trilqr...)) + warm_start!(solver, x0, y0) + trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/trimr.jl b/src/trimr.jl index 83deb17e2..de837707b 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -128,7 +128,8 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : @eval begin function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = TrimrSolver(A, b) - trimr!(solver, A, b, c, x0, y0; $(kwargs_trimr...)) + warm_start!(solver, x0, y0) + trimr!(solver, A, b, c; $(kwargs_trimr...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/usymlq.jl b/src/usymlq.jl index d5ae04335..d5891b192 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -105,7 +105,8 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = UsymlqSolver(A, b) - usymlq!(solver, A, b, c, x0; $(kwargs_usymlq...)) + warm_start!(solver, x0) + usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return (solver.x, solver.stats) end diff --git a/src/usymqr.jl b/src/usymqr.jl index 1d8bf205b..5a62a0122 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -103,7 +103,8 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, @eval begin function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} solver = UsymqrSolver(A, b) - usymqr!(solver, A, b, c, x0; $(kwargs_usymqr...)) + warm_start!(solver, x0) + usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return (solver.x, solver.stats) end From b22e980e7187cdab73d1f1debac181756bcbd8c3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 19:05:02 -0400 Subject: [PATCH 161/182] Fix my sed command --- src/krylov_solvers.jl | 10 +++++----- src/lslq.jl | 2 +- src/lsmr.jl | 2 +- src/lsqr.jl | 2 +- src/minres.jl | 4 ++-- src/symmlq.jl | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 65a227549..34e913860 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -95,7 +95,7 @@ end function MinresSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - MinresSolver(m, n, S; windows) + MinresSolver(m, n, S; window) end """ @@ -234,7 +234,7 @@ end function SymmlqSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - SymmlqSolver(m, n, S; windows) + SymmlqSolver(m, n, S; window) end """ @@ -1258,7 +1258,7 @@ end function LslqSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - LslqSolver(m, n, S; windows) + LslqSolver(m, n, S; window) end """ @@ -1306,7 +1306,7 @@ end function LsqrSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - LsqrSolver(m, n, S; windows) + LsqrSolver(m, n, S; window) end """ @@ -1356,7 +1356,7 @@ end function LsmrSolver(A, b; window :: Int=5) m, n = size(A) S = ktypeof(b) - LsmrSolver(m, n, S; windows) + LsmrSolver(m, n, S; window) end """ diff --git a/src/lslq.jl b/src/lslq.jl index ab046897d..1898926da 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -179,7 +179,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : @eval begin function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = LslqSolver(A, b; windows) + solver = LslqSolver(A, b; window) lslq!(solver, A, b; $(kwargs_lslq...)) return (solver.x, solver.stats) end diff --git a/src/lsmr.jl b/src/lsmr.jl index 779896315..2cbc6e4e3 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -156,7 +156,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, @eval begin function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = LsmrSolver(A, b; windows) + solver = LsmrSolver(A, b; window) lsmr!(solver, A, b; $(kwargs_lsmr...)) return (solver.x, solver.stats) end diff --git a/src/lsqr.jl b/src/lsqr.jl index 11c9d3366..bda4945b6 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -152,7 +152,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, @eval begin function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = LsqrSolver(A, b; windows) + solver = LsqrSolver(A, b; window) lsqr!(solver, A, b; $(kwargs_lsqr...)) return (solver.x, solver.stats) end diff --git a/src/minres.jl b/src/minres.jl index 04487b4a2..8358efb95 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -121,14 +121,14 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, @eval begin function minres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = MinresSolver(A, b; windows) + solver = MinresSolver(A, b; window) warm_start!(solver, x0) minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = MinresSolver(A, b; windows) + solver = MinresSolver(A, b; window) minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end diff --git a/src/symmlq.jl b/src/symmlq.jl index a97e847b9..86070076e 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -107,14 +107,14 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : @eval begin function symmlq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = SymmlqSolver(A, b; windows) + solver = SymmlqSolver(A, b; window) warm_start!(solver, x0) symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - solver = SymmlqSolver(A, b; windows) + solver = SymmlqSolver(A, b; window) symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end From ba849b429a51d6cdc5718473395bc3c156c4131d Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 11 May 2023 01:14:46 -0400 Subject: [PATCH 162/182] Test the verbose mode --- src/cg.jl | 8 +++--- src/cr.jl | 6 ++--- src/krylov_stats.jl | 2 +- src/usymqr.jl | 6 ++--- test/runtests.jl | 1 + test/test_verbose.jl | 60 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 test/test_verbose.jl diff --git a/src/cg.jl b/src/cg.jl index 734356928..a577601b6 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -172,7 +172,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v pNorm² = γ ε = atol + rtol * rNorm (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %8s\n", "k", "‖r‖", "pAp", "α", "σ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e", iter, rNorm) solved = rNorm ≤ ε tired = iter ≥ itmax @@ -204,7 +204,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v # Compute step size to boundary if applicable. σ = radius > 0 ? maximum(to_boundary(n, x, p, radius, dNorm2=pNorm²)) : α - kdisplay(iter, verbose) && @printf(iostream, "%8.1e %8.1e %8.1e\n", pAp, α, σ) + kdisplay(iter, verbose) && @printf(iostream, " %8.1e %8.1e %8.1e\n", pAp, α, σ) # Move along p from x to the boundary if either # the next step leads outside the trust region or @@ -241,9 +241,9 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v user_requested_exit = callback(solver) :: Bool timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e ", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e", iter, rNorm) end - (verbose > 0) && @printf(iostream, "\n") + (verbose > 0) && @printf(iostream, "\n\n") # Termination status solved && on_boundary && (status = "on trust-region boundary") diff --git a/src/cr.jl b/src/cr.jl index 275d5adff..751d0cdd4 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -193,8 +193,8 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema ArNorm = @knrm2(n, Ar) # ‖Ar‖ history && push!(ArNorms, ArNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") - kdisplay(iter, verbose) && @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) descent = pr > 0 # pᴴr > 0 means p is a descent direction solved = rNorm ≤ ε @@ -345,7 +345,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema iter = iter + 1 if kdisplay(iter, verbose) m = m - α * pr + α^2 * pAp / 2 - @printf(iostream, " %d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + @printf(iostream, "%5d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) end # Stopping conditions that do not depend on user input. diff --git a/src/krylov_stats.jl b/src/krylov_stats.jl index 6fb10df56..392912895 100644 --- a/src/krylov_stats.jl +++ b/src/krylov_stats.jl @@ -255,5 +255,5 @@ function show(io :: IO, stats :: KrylovStats) s *= @sprintf " %s\n" statfield end end - print(io, s) + print(io, s) end diff --git a/src/usymqr.jl b/src/usymqr.jl index 5a62a0122..7ab35616b 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -171,8 +171,8 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, ε = atol + rtol * rNorm κ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %8s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8s\n", iter, rNorm, " ✗ ✗ ✗ ✗") βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ @@ -332,7 +332,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm, AᴴrNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e\n", iter, rNorm, AᴴrNorm) end (verbose > 0) && @printf(iostream, "\n") diff --git a/test/runtests.jl b/test/runtests.jl index b69865f61..23bbea807 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -44,3 +44,4 @@ include("test_allocations.jl") include("test_mp.jl") include("test_solvers.jl") include("test_warm_start.jl") +include("test_verbose.jl") diff --git a/test/test_verbose.jl b/test/test_verbose.jl new file mode 100644 index 000000000..a40983a1c --- /dev/null +++ b/test/test_verbose.jl @@ -0,0 +1,60 @@ +function test_verbose(FC) + A = FC.(get_div_grad(4, 4, 4)) # Dimension m x n + m,n = size(A) + k = div(n, 2) + Au = A[1:k,:] # Dimension k x n + Ao = A[:,1:k] # Dimension m x k + b = Ao * ones(FC, k) # Dimension m + c = Au * ones(FC, n) # Dimension k + mem = 10 + + T = real(FC) + shifts = T[1; 2; 3; 4; 5] + nshifts = 5 + + for fn in (:cg, :cgls, :usymqr, :cgne, :cgs, :crmr, :cg_lanczos, :dqgmres, :diom, :cr, :gpmr, + :lslq, :lsqr, :lsmr, :lnlq, :craig, :bicgstab, :craigmr, :crls, :symmlq, :minres, + :bilq, :minres_qlp, :qmr, :usymlq, :tricg, :trimr, :trilqr, :bilqr, :gmres, :fom, + :fgmres, :cg_lanczos_shift) + + @testset "$fn" begin + io = IOBuffer() + if fn in (:trilqr, :bilqr) + @eval $fn($A, $b, $b, verbose=1, iostream=$io) + elseif fn in (:tricg, :trimr) + @eval $fn($Au, $c, $b, verbose=1, iostream=$io) + elseif fn in (:lnlq, :craig, :craigmr, :cgne, :crmr) + @eval $fn($Au, $c, verbose=1, iostream=$io) + elseif fn in (:lslq, :lsqr, :lsmr, :cgls, :crls) + @eval $fn($Ao, $b, verbose=1, iostream=$io) + elseif fn == :usymlq + @eval $fn($Au, $c, $b, verbose=1, iostream=$io) + elseif fn == :usymqr + @eval $fn($Ao, $b, $c, verbose=1, iostream=$io) + elseif fn == :gpmr + @eval $fn($Ao, $Au, $b, $c, verbose=1, iostream=$io) + elseif fn == :cg_lanczos_shift + @eval $fn($A, $b, $shifts, verbose=1, iostream=$io) + else + @eval $fn($A, $b, verbose=1, iostream=$io) + end + + if fn ∉ (:cg, :craig, :symmlq, :minres, :minres_qlp) + showed = String(take!(io)) + str = split(showed, '\n', keepempty=false) + first_row = fn in (:bilqr, :trilqr) ? 3 : 2 + str = str[first_row:end] + len_header = length(str[1]) + @test mapreduce(x -> length(x) == len_header, &, str) + end + end + end +end + +@testset "verbose" begin + for FC in (Float64, ComplexF64) + @testset "Data Type: $FC" begin + test_verbose(FC) + end + end +end From 5f7ea0fcbf2b6023566be747ac03eacb8020cc82 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 10 May 2023 19:59:10 -0400 Subject: [PATCH 163/182] Take into account the time to allocate the KrylovSolver and perform warm-start when the timemax option is used --- src/bicgstab.jl | 6 ++++++ src/bilq.jl | 6 ++++++ src/bilqr.jl | 6 ++++++ src/cg.jl | 6 ++++++ src/cg_lanczos.jl | 6 ++++++ src/cg_lanczos_shift.jl | 2 ++ src/cgls.jl | 2 ++ src/cgne.jl | 2 ++ src/cgs.jl | 6 ++++++ src/cr.jl | 6 ++++++ src/craig.jl | 2 ++ src/craigmr.jl | 2 ++ src/crls.jl | 2 ++ src/crmr.jl | 2 ++ src/diom.jl | 6 ++++++ src/dqgmres.jl | 6 ++++++ src/fgmres.jl | 6 ++++++ src/fom.jl | 6 ++++++ src/gmres.jl | 6 ++++++ src/gpmr.jl | 6 ++++++ src/lnlq.jl | 2 ++ src/lslq.jl | 2 ++ src/lsmr.jl | 2 ++ src/lsqr.jl | 2 ++ src/minres.jl | 6 ++++++ src/minres_qlp.jl | 6 ++++++ src/qmr.jl | 6 ++++++ src/symmlq.jl | 6 ++++++ src/tricg.jl | 6 ++++++ src/trilqr.jl | 6 ++++++ src/trimr.jl | 6 ++++++ src/usymlq.jl | 6 ++++++ src/usymqr.jl | 6 ++++++ 33 files changed, 154 insertions(+) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 727deb7cb..7284423b3 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -107,20 +107,26 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function bicgstab(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = BicgstabSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return (solver.x, solver.stats) end function bicgstab(A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = BicgstabSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return (solver.x, solver.stats) end function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return solver end diff --git a/src/bilq.jl b/src/bilq.jl index 9045d1927..e868cb3c9 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -92,20 +92,26 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function bilq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = BilqSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 bilq!(solver, A, b; $(kwargs_bilq...)) return (solver.x, solver.stats) end function bilq(A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = BilqSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 bilq!(solver, A, b; $(kwargs_bilq...)) return (solver.x, solver.stats) end function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 bilq!(solver, A, b; $(kwargs_bilq...)) return solver end diff --git a/src/bilqr.jl b/src/bilqr.jl index d77b9dd87..b2efd6c47 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -96,20 +96,26 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi @eval begin function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = BilqrSolver(A, b) warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return (solver.x, solver.y, solver.stats) end function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = BilqrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return (solver.x, solver.y, solver.stats) end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return solver end diff --git a/src/cg.jl b/src/cg.jl index a577601b6..5e048a63d 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -99,20 +99,26 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v @eval begin function cg(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cg!(solver, A, b; $(kwargs_cg...)) return (solver.x, solver.stats) end function cg(A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 cg!(solver, A, b; $(kwargs_cg...)) return (solver.x, solver.stats) end function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cg!(solver, A, b; $(kwargs_cg...)) return solver end diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 37ce9135f..114c1503a 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -95,20 +95,26 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax @eval begin function cg_lanczos(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgLanczosSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return (solver.x, solver.stats) end function cg_lanczos(A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgLanczosSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return (solver.x, solver.stats) end function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return solver end diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index aac7baee3..7a71a3ccd 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -90,8 +90,10 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t @eval begin function cg_lanczos_shift(A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() nshifts = length(shifts) solver = CgLanczosShiftSolver(A, b, nshifts) + timemax -= (time_ns() - start_time) / 1e9 cg_lanczos_shift!(solver, A, b, shifts; $(kwargs_cg_lanczos_shift...)) return (solver.x, solver.stats) end diff --git a/src/cgls.jl b/src/cgls.jl index ae7ff4fa7..24726dcfe 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -114,7 +114,9 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose @eval begin function cgls(A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CglsSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 cgls!(solver, A, b; $(kwargs_cgls...)) return (solver.x, solver.stats) end diff --git a/src/cgne.jl b/src/cgne.jl index eb7a83814..1aff7861d 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -119,7 +119,9 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor @eval begin function cgne(A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgneSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 cgne!(solver, A, b; $(kwargs_cgne...)) return (solver.x, solver.stats) end diff --git a/src/cgs.jl b/src/cgs.jl index ba9ce6940..b88c26dc2 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -108,20 +108,26 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist @eval begin function cgs(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgsSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cgs!(solver, A, b; $(kwargs_cgs...)) return (solver.x, solver.stats) end function cgs(A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CgsSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 cgs!(solver, A, b; $(kwargs_cgs...)) return (solver.x, solver.stats) end function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cgs!(solver, A, b; $(kwargs_cgs...)) return solver end diff --git a/src/cr.jl b/src/cr.jl index 751d0cdd4..5e3d86dba 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -106,20 +106,26 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema @eval begin function cr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CrSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cr!(solver, A, b; $(kwargs_cr...)) return (solver.x, solver.stats) end function cr(A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 cr!(solver, A, b; $(kwargs_cr...)) return (solver.x, solver.stats) end function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 cr!(solver, A, b; $(kwargs_cr...)) return solver end diff --git a/src/craig.jl b/src/craig.jl index a3ed1b2fa..ab9f0548f 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -157,7 +157,9 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at @eval begin function craig(A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CraigSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 craig!(solver, A, b; $(kwargs_craig...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/craigmr.jl b/src/craigmr.jl index 3d0eecf2e..f0c167af6 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -146,7 +146,9 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver @eval begin function craigmr(A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CraigmrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 craigmr!(solver, A, b; $(kwargs_craigmr...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/crls.jl b/src/crls.jl index 1692fafb3..bee5c45cd 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -105,7 +105,9 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose @eval begin function crls(A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CrlsSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 crls!(solver, A, b; $(kwargs_crls...)) return (solver.x, solver.stats) end diff --git a/src/crmr.jl b/src/crmr.jl index cdf259e13..a6432bd64 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -117,7 +117,9 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor @eval begin function crmr(A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = CrmrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 crmr!(solver, A, b; $(kwargs_crmr...)) return (solver.x, solver.stats) end diff --git a/src/diom.jl b/src/diom.jl index d6995f4cd..d74546af1 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -103,20 +103,26 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem @eval begin function diom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = DiomSolver(A, b, memory) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 diom!(solver, A, b; $(kwargs_diom...)) return (solver.x, solver.stats) end function diom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = DiomSolver(A, b, memory) + timemax -= (time_ns() - start_time) / 1e9 diom!(solver, A, b; $(kwargs_diom...)) return (solver.x, solver.stats) end function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 diom!(solver, A, b; $(kwargs_diom...)) return solver end diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 2323547e9..74ad14f69 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -103,20 +103,26 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti @eval begin function dqgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = DqgmresSolver(A, b, memory) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return (solver.x, solver.stats) end function dqgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = DqgmresSolver(A, b, memory) + timemax -= (time_ns() - start_time) / 1e9 dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return (solver.x, solver.stats) end function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return solver end diff --git a/src/fgmres.jl b/src/fgmres.jl index 5824ff2ad..46de192a0 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -106,20 +106,26 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i @eval begin function fgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = FgmresSolver(A, b, memory) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 fgmres!(solver, A, b; $(kwargs_fgmres...)) return (solver.x, solver.stats) end function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = FgmresSolver(A, b, memory) + timemax -= (time_ns() - start_time) / 1e9 fgmres!(solver, A, b; $(kwargs_fgmres...)) return (solver.x, solver.stats) end function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 fgmres!(solver, A, b; $(kwargs_fgmres...)) return solver end diff --git a/src/fom.jl b/src/fom.jl index e0019ae26..9d75494eb 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -99,20 +99,26 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma @eval begin function fom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = FomSolver(A, b, memory) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 fom!(solver, A, b; $(kwargs_fom...)) return (solver.x, solver.stats) end function fom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = FomSolver(A, b, memory) + timemax -= (time_ns() - start_time) / 1e9 fom!(solver, A, b; $(kwargs_fom...)) return (solver.x, solver.stats) end function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 fom!(solver, A, b; $(kwargs_fom...)) return solver end diff --git a/src/gmres.jl b/src/gmres.jl index 5b3914aca..209adb30f 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -99,20 +99,26 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it @eval begin function gmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = GmresSolver(A, b, memory) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 gmres!(solver, A, b; $(kwargs_gmres...)) return (solver.x, solver.stats) end function gmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = GmresSolver(A, b, memory) + timemax -= (time_ns() - start_time) / 1e9 gmres!(solver, A, b; $(kwargs_gmres...)) return (solver.x, solver.stats) end function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 gmres!(solver, A, b; $(kwargs_gmres...)) return solver end diff --git a/src/gpmr.jl b/src/gpmr.jl index c843d17ff..4e188489f 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -139,20 +139,26 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato @eval begin function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = GpmrSolver(A, b, memory) warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return (solver.x, solver.y, solver.stats) end function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = GpmrSolver(A, b, memory) + timemax -= (time_ns() - start_time) / 1e9 gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return (solver.x, solver.y, solver.stats) end function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return solver end diff --git a/src/lnlq.jl b/src/lnlq.jl index f4eb1932b..3957a72ff 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -153,7 +153,9 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly @eval begin function lnlq(A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = LnlqSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 lnlq!(solver, A, b; $(kwargs_lnlq...)) return (solver.x, solver.y, solver.stats) end diff --git a/src/lslq.jl b/src/lslq.jl index 1898926da..346e9aa2b 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -179,7 +179,9 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : @eval begin function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = LslqSolver(A, b; window) + timemax -= (time_ns() - start_time) / 1e9 lslq!(solver, A, b; $(kwargs_lslq...)) return (solver.x, solver.stats) end diff --git a/src/lsmr.jl b/src/lsmr.jl index 2cbc6e4e3..5cc4773bf 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -156,7 +156,9 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, @eval begin function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = LsmrSolver(A, b; window) + timemax -= (time_ns() - start_time) / 1e9 lsmr!(solver, A, b; $(kwargs_lsmr...)) return (solver.x, solver.stats) end diff --git a/src/lsqr.jl b/src/lsqr.jl index bda4945b6..ce866bf76 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -152,7 +152,9 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, @eval begin function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = LsqrSolver(A, b; window) + timemax -= (time_ns() - start_time) / 1e9 lsqr!(solver, A, b; $(kwargs_lsqr...)) return (solver.x, solver.stats) end diff --git a/src/minres.jl b/src/minres.jl index 8358efb95..0caa1d140 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -121,20 +121,26 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, @eval begin function minres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = MinresSolver(A, b; window) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = MinresSolver(A, b; window) + timemax -= (time_ns() - start_time) / 1e9 minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 minres!(solver, A, b; $(kwargs_minres...)) return solver end diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 021d3557d..c512d1d1f 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -103,20 +103,26 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve @eval begin function minres_qlp(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = MinresQlpSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return (solver.x, solver.stats) end function minres_qlp(A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = MinresQlpSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return (solver.x, solver.stats) end function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return solver end diff --git a/src/qmr.jl b/src/qmr.jl index a5c9f8486..f7ada1ba0 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -99,20 +99,26 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, @eval begin function qmr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = QmrSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 qmr!(solver, A, b; $(kwargs_qmr...)) return (solver.x, solver.stats) end function qmr(A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = QmrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 qmr!(solver, A, b; $(kwargs_qmr...)) return (solver.x, solver.stats) end function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 qmr!(solver, A, b; $(kwargs_qmr...)) return solver end diff --git a/src/symmlq.jl b/src/symmlq.jl index 86070076e..5e262e4bc 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -107,20 +107,26 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : @eval begin function symmlq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = SymmlqSolver(A, b; window) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = SymmlqSolver(A, b; window) + timemax -= (time_ns() - start_time) / 1e9 symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 symmlq!(solver, A, b; $(kwargs_symmlq...)) return solver end diff --git a/src/tricg.jl b/src/tricg.jl index abc7b1836..e9ab7bc77 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -126,20 +126,26 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax @eval begin function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = TricgSolver(A, b) warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 tricg!(solver, A, b, c; $(kwargs_tricg...)) return (solver.x, solver.y, solver.stats) end function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = TricgSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 tricg!(solver, A, b, c; $(kwargs_tricg...)) return (solver.x, solver.y, solver.stats) end function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 tricg!(solver, A, b, c; $(kwargs_tricg...)) return solver end diff --git a/src/trilqr.jl b/src/trilqr.jl index f98d61106..0cfdc3cc6 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -95,20 +95,26 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = TrilqrSolver(A, b) warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return (solver.x, solver.y, solver.stats) end function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = TrilqrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return (solver.x, solver.y, solver.stats) end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return solver end diff --git a/src/trimr.jl b/src/trimr.jl index de837707b..e1a1ea30f 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -127,20 +127,26 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : @eval begin function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = TrimrSolver(A, b) warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 trimr!(solver, A, b, c; $(kwargs_trimr...)) return (solver.x, solver.y, solver.stats) end function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = TrimrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 trimr!(solver, A, b, c; $(kwargs_trimr...)) return (solver.x, solver.y, solver.stats) end function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0, y0) + timemax -= (time_ns() - start_time) / 1e9 trimr!(solver, A, b, c; $(kwargs_trimr...)) return solver end diff --git a/src/usymlq.jl b/src/usymlq.jl index d5891b192..cadd557f1 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -104,20 +104,26 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, @eval begin function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = UsymlqSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return (solver.x, solver.stats) end function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = UsymlqSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return (solver.x, solver.stats) end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return solver end diff --git a/src/usymqr.jl b/src/usymqr.jl index 7ab35616b..713b89a6a 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -102,20 +102,26 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, @eval begin function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = UsymqrSolver(A, b) warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return (solver.x, solver.stats) end function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + start_time = time_ns() solver = UsymqrSolver(A, b) + timemax -= (time_ns() - start_time) / 1e9 usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return (solver.x, solver.stats) end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() warm_start!(solver, x0) + timemax -= (time_ns() - start_time) / 1e9 usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return solver end From b62f606c467334e84e078ad1181ecf6cd6a6ecc8 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 11 May 2023 22:33:05 -0400 Subject: [PATCH 164/182] Update the verbose mode to display a timer --- src/bicgstab.jl | 12 ++++++------ src/bilq.jl | 12 ++++++------ src/bilqr.jl | 16 ++++++++-------- src/cg.jl | 10 +++++----- src/cg_lanczos.jl | 14 +++++++------- src/cg_lanczos_shift.jl | 10 +++++----- src/cgls.jl | 8 ++++---- src/cgne.jl | 8 ++++---- src/cgs.jl | 12 ++++++------ src/cr.jl | 12 ++++++------ src/craig.jl | 8 ++++---- src/craigmr.jl | 8 ++++---- src/crls.jl | 8 ++++---- src/crmr.jl | 8 ++++---- src/diom.jl | 12 ++++++------ src/dqgmres.jl | 12 ++++++------ src/fgmres.jl | 12 ++++++------ src/fom.jl | 12 ++++++------ src/gmres.jl | 12 ++++++------ src/gpmr.jl | 12 ++++++------ src/krylov_utils.jl | 2 ++ src/lnlq.jl | 8 ++++---- src/lslq.jl | 8 ++++---- src/lsmr.jl | 8 ++++---- src/lsqr.jl | 8 ++++---- src/minres.jl | 12 ++++++------ src/minres_qlp.jl | 12 ++++++------ src/qmr.jl | 12 ++++++------ src/symmlq.jl | 12 ++++++------ src/tricg.jl | 12 ++++++------ src/trilqr.jl | 16 ++++++++-------- src/trimr.jl | 12 ++++++------ src/usymlq.jl | 12 ++++++------ src/usymqr.jl | 12 ++++++------ test/test_stats.jl | 32 ++++++++++++++++---------------- test/test_verbose.jl | 16 ++++++++-------- 36 files changed, 207 insertions(+), 205 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 7284423b3..dbe05c87d 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -110,7 +110,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = BicgstabSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return (solver.x, solver.stats) end @@ -118,7 +118,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, function bicgstab(A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BicgstabSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return (solver.x, solver.stats) end @@ -126,7 +126,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bicgstab!(solver, A, b; $(kwargs_bicgstab...)) return solver end @@ -197,8 +197,8 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s\n", "k", "‖rₖ‖", "|αₖ|", "|ωₖ|") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) + (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %5s\n", "k", "‖rₖ‖", "|αₖ|", "|ωₖ|", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e %.2fs\n", iter, rNorm, abs(α), abs(ω), ktimer(start_time)) next_ρ = @kdot(n, c, r) # ρ₁ = ⟨r̅₀,r₀⟩ if next_ρ == 0 @@ -257,7 +257,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, breakdown = (α == 0 || isnan(α)) timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e\n", iter, rNorm, abs(α), abs(ω)) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %8.1e %.2fs\n", iter, rNorm, abs(α), abs(ω), ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/bilq.jl b/src/bilq.jl index e868cb3c9..4af6b796b 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -95,7 +95,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = BilqSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bilq!(solver, A, b; $(kwargs_bilq...)) return (solver.x, solver.stats) end @@ -103,7 +103,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, function bilq(A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bilq!(solver, A, b; $(kwargs_bilq...)) return (solver.x, solver.stats) end @@ -111,7 +111,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bilq!(solver, A, b; $(kwargs_bilq...)) return solver end @@ -167,8 +167,8 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, itmax == 0 && (itmax = 2*n) ε = atol + rtol * bNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, bNorm, ktimer(start_time)) # Initialize the Lanczos biorthogonalization process. cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ @@ -348,7 +348,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, breakdown = !solved_lq && !solved_cg && (pᴴq == 0) timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm_lq, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/bilqr.jl b/src/bilqr.jl index b2efd6c47..05e24714b 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -99,7 +99,7 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi start_time = time_ns() solver = BilqrSolver(A, b) warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return (solver.x, solver.y, solver.stats) end @@ -107,7 +107,7 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return (solver.x, solver.y, solver.stats) end @@ -115,7 +115,7 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) bilqr!(solver, A, b, c; $(kwargs_bilqr...)) return solver end @@ -173,8 +173,8 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi history && push!(sNorms, cNorm) εL = atol + rtol * bNorm εQ = atol + rtol * cNorm - (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %5s\n", "k", "‖rₖ‖", "‖sₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %.2fs\n", iter, bNorm, cNorm, ktimer(start_time)) # Initialize the Lanczos biorthogonalization process. cᴴb = @kdot(n, s₀, r₀) # ⟨s₀,r₀⟩ = ⟨c - Aᴴy₀,b - Ax₀⟩ @@ -442,9 +442,9 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) - kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") - kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) + kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e %.2fs\n", iter, "✗ ✗ ✗ ✗", sNorm, ktimer(start_time)) + kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s %.2fs\n", iter, rNorm_lq, "✗ ✗ ✗ ✗", ktimer(start_time)) + kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e %.2fs\n", iter, rNorm_lq, sNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/cg.jl b/src/cg.jl index 5e048a63d..fc8cd996f 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -102,7 +102,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v start_time = time_ns() solver = CgSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg!(solver, A, b; $(kwargs_cg...)) return (solver.x, solver.stats) end @@ -110,7 +110,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v function cg(A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg!(solver, A, b; $(kwargs_cg...)) return (solver.x, solver.stats) end @@ -118,7 +118,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg!(solver, A, b; $(kwargs_cg...)) return solver end @@ -177,7 +177,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v pAp = zero(T) pNorm² = γ ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %8s\n", "k", "‖r‖", "pAp", "α", "σ") + (verbose > 0) && @printf(iostream, "%5s %7s %8s %8s %8s %5s\n", "k", "‖r‖", "pAp", "α", "σ", "timer") kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e", iter, rNorm) solved = rNorm ≤ ε @@ -210,7 +210,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v # Compute step size to boundary if applicable. σ = radius > 0 ? maximum(to_boundary(n, x, p, radius, dNorm2=pNorm²)) : α - kdisplay(iter, verbose) && @printf(iostream, " %8.1e %8.1e %8.1e\n", pAp, α, σ) + kdisplay(iter, verbose) && @printf(iostream, " %8.1e %8.1e %8.1e %.2fs\n", pAp, α, σ, ktimer(start_time)) # Move along p from x to the boundary if either # the next step leads outside the trust region or diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 114c1503a..7549463c5 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -98,7 +98,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax start_time = time_ns() solver = CgLanczosSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return (solver.x, solver.stats) end @@ -106,7 +106,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax function cg_lanczos(A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgLanczosSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return (solver.x, solver.stats) end @@ -114,7 +114,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) return solver end @@ -129,7 +129,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax (m == solver.m && n == solver.n) || error("(solver.m, solver.n) = ($(solver.m), $(solver.n)) is inconsistent with size(A) = ($m, $n)") m == n || error("System must be square") length(b) == n || error("Inconsistent problem size") - (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables\n", n, n) + (verbose > 0) && @printf(iostream, "CG-LANCZOS: system of %d equations in %d variables\n", n, n) # Tests M = Iₙ MisI = (M === I) @@ -188,8 +188,8 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax # Define stopping tolerance. ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) indefinite = false solved = rNorm ≤ ε @@ -233,7 +233,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax rNorm = abs(σ) # ‖rₖ₊₁‖_M = |σₖ₊₁| because rₖ₊₁ = σₖ₊₁ * vₖ₊₁ and ‖vₖ₊₁‖_M = 1 history && push!(rNorms, rNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 7a71a3ccd..3871528de 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -93,7 +93,7 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t start_time = time_ns() nshifts = length(shifts) solver = CgLanczosShiftSolver(A, b, nshifts) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cg_lanczos_shift!(solver, A, b, shifts; $(kwargs_cg_lanczos_shift...)) return (solver.x, solver.stats) end @@ -111,7 +111,7 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t nshifts = length(shifts) nshifts == solver.nshifts || error("solver.nshifts = $(solver.nshifts) is inconsistent with length(shifts) = $nshifts") - (verbose > 0) && @printf(iostream, "CG Lanczos: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) + (verbose > 0) && @printf(iostream, "CG-LANCZOS-SHIFT: system of %d equations in %d variables with %d shifts\n", n, n, nshifts) # Tests M = Iₙ MisI = (M === I) @@ -185,8 +185,8 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t itmax == 0 && (itmax = 2 * n) # Build format strings for printing. - (verbose > 0) && (fmt = Printf.Format("%5d" * repeat(" %8.1e", nshifts) * "\n")) - kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) + (verbose > 0) && (fmt = Printf.Format("%5d" * repeat(" %8.1e", nshifts) * " %.2fs\n")) + kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms..., ktimer(start_time)) solved = !reduce(|, not_cv) tired = iter ≥ itmax @@ -250,7 +250,7 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t not_cv[i] = check_curvature ? !(converged[i] || indefinite[i]) : !converged[i] end iter = iter + 1 - kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms...) + kdisplay(iter, verbose) && Printf.format(iostream, fmt, iter, rNorms..., ktimer(start_time)) user_requested_exit = callback(solver) :: Bool solved = !reduce(|, not_cv) diff --git a/src/cgls.jl b/src/cgls.jl index 24726dcfe..3b80e9209 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -116,7 +116,7 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose function cgls(A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CglsSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cgls!(solver, A, b; $(kwargs_cgls...)) return (solver.x, solver.stats) end @@ -173,8 +173,8 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) ε = atol + rtol * ArNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %5s\n", "k", "‖Aᴴr‖", "‖r‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %.2fs\n", iter, ArNorm, rNorm, ktimer(start_time)) status = "unknown" on_boundary = false @@ -211,7 +211,7 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %.2fs\n", iter, ArNorm, rNorm, ktimer(start_time)) user_requested_exit = callback(solver) :: Bool solved = (ArNorm ≤ ε) || on_boundary tired = iter ≥ itmax diff --git a/src/cgne.jl b/src/cgne.jl index 1aff7861d..301366f10 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -121,7 +121,7 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor function cgne(A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgneSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cgne!(solver, A, b; $(kwargs_cgne...)) return (solver.x, solver.stats) end @@ -182,8 +182,8 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol + rtol * pNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf(iostream, "%5s %8s\n", "k", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %5s\n", "k", "‖r‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %.2fs\n", iter, rNorm, ktimer(start_time)) status = "unknown" solved = rNorm ≤ ɛ_c @@ -213,7 +213,7 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor rNorm = sqrt(γ_next) history && push!(rNorms, rNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %.2fs\n", iter, rNorm, ktimer(start_time)) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. diff --git a/src/cgs.jl b/src/cgs.jl index b88c26dc2..a22fe19c8 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -111,7 +111,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist start_time = time_ns() solver = CgsSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cgs!(solver, A, b; $(kwargs_cgs...)) return (solver.x, solver.stats) end @@ -119,7 +119,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist function cgs(A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgsSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cgs!(solver, A, b; $(kwargs_cgs...)) return (solver.x, solver.stats) end @@ -127,7 +127,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cgs!(solver, A, b; $(kwargs_cgs...)) return solver end @@ -202,8 +202,8 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) u .= r # u₀ p .= r # p₀ @@ -261,7 +261,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist breakdown = (α == 0 || isnan(α)) timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/cr.jl b/src/cr.jl index 5e3d86dba..d0543af5b 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -109,7 +109,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema start_time = time_ns() solver = CrSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cr!(solver, A, b; $(kwargs_cr...)) return (solver.x, solver.stats) end @@ -117,7 +117,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema function cr(A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cr!(solver, A, b; $(kwargs_cr...)) return (solver.x, solver.stats) end @@ -125,7 +125,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) cr!(solver, A, b; $(kwargs_cr...)) return solver end @@ -199,8 +199,8 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema ArNorm = @knrm2(n, Ar) # ‖Ar‖ history && push!(ArNorms, ArNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s\n", "k", "‖x‖", "‖r‖", "quad") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s %5s\n", "k", "‖x‖", "‖r‖", "quad", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.1e %8.1e %8.1e %.2fs\n", iter, xNorm, rNorm, m, ktimer(start_time)) descent = pr > 0 # pᴴr > 0 means p is a descent direction solved = rNorm ≤ ε @@ -351,7 +351,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema iter = iter + 1 if kdisplay(iter, verbose) m = m - α * pr + α^2 * pAp / 2 - @printf(iostream, "%5d %8.1e %8.1e %8.1e\n", iter, xNorm, rNorm, m) + @printf(iostream, "%5d %8.1e %8.1e %8.1e %.2fs\n", iter, xNorm, rNorm, m, ktimer(start_time)) end # Stopping conditions that do not depend on user input. diff --git a/src/craig.jl b/src/craig.jl index ab9f0548f..adb1f1e9f 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -159,7 +159,7 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at function craig(A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CraigSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) craig!(solver, A, b; $(kwargs_craig...)) return (solver.x, solver.y, solver.stats) end @@ -245,8 +245,8 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol # Stopping tolerance for inconsistent systems. ctol = conlim > 0 ? 1/conlim : zero(T) # Stopping tolerance for ill-conditioned operators. - (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s %8s %8s %7s\n", "k", "‖r‖", "‖x‖", "‖A‖", "κ(A)", "α", "β") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e\n", iter, rNorm, xNorm, Anorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %8s %8s %8s %7s %5s\n", "k", "‖r‖", "‖x‖", "‖A‖", "κ(A)", "α", "β", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e %8s %7s %.2fs\n", iter, rNorm, xNorm, Anorm, Acond, " ✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗", ktimer(start_time)) bkwerr = one(T) # initial value of the backward error ‖r‖ / √(‖b‖² + ‖A‖² ‖x‖²) @@ -351,7 +351,7 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at ρ_prev = ρ # Only differs from α if λ > 0. - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e %8.1e %7.1e\n", iter, rNorm, xNorm, Anorm, Acond, α, β) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %8.2e %8.2e %8.1e %7.1e %.2fs\n", iter, rNorm, xNorm, Anorm, Acond, α, β, ktimer(start_time)) solved_lim = bkwerr ≤ btol solved_mach = one(T) + bkwerr ≤ one(T) diff --git a/src/craigmr.jl b/src/craigmr.jl index f0c167af6..a482046c7 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -148,7 +148,7 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver function craigmr(A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CraigmrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) craigmr!(solver, A, b; $(kwargs_craigmr...)) return (solver.x, solver.y, solver.stats) end @@ -219,8 +219,8 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β, α, β, α, 0, 1, Anorm²) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %5s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %.2fs\n", iter, β, α, β, α, 0, 1, Anorm², ktimer(start_time)) # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 @@ -346,7 +346,7 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver ArNorm = α * β * abs(ζ/ρ) history && push!(ArNorms, ArNorm) - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %.2fs\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², ktimer(start_time)) if λ > 0 (cdₖ, sdₖ, λₖ₊₁) = sym_givens(λ, λₐᵤₓ) diff --git a/src/crls.jl b/src/crls.jl index bee5c45cd..3974bf628 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -107,7 +107,7 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose function crls(A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrlsSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) crls!(solver, A, b; $(kwargs_crls...)) return (solver.x, solver.stats) end @@ -173,8 +173,8 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose λ > 0 && (γ += λ * ArNorm * ArNorm) history && push!(ArNorms, ArNorm) ε = atol + rtol * ArNorm - (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %5s\n", "k", "‖Aᴴr‖", "‖r‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %.2fs\n", iter, ArNorm, rNorm, ktimer(start_time)) status = "unknown" on_boundary = false @@ -235,7 +235,7 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %.2fs\n", iter, ArNorm, rNorm, ktimer(start_time)) user_requested_exit = callback(solver) :: Bool solved = (ArNorm ≤ ε) || on_boundary tired = iter ≥ itmax diff --git a/src/crmr.jl b/src/crmr.jl index a6432bd64..5b482c44c 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -119,7 +119,7 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor function crmr(A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrmrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) crmr!(solver, A, b; $(kwargs_crmr...)) return (solver.x, solver.stats) end @@ -178,8 +178,8 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor history && push!(ArNorms, ArNorm) ɛ_c = atol + rtol * rNorm # Stopping tolerance for consistent systems. ɛ_i = atol + rtol * ArNorm # Stopping tolerance for inconsistent systems. - (verbose > 0) && @printf(iostream, "%5s %8s %8s\n", "k", "‖Aᴴr‖", "‖r‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + (verbose > 0) && @printf(iostream, "%5s %8s %8s %5s\n", "k", "‖Aᴴr‖", "‖r‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %.2fs\n", iter, ArNorm, rNorm, ktimer(start_time)) status = "unknown" solved = rNorm ≤ ɛ_c @@ -211,7 +211,7 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor history && push!(rNorms, rNorm) history && push!(ArNorms, ArNorm) iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e\n", iter, ArNorm, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %8.2e %8.2e %.2fs\n", iter, ArNorm, rNorm, ktimer(start_time)) user_requested_exit = callback(solver) :: Bool solved = rNorm ≤ ɛ_c inconsistent = (rNorm > 100 * ɛ_c) && (ArNorm ≤ ɛ_i) diff --git a/src/diom.jl b/src/diom.jl index d74546af1..01803b2f2 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -106,7 +106,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem start_time = time_ns() solver = DiomSolver(A, b, memory) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) diom!(solver, A, b; $(kwargs_diom...)) return (solver.x, solver.stats) end @@ -114,7 +114,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem function diom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DiomSolver(A, b, memory) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) diom!(solver, A, b; $(kwargs_diom...)) return (solver.x, solver.stats) end @@ -122,7 +122,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) diom!(solver, A, b; $(kwargs_diom...)) return solver end @@ -181,8 +181,8 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) mem = length(V) # Memory for i = 1 : mem @@ -310,7 +310,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 74ad14f69..4e158fcdf 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -106,7 +106,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti start_time = time_ns() solver = DqgmresSolver(A, b, memory) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return (solver.x, solver.stats) end @@ -114,7 +114,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti function dqgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DqgmresSolver(A, b, memory) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return (solver.x, solver.stats) end @@ -122,7 +122,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) dqgmres!(solver, A, b; $(kwargs_dqgmres...)) return solver end @@ -181,8 +181,8 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) # Set up workspace. mem = length(V) # Memory. @@ -312,7 +312,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/fgmres.jl b/src/fgmres.jl index 46de192a0..3d9d87ac4 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -109,7 +109,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i start_time = time_ns() solver = FgmresSolver(A, b, memory) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) fgmres!(solver, A, b; $(kwargs_fgmres...)) return (solver.x, solver.stats) end @@ -117,7 +117,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FgmresSolver(A, b, memory) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) fgmres!(solver, A, b; $(kwargs_fgmres...)) return (solver.x, solver.stats) end @@ -125,7 +125,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) fgmres!(solver, A, b; $(kwargs_fgmres...)) return solver end @@ -196,8 +196,8 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i itmax == 0 && (itmax = 2*n) inner_itmax = itmax - (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s %5s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s %.2fs\n", npass, iter, rNorm, "✗ ✗ ✗ ✗", ktimer(start_time)) # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -320,7 +320,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e %.2fs\n", npass, iter+inner_iter, rNorm, Hbis, ktimer(start_time)) # Compute vₖ₊₁ if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) diff --git a/src/fom.jl b/src/fom.jl index 9d75494eb..6a1162bf9 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -102,7 +102,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma start_time = time_ns() solver = FomSolver(A, b, memory) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) fom!(solver, A, b; $(kwargs_fom...)) return (solver.x, solver.stats) end @@ -110,7 +110,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma function fom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FomSolver(A, b, memory) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) fom!(solver, A, b; $(kwargs_fom...)) return (solver.x, solver.stats) end @@ -118,7 +118,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) fom!(solver, A, b; $(kwargs_fom...)) return solver end @@ -191,8 +191,8 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma itmax == 0 && (itmax = 2*n) inner_itmax = itmax - (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s %5s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s %.2fs\n", npass, iter, rNorm, "✗ ✗ ✗ ✗", ktimer(start_time)) # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -303,7 +303,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e %.2fs\n", npass, iter+inner_iter, rNorm, Hbis, ktimer(start_time)) # Compute vₖ₊₁. if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) diff --git a/src/gmres.jl b/src/gmres.jl index 209adb30f..c199ba9ee 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -102,7 +102,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it start_time = time_ns() solver = GmresSolver(A, b, memory) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) gmres!(solver, A, b; $(kwargs_gmres...)) return (solver.x, solver.stats) end @@ -110,7 +110,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it function gmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GmresSolver(A, b, memory) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) gmres!(solver, A, b; $(kwargs_gmres...)) return (solver.x, solver.stats) end @@ -118,7 +118,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) gmres!(solver, A, b; $(kwargs_gmres...)) return solver end @@ -191,8 +191,8 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it itmax == 0 && (itmax = 2*n) inner_itmax = itmax - (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s\n", npass, iter, rNorm, "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %5s %7s %7s %5s\n", "pass", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7s %.2fs\n", npass, iter, rNorm, "✗ ✗ ✗ ✗", ktimer(start_time)) # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -313,7 +313,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it inner_tired = restart ? inner_iter ≥ min(mem, inner_itmax) : inner_iter ≥ inner_itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e\n", npass, iter+inner_iter, rNorm, Hbis) + kdisplay(iter+inner_iter, verbose) && @printf(iostream, "%5d %5d %7.1e %7.1e %.2fs\n", npass, iter+inner_iter, rNorm, Hbis, ktimer(start_time)) # Compute vₖ₊₁. if !(solved || inner_tired || breakdown || user_requested_exit || overtimed) diff --git a/src/gpmr.jl b/src/gpmr.jl index 4e188489f..a267dc7a9 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -142,7 +142,7 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato start_time = time_ns() solver = GpmrSolver(A, b, memory) warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return (solver.x, solver.y, solver.stats) end @@ -150,7 +150,7 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GpmrSolver(A, b, memory) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return (solver.x, solver.y, solver.stats) end @@ -158,7 +158,7 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) return solver end @@ -271,8 +271,8 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato zt[1] = β zt[2] = γ - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "fₖ₊₁.ₖ") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7s\n", iter, rNorm, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %5s\n", "k", "‖rₖ‖", "hₖ₊₁.ₖ", "fₖ₊₁.ₖ", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7s %.2fs\n", iter, rNorm, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗", ktimer(start_time)) # Tolerance for breakdown detection. btol = eps(T)^(3/4) @@ -461,7 +461,7 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, Haux, Faux) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, Haux, Faux, ktimer(start_time)) # Compute vₖ₊₁ and uₖ₊₁ if !(solved || tired || breakdown || user_requested_exit || overtimed) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 54497d2da..137a97386 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -291,6 +291,8 @@ allocate_if(bool, solver, v, S, n) = bool && isempty(solver.:($v)::S) && (solver kdisplay(iter, verbose) = (verbose > 0) && (mod(iter, verbose) == 0) +ktimer(start_time::UInt64) = (time_ns() - start_time) / 1e9 + mulorldiv!(y, P, x, ldiv::Bool) = ldiv ? ldiv!(y, P, x) : mul!(y, P, x) kdot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasReal = BLAS.dot(n, x, dx, y, dy) diff --git a/src/lnlq.jl b/src/lnlq.jl index 3957a72ff..81e44e6d9 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -155,7 +155,7 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly function lnlq(A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LnlqSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) lnlq!(solver, A, b; $(kwargs_lnlq...)) return (solver.x, solver.y, solver.stats) end @@ -221,8 +221,8 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, bNorm, ktimer(start_time)) # Update iteration index iter = iter + 1 @@ -502,7 +502,7 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly end timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm_lq, ktimer(start_time)) # Update iteration index. iter = iter + 1 diff --git a/src/lslq.jl b/src/lslq.jl index 346e9aa2b..22106dac3 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -181,7 +181,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LslqSolver(A, b; window) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) lslq!(solver, A, b; $(kwargs_lslq...)) return (solver.x, solver.stats) end @@ -307,8 +307,8 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %7s %7s %5s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "κ(A)", "‖xL‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², Acond, xlqNorm, ktimer(start_time)) status = "unknown" ε = atol + rtol * β₁ @@ -476,7 +476,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : overtimed = timer > timemax_ns iter = iter + 1 - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, ArNorm, β, α, c, s, Anorm, Acond, xlqNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/lsmr.jl b/src/lsmr.jl index 5cc4773bf..7419d1861 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -158,7 +158,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LsmrSolver(A, b; window) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) lsmr!(solver, A, b; $(kwargs_lsmr...)) return (solver.x, solver.stats) end @@ -263,8 +263,8 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm²) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %8s %7s %5s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "α", "cos", "sin", "‖A‖²", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %.2fs\n", iter, β₁, α, β₁, α, 0, 1, Anorm², ktimer(start_time)) # Aᴴb = 0 so x = 0 is a minimum least-squares solution if α == 0 @@ -390,7 +390,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, t1 = test1 / (one(T) + Anorm * xNorm / β₁) rNormtol = btol + axtol * Anorm * xNorm / β₁ - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e\n", iter, rNorm, ArNorm, β, α, c, s, Anorm²) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %.2fs\n", iter, rNorm, ArNorm, β, α, c, s, Anorm², ktimer(start_time)) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. diff --git a/src/lsqr.jl b/src/lsqr.jl index ce866bf76..18bdd0144 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -154,7 +154,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LsqrSolver(A, b; window) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) lsqr!(solver, A, b; $(kwargs_lsqr...)) return (solver.x, solver.stats) end @@ -238,8 +238,8 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, iter = 0 itmax == 0 && (itmax = m + n) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %7s %7s %7s %7s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %7s %7s %7s %7s %5s\n", "k", "α", "β", "‖r‖", "‖Aᴴr‖", "compat", "backwrd", "‖A‖", "κ(A)", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %.2fs\n", iter, β₁, α, β₁, α, 0, 1, Anorm, Acond, ktimer(start_time)) rNorm = β₁ r1Norm = rNorm @@ -380,7 +380,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, t1 = test1 / (one(T) + Anorm * xNorm / β₁) rNormtol = btol + axtol * Anorm * xNorm / β₁ - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e\n", iter, α, β, rNorm, ArNorm, test1, test2, Anorm, Acond) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %7.1e %.2fs\n", iter, α, β, rNorm, ArNorm, test1, test2, Anorm, Acond, ktimer(start_time)) # Stopping conditions that do not depend on user input. # This is to guard against tolerances that are unreasonably small. diff --git a/src/minres.jl b/src/minres.jl index 0caa1d140..09c3556fe 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -124,7 +124,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, start_time = time_ns() solver = MinresSolver(A, b; window) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end @@ -132,7 +132,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresSolver(A, b; window) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) minres!(solver, A, b; $(kwargs_minres...)) return (solver.x, solver.stats) end @@ -140,7 +140,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) minres!(solver, A, b; $(kwargs_minres...)) return solver end @@ -237,8 +237,8 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, iter = 0 itmax == 0 && (itmax = 2*n) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %8s %8s %7s %7s %7s %7s %5s\n", "k", "‖r‖", "‖Aᴴr‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "test2", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7s %7s %.2fs\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗", ktimer(start_time)) ε = atol + rtol * β₁ stats.status = "unknown" @@ -340,7 +340,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, Acond = γmax / γmin history && push!(Aconds, Acond) - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, test1, test2, ktimer(start_time)) if iter == 1 && β / β₁ ≤ 10 * ϵM # Aᴴb = 0 so x = 0 is a minimum least-squares solution diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index c512d1d1f..fa5429c58 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -106,7 +106,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve start_time = time_ns() solver = MinresQlpSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return (solver.x, solver.stats) end @@ -114,7 +114,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve function minres_qlp(A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresQlpSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return (solver.x, solver.stats) end @@ -122,7 +122,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) return solver end @@ -196,8 +196,8 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve ε = atol + rtol * rNorm κ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %7s %8s %7s\n", "k", "‖rₖ‖", "‖Arₖ₋₁‖", "βₖ₊₁", "Rₖ.ₖ", "Lₖ.ₖ", "‖A‖", "κ(A)", "backward") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7.1e %7s %8s %7.1e %7.1e %8s\n", iter, rNorm, "✗ ✗ ✗ ✗", βₖ, "✗ ✗ ✗ ✗", " ✗ ✗ ✗ ✗", ANorm, Acond, " ✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %7s %8s %7s %7s %8s %5s\n", "k", "‖rₖ‖", "‖Arₖ₋₁‖", "βₖ₊₁", "Rₖ.ₖ", "Lₖ.ₖ", "‖A‖", "κ(A)", "backward", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7s %7.1e %7s %8s %7.1e %7.1e %8s %.2fs\n", iter, rNorm, "✗ ✗ ✗ ✗", βₖ, "✗ ✗ ✗ ✗", " ✗ ✗ ✗ ✗", ANorm, Acond, " ✗ ✗ ✗ ✗", ktimer(start_time)) # Set up workspace. M⁻¹vₖ₋₁ .= zero(FC) @@ -457,7 +457,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve μbarₖ₋₁ = μbarₖ ζbarₖ = ζbarₖ₊₁ βₖ = βₖ₊₁ - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %7.1e %7.1e %8.1e\n", iter, rNorm, ArNorm, βₖ₊₁, λₖ, μbarₖ, ANorm, Acond, backward) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %7.1e %8.1e %7.1e %7.1e %8.1e %.2fs\n", iter, rNorm, ArNorm, βₖ₊₁, λₖ, μbarₖ, ANorm, Acond, backward, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/qmr.jl b/src/qmr.jl index f7ada1ba0..aa13f7bb0 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -102,7 +102,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, start_time = time_ns() solver = QmrSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) qmr!(solver, A, b; $(kwargs_qmr...)) return (solver.x, solver.stats) end @@ -110,7 +110,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, function qmr(A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = QmrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) qmr!(solver, A, b; $(kwargs_qmr...)) return (solver.x, solver.stats) end @@ -118,7 +118,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) qmr!(solver, A, b; $(kwargs_qmr...)) return solver end @@ -174,8 +174,8 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, itmax == 0 && (itmax = 2*n) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) # Initialize the Lanczos biorthogonalization process. cᴴb = @kdot(n, c, r₀) # ⟨c,r₀⟩ @@ -352,7 +352,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, breakdown = !solved && (pᴴq == 0) timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/symmlq.jl b/src/symmlq.jl index 5e262e4bc..de8519223 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -110,7 +110,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : start_time = time_ns() solver = SymmlqSolver(A, b; window) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end @@ -118,7 +118,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = SymmlqSolver(A, b; window) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) symmlq!(solver, A, b; $(kwargs_symmlq...)) return (solver.x, solver.stats) end @@ -126,7 +126,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) symmlq!(solver, A, b; $(kwargs_symmlq...)) return solver end @@ -266,8 +266,8 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : iter = 0 itmax == 0 && (itmax = 2 * n) - (verbose > 0) && @printf(iostream, "%5s %7s %7s %8s %8s %7s %7s %7s\n", "k", "‖r‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e\n", iter, rNorm, β, cold, sold, ANorm, Acond) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %8s %8s %7s %7s %7s %5s\n", "k", "‖r‖", "β", "cos", "sin", "‖A‖", "κ(A)", "test1", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7s %.2fs\n", iter, rNorm, β, cold, sold, ANorm, Acond, "✗ ✗ ✗ ✗", ktimer(start_time)) tol = atol + rtol * β₁ status = "unknown" @@ -402,7 +402,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : ANorm = sqrt(ANorm²) test1 = rNorm / (ANorm * xNorm) - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e\n", iter, rNorm, β, c, s, ANorm, Acond, test1) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, β, c, s, ANorm, Acond, test1, ktimer(start_time)) # Reset variables ϵold = ϵ diff --git a/src/tricg.jl b/src/tricg.jl index e9ab7bc77..81677879d 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -129,7 +129,7 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax start_time = time_ns() solver = TricgSolver(A, b) warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) tricg!(solver, A, b, c; $(kwargs_tricg...)) return (solver.x, solver.y, solver.stats) end @@ -137,7 +137,7 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TricgSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) tricg!(solver, A, b, c; $(kwargs_tricg...)) return (solver.x, solver.y, solver.stats) end @@ -145,7 +145,7 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) tricg!(solver, A, b, c; $(kwargs_tricg...)) return solver end @@ -260,8 +260,8 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax history && push!(rNorms, rNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %5s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, βₖ, γₖ, ktimer(start_time)) # Set up workspace. d₂ₖ₋₃ = d₂ₖ₋₂ = zero(T) @@ -444,7 +444,7 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, βₖ₊₁, γₖ₊₁, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/trilqr.jl b/src/trilqr.jl index 0cfdc3cc6..99158a42d 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -98,7 +98,7 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = TrilqrSolver(A, b) warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return (solver.x, solver.y, solver.stats) end @@ -106,7 +106,7 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrilqrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return (solver.x, solver.y, solver.stats) end @@ -114,7 +114,7 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) trilqr!(solver, A, b, c; $(kwargs_trilqr...)) return solver end @@ -172,8 +172,8 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, εL = atol + rtol * bNorm εQ = atol + rtol * cNorm ξ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %7s\n", "k", "‖rₖ‖", "‖sₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e\n", iter, bNorm, cNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %5s\n", "k", "‖rₖ‖", "‖sₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %.2fs\n", iter, bNorm, cNorm, ktimer(start_time)) # Set up workspace. βₖ = @knrm2(m, r₀) # β₁ = ‖r₀‖ = ‖v₁‖ @@ -422,9 +422,9 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e\n", iter, "", sNorm) - kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s\n", iter, rNorm_lq, "") - kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e\n", iter, rNorm_lq, sNorm) + kdisplay(iter, verbose) && solved_primal && !solved_dual && @printf(iostream, "%5d %7s %7.1e %.2fs\n", iter, "✗ ✗ ✗ ✗", sNorm, ktimer(start_time)) + kdisplay(iter, verbose) && !solved_primal && solved_dual && @printf(iostream, "%5d %7.1e %7s %.2fs\n", iter, rNorm_lq, "✗ ✗ ✗ ✗", ktimer(start_time)) + kdisplay(iter, verbose) && !solved_primal && !solved_dual && @printf(iostream, "%5d %7.1e %7.1e %.2fs\n", iter, rNorm_lq, sNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/trimr.jl b/src/trimr.jl index e1a1ea30f..d94a7fedb 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -130,7 +130,7 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : start_time = time_ns() solver = TrimrSolver(A, b) warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) trimr!(solver, A, b, c; $(kwargs_trimr...)) return (solver.x, solver.y, solver.stats) end @@ -138,7 +138,7 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrimrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) trimr!(solver, A, b, c; $(kwargs_trimr...)) return (solver.x, solver.y, solver.stats) end @@ -146,7 +146,7 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) trimr!(solver, A, b, c; $(kwargs_trimr...)) return solver end @@ -270,8 +270,8 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : history && push!(rNorms, rNorm) ε = atol + rtol * rNorm - (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ, γₖ) + (verbose > 0) && @printf(iostream, "%5s %7s %7s %7s %5s\n", "k", "‖rₖ‖", "βₖ₊₁", "γₖ₊₁", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, βₖ, γₖ, ktimer(start_time)) # Set up workspace. old_c₁ₖ = old_c₂ₖ = old_c₃ₖ = old_c₄ₖ = zero(T) @@ -547,7 +547,7 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e\n", iter, rNorm, βₖ₊₁, γₖ₊₁) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %.2fs\n", iter, rNorm, βₖ₊₁, γₖ₊₁, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/usymlq.jl b/src/usymlq.jl index cadd557f1..d3cafe809 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -107,7 +107,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = UsymlqSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return (solver.x, solver.stats) end @@ -115,7 +115,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymlqSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return (solver.x, solver.stats) end @@ -123,7 +123,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) usymlq!(solver, A, b, c; $(kwargs_usymlq...)) return solver end @@ -178,8 +178,8 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, itmax == 0 && (itmax = m+n) ε = atol + rtol * bNorm - (verbose > 0) && @printf(iostream, "%5s %7s\n", "k", "‖rₖ‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, bNorm) + (verbose > 0) && @printf(iostream, "%5s %7s %5s\n", "k", "‖rₖ‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, bNorm, ktimer(start_time)) βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ @@ -340,7 +340,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e\n", iter, rNorm_lq) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %.2fs\n", iter, rNorm_lq, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/src/usymqr.jl b/src/usymqr.jl index 713b89a6a..152189df7 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -105,7 +105,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, start_time = time_ns() solver = UsymqrSolver(A, b) warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return (solver.x, solver.stats) end @@ -113,7 +113,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymqrSolver(A, b) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return (solver.x, solver.stats) end @@ -121,7 +121,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= (time_ns() - start_time) / 1e9 + timemax -= ktimer(start_time) usymqr!(solver, A, b, c; $(kwargs_usymqr...)) return solver end @@ -177,8 +177,8 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, ε = atol + rtol * rNorm κ = zero(T) - (verbose > 0) && @printf(iostream, "%5s %7s %8s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖") - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8s\n", iter, rNorm, " ✗ ✗ ✗ ✗") + (verbose > 0) && @printf(iostream, "%5s %7s %8s %5s\n", "k", "‖rₖ‖", "‖Aᴴrₖ₋₁‖", "timer") + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8s %.2fs\n", iter, rNorm, " ✗ ✗ ✗ ✗", ktimer(start_time)) βₖ = @knrm2(m, r₀) # β₁ = ‖v₁‖ = ‖r₀‖ γₖ = @knrm2(n, c) # γ₁ = ‖u₁‖ = ‖c‖ @@ -338,7 +338,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, tired = iter ≥ itmax timer = time_ns() - start_time overtimed = timer > timemax_ns - kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e\n", iter, rNorm, AᴴrNorm) + kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %8.1e %.2fs\n", iter, rNorm, AᴴrNorm, ktimer(start_time)) end (verbose > 0) && @printf(iostream, "\n") diff --git a/test/test_stats.jl b/test/test_stats.jl index 186c56c20..db4829628 100644 --- a/test/test_stats.jl +++ b/test/test_stats.jl @@ -1,5 +1,5 @@ @testset "stats" begin - stats = Krylov.SimpleStats(0, true, true, Float64[1.0], Float64[2.0], Float64[], "t") + stats = Krylov.SimpleStats(0, true, true, Float64[1.0], Float64[2.0], Float64[], "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -11,14 +11,14 @@ residuals: [ 1.0e+00 ] Aresiduals: [ 2.0e+00 ] κ₂(A): [] - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LsmrStats(0, true, true, Float64[1.0], Float64[2.0], Float64(3.0), Float64(4.0), Float64(5.0), Float64(6.0), Float64(7.0), "t") + stats = Krylov.LsmrStats(0, true, true, Float64[1.0], Float64[2.0], Float64(3.0), Float64(4.0), Float64(5.0), Float64(6.0), Float64(7.0), "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -34,14 +34,14 @@ κ₂(A): 5.0 ‖A‖F: 6.0 xNorm: 7.0 - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LanczosStats(0, true, Float64[3.0], true, NaN, NaN, "t") + stats = Krylov.LanczosStats(0, true, Float64[3.0], true, NaN, NaN, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -53,14 +53,14 @@ indefinite: true ‖A‖F: NaN κ₂(A): NaN - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LanczosShiftStats(0, true, [Float64[0.9, 0.5], Float64[0.6, 0.4, 0.1]], BitVector([false, true]), NaN, NaN, "t") + stats = Krylov.LanczosShiftStats(0, true, [Float64[0.9, 0.5], Float64[0.6, 0.4, 0.1]], BitVector([false, true]), NaN, NaN, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -72,13 +72,13 @@ indefinite: Bool[0, 1] ‖A‖F: NaN κ₂(A): NaN - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.SymmlqStats(0, true, Float64[4.0], Union{Float64,Missing}[5.0, missing], Float64[6.0], Union{Float64,Missing}[7.0, missing], NaN, NaN, "t") + stats = Krylov.SymmlqStats(0, true, Float64[4.0], Union{Float64,Missing}[5.0, missing], Float64[6.0], Union{Float64,Missing}[7.0, missing], NaN, NaN, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -92,14 +92,14 @@ errors (cg): [ 7.0e+00 ✗✗✗✗ ] ‖A‖F: NaN κ₂(A): NaN - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.AdjointStats(0, true, true, Float64[8.0], Float64[9.0], "t") + stats = Krylov.AdjointStats(0, true, true, Float64[8.0], Float64[9.0], "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -110,14 +110,14 @@ solved dual: true residuals primal: [ 8.0e+00 ] residuals dual: [ 9.0e+00 ] - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LNLQStats(0, true, Float64[10.0], false, Float64[11.0], Float64[12.0], "t") + stats = Krylov.LNLQStats(0, true, Float64[10.0], false, Float64[11.0], Float64[12.0], "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -129,14 +129,14 @@ error with bnd: false error bnd x: [ 1.1e+01 ] error bnd y: [ 1.2e+01 ] - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LSLQStats(0, true, false, Float64[13.0], Float64[14.0], Float64[15.0], false, Float64[16.0], Float64[17.0], "t") + stats = Krylov.LSLQStats(0, true, false, Float64[13.0], Float64[14.0], Float64[15.0], false, Float64[16.0], Float64[17.0], "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -151,7 +151,7 @@ error with bnd: false error bound LQ: [ 1.6e+01 ] error bound CG: [ 1.7e+01 ] - status: t""" + status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) check_reset(stats) diff --git a/test/test_verbose.jl b/test/test_verbose.jl index a40983a1c..ebc42c8f7 100644 --- a/test/test_verbose.jl +++ b/test/test_verbose.jl @@ -39,14 +39,14 @@ function test_verbose(FC) @eval $fn($A, $b, verbose=1, iostream=$io) end - if fn ∉ (:cg, :craig, :symmlq, :minres, :minres_qlp) - showed = String(take!(io)) - str = split(showed, '\n', keepempty=false) - first_row = fn in (:bilqr, :trilqr) ? 3 : 2 - str = str[first_row:end] - len_header = length(str[1]) - @test mapreduce(x -> length(x) == len_header, &, str) - end + showed = String(take!(io)) + str = split(showed, '\n', keepempty=false) + nrows = length(str) + first_row = fn in (:bilqr, :trilqr) ? 3 : 2 + last_row = fn == :cg ? nrows-1 : nrows + str = str[first_row:last_row] + len_header = length(str[1]) + @test mapreduce(x -> length(x) == len_header, &, str) end end end From 55f577ccf6f04a0ecbc51dfc56a53d71e0c4b7fc Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 12 May 2023 13:52:40 -0400 Subject: [PATCH 165/182] Add a field timer in the statistics --- src/krylov_stats.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/krylov_stats.jl b/src/krylov_stats.jl index 392912895..ba217a597 100644 --- a/src/krylov_stats.jl +++ b/src/krylov_stats.jl @@ -12,6 +12,7 @@ Type for statistics returned by the majority of Krylov solvers, the attributes a - residuals - Aresiduals - Acond +- timer - status """ mutable struct SimpleStats{T} <: KrylovStats{T} @@ -21,6 +22,7 @@ mutable struct SimpleStats{T} <: KrylovStats{T} residuals :: Vector{T} Aresiduals :: Vector{T} Acond :: Vector{T} + timer :: Float64 status :: String end @@ -40,6 +42,7 @@ Type for statistics returned by LSMR. The attributes are: - Acond - Anorm - xNorm +- timer - status """ mutable struct LsmrStats{T} <: KrylovStats{T} @@ -53,6 +56,7 @@ mutable struct LsmrStats{T} <: KrylovStats{T} Acond :: T Anorm :: T xNorm :: T + timer :: Float64 status :: String end @@ -69,6 +73,7 @@ Type for statistics returned by CG-LANCZOS, the attributes are: - indefinite - Anorm - Acond +- timer - status """ mutable struct LanczosStats{T} <: KrylovStats{T} @@ -78,6 +83,7 @@ mutable struct LanczosStats{T} <: KrylovStats{T} indefinite :: Bool Anorm :: T Acond :: T + timer :: Float64 status :: String end @@ -93,6 +99,7 @@ Type for statistics returned by CG-LANCZOS with shifts, the attributes are: - indefinite - Anorm - Acond +- timer - status """ mutable struct LanczosShiftStats{T} <: KrylovStats{T} @@ -102,6 +109,7 @@ mutable struct LanczosShiftStats{T} <: KrylovStats{T} indefinite :: BitVector Anorm :: T Acond :: T + timer :: Float64 status :: String end @@ -121,6 +129,7 @@ Type for statistics returned by SYMMLQ, the attributes are: - errorscg - Anorm - Acond +- timer - status """ mutable struct SymmlqStats{T} <: KrylovStats{T} @@ -132,6 +141,7 @@ mutable struct SymmlqStats{T} <: KrylovStats{T} errorscg :: Vector{Union{T, Missing}} Anorm :: T Acond :: T + timer :: Float64 status :: String end @@ -149,6 +159,7 @@ Type for statistics returned by adjoint systems solvers BiLQR and TriLQR, the at - solved_dual - residuals_primal - residuals_dual +- timer - status """ mutable struct AdjointStats{T} <: KrylovStats{T} @@ -157,6 +168,7 @@ mutable struct AdjointStats{T} <: KrylovStats{T} solved_dual :: Bool residuals_primal :: Vector{T} residuals_dual :: Vector{T} + timer :: Float64 status :: String end @@ -173,6 +185,7 @@ Type for statistics returned by the LNLQ method, the attributes are: - error_with_bnd - error_bnd_x - error_bnd_y +- timer - status """ mutable struct LNLQStats{T} <: KrylovStats{T} @@ -182,6 +195,7 @@ mutable struct LNLQStats{T} <: KrylovStats{T} error_with_bnd :: Bool error_bnd_x :: Vector{T} error_bnd_y :: Vector{T} + timer :: Float64 status :: String end @@ -202,6 +216,7 @@ Type for statistics returned by the LSLQ method, the attributes are: - error_with_bnd - err_ubnds_lq - err_ubnds_cg +- timer - status """ mutable struct LSLQStats{T} <: KrylovStats{T} @@ -214,6 +229,7 @@ mutable struct LSLQStats{T} <: KrylovStats{T} error_with_bnd :: Bool err_ubnds_lq :: Vector{T} err_ubnds_cg :: Vector{T} + timer :: Float64 status :: String end @@ -251,6 +267,10 @@ function show(io :: IO, stats :: KrylovStats) statfield = getfield(stats, field) if isa(statfield, AbstractVector) && eltype(statfield) <: Union{Missing, AbstractFloat} s *= @sprintf " %s\n" vec2str(statfield) + elseif field_name == "timer" + (statfield < 1e-3) && (s *= @sprintf " %.2fμs\n" 1e6*statfield) + (1e-3 ≤ statfield < 1.00) && (s *= @sprintf " %.2fms\n" 1e3*statfield) + (statfield ≥ 1.00) && (s *= @sprintf " %.2fs\n" statfield) else s *= @sprintf " %s\n" statfield end From bd3e691003c9f12053f69bbdbcd27423546e6e59 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 12 May 2023 13:53:22 -0400 Subject: [PATCH 166/182] Update test_stats.jl --- test/test_stats.jl | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/test_stats.jl b/test/test_stats.jl index db4829628..f4c212d50 100644 --- a/test/test_stats.jl +++ b/test/test_stats.jl @@ -1,5 +1,5 @@ @testset "stats" begin - stats = Krylov.SimpleStats(0, true, true, Float64[1.0], Float64[2.0], Float64[], "unknown") + stats = Krylov.SimpleStats(0, true, true, Float64[1.0], Float64[2.0], Float64[], 1.234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -11,6 +11,7 @@ residuals: [ 1.0e+00 ] Aresiduals: [ 2.0e+00 ] κ₂(A): [] + timer: 1.23s status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) @@ -18,7 +19,7 @@ nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LsmrStats(0, true, true, Float64[1.0], Float64[2.0], Float64(3.0), Float64(4.0), Float64(5.0), Float64(6.0), Float64(7.0), "unknown") + stats = Krylov.LsmrStats(0, true, true, Float64[1.0], Float64[2.0], Float64(3.0), Float64(4.0), Float64(5.0), Float64(6.0), Float64(7.0), 0.1234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -34,6 +35,7 @@ κ₂(A): 5.0 ‖A‖F: 6.0 xNorm: 7.0 + timer: 123.40ms status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) @@ -41,7 +43,7 @@ nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LanczosStats(0, true, Float64[3.0], true, NaN, NaN, "unknown") + stats = Krylov.LanczosStats(0, true, Float64[3.0], true, NaN, NaN, 1.234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -53,6 +55,7 @@ indefinite: true ‖A‖F: NaN κ₂(A): NaN + timer: 1.23s status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) @@ -60,7 +63,7 @@ nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LanczosShiftStats(0, true, [Float64[0.9, 0.5], Float64[0.6, 0.4, 0.1]], BitVector([false, true]), NaN, NaN, "unknown") + stats = Krylov.LanczosShiftStats(0, true, [Float64[0.9, 0.5], Float64[0.6, 0.4, 0.1]], BitVector([false, true]), NaN, NaN, 0.00056789, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -72,13 +75,14 @@ indefinite: Bool[0, 1] ‖A‖F: NaN κ₂(A): NaN + timer: 567.89μs status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.SymmlqStats(0, true, Float64[4.0], Union{Float64,Missing}[5.0, missing], Float64[6.0], Union{Float64,Missing}[7.0, missing], NaN, NaN, "unknown") + stats = Krylov.SymmlqStats(0, true, Float64[4.0], Union{Float64,Missing}[5.0, missing], Float64[6.0], Union{Float64,Missing}[7.0, missing], NaN, NaN, 1.234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -92,6 +96,7 @@ errors (cg): [ 7.0e+00 ✗✗✗✗ ] ‖A‖F: NaN κ₂(A): NaN + timer: 1.23s status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) @@ -99,7 +104,7 @@ nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.AdjointStats(0, true, true, Float64[8.0], Float64[9.0], "unknown") + stats = Krylov.AdjointStats(0, true, true, Float64[8.0], Float64[9.0], 1.234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -110,6 +115,7 @@ solved dual: true residuals primal: [ 8.0e+00 ] residuals dual: [ 9.0e+00 ] + timer: 1.23s status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) @@ -117,7 +123,7 @@ nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LNLQStats(0, true, Float64[10.0], false, Float64[11.0], Float64[12.0], "unknown") + stats = Krylov.LNLQStats(0, true, Float64[10.0], false, Float64[11.0], Float64[12.0], 1.234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -129,6 +135,7 @@ error with bnd: false error bnd x: [ 1.1e+01 ] error bnd y: [ 1.2e+01 ] + timer: 1.23s status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) @@ -136,7 +143,7 @@ nbytes_allocated = @allocated Krylov.reset!(stats) @test nbytes_allocated == 0 - stats = Krylov.LSLQStats(0, true, false, Float64[13.0], Float64[14.0], Float64[15.0], false, Float64[16.0], Float64[17.0], "unknown") + stats = Krylov.LSLQStats(0, true, false, Float64[13.0], Float64[14.0], Float64[15.0], false, Float64[16.0], Float64[17.0], 1.234, "unknown") io = IOBuffer() show(io, stats) showed = String(take!(io)) @@ -151,6 +158,7 @@ error with bnd: false error bound LQ: [ 1.6e+01 ] error bound CG: [ 1.7e+01 ] + timer: 1.23s status: unknown""" @test strip.(split(chomp(showed), "\n")) == strip.(split(chomp(expected), "\n")) Krylov.reset!(stats) From e2eb7c450d284412f6a1515697811d78bc118cf4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 12 May 2023 13:53:56 -0400 Subject: [PATCH 167/182] Update the KrylovSolvers to support the timer in KrylovStats --- src/krylov_solvers.jl | 66 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 34e913860..569def7e6 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -87,7 +87,7 @@ function MinresSolver(m, n, S; window :: Int=5) y = S(undef, n) v = S(undef, 0) err_vec = zeros(T, window) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = MinresSolver{T,FC,S}(m, n, Δx, x, r1, r2, w1, w2, y, v, err_vec, false, stats) return solver end @@ -130,7 +130,7 @@ function CgSolver(m, n, S) p = S(undef, n) Ap = S(undef, n) z = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CgSolver{T,FC,S}(m, n, Δx, x, r, p, Ap, z, false, stats) return solver end @@ -175,7 +175,7 @@ function CrSolver(m, n, S) q = S(undef, n) Ar = S(undef, n) Mq = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CrSolver{T,FC,S}(m, n, Δx, x, r, p, q, Ar, Mq, false, stats) return solver end @@ -226,7 +226,7 @@ function SymmlqSolver(m, n, S; window :: Int=5) clist = zeros(T, window) zlist = zeros(T, window) sprod = ones(T, window) - stats = SymmlqStats(0, false, T[], Union{T, Missing}[], T[], Union{T, Missing}[], T(NaN), T(NaN), "unknown") + stats = SymmlqStats(0, false, T[], Union{T, Missing}[], T[], Union{T, Missing}[], T(NaN), T(NaN), 0.0, "unknown") solver = SymmlqSolver{T,FC,S}(m, n, Δx, x, Mvold, Mv, Mv_next, w̅, v, clist, zlist, sprod, false, stats) return solver end @@ -271,7 +271,7 @@ function CgLanczosSolver(m, n, S) p = S(undef, n) Mv_next = S(undef, n) v = S(undef, 0) - stats = LanczosStats(0, false, T[], false, T(NaN), T(NaN), "unknown") + stats = LanczosStats(0, false, T[], false, T(NaN), T(NaN), 0.0, "unknown") solver = CgLanczosSolver{T,FC,S}(m, n, Δx, x, Mv, Mv_prev, p, Mv_next, v, false, stats) return solver end @@ -329,7 +329,7 @@ function CgLanczosShiftSolver(m, n, nshifts, S) indefinite = BitVector(undef, nshifts) converged = BitVector(undef, nshifts) not_cv = BitVector(undef, nshifts) - stats = LanczosShiftStats(0, false, Vector{T}[T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), "unknown") + stats = LanczosShiftStats(0, false, Vector{T}[T[] for i = 1 : nshifts], indefinite, T(NaN), T(NaN), 0.0, "unknown") solver = CgLanczosShiftSolver{T,FC,S}(m, n, nshifts, Mv, Mv_prev, Mv_next, v, x, p, σ, δhat, ω, γ, rNorms, converged, not_cv, stats) return solver end @@ -376,7 +376,7 @@ function MinresQlpSolver(m, n, S) x = S(undef, n) p = S(undef, n) vₖ = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = MinresQlpSolver{T,FC,S}(m, n, Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats) return solver end @@ -429,7 +429,7 @@ function DqgmresSolver(m, n, memory, S) c = Vector{T}(undef, memory) s = Vector{FC}(undef, memory) H = Vector{FC}(undef, memory+1) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = DqgmresSolver{T,FC,S}(m, n, Δx, x, t, z, w, P, V, c, s, H, false, stats) return solver end @@ -480,7 +480,7 @@ function DiomSolver(m, n, memory, S) V = S[S(undef, n) for i = 1 : memory] L = Vector{FC}(undef, memory-1) H = Vector{FC}(undef, memory) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = DiomSolver{T,FC,S}(m, n, Δx, x, t, z, w, P, V, L, H, false, stats) return solver end @@ -529,7 +529,7 @@ function UsymlqSolver(m, n, S) vₖ₋₁ = S(undef, m) vₖ = S(undef, m) q = S(undef, m) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = UsymlqSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, p, Δx, x, d̅, vₖ₋₁, vₖ, q, false, stats) return solver end @@ -580,7 +580,7 @@ function UsymqrSolver(m, n, S) uₖ₋₁ = S(undef, n) uₖ = S(undef, n) p = S(undef, n) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = UsymqrSolver{T,FC,S}(m, n, vₖ₋₁, vₖ, q, Δx, x, wₖ₋₂, wₖ₋₁, uₖ₋₁, uₖ, p, false, stats) return solver end @@ -643,7 +643,7 @@ function TricgSolver(m, n, S) Δy = S(undef, 0) uₖ = S(undef, 0) vₖ = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = TricgSolver{T,FC,S}(m, n, y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) return solver end @@ -714,7 +714,7 @@ function TrimrSolver(m, n, S) Δy = S(undef, 0) uₖ = S(undef, 0) vₖ = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = TrimrSolver{T,FC,S}(m, n, y, N⁻¹uₖ₋₁, N⁻¹uₖ, p, gy₂ₖ₋₃, gy₂ₖ₋₂, gy₂ₖ₋₁, gy₂ₖ, x, M⁻¹vₖ₋₁, M⁻¹vₖ, q, gx₂ₖ₋₃, gx₂ₖ₋₂, gx₂ₖ₋₁, gx₂ₖ, Δx, Δy, uₖ, vₖ, false, stats) return solver end @@ -771,7 +771,7 @@ function TrilqrSolver(m, n, S) y = S(undef, m) wₖ₋₃ = S(undef, m) wₖ₋₂ = S(undef, m) - stats = AdjointStats(0, false, false, T[], T[], "unknown") + stats = AdjointStats(0, false, false, T[], T[], 0.0, "unknown") solver = TrilqrSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, p, d̅, Δx, x, vₖ₋₁, vₖ, q, Δy, y, wₖ₋₃, wₖ₋₂, false, stats) return solver end @@ -820,7 +820,7 @@ function CgsSolver(m, n, S) ts = S(undef, n) yz = S(undef, 0) vw = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CgsSolver{T,FC,S}(m, n, Δx, x, r, u, p, q, ts, yz, vw, false, stats) return solver end @@ -869,7 +869,7 @@ function BicgstabSolver(m, n, S) qd = S(undef, n) yz = S(undef, 0) t = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = BicgstabSolver{T,FC,S}(m, n, Δx, x, r, p, v, s, qd, yz, t, false, stats) return solver end @@ -918,7 +918,7 @@ function BilqSolver(m, n, S) Δx = S(undef, 0) x = S(undef, n) d̅ = S(undef, n) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = BilqSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, d̅, false, stats) return solver end @@ -969,7 +969,7 @@ function QmrSolver(m, n, S) x = S(undef, n) wₖ₋₂ = S(undef, n) wₖ₋₁ = S(undef, n) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = QmrSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, wₖ₋₂, wₖ₋₁, false, stats) return solver end @@ -1026,7 +1026,7 @@ function BilqrSolver(m, n, S) d̅ = S(undef, n) wₖ₋₃ = S(undef, n) wₖ₋₂ = S(undef, n) - stats = AdjointStats(0, false, false, T[], T[], "unknown") + stats = AdjointStats(0, false, false, T[], T[], 0.0, "unknown") solver = BilqrSolver{T,FC,S}(m, n, uₖ₋₁, uₖ, q, vₖ₋₁, vₖ, p, Δx, x, Δy, y, d̅, wₖ₋₃, wₖ₋₂, false, stats) return solver end @@ -1068,7 +1068,7 @@ function CglsSolver(m, n, S) r = S(undef, m) q = S(undef, m) Mr = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CglsSolver{T,FC,S}(m, n, x, p, s, r, q, Mr, stats) return solver end @@ -1114,7 +1114,7 @@ function CrlsSolver(m, n, S) Ap = S(undef, m) s = S(undef, m) Ms = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CrlsSolver{T,FC,S}(m, n, x, p, Ar, q, r, Ap, s, Ms, stats) return solver end @@ -1158,7 +1158,7 @@ function CgneSolver(m, n, S) q = S(undef, m) s = S(undef, 0) z = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CgneSolver{T,FC,S}(m, n, x, p, Aᴴz, r, q, s, z, stats) return solver end @@ -1202,7 +1202,7 @@ function CrmrSolver(m, n, S) q = S(undef, m) Nq = S(undef, 0) s = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CrmrSolver{T,FC,S}(m, n, x, p, Aᴴr, r, q, Nq, s, stats) return solver end @@ -1250,7 +1250,7 @@ function LslqSolver(m, n, S; window :: Int=5) u = S(undef, 0) v = S(undef, 0) err_vec = zeros(T, window) - stats = LSLQStats(0, false, false, T[], T[], T[], false, T[], T[], "unknown") + stats = LSLQStats(0, false, false, T[], T[], T[], false, T[], T[], 0.0, "unknown") solver = LslqSolver{T,FC,S}(m, n, x, Nv, Aᴴu, w̄, Mu, Av, u, v, err_vec, stats) return solver end @@ -1298,7 +1298,7 @@ function LsqrSolver(m, n, S; window :: Int=5) u = S(undef, 0) v = S(undef, 0) err_vec = zeros(T, window) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = LsqrSolver{T,FC,S}(m, n, x, Nv, Aᴴu, w, Mu, Av, u, v, err_vec, stats) return solver end @@ -1348,7 +1348,7 @@ function LsmrSolver(m, n, S; window :: Int=5) u = S(undef, 0) v = S(undef, 0) err_vec = zeros(T, window) - stats = LsmrStats(0, false, false, T[], T[], zero(T), zero(T), zero(T), zero(T), zero(T), "unknown") + stats = LsmrStats(0, false, false, T[], T[], zero(T), zero(T), zero(T), zero(T), zero(T), 0.0, "unknown") solver = LsmrSolver{T,FC,S}(m, n, x, Nv, Aᴴu, h, hbar, Mu, Av, u, v, err_vec, stats) return solver end @@ -1398,7 +1398,7 @@ function LnlqSolver(m, n, S) u = S(undef, 0) v = S(undef, 0) q = S(undef, 0) - stats = LNLQStats(0, false, T[], false, T[], T[], "unknown") + stats = LNLQStats(0, false, T[], false, T[], T[], 0.0, "unknown") solver = LnlqSolver{T,FC,S}(m, n, x, Nv, Aᴴu, y, w̄, Mu, Av, u, v, q, stats) return solver end @@ -1448,7 +1448,7 @@ function CraigSolver(m, n, S) u = S(undef, 0) v = S(undef, 0) w2 = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CraigSolver{T,FC,S}(m, n, x, Nv, Aᴴu, y, w, Mu, Av, u, v, w2, stats) return solver end @@ -1502,7 +1502,7 @@ function CraigmrSolver(m, n, S) u = S(undef, 0) v = S(undef, 0) q = S(undef, 0) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = CraigmrSolver{T,FC,S}(m, n, x, Nv, Aᴴu, d, y, Mu, w, wbar, Av, u, v, q, stats) return solver end @@ -1556,7 +1556,7 @@ function GmresSolver(m, n, memory, S) s = Vector{FC}(undef, memory) z = Vector{FC}(undef, memory) R = Vector{FC}(undef, div(memory * (memory+1), 2)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = GmresSolver{T,FC,S}(m, n, Δx, x, w, p, q, V, c, s, z, R, false, 0, stats) return solver end @@ -1610,7 +1610,7 @@ function FgmresSolver(m, n, memory, S) s = Vector{FC}(undef, memory) z = Vector{FC}(undef, memory) R = Vector{FC}(undef, div(memory * (memory+1), 2)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = FgmresSolver{T,FC,S}(m, n, Δx, x, w, q, V, Z, c, s, z, R, false, 0, stats) return solver end @@ -1661,7 +1661,7 @@ function FomSolver(m, n, memory, S) l = Vector{FC}(undef, memory) z = Vector{FC}(undef, memory) U = Vector{FC}(undef, div(memory * (memory+1), 2)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = FomSolver{T,FC,S}(m, n, Δx, x, w, p, q, V, l, z, U, false, stats) return solver end @@ -1726,7 +1726,7 @@ function GpmrSolver(m, n, memory, S) gc = Vector{T}(undef, 4 * memory) zt = Vector{FC}(undef, 2 * memory) R = Vector{FC}(undef, memory * (2 * memory + 1)) - stats = SimpleStats(0, false, false, T[], T[], T[], "unknown") + stats = SimpleStats(0, false, false, T[], T[], T[], 0.0, "unknown") solver = GpmrSolver{T,FC,S}(m, n, wA, wB, dA, dB, Δx, Δy, x, y, q, p, V, U, gs, gc, zt, R, false, stats) return solver end From fa0da9c58c73c0b0deddc1795b9072572da667d9 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 12 May 2023 14:30:57 -0400 Subject: [PATCH 168/182] Update stats.timer in all Krylov methods --- src/bicgstab.jl | 3 +++ src/bilq.jl | 3 +++ src/bilqr.jl | 4 +++- src/cg.jl | 2 ++ src/cg_lanczos.jl | 2 ++ src/cg_lanczos_shift.jl | 2 ++ src/cgls.jl | 2 ++ src/cgne.jl | 2 ++ src/cgs.jl | 3 +++ src/cr.jl | 4 ++++ src/craig.jl | 2 ++ src/craigmr.jl | 3 +++ src/crls.jl | 2 ++ src/crmr.jl | 2 ++ src/diom.jl | 2 ++ src/dqgmres.jl | 2 ++ src/fgmres.jl | 2 ++ src/fom.jl | 2 ++ src/gmres.jl | 2 ++ src/gpmr.jl | 1 + src/lnlq.jl | 2 ++ src/lslq.jl | 3 +++ src/lsmr.jl | 3 +++ src/lsqr.jl | 3 +++ src/minres.jl | 4 +++- src/minres_qlp.jl | 2 ++ src/qmr.jl | 3 +++ src/symmlq.jl | 2 ++ src/tricg.jl | 1 + src/trilqr.jl | 3 ++- src/trimr.jl | 1 + src/usymlq.jl | 2 ++ src/usymqr.jl | 2 ++ 33 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index dbe05c87d..db58af2a5 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -188,6 +188,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, if rNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -204,6 +205,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, if next_ρ == 0 stats.niter = 0 stats.solved, stats.inconsistent = false, false + stats.timer = ktimer(start_time) stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver @@ -276,6 +278,7 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/bilq.jl b/src/bilq.jl index 4af6b796b..c3f14c036 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -158,6 +158,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, stats.niter = 0 stats.solved = true stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -176,6 +177,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, stats.niter = 0 stats.solved = false stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver @@ -374,6 +376,7 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, stats.niter = iter stats.solved = solved_lq || solved_cg stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/bilqr.jl b/src/bilqr.jl index 05e24714b..a90d0b62e 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -182,6 +182,7 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi stats.niter = 0 stats.solved_primal = false stats.solved_dual = false + stats.timer = ktimer(start_time) stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver @@ -481,9 +482,10 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi # Update stats stats.niter = iter - stats.status = status stats.solved_primal = solved_primal stats.solved_dual = solved_dual + stats.timer = ktimer(start_time) + stats.status = status return solver end end diff --git a/src/cg.jl b/src/cg.jl index fc8cd996f..f36549ed9 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -166,6 +166,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v if γ == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -268,6 +269,7 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 7549463c5..c9c7e59ba 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -165,6 +165,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax stats.solved = true stats.Anorm = zero(T) stats.indefinite = false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -264,6 +265,7 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax stats.solved = solved stats.Anorm = sqrt(Anorm2) stats.indefinite = indefinite + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 3871528de..38e8cb832 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -151,6 +151,7 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t if β == 0 stats.niter = 0 stats.solved = true + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" return solver end @@ -269,6 +270,7 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t # Update stats. TODO: Estimate Anorm and Acond. stats.niter = iter stats.solved = solved + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/cgls.jl b/src/cgls.jl index 3b80e9209..2a027cc58 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -156,6 +156,7 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose if bNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(rNorms, zero(T)) history && push!(ArNorms, zero(T)) @@ -231,6 +232,7 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/cgne.jl b/src/cgne.jl index 301366f10..c6c8b0e9b 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -163,6 +163,7 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor if rNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" return solver end @@ -240,6 +241,7 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/cgs.jl b/src/cgs.jl index a22fe19c8..82765e573 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -183,6 +183,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist if rNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -193,6 +194,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist if ρ == 0 stats.niter = 0 stats.solved, stats.inconsistent = false, false + stats.timer = ktimer(start_time) stats.status = "Breakdown bᴴc = 0" solver.warm_start =false return solver @@ -280,6 +282,7 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/cr.jl b/src/cr.jl index d0543af5b..0d69c730c 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -176,6 +176,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema if ρ == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(ArNorms, zero(T)) solver.warm_start = false @@ -218,6 +219,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema (verbose > 0) && @printf(iostream, "nonpositive curvature detected: pᴴAp = %8.1e and rᴴAr = %8.1e\n", pAp, ρ) stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "nonpositive curvature" return solver end @@ -382,6 +384,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "solver encountered numerical issues" solver.warm_start = false return solver @@ -411,6 +414,7 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/craig.jl b/src/craig.jl index adb1f1e9f..562a23c6e 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -212,6 +212,7 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at if β₁ == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" return solver end @@ -391,6 +392,7 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/craigmr.jl b/src/craigmr.jl index a482046c7..4b8c5c627 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -201,6 +201,7 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver stats.solved, stats.inconsistent = true, false history && push!(rNorms, β) history && push!(ArNorms, zero(T)) + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" return solver end @@ -228,6 +229,7 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver stats.solved, stats.inconsistent = true, false history && push!(rNorms, β) history && push!(ArNorms, zero(T)) + stats.timer = ktimer(start_time) stats.status = "x = 0 is a minimum least-squares solution" return solver end @@ -384,6 +386,7 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/crls.jl b/src/crls.jl index 3974bf628..371113e3a 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -151,6 +151,7 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose if bNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(ArNorms, zero(T)) return solver @@ -256,6 +257,7 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/crmr.jl b/src/crmr.jl index 5b482c44c..292d05494 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -162,6 +162,7 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor if bNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(ArNorms, zero(T)) return solver @@ -232,6 +233,7 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/diom.jl b/src/diom.jl index 01803b2f2..726f64de9 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -172,6 +172,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem if rNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -328,6 +329,7 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 4e158fcdf..bb9790223 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -172,6 +172,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti if rNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -330,6 +331,7 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/fgmres.jl b/src/fgmres.jl index 3d9d87ac4..d8741e5b9 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -182,6 +182,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i if β == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -380,6 +381,7 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/fom.jl b/src/fom.jl index 6a1162bf9..3b6832d9f 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -177,6 +177,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma if β == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -360,6 +361,7 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma stats.niter = iter stats.solved = solved stats.inconsistent = !solved && breakdown + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/gmres.jl b/src/gmres.jl index c199ba9ee..e094ff4b3 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -177,6 +177,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it if β == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -377,6 +378,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/gpmr.jl b/src/gpmr.jl index a267dc7a9..0b84eb856 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -537,6 +537,7 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/lnlq.jl b/src/lnlq.jl index 81e44e6d9..c0cef6aa4 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -211,6 +211,7 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly stats.solved = true stats.error_with_bnd = false history && push!(rNorms, bNorm) + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" return solver end @@ -546,6 +547,7 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly stats.niter = iter stats.solved = solved_lq || solved_cg stats.error_with_bnd = complex_error_bnd + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/lslq.jl b/src/lslq.jl index 22106dac3..9f45a0f6c 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -239,6 +239,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : stats.error_with_bnd = false history && push!(rNorms, zero(T)) history && push!(ArNorms, zero(T)) + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" return solver end @@ -258,6 +259,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : stats.error_with_bnd = false history && push!(rNorms, β₁) history && push!(ArNorms, zero(T)) + stats.timer = ktimer(start_time) stats.status = "x = 0 is a minimum least-squares solution" return solver end @@ -500,6 +502,7 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : stats.solved = solved stats.inconsistent = !zero_resid stats.error_with_bnd = complex_error_bnd + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/lsmr.jl b/src/lsmr.jl index 7419d1861..2532cf273 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -210,6 +210,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, if β₁ == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(rNorms, zero(T)) history && push!(ArNorms, zero(T)) @@ -270,6 +271,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, if α == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a minimum least-squares solution" return solver end @@ -435,6 +437,7 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, stats.niter = iter stats.solved = solved stats.inconsistent = !zero_resid + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/lsqr.jl b/src/lsqr.jl index 18bdd0144..2fa291f2d 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -207,6 +207,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, if β₁ == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(rNorms, zero(T)) history && push!(ArNorms, zero(T)) @@ -252,6 +253,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, if α == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a minimum least-squares solution" return solver end @@ -420,6 +422,7 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, stats.niter = iter stats.solved = solved stats.inconsistent = !zero_resid + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/minres.jl b/src/minres.jl index 09c3556fe..697335b8a 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -196,6 +196,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, if β₁ == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" history && push!(rNorms, β₁) history && push!(ArNorms, zero(T)) @@ -241,7 +242,6 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, kdisplay(iter, verbose) && @printf(iostream, "%5d %7.1e %7.1e %7.1e %8.1e %8.1e %7.1e %7.1e %7s %7s %.2fs\n", iter, rNorm, ArNorm, β, cs, sn, ANorm, Acond, "✗ ✗ ✗ ✗", "✗ ✗ ✗ ✗", ktimer(start_time)) ε = atol + rtol * β₁ - stats.status = "unknown" solved = solved_mach = solved_lim = (rNorm ≤ rtol) tired = iter ≥ itmax ill_cond = ill_cond_mach = ill_cond_lim = false @@ -346,6 +346,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, # Aᴴb = 0 so x = 0 is a minimum least-squares solution stats.niter = 1 stats.solved, stats.inconsistent = true, true + stats.timer = ktimer(start_time) stats.status = "x is a minimum least-squares solution" solver.warm_start = false return solver @@ -395,6 +396,7 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, stats.niter = iter stats.solved = solved stats.inconsistent = !zero_resid + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index fa5429c58..4dac81295 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -186,6 +186,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve if rNorm == 0 stats.niter = 0 stats.solved, stats.inconsistent = true, false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -486,6 +487,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/qmr.jl b/src/qmr.jl index aa13f7bb0..2072232bc 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -165,6 +165,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, stats.niter = 0 stats.solved = true stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -183,6 +184,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, stats.niter = 0 stats.solved = false stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "Breakdown bᴴc = 0" solver.warm_start = false return solver @@ -371,6 +373,7 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, stats.niter = iter stats.solved = solved stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/symmlq.jl b/src/symmlq.jl index de8519223..ae759631c 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -186,6 +186,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : stats.Acond = T(NaN) history && push!(rNorms, zero(T)) history && push!(rcgNorms, zero(T)) + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -458,6 +459,7 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : stats.solved = solved stats.Anorm = ANorm stats.Acond = Acond + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/tricg.jl b/src/tricg.jl index 81677879d..cc443124d 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -464,6 +464,7 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax stats.niter = iter stats.solved = solved stats.inconsistent = !solved && breakdown + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/trilqr.jl b/src/trilqr.jl index 99158a42d..229778765 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -460,9 +460,10 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, # Update stats stats.niter = iter - stats.status = status stats.solved_primal = solved_primal stats.solved_dual = solved_dual + stats.timer = ktimer(start_time) + stats.status = status return solver end end diff --git a/src/trimr.jl b/src/trimr.jl index d94a7fedb..46c450015 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -567,6 +567,7 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : stats.niter = iter stats.solved = solved stats.inconsistent = !solved && breakdown + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/usymlq.jl b/src/usymlq.jl index d3cafe809..7bd0cf68b 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -169,6 +169,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, stats.niter = 0 stats.solved = true stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -365,6 +366,7 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, stats.niter = iter stats.solved = solved_lq || solved_cg stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = status return solver end diff --git a/src/usymqr.jl b/src/usymqr.jl index 152189df7..951081840 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -167,6 +167,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, stats.niter = 0 stats.solved = true stats.inconsistent = false + stats.timer = ktimer(start_time) stats.status = "x = 0 is a zero-residual solution" solver.warm_start = false return solver @@ -356,6 +357,7 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, stats.niter = iter stats.solved = solved stats.inconsistent = inconsistent + stats.timer = ktimer(start_time) stats.status = status return solver end From d6f3e51648122aa89f24dc13338cfd053722227b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Fri, 12 May 2023 15:52:33 -0400 Subject: [PATCH 169/182] Release 0.9.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6249e13f4..fcc02778a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Krylov" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.9.0" +version = "0.9.1" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 6d9def66866a3812e2d59d6ed7d54195b3b8c500 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 15 May 2023 11:36:28 -0400 Subject: [PATCH 170/182] Improve the reliability of solver.stats.timer --- src/bicgstab.jl | 12 +++++++++--- src/bilq.jl | 12 +++++++++--- src/bilqr.jl | 12 +++++++++--- src/cg.jl | 12 +++++++++--- src/cg_lanczos.jl | 12 +++++++++--- src/cg_lanczos_shift.jl | 4 +++- src/cgls.jl | 4 +++- src/cgne.jl | 4 +++- src/cgs.jl | 12 +++++++++--- src/cr.jl | 12 +++++++++--- src/craig.jl | 4 +++- src/craigmr.jl | 4 +++- src/crls.jl | 4 +++- src/crmr.jl | 4 +++- src/diom.jl | 12 +++++++++--- src/dqgmres.jl | 12 +++++++++--- src/fgmres.jl | 12 +++++++++--- src/fom.jl | 12 +++++++++--- src/gmres.jl | 12 +++++++++--- src/gpmr.jl | 12 +++++++++--- src/lnlq.jl | 4 +++- src/lslq.jl | 4 +++- src/lsmr.jl | 4 +++- src/lsqr.jl | 4 +++- src/minres.jl | 12 +++++++++--- src/minres_qlp.jl | 12 +++++++++--- src/qmr.jl | 12 +++++++++--- src/symmlq.jl | 12 +++++++++--- src/tricg.jl | 12 +++++++++--- src/trilqr.jl | 12 +++++++++--- src/trimr.jl | 12 +++++++++--- src/usymlq.jl | 12 +++++++++--- src/usymqr.jl | 12 +++++++++--- 33 files changed, 231 insertions(+), 77 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index db58af2a5..014f3e723 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -110,24 +110,30 @@ kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = BicgstabSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function bicgstab(A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BicgstabSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/bilq.jl b/src/bilq.jl index c3f14c036..61e8dd19a 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -95,24 +95,30 @@ kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = BilqSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bilq!(solver, A, b; $(kwargs_bilq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function bilq(A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bilq!(solver, A, b; $(kwargs_bilq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bilq!(solver, A, b; $(kwargs_bilq...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/bilqr.jl b/src/bilqr.jl index a90d0b62e..a0d7cd1ac 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -99,24 +99,30 @@ kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :hi start_time = time_ns() solver = BilqrSolver(A, b) warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/cg.jl b/src/cg.jl index f36549ed9..897585fc9 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -102,24 +102,30 @@ kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :v start_time = time_ns() solver = CgSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg!(solver, A, b; $(kwargs_cg...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cg(A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg!(solver, A, b; $(kwargs_cg...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg!(solver, A, b; $(kwargs_cg...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index c9c7e59ba..c14050e5e 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -98,24 +98,30 @@ kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax start_time = time_ns() solver = CgLanczosSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cg_lanczos(A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgLanczosSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 38e8cb832..b9ee12661 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -93,8 +93,10 @@ kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :t start_time = time_ns() nshifts = length(shifts) solver = CgLanczosShiftSolver(A, b, nshifts) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cg_lanczos_shift!(solver, A, b, shifts; $(kwargs_cg_lanczos_shift...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/cgls.jl b/src/cgls.jl index 2a027cc58..acb44ee3f 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -116,8 +116,10 @@ kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose function cgls(A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CglsSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cgls!(solver, A, b; $(kwargs_cgls...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/cgne.jl b/src/cgne.jl index c6c8b0e9b..fd53aabfe 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -121,8 +121,10 @@ kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor function cgne(A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgneSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cgne!(solver, A, b; $(kwargs_cgne...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/cgs.jl b/src/cgs.jl index 82765e573..af4a701d5 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -111,24 +111,30 @@ kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :hist start_time = time_ns() solver = CgsSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cgs!(solver, A, b; $(kwargs_cgs...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cgs(A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgsSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cgs!(solver, A, b; $(kwargs_cgs...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cgs!(solver, A, b; $(kwargs_cgs...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/cr.jl b/src/cr.jl index 0d69c730c..5d435d134 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -109,24 +109,30 @@ kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timema start_time = time_ns() solver = CrSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cr!(solver, A, b; $(kwargs_cr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cr(A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cr!(solver, A, b; $(kwargs_cr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time cr!(solver, A, b; $(kwargs_cr...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/craig.jl b/src/craig.jl index 562a23c6e..cbb8fb1da 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -159,8 +159,10 @@ kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :at function craig(A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CraigSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time craig!(solver, A, b; $(kwargs_craig...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end diff --git a/src/craigmr.jl b/src/craigmr.jl index 4b8c5c627..c1b7be5f4 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -148,8 +148,10 @@ kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :ver function craigmr(A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CraigmrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time craigmr!(solver, A, b; $(kwargs_craigmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end diff --git a/src/crls.jl b/src/crls.jl index 371113e3a..85d46a4b2 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -107,8 +107,10 @@ kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose function crls(A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrlsSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time crls!(solver, A, b; $(kwargs_crls...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/crmr.jl b/src/crmr.jl index 292d05494..15bb2f086 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -119,8 +119,10 @@ kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :histor function crmr(A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrmrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time crmr!(solver, A, b; $(kwargs_crmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/diom.jl b/src/diom.jl index 726f64de9..89b658b41 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -106,24 +106,30 @@ kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timem start_time = time_ns() solver = DiomSolver(A, b, memory) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time diom!(solver, A, b; $(kwargs_diom...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function diom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DiomSolver(A, b, memory) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time diom!(solver, A, b; $(kwargs_diom...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time diom!(solver, A, b; $(kwargs_diom...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/dqgmres.jl b/src/dqgmres.jl index bb9790223..929230df9 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -106,24 +106,30 @@ kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :ti start_time = time_ns() solver = DqgmresSolver(A, b, memory) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function dqgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DqgmresSolver(A, b, memory) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/fgmres.jl b/src/fgmres.jl index d8741e5b9..4cda64f2e 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -109,24 +109,30 @@ kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :i start_time = time_ns() solver = FgmresSolver(A, b, memory) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time fgmres!(solver, A, b; $(kwargs_fgmres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FgmresSolver(A, b, memory) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time fgmres!(solver, A, b; $(kwargs_fgmres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time fgmres!(solver, A, b; $(kwargs_fgmres...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/fom.jl b/src/fom.jl index 3b6832d9f..6215ae0f2 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -102,24 +102,30 @@ kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itma start_time = time_ns() solver = FomSolver(A, b, memory) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time fom!(solver, A, b; $(kwargs_fom...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function fom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FomSolver(A, b, memory) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time fom!(solver, A, b; $(kwargs_fom...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time fom!(solver, A, b; $(kwargs_fom...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/gmres.jl b/src/gmres.jl index e094ff4b3..3bdbd37f0 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -102,24 +102,30 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it start_time = time_ns() solver = GmresSolver(A, b, memory) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time gmres!(solver, A, b; $(kwargs_gmres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function gmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GmresSolver(A, b, memory) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time gmres!(solver, A, b; $(kwargs_gmres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time gmres!(solver, A, b; $(kwargs_gmres...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/gpmr.jl b/src/gpmr.jl index 0b84eb856..29af44fd4 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -142,24 +142,30 @@ kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :ato start_time = time_ns() solver = GpmrSolver(A, b, memory) warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GpmrSolver(A, b, memory) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/lnlq.jl b/src/lnlq.jl index c0cef6aa4..4bff30148 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -155,8 +155,10 @@ kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly function lnlq(A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LnlqSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time lnlq!(solver, A, b; $(kwargs_lnlq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end diff --git a/src/lslq.jl b/src/lslq.jl index 9f45a0f6c..81d15fc8b 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -181,8 +181,10 @@ kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, : function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LslqSolver(A, b; window) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time lslq!(solver, A, b; $(kwargs_lslq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/lsmr.jl b/src/lsmr.jl index 2532cf273..464176979 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -158,8 +158,10 @@ kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LsmrSolver(A, b; window) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time lsmr!(solver, A, b; $(kwargs_lsmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/lsqr.jl b/src/lsqr.jl index 2fa291f2d..194801566 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -154,8 +154,10 @@ kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LsqrSolver(A, b; window) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time lsqr!(solver, A, b; $(kwargs_lsqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end diff --git a/src/minres.jl b/src/minres.jl index 697335b8a..b1b762517 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -124,24 +124,30 @@ kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, start_time = time_ns() solver = MinresSolver(A, b; window) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time minres!(solver, A, b; $(kwargs_minres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresSolver(A, b; window) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time minres!(solver, A, b; $(kwargs_minres...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time minres!(solver, A, b; $(kwargs_minres...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 4dac81295..f047e0b72 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -106,24 +106,30 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve start_time = time_ns() solver = MinresQlpSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function minres_qlp(A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresQlpSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/qmr.jl b/src/qmr.jl index 2072232bc..88f8c6667 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -102,24 +102,30 @@ kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, start_time = time_ns() solver = QmrSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time qmr!(solver, A, b; $(kwargs_qmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function qmr(A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = QmrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time qmr!(solver, A, b; $(kwargs_qmr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time qmr!(solver, A, b; $(kwargs_qmr...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/symmlq.jl b/src/symmlq.jl index ae759631c..900a6005e 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -110,24 +110,30 @@ kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, : start_time = time_ns() solver = SymmlqSolver(A, b; window) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time symmlq!(solver, A, b; $(kwargs_symmlq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = SymmlqSolver(A, b; window) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time symmlq!(solver, A, b; $(kwargs_symmlq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time symmlq!(solver, A, b; $(kwargs_symmlq...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/tricg.jl b/src/tricg.jl index cc443124d..aef1924a1 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -129,24 +129,30 @@ kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax start_time = time_ns() solver = TricgSolver(A, b) warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time tricg!(solver, A, b, c; $(kwargs_tricg...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TricgSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time tricg!(solver, A, b, c; $(kwargs_tricg...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time tricg!(solver, A, b, c; $(kwargs_tricg...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/trilqr.jl b/src/trilqr.jl index 229778765..e55dce172 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -98,24 +98,30 @@ kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = TrilqrSolver(A, b) warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrilqrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/trimr.jl b/src/trimr.jl index 46c450015..697c7684e 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -130,24 +130,30 @@ kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, : start_time = time_ns() solver = TrimrSolver(A, b) warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time trimr!(solver, A, b, c; $(kwargs_trimr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrimrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time trimr!(solver, A, b, c; $(kwargs_trimr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0, y0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time trimr!(solver, A, b, c; $(kwargs_trimr...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/usymlq.jl b/src/usymlq.jl index 7bd0cf68b..22496ce68 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -107,24 +107,30 @@ kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, start_time = time_ns() solver = UsymlqSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymlqSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + solver.stats.timer += elapsed_time return solver end diff --git a/src/usymqr.jl b/src/usymqr.jl index 951081840..2d8c8c294 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -105,24 +105,30 @@ kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, start_time = time_ns() solver = UsymqrSolver(A, b) warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymqrSolver(A, b) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + solver.stats.timer += elapsed_time return (solver.x, solver.stats) end function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} start_time = time_ns() warm_start!(solver, x0) - timemax -= ktimer(start_time) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + solver.stats.timer += elapsed_time return solver end From 0de01165f76c9662ab748a41ad297a21506cb951 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 28 Jun 2023 17:36:05 -0400 Subject: [PATCH 171/182] [CI] Remove the build with Linux ARM --- .cirrus.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index f51d815a3..792aad121 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -7,11 +7,6 @@ task: matrix: - JULIA_VERSION: 1.6 - JULIA_VERSION: 1 - - name: Linux ARMv8 - arm_container: - image: ubuntu:latest - env: - - JULIA_VERSION: 1 - name: musl Linux container: image: alpine:3.14 From 1a1c59f1770b07b921892d277d1660a6423995ec Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 28 Jun 2023 17:24:30 -0400 Subject: [PATCH 172/182] Optimize the generic function solve! --- src/Krylov.jl | 1 + src/bicgstab.jl | 4 +++ src/bilq.jl | 4 +++ src/bilqr.jl | 5 +++ src/cg.jl | 4 +++ src/cg_lanczos.jl | 4 +++ src/cg_lanczos_shift.jl | 5 +++ src/cgls.jl | 4 +++ src/cgne.jl | 4 +++ src/cgs.jl | 4 +++ src/cr.jl | 4 +++ src/craig.jl | 4 +++ src/craigmr.jl | 4 +++ src/crls.jl | 4 +++ src/crmr.jl | 4 +++ src/diom.jl | 4 +++ src/dqgmres.jl | 4 +++ src/fgmres.jl | 4 +++ src/fom.jl | 4 +++ src/gmres.jl | 4 +++ src/gpmr.jl | 6 ++++ src/krylov_solve.jl | 46 +++++++++++++++++++++++++ src/krylov_solvers.jl | 74 ++++++++++++++++++----------------------- src/lnlq.jl | 4 +++ src/lslq.jl | 4 +++ src/lsmr.jl | 4 +++ src/lsqr.jl | 4 +++ src/minres.jl | 4 +++ src/minres_qlp.jl | 4 +++ src/qmr.jl | 4 +++ src/symmlq.jl | 4 +++ src/tricg.jl | 5 +++ src/trilqr.jl | 5 +++ src/trimr.jl | 5 +++ src/usymlq.jl | 5 +++ src/usymqr.jl | 5 +++ 36 files changed, 221 insertions(+), 41 deletions(-) create mode 100644 src/krylov_solve.jl diff --git a/src/Krylov.jl b/src/Krylov.jl index aadde1575..765e71d0e 100644 --- a/src/Krylov.jl +++ b/src/Krylov.jl @@ -51,4 +51,5 @@ include("lnlq.jl") include("craig.jl") include("craigmr.jl") +include("krylov_solve.jl") end diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 014f3e723..876129d1b 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -88,6 +88,9 @@ See [`BicgstabSolver`](@ref) for more details about the `solver`. """ function bicgstab! end +def_args_bicgstab = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_bicgstab = (:(; c::AbstractVector{FC} = b ), :(; M = I ), :(; N = I ), @@ -103,6 +106,7 @@ def_kwargs_bicgstab = (:(; c::AbstractVector{FC} = b ), def_kwargs_bicgstab = mapreduce(extract_parameters, vcat, def_kwargs_bicgstab) +args_bicgstab = (:A, :b) kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/bilq.jl b/src/bilq.jl index 61e8dd19a..58896b841 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -75,6 +75,9 @@ See [`BilqSolver`](@ref) for more details about the `solver`. """ function bilq! end +def_args_bilq = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_bilq = (:(; c::AbstractVector{FC} = b ), :(; transfer_to_bicg::Bool = true), :(; atol::T = √eps(T) ), @@ -88,6 +91,7 @@ def_kwargs_bilq = (:(; c::AbstractVector{FC} = b ), def_kwargs_bilq = mapreduce(extract_parameters, vcat, def_kwargs_bilq) +args_bilq = (:A, :b) kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/bilqr.jl b/src/bilqr.jl index a0d7cd1ac..49dcd25dc 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -80,6 +80,10 @@ See [`BilqrSolver`](@ref) for more details about the `solver`. """ function bilqr! end +def_args_bilqr = (:(A ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_bilqr = (:(; transfer_to_bicg::Bool = true), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -92,6 +96,7 @@ def_kwargs_bilqr = (:(; transfer_to_bicg::Bool = true), def_kwargs_bilqr = mapreduce(extract_parameters, vcat, def_kwargs_bilqr) +args_bilqr = (:A, :b, :c) kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cg.jl b/src/cg.jl index 897585fc9..97e34fb61 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -80,6 +80,9 @@ See [`CgSolver`](@ref) for more details about the `solver`. """ function cg! end +def_args_cg = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_cg = (:(; M = I ), :(; ldiv::Bool = false ), :(; radius::T = zero(T) ), @@ -95,6 +98,7 @@ def_kwargs_cg = (:(; M = I ), def_kwargs_cg = mapreduce(extract_parameters, vcat, def_kwargs_cg) +args_cg = (:A, :b) kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index c14050e5e..4f6e25084 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -77,6 +77,9 @@ See [`CgLanczosSolver`](@ref) for more details about the `solver`. """ function cg_lanczos! end +def_args_cg_lanczos = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_cg_lanczos = (:(; M = I ), :(; ldiv::Bool = false ), :(; check_curvature::Bool = false), @@ -91,6 +94,7 @@ def_kwargs_cg_lanczos = (:(; M = I ), def_kwargs_cg_lanczos = mapreduce(extract_parameters, vcat, def_kwargs_cg_lanczos) +args_cg_lanczos = (:A, :b) kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index b9ee12661..94799d69c 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -72,6 +72,10 @@ See [`CgLanczosShiftSolver`](@ref) for more details about the `solver`. """ function cg_lanczos_shift! end +def_args_cg_lanczos_shift = (:(A ), + :(b::AbstractVector{FC} ), + :(shifts::AbstractVector{T})) + def_kwargs_cg_lanczos_shift = (:(; M = I ), :(; ldiv::Bool = false ), :(; check_curvature::Bool = false), @@ -86,6 +90,7 @@ def_kwargs_cg_lanczos_shift = (:(; M = I ), def_kwargs_cg_lanczos_shift = mapreduce(extract_parameters, vcat, def_kwargs_cg_lanczos_shift) +args_cg_lanczos_shift = (:A, :b, :shifts) kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cgls.jl b/src/cgls.jl index acb44ee3f..9f244ed9f 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -95,6 +95,9 @@ See [`CglsSolver`](@ref) for more details about the `solver`. """ function cgls! end +def_args_cgls = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_cgls = (:(; M = I ), :(; ldiv::Bool = false ), :(; radius::T = zero(T) ), @@ -110,6 +113,7 @@ def_kwargs_cgls = (:(; M = I ), def_kwargs_cgls = mapreduce(extract_parameters, vcat, def_kwargs_cgls) +args_cgls = (:A, :b) kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cgne.jl b/src/cgne.jl index fd53aabfe..6bd9ccd2e 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -101,6 +101,9 @@ See [`CgneSolver`](@ref) for more details about the `solver`. """ function cgne! end +def_args_cgne = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_cgne = (:(; N = I ), :(; ldiv::Bool = false ), :(; λ::T = zero(T) ), @@ -115,6 +118,7 @@ def_kwargs_cgne = (:(; N = I ), def_kwargs_cgne = mapreduce(extract_parameters, vcat, def_kwargs_cgne) +args_cgne = (:A, :b) kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cgs.jl b/src/cgs.jl index af4a701d5..c80016ea3 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -89,6 +89,9 @@ See [`CgsSolver`](@ref) for more details about the `solver`. """ function cgs! end +def_args_cgs = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_cgs = (:(; c::AbstractVector{FC} = b ), :(; M = I ), :(; N = I ), @@ -104,6 +107,7 @@ def_kwargs_cgs = (:(; c::AbstractVector{FC} = b ), def_kwargs_cgs = mapreduce(extract_parameters, vcat, def_kwargs_cgs) +args_cgs = (:A, :b) kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cr.jl b/src/cr.jl index 5d435d134..3b1005ddd 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -86,6 +86,9 @@ See [`CrSolver`](@ref) for more details about the `solver`. """ function cr! end +def_args_cr = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_cr = (:(; M = I ), :(; ldiv::Bool = false ), :(; radius::T = zero(T) ), @@ -102,6 +105,7 @@ def_kwargs_cr = (:(; M = I ), def_kwargs_cr = mapreduce(extract_parameters, vcat, def_kwargs_cr) +args_cr = (:A, :b) kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/craig.jl b/src/craig.jl index cbb8fb1da..05b220757 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -134,6 +134,9 @@ See [`CraigSolver`](@ref) for more details about the `solver`. """ function craig! end +def_args_craig = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_craig = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -153,6 +156,7 @@ def_kwargs_craig = (:(; M = I ), def_kwargs_craig = mapreduce(extract_parameters, vcat, def_kwargs_craig) +args_craig = (:A, :b) kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/craigmr.jl b/src/craigmr.jl index c1b7be5f4..c0cba66c4 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -126,6 +126,9 @@ See [`CraigmrSolver`](@ref) for more details about the `solver`. """ function craigmr! end +def_args_craigmr = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_craigmr = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -142,6 +145,7 @@ def_kwargs_craigmr = (:(; M = I ), def_kwargs_craigmr = mapreduce(extract_parameters, vcat, def_kwargs_craigmr) +args_craigmr = (:A, :b) kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/crls.jl b/src/crls.jl index 85d46a4b2..632cb3f2e 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -86,6 +86,9 @@ See [`CrlsSolver`](@ref) for more details about the `solver`. """ function crls! end +def_args_crls = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_crls = (:(; M = I ), :(; ldiv::Bool = false ), :(; radius::T = zero(T) ), @@ -101,6 +104,7 @@ def_kwargs_crls = (:(; M = I ), def_kwargs_crls = mapreduce(extract_parameters, vcat, def_kwargs_crls) +args_crls = (:A, :b) kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/crmr.jl b/src/crmr.jl index 15bb2f086..36538c88b 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -99,6 +99,9 @@ See [`CrmrSolver`](@ref) for more details about the `solver`. """ function crmr! end +def_args_crmr = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_crmr = (:(; N = I ), :(; ldiv::Bool = false ), :(; λ::T = zero(T) ), @@ -113,6 +116,7 @@ def_kwargs_crmr = (:(; N = I ), def_kwargs_crmr = mapreduce(extract_parameters, vcat, def_kwargs_crmr) +args_crmr = (:A, :b) kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/diom.jl b/src/diom.jl index 89b658b41..18fe67b06 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -84,6 +84,9 @@ See [`DiomSolver`](@ref) for more details about the `solver`. """ function diom! end +def_args_diom = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_diom = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -99,6 +102,7 @@ def_kwargs_diom = (:(; M = I ), def_kwargs_diom = mapreduce(extract_parameters, vcat, def_kwargs_diom) +args_diom = (:A, :b) kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 929230df9..12d9f3449 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -84,6 +84,9 @@ See [`DqgmresSolver`](@ref) for more details about the `solver`. """ function dqgmres! end +def_args_dqgmres = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_dqgmres = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -99,6 +102,7 @@ def_kwargs_dqgmres = (:(; M = I ), def_kwargs_dqgmres = mapreduce(extract_parameters, vcat, def_kwargs_dqgmres) +args_dqgmres = (:A, :b) kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/fgmres.jl b/src/fgmres.jl index 4cda64f2e..4c931a318 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -86,6 +86,9 @@ See [`FgmresSolver`](@ref) for more details about the `solver`. """ function fgmres! end +def_args_fgmres = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_fgmres = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -102,6 +105,7 @@ def_kwargs_fgmres = (:(; M = I ), def_kwargs_fgmres = mapreduce(extract_parameters, vcat, def_kwargs_fgmres) +args_fgmres = (:A, :b) kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/fom.jl b/src/fom.jl index 6215ae0f2..800c6f8b2 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -79,6 +79,9 @@ See [`FomSolver`](@ref) for more details about the `solver`. """ function fom! end +def_args_fom = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_fom = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -95,6 +98,7 @@ def_kwargs_fom = (:(; M = I ), def_kwargs_fom = mapreduce(extract_parameters, vcat, def_kwargs_fom) +args_fom = (:A, :b) kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/gmres.jl b/src/gmres.jl index 3bdbd37f0..01590d3e1 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -79,6 +79,9 @@ See [`GmresSolver`](@ref) for more details about the `solver`. """ function gmres! end +def_args_gmres = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_gmres = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -95,6 +98,7 @@ def_kwargs_gmres = (:(; M = I ), def_kwargs_gmres = mapreduce(extract_parameters, vcat, def_kwargs_gmres) +args_gmres = (:A, :b) kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/gpmr.jl b/src/gpmr.jl index 29af44fd4..04d8f5976 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -115,6 +115,11 @@ See [`GpmrSolver`](@ref) for more details about the `solver`. """ function gpmr! end +def_args_gpmr = (:(A ), + :(B ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_gpmr = (:(; C = I ), :(; D = I ), :(; E = I ), @@ -135,6 +140,7 @@ def_kwargs_gpmr = (:(; C = I ), def_kwargs_gpmr = mapreduce(extract_parameters, vcat, def_kwargs_gpmr) +args_gpmr = (:A, :B, :b, :c) kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/krylov_solve.jl b/src/krylov_solve.jl new file mode 100644 index 000000000..be4b6b597 --- /dev/null +++ b/src/krylov_solve.jl @@ -0,0 +1,46 @@ +""" + solve!(solver, args...; kwargs...) + +Use the in-place Krylov method associated to `solver`. +""" +function solve! end + +for (KS, fun, args, def_args, kwargs, def_kwargs) in [ + (:LsmrSolver , :lsmr! , args_lsmr , def_args_lsmr , kwargs_lsmr , def_kwargs_lsmr ) + (:CgsSolver , :cgs! , args_cgs , def_args_cgs , kwargs_cgs , def_kwargs_cgs ) + (:UsymlqSolver , :usymlq! , args_usymlq , def_args_usymlq , kwargs_usymlq , def_kwargs_usymlq ) + (:LnlqSolver , :lnlq! , args_lnlq , def_args_lnlq , kwargs_lnlq , def_kwargs_lnlq ) + (:BicgstabSolver , :bicgstab! , args_bicgstab , def_args_bicgstab , kwargs_bicgstab , def_kwargs_bicgstab ) + (:CrlsSolver , :crls! , args_crls , def_args_crls , kwargs_crls , def_kwargs_crls ) + (:LsqrSolver , :lsqr! , args_lsqr , def_args_lsqr , kwargs_lsqr , def_kwargs_lsqr ) + (:MinresSolver , :minres! , args_minres , def_args_minres , kwargs_minres , def_kwargs_minres ) + (:CgneSolver , :cgne! , args_cgne , def_args_cgne , kwargs_cgne , def_kwargs_cgne ) + (:DqgmresSolver , :dqgmres! , args_dqgmres , def_args_dqgmres , kwargs_dqgmres , def_kwargs_dqgmres ) + (:SymmlqSolver , :symmlq! , args_symmlq , def_args_symmlq , kwargs_symmlq , def_kwargs_symmlq ) + (:TrimrSolver , :trimr! , args_trimr , def_args_trimr , kwargs_trimr , def_kwargs_trimr ) + (:UsymqrSolver , :usymqr! , args_usymqr , def_args_usymqr , kwargs_usymqr , def_kwargs_usymqr ) + (:BilqrSolver , :bilqr! , args_bilqr , def_args_bilqr , kwargs_bilqr , def_kwargs_bilqr ) + (:CrSolver , :cr! , args_cr , def_args_cr , kwargs_cr , def_kwargs_cr ) + (:CraigmrSolver , :craigmr! , args_craigmr , def_args_craigmr , kwargs_craigmr , def_kwargs_craigmr ) + (:TricgSolver , :tricg! , args_tricg , def_args_tricg , kwargs_tricg , def_kwargs_tricg ) + (:CraigSolver , :craig! , args_craig , def_args_craig , kwargs_craig , def_kwargs_craig ) + (:DiomSolver , :diom! , args_diom , def_args_diom , kwargs_diom , def_kwargs_diom ) + (:LslqSolver , :lslq! , args_lslq , def_args_lslq , kwargs_lslq , def_kwargs_lslq ) + (:TrilqrSolver , :trilqr! , args_trilqr , def_args_trilqr , kwargs_trilqr , def_kwargs_trilqr ) + (:CrmrSolver , :crmr! , args_crmr , def_args_crmr , kwargs_crmr , def_kwargs_crmr ) + (:CgSolver , :cg! , args_cg , def_args_cg , kwargs_cg , def_kwargs_cg ) + (:CgLanczosShiftSolver, :cg_lanczos_shift!, args_cg_lanczos_shift, def_args_cg_lanczos_shift, kwargs_cg_lanczos_shift, def_kwargs_cg_lanczos_shift) + (:CglsSolver , :cgls! , args_cgls , def_args_cgls , kwargs_cgls , def_kwargs_cgls ) + (:CgLanczosSolver , :cg_lanczos! , args_cg_lanczos , def_args_cg_lanczos , kwargs_cg_lanczos , def_kwargs_cg_lanczos ) + (:BilqSolver , :bilq! , args_bilq , def_args_bilq , kwargs_bilq , def_kwargs_bilq ) + (:MinresQlpSolver , :minres_qlp! , args_minres_qlp , def_args_minres_qlp , kwargs_minres_qlp , def_kwargs_minres_qlp ) + (:QmrSolver , :qmr! , args_qmr , def_args_qmr , kwargs_qmr , def_kwargs_qmr ) + (:GmresSolver , :gmres! , args_gmres , def_args_gmres , kwargs_gmres , def_kwargs_gmres ) + (:FgmresSolver , :fgmres! , args_fgmres , def_args_fgmres , kwargs_fgmres , def_kwargs_fgmres ) + (:FomSolver , :fom! , args_fom , def_args_fom , kwargs_fom , def_kwargs_fom ) + (:GpmrSolver , :gpmr! , args_gpmr , def_args_gpmr , kwargs_gpmr , def_kwargs_gpmr ) +] + @eval begin + solve!(solver :: $KS{T,FC,S}, $(def_args...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} = $(fun)(solver, $(args...); $(kwargs...)) + end +end diff --git a/src/krylov_solvers.jl b/src/krylov_solvers.jl index 569def7e6..0e905e807 100644 --- a/src/krylov_solvers.jl +++ b/src/krylov_solvers.jl @@ -1737,13 +1737,6 @@ function GpmrSolver(A, b, memory = 20) GpmrSolver(m, n, memory, S) end -""" - solve!(solver, args...; kwargs...) - -Use the in-place Krylov method associated to `solver`. -""" -function solve! end - """ solution(solver) @@ -1796,43 +1789,42 @@ Return the number of operator-vector products with `A'` performed by the Krylov function Atprod end for (KS, fun, nsol, nA, nAt, warm_start) in [ - (LsmrSolver , :lsmr! , 1, 1, 1, false) - (CgsSolver , :cgs! , 1, 2, 0, true ) - (UsymlqSolver , :usymlq! , 1, 1, 1, true ) - (LnlqSolver , :lnlq! , 2, 1, 1, false) - (BicgstabSolver , :bicgstab! , 1, 2, 0, true ) - (CrlsSolver , :crls! , 1, 1, 1, false) - (LsqrSolver , :lsqr! , 1, 1, 1, false) - (MinresSolver , :minres! , 1, 1, 0, true ) - (CgneSolver , :cgne! , 1, 1, 1, false) - (DqgmresSolver , :dqgmres! , 1, 1, 0, true ) - (SymmlqSolver , :symmlq! , 1, 1, 0, true ) - (TrimrSolver , :trimr! , 2, 1, 1, true ) - (UsymqrSolver , :usymqr! , 1, 1, 1, true ) - (BilqrSolver , :bilqr! , 2, 1, 1, true ) - (CrSolver , :cr! , 1, 1, 0, true ) - (CraigmrSolver , :craigmr! , 2, 1, 1, false) - (TricgSolver , :tricg! , 2, 1, 1, true ) - (CraigSolver , :craig! , 2, 1, 1, false) - (DiomSolver , :diom! , 1, 1, 0, true ) - (LslqSolver , :lslq! , 1, 1, 1, false) - (TrilqrSolver , :trilqr! , 2, 1, 1, true ) - (CrmrSolver , :crmr! , 1, 1, 1, false) - (CgSolver , :cg! , 1, 1, 0, true ) - (CgLanczosShiftSolver, :cg_lanczos_shift!, 1, 1, 0, false) - (CglsSolver , :cgls! , 1, 1, 1, false) - (CgLanczosSolver , :cg_lanczos! , 1, 1, 0, true ) - (BilqSolver , :bilq! , 1, 1, 1, true ) - (MinresQlpSolver , :minres_qlp! , 1, 1, 0, true ) - (QmrSolver , :qmr! , 1, 1, 1, true ) - (GmresSolver , :gmres! , 1, 1, 0, true ) - (FgmresSolver , :fgmres! , 1, 1, 0, true ) - (FomSolver , :fom! , 1, 1, 0, true ) - (GpmrSolver , :gpmr! , 2, 1, 0, true ) + (:LsmrSolver , :lsmr! , 1, 1, 1, false) + (:CgsSolver , :cgs! , 1, 2, 0, true ) + (:UsymlqSolver , :usymlq! , 1, 1, 1, true ) + (:LnlqSolver , :lnlq! , 2, 1, 1, false) + (:BicgstabSolver , :bicgstab! , 1, 2, 0, true ) + (:CrlsSolver , :crls! , 1, 1, 1, false) + (:LsqrSolver , :lsqr! , 1, 1, 1, false) + (:MinresSolver , :minres! , 1, 1, 0, true ) + (:CgneSolver , :cgne! , 1, 1, 1, false) + (:DqgmresSolver , :dqgmres! , 1, 1, 0, true ) + (:SymmlqSolver , :symmlq! , 1, 1, 0, true ) + (:TrimrSolver , :trimr! , 2, 1, 1, true ) + (:UsymqrSolver , :usymqr! , 1, 1, 1, true ) + (:BilqrSolver , :bilqr! , 2, 1, 1, true ) + (:CrSolver , :cr! , 1, 1, 0, true ) + (:CraigmrSolver , :craigmr! , 2, 1, 1, false) + (:TricgSolver , :tricg! , 2, 1, 1, true ) + (:CraigSolver , :craig! , 2, 1, 1, false) + (:DiomSolver , :diom! , 1, 1, 0, true ) + (:LslqSolver , :lslq! , 1, 1, 1, false) + (:TrilqrSolver , :trilqr! , 2, 1, 1, true ) + (:CrmrSolver , :crmr! , 1, 1, 1, false) + (:CgSolver , :cg! , 1, 1, 0, true ) + (:CgLanczosShiftSolver, :cg_lanczos_shift!, 1, 1, 0, false) + (:CglsSolver , :cgls! , 1, 1, 1, false) + (:CgLanczosSolver , :cg_lanczos! , 1, 1, 0, true ) + (:BilqSolver , :bilq! , 1, 1, 1, true ) + (:MinresQlpSolver , :minres_qlp! , 1, 1, 0, true ) + (:QmrSolver , :qmr! , 1, 1, 1, true ) + (:GmresSolver , :gmres! , 1, 1, 0, true ) + (:FgmresSolver , :fgmres! , 1, 1, 0, true ) + (:FomSolver , :fom! , 1, 1, 0, true ) + (:GpmrSolver , :gpmr! , 2, 1, 0, true ) ] @eval begin size(solver :: $KS) = solver.m, solver.n - solve!(solver :: $KS, args...; kwargs...) = $(fun)(solver, args...; kwargs...) statistics(solver :: $KS) = solver.stats niterations(solver :: $KS) = solver.stats.niter Aprod(solver :: $KS) = $nA * solver.stats.niter diff --git a/src/lnlq.jl b/src/lnlq.jl index 4bff30148..1dae08e14 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -129,6 +129,9 @@ See [`LnlqSolver`](@ref) for more details about the `solver`. """ function lnlq! end +def_args_lnlq = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_lnlq = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -149,6 +152,7 @@ def_kwargs_lnlq = (:(; M = I ), def_kwargs_lnlq = mapreduce(extract_parameters, vcat, def_kwargs_lnlq) +args_lnlq = (:A, :b) kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/lslq.jl b/src/lslq.jl index 81d15fc8b..ebf6b1146 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -153,6 +153,9 @@ See [`LslqSolver`](@ref) for more details about the `solver`. """ function lslq! end +def_args_lslq = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_lslq = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -175,6 +178,7 @@ def_kwargs_lslq = (:(; M = I ), def_kwargs_lslq = mapreduce(extract_parameters, vcat, def_kwargs_lslq) +args_lslq = (:A, :b) kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/lsmr.jl b/src/lsmr.jl index 464176979..b29326daf 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -131,6 +131,9 @@ See [`LsmrSolver`](@ref) for more details about the `solver`. """ function lsmr! end +def_args_lsmr = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_lsmr = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -152,6 +155,7 @@ def_kwargs_lsmr = (:(; M = I ), def_kwargs_lsmr = mapreduce(extract_parameters, vcat, def_kwargs_lsmr) +args_lsmr = (:A, :b) kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/lsqr.jl b/src/lsqr.jl index 194801566..9fd982419 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -127,6 +127,9 @@ See [`LsqrSolver`](@ref) for more details about the `solver`. """ function lsqr! end +def_args_lsqr = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_lsqr = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -148,6 +151,7 @@ def_kwargs_lsqr = (:(; M = I ), def_kwargs_lsqr = mapreduce(extract_parameters, vcat, def_kwargs_lsqr) +args_lsqr = (:A, :b) kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/minres.jl b/src/minres.jl index b1b762517..2531e8df3 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -101,6 +101,9 @@ See [`MinresSolver`](@ref) for more details about the `solver`. """ function minres! end +def_args_minres = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_minres = (:(; M = I ), :(; ldiv::Bool = false ), :(; λ::T = zero(T) ), @@ -117,6 +120,7 @@ def_kwargs_minres = (:(; M = I ), def_kwargs_minres = mapreduce(extract_parameters, vcat, def_kwargs_minres) +args_minres = (:A, :b) kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index f047e0b72..486895045 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -84,6 +84,9 @@ See [`MinresQlpSolver`](@ref) for more details about the `solver`. """ function minres_qlp! end +def_args_minres_qlp = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_minres_qlp = (:(; M = I ), :(; ldiv::Bool = false ), :(; λ::T = zero(T) ), @@ -99,6 +102,7 @@ def_kwargs_minres_qlp = (:(; M = I ), def_kwargs_minres_qlp = mapreduce(extract_parameters, vcat, def_kwargs_minres_qlp) +args_minres_qlp = (:A, :b) kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/qmr.jl b/src/qmr.jl index 88f8c6667..1d77db181 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -83,6 +83,9 @@ See [`QmrSolver`](@ref) for more details about the `solver`. """ function qmr! end +def_args_qmr = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_qmr = (:(; c::AbstractVector{FC} = b ), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -95,6 +98,7 @@ def_kwargs_qmr = (:(; c::AbstractVector{FC} = b ), def_kwargs_qmr = mapreduce(extract_parameters, vcat, def_kwargs_qmr) +args_qmr = (:A, :b) kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/symmlq.jl b/src/symmlq.jl index 900a6005e..9e0543e89 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -85,6 +85,9 @@ See [`SymmlqSolver`](@ref) for more details about the `solver`. """ function symmlq! end +def_args_symmlq = (:(A ), + :(b::AbstractVector{FC})) + def_kwargs_symmlq = (:(; M = I ), :(; ldiv::Bool = false ), :(; transfer_to_cg::Bool = true), @@ -103,6 +106,7 @@ def_kwargs_symmlq = (:(; M = I ), def_kwargs_symmlq = mapreduce(extract_parameters, vcat, def_kwargs_symmlq) +args_symmlq = (:A, :b) kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/tricg.jl b/src/tricg.jl index aef1924a1..0777cb5c0 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -103,6 +103,10 @@ See [`TricgSolver`](@ref) for more details about the `solver`. """ function tricg! end +def_args_tricg = (:(A ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_tricg = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -122,6 +126,7 @@ def_kwargs_tricg = (:(; M = I ), def_kwargs_tricg = mapreduce(extract_parameters, vcat, def_kwargs_tricg) +args_tricg = (:A, :b, :c) kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/trilqr.jl b/src/trilqr.jl index e55dce172..d8c4e0299 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -79,6 +79,10 @@ See [`TrilqrSolver`](@ref) for more details about the `solver`. """ function trilqr! end +def_args_trilqr = (:(A ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_trilqr = (:(; transfer_to_usymcg::Bool = true), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -91,6 +95,7 @@ def_kwargs_trilqr = (:(; transfer_to_usymcg::Bool = true), def_kwargs_trilqr = mapreduce(extract_parameters, vcat, def_kwargs_trilqr) +args_trilqr = (:A, :b, :c) kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/trimr.jl b/src/trimr.jl index 697c7684e..23df79a6c 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -103,6 +103,10 @@ See [`TrimrSolver`](@ref) for more details about the `solver`. """ function trimr! end +def_args_trimr = (:(A ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_trimr = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -123,6 +127,7 @@ def_kwargs_trimr = (:(; M = I ), def_kwargs_trimr = mapreduce(extract_parameters, vcat, def_kwargs_trimr) +args_trimr = (:A, :b, :c) kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/usymlq.jl b/src/usymlq.jl index 22496ce68..9785f168e 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -88,6 +88,10 @@ See [`UsymlqSolver`](@ref) for more details about the `solver`. """ function usymlq! end +def_args_usymlq = (:(A ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_usymlq = (:(; transfer_to_usymcg::Bool = true), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -100,6 +104,7 @@ def_kwargs_usymlq = (:(; transfer_to_usymcg::Bool = true), def_kwargs_usymlq = mapreduce(extract_parameters, vcat, def_kwargs_usymlq) +args_usymlq = (:A, :b, :c) kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/usymqr.jl b/src/usymqr.jl index 2d8c8c294..d97aace54 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -87,6 +87,10 @@ See [`UsymqrSolver`](@ref) for more details about the `solver`. """ function usymqr! end +def_args_usymqr = (:(A ), + :(b::AbstractVector{FC}), + :(c::AbstractVector{FC})) + def_kwargs_usymqr = (:(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), :(; itmax::Int = 0 ), @@ -98,6 +102,7 @@ def_kwargs_usymqr = (:(; atol::T = √eps(T) ), def_kwargs_usymqr = mapreduce(extract_parameters, vcat, def_kwargs_usymqr) +args_usymqr = (:A, :b, :c) kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin From dca42a51ce936ad55ba0bb69da91e88030ffeea7 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 28 Jun 2023 19:15:28 -0400 Subject: [PATCH 173/182] Add methods solve! with optional arguments --- src/bicgstab.jl | 3 ++ src/bilq.jl | 3 ++ src/bilqr.jl | 4 +++ src/cg.jl | 3 ++ src/cg_lanczos.jl | 3 ++ src/cgs.jl | 3 ++ src/cr.jl | 3 ++ src/diom.jl | 3 ++ src/dqgmres.jl | 3 ++ src/fgmres.jl | 3 ++ src/fom.jl | 3 ++ src/gmres.jl | 3 ++ src/gpmr.jl | 4 +++ src/krylov_solve.jl | 72 ++++++++++++++++++++++++--------------------- src/minres.jl | 3 ++ src/minres_qlp.jl | 3 ++ src/qmr.jl | 3 ++ src/symmlq.jl | 3 ++ src/tricg.jl | 4 +++ src/trilqr.jl | 4 +++ src/trimr.jl | 4 +++ src/usymlq.jl | 3 ++ src/usymqr.jl | 3 ++ 23 files changed, 109 insertions(+), 34 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index 876129d1b..e5bd9344d 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -91,6 +91,8 @@ function bicgstab! end def_args_bicgstab = (:(A ), :(b::AbstractVector{FC})) +def_optargs_bicgstab = (:(x0::AbstractVector),) + def_kwargs_bicgstab = (:(; c::AbstractVector{FC} = b ), :(; M = I ), :(; N = I ), @@ -107,6 +109,7 @@ def_kwargs_bicgstab = (:(; c::AbstractVector{FC} = b ), def_kwargs_bicgstab = mapreduce(extract_parameters, vcat, def_kwargs_bicgstab) args_bicgstab = (:A, :b) +optargs_bicgstab = (:x0,) kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/bilq.jl b/src/bilq.jl index 58896b841..bc985dec6 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -78,6 +78,8 @@ function bilq! end def_args_bilq = (:(A ), :(b::AbstractVector{FC})) +def_optargs_bilq = (:(x0::AbstractVector),) + def_kwargs_bilq = (:(; c::AbstractVector{FC} = b ), :(; transfer_to_bicg::Bool = true), :(; atol::T = √eps(T) ), @@ -92,6 +94,7 @@ def_kwargs_bilq = (:(; c::AbstractVector{FC} = b ), def_kwargs_bilq = mapreduce(extract_parameters, vcat, def_kwargs_bilq) args_bilq = (:A, :b) +optargs_bilq = (:x0,) kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/bilqr.jl b/src/bilqr.jl index 49dcd25dc..043ccf99c 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -84,6 +84,9 @@ def_args_bilqr = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_bilqr = (:(x0 :: AbstractVector), + :(y0 :: AbstractVector)) + def_kwargs_bilqr = (:(; transfer_to_bicg::Bool = true), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -97,6 +100,7 @@ def_kwargs_bilqr = (:(; transfer_to_bicg::Bool = true), def_kwargs_bilqr = mapreduce(extract_parameters, vcat, def_kwargs_bilqr) args_bilqr = (:A, :b, :c) +optargs_bilqr = (:x0, :y0) kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cg.jl b/src/cg.jl index 97e34fb61..4536bf6de 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -83,6 +83,8 @@ function cg! end def_args_cg = (:(A ), :(b::AbstractVector{FC})) +def_optargs_cg = (:(x0::AbstractVector),) + def_kwargs_cg = (:(; M = I ), :(; ldiv::Bool = false ), :(; radius::T = zero(T) ), @@ -99,6 +101,7 @@ def_kwargs_cg = (:(; M = I ), def_kwargs_cg = mapreduce(extract_parameters, vcat, def_kwargs_cg) args_cg = (:A, :b) +optargs_cg = (:x0,) kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index 4f6e25084..f63477ee0 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -80,6 +80,8 @@ function cg_lanczos! end def_args_cg_lanczos = (:(A ), :(b::AbstractVector{FC})) +def_optargs_cg_lanczos = (:(x0::AbstractVector),) + def_kwargs_cg_lanczos = (:(; M = I ), :(; ldiv::Bool = false ), :(; check_curvature::Bool = false), @@ -95,6 +97,7 @@ def_kwargs_cg_lanczos = (:(; M = I ), def_kwargs_cg_lanczos = mapreduce(extract_parameters, vcat, def_kwargs_cg_lanczos) args_cg_lanczos = (:A, :b) +optargs_cg_lanczos = (:x0,) kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cgs.jl b/src/cgs.jl index c80016ea3..82a6b7a89 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -92,6 +92,8 @@ function cgs! end def_args_cgs = (:(A ), :(b::AbstractVector{FC})) +def_optargs_cgs = (:(x0::AbstractVector),) + def_kwargs_cgs = (:(; c::AbstractVector{FC} = b ), :(; M = I ), :(; N = I ), @@ -108,6 +110,7 @@ def_kwargs_cgs = (:(; c::AbstractVector{FC} = b ), def_kwargs_cgs = mapreduce(extract_parameters, vcat, def_kwargs_cgs) args_cgs = (:A, :b) +optargs_cgs = (:x0,) kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/cr.jl b/src/cr.jl index 3b1005ddd..683d38173 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -89,6 +89,8 @@ function cr! end def_args_cr = (:(A ), :(b::AbstractVector{FC})) +def_optargs_cr = (:(x0::AbstractVector),) + def_kwargs_cr = (:(; M = I ), :(; ldiv::Bool = false ), :(; radius::T = zero(T) ), @@ -106,6 +108,7 @@ def_kwargs_cr = (:(; M = I ), def_kwargs_cr = mapreduce(extract_parameters, vcat, def_kwargs_cr) args_cr = (:A, :b) +optargs_cr = (:x0,) kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/diom.jl b/src/diom.jl index 18fe67b06..b54cf6850 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -87,6 +87,8 @@ function diom! end def_args_diom = (:(A ), :(b::AbstractVector{FC})) +def_optargs_diom = (:(x0::AbstractVector),) + def_kwargs_diom = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -103,6 +105,7 @@ def_kwargs_diom = (:(; M = I ), def_kwargs_diom = mapreduce(extract_parameters, vcat, def_kwargs_diom) args_diom = (:A, :b) +optargs_diom = (:x0,) kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 12d9f3449..4d29cd69f 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -87,6 +87,8 @@ function dqgmres! end def_args_dqgmres = (:(A ), :(b::AbstractVector{FC})) +def_optargs_dqgmres = (:(x0::AbstractVector),) + def_kwargs_dqgmres = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -103,6 +105,7 @@ def_kwargs_dqgmres = (:(; M = I ), def_kwargs_dqgmres = mapreduce(extract_parameters, vcat, def_kwargs_dqgmres) args_dqgmres = (:A, :b) +optargs_dqgmres = (:x0,) kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/fgmres.jl b/src/fgmres.jl index 4c931a318..cdf7a718f 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -89,6 +89,8 @@ function fgmres! end def_args_fgmres = (:(A ), :(b::AbstractVector{FC})) +def_optargs_fgmres = (:(x0::AbstractVector),) + def_kwargs_fgmres = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -106,6 +108,7 @@ def_kwargs_fgmres = (:(; M = I ), def_kwargs_fgmres = mapreduce(extract_parameters, vcat, def_kwargs_fgmres) args_fgmres = (:A, :b) +optargs_fgmres = (:x0,) kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/fom.jl b/src/fom.jl index 800c6f8b2..3878c607c 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -82,6 +82,8 @@ function fom! end def_args_fom = (:(A ), :(b::AbstractVector{FC})) +def_optargs_fom = (:(x0::AbstractVector),) + def_kwargs_fom = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -99,6 +101,7 @@ def_kwargs_fom = (:(; M = I ), def_kwargs_fom = mapreduce(extract_parameters, vcat, def_kwargs_fom) args_fom = (:A, :b) +optargs_fom = (:x0,) kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/gmres.jl b/src/gmres.jl index 01590d3e1..c92d13320 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -82,6 +82,8 @@ function gmres! end def_args_gmres = (:(A ), :(b::AbstractVector{FC})) +def_optargs_gmres = (:(x0::AbstractVector),) + def_kwargs_gmres = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -99,6 +101,7 @@ def_kwargs_gmres = (:(; M = I ), def_kwargs_gmres = mapreduce(extract_parameters, vcat, def_kwargs_gmres) args_gmres = (:A, :b) +optargs_gmres = (:x0,) kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/gpmr.jl b/src/gpmr.jl index 04d8f5976..a01698049 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -120,6 +120,9 @@ def_args_gpmr = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_gpmr = (:(x0 :: AbstractVector), + :(y0 :: AbstractVector)) + def_kwargs_gpmr = (:(; C = I ), :(; D = I ), :(; E = I ), @@ -141,6 +144,7 @@ def_kwargs_gpmr = (:(; C = I ), def_kwargs_gpmr = mapreduce(extract_parameters, vcat, def_kwargs_gpmr) args_gpmr = (:A, :B, :b, :c) +optargs_gpmr = (:x0, :y0) kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/krylov_solve.jl b/src/krylov_solve.jl index be4b6b597..4c23b9304 100644 --- a/src/krylov_solve.jl +++ b/src/krylov_solve.jl @@ -5,42 +5,46 @@ Use the in-place Krylov method associated to `solver`. """ function solve! end -for (KS, fun, args, def_args, kwargs, def_kwargs) in [ - (:LsmrSolver , :lsmr! , args_lsmr , def_args_lsmr , kwargs_lsmr , def_kwargs_lsmr ) - (:CgsSolver , :cgs! , args_cgs , def_args_cgs , kwargs_cgs , def_kwargs_cgs ) - (:UsymlqSolver , :usymlq! , args_usymlq , def_args_usymlq , kwargs_usymlq , def_kwargs_usymlq ) - (:LnlqSolver , :lnlq! , args_lnlq , def_args_lnlq , kwargs_lnlq , def_kwargs_lnlq ) - (:BicgstabSolver , :bicgstab! , args_bicgstab , def_args_bicgstab , kwargs_bicgstab , def_kwargs_bicgstab ) - (:CrlsSolver , :crls! , args_crls , def_args_crls , kwargs_crls , def_kwargs_crls ) - (:LsqrSolver , :lsqr! , args_lsqr , def_args_lsqr , kwargs_lsqr , def_kwargs_lsqr ) - (:MinresSolver , :minres! , args_minres , def_args_minres , kwargs_minres , def_kwargs_minres ) - (:CgneSolver , :cgne! , args_cgne , def_args_cgne , kwargs_cgne , def_kwargs_cgne ) - (:DqgmresSolver , :dqgmres! , args_dqgmres , def_args_dqgmres , kwargs_dqgmres , def_kwargs_dqgmres ) - (:SymmlqSolver , :symmlq! , args_symmlq , def_args_symmlq , kwargs_symmlq , def_kwargs_symmlq ) - (:TrimrSolver , :trimr! , args_trimr , def_args_trimr , kwargs_trimr , def_kwargs_trimr ) - (:UsymqrSolver , :usymqr! , args_usymqr , def_args_usymqr , kwargs_usymqr , def_kwargs_usymqr ) - (:BilqrSolver , :bilqr! , args_bilqr , def_args_bilqr , kwargs_bilqr , def_kwargs_bilqr ) - (:CrSolver , :cr! , args_cr , def_args_cr , kwargs_cr , def_kwargs_cr ) - (:CraigmrSolver , :craigmr! , args_craigmr , def_args_craigmr , kwargs_craigmr , def_kwargs_craigmr ) - (:TricgSolver , :tricg! , args_tricg , def_args_tricg , kwargs_tricg , def_kwargs_tricg ) - (:CraigSolver , :craig! , args_craig , def_args_craig , kwargs_craig , def_kwargs_craig ) - (:DiomSolver , :diom! , args_diom , def_args_diom , kwargs_diom , def_kwargs_diom ) - (:LslqSolver , :lslq! , args_lslq , def_args_lslq , kwargs_lslq , def_kwargs_lslq ) - (:TrilqrSolver , :trilqr! , args_trilqr , def_args_trilqr , kwargs_trilqr , def_kwargs_trilqr ) - (:CrmrSolver , :crmr! , args_crmr , def_args_crmr , kwargs_crmr , def_kwargs_crmr ) - (:CgSolver , :cg! , args_cg , def_args_cg , kwargs_cg , def_kwargs_cg ) - (:CgLanczosShiftSolver, :cg_lanczos_shift!, args_cg_lanczos_shift, def_args_cg_lanczos_shift, kwargs_cg_lanczos_shift, def_kwargs_cg_lanczos_shift) - (:CglsSolver , :cgls! , args_cgls , def_args_cgls , kwargs_cgls , def_kwargs_cgls ) - (:CgLanczosSolver , :cg_lanczos! , args_cg_lanczos , def_args_cg_lanczos , kwargs_cg_lanczos , def_kwargs_cg_lanczos ) - (:BilqSolver , :bilq! , args_bilq , def_args_bilq , kwargs_bilq , def_kwargs_bilq ) - (:MinresQlpSolver , :minres_qlp! , args_minres_qlp , def_args_minres_qlp , kwargs_minres_qlp , def_kwargs_minres_qlp ) - (:QmrSolver , :qmr! , args_qmr , def_args_qmr , kwargs_qmr , def_kwargs_qmr ) - (:GmresSolver , :gmres! , args_gmres , def_args_gmres , kwargs_gmres , def_kwargs_gmres ) - (:FgmresSolver , :fgmres! , args_fgmres , def_args_fgmres , kwargs_fgmres , def_kwargs_fgmres ) - (:FomSolver , :fom! , args_fom , def_args_fom , kwargs_fom , def_kwargs_fom ) - (:GpmrSolver , :gpmr! , args_gpmr , def_args_gpmr , kwargs_gpmr , def_kwargs_gpmr ) +for (KS, fun, args, def_args, optargs, def_optargs, kwargs, def_kwargs) in [ + (:LsmrSolver , :lsmr! , args_lsmr , def_args_lsmr , () , () , kwargs_lsmr , def_kwargs_lsmr ) + (:CgsSolver , :cgs! , args_cgs , def_args_cgs , optargs_cgs , def_optargs_cgs , kwargs_cgs , def_kwargs_cgs ) + (:UsymlqSolver , :usymlq! , args_usymlq , def_args_usymlq , optargs_usymlq , def_optargs_usymlq , kwargs_usymlq , def_kwargs_usymlq ) + (:LnlqSolver , :lnlq! , args_lnlq , def_args_lnlq , () , () , kwargs_lnlq , def_kwargs_lnlq ) + (:BicgstabSolver , :bicgstab! , args_bicgstab , def_args_bicgstab , optargs_bicgstab , def_optargs_bicgstab , kwargs_bicgstab , def_kwargs_bicgstab ) + (:CrlsSolver , :crls! , args_crls , def_args_crls , () , () , kwargs_crls , def_kwargs_crls ) + (:LsqrSolver , :lsqr! , args_lsqr , def_args_lsqr , () , () , kwargs_lsqr , def_kwargs_lsqr ) + (:MinresSolver , :minres! , args_minres , def_args_minres , optargs_minres , def_optargs_minres , kwargs_minres , def_kwargs_minres ) + (:CgneSolver , :cgne! , args_cgne , def_args_cgne , () , () , kwargs_cgne , def_kwargs_cgne ) + (:DqgmresSolver , :dqgmres! , args_dqgmres , def_args_dqgmres , optargs_dqgmres , def_optargs_dqgmres , kwargs_dqgmres , def_kwargs_dqgmres ) + (:SymmlqSolver , :symmlq! , args_symmlq , def_args_symmlq , optargs_symmlq , def_optargs_symmlq , kwargs_symmlq , def_kwargs_symmlq ) + (:TrimrSolver , :trimr! , args_trimr , def_args_trimr , optargs_trimr , def_optargs_trimr , kwargs_trimr , def_kwargs_trimr ) + (:UsymqrSolver , :usymqr! , args_usymqr , def_args_usymqr , optargs_usymqr , def_optargs_usymqr , kwargs_usymqr , def_kwargs_usymqr ) + (:BilqrSolver , :bilqr! , args_bilqr , def_args_bilqr , optargs_bilqr , def_optargs_bilqr , kwargs_bilqr , def_kwargs_bilqr ) + (:CrSolver , :cr! , args_cr , def_args_cr , optargs_cr , def_optargs_cr , kwargs_cr , def_kwargs_cr ) + (:CraigmrSolver , :craigmr! , args_craigmr , def_args_craigmr , () , () , kwargs_craigmr , def_kwargs_craigmr ) + (:TricgSolver , :tricg! , args_tricg , def_args_tricg , optargs_tricg , def_optargs_tricg , kwargs_tricg , def_kwargs_tricg ) + (:CraigSolver , :craig! , args_craig , def_args_craig , () , () , kwargs_craig , def_kwargs_craig ) + (:DiomSolver , :diom! , args_diom , def_args_diom , optargs_diom , def_optargs_diom , kwargs_diom , def_kwargs_diom ) + (:LslqSolver , :lslq! , args_lslq , def_args_lslq , () , () , kwargs_lslq , def_kwargs_lslq ) + (:TrilqrSolver , :trilqr! , args_trilqr , def_args_trilqr , optargs_trilqr , def_optargs_trilqr , kwargs_trilqr , def_kwargs_trilqr ) + (:CrmrSolver , :crmr! , args_crmr , def_args_crmr , () , () , kwargs_crmr , def_kwargs_crmr ) + (:CgSolver , :cg! , args_cg , def_args_cg , optargs_cg , def_optargs_cg , kwargs_cg , def_kwargs_cg ) + (:CgLanczosShiftSolver, :cg_lanczos_shift!, args_cg_lanczos_shift, def_args_cg_lanczos_shift, () , () , kwargs_cg_lanczos_shift, def_kwargs_cg_lanczos_shift) + (:CglsSolver , :cgls! , args_cgls , def_args_cgls , () , () , kwargs_cgls , def_kwargs_cgls ) + (:CgLanczosSolver , :cg_lanczos! , args_cg_lanczos , def_args_cg_lanczos , optargs_cg_lanczos, def_optargs_cg_lanczos, kwargs_cg_lanczos , def_kwargs_cg_lanczos ) + (:BilqSolver , :bilq! , args_bilq , def_args_bilq , optargs_bilq , def_optargs_bilq , kwargs_bilq , def_kwargs_bilq ) + (:MinresQlpSolver , :minres_qlp! , args_minres_qlp , def_args_minres_qlp , optargs_minres_qlp, def_optargs_minres_qlp, kwargs_minres_qlp , def_kwargs_minres_qlp ) + (:QmrSolver , :qmr! , args_qmr , def_args_qmr , optargs_qmr , def_optargs_qmr , kwargs_qmr , def_kwargs_qmr ) + (:GmresSolver , :gmres! , args_gmres , def_args_gmres , optargs_gmres , def_optargs_gmres , kwargs_gmres , def_kwargs_gmres ) + (:FgmresSolver , :fgmres! , args_fgmres , def_args_fgmres , optargs_fgmres , def_optargs_fgmres , kwargs_fgmres , def_kwargs_fgmres ) + (:FomSolver , :fom! , args_fom , def_args_fom , optargs_fom , def_optargs_fom , kwargs_fom , def_kwargs_fom ) + (:GpmrSolver , :gpmr! , args_gpmr , def_args_gpmr , optargs_gpmr , def_optargs_gpmr , kwargs_gpmr , def_kwargs_gpmr ) ] @eval begin solve!(solver :: $KS{T,FC,S}, $(def_args...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} = $(fun)(solver, $(args...); $(kwargs...)) + + if !isempty($optargs) + solve!(solver :: $KS{T,FC,S}, $(def_args...), $(def_optargs...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} = $(fun)(solver, $(args...), $(optargs...); $(kwargs...)) + end end end diff --git a/src/minres.jl b/src/minres.jl index 2531e8df3..70367b71d 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -104,6 +104,8 @@ function minres! end def_args_minres = (:(A ), :(b::AbstractVector{FC})) +def_optargs_minres = (:(x0::AbstractVector),) + def_kwargs_minres = (:(; M = I ), :(; ldiv::Bool = false ), :(; λ::T = zero(T) ), @@ -121,6 +123,7 @@ def_kwargs_minres = (:(; M = I ), def_kwargs_minres = mapreduce(extract_parameters, vcat, def_kwargs_minres) args_minres = (:A, :b) +optargs_minres = (:x0,) kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index 486895045..e00f9831e 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -87,6 +87,8 @@ function minres_qlp! end def_args_minres_qlp = (:(A ), :(b::AbstractVector{FC})) +def_optargs_minres_qlp = (:(x0::AbstractVector),) + def_kwargs_minres_qlp = (:(; M = I ), :(; ldiv::Bool = false ), :(; λ::T = zero(T) ), @@ -103,6 +105,7 @@ def_kwargs_minres_qlp = (:(; M = I ), def_kwargs_minres_qlp = mapreduce(extract_parameters, vcat, def_kwargs_minres_qlp) args_minres_qlp = (:A, :b) +optargs_minres_qlp = (:x0,) kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/qmr.jl b/src/qmr.jl index 1d77db181..c8fccb145 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -86,6 +86,8 @@ function qmr! end def_args_qmr = (:(A ), :(b::AbstractVector{FC})) +def_optargs_qmr = (:(x0::AbstractVector),) + def_kwargs_qmr = (:(; c::AbstractVector{FC} = b ), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -99,6 +101,7 @@ def_kwargs_qmr = (:(; c::AbstractVector{FC} = b ), def_kwargs_qmr = mapreduce(extract_parameters, vcat, def_kwargs_qmr) args_qmr = (:A, :b) +optargs_qmr = (:x0,) kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/symmlq.jl b/src/symmlq.jl index 9e0543e89..b1b702f6d 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -88,6 +88,8 @@ function symmlq! end def_args_symmlq = (:(A ), :(b::AbstractVector{FC})) +def_optargs_symmlq = (:(x0::AbstractVector),) + def_kwargs_symmlq = (:(; M = I ), :(; ldiv::Bool = false ), :(; transfer_to_cg::Bool = true), @@ -107,6 +109,7 @@ def_kwargs_symmlq = (:(; M = I ), def_kwargs_symmlq = mapreduce(extract_parameters, vcat, def_kwargs_symmlq) args_symmlq = (:A, :b) +optargs_symmlq = (:x0,) kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/tricg.jl b/src/tricg.jl index 0777cb5c0..93bcf4503 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -107,6 +107,9 @@ def_args_tricg = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_tricg = (:(x0::AbstractVector), + :(y0::AbstractVector)) + def_kwargs_tricg = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -127,6 +130,7 @@ def_kwargs_tricg = (:(; M = I ), def_kwargs_tricg = mapreduce(extract_parameters, vcat, def_kwargs_tricg) args_tricg = (:A, :b, :c) +optargs_tricg = (:x0, :y0) kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/trilqr.jl b/src/trilqr.jl index d8c4e0299..ea2991a16 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -83,6 +83,9 @@ def_args_trilqr = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_trilqr = (:(x0::AbstractVector), + :(y0::AbstractVector)) + def_kwargs_trilqr = (:(; transfer_to_usymcg::Bool = true), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -96,6 +99,7 @@ def_kwargs_trilqr = (:(; transfer_to_usymcg::Bool = true), def_kwargs_trilqr = mapreduce(extract_parameters, vcat, def_kwargs_trilqr) args_trilqr = (:A, :b, :c) +optargs_trilqr = (:x0, :y0) kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/trimr.jl b/src/trimr.jl index 23df79a6c..b8edef05b 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -107,6 +107,9 @@ def_args_trimr = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_trimr = (:(x0::AbstractVector), + :(y0::AbstractVector)) + def_kwargs_trimr = (:(; M = I ), :(; N = I ), :(; ldiv::Bool = false ), @@ -128,6 +131,7 @@ def_kwargs_trimr = (:(; M = I ), def_kwargs_trimr = mapreduce(extract_parameters, vcat, def_kwargs_trimr) args_trimr = (:A, :b, :c) +optargs_trimr = (:x0, :y0) kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/usymlq.jl b/src/usymlq.jl index 9785f168e..f434f62fd 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -92,6 +92,8 @@ def_args_usymlq = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_usymlq = (:(x0::AbstractVector),) + def_kwargs_usymlq = (:(; transfer_to_usymcg::Bool = true), :(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), @@ -105,6 +107,7 @@ def_kwargs_usymlq = (:(; transfer_to_usymcg::Bool = true), def_kwargs_usymlq = mapreduce(extract_parameters, vcat, def_kwargs_usymlq) args_usymlq = (:A, :b, :c) +optargs_usymlq = (:x0,) kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin diff --git a/src/usymqr.jl b/src/usymqr.jl index d97aace54..c9cd567dc 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -91,6 +91,8 @@ def_args_usymqr = (:(A ), :(b::AbstractVector{FC}), :(c::AbstractVector{FC})) +def_optargs_usymqr = (:(x0::AbstractVector),) + def_kwargs_usymqr = (:(; atol::T = √eps(T) ), :(; rtol::T = √eps(T) ), :(; itmax::Int = 0 ), @@ -103,6 +105,7 @@ def_kwargs_usymqr = (:(; atol::T = √eps(T) ), def_kwargs_usymqr = mapreduce(extract_parameters, vcat, def_kwargs_usymqr) args_usymqr = (:A, :b, :c) +optargs_usymqr = (:x0,) kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin From b3804c942a40722fb554fe764e447a32cc5478c6 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 28 Jun 2023 19:16:35 -0400 Subject: [PATCH 174/182] Add tests for in-place methods with keyword arguments --- test/test_warm_start.jl | 165 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 153 insertions(+), 12 deletions(-) diff --git a/test/test_warm_start.jl b/test/test_warm_start.jl index 232a5a9cf..c788ed7e8 100644 --- a/test/test_warm_start.jl +++ b/test/test_warm_start.jl @@ -8,41 +8,126 @@ function test_warm_start(FC) nshifts = 5 tol = 1.0e-6 + x, y, stats = bilqr(A, b, c, x0, y0) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + s = c - A' * y + resid = norm(s) / norm(c) + @test(resid ≤ tol) + + solver = BilqrSolver(A, b) + solve!(solver, A, b, c, x0, y0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + s = c - A' * solver.y + resid = norm(s) / norm(c) + @test(resid ≤ tol) + + x, y, stats = trilqr(A, b, c, x0, y0) + r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + s = c - A' * y + resid = norm(s) / norm(c) + @test(resid ≤ tol) + + solver = TrilqrSolver(A, b) + solve!(solver, A, b, c, x0, y0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + s = c - A' * solver.y + resid = norm(s) / norm(c) + @test(resid ≤ tol) + x, y, stats = tricg(A, b, b, x0, y0) r = [b - x - A * y; b - A' * x + y] resid = norm(r) / norm([b; b]) @test(resid ≤ tol) + solver = TricgSolver(A, b) + solve!(solver, A, b, b, x0, y0) + r = [b - solver.x - A * solver.y; b - A' * solver.x + solver.y] + resid = norm(r) / norm([b; b]) + @test(resid ≤ tol) + x, y, stats = trimr(A, b, b, x0, y0) r = [b - x - A * y; b - A' * x + y] resid = norm(r) / norm([b; b]) @test(resid ≤ tol) + solver = TrimrSolver(A, b) + solve!(solver, A, b, b, x0, y0) + r = [b - solver.x - A * solver.y; b - A' * solver.x + solver.y] + resid = norm(r) / norm([b; b]) + @test(resid ≤ tol) + x, y, stats = gpmr(A, A', b, b, x0, y0) r = [b - x - A * y; b - A' * x - y] resid = norm(r) / norm([b; b]) @test(resid ≤ tol) + solver = GpmrSolver(A, b) + solve!(solver, A, A', b, b, x0, y0) + r = [b - solver.x - A * solver.y; b - A' * solver.x - solver.y] + resid = norm(r) / norm([b; b]) + @test(resid ≤ tol) + x, stats = minres_qlp(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = MinresQlpSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = symmlq(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = SymmlqSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = cg(A, b, x0) r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + + solver = CgSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) @test(resid ≤ tol) x, stats = cr(A, b, x0) r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + + solver = CrSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) @test(resid ≤ tol) x, stats = cg_lanczos(A, b, x0) r = b - A * x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + + solver = CgLanczosSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) @test(resid ≤ tol) x, stats = minres(A, b, x0) @@ -50,75 +135,131 @@ function test_warm_start(FC) resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = MinresSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = diom(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = DiomSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = dqgmres(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = DqgmresSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = fom(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) - + + solver = FomSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = gmres(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = GmresSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = fgmres(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = FgmresSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = bicgstab(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = BicgstabSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = cgs(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) + solver = CgsSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) + @test(resid ≤ tol) + x, stats = bilq(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) - x, stats = qmr(A, b, x0) - r = b - A * x + solver = BilqSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x resid = norm(r) / norm(b) @test(resid ≤ tol) - x, stats = usymlq(A, b, c, x0) + x, stats = qmr(A, b, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) - x, stats = usymqr(A, b, c, x0) - r = b - A * x + solver = QmrSolver(A, b) + solve!(solver, A, b, x0) + r = b - A * solver.x resid = norm(r) / norm(b) @test(resid ≤ tol) - x, y, stats = bilqr(A, b, c, x0, y0) + x, stats = usymlq(A, b, c, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) - s = c - A' * y - resid = norm(s) / norm(c) + + solver = UsymlqSolver(A, b) + solve!(solver, A, b, c, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) @test(resid ≤ tol) - x, y, stats = trilqr(A, b, c, x0, y0) + x, stats = usymqr(A, b, c, x0) r = b - A * x resid = norm(r) / norm(b) @test(resid ≤ tol) - s = c - A' * y - resid = norm(s) / norm(c) + + solver = UsymqrSolver(A, b) + solve!(solver, A, b, c, x0) + r = b - A * solver.x + resid = norm(r) / norm(b) @test(resid ≤ tol) end From f780b59e6124a3c580c51f335af1410278a6487f Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Jul 2023 18:07:09 -0400 Subject: [PATCH 175/182] Fix tests with Apple GPUs --- test/gpu/gpu.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/gpu/gpu.jl b/test/gpu/gpu.jl index 09036ecac..65e123be1 100644 --- a/test/gpu/gpu.jl +++ b/test/gpu/gpu.jl @@ -47,6 +47,6 @@ function test_solver(S, M) end function test_conversion(S, M) - @test Krylov.vector_to_matrix(S) == M - @test Krylov.matrix_to_vector(M) == S + @test Krylov.vector_to_matrix(S) <: M + @test Krylov.matrix_to_vector(M) <: S end From 69e446fc13377668bdbaa10a65a468ed8a7b66a4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Jul 2023 08:11:16 -0400 Subject: [PATCH 176/182] Use metaprogramming to reduce the code --- src/bicgstab.jl | 22 ++++++---------------- src/bilq.jl | 22 ++++++---------------- src/bilqr.jl | 22 ++++++---------------- src/cg.jl | 22 ++++++---------------- src/cg_lanczos.jl | 22 ++++++---------------- src/cg_lanczos_shift.jl | 6 +++--- src/cgls.jl | 6 +++--- src/cgne.jl | 6 +++--- src/cgs.jl | 22 ++++++---------------- src/cr.jl | 22 ++++++---------------- src/craig.jl | 6 +++--- src/craigmr.jl | 6 +++--- src/crls.jl | 6 +++--- src/crmr.jl | 6 +++--- src/diom.jl | 22 ++++++---------------- src/dqgmres.jl | 22 ++++++---------------- src/fgmres.jl | 22 ++++++---------------- src/fom.jl | 22 ++++++---------------- src/gmres.jl | 22 ++++++---------------- src/gpmr.jl | 22 ++++++---------------- src/krylov_solve.jl | 10 ++++++++++ src/lnlq.jl | 6 +++--- src/lslq.jl | 6 +++--- src/lsmr.jl | 6 +++--- src/lsqr.jl | 6 +++--- src/minres.jl | 22 ++++++---------------- src/minres_qlp.jl | 22 ++++++---------------- src/qmr.jl | 22 ++++++---------------- src/symmlq.jl | 22 ++++++---------------- src/tricg.jl | 22 ++++++---------------- src/trilqr.jl | 22 ++++++---------------- src/trimr.jl | 22 ++++++---------------- src/usymlq.jl | 22 ++++++---------------- src/usymqr.jl | 22 ++++++---------------- 34 files changed, 175 insertions(+), 385 deletions(-) diff --git a/src/bicgstab.jl b/src/bicgstab.jl index e5bd9344d..16a3ceae9 100644 --- a/src/bicgstab.jl +++ b/src/bicgstab.jl @@ -113,38 +113,28 @@ optargs_bicgstab = (:x0,) kwargs_bicgstab = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function bicgstab(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function bicgstab($(def_args_bicgstab...), $(def_optargs_bicgstab...); $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BicgstabSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_bicgstab...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + bicgstab!(solver, $(args_bicgstab...); $(kwargs_bicgstab...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function bicgstab(A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function bicgstab($(def_args_bicgstab...); $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BicgstabSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - bicgstab!(solver, A, b; $(kwargs_bicgstab...)) + bicgstab!(solver, $(args_bicgstab...); $(kwargs_bicgstab...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - bicgstab!(solver, A, b; $(kwargs_bicgstab...)) - solver.stats.timer += elapsed_time - return solver - end - - function bicgstab!(solver :: BicgstabSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function bicgstab!(solver :: BicgstabSolver{T,FC,S}, $(def_args_bicgstab...); $(def_kwargs_bicgstab...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/bilq.jl b/src/bilq.jl index bc985dec6..2e8823e93 100644 --- a/src/bilq.jl +++ b/src/bilq.jl @@ -98,38 +98,28 @@ optargs_bilq = (:x0,) kwargs_bilq = (:c, :transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function bilq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function bilq($(def_args_bilq...), $(def_optargs_bilq...); $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_bilq...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - bilq!(solver, A, b; $(kwargs_bilq...)) + bilq!(solver, $(args_bilq...); $(kwargs_bilq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function bilq(A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function bilq($(def_args_bilq...); $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - bilq!(solver, A, b; $(kwargs_bilq...)) + bilq!(solver, $(args_bilq...); $(kwargs_bilq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - bilq!(solver, A, b; $(kwargs_bilq...)) - solver.stats.timer += elapsed_time - return solver - end - - function bilq!(solver :: BilqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function bilq!(solver :: BilqSolver{T,FC,S}, $(def_args_bilq...); $(def_kwargs_bilq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/bilqr.jl b/src/bilqr.jl index 043ccf99c..486ccceec 100644 --- a/src/bilqr.jl +++ b/src/bilqr.jl @@ -104,38 +104,28 @@ optargs_bilqr = (:x0, :y0) kwargs_bilqr = (:transfer_to_bicg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function bilqr($(def_args_bilqr...), $(def_optargs_bilqr...); $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqrSolver(A, b) - warm_start!(solver, x0, y0) + warm_start!(solver, $(optargs_bilqr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + bilqr!(solver, $(args_bilqr...); $(kwargs_bilqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function bilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function bilqr($(def_args_bilqr...); $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = BilqrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - bilqr!(solver, A, b, c; $(kwargs_bilqr...)) + bilqr!(solver, $(args_bilqr...); $(kwargs_bilqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0, y0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - bilqr!(solver, A, b, c; $(kwargs_bilqr...)) - solver.stats.timer += elapsed_time - return solver - end - - function bilqr!(solver :: BilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function bilqr!(solver :: BilqrSolver{T,FC,S}, $(def_args_bilqr...); $(def_kwargs_bilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cg.jl b/src/cg.jl index 4536bf6de..1345a6232 100644 --- a/src/cg.jl +++ b/src/cg.jl @@ -105,38 +105,28 @@ optargs_cg = (:x0,) kwargs_cg = (:M, :ldiv, :radius, :linesearch, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cg(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cg($(def_args_cg...), $(def_optargs_cg...); $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_cg...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cg!(solver, A, b; $(kwargs_cg...)) + cg!(solver, $(args_cg...); $(kwargs_cg...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cg(A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cg($(def_args_cg...); $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cg!(solver, A, b; $(kwargs_cg...)) + cg!(solver, $(args_cg...); $(kwargs_cg...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - cg!(solver, A, b; $(kwargs_cg...)) - solver.stats.timer += elapsed_time - return solver - end - - function cg!(solver :: CgSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cg!(solver :: CgSolver{T,FC,S}, $(def_args_cg...); $(def_kwargs_cg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cg_lanczos.jl b/src/cg_lanczos.jl index f63477ee0..2c5d72a64 100644 --- a/src/cg_lanczos.jl +++ b/src/cg_lanczos.jl @@ -101,38 +101,28 @@ optargs_cg_lanczos = (:x0,) kwargs_cg_lanczos = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cg_lanczos(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cg_lanczos($(def_args_cg_lanczos...), $(def_optargs_cg_lanczos...); $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgLanczosSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_cg_lanczos...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + cg_lanczos!(solver, $(args_cg_lanczos...); $(kwargs_cg_lanczos...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cg_lanczos(A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cg_lanczos($(def_args_cg_lanczos...); $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgLanczosSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) + cg_lanczos!(solver, $(args_cg_lanczos...); $(kwargs_cg_lanczos...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - cg_lanczos!(solver, A, b; $(kwargs_cg_lanczos...)) - solver.stats.timer += elapsed_time - return solver - end - - function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cg_lanczos!(solver :: CgLanczosSolver{T,FC,S}, $(def_args_cg_lanczos...); $(def_kwargs_cg_lanczos...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cg_lanczos_shift.jl b/src/cg_lanczos_shift.jl index 94799d69c..b523e5cc3 100644 --- a/src/cg_lanczos_shift.jl +++ b/src/cg_lanczos_shift.jl @@ -94,18 +94,18 @@ args_cg_lanczos_shift = (:A, :b, :shifts) kwargs_cg_lanczos_shift = (:M, :ldiv, :check_curvature, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cg_lanczos_shift(A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cg_lanczos_shift($(def_args_cg_lanczos_shift...); $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() nshifts = length(shifts) solver = CgLanczosShiftSolver(A, b, nshifts) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cg_lanczos_shift!(solver, A, b, shifts; $(kwargs_cg_lanczos_shift...)) + cg_lanczos_shift!(solver, $(args_cg_lanczos_shift...); $(kwargs_cg_lanczos_shift...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, A, b :: AbstractVector{FC}, shifts :: AbstractVector{T}; $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cg_lanczos_shift!(solver :: CgLanczosShiftSolver{T,FC,S}, $(def_args_cg_lanczos_shift...); $(def_kwargs_cg_lanczos_shift...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cgls.jl b/src/cgls.jl index 9f244ed9f..e36d5acbd 100644 --- a/src/cgls.jl +++ b/src/cgls.jl @@ -117,17 +117,17 @@ args_cgls = (:A, :b) kwargs_cgls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cgls(A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cgls($(def_args_cgls...); $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CglsSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cgls!(solver, A, b; $(kwargs_cgls...)) + cgls!(solver, $(args_cgls...); $(kwargs_cgls...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cgls!(solver :: CglsSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cgls!(solver :: CglsSolver{T,FC,S}, $(def_args_cgls...); $(def_kwargs_cgls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cgne.jl b/src/cgne.jl index 6bd9ccd2e..8a4e6dddb 100644 --- a/src/cgne.jl +++ b/src/cgne.jl @@ -122,17 +122,17 @@ args_cgne = (:A, :b) kwargs_cgne = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cgne(A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cgne($(def_args_cgne...); $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgneSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cgne!(solver, A, b; $(kwargs_cgne...)) + cgne!(solver, $(args_cgne...); $(kwargs_cgne...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cgne!(solver :: CgneSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cgne!(solver :: CgneSolver{T,FC,S}, $(def_args_cgne...); $(def_kwargs_cgne...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cgs.jl b/src/cgs.jl index 82a6b7a89..e95e74d17 100644 --- a/src/cgs.jl +++ b/src/cgs.jl @@ -114,38 +114,28 @@ optargs_cgs = (:x0,) kwargs_cgs = (:c, :M, :N, :ldiv, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cgs(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cgs($(def_args_cgs...), $(def_optargs_cgs...); $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgsSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_cgs...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cgs!(solver, A, b; $(kwargs_cgs...)) + cgs!(solver, $(args_cgs...); $(kwargs_cgs...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cgs(A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cgs($(def_args_cgs...); $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CgsSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cgs!(solver, A, b; $(kwargs_cgs...)) + cgs!(solver, $(args_cgs...); $(kwargs_cgs...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - cgs!(solver, A, b; $(kwargs_cgs...)) - solver.stats.timer += elapsed_time - return solver - end - - function cgs!(solver :: CgsSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cgs!(solver :: CgsSolver{T,FC,S}, $(def_args_cgs...); $(def_kwargs_cgs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/cr.jl b/src/cr.jl index 683d38173..96194f459 100644 --- a/src/cr.jl +++ b/src/cr.jl @@ -112,38 +112,28 @@ optargs_cr = (:x0,) kwargs_cr = (:M, :ldiv, :radius, :linesearch, :γ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function cr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cr($(def_args_cr...), $(def_optargs_cr...); $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_cr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cr!(solver, A, b; $(kwargs_cr...)) + cr!(solver, $(args_cr...); $(kwargs_cr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cr(A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function cr($(def_args_cr...); $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - cr!(solver, A, b; $(kwargs_cr...)) + cr!(solver, $(args_cr...); $(kwargs_cr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - cr!(solver, A, b; $(kwargs_cr...)) - solver.stats.timer += elapsed_time - return solver - end - - function cr!(solver :: CrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function cr!(solver :: CrSolver{T,FC,S}, $(def_args_cr...); $(def_kwargs_cr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/craig.jl b/src/craig.jl index 05b220757..46e8f93e5 100644 --- a/src/craig.jl +++ b/src/craig.jl @@ -160,17 +160,17 @@ args_craig = (:A, :b) kwargs_craig = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function craig(A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function craig($(def_args_craig...); $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CraigSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - craig!(solver, A, b; $(kwargs_craig...)) + craig!(solver, $(args_craig...); $(kwargs_craig...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function craig!(solver :: CraigSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function craig!(solver :: CraigSolver{T,FC,S}, $(def_args_craig...); $(def_kwargs_craig...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/craigmr.jl b/src/craigmr.jl index c0cba66c4..5f05aa2ae 100644 --- a/src/craigmr.jl +++ b/src/craigmr.jl @@ -149,17 +149,17 @@ args_craigmr = (:A, :b) kwargs_craigmr = (:M, :N, :ldiv, :sqd, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function craigmr(A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function craigmr($(def_args_craigmr...); $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CraigmrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - craigmr!(solver, A, b; $(kwargs_craigmr...)) + craigmr!(solver, $(args_craigmr...); $(kwargs_craigmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function craigmr!(solver :: CraigmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function craigmr!(solver :: CraigmrSolver{T,FC,S}, $(def_args_craigmr...); $(def_kwargs_craigmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/crls.jl b/src/crls.jl index 632cb3f2e..bf43fa79b 100644 --- a/src/crls.jl +++ b/src/crls.jl @@ -108,17 +108,17 @@ args_crls = (:A, :b) kwargs_crls = (:M, :ldiv, :radius, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function crls(A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function crls($(def_args_crls...); $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrlsSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - crls!(solver, A, b; $(kwargs_crls...)) + crls!(solver, $(args_crls...); $(kwargs_crls...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function crls!(solver :: CrlsSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function crls!(solver :: CrlsSolver{T,FC,S}, $(def_args_crls...); $(def_kwargs_crls...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/crmr.jl b/src/crmr.jl index 36538c88b..db333856c 100644 --- a/src/crmr.jl +++ b/src/crmr.jl @@ -120,17 +120,17 @@ args_crmr = (:A, :b) kwargs_crmr = (:N, :ldiv, :λ, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function crmr(A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function crmr($(def_args_crmr...); $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = CrmrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - crmr!(solver, A, b; $(kwargs_crmr...)) + crmr!(solver, $(args_crmr...); $(kwargs_crmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function crmr!(solver :: CrmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function crmr!(solver :: CrmrSolver{T,FC,S}, $(def_args_crmr...); $(def_kwargs_crmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/diom.jl b/src/diom.jl index b54cf6850..72ce462f6 100644 --- a/src/diom.jl +++ b/src/diom.jl @@ -109,38 +109,28 @@ optargs_diom = (:x0,) kwargs_diom = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function diom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function diom($(def_args_diom...), $(def_optargs_diom...); memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DiomSolver(A, b, memory) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_diom...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - diom!(solver, A, b; $(kwargs_diom...)) + diom!(solver, $(args_diom...); $(kwargs_diom...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function diom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function diom($(def_args_diom...); memory :: Int=20, $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DiomSolver(A, b, memory) elapsed_time = ktimer(start_time) timemax -= elapsed_time - diom!(solver, A, b; $(kwargs_diom...)) + diom!(solver, $(args_diom...); $(kwargs_diom...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - diom!(solver, A, b; $(kwargs_diom...)) - solver.stats.timer += elapsed_time - return solver - end - - function diom!(solver :: DiomSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function diom!(solver :: DiomSolver{T,FC,S}, $(def_args_diom...); $(def_kwargs_diom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/dqgmres.jl b/src/dqgmres.jl index 4d29cd69f..4c1e52b37 100644 --- a/src/dqgmres.jl +++ b/src/dqgmres.jl @@ -109,38 +109,28 @@ optargs_dqgmres = (:x0,) kwargs_dqgmres = (:M, :N, :ldiv, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function dqgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function dqgmres($(def_args_dqgmres...), $(def_optargs_dqgmres...); memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DqgmresSolver(A, b, memory) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_dqgmres...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + dqgmres!(solver, $(args_dqgmres...); $(kwargs_dqgmres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function dqgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function dqgmres($(def_args_dqgmres...); memory :: Int=20, $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = DqgmresSolver(A, b, memory) elapsed_time = ktimer(start_time) timemax -= elapsed_time - dqgmres!(solver, A, b; $(kwargs_dqgmres...)) + dqgmres!(solver, $(args_dqgmres...); $(kwargs_dqgmres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - dqgmres!(solver, A, b; $(kwargs_dqgmres...)) - solver.stats.timer += elapsed_time - return solver - end - - function dqgmres!(solver :: DqgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function dqgmres!(solver :: DqgmresSolver{T,FC,S}, $(def_args_dqgmres...); $(def_kwargs_dqgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/fgmres.jl b/src/fgmres.jl index cdf7a718f..1a68aac6c 100644 --- a/src/fgmres.jl +++ b/src/fgmres.jl @@ -112,38 +112,28 @@ optargs_fgmres = (:x0,) kwargs_fgmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function fgmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function fgmres($(def_args_fgmres...), $(def_optargs_fgmres...); memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FgmresSolver(A, b, memory) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_fgmres...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - fgmres!(solver, A, b; $(kwargs_fgmres...)) + fgmres!(solver, $(args_fgmres...); $(kwargs_fgmres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function fgmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function fgmres($(def_args_fgmres...); memory :: Int=20, $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FgmresSolver(A, b, memory) elapsed_time = ktimer(start_time) timemax -= elapsed_time - fgmres!(solver, A, b; $(kwargs_fgmres...)) + fgmres!(solver, $(args_fgmres...); $(kwargs_fgmres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - fgmres!(solver, A, b; $(kwargs_fgmres...)) - solver.stats.timer += elapsed_time - return solver - end - - function fgmres!(solver :: FgmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function fgmres!(solver :: FgmresSolver{T,FC,S}, $(def_args_fgmres...); $(def_kwargs_fgmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/fom.jl b/src/fom.jl index 3878c607c..351fb246f 100644 --- a/src/fom.jl +++ b/src/fom.jl @@ -105,38 +105,28 @@ optargs_fom = (:x0,) kwargs_fom = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function fom(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function fom($(def_args_fom...), $(def_optargs_fom...); memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FomSolver(A, b, memory) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_fom...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - fom!(solver, A, b; $(kwargs_fom...)) + fom!(solver, $(args_fom...); $(kwargs_fom...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function fom(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function fom($(def_args_fom...); memory :: Int=20, $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = FomSolver(A, b, memory) elapsed_time = ktimer(start_time) timemax -= elapsed_time - fom!(solver, A, b; $(kwargs_fom...)) + fom!(solver, $(args_fom...); $(kwargs_fom...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - fom!(solver, A, b; $(kwargs_fom...)) - solver.stats.timer += elapsed_time - return solver - end - - function fom!(solver :: FomSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function fom!(solver :: FomSolver{T,FC,S}, $(def_args_fom...); $(def_kwargs_fom...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/gmres.jl b/src/gmres.jl index c92d13320..7ee6e2341 100644 --- a/src/gmres.jl +++ b/src/gmres.jl @@ -105,38 +105,28 @@ optargs_gmres = (:x0,) kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function gmres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function gmres($(def_args_gmres...), $(def_optargs_gmres...); memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GmresSolver(A, b, memory) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_gmres...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - gmres!(solver, A, b; $(kwargs_gmres...)) + gmres!(solver, $(args_gmres...); $(kwargs_gmres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function gmres(A, b :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function gmres($(def_args_gmres...); memory :: Int=20, $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GmresSolver(A, b, memory) elapsed_time = ktimer(start_time) timemax -= elapsed_time - gmres!(solver, A, b; $(kwargs_gmres...)) + gmres!(solver, $(args_gmres...); $(kwargs_gmres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - gmres!(solver, A, b; $(kwargs_gmres...)) - solver.stats.timer += elapsed_time - return solver - end - - function gmres!(solver :: GmresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function gmres!(solver :: GmresSolver{T,FC,S}, $(def_args_gmres...); $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/gpmr.jl b/src/gpmr.jl index a01698049..1049c3b50 100644 --- a/src/gpmr.jl +++ b/src/gpmr.jl @@ -148,38 +148,28 @@ optargs_gpmr = (:x0, :y0) kwargs_gpmr = (:C, :D, :E, :F, :ldiv, :gsp, :λ, :μ, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function gpmr($(def_args_gpmr...), $(def_optargs_gpmr...); memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GpmrSolver(A, b, memory) - warm_start!(solver, x0, y0) + warm_start!(solver, $(optargs_gpmr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + gpmr!(solver, $(args_gpmr...); $(kwargs_gpmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function gpmr(A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function gpmr($(def_args_gpmr...); memory :: Int=20, $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = GpmrSolver(A, b, memory) elapsed_time = ktimer(start_time) timemax -= elapsed_time - gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) + gpmr!(solver, $(args_gpmr...); $(kwargs_gpmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0, y0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - gpmr!(solver, A, B, b, c; $(kwargs_gpmr...)) - solver.stats.timer += elapsed_time - return solver - end - - function gpmr!(solver :: GpmrSolver{T,FC,S}, A, B, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function gpmr!(solver :: GpmrSolver{T,FC,S}, $(def_args_gpmr...); $(def_kwargs_gpmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/krylov_solve.jl b/src/krylov_solve.jl index 4c23b9304..30a463dfa 100644 --- a/src/krylov_solve.jl +++ b/src/krylov_solve.jl @@ -44,6 +44,16 @@ for (KS, fun, args, def_args, optargs, def_optargs, kwargs, def_kwargs) in [ solve!(solver :: $KS{T,FC,S}, $(def_args...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} = $(fun)(solver, $(args...); $(kwargs...)) if !isempty($optargs) + function $(fun)(solver :: $KS{T,FC,S}, $(def_args...), $(def_optargs...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + start_time = time_ns() + warm_start!(solver, $(optargs...)) + elapsed_time = ktimer(start_time) + timemax -= elapsed_time + $(fun)(solver, $(args...); $(kwargs...)) + solver.stats.timer += elapsed_time + return solver + end + solve!(solver :: $KS{T,FC,S}, $(def_args...), $(def_optargs...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} = $(fun)(solver, $(args...), $(optargs...); $(kwargs...)) end end diff --git a/src/lnlq.jl b/src/lnlq.jl index 1dae08e14..f59f5daf4 100644 --- a/src/lnlq.jl +++ b/src/lnlq.jl @@ -156,17 +156,17 @@ args_lnlq = (:A, :b) kwargs_lnlq = (:M, :N, :ldiv, :transfer_to_craig, :sqd, :λ, :σ, :utolx, :utoly, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function lnlq(A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function lnlq($(def_args_lnlq...); $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LnlqSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - lnlq!(solver, A, b; $(kwargs_lnlq...)) + lnlq!(solver, $(args_lnlq...); $(kwargs_lnlq...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function lnlq!(solver :: LnlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function lnlq!(solver :: LnlqSolver{T,FC,S}, $(def_args_lnlq...); $(def_kwargs_lnlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/lslq.jl b/src/lslq.jl index ebf6b1146..3a549207e 100644 --- a/src/lslq.jl +++ b/src/lslq.jl @@ -182,17 +182,17 @@ args_lslq = (:A, :b) kwargs_lslq = (:M, :N, :ldiv, :transfer_to_lsqr, :sqd, :λ, :σ, :etol, :utol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function lslq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function lslq($(def_args_lslq...); window :: Int=5, $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LslqSolver(A, b; window) elapsed_time = ktimer(start_time) timemax -= elapsed_time - lslq!(solver, A, b; $(kwargs_lslq...)) + lslq!(solver, $(args_lslq...); $(kwargs_lslq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function lslq!(solver :: LslqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function lslq!(solver :: LslqSolver{T,FC,S}, $(def_args_lslq...); $(def_kwargs_lslq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/lsmr.jl b/src/lsmr.jl index b29326daf..085d941db 100644 --- a/src/lsmr.jl +++ b/src/lsmr.jl @@ -159,17 +159,17 @@ args_lsmr = (:A, :b) kwargs_lsmr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function lsmr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function lsmr($(def_args_lsmr...); window :: Int=5, $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LsmrSolver(A, b; window) elapsed_time = ktimer(start_time) timemax -= elapsed_time - lsmr!(solver, A, b; $(kwargs_lsmr...)) + lsmr!(solver, $(args_lsmr...); $(kwargs_lsmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function lsmr!(solver :: LsmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function lsmr!(solver :: LsmrSolver{T,FC,S}, $(def_args_lsmr...); $(def_kwargs_lsmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/lsqr.jl b/src/lsqr.jl index 9fd982419..fe7acc37c 100644 --- a/src/lsqr.jl +++ b/src/lsqr.jl @@ -155,17 +155,17 @@ args_lsqr = (:A, :b) kwargs_lsqr = (:M, :N, :ldiv, :sqd, :λ, :radius, :etol, :axtol, :btol, :conlim, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function lsqr(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function lsqr($(def_args_lsqr...); window :: Int=5, $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = LsqrSolver(A, b; window) elapsed_time = ktimer(start_time) timemax -= elapsed_time - lsqr!(solver, A, b; $(kwargs_lsqr...)) + lsqr!(solver, $(args_lsqr...); $(kwargs_lsqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function lsqr!(solver :: LsqrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function lsqr!(solver :: LsqrSolver{T,FC,S}, $(def_args_lsqr...); $(def_kwargs_lsqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/minres.jl b/src/minres.jl index 70367b71d..8e6659472 100644 --- a/src/minres.jl +++ b/src/minres.jl @@ -127,38 +127,28 @@ optargs_minres = (:x0,) kwargs_minres = (:M, :ldiv, :λ, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function minres(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function minres($(def_args_minres...), $(def_optargs_minres...); window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresSolver(A, b; window) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_minres...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - minres!(solver, A, b; $(kwargs_minres...)) + minres!(solver, $(args_minres...); $(kwargs_minres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function minres(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function minres($(def_args_minres...); window :: Int=5, $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresSolver(A, b; window) elapsed_time = ktimer(start_time) timemax -= elapsed_time - minres!(solver, A, b; $(kwargs_minres...)) + minres!(solver, $(args_minres...); $(kwargs_minres...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - minres!(solver, A, b; $(kwargs_minres...)) - solver.stats.timer += elapsed_time - return solver - end - - function minres!(solver :: MinresSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function minres!(solver :: MinresSolver{T,FC,S}, $(def_args_minres...); $(def_kwargs_minres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/minres_qlp.jl b/src/minres_qlp.jl index e00f9831e..5bc3399eb 100644 --- a/src/minres_qlp.jl +++ b/src/minres_qlp.jl @@ -109,38 +109,28 @@ optargs_minres_qlp = (:x0,) kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function minres_qlp(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function minres_qlp($(def_args_minres_qlp...), $(def_optargs_minres_qlp...); $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresQlpSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_minres_qlp...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + minres_qlp!(solver, $(args_minres_qlp...); $(kwargs_minres_qlp...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function minres_qlp(A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function minres_qlp($(def_args_minres_qlp...); $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = MinresQlpSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) + minres_qlp!(solver, $(args_minres_qlp...); $(kwargs_minres_qlp...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - minres_qlp!(solver, A, b; $(kwargs_minres_qlp...)) - solver.stats.timer += elapsed_time - return solver - end - - function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function minres_qlp!(solver :: MinresQlpSolver{T,FC,S}, $(def_args_minres_qlp...); $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/qmr.jl b/src/qmr.jl index c8fccb145..995392f0c 100644 --- a/src/qmr.jl +++ b/src/qmr.jl @@ -105,38 +105,28 @@ optargs_qmr = (:x0,) kwargs_qmr = (:c, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function qmr(A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function qmr($(def_args_qmr...), $(def_optargs_qmr...); $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = QmrSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_qmr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - qmr!(solver, A, b; $(kwargs_qmr...)) + qmr!(solver, $(args_qmr...); $(kwargs_qmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function qmr(A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function qmr($(def_args_qmr...); $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = QmrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - qmr!(solver, A, b; $(kwargs_qmr...)) + qmr!(solver, $(args_qmr...); $(kwargs_qmr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - qmr!(solver, A, b; $(kwargs_qmr...)) - solver.stats.timer += elapsed_time - return solver - end - - function qmr!(solver :: QmrSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function qmr!(solver :: QmrSolver{T,FC,S}, $(def_args_qmr...); $(def_kwargs_qmr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/symmlq.jl b/src/symmlq.jl index b1b702f6d..604698525 100644 --- a/src/symmlq.jl +++ b/src/symmlq.jl @@ -113,38 +113,28 @@ optargs_symmlq = (:x0,) kwargs_symmlq = (:M, :ldiv, :transfer_to_cg, :λ, :λest, :atol, :rtol, :etol, :conlim, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function symmlq(A, b :: AbstractVector{FC}, x0 :: AbstractVector; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function symmlq($(def_args_symmlq...), $(def_optargs_symmlq...); window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = SymmlqSolver(A, b; window) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_symmlq...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - symmlq!(solver, A, b; $(kwargs_symmlq...)) + symmlq!(solver, $(args_symmlq...); $(kwargs_symmlq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function symmlq(A, b :: AbstractVector{FC}; window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function symmlq($(def_args_symmlq...); window :: Int=5, $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = SymmlqSolver(A, b; window) elapsed_time = ktimer(start_time) timemax -= elapsed_time - symmlq!(solver, A, b; $(kwargs_symmlq...)) + symmlq!(solver, $(args_symmlq...); $(kwargs_symmlq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - symmlq!(solver, A, b; $(kwargs_symmlq...)) - solver.stats.timer += elapsed_time - return solver - end - - function symmlq!(solver :: SymmlqSolver{T,FC,S}, A, b :: AbstractVector{FC}; $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function symmlq!(solver :: SymmlqSolver{T,FC,S}, $(def_args_symmlq...); $(def_kwargs_symmlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/tricg.jl b/src/tricg.jl index 93bcf4503..8250e6dfc 100644 --- a/src/tricg.jl +++ b/src/tricg.jl @@ -134,38 +134,28 @@ optargs_tricg = (:x0, :y0) kwargs_tricg = (:M, :N, :ldiv, :spd, :snd, :flip, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function tricg($(def_args_tricg...), $(def_optargs_tricg...); $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TricgSolver(A, b) - warm_start!(solver, x0, y0) + warm_start!(solver, $(optargs_tricg...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - tricg!(solver, A, b, c; $(kwargs_tricg...)) + tricg!(solver, $(args_tricg...); $(kwargs_tricg...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function tricg(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function tricg($(def_args_tricg...); $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TricgSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - tricg!(solver, A, b, c; $(kwargs_tricg...)) + tricg!(solver, $(args_tricg...); $(kwargs_tricg...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0, y0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - tricg!(solver, A, b, c; $(kwargs_tricg...)) - solver.stats.timer += elapsed_time - return solver - end - - function tricg!(solver :: TricgSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function tricg!(solver :: TricgSolver{T,FC,S}, $(def_args_tricg...); $(def_kwargs_tricg...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/trilqr.jl b/src/trilqr.jl index ea2991a16..2b584c216 100644 --- a/src/trilqr.jl +++ b/src/trilqr.jl @@ -103,38 +103,28 @@ optargs_trilqr = (:x0, :y0) kwargs_trilqr = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function trilqr($(def_args_trilqr...), $(def_optargs_trilqr...); $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrilqrSolver(A, b) - warm_start!(solver, x0, y0) + warm_start!(solver, $(optargs_trilqr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + trilqr!(solver, $(args_trilqr...); $(kwargs_trilqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function trilqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function trilqr($(def_args_trilqr...); $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrilqrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - trilqr!(solver, A, b, c; $(kwargs_trilqr...)) + trilqr!(solver, $(args_trilqr...); $(kwargs_trilqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0, y0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - trilqr!(solver, A, b, c; $(kwargs_trilqr...)) - solver.stats.timer += elapsed_time - return solver - end - - function trilqr!(solver :: TrilqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function trilqr!(solver :: TrilqrSolver{T,FC,S}, $(def_args_trilqr...); $(def_kwargs_trilqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/trimr.jl b/src/trimr.jl index b8edef05b..ae61b785a 100644 --- a/src/trimr.jl +++ b/src/trimr.jl @@ -135,38 +135,28 @@ optargs_trimr = (:x0, :y0) kwargs_trimr = (:M, :N, :ldiv, :spd, :snd, :flip, :sp, :τ, :ν, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function trimr($(def_args_trimr...), $(def_optargs_trimr...); $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrimrSolver(A, b) - warm_start!(solver, x0, y0) + warm_start!(solver, $(optargs_trimr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - trimr!(solver, A, b, c; $(kwargs_trimr...)) + trimr!(solver, $(args_trimr...); $(kwargs_trimr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function trimr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function trimr($(def_args_trimr...); $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = TrimrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - trimr!(solver, A, b, c; $(kwargs_trimr...)) + trimr!(solver, $(args_trimr...); $(kwargs_trimr...)) solver.stats.timer += elapsed_time return (solver.x, solver.y, solver.stats) end - function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector, y0 :: AbstractVector; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0, y0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - trimr!(solver, A, b, c; $(kwargs_trimr...)) - solver.stats.timer += elapsed_time - return solver - end - - function trimr!(solver :: TrimrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function trimr!(solver :: TrimrSolver{T,FC,S}, $(def_args_trimr...); $(def_kwargs_trimr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/usymlq.jl b/src/usymlq.jl index f434f62fd..b80f0a622 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -111,38 +111,28 @@ optargs_usymlq = (:x0,) kwargs_usymlq = (:transfer_to_usymcg, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function usymlq($(def_args_usymlq...), $(def_optargs_usymlq...); $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymlqSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_usymlq...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + usymlq!(solver, $(args_usymlq...); $(kwargs_usymlq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function usymlq(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function usymlq($(def_args_usymlq...); $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymlqSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - usymlq!(solver, A, b, c; $(kwargs_usymlq...)) + usymlq!(solver, $(args_usymlq...); $(kwargs_usymlq...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - usymlq!(solver, A, b, c; $(kwargs_usymlq...)) - solver.stats.timer += elapsed_time - return solver - end - - function usymlq!(solver :: UsymlqSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function usymlq!(solver :: UsymlqSolver{T,FC,S}, $(def_args_usymlq...); $(def_kwargs_usymlq...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() diff --git a/src/usymqr.jl b/src/usymqr.jl index c9cd567dc..0aae23335 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -109,38 +109,28 @@ optargs_usymqr = (:x0,) kwargs_usymqr = (:atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream) @eval begin - function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function usymqr($(def_args_usymqr...), $(def_optargs_usymqr...); $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymqrSolver(A, b) - warm_start!(solver, x0) + warm_start!(solver, $(optargs_usymqr...)) elapsed_time = ktimer(start_time) timemax -= elapsed_time - usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + usymqr!(solver, $(args_usymqr...); $(kwargs_usymqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function usymqr(A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} + function usymqr($(def_args_usymqr...); $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} start_time = time_ns() solver = UsymqrSolver(A, b) elapsed_time = ktimer(start_time) timemax -= elapsed_time - usymqr!(solver, A, b, c; $(kwargs_usymqr...)) + usymqr!(solver, $(args_usymqr...); $(kwargs_usymqr...)) solver.stats.timer += elapsed_time return (solver.x, solver.stats) end - function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}, x0 :: AbstractVector; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} - start_time = time_ns() - warm_start!(solver, x0) - elapsed_time = ktimer(start_time) - timemax -= elapsed_time - usymqr!(solver, A, b, c; $(kwargs_usymqr...)) - solver.stats.timer += elapsed_time - return solver - end - - function usymqr!(solver :: UsymqrSolver{T,FC,S}, A, b :: AbstractVector{FC}, c :: AbstractVector{FC}; $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} + function usymqr!(solver :: UsymqrSolver{T,FC,S}, $(def_args_usymqr...); $(def_kwargs_usymqr...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} # Timer start_time = time_ns() From 896e4edb696a6bc78a534a902988aaa998f3c002 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Jul 2023 18:12:44 -0400 Subject: [PATCH 177/182] Update .builkite/pipeline.yml --- .buildkite/pipeline.yml | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 169198903..720bd9545 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,7 +2,7 @@ steps: - label: "Nvidia GPUs -- CUDA.jl" plugins: - JuliaCI/julia#v1: - version: 1.8 + version: 1.9 agents: queue: "juliagpu" cuda: "*" @@ -19,29 +19,30 @@ steps: include("test/gpu/nvidia.jl")' timeout_in_minutes: 30 - # - label: "AMD GPUs -- AMDGPU.jl" - # plugins: - # - JuliaCI/julia#v1: - # version: 1.8 - # agents: - # queue: "juliagpu" - # rocm: "*" - # rocmgpu: "*" - # env: - # JULIA_AMDGPU_CORE_MUST_LOAD: "1" - # JULIA_AMDGPU_HIP_MUST_LOAD: "1" - # command: | - # julia --color=yes --project -e ' - # using Pkg - # Pkg.add("AMDGPU") - # Pkg.instantiate() - # include("test/gpu/amd.jl")' - # timeout_in_minutes: 30 + - label: "AMD GPUs -- AMDGPU.jl" + plugins: + - JuliaCI/julia#v1: + version: 1.9 + agents: + queue: "juliagpu" + rocm: "*" + rocmgpu: "gfx1031" + env: + JULIA_AMDGPU_CORE_MUST_LOAD: "1" + JULIA_AMDGPU_HIP_MUST_LOAD: "1" + JULIA_AMDGPU_DISABLE_ARTIFACTS: "1" + command: | + julia --color=yes --project -e ' + using Pkg + Pkg.add("AMDGPU") + Pkg.instantiate() + include("test/gpu/amd.jl")' + timeout_in_minutes: 30 - label: "Intel GPUs -- oneAPI.jl" plugins: - JuliaCI/julia#v1: - version: 1.8 + version: 1.9 agents: queue: "juliagpu" intel: "*" @@ -56,7 +57,7 @@ steps: - label: "Apple M1 GPUs -- Metal.jl" plugins: - JuliaCI/julia#v1: - version: 1.8 + version: 1.9 agents: queue: "juliaecosystem" os: "macos" From 4182209168d434a6a2921e933276617a4bfe879b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Jul 2023 18:19:01 -0400 Subject: [PATCH 178/182] Keep tests with AMDGPU.jl commented --- .buildkite/pipeline.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 720bd9545..92ac58c74 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -19,25 +19,25 @@ steps: include("test/gpu/nvidia.jl")' timeout_in_minutes: 30 - - label: "AMD GPUs -- AMDGPU.jl" - plugins: - - JuliaCI/julia#v1: - version: 1.9 - agents: - queue: "juliagpu" - rocm: "*" - rocmgpu: "gfx1031" - env: - JULIA_AMDGPU_CORE_MUST_LOAD: "1" - JULIA_AMDGPU_HIP_MUST_LOAD: "1" - JULIA_AMDGPU_DISABLE_ARTIFACTS: "1" - command: | - julia --color=yes --project -e ' - using Pkg - Pkg.add("AMDGPU") - Pkg.instantiate() - include("test/gpu/amd.jl")' - timeout_in_minutes: 30 + # - label: "AMD GPUs -- AMDGPU.jl" + # plugins: + # - JuliaCI/julia#v1: + # version: 1.9 + # agents: + # queue: "juliagpu" + # rocm: "*" + # rocmgpu: "gfx1031" + # env: + # JULIA_AMDGPU_CORE_MUST_LOAD: "1" + # JULIA_AMDGPU_HIP_MUST_LOAD: "1" + # JULIA_AMDGPU_DISABLE_ARTIFACTS: "1" + # command: | + # julia --color=yes --project -e ' + # using Pkg + # Pkg.add("AMDGPU") + # Pkg.instantiate() + # include("test/gpu/amd.jl")' + # timeout_in_minutes: 30 - label: "Intel GPUs -- oneAPI.jl" plugins: From 88f518b791e3a42f0028b8568839e0925b7bb68b Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Jul 2023 18:44:42 -0400 Subject: [PATCH 179/182] Release 0.9.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index fcc02778a..7be1d9ee7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Krylov" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.9.1" +version = "0.9.2" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From b42f714f1355a3b08968e33cf4447491dee56219 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:42:23 +0200 Subject: [PATCH 180/182] ReshapedArrays support --- src/krylov_utils.jl | 470 ++++++++++++++++++++++---------------------- 1 file changed, 237 insertions(+), 233 deletions(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index 137a97386..fb7f02a64 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -7,7 +7,7 @@ const kstdout = Core.stdout FloatOrComplex{T} Union type of `T` and `Complex{T}` where T is an `AbstractFloat`. """ -const FloatOrComplex{T} = Union{T, Complex{T}} where T <: AbstractFloat +const FloatOrComplex{T} = Union{T,Complex{T}} where {T<:AbstractFloat} """ (c, s, ρ) = sym_givens(a, b) @@ -18,40 +18,40 @@ Given `a` and `b` reals, return `(c, s, ρ)` such that [ c s ] [ a ] = [ ρ ] [ s -c ] [ b ] = [ 0 ]. """ -function sym_givens(a :: T, b :: T) where T <: AbstractFloat - # - # Modeled after the corresponding Matlab function by M. A. Saunders and S.-C. Choi. - # http://www.stanford.edu/group/SOL/dissertations/sou-cheng-choi-thesis.pdf - # D. Orban, Montreal, May 2015. - - if b == 0 - if a == 0 - c = one(T) +function sym_givens(a::T, b::T) where {T<:AbstractFloat} + # + # Modeled after the corresponding Matlab function by M. A. Saunders and S.-C. Choi. + # http://www.stanford.edu/group/SOL/dissertations/sou-cheng-choi-thesis.pdf + # D. Orban, Montreal, May 2015. + + if b == 0 + if a == 0 + c = one(T) + else + c = sign(a) # In Julia, sign(0) = 0. + end + s = zero(T) + ρ = abs(a) + + elseif a == 0 + c = zero(T) + s = sign(b) + ρ = abs(b) + + elseif abs(b) > abs(a) + t = a / b + s = sign(b) / sqrt(one(T) + t * t) + c = s * t + ρ = b / s # Computationally better than ρ = a / c since |c| ≤ |s|. + else - c = sign(a) # In Julia, sign(0) = 0. + t = b / a + c = sign(a) / sqrt(one(T) + t * t) + s = c * t + ρ = a / c # Computationally better than ρ = b / s since |s| ≤ |c| end - s = zero(T) - ρ = abs(a) - - elseif a == 0 - c = zero(T) - s = sign(b) - ρ = abs(b) - - elseif abs(b) > abs(a) - t = a / b - s = sign(b) / sqrt(one(T) + t * t) - c = s * t - ρ = b / s # Computationally better than ρ = a / c since |c| ≤ |s|. - - else - t = b / a - c = sign(a) / sqrt(one(T) + t * t) - s = c * t - ρ = a / c # Computationally better than ρ = b / s since |s| ≤ |c| - end - - return (c, s, ρ) + + return (c, s, ρ) end """ @@ -62,43 +62,43 @@ c real and (s, ρ) complexes such that [ c s ] [ a ] = [ ρ ] [ s̅ -c ] [ b ] = [ 0 ]. """ -function sym_givens(a :: Complex{T}, b :: Complex{T}) where T <: AbstractFloat - # - # Modeled after the corresponding Fortran function by M. A. Saunders and S.-C. Choi. - # A. Montoison, Montreal, March 2020. - - abs_a = abs(a) - abs_b = abs(b) - - if abs_b == 0 - c = one(T) - s = zero(Complex{T}) - ρ = a - - elseif abs_a == 0 - c = zero(T) - s = one(Complex{T}) - ρ = b - - elseif abs_b > abs_a - t = abs_a / abs_b - c = one(T) / sqrt(one(T) + t * t) - s = c * conj((b / abs_b) / (a / abs_a)) - c = c * t - ρ = b / conj(s) - - else - t = abs_b / abs_a - c = one(T) / sqrt(one(T) + t * t) - s = c * t * conj((b / abs_b) / (a / abs_a)) - ρ = a / c - end - - return (c, s, ρ) +function sym_givens(a::Complex{T}, b::Complex{T}) where {T<:AbstractFloat} + # + # Modeled after the corresponding Fortran function by M. A. Saunders and S.-C. Choi. + # A. Montoison, Montreal, March 2020. + + abs_a = abs(a) + abs_b = abs(b) + + if abs_b == 0 + c = one(T) + s = zero(Complex{T}) + ρ = a + + elseif abs_a == 0 + c = zero(T) + s = one(Complex{T}) + ρ = b + + elseif abs_b > abs_a + t = abs_a / abs_b + c = one(T) / sqrt(one(T) + t * t) + s = c * conj((b / abs_b) / (a / abs_a)) + c = c * t + ρ = b / conj(s) + + else + t = abs_b / abs_a + c = one(T) / sqrt(one(T) + t * t) + s = c * t * conj((b / abs_b) / (a / abs_a)) + ρ = a / c + end + + return (c, s, ρ) end -sym_givens(a :: Complex{T}, b :: T) where T <: AbstractFloat = sym_givens(a, Complex{T}(b)) -sym_givens(a :: T, b :: Complex{T}) where T <: AbstractFloat = sym_givens(Complex{T}(a), b) +sym_givens(a::Complex{T}, b::T) where {T<:AbstractFloat} = sym_givens(a, Complex{T}(b)) +sym_givens(a::T, b::Complex{T}) where {T<:AbstractFloat} = sym_givens(Complex{T}(a), b) """ roots = roots_quadratic(q₂, q₁, q₀; nitref) @@ -111,48 +111,48 @@ where q₂, q₁ and q₀ are real. Care is taken to avoid numerical cancellation. Optionally, `nitref` steps of iterative refinement may be performed to improve accuracy. By default, `nitref=1`. """ -function roots_quadratic(q₂ :: T, q₁ :: T, q₀ :: T; - nitref :: Int=1) where T <: AbstractFloat - # Case where q(x) is linear. - if q₂ == zero(T) - if q₁ == zero(T) - q₀ == zero(T) || error("The quadratic `q` doesn't have real roots.") - root = zero(T) +function roots_quadratic(q₂::T, q₁::T, q₀::T; + nitref::Int=1) where {T<:AbstractFloat} + # Case where q(x) is linear. + if q₂ == zero(T) + if q₁ == zero(T) + q₀ == zero(T) || error("The quadratic `q` doesn't have real roots.") + root = zero(T) + else + root = -q₀ / q₁ + end + return (root, root) + end + + # Case where q(x) is indeed quadratic. + rhs = √eps(T) * q₁ * q₁ + if abs(q₀ * q₂) > rhs + ρ = q₁ * q₁ - 4 * q₂ * q₀ + ρ < 0 && return error("The quadratic `q` doesn't have real roots.") + d = -(q₁ + copysign(sqrt(ρ), q₁)) / 2 + root1 = d / q₂ + root2 = q₀ / d else - root = -q₀ / q₁ + # Ill-conditioned quadratic. + root1 = -q₁ / q₂ + root2 = zero(T) + end + + # Perform a few Newton iterations to improve accuracy. + for it = 1:nitref + q = (q₂ * root1 + q₁) * root1 + q₀ + dq = 2 * q₂ * root1 + q₁ + dq == zero(T) && continue + root1 = root1 - q / dq end - return (root, root) - end - - # Case where q(x) is indeed quadratic. - rhs = √eps(T) * q₁ * q₁ - if abs(q₀ * q₂) > rhs - ρ = q₁ * q₁ - 4 * q₂ * q₀ - ρ < 0 && return error("The quadratic `q` doesn't have real roots.") - d = -(q₁ + copysign(sqrt(ρ), q₁)) / 2 - root1 = d / q₂ - root2 = q₀ / d - else - # Ill-conditioned quadratic. - root1 = -q₁ / q₂ - root2 = zero(T) - end - - # Perform a few Newton iterations to improve accuracy. - for it = 1 : nitref - q = (q₂ * root1 + q₁) * root1 + q₀ - dq = 2 * q₂ * root1 + q₁ - dq == zero(T) && continue - root1 = root1 - q / dq - end - - for it = 1 : nitref - q = (q₂ * root2 + q₁) * root2 + q₀ - dq = 2 * q₂ * root2 + q₁ - dq == zero(T) && continue - root2 = root2 - q / dq - end - return (root1, root2) + + for it = 1:nitref + q = (q₂ * root2 + q₁) * root2 + q₀ + dq = 2 * q₂ * root2 + q₁ + dq == zero(T) && continue + root2 = root2 - q / dq + end + return (root1, root2) end """ @@ -164,38 +164,38 @@ Display an array in the form with (ndisp - 1)/2 elements on each side. """ -function vec2str(x :: AbstractVector{T}; ndisp :: Int=7) where T <: Union{AbstractFloat, Missing} - n = length(x) - if n ≤ ndisp - ndisp = n - nside = n - else - nside = max(1, div(ndisp - 1, 2)) - end - s = "[" - i = 1 - while i ≤ nside - if x[i] !== missing - s *= @sprintf("%8.1e ", x[i]) +function vec2str(x::AbstractVector{T}; ndisp::Int=7) where {T<:Union{AbstractFloat,Missing}} + n = length(x) + if n ≤ ndisp + ndisp = n + nside = n else - s *= " ✗✗✗✗ " + nside = max(1, div(ndisp - 1, 2)) end - i += 1 - end - if i ≤ div(n, 2) - s *= "... " - end - i = max(i, n - nside + 1) - while i ≤ n - if x[i] !== missing - s *= @sprintf("%8.1e ", x[i]) - else - s *= " ✗✗✗✗ " + s = "[" + i = 1 + while i ≤ nside + if x[i] !== missing + s *= @sprintf("%8.1e ", x[i]) + else + s *= " ✗✗✗✗ " + end + i += 1 end - i += 1 - end - s *= "]" - return s + if i ≤ div(n, 2) + s *= "... " + end + i = max(i, n - nside + 1) + while i ≤ n + if x[i] !== missing + s *= @sprintf("%8.1e ", x[i]) + else + s *= " ✗✗✗✗ " + end + i += 1 + end + s *= "]" + return s end """ @@ -205,36 +205,40 @@ Return the most relevant storage type `S` based on the type of `v`. """ function ktypeof end -function ktypeof(v::S) where S <: DenseVector - return S +function ktypeof(v::S) where {S<:DenseVector} + return S end -function ktypeof(v::S) where S <: AbstractVector - if S.name.name == :Zeros || S.name.name == :Ones +function ktypeof(v::S) where {S<:AbstractVector} + if S.name.name == :Zeros || S.name.name == :Ones + T = eltype(S) + return Vector{T} # FillArrays + else + return S # BlockArrays, PartitionedArrays, etc... + end +end + +function ktypeof(v::S) where {S<:SparseVector} T = eltype(S) - return Vector{T} # FillArrays - else - return S # BlockArrays, PartitionedArrays, etc... - end + return Vector{T} end -function ktypeof(v::S) where S <: SparseVector - T = eltype(S) - return Vector{T} +function ktypeof(v::S) where {S<:AbstractSparseVector} + return S.types[2] # return `CuVector` for a `CuSparseVector` end -function ktypeof(v::S) where S <: AbstractSparseVector - return S.types[2] # return `CuVector` for a `CuSparseVector` +function ktypeof(v::S) where {S<:SubArray} + vp = v.parent + if isa(vp, DenseMatrix) + M = typeof(vp) + return matrix_to_vector(M) # view of a row or a column of a matrix + else + return ktypeof(vp) # view of a vector + end end -function ktypeof(v::S) where S <: SubArray - vp = v.parent - if isa(vp, DenseMatrix) - M = typeof(vp) - return matrix_to_vector(M) # view of a row or a column of a matrix - else - return ktypeof(vp) # view of a vector - end +function ktypeof(v::S) where {T,S<:Base.ReshapedArray{T}} + return Vector{T} end """ @@ -242,17 +246,17 @@ end Return the dense matrix storage type `M` related to the dense vector storage type `S`. """ -function vector_to_matrix(::Type{S}) where S <: DenseVector - T = hasproperty(S, :body) ? S.body : S - par = T.parameters - npar = length(par) - (2 ≤ npar ≤ 3) || error("Type $S is not supported.") - if npar == 2 - M = T.name.wrapper{par[1], 2} - else - M = T.name.wrapper{par[1], 2, par[3]} - end - return M +function vector_to_matrix(::Type{S}) where {S<:DenseVector} + T = hasproperty(S, :body) ? S.body : S + par = T.parameters + npar = length(par) + (2 ≤ npar ≤ 3) || error("Type $S is not supported.") + if npar == 2 + M = T.name.wrapper{par[1],2} + else + M = T.name.wrapper{par[1],2,par[3]} + end + return M end """ @@ -260,17 +264,17 @@ end Return the dense vector storage type `S` related to the dense matrix storage type `M`. """ -function matrix_to_vector(::Type{M}) where M <: DenseMatrix - T = hasproperty(M, :body) ? M.body : M - par = T.parameters - npar = length(par) - (2 ≤ npar ≤ 3) || error("Type $M is not supported.") - if npar == 2 - S = T.name.wrapper{par[1], 1} - else - S = T.name.wrapper{par[1], 1, par[3]} - end - return S +function matrix_to_vector(::Type{M}) where {M<:DenseMatrix} + T = hasproperty(M, :body) ? M.body : M + par = T.parameters + npar = length(par) + (2 ≤ npar ≤ 3) || error("Type $M is not supported.") + if npar == 2 + S = T.name.wrapper{par[1],1} + else + S = T.name.wrapper{par[1],1,par[3]} + end + return S end """ @@ -295,72 +299,72 @@ ktimer(start_time::UInt64) = (time_ns() - start_time) / 1e9 mulorldiv!(y, P, x, ldiv::Bool) = ldiv ? ldiv!(y, P, x) : mul!(y, P, x) -kdot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasReal = BLAS.dot(n, x, dx, y, dy) -kdot(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasComplex = BLAS.dotc(n, x, dx, y, dy) -kdot(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = dot(x, y) +kdot(n::Integer, x::Vector{T}, dx::Integer, y::Vector{T}, dy::Integer) where {T<:BLAS.BlasReal} = BLAS.dot(n, x, dx, y, dy) +kdot(n::Integer, x::Vector{T}, dx::Integer, y::Vector{T}, dy::Integer) where {T<:BLAS.BlasComplex} = BLAS.dotc(n, x, dx, y, dy) +kdot(n::Integer, x::AbstractVector{T}, dx::Integer, y::AbstractVector{T}, dy::Integer) where {T<:FloatOrComplex} = dot(x, y) -kdotr(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: AbstractFloat = kdot(n, x, dx, y, dy) -kdotr(n :: Integer, x :: AbstractVector{Complex{T}}, dx :: Integer, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = real(kdot(n, x, dx, y, dy)) +kdotr(n::Integer, x::AbstractVector{T}, dx::Integer, y::AbstractVector{T}, dy::Integer) where {T<:AbstractFloat} = kdot(n, x, dx, y, dy) +kdotr(n::Integer, x::AbstractVector{Complex{T}}, dx::Integer, y::AbstractVector{Complex{T}}, dy::Integer) where {T<:AbstractFloat} = real(kdot(n, x, dx, y, dy)) -knrm2(n :: Integer, x :: Vector{T}, dx :: Integer) where T <: BLAS.BlasFloat = BLAS.nrm2(n, x, dx) -knrm2(n :: Integer, x :: AbstractVector{T}, dx :: Integer) where T <: FloatOrComplex = norm(x) +knrm2(n::Integer, x::Vector{T}, dx::Integer) where {T<:BLAS.BlasFloat} = BLAS.nrm2(n, x, dx) +knrm2(n::Integer, x::AbstractVector{T}, dx::Integer) where {T<:FloatOrComplex} = norm(x) -kscal!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer) where T <: BLAS.BlasFloat = BLAS.scal!(n, s, x, dx) -kscal!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer) where T <: FloatOrComplex = (x .*= s) -kscal!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer) where T <: AbstractFloat = kscal!(n, Complex{T}(s), x, dx) +kscal!(n::Integer, s::T, x::Vector{T}, dx::Integer) where {T<:BLAS.BlasFloat} = BLAS.scal!(n, s, x, dx) +kscal!(n::Integer, s::T, x::AbstractVector{T}, dx::Integer) where {T<:FloatOrComplex} = (x .*= s) +kscal!(n::Integer, s::T, x::AbstractVector{Complex{T}}, dx::Integer) where {T<:AbstractFloat} = kscal!(n, Complex{T}(s), x, dx) -kaxpy!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.axpy!(n, s, x, dx, y, dy) -kaxpy!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = axpy!(s, x, y) -kaxpy!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpy!(n, Complex{T}(s), x, dx, y, dy) +kaxpy!(n::Integer, s::T, x::Vector{T}, dx::Integer, y::Vector{T}, dy::Integer) where {T<:BLAS.BlasFloat} = BLAS.axpy!(n, s, x, dx, y, dy) +kaxpy!(n::Integer, s::T, x::AbstractVector{T}, dx::Integer, y::AbstractVector{T}, dy::Integer) where {T<:FloatOrComplex} = axpy!(s, x, y) +kaxpy!(n::Integer, s::T, x::AbstractVector{Complex{T}}, dx::Integer, y::AbstractVector{Complex{T}}, dy::Integer) where {T<:AbstractFloat} = kaxpy!(n, Complex{T}(s), x, dx, y, dy) -kaxpby!(n :: Integer, s :: T, x :: Vector{T}, dx :: Integer, t :: T, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.axpby!(n, s, x, dx, t, y, dy) -kaxpby!(n :: Integer, s :: T, x :: AbstractVector{T}, dx :: Integer, t :: T, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = axpby!(s, x, t, y) -kaxpby!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: Complex{T}, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpby!(n, Complex{T}(s), x, dx, t, y, dy) -kaxpby!(n :: Integer, s :: Complex{T}, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: T, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpby!(n, s, x, dx, Complex{T}(t), y, dy) -kaxpby!(n :: Integer, s :: T, x :: AbstractVector{Complex{T}}, dx :: Integer, t :: T, y :: AbstractVector{Complex{T}}, dy :: Integer) where T <: AbstractFloat = kaxpby!(n, Complex{T}(s), x, dx, Complex{T}(t), y, dy) +kaxpby!(n::Integer, s::T, x::Vector{T}, dx::Integer, t::T, y::Vector{T}, dy::Integer) where {T<:BLAS.BlasFloat} = BLAS.axpby!(n, s, x, dx, t, y, dy) +kaxpby!(n::Integer, s::T, x::AbstractVector{T}, dx::Integer, t::T, y::AbstractVector{T}, dy::Integer) where {T<:FloatOrComplex} = axpby!(s, x, t, y) +kaxpby!(n::Integer, s::T, x::AbstractVector{Complex{T}}, dx::Integer, t::Complex{T}, y::AbstractVector{Complex{T}}, dy::Integer) where {T<:AbstractFloat} = kaxpby!(n, Complex{T}(s), x, dx, t, y, dy) +kaxpby!(n::Integer, s::Complex{T}, x::AbstractVector{Complex{T}}, dx::Integer, t::T, y::AbstractVector{Complex{T}}, dy::Integer) where {T<:AbstractFloat} = kaxpby!(n, s, x, dx, Complex{T}(t), y, dy) +kaxpby!(n::Integer, s::T, x::AbstractVector{Complex{T}}, dx::Integer, t::T, y::AbstractVector{Complex{T}}, dy::Integer) where {T<:AbstractFloat} = kaxpby!(n, Complex{T}(s), x, dx, Complex{T}(t), y, dy) -kcopy!(n :: Integer, x :: Vector{T}, dx :: Integer, y :: Vector{T}, dy :: Integer) where T <: BLAS.BlasFloat = BLAS.blascopy!(n, x, dx, y, dy) -kcopy!(n :: Integer, x :: AbstractVector{T}, dx :: Integer, y :: AbstractVector{T}, dy :: Integer) where T <: FloatOrComplex = copyto!(y, x) +kcopy!(n::Integer, x::Vector{T}, dx::Integer, y::Vector{T}, dy::Integer) where {T<:BLAS.BlasFloat} = BLAS.blascopy!(n, x, dx, y, dy) +kcopy!(n::Integer, x::AbstractVector{T}, dx::Integer, y::AbstractVector{T}, dy::Integer) where {T<:FloatOrComplex} = copyto!(y, x) # the macros are just for readability, so we don't have to write the increments (always equal to 1) macro kdot(n, x, y) - return esc(:(Krylov.kdot($n, $x, 1, $y, 1))) + return esc(:(Krylov.kdot($n, $x, 1, $y, 1))) end macro kdotr(n, x, y) - return esc(:(Krylov.kdotr($n, $x, 1, $y, 1))) + return esc(:(Krylov.kdotr($n, $x, 1, $y, 1))) end macro knrm2(n, x) - return esc(:(Krylov.knrm2($n, $x, 1))) + return esc(:(Krylov.knrm2($n, $x, 1))) end macro kscal!(n, s, x) - return esc(:(Krylov.kscal!($n, $s, $x, 1))) + return esc(:(Krylov.kscal!($n, $s, $x, 1))) end macro kaxpy!(n, s, x, y) - return esc(:(Krylov.kaxpy!($n, $s, $x, 1, $y, 1))) + return esc(:(Krylov.kaxpy!($n, $s, $x, 1, $y, 1))) end macro kaxpby!(n, s, x, t, y) - return esc(:(Krylov.kaxpby!($n, $s, $x, 1, $t, $y, 1))) + return esc(:(Krylov.kaxpby!($n, $s, $x, 1, $t, $y, 1))) end macro kcopy!(n, x, y) - return esc(:(Krylov.kcopy!($n, $x, 1, $y, 1))) + return esc(:(Krylov.kcopy!($n, $x, 1, $y, 1))) end macro kswap(x, y) - quote - local tmp = $(esc(x)) - $(esc(x)) = $(esc(y)) - $(esc(y)) = tmp - end + quote + local tmp = $(esc(x)) + $(esc(x)) = $(esc(y)) + $(esc(y)) = tmp + end end macro kref!(n, x, y, c, s) - return esc(:(reflect!($x, $y, $c, $s))) + return esc(:(reflect!($x, $y, $c, $s))) end """ @@ -379,22 +383,22 @@ If `flip` is set to `true`, `σ1` and `σ2` are computed such that ‖x - σi d‖ = radius, i = 1, 2. """ -function to_boundary(n :: Int, x :: AbstractVector{FC}, d :: AbstractVector{FC}, radius :: T; flip :: Bool=false, xNorm2 :: T=zero(T), dNorm2 :: T=zero(T)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}} - radius > 0 || error("radius must be positive") - - # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - Δ²). - rxd = @kdotr(n, x, d) - flip && (rxd = -rxd) - dNorm2 == zero(T) && (dNorm2 = @kdotr(n, d, d)) - dNorm2 == zero(T) && error("zero direction") - xNorm2 == zero(T) && (xNorm2 = @kdotr(n, x, x)) - radius2 = radius * radius - (xNorm2 ≤ radius2) || error(@sprintf("outside of the trust region: ‖x‖²=%7.1e, Δ²=%7.1e", xNorm2, radius2)) - - # q₂ = ‖d‖², q₁ = xᴴd + dᴴx, q₀ = ‖x‖² - Δ² - # ‖x‖² ≤ Δ² ⟹ (q₁)² - 4 * q₂ * q₀ ≥ 0 - roots = roots_quadratic(dNorm2, 2 * rxd, xNorm2 - radius2) - return roots # `σ1` and `σ2` +function to_boundary(n::Int, x::AbstractVector{FC}, d::AbstractVector{FC}, radius::T; flip::Bool=false, xNorm2::T=zero(T), dNorm2::T=zero(T)) where {T<:AbstractFloat,FC<:FloatOrComplex{T}} + radius > 0 || error("radius must be positive") + + # ‖d‖² σ² + (xᴴd + dᴴx) σ + (‖x‖² - Δ²). + rxd = @kdotr(n, x, d) + flip && (rxd = -rxd) + dNorm2 == zero(T) && (dNorm2 = @kdotr(n, d, d)) + dNorm2 == zero(T) && error("zero direction") + xNorm2 == zero(T) && (xNorm2 = @kdotr(n, x, x)) + radius2 = radius * radius + (xNorm2 ≤ radius2) || error(@sprintf("outside of the trust region: ‖x‖²=%7.1e, Δ²=%7.1e", xNorm2, radius2)) + + # q₂ = ‖d‖², q₁ = xᴴd + dᴴx, q₀ = ‖x‖² - Δ² + # ‖x‖² ≤ Δ² ⟹ (q₁)² - 4 * q₂ * q₀ ≥ 0 + roots = roots_quadratic(dNorm2, 2 * rxd, xNorm2 - radius2) + return roots # `σ1` and `σ2` end """ @@ -404,8 +408,8 @@ Extract the arguments of an expression that is keyword parameter tuple. Implementation suggested by Mitchell J. O'Sullivan (@mosullivan93). """ function extract_parameters(ex::Expr) - Meta.isexpr(ex, :tuple, 1) && - Meta.isexpr((@inbounds p = ex.args[1]), :parameters) && - all(Base.Docs.validcall, p.args) || throw(ArgumentError("Given expression is not a kw parameter tuple [e.g. :(; x)]: $ex")) - return p.args + Meta.isexpr(ex, :tuple, 1) && + Meta.isexpr((@inbounds p = ex.args[1]), :parameters) && + all(Base.Docs.validcall, p.args) || throw(ArgumentError("Given expression is not a kw parameter tuple [e.g. :(; x)]: $ex")) + return p.args end From 5a41681ebce319a6dfc848a1fa421119c349c309 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:45:11 +0200 Subject: [PATCH 181/182] Add tests --- test/runtests.jl | 1 + test/test_arrays.jl | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 test/test_arrays.jl diff --git a/test/runtests.jl b/test/runtests.jl index 23bbea807..5454a82e8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,3 +45,4 @@ include("test_mp.jl") include("test_solvers.jl") include("test_warm_start.jl") include("test_verbose.jl") +include("test_arrays.jl") diff --git a/test/test_arrays.jl b/test/test_arrays.jl new file mode 100644 index 000000000..e1e9c6738 --- /dev/null +++ b/test/test_arrays.jl @@ -0,0 +1,11 @@ +@testset "Weird array types" begin + @testset "ReshapedArrays" begin + for T in (Float32, Float64) + A = rand(T, 4, 4) + b = reshape(sparse(rand(T, 2, 2)), 4) + @test Krylov.ktypeof(b) == Vector{T} + x, stats = gmres(A, b) + @test stats.solved + end + end +end From 1d33fb65602e6913d67d49a4169ff181505e2f5e Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:49:50 +0200 Subject: [PATCH 182/182] Only for reshaped arrays that are vectors --- src/krylov_utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index fb7f02a64..fb2c79066 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -237,7 +237,7 @@ function ktypeof(v::S) where {S<:SubArray} end end -function ktypeof(v::S) where {T,S<:Base.ReshapedArray{T}} +function ktypeof(v::S) where {T,S<:Base.ReshapedArray{T,1}} return Vector{T} end

    [M   A]  [x] = [b] diff --git a/docs/make.jl b/docs/make.jl index db49cb759..1b38a9a3e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,14 +13,14 @@ makedocs( pages = ["Home" => "index.md", "API" => "api.md", "Krylov processes" => "processes.md", - "Krylov methods" => ["Symmetric positive definite linear systems" => "solvers/spd.md", - "Symmetric indefinite linear systems" => "solvers/sid.md", - "Unsymmetric linear systems" => "solvers/unsymmetric.md", + "Krylov methods" => ["Hermitian positive definite linear systems" => "solvers/spd.md", + "Hermitian indefinite linear systems" => "solvers/sid.md", + "Non-Hermitian linear systems" => "solvers/unsymmetric.md", "Least-norm problems" => "solvers/ln.md", "Least-squares problems" => "solvers/ls.md", "Adjoint systems" => "solvers/as.md", - "Saddle-point and symmetric quasi-definite systems" => "solvers/sp_sqd.md", - "Generalized saddle-point and unsymmetric partitioned systems" => "solvers/gsp.md"], + "Saddle-point and Hermitian quasi-definite systems" => "solvers/sp_sqd.md", + "Generalized saddle-point and non-Hermitian partitioned systems" => "solvers/gsp.md"], "In-place methods" => "inplace.md", "Preconditioners" => "preconditioners.md", "GPU support" => "gpu.md", diff --git a/docs/src/index.md b/docs/src/index.md index 1b61c48b0..1a18e2315 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -51,7 +51,7 @@ Overdetermined sytems are less common but also occur. where **_A_** can have any shape. -5 - Saddle-point and symmetric quasi-definite (SQD) systems +5 - Saddle-point and Hermitian quasi-definite systems ```math \begin{bmatrix} M & \phantom{-}A \\ A^H & -N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \left(\begin{bmatrix} b \\ 0 \end{bmatrix},\begin{bmatrix} 0 \\ c \end{bmatrix},\begin{bmatrix} b \\ c \end{bmatrix}\right) @@ -59,7 +59,7 @@ where **_A_** can have any shape. where **_A_** can have any shape. -6 - Generalized saddle-point and unsymmetric partitioned systems +6 - Generalized saddle-point and non-Hermitian partitioned systems ```math \begin{bmatrix} M & A \\ B & N \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} b \\ c \end{bmatrix} From b01ffa0442be96d00759b623c9884d9e727299aa Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Oct 2022 23:57:58 -0400 Subject: [PATCH 081/182] Support a view of a matrix as a right-hand side b --- docs/src/api.md | 1 + src/krylov_utils.jl | 34 +++++++++++++++++++++++++++++----- test/gpu/amd.jl | 6 ++---- test/gpu/gpu.jl | 5 +++++ test/gpu/intel.jl | 6 ++---- test/gpu/metal.jl | 6 ++---- test/gpu/nvidia.jl | 24 ++++++++++++++++++++---- test/test_aux.jl | 43 ++++++++++++++++++++++++------------------- 8 files changed, 85 insertions(+), 40 deletions(-) diff --git a/docs/src/api.md b/docs/src/api.md index bad8b9245..238c86f1a 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -62,4 +62,5 @@ Krylov.ktypeof Krylov.kzeros Krylov.kones Krylov.vector_to_matrix +Krylov.matrix_to_vector ``` diff --git a/src/krylov_utils.jl b/src/krylov_utils.jl index b0a180dd3..0c0b5bf71 100644 --- a/src/krylov_utils.jl +++ b/src/krylov_utils.jl @@ -219,7 +219,13 @@ function ktypeof(v::S) where S <: AbstractVector end function ktypeof(v::S) where S <: SubArray - return ktypeof(v.parent) + vp = v.parent + if isa(vp, DenseMatrix) + M = typeof(vp) + return matrix_to_vector(M) # view of a row or a column of a matrix + else + return ktypeof(vp) # view of a vector + end end """ @@ -228,18 +234,36 @@ end Return the dense matrix storage type `M` related to the dense vector storage type `S`. """ function vector_to_matrix(::Type{S}) where S <: DenseVector - V = hasproperty(S, :body) ? S.body : S - par = V.parameters + T = hasproperty(S, :body) ? S.body : S + par = T.parameters npar = length(par) (2 ≤ npar ≤ 3) || error("Type $S is not supported.") if npar == 2 - M = V.name.wrapper{par[1], 2} + M = T.name.wrapper{par[1], 2} else - M = V.name.wrapper{par[1], 2, par[3]} + M = T.name.wrapper{par[1], 2, par[3]} end return M end +""" + S = matrix_to_vector(M) + +Return the dense vector storage type `S` related to the dense matrix storage type `M`. +""" +function matrix_to_vector(::Type{M}) where M <: DenseMatrix + T = hasproperty(M, :body) ? M.body : M + par = T.parameters + npar = length(par) + (2 ≤ npar ≤ 3) || error("Type $M is not supported.") + if npar == 2 + S = T.name.wrapper{par[1], 1} + else + S = T.name.wrapper{par[1], 1, par[3]} + end + return S +end + """ v = kzeros(S, n) diff --git a/test/gpu/amd.jl b/test/gpu/amd.jl index a5852c434..1708722b2 100644 --- a/test/gpu/amd.jl +++ b/test/gpu/amd.jl @@ -68,10 +68,8 @@ include("gpu.jl") # Krylov.@kref!(n, x, y, c, s) # end - @testset "vector_to_matrix" begin - S = ROCVector{FC} - M2 = Krylov.vector_to_matrix(S) - @test M2 == M + @testset "conversion -- $FC" begin + test_conversion(S, M) end ε = eps(T) diff --git a/test/gpu/gpu.jl b/test/gpu/gpu.jl index 4d934e9e5..09036ecac 100644 --- a/test/gpu/gpu.jl +++ b/test/gpu/gpu.jl @@ -45,3 +45,8 @@ function test_solver(S, M) solver = GmresSolver(n, n, memory, S) solve!(solver, A, b) # Test that we don't have errors end + +function test_conversion(S, M) + @test Krylov.vector_to_matrix(S) == M + @test Krylov.matrix_to_vector(M) == S +end diff --git a/test/gpu/intel.jl b/test/gpu/intel.jl index 5b8e88209..2e2812553 100644 --- a/test/gpu/intel.jl +++ b/test/gpu/intel.jl @@ -76,10 +76,8 @@ end # Krylov.@kref!(n, x, y, c, s) # end - @testset "vector_to_matrix" begin - S = oneVector{FC} - M2 = Krylov.vector_to_matrix(S) - @test M2 == M + @testset "conversion -- $FC" begin + test_conversion(S, M) end ε = eps(T) diff --git a/test/gpu/metal.jl b/test/gpu/metal.jl index 15720bbfc..3b83ab921 100644 --- a/test/gpu/metal.jl +++ b/test/gpu/metal.jl @@ -80,10 +80,8 @@ end # Krylov.@kref!(n, x, y, c, s) # end - @testset "vector_to_matrix" begin - S = MtlVector{FC} - M2 = Krylov.vector_to_matrix(S) - @test M2 == M + @testset "conversion -- $FC" begin + test_conversion(S, M) end ε = eps(T) diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index 32c1dd519..aeaf30683 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -94,6 +94,7 @@ include("gpu.jl") for FC in (Float32, Float64, ComplexF32, ComplexF64) S = CuVector{FC} + V = CuSparseVector{FC} M = CuMatrix{FC} T = real(FC) n = 10 @@ -144,10 +145,8 @@ include("gpu.jl") Krylov.@kref!(n, x, y, c, s) end - @testset "vector_to_matrix" begin - S = CuVector{FC} - M2 = Krylov.vector_to_matrix(S) - @test M2 == M + @testset "conversion -- $FC" begin + test_conversion(S, M) end ε = eps(T) @@ -177,5 +176,22 @@ include("gpu.jl") @testset "solver -- $FC" begin test_solver(S, M) end + + @testset "ktypeof -- $FC" begin + dv = S(rand(FC, 10)) + b = view(dv, 4:8) + @test Krylov.ktypeof(dv) <: S + @test Krylov.ktypeof(b) <: S + + dm = M(rand(FC, 10, 10)) + b = view(dm, :, 3) + @test Krylov.ktypeof(b) <: S + + sv = V(sprand(FC, 10, 0.5)) + b = view(sv, 4:8) + @test Krylov.ktypeof(sv) <: S + @test Krylov.ktypeof(b) <: S + end + end end end diff --git a/test/test_aux.jl b/test/test_aux.jl index f844368e8..6c43142c0 100644 --- a/test/test_aux.jl +++ b/test/test_aux.jl @@ -129,25 +129,21 @@ @testset "ktypeof" begin # test ktypeof - a = rand(Float32, 10) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float32} - @test Krylov.ktypeof(b) == Vector{Float32} - - a = rand(Float64, 10) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float64} - @test Krylov.ktypeof(b) == Vector{Float64} - - a = sprand(Float32, 10, 0.5) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float32} - @test Krylov.ktypeof(b) == Vector{Float32} - - a = sprand(Float64, 10, 0.5) - b = view(a, 4:8) - @test Krylov.ktypeof(a) == Vector{Float64} - @test Krylov.ktypeof(b) == Vector{Float64} + for FC in (Float32, Float64, ComplexF32, ComplexF64) + dv = rand(FC, 10) + b = view(dv, 4:8) + @test Krylov.ktypeof(dv) == Vector{FC} + @test Krylov.ktypeof(b) == Vector{FC} + + dm = rand(FC, 10, 10) + b = view(dm, :, 3) + @test Krylov.ktypeof(b) == Vector{FC} + + sv = sprand(FC, 10, 0.5) + b = view(sv, 4:8) + @test Krylov.ktypeof(sv) == Vector{FC} + @test Krylov.ktypeof(b) == Vector{FC} + end end @testset "vector_to_matrix" begin @@ -159,6 +155,15 @@ end end + @testset "matrix_to_vector" begin + # test matrix_to_vector + for FC in (Float32, Float64, ComplexF32, ComplexF64) + M = Matrix{FC} + S = Krylov.matrix_to_vector(M) + @test S == Vector{FC} + end + end + @testset "macros" begin # test macros for FC ∈ (Float16, Float32, Float64, ComplexF16, ComplexF32, ComplexF64) From 04c8f69671071d4254a4daa4c50fa3d1083db9b4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Mon, 17 Oct 2022 00:03:43 -0400 Subject: [PATCH 082/182] Update test/gpu/nvidia.jl --- test/gpu/nvidia.jl | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/gpu/nvidia.jl b/test/gpu/nvidia.jl index aeaf30683..286e0c99d 100644 --- a/test/gpu/nvidia.jl +++ b/test/gpu/nvidia.jl @@ -178,20 +178,19 @@ include("gpu.jl") end @testset "ktypeof -- $FC" begin - dv = S(rand(FC, 10)) - b = view(dv, 4:8) - @test Krylov.ktypeof(dv) <: S - @test Krylov.ktypeof(b) <: S - - dm = M(rand(FC, 10, 10)) - b = view(dm, :, 3) - @test Krylov.ktypeof(b) <: S - - sv = V(sprand(FC, 10, 0.5)) - b = view(sv, 4:8) - @test Krylov.ktypeof(sv) <: S - @test Krylov.ktypeof(b) <: S - end + dv = S(rand(FC, 10)) + b = view(dv, 4:8) + @test Krylov.ktypeof(dv) <: S + @test Krylov.ktypeof(b) <: S + + dm = M(rand(FC, 10, 10)) + b = view(dm, :, 3) + @test Krylov.ktypeof(b) <: S + + sv = V(sprand(FC, 10, 0.5)) + b = view(sv, 4:8) + @test Krylov.ktypeof(sv) <: S + @test Krylov.ktypeof(b) <: S end end end From 354cf4be6ab8b7d0b915df377fe6513143c8b4b3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 19 Oct 2022 17:41:48 -0400 Subject: [PATCH 083/182] Update test/test_utils.jl --- test/test_utils.jl | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/test_utils.jl b/test/test_utils.jl index 0ac2e1538..f1c3ca44e 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -5,47 +5,47 @@ include("callback_utils.jl") # Symmetric and positive definite systems. function symmetric_definite(n :: Int=10; FC=Float64) - α = FC <: Complex ? im : 1 + α = FC <: Complex ? FC(im) : one(FC) A = spdiagm(-1 => α * ones(FC, n-1), 0 => 4 * ones(FC, n), 1 => conj(α) * ones(FC, n-1)) - b = A * [1:n;] + b = A * FC[1:n;] return A, b end # Symmetric and indefinite systems. function symmetric_indefinite(n :: Int=10; FC=Float64) - α = FC <: Complex ? im : 1 + α = FC <: Complex ? FC(im) : one(FC) A = spdiagm(-1 => α * ones(FC, n-1), 0 => ones(FC, n), 1 => conj(α) * ones(FC, n-1)) - b = A * [1:n;] + b = A * FC[1:n;] return A, b end # Nonsymmetric and positive definite systems. function nonsymmetric_definite(n :: Int=10; FC=Float64) if FC <: Complex - A = [i == j ? n * one(FC) : im * one(FC) for i=1:n, j=1:n] + A = [i == j ? n * one(FC) : FC(im) * one(FC) for i=1:n, j=1:n] else A = [i == j ? n * one(FC) : i < j ? one(FC) : -one(FC) for i=1:n, j=1:n] end - b = A * [1:n;] + b = A * FC[1:n;] return A, b end # Nonsymmetric and indefinite systems. function nonsymmetric_indefinite(n :: Int=10; FC=Float64) if FC <: Complex - A = [i == j ? n * (-one(FC))^(i*j) : im * one(FC) for i=1:n, j=1:n] + A = [i == j ? n * (-one(FC))^(i*j) : FC(im) * one(FC) for i=1:n, j=1:n] else A = [i == j ? n * (-one(FC))^(i*j) : i < j ? one(FC) : -one(FC) for i=1:n, j=1:n] end - b = A * [1:n;] + b = A * FC[1:n;] return A, b end # Underdetermined and consistent systems. function under_consistent(n :: Int=10, m :: Int=25; FC=Float64) n < m || error("Square or overdetermined system!") - α = FC <: Complex ? im : 1 - A = [i/j - α * j/i for i=1:n, j=1:m] + α = FC <: Complex ? FC(im) : one(FC) + A = FC[i/j - α * j/i for i=1:n, j=1:m] b = A * ones(FC, m) return A, b end @@ -53,7 +53,7 @@ end # Underdetermined and inconsistent systems. function under_inconsistent(n :: Int=10, m :: Int=25; FC=Float64) n < m || error("Square or overdetermined system!") - α = FC <: Complex ? 1 + im : 1 + α = FC <: Complex ? FC(1 + im) : one(FC) A = α * ones(FC, n, m) b = [i == 1 ? -one(FC) : i * one(FC) for i=1:n] return A, b @@ -85,8 +85,8 @@ end # Overdetermined and consistent systems. function over_consistent(n :: Int=25, m :: Int=10; FC=Float64) n > m || error("Underdetermined or square system!") - α = FC <: Complex ? im : 1 - A = [i/j - α * j/i for i=1:n, j=1:m] + α = FC <: Complex ? FC(im) : one(FC) + A = FC[i/j - α * j/i for i=1:n, j=1:m] b = A * ones(FC, m) return A, b end @@ -94,7 +94,7 @@ end # Overdetermined and inconsistent systems. function over_inconsistent(n :: Int=25, m :: Int=10; FC=Float64) n > m || error("Underdetermined or square system!") - α = FC <: Complex ? 1 + im : 1 + α = FC <: Complex ? FC(1 + im) : one(FC) A = α * ones(FC, n, m) b = [i == 1 ? -one(FC) : i * one(FC) for i=1:n] return A, b @@ -163,16 +163,16 @@ end function underdetermined_adjoint(n :: Int=100, m :: Int=200; FC=Float64) n < m || error("Square or overdetermined system!") A = [i == j ? FC(10.0) : i < j ? one(FC) : -one(FC) for i=1:n, j=1:m] - b = A * [1:m;] - c = A' * [-n:-1;] + b = A * FC[1:m;] + c = A' * FC[-n:-1;] return A, b, c end # Square consistent adjoint systems. function square_adjoint(n :: Int=100; FC=Float64) A = [i == j ? FC(10.0) : i < j ? one(FC) : -one(FC) for i=1:n, j=1:n] - b = A * [1:n;] - c = A' * [-n:-1;] + b = A * FC[1:n;] + c = A' * FC[-n:-1;] return A, b, c end @@ -188,8 +188,8 @@ end function overdetermined_adjoint(n :: Int=200, m :: Int=100; FC=Float64) n > m || error("Underdetermined or square system!") A = [i == j ? FC(10.0) : i < j ? one(FC) : -one(FC) for i=1:n, j=1:m] - b = A * [1:m;] - c = A' * [-n:-1;] + b = A * FC[1:m;] + c = A' * FC[-n:-1;] return A, b, c end @@ -252,7 +252,7 @@ end # Square and preconditioned problems. function square_preconditioned(n :: Int=10; FC=Float64) A = ones(FC, n, n) + (n-1) * eye(n) - b = FC(10.0) * [1:n;] + b = 10 * FC[1:n;] M⁻¹ = FC(1/n) * eye(n) return A, b, M⁻¹ end From 80e75863c7eba9ff3b4afbdfd58894f42189f6e0 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Wed, 19 Oct 2022 17:16:48 -0400 Subject: [PATCH 084/182] Improve Krylov processes --- src/krylov_processes.jl | 97 ++++++++++++++++------------------------- test/test_processes.jl | 10 ++--- 2 files changed, 42 insertions(+), 65 deletions(-) diff --git a/src/krylov_processes.jl b/src/krylov_processes.jl index ea3663eb4..858822b02 100644 --- a/src/krylov_processes.jl +++ b/src/krylov_processes.jl @@ -44,6 +44,7 @@ function hermitian_lanczos(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOr V = M(undef, n, k+1) T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval) + pαᵢ = 1 # Position of αᵢ in the vector `nzval` for i = 1:k vᵢ = view(V,:,i) vᵢ₊₁ = q = view(V,:,i+1) @@ -53,17 +54,18 @@ function hermitian_lanczos(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOr end mul!(q, A, vᵢ) αᵢ = @kdotr(n, vᵢ, q) - T[i,i] = αᵢ + nzval[pαᵢ] = αᵢ # Tᵢ.ᵢ = αᵢ @kaxpy!(n, -αᵢ, vᵢ, q) if i ≥ 2 vᵢ₋₁ = view(V,:,i-1) - βᵢ = T[i,i-1] - T[i-1,i] = βᵢ + βᵢ = nzval[pαᵢ-2] # βᵢ = Tᵢ.ᵢ₋₁ + nzval[pαᵢ-1] = βᵢ # Tᵢ₋₁.ᵢ = βᵢ @kaxpy!(n, -βᵢ, vᵢ₋₁, q) end βᵢ₊₁ = @knrm2(n, q) - T[i+1,i] = βᵢ₊₁ + nzval[pαᵢ+1] = βᵢ₊₁ # Tᵢ₊₁.ᵢ = βᵢ₊₁ vᵢ₊₁ .= q ./ βᵢ₊₁ + pαᵢ = pαᵢ + 3 end return V, T end @@ -118,6 +120,7 @@ function nonhermitian_lanczos(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_T) Tᴴ = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_Tᴴ) + pαᵢ = 1 # Position of αᵢ and ᾱᵢ in the vectors `nzval_T` and `nzval_Tᴴ` for i = 1:k vᵢ = view(V,:,i) uᵢ = view(U,:,i) @@ -135,14 +138,14 @@ function nonhermitian_lanczos(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k if i ≥ 2 vᵢ₋₁ = view(V,:,i-1) uᵢ₋₁ = view(U,:,i-1) - βᵢ = T[i,i-1] - γᵢ = T[i-1,i] + βᵢ = nzval_T[pαᵢ-2] # βᵢ = Tᵢ.ᵢ₋₁ + γᵢ = nzval_T[pαᵢ-1] # γᵢ = Tᵢ₋₁.ᵢ @kaxpy!(n, - γᵢ , vᵢ₋₁, q) @kaxpy!(n, -conj(βᵢ), uᵢ₋₁, p) end αᵢ = @kdot(n, uᵢ, q) - T[i,i] = αᵢ - Tᴴ[i,i] = conj(αᵢ) + nzval_T[pαᵢ] = αᵢ # Tᵢ.ᵢ = αᵢ + nzval_Tᴴ[pαᵢ] = conj(αᵢ) # Tᴴᵢ.ᵢ = ᾱᵢ @kaxpy!(m, - αᵢ , vᵢ, q) @kaxpy!(n, -conj(αᵢ), uᵢ, p) pᴴq = @kdot(n, p, q) @@ -150,12 +153,13 @@ function nonhermitian_lanczos(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k γᵢ₊₁ = pᴴq / βᵢ₊₁ vᵢ₊₁ .= q ./ βᵢ₊₁ uᵢ₊₁ .= p ./ conj(γᵢ₊₁) - T[i+1,i] = βᵢ₊₁ - Tᴴ[i+1,i] = conj(γᵢ₊₁) + nzval_T[pαᵢ+1] = βᵢ₊₁ # Tᵢ₊₁.ᵢ = βᵢ₊₁ + nzval_Tᴴ[pαᵢ+1] = conj(γᵢ₊₁) # Tᴴᵢ₊₁.ᵢ = γ̄ᵢ₊₁ if i ≤ k-1 - T[i,i+1] = γᵢ₊₁ - Tᴴ[i,i+1] = conj(βᵢ₊₁) + nzval_T[pαᵢ+2] = γᵢ₊₁ # Tᵢ.ᵢ₊₁ = γᵢ₊₁ + nzval_Tᴴ[pαᵢ+2] = conj(βᵢ₊₁) # Tᴴᵢ.ᵢ₊₁ = β̄ᵢ₊₁ end + pαᵢ = pαᵢ + 3 end return V, T, U, Tᴴ end @@ -172,7 +176,7 @@ end #### Output arguments * `V`: a dense n × (k+1) matrix; -* `H`: a sparse (k+1) × k upper Hessenberg matrix. +* `H`: a dense (k+1) × k upper Hessenberg matrix. #### Reference @@ -183,22 +187,8 @@ function arnoldi(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComplex S = ktypeof(b) M = vector_to_matrix(S) - nnz = div(k*(k+1), 2) + k - colptr = zeros(Int, k+1) - rowval = zeros(Int, nnz) - nzval = zeros(FC, nnz) - - colptr[1] = 1 - for i = 1:k - pos = colptr[i] - colptr[i+1] = pos+i+1 - for j = 1:i+1 - rowval[pos+j-1] = j - end - end - V = M(undef, n, k+1) - H = SparseMatrixCSC(k+1, k, colptr, rowval, nzval) + H = zeros(FC, k+1, k) for i = 1:k vᵢ = view(V,:,i) @@ -263,6 +253,7 @@ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComple U = M(undef, m, k+1) L = SparseMatrixCSC(k+1, k+1, colptr, rowval, nzval) + pαᵢ = 1 # Position of αᵢ in the vector `nzval` for i = 1:k uᵢ = view(U,:,i) vᵢ = view(V,:,i) @@ -274,11 +265,11 @@ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComple uᵢ .= b ./ βᵢ mul!(wᵢ, Aᴴ, uᵢ) αᵢ = @knrm2(n, wᵢ) - L[1,1] = αᵢ + nzval[pαᵢ] = αᵢ # Lᵢ.ᵢ = αᵢ vᵢ .= wᵢ ./ αᵢ end mul!(q, A, vᵢ) - αᵢ = L[i,i] + αᵢ = nzval[pαᵢ] # αᵢ = Lᵢ.ᵢ @kaxpy!(m, -αᵢ, uᵢ, q) βᵢ₊₁ = @knrm2(m, q) uᵢ₊₁ .= q ./ βᵢ₊₁ @@ -286,8 +277,9 @@ function golub_kahan(A, b::AbstractVector{FC}, k::Int) where FC <: FloatOrComple @kaxpy!(n, -βᵢ₊₁, vᵢ, p) αᵢ₊₁ = @knrm2(n, p) vᵢ₊₁ .= p ./ αᵢ₊₁ - L[i+1,i] = βᵢ₊₁ - L[i+1,i+1] = αᵢ₊₁ + nzval[pαᵢ+1] = βᵢ₊₁ # Lᵢ₊₁.ᵢ = βᵢ₊₁ + nzval[pαᵢ+2] = αᵢ₊₁ # Lᵢ₊₁.ᵢ₊₁ = αᵢ₊₁ + pαᵢ = pαᵢ + 2 end return V, U, L end @@ -342,6 +334,7 @@ function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: T = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_T) Tᴴ = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_Tᴴ) + pαᵢ = 1 # Position of αᵢ and ᾱᵢ in the vectors `nzval_T` and `nzval_Tᴴ` for i = 1:k vᵢ = view(V,:,i) uᵢ = view(U,:,i) @@ -358,26 +351,27 @@ function saunders_simon_yip(A, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: if i ≥ 2 vᵢ₋₁ = view(V,:,i-1) uᵢ₋₁ = view(U,:,i-1) - βᵢ = T[i,i-1] - γᵢ = T[i-1,i] + βᵢ = nzval_T[pαᵢ-2] # βᵢ = Tᵢ.ᵢ₋₁ + γᵢ = nzval_T[pαᵢ-1] # γᵢ = Tᵢ₋₁.ᵢ @kaxpy!(m, -γᵢ, vᵢ₋₁, q) @kaxpy!(n, -βᵢ, uᵢ₋₁, p) end αᵢ = @kdot(m, vᵢ, q) - T[i,i] = αᵢ - Tᴴ[i,i] = conj(αᵢ) + nzval_T[pαᵢ] = αᵢ # Tᵢ.ᵢ = αᵢ + nzval_Tᴴ[pαᵢ] = conj(αᵢ) # Tᴴᵢ.ᵢ = ᾱᵢ @kaxpy!(m, - αᵢ , vᵢ, q) @kaxpy!(n, -conj(αᵢ), uᵢ, p) βᵢ₊₁ = @knrm2(m, q) γᵢ₊₁ = @knrm2(n, p) vᵢ₊₁ .= q ./ βᵢ₊₁ uᵢ₊₁ .= p ./ γᵢ₊₁ - T[i+1,i] = βᵢ₊₁ - Tᴴ[i+1,i] = conj(γᵢ₊₁) + nzval_T[pαᵢ+1] = βᵢ₊₁ # Tᵢ₊₁.ᵢ = βᵢ₊₁ + nzval_Tᴴ[pαᵢ+1] = γᵢ₊₁ # Tᴴᵢ₊₁.ᵢ = γᵢ₊₁ if i ≤ k-1 - T[i,i+1] = γᵢ₊₁ - Tᴴ[i,i+1] = conj(βᵢ₊₁) + nzval_T[pαᵢ+2] = γᵢ₊₁ # Tᵢ.ᵢ₊₁ = γᵢ₊₁ + nzval_Tᴴ[pαᵢ+2] = βᵢ₊₁ # Tᴴᵢ.ᵢ₊₁ = βᵢ₊₁ end + pαᵢ = pαᵢ + 3 end return V, T, U, Tᴴ end @@ -396,9 +390,9 @@ end #### Output arguments * `V`: a dense m × (k+1) matrix; -* `H`: a sparse (k+1) × k upper Hessenberg matrix; +* `H`: a dense (k+1) × k upper Hessenberg matrix; * `U`: a dense n × (k+1) matrix; -* `F`: a sparse (k+1) × k upper Hessenberg matrix. +* `F`: a dense (k+1) × k upper Hessenberg matrix. #### Reference @@ -409,25 +403,10 @@ function montoison_orban(A, B, b::AbstractVector{FC}, c::AbstractVector{FC}, k:: S = ktypeof(b) M = vector_to_matrix(S) - nnz = div(k*(k+1), 2) + k - colptr = zeros(Int, k+1) - rowval = zeros(Int, nnz) - nzval_H = zeros(FC, nnz) - nzval_F = zeros(FC, nnz) - - colptr[1] = 1 - for i = 1:k - pos = colptr[i] - colptr[i+1] = pos+i+1 - for j = 1:i+1 - rowval[pos+j-1] = j - end - end - V = M(undef, m, k+1) U = M(undef, n, k+1) - H = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_H) - F = SparseMatrixCSC(k+1, k, colptr, rowval, nzval_F) + H = zeros(FC, k+1, k) + F = zeros(FC, k+1, k) for i = 1:k vᵢ = view(V,:,i) diff --git a/test/test_processes.jl b/test/test_processes.jl index 40825b29a..eb3ad19af 100644 --- a/test/test_processes.jl +++ b/test/test_processes.jl @@ -26,7 +26,7 @@ end nbits_R = sizeof(R) nbits_I = sizeof(Int) - @testset "Data Type: FC" begin + @testset "Data Type: $FC" begin @testset "Hermitian Lanczos" begin A, b = symmetric_indefinite(n, FC=FC) @@ -41,7 +41,7 @@ end @test expected_hermitian_lanczos_bytes ≤ actual_hermitian_lanczos_bytes ≤ 1.02 * expected_hermitian_lanczos_bytes end - @testset "Non-hermitian Lanczos" begin + @testset "Non-Hermitian Lanczos" begin A, b = nonsymmetric_definite(n, FC=FC) c = -b V, T, U, Tᴴ = nonhermitian_lanczos(A, b, c, k) @@ -64,8 +64,7 @@ end @test A * V[:,1:k] ≈ V * H function storage_arnoldi_bytes(n, k) - nnz = div(k*(k+1), 2) + k - return (nnz + k+1) * nbits_I + nnz * nbits_FC + n*(k+1) * nbits_FC + return k*(k+1) * nbits_FC + n*(k+1) * nbits_FC end expected_arnoldi_bytes = storage_arnoldi_bytes(n, k) @@ -135,8 +134,7 @@ end @test K * Wₖ ≈ Wₖ₊₁ * G function storage_montoison_orban_bytes(m, n, k) - nnz = div(k*(k+1), 2) + k - return (nnz + k+1) * nbits_I + 2*nnz * nbits_FC + (n+m)*(k+1) * nbits_FC + return 2*k*(k+1) * nbits_FC + (n+m)*(k+1) * nbits_FC end expected_montoison_orban_bytes = storage_montoison_orban_bytes(m, n, k) From b9b2bb060e053d6a75790f9cfd1c162667d800bb Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 16 Oct 2022 22:48:08 -0400 Subject: [PATCH 085/182] Change the category of USYMLQ and USYMQR --- docs/src/solvers/ln.md | 7 +++++++ docs/src/solvers/ls.md | 7 +++++++ docs/src/solvers/unsymmetric.md | 14 -------------- src/usymlq.jl | 2 +- src/usymqr.jl | 3 ++- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/src/solvers/ln.md b/docs/src/solvers/ln.md index c5396ffdd..b638b8247 100644 --- a/docs/src/solvers/ln.md +++ b/docs/src/solvers/ln.md @@ -36,3 +36,10 @@ craig! craigmr craigmr! ``` + +## USYMLQ + +```@docs +usymlq +usymlq! +``` diff --git a/docs/src/solvers/ls.md b/docs/src/solvers/ls.md index f77057d94..fecfbc417 100644 --- a/docs/src/solvers/ls.md +++ b/docs/src/solvers/ls.md @@ -36,3 +36,10 @@ lsqr! lsmr lsmr! ``` + +## USYMQR + +```@docs +usymqr +usymqr! +``` diff --git a/docs/src/solvers/unsymmetric.md b/docs/src/solvers/unsymmetric.md index e559145a2..2c596361a 100644 --- a/docs/src/solvers/unsymmetric.md +++ b/docs/src/solvers/unsymmetric.md @@ -16,20 +16,6 @@ qmr qmr! ``` -## USYMLQ - -```@docs -usymlq -usymlq! -``` - -## USYMQR - -```@docs -usymqr -usymqr! -``` - ## CGS ```@docs diff --git a/src/usymlq.jl b/src/usymlq.jl index 1d6d3e1d8..adb4f52e2 100644 --- a/src/usymlq.jl +++ b/src/usymlq.jl @@ -32,7 +32,7 @@ export usymlq, usymlq! USYMLQ can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. -Solve the linear system Ax = b of size m × n using the USYMLQ method. +USYMLQ determines the least-norm solution of the consistent linear system Ax = b of size m × n. USYMLQ is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. diff --git a/src/usymqr.jl b/src/usymqr.jl index 003a46dc5..990422f24 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -31,7 +31,8 @@ export usymqr, usymqr! USYMQR can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. -Solve the linear system Ax = b of size m × n using USYMQR. +USYMQR solves the linear least-squares problem min ‖b - Ax‖² of size m × n. +If A is square and nonsingular, USYMQR solves Ax = b. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. From 417c7191de49e48e3431864490796e70336b23d1 Mon Sep 17 00:00:00 2001 From: Alexis <35051714+amontoison@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:58:24 -0400 Subject: [PATCH 086/182] Update src/usymqr.jl --- src/usymqr.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usymqr.jl b/src/usymqr.jl index 990422f24..5d9caa525 100644 --- a/src/usymqr.jl +++ b/src/usymqr.jl @@ -32,7 +32,7 @@ export usymqr, usymqr! USYMQR can be warm-started from an initial guess `x0` where `kwargs` are the same keyword arguments as above. USYMQR solves the linear least-squares problem min ‖b - Ax‖² of size m × n. -If A is square and nonsingular, USYMQR solves Ax = b. +USYMQR solves Ax = b if it is consistent. USYMQR is based on the orthogonal tridiagonalization process and requires two initial nonzero vectors `b` and `c`. The vector `c` is only used to initialize the process and a default value can be `b` or `Aᴴb` depending on the shape of `A`. From 9aea97441c11e159f3431b79900b7dd65f0a14d4 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 1 Nov 2022 13:29:41 -0400 Subject: [PATCH 087/182] Fix a typo in processes.md --- docs/src/processes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/processes.md b/docs/src/processes.md index 06845160b..da6087017 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -63,7 +63,7 @@ After $k$ iterations of the non-Hermitian Lanczos process (also named the Lanczo ```math \begin{align*} A V_k &= V_k T_k + \beta_{k+1} v_{k+1} e_k^T = V_{k+1} T_{k+1,k}, \\ - B U_k &= U_k T_k^H + \bar{\gamma}_{k+1} u_{k+1} e_k^T = U_{k+1} T_{k,k+1}^H, \\ + A^H U_k &= U_k T_k^H + \bar{\gamma}_{k+1} u_{k+1} e_k^T = U_{k+1} T_{k,k+1}^H, \\ V_k^H U_k &= U_k^H V_k = I_k, \end{align*} ``` @@ -191,7 +191,7 @@ After $k$ iterations of the Saunders-Simon-Yip process (also named the orthogona ```math \begin{align*} A U_k &= V_k T_k + \beta_{k+1} v_{k+1} e_k^T = V_{k+1} T_{k+1,k}, \\ - B V_k &= U_k T_k^H + \gamma_{k+1} u_{k+1} e_k^T = U_{k+1} T_{k,k+1}^H, \\ + A^H V_k &= U_k T_k^H + \gamma_{k+1} u_{k+1} e_k^T = U_{k+1} T_{k,k+1}^H, \\ V_k^H V_k &= U_k^H U_k = I_k, \end{align*} ``` From e970d251c5fd928905c37153d8dd95fa7812a79d Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Thu, 3 Nov 2022 19:01:48 -0400 Subject: [PATCH 088/182] Add a comment about the non-Hermitian Lanczos process --- docs/src/graphics/golub_kahan.png | Bin 135734 -> 134956 bytes docs/src/processes.md | 5 +++++ 2 files changed, 5 insertions(+) diff --git a/docs/src/graphics/golub_kahan.png b/docs/src/graphics/golub_kahan.png index 3baec4b4a040921ba555e4113f11a635dbf35e7e..32fc3d7b8c7f989c623f44bb6bbbe77d1c3361de 100644 GIT binary patch literal 134956 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

  • G1DZ)paQp` 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 literal 0 HcmV?d00001 diff --git a/docs/src/graphics/montoison_orban.png b/docs/src/graphics/montoison_orban.png new file mode 100644 index 0000000000000000000000000000000000000000..5a14eda0437e6aad38b5b53bb746c6224ec5f79e GIT binary patch literal 176065 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