Skip to content

Commit 079bcb9

Browse files
committed
Support extracting factors from cholfact(::SparseMatrixCSC)
Also supports extracting factors from ldltfact. A wide variety of "unconventional factors," like :PL, are supported because of the importance of pivoting in sparse factorizations. This also: - changes the behavior of sparse(cholfact(A)) to return the original matrix, just like full(cholfact(A)) for dense matrices *breaking* - Doesn't export transpose(::Sparse, ::Integer) (that seems like an internal interface) - Adds support for ctranspose
1 parent 753390b commit 079bcb9

2 files changed

Lines changed: 316 additions & 13 deletions

File tree

base/sparse/cholmod.jl

Lines changed: 209 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module CHOLMOD
33
import Base: (*), convert, copy, eltype, getindex, show, size
44

55
import Base.LinAlg: (\), A_mul_Bc, A_mul_Bt, Ac_ldiv_B, Ac_mul_B, At_ldiv_B, At_mul_B,
6-
cholfact, cholfact!, det, diag, ishermitian, isposdef,
6+
cholfact, det, diag, ishermitian, isposdef,
77
issym, ldltfact, logdet
88

99
import Base.SparseMatrix: sparse
@@ -54,10 +54,10 @@ end
5454
# Type definitions #
5555
####################
5656

57-
# The three core data types for CHOLMOD: Dense, Sparse and Factor. CHOLMOD
58-
# manages the memory, so the Julia versions only wrap a pointer to a struct.
59-
# Therefore finalizers should be registret each time a pointer is returned from
60-
# CHOLMOD.
57+
# The three core data types for CHOLMOD: Dense, Sparse and Factor.
58+
# CHOLMOD manages the memory, so the Julia versions only wrap a
59+
# pointer to a struct. Therefore finalizers should be registered each
60+
# time a pointer is returned from CHOLMOD.
6161

6262
# Dense
6363
immutable C_Dense{T<:VTypes}
@@ -195,6 +195,28 @@ type Factor{Tv,Ti} <: Factorization{Tv}
195195
p::Ptr{C_Factor{Tv,Ti}}
196196
end
197197

198+
# FactorComponent, for encoding particular factors from a factorization
199+
type FactorComponent{Tv,Ti,S} <: AbstractMatrix{Tv}
200+
F::Factor{Tv,Ti}
201+
202+
function FactorComponent(F::Factor{Tv,Ti})
203+
s = unsafe_load(F.p)
204+
if bool(s.is_ll)
205+
S == :L || S == :U || S == :PL || S == :UPt || throw(CHOLMODException(S, " not supported for sparse LLt matrices; try :L, :U, :PL, or :UPt"))
206+
else
207+
S == :L || S == :U || S == :PL || S == :UPt ||
208+
S == :D || S == :LD || S == :DU || S == :PLD || S == :DUPt ||
209+
throw(CHOLMODException(S, " not supported for sparse LDLt matrices; try :L, :U, :PL, :UPt, :D, :LD, :DU, :PLD, or :DUPt"))
210+
end
211+
new(F)
212+
end
213+
end
214+
function FactorComponent{Tv,Ti}(F::Factor{Tv,Ti}, sym::Symbol)
215+
FactorComponent{Tv,Ti,sym}(F)
216+
end
217+
218+
Factor(FC::FactorComponent) = Factor(FC.F)
219+
198220
#################
199221
# Thin wrappers #
200222
#################
@@ -400,7 +422,7 @@ for Ti in IndexTypes
400422
s
401423
end
402424

