From 101c55d0112278b3b42bdabf7d7a154964baab43 Mon Sep 17 00:00:00 2001 From: Ewan Gilligan Date: Wed, 2 Jun 2021 15:21:14 +0100 Subject: [PATCH 1/2] Add Zykov and Christofides Algorithms for Chromatic Number Style changes from review Duplicate tests already included Add outline for Byskov algorithm Add function to find three colourable subgraphs Moved three colourable processing to before Byskov Added declaration for Byskovs Handled Special case for three vertices and no maximal independent sets Wasn't actually getting the complement of the independent set Create category isDigraphAlgorithm and sub-category isDigraphColouringAlgorithm Will be used to distinguish different colouring algorithms that are available. Add Bound Global objects for Digraph Colouring Algorithms Update original functions to use new method selection Update test for new method selection Forgot to subtract set before getting the induced subgraph Remove from extreme test as it is far too large for these tests Move tests that are too slow Special case was not needed Add loop checks to Lawler and Byskov Algorithms Remove Extreme Tests Add standard tests that will run in a reasonable amount of time Not all chromatic number tests were duplicated, as some would use too much memory or take too long Fix formatting More formatting issues Should be the last one Added Documentation Misc Comments Make label match docs Optimise away the subdigraph use Remove another copy Fix lawler issues Optimise Byskov clarify todo Revert to using induced subdigraph Fix some linting Missed a few Fix Indent Add method selection objects for Zykov Initial algorithm skeleton Fill in the rest Forgot about edge direction Documentation Start adaptation for pruned trees Simplify logic Add tests for Zykov Add filters and method objects for Christofides Initialise variable for Christofides Update comments Initial version of Christofides implemented. Fixed a few syntax errors Fix calculation of MIS Remove debug print Cleanup christofides fix typo Convert to using Blists Need to benchmark to check improvement Fix formatting Missed some trailing whitespace Restart attempt at in place zykov Revert to just copying In place is more hassle than it's worth Remove redundant extra check Use new function for Christofides Fix merge issues Add vertex ordering to Zykov Add Christofides test Optimise clique finder used Fix lint remove duplicate bib Update for new method selection Fix test output Update Docs Remove unused reference Fix spelling mistake --- doc/attr.xml | 4 + doc/digraphs.bib | 32 ++++++++ gap/attr.gi | 179 ++++++++++++++++++++++++++++++++++++++++++ tst/standard/attr.tst | 112 ++++++++++++++++++++++++++ 4 files changed, 327 insertions(+) diff --git a/doc/attr.xml b/doc/attr.xml index d525a100d..00483733c 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -1427,6 +1427,10 @@ gap> DigraphLoops(D); byskov - Byskov's Algorithm + zykov - Zykov's Algorithm + + christofides - Christofides's Algorithm + x > y); + # Choose two non-adjacent vertices x, y + # This is just done by ascending ordering. + found := false; + for x_i in [1 .. nr] do + x := vertices[x_i]; + for y_i in [x_i + 1 .. nr] do + y := vertices[y_i]; + if not adjacent(x, y) then + found := true; + break; + fi; + od; + if found then + break; + fi; + od; + Assert(1, x <> y, "x and y must be different"); + Assert(1, found, "No adjacent vertices"); + # Colour the vertex contraction. + # A contraction of a graph effectively merges two non adjacent vertices + # into a single new vertex with the edges merged. + # We merge y into x, keeping x. + D_contract := DigraphMutableCopy(D); + for v in vertices do + # Iterate over all vertices that + if v = x or v = y then + continue; + fi; + # Add any edge that involves y, but not already x to avoid duplication. + if adjacent(v, y) and not adjacent(v, x) then + DigraphAddEdge(D_contract, x, v); + DigraphAddEdge(D_contract, v, x); + fi; + od; + DigraphRemoveVertex(D_contract, y); + ZykovReduce(D_contract); + # Colour the edge addition + # This just adds symmetric edges between x and y; + DigraphAddEdge(D, [x, y]); + DigraphAddEdge(D, [y, x]); + ZykovReduce(D); + # Undo changes to the graph + DigraphRemoveEdge(D, [x, y]); + DigraphRemoveEdge(D, [y, x]); + fi; + end; + # Algorithm requires an undirected graph. + D := DigraphSymmetricClosure(DigraphMutableCopy(D)); + # Use greedy colouring as an upper bound + chrom := RankOfTransformation(DigraphGreedyColouring(D), nr); + ZykovReduce(D); + return chrom; +end +); + +BindGlobal("DIGRAPHS_ChromaticNumberChristofides", +function(D) + local nr, I, n, T, b, unprocessed, i, v_without_t, j, u, min_occurences, + cur_occurences, chrom, colouring, stack, vertices; + + nr := DigraphNrVertices(D); + vertices := List(DigraphVertices(D)); + # Initialise the required variables. + # Calculate all maximal independent sets of D. + I := DigraphMaximalIndependentSets(D); + # Convert each MIS into a BList + I := List(I, i -> BlistList(vertices, i)); + # Upper bound for chromatic number. + chrom := nr; + # Set of vertices of D not in the current subgraph at level n. + T := ListWithIdenticalEntries(nr, false); + # Current search level of the subgraph tree. + n := 0; + # The maximal independent sets of V \ T at level n. + b := [ListWithIdenticalEntries(nr, false)]; + # Number of unprocessed MIS's of V \ T from level 1 to n + unprocessed := ListWithIdenticalEntries(nr, 0); + # Would be jth colour class of the chromatic colouring of G. + colouring := List([1 .. nr], i -> BlistList(vertices, [i])); + # Stores current unprocessed MIS's of V \ T at level 1 to level n + stack := []; + # Now perform the search. + repeat + # Step 2 + if n < chrom then + # Step 3 + # If V = T then we've reached a null subgraph + if SizeBlist(T) = nr then + chrom := n; + SubtractBlist(T, b[n + 1]); + for i in [1 .. chrom] do + colouring[i] := b[i]; + # TODO set colouring attribute + od; + else + # Step 4 + # Compute the maximal independent sets of V \ T + v_without_t := DIGRAPHS_MaximalIndependentSetsSubtractedSet(I, T, + infinity); + # Step 5 + # Pick u in V \ T such that u is in the fewest maximal independent sets. + u := -1; + min_occurences := infinity; + for i in vertices do + # Skip elements of T. + if T[i] then + continue; + fi; + cur_occurences := 0; + for j in v_without_t do + if j[i] then + cur_occurences := cur_occurences + 1; + fi; + od; + if cur_occurences < min_occurences then + min_occurences := cur_occurences; + u := i; + fi; + od; + Assert(1, u <> -1, "Vertex must be picked"); + # Remove maximal independent sets not containing u. + v_without_t := Filtered(v_without_t, x -> x[u]); + # Add these MISs to the stack + Append(stack, v_without_t); + # Search has moved one level deeper + n := n + 1; + unprocessed[n] := Length(v_without_t); + fi; + else + # if n >= g then T = T \ b[n] + # This exceeds the current best bound, so stop search. + SubtractBlist(T, b[n + 1]); + fi; + # Step 6 + while n <> 0 do + # step 7 + if unprocessed[n] = 0 then + n := n - 1; + SubtractBlist(T, b[n + 1]); + else + # Step 8 + # take an element from the top of the stack + i := Remove(stack); + unprocessed[n] := unprocessed[n] - 1; + b[n + 1] := i; + UniteBlist(T, i); + break; + fi; + od; + until n = 0; + return chrom; +end +); + InstallMethod(ChromaticNumber, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) @@ -373,6 +548,10 @@ function(D) return DIGRAPHS_ChromaticNumberLawler(D); elif ValueOption("byskov") <> fail then return DIGRAPHS_ChromaticNumberByskov(D); + elif ValueOption("zykov") <> fail then + return DIGRAPHS_ChromaticNumberZykov(D); + elif ValueOption("christofides") <> fail then + return DIGRAPHS_ChromaticNumberChristofides(D); fi; # The chromatic number of is at least 3 and at most nr diff --git a/tst/standard/attr.tst b/tst/standard/attr.tst index a9f73b3a6..343ace09a 100644 --- a/tst/standard/attr.tst +++ b/tst/standard/attr.tst @@ -1654,6 +1654,118 @@ Error, the argument must be a digraph with no loops, gap> DIGRAPHS_UnderThreeColourable(EmptyDigraph(0)); 0 +# Test ChromaticNumber Zykov +gap> ChromaticNumber(NullDigraph(10) : zykov); +1 +gap> ChromaticNumber(CompleteDigraph(10) : zykov); +10 +gap> ChromaticNumber(CompleteBipartiteDigraph(5, 5) : zykov); +2 +gap> ChromaticNumber(DigraphRemoveEdge(CompleteDigraph(10), [1, 2]) : zykov); +10 +gap> ChromaticNumber(Digraph([[4, 8], [6, 10], [9], [2, 3, 9], [], +> [3], [4], [6], [], [5, 7]]) : zykov); +3 +gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1), +> Digraph([[2], [4], [1, 2], [3]])) : zykov); +3 +gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1), +> Digraph([[2], [4], [1, 2], [3], [1, 2, 3]])) : zykov); +4 +gap> gr := Digraph([[2, 3, 4], [3], [], []]); + +gap> ChromaticNumber(gr : zykov); +3 +gap> ChromaticNumber(EmptyDigraph(0) : zykov); +0 +gap> gr := CompleteDigraph(4);; +gap> gr := DigraphAddVertex(gr);; +gap> ChromaticNumber(gr : zykov); +4 +gap> gr := Digraph([[2, 4, 7, 3], [3, 5, 8, 1], [1, 6, 9, 2], +> [5, 7, 1, 6], [6, 8, 2, 4], [4, 9, 3, 5], [8, 1, 4, 9], [9, 2, 5, 7], +> [7, 3, 6, 8]]);; +gap> ChromaticNumber(gr : zykov); +3 +gap> gr := DigraphSymmetricClosure(ChainDigraph(5)); + +gap> ChromaticNumber(gr : zykov); +2 +gap> gr := DigraphFromGraph6String("KmKk~K??G@_@"); + +gap> ChromaticNumber(gr : zykov); +4 +gap> gr := CycleDigraph(7); + +gap> ChromaticNumber(gr : zykov); +3 +gap> ChromaticNumber(gr : zykov); +3 +gap> ChromaticNumber(gr : zykov); +3 +gap> a := DigraphRemoveEdges(CompleteDigraph(50), [[1, 2], [2, 1]]);; +gap> b := DigraphAddVertex(a);; +gap> ChromaticNumber(a : zykov); +49 +gap> ChromaticNumber(b : zykov); +49 + +# Test ChromaticNumber Christofides +gap> ChromaticNumber(NullDigraph(10) : christofides); +1 +gap> ChromaticNumber(CompleteDigraph(10) : christofides); +10 +gap> ChromaticNumber(CompleteBipartiteDigraph(5, 5) : christofides); +2 +gap> ChromaticNumber(DigraphRemoveEdge(CompleteDigraph(10), [1, 2]) : christofides); +10 +gap> ChromaticNumber(Digraph([[4, 8], [6, 10], [9], [2, 3, 9], [], +> [3], [4], [6], [], [5, 7]]) : christofides); +3 +gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1), +> Digraph([[2], [4], [1, 2], [3]])) : christofides); +3 +gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1), +> Digraph([[2], [4], [1, 2], [3], [1, 2, 3]])) : christofides); +4 +gap> gr := Digraph([[2, 3, 4], [3], [], []]); + +gap> ChromaticNumber(gr : christofides); +3 +gap> ChromaticNumber(EmptyDigraph(0) : christofides); +0 +gap> gr := CompleteDigraph(4);; +gap> gr := DigraphAddVertex(gr);; +gap> ChromaticNumber(gr : christofides); +4 +gap> gr := Digraph([[2, 4, 7, 3], [3, 5, 8, 1], [1, 6, 9, 2], +> [5, 7, 1, 6], [6, 8, 2, 4], [4, 9, 3, 5], [8, 1, 4, 9], [9, 2, 5, 7], +> [7, 3, 6, 8]]);; +gap> ChromaticNumber(gr : christofides); +3 +gap> gr := DigraphSymmetricClosure(ChainDigraph(5)); + +gap> ChromaticNumber(gr : christofides); +2 +gap> gr := DigraphFromGraph6String("KmKk~K??G@_@"); + +gap> ChromaticNumber(gr : christofides); +4 +gap> gr := CycleDigraph(7); + +gap> ChromaticNumber(gr : christofides); +3 +gap> ChromaticNumber(gr : christofides); +3 +gap> ChromaticNumber(gr : christofides); +3 +gap> a := DigraphRemoveEdges(CompleteDigraph(50), [[1, 2], [2, 1]]);; +gap> b := DigraphAddVertex(a);; +gap> ChromaticNumber(a : christofides); +49 +gap> ChromaticNumber(b : christofides); +49 + # DegreeMatrix gap> gr := Digraph([[2, 3, 4], [2, 5], [1, 5, 4], [1], [1, 1, 2, 4]]);; gap> DegreeMatrix(gr); From 0af7fc2491e8465162c9dc27dc4ea6f7293506e8 Mon Sep 17 00:00:00 2001 From: Ewan Gilligan Date: Thu, 13 Jan 2022 12:28:23 +0000 Subject: [PATCH 2/2] Make requested changes Add additional explanation to Zykov Improve vertex picking for Zykov Simplify vertex picking for Christofides --- gap/attr.gi | 91 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 9496042c6..74c439029 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -375,46 +375,58 @@ function(D) nr := DigraphNrVertices(D); # Recursive function call ZykovReduce := function(D) - local nr, D_contract, adjacent, vertices, v, x, y, x_i, y_i, found, deg; + local nr, D_contract, vertices, v, x, y, i, j, adjacent; nr := DigraphNrVertices(D); # Update upper bound if possible. chrom := Minimum(nr, chrom); - # Leaf nodes are either complete graphs or q-cliques. The chromatic number - # is then the smallest q-clique found. + # Leaf nodes are either complete graphs or cliques that have size equal to + # the current upper bound. The chromatic number is then the smallest clique + # found. + # Cliques finder arguments: + # digraph = D - The graph + # hook = fail - hook is not required + # user_param = [] - user_param is a list as hook is fail + # limit = 1 - We only need one clique + # include = exclude = [] - We check all vertices + # max = false - This clique need not be maximal + # size = chrom - We want a clique the size of our upper bound + # reps = true - As we only care about the existence of the clique, + # we can instead search for representatives which is more efficient. if not IsCompleteDigraph(D) and IsEmpty(CliquesFinder(D, fail, [], 1, [], [], false, chrom, true)) then - # Get adjacency function - adjacent := DigraphAdjacencyFunction(D); # Sort vertices by degree, so that higher degree vertices are picked first - vertices := [1 .. nr]; - deg := ShallowCopy(OutDegrees(D)); - SortParallel(deg, vertices, {x, y} -> x > y); + # Picking higher degree vertices will make it more likely a clique will + # form in one of the modified graphs, which will terminate the recursion. + vertices := DigraphWelshPowellOrder(D); + # Get the adjacency function + adjacent := DigraphAdjacencyFunction(D); # Choose two non-adjacent vertices x, y - # This is just done by ascending ordering. - found := false; - for x_i in [1 .. nr] do - x := vertices[x_i]; - for y_i in [x_i + 1 .. nr] do - y := vertices[y_i]; - if not adjacent(x, y) then - found := true; - break; - fi; - od; - if found then + for i in [1 .. nr] do + x := vertices[i]; + # Search for the first vertex not adjacent to all others + # This is guaranteed to exist as D is not the complete graph. + if OutDegreeOfVertex(D, x) < nr - 1 then + # Now search for a non-adjacent vertex, prioritising higher degree ones + for j in [i + 1 .. nr] do + y := vertices[j]; + if not adjacent(x, y) then + break; + fi; + od; break; fi; od; Assert(1, x <> y, "x and y must be different"); - Assert(1, found, "No adjacent vertices"); # Colour the vertex contraction. # A contraction of a graph effectively merges two non adjacent vertices # into a single new vertex with the edges merged. # We merge y into x, keeping x. + + # TODO Use DigraphContractEdge once it is implemented. D_contract := DigraphMutableCopy(D); for v in vertices do - # Iterate over all vertices that + # Iterate over all vertices that are not x or y if v = x or v = y then continue; fi; @@ -436,8 +448,10 @@ function(D) DigraphRemoveEdge(D, [y, x]); fi; end; - # Algorithm requires an undirected graph. - D := DigraphSymmetricClosure(DigraphMutableCopy(D)); + # Algorithm requires an undirected graph without multiple edges. + D := DigraphMutableCopy(D); + D := DigraphRemoveAllMultipleEdges(D); + D := DigraphSymmetricClosure(D); # Use greedy colouring as an upper bound chrom := RankOfTransformation(DigraphGreedyColouring(D), nr); ZykovReduce(D); @@ -447,8 +461,8 @@ end BindGlobal("DIGRAPHS_ChromaticNumberChristofides", function(D) - local nr, I, n, T, b, unprocessed, i, v_without_t, j, u, min_occurences, - cur_occurences, chrom, colouring, stack, vertices; + local nr, I, n, T, b, unprocessed, i, v_without_t, j, u, min_occurrences, + cur_occurrences, chrom, colouring, stack, vertices; nr := DigraphNrVertices(D); vertices := List(DigraphVertices(D)); @@ -492,23 +506,20 @@ function(D) # Step 5 # Pick u in V \ T such that u is in the fewest maximal independent sets. u := -1; - min_occurences := infinity; - for i in vertices do - # Skip elements of T. - if T[i] then - continue; - fi; - cur_occurences := 0; - for j in v_without_t do - if j[i] then - cur_occurences := cur_occurences + 1; - fi; - od; - if cur_occurences < min_occurences then - min_occurences := cur_occurences; + min_occurrences := infinity; + # Flip T to get V \ T + FlipBlist(T); + # Convert to list to iterate over the vertices. + for i in ListBlist(vertices, T) do + # Count how many times this vertex appears in a MIS + cur_occurrences := Number(v_without_t, j -> j[i]); + if cur_occurrences < min_occurrences then + min_occurrences := cur_occurrences; u := i; fi; od; + # Revert changes to T + FlipBlist(T); Assert(1, u <> -1, "Vertex must be picked"); # Remove maximal independent sets not containing u. v_without_t := Filtered(v_without_t, x -> x[u]);