From 73757f276c5150ecbaf2df4e0d6808a5a8c6e6f4 Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 15:57:03 +0000 Subject: [PATCH 01/22] Working IsCograph code --- IsCograph.g | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 IsCograph.g diff --git a/IsCograph.g b/IsCograph.g new file mode 100644 index 000000000..365d82c76 --- /dev/null +++ b/IsCograph.g @@ -0,0 +1,178 @@ +# Function that identifies a cograph from a symmetric digraph +# Created from the algorithm described in the paper "A Simple Linear Time Recognition Algorithm for Cographs" +# Habib, M & Paul, C & Viennot (2005). A Simple Linear Time Recognition Algorithm for Cographs. Discrete Applied Mathematics. 145(2). 183-197. https://doi.org/10.1016/j.dam.2004.01.011. + +IsCograph := function(D) + local verts, P, origin, adj, part, neighbours, n_x, + used_parts, unused_parts, unused_parts_refined, + k, y, N_y, M, p, m, ma, j,n,v, + zl, zr, prevorigin, new_P, t, current_part, zrpart, pivot, + upd_part, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, N_succz, options, list, subpart; + + # input must be symmetric + if not IsSymmetricDigraph(D) then; + Error("IsCograph: argument must be a symmetric digraph"); + fi; + + verts := DigraphVertices(D); + P := [verts]; + + # a graph with fewer than 4 vertices cannot contain a P4 graph + if Length(verts) < 4 then + return true; + fi; + + origin := 1; + adj := OutNeighboursOfVertex(D, origin); + if Length(adj) = 0 or Length(adj) = Length(verts) - 1 then + return IsCograph(InducedSubdigraph(D, Filtered(verts, v -> v <> origin))); + fi; + + # Algorithm 3: Partition Refinement + while ForAll(P, part -> Length(part) <= 1) = false do + k := PositionProperty(P, part -> origin in part); + if Length(P[k]) > 1 then + part := Remove(P, k); + neighbours := OutNeighboursOfVertex(D, origin); + part := [Filtered(neighbours, p -> p in part), [origin], Difference(part, Union([origin], neighbours))]; + unused_parts := [part[1], part[3]]; + used_parts := [origin]; + for p in Filtered(part, u -> u <> []) do + Add(P, p, k); + od; + fi; + + # Procedure 3 + new_P := ShallowCopy(P); + if ForAll(new_P, part -> Length(part) <= 1) = true then + break; + fi; + + while Length(Filtered(unused_parts, u -> u <> [])) > 0 do + options := Filtered(unused_parts, part -> Length(part) > 0); + list := List(options, j -> Minimum(j)); + subpart := unused_parts[Position(list, Minimum(list))]; + + if Filtered(subpart, u -> u in used_parts) = [] then + pivot := Minimum(subpart); + else + pivot := subpart[part -> p in used_parts][1]; + fi; + + # Procedure 4 + M := []; + current_part := ShallowCopy(new_P[PositionProperty(new_P, part -> pivot in part)]); + pivotset := OutNeighboursOfVertex(D, pivot); + + for p in Difference(new_P, [current_part]) do + if Intersection(p, pivotset) <> [] and Intersection(p, pivotset) <> p and Intersection(p, pivotset) <> [origin] then + k := ShallowCopy(Position(new_P, p)); + Remove(new_P, k); + Add(M, p); + fi; + od; + + if M <> [] then + for m in M do + ma := Filtered(m, p -> p in pivotset); + upd_m := [ma, Difference(m, ma)]; + for t in Filtered(upd_m, x -> x <> []) do + Add(new_P, t, k); + od; + if m in unused_parts then + Remove(unused_parts, Position(unused_parts, m)); + if not ma in unused_parts and ma <> [] then + Add(unused_parts, ma); + fi; + if not Difference(m, ma) in unused_parts and Difference(m,ma) <> [] then + Add(unused_parts, Difference(m, ma)); + fi; + else + if Minimum(m) in upd_m[1] then + Add(unused_parts, upd_m[2]); + else + Add(unused_parts, upd_m[1]); + fi; + fi; + Add(used_parts, m); + Add(used_parts, pivot); + od; + fi; + if current_part in unused_parts then + Remove(unused_parts, Position(unused_parts, current_part)); + fi; + Add(used_parts, current_part); + od; + + P := ShallowCopy(new_P); + prevorigin := ShallowCopy(origin); + + zlpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) > Position(P, part)); + zrpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) < Position(P, part)); + + if zlpart = fail or zrpart = fail then + if zlpart = fail and zrpart = fail then + continue; + elif zrpart = fail then + zl := ShallowCopy(Minimum(P[zlpart])); + origin := ShallowCopy(zl); + else + zr := ShallowCopy(Minimum(P[zrpart])); + origin := ShallowCopy(zr); + fi; + else + zl := ShallowCopy(Minimum(P[zlpart])); + zr := ShallowCopy(Minimum(P[zrpart])); + if zl in OutNeighboursOfVertex(D, zr) then + origin := ShallowCopy(zl); + else + origin := ShallowCopy(zr); + fi; + fi; + od; + + # Algorithm 5: Recognition Test + + sigma := [0]; + for p in P do + for v in p do + Add(sigma, v); + od; + od; + Add(sigma, Length(verts) + 1); + + z := sigma[2]; + + while z <> Length(verts) + 1 do + succz := sigma[Position(sigma, z) + 1]; + precz := sigma[Position(sigma, z) - 1]; + N_z := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, z)); + if precz <> 0 then + N_precz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, precz)); + else + N_precz := [0]; + fi; + + if succz <> Length(verts) + 1 then + N_succz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, succz)); + else + N_succz := [0]; + fi; + + if N_z = N_precz or Union(N_z, [z]) = Union(N_precz, [precz]) then + Remove(sigma, Position(sigma, precz)); + elif N_z = N_succz or Union(N_z, [z]) = Union(N_succz, [succz]) then + z := succz; + Remove(sigma, Position(sigma, precz) + 1); + else + z := succz; + fi; + od; + + if Length(Difference(sigma, [0, Length(verts)+1])) = 1 then + return true; + else + return false; + fi; + +end; \ No newline at end of file From 888fe0f1ffd22412752a8a3fe4c38d09a6ed57a6 Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 16:44:39 +0000 Subject: [PATCH 02/22] gaplint flagged changes --- IsCograph.g | 65 ++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/IsCograph.g b/IsCograph.g index 365d82c76..0904c84cb 100644 --- a/IsCograph.g +++ b/IsCograph.g @@ -1,13 +1,17 @@ # Function that identifies a cograph from a symmetric digraph -# Created from the algorithm described in the paper "A Simple Linear Time Recognition Algorithm for Cographs" -# Habib, M & Paul, C & Viennot (2005). A Simple Linear Time Recognition Algorithm for Cographs. Discrete Applied Mathematics. 145(2). 183-197. https://doi.org/10.1016/j.dam.2004.01.011. + +# Created from the algorithm described in the paper "A Simple +# Linear Time Recognition Algorithm for Cographs" + +# Habib, M & Paul, C & Viennot (2005). A Simple Linear Time +# Recognition Algorithm for Cographs. Discrete Applied Mathematics. +# 145(2). 183-197. https://doi.org/10.1016/j.dam.2004.01.011. IsCograph := function(D) - local verts, P, origin, adj, part, neighbours, n_x, - used_parts, unused_parts, unused_parts_refined, - k, y, N_y, M, p, m, ma, j,n,v, - zl, zr, prevorigin, new_P, t, current_part, zrpart, pivot, - upd_part, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, N_succz, options, list, subpart; + local verts, P, origin, adj, part, neighbours, used_parts, unused_parts, + k, M, p, m, ma, j, n, v, zl, zr, prevorigin, new_P, t, current_part, zrpart, + pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, + N_succz, options, list, subpart; # input must be symmetric if not IsSymmetricDigraph(D) then; @@ -34,7 +38,8 @@ IsCograph := function(D) if Length(P[k]) > 1 then part := Remove(P, k); neighbours := OutNeighboursOfVertex(D, origin); - part := [Filtered(neighbours, p -> p in part), [origin], Difference(part, Union([origin], neighbours))]; + part := [Filtered(neighbours, p -> p in part), [origin], + Difference(part, Union([origin], neighbours))]; unused_parts := [part[1], part[3]]; used_parts := [origin]; for p in Filtered(part, u -> u <> []) do @@ -61,11 +66,14 @@ IsCograph := function(D) # Procedure 4 M := []; - current_part := ShallowCopy(new_P[PositionProperty(new_P, part -> pivot in part)]); + current_part := ShallowCopy(new_P[PositionProperty(new_P, + part -> pivot in part)]); pivotset := OutNeighboursOfVertex(D, pivot); for p in Difference(new_P, [current_part]) do - if Intersection(p, pivotset) <> [] and Intersection(p, pivotset) <> p and Intersection(p, pivotset) <> [origin] then + if Intersection(p, pivotset) <> [] and + Intersection(p, pivotset) <> p + and Intersection(p, pivotset) <> [origin] then k := ShallowCopy(Position(new_P, p)); Remove(new_P, k); Add(M, p); @@ -84,7 +92,8 @@ IsCograph := function(D) if not ma in unused_parts and ma <> [] then Add(unused_parts, ma); fi; - if not Difference(m, ma) in unused_parts and Difference(m,ma) <> [] then + if not Difference(m, ma) in unused_parts and + Difference(m,ma) <> [] then Add(unused_parts, Difference(m, ma)); fi; else @@ -105,10 +114,11 @@ IsCograph := function(D) od; P := ShallowCopy(new_P); - prevorigin := ShallowCopy(origin); - zlpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) > Position(P, part)); - zrpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) < Position(P, part)); + zlpart := PositionProperty(P, part -> Length(part) > 1 + and Position(P, [origin]) > Position(P, part)); + zrpart := PositionProperty(P, part -> Length(part) > 1 + and Position(P, [origin]) < Position(P, part)); if zlpart = fail or zrpart = fail then if zlpart = fail and zrpart = fail then @@ -140,39 +150,34 @@ IsCograph := function(D) od; od; Add(sigma, Length(verts) + 1); - z := sigma[2]; - while z <> Length(verts) + 1 do succz := sigma[Position(sigma, z) + 1]; precz := sigma[Position(sigma, z) - 1]; - N_z := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, z)); + N_z := Filtered(sigma, n -> n in + OutNeighboursOfVertex(D, z)); if precz <> 0 then - N_precz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, precz)); + N_precz := Filtered(sigma, n -> n in + OutNeighboursOfVertex(D, precz)); else N_precz := [0]; fi; - if succz <> Length(verts) + 1 then - N_succz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, succz)); + N_succz := Filtered(sigma, n -> n in + OutNeighboursOfVertex(D, succz)); else N_succz := [0]; fi; - - if N_z = N_precz or Union(N_z, [z]) = Union(N_precz, [precz]) then + if N_z = N_precz or Union(N_z, [z]) = + Union(N_precz, [precz]) then Remove(sigma, Position(sigma, precz)); - elif N_z = N_succz or Union(N_z, [z]) = Union(N_succz, [succz]) then + elif N_z = N_succz or Union(N_z, [z]) = + Union(N_succz, [succz]) then z := succz; Remove(sigma, Position(sigma, precz) + 1); else z := succz; fi; od; - - if Length(Difference(sigma, [0, Length(verts)+1])) = 1 then - return true; - else - return false; - fi; - + return Length(Difference(sigma, [0, Length(verts)+1])) = 1; end; \ No newline at end of file From 63f151e134574fec4a1cc8906b35e326bc1d9975 Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 17:19:24 +0000 Subject: [PATCH 03/22] Fix further gaplint flagged errors and comments --- IsCograph.g | 82 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/IsCograph.g b/IsCograph.g index 0904c84cb..0b0e17090 100644 --- a/IsCograph.g +++ b/IsCograph.g @@ -1,15 +1,15 @@ # Function that identifies a cograph from a symmetric digraph -# Created from the algorithm described in the paper "A Simple +# Created from the algorithm described in the paper "A Simple # Linear Time Recognition Algorithm for Cographs" -# Habib, M & Paul, C & Viennot (2005). A Simple Linear Time -# Recognition Algorithm for Cographs. Discrete Applied Mathematics. +# Habib, M & Paul, C & Viennot (2005). A Simple Linear Time +# Recognition Algorithm for Cographs. Discrete Applied Mathematics. # 145(2). 183-197. https://doi.org/10.1016/j.dam.2004.01.011. IsCograph := function(D) - local verts, P, origin, adj, part, neighbours, used_parts, unused_parts, - k, M, p, m, ma, j, n, v, zl, zr, prevorigin, new_P, t, current_part, zrpart, + local verts, P, origin, adj, part, neighbours, used_parts, unused_parts, + k, M, p, m, ma, j, n, v, zl, zr, new_P, t, current_part, zrpart, pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, N_succz, options, list, subpart; @@ -21,24 +21,27 @@ IsCograph := function(D) verts := DigraphVertices(D); P := [verts]; - # a graph with fewer than 4 vertices cannot contain a P4 graph + # a graph with fewer than 4 vertices cannot contain P4 graph if Length(verts) < 4 then return true; fi; + # set origin to be a non-isolated or universal vertex origin := 1; adj := OutNeighboursOfVertex(D, origin); if Length(adj) = 0 or Length(adj) = Length(verts) - 1 then - return IsCograph(InducedSubdigraph(D, Filtered(verts, v -> v <> origin))); + return IsCograph(InducedSubdigraph(D, Filtered(verts, v -> + v <> origin))); fi; - # Algorithm 3: Partition Refinement + # Algorithm 2: Partition Refinement + # while there exist non-singleton parts, refine using rule 1 while ForAll(P, part -> Length(part) <= 1) = false do k := PositionProperty(P, part -> origin in part); if Length(P[k]) > 1 then part := Remove(P, k); neighbours := OutNeighboursOfVertex(D, origin); - part := [Filtered(neighbours, p -> p in part), [origin], + part := [Filtered(neighbours, p -> p in part), [origin], Difference(part, Union([origin], neighbours))]; unused_parts := [part[1], part[3]]; used_parts := [origin]; @@ -49,10 +52,8 @@ IsCograph := function(D) # Procedure 3 new_P := ShallowCopy(P); - if ForAll(new_P, part -> Length(part) <= 1) = true then - break; - fi; - + # while we have unused parts, pick an unused part, set an unused pivot + # and refine with rule 2 using the neighbours of the pivot while Length(Filtered(unused_parts, u -> u <> [])) > 0 do options := Filtered(unused_parts, part -> Length(part) > 0); list := List(options, j -> Minimum(j)); @@ -66,20 +67,19 @@ IsCograph := function(D) # Procedure 4 M := []; - current_part := ShallowCopy(new_P[PositionProperty(new_P, + current_part := ShallowCopy(new_P[PositionProperty(new_P, part -> pivot in part)]); - pivotset := OutNeighboursOfVertex(D, pivot); + pivotset := OutNeighboursOfVertex(D, pivot); for p in Difference(new_P, [current_part]) do - if Intersection(p, pivotset) <> [] and - Intersection(p, pivotset) <> p + if Intersection(p, pivotset) <> [] and + Intersection(p, pivotset) <> p and Intersection(p, pivotset) <> [origin] then k := ShallowCopy(Position(new_P, p)); Remove(new_P, k); Add(M, p); fi; od; - if M <> [] then for m in M do ma := Filtered(m, p -> p in pivotset); @@ -93,7 +93,7 @@ IsCograph := function(D) Add(unused_parts, ma); fi; if not Difference(m, ma) in unused_parts and - Difference(m,ma) <> [] then + Difference(m, ma) <> [] then Add(unused_parts, Difference(m, ma)); fi; else @@ -110,39 +110,41 @@ IsCograph := function(D) if current_part in unused_parts then Remove(unused_parts, Position(unused_parts, current_part)); fi; - Add(used_parts, current_part); - od; - + Add(used_parts, current_part); + od; P := ShallowCopy(new_P); - - zlpart := PositionProperty(P, part -> Length(part) > 1 + # consider the pivots either side of origin + zlpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) > Position(P, part)); - zrpart := PositionProperty(P, part -> Length(part) > 1 + zrpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) < Position(P, part)); - + # if there is no part on rhs or lhs, consider + # only the existing ones if zlpart = fail or zrpart = fail then if zlpart = fail and zrpart = fail then continue; - elif zrpart = fail then + elif zrpart = fail then zl := ShallowCopy(Minimum(P[zlpart])); origin := ShallowCopy(zl); else zr := ShallowCopy(Minimum(P[zrpart])); origin := ShallowCopy(zr); fi; + # if both exist, if they are adjacent in G, set + # origin to be the left pivot, else the right pivot else zl := ShallowCopy(Minimum(P[zlpart])); zr := ShallowCopy(Minimum(P[zrpart])); if zl in OutNeighboursOfVertex(D, zr) then origin := ShallowCopy(zl); - else + else origin := ShallowCopy(zr); fi; fi; od; - - # Algorithm 5: Recognition Test + # Algorithm 5: Recognition Test + # add markers to either end of permutation sigma := [0]; for p in P do for v in p do @@ -150,14 +152,20 @@ IsCograph := function(D) od; od; Add(sigma, Length(verts) + 1); + + # move left to right z := sigma[2]; while z <> Length(verts) + 1 do + # calculate neighbours of z, predecessor and + # successor succz := sigma[Position(sigma, z) + 1]; precz := sigma[Position(sigma, z) - 1]; - N_z := Filtered(sigma, n -> n in + N_z := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, z)); + # deal with cases where predecessor or + # successor is a marker if precz <> 0 then - N_precz := Filtered(sigma, n -> n in + N_precz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, precz)); else N_precz := [0]; @@ -168,10 +176,12 @@ IsCograph := function(D) else N_succz := [0]; fi; - if N_z = N_precz or Union(N_z, [z]) = + # if z shares a neighbourhood with predecessor or successor, + # remove the predecessor and move right + if N_z = N_precz or Union(N_z, [z]) = Union(N_precz, [precz]) then Remove(sigma, Position(sigma, precz)); - elif N_z = N_succz or Union(N_z, [z]) = + elif N_z = N_succz or Union(N_z, [z]) = Union(N_succz, [succz]) then z := succz; Remove(sigma, Position(sigma, precz) + 1); @@ -179,5 +189,7 @@ IsCograph := function(D) z := succz; fi; od; - return Length(Difference(sigma, [0, Length(verts)+1])) = 1; + # continue until we hit end marker + # if only markers remain, G is a cograph + return Length(Difference(sigma, [0, Length(verts) + 1])) = 1; end; \ No newline at end of file From e94f027da1dda95fe755ef2418e725bed99c5edc Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 17:44:30 +0000 Subject: [PATCH 04/22] Fix whitespace issues and add further comments --- IsCograph.g | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/IsCograph.g b/IsCograph.g index 0b0e17090..03abcc88e 100644 --- a/IsCograph.g +++ b/IsCograph.g @@ -8,7 +8,7 @@ # 145(2). 183-197. https://doi.org/10.1016/j.dam.2004.01.011. IsCograph := function(D) - local verts, P, origin, adj, part, neighbours, used_parts, unused_parts, + local verts, P, origin, adj, part, used_parts, unused_parts, k, M, p, m, ma, j, n, v, zl, zr, new_P, t, current_part, zrpart, pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, N_succz, options, list, subpart; @@ -39,18 +39,18 @@ IsCograph := function(D) while ForAll(P, part -> Length(part) <= 1) = false do k := PositionProperty(P, part -> origin in part); if Length(P[k]) > 1 then - part := Remove(P, k); - neighbours := OutNeighboursOfVertex(D, origin); - part := [Filtered(neighbours, p -> p in part), [origin], - Difference(part, Union([origin], neighbours))]; + part := [Filtered(OutNeighboursOfVertex(D, origin), p -> p in P[k]), + [origin], Difference(P[k], Union([origin], OutNeighboursOfVertex + (D, origin)))]; unused_parts := [part[1], part[3]]; used_parts := [origin]; + Remove(P,k); for p in Filtered(part, u -> u <> []) do Add(P, p, k); od; fi; - # Procedure 3 + # Procedure 3 new_P := ShallowCopy(P); # while we have unused parts, pick an unused part, set an unused pivot # and refine with rule 2 using the neighbours of the pivot @@ -66,6 +66,8 @@ IsCograph := function(D) fi; # Procedure 4 + # M is the set of parts strictly intersected by the + # neighbourhood of the pivot M := []; current_part := ShallowCopy(new_P[PositionProperty(new_P, part -> pivot in part)]); @@ -80,22 +82,29 @@ IsCograph := function(D) Add(M, p); fi; od; + # if we have parts to refine, do so if M <> [] then + # for each part in M, split into those in pivot set + # and those not for m in M do ma := Filtered(m, p -> p in pivotset); upd_m := [ma, Difference(m, ma)]; for t in Filtered(upd_m, x -> x <> []) do Add(new_P, t, k); od; + # if our part is unused, mark the new parts as unused + # and mark this one as used if m in unused_parts then Remove(unused_parts, Position(unused_parts, m)); if not ma in unused_parts and ma <> [] then Add(unused_parts, ma); fi; - if not Difference(m, ma) in unused_parts and + if not Difference(m, ma) in unused_parts and Difference(m, ma) <> [] then Add(unused_parts, Difference(m, ma)); fi; + # otherwise the new subpart not containing the pivot + # is unused else if Minimum(m) in upd_m[1] then Add(unused_parts, upd_m[2]); @@ -111,9 +120,9 @@ IsCograph := function(D) Remove(unused_parts, Position(unused_parts, current_part)); fi; Add(used_parts, current_part); - od; + od; P := ShallowCopy(new_P); - # consider the pivots either side of origin + # consider the pivots either side of origin zlpart := PositionProperty(P, part -> Length(part) > 1 and Position(P, [origin]) > Position(P, part)); zrpart := PositionProperty(P, part -> Length(part) > 1 @@ -130,7 +139,7 @@ IsCograph := function(D) zr := ShallowCopy(Minimum(P[zrpart])); origin := ShallowCopy(zr); fi; - # if both exist, if they are adjacent in G, set + # if both exist, if they are adjacent in G, set # origin to be the left pivot, else the right pivot else zl := ShallowCopy(Minimum(P[zlpart])); @@ -156,14 +165,14 @@ IsCograph := function(D) # move left to right z := sigma[2]; while z <> Length(verts) + 1 do - # calculate neighbours of z, predecessor and - # successor + # calculate neighbours of z, predecessor and + # successor succz := sigma[Position(sigma, z) + 1]; precz := sigma[Position(sigma, z) - 1]; N_z := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, z)); - # deal with cases where predecessor or - # successor is a marker + # deal with cases where predecessor or + # successor is a marker if precz <> 0 then N_precz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, precz)); @@ -171,18 +180,18 @@ IsCograph := function(D) N_precz := [0]; fi; if succz <> Length(verts) + 1 then - N_succz := Filtered(sigma, n -> n in + N_succz := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, succz)); else N_succz := [0]; fi; - # if z shares a neighbourhood with predecessor or successor, - # remove the predecessor and move right + # if z shares a neighbourhood with predecessor or successor, + # remove the predecessor and move right if N_z = N_precz or Union(N_z, [z]) = - Union(N_precz, [precz]) then + Union(N_precz, [precz]) then Remove(sigma, Position(sigma, precz)); elif N_z = N_succz or Union(N_z, [z]) = - Union(N_succz, [succz]) then + Union(N_succz, [succz]) then z := succz; Remove(sigma, Position(sigma, precz) + 1); else From c91bd5b84bd4c708e3090af4f08f4bb7c7f85cec Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 17:49:47 +0000 Subject: [PATCH 05/22] indentation issues fixed --- IsCograph.g | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/IsCograph.g b/IsCograph.g index 03abcc88e..8bd9679c7 100644 --- a/IsCograph.g +++ b/IsCograph.g @@ -39,12 +39,14 @@ IsCograph := function(D) while ForAll(P, part -> Length(part) <= 1) = false do k := PositionProperty(P, part -> origin in part); if Length(P[k]) > 1 then - part := [Filtered(OutNeighboursOfVertex(D, origin), p -> p in P[k]), - [origin], Difference(P[k], Union([origin], OutNeighboursOfVertex - (D, origin)))]; + part := [Filtered(OutNeighboursOfVertex(D, origin), p -> p + in P[k]), [origin], Difference(P[k], Union([origin], + OutNeighboursOfVertex(D, origin)))]; + # make note of unused and used parts unused_parts := [part[1], part[3]]; used_parts := [origin]; - Remove(P,k); + # replace the used part with our new refinement + Remove(P, k); for p in Filtered(part, u -> u <> []) do Add(P, p, k); od; @@ -58,7 +60,8 @@ IsCograph := function(D) options := Filtered(unused_parts, part -> Length(part) > 0); list := List(options, j -> Minimum(j)); subpart := unused_parts[Position(list, Minimum(list))]; - + # pick our pivot to be either an unused vertex in the subpart, + # or if none exist, any vertex in the subpart if Filtered(subpart, u -> u in used_parts) = [] then pivot := Minimum(subpart); else @@ -72,7 +75,8 @@ IsCograph := function(D) current_part := ShallowCopy(new_P[PositionProperty(new_P, part -> pivot in part)]); pivotset := OutNeighboursOfVertex(D, pivot); - + # Add to M any part that is strictly intersected + # by pivotset for p in Difference(new_P, [current_part]) do if Intersection(p, pivotset) <> [] and Intersection(p, pivotset) <> p @@ -188,10 +192,10 @@ IsCograph := function(D) # if z shares a neighbourhood with predecessor or successor, # remove the predecessor and move right if N_z = N_precz or Union(N_z, [z]) = - Union(N_precz, [precz]) then + Union(N_precz, [precz]) then Remove(sigma, Position(sigma, precz)); elif N_z = N_succz or Union(N_z, [z]) = - Union(N_succz, [succz]) then + Union(N_succz, [succz]) then z := succz; Remove(sigma, Position(sigma, precz) + 1); else From 21387de74ca75d474788ba8c3e1685e21547de74 Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 17:58:32 +0000 Subject: [PATCH 06/22] Change j -> Minimum(j) to 'minimum' --- IsCograph.g | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IsCograph.g b/IsCograph.g index 8bd9679c7..1e1a84c15 100644 --- a/IsCograph.g +++ b/IsCograph.g @@ -58,7 +58,7 @@ IsCograph := function(D) # and refine with rule 2 using the neighbours of the pivot while Length(Filtered(unused_parts, u -> u <> [])) > 0 do options := Filtered(unused_parts, part -> Length(part) > 0); - list := List(options, j -> Minimum(j)); + list := List(options, Minimum); subpart := unused_parts[Position(list, Minimum(list))]; # pick our pivot to be either an unused vertex in the subpart, # or if none exist, any vertex in the subpart From c6e970a13d1f6a652e955b1bab06e2e08347047d Mon Sep 17 00:00:00 2001 From: Cora Aked Date: Wed, 3 Dec 2025 18:00:25 +0000 Subject: [PATCH 07/22] remove unused local variable --- IsCograph.g | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IsCograph.g b/IsCograph.g index 1e1a84c15..375081fb6 100644 --- a/IsCograph.g +++ b/IsCograph.g @@ -9,7 +9,7 @@ IsCograph := function(D) local verts, P, origin, adj, part, used_parts, unused_parts, - k, M, p, m, ma, j, n, v, zl, zr, new_P, t, current_part, zrpart, + k, M, p, m, ma, n, v, zl, zr, new_P, t, current_part, zrpart, pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, N_succz, options, list, subpart; From 17f0a7e66a1a0c0b0dd50ac14dd20730ca830a1b Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Feb 2026 13:18:41 +0000 Subject: [PATCH 08/22] Add IsCograph to prop.gi and prop.gd --- gap/prop.gd | 1 + gap/prop.gi | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) 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..f96c78f40 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -784,3 +784,206 @@ function(D) fi; od; end); + +InstallMethod(IsCograph, +"for a symmetric digraph", +[IsDigraph], +function(D) + local verts, P, origin, adj, part, used_parts, unused_parts, + k, M, p, m, ma, n, v, zl, zr, new_P, t, current_part, zrpart, + pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, + N_succz, options, list, subpart; + + # input must be symmetric + if not IsSymmetricDigraph(D) then; + Error("IsCograph: argument must be a symmetric digraph"); + fi; + + verts := DigraphVertices(D); + P := [verts]; + + # a graph with fewer than 4 vertices cannot contain P4 graph + if Length(verts) < 4 then + return true; + fi; + + # set origin to be a non-isolated or universal vertex + origin := 1; + adj := OutNeighboursOfVertex(D, origin); + if Length(adj) = 0 or Length(adj) = Length(verts) - 1 then + return IsCograph(InducedSubdigraph(D, Filtered(verts, v -> + v <> origin))); + fi; + + # Algorithm 2: Partition Refinement + # while there exist non-singleton parts, refine using rule 1 + while ForAll(P, part -> Length(part) <= 1) = false do + k := PositionProperty(P, part -> origin in part); + if Length(P[k]) > 1 then + part := [Filtered(OutNeighboursOfVertex(D, origin), p -> p + in P[k]), [origin], Difference(P[k], Union([origin], + OutNeighboursOfVertex(D, origin)))]; + # make note of unused and used parts + unused_parts := [part[1], part[3]]; + used_parts := [origin]; + # replace the used part with our new refinement + Remove(P, k); + for p in Filtered(part, u -> u <> []) do + Add(P, p, k); + od; + fi; + + # Procedure 3 + new_P := ShallowCopy(P); + # while we have unused parts, pick an unused part, set an unused pivot + # and refine with rule 2 using the neighbours of the pivot + while Length(Filtered(unused_parts, u -> u <> [])) > 0 do + options := Filtered(unused_parts, part -> Length(part) > 0); + list := List(options, Minimum); + subpart := unused_parts[Position(list, Minimum(list))]; + # pick our pivot to be either an unused vertex in the subpart, + # or if none exist, any vertex in the subpart + if Filtered(subpart, u -> u in used_parts) = [] then + pivot := Minimum(subpart); + else + pivot := subpart[part -> p in used_parts][1]; + fi; + + # Procedure 4 + # M is the set of parts strictly intersected by the + # neighbourhood of the pivot + M := []; + current_part := ShallowCopy(new_P[PositionProperty(new_P, + part -> pivot in part)]); + pivotset := OutNeighboursOfVertex(D, pivot); + # Add to M any part that is strictly intersected + # by pivotset + for p in Difference(new_P, [current_part]) do + if Intersection(p, pivotset) <> [] and + Intersection(p, pivotset) <> p + and Intersection(p, pivotset) <> [origin] then + k := ShallowCopy(Position(new_P, p)); + Remove(new_P, k); + Add(M, p); + fi; + od; + # if we have parts to refine, do so + if M <> [] then + # for each part in M, split into those in pivot set + # and those not + for m in M do + ma := Filtered(m, p -> p in pivotset); + upd_m := [ma, Difference(m, ma)]; + for t in Filtered(upd_m, x -> x <> []) do + Add(new_P, t, k); + od; + # if our part is unused, mark the new parts as unused + # and mark this one as used + if m in unused_parts then + Remove(unused_parts, Position(unused_parts, m)); + if not ma in unused_parts and ma <> [] then + Add(unused_parts, ma); + fi; + if not Difference(m, ma) in unused_parts and + Difference(m, ma) <> [] then + Add(unused_parts, Difference(m, ma)); + fi; + # otherwise the new subpart not containing the pivot + # is unused + else + if Minimum(m) in upd_m[1] then + Add(unused_parts, upd_m[2]); + else + Add(unused_parts, upd_m[1]); + fi; + fi; + Add(used_parts, m); + Add(used_parts, pivot); + od; + fi; + if current_part in unused_parts then + Remove(unused_parts, Position(unused_parts, current_part)); + fi; + Add(used_parts, current_part); + od; + P := ShallowCopy(new_P); + # consider the pivots either side of origin + zlpart := PositionProperty(P, part -> Length(part) > 1 + and Position(P, [origin]) > Position(P, part)); + zrpart := PositionProperty(P, part -> Length(part) > 1 + and Position(P, [origin]) < Position(P, part)); + # if there is no part on rhs or lhs, consider + # only the existing ones + if zlpart = fail or zrpart = fail then + if zlpart = fail and zrpart = fail then + continue; + elif zrpart = fail then + zl := ShallowCopy(Minimum(P[zlpart])); + origin := ShallowCopy(zl); + else + zr := ShallowCopy(Minimum(P[zrpart])); + origin := ShallowCopy(zr); + fi; + # if both exist, if they are adjacent in G, set + # origin to be the left pivot, else the right pivot + else + zl := ShallowCopy(Minimum(P[zlpart])); + zr := ShallowCopy(Minimum(P[zrpart])); + if zl in OutNeighboursOfVertex(D, zr) then + origin := ShallowCopy(zl); + else + origin := ShallowCopy(zr); + fi; + fi; + od; + + # Algorithm 5: Recognition Test + # add markers to either end of permutation + sigma := [0]; + for p in P do + for v in p do + Add(sigma, v); + od; + od; + Add(sigma, Length(verts) + 1); + + # move left to right + z := sigma[2]; + while z <> Length(verts) + 1 do + # calculate neighbours of z, predecessor and + # successor + succz := sigma[Position(sigma, z) + 1]; + precz := sigma[Position(sigma, z) - 1]; + N_z := Filtered(sigma, n -> n in + OutNeighboursOfVertex(D, z)); + # deal with cases where predecessor or + # successor is a marker + if precz <> 0 then + N_precz := Filtered(sigma, n -> n in + OutNeighboursOfVertex(D, precz)); + else + N_precz := [0]; + fi; + if succz <> Length(verts) + 1 then + N_succz := Filtered(sigma, n -> n in + OutNeighboursOfVertex(D, succz)); + else + N_succz := [0]; + fi; + # if z shares a neighbourhood with predecessor or successor, + # remove the predecessor and move right + if N_z = N_precz or Union(N_z, [z]) = + Union(N_precz, [precz]) then + Remove(sigma, Position(sigma, precz)); + elif N_z = N_succz or Union(N_z, [z]) = + Union(N_succz, [succz]) then + z := succz; + Remove(sigma, Position(sigma, precz) + 1); + else + z := succz; + fi; + od; + # continue until we hit end marker + # if only markers remain, G is a cograph + return Length(Difference(sigma, [0, Length(verts) + 1])) = 1; +end); From 350f224891c97ceb8e5832f0908653f805fb4c58 Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 15:10:07 +0000 Subject: [PATCH 09/22] Fixed bugs in IsCograph --- gap/prop.gi | 285 +++++++++++++++++++++------------------------------- 1 file changed, 114 insertions(+), 171 deletions(-) diff --git a/gap/prop.gi b/gap/prop.gi index f96c78f40..6b44cdaef 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -786,204 +786,147 @@ function(D) end); InstallMethod(IsCograph, -"for a symmetric digraph", +"for a symmetric digraph without loops or multiple edges", [IsDigraph], function(D) - local verts, P, origin, adj, part, used_parts, unused_parts, - k, M, p, m, ma, n, v, zl, zr, new_P, t, current_part, zrpart, - pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, - N_succz, options, list, subpart; + local V, P, x, N, parts, unused_parts, p, C, k, y, M, X, X_a, + used_pivots, C_origin, lpart, rpart, zl, zr, origin, sigma, prec, + succ, N_prec, N_succ; - # input must be symmetric - if not IsSymmetricDigraph(D) then; + # D must be a symmetric digraph without loops or multiple edges + if not IsSymmetricDigraph(D) then; Error("IsCograph: argument must be a symmetric digraph"); - fi; + 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; - verts := DigraphVertices(D); - P := [verts]; + V := DigraphVertices(D); - # a graph with fewer than 4 vertices cannot contain P4 graph - if Length(verts) < 4 then - return true; - fi; + if Length(V) < 4 then + return true; + fi; - # set origin to be a non-isolated or universal vertex - origin := 1; - adj := OutNeighboursOfVertex(D, origin); - if Length(adj) = 0 or Length(adj) = Length(verts) - 1 then - return IsCograph(InducedSubdigraph(D, Filtered(verts, v -> - v <> origin))); - fi; + P := [V]; + used_pivots := []; + origin := V[1]; - # Algorithm 2: Partition Refinement - # while there exist non-singleton parts, refine using rule 1 - while ForAll(P, part -> Length(part) <= 1) = false do - k := PositionProperty(P, part -> origin in part); - if Length(P[k]) > 1 then - part := [Filtered(OutNeighboursOfVertex(D, origin), p -> p - in P[k]), [origin], Difference(P[k], Union([origin], - OutNeighboursOfVertex(D, origin)))]; - # make note of unused and used parts - unused_parts := [part[1], part[3]]; - used_parts := [origin]; - # replace the used part with our new refinement - Remove(P, k); - for p in Filtered(part, u -> u <> []) do - Add(P, p, k); - od; - fi; + # 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; - # Procedure 3 - new_P := ShallowCopy(P); - # while we have unused parts, pick an unused part, set an unused pivot - # and refine with rule 2 using the neighbours of the pivot - while Length(Filtered(unused_parts, u -> u <> [])) > 0 do - options := Filtered(unused_parts, part -> Length(part) > 0); - list := List(options, Minimum); - subpart := unused_parts[Position(list, Minimum(list))]; - # pick our pivot to be either an unused vertex in the subpart, - # or if none exist, any vertex in the subpart - if Filtered(subpart, u -> u in used_parts) = [] then - pivot := Minimum(subpart); - else - pivot := subpart[part -> p in used_parts][1]; - fi; - - # Procedure 4 - # M is the set of parts strictly intersected by the - # neighbourhood of the pivot - M := []; - current_part := ShallowCopy(new_P[PositionProperty(new_P, - part -> pivot in part)]); - pivotset := OutNeighboursOfVertex(D, pivot); - # Add to M any part that is strictly intersected - # by pivotset - for p in Difference(new_P, [current_part]) do - if Intersection(p, pivotset) <> [] and - Intersection(p, pivotset) <> p - and Intersection(p, pivotset) <> [origin] then - k := ShallowCopy(Position(new_P, p)); - Remove(new_P, k); - Add(M, p); - fi; - od; - # if we have parts to refine, do so - if M <> [] then - # for each part in M, split into those in pivot set - # and those not - for m in M do - ma := Filtered(m, p -> p in pivotset); - upd_m := [ma, Difference(m, ma)]; - for t in Filtered(upd_m, x -> x <> []) do - Add(new_P, t, k); - od; - # if our part is unused, mark the new parts as unused - # and mark this one as used - if m in unused_parts then - Remove(unused_parts, Position(unused_parts, m)); - if not ma in unused_parts and ma <> [] then - Add(unused_parts, ma); - fi; - if not Difference(m, ma) in unused_parts and - Difference(m, ma) <> [] then - Add(unused_parts, Difference(m, ma)); - fi; - # otherwise the new subpart not containing the pivot - # is unused - else - if Minimum(m) in upd_m[1] then - Add(unused_parts, upd_m[2]); - else - Add(unused_parts, upd_m[1]); - fi; - fi; - Add(used_parts, m); - Add(used_parts, pivot); - od; - fi; - if current_part in unused_parts then - Remove(unused_parts, Position(unused_parts, current_part)); - fi; - Add(used_parts, current_part); - od; - P := ShallowCopy(new_P); - # consider the pivots either side of origin - zlpart := PositionProperty(P, part -> Length(part) > 1 - and Position(P, [origin]) > Position(P, part)); - zrpart := PositionProperty(P, part -> Length(part) > 1 - and Position(P, [origin]) < Position(P, part)); - # if there is no part on rhs or lhs, consider - # only the existing ones - if zlpart = fail or zrpart = fail then - if zlpart = fail and zrpart = fail then - continue; - elif zrpart = fail then - zl := ShallowCopy(Minimum(P[zlpart])); - origin := ShallowCopy(zl); - else - zr := ShallowCopy(Minimum(P[zrpart])); - origin := ShallowCopy(zr); - fi; - # if both exist, if they are adjacent in G, set - # origin to be the left pivot, else the right pivot + while not ForAll(P, p -> Length(p) <= 1) do + C_origin := P[PositionProperty(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 + Length(Difference(p, N)) > 0 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 - zl := ShallowCopy(Minimum(P[zlpart])); - zr := ShallowCopy(Minimum(P[zrpart])); - if zl in OutNeighboursOfVertex(D, zr) then - origin := ShallowCopy(zl); - else - origin := ShallowCopy(zr); - fi; + 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; - # Algorithm 5: Recognition Test - # add markers to either end of permutation + # Choose new origin + lpart := Filtered(P, p -> Position(P, p) < Position(P, [origin]) and + Length(p) > 1); + rpart := Filtered(P, p -> Position(P, p) > Position(P, [origin]) and + Length(p) > 1); + if Length(lpart) = 0 or Length(rpart) = 0 then + if Length(lpart) = 0 and Length(rpart) = 0 then + continue; + elif Length(lpart) = 0 then + origin := IntersectionSet(used_pivots, rpart[1])[1]; + else + origin := IntersectionSet(used_pivots, Last(lpart))[1]; + fi; + 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 - for v in p do - Add(sigma, v); - od; + Add(sigma, p[1]); od; - Add(sigma, Length(verts) + 1); + Add(sigma, Length(V) + 1); # move left to right - z := sigma[2]; - while z <> Length(verts) + 1 do - # calculate neighbours of z, predecessor and + x := sigma[2]; + while x <> Length(V) + 1 do + # calculate neighbours of x, predecessor and # successor - succz := sigma[Position(sigma, z) + 1]; - precz := sigma[Position(sigma, z) - 1]; - N_z := Filtered(sigma, n -> n in - OutNeighboursOfVertex(D, z)); - # deal with cases where predecessor or - # successor is a marker - if precz <> 0 then - N_precz := Filtered(sigma, n -> n in - OutNeighboursOfVertex(D, precz)); + succ := sigma[Position(sigma, x) + 1]; + prec := sigma[Position(sigma, x) - 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_precz := [0]; + N_prec := [0]; fi; - if succz <> Length(verts) + 1 then - N_succz := Filtered(sigma, n -> n in - OutNeighboursOfVertex(D, succz)); + if succ <> Length(V) + 1 then + N_succ := Filtered(sigma, n -> n in OutNeighboursOfVertex(D, succ)); else - N_succz := [0]; + N_succ := [0]; fi; - # if z shares a neighbourhood with predecessor or successor, + # if x shares a neighbourhood with predecessor or successor, # remove the predecessor and move right - if N_z = N_precz or Union(N_z, [z]) = - Union(N_precz, [precz]) then - Remove(sigma, Position(sigma, precz)); - elif N_z = N_succz or Union(N_z, [z]) = - Union(N_succz, [succz]) then - z := succz; - Remove(sigma, Position(sigma, precz) + 1); + if N = N_prec or Union(N, [x]) = Union(N_prec, [prec]) then + Remove(sigma, Position(sigma, prec)); + elif N = N_succ or Union(N, [x]) = Union(N_succ, [succ]) then + x := succ; + Remove(sigma, Position(sigma, prec) + 1); else - z := succz; + x := succ; fi; od; # continue until we hit end marker # if only markers remain, G is a cograph - return Length(Difference(sigma, [0, Length(verts) + 1])) = 1; + return Length(Difference(sigma, [0, Length(V) + 1])) = 1; end); From ff97332eadf48025342e75d966b7bea0b6d7a24b Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 15:38:28 +0000 Subject: [PATCH 10/22] Added tests for IsCograph --- tst/standard/prop.tst | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tst/standard/prop.tst b/tst/standard/prop.tst index 7a5520ad1..13a212115 100644 --- a/tst/standard/prop.tst +++ b/tst/standard/prop.tst @@ -1274,6 +1274,56 @@ 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 + # IsJoinSemilatticeDigraph, IsMeetSemilatticeDigraph, and IsLatticeDigraph gap> gr := Digraph([[1, 2], [2]]); From 4a93c81f1120a1dd32b9f7f255cec0f7c757d0cb Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 16:57:50 +0000 Subject: [PATCH 11/22] Added doc for IsCograph --- doc/prop.xml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/prop.xml b/doc/prop.xml index 38fff9e32..9325f3f18 100644 --- a/doc/prop.xml +++ b/doc/prop.xml @@ -875,6 +875,46 @@ 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, otherwise the property returns + false. +

