Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions gap/oper.gd
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,6 @@ DeclareOperation("PartialOrderDigraphJoinOfVertices",
[IsDigraph, IsPosInt, IsPosInt]);
DeclareOperation("PartialOrderDigraphMeetOfVertices",
[IsDigraph, IsPosInt, IsPosInt]);

DeclareOperation("IsJoinIrreducible", [IsDigraph, IsPosInt]);
DeclareOperation("IsMeetIrreducible", [IsDigraph, IsPosInt]);
32 changes: 32 additions & 0 deletions gap/oper.gi
Original file line number Diff line number Diff line change
Expand Up @@ -2720,6 +2720,38 @@ function(D, i, j)
return fail;
end);

InstallMethod(IsJoinIrreducible,
"for a digraph and a positive integer",
[IsDigraph, IsPosInt],
function(D, v)
local hasse;
if not IsPartialOrderDigraph(D) then
ErrorNoReturn("the 1st argument <D> must satisfy IsPartialOrderDigraph,");
elif not v in DigraphVertices(D) then
ErrorNoReturn("the 2nd argument <v> must be a vertex of the ",
"1st argument <D>,");
fi;
hasse := DigraphReflexiveTransitiveReduction(DigraphMutableCopyIfMutable(D));
# join-irreducible iff at most one lower cover in the Hasse diagram
Comment on lines +2728 to +2735
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only explicit precondition checked is IsPartialOrderDigraph(D), but IsPartialOrderDigraph does not exclude multidigraphs. If D has multiple edges, DigraphReflexiveTransitiveReduction will throw a different error (“must be a digraph with no multiple edges”) from inside this method. Consider either rejecting multidigraphs up front with a clear error, or normalizing the copied digraph by removing multiple edges before computing the reduction.

Copilot uses AI. Check for mistakes.
return InDegrees(hasse)[v] <= 1;
Comment on lines +2727 to +2736
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsJoinIrreducible is implemented as “at most one lower cover in the Hasse diagram”. This implication only holds in (finite) join-semilattices/lattices; for a general partial order it can be wrong (e.g. two incomparable minimal upper bounds means joins may not exist, yet a vertex can have multiple lower covers while still not being the join of any two smaller vertices). Consider either tightening the precondition to IsJoinSemilatticeDigraph/IsLatticeDigraph (and error accordingly), or implementing the join-based definition via PartialOrderDigraphJoinOfVertices so the result matches the operation’s stated meaning for arbitrary partial orders.

Suggested change
local hasse;
if not IsPartialOrderDigraph(D) then
ErrorNoReturn("the 1st argument <D> must satisfy IsPartialOrderDigraph,");
elif not v in DigraphVertices(D) then
ErrorNoReturn("the 2nd argument <v> must be a vertex of the ",
"1st argument <D>,");
fi;
hasse := DigraphReflexiveTransitiveReduction(DigraphMutableCopyIfMutable(D));
# join-irreducible iff at most one lower cover in the Hasse diagram
return InDegrees(hasse)[v] <= 1;
local verts, i, j, x, y, join;
if not IsPartialOrderDigraph(D) then
ErrorNoReturn("the 1st argument <D> must satisfy IsPartialOrderDigraph,");
elif not v in DigraphVertices(D) then
ErrorNoReturn("the 2nd argument <v> must be a vertex of the ",
"1st argument <D>,");
fi;
# An element v is join-irreducible if it is not the join of
# two distinct strictly smaller elements. We check all unordered
# pairs of vertices (x, y) with x <> y and neither equal to v.
verts := DigraphVertices(D);
for i in [1 .. Length(verts) - 1] do
x := verts[i];
if x = v then
continue;
fi;
for j in [i + 1 .. Length(verts)] do
y := verts[j];
if y = v then
continue;
fi;
join := PartialOrderDigraphJoinOfVertices(D, x, y);
if join = v then
# v can be expressed as the join of two distinct smaller elements
return false;
fi;
od;
od;
# No such pair found; v is join-irreducible.
return true;

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsJoinIrreducible uses InDegrees(hasse)[v], which computes indegrees for all vertices even though only one value is needed. Using InDegreeOfVertexNC(hasse, v) (mirroring the OutDegreeOfVertexNC usage below) avoids the extra O(|V|+|E|) work per call on large digraphs.

Suggested change
return InDegrees(hasse)[v] <= 1;
return InDegreeOfVertexNC(hasse, v) <= 1;

Copilot uses AI. Check for mistakes.
end);

