From c228c645dcfb8791b1f7e42bede0219081a2b6d9 Mon Sep 17 00:00:00 2001 From: hulpke Date: Fri, 21 Aug 2026 13:16:48 -0600 Subject: [PATCH 1/3] ENHANCE: Performance of Isomorphism Add a documented global function `AsAutomorphism(sub,autom)`, replacing the local `asAutom`/`asAutomorphism` helpers that were duplicated in `AutomorphismGroup` and `PatheticIsomorphism`. Factor the normal subgroup class stabilization into a local function `stabilizeSubgroupClass`, using permutation action and set stabilizer instead of `OrbitStabilizerAlgorithm`, lower the index threshold to 10^3 for using it. Keep the inner automorphism group as permutations for the condition search. When finding the isomorphism through multiple orbits and stabilizers, reduce the number of generators. Clean out some dead code and careless double computation. Ensure existing permrep is used for stabilizer. Avoid searching hard for 2 generators if the cost seems high. --- lib/autsr.gi | 219 ++++++++++++++++++++++---------------- lib/morpheus.gi | 12 ++- lib/oprt.gd | 45 ++++++++ tst/testextra/grpauto.tst | 14 +++ 4 files changed, 196 insertions(+), 94 deletions(-) diff --git a/lib/autsr.gi b/lib/autsr.gi index 916368ec79..dee27ccd77 100644 --- a/lib/autsr.gi +++ b/lib/autsr.gi @@ -104,11 +104,10 @@ end ); # case the values of the relators on pre-images in G do not depend on the choice # of representatives and can be used to deduce the module automorphism # belonging to a factor group automorphism. -BindGlobal("AGSRFindRels",function(nat,newgens) +BindGlobal("AGSRFindRels",function(C,nat,newgens) local # -- setup -- M, # the module: kernel of the factor map `nat` - C, # the centralizer of M in the source group p, # the smallest prime dividing |M| # -- result accumulator -- all, # the collected relators (returned in the `rels` component) @@ -130,29 +129,14 @@ local # -- loop counters -- i, # loop index over the relators cnt; # iteration counter (bail out after too many attempts) + M:=KernelOfMultiplicativeGeneralMapping(nat); - C:=Centralizer(Source(nat),M); - if not IsSubset(FrattiniSubgroup(C),M) then - return fail; - fi; p:=SmallestPrimeDivisor(Size(M)); all:=[]; pregens:=SmallGeneratingSet(C); gens:=List(pregens,x->ImagesRepresentative(nat,x)); sub:=SubgroupNC(Image(nat),gens); - #if newgens=true then - # # so generators new - # sub:=TrivialSubgroup(Image(nat)); - # while Size(sub)ForAll(ExponentSums(x),x->x mod p=0)); rels:=List(rels,x->ElementOfFpGroup(FamilyObj(One(fp)),x)); new:=RestrictedMapping(nat,C)*hom; + Assert(1,IsGroupGeneralMappingByImages(new)); # otherwise pre image=id pre:=List(rels,x->PreImagesRepresentative(new,x)); for i in [1..Length(rels)] do if not pre[i] in sub then @@ -183,6 +168,7 @@ end); BindGlobal("AGSRPrepareAutomLift",function(G,pcgs,nat) local + C, # kernel centralizer # -- the cocycle setup record (main structure, returned) -- ocr, # the one-cohomology record collecting all data for the # automorphism lift @@ -202,11 +188,7 @@ local i, # loop index (over relators / matrix rows) g, # loop index over generators v, # loop variable over the rows of `RS` - cnt, # counter bounding the search for module-automorphism relators - # -- vestigial (computed but never used) -- - tmp, # zero module vector; only feeds the unused `L0` - L0, # assembled but never read afterwards - R; # assembled but never read afterwards + cnt; # counter bounding the search for module-automorphism relators ocr:=rec(group:=G,modulePcgs:=pcgs); fphom:=IsomorphismFpGroup(G); @@ -231,11 +213,18 @@ local 100 then cnt:=0; - repeat - ocr.trickrels:=AGSRFindRels(nat,cnt>3); - cnt:=cnt+1; - until ocr.trickrels<>fail or 2^cnt>100*Size(ocr.moduleauts); - if ocr.trickrels=fail then Info(InfoMorph,1,"trickrels fails");fi; + C:=Centralizer(Source(nat),KernelOfMultiplicativeGeneralMapping(nat)); + if not IsSubset(FrattiniSubgroup(C), + KernelOfMultiplicativeGeneralMapping(nat)) then + + ocr.trickrels:=fail; # not possible + else + repeat + ocr.trickrels:=AGSRFindRels(C,nat,cnt>3); + cnt:=cnt+1; + until ocr.trickrels<>fail or 2^cnt>100*Size(ocr.moduleauts); + fi; + if ocr.trickrels=fail then Info(InfoMorph,1,"trickrels fails");fi; else ocr.trickrels:=fail; fi; @@ -249,11 +238,13 @@ local # Initialize system. len:=Length(ocr.generators); dim:=Length(pcgs); - tmp := ocr.moduleMap( ocr.identity ); - L0 := Concatenation( List( [ 1 .. len ], x -> tmp ) ); - ConvertToVectorRep(L0,ocr.field); - R := ListWithIdenticalEntries( len * dim,Zero( ocr.field ) ); - ConvertToVectorRep(R,ocr.field); + +# not used +# tmp := ocr.moduleMap( ocr.identity ); +# L0 := Concatenation( List( [ 1 .. len ], x -> tmp ) ); +# ConvertToVectorRep(L0,ocr.field); +# R := ListWithIdenticalEntries( len * dim,Zero( ocr.field ) ); +# ConvertToVectorRep(R,ocr.field); rels:=ocr.relators; mat:=List([1..len*dim],x->[]); @@ -864,6 +855,7 @@ local sel, # indices of the subgroups sharing the current fingerprint # -- loop counter -- i; # loop variable over the distinct fingerprints + fp:=function(x) local l; # the fingerprint list being assembled (size, class data, abelian @@ -961,6 +953,10 @@ end); # main automorphism method -- currently still using factor groups, but # nevertheless faster.. +#AsIsomorphism +InstallGlobalFunction(AsAutomorphism, + function(subgroup,autom) return Image(autom,subgroup);end); + # option somechar may be a list of characteristic subgroups, or a record with # component subgroups, orbits BindGlobal("AutomGrpSR",function(G) @@ -968,8 +964,6 @@ local # -- nested helper functions -- isBadPermrep, # helper: heuristic test for whether a permutation degree # should be reduced - asAutom, # helper: apply an automorphism to a subgroup, - # Image(hom,sub) makeaqiso, # helper: build the permutation representation AQiso/AQP of # the current AQ stablim, # helper: stabilizer computation with an index limit @@ -982,6 +976,7 @@ local # subgroups) scharorb, # characteristic orbits taken from the `someCharacteristics` # record + directs, # direct factors to be preserved, if given ff, # the fitting-free lift setup of G r, # the solvable radical of G, ff.radical rlgf, # the LG-series layer boundaries of the radical @@ -1079,6 +1074,7 @@ local jorpo, # the positions of the pair inside `jorb` substb, # the stabilizer subgroup enforcing a characteristic # subgroup/orbit + stabilizeSubgroupClass, # fct to fix class of subgroups # -- loop counters -- i, # main loop index over the series steps j, # loop index @@ -1091,7 +1087,8 @@ local return NrMovedPoints(g)^3>Size(g)*Index(g,DerivedSubgroup(g)); end; - asAutom:=function(sub,hom) return Image(hom,sub);end; + directs:=ValueOption("directs"); + #if directs<>fail and not ForAll(directs,x->IsNormal(G,x)) then Error("DIR");fi; actbase:=ValueOption("autactbase"); nosucl:=fail; @@ -1370,9 +1367,7 @@ local fratsim:=Length(b)=0; if not fratsim then b:=List(b,x->PreImagesRepresentative(hom,PcElementByExponents(MPcgs,x))); - for j in b do - N:=ClosureSubgroup(N,b); - od; + N:=ClosureSubgroup(N,b); # insert in series for j in [Length(ser),Length(ser)-1..i+1] do ser[j+1]:=ser[j]; @@ -1398,7 +1393,7 @@ local Info(InfoMorph,3,"radical automorphism stabilizer"); SetIsGroupOfAutomorphismsFiniteGroup(rada,true); NiceMonomorphism(rada:autactbase:=fail,someCharacteristics:=fail); - rada:=Stabilizer(rada,N,asAutom); + rada:=Stabilizer(rada,N,AsAutomorphism); fi; fi; until split or fratsim; @@ -1584,31 +1579,57 @@ local sub:=SubgroupProperty(sub,precond,Aperm); fi; - if IndexNC(sub,Aperm)>10^6 then - # try to find characteristic subgroups + stabilizeSubgroupClass:=function(class) + local k,kpre,classorb,i,set,kperm,acthom,stb,moves; + k:=SmallGeneratingSet(sub); + kpre:=List(k,x->PreImagesRepresentative(AQiso,x)); + classorb:=[]; + moves:=false; + for i in class do + if not i in classorb then + set:=Orbit(sub,i,k,kpre,AsAutomorphism); + classorb:=Union(classorb,set); + moves:=moves or Length(set)>1; + fi; + od; + if moves then + set:=Set(List(class,x->Position(classorb,x))); + Info(InfoMorph,3,"orbslen=",Length(classorb)); + kperm:=List(k,x->Permutation(x,classorb,k,kpre,AsAutomorphism)); + acthom:=GroupHomomorphismByImagesNC(sub,Group(kperm),k,kperm); + + stb:=Stabilizer(Range(acthom),set,OnSets); + stb:=PreImage(acthom,stb); + if IndexNC(sub,stb)>1 then + Info(InfoMorph,3,"Improved index ",IndexNC(sub,stb)); + sub:=stb; + Aperm:=Intersection(Aperm,sub); + fi; + fi; + end; + +# # do we want to preserve direct factors? + + # + # do not bother if it is small + if IndexNC(sub,Aperm)>10^3 then + # try to stabilize classes of normal subgroups that must be + # automorphism invariant Info(InfoMorph,2,"Use normal subgroup classes"); - if nosucl=fail then nosucl:=AGSRNormalSubgroupClasses(G);fi; - nosuf:=List(nosucl,x->Set(List(x,y->Image(lhom,y)))); + if nosucl=fail then + nosucl:=AGSRNormalSubgroupClasses(G:directs:=directs); + if directs<>fail then Add(nosucl,directs);fi; + fi; + nosuf:=Unique(List(nosucl,x->Set(List(x,y->Image(lhom,y))))); nosuf:=Filtered(nosuf,x->Size(x[1])>1 and Size(x[1])[Length(x),Size(x[1])]); for j in nosuf do # stabilize class - k:=SmallGeneratingSet(sub); - ac:=OrbitStabilizerAlgorithm(sub,false,false, - k,List(k,x->PreImagesRepresentative(AQiso,x)), - rec(pnt:=j, - act:= - function(set,phi) - #local phi; - #phi:=PreImagesRepresentative(AQiso,perm); - return Set(List(set,x->Image(phi,x))); - end, - onlystab:=true)); - Info(InfoMorph,3,"Improved index ",IndexNC(sub,ac.stabilizer)); - if Size(ac.stabilizer)fail then + nosuf:=List(directs,x->Image(lhom,x)); + stabilizeSubgroupClass(nosuf); fi; j:=Size(sub); @@ -1704,7 +1725,7 @@ local Info(InfoMorph,3,"radical automorphism stabilizer"); NiceMonomorphism(rada:autactbase:=fail,someCharacteristics:=fail); SetIsGroupOfAutomorphismsFiniteGroup(rada,true); - rada:=Stabilizer(rada,k,asAutom); + rada:=Stabilizer(rada,k,AsAutomorphism); fi; od; # move back to bad degree @@ -1783,14 +1804,14 @@ local for j in u do if IsList(j) then # stabilizer set of subgroups - jorb:=ShallowCopy(Orbit(AQP,j[1],C[2],C[1],asAutom)); + jorb:=ShallowCopy(Orbit(AQP,j[1],C[2],C[1],AsAutomorphism)); jorpo:=[Position(jorb,j[1]),Position(jorb,j[2])]; if jorpo[2]=fail then - Append(jorb,Orbit(AQP,j[1],C[2],C[1],asAutom)); + Append(jorb,Orbit(AQP,j[1],C[2],C[1],AsAutomorphism)); jorpo[2]:=Position(jorb,j[2]); fi; if Length(jorb)>Length(j) then - B:=ActionHomomorphism(AQP,jorb,C[2],C[1],asAutom); + B:=ActionHomomorphism(AQP,jorb,C[2],C[1],AsAutomorphism); substb:=Group(List(C[2],x->ImagesRepresentative(B,x)),()); substb:=Stabilizer(substb,Set(jorpo),OnSets); substb:=PreImage(B,substb); @@ -1802,7 +1823,7 @@ local else - substb:=Stabilizer(AQP,j,C[2],C[1],asAutom); + substb:=Stabilizer(AQP,j,C[2],C[1],AsAutomorphism); Info(InfoMorph,2,"Stabilize characteristic subgroup ",Size(j), " :",Size(AQP)/Size(substb) ); fi; @@ -1919,7 +1940,7 @@ local i:=i+1; else l:=s[i]; - for j in r do; + for j in r do l:=ClosureGroup(l,PcElementByExponents(pcgs,j)); od; if Size(l)Size(s[i]) then @@ -1973,7 +1994,8 @@ local p, # the property / fingerprint list being assembled (return value) b; # sorted abelian invariants, folded into the fingerprint - if ID_AVAILABLE(Size(a))<>fail then + if ID_AVAILABLE(Size(a))<>fail + and ValueOption(NO_PRECOMPUTED_DATA_OPTION)<>true then p:=ShallowCopy(-IdGroup(a)); # negative avoids clash with others else p:=[Size(a)]; @@ -2145,9 +2167,6 @@ end); # isomorphism available and there are many generators InstallGlobalFunction(PatheticIsomorphism,function(G,H) local - # -- nested helper function -- - asAutomorphism, # helper: apply an automorphism to a subgroup, - # Image(hom,sub) # -- matched characteristic subgroups -- d, # NOTE: two roles -- the matched-characteristics # record, then the direct product G x H @@ -2184,15 +2203,14 @@ local iso, # NOTE: two roles -- the isomorphism from a recursive # call, and later a flag for whether the permutation # representation has been built + makenewa, + origa, # -- loop counters -- i, # NOTE: two roles -- a loop index, and an # IsomorphismPermGroup used to reduce a non-perm/pc input # group j; # loop index - asAutomorphism:=function(sub,hom) - return Image(hom,sub); - end; # TODO: use matgrp package if not (IsPermGroup(G) or IsPcGroup(G)) then @@ -2287,26 +2305,44 @@ local a:=AutomorphismGroup(d:autactbase:=aab,someCharacteristics:=somechar, directs:=aab, delaypermrep:=true ); + + origa:=a; + makenewa:=function(makesmall) + local map,as; + if not HasIsomorphismPermGroup(origa) and IsBound(origa!.makeaqiso) then + origa!.makeaqiso(); + fi; + map:=IsomorphismPermGroup(origa); + if makesmall then + as:=SmallGeneratingSet(Image(map,a)); + if Length(as)PreImagesRepresentative(map,x)); + Info(InfoMorph,1,"Genreduction:",Length(GeneratorsOfGroup(a)), + "=>",Length(as)); + a:=SubgroupNC(Parent(a),as); + fi; + fi; + SetIsGroupOfAutomorphismsFiniteGroup(a,true); + SetNiceMonomorphism(a,map); + SetIsomorphismPermGroup(a,map); + end; + + for i in cG do if not ForAll(GeneratorsOfGroup(a),x->Image(x,i)=i) then - a:=Stabilizer(a,i,asAutomorphism); + makenewa(Length(GeneratorsOfGroup(a))>12); + a:=Stabilizer(a,i,AsAutomorphism); fi; od; + makenewa(Length(GeneratorsOfGroup(a))>12); iso:=fail; - #if NrMovedPoints(api)>5000 then - # K:=SmallerDegreePermutationRepresentation(api); - # Info(InfoMorph,2,"Permdegree reduced ", -# NrMovedPoints(api),"->",NrMovedPoints(Image(K))); -# iso:=iso*K; -# api:=Image(iso); -# fi; # now work in reverse through the characteristic factors conj:=One(a); K:=Image(e1,G); L:=Image(e2,H); - map:=AGBoundedOrbrep(a,K,L,asAutomorphism,20); + map:=AGBoundedOrbrep(a,K,L,AsAutomorphism,100); if map=false then Info(InfoMorph,1,"Shortorb test noniso"); return fail; @@ -2315,7 +2351,8 @@ local Info(InfoMorph,1,"Shortorb test iso found"); else - as:=a; + + #as:=a; Add(cG,TrivialSubgroup(d)); SortBy(cG,x->-Size(x)); @@ -2328,24 +2365,26 @@ local if iso<>fail then map:=fail; else - map:=AGBoundedOrbrep(as,u,v,asAutomorphism,100); + makenewa(Length(GeneratorsOfGroup(a))>8); + map:=AGBoundedOrbrep(a,u,v,AsAutomorphism,200); fi; if map=false then Info(InfoMorph,1,"Shortorb factor noniso"); return fail; elif map<>fail then Info(InfoMorph,1,"Shortorb factor reduce ",map.orblen); - as:=SubgroupNC(Parent(as),map.stabgens); + a:=SubgroupNC(Parent(a),map.stabgens); + makenewa(Length(GeneratorsOfGroup(a))>8); map:=map.rep; conj:=conj*map; K:=Image(map,K); - as:=as^map; + a:=a^map; else if iso=fail then Info(InfoMorph,1,"Shortorb failed, get delayed permrep"); - if IsBound(a!.makeaqiso) then a!.makeaqiso();fi; + makenewa(Length(GeneratorsOfGroup(a))>8); iso:=IsomorphismPermGroup(a:autactbase:=aab); - api:=Image(iso,as); + api:=Image(iso,a); fi; if IsSolvableGroup(api) then @@ -2354,7 +2393,7 @@ local gens:=SmallGeneratingSet(api); fi; pre:=List(gens,x->PreImagesRepresentative(iso,x)); - map:=RepresentativeAction(SubgroupNC(a,pre),u,v,asAutomorphism); + map:=RepresentativeAction(SubgroupNC(Parent(a),pre),u,v,AsAutomorphism); if map=fail then return fail; fi; @@ -2362,7 +2401,7 @@ local K:=Image(map,K); if Size(i)>1 then - u:=Stabilizer(api,v,gens,pre,asAutomorphism); + u:=Stabilizer(api,v,gens,pre,AsAutomorphism); Info(InfoMorph,1,"Factor ",Size(d)/Size(i),": ", "reduce by ",Size(api)/Size(u)); api:=u; diff --git a/lib/morpheus.gi b/lib/morpheus.gi index dfcbd155a8..c1528ade2e 100644 --- a/lib/morpheus.gi +++ b/lib/morpheus.gi @@ -1754,7 +1754,7 @@ end); ## InstallGlobalFunction(Morphium,function(G,H,DoAuto) local combi,Gr,Gcl,Ggc,Hr,Hcl,bg,bpri,x,dat, - gens,i,c,hom,elms,price,result,inns,bcl,vsu; + gens,i,c,hom,elms,price,result,inns,bcl,vsu,costlimit; if IsSolvableGroup(G) and CanEasilyComputePcgs(G) then gens:=MinimalGeneratingSet(G); @@ -1766,6 +1766,8 @@ local combi,Gr,Gcl,Ggc,Hr,Hcl,bg,bpri,x,dat, Ggc:=List(gens,i->First(Gcl,j->ForAny(j,j->ForAny(j.classes,k->i in k)))); combi:=List(Ggc,i->Concatenation(List(i,i->i.classes))); + + costlimit:=ValueOption("costlimit"); price:=Product(combi,i->Sum(i,Size)); Info(InfoMorph,1,"generating system ",Sum(Flat(combi),Size), " of price:",price,""); @@ -1810,6 +1812,9 @@ local combi,Gr,Gcl,Ggc,Hr,Hcl,bg,bpri,x,dat, gens:=bg; else + if costlimit<>fail and price>costlimit*20 and Sum(Gcl,Length)>30 then + return -1; + fi; gens:=MorFindGeneratingSystem(G,Gcl); fi; @@ -1931,10 +1936,9 @@ local combi,Gr,Gcl,Ggc,Hr,Hcl,bg,bpri,x,dat, fi; result.inner:=inns; else - dat:=ValueOption("costlimit"); - if IsInt(dat) and Product(List(combi,x->Sum(x,Size)))>dat then + if IsInt(costlimit) and Product(List(combi,x->Sum(x,Size)))>costlimit then Info(InfoMorph,2,"Morpheus seems to be to costly: ", - Product(List(combi,x->Sum(x,Size)))," vs ",dat); + Product(List(combi,x->Sum(x,Size)))," vs ",costlimit); return -1; # not fail, as this is valid fi; result:=MorClassLoop(H,combi,result,7); diff --git a/lib/oprt.gd b/lib/oprt.gd index 5d44cdc3d7..e40960ff3b 100644 --- a/lib/oprt.gd +++ b/lib/oprt.gd @@ -3004,6 +3004,51 @@ DeclareGlobalFunction("OnTuplesSets"); ## DeclareGlobalFunction("OnTuplesTuples"); +############################################################################# +## +#F AsAutomorphism( , ) +## +## <#GAPDoc Label="AsAutomorphism"> +## +## +## +## +## returns the image of the structure sub under the automorphism +## autom, that is Image(autom,sub). +##

