Skip to content

Commit b8adb8a

Browse files
frankiegilliscoraakedmtorpey
authored
Implement IsCograph with bug fixes (#904)
* Working IsCograph code * gaplint flagged changes * Fix further gaplint flagged errors and comments * Fix whitespace issues and add further comments * indentation issues fixed * Change j -> Minimum(j) to 'minimum' * remove unused local variable * Add IsCograph to prop.gi and prop.gd * Fixed bugs in IsCograph * Added tests for IsCograph * Added doc for IsCograph * Delete IsCograph.g * Removed trailing whitespace * Fixed linting issues * Added further tests for CodeCov * Did not, in fact, fix linting * The linting strikes back * Corrected reference for algorithm in IsCograph * Fixed line too long in prop.xml * Fix minor doc issues * Optimisations for IsCograph * IsCograph for mutable digraphs --------- Co-authored-by: Cora Aked <coramay.aked@gmail.com> Co-authored-by: Michael Young <mct25@st-andrews.ac.uk>
1 parent 5fd889a commit b8adb8a

6 files changed

Lines changed: 287 additions & 1 deletion

File tree

doc/digraphs.bib

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ @article{Gab00
8282
Bdsk-Url-1 = {https://www.sciencedirect.com/science/article/pii/S002001900000051X},
8383
}
8484

85+
@article{HP05,
86+
title = {A simple linear time algorithm for cograph recognition},
87+
journal = {Discrete Applied Mathematics},
88+
volume = {145},
89+
number = {2},
90+
pages = {183-197},
91+
year = {2005},
92+
note = {Structural Decompositions, Width Parameters, and Graph Labelings},
93+
issn = {0166-218X},
94+
doi = {https://doi.org/10.1016/j.dam.2004.01.011},
95+
url = {https://www.sciencedirect.com/science/article/pii/S0166218X04002446},
96+
author = {Michel Habib and Christophe Paul},
97+
keywords = {Modular decomposition, Graphs, Algorithms},
98+
}
99+
85100
@inproceedings{JK07,
86101
Author = {Tommi Junttila and Petteri Kaski},
87102
Booktitle = {Proceedings of the Ninth Workshop on Algorithm Engineering and

doc/prop.xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,50 @@ true
875875
</ManSection>
876876
<#/GAPDoc>
877877

878+
<#GAPDoc Label="IsCograph">
879+
<ManSection>
880+
<Prop Name="IsCograph" Arg="digraph"/>
881+
<Returns><K>true</K> or <K>false</K>.</Returns>
882+
<Description>
883+
If <A>digraph</A> is a symmetric digraph without loops or multiple edges, then the
884+
property returns true if <A>digraph</A> is a cograph, and false if it is not.
885+
<P/>
886+
887+
A symmetric digraph without loops or multiple edges is called a <E>cograph</E>
888+
if it does not contain a copy of the path graph on four vertices as
889+
an induced subgraph. Equivalently, cographs are those graphs which may be
890+
reached starting from a single vertex under the operations of series and parallel
891+
composition.
892+
<P/>
893+
894+
This function implements the algorithm for cograph recogntition given in
895+
<Cite Key="HP05"/>.
896+
<P/>
897+
898+
&MUTABLE_RECOMPUTED_PROP;
899+
900+
<Example><![CDATA[
901+
gap> D := DigraphByEdges([[1, 2], [2, 3], [3, 4], [4, 1]]);
902+
<immutable digraph with 4 vertices, 4 edges>
903+
gap> D := DigraphSymmetricClosure(D);
904+
<immutable symmetric digraph with 4 vertices, 8 edges>
905+
gap> IsCograph(D);
906+
true
907+
gap> D := DigraphByEdges([[1, 2], [2, 3], [3, 4]]);
908+
<immutable digraph with 4 vertices, 3 edges>
909+
gap> D := DigraphSymmetricClosure(D);
910+
<immutable symmetric digraph with 4 vertices, 6 edges>
911+
gap> IsCograph(D);
912+
false
913+
gap> D := CompleteDigraph(5);
914+
<immutable complete digraph with 5 vertices>
915+
gap> IsCograph(D);
916+
true
917+
]]></Example>
918+
</Description>
919+
</ManSection>
920+
<#/GAPDoc>
921+
878922
<#GAPDoc Label="IsFunctionalDigraph">
879923
<ManSection>
880924
<Prop Name="IsFunctionalDigraph" Arg="digraph"/>

doc/z-chap5.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
<#Include Label="IsEdgeTransitive">
8282
<#Include Label="IsVertexTransitive">
8383
<#Include Label="Is2EdgeTransitive">
84+
<#Include Label="IsCograph">
8485
</Section>
8586

8687

gap/prop.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ DeclareProperty("IsPermutationDigraph", IsDigraph);
5454
DeclareProperty("IsDistributiveLatticeDigraph", IsDigraph);
5555
DeclareProperty("IsModularLatticeDigraph", IsDigraph);
5656
DeclareProperty("Is2EdgeTransitive", IsDigraph);
57+
DeclareProperty("IsCograph", IsDigraph);
5758
DeclareSynonymAttr("IsLatticeDigraph",
5859
IsMeetSemilatticeDigraph and IsJoinSemilatticeDigraph);
5960
DeclareSynonymAttr("IsPreorderDigraph",

gap/prop.gi

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,3 +784,165 @@ function(D)
784784
fi;
785785
od;
786786
end);
787+
788+
InstallMethod(IsCograph,
789+
"for an immutable digraph",
790+
[IsImmutableDigraph],
791+
function(D)
792+
local V, P, x, N, parts, unused_parts, p, C, k, y, M, X, X_a,
793+
used_pivots, C_origin, po, lpart, rpart, zl, zr, origin,
794+
sigma, posx, prec, succ, N_prec, N_succ;
795+
796+
# D must be a symmetric digraph without loops or multiple edges
797+
if not IsSymmetricDigraph(D) then;
798+
Error("IsCograph: argument must be a symmetric digraph");
799+
elif DigraphHasLoops(D) then;
800+
Error("IsCograph: argument must be a digraph without loops");
801+
elif IsMultiDigraph(D) then;
802+
Error("IsCograph: argument must be a digraph without multiple edges");
803+
fi;
804+
805+
V := DigraphVertices(D);
806+
807+
if Length(V) < 4 then
808+
return true;
809+
fi;
810+
811+
P := [V];
812+
used_pivots := [];
813+
origin := V[1];
814+
815+
# If origin is an isolated or universal vertex, then recurse
816+
# on D[V \ {origin}]
817+
if Length(OutNeighboursOfVertex(D, origin)) = 0 or
818+
Length(OutNeighboursOfVertex(D, origin)) = Length(V) - 1 then
819+
return IsCograph(DigraphRemoveVertex(D, origin));
820+
fi;
821+
822+
while not ForAll(P, p -> Length(p) <= 1) do
823+
C_origin := First(P, p -> origin in p);
824+
825+
# Initialise
826+
N := IntersectionSet(OutNeighboursOfVertex(D, origin), C_origin);
827+
parts := [N, [origin], Difference(C_origin, UnionSet(N, [origin]))];
828+
unused_parts := Filtered([parts[1], parts[3]], p -> Length(p) > 0);
829+
k := Position(P, C_origin);
830+
Remove(P, k);
831+
for p in parts do
832+
if Length(p) > 0 then
833+
Add(P, p, k);
834+
fi;
835+
od;
836+
837+
# Refine
838+
while Length(unused_parts) > 0 do
839+
C := unused_parts[1];
840+
y := C[1];
841+
Add(used_pivots, y);
842+
N := OutNeighboursOfVertex(D, y);
843+
M := Filtered(P, p -> Length(IntersectionSet(p, N)) > 0 and
844+
not IsSubset(N, p) and
845+
p <> C);
846+
for X in M do
847+
X_a := IntersectionSet(X, N);
848+
k := Position(P, X);
849+
Remove(P, k);
850+
Add(P, X_a, k);
851+
Add(P, Difference(X, X_a), k);
852+
if X in unused_parts then
853+
Remove(unused_parts, Position(unused_parts, X));
854+
Add(unused_parts, X_a);
855+
Add(unused_parts, Difference(X, X_a));
856+
else
857+
x := IntersectionSet(used_pivots, X)[1];
858+
if x in X_a then
859+
Add(unused_parts, Difference(X, X_a));
860+
else
861+
Add(unused_parts, X_a);
862+
fi;
863+
fi;
864+
od;
865+
Remove(unused_parts, Position(unused_parts, C));
866+
od;
867+
868+
# Choose new origin
869+
lpart := [];
870+
rpart := [];
871+
po := Position(P, [origin]);
872+
for p in P{[1 .. po - 1]} do
873+
if Length(p) > 1 then
874+
Add(lpart, p);
875+
fi;
876+
od;
877+
for p in P{[po + 1 .. Length(P)]} do
878+
if Length(p) > 1 then
879+
Add(rpart, p);
880+
fi;
881+
od;
882+
883+
if IsEmpty(lpart) then
884+
if IsEmpty(rpart) then
885+
continue;
886+
fi;
887+
origin := IntersectionSet(used_pivots, rpart[1])[1];
888+
elif IsEmpty(rpart) then
889+
origin := IntersectionSet(used_pivots, Last(lpart))[1];
890+
else
891+
zl := IntersectionSet(used_pivots, Last(lpart))[1];
892+
zr := IntersectionSet(used_pivots, rpart[1])[1];
893+
if zl in OutNeighboursOfVertex(D, zr) then
894+
origin := zl;
895+
else
896+
origin := zr;
897+
fi;
898+
fi;
899+
od;
900+
901+
# Recognition Test
902+
sigma := [0];
903+
for p in P do
904+
Add(sigma, p[1]);
905+
od;
906+
Add(sigma, Length(V) + 1);
907+
908+
# move left to right
909+
posx := 2;
910+
x := sigma[posx];
911+
while x <> Length(V) + 1 do
912+
# calculate neighbours of x, predecessor and successor
913+
prec := sigma[posx - 1];
914+
succ := sigma[posx + 1];
915+
N := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, x));
916+
# deal with cases where predecessor or successor is a marker
917+
if prec <> 0 then
918+
N_prec := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, prec));
919+
else
920+
N_prec := [0];
921+
fi;
922+
if succ <> Length(V) + 1 then
923+
N_succ := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, succ));
924+
else
925+
N_succ := [0];
926+
fi;
927+
# if x shares a neighbourhood with predecessor or successor,
928+
# remove the predecessor and move right
929+
if N = N_prec or Union(N, [x]) = Union(N_prec, [prec]) then
930+
Remove(sigma, posx - 1);
931+
posx := posx - 1;
932+
elif N = N_succ or Union(N, [x]) = Union(N_succ, [succ]) then
933+
Remove(sigma, posx);
934+
x := succ;
935+
else
936+
posx := posx + 1;
937+
x := succ;
938+
fi;
939+
od;
940+
# continue until we hit end marker
941+
# if only markers remain, G is a cograph
942+
return Length(Difference(sigma, [0, Length(V) + 1])) = 1;
943+
end);
944+
945+
InstallMethod(IsCograph,
946+
"for a mutable digraph",
947+
[IsMutableDigraph],
948+
D -> IsCograph(DigraphImmutableCopy(D)));

