diff --git a/doc/digraphs.bib b/doc/digraphs.bib
index a574870c5..4896387d7 100644
--- a/doc/digraphs.bib
+++ b/doc/digraphs.bib
@@ -82,6 +82,21 @@ @article{Gab00
Bdsk-Url-1 = {https://www.sciencedirect.com/science/article/pii/S002001900000051X},
}
+@article{HP05,
+ title = {A simple linear time algorithm for cograph recognition},
+ journal = {Discrete Applied Mathematics},
+ volume = {145},
+ number = {2},
+ pages = {183-197},
+ year = {2005},
+ note = {Structural Decompositions, Width Parameters, and Graph Labelings},
+ issn = {0166-218X},
+ doi = {https://doi.org/10.1016/j.dam.2004.01.011},
+ url = {https://www.sciencedirect.com/science/article/pii/S0166218X04002446},
+ author = {Michel Habib and Christophe Paul},
+ keywords = {Modular decomposition, Graphs, Algorithms},
+}
+
@inproceedings{JK07,
Author = {Tommi Junttila and Petteri Kaski},
Booktitle = {Proceedings of the Ninth Workshop on Algorithm Engineering and
diff --git a/doc/prop.xml b/doc/prop.xml
index 38fff9e32..8a7b39790 100644
--- a/doc/prop.xml
+++ b/doc/prop.xml
@@ -875,6 +875,50 @@ true
<#/GAPDoc>
+<#GAPDoc Label="IsCograph">
+
+
+ true or false.
+
+ If digraph is a symmetric digraph without loops or multiple edges, then the
+ property returns true if digraph is a cograph, and false if it is not.
+
+
+ A symmetric digraph without loops or multiple edges is called a cograph
+ if it does not contain a copy of the path graph on four vertices as
+ an induced subgraph. Equivalently, cographs are those graphs which may be
+ reached starting from a single vertex under the operations of series and parallel
+ composition.
+
+
+ This function implements the algorithm for cograph recogntition given in
+ .
+
+
+ &MUTABLE_RECOMPUTED_PROP;
+
+ D := DigraphByEdges([[1, 2], [2, 3], [3, 4], [4, 1]]);
+
+gap> D := DigraphSymmetricClosure(D);
+
+gap> IsCograph(D);
+true
+gap> D := DigraphByEdges([[1, 2], [2, 3], [3, 4]]);
+
+gap> D := DigraphSymmetricClosure(D);
+
+gap> IsCograph(D);
+false
+gap> D := CompleteDigraph(5);
+
+gap> IsCograph(D);
+true
+]]>
+
+
+<#/GAPDoc>
+
<#GAPDoc Label="IsFunctionalDigraph">
diff --git a/doc/z-chap5.xml b/doc/z-chap5.xml
index 21576bc93..b97a694b3 100644
--- a/doc/z-chap5.xml
+++ b/doc/z-chap5.xml
@@ -81,6 +81,7 @@
<#Include Label="IsEdgeTransitive">
<#Include Label="IsVertexTransitive">
<#Include Label="Is2EdgeTransitive">
+ <#Include Label="IsCograph">
diff --git a/gap/prop.gd b/gap/prop.gd
index 5ef95bbd0..808d67dc7 100644
--- a/gap/prop.gd
+++ b/gap/prop.gd
@@ -54,6 +54,7 @@ DeclareProperty("IsPermutationDigraph", IsDigraph);
DeclareProperty("IsDistributiveLatticeDigraph", IsDigraph);
DeclareProperty("IsModularLatticeDigraph", IsDigraph);
DeclareProperty("Is2EdgeTransitive", IsDigraph);
+DeclareProperty("IsCograph", IsDigraph);
DeclareSynonymAttr("IsLatticeDigraph",
IsMeetSemilatticeDigraph and IsJoinSemilatticeDigraph);
DeclareSynonymAttr("IsPreorderDigraph",
diff --git a/gap/prop.gi b/gap/prop.gi
index 3f4783243..0c564ef17 100644
--- a/gap/prop.gi
+++ b/gap/prop.gi
@@ -784,3 +784,165 @@ function(D)
fi;
od;
end);
+
+InstallMethod(IsCograph,
+"for an immutable digraph",
+[IsImmutableDigraph],
+function(D)
+ local V, P, x, N, parts, unused_parts, p, C, k, y, M, X, X_a,
+ used_pivots, C_origin, po, lpart, rpart, zl, zr, origin,
+ sigma, posx, prec, succ, N_prec, N_succ;
+
+ # D must be a symmetric digraph without loops or multiple edges
+ if not IsSymmetricDigraph(D) then;
+ Error("IsCograph: argument must be a symmetric digraph");
+ elif DigraphHasLoops(D) then;
+ Error("IsCograph: argument must be a digraph without loops");
+ elif IsMultiDigraph(D) then;
+ Error("IsCograph: argument must be a digraph without multiple edges");
+ fi;
+
+ V := DigraphVertices(D);
+
+ if Length(V) < 4 then
+ return true;
+ fi;
+
+ P := [V];
+ used_pivots := [];
+ origin := V[1];
+
+ # If origin is an isolated or universal vertex, then recurse
+ # on D[V \ {origin}]
+ if Length(OutNeighboursOfVertex(D, origin)) = 0 or
+ Length(OutNeighboursOfVertex(D, origin)) = Length(V) - 1 then
+ return IsCograph(DigraphRemoveVertex(D, origin));
+ fi;
+
+ while not ForAll(P, p -> Length(p) <= 1) do
+ C_origin := First(P, p -> origin in p);
+
+ # Initialise
+ N := IntersectionSet(OutNeighboursOfVertex(D, origin), C_origin);
+ parts := [N, [origin], Difference(C_origin, UnionSet(N, [origin]))];
+ unused_parts := Filtered([parts[1], parts[3]], p -> Length(p) > 0);
+ k := Position(P, C_origin);
+ Remove(P, k);
+ for p in parts do
+ if Length(p) > 0 then
+ Add(P, p, k);
+ fi;
+ od;
+
+ # Refine
+ while Length(unused_parts) > 0 do
+ C := unused_parts[1];
+ y := C[1];
+ Add(used_pivots, y);
+ N := OutNeighboursOfVertex(D, y);
+ M := Filtered(P, p -> Length(IntersectionSet(p, N)) > 0 and
+ not IsSubset(N, p) and
+ p <> C);
+ for X in M do
+ X_a := IntersectionSet(X, N);
+ k := Position(P, X);
+ Remove(P, k);
+ Add(P, X_a, k);
+ Add(P, Difference(X, X_a), k);
+ if X in unused_parts then
+ Remove(unused_parts, Position(unused_parts, X));
+ Add(unused_parts, X_a);
+ Add(unused_parts, Difference(X, X_a));
+ else
+ x := IntersectionSet(used_pivots, X)[1];
+ if x in X_a then
+ Add(unused_parts, Difference(X, X_a));
+ else
+ Add(unused_parts, X_a);
+ fi;
+ fi;
+ od;
+ Remove(unused_parts, Position(unused_parts, C));
+ od;
+
+ # Choose new origin
+ lpart := [];
+ rpart := [];
+ po := Position(P, [origin]);
+ for p in P{[1 .. po - 1]} do
+ if Length(p) > 1 then
+ Add(lpart, p);
+ fi;
+ od;
+ for p in P{[po + 1 .. Length(P)]} do
+ if Length(p) > 1 then
+ Add(rpart, p);
+ fi;
+ od;
+
+ if IsEmpty(lpart) then
+ if IsEmpty(rpart) then
+ continue;
+ fi;
+ origin := IntersectionSet(used_pivots, rpart[1])[1];
+ elif IsEmpty(rpart) then
+ origin := IntersectionSet(used_pivots, Last(lpart))[1];
+ else
+ zl := IntersectionSet(used_pivots, Last(lpart))[1];
+ zr := IntersectionSet(used_pivots, rpart[1])[1];
+ if zl in OutNeighboursOfVertex(D, zr) then
+ origin := zl;
+ else
+ origin := zr;
+ fi;
+ fi;
+ od;
+
+ # Recognition Test
+ sigma := [0];
+ for p in P do
+ Add(sigma, p[1]);
+ od;
+ Add(sigma, Length(V) + 1);
+
+ # move left to right
+ posx := 2;
+ x := sigma[posx];
+ while x <> Length(V) + 1 do
+ # calculate neighbours of x, predecessor and successor
+ prec := sigma[posx - 1];
+ succ := sigma[posx + 1];
+ N := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, x));
+ # deal with cases where predecessor or successor is a marker
+ if prec <> 0 then
+ N_prec := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, prec));
+ else
+ N_prec := [0];
+ fi;
+ if succ <> Length(V) + 1 then
+ N_succ := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, succ));
+ else
+ N_succ := [0];
+ fi;
+ # if x shares a neighbourhood with predecessor or successor,
+ # remove the predecessor and move right
+ if N = N_prec or Union(N, [x]) = Union(N_prec, [prec]) then
+ Remove(sigma, posx - 1);
+ posx := posx - 1;
+ elif N = N_succ or Union(N, [x]) = Union(N_succ, [succ]) then
+ Remove(sigma, posx);
+ x := succ;
+ else
+ posx := posx + 1;
+ x := succ;
+ fi;
+ od;
+ # continue until we hit end marker
+ # if only markers remain, G is a cograph
+ return Length(Difference(sigma, [0, Length(V) + 1])) = 1;
+end);
+
+InstallMethod(IsCograph,
+"for a mutable digraph",
+[IsMutableDigraph],
+D -> IsCograph(DigraphImmutableCopy(D)));
diff --git a/tst/standard/prop.tst b/tst/standard/prop.tst
index 7a5520ad1..623ff6713 100644
--- a/tst/standard/prop.tst
+++ b/tst/standard/prop.tst
@@ -12,7 +12,7 @@
#@local D, DigraphNrVertices, DigraphRange, DigraphSource, DigraphVertices, G
#@local M3, M5, N5, adj, circuit, complete100, comps, g, g1, g2, g3, g4, g5, g6
#@local gr, gr1, gr2, gr3, gr4, gr5, gr6, grid, i, id, j, loop, mat, multiple
-#@local nottrans, r, range, source, trans
+#@local mut, nottrans, r, range, source, trans
gap> START_TEST("Digraphs package: standard/prop.tst");
gap> LoadPackage("digraphs", false);;
@@ -1274,6 +1274,69 @@ false
gap> g;
+# IsCograph
+gap> g := DigraphByEdges([[1, 2], [2, 3]]);
+
+gap> IsCograph(g);
+Error, IsCograph: argument must be a symmetric digraph
+gap> g := DigraphByEdges([[1, 2], [2, 1], [1, 1]]);
+
+gap> IsCograph(g);
+Error, IsCograph: argument must be a digraph without loops
+gap> g := DigraphByEdges([[1, 2], [1, 2], [2, 1], [2, 1]]);
+
+gap> IsCograph(g);
+Error, IsCograph: argument must be a digraph without multiple edges
+gap> g := Digraph([]);
+
+gap> IsCograph(g);
+true
+gap> g := Digraph([[]]);
+
+gap> IsCograph(g);
+true
+gap> g := EmptyDigraph(10);
+
+gap> IsCograph(g);
+true
+gap> g := DigraphRemoveLoops(CompleteDigraph(10));
+
+gap> IsCograph(g);
+true
+gap> g := DigraphRemoveLoops(DigraphSymmetricClosure(CycleDigraph(4)));
+
+gap> IsCograph(g);
+true
+gap> g := DigraphRemoveLoops(DigraphSymmetricClosure(CycleDigraph(5)));
+
+gap> IsCograph(g);
+false
+gap> g := DigraphSymmetricClosure(DigraphByEdges([[1, 2], [2, 3], [3, 4]]));
+
+gap> IsCograph(g);
+false
+gap> g := DigraphFromGraph6String("HtilDEa");
+
+gap> IsCograph(g);
+true
+gap> g := DigraphFromGraph6String("K~zf~z|~Vy~i");
+
+gap> IsCograph(g);
+true
+gap> D := DigraphFromGraph6String("L~~v~z|~Vz~m~[");
+
+gap> IsCograph(D);
+true
+gap> D := DigraphFromGraph6String("L~~vffr{~f}[{x");
+
+gap> IsCograph(D);
+true
+gap> mut := DigraphMutableCopy(D);;
+gap> IsCograph(mut);
+true
+gap> mut = D;
+true
+
# IsJoinSemilatticeDigraph, IsMeetSemilatticeDigraph, and IsLatticeDigraph
gap> gr := Digraph([[1, 2], [2]]);