Skip to content

Commit

Permalink
avoid unscaling of L if all factorization attempts failed, alpha api (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffroyleconte authored Oct 3, 2022
1 parent a22bd6f commit 4d26e45
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 36 deletions.
98 changes: 78 additions & 20 deletions src/LimitedLDLFactorizations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,25 @@ The modified version is described in [3,4].
"""
module LimitedLDLFactorizations

export lldl, lldl_factorize!, \, ldiv!, nnz, LimitedLDLFactorization
export lldl, lldl_factorize!, \, ldiv!, nnz, LimitedLDLFactorization, factorized,
update_shift!, update_shift_increase_factor!

using AMD, LinearAlgebra, SparseArrays

mutable struct LLDLException <: Exception
msg::String
end

const error_string = "LLDL factorization was not computed or failed"

mutable struct LimitedLDLFactorization{
T <: Real,
Ti <: Integer,
V1 <: AbstractVector,
V2 <: AbstractVector,
}
__factorized::Bool

n::Int
colptr::Vector{Ti}
rowind::Vector{Ti}
Expand All @@ -44,6 +53,8 @@ mutable struct LimitedLDLFactorization{
D::Vector{T}
P::V1
α::T
α_increase_factor::T
α_out::T # α value after factorization (may be different of α if attempt_lldl! had failures)

memory::Int

Expand All @@ -60,11 +71,40 @@ mutable struct LimitedLDLFactorization{
computed_posneg::Bool # true if pos and neg are computed (becomes false after factorization)
end

"""
isfact = factorized(LLDL)
Returns true if the most recent factorization stored in `LLDL` [`LimitedLDLFactorization`](@ref) succeeded.
"""
factorized(LLDL::LimitedLDLFactorization) = LLDL.__factorized

"""
update_shift!(LLDL, α)
Updates the shift `α` of the `LimitedLDLFactorization` object `LLDL`.
"""
function update_shift!(LLDL::LimitedLDLFactorization{T}, α::T) where {T <: Real}
LLDL.α = α
LLDL
end

"""
update_shift_increase_factor!(LLDL, α_increase_factor)
Updates the shift increase value `α_increase_factor` of the `LimitedLDLFactorization` object `LLDL`
by which the shift `α` will be increased each time a `attempt_lldl!` fails.
"""
function update_shift_increase_factor!(LLDL::LimitedLDLFactorization{T}, α_increase_factor::Number) where {T <: Real}
LLDL.α_increase_factor = T(α_increase_factor)
LLDL
end

function LimitedLDLFactorization(
T::SparseMatrixCSC{Tv, Ti},
P::AbstractVector{<:Integer},
memory::Int,
α::Number,
α::Tf,
α_increase_factor::Tf,
n::Int,
nnzT::Int,
::Type{Tf},
Expand Down Expand Up @@ -106,6 +146,7 @@ function LimitedLDLFactorization(
Lnzvals = view(lvals, 1:nnzLmax)

return LimitedLDLFactorization(
false,
n,
colptr,
rowind,
Expand All @@ -116,7 +157,9 @@ function LimitedLDLFactorization(
adiag,
d,
P,
Tf(α),
α,
α_increase_factor,
zero(Tf),
memory,
Pinv,
wa1,
Expand All @@ -132,8 +175,8 @@ function LimitedLDLFactorization(
end

"""
LLDL = LimitedLDLFactorization(T; P = amd(T), memory = 0, α = 0)
LLDL = LimitedLDLFactorization(T, ::Type{Tf}; P = amd(T), memory = 0, α = 0)
LLDL = LimitedLDLFactorization(T; P = amd(T), memory = 0, α = 0, α_increase_factor = 10)
LLDL = LimitedLDLFactorization(T, ::Type{Tf}; P = amd(T), memory = 0, α = 0, α_increase_factor = 10)
Perform the allocations for the LLDL factorization of symmetric matrix whose lower triangle is `T`
with the permutation vector `P`.
Expand All @@ -149,7 +192,10 @@ with the permutation vector `P`.
`T` is the strict lower triangle of A and `n` is the size of `A`;
- `α::Number=0`: initial value of the shift in case the incomplete LDLᵀ
factorization of `A` is found to not exist. The shift will be
gradually increased from this initial value until success.
gradually increased from this initial value until success;
- `α_increase_factor::Number=10`: value by which the shift will be increased after
the incomplete LDLᵀ factorization of `T` is found
to not exist.
# Example
A = sprand(Float64, 10, 10, 0.2)
Expand All @@ -163,11 +209,12 @@ function LimitedLDLFactorization(
P::AbstractVector{<:Integer} = amd(T),
memory::Int = 0,
α::Number = 0,
α_increase_factor::Number = 10,
) where {Tv <: Number, Ti <: Integer, Tf <: Real}
memory < 0 && error("limited-memory parameter must be nonnegative")
n = size(T, 1)
n != size(T, 2) && error("input matrix must be square")
return LimitedLDLFactorization(T, P, memory, α, n, nnz(T), Tf)
return LimitedLDLFactorization(T, P, memory, Tf(α), Tf(α_increase_factor), n, nnz(T), Tf)
end

LimitedLDLFactorization(T::SparseMatrixCSC{Tv, Ti}; kwargs...) where {Tv <: Number, Ti <: Integer} =
Expand Down Expand Up @@ -198,13 +245,12 @@ function lldl_factorize!(
n != size(T, 2) && error("input matrix must be square")

colptr = S.colptr
Lrowind = S.Lrowind
Lnzvals = S.Lnzvals

nnzT = nnz(T)
nnzT_nodiag = nnzT - S.nnz_diag
memory = S.memory
α = S.α
α_increase_factor = S.α_increase_factor

P = S.P
Pinv = S.Pinv
Expand Down Expand Up @@ -352,21 +398,25 @@ function lldl_factorize!(
# Increase shift if the factorization didn't succeed.
if !factorized
nb_increase_α += 1
α === 0) ? α_min : 2 * α
α === 0) ? α_min : α_increase_factor * α
tired = nb_increase_α > max_increase_α
end
end

S.__factorized = factorized

# Unscale L.
@inbounds @simd for col = 1:n
scol = s[col]
d[col] /= scol * scol
@inbounds @simd for k = colptr[col]:(colptr[col + 1] - 1)
lvals[k] *= scol / s[rowind[k]]
if factorized
@inbounds @simd for col = 1:n
scol = s[col]
d[col] /= scol * scol
@inbounds @simd for k = colptr[col]:(colptr[col + 1] - 1)
lvals[k] *= scol / s[rowind[k]]
end
end
end

S.α = α
S.α_out = α
nz = colptr[end] - 1
S.Lrowind = view(rowind, 1:nz)
S.Lnzvals = view(lvals, 1:nz)
Expand All @@ -390,9 +440,12 @@ Compute the limited-memory LDLᵀ factorization of `A`.
- `memory::Int=0`: extra amount of memory to allocate for the incomplete factor `L`.
The total memory allocated is nnz(T) + n * `memory`, where
`T` is the strict lower triangle of A and `n` is the size of `A`;
- `α::Tv=Tv(0)`: initial value of the shift in case the incomplete LDLᵀ
- `α::Number=0`: initial value of the shift in case the incomplete LDLᵀ
factorization of `A` is found to not exist. The shift will be
gradually increased from this initial value until success;
- `α_increase_factor::Number = 10`: value by which the shift will be increased after
the incomplete LDLᵀ factorization of `T` is found
to not exist.
- `droptol::Tv=Tv(0)`: to further sparsify `L`, all elements with magnitude smaller
than `droptol` are dropped;
- `check_tril::Bool = true`: check if `A` is a lower triangular matrix.
Expand All @@ -410,12 +463,13 @@ function lldl(
::Type{Tf};
P::AbstractVector{<:Integer} = amd(A),
memory::Int = 0,
droptol::Real = Tv(0),
α::Number = 0,
droptol::Number = 0,
α_increase_factor::Number = 10,
check_tril::Bool = true,
) where {Tv <: Number, Ti <: Integer, Tf <: Real}
T = (!check_tril || istril(A)) ? A : tril(A)
S = LimitedLDLFactorization(T, Tf; P = P, memory = memory, α = α)
S = LimitedLDLFactorization(T, Tf; P = P, memory = memory, α = α, α_increase_factor = α_increase_factor)
lldl_factorize!(S, T, droptol = Tf(droptol))
end

Expand Down Expand Up @@ -737,14 +791,18 @@ end
import Base.(\)
function (\)(LLDL::LimitedLDLFactorization, b::AbstractVector)
y = copy(b)
factorized(LLDL) || throw(LLDLException(error_string))
lldl_solve!(LLDL.n, y, LLDL.colptr, LLDL.Lrowind, LLDL.Lnzvals, LLDL.D, LLDL.P)
end

import LinearAlgebra.ldiv!
ldiv!(LLDL::LimitedLDLFactorization, b::AbstractVector) =
function ldiv!(LLDL::LimitedLDLFactorization, b::AbstractVector)
factorized(LLDL) || throw(LLDLException(error_string))
lldl_solve!(LLDL.n, b, LLDL.colptr, LLDL.Lrowind, LLDL.Lnzvals, LLDL.D, LLDL.P)
end

function ldiv!(y::AbstractVector, LLDL::LimitedLDLFactorization, b::AbstractVector)
factorized(LLDL) || throw(LLDLException(error_string))
y .= b
lldl_solve!(LLDL.n, y, LLDL.colptr, LLDL.Lrowind, LLDL.Lnzvals, LLDL.D, LLDL.P)
end
Expand Down
38 changes: 22 additions & 16 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ using AMD, Metis, LimitedLDLFactorizations
LLDL = lldl(A, P = perm, memory = 0)
nnzl0 = nnz(LLDL)
@test nnzl0 == nnz(tril(A))
@test LLDL.α == 0
@test LLDL.α_out == 0

LLDL = lldl(Symmetric(A, :L), P = perm, memory = 5) # test symmetric lldl
nnzl5 = nnz(LLDL)
@test nnzl5 nnzl0
@test LLDL.α == 0
@test LLDL.α_out == 0

LLDL = lldl(A, P = perm, memory = 10)
@test nnz(LLDL) nnzl5
@test LLDL.α == 0
@test LLDL.α_out == 0
L = LLDL.L + I
@test norm(L * diagm(0 => LLDL.D) * L' - A[perm, perm]) sqrt(eps()) * norm(A)

Expand All @@ -56,13 +56,13 @@ end
1.0 0.0
]
LLDL = lldl(A)
@test LLDL.α sqrt(eps())
@test LLDL.α_out sqrt(eps())
@test LLDL.D[1] > 0
@test LLDL.D[2] < 0

# specify our own shift
LLDL = lldl(A, α = 1.0e-2)
@test LLDL.α 1.0e-2
@test LLDL.α_out 1.0e-2
@test LLDL.D[1] > 0
@test LLDL.D[2] < 0
end
Expand All @@ -88,16 +88,16 @@ end
LLDL = lldl(B, P = perm, memory = 0)
nnzl0 = nnz(LLDL)
@test nnzl0 == nnz(tril(A))
@test LLDL.α == 0
@test LLDL.α_out == 0

LLDL = lldl(B, P = perm, memory = 5)
nnzl5 = nnz(LLDL)
@test nnzl5 nnzl0
@test LLDL.α == 0
@test LLDL.α_out == 0

LLDL = lldl(B, P = perm, memory = 10)
@test nnz(LLDL) nnzl5
@test LLDL.α == 0
@test LLDL.α_out == 0
L = LLDL.L + I
@test norm(L * diagm(0 => LLDL.D) * L' - A[perm, perm]) sqrt(eps()) * norm(A)

Expand Down Expand Up @@ -133,8 +133,12 @@ end
Alow = tril(A)
perm = amd(A)
LLDL = LimitedLDLFactorization(Alow, P = perm, memory = 10)
@test !factorized(LLDL)
@test_throws LimitedLDLFactorizations.LLDLException LLDL \ rand(size(A, 1))

lldl_factorize!(LLDL, Alow)
@test LLDL.α == 0
@test factorized(LLDL)
@test LLDL.α_out == 0
L = LLDL.L + I
@test norm(L * diagm(0 => LLDL.D) * L' - A[perm, perm]) sqrt(eps()) * norm(A)

Expand Down Expand Up @@ -167,7 +171,7 @@ end
lldl_factorize!(LLDL, A2low)
allocs = @allocated lldl_factorize!(LLDL, A2low)
@test allocs == 0
@test LLDL.α == 0
@test LLDL.α_out == 0
L = LLDL.L + I
@test norm(L * diagm(0 => LLDL.D) * L' - A2[perm, perm]) sqrt(eps()) * norm(A2)

Expand All @@ -186,18 +190,20 @@ end

@testset "with shift lower triangle" begin
# this matrix requires a shift
A = [
A = sparse([
1.0 0.0
1.0 0.0
]
])
LLDL = lldl(A)
@test LLDL.α sqrt(eps())
@test LLDL.α_out sqrt(eps())
@test LLDL.D[1] > 0
@test LLDL.D[2] < 0

# specify our own shift
LLDL = lldl(A, α = 1.0e-2)
@test LLDL.α 1.0e-2
update_shift!(LLDL, 1.0e-2)
update_shift_increase_factor!(LLDL, 5)
lldl_factorize!(LLDL, A)
@test LLDL.α_out 1.0e-2
@test LLDL.D[1] > 0
@test LLDL.D[2] < 0
end
Expand All @@ -217,7 +223,7 @@ end
]
A = convert(SparseMatrixCSC{Float64, Int32}, sparse(A))
LLDL = lldl(A, memory = 5)
@test LLDL.α == 0
@test LLDL.α_out == 0
end

@testset "test lower-precision factorization" begin
Expand Down

0 comments on commit 4d26e45

Please sign in to comment.