From 6f557ededebc7a0faf69f68bd13f48081783e91d Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 10 Aug 2024 10:45:59 -0400 Subject: [PATCH 1/5] Implement TreeSet-based acyclic decompression --- src/decompression.jl | 93 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/decompression.jl b/src/decompression.jl index a640901..d8bddc0 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -295,3 +295,96 @@ function decompress_aux!( end return A end + +function decompress_aux!( + A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::TreeSetColoringResult +) where {R<:Real} + A .= zero(R) + S = get_matrix(result) + color = column_colors(result) + forest = result.tree_set.forest + + ntrees = forest.internal.ngroups + for edge in forest.revmap + # ensure that all paths are compressed + find_root!(forest, edge) + end + roots = forest.internal.parents + unique_roots = unique(roots) + + trees = [Int[] for i in 1:ntrees] + k = 0 + for root in unique_roots + k += 1 + for (pos, val) in enumerate(roots) + if root == val + push!(trees[k], pos) + end + end + end + + degrees = [Dict{Int,Int}() for k in 1:ntrees] + for k in 1:ntrees + tree = trees[k] + degree = degrees[k] + for edge_index in tree + i, j = forest.revmap[edge_index] + !haskey(degree, i) && (degree[i] = 0) + !haskey(degree, j) && (degree[j] = 0) + degree[i] += 1 + degree[j] += 1 + end + end + + n = checksquare(A) + stored_values = Vector{R}(undef, n) + + # Recover the diagonal coefficients of A + for i in axes(A, 1) + if !iszero(S[i, i]) + A[i, i] = B[i, color[i]] + end + end + + # Recover the off-diagonal coefficients of A + for k in 1:ntrees + tree = trees[k] + degree = degrees[k] + + nedges = length(tree) + if nedges == 1 + edge_index = tree[1] + i, j = forest.revmap[edge_index] + val = B[i, color[j]] + A[i, j] = val + A[j, i] = val + else + for edge_index in tree + i, j = forest.revmap[edge_index] + stored_values[i] = zero(R) + stored_values[j] = zero(R) + end + + while sum(values(degree)) != 0 + for (t, edge_index) in enumerate(tree) + if edge_index != 0 + i, j = forest.revmap[edge_index] + if (degree[i] == 1) || (degree[j] == 1) # leaf vertex + if degree[i] > degree[j] # vertex i is the parent of vertex j + i, j = j, i # ensure that i always denotes a leaf vertex + end + degree[i] -= 1 # decrease the degree of vertex i + degree[j] -= 1 # decrease the degree of vertex j + tree[t] = 0 # remove the edge (i,j) + val = B[i, color[j]] - stored_values[i] + stored_values[j] = stored_values[j] + val + A[i, j] = val + A[j, i] = val + end + end + end + end + end + end + return A +end From 5d8fb3b117a130e6bb7a8bf7e52ea538de90a228 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sat, 10 Aug 2024 23:39:38 -0400 Subject: [PATCH 2/5] Add comments in decompression.jl --- src/decompression.jl | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/decompression.jl b/src/decompression.jl index d8bddc0..cbf5f8b 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -299,30 +299,37 @@ end function decompress_aux!( A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::TreeSetColoringResult ) where {R<:Real} + n = checksquare(A) A .= zero(R) S = get_matrix(result) color = column_colors(result) - forest = result.tree_set.forest + # forest is a structure DisjointSets from DataStructures.jl + # - forest.intmap: a dictionary that maps an edge (i, j) to an integer k + # - forest.revmap: a dictionary that does the reverse of intmap, mapping an integer k to an edge (i, j) + # - forest.internal.ngroups: the number of trees in the forest + forest = result.tree_set.forest ntrees = forest.internal.ngroups - for edge in forest.revmap - # ensure that all paths are compressed - find_root!(forest, edge) - end - roots = forest.internal.parents - unique_roots = unique(roots) + # vector of trees where each tree contains the indices of its edges trees = [Int[] for i in 1:ntrees] + + # dictionary that maps a tree's root to the index of the tree + roots = Dict{Int, Int}() + k = 0 - for root in unique_roots - k += 1 - for (pos, val) in enumerate(roots) - if root == val - push!(trees[k], pos) - end + for edge in forest.revmap + root_edge = find_root!(forest, edge) + root = forest.intmap[root_edge] + if !haskey(roots, root) + k += 1 + roots[root] = k end + index_tree = roots[root] + push!(trees[index_tree], forest.intmap[edge]) end + # vector of dictionaries where each dictionary stores the degree of each vertex in a tree degrees = [Dict{Int,Int}() for k in 1:ntrees] for k in 1:ntrees tree = trees[k] @@ -336,7 +343,8 @@ function decompress_aux!( end end - n = checksquare(A) + # stored_values holds the sum of edge values for subtrees in a tree. + # For each vertex i, stored_values[i] is the sum of edge values in the subtree rooted at i. stored_values = Vector{R}(undef, n) # Recover the diagonal coefficients of A From ba9693497ca26c9808742c0a68d3be26825ca394 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Sun, 11 Aug 2024 00:20:05 -0400 Subject: [PATCH 3/5] Optimize acyclic decompression --- src/decompression.jl | 64 +++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/decompression.jl b/src/decompression.jl index cbf5f8b..a47c851 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -343,6 +343,29 @@ function decompress_aux!( end end + # depth-first search (DFS) traversal order for each tree in the forest + dfs_orders = [Vector{Tuple{Int,Int}}() for k in 1:ntrees] + for k in 1:ntrees + tree = trees[k] + degree = degrees[k] + while sum(values(degree)) != 0 + for (t, edge_index) in enumerate(tree) + if edge_index != 0 + i, j = forest.revmap[edge_index] + if (degree[i] == 1) || (degree[j] == 1) # leaf vertex + if degree[i] > degree[j] # vertex i is the parent of vertex j + i, j = j, i # ensure that i always denotes a leaf vertex + end + degree[i] -= 1 # decrease the degree of vertex i + degree[j] -= 1 # decrease the degree of vertex j + tree[t] = 0 # remove the edge (i,j) + push!(dfs_orders[k], (i,j)) + end + end + end + end + end + # stored_values holds the sum of edge values for subtrees in a tree. # For each vertex i, stored_values[i] is the sum of edge values in the subtree rooted at i. stored_values = Vector{R}(undef, n) @@ -356,42 +379,17 @@ function decompress_aux!( # Recover the off-diagonal coefficients of A for k in 1:ntrees - tree = trees[k] - degree = degrees[k] + vertices = keys(degrees[k]) + for vertex in vertices + stored_values[vertex] = zero(R) + end - nedges = length(tree) - if nedges == 1 - edge_index = tree[1] - i, j = forest.revmap[edge_index] - val = B[i, color[j]] + tree = dfs_orders[k] + for (i, j) in tree + val = B[i, color[j]] - stored_values[i] + stored_values[j] = stored_values[j] + val A[i, j] = val A[j, i] = val - else - for edge_index in tree - i, j = forest.revmap[edge_index] - stored_values[i] = zero(R) - stored_values[j] = zero(R) - end - - while sum(values(degree)) != 0 - for (t, edge_index) in enumerate(tree) - if edge_index != 0 - i, j = forest.revmap[edge_index] - if (degree[i] == 1) || (degree[j] == 1) # leaf vertex - if degree[i] > degree[j] # vertex i is the parent of vertex j - i, j = j, i # ensure that i always denotes a leaf vertex - end - degree[i] -= 1 # decrease the degree of vertex i - degree[j] -= 1 # decrease the degree of vertex j - tree[t] = 0 # remove the edge (i,j) - val = B[i, color[j]] - stored_values[i] - stored_values[j] = stored_values[j] + val - A[i, j] = val - A[j, i] = val - end - end - end - end end end return A From 796ae62af64779664b4b8a3b515d790769ab149e Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Sun, 11 Aug 2024 07:50:19 +0200 Subject: [PATCH 4/5] Format --- src/decompression.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decompression.jl b/src/decompression.jl index a47c851..6392cae 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -315,7 +315,7 @@ function decompress_aux!( trees = [Int[] for i in 1:ntrees] # dictionary that maps a tree's root to the index of the tree - roots = Dict{Int, Int}() + roots = Dict{Int,Int}() k = 0 for edge in forest.revmap @@ -359,7 +359,7 @@ function decompress_aux!( degree[i] -= 1 # decrease the degree of vertex i degree[j] -= 1 # decrease the degree of vertex j tree[t] = 0 # remove the edge (i,j) - push!(dfs_orders[k], (i,j)) + push!(dfs_orders[k], (i, j)) end end end From a1e336c3284b88d3ba457d56b9c5ad7485ffe234 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Sun, 11 Aug 2024 08:00:07 +0200 Subject: [PATCH 5/5] Second decompression test --- test/utils.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/utils.jl b/test/utils.jl index faf7d6c..717bb9d 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -22,10 +22,12 @@ function test_coloring_decompression( B = compress(A, result) !isnothing(color0) && @test color == color0 !isnothing(B0) && @test B == B0 - @test decompress(B, result) ≈ A0 @test decompress(B, default_result) ≈ A0 - @test decompress!(respectful_similar(A), B, result) ≈ A0 + @test decompress(B, result) ≈ A0 + @test decompress(B, result) ≈ A0 # check result wasn't modified @test decompress!(respectful_similar(A), B, default_result) ≈ A0 + @test decompress!(respectful_similar(A), B, result) ≈ A0 + @test decompress!(respectful_similar(A), B, result) ≈ A0 end @test all(color_vec .== Ref(color_vec[1])) end