Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
73757f2
Working IsCograph code
coraaked Dec 3, 2025
888fe0f
gaplint flagged changes
coraaked Dec 3, 2025
63f151e
Fix further gaplint flagged errors and comments
coraaked Dec 3, 2025
e94f027
Fix whitespace issues and add further comments
coraaked Dec 3, 2025
c91bd5b
indentation issues fixed
coraaked Dec 3, 2025
21387de
Change j -> Minimum(j) to 'minimum'
coraaked Dec 3, 2025
c6e970a
remove unused local variable
coraaked Dec 3, 2025
17f0a7e
Add IsCograph to prop.gi and prop.gd
frankiegillis Feb 5, 2026
51fb642
Merge branch 'main' of github.com:digraphs/Digraphs into cographs-new
frankiegillis Mar 5, 2026
350f224
Fixed bugs in IsCograph
frankiegillis Mar 5, 2026
ff97332
Added tests for IsCograph
frankiegillis Mar 5, 2026
4a93c81
Added doc for IsCograph
frankiegillis Mar 5, 2026
4d475cd
Delete IsCograph.g
frankiegillis Mar 5, 2026
038ce5c
Removed trailing whitespace
frankiegillis Mar 5, 2026
aee7e14
Merge branch 'IsCograph' of github.com:frankiegillis/Digraphs into Is…
frankiegillis Mar 5, 2026
1a5c1e3
Fixed linting issues
frankiegillis Mar 5, 2026
7fb0733
Added further tests for CodeCov
frankiegillis Mar 5, 2026
e214314
Did not, in fact, fix linting
frankiegillis Mar 5, 2026
41b56e3
The linting strikes back
frankiegillis Mar 5, 2026
0ba330c
Corrected reference for algorithm in IsCograph
frankiegillis Mar 10, 2026
1ec1012
Fixed line too long in prop.xml
frankiegillis Mar 10, 2026
387ab52
Fix minor doc issues
mtorpey Mar 18, 2026
c628bd7
Optimisations for IsCograph
mtorpey Mar 18, 2026
21a42b7
IsCograph for mutable digraphs
mtorpey Mar 18, 2026
93b8cbf
Merge pull request #1 from mtorpey/IsCograph
frankiegillis Mar 19, 2026
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
15 changes: 15 additions & 0 deletions doc/digraphs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions doc/prop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,50 @@ true
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="IsCograph">
<ManSection>
<Prop Name="IsCograph" Arg="digraph"/>
<Returns><K>true</K> or <K>false</K>.</Returns>
<Description>
If <A>digraph</A> is a symmetric digraph without loops or multiple edges, then the
property returns true if <A>digraph</A> is a cograph, and false if it is not.
<P/>

A symmetric digraph without loops or multiple edges is called a <E>cograph</E>
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.
<P/>

This function implements the algorithm for cograph recogntition given in
<Cite Key="HP05"/>.
<P/>

&MUTABLE_RECOMPUTED_PROP;

<Example><![CDATA[
gap> D := DigraphByEdges([[1, 2], [2, 3], [3, 4], [4, 1]]);
<immutable digraph with 4 vertices, 4 edges>
gap> D := DigraphSymmetricClosure(D);
<immutable symmetric digraph with 4 vertices, 8 edges>
gap> IsCograph(D);
true
gap> D := DigraphByEdges([[1, 2], [2, 3], [3, 4]]);
<immutable digraph with 4 vertices, 3 edges>
gap> D := DigraphSymmetricClosure(D);
<immutable symmetric digraph with 4 vertices, 6 edges>
gap> IsCograph(D);
false
gap> D := CompleteDigraph(5);
<immutable complete digraph with 5 vertices>
gap> IsCograph(D);
true
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="IsFunctionalDigraph">
<ManSection>
<Prop Name="IsFunctionalDigraph" Arg="digraph"/>
Expand Down
1 change: 1 addition & 0 deletions doc/z-chap5.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<#Include Label="IsEdgeTransitive">
<#Include Label="IsVertexTransitive">
<#Include Label="Is2EdgeTransitive">
<#Include Label="IsCograph">
</Section>


Expand Down
1 change: 1 addition & 0 deletions gap/prop.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
162 changes: 162 additions & 0 deletions gap/prop.gi
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
65 changes: 64 additions & 1 deletion tst/standard/prop.tst
Original file line number Diff line number Diff line change
Expand Up @@ -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);;

Expand Down Expand Up @@ -1274,6 +1274,69 @@ false
gap> g;
<mutable empty digraph with 10 vertices>

# IsCograph
gap> g := DigraphByEdges([[1, 2], [2, 3]]);
<immutable digraph with 3 vertices, 2 edges>
gap> IsCograph(g);
Error, IsCograph: argument must be a symmetric digraph
gap> g := DigraphByEdges([[1, 2], [2, 1], [1, 1]]);
<immutable digraph with 2 vertices, 3 edges>
gap> IsCograph(g);
Error, IsCograph: argument must be a digraph without loops
gap> g := DigraphByEdges([[1, 2], [1, 2], [2, 1], [2, 1]]);
<immutable multidigraph with 2 vertices, 4 edges>
gap> IsCograph(g);
Error, IsCograph: argument must be a digraph without multiple edges
gap> g := Digraph([]);
<immutable empty digraph with 0 vertices>
gap> IsCograph(g);
true
gap> g := Digraph([[]]);
<immutable empty digraph with 1 vertex>
gap> IsCograph(g);
true
gap> g := EmptyDigraph(10);
<immutable empty digraph with 10 vertices>
gap> IsCograph(g);
true
gap> g := DigraphRemoveLoops(CompleteDigraph(10));
<immutable digraph with 10 vertices, 90 edges>
gap> IsCograph(g);
true
gap> g := DigraphRemoveLoops(DigraphSymmetricClosure(CycleDigraph(4)));
<immutable digraph with 4 vertices, 8 edges>
gap> IsCograph(g);
true
gap> g := DigraphRemoveLoops(DigraphSymmetricClosure(CycleDigraph(5)));
<immutable digraph with 5 vertices, 10 edges>
gap> IsCograph(g);
false
gap> g := DigraphSymmetricClosure(DigraphByEdges([[1, 2], [2, 3], [3, 4]]));
<immutable symmetric digraph with 4 vertices, 6 edges>
gap> IsCograph(g);
false
gap> g := DigraphFromGraph6String("HtilDEa");
<immutable symmetric digraph with 9 vertices, 34 edges>
gap> IsCograph(g);
true
gap> g := DigraphFromGraph6String("K~zf~z|~Vy~i");
<immutable symmetric digraph with 12 vertices, 108 edges>
gap> IsCograph(g);
true
gap> D := DigraphFromGraph6String("L~~v~z|~Vz~m~[");
<immutable symmetric digraph with 13 vertices, 134 edges>
gap> IsCograph(D);
true
gap> D := DigraphFromGraph6String("L~~vffr{~f}[{x");
<immutable symmetric digraph with 13 vertices, 118 edges>
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]]);
<immutable digraph with 2 vertices, 3 edges>
Expand Down
Loading