mollusk 0e4acfb8f2 fix incorrect folder name for julia-0.6.x
Former-commit-id: ef2c7401e0876f22d2f7762d182cfbcd5a7d9c70
2018-06-11 03:28:36 -07:00

1726 lines
61 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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