+ + 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 + "Habib, M and Paul, C and Viennot (2005), A Simple Linear Time Recognition Algorithm + for Cographs". +

+ + &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 +]]> + + +<#/GAPDoc> + <#GAPDoc Label="IsFunctionalDigraph"> @@ -902,6 +942,10 @@ gap> gr3 := Digraph(3, [1, 2, 3], [2, 3, 1]); gap> IsFunctionalDigraph(gr3); true +gap> D := CompleteDigraph(5); + +gap> IsCograph(D); +true ]]> From 4d475cd142387816848e6e402f1df8648d505c0b Mon Sep 17 00:00:00 2001 From: frankiegillis Date: Thu, 5 Mar 2026 17:16:01 +0000 Subject: [PATCH 12/22] Delete IsCograph.g --- IsCograph.g | 208 ---------------------------------------------------- 1 file changed, 208 deletions(-) delete mode 100644 IsCograph.g diff --git a/IsCograph.g b/IsCograph.g deleted file mode 100644 index 375081fb6..000000000 --- a/IsCograph.g +++ /dev/null @@ -1,208 +0,0 @@ -# Function that identifies a cograph from a symmetric digraph - -# Created from the algorithm described in the paper "A Simple -# Linear Time Recognition Algorithm for Cographs" - -# Habib, M & Paul, C & Viennot (2005). A Simple Linear Time -# Recognition Algorithm for Cographs. Discrete Applied Mathematics. -# 145(2). 183-197. https://doi.org/10.1016/j.dam.2004.01.011. - -IsCograph := function(D) - local verts, P, origin, adj, part, used_parts, unused_parts, - k, M, p, m, ma, n, v, zl, zr, new_P, t, current_part, zrpart, - pivot, zlpart, upd_m, pivotset, sigma, succz, precz, z, N_z, N_precz, - N_succz, options, list, subpart; - - # input must be symmetric - if not IsSymmetricDigraph(D) then; - Error("IsCograph: argument must be a symmetric digraph"); - fi; - - verts := DigraphVertices(D); - P := [verts]; - - # a graph with fewer than 4 vertices cannot contain P4 graph - if Length(verts) < 4 then - return true; - fi; - - # set origin to be a non-isolated or universal vertex - origin := 1; - adj := OutNeighboursOfVertex(D, origin); - if Length(adj) = 0 or Length(adj) = Length(verts) - 1 then - return IsCograph(InducedSubdigraph(D, Filtered(verts, v -> - v <> origin))); - fi; - - # Algorithm 2: Partition Refinement - # while there exist non-singleton parts, refine using rule 1 - while ForAll(P, part -> Length(part) <= 1) = false do - k := PositionProperty(P, part -> origin in part); - if Length(P[k]) > 1 then - part := [Filtered(OutNeighboursOfVertex(D, origin), p -> p - in P[k]), [origin], Difference(P[k], Union([origin], - OutNeighboursOfVertex(D, origin)))]; - # make note of unused and used parts - unused_parts := [part[1], part[3]]; - used_parts := [origin]; - # replace the used part with our new refinement - Remove(P, k); - for p in Filtered(part, u -> u <> []) do - Add(P, p, k); - od; - fi; - - # Procedure 3 - new_P := ShallowCopy(P); - # while we have unused parts, pick an unused part, set an unused pivot - # and refine with rule 2 using the neighbours of the pivot - while Length(Filtered(unused_parts, u -> u <> [])) > 0 do - options := Filtered(unused_parts, part -> Length(part) > 0); - list := List(options, Minimum); - subpart := unused_parts[Position(list, Minimum(list))]; - # pick our pivot to be either an unused vertex in the subpart, - # or if none exist, any vertex in the subpart - if Filtered(subpart, u -> u in used_parts) = [] then - pivot := Minimum(subpart); - else - pivot := subpart[part -> p in used_parts][1]; - fi; - - # Procedure 4 - # M is the set of parts strictly intersected by the - # neighbourhood of the pivot - M := []; - current_part := ShallowCopy(new_P[PositionProperty(new_P, - part -> pivot in part)]); - pivotset := OutNeighboursOfVertex(D, pivot); - # Add to M any part that is strictly intersected - # by pivotset - for p in Difference(new_P, [current_part]) do - if Intersection(p, pivotset) <> [] and - Intersection(p, pivotset) <> p - and Intersection(p, pivotset) <> [origin] then - k := ShallowCopy(Position(new_P, p)); - Remove(new_P, k); - Add(M, p); - fi; - od; - # if we have parts to refine, do so - if M <> [] then - # for each part in M, split into those in pivot set - # and those not - for m in M do - ma := Filtered(m, p -> p in pivotset); - upd_m := [ma, Difference(m, ma)]; - for t in Filtered(upd_m, x -> x <> []) do - Add(new_P, t, k); - od; - # if our part is unused, mark the new parts as unused - # and mark this one as used - if m in unused_parts then - Remove(unused_parts, Position(unused_parts, m)); - if not ma in unused_parts and ma <> [] then - Add(unused_parts, ma); - fi; - if not Difference(m, ma) in unused_parts and - Difference(m, ma) <> [] then - Add(unused_parts, Difference(m, ma)); - fi; - # otherwise the new subpart not containing the pivot - # is unused - else - if Minimum(m) in upd_m[1] then - Add(unused_parts, upd_m[2]); - else - Add(unused_parts, upd_m[1]); - fi; - fi; - Add(used_parts, m); - Add(used_parts, pivot); - od; - fi; - if current_part in unused_parts then - Remove(unused_parts, Position(unused_parts, current_part)); - fi; - Add(used_parts, current_part); - od; - P := ShallowCopy(new_P); - # consider the pivots either side of origin - zlpart := PositionProperty(P, part -> Length(part) > 1 - and Position(P, [origin]) > Position(P, part)); - zrpart := PositionProperty(P, part -> Length(part) > 1 - and Position(P, [origin]) < Position(P, part)); - # if there is no part on rhs or lhs, consider - # only the existing ones - if zlpart = fail or zrpart = fail then - if zlpart = fail and zrpart = fail then - continue; - elif zrpart = fail then - zl := ShallowCopy(Minimum(P[zlpart])); - origin := ShallowCopy(zl); - else - zr := ShallowCopy(Minimum(P[zrpart])); - origin := ShallowCopy(zr); - fi; - # if both exist, if they are adjacent in G, set - # origin to be the left pivot, else the right pivot - else - zl := ShallowCopy(Minimum(P[zlpart])); - zr := ShallowCopy(Minimum(P[zrpart])); - if zl in OutNeighboursOfVertex(D, zr) then - origin := ShallowCopy(zl); - else - origin := ShallowCopy(zr); - fi; - fi; - od; - - # Algorithm 5: Recognition Test - # add markers to either end of permutation - sigma := [0]; - for p in P do - for v in p do - Add(sigma, v); - od; - od; - Add(sigma, Length(verts) + 1); - - # move left to right - z := sigma[2]; - while z <> Length(verts) + 1 do - # calculate neighbours of z, predecessor and - # successor - succz := sigma[Position(sigma, z) + 1]; - precz := sigma[Position(sigma, z) - 1]; - N_z := Filtered(sigma, n -> n in - OutNeighboursOfVertex(D, z)); - # deal with cases where predecessor or - # successor is a marker - if precz <> 0 then - N_precz := Filtered(sigma, n -> n in - OutNeighboursOfVertex(D, precz)); - else - N_precz := [0]; - fi; - if succz <> Length(verts) + 1 then - N_succz := Filtered(sigma, n -> n in - OutNeighboursOfVertex(D, succz)); - else - N_succz := [0]; - fi; - # if z shares a neighbourhood with predecessor or successor, - # remove the predecessor and move right - if N_z = N_precz or Union(N_z, [z]) = - Union(N_precz, [precz]) then - Remove(sigma, Position(sigma, precz)); - elif N_z = N_succz or Union(N_z, [z]) = - Union(N_succz, [succz]) then - z := succz; - Remove(sigma, Position(sigma, precz) + 1); - else - z := succz; - fi; - od; - # continue until we hit end marker - # if only markers remain, G is a cograph - return Length(Difference(sigma, [0, Length(verts) + 1])) = 1; -end; \ No newline at end of file From 038ce5c97b3d7c9626f18c0334d4d4b43ae099cc Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 17:22:25 +0000 Subject: [PATCH 13/22] Removed trailing whitespace --- gap/prop.gi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gap/prop.gi b/gap/prop.gi index 6b44cdaef..64b31d657 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -833,9 +833,9 @@ function(D) Add(P, p, k); fi; od; - + # Refine - while Length(unused_parts) > 0 do + while Length(unused_parts) > 0 do C := unused_parts[1]; y := C[1]; Add(used_pivots, y); @@ -868,7 +868,7 @@ function(D) # Choose new origin lpart := Filtered(P, p -> Position(P, p) < Position(P, [origin]) and Length(p) > 1); - rpart := Filtered(P, p -> Position(P, p) > Position(P, [origin]) and + rpart := Filtered(P, p -> Position(P, p) > Position(P, [origin]) and Length(p) > 1); if Length(lpart) = 0 or Length(rpart) = 0 then if Length(lpart) = 0 and Length(rpart) = 0 then @@ -888,7 +888,7 @@ function(D) fi; fi; od; - + # Recognition Test sigma := [0]; for p in P do From 1a5c1e397b11187bd7376283393c521833f04a96 Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 18:09:31 +0000 Subject: [PATCH 14/22] Fixed linting issues --- gap/prop.gi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gap/prop.gi b/gap/prop.gi index 64b31d657..318640202 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -814,8 +814,8 @@ function(D) # 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 + if Length(OutNeighboursOfVertex(D, origin)) = 0 + or Length(OutNeighboursOfVertex(D, origin)) = Length(V) - 1 then return IsCograph(DigraphRemoveVertex(D, origin)); fi; @@ -833,7 +833,7 @@ function(D) Add(P, p, k); fi; od; - + # Refine while Length(unused_parts) > 0 do C := unused_parts[1]; @@ -888,7 +888,7 @@ function(D) fi; fi; od; - + # Recognition Test sigma := [0]; for p in P do @@ -919,7 +919,7 @@ function(D) # remove the predecessor and move right if N = N_prec or Union(N, [x]) = Union(N_prec, [prec]) then Remove(sigma, Position(sigma, prec)); - elif N = N_succ or Union(N, [x]) = Union(N_succ, [succ]) then + elif N = N_succ or Union(N, [x]) = Union(N_succ, [succ]) then x := succ; Remove(sigma, Position(sigma, prec) + 1); else From 7fb0733ccc08fa7d077846078047fd2404c696ea Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 19:06:33 +0000 Subject: [PATCH 15/22] Added further tests for CodeCov --- tst/standard/prop.tst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tst/standard/prop.tst b/tst/standard/prop.tst index 13a212115..92c1a50e7 100644 --- a/tst/standard/prop.tst +++ b/tst/standard/prop.tst @@ -1323,6 +1323,14 @@ 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 # IsJoinSemilatticeDigraph, IsMeetSemilatticeDigraph, and IsLatticeDigraph gap> gr := Digraph([[1, 2], [2]]); From e21431401ba0891e8ce7916284bed226263b97d4 Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 19:16:42 +0000 Subject: [PATCH 16/22] Did not, in fact, fix linting --- gap/prop.gi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gap/prop.gi b/gap/prop.gi index 318640202..784cb34e0 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -814,9 +814,9 @@ function(D) # 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)); + 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 From 41b56e3ff12fe89604377d6caa78d6d419dbe2a5 Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Thu, 5 Mar 2026 19:19:54 +0000 Subject: [PATCH 17/22] The linting strikes back --- gap/prop.gi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gap/prop.gi b/gap/prop.gi index 784cb34e0..c95e8a2ce 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -815,8 +815,8 @@ function(D) # 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)); + Length(OutNeighboursOfVertex(D, origin)) = Length(V) - 1 then + return IsCograph(DigraphRemoveVertex(D, origin)); fi; while not ForAll(P, p -> Length(p) <= 1) do From 0ba330ce621b4435fe4f76e1cf8d73655932e1a8 Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Tue, 10 Mar 2026 10:04:09 +0000 Subject: [PATCH 18/22] Corrected reference for algorithm in IsCograph --- doc/prop.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/prop.xml b/doc/prop.xml index 9325f3f18..451ebe46e 100644 --- a/doc/prop.xml +++ b/doc/prop.xml @@ -891,8 +891,7 @@ true reached starting from a single vertex under the operations of series and parallel composition. This function implements the algorithm for cograph recogntition given in - "Habib, M and Paul, C and Viennot (2005), A Simple Linear Time Recognition Algorithm - for Cographs". + "Habib, M., and Paul, C. (2005). A simple linear time algorithm for cograph recognition. Discrete Applied Mathematics, 145(2), 183-197."

