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]]);