InstallMethod(IsMeetIrreducible,
"for a digraph and a positive integer",
[IsDigraph, IsPosInt],
function(D, v)
local hasse;
if not IsPartialOrderDigraph(D) then
ErrorNoReturn("the 1st argument <D> must satisfy IsPartialOrderDigraph,");
elif not v in DigraphVertices(D) then
ErrorNoReturn("the 2nd argument <v> must be a vertex of the ",
"1st argument <D>,");
fi;
hasse := DigraphReflexiveTransitiveReduction(DigraphMutableCopyIfMutable(D));
# meet-irreducible iff at most one upper cover in the Hasse diagram
return OutDegreeOfVertexNC(hasse, v) <= 1;
Comment on lines +2744 to +2752
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsMeetIrreducible relies on “at most one upper cover in the Hasse diagram”, which (like the join case) is only equivalent to “not a meet of two distinct larger elements” when meets exist appropriately (e.g. meet-semilattices/lattices). For arbitrary partial orders, vertices can have multiple upper covers while no such meet representation exists. Either restrict the accepted digraphs to IsMeetSemilatticeDigraph/IsLatticeDigraph, or compute using the actual meet operation to match the documented semantics.

Copilot uses AI. Check for mistakes.
end);

InstallMethod(DigraphKings, "for a digraph and a positive integer",
[IsDigraph, IsPosInt],
function(D, n)
Expand Down
121 changes: 112 additions & 9 deletions tst/standard/oper.tst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#@local out, out1, out2, out3, p1, p2, path, preorder, qr, r, res, rtclosure, t
#@local tclosure, u1, u2, x
#@local p, q, idp, idt, M
#@local b2, chain, lattice, m3
gap> START_TEST("Digraphs package: standard/oper.tst");
gap> LoadPackage("digraphs", false);;

Expand Down Expand Up @@ -1514,18 +1515,18 @@ gap> gr := ChainDigraph(5);
<immutable chain digraph with 5 vertices>
gap> DigraphRandomWalk(gr, 2, 100);
[ [ 2, 3, 4, 5 ], [ 1, 1, 1 ] ]
gap> DigraphRandomWalk(gr, 2, 2);
gap> DigraphRandomWalk(gr, 2, 2);
[ [ 2, 3, 4 ], [ 1, 1 ] ]
gap> DigraphRandomWalk(gr, 5, 100);
[ [ 5 ], [ ] ]
gap> gr := CompleteBipartiteDigraph(10, 8);;
gap> DigraphRandomWalk(gr, 3, 0);
gap> DigraphRandomWalk(gr, 3, 0);
[ [ 3 ], [ ] ]
gap> DigraphRandomWalk(gr, 19, 5);
Error, the 2nd argument <v> must be a vertex of the 1st argument <D>,
gap> DigraphRandomWalk(gr, 123, 5);
Error, the 2nd argument <v> must be a vertex of the 1st argument <D>,
gap> DigraphRandomWalk(gr, 3, -1);
gap> DigraphRandomWalk(gr, 3, -1);
Error, the 3rd argument <t> must be a non-negative int,

# DigraphLayers
Expand Down Expand Up @@ -1822,6 +1823,108 @@ fail
gap> PartialOrderDigraphJoinOfVertices(gr1, 3, 4);
fail

# IsJoinIrreducible, IsMeetIrreducible

# The pentagon lattice N5: 1 < {2, 3}, 3 < 4, {2, 4} < 5
gap> gr := Digraph([[2, 3], [5], [4], [5], []]);
<immutable digraph with 5 vertices, 5 edges>
gap> lattice := DigraphReflexiveTransitiveClosure(gr);
<immutable preorder digraph with 5 vertices, 13 edges>
gap> IsJoinIrreducible(lattice, 1);
true
gap> IsJoinIrreducible(lattice, 2);
true
gap> IsJoinIrreducible(lattice, 3);
true
gap> IsJoinIrreducible(lattice, 4);
true
gap> IsJoinIrreducible(lattice, 5);
false
gap> IsMeetIrreducible(lattice, 1);
false
gap> IsMeetIrreducible(lattice, 2);
true
gap> IsMeetIrreducible(lattice, 3);
true
gap> IsMeetIrreducible(lattice, 4);
true
gap> IsMeetIrreducible(lattice, 5);
true

# A 4-element chain: every element is doubly irreducible
gap> chain := DigraphReflexiveTransitiveClosure(ChainDigraph(4));
<immutable preorder digraph with 4 vertices, 10 edges>
gap> IsJoinIrreducible(chain, 1);
true
gap> IsJoinIrreducible(chain, 2);
true
gap> IsJoinIrreducible(chain, 3);
true
gap> IsJoinIrreducible(chain, 4);
true
gap> IsMeetIrreducible(chain, 1);
true
gap> IsMeetIrreducible(chain, 2);
true
gap> IsMeetIrreducible(chain, 3);
true
gap> IsMeetIrreducible(chain, 4);
true

# The Boolean lattice B2: 1 < {2, 3} < 4
gap> b2 := DigraphReflexiveTransitiveClosure(Digraph([[2, 3], [4], [4], []]));
<immutable preorder digraph with 4 vertices, 9 edges>
gap> IsJoinIrreducible(b2, 1);
true
gap> IsJoinIrreducible(b2, 2);
true
gap> IsJoinIrreducible(b2, 3);
true
gap> IsJoinIrreducible(b2, 4);
false
gap> IsMeetIrreducible(b2, 1);
false
gap> IsMeetIrreducible(b2, 2);
true
gap> IsMeetIrreducible(b2, 3);
true
gap> IsMeetIrreducible(b2, 4);
true

# The M3 lattice: 1 < {2, 3, 4} < 5
gap> m3 := DigraphReflexiveTransitiveClosure(Digraph([[2, 3, 4], [5], [5], [5], []]));
<immutable preorder digraph with 5 vertices, 12 edges>
gap> IsJoinIrreducible(m3, 1);
true
gap> IsJoinIrreducible(m3, 2);
true
gap> IsJoinIrreducible(m3, 3);
true
gap> IsJoinIrreducible(m3, 4);
true
gap> IsJoinIrreducible(m3, 5);
false
gap> IsMeetIrreducible(m3, 1);
false
gap> IsMeetIrreducible(m3, 2);
true
gap> IsMeetIrreducible(m3, 3);
true
gap> IsMeetIrreducible(m3, 4);
true
gap> IsMeetIrreducible(m3, 5);
true

# Error handling
gap> IsJoinIrreducible(CycleDigraph(3), 1);
Error, the 1st argument <D> must satisfy IsPartialOrderDigraph,
gap> IsMeetIrreducible(CycleDigraph(3), 1);
Error, the 1st argument <D> must satisfy IsPartialOrderDigraph,
gap> IsJoinIrreducible(Digraph([[1]]), 2);
Error, the 2nd argument <v> must be a vertex of the 1st argument <D>,
gap> IsMeetIrreducible(Digraph([[1]]), 2);
Error, the 2nd argument <v> must be a vertex of the 1st argument <D>,

# DigraphClosure
gap> gr := Digraph([[4, 5, 6, 7, 9], [7, 3], [2, 6, 7, 9, 10],
> [5, 6, 7, 1, 9], [1, 4, 6, 7], [7, 1, 3, 4, 5],
Expand Down Expand Up @@ -2460,9 +2563,9 @@ gap> ConormalProduct(CycleDigraph(2), CycleDigraph(8));
#HomomorphicProduct
gap> D := Digraph([[2, 3], [1, 3, 3], [1, 2, 2]]);
<immutable multidigraph with 3 vertices, 8 edges>
gap> HomomorphicProduct(D, D);
gap> HomomorphicProduct(D, D);
Error, the 1st argument (a digraph) must not satisfy IsMultiDigraph
gap> DigraphSymmetricClosure(CycleDigraph(6));
gap> DigraphSymmetricClosure(CycleDigraph(6));
<immutable symmetric digraph with 6 vertices, 12 edges>
gap> HomomorphicProduct(PetersenGraph(), last);
<immutable digraph with 60 vertices, 1080 edges>
Expand Down Expand Up @@ -2525,7 +2628,7 @@ gap> StrongProduct(NullDigraph(0), CompleteDigraph(3));
<immutable empty digraph with 0 vertices>
gap> D1 := Digraph([[2], [1, 3, 4], [2, 5], [2, 5], [3, 4]]);
<immutable digraph with 5 vertices, 10 edges>
gap> D2 := Digraph([[2], [1, 3, 4], [2], [2]]);
gap> D2 := Digraph([[2], [1, 3, 4], [2], [2]]);
<immutable digraph with 4 vertices, 6 edges>
gap> LexicographicProduct(D1, D2);
<immutable digraph with 20 vertices, 190 edges>
Expand All @@ -2545,7 +2648,7 @@ gap> OutNeighbours(last);
[ 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20 ],
[ 9, 10, 11, 12, 13, 14, 15, 16, 18 ],
[ 9, 10, 11, 12, 13, 14, 15, 16, 18 ] ]
gap> LexicographicProduct(ChainDigraph(3), CycleDigraph(7));
gap> LexicographicProduct(ChainDigraph(3), CycleDigraph(7));
<immutable digraph with 21 vertices, 119 edges>

# SwapDigraphs
Expand Down Expand Up @@ -3032,7 +3135,7 @@ Error, expected an edge between the 2nd and 3rd arguments (vertices) 1 and

# DigraphContractEdge: Edge is a looped edge (u = v)
gap> D := DigraphByEdges([[1, 1], [2, 1], [1, 2]]);;
gap> DigraphVertexLabels(D);;
gap> DigraphVertexLabels(D);;
gap> C := DigraphContractEdge(D, 1, 1);
Error, The 2nd argument <u> must not be equal to the 3rd argument <v>
gap> DigraphHasLoops(D);
Expand Down Expand Up @@ -3196,7 +3299,7 @@ Error, expected an edge between the 2nd and 3rd arguments (vertices) 1 and

# DigraphContractEdge: Edge is a looped edge (u = v) (mutable)
gap> D := DigraphByEdges(IsMutableDigraph, [[1, 1], [2, 1], [1, 2]]);;
gap> DigraphVertexLabels(D);;
gap> DigraphVertexLabels(D);;
gap> DigraphContractEdge(D, 1, 1);
Error, The 2nd argument <u> must not be equal to the 3rd argument <v>
gap> DigraphHasLoops(D);
Expand Down
Loading