Skip to content

Commit

Permalink
grahom: improve DigraphColouring
Browse files Browse the repository at this point in the history
This commit implements the Welsh-Powell algorithm for digraph colouring.
This implementation is almost always as good as the previous one
(in terms of the number of colours required), and is faster (approx.
4 times faster on DigraphRemoveLoops(RandomDigraph(10000, 0.1))).
  • Loading branch information
james-d-mitchell authored and wilfwilson committed Nov 28, 2018
1 parent 345030d commit 9b520d7
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 88 deletions.
14 changes: 14 additions & 0 deletions doc/digraphs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,17 @@ @article {vanLintSchrijver1981
DOI = {10.1007/BF02579178},
URL = {http://dx.doi.org/10.1007/BF02579178},
}

@article{Welsh1967aa,
author = {Welsh, D. J. A. and Powell, M. B.},
title = {An upper bound for the chromatic number of a graph and its application to timetabling problems},
journal = {The Computer Journal},
volume = {10},
number = {1},
pages = {85-86},
year = {1967},
doi = {10.1093/comjnl/10.1.85},
URL = {http://dx.doi.org/10.1093/comjnl/10.1.85},
eprint = {/oup/backfile/content_public/journal/comjnl/10/1/10.1093/comjnl/10.1.85/2/100085.pdf}
}

83 changes: 74 additions & 9 deletions doc/grahom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,6 @@ gap> GeneratorsOfEndomorphismMonoid(gr, [[1], [2, 3]]);
Label="for a digraph and a number of colours"/>
<Oper Name="DigraphColoring" Arg="digraph, n"
Label="for a digraph and a number of colours"/>
<Attr Name="DigraphColouring" Arg="digraph"
Label="for a digraph"/>
<Attr Name="DigraphColoring" Arg="digraph"
Label="for a digraph"/>
<Returns> A transformation, or <K>fail</K>.</Returns>
<Description>
A <E>proper colouring</E> of a digraph is a labelling of its vertices in
Expand All @@ -381,10 +377,8 @@ gap> GeneratorsOfEndomorphismMonoid(gr, [[1], [2, 3]]);
from <A>digraph</A> onto the complete digraph with <A>n</A> vertices if one
exists, else it returns <K>fail</K>. <P/>

If the optional second argument <A>n</A> is not provided, then
<C>DigraphColouring</C> uses a greedy algorithm to obtain some proper
colouring of <A>digraph</A>, which may not use the minimal number of
colours. <P/>
See also <Ref Attr="DigraphGreedyColouring" Label="for a digraph"/> and
<P/>

Note that a digraph with at least two vertices has a 2-colouring if and only
if it is bipartite, see <Ref Prop="IsBipartiteDigraph"/>.
Expand All @@ -399,13 +393,84 @@ gap> t := DigraphColouring(gr, 2);
Transformation( [ 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 ] )
gap> ForAll(DigraphEdges(gr), e -> e[1] ^ t <> e[2] ^ t);
true
gap> DigraphColouring(gr);
gap> DigraphGreedyColouring(gr);
Transformation( [ 2, 1, 2, 1, 2, 1, 2, 1, 2, 1 ] )
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="DigraphGreedyColouring">
<ManSection>
<Oper Name="DigraphGreedyColouring"
Arg="digraph, order"
Label="for a digraph and vertex order"/>
<Oper Name="DigraphGreedyColouring"
Arg="digraph, func"
Label="for a digraph and vertex order function"/>
<Attr Name="DigraphGreedyColouring"
Arg="digraph"
Label="for a digraph"/>
<Returns> A transformation, or <K>fail</K>.</Returns>
<Description>
A <E>proper colouring</E> of a digraph is a labelling of its vertices in
such a way that adjacent vertices have different labels. Note that a digraph
with loops (<Ref Prop="DigraphHasLoops"/>) does not have any proper
colouring.
<P/>

If <A>digraph</A> is a digraph and <A>order</A> is a dense list consisting
of all of the vertices of <A>digraph</A> (in any order), then
<C>DigraphGreedyColouring</C>
uses a greedy algorithm with the specified order to obtain some proper
colouring of <A>digraph</A>, which may not use the minimal number of
colours. <P/>

If <A>digraph</A> is a digraph and <A>func</A> is a function whose argument
is a digraph, and that returns a dense list <A>order</A>, then
<C>DigraphGreedyColouring(<A>digraph</A>, <A>func</A>)</C> returns
<C>DigraphGreedyColouring(<A>digraph</A>, <A>func</A>(<A>digraph</A>))</C>.
<P/>

If the optional second argument (either a list or a function), is not
specified, then <Ref Attr="DigraphWelshPowellOrder"/> is used by default.
<P/>

See also
<Ref Oper="DigraphColouring"
Label="for a digraph and a number of colours"/>.
<P/>

<Example><![CDATA[
gap> DigraphGreedyColouring(ChainDigraph(10));
Transformation( [ 2, 1, 2, 1, 2, 1, 2, 1, 2, 1 ] )
gap> DigraphGreedyColouring(ChainDigraph(10), [1 .. 10]);
Transformation( [ 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 ] )
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="DigraphWelshPowellOrder">
<ManSection>
<Attr Name="DigraphWelshPowellOrder" Arg="digraph"/>
<Returns> A list of the vertices.</Returns>
<Description>
<C>DigraphWelshPowellOrder</C> returns a list of all of the vertices of
the digraph <A>digraph</A> ordered according to the sum of the number of
out- and in-neighbours, from highest to lowest.
<P/>

<Example><![CDATA[
gap> DigraphWelshPowellOrder(Digraph([[4], [9], [9], [],
> [4, 6, 9], [1], [], [],
> [4, 5], [4, 5]]));
[ 5, 9, 4, 1, 6, 10, 2, 3, 7, 8 ]
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="DigraphEmbedding">
<ManSection>
<Oper Name="DigraphEmbedding" Arg="digraph1, digraph2"/>
Expand Down
2 changes: 1 addition & 1 deletion doc/prop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ true
vertices of <A>digraph</A> can be partitioned into two non-empty sets such
that the source and range of any edge of <A>digraph</A> lie in distinct sets.
Equivalently, a digraph is bipartite if and only if it is 2-colorable; see
<Ref Attr="DigraphColouring" Label="for a digraph"/>. <P/>
<Ref Attr="DigraphGreedyColouring" Label="for a digraph"/>. <P/>

See also <Ref Attr="DigraphBicomponents"/>.
<Example><![CDATA[
Expand Down
8 changes: 3 additions & 5 deletions doc/z-appA.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1204,13 +1204,13 @@
<C>VertexColouring</C>
</Item>
<Item>
<Ref Attr="DigraphColouring" Label="for a digraph"/>
<Ref Attr="DigraphGreedyColouring" Label="for a digraph"/>
</Item>
<Item>
<Alt Only="HTML">
<C>VertexColouring</C> in &Grape; is equivalent to <Ref
Attr="DigraphColouring" Label="for a digraph"/> in &Digraphs;,
except it return a transformation rather than a list of vertex
Attr="DigraphGreedyColouring" Label="for a digraph"/> in &Digraphs;,
except it returns a transformation rather than a list of vertex
colors.
</Alt>
</Item>
Expand Down Expand Up @@ -1357,6 +1357,4 @@

</Table>
</Section>


</Appendix>
2 changes: 2 additions & 0 deletions doc/z-chap6.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ from} $E_a$ \emph{to} $E_b$. In this case we say that $E_a$ and $E_b$ are
<#Include Label="EpimorphismsDigraphs">
<#Include Label="GeneratorsOfEndomorphismMonoid">
<#Include Label="DigraphColouring">
<#Include Label="DigraphGreedyColouring">
<#Include Label="DigraphWelshPowellOrder">
<#Include Label="DigraphEmbedding">
<#Include Label="ChromaticNumber">
<#Include Label="IsDigraphHomomorphism">
Expand Down
14 changes: 5 additions & 9 deletions gap/attr.gi
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ end);
InstallMethod(ChromaticNumber, "for a digraph", [IsDigraph],
function(digraph)
local nr, comps, upper, chrom, tmp_comps, tmp_upper, n, comp, bound, clique,
cmp, c, i;
c, i;

nr := DigraphNrVertices(digraph);

Expand Down Expand Up @@ -128,7 +128,7 @@ function(digraph)
# do not yet know.
if IsConnectedDigraph(digraph) then
comps := [digraph];
upper := [RankOfTransformation(DigraphColoring(digraph), nr)];
upper := [RankOfTransformation(DigraphGreedyColouring(digraph), nr)];
chrom := Maximum(CliqueNumber(digraph), chrom);
else
tmp_comps := [];
Expand All @@ -151,7 +151,7 @@ function(digraph)
# If comp is bipartite, then its chromatic number is 2, and, since
# the chromatic number of digraph is >= 3, this component can be
# ignored.
bound := RankOfTransformation(DigraphColoring(comp),
bound := RankOfTransformation(DigraphGreedyColouring(comp),
DigraphNrVertices(comp));
if bound > chrom then
# If bound <= chrom, then comp can be coloured by at most chrom
Expand Down Expand Up @@ -187,16 +187,12 @@ function(digraph)
od;

# Sort by size, since smaller components are easier to colour
# TODO replace by 2-arg lambda
cmp := function(x, y)
return Size(x) < Size(y);
end;
SortParallel(comps, upper, cmp);
SortParallel(comps, upper, {x, y} -> Size(x) < Size(y));
fi;
for i in [1 .. Length(comps)] do
# <c> is the current best upper bound for the chromatic number of comps[i]
c := upper[i];
while c > chrom and DigraphColoring(comps[i], c - 1) <> fail do
while c > chrom and DigraphColouring(comps[i], c - 1) <> fail do
c := c - 1;
od;
if c > chrom then
Expand Down
11 changes: 9 additions & 2 deletions gap/grahom.gd
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@ DeclareOperation("EpimorphismsDigraphsRepresentatives", [IsDigraph, IsDigraph]);
DeclareOperation("DigraphEmbedding", [IsDigraph, IsDigraph]);

DeclareOperation("DigraphColouring", [IsDigraph, IsInt]);
DeclareAttribute("DigraphColouring", IsDigraph);
DeclareSynonymAttr("DigraphColoring", DigraphColouring);

DeclareOperation("DigraphGreedyColouring", [IsDigraph, IsHomogeneousList]);
DeclareOperation("DigraphGreedyColouringNC", [IsDigraph, IsHomogeneousList]);
DeclareOperation("DigraphGreedyColouring", [IsDigraph, IsFunction]);

DeclareAttribute("DigraphGreedyColouring", IsDigraph);
DeclareSynonym("DigraphGreedyColoring", DigraphGreedyColouring);

DeclareAttribute("DigraphWelshPowellOrder", IsDigraph);

DeclareGlobalFunction("HomomorphismDigraphsFinder");

Expand Down
102 changes: 70 additions & 32 deletions gap/grahom.gi
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,15 @@ function(digraph, n)
"the second argument <n> must be a non-negative integer,");
fi;

if HasDigraphGreedyColouring(digraph) then
if DigraphGreedyColouring(digraph) = fail then
return fail;
elif RankOfTransformation(DigraphGreedyColouring(digraph),
DigraphNrVertices(digraph)) = n then
return DigraphGreedyColouring(digraph);
fi;
fi;

# Only the null digraph with 0 vertices can be coloured with 0 colours
if n = 0 then
if DigraphNrVertices(digraph) = 0 then
Expand All @@ -222,49 +231,78 @@ function(digraph, n)
return DigraphEpimorphism(digraph, CompleteDigraph(n));
end);

#
InstallMethod(DigraphGreedyColouring, "for a digraph", [IsDigraph],
function(D)
return DigraphGreedyColouringNC(D, DigraphWelshPowellOrder(D));
end);

InstallMethod(DigraphColouring, "for a digraph", [IsDigraph],
function(digraph)
local vertices, maxcolour, out_nbs, in_nbs, colouring, usedcolours,
nextcolour, v, u;
InstallMethod(DigraphGreedyColouring, "for a digraph",
[IsDigraph, IsHomogeneousList],
function(D, order)
local n;
n := DigraphNrVertices(D);
if Length(order) <> n or ForAny(order, x -> (not IsPosInt(x)) or x > n) then
ErrorNoReturn("the second argument <order> must be a permutation of ",
[1 .. n]);
fi;
return DigraphGreedyColouringNC(D, order);
end);

InstallMethod(DigraphGreedyColouringNC, "for a digraph and a homogeneous list",
[IsDigraph, IsHomogeneousList],
function(digraph, order)
local n, colour, colouring, out, inn, empty, all, available, nr_coloured, v;

n := DigraphNrVertices(digraph);

if DigraphNrVertices(digraph) = 0 then
if n = 0 then
return IdentityTransformation;
elif DigraphHasLoops(digraph) then
return fail;
fi;

vertices := DigraphVertices(digraph);
maxcolour := 0;
out_nbs := OutNeighbours(digraph);
in_nbs := InNeighbours(digraph);

colouring := [];

for v in vertices do
usedcolours := BlistList([1 .. maxcolour + 1], []);
for u in out_nbs[v] do
if IsBound(colouring[u]) then
usedcolours[colouring[u]] := true;
fi;
od;
for u in in_nbs[v] do
if IsBound(colouring[u]) then
usedcolours[colouring[u]] := true;
colour := 1;
colouring := ListWithIdenticalEntries(n, 0);
out := OutNeighbours(digraph);
inn := InNeighbours(digraph);
empty := BlistList([1 .. n], []);
all := BlistList([1 .. n], [1 .. n]);
available := BlistList([1 .. n], [1 .. n]);

nr_coloured := 0;

while nr_coloured < n do
for v in order do
if colouring[v] = 0 and available[v] then
nr_coloured := nr_coloured + 1;
colouring[v] := colour;
available[v] := false;
SubtractBlist(available, BlistList([1 .. n], out[v]));
SubtractBlist(available, BlistList([1 .. n], inn[v]));
if available = empty then
break;
fi;
fi;
od;
nextcolour := 1;
while usedcolours[nextcolour] do
nextcolour := nextcolour + 1;
od;
colouring[v] := nextcolour;
if colouring[v] > maxcolour then
maxcolour := colouring[v];
fi;
UniteBlist(available, all);
colour := colour + 1;
od;
return TransformationNC(colouring);
end);

return Transformation(colouring);
InstallMethod(DigraphGreedyColouring, "for a digraph and a function",
[IsDigraph, IsFunction],
function(D, func)
return DigraphGreedyColouringNC(D, func(D));
end);

InstallMethod(DigraphWelshPowellOrder, "for a digraph", [IsDigraph],
function(digraph)
local order, deg;
order := [1 .. DigraphNrVertices(digraph)];
deg := ShallowCopy(OutDegrees(digraph)) + InDegrees(digraph);
SortParallel(deg, order, {x, y} -> x > y);
return order;
end);

################################################################################
Expand Down
14 changes: 7 additions & 7 deletions tst/standard/attr.tst
Original file line number Diff line number Diff line change
Expand Up @@ -1301,16 +1301,16 @@ gap> gr := DigraphFromDigraph6String(Concatenation(
<digraph with 45 vertices, 180 edges>
gap> ChromaticNumber(gr);
3
gap> DigraphColoring(gr, 3);
gap> DigraphColouring(gr, 3);
Transformation( [ 1, 2, 2, 1, 2, 1, 3, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 1, 3, 3,
3, 2, 3, 3, 2, 2, 1, 3, 1, 3, 3, 3, 2, 1, 3, 1, 3, 1, 1, 2, 2, 3, 3, 3,
2 ] )
gap> DigraphColoring(gr, 2);
gap> DigraphColouring(gr, 2);
fail
gap> DigraphColoring(gr);
Transformation( [ 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 2, 3,
3, 2, 3, 3, 3, 2, 1, 4, 4, 3, 3, 3, 3, 1, 3, 1, 3, 4, 4, 2, 2, 5, 3, 3,
4 ] )
gap> DigraphGreedyColoring(gr);
Transformation( [ 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2, 2, 3,
3, 2, 3, 3, 3, 2, 1, 4, 4, 2, 3, 3, 3, 3, 3, 1, 3, 4, 4, 3, 2, 1, 4, 3,
1 ] )
gap> gr := Digraph([[2, 3, 4], [3], [], []]);
<digraph with 4 vertices, 4 edges>
gap> ChromaticNumber(gr);
Expand Down Expand Up @@ -1368,7 +1368,7 @@ gap> ChromaticNumber(gr);
3
gap> gr := DigraphSymmetricClosure(ChainDigraph(5));
<digraph with 5 vertices, 8 edges>
gap> DigraphColoring(gr);;
gap> DigraphGreedyColoring(gr);;
gap> ChromaticNumber(gr);
2
gap> gr := DigraphFromGraph6String("KmKk~K??G@_@");
Expand Down
Loading

0 comments on commit 9b520d7

Please sign in to comment.