text:
# 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(A::Sparse{Tv}) where Tv<:VTypes
    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(A::Sparse{Tv}, cmmn::Vector{UInt8}) where Tv<:VTypes
    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(A::Sparse{Tv}, perm::Vector{SuiteSparse_long},
                   cmmn::Vector{UInt8}) where Tv<:VTypes
    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(::Type{Sparse},
                 A::Union{SparseMatrixCSC{BigFloat,Ti},
                          Symmetric{BigFloat,SparseMatrixCSC{BigFloat,Ti}},
                          Hermitian{Complex{BigFloat},SparseMatrixCSC{Complex{BigFloat},Ti}}},
                 args...) where Ti<:ITypes
    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`, `Symmetric{SparseMatrixCSC}`,
or `Hermitian{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`, `Symmetric{SparseMatrixCSC}`, or
`Hermitian{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`, `Symmetric{SparseMatrixCSC}`, or
`Hermitian{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`, `Symmetric{SparseMatrixCSC}`, or
`Hermitian{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

max_stars_repo_name: caodi/julia

max_issues_repo_name: caodi/julia

max_forks_repo_name: caodi/julia


text:
using Random

"""
Approximately solve the QAP through random sampling.
"""
function qap_random(A, B, obj; iter=-1)
	@assert obj in [:min, :max]
	N = size(A, 1)

	iter = (iter == -1) ? 10N : iter

	best_sol = ((obj == :min) ? Inf : -Inf, nothing)

	for i in 1:iter
		perm = randperm(N)
		s = sum(A .* B[perm, perm])

		if obj == :min && s < best_sol[1]
			best_sol = (s, perm)
		elseif obj == :max && s > best_sol[1]
			best_sol = (s, perm)
		end
	end

	perm = best_sol[2]
	P = Matrix(I, N, N)[perm,:]

	return P, perm
end

ext: jl


text:
using LinearAlgebra
import ExpmV.normAm
import LinearAlgebra:BlasFloat, checksquare

export expm3

"""
    expm3(X)

Matrix exponential computed using
a scaling and squaring algorithm with a Taylor approximation evaluated
using a new decomposition.

When the maximal order is selected, estimates of norm(A^9,1) are used
to reduce overscaling

The choice of order and scaling is based solely on norm(A,1)
"""
function expm3(A::StridedMatrix{T}) where {T<:BlasFloat}

    """
        expm_params(A)

    expm_helper Obtain scaling parameter and order of the Taylor
    approximant
    """
    function expm_params(A)

        m_vals = [1 2 4 8 12 18]
        if T == Float64

            # theta_m for m=2:18.
            theta = T[
                2.220446049250313e-16  # m_vals = 1
                2.580956802971767e-08  # m_vals = 2
                3.397168839976962e-04  # m_vals = 4
                4.991228871115323e-02  # m_vals = 8
                2.996158913811580e-01  # m_vals = 12
                1.090863719290036e+00
            ]# m_vals = 18

        elseif T == Float32

            # theta_m for m=1:7.
            theta = T[
                1.192092800768788e-7   # m_vals = 1
                5.978858893805233e-04  # m_vals = 2
                5.116619363445086e-02  # m_vals = 4
                5.800524627688768e-01  # m_vals = 8
               #7.795113374358031e-01
               #9.951840790004457e-01
               #1.223479542424143e+00
                1.461661507209034e+00  # m_vals = 12
                3.010066362817634e+00
            ]# m_vals = 18

        else
            throw("Input matrix element type must be Float32 or Float64")
        end

        s = 0

        normA = opnorm(A, 1)

        Tp = []
        scale = Int[]

        # Find appropriate order:
        push!(Tp, copy(A))
        push!(scale, 1)
        ## Check if degree 1 is sufficient (No info available)
        if (normA < theta[1])
            degree = 1
            return s, degree, Tp, scale
        end
        push!(Tp, A * A)
        push!(scale, 2)
        if (normA < theta[2])
            degree = 2
            return s, degree, Tp, scale
        end
        if (normA < theta[3])
            degree = 4
            return s, degree, Tp, scale
        end
        # At degree 8, we could do the first estimates
        # with pairs (2,3), (3,4), (3,5), (2,9) [Also (2,5), (2,7)]

        if (normA < theta[4])
            degree = 8
            return s, degree, Tp, scale
        end
        push!(Tp, A * Tp[2])
        push!(scale, 3)
        if (normA < theta[5])
            degree = 12
            return s, degree, Tp, scale
        end

        # Otherwise degree 18
        # Here, we only gain the pairs (3, 8) and (4, 7),
        # but both are not expected to
        # yield improvements, however, we also get (2, 2k+1) until (2,17)
        degree = 18
        push!(Tp, Tp[3] * Tp[3])
        push!(scale, 6)
        d2 = norm(Tp[2], 1)^(1 / 2)
        d3 = norm(Tp[3], 1)^(1 / 3)
        d6 = norm(Tp[4], 1)^(1 / 6)
        eta = min(max(d2, d3), normA)
        quotients = minimum([d2 / normA, d3 / normA, d6 / normA])
        if (quotients < 1 / 2^4)  ## strong decay
            d9 = normAm(A, 9)^(1 / 9)
            d19 = normA
            eta = min([eta, max(d2, d9), max(d2, d19)])
        end
        s = max(ceil(log2(eta / theta[6])), 0)
        if isinf(s)
            # Overflow in ell subfunction. Revert to old estimate.
            x = opnorm(T, 1) / theta[end]
            t, s = significand(x) / 2, exponent(x) + 1
            s = s - Int(t == 0.5) # adjust s if normA/theta(end) is a power of 2.
        end

        return s, degree, Tp, scale

    end

    s, degree, Tp, scale = expm_params(A)

    # Rescale the powers of A appropriately.

    if s != 0
        for i in eachindex(Tp)
            Tp[i] ./= 2 .^ (s * scale[i])
        end
    end

    """
        taylor_approximant_of_degree(order, Tp)

    Evaluate the Taylor polynomial to given degree.
    Improved Paterson Stockmeyer scheme, with Pade at end point
    """

    order = degree

    X = Tp[1]
    order >= 4 && (A2 = Tp[2])
    order > 8  && (A3 = Tp[3])

    if order == 1
        E = I + X
    elseif order == 2
        E = I + X
    elseif order == 4
        E = I + X + A2 / 2 * (I + X / 3 + A2 / 3 / 4)
    elseif order == 8
        # Minimizes ||coefficients||_1
        x3 = 2 / 3
        a1 = 1 / 88 * (1 + sqrt(177)) * x3
        a2 = 1 / 352 * (1 + sqrt(177)) * x3
        u0 = 1
        u1 = 1
        u2 = 1 / 630 * (857 - 58 * sqrt(177))
        c0 = (-271 + 29 * sqrt(177)) / (315 * x3)
        c1 = (11 * (-1 + sqrt(177))) / (1260 * x3)
        c2 = (11 * (-9 + sqrt(177))) / (5040 * x3)
        c4 = -((-89 + sqrt(177)) / (5040 * x3^2))
        # Matrix products
        A4 = A2 * (a1 * X + a2 * A2)
        A8 = (x3 * A2 + 1 * A4) * (c0 * I + c1 * X + c2 * A2 + c4 * A4)
        E = u0 * I + u1 * X + u2 * A2 + +A8
    elseif order == 12 # lower values of ||_||_1
        a01 = -0.0186023205146205532243437300433
        a02 = 4.60000000000000000000000000000
        a03 = 0.211693118299809442949323323336
        a04 = 0
        a11 = -0.00500702322573317730979741843919
        a12 = 0.992875103538486836140479571505
        a13 = 0.158224384715726725371768893252
        a14 = -0.131810610138301840156819349464
        a21 = -0.573420122960522263905952420789
        a22 = -0.132445561052799638845074997454
        a23 = 0.165635169436727415011171668419
        a24 = -0.0202785554058925907933568229945
        a31 = -0.133399693943892059700768926983
        a32 = 0.00172990000000000000000000000000
        a33 = 0.0107862779315792425026320640108
        a34 = -0.00675951846863086359778560766482
        q31 = a01 * I + a11 * X + a21 * A2 + a31 * A3
        q32 = a02 * I + a12 * X + a22 * A2 + a32 * A3
        q33 = a03 * I + a13 * X + a23 * A2 + a33 * A3
        q34 = a04 * I + a14 * X + a24 * A2 + a34 * A3
        # Matrix products
        q61 = q33 + q34 * q34
        E = (q31 + (q32 + q61) * q61)
    elseif order == 18
        # Minimizes ||coefficients||_1
        a01 = 0
        a11 = -0.100365581030144620014614939405
        a21 = -0.00802924648241156960116919515244
        a31 = -0.000892138498045729955685466128049
        b01 = 0
        b11 = 0.3978497494996450761451961277102845756965081084076856223845951607640145373149032030404660339703426170
        b21 = 1.367837784604117199225237068782228242106453540654915795267462332707000476284638745738812082761860458
        b31 = 0.4982896225253826775568588172622733561849181397319696269923450773179744578906675427707618377504305561
        b61 = -0.0006378981945947233092415500564919285518773827581013332055148653241353120789646323186965398317523194760
        b02 = -10.96763960529620625935174626753684124863041876254774214673058385106461743913502064396554589372626845
        b12 = 1.680158138789061971827854017248105095278579593010566858091585875627364747895724070033586802947436157
        b22 = 0.05717798464788655127028717132252481317274975093707600394506105236273081373356534970690710643085727120
        b32 = -0.006982101224880520842904664838015531664389838152077753258353447125605205335288966277257918925881337834
        b62 = 0.00003349750170860705383133673406684398020225996444991565389728295589367037178816169580298011831485225359
        b03 = -0.09043168323908105619714688770183495499470866281162649735086602288456671216199491949073419844120202066
        b13 = -0.06764045190713819075600799003797074854562826013273060070581796689746210078264908587143648477465431597
        b23 = 0.06759613017704596460827991950789370565071000768138331455747136830953224978586305376886338214283464385
        b33 = 0.02955525704293155274260691822422312437473046472743510159951445245685893720550532525991666174704105350
        b63 = -0.00001391802575160607011247399793437424028074102305234870853220069004278819595344896368546425681168813708
        b04 = 0
        b14 = 0
        b24 = -0.0923364619367118592764570775143
        b34 = -0.0169364939002081717191385115723
        b64 = -0.0000140086798182036159794363205726

        # Extra matrix product
        A6 = Tp[4]
        q31 = a01 * I + a11 * X + a21 * A2 + a31 * A3
        q61 = (b01 * I + b11 * X + b21 * A2 + b31 * A3 + b61 * A6)
        q62 = (b02 * I + b12 * X + b22 * A2 + b32 * A3 + b62 * A6)
        q63 = (b03 * I + b13 * X + b23 * A2 + b33 * A3 + b63 * A6)
        q64 = (b04 * I + b14 * X + b24 * A2 + b34 * A3 + b64 * A6)
        q91 = q31 * q64 + q63
        q18 = q61 + (q62 + q91) * q91
        E = q18
    else
        throw("expm3= degree:$order")
    end

    for i = 1:s
        E = E * E  # Squaring
    end

    E

end


text:
module SINDY
    greet() = print("Bear with us while we install SINDY.jl - a library for sparse identification of Dynamical Systems.")

    include("Parsers.jl")
    include("NumDiff.jl")
    include("Library.jl")
    include("Optimizers.jl")

    # TODO create a SINDY class that takes in data, the optimizer type ...
    # This needs to be automated.
end


text:
@static if VERSION < v"0.7-"

    @enum OraResult::Int32 ORA_SUCCESS=0 ORA_FAILURE=-1
    @enum OraAuthMode::UInt32 ORA_MODE_AUTH_DEFAULT=0 ORA_MODE_AUTH_SYSDBA=2 ORA_MODE_AUTH_SYSOPER=4 ORA_MODE_AUTH_PRELIM=8 ORA_MODE_AUTH_SYSASM=32768 ORA_MODE_AUTH_SYSBKP=131072 ORA_MODE_AUTH_SYSDGD=262144 ORA_MODE_AUTH_SYSKMT=524288 ORA_MODE_AUTH_SYSRAC=1048576
    @enum OraConnCloseMode::UInt32 ORA_MODE_CONN_CLOSE_DEFAULT=0 ORA_MODE_CONN_CLOSE_DROP=1 ORA_MODE_CONN_CLOSE_RETAG=2
    @enum OraCreateMode::UInt32 ORA_MODE_CREATE_DEFAULT=0 ORA_MODE_CREATE_THREADED=1 ORA_MODE_CREATE_EVENTS=4
    @enum OraPoolGetMode::UInt8 ORA_MODE_POOL_GET_WAIT=0 ORA_MODE_POOL_GET_NOWAIT=1 ORA_MODE_POOL_GET_FORCEGET=2 ORA_MODE_POOL_GET_TIMEDWAIT=3
    @enum OraPurity::UInt32 ORA_PURITY_DEFAULT=0 ORA_PURITY_NEW=1 ORA_PURITY_SELF=2
    @enum OraShutdownMode::UInt32 ORA_MODE_SHUTDOWN_DEFAULT=0 ORA_MODE_SHUTDOWN_TRANSACTIONAL=1 ORA_MODE_SHUTDOWN_TRANSACTIONAL_LOCAL=2 ORA_MODE_SHUTDOWN_IMMEDIATE=3 ORA_MODE_SHUTDOWN_ABORT=4 ORA_MODE_SHUTDOWN_FINAL=5
    @enum OraStartupMode::UInt32 ORA_MODE_STARTUP_DEFAULT=0 ORA_MODE_STARTUP_FORCE=1 ORA_MODE_STARTUP_RESTRICT=2
    @enum OraExecMode::UInt32 ORA_MODE_EXEC_DEFAULT=0 ORA_MODE_EXEC_DESCRIBE_ONLY=16 ORA_MODE_EXEC_COMMIT_ON_SUCCESS=32 ORA_MODE_EXEC_BATCH_ERRORS=128 ORA_MODE_EXEC_PARSE_ONLY=256 ORA_MODE_EXEC_ARRAY_DML_ROWCOUNTS=1048576
    @enum OraOracleTypeNum::UInt32 ORA_ORACLE_TYPE_NONE=2000 ORA_ORACLE_TYPE_VARCHAR=2001 ORA_ORACLE_TYPE_NVARCHAR=2002 ORA_ORACLE_TYPE_CHAR=2003 ORA_ORACLE_TYPE_NCHAR=2004 ORA_ORACLE_TYPE_ROWID=2005 ORA_ORACLE_TYPE_RAW=2006 ORA_ORACLE_TYPE_NATIVE_FLOAT=2007 ORA_ORACLE_TYPE_NATIVE_DOUBLE=2008 ORA_ORACLE_TYPE_NATIVE_INT=2009 ORA_ORACLE_TYPE_NUMBER=2010 ORA_ORACLE_TYPE_DATE=2011 ORA_ORACLE_TYPE_TIMESTAMP=2012 ORA_ORACLE_TYPE_TIMESTAMP_TZ=2013 ORA_ORACLE_TYPE_TIMESTAMP_LTZ=2014 ORA_ORACLE_TYPE_INTERVAL_DS=2015 ORA_ORACLE_TYPE_INTERVAL_YM=2016 ORA_ORACLE_TYPE_CLOB=2017 ORA_ORACLE_TYPE_NCLOB=2018 ORA_ORACLE_TYPE_BLOB=2019 ORA_ORACLE_TYPE_BFILE=2020 ORA_ORACLE_TYPE_STMT=2021 ORA_ORACLE_TYPE_BOOLEAN=2022 ORA_ORACLE_TYPE_OBJECT=2023 ORA_ORACLE_TYPE_LONG_VARCHAR=2024 ORA_ORACLE_TYPE_LONG_RAW=2025 ORA_ORACLE_TYPE_NATIVE_UINT=2026 ORA_ORACLE_TYPE_MAX=2027
    @enum OraNativeTypeNum::UInt32 ORA_NATIVE_TYPE_INT64=3000 ORA_NATIVE_TYPE_UINT64=3001 ORA_NATIVE_TYPE_FLOAT=3002 ORA_NATIVE_TYPE_DOUBLE=3003 ORA_NATIVE_TYPE_BYTES=3004 ORA_NATIVE_TYPE_TIMESTAMP=3005 ORA_NATIVE_TYPE_INTERVAL_DS=3006 ORA_NATIVE_TYPE_INTERVAL_YM=3007 ORA_NATIVE_TYPE_LOB=3008 ORA_NATIVE_TYPE_OBJECT=3009 ORA_NATIVE_TYPE_STMT=3010 ORA_NATIVE_TYPE_BOOLEAN=3011 ORA_NATIVE_TYPE_ROWID=3012 ORA_NATIVE_TYPE_NUMBER=3013
    @enum OraStatementType::UInt16 ORA_STMT_TYPE_UNKNOWN=0 ORA_STMT_TYPE_SELECT=1 ORA_STMT_TYPE_UPDATE=2 ORA_STMT_TYPE_DELETE=3 ORA_STMT_TYPE_INSERT=4 ORA_STMT_TYPE_CREATE=5 ORA_STMT_TYPE_DROP=6 ORA_STMT_TYPE_ALTER=7 ORA_STMT_TYPE_BEGIN=8 ORA_STMT_TYPE_DECLARE=9 ORA_STMT_TYPE_CALL=10 ORA_STMT_TYPE_EXPLAIN_PLAN=15 ORA_STMT_TYPE_MERGE=16 ORA_STMT_TYPE_ROLLBACK=17 ORA_STMT_TYPE_COMMIT=21
    @enum OraPoolCloseMode::UInt32 ORA_MODE_POOL_CLOSE_DEFAULT=0x0000 ORA_MODE_POOL_CLOSE_FORCE=0x0001

else

    @enum OraResult::Int32 begin
        ORA_SUCCESS = 0
        ORA_FAILURE = -1
    end

    """
    This enumeration identifies the mode to use when authorizing connections to the database.
    """
    @enum OraAuthMode::UInt32 begin
        ORA_MODE_AUTH_DEFAULT = 0
        ORA_MODE_AUTH_SYSDBA  = 2
        ORA_MODE_AUTH_SYSOPER = 4
        ORA_MODE_AUTH_PRELIM  = 8
        ORA_MODE_AUTH_SYSASM  = 32768
        ORA_MODE_AUTH_SYSBKP  = 131072
        ORA_MODE_AUTH_SYSDGD  = 262144
        ORA_MODE_AUTH_SYSKMT  = 524288
        ORA_MODE_AUTH_SYSRAC  = 1048576
    end

    @enum OraConnCloseMode::UInt32 begin
        ORA_MODE_CONN_CLOSE_DEFAULT = 0
        ORA_MODE_CONN_CLOSE_DROP    = 1
        ORA_MODE_CONN_CLOSE_RETAG   = 2
    end

    """
    This enumeration identifies the mode to use when creating connections to the database. Note that the OCI objects mode is always enabled.
    """
    @enum OraCreateMode::UInt32 begin
        ORA_MODE_CREATE_DEFAULT  = 0
        ORA_MODE_CREATE_THREADED = 1
        ORA_MODE_CREATE_EVENTS   = 4
    end

    """
    This enumeration identifies the mode to use when getting sessions from a session pool.
    """
    @enum OraPoolGetMode::UInt8 begin
        ORA_MODE_POOL_GET_WAIT      = 0
        ORA_MODE_POOL_GET_NOWAIT    = 1
        ORA_MODE_POOL_GET_FORCEGET  = 2
        ORA_MODE_POOL_GET_TIMEDWAIT = 3
    end

    """
    This enumeration identifies the purity of the sessions that are acquired when using connection classes during connection creation.
    """
    @enum OraPurity::UInt32 begin
        ORA_PURITY_DEFAULT = 0
        ORA_PURITY_NEW     = 1
        ORA_PURITY_SELF    = 2
    end

    @enum OraShutdownMode::UInt32 begin
        ORA_MODE_SHUTDOWN_DEFAULT             = 0
        ORA_MODE_SHUTDOWN_TRANSACTIONAL       = 1
        ORA_MODE_SHUTDOWN_TRANSACTIONAL_LOCAL = 2
        ORA_MODE_SHUTDOWN_IMMEDIATE           = 3
        ORA_MODE_SHUTDOWN_ABORT               = 4
        ORA_MODE_SHUTDOWN_FINAL               = 5
    end

    @enum OraStartupMode::UInt32 begin
        ORA_MODE_STARTUP_DEFAULT  = 0
        ORA_MODE_STARTUP_FORCE    = 1
        ORA_MODE_STARTUP_RESTRICT = 2
    end

    @enum OraExecMode::UInt32 begin
        ORA_MODE_EXEC_DEFAULT             = 0
        ORA_MODE_EXEC_DESCRIBE_ONLY       = 16
        ORA_MODE_EXEC_COMMIT_ON_SUCCESS   = 32
        ORA_MODE_EXEC_BATCH_ERRORS        = 128
        ORA_MODE_EXEC_PARSE_ONLY          = 256
        ORA_MODE_EXEC_ARRAY_DML_ROWCOUNTS = 1048576
    end

    @enum OraOracleTypeNum::UInt32 begin
        ORA_ORACLE_TYPE_NONE          = 2000
        ORA_ORACLE_TYPE_VARCHAR       = 2001
        ORA_ORACLE_TYPE_NVARCHAR      = 2002
        ORA_ORACLE_TYPE_CHAR          = 2003
        ORA_ORACLE_TYPE_NCHAR         = 2004
        ORA_ORACLE_TYPE_ROWID         = 2005
        ORA_ORACLE_TYPE_RAW           = 2006
        ORA_ORACLE_TYPE_NATIVE_FLOAT  = 2007
        ORA_ORACLE_TYPE_NATIVE_DOUBLE = 2008
        ORA_ORACLE_TYPE_NATIVE_INT    = 2009
        ORA_ORACLE_TYPE_NUMBER        = 2010
        ORA_ORACLE_TYPE_DATE          = 2011
        ORA_ORACLE_TYPE_TIMESTAMP     = 2012
        ORA_ORACLE_TYPE_TIMESTAMP_TZ  = 2013
        ORA_ORACLE_TYPE_TIMESTAMP_LTZ = 2014
        ORA_ORACLE_TYPE_INTERVAL_DS   = 2015
        ORA_ORACLE_TYPE_INTERVAL_YM   = 2016
        ORA_ORACLE_TYPE_CLOB          = 2017
        ORA_ORACLE_TYPE_NCLOB         = 2018
        ORA_ORACLE_TYPE_BLOB          = 2019
        ORA_ORACLE_TYPE_BFILE         = 2020
        ORA_ORACLE_TYPE_STMT          = 2021
        ORA_ORACLE_TYPE_BOOLEAN       = 2022
        ORA_ORACLE_TYPE_OBJECT        = 2023
        ORA_ORACLE_TYPE_LONG_VARCHAR  = 2024
        ORA_ORACLE_TYPE_LONG_RAW      = 2025
        ORA_ORACLE_TYPE_NATIVE_UINT   = 2026
        ORA_ORACLE_TYPE_MAX           = 2027
    end

    @enum OraNativeTypeNum::UInt32 begin
        ORA_NATIVE_TYPE_INT64       = 3000
        ORA_NATIVE_TYPE_UINT64      = 3001
        ORA_NATIVE_TYPE_FLOAT       = 3002
        ORA_NATIVE_TYPE_DOUBLE      = 3003
        ORA_NATIVE_TYPE_BYTES       = 3004
        ORA_NATIVE_TYPE_TIMESTAMP   = 3005
        ORA_NATIVE_TYPE_INTERVAL_DS = 3006
        ORA_NATIVE_TYPE_INTERVAL_YM = 3007
        ORA_NATIVE_TYPE_LOB         = 3008
        ORA_NATIVE_TYPE_OBJECT      = 3009
        ORA_NATIVE_TYPE_STMT        = 3010
        ORA_NATIVE_TYPE_BOOLEAN     = 3011
        ORA_NATIVE_TYPE_ROWID       = 3012
        ORA_NATIVE_TYPE_NUMBER      = 3013
    end

    @enum OraStatementType::UInt16 begin
        ORA_STMT_TYPE_UNKNOWN      = 0
        ORA_STMT_TYPE_SELECT       = 1
        ORA_STMT_TYPE_UPDATE       = 2
        ORA_STMT_TYPE_DELETE       = 3
        ORA_STMT_TYPE_INSERT       = 4
        ORA_STMT_TYPE_CREATE       = 5
        ORA_STMT_TYPE_DROP         = 6
        ORA_STMT_TYPE_ALTER        = 7
        ORA_STMT_TYPE_BEGIN        = 8
        ORA_STMT_TYPE_DECLARE      = 9
        ORA_STMT_TYPE_CALL         = 10
        ORA_STMT_TYPE_EXPLAIN_PLAN = 15
        ORA_STMT_TYPE_MERGE        = 16
        ORA_STMT_TYPE_ROLLBACK     = 17
        ORA_STMT_TYPE_COMMIT       = 21
    end

    @enum OraPoolCloseMode::UInt32 begin
        ORA_MODE_POOL_CLOSE_DEFAULT = 0x0000
        ORA_MODE_POOL_CLOSE_FORCE   = 0x0001
    end
end

#
# Enable bit arithmetic for enums that can be "ored" together.
#

Base.:|(a::OraExecMode, b::OraExecMode) = UInt32(a) | UInt32(b)


text:
"""
    Econometrics

    Econometrics in Julia.
"""
module Econometrics
    using Base.Iterators: flatten
    using CategoricalArrays: CategoricalValue, levels, levels!, isordered, ordered!
    using Distributions: cdf, ccdf, Categorical, Chisq, FDist, logpdf, Logistic, Normal,
                         pdf, TDist,
                         # Statistics
                         mean, quantile, var
    using FillArrays: Ones
    using ForwardDiff: Dual, value
    using LinearAlgebra: bunchkaufman, bunchkaufman!, cholesky!, diag, diagm,
                         Diagonal, Hermitian, I, LowerTriangular, qr,
                         UpperTriangular
    using Optim: hessian!, optimize, minimizer, TwiceDifferentiable
    using Parameters: @unpack, @pack!
    using Printf: @sprintf
    using StatsBase: aic, aicc, bic, harmmean, FrequencyWeights, CoefTable,
                     ConvergenceException, Weights
    using StatsFuns: softmax
    using StatsModels: AbstractContrasts, AbstractTerm, apply_schema, CategoricalTerm,
                       ConstantTerm, ContrastsMatrix, DummyCoding, @formula, FormulaTerm,
                       FunctionTerm, InteractionTerm, InterceptTerm, MatrixTerm,
                       modelcols, schema, terms, termvars
    using Tables: Tables, columns, eachcolumn, select, materializer, rows
    import Base: show
    import StatsBase: coef, coefnames, coeftable, confint, deviance, islinear,
                      nulldeviance, loglikelihood, nullloglikelihood, score, nobs,
                      dof, dof_residual, mss, rss, informationmatrix, vcov, stderror,
                      weights, isfitted, fit, fit!, r2, adjr2, fitted, response,
                      meanresponse, modelmatrix, leverage, residuals, predict,
                      predict!, dof_residual, RegressionModel, params
    import StatsModels: hasintercept, implicit_intercept
    # Compat
    if !@isdefined(isnothing)
        isnothing(::Any) = false
        isnothing(::Nothing) = true
    end
    if !@isdefined(ismissing)
        isnothing(::Any) = false
        isnothing(::Missing) = true
    end
    foreach(file -> include(joinpath(dirname(@__DIR__), "src", "$file.jl")),
            ["structs", "transformations", "formula", "main", "solvers", "statsbase", "wald"])
    export @formula, DummyCoding, aic, aicc, bic, coef, coefnames,
           coeftable, confint, deviance, islinear, nulldeviance, loglikelihood,
           nullloglikelihood, nobs, dof, mss, rss, informationmatrix, vcov, stderror,
           weights, isfitted, fit, fit!, r2, adjr2, fitted, response, meanresponse,
           modelmatrix, leverage, residuals, predict, dof_residual,
           params,
           hasintercept,
           EconometricModel, absorb, BetweenEstimator, RandomEffectsEstimator,
           ContinuousResponse,
           OIM, HC0, HC1, HC2, HC3, HC4
end


text:
using Oceananigans.Architectures
using Oceananigans.Architectures: device_event
using Oceananigans.BoundaryConditions
using Oceananigans.TurbulenceClosures: calculate_diffusivities!
using Oceananigans.ImmersedBoundaries: mask_immersed_field!, mask_immersed_reduced_field_xy!
using Oceananigans.Models.NonhydrostaticModels: update_hydrostatic_pressure!

import Oceananigans.TimeSteppers: update_state!

compute_auxiliary_fields!(auxiliary_fields) = Tuple(compute!(a) for a in auxiliary_fields)

"""
    update_state!(model::HydrostaticFreeSurfaceModel)

Update peripheral aspects of the model (auxiliary fields, halo regions, diffusivities,
hydrostatic pressure) to the current model state.
"""
update_state!(model::HydrostaticFreeSurfaceModel) = update_state!(model, model.grid)

function update_state!(model::HydrostaticFreeSurfaceModel, grid)

    @apply_regionally masking_actions!(model)

    fill_halo_regions!(prognostic_fields(model), model.clock, fields(model))
    fill_horizontal_velocity_halos!(model.velocities.u, model.velocities.v, model.architecture)

    @apply_regionally update_state_actions!(model)

    fill_halo_regions!(model.velocities.w, model.clock, fields(model))
    fill_halo_regions!(model.diffusivity_fields, model.clock, fields(model))
    fill_halo_regions!(model.pressure.pHY′)

    return nothing
end

# Mask immersed fields
function masking_actions!(model)
    η = displacement(model.free_surface)
    masking_events = Any[mask_immersed_field!(field)
                         for field in merge(model.auxiliary_fields, prognostic_fields(model)) if field !== η]
    push!(masking_events, mask_immersed_reduced_field_xy!(η, k=size(model.grid, 3)))
    wait(device(model.architecture), MultiEvent(Tuple(masking_events)))
end

function update_state_actions!(model)
    compute_w_from_continuity!(model)
    calculate_diffusivities!(model.diffusivity_fields, model.closure, model)
    update_hydrostatic_pressure!(model.pressure.pHY′, model.architecture, model.grid, model.buoyancy, model.tracers)
end


text:
function multistar_inequalities!(
    cut_manager::CutManager,
    demand::Vector{Int},
    capacity::Int,
    edge_tail::Vector{Int},
    edge_head::Vector{Int},
    edge_x::Vector{Float64};
    max_n_cuts = 100
)
    n_customers, _demand, n_edges, _edge_tail, _edge_head, _edge_x = input_conversion(demand, edge_tail, edge_head, edge_x)

    new_cmp = init_CMP()
    _max_violation = Ref{Cdouble}()

    ccall(
        (:MSTARSEP_SeparateMultiStarCuts, LIB_CVRPSEP),
        Cvoid,
        (
            Cint, Ptr{Cint}, Cint,
            Cint, Ptr{Cint}, Ptr{Cint}, Ptr{Cdouble},
            CnstrMgrPointer, Cint,
            Ref{Cdouble}, CnstrMgrPointer
        ),
        Cint(n_customers), Cint.(_demand), Cint(capacity),
        Cint(n_edges), Cint.(_edge_tail), Cint.(_edge_head), Cdouble.(_edge_x),
        cut_manager.cmp, Cint(max_n_cuts),
        _max_violation, new_cmp
    )

    cut_manager.new_cuts = convert_CMP(new_cmp)
    jl_CMGR_MoveCnstr!(new_cmp, cut_manager.cmp)
    cut_manager.violation = Float64(_max_violation[])

    N = map(x -> x.IntList, cut_manager.new_cuts)
    T = map(x -> x.ExtList, cut_manager.new_cuts)
    C = map(x -> x.CList, cut_manager.new_cuts)

    for i in 1:length(N)
        N[i] .+= 1
        T[i] .+= 1
        C[i] .+= 1
    end

    A = map(x -> x.A, cut_manager.new_cuts)
    B = map(x -> x.B, cut_manager.new_cuts)
    L = map(x -> x.L, cut_manager.new_cuts)

    return N, T, C, A, B, L
end


text:
# usage example for package developer:
#
#     import EdgeDetectionAPI: AbstractEdgeThinningAlgorithm,
#                             thin_edges, thin_edges!,
#                             thin_subpixel_edges, thin_subpixel_edges!

"""
    AbstractEdgeThinningAlgorithm <: AbstractImageFilter

A root type for `ImageEdgeDetection` package.

Any concrete edge thinning algorithm shall subtype it to support
[`thin_edges`](@ref), [`thin_edges!`](@ref), [`thin_subpixel_edges`](@ref) and
[`thin_subpixel_edges!`](@ref)  APIs.

# Examples

All edge thinning algorithms in ImageEdgeDetection are called in the
following pattern:

```julia
# first generate an algorithm instance
f = NonmaximaSuppression()

# determine the image gradients
g₁, g₂ = imgradients(img, KernelFactors.scharr)

# determine the gradient magnitude
mag = hypot.(g₁, g₂)

# then pass the algorithm to `thin_edges`
thinned_edges = thin_edges(mag, g₁, g₂, f)

# or use in-place version `thin_edges!`
thinned_edges = zeros(eltype(mag), axes(mag))
thin_edges!(thinned_edges, mag, g₁, g₂, f)
```

For more examples, please check [`thin_edges`](@ref),
[`thin_edges!`](@ref) and concrete algorithms.

One can also perform non-maxima suppression to subpixel precision using
[`thin_subpixel_edges`](@ref) and [`thin_subpixel_edges!`](@ref).
The function returns an edge image as well as a accompanying matrix of length-2
vectors which, when added to the edge image coordinates, specify the location
of an edge to subpixel precision.

"""
abstract type AbstractEdgeThinningAlgorithm <: AbstractImageFilter end

thin_edges(mag::AbstractArray, g₁::AbstractArray, g₂::AbstractArray,
           f::AbstractEdgeThinningAlgorithm,
           args...; kwargs...) =
           f(zeros(eltype(mag), axes(mag)), mag, g₁, g₂, args... ; kwargs...)

function thin_edges(::Type{T},
                    mag::AbstractArray,
                    g₁::AbstractArray,
                    g₂::AbstractArray,
                    f::AbstractEdgeThinningAlgorithm,
                    args...; kwargs...) where T
    out = zeros(T, axes(mag))
    return f(out, mag, g₁, g₂, args...; kwargs...)
end

thin_edges!(out::AbstractArray, mag::AbstractArray, g₁::AbstractArray, g₂::AbstractArray,
            f::AbstractEdgeThinningAlgorithm,
            args...; kwargs...) =
            f(out, mag, g₁, g₂, args...; kwargs...)

thin_edges!(mag::AbstractArray, g₁::AbstractArray, g₂::AbstractArray,
            f::AbstractEdgeThinningAlgorithm,
            args...; kwargs...) =
            f(mag, mag, g₁, g₂, args...; kwargs...)

thin_subpixel_edges(mag::AbstractArray, g₁::AbstractArray, g₂::AbstractArray,
                    f::AbstractEdgeThinningAlgorithm,
                    args...; kwargs...) =
                    f(zeros(eltype(mag), axes(mag)), zeros(SVector{2,Float64}, axes(mag)), mag, g₁, g₂, args... ; kwargs...)

thin_subpixel_edges!(out₁::AbstractArray, out₂::AbstractArray{<:StaticVector}, mag::AbstractArray, g₁::AbstractArray, g₂::AbstractArray,
            f::AbstractEdgeThinningAlgorithm,
            args...; kwargs...) =
            f(out₁, out₂, mag, g₁, g₂, args...; kwargs...)

function thin_subpixel_edges(::Type{T₁}, ::Type{T₂},
                    mag::AbstractArray,
                    g₁::AbstractArray,
                    g₂::AbstractArray,
                    f::AbstractEdgeThinningAlgorithm,
                    args...; kwargs...) where {T₁, T₂}
    out₁ = zeros(T₁, axes(mag))
    out₂ = zeros(T₂, axes(mag))
    return f(out₁, out₂, mag, g₁, g₂, args...; kwargs...)
end
### Docstrings

"""
    thin_edges!([out,] mag, g₁, g₂, f::AbstractEdgeThinningAlgorithm, args...; kwargs...)

Isolate local maxima of gradient magnitude `mag` along the local gradient
direction. The arguments `g₁` and `g₂` represent the  gradient in the first
spatial dimension (y), and the second spatial dimension (x), respectively.

# Output

If `out` is specified, it will be changed in place. Otherwise `mag` will be changed in place.

# Examples

Just simply pass an algorithm to `thin_edges!`:

```julia
using TestImages, ImageFiltering
img =  Gray.(testimage("mandril"))

# Gradient in the first and second spatial dimension
g₁, g₂ = imgradients(img, KernelFactors.scharr)

# Gradient magnitude
mag = hypot.(g₁, g₂)

f = SubpixelNonmaximaSuppression(threshold = Percentile(10))

thinned_edges = zeros(eltype(mag), axes(mag))
thin_edges!(thinned_edges, mag, g₁, g₂, f)
```

For cases you just want to change `mag` in place, you don't necessarily need to manually
allocate `thinned_edges`; just use the convenient method:

```julia
thin_edges!(mag, g₁, g₂, f)
```

See also: [`thin_edges`](@ref)
"""
thin_edges!

"""
    thin_edges([T::Type,] mag, g₁, g₂, f::AbstractEdgeThinningAlgorithm, args...; kwargs...)

Using algorithm `f`, thin the edge-response based on the edge magnitude `mag`,
the  gradient in the first spatial dimension `g₁`, and the gradient in the
second spatial dimension `g₂`.

# Output

Returns an `Array{T}` representing the thinned edge response.

If `T` is not specified, then it's inferred. Note that `T` must represent or
wrap a floating point number.

# Examples

Just simply pass the input image and algorithm to `thin_edges`

```julia
using TestImages, ImageFiltering
img =  Gray.(testimage("mandril"))

# Gradient in the first and second spatial dimension
g₁, g₂ = imgradients(img, KernelFactors.scharr)

# Gradient magnitude
mag = hypot.(g₁, g₂)

f = NonmaximaSuppression(threshold = Percentile(10))

thinned_edges = thin_edges(mag, g₁, g₂, f)
```

This reads as "`thin_edges` based on the edge response magnitude, and spatial gradients, using algorithm `f`".

You can also explicitly specify the return type:

```julia
thinned_edges_float32 = thin_edges(Gray{Float32}, mag, g₁, g₂, f)
```

See also [`thin_edges!`](@ref) for in-place edge thinning.
"""
thin_edges

"""
    out₁, out₂ = thin_subpixel_edges(mag, g₁, g₂, f::AbstractEdgeThinningAlgorithm, args...; kwargs...)

Isolate local maxima of gradient magnitude `mag` along the local gradient
direction. The arguments `g₁` and `g₂` represent the  gradient in the first
spatial dimension (y), and the second spatial dimension (x), respectively.

# Output

The integer components of the local maxima correspond to non-zero row and column
entries in `out₁`. The accompanying subpixel offsets  are stored in a 2-D array
`out₂` as length-2 vectors. One can recover the  subpixel coordinates by adding
the subpixel offsets to the integer components.

# Examples

Just simply pass an algorithm to `thin_subpixel_edges`:

```julia
using TestImages, ImageFiltering
img =  Gray.(testimage("mandril"))

# Gradient in the first and second spatial dimension
g₁, g₂ = imgradients(img, KernelFactors.scharr)

# Gradient magnitude
mag = hypot.(g₁, g₂)

f = SubpixelNonmaximaSuppression(threshold = Percentile(10))

thinned_edges, offsets = thin_subpixel_edges(mag, g₁, g₂, f)
```

See also: [`thin_subpixel_edges!`](@ref)
"""
thin_subpixel_edges

"""
    thin_subpixel_edges!(out₁, out₂, mag, g₁, g₂, f::AbstractEdgeThinningAlgorithm, args...; kwargs...)

Isolate local maxima of gradient magnitude `mag` along the local gradient
direction. The arguments `g₁` and `g₂` represent the  gradient in the first
spatial dimension (y), and the second spatial dimension (x), respectively.

# Output

The integer components of the local maxima correspond to non-zero row and column
entries in `out₁`. The accompanying subpixel offsets  are stored in a 2-D array
`out₂` as length-2 vectors. One can recover the  subpixel coordinates by adding
the subpixel offsets to the integer components.

# Examples

Just simply pass an algorithm to `thin_subpixel_edges!`:

```julia
using TestImages, ImageFiltering
img =  Gray.(testimage("mandril"))

# Gradient in the first and second spatial dimension
g₁, g₂ = imgradients(img, KernelFactors.scharr)

# Gradient magnitude
mag = hypot.(g₁, g₂)

f = SubpixelNonmaximaSuppression(threshold = Percentile(10))

thinned_edges = zeros(eltype(mag), axes(mag))
offsets = zeros(SVector{2,Float64}, axes(mag))
thin_subpixel_edges!(thinned_edges, offsets, mag, g₁, g₂, f)
```

See also: [`thin_subpixel_edges`](@ref)
"""
thin_subpixel_edges!


text:
include("utils.jl")

abstract type Optimizer end

abstract type GlobalOptimizer <: Optimizer end

abstract type LocalOptimizer <: Optimizer end

isglobal(::GlobalOptimizer) = true
isglobal(::LocalOptimizer) = false

minimize(o::Optimizer, f, g, lb, ub, x) =
    minimize(o, wrap_function(f, g), lb, ub, x)
maximize(o::Optimizer, f, g, lb, ub, x) =
    maximize(o, wrap_function(f, g), lb, ub, x)
isglobal(o::Optimizer) = error("not implemented")
need_gradient(o::Optimizer) = error("not implemented")

include("stochastic_opt.jl")
include("gradient_opt.jl")
include("gradfree_opt.jl")


text:
function KeplerianPropagatorBuilder(arg0::Orbit, arg1::PositionAngle, arg2::jdouble)
    return KeplerianPropagatorBuilder((Orbit, PositionAngle, jdouble), arg0, arg1, arg2)
end

function KeplerianPropagatorBuilder(arg0::Orbit, arg1::PositionAngle, arg2::jdouble, arg3::AttitudeProvider)
    return KeplerianPropagatorBuilder((Orbit, PositionAngle, jdouble, AttitudeProvider), arg0, arg1, arg2, arg3)
end

function build_propagator(obj::KeplerianPropagatorBuilder, arg0::Vector{jdouble})
    return jcall(obj, "buildPropagator", Propagator, (Vector{jdouble},), arg0)
end


text:
"""Compare the feature value in an example to the feature value in this question."""
function match(question::Question, example::Features):Bool
    val = example[question.column]
    ifelse(is_numeric(val), val >= question.value, val == question.value)
end


text:
include("../src/RationalFunctionFields.jl")

using .RationalFunctionFields
using .RationalFunctionFields: load_generators, new_generating_set,
                         field_generators

Singular = RationalFunctionFields.Singular
Logging = RationalFunctionFields.Logging

logger = Logging.SimpleLogger(stderr, Logging.Debug)
Logging.global_logger(logger)

set = load_generators("../data/Sontag_gen.txt")

gb = new_generating_set(set)

println( "\ngb ", gb )
println( "\ngenerators ", field_generators(gb) )


text:
using Base.Test
using Lattice