# This file is a part of Julia. License is MIT: http://julialang.org/license module CHOLMOD import Base: (*), convert, copy, eltype, get, getindex, show, showarray, size, linearindexing, LinearFast, LinearSlow, 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 = Array(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 = Array(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 = Array(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 SuiteSparseStruct # 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 immutable 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 type Dense{T<:VTypes} <: DenseMatrix{T} p::Ptr{C_Dense{T}} end # Sparse immutable 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 immutable 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 type Sparse{Tv<:VTypes} <: AbstractSparseMatrix{Tv,SuiteSparse_long} p::Ptr{C_Sparse{Tv}} function Sparse(p::Ptr{C_Sparse{Tv}}) if p == C_NULL throw(ArgumentError("sparse matrix construction failed for unknown reasons. Please submit a bug report.")) end new(p) end end Sparse{Tv<:VTypes}(p::Ptr{C_Sparse{Tv}}) = Sparse{Tv}(p) # Factor if build_version >= v"2.1.0" # CHOLMOD version 2.1.0 or later immutable 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 immutable 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 type Factor{Tv} <: Factorization{Tv} p::Ptr{C_Factor{Tv}} function Factor(p::Ptr{C_Factor{Tv}}) if p == C_NULL throw(ArgumentError("factorization construction failed for unknown reasons. Please submit a bug report.")) end new(p) end end Factor{Tv<:VTypes}(p::Ptr{C_Factor{Tv}}) = 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{T<:SuiteSparseStruct}(p::Ptr{T}) 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 type FactorComponent{Tv,S} <: AbstractMatrix{Tv} F::Factor{Tv} function FactorComponent(F::Factor{Tv}) s = unsafe_load(get(F.p)) if s.is_ll != 0 S == :L || S == :U || S == :PtL || S == :UP || throw(CHOLMODException(string(S, " not supported for sparse LLt matrices; try :L, :U, :PtL, or :UP"))) else 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{Tv}(F::Factor{Tv}, sym::Symbol) 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!{T}(p::Ptr{C_Dense{T}}) = ccall((:cholmod_l_free_dense, :libcholmod), Cint, (Ref{Ptr{C_Dense{T}}}, Ptr{Void}), p, common()) function zeros{T<:VTypes}(m::Integer, n::Integer, ::Type{T}) 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{T<:VTypes}(m::Integer, n::Integer, ::Type{T}) 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{T<:VTypes}(m::Integer, n::Integer, ::Type{T}) 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{Tv<:VTypes}(A::Dense{Tv}) 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 ### cholmod_matrixops.h ### function norm_dense{Tv<:VTypes}(D::Dense{Tv}, p::Integer) 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{T<:VTypes}(A::Dense{T}) 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!{Tv<:VTypes}(ptr::Ptr{C_Sparse{Tv}}) @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!{Tv<:VTypes}(ptr::Ptr{C_Factor{Tv}}) # 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{Tv<:VRealTypes}(A::Sparse{Tv}, fset::Vector{SuiteSparse_long}, mode::Integer) 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{Tv<:VTypes}(A::Sparse{Tv}) 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{Tv<:VTypes}(D::Dense{Tv}, ::Type{SuiteSparse_long}) 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!{Tv<:VTypes}(F::Factor{Tv}) 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!{Tv<:VTypes}(::Type{Float64}, to_ll::Bool, to_super::Bool, to_packed::Bool, to_monotonic::Bool, F::Factor{Tv}) @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!{Tv<:VTypes}(::Type{Complex{Float64}}, to_ll::Bool, to_super::Bool, to_packed::Bool, to_monotonic::Bool, F::Factor{Tv}) @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{Tv<:VTypes}(A::Sparse{Tv}) ccall((@cholmod_name("check_sparse", SuiteSparse_long),:libcholmod), Cint, (Ptr{C_Sparse{Tv}}, Ptr{UInt8}), get(A.p), common())!=0 end function check_factor{Tv<:VTypes}(F::Factor{Tv}) 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{Tv<:VTypes}(m::Integer, n::Integer, ::Type{Tv}) 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{Tv<:VTypes}(m::Integer, n::Integer, nzmax::Integer, ::Type{Tv}) 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_{Tv<:VTypes}(A::Sparse{Tv}, values::Integer) 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{Tv<:VTypes}(F::Factor{Tv}) 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{Tv<:VTypes}(A::Sparse{Tv}) 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{Tv<:VRealTypes}(A::Sparse{Tv}, stype::Integer, mode::Integer) 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{Tv<:VTypes}(A::Sparse{Tv}, name::ASCIIString) 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{Tv<:VTypes}(F::Factor{Tv}, name::ASCIIString) 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{Tv<:VRealTypes}(A::Sparse{Tv}, B::Sparse{Tv}, stype::Integer, values::Bool, sorted::Bool) 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{Tv<:VTypes}(A::Sparse{Tv}, norm::Integer) 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{Tv<:VRealTypes}(A::Sparse{Tv}, B::Sparse{Tv}, values::Bool) 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!{Tv<:VRealTypes}(S::Dense{Tv}, scale::Integer, A::Sparse{Tv}) sS = unsafe_load(get(S.p)) sA = unsafe_load(get(A.p)) sS.ncol == 1 || sS.nrow == 1 || throw(DimensionMismatch("first argument must be a vector")) 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!{Tv<:VTypes}(A::Sparse{Tv}, transpose::Bool, α::Number, β::Number, X::Dense{Tv}, Y::Dense{Tv}) 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{Tv<:VRealTypes}(A::Sparse{Tv}, B::Sparse{Tv}, values::Bool) 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{Tv<:VTypes}(A::Sparse{Tv}, option::Integer) xmatched = Array(SuiteSparse_long, 1) pmatched = Array(SuiteSparse_long, 1) nzoffdiag = Array(SuiteSparse_long, 1) nzdiag = Array(SuiteSparse_long, 1) 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[1], pmatched[1], nzoffdiag[1], nzdiag[1] 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!{Tv<:VTypes}(A::Sparse{Tv}, F::Factor{Tv}, cmmn::Vector{UInt8}) @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!{Tv<:VTypes}(A::Sparse{Tv}, β::Real, F::Factor{Tv}, cmmn::Vector{UInt8}) # 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{Tv<:VTypes}(sys::Integer, F::Factor{Tv}, B::Dense{Tv}) 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{Tv<:VTypes}(sys::Integer, F::Factor{Tv}, B::Sparse{Tv}) 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 = pointer_to_array(s.Perm, s.n, false) p+1 end get_perm(FC::FactorComponent) = get_perm(Factor(FC)) ######################### # High level interfaces # ######################### # Convertion/construction function convert{T<:VTypes}(::Type{Dense{T}}, A::VecOrMat) 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::VecOrMat) 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 (::Type{Sparse}){Tv<:VTypes}(m::Integer, n::Integer, colptr::Vector{SuiteSparse_long}, rowval::Vector{SuiteSparse_long}, nzval::Vector{Tv}, stype) # check if columns are sorted iss = true for i = 2:length(colptr) if !issorted(sub(rowval, colptr[i - 1] + 1:colptr[i])) iss = false break end end o = allocate_sparse(m, n, length(nzval), iss, true, stype, Tv) s = unsafe_load(o.p) unsafe_copy!(s.p, pointer(colptr), length(colptr)) unsafe_copy!(s.i, pointer(rowval), length(rowval)) unsafe_copy!(s.x, pointer(nzval), length(nzval)) @isok check_sparse(o) return o end function (::Type{Sparse}){Tv<:VTypes}(m::Integer, n::Integer, colptr::Vector{SuiteSparse_long}, rowval::Vector{SuiteSparse_long}, nzval::Vector{Tv}) o = Sparse(m, n, colptr, rowval, nzval, 0) # check if array is symmetric and change stype if it is if ishermitian(o) change_stype!(o, -1) end o end function (::Type{Sparse}){Tv<:VTypes}(A::SparseMatrixCSC{Tv,SuiteSparse_long}, stype::Integer) o = allocate_sparse(A.m, A.n, length(A.nzval), true, true, stype, Tv) s = unsafe_load(o.p) for i = 1:length(A.colptr) unsafe_store!(s.p, A.colptr[i] - 1, i) end for i = 1:length(A.rowval) unsafe_store!(s.i, A.rowval[i] - 1, i) end unsafe_copy!(s.x, pointer(A.nzval), length(A.nzval)) @isok check_sparse(o) return o end # convert SparseVectors into CHOLMOD Sparse types through a mx1 CSC matrix convert{Tv<:VTypes}(::Type{Sparse}, A::SparseVector{Tv,SuiteSparse_long}) = convert(Sparse, convert(SparseMatrixCSC, A)) function convert{Tv<:VTypes,Ti<:ITypes}(::Type{Sparse}, A::SparseMatrixCSC{Tv,Ti}) 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{Ti<:ITypes}(::Type{Sparse}, A::SparseMatrixCSC{Complex{Float32},Ti}) = 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{Tv<:VTypes}(::Type{Sparse}, A::Hermitian{Tv,SparseMatrixCSC{Tv,SuiteSparse_long}}) = 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{T,Ti<:ITypes}(::Type{Sparse}, A::Union{SparseMatrixCSC{T,Ti}, Symmetric{T,SparseMatrixCSC{T,Ti}}, Hermitian{T,SparseMatrixCSC{T,Ti}}}, args...) 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::ByteString) open(filename) do f return read_sparse(f, SuiteSparse_long) end end ## convertion back to base Julia types function convert{T}(::Type{Matrix{T}}, D::Dense{T}) s = unsafe_load(D.p) a = Array(T, s.nrow, s.ncol) if s.d == s.nrow unsafe_copy!(pointer(a), s.x, s.d*s.ncol) else for j = 1:s.ncol for i = 1:s.nrow a[i,j] = unsafe_load(s.x, i + (j - 1)*s.d) end end end a end convert{T}(::Type{Matrix}, D::Dense{T}) = convert(Matrix{T}, D) function convert{T}(::Type{Vector{T}}, D::Dense{T}) if size(D, 2) > 1 throw(DimensionMismatch("input must be a vector but had $(size(D, 2)) columns")) end reshape(convert(Matrix, D), size(D, 1)) end convert{T}(::Type{Vector}, D::Dense{T}) = convert(Vector{T}, D) function convert{Tv}(::Type{SparseMatrixCSC{Tv,SuiteSparse_long}}, A::Sparse{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 return SparseMatrixCSC(s.nrow, s.ncol, increment(pointer_to_array(s.p, (s.ncol + 1,), false)), increment(pointer_to_array(s.i, (s.nzmax,), false)), copy(pointer_to_array(s.x, (s.nzmax,), false))) 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 return Symmetric(SparseMatrixCSC(s.nrow, s.ncol, increment(pointer_to_array(s.p, (s.ncol + 1,), false)), increment(pointer_to_array(s.i, (s.nzmax,), false)), copy(pointer_to_array(s.x, (s.nzmax,), false))), s.stype > 0 ? :U : :L) end function convert{Tv<:VTypes}(::Type{Hermitian{Tv,SparseMatrixCSC{Tv,SuiteSparse_long}}}, A::Sparse{Tv}) s = unsafe_load(A.p) if !ishermitian(A) throw(ArgumentError("matrix is not Hermitian")) end return Hermitian(SparseMatrixCSC(s.nrow, s.ncol, increment(pointer_to_array(s.p, (s.ncol + 1,), false)), increment(pointer_to_array(s.i, (s.nzmax,), false)), copy(pointer_to_array(s.x, (s.nzmax,), false))), s.stype > 0 ? :U : :L) 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 = Array(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{Tv}(FC::FactorComponent{Tv,:L}) F = Factor(FC) s = unsafe_load(F.p) s.is_ll != 0 || throw(CHOLMODException("sparse: supported only for :LD on LDLt factorizations")) sparse(Sparse(F)) end sparse{Tv}(FC::FactorComponent{Tv,:LD}) = 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(fieldnames(C_Sparse) .== :stype)) 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{T<:VTypes}(::Type{Dense{T}}) = T eltype{T<:VTypes}(::Type{Factor{T}}) = T eltype{T<:VTypes}(::Type{Sparse{T}}) = T nnz(F::Factor) = nnz(Sparse(F)) function show(io::IO, F::Factor) println(io, typeof(F)) showfactor(io, F) end # FactorComponent is a subtype of AbstractArray and we therefore define showarray instead of show function showarray(io::IO, FC::FactorComponent; kargs...) 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 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 linearindexing(::Dense) = LinearFast() size(FC::FactorComponent, i::Integer) = size(FC.F, i) size(FC::FactorComponent) = size(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:L}) = FactorComponent{Tv,:U}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:U}) = FactorComponent{Tv,:L}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:PtL}) = FactorComponent{Tv,:UP}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:UP}) = FactorComponent{Tv,:PtL}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:D}) = FC ctranspose{Tv}(FC::FactorComponent{Tv,:LD}) = FactorComponent{Tv,:DU}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:DU}) = FactorComponent{Tv,:LD}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:PtLD}) = FactorComponent{Tv,:DUP}(FC.F) ctranspose{Tv}(FC::FactorComponent{Tv,:DUP}) = 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 linearindexing(::Sparse) = LinearSlow() function getindex{T}(A::Sparse{T}, i0::Integer, i1::Integer) 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(pointer_to_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 = Array(eltype(S), size(S, 1)) fill!(d, 0) col = 1 for k = 1:length(S.nzval) 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{Tv<:VRealTypes}(A::Sparse{Tv}, B::Sparse{Tv}) cm = common() if !is(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 is(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_{Tv<:VTypes}(A::Sparse{Tv}, 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!{Tv}(F::Factor{Tv}, A::Sparse{Tv}; shift::Real=0.0) 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::Union{SparseMatrixCSC{<:Real}, SparseMatrixCSC{Complex{<:Real}}, Symmetric{<:Real,SparseMatrixCSC{<:Real,SuiteSparse_long}}, Hermitian{Complex{<:Real},SparseMatrixCSC{Complex{<:Real},SuiteSparse_long}}}; shift = 0.0) -> CHOLMOD.Factor Compute the Cholesky (``LL'``) factorization of `A`, reusing the symbolic factorization `F`. ** 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!{T<:Real}(F::Factor, A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}}; shift = 0.0) = 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(::Union{SparseMatrixCSC{<:Real},SparseMatrixCSC{Complex{<:Real}}, Symmetric{<:Real,SparseMatrixCSC{<:Real, SuiteSparse_long}},Hermitian{Complex{<:Real},SparseMatrixCSC{Complex{<:Real}, SuiteSparse_long}}}; shift = 0.0, perm=Int[]) -> CHOLMOD.Factor Compute the Cholesky factorization of a sparse positive definite matrix `A`. 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`, `det`, `logdet` 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 extact "combined" factors like `PtL = F[:PtL]` (the equivalent of `P'*L`) and `LtP = F[:UP]` (the equivalent of `L'*P`). Setting 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{T<:Real}(A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}}; kws...) = cholfact(Sparse(A); kws...) function ldltfact!{Tv}(F::Factor{Tv}, A::Sparse{Tv}; shift::Real=0.0) cm = common() # Makes it an LDLt unsafe_store!(common_final_ll, 0) # Compute the numerical factorization factorize_p!(A, shift, F, cm) # Really make sure it's an LDLt by avoiding supernodal factorisation unsafe_store!(common_supernodal, 0) 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::Union{SparseMatrixCSC{<:Real}, SparseMatrixCSC{Complex{<:Real}}, Symmetric{<:Real,SparseMatrixCSC{<:Real,SuiteSparse_long}}, Hermitian{Complex{<:Real},SparseMatrixCSC{Complex{<:Real},SuiteSparse_long}}}; shift = 0.0) -> CHOLMOD.Factor Compute the ``LDL'`` factorization of `A`, reusing the symbolic factorization `F`. ** 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!{T<:Real}(F::Factor, A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}}; shift = 0.0) = 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) # 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)) s.minor < size(A, 1) && throw(Base.LinAlg.ArgumentError("matrix has one or more zero pivots")) return F end """ ldltfact(::Union{SparseMatrixCSC{<:Real},SparseMatrixCSC{Complex{<:Real}}, Symmetric{<:Real,SparseMatrixCSC{<:Real,SuiteSparse_long}}, Hermitian{Complex{<:Real},SparseMatrixCSC{Complex{<:Real},SuiteSparse_long}}}; shift = 0.0, perm=Int[]) -> CHOLMOD.Factor Compute the ``LDL'`` factorization of a sparse symmetric or Hermitian matrix. 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`, `det`, and `logdet`. 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's typically preferable to extact "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 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{T<:Real}(A::Union{SparseMatrixCSC{T},SparseMatrixCSC{Complex{T}}, Symmetric{T,SparseMatrixCSC{T,SuiteSparse_long}}, Hermitian{Complex{T},SparseMatrixCSC{Complex{T},SuiteSparse_long}}}; kws...) = 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 (\){T}(L::FactorComponent{T,:L}, B::$T) ($f)(CHOLMOD_L, Factor(L), B) end function (\){T}(L::FactorComponent{T,:U}, B::$T) ($f)(CHOLMOD_Lt, Factor(L), B) end # Solve PLx = b and L'P'x=b where A = P*L*L'*P' function (\){T}(L::FactorComponent{T,:PtL}, B::$T) F = Factor(L) ($f)(CHOLMOD_L, F, ($f)(CHOLMOD_P, F, B)) # Confusingly, CHOLMOD_P solves P'x = b end function (\){T}(L::FactorComponent{T,:UP}, B::$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 (\){T}(L::FactorComponent{T,:D}, B::$T) ($f)(CHOLMOD_D, Factor(L), B) end function (\){T}(L::FactorComponent{T,:LD}, B::$T) ($f)(CHOLMOD_LD, Factor(L), B) end function (\){T}(L::FactorComponent{T,:DU}, B::$T) ($f)(CHOLMOD_DLt, Factor(L), B) end function (\){T}(L::FactorComponent{T,:PtLD}, B::$T) F = Factor(L) ($f)(CHOLMOD_LD, F, ($f)(CHOLMOD_P, F, B)) end function (\){T}(L::FactorComponent{T,:DUP}, B::$T) F = Factor(L) ($f)(CHOLMOD_Pt, F, ($f)(CHOLMOD_DLt, F, B)) end end end typealias 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 (\){T}(L::Factor{T}, B::Dense{T}) = solve(CHOLMOD_A, L, B) (\)(L::Factor{Float64}, B::VecOrMat{Complex{Float64}}) = L\real(B) + L\imag(B) (\)(L::Factor, b::Vector) = Vector(L\convert(Dense{eltype(L)}, b)) (\)(L::Factor, B::Matrix) = Matrix(L\convert(Dense{eltype(L)}, 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)) ## Other convenience methods function diag{Tv}(F::Factor{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{Tv<:VTypes}(F::Factor{Tv}) 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{Tv<:VTypes}(A::SparseMatrixCSC{Tv,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 issymmetric(A::Sparse) s = unsafe_load(A.p) if s.stype != 0 return isreal(A) end i = symmetry(A, 1)[1] return i == MM_SYMMETRIC || i == MM_SYMMETRIC_POSDIAG end function ishermitian(A::Sparse{Float64}) s = unsafe_load(A.p) if s.stype != 0 return true else i = symmetry(A, 1)[1] 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] return i == MM_HERMITIAN || i == MM_HERMITIAN_POSDIAG end end (*){Ti}(A::Symmetric{Float64,SparseMatrixCSC{Float64,Ti}}, B::SparseVecOrMat{Float64,Ti}) = sparse(Sparse(A)*Sparse(B)) (*){Ti}(A::Hermitian{Complex{Float64},SparseMatrixCSC{Complex{Float64},Ti}}, B::SparseVecOrMat{Complex{Float64},Ti}) = sparse(Sparse(A)*Sparse(B)) end #module