diff --git a/src/Graphs.jl b/src/Graphs.jl index bbddf395d..976b62ce5 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -55,6 +55,7 @@ complement, reverse, reverse!, blockdiag, union, intersect, difference, symmetric_difference, join, tensor_product, cartesian_product, crosspath, induced_subgraph, egonet, merge_vertices!, merge_vertices, +mycielski, # bfs gdistances, gdistances!, bfs_tree, bfs_parents, has_path, diff --git a/src/operators.jl b/src/operators.jl index daa597b66..56ca704ec 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -851,3 +851,74 @@ function merge_vertices!(g::Graph{T}, vs::Vector{U} where U <: Integer) where T return new_vertex_ids end + +""" + mycielski(g) + +Returns a graph after applying the Mycielski operator to the input. The Mycielski operator +returns a new graph with `2n+1` vertices and `3e+n` edges and will increase the chromatic +number of the graph by 1. + +The Mycielski operation can be repeated by using the `iterations` kwarg. Each time, it will +apply the operator to the previous iterations graph. + +For each vertex in the original graph, that vertex and a copy of it are added to the new graph. +Then, for each edge `(x, y)` in the original graph, the edges `(x, y)`, `(x', y)`, and `(x, y')` +are added to the new graph, where `x'` and `y'` are the "copies" of `x` and `y`, respectively. +In other words, the original graph is present as a subgraph, and each vertex in the original graph +is connected to all of its neighbors' copies. Finally, one last vertex `w` is added to the graph +and an edge connecting each copy vertex `x'` to `w` is added. + +See [Mycielskian](https://en.wikipedia.org/wiki/Mycielskian) for more information. + +# Examples +```jldoctest +julia> c = CycleGraph(5) +{5, 5} undirected simple Int64 graph + +julia> m = Graphs.mycielski(c) +{11, 20} undirected simple Int64 graph + +julia> collect(edges(m)) +20-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: + Edge 1 => 2 + Edge 1 => 5 + Edge 1 => 7 + Edge 1 => 10 + Edge 2 => 3 + Edge 2 => 6 + Edge 2 => 8 + Edge 3 => 4 + Edge 3 => 7 + Edge 3 => 9 + Edge 4 => 5 + Edge 4 => 8 + Edge 4 => 10 + Edge 5 => 6 + Edge 5 => 9 + Edge 6 => 11 + Edge 7 => 11 + Edge 8 => 11 + Edge 9 => 11 + Edge 10 => 11 +``` +""" +@traitfn function mycielski(g::AbstractGraph::(!IsDirected); iterations = 1) + out = deepcopy(g) + for _ in 1:iterations + N = nv(out) + add_vertices!(out, N + 1) + w = nv(out) + for e in collect(edges(out)) + x=e.src + y=e.dst + add_edge!(out, x, y+N) + add_edge!(out, x+N, y) + end + + for v in 1:N + add_edge!(out, v+N, w) + end + end + return out +end diff --git a/test/operators.jl b/test/operators.jl index 891816753..6969c7461 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -320,4 +320,28 @@ @testset "Length: $g" for g in testgraphs(SimpleGraph(100)) @test length(g) == 10000 end + + + @testset "Mycielski Operator" begin + g = complete_graph(2) + + m = mycielski(g; iterations = 8) + @test nv(m) == 767 + @test ne(m) == 22196 + + # ensure it is not done in-place + @test nv(g) == 2 + @test ne(g) == 1 + + # check that mycielski preserves triangle-freeness + g = complete_bipartite_graph(10, 5) + m = mycielski(g) + @test nv(m) == 2*15 + 1 + @test ne(m) == 3*50 + 15 + @test all(iszero, triangles(m)) + + # ensure it is not done in-place + @test nv(g) == 15 + @test ne(g) == 50 + end end