From 17fe53789668eab1302fed1cae8e25cde6b4a5a3 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 23 Jul 2026 03:54:34 -0400 Subject: [PATCH 1/4] Add public same_sparsity_structure(A, B) A conservative, allocation-free query returning `true` only when `A` and `B` are known to share an identical sparsity pattern (and shape) -- the condition under which a preallocated structure (or a cached symbolic factorization) may be reused for values-only updates without a structural rebuild. Motivation: this check is currently reinvented in several places (e.g. LinearSolve's internal `pattern_changed`, and a local helper in OrdinaryDiffEq's calc_J!). ArrayInterface already owns the generic sparse- structure interface (`has_sparsestruct`, `findstructralnz`); this adds the matrix-vs-matrix structural-equality query alongside them. Unlike `findstructralnz`, it allocates nothing on its fast paths, so it is usable in hot loops (e.g. deciding per Jacobian evaluation whether a sparse jac_prototype needs rebuilding). Methods: - generic fallback -> false (conservative: false means "not cheaply provable", not "provably different") - Diagonal / Bidiagonal (with uplo) / Tridiagonal / SymTridiagonal by shape - AbstractSparseMatrixCSC via colptr/rowval comparison (SparseArrays ext) - CuSparseMatrixCSC / CuSparseMatrixCSR via their index arrays (CUDA ext) Declared `public`, documented in docs/src/sparsearrays.md, and tested. Minor version bump for the additive public API. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LWooL8qipuUf7nTrDUUzqY --- Project.toml | 2 +- docs/src/sparsearrays.md | 1 + ext/ArrayInterfaceCUDAExt.jl | 13 +++++++++++ ext/ArrayInterfaceSparseArraysExt.jl | 10 +++++++- src/ArrayInterface.jl | 35 ++++++++++++++++++++++++++-- test/core.jl | 26 +++++++++++++++++++++ 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index e568b052e..c2d271828 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ArrayInterface" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.27.0" +version = "7.28.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" diff --git a/docs/src/sparsearrays.md b/docs/src/sparsearrays.md index 97fc9ba00..50d220581 100644 --- a/docs/src/sparsearrays.md +++ b/docs/src/sparsearrays.md @@ -11,6 +11,7 @@ These routines allow for improving sparse iteration and indexing. ArrayInterface.isstructured ArrayInterface.findstructralnz ArrayInterface.has_sparsestruct +ArrayInterface.same_sparsity_structure ``` ## Matrix Coloring diff --git a/ext/ArrayInterfaceCUDAExt.jl b/ext/ArrayInterfaceCUDAExt.jl index 7ba6216a8..95b7b7170 100644 --- a/ext/ArrayInterfaceCUDAExt.jl +++ b/ext/ArrayInterfaceCUDAExt.jl @@ -25,6 +25,19 @@ const CuSparseArray = Union{ } ArrayInterface.can_setindex(::Type{<:CuSparseArray}) = false +# GPU CSC/CSR do not subtype `SparseArrays.AbstractSparseMatrixCSC`, so they need their own +# structure comparison. The stored index arrays are `CuVector`s; `==` reduces on-device. +function ArrayInterface.same_sparsity_structure( + A::CUSPARSE.CuSparseMatrixCSC, B::CUSPARSE.CuSparseMatrixCSC + ) + size(A) == size(B) && A.colPtr == B.colPtr && A.rowVal == B.rowVal +end +function ArrayInterface.same_sparsity_structure( + A::CUSPARSE.CuSparseMatrixCSR, B::CUSPARSE.CuSparseMatrixCSR + ) + size(A) == size(B) && A.rowPtr == B.rowPtr && A.colVal == B.colVal +end + function ArrayInterface.promote_eltype( ::Type{<:CUDA.CuArray{T, N, M}}, ::Type{T2} ) where {T, N, M, T2} diff --git a/ext/ArrayInterfaceSparseArraysExt.jl b/ext/ArrayInterfaceSparseArraysExt.jl index 9f0e06b43..9451e2be0 100644 --- a/ext/ArrayInterfaceSparseArraysExt.jl +++ b/ext/ArrayInterfaceSparseArraysExt.jl @@ -1,8 +1,9 @@ module ArrayInterfaceSparseArraysExt -import ArrayInterface: buffer, has_sparsestruct, issingular, findstructralnz, bunchkaufman_instance, DEFAULT_CHOLESKY_PIVOT, cholesky_instance, ldlt_instance, lu_instance, qr_instance +import ArrayInterface: buffer, has_sparsestruct, issingular, findstructralnz, same_sparsity_structure, bunchkaufman_instance, DEFAULT_CHOLESKY_PIVOT, cholesky_instance, ldlt_instance, lu_instance, qr_instance using ArrayInterface.LinearAlgebra using SparseArrays +using SparseArrays: AbstractSparseMatrixCSC, getcolptr, rowvals buffer(x::SparseMatrixCSC) = getfield(x, :nzval) buffer(x::SparseVector) = getfield(x, :nzval) @@ -14,6 +15,13 @@ function findstructralnz(x::SparseMatrixCSC) (rowind, colind) end +# Compares the stored index arrays directly (no `findnz` allocation), so it is cheap +# enough for per-iteration reuse checks. Covers any CPU CSC type implementing the +# `getcolptr`/`rowvals` interface. +function same_sparsity_structure(A::AbstractSparseMatrixCSC, B::AbstractSparseMatrixCSC) + Base.size(A) == Base.size(B) && getcolptr(A) == getcolptr(B) && rowvals(A) == rowvals(B) +end + function bunchkaufman_instance(A::SparseMatrixCSC{Tv, Ti}) where {Tv, Ti} bunchkaufman(SparseMatrixCSC{Tv, Ti}(similar(A, 1, 1)), check = false) end diff --git a/src/ArrayInterface.jl b/src/ArrayInterface.jl index 0b2593a4e..5a755b2e2 100644 --- a/src/ArrayInterface.jl +++ b/src/ArrayInterface.jl @@ -341,6 +341,37 @@ function findstructralnz(x::Union{Tridiagonal, SymTridiagonal}) (rowind, colind) end +""" + same_sparsity_structure(A, B) -> Bool + +Return `true` when `A` and `B` are known to store their nonzeros in exactly the same +structural positions, i.e. they share an identical sparsity pattern (and shape). This is +the condition under which values may be written into `B` reusing `A`'s stored layout (or a +cached symbolic factorization of `A` reused for `B`) without rebuilding the structure. + +The check is *conservative*: it returns `true` only when identical structure can be +established cheaply for the given types, and `false` otherwise. A `false` result therefore +does not prove the structures differ -- it means equality could not be confirmed cheaply, so +a caller should fall back to reconstructing the structure. The generic fallback returns +`false`; specialized methods are provided for the sparse and structured matrix types for +which the pattern is decided by shape (plus `uplo`/format) or by comparing the stored +index arrays (e.g. `colptr`/`rowval` for CSC). + +Unlike `findstructralnz`, this performs no allocation on its fast paths, so it is suitable +for use in hot loops (for example, deciding per Jacobian evaluation whether a sparse +`jac_prototype`'s structure needs rebuilding). +""" +same_sparsity_structure(A, B) = false +same_sparsity_structure(A::Diagonal, B::Diagonal) = Base.size(A) == Base.size(B) +function same_sparsity_structure(A::Bidiagonal, B::Bidiagonal) + Base.size(A) == Base.size(B) && A.uplo == B.uplo +end +# Same concrete banded type (both Tridiagonal or both SymTridiagonal); a mixed pair falls +# through to the conservative generic `false`. +function same_sparsity_structure(A::T, B::T) where {T <: Union{Tridiagonal, SymTridiagonal}} + Base.size(A) == Base.size(B) +end + abstract type ColoringAlgorithm end """ @@ -1005,8 +1036,8 @@ end :cholesky_instance, :ldlt_instance, :lu_instance, :qr_instance, :svd_instance, # sparse arrays - :isstructured, :findstructralnz, :has_sparsestruct, :fast_matrix_colors, - :matrix_colors, + :isstructured, :findstructralnz, :has_sparsestruct, :same_sparsity_structure, + :fast_matrix_colors, :matrix_colors, # wrapping :is_forwarding_wrapper, :buffer, :parent_type, # tuples diff --git a/test/core.jl b/test/core.jl index 62cabf885..5342cbcad 100644 --- a/test/core.jl +++ b/test/core.jl @@ -216,6 +216,32 @@ end @test [STri[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,2,3,4,5,6,7,5,6,7] end +@testset "same_sparsity_structure" begin + sss = ArrayInterface.same_sparsity_structure + + # structured matrices: pattern is fixed by shape (plus uplo) + @test sss(Diagonal([1,2,3]), Diagonal([4,5,6])) + @test !sss(Diagonal([1,2,3]), Diagonal([4,5])) + @test sss(Bidiagonal([1,2,3],[7,8],:U), Bidiagonal([4,5,6],[9,1],:U)) + @test !sss(Bidiagonal([1,2,3],[7,8],:U), Bidiagonal([4,5,6],[9,1],:L)) + @test sss(Tridiagonal([1,2],[1,2,3],[4,5]), Tridiagonal([6,7],[8,9,1],[2,3])) + @test !sss(Tridiagonal([1,2],[1,2,3],[4,5]), Tridiagonal([1],[1,2],[4])) + @test sss(SymTridiagonal([1,2,3],[4,5]), SymTridiagonal([6,7,8],[9,1])) + + # different / mixed types fall through to the conservative generic `false` + @test !sss(Diagonal([1,2,3]), Bidiagonal([1,2,3],[7,8],:U)) + @test !sss(rand(3,3), rand(3,3)) + + # sparse CSC: compares the stored index arrays + A = sparse([1,2,3],[1,2,3],[1.0,2.0,3.0]) + B = sparse([1,2,3],[1,2,3],[4.0,5.0,6.0]) # same pattern, different values + C = sparse([1,2,3],[1,2,1],[4.0,5.0,6.0]) # different pattern + @test sss(A, B) + @test !sss(A, C) + @test sss(A, similar(A)) # `similar` preserves the pattern + @test !sss(A, sparse([1,2],[1,2],[1.0,2.0])) # different size +end + @testset "ndims_index" begin @test @inferred(ArrayInterface.ndims_index(CartesianIndices(()))) == 1 @test @inferred(ArrayInterface.ndims_index(trues(2, 2))) == 2 From 607a102a3aaed5997a988366707318df0b95940a Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 23 Jul 2026 04:04:38 -0400 Subject: [PATCH 2/4] Drop the false fallback from same_sparsity_structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the generic `same_sparsity_structure(A, B) = false` catch-all. Unlike `has_sparsestruct(::Type) = false` (where `false` is correct — "no special structure"), a blanket `false` here is semantically wrong: it would claim two identical dense matrices have different structure. An unsupported argument pair now throws `MethodError` instead of silently returning a possibly-wrong default, so callers get a positive true/false from a real method or a clear signal that the type needs one. `has_sparsestruct` can gate applicability. Tests updated: the dense and mixed-type cases now assert `@test_throws MethodError`. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LWooL8qipuUf7nTrDUUzqY --- src/ArrayInterface.jl | 28 +++++++++++++--------------- test/core.jl | 7 ++++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/ArrayInterface.jl b/src/ArrayInterface.jl index 5a755b2e2..912a68220 100644 --- a/src/ArrayInterface.jl +++ b/src/ArrayInterface.jl @@ -344,30 +344,28 @@ end """ same_sparsity_structure(A, B) -> Bool -Return `true` when `A` and `B` are known to store their nonzeros in exactly the same -structural positions, i.e. they share an identical sparsity pattern (and shape). This is -the condition under which values may be written into `B` reusing `A`'s stored layout (or a -cached symbolic factorization of `A` reused for `B`) without rebuilding the structure. - -The check is *conservative*: it returns `true` only when identical structure can be -established cheaply for the given types, and `false` otherwise. A `false` result therefore -does not prove the structures differ -- it means equality could not be confirmed cheaply, so -a caller should fall back to reconstructing the structure. The generic fallback returns -`false`; specialized methods are provided for the sparse and structured matrix types for -which the pattern is decided by shape (plus `uplo`/format) or by comparing the stored -index arrays (e.g. `colptr`/`rowval` for CSC). +Return `true` when `A` and `B` store their nonzeros in exactly the same structural +positions, i.e. they share an identical sparsity pattern (and shape). This is the condition +under which values may be written into `B` reusing `A`'s stored layout (or a cached symbolic +factorization of `A` reused for `B`) without rebuilding the structure. + +Defined for the sparse and structured matrix types whose pattern is decided by shape (plus +`uplo`/format) or by comparing the stored index arrays (e.g. `colptr`/`rowval` for CSC). +There is deliberately no generic fallback: an unsupported argument pair throws a +`MethodError` rather than silently returning a (possibly wrong) default, so callers get a +positive `true`/`false` answer or a clear signal that the type needs a method. `has_sparsestruct` +can be used to check applicability first. Unlike `findstructralnz`, this performs no allocation on its fast paths, so it is suitable for use in hot loops (for example, deciding per Jacobian evaluation whether a sparse `jac_prototype`'s structure needs rebuilding). """ -same_sparsity_structure(A, B) = false +function same_sparsity_structure end same_sparsity_structure(A::Diagonal, B::Diagonal) = Base.size(A) == Base.size(B) function same_sparsity_structure(A::Bidiagonal, B::Bidiagonal) Base.size(A) == Base.size(B) && A.uplo == B.uplo end -# Same concrete banded type (both Tridiagonal or both SymTridiagonal); a mixed pair falls -# through to the conservative generic `false`. +# Same concrete banded type (both Tridiagonal or both SymTridiagonal). function same_sparsity_structure(A::T, B::T) where {T <: Union{Tridiagonal, SymTridiagonal}} Base.size(A) == Base.size(B) end diff --git a/test/core.jl b/test/core.jl index 5342cbcad..d2c163c33 100644 --- a/test/core.jl +++ b/test/core.jl @@ -228,9 +228,10 @@ end @test !sss(Tridiagonal([1,2],[1,2,3],[4,5]), Tridiagonal([1],[1,2],[4])) @test sss(SymTridiagonal([1,2,3],[4,5]), SymTridiagonal([6,7,8],[9,1])) - # different / mixed types fall through to the conservative generic `false` - @test !sss(Diagonal([1,2,3]), Bidiagonal([1,2,3],[7,8],:U)) - @test !sss(rand(3,3), rand(3,3)) + # no generic fallback: unsupported / mixed-type pairs error rather than silently + # returning a possibly-wrong default + @test_throws MethodError sss(Diagonal([1,2,3]), Bidiagonal([1,2,3],[7,8],:U)) + @test_throws MethodError sss(rand(3,3), rand(3,3)) # sparse CSC: compares the stored index arrays A = sparse([1,2,3],[1,2,3],[1.0,2.0,3.0]) From 258116cb62684146e9127bff46e7f75accc2abe0 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 23 Jul 2026 04:20:51 -0400 Subject: [PATCH 3/4] Handle dense and mixed-category pairs in same_sparsity_structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the removed catch-all with category-aware methods so every AbstractArray pair gets a real answer instead of a MethodError: - dense (`DenseArray`) pair: identical shape ⇒ identical (full) structure - different structural categories (structured-vs-dense, or two different structured types): `false` -- a layout is not reused across categories, so this is treated as "not the same structure" rather than compared position-by-position The dense method is what makes this correct where the original blanket `false` was wrong (it had reported identical dense matrices as differing). Fast paths remain allocation-free (`@allocated == 0` for CSC/Diagonal/dense in a function barrier). Tests updated accordingly. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LWooL8qipuUf7nTrDUUzqY --- src/ArrayInterface.jl | 23 +++++++++++++++-------- test/core.jl | 13 +++++++++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/ArrayInterface.jl b/src/ArrayInterface.jl index 912a68220..bf790babf 100644 --- a/src/ArrayInterface.jl +++ b/src/ArrayInterface.jl @@ -349,23 +349,30 @@ positions, i.e. they share an identical sparsity pattern (and shape). This is th under which values may be written into `B` reusing `A`'s stored layout (or a cached symbolic factorization of `A` reused for `B`) without rebuilding the structure. -Defined for the sparse and structured matrix types whose pattern is decided by shape (plus -`uplo`/format) or by comparing the stored index arrays (e.g. `colptr`/`rowval` for CSC). -There is deliberately no generic fallback: an unsupported argument pair throws a -`MethodError` rather than silently returning a (possibly wrong) default, so callers get a -positive `true`/`false` answer or a clear signal that the type needs a method. `has_sparsestruct` -can be used to check applicability first. +Semantics by category: + + - Dense arrays store every position, so two of them share a structure exactly when they + have the same shape. + - Structured (`Diagonal`, `Bidiagonal`, `Tridiagonal`, `SymTridiagonal`) and sparse + (`SparseMatrixCSC`, GPU CSC/CSR) matrices compare by their pattern — shape (plus + `uplo`/format) or the stored index arrays (e.g. `colptr`/`rowval` for CSC). + - A pair from different structural categories (e.g. a structured matrix and a dense one, + or two different structured types) returns `false`: for the intended reuse use case a + layout is not reused across categories, so this is treated as "not the same structure" + rather than compared position-by-position. Unlike `findstructralnz`, this performs no allocation on its fast paths, so it is suitable for use in hot loops (for example, deciding per Jacobian evaluation whether a sparse `jac_prototype`'s structure needs rebuilding). """ -function same_sparsity_structure end +same_sparsity_structure(A::AbstractArray, B::AbstractArray) = false +same_sparsity_structure(A::DenseArray, B::DenseArray) = Base.size(A) == Base.size(B) same_sparsity_structure(A::Diagonal, B::Diagonal) = Base.size(A) == Base.size(B) function same_sparsity_structure(A::Bidiagonal, B::Bidiagonal) Base.size(A) == Base.size(B) && A.uplo == B.uplo end -# Same concrete banded type (both Tridiagonal or both SymTridiagonal). +# Same concrete banded type (both Tridiagonal or both SymTridiagonal); a mixed pair falls +# through to the category fallback (`false`). function same_sparsity_structure(A::T, B::T) where {T <: Union{Tridiagonal, SymTridiagonal}} Base.size(A) == Base.size(B) end diff --git a/test/core.jl b/test/core.jl index d2c163c33..5f1d7e948 100644 --- a/test/core.jl +++ b/test/core.jl @@ -228,10 +228,15 @@ end @test !sss(Tridiagonal([1,2],[1,2,3],[4,5]), Tridiagonal([1],[1,2],[4])) @test sss(SymTridiagonal([1,2,3],[4,5]), SymTridiagonal([6,7,8],[9,1])) - # no generic fallback: unsupported / mixed-type pairs error rather than silently - # returning a possibly-wrong default - @test_throws MethodError sss(Diagonal([1,2,3]), Bidiagonal([1,2,3],[7,8],:U)) - @test_throws MethodError sss(rand(3,3), rand(3,3)) + # dense arrays: every position is structural, so same shape ⇒ same structure + @test sss(rand(3,3), rand(3,3)) + @test !sss(rand(3,3), rand(3,2)) + @test sss(rand(4), rand(4)) + + # different structural categories ⇒ false (not compared position-by-position) + @test !sss(Diagonal([1,2,3]), Bidiagonal([1,2,3],[7,8],:U)) + @test !sss(Diagonal([1,2,3]), rand(3,3)) + @test !sss(Tridiagonal([1,2],[1,2,3],[4,5]), SymTridiagonal([1,2,3],[4,5])) # sparse CSC: compares the stored index arrays A = sparse([1,2,3],[1,2,3],[1.0,2.0,3.0]) From c488273edd53f0e87c340321391fc615d86558db Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 23 Jul 2026 05:54:14 -0400 Subject: [PATCH 4/4] Distinguish different-type (false) from same-type-no-method (error) The `AbstractArray` fallback returned `false` for every unhandled pair, which fabricated an answer for two arrays of the *same* base type that simply lack a specialized method (e.g. two `UpperTriangular`, which do share a structure). Split the fallback: - different base type (parameterless_type differs) -> false (different category) - same base type, no specialized method -> throw MethodError (unknown; define one) Also make the Tridiagonal/SymTridiagonal methods dispatch on the base type instead of `A::T, B::T`, so differing type parameters (e.g. Tridiagonal{Float64} vs Tridiagonal{Int}) still compare as the same structure rather than falling through to the fallback. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LWooL8qipuUf7nTrDUUzqY --- src/ArrayInterface.jl | 28 ++++++++++++++++++---------- test/core.jl | 8 +++++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/ArrayInterface.jl b/src/ArrayInterface.jl index bf790babf..0ecfec038 100644 --- a/src/ArrayInterface.jl +++ b/src/ArrayInterface.jl @@ -356,26 +356,34 @@ Semantics by category: - Structured (`Diagonal`, `Bidiagonal`, `Tridiagonal`, `SymTridiagonal`) and sparse (`SparseMatrixCSC`, GPU CSC/CSR) matrices compare by their pattern — shape (plus `uplo`/format) or the stored index arrays (e.g. `colptr`/`rowval` for CSC). - - A pair from different structural categories (e.g. a structured matrix and a dense one, - or two different structured types) returns `false`: for the intended reuse use case a - layout is not reused across categories, so this is treated as "not the same structure" - rather than compared position-by-position. + - Two arrays whose base types differ (e.g. a structured matrix and a dense one, or two + different structured types) return `false`: a layout is not reused across categories, + so they are treated as "not the same structure". + - Two arrays of the *same* base type with no specialized method are **unknown**, not + `false`: rather than fabricate an answer, a `MethodError` is thrown so the type can + define its own method. (`has_sparsestruct` can be used to check applicability first.) Unlike `findstructralnz`, this performs no allocation on its fast paths, so it is suitable for use in hot loops (for example, deciding per Jacobian evaluation whether a sparse `jac_prototype`'s structure needs rebuilding). """ -same_sparsity_structure(A::AbstractArray, B::AbstractArray) = false +function same_sparsity_structure(A::AbstractArray, B::AbstractArray) + # Same base type but no specialized method: we genuinely do not know -- error rather + # than return a possibly-wrong `false`. Different base types cannot share a structural + # category, so those are `false`. + parameterless_type(A) === parameterless_type(B) && + throw(MethodError(same_sparsity_structure, (A, B))) + return false +end same_sparsity_structure(A::DenseArray, B::DenseArray) = Base.size(A) == Base.size(B) same_sparsity_structure(A::Diagonal, B::Diagonal) = Base.size(A) == Base.size(B) function same_sparsity_structure(A::Bidiagonal, B::Bidiagonal) Base.size(A) == Base.size(B) && A.uplo == B.uplo end -# Same concrete banded type (both Tridiagonal or both SymTridiagonal); a mixed pair falls -# through to the category fallback (`false`). -function same_sparsity_structure(A::T, B::T) where {T <: Union{Tridiagonal, SymTridiagonal}} - Base.size(A) == Base.size(B) -end +# Base-type methods (not `A::T, B::T`) so differing type parameters, e.g. +# `Tridiagonal{Float64}` vs `Tridiagonal{Int}`, still compare as the same structure. +same_sparsity_structure(A::Tridiagonal, B::Tridiagonal) = Base.size(A) == Base.size(B) +same_sparsity_structure(A::SymTridiagonal, B::SymTridiagonal) = Base.size(A) == Base.size(B) abstract type ColoringAlgorithm end diff --git a/test/core.jl b/test/core.jl index 5f1d7e948..3ad091b5d 100644 --- a/test/core.jl +++ b/test/core.jl @@ -228,16 +228,22 @@ end @test !sss(Tridiagonal([1,2],[1,2,3],[4,5]), Tridiagonal([1],[1,2],[4])) @test sss(SymTridiagonal([1,2,3],[4,5]), SymTridiagonal([6,7,8],[9,1])) + # same base type, differing type parameters ⇒ still the same structure + @test sss(Tridiagonal([1,2],[1,2,3],[4,5]), Tridiagonal([1.0,2],[1.0,2,3],[4.0,5])) + # dense arrays: every position is structural, so same shape ⇒ same structure @test sss(rand(3,3), rand(3,3)) @test !sss(rand(3,3), rand(3,2)) @test sss(rand(4), rand(4)) - # different structural categories ⇒ false (not compared position-by-position) + # different base types ⇒ false (not compared position-by-position) @test !sss(Diagonal([1,2,3]), Bidiagonal([1,2,3],[7,8],:U)) @test !sss(Diagonal([1,2,3]), rand(3,3)) @test !sss(Tridiagonal([1,2],[1,2,3],[4,5]), SymTridiagonal([1,2,3],[4,5])) + # same base type but no specialized method ⇒ unknown, error rather than fabricate false + @test_throws MethodError sss(UpperTriangular(rand(3,3)), UpperTriangular(rand(3,3))) + # sparse CSC: compares the stored index arrays A = sparse([1,2,3],[1,2,3],[1.0,2.0,3.0]) B = sparse([1,2,3],[1,2,3],[4.0,5.0,6.0]) # same pattern, different values