403-
function transpose{Tv<:VTypes}(A::Sparse{Tv,$Ti}, values::Integer)
425+
function transpose_{Tv<:VTypes}(A::Sparse{Tv,$Ti}, values::Integer)
404426
s = Sparse(ccall((@cholmod_name("transpose", $Ti),:libcholmod), Ptr{C_Sparse{Tv,$Ti}},
405427
(Ptr{C_Sparse{Tv,$Ti}}, Cint, Ptr{UInt8}),
406428
A.p, values, common($Ti)))
@@ -550,6 +572,25 @@ for Ti in IndexTypes
550572
finalizer(f, free!)
551573
f
552574
end
575+
function analyze_p{Tv<:VTypes}(A::Sparse{Tv,$Ti}, perm::Vector{$Ti}, fset::Vector{$Ti}, cmmn::Vector{UInt8})
576+
f = Factor(ccall((@cholmod_name("analyze_p", $Ti),:libcholmod),
577+
Ptr{C_Factor{Tv,$Ti}},
578+
(Ptr{C_Sparse{Tv,$Ti}}, Ptr{$Ti}, Ptr{$Ti}, Csize_t, Ptr{UInt8}),
579+
A.p, perm, fset, length(fset), cmmn))
580+
finalizer(f, free!)
581+
f
582+
end
583+
function analyze_p2{Tv<:VTypes}(forchol::Bool, A::Sparse{Tv,$Ti}, perm::Vector{$Ti}, fset::Vector{$Ti}, cmmn::Vector{UInt8})
584+
sA = unsafe_load(A.p)
585+
length(perm) == sA.nrow || throw(ArgumentError("permutation is of the wrong size"))
586+
587+
f = Factor(ccall((@cholmod_name("analyze_p2", $Ti),:libcholmod),
588+
Ptr{C_Factor{Tv,$Ti}},
589+
(Cint, Ptr{C_Sparse{Tv,$Ti}}, Ptr{$Ti}, Ptr{$Ti}, Csize_t, Ptr{UInt8}),
590+
forchol, A.p, perm, fset, length(fset), cmmn))
591+
finalizer(f, free!)
592+
f
593+
end
553594
function factorize!{Tv<:VTypes}(A::Sparse{Tv,$Ti}, F::Factor{Tv,$Ti}, cmmn::Vector{UInt8})
554595
@isok ccall((@cholmod_name("factorize", $Ti),:libcholmod), Cint,
555596
(Ptr{C_Sparse{Tv,$Ti}}, Ptr{C_Factor{Tv,$Ti}}, Ptr{UInt8}),
@@ -576,7 +617,7 @@ for Ti in IndexTypes
576617
d
577618
end
578619

579-
function spsolve{Tv<:VTypes}(sys::Integer, F::Factor{Tv,$Ti}, B::Sparse{Tv,$Ti})
620+
function solve{Tv<:VTypes}(sys::Integer, F::Factor{Tv,$Ti}, B::Sparse{Tv,$Ti})
580621
if size(F,1) != size(B,1)
581622
throw(DimensionMismatch("LHS and RHS should have the same number of rows. LHS has $(size(F,1)) rows, but RHS has $(size(B,1)) rows."))
582623
end
@@ -602,6 +643,13 @@ for Ti in IndexTypes
602643
end
603644
end
604645

646+
function get_perm(F::Factor)
647+
s = unsafe_load(F.p)
648+
p = pointer_to_array(s.Perm, s.n, false)
649+
p+1
650+
end
651+
get_perm(FC::FactorComponent) = get_perm(Factor(FC))
652+
605653
#########################
606654
# High level interfaces #
607655
#########################
@@ -790,9 +838,33 @@ function sparse{Ti}(A::Sparse{Complex{Float64},Ti}) # Notice! Cannot be type sta
790838
end
791839
return convert(Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},Ti}}, A)
792840
end
793-
sparse(L::Factor) = sparse(Sparse(L))
841+
function sparse(F::Factor)
842+
s = unsafe_load(F.p)
843+
if bool(s.is_ll)
844+
L = Sparse(F)
845+
A = sparse(L*L')
846+
else
847+
LD = sparse(F[:LD])
848+
L, d = getLd!(LD)
849+
A = scale(L, d)*L'
850+
end
851+
p = get_perm(F)
852+
if p != [1:s.n;]
853+
A = A[p,p]
854+
end
855+
A
856+
end
857+
794858
sparse(D::Dense) = sparse(Sparse(D))
795859

860+
function sparse{Tv,Ti}(FC::FactorComponent{Tv,Ti,:L})
861+
F = Factor(FC)
862+
s = unsafe_load(F.p)
863+
bool(s.is_ll) || throw(CHOLMODException("sparse: supported only for :LD on LDLt factorizations"))
864+
sparse(Sparse(F))
865+
end
866+
sparse{Tv,Ti}(FC::FactorComponent{Tv,Ti,:LD}) = sparse(Sparse(Factor(FC)))
867+
796868
# Calculate the offset into the stype field of the cholmod_sparse_struct and
797869
# change the value
798870
let offidx=findfirst(fieldnames(C_Sparse) .== :stype)
@@ -845,6 +917,19 @@ function size(F::Factor, i::Integer)
845917
return 1
846918
end
847919

920+
size(FC::FactorComponent, i::Integer) = size(FC.F, i)
921+
size(FC::FactorComponent) = size(FC.F)
922+
923+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:L}) = FactorComponent{Tv,Ti,:U}(FC.F)
924+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:U}) = FactorComponent{Tv,Ti,:L}(FC.F)
925+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:PL}) = FactorComponent{Tv,Ti,:UPt}(FC.F)
926+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:UPt}) = FactorComponent{Tv,Ti,:PL}(FC.F)
927+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:D}) = FC
928+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:LD}) = FactorComponent{Tv,Ti,:DU}(FC.F)
929+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:DU}) = FactorComponent{Tv,Ti,:LD}(FC.F)
930+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:PLD}) = FactorComponent{Tv,Ti,:DUPt}(FC.F)
931+
ctranspose{Tv,Ti}(FC::FactorComponent{Tv,Ti,:DUPt}) = FactorComponent{Tv,Ti,:PLD}(FC.F)
932+
848933
function getindex(A::Dense, i::Integer)
849934
s = unsafe_load(A.p)
850935
0 < i <= s.nrow*s.ncol || throw(BoundsError())
@@ -871,6 +956,27 @@ function getindex{T}(A::Sparse{T}, i0::Integer, i1::Integer)
871956
((r1 > r2) || (unsafe_load(s.i, r1) + 1 != i0)) ? zero(T) : unsafe_load(s.x, r1)
872957
end
873958

