From ea3b4047e2d06d73a6aa34f7179fe887341a800f Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:14:03 +0200 Subject: [PATCH] Custom adjacency graph from bipartite --- docs/src/dev.md | 4 ++- src/coloring.jl | 16 +++++------ src/graph.jl | 75 ++++++++++++++++++++++++++++++++++++++++--------- src/order.jl | 2 +- test/graph.jl | 36 +++++++++++++++++++++++- 5 files changed, 109 insertions(+), 24 deletions(-) diff --git a/docs/src/dev.md b/docs/src/dev.md index b8aa279..eb3a837 100644 --- a/docs/src/dev.md +++ b/docs/src/dev.md @@ -11,8 +11,10 @@ The docstrings on this page describe internals, they are not part of the public ```@docs SparseMatrixColorings.SparsityPatternCSC -SparseMatrixColorings.AdjacencyGraph SparseMatrixColorings.BipartiteGraph +SparseMatrixColorings.AbstractAdjacencyGraph +SparseMatrixColorings.AdjacencyGraph +SparseMatrixColorings.AdjacencyFromBipartiteGraph SparseMatrixColorings.vertices SparseMatrixColorings.neighbors transpose diff --git a/src/coloring.jl b/src/coloring.jl index 126318f..1cf74bd 100644 --- a/src/coloring.jl +++ b/src/coloring.jl @@ -54,7 +54,7 @@ function partial_distance2_coloring!( end """ - star_coloring(g::AdjacencyGraph, order::AbstractOrder) + star_coloring(g::AbstractAdjacencyGraph, order::AbstractOrder) Compute a star coloring of all vertices in the adjacency graph `g` and return a tuple `(color, star_set)`, where @@ -67,14 +67,14 @@ The vertices are colored in a greedy fashion, following the `order` supplied. # See also -- [`AdjacencyGraph`](@ref) +- [`AbstractAdjacencyGraph`](@ref) - [`AbstractOrder`](@ref) # References > [_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) +function star_coloring(g::AbstractAdjacencyGraph, order::AbstractOrder) # Initialize data structures nv = nb_vertices(g) color = zeros(Int, nv) @@ -157,7 +157,7 @@ function _treat!( treated::AbstractVector{<:Integer}, forbidden_colors::AbstractVector{<:Integer}, # not modified - g::AdjacencyGraph, + g::AbstractAdjacencyGraph, v::Integer, w::Integer, color::AbstractVector{<:Integer}, @@ -175,7 +175,7 @@ function _update_stars!( star::Dict{<:Tuple,<:Integer}, hub::AbstractVector{<:Integer}, # not modified - g::AdjacencyGraph, + g::AbstractAdjacencyGraph, v::Integer, color::AbstractVector{<:Integer}, first_neighbor::AbstractVector{<:Tuple}, @@ -247,7 +247,7 @@ function symmetric_coefficient( end """ - acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder) + acyclic_coloring(g::AbstractAdjacencyGraph, order::AbstractOrder) Compute an acyclic coloring of all vertices in the adjacency graph `g` and return a tuple `(color, tree_set)`, where @@ -260,14 +260,14 @@ The vertices are colored in a greedy fashion, following the `order` supplied. # See also -- [`AdjacencyGraph`](@ref) +- [`AbstractAdjacencyGraph`](@ref) - [`AbstractOrder`](@ref) # References > [_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) +function acyclic_coloring(g::AbstractAdjacencyGraph, order::AbstractOrder) # Initialize data structures nv = nb_vertices(g) ne = nb_edges(g) diff --git a/src/graph.jl b/src/graph.jl index 3810c3a..30265a1 100644 --- a/src/graph.jl +++ b/src/graph.jl @@ -95,6 +95,16 @@ end ## Adjacency graph +""" + AbstractAdjacencyGraph{T} + +Supertype for various adjacency graph implementations: + +- [`AdjacencyGraph`](@ref) +- [`AdjacencyFromBipartiteGraph`](@ref) +""" +abstract type AbstractAdjacencyGraph{T} end + """ AdjacencyGraph{T} @@ -117,7 +127,7 @@ The adjacency graph of a symmetrix matric `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} +struct AdjacencyGraph{T} <: AbstractAdjacencyGraph{T} S::SparsityPatternCSC{T} end @@ -126,7 +136,7 @@ AdjacencyGraph(A::SparseMatrixCSC) = AdjacencyGraph(SparsityPatternCSC(A)) pattern(g::AdjacencyGraph) = g.S nb_vertices(g::AdjacencyGraph) = pattern(g).n -vertices(g::AdjacencyGraph) = 1:nb_vertices(g) +vertices(g::AbstractAdjacencyGraph) = 1:nb_vertices(g) function neighbors(g::AdjacencyGraph, v::Integer) S = pattern(g) @@ -134,7 +144,7 @@ function neighbors(g::AdjacencyGraph, v::Integer) return Iterators.filter(!=(v), neighbors_with_loops) # TODO: optimize end -function degree(g::AdjacencyGraph, v::Integer) +function degree(g::AbstractAdjacencyGraph, v::Integer) d = 0 for u in neighbors(g, v) if u != v @@ -144,22 +154,18 @@ function degree(g::AdjacencyGraph, v::Integer) return d end -function nb_edges(g::AdjacencyGraph) - S = pattern(g) +function nb_edges(g::AbstractAdjacencyGraph) ne = 0 - for j in vertices(g) - for k in nzrange(S, j) - i = rowvals(S)[k] - if i > j - ne += 1 - end + for v in vertices(g) + for u in neighbors(g, v) + ne += 1 end end return ne end -maximum_degree(g::AdjacencyGraph) = maximum(Base.Fix1(degree, g), vertices(g)) -minimum_degree(g::AdjacencyGraph) = minimum(Base.Fix1(degree, g), vertices(g)) +maximum_degree(g::AbstractAdjacencyGraph) = maximum(Base.Fix1(degree, g), vertices(g)) +minimum_degree(g::AbstractAdjacencyGraph) = minimum(Base.Fix1(degree, g), vertices(g)) ## Bipartite graph @@ -258,3 +264,46 @@ function degree_dist2(bg::BipartiteGraph{T}, ::Val{side}, v::Integer) where {T,s end return length(neighbors_dist2) end + +## Adjacency graph from bipartite + +""" + AdjacencyFromBipartiteGraph{T} + +Custom version of [`AdjacencyGraph`](@ref) constructed from a [`BipartiteGraph`](@ref). +If the bipartite graph represents a matrix `A`, then this graph represents the block matrix `[0 A; A' 0]` (of size `(n+m) x (n+m)`). + +# Constructors + + AdjacencyFromBipartiteGraph(A::AbstractMatrix) + +# Fields + +- `bg::BipartiteGraph{T}`: bipartite graph representation of the matrix `A` +""" +struct AdjacencyFromBipartiteGraph{T} <: AbstractAdjacencyGraph{T} + bg::BipartiteGraph{T} +end + +function AdjacencyFromBipartiteGraph(A::AbstractMatrix; kwargs...) + return AdjacencyFromBipartiteGraph(BipartiteGraph(A; kwargs...)) +end + +function nb_vertices(abg::AdjacencyFromBipartiteGraph) + @compat (; bg) = abg + m, n = nb_vertices(bg, Val(1)), nb_vertices(bg, Val(2)) + return m + n +end + +function neighbors(abg::AdjacencyFromBipartiteGraph, v::Integer) + @compat (; bg) = abg + m, n = nb_vertices(bg, Val(1)), nb_vertices(bg, Val(2)) + if 1 <= v <= n + # v is a column + return neighbors(bg, Val(2), v) + else + # v is a row + @assert n + 1 <= v <= n + m + return neighbors(bg, Val(1), v - n) + end +end diff --git a/src/order.jl b/src/order.jl index 0d2afe6..fc5a94c 100644 --- a/src/order.jl +++ b/src/order.jl @@ -21,7 +21,7 @@ Instance of [`AbstractOrder`](@ref) which sorts vertices using their index in th """ struct NaturalOrder <: AbstractOrder end -function vertices(g::AdjacencyGraph, ::NaturalOrder) +function vertices(g::AbstractAdjacencyGraph, ::NaturalOrder) return vertices(g) end diff --git a/test/graph.jl b/test/graph.jl index 1eddd1f..99c16ea 100644 --- a/test/graph.jl +++ b/test/graph.jl @@ -3,12 +3,15 @@ using SparseArrays using SparseMatrixColorings: SparsityPatternCSC, AdjacencyGraph, + AdjacencyFromBipartiteGraph, BipartiteGraph, degree, degree_dist2, nb_vertices, nb_edges, - neighbors + neighbors, + star_coloring, + acyclic_coloring using Test ## SparsityPatternCSC @@ -118,3 +121,34 @@ end; @test collect(neighbors(g, 7)) == [1, 2, 4, 5, 6, 8] @test collect(neighbors(g, 8)) == [1, 2, 3, 5, 6, 7] end + +@testset "AdjacencyFromBipartiteGraph" begin + A = sparse([ + 1 0 0 0 0 1 1 1 + 0 1 0 0 1 0 1 1 + 0 0 1 0 1 1 0 1 + 0 0 0 1 1 1 1 0 + ]) + + abg = AdjacencyFromBipartiteGraph(A) + + @test nb_vertices(abg) == 4 + 8 + # neighbors of columns + @test neighbors(abg, 1) == [1] + @test neighbors(abg, 2) == [2] + @test neighbors(abg, 3) == [3] + @test neighbors(abg, 4) == [4] + @test neighbors(abg, 5) == [2, 3, 4] + @test neighbors(abg, 6) == [1, 3, 4] + @test neighbors(abg, 7) == [1, 2, 4] + @test neighbors(abg, 8) == [1, 2, 3] + # neighbors of rows + @test neighbors(abg, 8 + 1) == [1, 6, 7, 8] + @test neighbors(abg, 8 + 2) == [2, 5, 7, 8] + @test neighbors(abg, 8 + 3) == [3, 5, 6, 8] + @test neighbors(abg, 8 + 4) == [4, 5, 6, 7] + + # TODO: remove once we have better tests, this is just to check whether it runs + @test length(star_coloring(abg, NaturalOrder())[1]) == 12 + @test length(acyclic_coloring(abg, NaturalOrder())[1]) == 12 +end