&MUTABLE_RECOMPUTED_PROP; From 1ec1012a623eda94e7886d001fcf220a96d09344 Mon Sep 17 00:00:00 2001 From: Frankie Gillis Date: Tue, 10 Mar 2026 10:05:26 +0000 Subject: [PATCH 19/22] Fixed line too long in prop.xml --- doc/prop.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/prop.xml b/doc/prop.xml index 451ebe46e..e27f1abc9 100644 --- a/doc/prop.xml +++ b/doc/prop.xml @@ -891,7 +891,8 @@ true reached starting from a single vertex under the operations of series and parallel composition. This function implements the algorithm for cograph recogntition given in - "Habib, M., and Paul, C. (2005). A simple linear time algorithm for cograph recognition. Discrete Applied Mathematics, 145(2), 183-197." + "Habib, M., and Paul, C. (2005). A simple linear time algorithm for cograph recognition. + Discrete Applied Mathematics, 145(2), 183-197."

&MUTABLE_RECOMPUTED_PROP; From 387ab52c33156fb7bf91e158f25ec16190f2c429 Mon Sep 17 00:00:00 2001 From: Michael Young Date: Wed, 18 Mar 2026 16:07:16 +0000 Subject: [PATCH 20/22] Fix minor doc issues --- doc/digraphs.bib | 15 +++++++++++++++ doc/prop.xml | 16 ++++++++-------- doc/z-chap5.xml | 1 + 3 files changed, 24 insertions(+), 8 deletions(-) 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 e27f1abc9..8a7b39790 100644 --- a/doc/prop.xml +++ b/doc/prop.xml @@ -881,8 +881,7 @@ true true or false. If digraph is a symmetric digraph without loops or multiple edges, then the - property returns true if digraph is a cograph, otherwise the property returns - false. + 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 @@ -890,9 +889,10 @@ true 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 - "Habib, M., and Paul, C. (2005). A simple linear time algorithm for cograph recognition. - Discrete Applied Mathematics, 145(2), 183-197." + .