959+
function getindex(F::Factor, sym::Symbol)
960+
sym == :p && return get_perm(F)
961+
FactorComponent(F, sym)
962+
end
963+
964+
function getLd!(S::SparseMatrixCSC)
965+
d = Array(eltype(S), size(S, 1))
966+
fill!(d, 0)
967+
col = 1
968+
for k = 1:length(S.nzval)
969+
while k >= S.colptr[col+1]
970+
col += 1
971+
end
972+
if S.rowval[k] == col
973+
d[col] = S.nzval[k]
974+
S.nzval[k] = 1
975+
end
976+
end
977+
S, d
978+
end
979+
874980
## Multiplication
875981
(*)(A::Sparse, B::Sparse) = ssmult(A, B, 0, true, true)
876982
(*)(A::Sparse, B::Dense) = sdmult!(A, false, 1., 0., B, zeros(size(A, 1), size(B, 2)))
@@ -880,7 +986,7 @@ function A_mul_Bc{Tv<:VRealTypes,Ti<:ITypes}(A::Sparse{Tv,Ti}, B::Sparse{Tv,Ti})
880986
cm = common(Ti)
881987

882988
if !is(A,B)
883-
aa1 = transpose(B, 2)
989+
aa1 = transpose_(B, 2)
884990
## result of ssmult will have stype==0, contain numerical values and be sorted
885991
return ssmult(A, aa1, 0, true, true)
886992
end
@@ -898,7 +1004,7 @@ function A_mul_Bc{Tv<:VRealTypes,Ti<:ITypes}(A::Sparse{Tv,Ti}, B::Sparse{Tv,Ti})
8981004
end
8991005

9001006
function Ac_mul_B(A::Sparse, B::Sparse)
901-
aa1 = transpose(A, 2)
1007+
aa1 = transpose_(A, 2)
9021008
if is(A,B)
9031009
return A_mul_Bc(aa1, aa1)
9041010
end
@@ -943,6 +1049,25 @@ function cholfact{Tv<:VTypes,Ti<:ITypes}(A::Sparse{Tv,Ti}, β::Tv)
9431049
return F
9441050
end
9451051