tst/standard/prop.tst

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#@local D, DigraphNrVertices, DigraphRange, DigraphSource, DigraphVertices, G
1313
#@local M3, M5, N5, adj, circuit, complete100, comps, g, g1, g2, g3, g4, g5, g6
1414
#@local gr, gr1, gr2, gr3, gr4, gr5, gr6, grid, i, id, j, loop, mat, multiple
15-
#@local nottrans, r, range, source, trans
15+
#@local mut, nottrans, r, range, source, trans
1616
gap> START_TEST("Digraphs package: standard/prop.tst");
1717
gap> LoadPackage("digraphs", false);;
1818

@@ -1274,6 +1274,69 @@ false
12741274
gap> g;
12751275
<mutable empty digraph with 10 vertices>
12761276

1277+
# IsCograph
1278+
gap> g := DigraphByEdges([[1, 2], [2, 3]]);
1279+
<immutable digraph with 3 vertices, 2 edges>
1280+
gap> IsCograph(g);
1281+
Error, IsCograph: argument must be a symmetric digraph
1282+
gap> g := DigraphByEdges([[1, 2], [2, 1], [1, 1]]);
1283+
<immutable digraph with 2 vertices, 3 edges>
1284+
gap> IsCograph(g);
1285+
Error, IsCograph: argument must be a digraph without loops
1286+
gap> g := DigraphByEdges([[1, 2], [1, 2], [2, 1], [2, 1]]);
1287+
<immutable multidigraph with 2 vertices, 4 edges>
1288+
gap> IsCograph(g);
1289+
Error, IsCograph: argument must be a digraph without multiple edges
1290+
gap> g := Digraph([]);
1291+
<immutable empty digraph with 0 vertices>
1292+
gap> IsCograph(g);
1293+
true
1294+
gap> g := Digraph([[]]);
1295+
<immutable empty digraph with 1 vertex>
1296+
gap> IsCograph(g);
1297+
true
1298+
gap> g := EmptyDigraph(10);
1299+
<immutable empty digraph with 10 vertices>
1300+
gap> IsCograph(g);
1301+
true
1302+
gap> g := DigraphRemoveLoops(CompleteDigraph(10));
1303+
<immutable digraph with 10 vertices, 90 edges>
1304+
gap> IsCograph(g);
1305+
true
1306+
gap> g := DigraphRemoveLoops(DigraphSymmetricClosure(CycleDigraph(4)));
1307+
<immutable digraph with 4 vertices, 8 edges>
1308+
gap> IsCograph(g);
1309+
true
1310+
gap> g := DigraphRemoveLoops(DigraphSymmetricClosure(CycleDigraph(5)));
1311+
<immutable digraph with 5 vertices, 10 edges>
1312+
gap> IsCograph(g);
1313+
false
1314+
gap> g := DigraphSymmetricClosure(DigraphByEdges([[1, 2], [2, 3], [3, 4]]));
1315+
<immutable symmetric digraph with 4 vertices, 6 edges>
1316+
gap> IsCograph(g);
1317+
false
1318+
gap> g := DigraphFromGraph6String("HtilDEa");
1319+
<immutable symmetric digraph with 9 vertices, 34 edges>
1320+
gap> IsCograph(g);
1321+
true
1322+
gap> g := DigraphFromGraph6String("K~zf~z|~Vy~i");
1323+
<immutable symmetric digraph with 12 vertices, 108 edges>
1324+
gap> IsCograph(g);
1325+
true
1326+
gap> D := DigraphFromGraph6String("L~~v~z|~Vz~m~[");
1327+
<immutable symmetric digraph with 13 vertices, 134 edges>
1328+
gap> IsCograph(D);
1329+
true
1330+
gap> D := DigraphFromGraph6String("L~~vffr{~f}[{x");
1331+
<immutable symmetric digraph with 13 vertices, 118 edges>
1332+
gap> IsCograph(D);
1333+
true
1334+
gap> mut := DigraphMutableCopy(D);;
1335+
gap> IsCograph(mut);
1336+
true
1337+
gap> mut = D;
1338+
true
1339+
12771340
# IsJoinSemilatticeDigraph, IsMeetSemilatticeDigraph, and IsLatticeDigraph
12781341
gap> gr := Digraph([[1, 2], [2]]);
12791342
<immutable digraph with 2 vertices, 3 edges>

0 commit comments

Comments
 (0)