Skip to content

Commit 95825f6

Browse files
authored
Merge pull request #32 from luke-kiernan/separate-sym-factor-rebase
2 parents 1d0ce6f + 817a608 commit 95825f6

3 files changed

Lines changed: 93 additions & 22 deletions

File tree

docs/src/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,8 @@ Please cite both [SuiteSparse](https://github.com/DrTimothyAldenDavis/SuiteSpars
4646

4747
```@docs
4848
klu
49+
klu!
50+
klu_refactor!
51+
nonzeros
52+
solve!
4953
```

src/KLU.jl

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ module KLU
22

33
using SparseArrays
44
using SparseArrays: SparseMatrixCSC
5-
import SparseArrays: nnz
5+
import SparseArrays: nnz, nonzeros
66

77
export klu, klu!
8+
export klu_factor!, klu_refactor!, solve!
9+
export nonzeros
810

911
const libklu = :libklu
1012
include("wrappers.jl")
@@ -65,10 +67,10 @@ Data structure for parameters of and statistics generated by KLU functions.
6567
# Fields
6668
- `tol::Float64`: Partial pivoting tolerance for diagonal preference
6769
- `btf::Int64`: If `btf != 0` use BTF pre-ordering
68-
- `ordering::Int64`: If `ordering == 0` use AMD to permute, if `ordering == 1` use COLAMD,
70+
- `ordering::Int64`: If `ordering == 0` use AMD to permute, if `ordering == 1` use COLAMD, \
6971
if `ordering == 3` use the user provided ordering function.
70-
- `scale::Int64`: If `scale == 1` then `A[:,i] ./= sum(abs.(A[:,i]))`, if `scale == 2` then
71-
`A[:,i] ./= maximum(abs.(A[:,i]))`. If `scale == 0` no scaling is done, and the input is
72+
- `scale::Int64`: If `scale == 1` then `A[:,i] ./= sum(abs.(A[:,i]))`, if `scale == 2` then \
73+
`A[:,i] ./= maximum(abs.(A[:,i]))`. If `scale == 0` no scaling is done, and the input is \
7274
checked for errors if `scale >= 0`.
7375
7476
See the [KLU User Guide](https://github.com/DrTimothyAldenDavis/SuiteSparse/raw/master/KLU/Doc/KLU_UserGuide.pdf)
@@ -185,6 +187,15 @@ function size(K::AbstractKLUFactorization, dim::Integer)
185187
end
186188

187189
nnz(K::AbstractKLUFactorization) = K.lnz + K.unz + K.nzoff
190+
"""The nonzeros of the factorization `K`.
191+
!!! warning "nonzeros(K) may or may not point to nonzeros(A)"
192+
After `K = klu(A)`, it may or may not be the case that `nonzeros(K) === nonzeros(A)`. \
193+
If `A` is of type `SparseMatrixCSC{Float32, Int64}`, then `nonzeros(K)` will be \
194+
a `Vector{Float64}`, while `nonzeros(A)` is a `Vector{Float32}`, so they're definitely distinct. \
195+
On the other hand, if `A` has value type `Float64`, then changing the values in `nonzeros(A)` \
196+
will change `nonzeros(K)` as well.
197+
"""
198+
nonzeros(K::AbstractKLUFactorization) = K.nzval
188199

189200
if !isdefined(LinearAlgebra, :AdjointFactorization) # VERSION < v"1.10-"
190201
Base.adjoint(K::AbstractKLUFactorization) = Adjoint(K)
@@ -275,6 +286,7 @@ function getproperty(klu::AbstractKLUFactorization{Tv, Ti}, s::Symbol) where {Tv
275286
Lp = Vector{Ti}(undef, klu.n + 1)
276287
Li = Vector{Ti}(undef, lnz)
277288
Lx = Vector{Float64}(undef, lnz)
289+
# could also be replaced with multiple dispatch.
278290
Lz = Tv == Float64 ? C_NULL : Vector{Float64}(undef, lnz)
279291
_extract!(klu; Lp, Li, Lx, Lz)
280292
return Lp, Li, Lx, Lz
@@ -375,6 +387,11 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, K::AbstractKLUFactorizat
375387
end
376388
end
377389

390+
"""
391+
klu_analyze!(K::KLUFactorization; check = true)
392+
393+
Compute and store (as `K._symbolic`) a symbolic factorization of the sparse matrix \
394+
represented by the fields of `K`."""
378395
function klu_analyze!(K::KLUFactorization{Tv, Ti}; check=true) where {Tv, Ti<:KLUITypes}
379396
if K._symbolic != C_NULL return K end
380397
if Ti == Int64
@@ -390,7 +407,10 @@ function klu_analyze!(K::KLUFactorization{Tv, Ti}; check=true) where {Tv, Ti<:KL
390407
return K
391408
end
392409

393-
# User provided permutation vectors:
410+
"""
411+
klu_analyze!(K::KLUFactorization{Tv, Ti}, P::Vector{Ti}, Q::Vector{Ti}; check = true)
412+
413+
Variant of `klu_analyze!` that allows for user-provided permutation permutation vectors `P` and `Q`."""
394414
function klu_analyze!(K::KLUFactorization{Tv, Ti}, P::Vector{Ti}, Q::Vector{Ti}; check=true) where {Tv, Ti<:KLUITypes}
395415
if K._symbolic != C_NULL return K end
396416
if Ti == Int64
@@ -496,11 +516,11 @@ existing `KLUFactorization` instance.
496516
# Arguments
497517
- `K::KLUFactorization`: The matrix factorization object to be factored.
498518
- `check::Bool`: If `true` (default) check for errors after the factorization. If `false` errors must be checked by the user with `klu.common.status`.
499-
- `allowsingular::Bool`: If `true` (default `false`) allow the factorization to proceed even if the matrix is singular. Note that this will allow for
500-
silent divide by zero errors in subsequent `solve!` or `ldiv!` calls if singularity is not checked by the user with `klu.common.status == KLU.KLU_SINGULAR`
519+
- `allowsingular::Bool`: If `true` (default `false`) allow the factorization to proceed even if the matrix is singular. Note that this will allow for \
520+
silent divide by zero errors in subsequent `solve!` or `ldiv!` calls if singularity is not checked by the user with `klu.common.status == KLU.KLU_SINGULAR`
501521
502-
The `K.common` struct can be used to modify certain options and parameters, see the
503-
[KLU documentation](https://github.com/DrTimothyAldenDavis/SuiteSparse/raw/master/KLU/Doc/KLU_UserGuide.pdf)
522+
The `K.common` struct can be used to modify certain options and parameters, see the \
523+
[KLU documentation](https://github.com/DrTimothyAldenDavis/SuiteSparse/raw/master/KLU/Doc/KLU_UserGuide.pdf) \
504524
or [`klu_common`](@ref) for more information.
505525
"""
506526
klu_factor!
@@ -538,8 +558,10 @@ The relation between `K` and `A` is
538558
# Arguments
539559
- `A::SparseMatrixCSC` or `n::Integer`, `colptr::Vector{Ti}`, `rowval::Vector{Ti}`, `nzval::Vector{Tv}`: The sparse matrix or the zero-based sparse matrix components to be factored.
540560
- `check::Bool`: If `true` (default) check for errors after the factorization. If `false` errors must be checked by the user with `klu.common.status`.
541-
- `allowsingular::Bool`: If `true` (default `false`) allow the factorization to proceed even if the matrix is singular. Note that this will allow for
542-
silent divide by zero errors in subsequent `solve!` or `ldiv!` calls if singularity is not checked by the user with `klu.common.status == KLU.KLU_SINGULAR`
561+
- `allowsingular::Bool`: If `true` (default `false`) allow the factorization to proceed even if the matrix is singular.\
562+
Note that this will allow for silent divide by zero errors in subsequent `solve!` or `ldiv!` calls if singularity is not checked by the user with `klu.common.status == KLU.KLU_SINGULAR`
563+
- `full_factor::Bool`: if `true` (default), perform both numeric and symbolic factorization. If `false`, only perform symbolic factorization. \
564+
Useful for cases where only the sparse structure of `A` is known at time of construction.
543565
544566
!!! note
545567
`klu(A::SparseMatrixCSC)` uses the KLU[^ACM907] library that is part of
@@ -549,31 +571,56 @@ The relation between `K` and `A` is
549571
550572
[^ACM907]: Davis, Timothy A., & Palamadai Natarajan, E. (2010). Algorithm 907: KLU, A Direct Sparse Solver for Circuit Simulation Problems. ACM Trans. Math. Softw., 37(3). doi:10.1145/1824801.1824814
551573
"""
552-
function klu(n, colptr::Vector{Ti}, rowval::Vector{Ti}, nzval::Vector{Tv}; check=true, allowsingular=false) where {Ti<:KLUITypes, Tv<:AbstractFloat}
574+
function klu(n,
575+
colptr::Vector{Ti},
576+
rowval::Vector{Ti},
577+
nzval::Vector{Tv};
578+
check=true,
579+
allowsingular=false,
580+
full_factor=true,
581+
) where {Ti<:KLUITypes, Tv<:AbstractFloat}
553582
if Tv != Float64
554583
nzval = convert(Vector{Float64}, nzval)
555584
end
556585
K = KLUFactorization(n, colptr, rowval, nzval)
557-
return klu_factor!(K; check, allowsingular)
586+
return full_factor ? klu_factor!(K; check, allowsingular) : klu_analyze!(K; check)
558587
end
559588

560-
function klu(n, colptr::Vector{Ti}, rowval::Vector{Ti}, nzval::Vector{Tv}; check=true, allowsingular=false) where {Ti<:KLUITypes, Tv<:Complex}
589+
function klu(n,
590+
colptr::Vector{Ti},
591+
rowval::Vector{Ti},
592+
nzval::Vector{Tv};
593+
check=true,
594+
allowsingular=false,
595+
full_factor=true,
596+
) where {Ti<:KLUITypes, Tv<:Complex}
561597
if Tv != ComplexF64
562598
nzval = convert(Vector{ComplexF64}, nzval)
563599
end
564600
K = KLUFactorization(n, colptr, rowval, nzval)
565-
return klu_factor!(K; check, allowsingular)
601+
return full_factor ? klu_factor!(K; check, allowsingular) : klu_analyze!(K; check)
566602
end
567603

568-
function klu(A::SparseMatrixCSC{Tv, Ti}; check=true, allowsingular=false) where {Tv<:Union{AbstractFloat, Complex}, Ti<:KLUITypes}
604+
function klu(A::SparseMatrixCSC{Tv, Ti};
605+
check=true,
606+
allowsingular=false,
607+
full_factor=true,
608+
) where {Tv<:Union{AbstractFloat, Complex}, Ti<:KLUITypes}
569609
n = size(A, 1)
570610
n == size(A, 2) || throw(DimensionMismatch())
571-
return klu(n, decrement(A.colptr), decrement(A.rowval), A.nzval; check, allowsingular)
611+
return klu(n,
612+
decrement(A.colptr),
613+
decrement(A.rowval),
614+
A.nzval;
615+
check,
616+
allowsingular,
617+
full_factor
618+
)
572619
end
573620

574621
"""
575622
klu!(K::KLUFactorization, A::SparseMatrixCSC; check=true, allowsingular=false) -> K::KLUFactorization
576-
klu(K::KLUFactorization, nzval::Vector{Tv}; check=true, allowsingular=false) -> K::KLUFactorization
623+
klu!(K::KLUFactorization, nzval::Vector{Tv}; check=true, allowsingular=false) -> K::KLUFactorization
577624
578625
Recompute the KLU factorization `K` using the values of `A` or `nzval`. The pattern of the original
579626
matrix used to create `K` must match the pattern of `A`.
@@ -589,7 +636,7 @@ See also: [`klu`](@ref)
589636
- `A::SparseMatrixCSC` or `n::Integer`, `colptr::Vector{Ti}`, `rowval::Vector{Ti}`, `nzval::Vector{Tv}`: The sparse matrix or the zero-based sparse matrix components to be factored.
590637
- `check::Bool`: If `true` (default) check for errors after the factorization. If `false` errors must be checked by the user with `klu.common.status`.
591638
- `allowsingular::Bool`: If `true` (default `false`) allow the factorization to proceed even if the matrix is singular. Note that this will allow for
592-
silent divide by zero errors in subsequent `solve!` or `ldiv!` calls if singularity is not checked by the user with `klu.common.status == KLU.KLU_SINGULAR`
639+
silent divide by zero errors in subsequent `solve!` or `ldiv!` calls if singularity is not checked by the user with `klu.common.status == KLU.KLU_SINGULAR`
593640
594641
!!! note
595642
`klu(A::SparseMatrixCSC)` uses the KLU[^ACM907] library that is part of
@@ -641,6 +688,12 @@ function klu!(K::KLUFactorization{U}, S::SparseMatrixCSC{U}; check=true, allowsi
641688
return klu!(K, S.nzval; check, allowsingular)
642689
end
643690

691+
"""
692+
klu_refactor!(...) -> K::KLUFactorization
693+
694+
Same as [`klu!`](@ref): alias for naming consistency reasons."""
695+
klu_refactor!(args...) = klu!(args...)
696+
644697
#B is the modified argument here. To match with the math it should be (klu, B). But convention is
645698
# modified first. Thoughts?
646699

@@ -665,13 +718,13 @@ This function overwrites `B` with the solution `X`, for a new solution vector `X
665718
"""
666719
solve!
667720
for Tv KLUValueTypes, Ti KLUIndexTypes
668-
solve = _klu_name("solve", Tv, Ti)
721+
solve_name = _klu_name("solve", Tv, Ti)
669722
@eval begin
670723
function solve!(klu::AbstractKLUFactorization{$Tv, $Ti}, B::StridedVecOrMat{$Tv}; check=true)
671724
stride(B, 1) == 1 || throw(ArgumentError("B must have unit strides"))
672725
klu._numeric == C_NULL && klu_factor!(klu)
673726
size(B, 1) == size(klu, 1) || throw(DimensionMismatch())
674-
isok = $solve(klu._symbolic, klu._numeric, size(B, 1), size(B, 2), B, Ref(klu.common))
727+
isok = $solve_name(klu._symbolic, klu._numeric, size(B, 1), size(B, 2), B, Ref(klu.common))
675728
isok == 0 && check && kluerror(klu.common)
676729
return B
677730
end

test/runtests.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ using KLU
22
using KLU: increment!, KLUITypes, decrement, klu!, KLUFactorization,
33
klu_analyze!, klu_factor!
44
using Test
5-
using SparseArrays: SparseMatrixCSC, sparse, nnz
5+
using SparseArrays: SparseMatrixCSC, sparse, nnz, nonzeros
66
using LinearAlgebra
77

88
@testset "KLU Wrappers" begin
@@ -123,3 +123,17 @@ end
123123
@test issuccess(klu(A; allowsingular=true); allowsingular=true)
124124
end
125125
end
126+
127+
@testset "full_factor = false" begin
128+
for T in (Float64, ComplexF64, Float32, ComplexF32)
129+
A = sparse(Matrix{T}([1 0; 1 1]))
130+
nonzeros(A) .= zero(T)
131+
F = klu(A; full_factor = false)
132+
# we do need both here--for non 64 bit types, the nzval of F is not the same as A's.
133+
nonzeros(A) .= one(T)
134+
nonzeros(F) .= one(T)
135+
klu_factor!(F)
136+
b = ones(T, 2)
137+
@test F\b A\b
138+
end
139+
end

0 commit comments

Comments
 (0)