1052+
function cholfact{Tv<:VTypes,Ti<:ITypes,Tii<:Integer}(A::Sparse{Tv,Ti}, perm::Vector{Tii})
1053+
sA = unsafe_load(A.p)
1054+
sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian"))
1055+
isperm(perm) || throw(ArgumentError("perm is not a permutation"))
1056+
1057+
cm = common(Ti)
1058+
1059+
# Hack! makes it a llt
1060+
cm[common_final_ll] = reinterpret(UInt8, [one(Cint)])
1061+
1062+
F = analyze_p2(true, A, convert(Vector{Ti}, perm-1), convert(Vector{Ti}, [0:sA.ncol-1;]), cm)
1063+
factorize!(A, F, cm)
1064+
1065+
s = unsafe_load(F.p)
1066+
s.minor < size(A, 1) && throw(Base.LinAlg.PosDefException(s.minor))
1067+
return F
1068+
end
1069+
cholfact(A::SparseMatrixCSC, perm) = cholfact(Sparse(A), perm)
1070+
9461071
function ldltfact(A::Sparse)
9471072
sA = unsafe_load(A.p)
9481073
sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian"))
@@ -987,6 +1112,30 @@ function ldltfact{Tv<:VTypes,Ti<:ITypes}(A::Sparse{Tv,Ti}, β::Tv)
9871112
return F
9881113
end
9891114

