Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions .ci/check-whitespace.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env julia

const patterns = split("""
*.jl
*.md
*.yml
*Makefile
""")

const is_gha = something(tryparse(Bool, get(ENV, "GITHUB_ACTIONS", "false")), false)

# Note: `git ls-files` gives `/` as a path separator on Windows,
# so we just use `/` for all platforms.
allow_tabs(path) =
endswith(path, "Makefile") ||
endswith(path, ".make") ||
endswith(path, ".mk")

function check_whitespace()
errors = Set{Tuple{String,Int,String}}()
files_to_check = filter(arg -> !startswith(arg, "-"), ARGS)
if isempty(files_to_check)
if "--stdin" in ARGS
files_to_check = collect(eachline(stdin))
else
files_to_check = collect(eachline(`git ls-files -- $patterns`))
end
end

files_fixed = 0
if "--fix" in ARGS
for path in files_to_check
content = newcontent = read(path, String)
isempty(content) && continue
if !allow_tabs(path)
tabpattern = r"^([ \t]+)"m => (x -> replace(x, r"((?: {4})*)( *\t)" => s"\1 ")) # Replace tab sequences at start of line after any number of 4-space groups
newcontent = replace(newcontent, tabpattern)
end
newcontent = replace(newcontent,
r"\s*$" => '\n', # Remove trailing whitespace and normalize line ending at eof
r"\s*?[\r\n]" => '\n', # Remove trailing whitespace and normalize line endings on each line
r"\xa0" => ' ' # Replace non-breaking spaces
)
if content != newcontent
write(path, newcontent)
files_fixed += 1
end
end
if files_fixed > 0
println(stderr, "Fixed whitespace issues in $files_fixed files.")
end
end

for path in files_to_check
lineno = 0
non_blank = 0

file_err(msg) = push!(errors, (path, 0, msg))
line_err(msg) = push!(errors, (path, lineno, msg))

isfile(path) || continue
for line in eachline(path, keep=true)
lineno += 1
contains(line, '\r') && file_err("non-UNIX line endings")
contains(line, '\ua0') && line_err("non-breaking space")
allow_tabs(path) ||
contains(line, '\t') && line_err("tab")
endswith(line, '\n') || line_err("no trailing newline")
line = chomp(line)
endswith(line, r"\s") && line_err("trailing whitespace")
contains(line, r"\S") && (non_blank = lineno)
end
non_blank < lineno && line_err("trailing blank lines")
end

if isempty(errors)
println(stderr, "Whitespace check found no issues.")
exit(0)
else
println(stderr, "Whitespace check found $(length(errors)) issues:")
for (path, lineno, msg) in sort!(collect(errors))
if lineno == 0
println(stderr, "$path -- $msg")
if is_gha
println(stdout, "::warning title=Whitespace check,file=", path, "::", msg)
end
else
println(stderr, "$path:$lineno -- $msg")
if is_gha
println(stdout, "::warning title=Whitespace check,file=", path, ",line=", lineno, "::", msg)
end
end
end
exit(1)
end
end

check_whitespace()
26 changes: 26 additions & 0 deletions .github/workflows/Whitespace.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Whitespace

permissions: {}

on:
push:
branches:
- main
pull_request:

jobs:
whitespace:
name: Check whitespace
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Checkout the JuliaSparse/SparseArrays.jl repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: julia-actions/setup-julia@f6f565d9f7cf12f53dc8045742460d6260ad3b39 # v3.0.1
with:
version: '1.11.6'
- name: Check whitespace
run: |
julia .ci/check-whitespace.jl
2 changes: 1 addition & 1 deletion gen/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ all: clean download
julia --project generator.jl ./SuiteSparse-$(VER)

clean:
rm -fr *.tar.gz SuiteSparse*
rm -fr *.tar.gz SuiteSparse*

