Skip to content

Commit 3246a9b

Browse files
authored
Save allocations for bipartite graph with symmetric patterns (#102)
1 parent d8f579d commit 3246a9b

5 files changed

Lines changed: 72 additions & 17 deletions

File tree

src/graph.jl

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ end
7979
## Construct from matrices
8080

8181
"""
82-
adjacency_graph(H::AbstractMatrix)
82+
adjacency_graph(A::SparseMatrixCSC)
8383
8484
Return a [`Graph`](@ref) representing the nonzeros of a symmetric matrix (typically a Hessian matrix).
8585
@@ -92,10 +92,10 @@ The adjacency graph of a symmetrix matric `A ∈ ℝ^{n × n}` is `G(A) = (V, E)
9292
9393
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
9494
"""
95-
adjacency_graph(H::SparseMatrixCSC) = Graph(H - Diagonal(H))
95+
adjacency_graph(A::SparseMatrixCSC) = Graph(A - Diagonal(A))
9696

9797
"""
98-
bipartite_graph(J::AbstractMatrix)
98+
bipartite_graph(A::SparseMatrixCSC; symmetric_pattern::Bool)
9999
100100
Return a [`BipartiteGraph`](@ref) representing the nonzeros of a non-symmetric matrix (typically a Jacobian matrix).
101101
@@ -105,12 +105,19 @@ The bipartite graph of a matrix `A ∈ ℝ^{m × n}` is `Gb(A) = (V₁, V₂, E)
105105
- `V₂ = 1:n` is the set of columns `j`
106106
- `(i, j) ∈ E` whenever `A[i, j] ≠ 0`
107107
108+
When `symmetric_pattern` is `true`, this construction is more efficient.
109+
108110
# References
109111
110112
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
111113
"""
112-
function bipartite_graph(J::SparseMatrixCSC)
113-
g1 = Graph(sparse(transpose(J))) # rows to columns
114-
g2 = Graph(J) # columns to rows
114+
function bipartite_graph(A::SparseMatrixCSC; symmetric_pattern::Bool=false)
115+
g2 = Graph(A) # columns to rows
116+
if symmetric_pattern
117+
checksquare(A) # proxy for checking full symmetry
118+
g1 = g2
119+
else
120+
g1 = Graph(sparse(transpose(A))) # rows to columns
121+
end
115122
return BipartiteGraph(g1, g2)
116123
end

src/interface.jl

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,18 @@ end
117117
S::AbstractMatrix,
118118
problem::ColoringProblem,
119119
algo::GreedyColoringAlgorithm;
120-
[decompression_eltype=Float64]
120+
[decompression_eltype=Float64, symmetric_pattern=false]
121121
)
122122
123123
Solve a [`ColoringProblem`](@ref) on the matrix `S` with a [`GreedyColoringAlgorithm`](@ref) and return an [`AbstractColoringResult`](@ref).
124124
125125
The result can be used to [`compress`](@ref) and [`decompress`](@ref) a matrix `A` with the same sparsity pattern as `S`.
126126
If `eltype(A) == decompression_eltype`, decompression might be faster.
127127
128+
For a `:nonsymmetric` problem (and only then), setting `symmetric_pattern=true` indicates that the pattern of nonzeros is symmetric.
129+
This condition is weaker than the symmetry of actual values, so it can happen for some Jacobians.
130+
Specifying it allows faster construction of the bipartite graph.
131+
128132
# Example
129133
130134
```jldoctest
@@ -173,10 +177,13 @@ function coloring(
173177
A::AbstractMatrix,
174178
::ColoringProblem{:nonsymmetric,:column},
175179
algo::GreedyColoringAlgorithm;
176-
decompression_eltype=Float64,
180+
decompression_eltype::Type=Float64,
181+
symmetric_pattern::Bool=false,
177182
)
178183
S = sparse(A)
179-
bg = bipartite_graph(S)
184+
bg = bipartite_graph(
185+
S; symmetric_pattern=symmetric_pattern || A isa Union{Symmetric,Hermitian}
186+
)
180187
color = partial_distance2_coloring(bg, Val(2), algo.order)
181188
return ColumnColoringResult(S, color)
182189
end
@@ -185,10 +192,13 @@ function coloring(
185192
A::AbstractMatrix,
186193
::ColoringProblem{:nonsymmetric,:row},
187194
algo::GreedyColoringAlgorithm;
188-
decompression_eltype=Float64,
195+
decompression_eltype::Type=Float64,
196+
symmetric_pattern::Bool=false,
189197
)
190198
S = sparse(A)
191-
bg = bipartite_graph(S)
199+
bg = bipartite_graph(
200+
S; symmetric_pattern=symmetric_pattern || A isa Union{Symmetric,Hermitian}
201+
)
192202
color = partial_distance2_coloring(bg, Val(1), algo.order)
193203
return RowColoringResult(S, color)
194204
end
@@ -197,7 +207,7 @@ function coloring(
197207
A::AbstractMatrix,
198208
::ColoringProblem{:symmetric,:column},
199209
algo::GreedyColoringAlgorithm{:direct};
200-
decompression_eltype=Float64,
210+
decompression_eltype::Type=Float64,
201211
)
202212
S = sparse(A)
203213
ag = adjacency_graph(S)
@@ -209,7 +219,7 @@ function coloring(
209219
A::AbstractMatrix,
210220
::ColoringProblem{:symmetric,:column},
211221
algo::GreedyColoringAlgorithm{:substitution};
212-
decompression_eltype=Float64,
222+
decompression_eltype::Type=Float64,
213223
)
214224
S = sparse(A)
215225
ag = adjacency_graph(S)
@@ -221,14 +231,14 @@ end
221231

222232
function ADTypes.column_coloring(A::AbstractMatrix, algo::GreedyColoringAlgorithm)
223233
S = sparse(A)
224-
bg = bipartite_graph(S)
234+
bg = bipartite_graph(S; symmetric_pattern=A isa Union{Symmetric,Hermitian})
225235
color = partial_distance2_coloring(bg, Val(2), algo.order)
226236
return color
227237
end
228238

229239
function ADTypes.row_coloring(A::AbstractMatrix, algo::GreedyColoringAlgorithm)
230240
S = sparse(A)
231-
bg = bipartite_graph(S)
241+
bg = bipartite_graph(S; symmetric_pattern=A isa Union{Symmetric,Hermitian})
232242
color = partial_distance2_coloring(bg, Val(1), algo.order)
233243
return color
234244
end

test/graph.jl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ end;
2828
0 0 0 1 1 1 1 0
2929
])
3030

31-
bg = bipartite_graph(A)
31+
bg = bipartite_graph(A; symmetric_pattern=false)
32+
@test_throws DimensionMismatch bipartite_graph(A; symmetric_pattern=true)
3233
@test length(bg, Val(1)) == 4
3334
@test length(bg, Val(2)) == 8
3435
# neighbors of rows
@@ -45,6 +46,21 @@ end;
4546
@test neighbors(bg, Val(2), 6) == [1, 3, 4]
4647
@test neighbors(bg, Val(2), 7) == [1, 2, 4]
4748
@test neighbors(bg, Val(2), 8) == [1, 2, 3]
49+
50+
A = sparse([
51+
1 0 1 1
52+
0 1 0 1
53+
1 0 1 0
54+
1 1 0 1
55+
])
56+
bg = bipartite_graph(A; symmetric_pattern=true)
57+
@test length(bg, Val(1)) == 4
58+
@test length(bg, Val(2)) == 4
59+
# neighbors of rows and columns
60+
@test neighbors(bg, Val(1), 1) == neighbors(bg, Val(2), 1) == [1, 3, 4]
61+
@test neighbors(bg, Val(1), 2) == neighbors(bg, Val(2), 2) == [2, 4]
62+
@test neighbors(bg, Val(1), 3) == neighbors(bg, Val(2), 3) == [1, 3]
63+
@test neighbors(bg, Val(1), 4) == neighbors(bg, Val(2), 4) == [1, 2, 4]
4864
end;
4965

5066
## Adjacency graph (fig 3.1 of "What color is your Jacobian?")

test/random.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ symmetric_params = vcat(
3333
@test directly_recoverable_columns(A0, color0)
3434
test_coloring_decompression(A0, problem, algo; color0)
3535
end
36+
@testset "$((; n, p))" for (n, p) in symmetric_params
37+
A0 = sparse(Symmetric(sprand(rng, n, n, p)))
38+
color0 = column_coloring(A0, algo)
39+
@test structurally_orthogonal_columns(A0, color0)
40+
@test directly_recoverable_columns(A0, color0)
41+
test_coloring_decompression(A0, problem, algo; color0)
42+
end
3643
end;
3744

3845
@testset "Row coloring & decompression" begin
@@ -45,6 +52,13 @@ end;
4552
@test directly_recoverable_columns(transpose(A0), color0)
4653
test_coloring_decompression(A0, problem, algo; color0)
4754
end
55+
@testset "$((; n, p))" for (n, p) in symmetric_params
56+
A0 = sparse(Symmetric(sprand(rng, n, n, p)))
57+
color0 = row_coloring(A0, algo)
58+
@test structurally_orthogonal_columns(transpose(A0), color0)
59+
@test directly_recoverable_columns(transpose(A0), color0)
60+
test_coloring_decompression(A0, problem, algo; color0)
61+
end
4862
end;
4963

5064
@testset "Symmetric coloring & direct decompression" begin

test/utils.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@ function test_coloring_decompression(
1212
) where {structure,partition,decompression}
1313
color_vec = Vector{Int}[]
1414
@testset "$(typeof(A))" for A in matrix_versions(A0)
15-
result = coloring(A, problem, algo; decompression_eltype=Float64)
15+
yield()
16+
17+
if structure == :nonsymmetric && issymmetric(A)
18+
result = coloring(
19+
A, problem, algo; decompression_eltype=Float64, symmetric_pattern=true
20+
)
21+
else
22+
result = coloring(A, problem, algo; decompression_eltype=Float64)
23+
end
1624
color = if partition == :column
1725
column_colors(result)
1826
elseif partition == :row

0 commit comments

Comments
 (0)