1115+
function ldltfact{Tv<:VTypes,Ti<:ITypes,Tii<:Integer}(A::Sparse{Tv,Ti}, perm::Vector{Tii})
1116+
sA = unsafe_load(A.p)
1117+
sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian"))
1118+
isperm(perm) || throw(ArgumentError("perm is not a permutation"))
1119+
1120+
cm = common(indtype(A))
1121+
1122+
# Hack! makes it a ldlt
1123+
cm[common_final_ll] = reinterpret(UInt8, [zero(Cint)])
1124+
1125+
# Hack! really make sure it's a ldlt by avoiding supernodal factorisation
1126+
cm[common_supernodal] = reinterpret(UInt8, [zero(Cint)])
1127+
1128+
F = analyze_p2(true, A, convert(Vector{Ti}, perm-1), convert(Vector{Ti}, [0:sA.ncol-1;]), cm)
1129+
factorize!(A, F, cm)
1130+
1131+
# Check if decomposition failed
1132+
s = unsafe_load(F.p)
1133+
s.minor < size(A, 1) && throw(Base.LinAlg.ArgumentError("matrix has one or more zero pivots"))
1134+
1135+
return F
1136+
end
1137+
ldltfact(A::SparseMatrixCSC, perm) = ldltfact(Sparse(A), perm)
1138+
9901139
cholfact{T<:VTypes}(A::Sparse{T}, β::Number) = cholfact(A, convert(T, β))
9911140
cholfact(A::SparseMatrixCSC) = cholfact(Sparse(A))
9921141
cholfact(A::SparseMatrixCSC, β::Number) = cholfact(Sparse(A), β)
@@ -1024,16 +1173,63 @@ update!{T<:VTypes}(F::Factor{T}, A::SparseMatrixCSC{T}, β::Number) = update!(F,
10241173

10251174
## Solvers
10261175

1176+
# Solve Lx = b and L'x=b where A = L*L'
1177+
function (\){T,Ti}(L::FactorComponent{T,Ti,:L}, B::Union(Dense,Sparse))
1178+
solve(CHOLMOD_L, Factor(L), B)
1179+
end
1180+
function (\){T,Ti}(L::FactorComponent{T,Ti,:U}, B::Union(Dense,Sparse))
1181+
solve(CHOLMOD_Lt, Factor(L), B)
1182+
end
1183+
# Solve PLx = b and L'P'x=b where A = P*L*L'*P'
1184+
function (\){T,Ti}(L::FactorComponent{T,Ti,:PL}, B::Union(Dense,Sparse))
1185+
F = Factor(L)
1186+
solve(CHOLMOD_L, F, solve(CHOLMOD_Pt, F, B)) # Confusingly, CHOLMOD_Pt solves Px = b
1187+
end
1188+
function (\){T,Ti}(L::FactorComponent{T,Ti,:UPt}, B::Union(Dense,Sparse))
1189+
F = Factor(L)
1190+
solve(CHOLMOD_P, F, solve(CHOLMOD_Lt, F, B))
1191+
end
1192+
# Solve various equations for A = L*D*L' and A = P*L*D*L'*P'
1193+
function (\){T,Ti}(L::FactorComponent{T,Ti,:D}, B::Union(Dense,Sparse))
1194+
solve(CHOLMOD_D, Factor(L), B)
1195+
end
1196+
function (\){T,Ti}(L::FactorComponent{T,Ti,:LD}, B::Union(Dense,Sparse))
1197+
solve(CHOLMOD_LD, Factor(L), B)
1198+
end
1199+
function (\){T,Ti}(L::FactorComponent{T,Ti,:DU}, B::Union(Dense,Sparse))
1200+
solve(CHOLMOD_DLt, Factor(L), B)
1201+
end
1202+
function (\){T,Ti}(L::FactorComponent{T,Ti,:PLD}, B::Union(Dense,Sparse))
1203+
F = Factor(L)
1204+
solve(CHOLMOD_LD, F, solve(CHOLMOD_Pt, F, B))
1205+
end
1206+
function (\){T,Ti}(L::FactorComponent{T,Ti,:DUPt}, B::Union(Dense,Sparse))
1207+
F = Factor(L)
1208+
solve(CHOLMOD_P, F, solve(CHOLMOD_DLt, F, B))
1209+
end
1210+
1211+
function (\)(L::FactorComponent, b::Vector)
1212+
reshape(convert(Matrix, L\Dense(b)), length(b))
1213+
end
1214+
function (\)(L::FactorComponent, B::Matrix)
1215+
convert(Matrix, L\Dense(B))
1216+
end
1217+
function (\)(L::FactorComponent, B::SparseMatrixCSC)
1218+
sparse(L\Sparse(B,0))
1219+
end
1220+
1221+
Ac_ldiv_B(L::FactorComponent, B) = ctranspose(L)\B
1222+
10271223
(\)(L::Factor, B::Dense) = solve(CHOLMOD_A, L, B)
10281224
(\)(L::Factor, b::Vector) = reshape(convert(Matrix, solve(CHOLMOD_A, L, Dense(b))), length(b))
10291225
(\)(L::Factor, B::Matrix) = convert(Matrix, solve(CHOLMOD_A, L, Dense(B)))
1030-
(\)(L::Factor, B::Sparse) = spsolve(CHOLMOD_A, L, B)
1226+
(\)(L::Factor, B::Sparse) = solve(CHOLMOD_A, L, B)
10311227
# When right hand side is sparse, we have to ensure that the rhs is not marked as symmetric.
1032-
(\)(L::Factor, B::SparseMatrixCSC) = sparse(spsolve(CHOLMOD_A, L, Sparse(B, 0)))
1228+
(\)(L::Factor, B::SparseMatrixCSC) = sparse(solve(CHOLMOD_A, L, Sparse(B, 0)))
10331229

10341230
Ac_ldiv_B(L::Factor, B::Dense) = solve(CHOLMOD_A, L, B)
10351231
Ac_ldiv_B(L::Factor, B::VecOrMat) = convert(Matrix, solve(CHOLMOD_A, L, Dense(B)))
1036-
Ac_ldiv_B(L::Factor, B::Sparse) = spsolve(CHOLMOD_A, L, B)
1232+
Ac_ldiv_B(L::Factor, B::Sparse) = solve(CHOLMOD_A, L, B)
10371233
Ac_ldiv_B(L::Factor, B::SparseMatrixCSC) = Ac_ldiv_B(L, Sparse(B))
10381234

10391235
## Other convenience methods

0 commit comments

Comments
 (0)