From d6b8cca8e39cf3dee671b8a9eb20c2fc1a7512df Mon Sep 17 00:00:00 2001 From: Danijel Jukic Date: Fri, 26 Sep 2025 13:58:00 +0200 Subject: [PATCH] define storagetraits --- docs/make.jl | 1 + docs/src/dsatur.md | 11 ++-- docs/src/greedy.md | 11 ++-- docs/src/storage.md | 93 +++++++++++++++++++++++++++++ docs/src/workstream.md | 24 +++----- ext/GraphsColoringGraphs.jl | 38 ++++++++++++ src/GraphsColoring.jl | 8 ++- src/color.jl | 8 +-- src/storage.jl | 115 ++++++++++++++++++++++++++++++++++++ src/workstream.jl | 15 +++-- test/test_dsatur.jl | 51 ++++++++++------ test/test_greedy.jl | 47 ++++++++++----- test/test_workstream.jl | 49 ++++++++++----- 13 files changed, 384 insertions(+), 87 deletions(-) create mode 100644 docs/src/storage.md create mode 100644 src/storage.jl diff --git a/docs/make.jl b/docs/make.jl index 2dca8e2..2dd495e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -67,6 +67,7 @@ makedocs(; "DSatur" => "dsatur.md", "Workstream" => "workstream.md", ], + "Storing colors" => "storage.md", "Contributing" => "contributing.md", "API Reference" => "apiref.md", ], diff --git a/docs/src/dsatur.md b/docs/src/dsatur.md index 7ca1214..89a2399 100644 --- a/docs/src/dsatur.md +++ b/docs/src/dsatur.md @@ -30,15 +30,12 @@ conflicts = GraphsColoring.conflictmatrix(X) colors = GraphsColoring.color(conflicts; algorithm=DSATUR()) -for (i, color) in enumerate(colors) - println("Color $i has $(length(color)) elements") -end - facecolors = zeros(size(conflicts, 1)) -for (i, color) in enumerate(colors) - for element in color - facecolors[element] = i +for color in eachindex(colors) + println("Color $color has $(length(colors[color])) elements") + for element in colors[color] + facecolors[element] = color end end diff --git a/docs/src/greedy.md b/docs/src/greedy.md index d35eda4..d15854d 100644 --- a/docs/src/greedy.md +++ b/docs/src/greedy.md @@ -21,15 +21,12 @@ conflicts = GraphsColoring.conflictmatrix(X) colors = GraphsColoring.color(conflicts; algorithm=Greedy()) -for (i, color) in enumerate(colors) - println("Color $i has $(length(color)) elements") -end - facecolors = zeros(size(conflicts, 1)) -for (i, color) in enumerate(colors) - for element in color - facecolors[element] = i +for color in eachindex(colors) + println("Color $color has $(length(colors[color])) elements") + for element in colors[color] + facecolors[element] = color end end diff --git a/docs/src/storage.md b/docs/src/storage.md new file mode 100644 index 0000000..9f1fcf2 --- /dev/null +++ b/docs/src/storage.md @@ -0,0 +1,93 @@ +# Storing and Accessing Coloring Results + +Coloring results in `GraphsColoring` can be stored in two primary formats: + +## `GroupedColoring` (Default) + +- **Storage**: Colors are stored as a vector of vectors, where each inner vector contains the indices of elements assigned to a specific color. +- **Optimization**: Efficient retrieval of all elements belonging to a specific color group. +- **Best for**: Operations that frequently access elements by color (e.g., processing color groups in parallel, analyzing group sizes, or visualizing color distributions). +- **Default behavior**: This is the default storage format when no `storage` parameter is specified. + +## `Graphs.Coloring` + +- **Storage**: Colors are stored as a flat vector where `colors[i]` gives the color assigned to element `i`. +- **Optimization**: Efficient access to the color of a specific element. +- **Best for**: Operations that frequently query the color of individual elements. + +--- + +## Usage Examples + +### Using `GroupedColoring` (Default) + +```@example +using PlotlyJS#hide +using CompScienceMeshes#hide +using BEAST#hide +using GraphsColoring#hide + +m = meshsphere(1.0, 0.1)#hide +X = raviartthomas(m)#hide + +conflicts = GraphsColoring.conflictmatrix(X)#hide + +colors = GraphsColoring.color(conflicts; storage=GraphsColoring.GroupedColors()) +``` + +### Using `Graphs.Coloring` + +```@example +using PlotlyJS#hide +using CompScienceMeshes#hide +using BEAST#hide +using GraphsColoring#hide + +m = meshsphere(1.0, 0.1)#hide +X = raviartthomas(m)#hide + +conflicts = GraphsColoring.conflictmatrix(X)#hide + +colors = GraphsColoring.color(conflicts; storage=GraphsColoring.GraphsColors()) +``` + +--- + +## Accessing Coloring Results + +Both storage formats support standard Julia interfaces: + +| Operation | Description | +|---------|-------------| +| `numcolors(c)` | Returns the total number of distinct colors | +| `eachindex(c)` | Iterates over color group indices (1-based) | +| `c[i]` | Retrieves the i-th color group (as a vector of element indices) | +| `length(c[i])` | Gets the number of elements in the i-th color group | + +### Example: Accessing Colors + +```@example +using PlotlyJS#hide +using CompScienceMeshes#hide +using BEAST#hide +using GraphsColoring#hide + +m = meshsphere(1.0, 0.1)#hide +X = raviartthomas(m)#hide + +conflicts = GraphsColoring.conflictmatrix(X)#hide + +colors = GraphsColoring.color(conflicts; algorithm=WorkstreamGreedy, storage=GraphsColoring.GraphsColors()) # hide + +println("We have $(numcolors(colors))") +for color in eachindex(colors) + println("Color $color has $(length(colors[color])) elements. The members of color $color are $(colors[color])") +end +``` + +--- + +## Notes + +- The default storage is `GroupedColoring` because it enables efficient group-based operations. +- The `Graphs.Coloring` format is more efficient for very large problems when only individual element colors are needed. diff --git a/docs/src/workstream.md b/docs/src/workstream.md index 4a2ba05..5fa7a23 100644 --- a/docs/src/workstream.md +++ b/docs/src/workstream.md @@ -44,15 +44,12 @@ conflicts = GraphsColoring.conflictmatrix(X) colors = GraphsColoring.color(conflicts; algorithm=WorkstreamDSATUR) -for (i, color) in enumerate(colors) - println("Color $i has $(length(color)) elements") -end - facecolors = zeros(size(conflicts, 1)) -for (i, color) in enumerate(colors) - for element in color - facecolors[element] = i +for color in eachindex(colors) + println("Color $color has $(length(colors[color])) elements") + for element in colors[color] + facecolors[element] = color end end @@ -92,17 +89,14 @@ conflicts = GraphsColoring.conflictmatrix(X)#hide colors = GraphsColoring.color(conflicts; algorithm=WorkstreamGreedy) -for (i, color) in enumerate(colors)#hide - println("Color $i has $(length(color)) elements")#hide -end#hide - facecolors = zeros(size(conflicts, 1))#hide -for (i, color) in enumerate(colors)#hide - for element in color#hide - facecolors[element] = i#hide +for color in eachindex(colors)#hide + println("Color $color has $(length(colors[color])) elements")#hide + for element in colors[color]#hide + facecolors[element] = color#hide end#hide -end #hide +end#hide p = PlotlyJS.plot(#hide patch(m, facecolors; showscale=false),#hide diff --git a/ext/GraphsColoringGraphs.jl b/ext/GraphsColoringGraphs.jl index dc70a5d..a428f2e 100644 --- a/ext/GraphsColoringGraphs.jl +++ b/ext/GraphsColoringGraphs.jl @@ -3,6 +3,7 @@ using GraphsColoring using Graphs import GraphsColoring: conflictgraph, conflictmatrix, _neighbors, _numelements, noconflicts +import GraphsColoring: numcolors, colors # creates conflict graph as SimpleGraph from Graphs.jl (https://github.com/JuliaGraphs/Graphs.jl) # from conflictmatrix @@ -93,4 +94,41 @@ function noconflicts(g::AbstractGraph) return iszero(ne(g)) end +function numcolors(c::Graphs.Coloring) + return c.num_colors +end + +function colors(c::Graphs.Coloring) + return c.colors +end + +function (::GraphsColoring.GraphsColors)(maxcolor, colors, elements) + return Graphs.Coloring(maxcolor, colors) +end +function (::GraphsColoring.GraphsColors)(colors) + numcolors, colors = _groupedcolorstographcoloring(colors) + return Graphs.Coloring(numcolors, colors) +end + +function _groupedcolorstographcoloring(colors) + newcolors = zeros(eltype(eltype(colors)), sum(length, colors)) + for (i, color) in enumerate(colors) + newcolors[color] .= i + end + return length(colors), newcolors +end + +function Base.convert(::Type{<:Graphs.Coloring}, colors::GraphsColoring.GroupedColoring) + numcolors, colors = _groupedcolorstographcoloring(GraphsColoring.colors(colors)) + return Graphs.Coloring(numcolors, colors) +end + +function Base.eachindex(c::Graphs.Coloring) + return Base.OneTo(numcolors(c)) +end + +function Base.getindex(c::Graphs.Coloring, color) + return findall(isequal(color), colors(c)) +end + end # module GraphsColoringGraphs diff --git a/src/GraphsColoring.jl b/src/GraphsColoring.jl index 25880f2..d9409cf 100644 --- a/src/GraphsColoring.jl +++ b/src/GraphsColoring.jl @@ -13,6 +13,7 @@ function conflicts end """ function conflictgraph end +include("storage.jl") include("conflicts.jl") include("greedy.jl") include("dsatur.jl") @@ -34,11 +35,12 @@ not have any conflicts. - A `Vector{Vector{Int}}` where each inner `Vector` represents the elements of one color. """ -function color(conflicts; algorithm=Workstream(DSATUR())) - return color(conflicts, algorithm) +function color(conflicts; algorithm=Workstream(DSATUR()), storage=GroupedColors()) + return color(conflicts, algorithm, storage) end -export color, WorkstreamDSATUR, WorkstreamGreedy, Workstream, DSATUR, Greedy +export numcolors, + colors, color, WorkstreamDSATUR, WorkstreamGreedy, Workstream, DSATUR, Greedy export PassThroughConflictFunctor if !isdefined(Base, :get_extension) diff --git a/src/color.jl b/src/color.jl index 1c1a167..bcf51a2 100644 --- a/src/color.jl +++ b/src/color.jl @@ -17,7 +17,9 @@ The colors are sorted such that the number of their members decrease. This function implements a generic coloring workflow that can be used with different algorithms, such as DSATUR and Greedy. """ -function color(conflicts, algorithm, elements=1:_numelements(conflicts)) +function color( + conflicts, algorithm, storage=GroupedColorConst, elements=1:_numelements(conflicts) +) elementtoelementid = Dict{Int,Int}(zip(elements, eachindex(elements))) maxcolor = 1 @@ -75,7 +77,5 @@ function color(conflicts, algorithm, elements=1:_numelements(conflicts)) end end - colors = Vector{Int}[elements[findall(isequal(color), colors)] for color in 1:maxcolor] - sort!(colors; by=length, rev=true) - return colors + return storage(maxcolor, colors, elements) end diff --git a/src/storage.jl b/src/storage.jl new file mode 100644 index 0000000..56d8ef7 --- /dev/null +++ b/src/storage.jl @@ -0,0 +1,115 @@ +""" + GroupedColors + +A trait that indicates that a coloring operation should return results as a `GroupedColoring`. +""" +struct GroupedColors end +const GroupedColorConst = GroupedColors() +function (::GroupedColors)(maxcolor, colors, elements) + colors = Vector{Int}[elements[findall(isequal(color), colors)] for color in 1:maxcolor] + return GroupedColoring(colors) +end +function (::GroupedColors)(colors) + return GroupedColoring(colors) +end + +""" + GroupedColors + +A trait that indicates that a coloring operation should return results as a `Graphs.Coloring`. +""" +struct GraphsColors end +const GraphColorsConst = GraphsColors() + +""" + GroupedColoring{I} + +A data structure that represents a coloring of elements as a vector of vectors, where each +inner vector contains the indices of elements assigned to a particular color. + +This representation is optimized for efficiently retrieving all elements belonging to a +specific color group, making it ideal for operations that frequently access elements by color. + +# Type Parameters + + - `I`: The integer type used to represent element indices (e.g., `Int`, `Int64`, `UInt32`). + +# Fields + + - `colors`: A vector of vectors, where `colors[i]` contains the indices of all elements assigned + to color `i`. The vector is sorted by group size in descending order (largest groups first) + for consistency. + +# Notes + + The constructor automatically sorts the color groups by size in descending order using `sort!` + with `by=length, rev=true`. +""" +struct GroupedColoring{I} + colors::Vector{Vector{I}} + function GroupedColoring(colors) + return new{eltype(eltype(colors))}(sort!(colors; by=length, rev=true)) + end +end + +""" + numcolors(c) + +Return the number of distinct colors used in the coloring. + +# Arguments + + - `c`: A coloring object representing a coloring of a graph or similar structure, + where elements are grouped by color. + +# Returns + + - An integer representing the number of distinct colors used in the coloring. +""" +function numcolors(c::GroupedColoring) + return length(colors(c)) +end + +function colors(c::GroupedColoring) + return c.colors +end + +""" + Base.eachindex(c::Union{GroupedColoring,Graphs.Coloring}) + +Return an iterator over the indices of the color groups in `c`. + +# Arguments + + - `c`: An object representing a coloring. + +# Returns + + - An iterator over the indices of the color groups (typically `1:numcolors(c))`). + +# Notes + +This method implements the `eachindex` interface for `GroupedColoring` and `Graphs.Coloring`, making it compatible +with Julia's standard iteration patterns. +""" +function Base.eachindex(c::GroupedColoring) + return Base.eachindex(colors(c)) +end + +""" + Base.getindex(c::Union{GroupedColoring,Graphs.Coloring}, color) + +Return the i-th color group from the coloring object `c`. + +# Arguments + + - `c`: An object representing a coloring. + - `color`: An integer index specifying which color group to retrieve (must be within valid bounds). + +# Returns + + - The i-th color group (typically a vector of elements assigned to that color). +""" +function Base.getindex(c::GroupedColoring, i) + return Base.getindex(colors(c), i) +end diff --git a/src/workstream.jl b/src/workstream.jl index be41158..b8eda55 100644 --- a/src/workstream.jl +++ b/src/workstream.jl @@ -122,7 +122,9 @@ algorithm to color the elements within that zone. function colorzones(conflicts, zones, algorithm) zonecolors = Vector{Vector{Vector{Int}}}(undef, length(zones)) for i in eachindex(zones) - zonecolors[i] = color(conflicts, algorithm, collect(zones[i])) + zonecolors[i] = colors( + color(conflicts, algorithm, GroupedColorConst, collect(zones[i])) + ) end return zonecolors end @@ -220,16 +222,21 @@ See [1] for more information on the workstream design pattern. [1] B. Turcksin, M. Kronbichler, and W. Bangerth, **WorkStream – A Design Pattern for Multicore-Enabled Finite Element Computations**, 2017 """ -function color(conflicts, algorithm::Workstream, elements=1:_numelements(conflicts)) +function color( + conflicts, + algorithm::Workstream, + storage=GroupedColorConst, + elements=1:_numelements(conflicts), +) !(elements == 1:_numelements(conflicts)) && return error("Workstream coloring only supports coloring all elements.") - noconflicts(conflicts) && return [collect(elements)] + noconflicts(conflicts) && return storage([collect(elements)]) oddzones, evenzones = partition(conflicts) oddcoloredzones = colorzones(conflicts, oddzones, algorithm.algorithm) evencoloredzones = colorzones(conflicts, evenzones, algorithm.algorithm) - return gather(oddcoloredzones, evencoloredzones) + return storage(gather(oddcoloredzones, evencoloredzones)) end diff --git a/test/test_dsatur.jl b/test/test_dsatur.jl index cb397f1..a214943 100644 --- a/test/test_dsatur.jl +++ b/test/test_dsatur.jl @@ -10,7 +10,7 @@ conflictexamples = load( @testset "DSATUR Coloring" begin algorithm = GraphsColoring.DSATUR() - ms = ["rectangle", "cuboid", "sphere"] + ms = ["rectangle", "cuboid", "twospheres"] Xs = ["rt", "lagrangecxd0", "lagrangec0d1", "bc"] for m in ms @@ -20,27 +20,44 @@ conflictexamples = load( c = GraphsColoring.PassThroughConflictFunctor(elements, conflicts, conflictids) for s in [GraphsColoring.conflictmatrix(c), GraphsColoring.conflictgraph(c)] - @test GraphsColoring.color(s; algorithm=GraphsColoring.DSATUR()) == - GraphsColoring.color(s, GraphsColoring.DSATUR()) - println("coloring with $(typeof(algorithm))") + for algorithm in [WorkstreamDSATUR, WorkstreamGreedy] + c1 = GraphsColoring.color(s, algorithm, GraphsColoring.GraphsColors()) + c2 = GraphsColoring.color(s, algorithm, GraphsColoring.GroupedColors()) - colors = GraphsColoring.color(s, algorithm) + c_converted = convert(typeof(c1), c2) + @test GraphsColoring.numcolors(c1) == + GraphsColoring.numcolors(c_converted) + @test GraphsColoring.colors(c1) == GraphsColoring.colors(c_converted) + end - println("We have $(length(colors)) colors") + for storage in + [GraphsColoring.GroupedColors(), GraphsColoring.GraphsColors()] + @test GraphsColoring.colors( + GraphsColoring.color(s; algorithm=algorithm, storage=storage) + ) == GraphsColoring.colors(GraphsColoring.color(s, algorithm, storage)) - for color in eachindex(colors) - println("in color $color we have $(length(colors[color])) elements") - color == length(colors) && continue - @test length(colors[color]) >= length(colors[color + 1]) - end + println("coloring with $(typeof(algorithm))") + + colors = GraphsColoring.color(s, algorithm, storage) + + println("We have $(numcolors(colors)) colors") + + for color in eachindex(colors) + println("in color $color we have $(length(colors[color])) elements") + !(colors isa GraphsColoring.GroupedColoring) && continue + color == numcolors(colors) && continue + @test length(colors[color]) >= length(colors[color + 1]) + end - for color in eachindex(colors) - elements = colors[color] + for color in eachindex(colors) + elements = colors[color] - for testelement in elements - for trialelement in elements - testelement == trialelement && continue - @test testelement ∉ GraphsColoring._neighbors(s, trialelement) + for testelement in elements + for trialelement in elements + testelement == trialelement && continue + @test testelement ∉ + GraphsColoring._neighbors(s, trialelement) + end end end end diff --git a/test/test_greedy.jl b/test/test_greedy.jl index 9191b2d..4509f03 100644 --- a/test/test_greedy.jl +++ b/test/test_greedy.jl @@ -19,28 +19,43 @@ conflictexamples = load( c = GraphsColoring.PassThroughConflictFunctor(elements, conflicts, conflictids) for s in [GraphsColoring.conflictmatrix(c), GraphsColoring.conflictgraph(c)] - @test GraphsColoring.color(s; algorithm=GraphsColoring.Greedy()) == - GraphsColoring.color(s, GraphsColoring.Greedy()) + for algorithm in [WorkstreamDSATUR, WorkstreamGreedy] + c1 = GraphsColoring.color(s, algorithm, GraphsColoring.GraphsColors()) + c2 = GraphsColoring.color(s, algorithm, GraphsColoring.GroupedColors()) - println("coloring with $(typeof(algorithm))") + c_converted = convert(typeof(c1), c2) + @test GraphsColoring.numcolors(c1) == + GraphsColoring.numcolors(c_converted) + @test GraphsColoring.colors(c1) == GraphsColoring.colors(c_converted) + end + for storage in + [GraphsColoring.GroupedColors(), GraphsColoring.GraphsColors()] + @test GraphsColoring.colors( + GraphsColoring.color(s; algorithm=algorithm, storage=storage) + ) == GraphsColoring.colors(GraphsColoring.color(s, algorithm, storage)) - colors = GraphsColoring.color(s, algorithm) + println("coloring with $(typeof(algorithm))") - println("We have $(length(colors)) colors") + colors = GraphsColoring.color(s, algorithm, storage) - for color in eachindex(colors) - println("in color $color we have $(length(colors[color])) elements") - color == length(colors) && continue - @test length(colors[color]) >= length(colors[color + 1]) - end + println("We have $(numcolors(colors)) colors") + + for color in eachindex(colors) + println("in color $color we have $(length(colors[color])) elements") + !(colors isa GraphsColoring.GroupedColoring) && continue + color == numcolors(colors) && continue + @test length(colors[color]) >= length(colors[color + 1]) + end - for color in eachindex(colors) - elements = colors[color] + for color in eachindex(colors) + elements = colors[color] - for testelement in elements - for trialelement in elements - testelement == trialelement && continue - @test testelement ∉ GraphsColoring._neighbors(s, trialelement) + for testelement in elements + for trialelement in elements + testelement == trialelement && continue + @test testelement ∉ + GraphsColoring._neighbors(s, trialelement) + end end end end diff --git a/test/test_workstream.jl b/test/test_workstream.jl index 6a95b60..bbd45c2 100644 --- a/test/test_workstream.jl +++ b/test/test_workstream.jl @@ -17,21 +17,42 @@ conflictexamples = load( c = GraphsColoring.PassThroughConflictFunctor(elements, conflicts, conflictids) for s in [GraphsColoring.conflictmatrix(c), GraphsColoring.conflictgraph(c)] - @test GraphsColoring.color(s) == GraphsColoring.color(s, WorkstreamDSATUR) - for algorithm in [WorkstreamDSATUR, WorkstreamGreedy] - println("coloring with $(typeof(algorithm))") - - colors = GraphsColoring.color(s, algorithm) - - @test sort(vcat(colors...)) == 1:GraphsColoring._numelements(s) - for color in eachindex(colors) - elements = colors[color] - for testelement in elements - for trialelement in elements - testelement == trialelement && continue - @test testelement ∉ - GraphsColoring._neighbors(s, trialelement) + c1 = GraphsColoring.color(s, algorithm, GraphsColoring.GraphsColors()) + c2 = GraphsColoring.color(s, algorithm, GraphsColoring.GroupedColors()) + + c_converted = convert(typeof(c1), c2) + @test GraphsColoring.numcolors(c1) == + GraphsColoring.numcolors(c_converted) + @test GraphsColoring.colors(c1) == GraphsColoring.colors(c_converted) + end + + for storage in + [GraphsColoring.GroupedColors(), GraphsColoring.GraphsColors()] + @test GraphsColoring.color(s; storage=storage).colors == + GraphsColoring.color(s, WorkstreamDSATUR, storage).colors + + for algorithm in [WorkstreamDSATUR, WorkstreamGreedy] + println("coloring with $(typeof(algorithm))") + + colors = GraphsColoring.color(s, algorithm, storage) + + if colors isa GraphsColoring.GroupedColoring + @test sort(vcat(colors.colors...)) == + 1:GraphsColoring._numelements(s) + + elseif colors isa Graphs.Coloring + @test length(colors.colors) == GraphsColoring._numelements(s) + end + + for color in eachindex(colors) + elements = colors[color] + for testelement in elements + for trialelement in elements + testelement == trialelement && continue + @test testelement ∉ + GraphsColoring._neighbors(s, trialelement) + end end end end