Skip to content

Commit 1e83716

Browse files
authored
Add compressed visualization (#157)
* Add compressed visualization * Add tests * Typo * Bump version
1 parent 0246517 commit 1e83716

6 files changed

Lines changed: 231 additions & 76 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseMatrixColorings"
22
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
33
authors = ["Guillaume Dalle", "Alexis Montoison"]
4-
version = "0.4.9"
4+
version = "0.4.10"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
55
DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656"
66
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
77
SparseMatrixColorings = "0a514795-09f3-496d-8182-132a7b665d35"
8+
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"

docs/src/vis.md

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@ SparseMatrixColorings provides some internal utilities for visualization of matr
77
Using it requires loading at least [Colors.jl](https://github.com/JuliaGraphics/Colors.jl).
88
We recommend loading the full [Images.jl](https://github.com/JuliaImages/Images.jl) package for convenience, which includes Colors.jl.
99

10-
## Basic usage
11-
12-
To obtain a visualization, simply call `show_colors` on a coloring result:
13-
1410
```@example img
11+
using ColorSchemes
1512
using Images
16-
using SparseMatrixColorings, SparseArrays
13+
using SparseArrays
14+
using SparseMatrixColorings
1715
using SparseMatrixColorings: show_colors
16+
using StableRNGs
17+
```
18+
19+
## Basic usage
20+
21+
To obtain a visualization, simply call `show_colors` on a coloring result. It returns a tuple of outputs, corresponding to the matrix and its compression(s):
1822

23+
```@example img
1924
S = sparse([
2025
0 0 1 1 0 1
2126
1 0 0 0 1 0
@@ -26,7 +31,20 @@ S = sparse([
2631
problem = ColoringProblem(; structure=:nonsymmetric, partition=:column)
2732
algo = GreedyColoringAlgorithm(; decompression=:direct)
2833
result = coloring(S, problem, algo)
29-
show_colors(result)
34+
35+
A_img, B_img = show_colors(result; scale=3)
36+
```
37+
38+
The colors on the original matrix look like this:
39+
40+
```@example img
41+
A_img
42+
```
43+
44+
And its column compression looks like that:
45+
46+
```@example img
47+
B_img
3048
```
3149

3250
!!! tip "Terminal support"
@@ -40,12 +58,45 @@ The size of the matrix entries is defined by `scale`, while gaps between them ar
4058
We recommend using the [ColorSchemes.jl](https://github.com/JuliaGraphics/ColorSchemes.jl) catalogue to customize the `colorscheme`.
4159
Finally, a background color can be passed via the `background` keyword argument. To obtain transparent backgrounds, use the `RGBA` type.
4260

61+
We demonstrate this on a bidirectional coloring.
62+
4363
```@example img
44-
using ColorSchemes
4564
46-
julia_colors = ColorSchemes.julia
47-
white = RGB(1, 1, 1)
48-
show_colors(result; colorscheme=julia_colors, background=white, scale=5, pad=1)
65+
S = sparse([
66+
1 1 1 1 1 1 1
67+
1 0 0 0 0 0 1
68+
1 0 0 0 0 0 1
69+
1 0 0 0 0 0 1
70+
1 1 1 1 1 1 1
71+
])
72+
73+
problem_bi = ColoringProblem(; structure=:nonsymmetric, partition=:bidirectional)
74+
algo_bi = GreedyColoringAlgorithm(RandomOrder(StableRNG(0)); decompression=:direct)
75+
result_bi = coloring(S, problem_bi, algo_bi)
76+
77+
A_img, Br_img, Bc_img = show_colors(
78+
result_bi;
79+
colorscheme=ColorSchemes.progress,
80+
background=RGB(1, 1, 1), # white
81+
scale=10,
82+
pad=2
83+
)
84+
```
85+
86+
In the bidirectional case, columns and rows can both get colors:
87+
88+
```@example img
89+
A_img
90+
```
91+
92+
And there are two compression results, one by row and one by column:
93+
94+
```@example img
95+
Br_img
96+
```
97+
98+
```@example img
99+
Bc_img
49100
```
50101

51102
## Working with large matrices
@@ -58,15 +109,15 @@ S = sprand(50, 50, 0.1) # sample sparse matrix
58109
problem = ColoringProblem(; structure=:nonsymmetric, partition=:column)
59110
algo = GreedyColoringAlgorithm(; decompression=:direct)
60111
result = coloring(S, problem, algo)
61-
show_colors(result; scale=5, pad=1)
112+
show_colors(result; scale=5, pad=1)[1]
62113
```
63114

64115
Instead of the default `distinguishable_colors` from Colors.jl, one can subsample a continuous colorscheme from ColorSchemes.jl:
65116

66117
```@example img
67118
ncolors = maximum(column_colors(result)) # for partition=:column
68119
colorscheme = get(ColorSchemes.rainbow, range(0.0, 1.0, length=ncolors))
69-
show_colors(result; colorscheme=colorscheme, scale=5, pad=1)
120+
show_colors(result; colorscheme=colorscheme, scale=5, pad=1)[1]
70121
```
71122

72123
## Saving images
@@ -75,8 +126,8 @@ The resulting image can be saved to a variety of formats, like PNG.
75126
The `scale` and `pad` parameters determine the number of pixels, and thus the size of the file.
76127

77128
```julia
78-
img = show_colors(result, scale=5)
79-
save("coloring.png", img)
129+
A_img, _ = show_colors(result, scale=5)
130+
save("coloring.png", A_img)
80131
```
81132

82133
Refer to the JuliaImages [documentation on saving](https://juliaimages.org/stable/function_reference/#ref_io) for more information.

ext/SparseMatrixColoringsColorsExt.jl

Lines changed: 123 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ using SparseMatrixColorings:
2222
sparsity_pattern,
2323
column_colors,
2424
row_colors,
25-
ncolors
25+
ncolors,
26+
compress
2627
using Colors: Colorant, RGB, RGBA, distinguishable_colors
2728

2829
const DEFAULT_BACKGROUND = RGBA(0, 0, 0, 0)
@@ -53,8 +54,8 @@ function SparseMatrixColorings.show_colors(
5354
else
5455
colorscheme = default_colorscheme(ncolors(res), convert(RGB, background))
5556
end
56-
out = allocate_output(res, background, scale, pad)
57-
return show_colors!(out, res, colorscheme, scale, pad)
57+
outs = allocate_outputs(res, background, scale, pad)
58+
return show_colors!(outs..., res, colorscheme, scale, pad)
5859
end
5960

6061
function promote_colors(colorscheme, background)
@@ -65,15 +66,41 @@ function promote_colors(colorscheme, background)
6566
return colorscheme, background
6667
end
6768

68-
function allocate_output(
69-
res::AbstractColoringResult, background::Colorant, scale::Int, pad::Int
70-
)
69+
function allocate_outputs(
70+
res::Union{AbstractColoringResult{s,:column},AbstractColoringResult{s,:row}},
71+
background::Colorant,
72+
scale::Int,
73+
pad::Int,
74+
) where {s}
75+
A = sparsity_pattern(res)
76+
B = compress(A, res)
77+
Base.require_one_based_indexing(A)
78+
Base.require_one_based_indexing(B)
79+
hA, wA = size(A) .* (scale + pad) .+ pad
80+
hB, wB = size(B) .* (scale + pad) .+ pad
81+
A_img = fill(background, hA, wA)
82+
B_img = fill(background, hB, wB)
83+
return A_img, B_img
84+
end
85+
86+
function allocate_outputs(
87+
res::AbstractColoringResult{s,:bidirectional},
88+
background::Colorant,
89+
scale::Int,
90+
pad::Int,
91+
) where {s}
7192
A = sparsity_pattern(res)
93+
Br, Bc = compress(A, res)
7294
Base.require_one_based_indexing(A)
73-
hi, wi = size(A)
74-
h = hi * (scale + pad) + pad
75-
w = wi * (scale + pad) + pad
76-
return fill(background, h, w)
95+
Base.require_one_based_indexing(Br)
96+
Base.require_one_based_indexing(Bc)
97+
hA, wA = size(A) .* (scale + pad) .+ pad
98+
hBr, wBr = size(Br) .* (scale + pad) .+ pad
99+
hBc, wBc = size(Bc) .* (scale + pad) .+ pad
100+
A_img = fill(background, hA, wA)
101+
Br_img = fill(background, hBr, wBr)
102+
Bc_img = fill(background, hBc, wBc)
103+
return A_img, Br_img, Bc_img
77104
end
78105

79106
# Given a CartesianIndex I of an entry in the original matrix,
@@ -86,61 +113,120 @@ end
86113
## Implementations for different AbstractColoringResult types start here
87114

88115
function show_colors!(
89-
out, res::AbstractColoringResult{s,:column}, colorscheme, scale, pad
116+
A_img::AbstractMatrix{<:Colorant},
117+
B_img::AbstractMatrix{<:Colorant},
118+
res::AbstractColoringResult{s,:column},
119+
colorscheme,
120+
scale,
121+
pad,
90122
) where {s}
91-
color_indices = mod1.(column_colors(res), length(colorscheme)) # cycle color indices if necessary
92-
colors = colorscheme[color_indices]
93-
pattern = sparsity_pattern(res)
94-
for I in CartesianIndices(pattern)
95-
if !iszero(pattern[I])
123+
# cycle color indices if necessary
124+
A_color_indices = mod1.(column_colors(res), length(colorscheme))
125+
B_color_indices = mod1.(1:ncolors(res), length(colorscheme))
126+
A_colors = colorscheme[A_color_indices]
127+
B_colors = colorscheme[B_color_indices]
128+
A = sparsity_pattern(res)
129+
B = compress(A, res)
130+
for I in CartesianIndices(A)
131+
if !iszero(A[I])
132+
r, c = Tuple(I)
133+
area = matrix_entry_area(I, scale, pad)
134+
A_img[area] .= A_colors[c]
135+
end
136+
end
137+
for I in CartesianIndices(B)
138+
if !iszero(B[I])
96139
r, c = Tuple(I)
97140
area = matrix_entry_area(I, scale, pad)
98-
out[area] .= colors[c]
141+
B_img[area] .= B_colors[c]
99142
end
100143
end
101-
return out
144+
return A_img, B_img
102145
end
103146

104147
function show_colors!(
105-
out, res::AbstractColoringResult{s,:row}, colorscheme, scale, pad
148+
A_img::AbstractMatrix{<:Colorant},
149+
B_img::AbstractMatrix{<:Colorant},
150+
res::AbstractColoringResult{s,:row},
151+
colorscheme,
152+
scale,
153+
pad,
106154
) where {s}
107-
color_indices = mod1.(row_colors(res), length(colorscheme)) # cycle color indices if necessary
108-
colors = colorscheme[color_indices]
109-
pattern = sparsity_pattern(res)
110-
for I in CartesianIndices(pattern)
111-
if !iszero(pattern[I])
155+
# cycle color indices if necessary
156+
A_color_indices = mod1.(row_colors(res), length(colorscheme))
157+
B_color_indices = mod1.(1:ncolors(res), length(colorscheme))
158+
A_colors = colorscheme[A_color_indices]
159+
B_colors = colorscheme[B_color_indices]
160+
A = sparsity_pattern(res)
161+
B = compress(A, res)
162+
for I in CartesianIndices(A)
163+
if !iszero(A[I])
112164
r, c = Tuple(I)
113165
area = matrix_entry_area(I, scale, pad)
114-
out[area] .= colors[r]
166+
A_img[area] .= A_colors[r]
115167
end
116168
end
117-
return out
169+
for I in CartesianIndices(B)
170+
if !iszero(B[I])
171+
r, c = Tuple(I)
172+
area = matrix_entry_area(I, scale, pad)
173+
B_img[area] .= B_colors[r]
174+
end
175+
end
176+
return A_img, B_img
118177
end
119178

120179
function show_colors!(
121-
out, res::AbstractColoringResult{s,:bidirectional}, colorscheme, scale, pad
180+
A_img::AbstractMatrix{<:Colorant},
181+
Br_img::AbstractMatrix{<:Colorant},
182+
Bc_img::AbstractMatrix{<:Colorant},
183+
res::AbstractColoringResult{s,:bidirectional},
184+
colorscheme,
185+
scale,
186+
pad,
122187
) where {s}
123188
scale < 3 && throw(ArgumentError("`scale` has to be ≥ 3 to visualize bicoloring"))
124-
ccolor_indices = mod1.(column_colors(res), length(colorscheme)) # cycle color indices if necessary
189+
# cycle color indices if necessary
125190
row_shift = maximum(column_colors(res))
126-
rcolor_indices = mod1.(row_shift .+ row_colors(res), length(colorscheme)) # cycle color indices if necessary
127-
ccolors = colorscheme[ccolor_indices]
128-
rcolors = colorscheme[rcolor_indices]
129-
pattern = sparsity_pattern(res)
130-
for I in CartesianIndices(pattern)
131-
if !iszero(pattern[I])
191+
A_ccolor_indices = mod1.(column_colors(res), length(colorscheme))
192+
A_rcolor_indices = mod1.(row_shift .+ row_colors(res), length(colorscheme))
193+
B_ccolor_indices = mod1.(1:maximum(column_colors(res)), length(colorscheme))
194+
B_rcolor_indices =
195+
mod1.((row_shift + 1):(row_shift + maximum(row_colors(res))), length(colorscheme))
196+
A_ccolors = colorscheme[A_ccolor_indices]
197+
A_rcolors = colorscheme[A_rcolor_indices]
198+
B_ccolors = colorscheme[B_ccolor_indices]
199+
B_rcolors = colorscheme[B_rcolor_indices]
200+
A = sparsity_pattern(res)
201+
Br, Bc = compress(A, res)
202+
for I in CartesianIndices(A)
203+
if !iszero(A[I])
132204
r, c = Tuple(I)
133205
area = matrix_entry_area(I, scale, pad)
134206
for i in axes(area, 1), j in axes(area, 2)
135207
if j > i
136-
out[area[i, j]] = ccolors[c]
208+
A_img[area[i, j]] = A_ccolors[c]
137209
elseif i > j
138-
out[area[i, j]] = rcolors[r]
210+
A_img[area[i, j]] = A_rcolors[r]
139211
end
140212
end
141213
end
142214
end
143-
return out
215+
for I in CartesianIndices(Br)
216+
if !iszero(Br[I])
217+
r, c = Tuple(I)
218+
area = matrix_entry_area(I, scale, pad)
219+
Br_img[area] .= B_rcolors[r]
220+
end
221+
end
222+
for I in CartesianIndices(Bc)
223+
if !iszero(Bc[I])
224+
r, c = Tuple(I)
225+
area = matrix_entry_area(I, scale, pad)
226+
Bc_img[area] .= B_ccolors[c]
227+
end
228+
end
229+
return A_img, Br_img, Bc_img
144230
end
145231

146232
end # module

src/show_colors.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
"""
44
show_colors(result; kwargs...)
55
6-
Return an image visualizing an [`AbstractColoringResult`](@ref), with the help of the the [JuliaImages](https://juliaimages.org) ecosystem.
6+
Create a visualization for an [`AbstractColoringResult`](@ref), with the help of the the [JuliaImages](https://juliaimages.org) ecosystem.
7+
8+
- For `:column` or `:row` colorings, it returns a tuple `(A_img, B_img)`.
9+
- For `:bidirectional` colorings, it returns a tuple `(A_img, Br_img, Bc_img)`.
710
811
!!! warning
912
This function is implemented in a package extension, using it requires loading [Colors.jl](https://github.com/JuliaGraphics/Colors.jl).

0 commit comments

Comments
 (0)