download:
curl -L -O https://github.com/JuliaBinaryWrappers/SuiteSparse_jll.jl/releases/download/SuiteSparse-v$(VER)%2B0/SuiteSparse.v$(VER).x86_64-linux-gnu.tar.gz
2 changes: 1 addition & 1 deletion src/SparseArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ if Base.USE_GPL_LIBS
include("solvers/spqr.jl")
end

zero(a::AbstractSparseArray) = spzeros(eltype(a), size(a)...)
zero(a::AbstractSparseArray{Tv,Ti}) where {Tv,Ti} = spzeros(Tv, Ti, size(a)...)

LinearAlgebra.diagzero(D::Diagonal{<:AbstractSparseMatrix{T}},i,j) where {T} =
spzeros(T, size(D.diag[i], 1), size(D.diag[j], 2))
Expand Down
3 changes: 2 additions & 1 deletion src/solvers/cholmod.jl
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ function Sparse(A::SparseMatrixCSC{<:Union{ComplexF16, ComplexF32}}, stype::Inte
end

# convert SparseVectors into CHOLMOD Sparse types through a mx1 CSC matrix
Sparse(A::SparseVector) = Sparse(SparseMatrixCSC(A))
Sparse(A::SparseVector) = Sparse(SparseMatrixCSC(A), 0)
function Sparse{Tv, Ti}(A::SparseMatrixCSC) where {Tv<:VTypes, Ti<:ITypes}
o = Sparse{Tv, Ti}(A, 0)
# check if array is symmetric and change stype if it is
Expand Down Expand Up @@ -1135,6 +1135,7 @@ function SparseVector{Tv, Ti}(A::Sparse{Tv, Ti}) where {Tv, Ti<:ITypes}
end
args = _extract_args(s, Tv)
s.sorted == 0 && _sort_buffers!(args...);
_trim_nz_builder!(args...)
return SparseVector(args[1], args[4], args[5])
end

Expand Down
4 changes: 2 additions & 2 deletions src/solvers/spqr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function _qr!(ordering::Integer, tol::Real, econ::Integer, getCTX::Integer,
# Free memory allocated by SPQR. This call will make sure that the
# correct deallocator function is called and that the memory count in
# the common struct is updated
Ti === Int64 ?
Ti === Int64 ?
cholmod_l_free(n, sizeof(Ti), e, CHOLMOD.getcommon(Ti)) :
cholmod_free(n, sizeof(Ti), e, CHOLMOD.getcommon(Ti))
end
Expand All @@ -100,7 +100,7 @@ function _qr!(ordering::Integer, tol::Real, econ::Integer, getCTX::Integer,
# Free memory allocated by SPQR. This call will make sure that the
# correct deallocator function is called and that the memory count in
# the common struct is updated
Ti === Int64 ?
Ti === Int64 ?
cholmod_l_free(m, sizeof(Ti), hpinv, CHOLMOD.getcommon(Ti)) :
cholmod_free(m, sizeof(Ti), hpinv, CHOLMOD.getcommon(Ti))
end
Expand Down
3 changes: 2 additions & 1 deletion src/solvers/umfpack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,8 @@ function lu!(F::UmfpackLU{Tv, Ti}, S::AbstractSparseMatrixCSC;
return lu!(F; reuse_symbolic, check, q)
end

function lu!(F::UmfpackLU; check::Bool=true, reuse_symbolic::Bool=true, q=nothing)
function lu!(F::UmfpackLU{Tv, Ti}; check::Bool=true, reuse_symbolic::Bool=true,
q=nothing) where {Tv, Ti}
if !reuse_symbolic && _isnotnull(F.symbolic)
F.symbolic = Symbolic{Tv, Ti}(C_NULL)
end
Expand Down
1 change: 0 additions & 1 deletion src/sparseconvert.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,3 @@ function _sparse_gen(m, n, newcolptr, newrowval, newnzval)
newcolptr[1] = 1
SparseMatrixCSC(m, n, newcolptr, newrowval, newnzval)
end

12 changes: 5 additions & 7 deletions src/sparsematrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4513,7 +4513,7 @@ function Base.swaprows!(A::AbstractSparseMatrixCSC, i, j)
iidx = searchsortedfirst(@view(rows[rr]), i)
has_i = iidx <= length(rr) && rows[rr[iidx]] == i

jrange = has_i ? (iidx:last(rr)) : rr
jrange = has_i ? (rr[iidx]:last(rr)) : rr
jidx = searchsortedlast(@view(rows[jrange]), j)
has_j = jidx != 0 && rows[jrange[jidx]] == j

Expand All @@ -4527,18 +4527,16 @@ function Base.swaprows!(A::AbstractSparseMatrixCSC, i, j)
# Update the rowval and then rotate both nonzeros
# and the remaining rowvals into the correct place
rows[rr[iidx]] = j
jidx == 0 && continue
rotate_range = rr[iidx]:jrange[jidx]
circshift!(@view(vals[rotate_range]), 1)
circshift!(@view(rows[rotate_range]), 1)
circshift!(@view(vals[rotate_range]), -1)
circshift!(@view(rows[rotate_range]), -1)
else
# Same as i, but in the opposite direction
@assert has_j
rows[jrange[jidx]] = i
iidx > length(rr) && continue
rotate_range = rr[iidx]:jrange[jidx]
circshift!(@view(vals[rotate_range]), -1)
circshift!(@view(rows[rotate_range]), -1)
circshift!(@view(vals[rotate_range]), 1)
circshift!(@view(rows[rotate_range]), 1)
end
end
return nothing
Expand Down
2 changes: 1 addition & 1 deletion src/sparsevector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,7 @@ for (fun, comp, word) in ((:findmin, :(<), "minimum"), (:findmax, :(>), "maximum
m == n && return val, index
nzinds = nonzeroinds(x)
zeroval = f(zero(T))
$comp(val, zeroval) && return val, nzinds[index]
($comp(val, zeroval) || isnan(val)) && return val, nzinds[index]
# we need to find the first zero, which could be stored or implicit
# we try to avoid findfirst(iszero, x)
sindex = findfirst(_iszero, nzvals) # first stored zero, if any
Expand Down
10 changes: 9 additions & 1 deletion test/cholmod.jl
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,14 @@ end
@test chI \ sparseI ≈ sparseI
end

@testset "Issue 630" begin
sparseI = sparse(1.0I, 1, 1)
@test cholesky(sparseI) \ sparse([1.0]) == [1]
sparseI = sparse(1.0I, 2, 2)
res = cholesky(sparseI) \ spzeros(2)
@test isempty(nonzeros(res))
end

@testset "Real factorization and complex rhs" begin
A = sprandn(5, 5, 0.4) |> t -> t't + I
B = complex.(randn(5, 5), randn(5, 5))
Expand Down Expand Up @@ -1017,7 +1025,7 @@ end
end

f = ones(size(K, 1))
u = K \ f
u = K \ f
residual = norm(f - K * u) / norm(f)
@test residual < 1e-6
end
Expand Down
2 changes: 1 addition & 1 deletion test/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ end
@test f(x, y, z) == 0
t = similar(x)
@test typeof(t) == typeof(x)
@test struct_eq(t, x)
@test struct_eq(t, x)
end

@testset "Issue #190" begin
Expand Down
17 changes: 15 additions & 2 deletions test/forbidproperties.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

using SparseArrays
Base.getproperty(::SparseMatrixCSC, ::Symbol) = error("use accessor function")
Base.getproperty(::SparseVector, ::Symbol) = error("use accessor function")
# Only define each `getproperty` override if it isn't already defined. This
# file is `include`d from multiple sibling test modules, and without this
# guard each subsequent include would re-set the same `Base` methods,
# producing spurious method-overwrite warnings (depending on `--warn-overwrite`).
# `hasmethod` is not sufficient here because the generic `Base.getproperty`
# fallback for `Any` always exists; we instead check whether `which` returns
# that fallback method.
let fallback = which(Base.getproperty, Tuple{Any, Symbol})
if which(Base.getproperty, Tuple{SparseMatrixCSC, Symbol}) === fallback
Base.getproperty(::SparseMatrixCSC, ::Symbol) = error("use accessor function")
end
if which(Base.getproperty, Tuple{SparseVector, Symbol}) === fallback
Base.getproperty(::SparseVector, ::Symbol) = error("use accessor function")
end
end
7 changes: 7 additions & 0 deletions test/issues.jl
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,13 @@ end
7 16 4])
end

@testset "Issue #574" begin
a = spzeros(Float32, Int16, 2, 3)
v = spzeros(Float32, Int16, 2)
@test eltype(rowvals(zero(a))) <: Int16
@test eltype(rowvals(zero(v))) <: Int16
end

end # SparseTestsBase

end # module
15 changes: 15 additions & 0 deletions test/sparsematrix_constructors_indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,21 @@ using Base: swaprows!, swapcols!
f!(Scopy, i, j); f!(Sdense, i, j)
@test Scopy == Sdense
end

for (A, i, j) in (
(sparse([1.0 2.0 3.0;
0.0 0.0 0.0;
4.0 5.0 6.0]), 1, 2),
(sparse([1.0 0.0 5.0;
0.0 2.0 0.0;
0.0 3.0 6.0;
7.0 4.0 0.0]), 1, 2),
(sparse(reshape([1.0, 2.0, 3.0, 4.0, 0.0, 0.0], 6, 1)), 2, 6))
Scopy = copy(A)
Sdense = Array(A)
swaprows!(Scopy, i, j); swaprows!(Sdense, i, j)
@test Scopy == Sdense
end
end

@testset "sprandn with type $T" for T in (Float64, Float32, Float16, ComplexF64, ComplexF32, ComplexF16)
Expand Down
5 changes: 5 additions & 0 deletions test/sparsevector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,11 @@ end
@test all(!iszero, v)
@test !any(iszero, v)
end

let v = sparse([0, NaN]) #issue #714
@test findmin(v) === (NaN, 2)
@test findmax(v) === (NaN, 2)
end
end

### linalg
Expand Down
16 changes: 11 additions & 5 deletions test/umfpack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ end
umfpack_report(x.b)
end

@testset "Reuse symbolic LU factorization" begin
@testset "Do/do not reuse symbolic LU factorization" for reuse ∈ (true, false)
A1 = sparse(increment!([0,4,1,1,2,2,0,1,2,3,4,4]),
increment!([0,4,0,2,1,2,1,4,3,2,1,2]),
[2.,1.,3.,4.,-1.,-3.,3.,9.,2.,1.,4.,2.], 5, 5)
Expand All @@ -420,7 +420,7 @@ end
b = Tv[8., 45., -3., 3., 19.]
F = lu(A)
umfpack_report(F)
lu!(F, B)
lu!(F, B; reuse_symbolic=reuse)
umfpack_report(F)
@test F\b ≈ B\b ≈ Matrix(B)\b

Expand All @@ -429,14 +429,20 @@ end
C[4, 3] = Tv(0)
F = lu(A)
umfpack_report(F)
@test_throws SingularException lu!(F, C)
@test_throws SingularException lu!(F, C; reuse_symbolic=reuse)
# change of nonzero pattern
D = copy(B)
D[5, 1] = Tv(1.0)
F = lu(A)
umfpack_report(F)
@test_throws ArgumentError lu!(F, D)
umfpack_report(F)
if reuse
@test_throws ArgumentError lu!(F, D; reuse_symbolic=reuse)
umfpack_report(F)
else
lu!(F, D; reuse_symbolic=reuse)
umfpack_report(F)
@test F\b ≈ D\b ≈ Matrix(D)\b
end
end
end
end
Expand Down
Loading