+## This function is an action function in the sense of +## and thus is intended to be +## passed as last argument to functions such as +## , +## or . It describes the action of a group +## of automorphisms (or, more generally, of any group whose elements are +## endomorphisms of a common structure) on the subgroups, subalgebras or +## other substructures of this structure. The acting group must consist of +## mappings whose source contains sub; no test for this is performed. +##

+## Note that this action is not the same as +## applied to the elements of +## sub: it is the whole structure that is mapped, and the result is +## again a structure of the same kind. +## g:=Group((1,2),(3,4));; +## gap> a:=AutomorphismGroup(g);; +## gap> u:=Subgroup(g,[(1,2)]); +## Group([ (1,2) ]) +## gap> orb:=Orbit(a,u,AsAutomorphism);; +## gap> Length(orb); +## 3 +## gap> Size(Stabilizer(a,u,AsAutomorphism)); +## 2 +## gap> Size(Action(a,orb,AsAutomorphism)); +## 6 +## ]]> +## +## +## <#/GAPDoc> +## +DeclareGlobalFunction( "AsAutomorphism" ); + ############################################################################# ## #O DomainForAction( , , ) diff --git a/tst/testextra/grpauto.tst b/tst/testextra/grpauto.tst index bf188dcc9c..0cf4401cba 100644 --- a/tst/testextra/grpauto.tst +++ b/tst/testextra/grpauto.tst @@ -323,4 +323,18 @@ gap> gp2:=Group((3,19,53,9,34)(6,29,69,15,48)(8,72,52,33,18) > (174,178,233,203,238)(177,217,244,190,194));; gap> IsomorphismGroups(gp1,gp2)=fail; false +gap> g:=Group([(1,2)(5,9,6,7,8)(10,17)(11,16)(12,15)(13,14)(18,21)(22,25), +> (1,3,2,4)(6,9)(7,8)(10,13)(11,12)(14,17)(15,16)(18,21)(19,20) +> (22,25)(23,24),(1,3,2,4)(5,6,7,9,8)(10,12)(11,13)(14,16) +> (15,17)(18,22)(19,23)(20,24)(21,25), +> (1,3,2,4)(5,8,9)(10,13)(11,12)(18,23)(19,22)(20,25)(21,24), +> (1,3,2,4)(5,8,7,6,9)(10,11)(12,13)(14,16)(15,17)(18,20)(19,21) +> (22,24)(23,25)]);; +gap> h:=Group([(5,8,6)(10,11)(12,19,14,13)(15,17,16,18), +> (5,8,7,9,6)(10,11)(12,14)(13,19)(15,16)(17,18), +> (1,2)(3,4)(5,8,9,7,6)(12,17)(13,16)(14,18)(15,19), +> (1,3,4,2)(6,8)(7,9)(10,11)(12,18)(13,15)(14,17)(16,19), +> (1,3,4,2)(5,7,9,8,6)(12,14)(15,16)]);; +gap> IsomorphismGroups(g,h)=fail; +false gap>STOP_TEST("grpauto.tst"); From de6f798905a9e5ec92738a7043d34c64e04e2131 Mon Sep 17 00:00:00 2001 From: Alexander Hulpke Date: Sun, 30 Aug 2026 15:27:22 -0600 Subject: [PATCH 2/3] Added test file from #6541 --- .../2026-08-29-IsomorphismGroups.tst | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tst/testbugfix/2026-08-29-IsomorphismGroups.tst diff --git a/tst/testbugfix/2026-08-29-IsomorphismGroups.tst b/tst/testbugfix/2026-08-29-IsomorphismGroups.tst new file mode 100644 index 0000000000..d634fb67a8 --- /dev/null +++ b/tst/testbugfix/2026-08-29-IsomorphismGroups.tst @@ -0,0 +1,45 @@ +# IsomorphismGroups returned fail for isomorphic groups for some random +# states: an automorphism found by random search need not stabilize the +# normal subgroup classes used to restrict the lifting search, so the +# known subgroup was not contained in the search space, invalidating the +# backtrack. See https://github.com/gap-system/gap/issues/6537 +gap> START_TEST("2026-08-29-IsomorphismGroups.tst"); + +# +gap> gens1:=[ +> (1,4)(2,5)(3,11,10,9,7,8,6,12)(13,16)(14,17)(15,23,22,21,19,20,18,24) +> (27,35,34,33,31,32,30,36)(39,47,46,45,43,44,42,48), +> (1,3,2,4,7,5)(6,11,12,10,8,9)(13,19,17,16,15,14)(18,23,24,22,20,21) +> (25,27,29,28,31,26)(30,32,33,34,35,36)(37,43,38,40,39,41)(42,44,45,46,47,48), +> (1,13,4,16)(2,14,5,17)(3,15,7,19)(6,18,10,22)(8,20,11,23)(9,21,12,24) +> (25,37,28,40)(26,38,29,41)(27,39,31,43)(30,42,34,46)(32,44,35,47)(33,45,36,48), +> (1,7,8)(2,10,12)(3,11,4)(5,6,9)(13,15,23)(14,22,24)(16,19,20)(17,18,21) +> (25,31,35)(26,30,36)(27,32,28)(29,34,33)(37,39,44)(38,42,48)(40,43,47)(41,46,45), +> (1,25)(2,26)(3,27)(4,28)(5,29)(6,30)(7,31)(8,32)(9,33)(10,34)(11,35)(12,36) +> (13,37)(14,38)(15,39)(16,40)(17,41)(18,42)(19,43)(20,44)(21,45)(22,46)(23,47) +> (24,48)];; +gap> gens2:=[ +> (1,3,2,4,7,5)(6,11,12,10,8,9)(13,15,14,16,19,17)(18,23,24,22,20,21) +> (25,27,29,28,31,26)(30,32,36,34,35,33)(37,39,41,40,43,38)(42,44,48,46,47,45), +> (1,13,4,16)(2,14,5,17)(3,15,7,19)(6,18,10,22)(8,20,11,23)(9,21,12,24) +> (25,40,28,37)(26,41,29,38)(27,43,31,39)(30,46,34,42)(32,47,35,44)(33,48,36,45), +> (1,25,4,28)(2,26,5,29)(3,27,7,31)(6,30,10,34)(8,32,11,35)(9,33,12,36) +> (13,37,16,40)(14,38,17,41)(15,39,19,43)(18,42,22,46)(20,44,23,47)(21,45,24,48), +> (1,7,8)(2,10,12)(3,11,4)(5,6,9)(13,19,20)(14,22,24)(15,23,16)(17,18,21) +> (25,35,27)(26,33,34)(28,32,31)(29,36,30)(37,47,39)(38,45,46)(40,44,43)(41,48,42), +> (1,48)(2,47)(3,46)(4,45)(5,44)(6,39)(7,42)(8,38)(9,37)(10,43)(11,41)(12,40) +> (13,36)(14,35)(15,34)(16,33)(17,32)(18,27)(19,30)(20,26)(21,25)(22,31)(23,29) +> (24,28)];; +gap> G1:=Group(gens1);; G2:=Group(gens2);; + +# Before the fix this failed for i=3. The earlier iterations are needed: +# they store attribute values in G1 and G2 that influence the later ones. +gap> for i in [1..3] do +> Reset(GlobalMersenneTwister,i); Reset(GlobalRandomSource,i); +> if IsomorphismGroups(G1,G2)=fail then +> Print("wrong fail for seed ",i,"\n"); +> fi; +> od; + +# +gap> STOP_TEST("2026-08-29-IsomorphismGroups.tst"); From c5038419c0b06264fb59f75de347e6cd5d56b5d2 Mon Sep 17 00:00:00 2001 From: hulpke Date: Tue, 1 Sep 2026 09:01:12 -0600 Subject: [PATCH 3/3] Re-enable prior quick search before trying to reduce generators. --- lib/autsr.gi | 159 +++++++++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 67 deletions(-) diff --git a/lib/autsr.gi b/lib/autsr.gi index dee27ccd77..ada05c5808 100644 --- a/lib/autsr.gi +++ b/lib/autsr.gi @@ -2309,8 +2309,15 @@ local origa:=a; makenewa:=function(makesmall) local map,as; - if not HasIsomorphismPermGroup(origa) and IsBound(origa!.makeaqiso) then - origa!.makeaqiso(); + if not HasIsomorphismPermGroup(origa) then + if IsBound(origa!.makeaqiso) then + origa!.makeaqiso(); + else + as:=NiceMonomorphism(origa:autactbase:=aab,someCharacteristics:=somechar, + directs:=aab, + delaypermrep:=true ); + SetIsomorphismPermGroup(origa,as); + fi; fi; map:=IsomorphismPermGroup(origa); if makesmall then @@ -2334,82 +2341,100 @@ local a:=Stabilizer(a,i,AsAutomorphism); fi; od; - makenewa(Length(GeneratorsOfGroup(a))>12); - iso:=fail; - - # now work in reverse through the characteristic factors - conj:=One(a); - K:=Image(e1,G); - L:=Image(e2,H); - map:=AGBoundedOrbrep(a,K,L,AsAutomorphism,100); - if map=false then - Info(InfoMorph,1,"Shortorb test noniso"); - return fail; - elif map<>fail then - conj:=map.rep; - Info(InfoMorph,1,"Shortorb test iso found"); - else + conj:=fail; + if Length(GeneratorsOfGroup(a))>12 then + # does old quick test work without attempting generator reduction? + conj:=One(a); + K:=Image(e1,G); + L:=Image(e2,H); + map:=AGBoundedOrbrep(a,K,L,AsAutomorphism,20); + if map=false then + Info(InfoMorph,1,"Shortorb test noniso"); + return fail; + elif map<>fail then + conj:=map.rep; + Info(InfoMorph,1,"Shortorb test iso found"); + else + conj:=fail; + fi; + fi; + if conj=fail then + makenewa(Length(GeneratorsOfGroup(a))>12); + iso:=fail; + + # now work in reverse through the characteristic factors + conj:=One(a); + K:=Image(e1,G); + L:=Image(e2,H); + map:=AGBoundedOrbrep(a,K,L,AsAutomorphism,100); + if map=false then + Info(InfoMorph,1,"Shortorb test noniso"); + return fail; + elif map<>fail then + conj:=map.rep; + Info(InfoMorph,1,"Shortorb test iso found"); + else - #as:=a; - Add(cG,TrivialSubgroup(d)); + Add(cG,TrivialSubgroup(d)); - SortBy(cG,x->-Size(x)); - for i in cG do - u:=ClosureGroup(i,K); - v:=ClosureGroup(i,L); - if u<>v then + SortBy(cG,x->-Size(x)); + for i in cG do + u:=ClosureGroup(i,K); + v:=ClosureGroup(i,L); + if u<>v then - # try cheap orbit stabilizer first - if iso<>fail then - map:=fail; - else - makenewa(Length(GeneratorsOfGroup(a))>8); - map:=AGBoundedOrbrep(a,u,v,AsAutomorphism,200); - fi; - if map=false then - Info(InfoMorph,1,"Shortorb factor noniso"); - return fail; - elif map<>fail then - Info(InfoMorph,1,"Shortorb factor reduce ",map.orblen); - a:=SubgroupNC(Parent(a),map.stabgens); - makenewa(Length(GeneratorsOfGroup(a))>8); - map:=map.rep; - conj:=conj*map; - K:=Image(map,K); - a:=a^map; - else - if iso=fail then - Info(InfoMorph,1,"Shortorb failed, get delayed permrep"); - makenewa(Length(GeneratorsOfGroup(a))>8); - iso:=IsomorphismPermGroup(a:autactbase:=aab); - api:=Image(iso,a); - fi; - - if IsSolvableGroup(api) then - gens:=Pcgs(api); + # try cheap orbit stabilizer first + if iso<>fail then + map:=fail; else - gens:=SmallGeneratingSet(api); + makenewa(Length(GeneratorsOfGroup(a))>8); + map:=AGBoundedOrbrep(a,u,v,AsAutomorphism,200); fi; - pre:=List(gens,x->PreImagesRepresentative(iso,x)); - map:=RepresentativeAction(SubgroupNC(Parent(a),pre),u,v,AsAutomorphism); - if map=fail then + if map=false then + Info(InfoMorph,1,"Shortorb factor noniso"); return fail; - fi; - conj:=conj*map; - K:=Image(map,K); - - if Size(i)>1 then - u:=Stabilizer(api,v,gens,pre,AsAutomorphism); - Info(InfoMorph,1,"Factor ",Size(d)/Size(i),": ", - "reduce by ",Size(api)/Size(u)); - api:=u; + elif map<>fail then + Info(InfoMorph,1,"Shortorb factor reduce ",map.orblen); + a:=SubgroupNC(Parent(a),map.stabgens); + makenewa(Length(GeneratorsOfGroup(a))>8); + map:=map.rep; + conj:=conj*map; + K:=Image(map,K); + a:=a^map; + else + if iso=fail then + Info(InfoMorph,1,"Shortorb failed, get delayed permrep"); + makenewa(Length(GeneratorsOfGroup(a))>8); + iso:=IsomorphismPermGroup(a:autactbase:=aab); + api:=Image(iso,a); + fi; + + if IsSolvableGroup(api) then + gens:=Pcgs(api); + else + gens:=SmallGeneratingSet(api); + fi; + pre:=List(gens,x->PreImagesRepresentative(iso,x)); + map:=RepresentativeAction(SubgroupNC(Parent(a),pre),u,v,AsAutomorphism); + if map=fail then + return fail; + fi; + conj:=conj*map; + K:=Image(map,K); + + if Size(i)>1 then + u:=Stabilizer(api,v,gens,pre,AsAutomorphism); + Info(InfoMorph,1,"Factor ",Size(d)/Size(i),": ", + "reduce by ",Size(api)/Size(u)); + api:=u; + fi; fi; fi; - fi; - od; + od; + fi; fi; return GroupHomomorphismByImagesNC(G,H,GeneratorsOfGroup(G),