Skip to content

Commit 32fd72d

Browse files
EwanGilliganjames-d-mitchellJoseph-Edwards
authored
Add Zykov and Christofides Algorithms for Chromatic Number (#491)
* 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 * Make requested changes Add additional explanation to Zykov Improve vertex picking for Zykov Simplify vertex picking for Christofides * lint * Update comment --------- Co-authored-by: James D. Mitchell <jdm3@st-andrews.ac.uk> Co-authored-by: Joseph Edwards <josephdavidedwards@gmail.com>
1 parent 0101a73 commit 32fd72d

4 files changed

Lines changed: 340 additions & 0 deletions

File tree

doc/attr.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,10 @@ gap> DigraphLoops(D);
17171717
<Cite Key="Law1976"/></Item>
17181718
<Item><C>byskov</C> - Byskov's Algorithm
17191719
<Cite Key="Bys2002"/></Item>
1720+
<Item><C>zykov</C> - Zykov's Algorithm
1721+
<Cite Key="Corneil1973"/></Item>
1722+
<Item><C>christofides</C> - Christofides's Algorithm
1723+
<Cite Key="Wang1974"/></Item>
17201724
</List>
17211725

17221726
<Example><![CDATA[

doc/digraphs.bib

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,35 @@ @InProceedings{US14
193193
pages="313--324",
194194
isbn="978-3-319-11812-3"
195195
}
196+
197+
@article{Corneil1973,
198+
author = {Corneil, D. G. and Graham, B.},
199+
title = {An Algorithm for Determining the Chromatic Number of a Graph},
200+
journal = {SIAM Journal on Computing},
201+
volume = {2},
202+
number = {4},
203+
pages = {311-318},
204+
year = {1973},
205+
doi = {10.1137/0202026},
206+
URL = {https://doi.org/10.1137/0202026},
207+
eprint = {https://doi.org/10.1137/0202026}
208+
}
209+
210+
@article{Wang1974,
211+
author= {Wang, Chung C.},
212+
title = {An Algorithm for the Chromatic Number of a Graph},
213+
year = {1974},
214+
issue_date = {July 1974},
215+
publisher = {Association for Computing Machinery},
216+
address = {New York, NY, USA},
217+
volume = {21},
218+
number = {3},
219+
issn = {0004-5411},
220+
url = {https://doi.org/10.1145/321832.321837},
221+
doi = {10.1145/321832.321837},
222+
abstract = {Christofides' algorithm for finding the chromatic number of a graph is improved both in speed and memory space by using a depth-first search rule to search for a shortest path in a reduced subgraph tree.},
223+
journal = {J. ACM},
224+
month = jul,
225+
pages = {385–391},
226+
numpages = {7}
227+
}

gap/attr.gi

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,194 @@ function(D)
513513
end
514514
);
515515

516+
BindGlobal("DIGRAPHS_ChromaticNumberZykov",
517+
function(D)
518+
local nr, ZykovReduce, chrom;
519+
nr := DigraphNrVertices(D);
520+
# Recursive function call
521+
ZykovReduce := function(D)
522+
local nr, D_contract, vertices, v, x, y, i, j, adjacent;
523+
nr := DigraphNrVertices(D);
524+
# Update upper bound if possible.
525+
chrom := Minimum(nr, chrom);
526+
# Leaf nodes are either complete graphs or cliques that have size equal to
527+
# the current upper bound. The chromatic number is then the smallest clique
528+
# found.
529+
# Cliques finder arguments:
530+
# digraph = D - The graph
531+
# hook = fail - hook is not required
532+
# user_param = [] - user_param is a list as hook is fail
533+
# limit = 1 - We only need one clique
534+
# include = exclude = [] - We check all vertices
535+
# max = false - This clique need not be maximal
536+
# size = chrom - We want a clique the size of our upper bound
537+
# reps = true - As we only care about the existence of the clique,
538+
# we can instead search for representatives which is more efficient.
539+
if not IsCompleteDigraph(D) and IsEmpty(CliquesFinder(D, fail, [], 1, [],
540+
[], false, chrom,
541+
true)) then
542+
# Sort vertices by degree, so that higher degree vertices are picked first
543+
# Picking higher degree vertices will make it more likely a clique will
544+
# form in one of the modified graphs, which will terminate the recursion.
545+
vertices := DigraphWelshPowellOrder(D);
546+
# Get the adjacency function
547+
adjacent := DigraphAdjacencyFunction(D);
548+
# Choose two non-adjacent vertices x, y
549+
for i in [1 .. nr] do
550+
x := vertices[i];
551+
# Search for the first vertex not adjacent to all others
552+
# This is guaranteed to exist as D is not the complete graph.
553+
if OutDegreeOfVertex(D, x) < nr - 1 then
554+
# Now search for a non-adjacent vertex, prioritising higher degree
555+
# ones
556+
for j in [i + 1 .. nr] do
557+
y := vertices[j];
558+
if not adjacent(x, y) then
559+
break;
560+
fi;
561+
od;
562+
break;
563+
fi;
564+
od;
565+
Assert(1, x <> y, "x and y must be different");
566+
# Colour the vertex contraction.
567+
# A contraction of a graph effectively merges two non adjacent vertices
568+
# into a single new vertex with the edges merged.
569+
# We merge y into x, keeping x.
570+
571+
# We could potentially use quotient digraph here, but the increased
572+
# generality might cause this to get slower.
573+
D_contract := DigraphMutableCopy(D);
574+
for v in vertices do
575+
# Iterate over all vertices that are not x or y
576+
if v = x or v = y then
577+
continue;
578+
fi;
579+
# Add any edge that involves y, but not already x to avoid duplication.
580+
if adjacent(v, y) and not adjacent(v, x) then
581+
DigraphAddEdge(D_contract, x, v);
582+
DigraphAddEdge(D_contract, v, x);
583+
fi;
584+
od;
585+
DigraphRemoveVertex(D_contract, y);
586+
ZykovReduce(D_contract);
587+
# Colour the edge addition
588+
# This just adds symmetric edges between x and y;
589+
DigraphAddEdge(D, [x, y]);
590+
DigraphAddEdge(D, [y, x]);
591+
ZykovReduce(D);
592+
# Undo changes to the graph
593+
DigraphRemoveEdge(D, [x, y]);
594+
DigraphRemoveEdge(D, [y, x]);
595+
fi;
596+
end;
597+
# Algorithm requires an undirected graph without multiple edges.
598+
D := DigraphMutableCopy(D);
599+
D := DigraphRemoveAllMultipleEdges(D);
600+
D := DigraphSymmetricClosure(D);
601+
# Use greedy colouring as an upper bound
602+
chrom := RankOfTransformation(DigraphGreedyColouring(D), nr);
603+
ZykovReduce(D);
604+
return chrom;
605+
end
606+
);
607+
608+
BindGlobal("DIGRAPHS_ChromaticNumberChristofides",
609+
function(D)
610+
local nr, I, n, T, b, unprocessed, i, v_without_t, j, u, min_occurrences,
611+
cur_occurrences, chrom, colouring, stack, vertices;
612+
613+
nr := DigraphNrVertices(D);
614+
vertices := List(DigraphVertices(D));
615+
# Initialise the required variables.
616+
# Calculate all maximal independent sets of D.
617+
I := DigraphMaximalIndependentSets(D);
618+
# Convert each MIS into a BList
619+
I := List(I, i -> BlistList(vertices, i));
620+
# Upper bound for chromatic number.
621+
chrom := nr;
622+
# Set of vertices of D not in the current subgraph at level n.
623+
T := ListWithIdenticalEntries(nr, false);
624+
# Current search level of the subgraph tree.
625+
n := 0;
626+
# The maximal independent sets of V \ T at level n.
627+
b := [ListWithIdenticalEntries(nr, false)];
628+
# Number of unprocessed MIS's of V \ T from level 1 to n
629+
unprocessed := ListWithIdenticalEntries(nr, 0);
630+
# Would be jth colour class of the chromatic colouring of G.
631+
colouring := List([1 .. nr], i -> BlistList(vertices, [i]));
632+
# Stores current unprocessed MIS's of V \ T at level 1 to level n
633+
stack := [];
634+
# Now perform the search.
635+
repeat
636+
# Step 2
637+
if n < chrom then
638+
# Step 3
639+
# If V = T then we've reached a null subgraph
640+
if SizeBlist(T) = nr then
641+
chrom := n;
642+
SubtractBlist(T, b[n + 1]);
643+
for i in [1 .. chrom] do
644+
colouring[i] := b[i];
645+
# TODO set colouring attribute
646+
od;
647+
else
648+
# Step 4
649+
# Compute the maximal independent sets of V \ T
650+
v_without_t := DIGRAPHS_MaximalIndependentSetsSubtractedSet(I, T,
651+
infinity);
652+
# Step 5
653+
# Pick u in V \ T such that u is in the fewest maximal independent sets.
654+
u := -1;
655+
min_occurrences := infinity;
656+
# Flip T to get V \ T
657+
FlipBlist(T);
658+
# Convert to list to iterate over the vertices.
659+
for i in ListBlist(vertices, T) do
660+
# Count how many times this vertex appears in a MIS
661+
cur_occurrences := Number(v_without_t, j -> j[i]);
662+
if cur_occurrences < min_occurrences then
663+
min_occurrences := cur_occurrences;
664+
u := i;
665+
fi;
666+
od;
667+
# Revert changes to T
668+
FlipBlist(T);
669+
Assert(1, u <> -1, "Vertex must be picked");
670+
# Remove maximal independent sets not containing u.
671+
v_without_t := Filtered(v_without_t, x -> x[u]);
672+
# Add these MISs to the stack
673+
Append(stack, v_without_t);
674+
# Search has moved one level deeper
675+
n := n + 1;
676+
unprocessed[n] := Length(v_without_t);
677+
fi;
678+
else
679+
# if n >= g then T = T \ b[n]
680+
# This exceeds the current best bound, so stop search.
681+
SubtractBlist(T, b[n + 1]);
682+
fi;
683+
# Step 6
684+
while n <> 0 do
685+
# step 7
686+
if unprocessed[n] = 0 then
687+
n := n - 1;
688+
SubtractBlist(T, b[n + 1]);
689+
else
690+
# Step 8
691+
# take an element from the top of the stack
692+
i := Remove(stack);
693+
unprocessed[n] := unprocessed[n] - 1;
694+
b[n + 1] := i;
695+
UniteBlist(T, i);
696+
break;
697+
fi;
698+
od;
699+
until n = 0;
700+
return chrom;
701+
end
702+
);
703+
516704
InstallMethod(ChromaticNumber, "for a digraph by out-neighbours",
517705
[IsDigraphByOutNeighboursRep],
518706
function(D)
@@ -536,6 +724,10 @@ function(D)
536724
return DIGRAPHS_ChromaticNumberLawler(D);
537725
elif ValueOption("byskov") <> fail then
538726
return DIGRAPHS_ChromaticNumberByskov(D);
727+
elif ValueOption("zykov") <> fail then
728+
return DIGRAPHS_ChromaticNumberZykov(D);
729+
elif ValueOption("christofides") <> fail then
730+
return DIGRAPHS_ChromaticNumberChristofides(D);
539731
fi;
540732

541733
# The chromatic number of <D> is at least 3 and at most nr

tst/standard/attr.tst

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,118 @@ Error, the argument <D> must be a digraph with no loops,
18231823
gap> DIGRAPHS_UnderThreeColourable(EmptyDigraph(0));
18241824
0
18251825

1826+
# Test ChromaticNumber Zykov
1827+
gap> ChromaticNumber(NullDigraph(10) : zykov);
1828+
1
1829+
gap> ChromaticNumber(CompleteDigraph(10) : zykov);
1830+
10
1831+
gap> ChromaticNumber(CompleteBipartiteDigraph(5, 5) : zykov);
1832+
2
1833+
gap> ChromaticNumber(DigraphRemoveEdge(CompleteDigraph(10), [1, 2]) : zykov);
1834+
10
1835+
gap> ChromaticNumber(Digraph([[4, 8], [6, 10], [9], [2, 3, 9], [],
1836+
> [3], [4], [6], [], [5, 7]]) : zykov);
1837+
3
1838+
gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1),
1839+
> Digraph([[2], [4], [1, 2], [3]])) : zykov);
1840+
3
1841+
gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1),
1842+
> Digraph([[2], [4], [1, 2], [3], [1, 2, 3]])) : zykov);
1843+
4
1844+
gap> gr := Digraph([[2, 3, 4], [3], [], []]);
1845+
<immutable digraph with 4 vertices, 4 edges>
1846+
gap> ChromaticNumber(gr : zykov);
1847+
3
1848+
gap> ChromaticNumber(EmptyDigraph(0) : zykov);
1849+
0
1850+
gap> gr := CompleteDigraph(4);;
1851+
gap> gr := DigraphAddVertex(gr);;
1852+
gap> ChromaticNumber(gr : zykov);
1853+
4
1854+
gap> gr := Digraph([[2, 4, 7, 3], [3, 5, 8, 1], [1, 6, 9, 2],
1855+
> [5, 7, 1, 6], [6, 8, 2, 4], [4, 9, 3, 5], [8, 1, 4, 9], [9, 2, 5, 7],
1856+
> [7, 3, 6, 8]]);;
1857+
gap> ChromaticNumber(gr : zykov);
1858+
3
1859+
gap> gr := DigraphSymmetricClosure(ChainDigraph(5));
1860+
<immutable symmetric digraph with 5 vertices, 8 edges>
1861+
gap> ChromaticNumber(gr : zykov);
1862+
2
1863+
gap> gr := DigraphFromGraph6String("KmKk~K??G@_@");
1864+
<immutable symmetric digraph with 12 vertices, 42 edges>
1865+
gap> ChromaticNumber(gr : zykov);
1866+
4
1867+
gap> gr := CycleDigraph(7);
1868+
<immutable cycle digraph with 7 vertices>
1869+
gap> ChromaticNumber(gr : zykov);
1870+
3
1871+
gap> ChromaticNumber(gr : zykov);
1872+
3
1873+
gap> ChromaticNumber(gr : zykov);
1874+
3
1875+
gap> a := DigraphRemoveEdges(CompleteDigraph(50), [[1, 2], [2, 1]]);;
1876+
gap> b := DigraphAddVertex(a);;
1877+
gap> ChromaticNumber(a : zykov);
1878+
49
1879+
gap> ChromaticNumber(b : zykov);
1880+
49
1881+
1882+
# Test ChromaticNumber Christofides
1883+
gap> ChromaticNumber(NullDigraph(10) : christofides);
1884+
1
1885+
gap> ChromaticNumber(CompleteDigraph(10) : christofides);
1886+
10
1887+
gap> ChromaticNumber(CompleteBipartiteDigraph(5, 5) : christofides);
1888+
2
1889+
gap> ChromaticNumber(DigraphRemoveEdge(CompleteDigraph(10), [1, 2]) : christofides);
1890+
10
1891+
gap> ChromaticNumber(Digraph([[4, 8], [6, 10], [9], [2, 3, 9], [],
1892+
> [3], [4], [6], [], [5, 7]]) : christofides);
1893+
3
1894+
gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1),
1895+
> Digraph([[2], [4], [1, 2], [3]])) : christofides);
1896+
3
1897+
gap> ChromaticNumber(DigraphDisjointUnion(CompleteDigraph(1),
1898+
> Digraph([[2], [4], [1, 2], [3], [1, 2, 3]])) : christofides);
1899+
4
1900+
gap> gr := Digraph([[2, 3, 4], [3], [], []]);
1901+
<immutable digraph with 4 vertices, 4 edges>
1902+
gap> ChromaticNumber(gr : christofides);
1903+
3
1904+
gap> ChromaticNumber(EmptyDigraph(0) : christofides);
1905+
0
1906+
gap> gr := CompleteDigraph(4);;
1907+
gap> gr := DigraphAddVertex(gr);;
1908+
gap> ChromaticNumber(gr : christofides);
1909+
4
1910+
gap> gr := Digraph([[2, 4, 7, 3], [3, 5, 8, 1], [1, 6, 9, 2],
1911+
> [5, 7, 1, 6], [6, 8, 2, 4], [4, 9, 3, 5], [8, 1, 4, 9], [9, 2, 5, 7],
1912+
> [7, 3, 6, 8]]);;
1913+
gap> ChromaticNumber(gr : christofides);
1914+
3
1915+
gap> gr := DigraphSymmetricClosure(ChainDigraph(5));
1916+
<immutable symmetric digraph with 5 vertices, 8 edges>
1917+
gap> ChromaticNumber(gr : christofides);
1918+
2
1919+
gap> gr := DigraphFromGraph6String("KmKk~K??G@_@");
1920+
<immutable symmetric digraph with 12 vertices, 42 edges>
1921+
gap> ChromaticNumber(gr : christofides);
1922+
4
1923+
gap> gr := CycleDigraph(7);
1924+
<immutable cycle digraph with 7 vertices>
1925+
gap> ChromaticNumber(gr : christofides);
1926+
3
1927+
gap> ChromaticNumber(gr : christofides);
1928+
3
1929+
gap> ChromaticNumber(gr : christofides);
1930+
3
1931+
gap> a := DigraphRemoveEdges(CompleteDigraph(50), [[1, 2], [2, 1]]);;
1932+
gap> b := DigraphAddVertex(a);;
1933+
gap> ChromaticNumber(a : christofides);
1934+
49
1935+
gap> ChromaticNumber(b : christofides);
1936+
49
1937+
18261938
# DegreeMatrix
18271939
gap> gr := Digraph([[2, 3, 4], [2, 5], [1, 5, 4], [1], [1, 1, 2, 4]]);;
18281940
gap> DegreeMatrix(gr);

0 commit comments

Comments
 (0)