&MUTABLE_RECOMPUTED_PROP; @@ -910,6 +910,10 @@ gap> D := DigraphSymmetricClosure(D); gap> IsCograph(D); false +gap> D := CompleteDigraph(5); + +gap> IsCograph(D); +true ]]> @@ -942,10 +946,6 @@ gap> gr3 := Digraph(3, [1, 2, 3], [2, 3, 1]); gap> IsFunctionalDigraph(gr3); true -gap> D := CompleteDigraph(5); - -gap> IsCograph(D); -true ]]> 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"> From c628bd717606c16d0b12b5bee275540be96d00fd Mon Sep 17 00:00:00 2001 From: Michael Young Date: Wed, 18 Mar 2026 16:09:26 +0000 Subject: [PATCH 21/22] Optimisations for IsCograph --- gap/prop.gi | 57 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/gap/prop.gi b/gap/prop.gi index c95e8a2ce..b30190bab 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -786,12 +786,12 @@ function(D) end); InstallMethod(IsCograph, -"for a symmetric digraph without loops or multiple edges", -[IsDigraph], +"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, lpart, rpart, zl, zr, origin, sigma, prec, - succ, N_prec, N_succ; + 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; @@ -820,7 +820,7 @@ function(D) fi; while not ForAll(P, p -> Length(p) <= 1) do - C_origin := P[PositionProperty(P, p -> origin in p)]; + C_origin := First(P, p -> origin in p); # Initialise N := IntersectionSet(OutNeighboursOfVertex(D, origin), C_origin); @@ -841,7 +841,7 @@ function(D) Add(used_pivots, y); N := OutNeighboursOfVertex(D, y); M := Filtered(P, p -> Length(IntersectionSet(p, N)) > 0 and - Length(Difference(p, N)) > 0 and + not IsSubset(N, p) and p <> C); for X in M do X_a := IntersectionSet(X, N); @@ -866,18 +866,27 @@ function(D) od; # Choose new origin - lpart := Filtered(P, p -> Position(P, p) < Position(P, [origin]) and - Length(p) > 1); - rpart := Filtered(P, p -> Position(P, p) > Position(P, [origin]) and - Length(p) > 1); - if Length(lpart) = 0 or Length(rpart) = 0 then - if Length(lpart) = 0 and Length(rpart) = 0 then + 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; - elif Length(lpart) = 0 then - origin := IntersectionSet(used_pivots, rpart[1])[1]; - else - origin := IntersectionSet(used_pivots, Last(lpart))[1]; 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]; @@ -897,12 +906,12 @@ function(D) Add(sigma, Length(V) + 1); # move left to right - x := sigma[2]; + posx := 2; + x := sigma[posx]; while x <> Length(V) + 1 do - # calculate neighbours of x, predecessor and - # successor - succ := sigma[Position(sigma, x) + 1]; - prec := sigma[Position(sigma, x) - 1]; + # 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 @@ -918,11 +927,13 @@ function(D) # 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, Position(sigma, prec)); + 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; - Remove(sigma, Position(sigma, prec) + 1); else + posx := posx + 1; x := succ; fi; od; From 21a42b7c6d13fd0592631661639dae86648b33da Mon Sep 17 00:00:00 2001 From: Michael Young Date: Wed, 18 Mar 2026 16:14:19 +0000 Subject: [PATCH 22/22] IsCograph for mutable digraphs --- gap/prop.gi | 5 +++++ tst/standard/prop.tst | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/gap/prop.gi b/gap/prop.gi index b30190bab..0c564ef17 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -941,3 +941,8 @@ function(D) # 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 92c1a50e7..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);; @@ -1331,6 +1331,11 @@ 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]]);