Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Zykov and Christofides Algorithms for Chromatic Number #491

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/attr.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,10 @@ gap> DigraphLoops(D);
<Cite Key="Law1976"/></Item>
<Item><C>byskov</C> - Byskov's Algorithm
<Cite Key="Bys2002"/></Item>
<Item><C>zykov</C> - Zykov's Algorithm
<Cite Key="Corneil1973"/></Item>
<Item><C>christofides</C> - Christofides's Algorithm
<Cite Key="Wang1974"/></Item>
</List>

<Example><![CDATA[
Expand Down
32 changes: 32 additions & 0 deletions doc/digraphs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,35 @@ @article{Bys2002
Journal = {BRICS Report Series},
Doi = {10.7146/brics.v9i45.21760}
}

@article{Corneil1973,
author = {Corneil, D. G. and Graham, B.},
title = {An Algorithm for Determining the Chromatic Number of a Graph},
journal = {SIAM Journal on Computing},
volume = {2},
number = {4},
pages = {311-318},
year = {1973},
doi = {10.1137/0202026},
URL = {https://doi.org/10.1137/0202026},
eprint = {https://doi.org/10.1137/0202026}
}

@article{Wang1974,
author= {Wang, Chung C.},
title = {An Algorithm for the Chromatic Number of a Graph},
year = {1974},
issue_date = {July 1974},
publisher = {Association for Computing Machinery},
address = {New York, NY, USA},
volume = {21},
number = {3},
issn = {0004-5411},
url = {https://doi.org/10.1145/321832.321837},
doi = {10.1145/321832.321837},
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.},
journal = {J. ACM},
month = jul,
pages = {385–391},
numpages = {7}
}
190 changes: 190 additions & 0 deletions gap/attr.gi
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,192 @@ function(D)
end
);

BindGlobal("DIGRAPHS_ChromaticNumberZykov",
function(D)
local nr, ZykovReduce, chrom;
nr := DigraphNrVertices(D);
# Recursive function call
ZykovReduce := function(D)
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 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, [],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you possibly add a comment to this line about what it is that the CliquesFinder function is looking for here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some additional explanation for the CliqueFinder call.

[], false, chrom,
true)) then
# Sort vertices by degree, so that higher degree vertices are picked first
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already implemented in DigraphWelshPowellOrder maybe you could use that instead of rolling your own?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for letting me know about DigraphWelshPowellOrder, I'll make the change to use that instead!

# 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
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");
# 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you use DigraphContractEdge to do this step too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking through the manual I can't seem to find any reference to DigraphContractEdge, but I believe it would be possible to replace the step with DigraphQuotient by taking the partition the two adjacent vertices in one part, and then the remaining vertices are singleton parts. I need to look into the implementation of DigraphQuotient, as it seems more general that what is needed here and so could be slower.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now there is an open pull request for DigraphContractEdge, so I'll use that once the required changes are made.

for v in vertices do
# Iterate over all vertices that are not x or y
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 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);
return chrom;
end
);

BindGlobal("DIGRAPHS_ChromaticNumberChristofides",
function(D)
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));
# 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_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]);
# 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)
Expand All @@ -392,6 +578,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 <D> is at least 3 and at most nr
Expand Down
112 changes: 112 additions & 0 deletions tst/standard/attr.tst
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,118 @@ Error, the argument <D> 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], [], []]);
<immutable digraph with 4 vertices, 4 edges>
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));
<immutable symmetric digraph with 5 vertices, 8 edges>
gap> ChromaticNumber(gr : zykov);
2
gap> gr := DigraphFromGraph6String("KmKk~K??G@_@");
<immutable symmetric digraph with 12 vertices, 42 edges>
gap> ChromaticNumber(gr : zykov);
4
gap> gr := CycleDigraph(7);
<immutable cycle digraph with 7 vertices>
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], [], []]);
<immutable digraph with 4 vertices, 4 edges>
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));
<immutable symmetric digraph with 5 vertices, 8 edges>
gap> ChromaticNumber(gr : christofides);
2
gap> gr := DigraphFromGraph6String("KmKk~K??G@_@");
<immutable symmetric digraph with 12 vertices, 42 edges>
gap> ChromaticNumber(gr : christofides);
4
gap> gr := CycleDigraph(7);
<immutable cycle digraph with 7 vertices>
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);
Expand Down