diff --git a/src/linalg.jl b/src/linalg.jl index 446f071d..9caf66d0 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -988,18 +988,21 @@ default, throws an `ArgumentError`, so `cond(A)` does too: use `cond(Array(A))` small enough to store dense. """ function cond(A::AbstractSparseMatrixCSC, p::Real=2) + # the type of dense `cond`, whichever precision the factorization of `A` works in + T = float(real(eltype(A))) # as for a dense matrix; the estimator of the inverse's norm needs at least one column - (p == 1 || p == Inf) && checksquare(A) == 0 && return zero(real(float(eltype(A)))) + (p == 1 || p == Inf) && checksquare(A) == 0 && return zero(T) if p == 1 || p == Inf - # a singular matrix has an infinite condition number, as for a dense matrix; a - # triangular or diagonal `factorize(A)` reports it only in the solve - normAinv = try - p == 1 ? opnormestinv(A) : opnormestinv(copy(A')) + B = p == 1 ? A : copy(A') + # a singular matrix has an infinite condition number, as for a dense matrix + F = try + factorize(B) catch e e isa SingularException || rethrow() - return convert(float(real(eltype(A))), Inf) + return convert(T, Inf) end - return opnorm(A, p) * normAinv + _issingularfactor(F) && return convert(T, Inf) + return convert(T, opnorm(A, p) * _opnormestinv(B, F, min(2, size(B, 1)))) elseif p == 2 throw(ArgumentError("only 1- and Inf-norm condition numbers are implemented for sparse matrices, for 2-norm try cond(Array(A), 2) instead")) else @@ -1007,11 +1010,22 @@ function cond(A::AbstractSparseMatrixCSC, p::Real=2) end end -function opnormestinv(A::AbstractSparseMatrixCSC{T}, t::Integer = min(2,maximum(size(A)))) where T +# `factorize` throws for a singular LU or Hermitian factorization, but leaves a triangular +# or diagonal one to the solve +_issingularfactor(F) = false +_issingularfactor(D::Diagonal) = any(iszero, D.diag) +_issingularfactor(L::LowerTriangular{<:Any,<:AbstractSparseMatrixCSC}) = !iszero(_trisingular(false, parent(L))) +_issingularfactor(U::UpperTriangular{<:Any,<:AbstractSparseMatrixCSC}) = !iszero(_trisingular(true, parent(U))) + +function opnormestinv(A::AbstractSparseMatrixCSC, t::Integer = min(2,maximum(size(A)))) + checksquare(A) + return _opnormestinv(A, factorize(A), t) +end + +# the estimate for the factorization `F` of `A` +function _opnormestinv(A::AbstractSparseMatrixCSC{T}, F, t::Integer) where T maxiter = 5 - # Check the input - n = checksquare(A) - F = factorize(A) + n = size(A, 1) if t <= 0 throw(ArgumentError("number of blocks must be a positive integer")) end diff --git a/test/solvers/issues.jl b/test/solvers/issues.jl index 60e094f9..2eff6dfa 100644 --- a/test/solvers/issues.jl +++ b/test/solvers/issues.jl @@ -256,8 +256,13 @@ end @testset "cond of a singular matrix is Inf" begin @test cond(sparse([1.0 2; 3 6]), 1) === Inf - # the triangular solve, not the factorization, finds this one singular + # `factorize` leaves the zero on the diagonal of a triangular or diagonal factor @test cond(sparse([1.0 0; 2 0]), Inf) === Inf + @test cond(sparse([1.0 0; 0 0]), 1) === Inf + # Hermitian, so `factorize` tries Cholesky and LDLt before the LU finds it singular + @test cond(sparse([1.0 1; 1 1]), 1) === Inf + # the LU works in double precision, but the result is of the matrix's precision + @test cond(sparse(Float32[1 2; 3 4]), 1) isa Float32 end end # module