diff --git a/src/adtypes.jl b/src/adtypes.jl index 7325da2f..7500b1e2 100644 --- a/src/adtypes.jl +++ b/src/adtypes.jl @@ -5,7 +5,7 @@ function coloring( kwargs..., ) bg = BipartiteGraph(A) - color = convert(Vector{Int}, ADTypes.column_coloring(A, algo)) + color = convert(Vector{eltype(bg)}, ADTypes.column_coloring(A, algo)) return ColumnColoringResult(A, bg, color) end @@ -16,6 +16,6 @@ function coloring( kwargs..., ) bg = BipartiteGraph(A) - color = convert(Vector{Int}, ADTypes.row_coloring(A, algo)) + color = convert(Vector{eltype(bg)}, ADTypes.row_coloring(A, algo)) return RowColoringResult(A, bg, color) end diff --git a/src/check.jl b/src/check.jl index ad7c5528..f47f0958 100644 --- a/src/check.jl +++ b/src/check.jl @@ -262,8 +262,8 @@ function directly_recoverable_columns( end """ - valid_dynamic_order(g::AdjacencyGraph, π::AbstractVector{Int}, order::DynamicDegreeBasedOrder) - valid_dynamic_order(bg::AdjacencyGraph, ::Val{side}, π::AbstractVector{Int}, order::DynamicDegreeBasedOrder) + valid_dynamic_order(g::AdjacencyGraph, π::AbstractVector{<:Integer}, order::DynamicDegreeBasedOrder) + valid_dynamic_order(bg::AdjacencyGraph, ::Val{side}, π::AbstractVector{<:Integer}, order::DynamicDegreeBasedOrder) Check that a permutation `π` corresponds to a valid application of a [`DynamicDegreeBasedOrder`](@ref). @@ -273,7 +273,9 @@ This is done by checking, for each ordered vertex, that its back- or forward-deg This function is not coded with efficiency in mind, it is designed for small-scale tests. """ function valid_dynamic_order( - g::AdjacencyGraph, π::AbstractVector{Int}, ::DynamicDegreeBasedOrder{degtype,direction} + g::AdjacencyGraph, + π::AbstractVector{<:Integer}, + ::DynamicDegreeBasedOrder{degtype,direction}, ) where {degtype,direction} length(π) != nb_vertices(g) && return false length(unique(π)) != nb_vertices(g) && return false @@ -300,7 +302,7 @@ end function valid_dynamic_order( g::BipartiteGraph, ::Val{side}, - π::AbstractVector{Int}, + π::AbstractVector{<:Integer}, ::DynamicDegreeBasedOrder{degtype,direction}, ) where {side,degtype,direction} length(π) != nb_vertices(g, Val(side)) && return false diff --git a/src/coloring.jl b/src/coloring.jl index 587edc1c..bfecc0d0 100644 --- a/src/coloring.jl +++ b/src/coloring.jl @@ -17,18 +17,18 @@ The vertices are colored in a greedy fashion, following the `order` supplied. > [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005), Algorithm 3.2 """ function partial_distance2_coloring( - bg::BipartiteGraph, ::Val{side}, order::AbstractOrder -) where {side} - color = Vector{Int}(undef, nb_vertices(bg, Val(side))) - forbidden_colors = Vector{Int}(undef, nb_vertices(bg, Val(side))) + bg::BipartiteGraph{T}, ::Val{side}, order::AbstractOrder +) where {T,side} + color = Vector{T}(undef, nb_vertices(bg, Val(side))) + forbidden_colors = Vector{T}(undef, nb_vertices(bg, Val(side))) vertices_in_order = vertices(bg, Val(side), order) partial_distance2_coloring!(color, forbidden_colors, bg, Val(side), vertices_in_order) return color end function partial_distance2_coloring!( - color::Vector{Int}, - forbidden_colors::Vector{Int}, + color::AbstractVector{<:Integer}, + forbidden_colors::AbstractVector{<:Integer}, bg::BipartiteGraph, ::Val{side}, vertices_in_order::AbstractVector{<:Integer}, @@ -76,16 +76,18 @@ If `postprocessing=true`, some colors might be replaced with `0` (the "neutral" > [_New Acyclic and Star Coloring Algorithms with Application to Computing Hessians_](https://epubs.siam.org/doi/abs/10.1137/050639879), Gebremedhin et al. (2007), Algorithm 4.1 """ -function star_coloring(g::AdjacencyGraph, order::AbstractOrder, postprocessing::Bool) +function star_coloring( + g::AdjacencyGraph{T}, order::AbstractOrder, postprocessing::Bool +) where {T<:Integer} # Initialize data structures nv = nb_vertices(g) ne = nb_edges(g) - color = zeros(Int, nv) - forbidden_colors = zeros(Int, nv) - first_neighbor = fill((0, 0, 0), nv) # at first no neighbors have been encountered - treated = zeros(Int, nv) - star = Vector{Int}(undef, ne) - hub = Int[] # one hub for each star, including the trivial ones + color = zeros(T, nv) + forbidden_colors = zeros(T, nv) + first_neighbor = fill((zero(T), zero(T), zero(T)), nv) # at first no neighbors have been encountered + treated = zeros(T, nv) + star = Vector{T}(undef, ne) + hub = T[] # one hub for each star, including the trivial ones vertices_in_order = vertices(g, order) for v in vertices_in_order @@ -196,11 +198,11 @@ Encode a set of 2-colored stars resulting from the [`star_coloring`](@ref) algor $TYPEDFIELDS """ -struct StarSet +struct StarSet{T} "a mapping from edges (pair of vertices) to their star index" - star::Vector{Int} + star::Vector{T} "a mapping from star indices to their hub (undefined hubs for single-edge stars are the negative value of one of the vertices, picked arbitrarily)" - hub::Vector{Int} + hub::Vector{T} end """ @@ -226,15 +228,17 @@ If `postprocessing=true`, some colors might be replaced with `0` (the "neutral" > [_New Acyclic and Star Coloring Algorithms with Application to Computing Hessians_](https://epubs.siam.org/doi/abs/10.1137/050639879), Gebremedhin et al. (2007), Algorithm 3.1 """ -function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder, postprocessing::Bool) +function acyclic_coloring( + g::AdjacencyGraph{T}, order::AbstractOrder, postprocessing::Bool +) where {T<:Integer} # Initialize data structures nv = nb_vertices(g) ne = nb_edges(g) - color = zeros(Int, nv) - forbidden_colors = zeros(Int, nv) - first_neighbor = fill((0, 0, 0), nv) # at first no neighbors have been encountered - first_visit_to_tree = fill((0, 0), ne) - forest = Forest{Int}(ne) + color = zeros(T, nv) + forbidden_colors = zeros(T, nv) + first_neighbor = fill((zero(T), zero(T), zero(T)), nv) # at first no neighbors have been encountered + first_visit_to_tree = fill((zero(T), zero(T)), ne) + forest = Forest{T}(ne) vertices_in_order = vertices(g, order) for v in vertices_in_order @@ -367,23 +371,23 @@ Encode a set of 2-colored trees resulting from the [`acyclic_coloring`](@ref) al $TYPEDFIELDS """ -struct TreeSet - reverse_bfs_orders::Vector{Vector{Tuple{Int,Int}}} +struct TreeSet{T} + reverse_bfs_orders::Vector{Vector{Tuple{T,T}}} is_star::Vector{Bool} end -function TreeSet(g::AdjacencyGraph, forest::Forest{Int}) +function TreeSet(g::AdjacencyGraph{T}, forest::Forest{T}) where {T} S = pattern(g) edge_to_index = edge_indices(g) nv = nb_vertices(g) nt = forest.num_trees # dictionary that maps a tree's root to the index of the tree - roots = Dict{Int,Int}() + roots = Dict{T,T}() sizehint!(roots, nt) # vector of dictionaries where each dictionary stores the neighbors of each vertex in a tree - trees = [Dict{Int,Vector{Int}}() for i in 1:nt] + trees = [Dict{T,Vector{T}}() for i in 1:nt] # current number of roots found nr = 0 @@ -423,10 +427,10 @@ function TreeSet(g::AdjacencyGraph, forest::Forest{Int}) end # degrees is a vector of integers that stores the degree of each vertex in a tree - degrees = Vector{Int}(undef, nv) + degrees = Vector{T}(undef, nv) # reverse breadth first (BFS) traversal order for each tree in the forest - reverse_bfs_orders = [Tuple{Int,Int}[] for i in 1:nt] + reverse_bfs_orders = [Tuple{T,T}[] for i in 1:nt] # nvmax is the number of vertices of the biggest tree in the forest nvmax = 0 @@ -436,7 +440,7 @@ function TreeSet(g::AdjacencyGraph, forest::Forest{Int}) end # Create a queue with a fixed size nvmax - queue = Vector{Int}(undef, nvmax) + queue = Vector{T}(undef, nvmax) # Specify if each tree in the forest is a star, # meaning that one vertex is directly connected to all other vertices in the tree @@ -519,7 +523,7 @@ function postprocess!( color::AbstractVector{<:Integer}, star_or_tree_set::Union{StarSet,TreeSet}, g::AdjacencyGraph, - offsets::Vector{Int}, + offsets::AbstractVector{<:Integer}, ) S = pattern(g) edge_to_index = edge_indices(g) diff --git a/src/constant.jl b/src/constant.jl index c093f5f7..d79caab0 100644 --- a/src/constant.jl +++ b/src/constant.jl @@ -14,7 +14,7 @@ Indeed, for symmetric coloring problems, we need more than just the vector of co - `partition::Symbol`: either `:row` or `:column`. - `matrix_template::AbstractMatrix`: matrix for which the vector of colors was precomputed (the algorithm will only accept matrices of the exact same size). -- `color::Vector{Int}`: vector of integer colors, one for each row or column (depending on `partition`). +- `color::Vector{<:Integer}`: vector of integer colors, one for each row or column (depending on `partition`). !!! warning The second constructor (based on keyword arguments) is type-unstable. @@ -65,33 +65,36 @@ julia> column_colors(result) - [`ADTypes.row_coloring`](@extref ADTypes.row_coloring) """ struct ConstantColoringAlgorithm{ - partition,M<:AbstractMatrix,R<:AbstractColoringResult{:nonsymmetric,partition,:direct} + partition, + M<:AbstractMatrix, + T<:Integer, + R<:AbstractColoringResult{:nonsymmetric,partition,:direct}, } <: ADTypes.AbstractColoringAlgorithm matrix_template::M - color::Vector{Int} + color::Vector{T} result::R end function ConstantColoringAlgorithm{:column}( - matrix_template::AbstractMatrix, color::Vector{Int} + matrix_template::AbstractMatrix, color::Vector{<:Integer} ) bg = BipartiteGraph(matrix_template) result = ColumnColoringResult(matrix_template, bg, color) - M, R = typeof(matrix_template), typeof(result) - return ConstantColoringAlgorithm{:column,M,R}(matrix_template, color, result) + T, M, R = eltype(bg), typeof(matrix_template), typeof(result) + return ConstantColoringAlgorithm{:column,M,T,R}(matrix_template, color, result) end function ConstantColoringAlgorithm{:row}( - matrix_template::AbstractMatrix, color::Vector{Int} + matrix_template::AbstractMatrix, color::Vector{<:Integer} ) bg = BipartiteGraph(matrix_template) result = RowColoringResult(matrix_template, bg, color) - M, R = typeof(matrix_template), typeof(result) - return ConstantColoringAlgorithm{:row,M,R}(matrix_template, color, result) + T, M, R = eltype(bg), typeof(matrix_template), typeof(result) + return ConstantColoringAlgorithm{:row,M,T,R}(matrix_template, color, result) end function ConstantColoringAlgorithm( - matrix_template::AbstractMatrix, color::Vector{Int}; partition=:column + matrix_template::AbstractMatrix, color::Vector{<:Integer}; partition::Symbol=:column ) return ConstantColoringAlgorithm{partition}(matrix_template, color) end diff --git a/src/decompression.jl b/src/decompression.jl index dea896e6..64e3c517 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -677,12 +677,12 @@ function decompress!( result::LinearSystemColoringResult, uplo::Symbol=:F, ) - (; color, strict_upper_nonzero_inds, T_factorization, strict_upper_nonzeros_A) = result + (; color, strict_upper_nonzero_inds, M_factorization, strict_upper_nonzeros_A) = result S = result.ag.S uplo == :F && check_same_pattern(A, S) # TODO: for some reason I cannot use ldiv! with a sparse QR - strict_upper_nonzeros_A = T_factorization \ vec(B) + strict_upper_nonzeros_A = M_factorization \ vec(B) fill!(A, zero(eltype(A))) for i in axes(A, 1) if !iszero(S[i, i]) diff --git a/src/graph.jl b/src/graph.jl index 047fdae4..7f3863e3 100644 --- a/src/graph.jl +++ b/src/graph.jl @@ -23,8 +23,9 @@ end SparsityPatternCSC(A::SparseMatrixCSC) = SparsityPatternCSC(A.m, A.n, A.colptr, A.rowval) +Base.eltype(::SparsityPatternCSC{T}) where {T} = T Base.size(S::SparsityPatternCSC) = (S.m, S.n) -Base.size(S::SparsityPatternCSC, d) = d::Integer <= 2 ? size(S)[d] : 1 +Base.size(S::SparsityPatternCSC, d::Integer) = d::Integer <= 2 ? size(S)[d] : 1 Base.axes(S::SparsityPatternCSC, d::Integer) = Base.OneTo(size(S, d)) SparseArrays.nnz(S::SparsityPatternCSC) = length(S.rowval) @@ -222,11 +223,13 @@ The adjacency graph of a symmetric matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E) > [_What Color Is Your Jacobian? SparsityPatternCSC Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005) """ -struct AdjacencyGraph{T,has_diagonal} +struct AdjacencyGraph{T<:Integer,has_diagonal} S::SparsityPatternCSC{T} edge_to_index::Vector{T} end +Base.eltype(::AdjacencyGraph{T}) where {T} = T + function AdjacencyGraph( S::SparsityPatternCSC{T}, edge_to_index::Vector{T}=build_edge_to_index(S); @@ -298,7 +301,7 @@ function has_neighbor(g::AdjacencyGraph, v::Integer, u::Integer) return false end -function degree_in_subset(g::AdjacencyGraph, v::Integer, subset::AbstractVector{Int}) +function degree_in_subset(g::AdjacencyGraph, v::Integer, subset::AbstractVector{<:Integer}) d = 0 for u in subset if has_neighbor(g, v, u) @@ -338,11 +341,13 @@ When `symmetric_pattern` is `true`, this construction is more efficient. > [_What Color Is Your Jacobian? SparsityPatternCSC Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005) """ -struct BipartiteGraph{T} +struct BipartiteGraph{T<:Integer} S1::SparsityPatternCSC{T} S2::SparsityPatternCSC{T} end +Base.eltype(::BipartiteGraph{T}) where {T} = T + function BipartiteGraph(A::AbstractMatrix; symmetric_pattern::Bool=false) return BipartiteGraph(SparseMatrixCSC(A); symmetric_pattern) end @@ -425,7 +430,7 @@ function has_neighbor_dist2( end function degree_dist2_in_subset( - bg::BipartiteGraph, ::Val{side}, v::Integer, subset::AbstractVector{Int} + bg::BipartiteGraph, ::Val{side}, v::Integer, subset::AbstractVector{<:Integer} ) where {side} d = 0 for u in subset diff --git a/src/interface.jl b/src/interface.jl index 3997993d..f54b5f73 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -306,7 +306,9 @@ function _coloring( symmetric_result = StarSetColoringResult(A_and_Aᵀ, ag, color, star_set) return BicoloringResult(A, ag, symmetric_result, R) else - row_color, column_color, _ = remap_colors(color, maximum(color), size(A)...) + row_color, column_color, _ = remap_colors( + eltype(ag), color, maximum(color), size(A)... + ) return row_color, column_color end end @@ -326,7 +328,9 @@ function _coloring( symmetric_result = TreeSetColoringResult(A_and_Aᵀ, ag, color, tree_set, R) return BicoloringResult(A, ag, symmetric_result, R) else - row_color, column_color, _ = remap_colors(color, maximum(color), size(A)...) + row_color, column_color, _ = remap_colors( + eltype(ag), color, maximum(color), size(A)... + ) return row_color, column_color end end diff --git a/src/order.jl b/src/order.jl index 8be4661f..11aeee3f 100644 --- a/src/order.jl +++ b/src/order.jl @@ -84,11 +84,11 @@ function vertices(g::AdjacencyGraph, ::LargestFirst) return sort(vertices(g); by=criterion, rev=true) end -function vertices(bg::BipartiteGraph, ::Val{side}, ::LargestFirst) where {side} +function vertices(bg::BipartiteGraph{T}, ::Val{side}, ::LargestFirst) where {T,side} other_side = 3 - side n = nb_vertices(bg, Val(side)) visited = falses(n) # necessary for distance-2 neighborhoods - degrees_dist2 = zeros(Int, n) + degrees_dist2 = zeros(T, n) for v in vertices(bg, Val(side)) fill!(visited, false) for u in neighbors(bg, Val(side), v) @@ -129,27 +129,27 @@ Instance of [`AbstractOrder`](@ref) which sorts vertices using a dynamically com """ struct DynamicDegreeBasedOrder{degtype,direction} <: AbstractOrder end -struct DegreeBuckets - degrees::Vector{Int} - bucket_storage::Vector{Int} - bucket_low::Vector{Int} - bucket_high::Vector{Int} - positions::Vector{Int} +struct DegreeBuckets{T} + degrees::Vector{T} + bucket_storage::Vector{T} + bucket_low::Vector{T} + bucket_high::Vector{T} + positions::Vector{T} end -function DegreeBuckets(degrees::Vector{Int}, dmax) +function DegreeBuckets(::Type{T}, degrees::Vector{<:Integer}, dmax::Integer) where {T} # number of vertices per degree class - deg_count = zeros(Int, dmax + 1) + deg_count = zeros(T, dmax + 1) for d in degrees deg_count[d + 1] += 1 end # bucket limits bucket_high = cumsum(deg_count) - bucket_low = vcat(0, @view(bucket_high[1:(end - 1)])) + bucket_low = vcat(zero(T), @view(bucket_high[1:(end - 1)])) bucket_low .+= 1 # assign each vertex to the correct position inside its degree class - bucket_storage = similar(degrees, Int) - positions = similar(degrees, Int) + bucket_storage = similar(degrees, T) + positions = similar(degrees, T) for v in eachindex(positions, degrees) d = degrees[v] positions[v] = bucket_high[d + 1] - deg_count[d + 1] + 1 @@ -168,9 +168,9 @@ function degree_increasing(; degtype, direction) return increasing end -function mark_ordered!(db::DegreeBuckets, v::Integer) +function mark_ordered!(db::DegreeBuckets{T}, v::Integer) where {T} db.degrees[v] = -1 - db.positions[v] = typemin(Int) + db.positions[v] = typemin(T) return nothing end @@ -248,15 +248,15 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype, direction) end function vertices( - g::AdjacencyGraph, ::DynamicDegreeBasedOrder{degtype,direction} -) where {degtype,direction} + g::AdjacencyGraph{T}, ::DynamicDegreeBasedOrder{degtype,direction} +) where {T<:Integer,degtype,direction} if degree_increasing(; degtype, direction) - degrees = zeros(Int, nb_vertices(g)) + degrees = zeros(T, nb_vertices(g)) else degrees = [degree(g, v) for v in vertices(g)] end - db = DegreeBuckets(degrees, maximum_degree(g)) - π = Int[] + db = DegreeBuckets(T, degrees, maximum_degree(g)) + π = T[] sizehint!(π, nb_vertices(g)) for _ in 1:nb_vertices(g) u = pop_next_candidate!(db; direction) @@ -271,12 +271,12 @@ function vertices( end function vertices( - g::BipartiteGraph, ::Val{side}, ::DynamicDegreeBasedOrder{degtype,direction} -) where {side,degtype,direction} + g::BipartiteGraph{T}, ::Val{side}, ::DynamicDegreeBasedOrder{degtype,direction} +) where {T<:Integer,side,degtype,direction} other_side = 3 - side # compute dist-2 degrees in an optimized way n = nb_vertices(g, Val(side)) - degrees_dist2 = zeros(Int, n) + degrees_dist2 = zeros(T, n) dist2_neighbor = falses(n) for v in vertices(g, Val(side)) fill!(dist2_neighbor, false) @@ -288,13 +288,13 @@ function vertices( degrees_dist2[v] = sum(dist2_neighbor) end if degree_increasing(; degtype, direction) - degrees = zeros(Int, n) + degrees = zeros(T, n) else degrees = degrees_dist2 end maxd2 = maximum(degrees_dist2) - db = DegreeBuckets(degrees, maxd2) - π = Int[] + db = DegreeBuckets(T, degrees, maxd2) + π = T[] sizehint!(π, n) visited = falses(n) for _ in 1:nb_vertices(g, Val(side)) diff --git a/src/result.jl b/src/result.jl index ae8d7ce1..b1ec9409 100644 --- a/src/result.jl +++ b/src/result.jl @@ -75,17 +75,17 @@ function ncolors(res::AbstractColoringResult{structure,:bidirectional}) where {s end """ - group_by_color(color::Vector{Int}) + group_by_color(color::AbstractVector{<:Integer}) Create a color-indexed vector `group` such that `i ∈ group[c]` iff `color[i] == c` for all `c > 0`. Assumes the colors are contiguously numbered from `0` to some `cmax`. """ -function group_by_color(color::AbstractVector{<:Integer}) +function group_by_color(::Type{T}, color::AbstractVector) where {T<:Integer} cmin, cmax = extrema(color) @assert cmin >= 0 # Compute group sizes and offsets for a joint storage - group_sizes = zeros(Int, cmax) # allocation 1, size cmax + group_sizes = zeros(T, cmax) # allocation 1, size cmax for c in color if c > 0 group_sizes[c] += 1 @@ -93,7 +93,7 @@ function group_by_color(color::AbstractVector{<:Integer}) end group_offsets = cumsum(group_sizes) # allocation 2, size cmax # Concatenate all groups inside a single vector - group_flat = similar(color, sum(group_sizes)) # allocation 3, size <= n + group_flat = Vector{T}(undef, sum(group_sizes)) # allocation 3, size <= n for (k, c) in enumerate(color) if c > 0 i = group_offsets[c] - group_sizes[c] + 1 @@ -110,6 +110,10 @@ function group_by_color(color::AbstractVector{<:Integer}) return group end +group_by_color(color::AbstractVector) = group_by_color(Int, color) + +const AbstractGroups{T} = AbstractVector{<:AbstractVector{T}} + column_colors(result::AbstractColoringResult{s,:column}) where {s} = result.color column_groups(result::AbstractColoringResult{s,:column}) where {s} = result.group @@ -141,26 +145,29 @@ $TYPEDFIELDS - [`AbstractColoringResult`](@ref) """ -struct ColumnColoringResult{M<:AbstractMatrix,G<:BipartiteGraph,V} <: - AbstractColoringResult{:nonsymmetric,:column,:direct} +struct ColumnColoringResult{ + M<:AbstractMatrix,T<:Integer,G<:BipartiteGraph{T},GT<:AbstractGroups{T} +} <: AbstractColoringResult{:nonsymmetric,:column,:direct} "matrix that was colored" A::M "bipartite graph that was used for coloring" bg::G "one integer color for each column or row (depending on `partition`)" - color::Vector{Int} + color::Vector{T} "color groups for columns or rows (depending on `partition`)" - group::V + group::GT "flattened indices mapping the compressed matrix `B` to the uncompressed matrix `A` when `A isa SparseMatrixCSC`. They satisfy `nonzeros(A)[k] = vec(B)[compressed_indices[k]]`" - compressed_indices::Vector{Int} + compressed_indices::Vector{T} end -function ColumnColoringResult(A::AbstractMatrix, bg::BipartiteGraph, color::Vector{Int}) +function ColumnColoringResult( + A::AbstractMatrix, bg::BipartiteGraph{T}, color::Vector{<:Integer} +) where {T<:Integer} S = bg.S2 - group = group_by_color(color) + group = group_by_color(T, color) n = size(S, 1) rv = rowvals(S) - compressed_indices = zeros(Int, nnz(S)) + compressed_indices = zeros(T, nnz(S)) for j in axes(S, 2) for k in nzrange(S, j) i = rv[k] @@ -187,21 +194,24 @@ $TYPEDFIELDS - [`AbstractColoringResult`](@ref) """ -struct RowColoringResult{M<:AbstractMatrix,G<:BipartiteGraph,V} <: - AbstractColoringResult{:nonsymmetric,:row,:direct} +struct RowColoringResult{ + M<:AbstractMatrix,T<:Integer,G<:BipartiteGraph{T},GT<:AbstractGroups{T} +} <: AbstractColoringResult{:nonsymmetric,:row,:direct} A::M bg::G - color::Vector{Int} - group::V - compressed_indices::Vector{Int} + color::Vector{T} + group::GT + compressed_indices::Vector{T} end -function RowColoringResult(A::AbstractMatrix, bg::BipartiteGraph, color::Vector{Int}) +function RowColoringResult( + A::AbstractMatrix, bg::BipartiteGraph{T}, color::Vector{<:Integer} +) where {T<:Integer} S = bg.S2 - group = group_by_color(color) + group = group_by_color(T, color) C = length(group) # ncolors rv = rowvals(S) - compressed_indices = zeros(Int, nnz(S)) + compressed_indices = zeros(T, nnz(S)) for j in axes(S, 2) for k in nzrange(S, j) i = rv[k] @@ -228,26 +238,30 @@ $TYPEDFIELDS - [`AbstractColoringResult`](@ref) """ -struct StarSetColoringResult{M<:AbstractMatrix,G<:AdjacencyGraph,V} <: - AbstractColoringResult{:symmetric,:column,:direct} +struct StarSetColoringResult{ + M<:AbstractMatrix,T<:Integer,G<:AdjacencyGraph{T},GT<:AbstractGroups{T} +} <: AbstractColoringResult{:symmetric,:column,:direct} A::M ag::G - color::Vector{Int} - group::V - star_set::StarSet - compressed_indices::Vector{Int} + color::Vector{T} + group::GT + star_set::StarSet{T} + compressed_indices::Vector{T} end function StarSetColoringResult( - A::AbstractMatrix, ag::AdjacencyGraph, color::Vector{Int}, star_set::StarSet -) + A::AbstractMatrix, + ag::AdjacencyGraph{T}, + color::Vector{<:Integer}, + star_set::StarSet{<:Integer}, +) where {T<:Integer} (; star, hub) = star_set S = pattern(ag) edge_to_index = edge_indices(ag) n = S.n - group = group_by_color(color) + group = group_by_color(T, color) rvS = rowvals(S) - compressed_indices = zeros(Int, nnz(S)) # needs to be independent from the storage in the graph, in case the graph gets reused + compressed_indices = zeros(T, nnz(S)) # needs to be independent from the storage in the graph, in case the graph gets reused for j in axes(S, 2) for k in nzrange(S, j) i = rvS[k] @@ -293,36 +307,37 @@ $TYPEDFIELDS - [`AbstractColoringResult`](@ref) """ -struct TreeSetColoringResult{M<:AbstractMatrix,G<:AdjacencyGraph,V,R} <: - AbstractColoringResult{:symmetric,:column,:substitution} +struct TreeSetColoringResult{ + M<:AbstractMatrix,T<:Integer,G<:AdjacencyGraph{T},GT<:AbstractGroups{T},R +} <: AbstractColoringResult{:symmetric,:column,:substitution} A::M ag::G - color::Vector{Int} - group::V - reverse_bfs_orders::Vector{Vector{Tuple{Int,Int}}} - diagonal_indices::Vector{Int} - diagonal_nzind::Vector{Int} - lower_triangle_offsets::Vector{Int} - upper_triangle_offsets::Vector{Int} + color::Vector{T} + group::GT + reverse_bfs_orders::Vector{Vector{Tuple{T,T}}} + diagonal_indices::Vector{T} + diagonal_nzind::Vector{T} + lower_triangle_offsets::Vector{T} + upper_triangle_offsets::Vector{T} buffer::Vector{R} end function TreeSetColoringResult( A::AbstractMatrix, - ag::AdjacencyGraph, - color::Vector{Int}, - tree_set::TreeSet, + ag::AdjacencyGraph{T}, + color::Vector{<:Integer}, + tree_set::TreeSet{<:Integer}, decompression_eltype::Type{R}, -) where {R} +) where {T<:Integer,R} (; reverse_bfs_orders) = tree_set (; S) = ag nvertices = length(color) - group = group_by_color(color) + group = group_by_color(T, color) rv = rowvals(S) # Vector for the decompression of the diagonal coefficients - diagonal_indices = Int[] - diagonal_nzind = Int[] + diagonal_indices = T[] + diagonal_nzind = T[] ndiag = 0 if has_diagonal(ag) @@ -340,8 +355,8 @@ function TreeSetColoringResult( # Vectors for the decompression of the off-diagonal coefficients nedges = (nnz(S) - ndiag) ÷ 2 - lower_triangle_offsets = Vector{Int}(undef, nedges) - upper_triangle_offsets = Vector{Int}(undef, nedges) + lower_triangle_offsets = Vector{T}(undef, nedges) + upper_triangle_offsets = Vector{T}(undef, nedges) # Index in lower_triangle_offsets and upper_triangle_offsets index_offsets = 0 @@ -415,30 +430,34 @@ $TYPEDFIELDS - [`AbstractColoringResult`](@ref) """ -struct LinearSystemColoringResult{M<:AbstractMatrix,G<:AdjacencyGraph,V,R,F} <: - AbstractColoringResult{:symmetric,:column,:substitution} +struct LinearSystemColoringResult{ + M<:AbstractMatrix,T<:Integer,G<:AdjacencyGraph{T},GT<:AbstractGroups{T},R,F +} <: AbstractColoringResult{:symmetric,:column,:substitution} A::M ag::G - color::Vector{Int} - group::V - strict_upper_nonzero_inds::Vector{Tuple{Int,Int}} + color::Vector{T} + group::GT + strict_upper_nonzero_inds::Vector{Tuple{T,T}} strict_upper_nonzeros_A::Vector{R} # TODO: adjust type - T_factorization::F # TODO: adjust type + M_factorization::F # TODO: adjust type end function LinearSystemColoringResult( - A::AbstractMatrix, ag::AdjacencyGraph, color::Vector{Int}, decompression_eltype::Type{R} -) where {R} - group = group_by_color(color) + A::AbstractMatrix, + ag::AdjacencyGraph{T}, + color::Vector{<:Integer}, + decompression_eltype::Type{R}, +) where {T<:Integer,R<:Real} + group = group_by_color(T, color) C = length(group) # ncolors S = ag.S rv = rowvals(S) - # build T such that T * strict_upper_nonzeros(A) = B + # build M such that M * strict_upper_nonzeros(A) = B # and solve a linear least-squares problem # only consider the strict upper triangle of A because of symmetry n = checksquare(S) - strict_upper_nonzero_inds = Tuple{Int,Int}[] + strict_upper_nonzero_inds = Tuple{T,T}[] for j in axes(S, 2) for k in nzrange(S, j) i = rv[k] @@ -446,22 +465,23 @@ function LinearSystemColoringResult( end end - T = spzeros(float(R), n * C, length(strict_upper_nonzero_inds)) + # type annotated because JET was unhappy + M::SparseMatrixCSC = spzeros(float(R), n * C, length(strict_upper_nonzero_inds)) for (l, (i, j)) in enumerate(strict_upper_nonzero_inds) ci = color[i] cj = color[j] if ci > 0 ki = (ci - 1) * n + j # A[i, j] appears in B[j, ci] - T[ki, l] = 1 + M[ki, l] = 1 end if cj > 0 kj = (cj - 1) * n + i # A[i, j] appears in B[i, cj] - T[kj, l] = 1 + M[kj, l] = 1 end end - T_factorization = factorize(T) + M_factorization = factorize(M) - strict_upper_nonzeros_A = Vector{float(R)}(undef, size(T, 2)) + strict_upper_nonzeros_A = Vector{float(R)}(undef, size(M, 2)) return LinearSystemColoringResult( A, @@ -470,14 +490,14 @@ function LinearSystemColoringResult( group, strict_upper_nonzero_inds, strict_upper_nonzeros_A, - T_factorization, + M_factorization, ) end ## Bicoloring result """ - remap_colors(color::Vector{Int}, num_sym_colors::Int, m::Int, n::Int) + remap_colors(color::Vector{<:Integer}, num_sym_colors::Integer, m::Integer, n::Integer) Return a tuple `(row_color, column_color, symmetric_to_row, symmetric_to_column)` such that `row_color` and `column_color` are vectors containing the renumbered colors for rows and columns. `symmetric_to_row` and `symmetric_to_column` are vectors that map symmetric colors to row and column colors. @@ -490,10 +510,12 @@ For all vertex indices `j` between `1` and `n` we have: column_color[j] = symmetric_to_column[color[j]] """ -function remap_colors(color::Vector{Int}, num_sym_colors::Int, m::Int, n::Int) +function remap_colors( + ::Type{T}, color::Vector{<:Integer}, num_sym_colors::Integer, m::Integer, n::Integer +) where {T<:Integer} # Map symmetric colors to column colors - symmetric_to_column = zeros(Int, num_sym_colors) - column_color = zeros(Int, n) + symmetric_to_column = zeros(T, num_sym_colors) + column_color = zeros(T, n) counter = 0 for j in 1:n @@ -509,8 +531,8 @@ function remap_colors(color::Vector{Int}, num_sym_colors::Int, m::Int, n::Int) end # Map symmetric colors to row colors - symmetric_to_row = zeros(Int, num_sym_colors) - row_color = zeros(Int, m) + symmetric_to_row = zeros(T, num_sym_colors) + row_color = zeros(T, m) counter = 0 for i in (n + 1):(n + m) @@ -543,9 +565,10 @@ $TYPEDFIELDS """ struct BicoloringResult{ M<:AbstractMatrix, - G<:AdjacencyGraph, + T<:Integer, + G<:AdjacencyGraph{T}, decompression, - V, + GT<:AbstractGroups{T}, SR<:AbstractColoringResult{:symmetric,:column,decompression}, R, } <: AbstractColoringResult{:nonsymmetric,:bidirectional,decompression} @@ -554,25 +577,25 @@ struct BicoloringResult{ "augmented adjacency graph that was used for bicoloring" abg::G "one integer color for each column" - column_color::Vector{Int} + column_color::Vector{T} "one integer color for each row" - row_color::Vector{Int} + row_color::Vector{T} "color groups for columns" - column_group::V + column_group::GT "color groups for rows" - row_group::V + row_group::GT "result for the coloring of the symmetric 2 x 2 block matrix" symmetric_result::SR "maps symmetric colors to column colors" - symmetric_to_column::Vector{Int} + symmetric_to_column::Vector{T} "maps symmetric colors to row colors" - symmetric_to_row::Vector{Int} + symmetric_to_row::Vector{T} "combination of `Br` and `Bc` (almost a concatenation up to color remapping)" Br_and_Bc::Matrix{R} "CSC storage of `A_and_noAᵀ - `colptr`" - large_colptr::Vector{Int} + large_colptr::Vector{T} "CSC storage of `A_and_noAᵀ - `rowval`" - large_rowval::Vector{Int} + large_rowval::Vector{T} end column_colors(result::BicoloringResult) = result.column_color @@ -583,18 +606,18 @@ row_groups(result::BicoloringResult) = result.row_group function BicoloringResult( A::AbstractMatrix, - ag::AdjacencyGraph, + ag::AdjacencyGraph{T}, symmetric_result::AbstractColoringResult{:symmetric,:column}, decompression_eltype::Type{R}, -) where {R} +) where {T,R} m, n = size(A) symmetric_color = column_colors(symmetric_result) num_sym_colors = maximum(symmetric_color) row_color, column_color, symmetric_to_row, symmetric_to_column = remap_colors( - symmetric_color, num_sym_colors, m, n + T, symmetric_color, num_sym_colors, m, n ) - column_group = group_by_color(column_color) - row_group = group_by_color(row_color) + column_group = group_by_color(T, column_color) + row_group = group_by_color(T, row_color) Br_and_Bc = Matrix{R}(undef, n + m, num_sym_colors) large_colptr = copy(ag.S.colptr) large_colptr[(n + 2):end] .= large_colptr[n + 1] # last few columns are empty diff --git a/test/graph.jl b/test/graph.jl index 477ebce4..9c8f66c1 100644 --- a/test/graph.jl +++ b/test/graph.jl @@ -15,6 +15,7 @@ using Test ## SparsityPatternCSC @testset "SparsityPatternCSC" begin + @test eltype(SparsityPatternCSC(sprand(10, 10, 0.1))) == Int @testset "Transpose" begin for _ in 1:1000 m, n = rand(100:1000), rand(100:1000) @@ -90,6 +91,7 @@ end ]) bg = BipartiteGraph(A; symmetric_pattern=false) + @test eltype(bg) == Int @test_throws DimensionMismatch BipartiteGraph(A; symmetric_pattern=true) @test nb_vertices(bg, Val(1)) == 4 @test nb_vertices(bg, Val(2)) == 8 @@ -143,6 +145,7 @@ end; ]) g = AdjacencyGraph(transpose(A) * A) + @test eltype(g) == Int @test nb_vertices(g) == 8 # wrong neighbors, it's okay they are filtered after @test collect(neighbors(g, 1)) == sort(vcat(1, [6, 7, 8])) diff --git a/test/type_stability.jl b/test/type_stability.jl index cd18e21c..cd01df8c 100644 --- a/test/type_stability.jl +++ b/test/type_stability.jl @@ -162,3 +162,41 @@ end; @inferred decompress(B, result) end end; + +@testset "Single precision" begin + A = convert( + SparseMatrixCSC{Float32,Int32}, + sparse(Symmetric(sprand(rng, Float32, 100, 100, 0.1))), + ) + @testset "$structure - $partition - $decompression" for ( + structure, partition, decompression + ) in [ + (:nonsymmetric, :column, :direct), + (:nonsymmetric, :row, :direct), + (:symmetric, :column, :direct), + (:symmetric, :column, :substitution), + (:nonsymmetric, :bidirectional, :direct), + (:nonsymmetric, :bidirectional, :substitution), + ] + result = coloring( + A, + ColoringProblem(; structure, partition), + GreedyColoringAlgorithm(; decompression); + ) + if partition in (:column, :bidirectional) + @test eltype(column_colors(result)) == Int32 + @test eltype(column_groups(result)[1]) == Int32 + end + if partition in (:row, :bidirectional) + @test eltype(row_colors(result)) == Int32 + @test eltype(row_groups(result)[1]) == Int32 + end + if partition == :bidirectional + Br, Bc = compress(A, result) + @test decompress(Br, Bc, result) isa SparseMatrixCSC{Float32,Int32} + else + B = compress(A, result) + @test decompress(B, result) isa SparseMatrixCSC{Float32,Int32} + end + end +end