# This file is a part of Julia. License is MIT: https://julialang.org/license module CHOLMOD import Base: (*), convert, copy, eltype, get, getindex, show, size, IndexStyle, IndexLinear, IndexCartesian, ctranspose import Base.LinAlg: (\), A_mul_Bc, A_mul_Bt, Ac_ldiv_B, Ac_mul_B, At_ldiv_B, At_mul_B, cholfact, cholfact!, det, diag, ishermitian, isposdef, issymmetric, ldltfact, ldltfact!, logdet importall ..SparseArrays export Dense, Factor, Sparse import ..SparseArrays: AbstractSparseMatrix, SparseMatrixCSC, increment, indtype ######### # Setup # ######### include("cholmod_h.jl") const CHOLMOD_MIN_VERSION = v"2.1.1" ### These offsets are defined in SuiteSparse_wrapper.c const common_size = ccall((:jl_cholmod_common_size,:libsuitesparse_wrapper),Int,()) const cholmod_com_offsets = Vector{Csize_t}(19) ccall((:jl_cholmod_common_offsets, :libsuitesparse_wrapper), Void, (Ptr{Csize_t},), cholmod_com_offsets) ## macro to generate the name of the C function according to the integer type macro cholmod_name(nm, typ) string("cholmod_", eval(typ) == SuiteSparse_long ? "l_" : "", nm) end function start(a::Vector{UInt8}) @isok ccall((@cholmod_name("start", SuiteSparse_long), :libcholmod), Cint, (Ptr{UInt8},), a) return a end function finish(a::Vector{UInt8}) @isok ccall((@cholmod_name("finish", SuiteSparse_long), :libcholmod), Cint, (Ptr{UInt8},), a) return a end function defaults(a::Vector{UInt8}) @isok ccall((@cholmod_name("defaults", SuiteSparse_long), :libcholmod), Cint, (Ptr{UInt8},), a) return a end common() = commonStruct const build_version_array = Vector{Cint}(3) ccall((:jl_cholmod_version, :libsuitesparse_wrapper), Cint, (Ptr{Cint},), build_version_array) const build_version = VersionNumber(build_version_array...) function __init__() try ### Check if the linked library is compatible with the Julia code if Libdl.dlsym_e(Libdl.dlopen("libcholmod"), :cholmod_version) != C_NULL current_version_array = Vector{Cint}(3) ccall((:cholmod_version, :libcholmod), Cint, (Ptr{Cint},), current_version_array) current_version = VersionNumber(current_version_array...) else # CHOLMOD < 2.1.1 does not include cholmod_version() current_version = v"0.0.0" end if current_version < CHOLMOD_MIN_VERSION warn(""" CHOLMOD version incompatibility Julia was compiled with CHOLMOD version $build_version. It is currently linked with a version older than $(CHOLMOD_MIN_VERSION). This might cause Julia to terminate when working with sparse matrix factorizations, e.g. solving systems of equations with \\. It is recommended that you use Julia with a recent version of CHOLMOD, or download the generic binaries from www.julialang.org, which ship with the correct versions of all dependencies. """) elseif build_version_array[1] != current_version_array[1] warn(""" CHOLMOD version incompatibility Julia was compiled with CHOLMOD version $build_version. It is currently linked with version $current_version. This might cause Julia to terminate when working with sparse matrix factorizations, e.g. solving systems of equations with \\. It is recommended that you use Julia with the same major version of CHOLMOD as the one used during the build, or download the generic binaries from www.julialang.org, which ship with the correct versions of all dependencies. """) end intsize = Int(ccall((:jl_cholmod_sizeof_long,:libsuitesparse_wrapper),Csize_t,())) if intsize != 4length(IndexTypes) warn(""" CHOLMOD integer size incompatibility Julia was compiled with a version of CHOLMOD that supported $(32length(IndexTypes)) bit integers. It is currently linked with version that supports $(8intsize) integers. This might cause Julia to terminate when working with sparse matrix factorizations, e.g. solving systems of equations with \\. This problem can be fixed by modifying the Julia build configuration or by downloading the OS X or generic Linux binary from www.julialang.org, which include the correct versions of all dependencies. """) end ### Initiate CHOLMOD ### The common struct. Controls the type of factorization and keeps pointers ### to temporary memory. global const commonStruct = fill(0xff, common_size) global const common_supernodal = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[4] + 1)) global const common_final_ll = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[7] + 1)) global const common_print = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[13] + 1)) global const common_itype = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[18] + 1)) global const common_dtype = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[19] + 1)) global const common_nmethods = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[15] + 1)) global const common_postorder = convert(Ptr{Cint}, pointer(commonStruct, cholmod_com_offsets[17] + 1)) start(commonStruct) # initializes CHOLMOD set_print_level(commonStruct, 0) # no printing from CHOLMOD by default # Register gc tracked allocator if CHOLMOD is new enough if current_version >= v"3.0.0" cnfg = cglobal((:SuiteSparse_config, :libsuitesparseconfig), Ptr{Void}) unsafe_store!(cnfg, cglobal(:jl_malloc, Ptr{Void}), 1) unsafe_store!(cnfg, cglobal(:jl_calloc, Ptr{Void}), 2) unsafe_store!(cnfg, cglobal(:jl_realloc, Ptr{Void}), 3) unsafe_store!(cnfg, cglobal(:jl_free, Ptr{Void}), 4) end catch ex Base.showerror_nostdio(ex, "WARNING: Error during initialization of module CHOLMOD") end end function set_print_level(cm::Array{UInt8}, lev::Integer) global common_print unsafe_store!(common_print, lev) end #################### # Type definitions # #################### abstract type SuiteSparseStruct end # The three core data types for CHOLMOD: Dense, Sparse and Factor. # CHOLMOD manages the memory, so the Julia versions only wrap a # pointer to a struct. Therefore finalizers should be registered each # time a pointer is returned from CHOLMOD. # Dense struct C_Dense{T<:VTypes} <: SuiteSparseStruct nrow::Csize_t ncol::Csize_t nzmax::Csize_t d::Csize_t x::Ptr{T} z::Ptr{Void} xtype::Cint dtype::Cint end mutable struct Dense{T<:VTypes} <: DenseMatrix{T} p::Ptr{C_Dense{T}} end # Sparse struct C_Sparse{Tv<:VTypes} <: SuiteSparseStruct nrow::Csize_t ncol::Csize_t nzmax::Csize_t p::Ptr{SuiteSparse_long} i::Ptr{SuiteSparse_long} nz::Ptr{SuiteSparse_long} x::Ptr{Tv} z::Ptr{Void} stype::Cint itype::Cint xtype::Cint dtype::Cint sorted::Cint packed::Cint end # Corresponds to the exact definition of cholmod_sparse_struct in the library. # Useful when reading matrices of unknown type from files as in # cholmod_read_sparse struct C_SparseVoid <: SuiteSparseStruct nrow::Csize_t ncol::Csize_t nzmax::Csize_t p::Ptr{Void} i::Ptr{Void} nz::Ptr{Void} x::Ptr{Void} z::Ptr{Void} stype::Cint itype::Cint xtype::Cint dtype::Cint sorted::Cint packed::Cint end mutable struct Sparse{Tv<:VTypes} <: AbstractSparseMatrix{Tv,SuiteSparse_long} p::Ptr{C_Sparse{Tv}} function Sparse{Tv}(p::Ptr{C_Sparse{Tv}}) where Tv<:VTypes if p == C_NULL throw(ArgumentError("sparse matrix construction failed for " * "unknown reasons. Please submit a bug report.")) end new(p) end end Sparse(p::Ptr{C_Sparse{Tv}}) where {Tv<:VTypes} = Sparse{Tv}(p) # Factor if build_version >= v"2.1.0" # CHOLMOD version 2.1.0 or later struct C_Factor{Tv<:VTypes} <: SuiteSparseStruct n::Csize_t minor::Csize_t Perm::Ptr{SuiteSparse_long} ColCount::Ptr{SuiteSparse_long} IPerm::Ptr{SuiteSparse_long} # this pointer was added in verison 2.1.0 nzmax::Csize_t p::Ptr{SuiteSparse_long} i::Ptr{SuiteSparse_long} x::Ptr{Tv} z::Ptr{Void} nz::Ptr{SuiteSparse_long} next::Ptr{SuiteSparse_long} prev::Ptr{SuiteSparse_long} nsuper::Csize_t ssize::Csize_t xsize::Csize_t maxcsize::Csize_t maxesize::Csize_t super::Ptr{SuiteSparse_long} pi::Ptr{SuiteSparse_long} px::Ptr{SuiteSparse_long} s::Ptr{SuiteSparse_long} ordering::Cint is_ll::Cint is_super::Cint is_monotonic::Cint itype::Cint xtype::Cint dtype::Cint end else struct C_Factor{Tv<:VTypes} <: SuiteSparseStruct n::Csize_t minor::Csize_t Perm::Ptr{SuiteSparse_long} ColCount::Ptr{SuiteSparse_long} nzmax::Csize_t p::Ptr{SuiteSparse_long} i::Ptr{SuiteSparse_long} x::Ptr{Tv} z::Ptr{Void} nz::Ptr{SuiteSparse_long} next::Ptr{SuiteSparse_long} prev::Ptr{SuiteSparse_long} nsuper::Csize_t ssize::Csize_t xsize::Csize_t maxcsize::Csize_t maxesize::Csize_t super::Ptr{SuiteSparse_long} pi::Ptr{SuiteSparse_long} px::Ptr{SuiteSparse_long} s::Ptr{SuiteSparse_long} ordering::Cint is_ll::Cint is_super::Cint is_monotonic::Cint itype::Cint xtype::Cint dtype::Cint end end mutable struct Factor{Tv} <: Factorization{Tv} p::Ptr{C_Factor{Tv}} function Factor{Tv}(p::Ptr{C_Factor{Tv}}) where Tv if p == C_NULL throw(ArgumentError("factorization construction failed for " * "unknown reasons. Please submit a bug report.")) end new(p) end end Factor(p::Ptr{C_Factor{Tv}}) where {Tv<:VTypes} = Factor{Tv}(p) # Define get similar to get(Nullable) to check pointers. All pointer loads # should be wrapped in get to make sure that SuiteSparse is not called with # a C_NULL pointer which could cause a segfault. Pointers are set to null # when serialized so this can happen when mutiple processes are in use. function get(p::Ptr{T}) where T<:SuiteSparseStruct if p == C_NULL throw(ArgumentError("pointer to the $T object is null. This can " * "happen if the object has been serialized.")) else return p end end # FactorComponent, for encoding particular factors from a factorization mutable struct FactorComponent{Tv,S} <: AbstractMatrix{Tv} F::Factor{Tv} function FactorComponent{Tv,S}(F::Factor{Tv}) where {Tv,S} s = unsafe_load(get(F.p)) if s.is_ll != 0 if !(S == :L || S == :U || S == :PtL || S == :UP) throw(CHOLMODException(string(S, " not supported for sparse ", "LLt matrices; try :L, :U, :PtL, or :UP"))) end elseif !(S == :L || S == :U || S == :PtL || S == :UP || S == :D || S == :LD || S == :DU || S == :PtLD || S == :DUP) throw(CHOLMODException(string(S, " not supported for sparse LDLt ", "matrices; try :L, :U, :PtL, :UP, :D, :LD, :DU, :PtLD, or :DUP"))) end new(F) end end function FactorComponent(F::Factor{Tv}, sym::Symbol) where Tv FactorComponent{Tv,sym}(F) end Factor(FC::FactorComponent) = Factor(FC.F) ################# # Thin wrappers # ################# # Dense wrappers ## Note! Integer type defaults to Cint, but this is actually not necessary, but ## making this a choice would require another type parameter in the Dense type ### cholmod_core_h ### function allocate_dense(nrow::Integer, ncol::Integer, d::Integer, ::Type{Float64}) d = Dense(ccall((:cholmod_l_allocate_dense, :libcholmod), Ptr{C_Dense{Float64}}, (Csize_t, Csize_t, Csize_t, Cint, Ptr{Void}), nrow, ncol, d, REAL, common())) finalizer(d, free!) d end function allocate_dense(nrow::Integer, ncol::Integer, d::Integer, ::Type{Complex{Float64}}) d = Dense(ccall((:cholmod_l_allocate_dense, :libcholmod), Ptr{C_Dense{Complex{Float64}}}, (Csize_t, Csize_t, Csize_t, Cint, Ptr{Void}), nrow, ncol, d, COMPLEX, common())) finalizer(d, free!) d end free_dense!(p::Ptr{C_Dense{T}}) where {T} = ccall((:cholmod_l_free_dense, :libcholmod), Cint, (Ref{Ptr{C_Dense{T}}}, Ptr{Void}), p, common()) function zeros(m::Integer, n::Integer, ::Type{T}) where T<:VTypes d = Dense(ccall((:cholmod_l_zeros, :libcholmod), Ptr{C_Dense{T}}, (Csize_t, Csize_t, Cint, Ptr{UInt8}), m, n, xtyp(T), common())) finalizer(d, free!) d end zeros(m::Integer, n::Integer) = zeros(m, n, Float64) function ones(m::Integer, n::Integer, ::Type{T}) where T<:VTypes d = Dense(ccall((:cholmod_l_ones, :libcholmod), Ptr{C_Dense{T}}, (Csize_t, Csize_t, Cint, Ptr{UInt8}), m, n, xtyp(T), common())) finalizer(d, free!) d end ones(m::Integer, n::Integer) = ones(m, n, Float64) function eye(m::Integer, n::Integer, ::Type{T}) where T<:VTypes d = Dense(ccall((:cholmod_l_eye, :libcholmod), Ptr{C_Dense{T}}, (Csize_t, Csize_t, Cint, Ptr{UInt8}), m, n, xtyp(T), common())) finalizer(d, free!) d end eye(m::Integer, n::Integer) = eye(m, n, Float64) eye(n::Integer) = eye(n, n, Float64) function copy_dense(A::Dense{Tv}) where Tv<:VTypes d = Dense(ccall((:cholmod_l_copy_dense, :libcholmod), Ptr{C_Dense{Tv}}, (Ptr{C_Dense{Tv}}, Ptr{UInt8}), get(A.p), common())) finalizer(d, free!) d end function sort!(S::Sparse{Tv}) where Tv<:VTypes @isok ccall((:cholmod_l_sort, :libcholmod), SuiteSparse_long, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(S.p), common()) return S end ### cholmod_matrixops.h ### function norm_dense(D::Dense{Tv}, p::Integer) where Tv<:VTypes s = unsafe_load(get(D.p)) if p == 2 if s.ncol > 1 throw(ArgumentError("2 norm only supported when matrix has one column")) end elseif p != 0 && p != 1 throw(ArgumentError("second argument must be either 0 (Inf norm), 1, or 2")) end ccall((:cholmod_l_norm_dense, :libcholmod), Cdouble, (Ptr{C_Dense{Tv}}, Cint, Ptr{UInt8}), get(D.p), p, common()) end ### cholmod_check.h ### function check_dense(A::Dense{T}) where T<:VTypes ccall((:cholmod_l_check_dense, :libcholmod), Cint, (Ptr{C_Dense{T}}, Ptr{UInt8}), A.p, common())!=0 end # Non-Dense wrappers ### cholmod_core.h ### function allocate_sparse(nrow::Integer, ncol::Integer, nzmax::Integer, sorted::Bool, packed::Bool, stype::Integer, ::Type{Float64}) s = Sparse(ccall((@cholmod_name("allocate_sparse", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Float64}}, (Csize_t, Csize_t, Csize_t, Cint, Cint, Cint, Cint, Ptr{Void}), nrow, ncol, nzmax, sorted, packed, stype, REAL, common())) finalizer(s, free!) s end function allocate_sparse(nrow::Integer, ncol::Integer, nzmax::Integer, sorted::Bool, packed::Bool, stype::Integer, ::Type{Complex{Float64}}) s = Sparse(ccall((@cholmod_name("allocate_sparse", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Complex{Float64}}}, (Csize_t, Csize_t, Csize_t, Cint, Cint, Cint, Cint, Ptr{Void}), nrow, ncol, nzmax, sorted, packed, stype, COMPLEX, common())) finalizer(s, free!) s end function free_sparse!(ptr::Ptr{C_Sparse{Tv}}) where Tv<:VTypes @isok ccall((@cholmod_name("free_sparse", SuiteSparse_long), :libcholmod), Cint, (Ptr{Ptr{C_Sparse{Tv}}}, Ptr{UInt8}), &ptr, common()) end function free_sparse!(ptr::Ptr{C_SparseVoid}) @isok ccall((@cholmod_name("free_sparse", SuiteSparse_long), :libcholmod), Cint, (Ptr{Ptr{C_SparseVoid}}, Ptr{UInt8}), &ptr, common()) end function free_factor!(ptr::Ptr{C_Factor{Tv}}) where Tv<:VTypes # Warning! Important that finalizer doesn't modify the global Common struct. @isok ccall((@cholmod_name("free_factor", SuiteSparse_long), :libcholmod), Cint, (Ptr{Ptr{C_Factor{Tv}}}, Ptr{Void}), &ptr, common()) end function aat(A::Sparse{Tv}, fset::Vector{SuiteSparse_long}, mode::Integer) where Tv<:VRealTypes s = Sparse(ccall((@cholmod_name("aat", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{SuiteSparse_long}, Csize_t, Cint, Ptr{UInt8}), get(A.p), fset, length(fset), mode, common())) finalizer(s, free!) s end function sparse_to_dense(A::Sparse{Tv}) where Tv<:VTypes d = Dense(ccall((@cholmod_name("sparse_to_dense", SuiteSparse_long),:libcholmod), Ptr{C_Dense{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(A.p), common())) finalizer(d, free!) d end function dense_to_sparse(D::Dense{Tv}, ::Type{SuiteSparse_long}) where Tv<:VTypes s = Sparse(ccall((@cholmod_name("dense_to_sparse", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Dense{Tv}}, Cint, Ptr{UInt8}), get(D.p), true, common())) finalizer(s, free!) s end function factor_to_sparse!(F::Factor{Tv}) where Tv<:VTypes ss = unsafe_load(F.p) ss.xtype > PATTERN || throw(CHOLMODException("only numeric factors are supported")) s = Sparse(ccall((@cholmod_name("factor_to_sparse", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Factor{Tv}}, Ptr{UInt8}), get(F.p), common())) finalizer(s, free!) s end function change_factor!(::Type{Float64}, to_ll::Bool, to_super::Bool, to_packed::Bool, to_monotonic::Bool, F::Factor{Tv}) where Tv<:VTypes @isok ccall((@cholmod_name("change_factor", SuiteSparse_long),:libcholmod), Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{C_Factor{Tv}}, Ptr{UInt8}), REAL, to_ll, to_super, to_packed, to_monotonic, get(F.p), common()) Factor{Float64}(F.p) end function change_factor!(::Type{Complex{Float64}}, to_ll::Bool, to_super::Bool, to_packed::Bool, to_monotonic::Bool, F::Factor{Tv}) where Tv<:VTypes @isok ccall((@cholmod_name("change_factor", SuiteSparse_long),:libcholmod), Cint, (Cint, Cint, Cint, Cint, Cint, Ptr{C_Factor{Tv}}, Ptr{UInt8}), COMPLEX, to_ll, to_super, to_packed, to_monotonic, get(F.p), common()) Factor{Complex{Float64}}(F.p) end function check_sparse(A::Sparse{Tv}) where Tv<:VTypes ccall((@cholmod_name("check_sparse", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(A.p), common())!=0 end function check_factor(F::Factor{Tv}) where Tv<:VTypes ccall((@cholmod_name("check_factor", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Factor{Tv}}, Ptr{UInt8}), get(F.p), common())!=0 end function nnz{Tv<:VTypes}(A::Sparse{Tv}) ccall((@cholmod_name("nnz", SuiteSparse_long),:libcholmod), Int, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(A.p), common()) end function speye(m::Integer, n::Integer, ::Type{Tv}) where Tv<:VTypes s = Sparse(ccall((@cholmod_name("speye", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Tv}}, (Csize_t, Csize_t, Cint, Ptr{UInt8}), m, n, xtyp(Tv), common())) finalizer(s, free!) s end function spzeros(m::Integer, n::Integer, nzmax::Integer, ::Type{Tv}) where Tv<:VTypes s = Sparse(ccall((@cholmod_name("spzeros", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Tv}}, (Csize_t, Csize_t, Csize_t, Cint, Ptr{UInt8}), m, n, nzmax, xtyp(Tv), common())) finalizer(s, free!) s end function transpose_(A::Sparse{Tv}, values::Integer) where Tv<:VTypes s = Sparse(ccall((@cholmod_name("transpose", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Cint, Ptr{UInt8}), get(A.p), values, common())) finalizer(s, free!) s end function copy_factor(F::Factor{Tv}) where Tv<:VTypes f = Factor(ccall((@cholmod_name("copy_factor", SuiteSparse_long),:libcholmod), Ptr{C_Factor{Tv}}, (Ptr{C_Factor{Tv}}, Ptr{UInt8}), get(F.p), common())) finalizer(f, free!) f end function copy_sparse(A::Sparse{Tv}) where Tv<:VTypes s = Sparse(ccall((@cholmod_name("copy_sparse", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(A.p), common())) finalizer(s, free!) s end function copy(A::Sparse{Tv}, stype::Integer, mode::Integer) where Tv<:VRealTypes s = Sparse(ccall((@cholmod_name("copy", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Cint, Cint, Ptr{UInt8}), get(A.p), stype, mode, common())) finalizer(s, free!) s end ### cholmod_check.h ### function print_sparse(A::Sparse{Tv}, name::String) where Tv<:VTypes isascii(name) || error("non-ASCII name: $name") cm = common() set_print_level(cm, 3) @isok ccall((@cholmod_name("print_sparse", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}, Ptr{UInt8}), get(A.p), name, cm) nothing end function print_factor(F::Factor{Tv}, name::String) where Tv<:VTypes cm = common() set_print_level(cm, 3) @isok ccall((@cholmod_name("print_factor", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Factor{Tv}}, Ptr{UInt8}, Ptr{UInt8}), get(F.p), name, cm) nothing end ### cholmod_matrixops.h ### function ssmult(A::Sparse{Tv}, B::Sparse{Tv}, stype::Integer, values::Bool, sorted::Bool) where Tv<:VRealTypes lA = unsafe_load(get(A.p)) lB = unsafe_load(get(B.p)) if lA.ncol != lB.nrow throw(DimensionMismatch("inner matrix dimensions do not fit")) end s = Sparse(ccall((@cholmod_name("ssmult", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{C_Sparse{Tv}}, Cint, Cint, Cint, Ptr{UInt8}), get(A.p), get(B.p), stype, values, sorted, common())) finalizer(s, free!) s end function norm_sparse(A::Sparse{Tv}, norm::Integer) where Tv<:VTypes if norm != 0 && norm != 1 throw(ArgumentError("norm argument must be either 0 or 1")) end ccall((@cholmod_name("norm_sparse", SuiteSparse_long), :libcholmod), Cdouble, (Ptr{C_Sparse{Tv}}, Cint, Ptr{UInt8}), get(A.p), norm, common()) end function horzcat(A::Sparse{Tv}, B::Sparse{Tv}, values::Bool) where Tv<:VRealTypes s = Sparse(ccall((@cholmod_name("horzcat", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{C_Sparse{Tv}}, Cint, Ptr{UInt8}), get(A.p), get(B.p), values, common())) finalizer(s, free!) s end function scale!(S::Dense{Tv}, scale::Integer, A::Sparse{Tv}) where Tv<:VRealTypes sS = unsafe_load(get(S.p)) sA = unsafe_load(get(A.p)) if sS.ncol != 1 && sS.nrow != 1 throw(DimensionMismatch("first argument must be a vector")) end if scale == SCALAR && sS.nrow != 1 throw(DimensionMismatch("scaling argument must have length one")) elseif scale == ROW && sS.nrow*sS.ncol != sA.nrow throw(DimensionMismatch("scaling vector has length $(sS.nrow*sS.ncol), " * "but matrix has $(sA.nrow) rows.")) elseif scale == COL && sS.nrow*sS.ncol != sA.ncol throw(DimensionMismatch("scaling vector has length $(sS.nrow*sS.ncol), " * "but matrix has $(sA.ncol) columns")) elseif scale == SYM if sA.nrow != sA.ncol throw(DimensionMismatch("matrix must be square")) elseif sS.nrow*sS.ncol != sA.nrow throw(DimensionMismatch("scaling vector has length $(sS.nrow*sS.ncol), " * "but matrix has $(sA.ncol) columns and rows")) end end sA = unsafe_load(get(A.p)) @isok ccall((@cholmod_name("scale",SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Dense{Tv}}, Cint, Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(S.p), scale, get(A.p), common()) A end function sdmult!(A::Sparse{Tv}, transpose::Bool, α::Number, β::Number, X::Dense{Tv}, Y::Dense{Tv}) where Tv<:VTypes m, n = size(A) nc = transpose ? m : n nr = transpose ? n : m if nc != size(X, 1) throw(DimensionMismatch("incompatible dimensions, $nc and $(size(X,1))")) end @isok ccall((@cholmod_name("sdmult", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Cint, Ref{Complex128}, Ref{Complex128}, Ptr{C_Dense{Tv}}, Ptr{C_Dense{Tv}}, Ptr{UInt8}), get(A.p), transpose, α, β, get(X.p), get(Y.p), common()) Y end function vertcat(A::Sparse{Tv}, B::Sparse{Tv}, values::Bool) where Tv<:VRealTypes s = Sparse(ccall((@cholmod_name("vertcat", SuiteSparse_long), :libcholmod), Ptr{C_Sparse{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{C_Sparse{Tv}}, Cint, Ptr{UInt8}), get(A.p), get(B.p), values, common())) finalizer(s, free!) s end function symmetry(A::Sparse{Tv}, option::Integer) where Tv<:VTypes xmatched = Ref{SuiteSparse_long}() pmatched = Ref{SuiteSparse_long}() nzoffdiag = Ref{SuiteSparse_long}() nzdiag = Ref{SuiteSparse_long}() rv = ccall((@cholmod_name("symmetry", SuiteSparse_long), :libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Cint, Ptr{SuiteSparse_long}, Ptr{SuiteSparse_long}, Ptr{SuiteSparse_long}, Ptr{SuiteSparse_long}, Ptr{UInt8}), get(A.p), option, xmatched, pmatched, nzoffdiag, nzdiag, common()) rv, xmatched[], pmatched[], nzoffdiag[], nzdiag[] end # cholmod_cholesky.h # For analyze, analyze_p, and factorize_p!, the Common argument must be # supplied in order to control if the factorization is LLt or LDLt function analyze{Tv<:VTypes}(A::Sparse{Tv}, cmmn::Vector{UInt8}) f = Factor(ccall((@cholmod_name("analyze", SuiteSparse_long),:libcholmod), Ptr{C_Factor{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(A.p), cmmn)) finalizer(f, free!) f end function analyze_p{Tv<:VTypes}(A::Sparse{Tv}, perm::Vector{SuiteSparse_long}, cmmn::Vector{UInt8}) length(perm) != size(A,1) && throw(BoundsError()) f = Factor(ccall((@cholmod_name("analyze_p", SuiteSparse_long),:libcholmod), Ptr{C_Factor{Tv}}, (Ptr{C_Sparse{Tv}}, Ptr{SuiteSparse_long}, Ptr{SuiteSparse_long}, Csize_t, Ptr{UInt8}), get(A.p), perm, C_NULL, 0, cmmn)) finalizer(f, free!) f end function factorize!(A::Sparse{Tv}, F::Factor{Tv}, cmmn::Vector{UInt8}) where Tv<:VTypes @isok ccall((@cholmod_name("factorize", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Ptr{C_Factor{Tv}}, Ptr{UInt8}), get(A.p), get(F.p), cmmn) F end function factorize_p!(A::Sparse{Tv}, β::Real, F::Factor{Tv}, cmmn::Vector{UInt8}) where Tv<:VTypes # note that β is passed as a complex number (double beta[2]), # but the CHOLMOD manual says that only beta[0] (real part) is used @isok ccall((@cholmod_name("factorize_p", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Ref{Complex128}, Ptr{SuiteSparse_long}, Csize_t, Ptr{C_Factor{Tv}}, Ptr{UInt8}), get(A.p), β, C_NULL, 0, get(F.p), cmmn) F end function solve(sys::Integer, F::Factor{Tv}, B::Dense{Tv}) where Tv<:VTypes if size(F,1) != size(B,1) 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.")) end d = Dense(ccall((@cholmod_name("solve", SuiteSparse_long),:libcholmod), Ptr{C_Dense{Tv}}, (Cint, Ptr{C_Factor{Tv}}, Ptr{C_Dense{Tv}}, Ptr{UInt8}), sys, get(F.p), get(B.p), common())) finalizer(d, free!) d end function spsolve(sys::Integer, F::Factor{Tv}, B::Sparse{Tv}) where Tv<:VTypes if size(F,1) != size(B,1) 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.")) end s = Sparse(ccall((@cholmod_name("spsolve", SuiteSparse_long),:libcholmod), Ptr{C_Sparse{Tv}}, (Cint, Ptr{C_Factor{Tv}}, Ptr{C_Sparse{Tv}}, Ptr{UInt8}), sys, get(F.p), get(B.p), common())) finalizer(s, free!) s end # Autodetects the types function read_sparse(file::Libc.FILE, ::Type{SuiteSparse_long}) ptr = ccall((@cholmod_name("read_sparse", SuiteSparse_long), :libcholmod), Ptr{C_SparseVoid}, (Ptr{Void}, Ptr{UInt8}), file.ptr, common()) if ptr == C_NULL throw(ArgumentError("sparse matrix construction failed. Check that input file is valid.")) end s = Sparse(ptr) finalizer(s, free!) s end function read_sparse(file::IO, T) cfile = Libc.FILE(file) try return read_sparse(cfile, T) finally close(cfile) end end function get_perm(F::Factor) s = unsafe_load(get(F.p)) p = unsafe_wrap(Array, s.Perm, s.n, false) p+1 end get_perm(FC::FactorComponent) = get_perm(Factor(FC)) ######################### # High level interfaces # ######################### # Convertion/construction function convert(::Type{Dense{T}}, A::StridedVecOrMat) where T<:VTypes d = allocate_dense(size(A, 1), size(A, 2), stride(A, 2), T) s = unsafe_load(d.p) for i in eachindex(A) unsafe_store!(s.x, A[i], i) end d end function convert(::Type{Dense}, A::StridedVecOrMat) T = promote_type(eltype(A), Float64) return convert(Dense{T}, A) end convert(::Type{Dense}, A::Sparse) = sparse_to_dense(A) # This constructior assumes zero based colptr and rowval function Sparse(m::Integer, n::Integer, colptr0::Vector{SuiteSparse_long}, rowval0::Vector{SuiteSparse_long}, nzval::Vector{Tv}, stype) where Tv<:VTypes # checks ## length of input if length(colptr0) <= n throw(ArgumentError("length of colptr0 must be at least n + 1 = $(n + 1) but was $(length(colptr0))")) end if colptr0[n + 1] > length(rowval0) throw(ArgumentError("length of rowval0 is $(length(rowval0)) but value of colptr0 requires length to be at least $(colptr0[n + 1])")) end if colptr0[n + 1] > length(nzval) throw(ArgumentError("length of nzval is $(length(nzval)) but value of colptr0 requires length to be at least $(colptr0[n + 1])")) end ## columns are sorted iss = true for i = 2:length(colptr0) if !issorted(view(rowval0, colptr0[i - 1] + 1:colptr0[i])) iss = false break end end o = allocate_sparse(m, n, colptr0[n + 1], iss, true, stype, Tv) s = unsafe_load(o.p) unsafe_copy!(s.p, pointer(colptr0), n + 1) unsafe_copy!(s.i, pointer(rowval0), colptr0[n + 1]) unsafe_copy!(s.x, pointer(nzval) , colptr0[n + 1]) @isok check_sparse(o) return o end function Sparse(m::Integer, n::Integer, colptr0::Vector{SuiteSparse_long}, rowval0::Vector{SuiteSparse_long}, nzval::Vector{<:VTypes}) o = Sparse(m, n, colptr0, rowval0, nzval, 0) # sort indices sort!(o) # check if array is symmetric and change stype if it is if ishermitian(o) change_stype!(o, -1) end o end function Sparse(A::SparseMatrixCSC{Tv,SuiteSparse_long}, stype::Integer) where Tv<:VTypes ## Check length of input. This should never fail but see #20024 if length(A.colptr) <= A.n throw(ArgumentError("length of colptr must be at least size(A,2) + 1 = $(A.n + 1) but was $(length(A.colptr))")) end if nnz(A) > length(A.rowval) throw(ArgumentError("length of rowval is $(length(A.rowval)) but value of colptr requires length to be at least $(nnz(A))")) end if nnz(A) > length(A.nzval) throw(ArgumentError("length of nzval is $(length(A.nzval)) but value of colptr requires length to be at least $(nnz(A))")) end o = allocate_sparse(A.m, A.n, nnz(A), true, true, stype, Tv) s = unsafe_load(o.p) for i = 1:(A.n + 1) unsafe_store!(s.p, A.colptr[i] - 1, i) end for i = 1:nnz(A) unsafe_store!(s.i, A.rowval[i] - 1, i) end unsafe_copy!(s.x, pointer(A.nzval), nnz(A)) @isok check_sparse(o) return o end # convert SparseVectors into CHOLMOD Sparse types through a mx1 CSC matrix convert(::Type{Sparse}, A::SparseVector{<:VTypes,SuiteSparse_long}) = convert(Sparse, convert(SparseMatrixCSC, A)) function convert(::Type{Sparse}, A::SparseMatrixCSC{<:VTypes,<:ITypes}) o = Sparse(A, 0) # check if array is symmetric and change stype if it is if ishermitian(o) change_stype!(o, -1) end o end convert(::Type{Sparse}, A::SparseMatrixCSC{Complex{Float32},<:ITypes}) = convert(Sparse, convert(SparseMatrixCSC{Complex{Float64},SuiteSparse_long}, A)) convert(::Type{Sparse}, A::Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}) = Sparse(A.data, A.uplo == 'L' ? -1 : 1) convert(::Type{Sparse}, A::Hermitian{Tv,SparseMatrixCSC{Tv,SuiteSparse_long}}) where {Tv<:VTypes} = Sparse(A.data, A.uplo == 'L' ? -1 : 1) function convert{Ti<:ITypes}(::Type{Sparse}, A::Union{SparseMatrixCSC{BigFloat,Ti}, Symmetric{BigFloat,SparseMatrixCSC{BigFloat,Ti}}, Hermitian{Complex{BigFloat},SparseMatrixCSC{Complex{BigFloat},Ti}}}, args...) throw(MethodError(convert, (Sparse, A))) end function convert(::Type{Sparse}, A::Union{SparseMatrixCSC{T,Ti}, Symmetric{T,SparseMatrixCSC{T,Ti}}, Hermitian{T,SparseMatrixCSC{T,Ti}}}, args...) where T where Ti<:ITypes return Sparse(convert(AbstractMatrix{promote_type(Float64, T)}, A), args...) end # Useful when reading in files, but not type stable function convert(::Type{Sparse}, p::Ptr{C_SparseVoid}) if p == C_NULL throw(ArgumentError("sparse matrix construction failed for " * "unknown reasons. Please submit a bug report.")) end s = unsafe_load(p) # Check integer type if s.itype == INT free_sparse!(p) throw(CHOLMODException("the value of itype was $s.itype. " * "Only integer type of $SuiteSparse_long is supported.")) elseif s.itype == INTLONG free_sparse!(p) throw(CHOLMODException("the value of itype was $s.itype. This combination " * "of integer types shouldn't happen. Please submit a bug report.")) elseif s.itype != LONG # must be s.itype == LONG free_sparse!(p) throw(CHOLMODException("illegal value of itype: $s.itype")) end # Check for double or single precision if s.dtype == DOUBLE Tv = Float64 elseif s.dtype == SINGLE # Tv = Float32 # this should be supported at some point free_sparse!(p) throw(CHOLMODException("single precision not supported yet")) else free_sparse!(p) throw(CHOLMODException("illegal value of dtype: $s.dtype")) end # Check for real or complex if s.xtype == COMPLEX Tv = Complex{Tv} elseif s.xtype != REAL free_sparse!(p) throw(CHOLMODException("illegal value of xtype: $s.xtype")) end return Sparse(convert(Ptr{C_Sparse{Tv}}, p)) end convert(::Type{Sparse}, A::Dense) = dense_to_sparse(A, SuiteSparse_long) convert(::Type{Sparse}, L::Factor) = factor_to_sparse!(copy(L)) function (::Type{Sparse})(filename::String) open(filename) do f return read_sparse(f, SuiteSparse_long) end end ## convertion back to base Julia types function convert(::Type{Matrix{T}}, D::Dense{T}) where T s = unsafe_load(D.p) a = Matrix{T}(s.nrow, s.ncol) copy!(a, D) end Base.copy!(dest::Base.PermutedDimsArrays.PermutedDimsArray, src::Dense) = _copy!(dest, src) # ambig Base.copy!(dest::Dense{T}, D::Dense{T}) where {T<:VTypes} = _copy!(dest, D) Base.copy!(dest::AbstractArray{T}, D::Dense{T}) where {T<:VTypes} = _copy!(dest, D) Base.copy!(dest::AbstractArray{T,2}, D::Dense{T}) where {T<:VTypes} = _copy!(dest, D) Base.copy!(dest::AbstractArray, D::Dense) = _copy!(dest, D) function _copy!(dest::AbstractArray, D::Dense) s = unsafe_load(D.p) n = s.nrow*s.ncol n <= length(dest) || throw(BoundsError(dest, n)) if s.d == s.nrow && isa(dest, Array) unsafe_copy!(pointer(dest), s.x, s.d*s.ncol) else k = 0 for j = 1:s.ncol for i = 1:s.nrow dest[k+=1] = unsafe_load(s.x, i + (j - 1)*s.d) end end end dest end convert(::Type{Matrix}, D::Dense{T}) where {T} = convert(Matrix{T}, D) function convert(::Type{Vector{T}}, D::Dense{T}) where T if size(D, 2) > 1 throw(DimensionMismatch("input must be a vector but had $(size(D, 2)) columns")) end copy!(Vector{T}(size(D, 1)), D) end convert(::Type{Vector}, D::Dense{T}) where {T} = convert(Vector{T}, D) function convert(::Type{SparseMatrixCSC{Tv,SuiteSparse_long}}, A::Sparse{Tv}) where Tv s = unsafe_load(A.p) if s.stype != 0 throw(ArgumentError("matrix has stype != 0. Convert to matrix " * "with stype == 0 before converting to SparseMatrixCSC")) end B = SparseMatrixCSC(s.nrow, s.ncol, increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), false)), increment(unsafe_wrap(Array, s.i, (s.nzmax,), false)), copy(unsafe_wrap(Array, s.x, (s.nzmax,), false))) if s.sorted == 0 return SparseArrays.sortSparseMatrixCSC!(B) else return B end end function convert(::Type{Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}}, A::Sparse{Float64}) s = unsafe_load(A.p) if !issymmetric(A) throw(ArgumentError("matrix is not symmetric")) end B = Symmetric(SparseMatrixCSC(s.nrow, s.ncol, increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), false)), increment(unsafe_wrap(Array, s.i, (s.nzmax,), false)), copy(unsafe_wrap(Array, s.x, (s.nzmax,), false))), s.stype > 0 ? :U : :L) if s.sorted == 0 return SparseArrays.sortSparseMatrixCSC!(B.data) else return B end end function convert(::Type{Hermitian{Tv,SparseMatrixCSC{Tv,SuiteSparse_long}}}, A::Sparse{Tv}) where Tv<:VTypes s = unsafe_load(A.p) if !ishermitian(A) throw(ArgumentError("matrix is not Hermitian")) end B = Hermitian(SparseMatrixCSC(s.nrow, s.ncol, increment(unsafe_wrap(Array, s.p, (s.ncol + 1,), false)), increment(unsafe_wrap(Array, s.i, (s.nzmax,), false)), copy(unsafe_wrap(Array, s.x, (s.nzmax,), false))), s.stype > 0 ? :U : :L) if s.sorted == 0 return SparseArrays.sortSparseMatrixCSC!(B.data) else return B end end function sparse(A::Sparse{Float64}) # Notice! Cannot be type stable because of stype s = unsafe_load(A.p) if s.stype == 0 return convert(SparseMatrixCSC{Float64,SuiteSparse_long}, A) end return convert(Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}, A) end function sparse(A::Sparse{Complex{Float64}}) # Notice! Cannot be type stable because of stype s = unsafe_load(A.p) if s.stype == 0 return convert(SparseMatrixCSC{Complex{Float64},SuiteSparse_long}, A) end return convert(Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},SuiteSparse_long}}, A) end function sparse(F::Factor) s = unsafe_load(F.p) if s.is_ll != 0 L = Sparse(F) A = sparse(L*L') else LD = sparse(F[:LD]) L, d = getLd!(LD) A = (L * Diagonal(d)) * L' end SparseArrays.sortSparseMatrixCSC!(A) p = get_perm(F) if p != [1:s.n;] pinv = Vector{Int}(length(p)) for k = 1:length(p) pinv[p[k]] = k end A = A[pinv,pinv] end A end sparse(D::Dense) = sparse(Sparse(D)) function sparse(FC::FactorComponent{Tv,:L}) where Tv F = Factor(FC) s = unsafe_load(F.p) if s.is_ll == 0 throw(CHOLMODException("sparse: supported only for :LD on LDLt factorizations")) end sparse(Sparse(F)) end sparse(FC::FactorComponent{Tv,:LD}) where {Tv} = sparse(Sparse(Factor(FC))) # Calculate the offset into the stype field of the cholmod_sparse_struct and # change the value let offset = fieldoffset(C_Sparse{Float64}, findfirst(name -> name === :stype, fieldnames(C_Sparse{Float64}))) global change_stype! function change_stype!(A::Sparse, i::Integer) unsafe_store!(convert(Ptr{Cint}, A.p), i, div(offset, 4) + 1) return A end end free!(A::Dense) = free_dense!(A.p) free!(A::Sparse) = free_sparse!(A.p) free!(F::Factor) = free_factor!(F.p) eltype(::Type{Dense{T}}) where {T<:VTypes} = T eltype(::Type{Factor{T}}) where {T<:VTypes} = T eltype(::Type{Sparse{T}}) where {T<:VTypes} = T nnz(F::Factor) = nnz(Sparse(F)) function show(io::IO, F::Factor) println(io, typeof(F)) showfactor(io, F) end function show(io::IO, FC::FactorComponent) println(io, typeof(FC)) showfactor(io, Factor(FC)) end function showfactor(io::IO, F::Factor) s = unsafe_load(get(F.p)) @printf(io, "type: %12s\n", s.is_ll!=0 ? "LLt" : "LDLt") @printf(io, "method: %10s\n", s.is_super!=0 ? "supernodal" : "simplicial") @printf(io, "maxnnz: %10d\n", Int(s.nzmax)) @printf(io, "nnz: %13d\n", nnz(F)) end # getindex not defined for these, so don't use the normal array printer show(io::IO, ::MIME"text/plain", FC::FactorComponent) = show(io, FC) show(io::IO, ::MIME"text/plain", F::Factor) = show(io, F) isvalid(A::Dense) = check_dense(A) isvalid(A::Sparse) = check_sparse(A) isvalid(A::Factor) = check_factor(A) copy(A::Dense) = copy_dense(A) copy(A::Sparse) = copy_sparse(A) copy(A::Factor) = copy_factor(A) function size(A::Union{Dense,Sparse}) s = unsafe_load(get(A.p)) return (Int(s.nrow), Int(s.ncol)) end function size(F::Factor, i::Integer) if i < 1 throw(ArgumentError("dimension must be positive")) end s = unsafe_load(get(F.p)) if i <= 2 return Int(s.n) end return 1 end size(F::Factor) = (size(F, 1), size(F, 2)) IndexStyle(::Dense) = IndexLinear() size(FC::FactorComponent, i::Integer) = size(FC.F, i) size(FC::FactorComponent) = size(FC.F) ctranspose(FC::FactorComponent{Tv,:L}) where {Tv} = FactorComponent{Tv,:U}(FC.F) ctranspose(FC::FactorComponent{Tv,:U}) where {Tv} = FactorComponent{Tv,:L}(FC.F) ctranspose(FC::FactorComponent{Tv,:PtL}) where {Tv} = FactorComponent{Tv,:UP}(FC.F) ctranspose(FC::FactorComponent{Tv,:UP}) where {Tv} = FactorComponent{Tv,:PtL}(FC.F) ctranspose(FC::FactorComponent{Tv,:D}) where {Tv} = FC ctranspose(FC::FactorComponent{Tv,:LD}) where {Tv} = FactorComponent{Tv,:DU}(FC.F) ctranspose(FC::FactorComponent{Tv,:DU}) where {Tv} = FactorComponent{Tv,:LD}(FC.F) ctranspose(FC::FactorComponent{Tv,:PtLD}) where {Tv} = FactorComponent{Tv,:DUP}(FC.F) ctranspose(FC::FactorComponent{Tv,:DUP}) where {Tv} = FactorComponent{Tv,:PtLD}(FC.F) function getindex(A::Dense, i::Integer) s = unsafe_load(get(A.p)) 0 < i <= s.nrow*s.ncol || throw(BoundsError()) unsafe_load(s.x, i) end IndexStyle(::Sparse) = IndexCartesian() function getindex(A::Sparse{T}, i0::Integer, i1::Integer) where T s = unsafe_load(get(A.p)) !(1 <= i0 <= s.nrow && 1 <= i1 <= s.ncol) && throw(BoundsError()) s.stype < 0 && i0 < i1 && return conj(A[i1,i0]) s.stype > 0 && i0 > i1 && return conj(A[i1,i0]) r1 = Int(unsafe_load(s.p, i1) + 1) r2 = Int(unsafe_load(s.p, i1 + 1)) (r1 > r2) && return zero(T) r1 = Int(searchsortedfirst(unsafe_wrap(Array, s.i, (s.nzmax,), false), i0 - 1, r1, r2, Base.Order.Forward)) ((r1 > r2) || (unsafe_load(s.i, r1) + 1 != i0)) ? zero(T) : unsafe_load(s.x, r1) end function getindex(F::Factor, sym::Symbol) sym == :p && return get_perm(F) FactorComponent(F, sym) end function getLd!(S::SparseMatrixCSC) d = Vector{eltype(S)}(size(S, 1)) fill!(d, 0) col = 1 for k = 1:nnz(S) while k >= S.colptr[col+1] col += 1 end if S.rowval[k] == col d[col] = S.nzval[k] S.nzval[k] = 1 end end S, d end ## Multiplication (*)(A::Sparse, B::Sparse) = ssmult(A, B, 0, true, true) (*)(A::Sparse, B::Dense) = sdmult!(A, false, 1., 0., B, zeros(size(A, 1), size(B, 2))) (*)(A::Sparse, B::VecOrMat) = (*)(A, Dense(B)) function A_mul_Bc(A::Sparse{Tv}, B::Sparse{Tv}) where Tv<:VRealTypes cm = common() if A !== B aa1 = transpose_(B, 2) ## result of ssmult will have stype==0, contain numerical values and be sorted return ssmult(A, aa1, 0, true, true) end ## The A*A' case is handled by cholmod_aat. This routine requires ## A->stype == 0 (storage of upper and lower parts). If neccesary ## the matrix A is first converted to stype == 0 s = unsafe_load(A.p) if s.stype != 0 aa1 = copy(A, 0, 1) return aat(aa1, SuiteSparse_long[0:s.ncol-1;], 1) else return aat(A, SuiteSparse_long[0:s.ncol-1;], 1) end end function Ac_mul_B(A::Sparse, B::Sparse) aa1 = transpose_(A, 2) if A === B return A_mul_Bc(aa1, aa1) end ## result of ssmult will have stype==0, contain numerical values and be sorted return ssmult(aa1, B, 0, true, true) end Ac_mul_B(A::Sparse, B::Dense) = sdmult!(A, true, 1., 0., B, zeros(size(A, 2), size(B, 2))) Ac_mul_B(A::Sparse, B::VecOrMat) = Ac_mul_B(A, Dense(B)) ## Factorization methods ## Compute that symbolic factorization only function fact_(A::Sparse{<:VTypes}, cm::Array{UInt8}; perm::AbstractVector{SuiteSparse_long}=SuiteSparse_long[], postorder::Bool=true, userperm_only::Bool=true) sA = unsafe_load(get(A.p)) sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian")) if !postorder unsafe_store!(common_postorder, 0) end if isempty(perm) F = analyze(A, cm) else # user permutation provided if userperm_only # use perm even if it is worse than AMD unsafe_store!(common_nmethods, 1) end F = analyze_p(A, SuiteSparse_long[p-1 for p in perm], cm) end return F end function cholfact!(F::Factor{Tv}, A::Sparse{Tv}; shift::Real=0.0) where Tv cm = common() # Makes it an LLt unsafe_store!(common_final_ll, 1) # Compute the numerical factorization factorize_p!(A, shift, F, cm) s = unsafe_load(get(F.p)) s.minor < size(A, 1) && throw(Base.LinAlg.PosDefException(s.minor)) return F end """ cholfact!(F::Factor, A; shift = 0.0) -> CHOLMOD.Factor Compute the Cholesky (``LL'``) factorization of `A`, reusing the symbolic factorization `F`. `A` must be a [`SparseMatrixCSC`](@ref) or a [`Symmetric`](@ref)/ [`Hermitian`](@ref) view of a `SparseMatrixCSC`. Note that even if `A` doesn't have the type tag, it must still be symmetric or Hermitian. See also [`cholfact`](@ref). !!! note This method uses the CHOLMOD library from SuiteSparse, which only supports doubles or complex doubles. Input matrices not of those element types will be converted to `SparseMatrixCSC{Float64}` or `SparseMatrixCSC{Complex128}` as appropriate. """ cholfact!(F::Factor, A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}, Hermitian{T,SparseMatrixCSC{T,SuiteSparse_long}}}; shift = 0.0) where {T<:Real} = cholfact!(F, Sparse(A); shift = shift) function cholfact(A::Sparse; shift::Real=0.0, perm::AbstractVector{SuiteSparse_long}=SuiteSparse_long[]) cm = defaults(common()) set_print_level(cm, 0) # Compute the symbolic factorization F = fact_(A, cm; perm = perm) # Compute the numerical factorization cholfact!(F, A; shift = shift) s = unsafe_load(get(F.p)) s.minor < size(A, 1) && throw(Base.LinAlg.PosDefException(s.minor)) return F end """ cholfact(A; shift = 0.0, perm = Int[]) -> CHOLMOD.Factor Compute the Cholesky factorization of a sparse positive definite matrix `A`. `A` must be a [`SparseMatrixCSC`](@ref) or a [`Symmetric`](@ref)/[`Hermitian`](@ref) view of a `SparseMatrixCSC`. Note that even if `A` doesn't have the type tag, it must still be symmetric or Hermitian. A fill-reducing permutation is used. `F = cholfact(A)` is most frequently used to solve systems of equations with `F\\b`, but also the methods [`diag`](@ref), [`det`](@ref), and [`logdet`](@ref) are defined for `F`. You can also extract individual factors from `F`, using `F[:L]`. However, since pivoting is on by default, the factorization is internally represented as `A == P'*L*L'*P` with a permutation matrix `P`; using just `L` without accounting for `P` will give incorrect answers. To include the effects of permutation, it's typically preferable to extract "combined" factors like `PtL = F[:PtL]` (the equivalent of `P'*L`) and `LtP = F[:UP]` (the equivalent of `L'*P`). Setting the optional `shift` keyword argument computes the factorization of `A+shift*I` instead of `A`. If the `perm` argument is nonempty, it should be a permutation of `1:size(A,1)` giving the ordering to use (instead of CHOLMOD's default AMD ordering). !!! note This method uses the CHOLMOD library from SuiteSparse, which only supports doubles or complex doubles. Input matrices not of those element types will be converted to `SparseMatrixCSC{Float64}` or `SparseMatrixCSC{Complex128}` as appropriate. Many other functions from CHOLMOD are wrapped but not exported from the `Base.SparseArrays.CHOLMOD` module. """ cholfact(A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}, Hermitian{T,SparseMatrixCSC{T,SuiteSparse_long}}}; kws...) where {T<:Real} = cholfact(Sparse(A); kws...) function ldltfact!(F::Factor{Tv}, A::Sparse{Tv}; shift::Real=0.0) where Tv cm = common() # Compute the numerical factorization factorize_p!(A, shift, F, cm) s = unsafe_load(get(F.p)) s.minor < size(A, 1) && throw(Base.LinAlg.ArgumentError("matrix has one or more zero pivots")) return F end """ ldltfact!(F::Factor, A; shift = 0.0) -> CHOLMOD.Factor Compute the ``LDL'`` factorization of `A`, reusing the symbolic factorization `F`. `A` must be a [`SparseMatrixCSC`](@ref) or a [`Symmetric`](@ref)/[`Hermitian`](@ref) view of a `SparseMatrixCSC`. Note that even if `A` doesn't have the type tag, it must still be symmetric or Hermitian. See also [`ldltfact`](@ref). !!! note This method uses the CHOLMOD library from SuiteSparse, which only supports doubles or complex doubles. Input matrices not of those element types will be converted to `SparseMatrixCSC{Float64}` or `SparseMatrixCSC{Complex128}` as appropriate. """ ldltfact!(F::Factor, A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}, Hermitian{T,SparseMatrixCSC{T,SuiteSparse_long}}}; shift = 0.0) where {T<:Real} = ldltfact!(F, Sparse(A), shift = shift) function ldltfact(A::Sparse; shift::Real=0.0, perm::AbstractVector{SuiteSparse_long}=SuiteSparse_long[]) cm = defaults(common()) set_print_level(cm, 0) # Makes it an LDLt unsafe_store!(common_final_ll, 0) # Really make sure it's an LDLt by avoiding supernodal factorization unsafe_store!(common_supernodal, 0) # Compute the symbolic factorization F = fact_(A, cm; perm = perm) # Compute the numerical factorization ldltfact!(F, A; shift = shift) s = unsafe_load(get(F.p)) if s.minor < size(A, 1) throw(Base.LinAlg.ArgumentError("matrix has one or more zero pivots")) end return F end """ ldltfact(A; shift = 0.0, perm=Int[]) -> CHOLMOD.Factor Compute the ``LDL'`` factorization of a sparse matrix `A`. `A` must be a [`SparseMatrixCSC`](@ref) or a [`Symmetric`](@ref)/[`Hermitian`](@ref) view of a `SparseMatrixCSC`. Note that even if `A` doesn't have the type tag, it must still be symmetric or Hermitian. A fill-reducing permutation is used. `F = ldltfact(A)` is most frequently used to solve systems of equations `A*x = b` with `F\\b`. The returned factorization object `F` also supports the methods [`diag`](@ref), [`det`](@ref), [`logdet`](@ref), and [`inv`](@ref). You can extract individual factors from `F` using `F[:L]`. However, since pivoting is on by default, the factorization is internally represented as `A == P'*L*D*L'*P` with a permutation matrix `P`; using just `L` without accounting for `P` will give incorrect answers. To include the effects of permutation, it is typically preferable to extract "combined" factors like `PtL = F[:PtL]` (the equivalent of `P'*L`) and `LtP = F[:UP]` (the equivalent of `L'*P`). The complete list of supported factors is `:L, :PtL, :D, :UP, :U, :LD, :DU, :PtLD, :DUP`. Setting the optional `shift` keyword argument computes the factorization of `A+shift*I` instead of `A`. If the `perm` argument is nonempty, it should be a permutation of `1:size(A,1)` giving the ordering to use (instead of CHOLMOD's default AMD ordering). !!! note This method uses the CHOLMOD library from SuiteSparse, which only supports doubles or complex doubles. Input matrices not of those element types will be converted to `SparseMatrixCSC{Float64}` or `SparseMatrixCSC{Complex128}` as appropriate. Many other functions from CHOLMOD are wrapped but not exported from the `Base.SparseArrays.CHOLMOD` module. """ ldltfact(A::Union{SparseMatrixCSC{T},SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}, Hermitian{T,SparseMatrixCSC{T,SuiteSparse_long}}}; kws...) where {T<:Real} = ldltfact(Sparse(A); kws...) ## Solvers for (T, f) in ((:Dense, :solve), (:Sparse, :spsolve)) @eval begin # Solve Lx = b and L'x=b where A = L*L' function (\)(L::FactorComponent{T,:L}, B::$T) where T ($f)(CHOLMOD_L, Factor(L), B) end function (\)(L::FactorComponent{T,:U}, B::$T) where T ($f)(CHOLMOD_Lt, Factor(L), B) end # Solve PLx = b and L'P'x=b where A = P*L*L'*P' function (\)(L::FactorComponent{T,:PtL}, B::$T) where T F = Factor(L) ($f)(CHOLMOD_L, F, ($f)(CHOLMOD_P, F, B)) # Confusingly, CHOLMOD_P solves P'x = b end function (\)(L::FactorComponent{T,:UP}, B::$T) where T F = Factor(L) ($f)(CHOLMOD_Pt, F, ($f)(CHOLMOD_Lt, F, B)) end # Solve various equations for A = L*D*L' and A = P*L*D*L'*P' function (\)(L::FactorComponent{T,:D}, B::$T) where T ($f)(CHOLMOD_D, Factor(L), B) end function (\)(L::FactorComponent{T,:LD}, B::$T) where T ($f)(CHOLMOD_LD, Factor(L), B) end function (\)(L::FactorComponent{T,:DU}, B::$T) where T ($f)(CHOLMOD_DLt, Factor(L), B) end function (\)(L::FactorComponent{T,:PtLD}, B::$T) where T F = Factor(L) ($f)(CHOLMOD_LD, F, ($f)(CHOLMOD_P, F, B)) end function (\)(L::FactorComponent{T,:DUP}, B::$T) where T F = Factor(L) ($f)(CHOLMOD_Pt, F, ($f)(CHOLMOD_DLt, F, B)) end end end SparseVecOrMat{Tv,Ti} = Union{SparseVector{Tv,Ti}, SparseMatrixCSC{Tv,Ti}} function (\)(L::FactorComponent, b::Vector) reshape(convert(Matrix, L\Dense(b)), length(b)) end function (\)(L::FactorComponent, B::Matrix) convert(Matrix, L\Dense(B)) end function (\)(L::FactorComponent, B::SparseVecOrMat) sparse(L\Sparse(B,0)) end Ac_ldiv_B(L::FactorComponent, B) = ctranspose(L)\B Ac_ldiv_B(L::FactorComponent, B::RowVector) = ctranspose(L)\B # ambiguity (\)(L::Factor{T}, B::Dense{T}) where {T<:VTypes} = solve(CHOLMOD_A, L, B) # Explicit typevars are necessary to avoid ambiguities with defs in linalg/factorizations.jl # Likewise the two following explicit Vector and Matrix defs (rather than a single VecOrMat) (\)(L::Factor{T}, B::Vector{Complex{T}}) where {T<:Float64} = complex.(L\real(B), L\imag(B)) (\)(L::Factor{T}, B::Matrix{Complex{T}}) where {T<:Float64} = complex.(L\real(B), L\imag(B)) (\)(L::Factor{T}, b::StridedVector) where {T<:VTypes} = Vector(L\convert(Dense{T}, b)) (\)(L::Factor{T}, B::StridedMatrix) where {T<:VTypes} = Matrix(L\convert(Dense{T}, B)) (\)(L::Factor, B::Sparse) = spsolve(CHOLMOD_A, L, B) # When right hand side is sparse, we have to ensure that the rhs is not marked as symmetric. (\)(L::Factor, B::SparseVecOrMat) = sparse(spsolve(CHOLMOD_A, L, Sparse(B, 0))) Ac_ldiv_B(L::Factor, B::Dense) = solve(CHOLMOD_A, L, B) Ac_ldiv_B(L::Factor, B::VecOrMat) = convert(Matrix, solve(CHOLMOD_A, L, Dense(B))) Ac_ldiv_B(L::Factor, B::Sparse) = spsolve(CHOLMOD_A, L, B) Ac_ldiv_B(L::Factor, B::SparseVecOrMat) = Ac_ldiv_B(L, Sparse(B)) for f in (:\, :Ac_ldiv_B) @eval function ($f)(A::Union{Symmetric{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}, Hermitian{Float64,SparseMatrixCSC{Float64,SuiteSparse_long}}, Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},SuiteSparse_long}}}, B::StridedVecOrMat) try return ($f)(cholfact(A), B) catch e isa(e, LinAlg.PosDefException) || rethrow(e) return ($f)(ldltfact(A) , B) end end end ## Other convenience methods function diag(F::Factor{Tv}) where Tv f = unsafe_load(get(F.p)) fsuper = f.super fpi = f.pi res = Base.zeros(Tv, Int(f.n)) xv = f.x if f.is_super!=0 px = f.px pos = 1 for i in 1:f.nsuper base = unsafe_load(px, i) + 1 res[pos] = unsafe_load(xv, base) pos += 1 for j in 1:unsafe_load(fsuper, i + 1) - unsafe_load(fsuper, i) - 1 res[pos] = unsafe_load(xv, base + j*(unsafe_load(fpi, i + 1) - unsafe_load(fpi, i) + 1)) pos += 1 end end else c0 = f.p r0 = f.i xv = f.x for j in 1:f.n jj = unsafe_load(c0, j) + 1 assert(unsafe_load(r0, jj) == j - 1) res[j] = unsafe_load(xv, jj) end end res end function logdet(F::Factor{Tv}) where Tv<:VTypes f = unsafe_load(get(F.p)) res = zero(Tv) for d in diag(F); res += log(abs(d)) end f.is_ll!=0 ? 2res : res end det(L::Factor) = exp(logdet(L)) function isposdef(A::SparseMatrixCSC{<:VTypes,SuiteSparse_long}) if !ishermitian(A) return false end try f = cholfact(A) catch e isa(e, LinAlg.PosDefException) || rethrow(e) return false end true end function ishermitian(A::Sparse{Float64}) s = unsafe_load(A.p) if s.stype != 0 return true else i = symmetry(A, 1)[1] if i < 0 throw(CHOLMODException("negative value returned from CHOLMOD's symmetry function. This is either because the indices are not sorted or because of a memory error")) end return i == MM_SYMMETRIC || i == MM_SYMMETRIC_POSDIAG end end function ishermitian(A::Sparse{Complex{Float64}}) s = unsafe_load(A.p) if s.stype != 0 return true else i = symmetry(A, 1)[1] if i < 0 throw(CHOLMODException("negative value returned from CHOLMOD's symmetry function. This is either because the indices are not sorted or because of a memory error")) end return i == MM_HERMITIAN || i == MM_HERMITIAN_POSDIAG end end (*)(A::Symmetric{Float64,SparseMatrixCSC{Float64,Ti}}, B::SparseVecOrMat{Float64,Ti}) where {Ti} = sparse(Sparse(A)*Sparse(B)) (*)(A::Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},Ti}}, B::SparseVecOrMat{Complex{Float64},Ti}) where {Ti} = sparse(Sparse(A)*Sparse(B)) (*)(A::Hermitian{Float64,SparseMatrixCSC{Float64,Ti}}, B::SparseVecOrMat{Float64,Ti}) where {Ti} = sparse(Sparse(A)*Sparse(B)) (*)(A::SparseVecOrMat{Float64,Ti}, B::Symmetric{Float64,SparseMatrixCSC{Float64,Ti}}) where {Ti} = sparse(Sparse(A)*Sparse(B)) (*)(A::SparseVecOrMat{Complex{Float64},Ti}, B::Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},Ti}}) where {Ti} = sparse(Sparse(A)*Sparse(B)) (*)(A::SparseVecOrMat{Float64,Ti}, B::Hermitian{Float64,SparseMatrixCSC{Float64,Ti}}) where {Ti} = sparse(Sparse(A)*Sparse(B)) end #module