diff --git a/gap/prop.gd b/gap/prop.gd index 808d67dc7..157942b53 100644 --- a/gap/prop.gd +++ b/gap/prop.gd @@ -55,6 +55,7 @@ DeclareProperty("IsDistributiveLatticeDigraph", IsDigraph); DeclareProperty("IsModularLatticeDigraph", IsDigraph); DeclareProperty("Is2EdgeTransitive", IsDigraph); DeclareProperty("IsCograph", IsDigraph); +DeclareProperty("IsBetweenCoverAndLattice", IsDigraph); DeclareSynonymAttr("IsLatticeDigraph", IsMeetSemilatticeDigraph and IsJoinSemilatticeDigraph); DeclareSynonymAttr("IsPreorderDigraph", @@ -82,6 +83,7 @@ InstallTrueMethod(IsAcyclicDigraph, IsEmptyDigraph); InstallTrueMethod(IsAcyclicDigraph, IsTournament and IsTransitiveDigraph); InstallTrueMethod(IsAntisymmetricDigraph, IsAcyclicDigraph); InstallTrueMethod(IsAntisymmetricDigraph, IsDigraph and IsTournament); +InstallTrueMethod(IsBetweenCoverAndLattice, IsLatticeDigraph); InstallTrueMethod(IsBipartiteDigraph, IsCompleteBipartiteDigraph); InstallTrueMethod(IsBipartiteDigraph, IsDigraph and IsUndirectedForest); InstallTrueMethod(IsCompleteMultipartiteDigraph, IsCompleteBipartiteDigraph); diff --git a/gap/prop.gi b/gap/prop.gi index 0c564ef17..fdcb9107e 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -91,6 +91,60 @@ function(D, P, U, join) return tab; end); +# Variant of DIGRAPHS_MeetJoinTable that does not require the input digraph +# to have all transitive edges: reachability between previously processed +# vertices is checked via the table being constructed, rather than via +# direct edge queries on the input digraph. +# Note: above descriptive comment is LLM-generated. +BindGlobal("DIGRAPHS_MeetJoinTableBetweenCover", +function(N, P, U, join) + local ord, tab, S, i, x, T, l, q, z, y; + + tab := List([1 .. N], x -> []); + + ord := []; + for i in [1 .. N] do + ord[P[i]] := i; + od; + + S := []; + + for x in P do + tab[x, x] := x; + for y in S do + T := []; + for z in U[x] do + Add(T, tab[y, z]); + od; + T := Set(T); + l := Length(T); + if l = 0 then + return fail; + fi; + q := T[l]; + for i in [1 .. l - 1] do + z := T[i]; + if ord[z] > ord[q] then + q := z; + fi; + od; + for z in T do + # the below conditions are the only part that + # differs from MeetJoinTable above + if join and tab[q, z] <> z then + return fail; + elif not join and tab[z, q] <> z then + return fail; + fi; + od; + tab[x, y] := q; + tab[y, x] := q; + od; + Add(S, x); + od; + return tab; +end); + InstallMethod(DIGRAPHS_IsJoinSemilatticeAndJoinTable, "for a digraph", [IsDigraph], function(D) @@ -137,6 +191,42 @@ InstallMethod(IsMeetSemilatticeDigraph, "for a digraph", [IsDigraph], D -> DIGRAPHS_IsMeetSemilatticeAndMeetTable(D)[1]); +InstallMethod(IsBetweenCoverAndLattice, "for a digraph", +[IsDigraph], +function(D) + local copy, order, hasse, neighbours, table; + if IsMultiDigraph(D) then + ErrorNoReturn("the argument must not be a multidigraph,"); + fi; + # 1. Topologically sort the nodes in D. + copy := DigraphRemoveLoops(DigraphMutableCopyIfMutable(D)); + order := DigraphTopologicalSort(copy); + if order = fail then + return false; + fi; + + # 2. Iterate through pairs of nodes of D in topological order + # and construct a table of their meets. + hasse := DigraphTransitiveReduction(copy); + neighbours := InNeighbours(hasse); + table := DIGRAPHS_MeetJoinTableBetweenCover( + DigraphNrVertices(copy), + Reversed(order), + neighbours, + false); + if table = fail then + return false; + fi; + + neighbours := OutNeighbours(hasse); + table := DIGRAPHS_MeetJoinTableBetweenCover( + DigraphNrVertices(copy), + order, + neighbours, + true); + return table <> fail; +end); + InstallImmediateMethod(IsStronglyConnectedDigraph, IsDigraph and HasDigraphStronglyConnectedComponents, 0, D -> Length(DigraphStronglyConnectedComponents(D).comps) = 1); diff --git a/tst/standard/prop.tst b/tst/standard/prop.tst index 623ff6713..c270c4d95 100644 --- a/tst/standard/prop.tst +++ b/tst/standard/prop.tst @@ -1427,6 +1427,48 @@ false gap> IsLatticeDigraph(gr); false +# IsBetweenCoverAndLattice +gap> IsBetweenCoverAndLattice(Digraph([])); +true +gap> IsBetweenCoverAndLattice(Digraph([[]])); +true +gap> IsBetweenCoverAndLattice(Digraph([[1]])); +true +gap> IsBetweenCoverAndLattice(ChainDigraph(5)); +true +gap> IsBetweenCoverAndLattice(DigraphReflexiveTransitiveClosure(ChainDigraph(5))); +true +gap> D := Digraph([[2, 3], [4], [4], []]); + +gap> IsLatticeDigraph(D); +false +gap> IsBetweenCoverAndLattice(D); +true +gap> D := Digraph([[2, 3, 4], [4], [4], []]); + +gap> IsBetweenCoverAndLattice(D); +true +gap> D := DigraphReflexiveTransitiveClosure(Digraph([[2, 3], [4], [4], []])); + +gap> IsLatticeDigraph(D); +true +gap> IsBetweenCoverAndLattice(D); +true +gap> D := Digraph([[3, 4], [3, 4], [5], [5], []]); + +gap> IsBetweenCoverAndLattice(D); +false +gap> IsBetweenCoverAndLattice(CycleDigraph(3)); +false +gap> IsBetweenCoverAndLattice(Digraph([[], []])); +false +gap> D := Digraph(IsMutable, [[2, 3], [4], [4], []]); + +gap> IsBetweenCoverAndLattice(D); +true +gap> D; + + # IsDistributiveLatticeDigraph gap> D := Digraph([[11, 10], [], [2], [2], [3], [4, 3], [6, 5], [7], [7], > [8], [9, 8]]);