From 465300da1ab573bb51977d03f038c35926daed90 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 30 Sep 2026 09:59:34 +0200 Subject: [PATCH 1/2] Fix IsFlagTransitiveGeometry for rank >= 4 The Buekenhout-Hermand criterion must hold for every set of types J with |J| >= 3, but only the suffixes {k..n} were checked. For rank 4 this skipped {1,2,3}, {1,2,4} and {1,3,4}, so some coset incidence systems with several orbits on flags were reported as flag-transitive. Check all such J, with the minimal type of J as the distinguished one. Fixes #57 Assisted-by: Claude Code (Opus 5.5) --- lib/diagram.gi | 67 +++++++++++++++++++++++--------------------------- tst/bugfix.tst | 11 +++++++++ 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/diagram.gi b/lib/diagram.gi index 7a7dadf..b2941d6 100644 --- a/lib/diagram.gi +++ b/lib/diagram.gi @@ -505,52 +505,47 @@ InstallMethod( BorelSubgroup, "for coset geometries", InstallMethod( IsFlagTransitiveGeometry, "for coset geometries", [ IsCosetGeometry ], function( cg ) - ## From Buekenhout's chapter in the "Handbook of Incidence Geometry" - ## a coset geometry with parabolics {Gi} is flag-transitive if - ## (G1 G2) \cap (G1 G3) \cap ... \cap (G1 Gn) = G1( G2 \cap...\cap Gn), - ## (G2 G3) \cap ...\cap (G2 Gn) = G2( G3 \cap ... \cap Gn), - ## ..., (G(n-2) G(n-1)) \cap (G(n-2) G(n)) = G(n-2)( G(n-1)\cap Gn ) - ## For each case, we have that the right-hand side is contained in - ## the left-hand side, so it suffices to compute sizes of the entities - ## in each equation. - - local g, parabolics, gi, gj, orb, trans, rank, - left, int, newint, act, right, i, j; + ## Buekenhout and Hermand: a coset geometry with parabolics {Gi} is + ## flag-transitive if and only if for every set J of types with |J| >= 3, + ## and a = min(J), K = J - {a}: + ## \cap_{j in K} (Ga Gj) = Ga (\cap_{j in K} Gj). + ## The right-hand side is contained in the left-hand side, so it suffices + ## to compare sizes, both divided by |Ga|. The left-hand side corresponds + ## to the intersection of the orbits of the Gj on the trivial coset in the + ## action of G on the right cosets of Ga. + + local g, parabolics, rank, orbits, act, J, a, K, orb, int, left, right, i, j; g := cg!.group; parabolics := cg!.parabolics; rank := Size(parabolics); - ## Note that |G1( G2 \cap...\cap Gn)| = |( G2 \cap...\cap Gn) : (G1\cap G2 \cap...\cap Gn)|. - ## So to efficiently compute the right hand side, we do the "shortest one" first - ## and iterate. To compute the left hand sides is much more difficult. - ## For the first case, we need to compute the intersections of the orbits - ## of G2, G3, ..., Gn on the trivial coset G1 in the action of G on - ## the right cosets of G1. - - ## Start from the end, where the number of intersections are shortest - ## and go upwards. - - int := Intersection( parabolics[rank - 1], parabolics[rank] ); - - for i in [2..rank-1] do - # compute right-hand side - newint := Intersection( int, parabolics[rank - i] ); - right := Size(int) / Size(newint); - - # compute left-hand side - gi := parabolics[rank-i]; - act := FactorCosetAction(g, gi); - orb := [1..Index(g,gi)]; - for j in [rank-i+1..rank] do - gj := Image(act, parabolics[j]); - orb := Intersection(orb, Orbit(gj, 1)); + ## orbits[i][j] is the orbit of Gj on the trivial coset of Gi, for i < j + orbits := []; + for i in [1..rank-2] do + act := FactorCosetAction(g, parabolics[i]); + orbits[i] := []; + for j in [i+1..rank] do + orbits[i][j] := Set(Orbit(Image(act, parabolics[j]), 1)); od; + od; + + for J in Combinations([1..rank]) do + if Size(J) < 3 then + continue; + fi; + a := J[1]; + K := J{[2..Size(J)]}; + + orb := Intersection(orbits[a]{K}); left := Size(orb); + + int := Intersection(parabolics{K}); + right := Index(int, Intersection(int, parabolics[a])); + if left > right then return false; fi; - int := newint; od; return true; end ); diff --git a/tst/bugfix.tst b/tst/bugfix.tst index 75a7986..c6b362b 100644 --- a/tst/bugfix.tst +++ b/tst/bugfix.tst @@ -28,5 +28,16 @@ true gap> PreImagesSet(d, pts) = PreImagesSetNC(d, pts); true +# verify IsFlagTransitiveGeometry checks every subset of types, not just +# suffixes: here flags of type {1,3,4} form two orbits; +# see +gap> r0 := (1,2)(3,5)(4,6)(7,8);; r1 := (1,2)(3,7)(4,5)(6,8);; +gap> r2 := (1,3)(2,5)(4,7)(6,8);; r3 := (1,4)(2,6)(3,7)(5,8);; +gap> G := Group(r0, r1, r2, r3);; +gap> cg := CosetGeometry(G, [Subgroup(G, [r1, r2, r3]), Subgroup(G, [r0, r2, r3]), +> Subgroup(G, [r0, r1, r3]), Subgroup(G, [r0, r1, r2])]);; +gap> IsFlagTransitiveGeometry(cg); +false + # gap> STOP_TEST("bugfix.tst", 1 ); From 93fc1a56f5b956e69b608453d9dd00bfdffad1d9 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 14 Oct 2026 17:43:35 +0200 Subject: [PATCH 2/2] Don't assume point 1 of the coset action is the trivial coset GAP does not document which point of FactorCosetAction corresponds to the subgroup itself. Build the action from an explicit transversal and look the trivial coset up instead. Also add a flag-transitive rank-4 case to the regression test, so a method that always answers false would fail it. Assisted-by: Claude Code (Fable 5.1) --- lib/diagram.gi | 8 +++++--- tst/bugfix.tst | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/diagram.gi b/lib/diagram.gi index b2941d6..7718238 100644 --- a/lib/diagram.gi +++ b/lib/diagram.gi @@ -514,7 +514,7 @@ InstallMethod( IsFlagTransitiveGeometry, "for coset geometries", ## to the intersection of the orbits of the Gj on the trivial coset in the ## action of G on the right cosets of Ga. - local g, parabolics, rank, orbits, act, J, a, K, orb, int, left, right, i, j; + local g, parabolics, rank, orbits, rt, act, pt, J, a, K, orb, int, left, right, i, j; g := cg!.group; parabolics := cg!.parabolics; @@ -523,10 +523,12 @@ InstallMethod( IsFlagTransitiveGeometry, "for coset geometries", ## orbits[i][j] is the orbit of Gj on the trivial coset of Gi, for i < j orbits := []; for i in [1..rank-2] do - act := FactorCosetAction(g, parabolics[i]); + rt := RightTransversal(g, parabolics[i]); + act := ActionHomomorphism(g, rt, OnRight); + pt := PositionCanonical(rt, One(g)); orbits[i] := []; for j in [i+1..rank] do - orbits[i][j] := Set(Orbit(Image(act, parabolics[j]), 1)); + orbits[i][j] := Set(Orbit(Image(act, parabolics[j]), pt)); od; od; diff --git a/tst/bugfix.tst b/tst/bugfix.tst index c6b362b..d4bafbf 100644 --- a/tst/bugfix.tst +++ b/tst/bugfix.tst @@ -39,5 +39,11 @@ gap> cg := CosetGeometry(G, [Subgroup(G, [r1, r2, r3]), Subgroup(G, [r0, r2, r3] gap> IsFlagTransitiveGeometry(cg); false +# and a flag-transitive rank-4 example: the Coxeter complex of S5 +gap> G := SymmetricGroup(5);; s := List([1..4], i -> (i, i+1));; +gap> cg := CosetGeometry(G, List([1..4], i -> Subgroup(G, s{Difference([1..4], [i])})));; +gap> IsFlagTransitiveGeometry(cg); +true + # gap> STOP_TEST("bugfix.tst", 1 );