#############################################################################
##
##  This file is part of GAP, a system for computational discrete algebra.
##  This file's authors include Alexander Hulpke, Soley Jonsdottir.
##
##  Copyright of GAP belongs to its developers, whose names are too numerous
##  to list here. Please refer to the COPYRIGHT file for details.
##
##  SPDX-License-Identifier: GPL-2.0-or-later
##
##  This  file  contains an implementation of the Cannon/Holt automorphism
##  group algorithm:
##    Automorphism group computation and isomorphism testing in finite groups.
##    J. Symb. Comput. 35, No. 3, 241-267 (2003)

# call as big,perm,aut (the latter two can be groups or generator lists,
# optional permiso.
# returns two groups with corresponding generators
BindGlobal( "AGSRReducedGens", function(arg)
local big,bp,A,permgens,s,auts,sel,i,sub;
  big:=arg[1];
  bp:=arg[2];
  A:=arg[3];

  if IsGroup(bp) then
    permgens:=GeneratorsOfGroup(bp);
  else
    permgens:=bp;
    bp:=SubgroupNC(Parent(big),permgens);
  fi;
  SetSize(bp,Size(big));
  if IsGroup(A) then
    auts:=GeneratorsOfGroup(A);
  else
    auts:=A;
    A:=fail;
  fi;
  if Length(permgens)<>Length(auts) then Error("correspondence!");fi;
  s:=SmallGeneratingSet(big);
  if Length(s)+2>=Length(permgens) then
    return fail;
  fi;

  # first try to reduce by dropping generators
  sel:=[1..Length(permgens)];
  for i in [1..Length(permgens)] do
    sub:=SubgroupNC(Parent(big),permgens{Difference(sel,[i])});
    if Size(sub)=Size(big) then
      RemoveSet(sel,i);
      bp:=sub;
    fi;
  od;
  # good enough?
  if Length(s)+2<Length(sel) then
    if Length(arg)>3 then
      i:=InverseGeneralMapping(arg[4]);
    else
      i:=GroupHomomorphismByImagesNC(big,Group(auts),permgens,auts);
    fi;
    auts:=List(s,x->ImagesRepresentative(i,x));
    bp:=SubgroupNC(Parent(big),s);
    SetSize(bp,Size(big));
    auts:=Group(auts);
  else
    auts:=Group(auts{sel});
  fi;
  SetIsGroupOfAutomorphismsFiniteGroup(auts,true);
  SetIsFinite(auts,true);
  SetSize(auts,Size(bp));
  if A<>fail then
    if HasInnerAutomorphismsAutomorphismGroup(A) then
      SetInnerAutomorphismsAutomorphismGroup(auts,
        InnerAutomorphismsAutomorphismGroup(A));
    fi;
    if HasNiceMonomorphism(A) then
      SetNiceMonomorphism(auts,NiceMonomorphism(A));
      SetNiceObject(auts,NiceObject(A));
    fi;
  fi;
  return [bp,auts];
end );

# If M<=Frat(C_G(M)), try to find relators for C/M that in G evaluate to
# generators of M and for which exponent sums are multiples of p. In this
# case the values of the relators on pre-images in G do not depend on choice
# of representatives and can be used to deduce the module automorphism
# belonging to a factor group automorphism.
BindGlobal("AGSRFindRels",function(nat,newgens)
local C,M,p,all,gens,sub,q,hom,fp,rels,new,pre,i,free,cnt;
  M:=KernelOfMultiplicativeGeneralMapping(nat);
  C:=Centralizer(Source(nat),M);
  if not IsSubset(FrattiniSubgroup(C),M) then
    return fail;
  fi;
  p:=SmallestPrimeDivisor(Size(M));
  all:=[];
  if newgens=true then
    # so generators new
    sub:=TrivialSubgroup(Image(nat));
    while Size(sub)<Size(Image(nat)) do
      sub:=ClosureGroup(sub,ImagesRepresentative(nat,Random(C)));
    od;
  else
    sub:=Image(nat,C);
  fi;

  gens:=SmallGeneratingSet(sub);

  free:=FreeGroup(Length(gens));
  sub:=TrivialSubgroup(M);
  cnt:=0;
  while Size(sub)<Size(M) do
    q:=Group(gens);
    SetSize(q,Size(Image(nat,C)));
    # use `ByGenerators` to force more random relators
    hom:=IsomorphismFpGroupByGenerators(q,gens);
    fp:=Range(hom);
    rels:=Filtered(RelatorsOfFpGroup(fp),x->ForAll(ExponentSums(x),x->x mod p=0));
    rels:=List(rels,x->ElementOfFpGroup(FamilyObj(One(fp)),x));
    new:=RestrictedMapping(nat,C)*hom;
    pre:=List(rels,x->PreImagesRepresentative(new,x));
    for i in [1..Length(rels)] do
      if not pre[i] in sub then
        Add(all,MappedWord(rels[i],
          GeneratorsOfGroup(fp),GeneratorsOfGroup(free)));
        sub:=ClosureGroup(sub,pre[i]);
      fi;
    od;
    cnt:=cnt+1;
    if cnt>5 then return fail;fi;
  od;
  return rec(gens:=gens,free:=free,rels:=all);
end);

BindGlobal("AGSRPrepareAutomLift",function(G,pcgs,nat)
local ocr,fphom,fpg,free,len,dim,tmp,L0,R,rels,mat,r,RS,i,g,v,cnt;

  ocr:=rec(group:=G,modulePcgs:=pcgs);
  fphom:=IsomorphismFpGroup(G);
  ocr.identity := One(ocr.modulePcgs[1]);
  fpg:=FreeGeneratorsOfFpGroup(Range(fphom));
  ocr.factorpres:=[fpg,RelatorsOfFpGroup(Range(fphom))];
  ocr.generators:=List(GeneratorsOfGroup(Range(fphom)),
                        i->PreImagesRepresentative(fphom,i));
  OCAddMatrices(ocr,ocr.generators);
  OCAddRelations(ocr,ocr.generators);
  OCAddSumMatrices(ocr,ocr.generators);
  OCAddToFunctions(ocr);

  ocr.module:=GModuleByMats(
    LinearActionLayer(G,ocr.generators,ocr.modulePcgs),ocr.field);
  ocr.moduleauts:=MTX.ModuleAutomorphisms(ocr.module);

  if Size(ocr.moduleauts)>
      # Finding the relations comes at a cost that needs to be plausible
      # with searching multiple times through the automorphism group. This
      # order bound is a heuristic that seems to be OK by magnitude.
      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;
  else
    ocr.trickrels:=fail;
  fi;

  ocr.factorgens:=List(ocr.generators,i->Image(nat,i));
  free:=FreeGroup(Length(ocr.generators),"f");
  ocr.free:=free;
  ocr.decomp:=GroupGeneralMappingByImages(Image(nat,G),free,
        ocr.factorgens,GeneratorsOfGroup(free));

  # 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);

  rels:=ocr.relators;
  mat:=List([1..len*dim],x->[]);
  for i in mat do
    ConvertToVectorRep(i,ocr.field);
  od;
  for i in [1..Length(rels)] do
    Info(InfoCoh,2,"  relation ", i, " (",Length(rels),")");
    r:=1;
    for g  in [1..len]  do
      RS:=OCEquationMatrix(ocr,rels[i],g);
      for v in RS do
        Append(mat[r],v);
        r:=r+1;
      od;
    od;
  od;
  ocr.matrix:=ImmutableMatrix(ocr.field,mat);
  ocr.semiech:=ShallowCopy(SemiEchelonMatTransformation(ocr.matrix));
  ocr.semiech.numrows:=NrRows(ocr.matrix);
  return ocr;
end);

# solve using the stored LR decomposition
BindGlobal("AGSRSolMat",function(sem,vec)
local i,vno,x,z,sol;
  z := ZeroOfBaseDomain(sem.vectors);
  sol := ListWithIdenticalEntries(sem.numrows,z);
  ConvertToVectorRepNC(sol);
  for i in [1..Length(vec)] do
    vno := sem.heads[i];
    if vno <> 0 then
      x := vec[i];
      if x <> z then
        AddRowVector(vec, sem.vectors[vno], -x);
        AddRowVector(sol, sem.coeffs[vno], x);
      fi;
    fi;
  od;
  if IsZero(vec) then
    return sol;
  else
    return fail;
  fi;
end);

#############################################################################
##
#F  OCEquationVectorAutom(<ocr>,<r>,<genimages>)
##
BindGlobal("OCEquationVectorAutom",function(ocr,r,genimages)
local n,i;

  # If <r> has   an entry 'conjugated'   the records is  no relator  for  a
  # presentation,but belongs to relation
  #       (g_i n_i) ^ s_j =<r>
  # which is  used to determinate  normal  complements.   [i,j] is bound to
  # <conjugated>.
  if IsBound(r.conjugated)  then
    Error("not yet implemented");
  fi;
  n:=ocr.identity;

  for i in [1 .. Length(r.generators)] do
    n:=n*genimages[r.generators[i]]^r.powers[i];
  od;

  Assert(1,n in GroupByGenerators(NumeratorOfModuloPcgs(ocr.modulePcgs)));

  return ShallowCopy(ocr.moduleMap(n));

end);

BindGlobal("AGSRAutomLift",function(ocr,nat,fhom,miso)
  local v, rels, genimages, v1, psim, w, s, t, l, hom, i, e, j,ep,phom,enum;

  v:=[];
  rels:=ocr.relators;
  genimages:=List(ocr.factorgens,i->MappedWord(
                ImagesRepresentative(ocr.decomp,Image(fhom,i)),
                GeneratorsOfGroup(ocr.free),
                ocr.generators));
  for i in [1..Length(rels)] do
    v1:=OCEquationVectorAutom(ocr,rels[i],genimages);
    Add(v,v1);
  od;

  #for ep in Enumerator(ocr.moduleauts) do
  if ocr.trickrels<>fail then
    # special case for M<=Frat(C_G(M)). Use special relators for factor that
    # allow to deduce corresponding module aut.
    t:=ocr.trickrels;
    phom:=IdentityMapping(ocr.moduleauts);
    s:=List(t.gens,x->PreImagesRepresentative(nat,x));
    l:=List(t.gens,x->PreImagesRepresentative(nat,ImagesRepresentative(fhom,x)));
    s:=List(t.rels,x->MappedWord(x,GeneratorsOfGroup(t.free),s));
    l:=List(t.rels,x->MappedWord(x,GeneratorsOfGroup(t.free),l));

    s:=List(s,x->ExponentsOfPcElement(ocr.modulePcgs,x))*One(ocr.field);
    l:=List(l,x->ExponentsOfPcElement(ocr.modulePcgs,x))*One(ocr.field);
    Info(InfoMorph,5,"Deduced corresponding module automorphism");
    if RankMat(l)<Length(l) then
      return fail;
    fi;
    enum:=[s^-1*l];

  else
    phom:=IsomorphismPermGroup(ocr.moduleauts);
    enum:=Enumerator(Image(phom,ocr.moduleauts));
    Info(InfoMorph,5,"Search through module automorphisms of size ",
      Size(Image(phom)));
  fi;
  for ep in enum do
    e:=PreImagesRepresentative(phom,ep);
    psim:=e*miso;
    psim:=psim^-1;
    w:=-List(v,i->i*psim);
    #s:=SolutionMat(ocr.matrix,Concatenation(w));
    s:=AGSRSolMat(ocr.semiech,Concatenation(w));
    if s<>fail then
      psim:=psim^-1;
      t:=[];
      ConvertToVectorRep(t,ocr.field);
      l:=Length(ocr.modulePcgs);
      for i in [1..Length(genimages)] do
        v1:=s{[(i-1)*l+1..(i*l)]}*psim;
          for j in [1..Length(v1)] do
            t[(i-1)*l+j]:=v1[j];
          od;
      od;
      s:=ocr.cocycleToList(t);
      for i in [1..Length(genimages)] do
        genimages[i]:=genimages[i]*s[i];
      od;

      # later use NC version
      hom:=GroupHomomorphismByImagesNC(ocr.group,ocr.group,
              ocr.generators,genimages);
      Assert(2,IsBijective(hom));
      return hom;
    fi;
  od;
  return fail;

end);


# Find a larger subgroup that satisfies a condition, when testing
# the condition can become expensive.
# First try `SubgroupProperty`, but when it stalls attempt to find
# minimal supergroups and prove that none of them satisfies.
BindGlobal("SubgroupConditionAboveAux",function(G,cond,S1,avoid)
local S,c,hom,q,a,b,i,t,have,ups,new,u,good,abort,clim,worked,pp,
  cnt,locond,tstcnt,setupc,havetest;

  setupc:=function()
    hom:=NaturalHomomorphismByNormalSubgroupNC(G,u);
    q:=Image(hom,G);
    ups:=Image(hom,avoid);
    # aim for zuppos that cannot intersect avoid
    c:=ConjugacyClasses(q);
    c:=Filtered(c,x->IsPrimePowerInt(Order(Representative(x))) and
      not Representative(x) in ups);
    # elements that do not have prime-order power in the subgroup avoid
    c:=Filtered(c,x->not Representative(x)^
      (Order(Representative(x))/SmallestPrimeDivisor(Order(Representative(x))))
      in ups);
    # this also implies prime powers after the respective primes
    SortBy(c,Size);

    Info(InfoMorph,3,Length(c)," classes with ",Sum(c,Size)," subgroups");
  end;

  S:=S1;
  Info(InfoMorph,2,"SubgroupAbove ",IndexNC(G,S));

  # Strategy: First try `SubgroupProperty` with a bailout limit, just in
  # case it finds (enough) elements. The bailout limit is set smaller if the
  # factor is solvable, as it will be cheaper to find complementing zuppos
  # in this case.

  u:=Intersection(S,avoid);
  if IsNormal(G,u) and IsNormal(G,avoid)
    and HasSolvableFactorGroup(G,u) then
    setupc();
    clim:=Minimum(Maximum(QuoInt(Sum(c,Size),4),10),1000);
  else
    hom:=fail;
    # if less than 1/100 percent of elements succeed, assume close
    # to the subgroup has been found, and rather aim to prove there
    # will not be more.
    c:=fail;
    clim:=Maximum(QuoInt(IndexNC(G,S),10000),1000);
  fi;




  # first try, how far `SubgroupProperty` goes
  b:=0;
  tstcnt:=0;
  abort:=false;
  havetest:=[];

  if IsPermGroup(G) then
    worked:=SubgroupProperty(G,
      function(elm)
        if abort then
          return true; # are we bailing out since it behaves too badly?
        fi;
        # would it contribute to avoid outside S?
        if elm in avoid or
          ForAny(Set(Factors(Order(elm))),e->elm^e in avoid and not elm^e in S)
        then
          # cannot be good
          return false;
        fi;

        tstcnt:=tstcnt+1;
        AddSet(havetest,elm);
        if cond(elm) then
          S:=ClosureGroup(S,elm); # remember
          Info(InfoMorph,3,"New element ",IndexNC(G,S));
          b:=0;
          return true;
        else
          b:=b+1;
          if b>clim then
            abort:=true;
          fi;
          return false;
        fi;
      end,S);
  else
    worked:=S;
    abort:=true;
  fi;

  if abort=false then
    # we actually found the subgroup
    Info(InfoMorph,2,"SubgroupProperty finds index ",IndexNC(G,worked));
    return worked;
  fi;

  Info(InfoMorph,2,"SubgroupProperty did ",tstcnt," tests and improved by ",
    IndexNC(S,S1));

  if not (IsNormal(G,u) and IsNormal(G,avoid)) then
    Error("may only call if normalizing");
  fi;

  if c=fail then setupc();fi;
  havetest:=Set(List(havetest,x->ImagesRepresentative(hom,x)));

  locond:=function(elm)
            tstcnt:=tstcnt+1;
            return cond(elm);
          end;

  clim:=
    # avoid writing down a permutation representation on more than
    150000;
    #cosets, as it gets too memory expensive.


  worked:=[]; # indicate whether class worked


  # now run over all Zuppos (Conjugates of class representatives) in factor  that do
  # not intersect (the image of) avoid. These, that saisfy, will span the correct subgroup.
  i:=1;
  while worked<>fail and i<=Length(c) do

    #should we abort?
    if Sum(c{[i..Length(c)]},Size)>IndexNC(G,S) and IndexNC(G,S)<=clim then
      a:=Normalizer(G,S);
      # if the normalizer index is large, there will be many subgroups above
      if IndexNC(a,S)<200 then
        worked:=fail; # abort and go to other method
      fi;
    fi;

    # if prime powers, the primes must have worked
    a:=Representative(c[i]);

    # no coprime power in earlier class?
    have:=worked<>fail and
      ForAll(Filtered([2..Order(a)-1],o->Gcd(o,Order(a))=1),
        o->PositionProperty(c{[1..i-1]},x->a^o in x)=fail);

    pp:=false;
    if have and not IsPrimeInt(Order(a)) then
      pp:=SmallestPrimeDivisor(Order(a));
      a:=a^(Order(a)/pp);
      have:=worked[PositionProperty(c,x->a in x)];
    fi;

    if have then
      have:=false;
      a:=Representative(c[i]);
      t:=Orbit(q,a);
      t:=Filtered(t,x->not x in havetest);

      cnt:=0;
      for b in t do
        new:=PreImagesRepresentative(hom,b);
        if (pp=false or new^pp in S) and locond(new) then
          S:=ClosureGroup(S,new);
          have:=true;
          cnt:=cnt+1;
        fi;
      od;
      Info(InfoMorph,4,"Did class ",i," order =",Order(Representative(c[i])),
            ", len=",Size(c[i])," newtest=", Length(t),
            " found ",cnt,": ",Size(S));
    fi;
    if worked<>fail then Add(worked,have);fi;
    i:=i+1;
  od;

  if worked=fail then
    # still need to test
    Info(InfoMorph,2,"Go to blocks test");

    if Index(G,S)>clim then Error("clim"); fi;

    a:=SmallGeneratingSet(G);
    if Length(GeneratorsOfGroup(G))>Length(a) then
      b:=Size(G);
      G:=Group(a);
      SetSize(G,b);
    fi;

    repeat
      good:=false;
      u:=Filtered(GeneratorsOfGroup(G),x->cond(x) and not x in S);
      for i in u do
        S:=ClosureGroup(S,i);
      od;

      # try to prove no supergroup works
      t:=RightTransversal(G,S:noascendingchain); # don't try to be clever in
      # decomposing transversal, as this could be hard
      a:=Action(G,t,OnRight); # coset action, don't need homomorphism
      b:=RepresentativesMinimalBlocks(a,MovedPoints(a));
      Info(InfoMorph,3,"Above are ",Length(b)," blocks");

      if IsPermGroup(G) and Length(b)*10>IndexNC(G,S) then
        # there are too many blocks. Direct test is cheaper!
        S:=SubgroupProperty(G,locond,S);
      else

        for i in [1..Length(b)] do
          CompletionBar(InfoMorph,3,"SubgroupsAboveBlocks ",i/Length(b));
          c:=First(b[i],x->x>1);
          if cond(t[c]) then
            S:=ClosureGroup(S,t[c]);
            good:=true;
          fi;
        od;
        CompletionBar(InfoMorph,3,"SubgroupsAboveBlocks ",false);
      fi;
    until good=false;

  fi;

  Info(InfoMorph,3,"Did ",tstcnt," tests, grow by ",Index(S,S1));
  return S;

end);

# Same syntax as `SubgroupProperty`, but assumption that the tests are
# expensive. Thus minimize number of tests by doing more group calculations
InstallGlobalFunction(SubgroupConditionAbove,function(G,cond,Sorig)
local cs,nr,u,no,un,S,rad,res,ise,uno;


  # first, try to find a few elements that work (and catch the case that the
  # subgroup has small index. Ensure we test at least once for each possible
  # step.
  nr:=2^LogInt(Size(G),1000)+Length(Factors(Size(G)));
  u:=[];
  if IsPermGroup(G) then
    S:=SubgroupProperty(G,
      function(elm)
        if nr<0 then return true;fi;
        nr:=nr-1;
        if cond(elm) then
          Add(u,elm);
          return true;
        else
          return false;
        fi;
      end,Sorig);
  else
    u:=Filtered(GeneratorsOfGroup(G),cond);
    nr:=-1;
  fi;

  if nr>=0 then # succeeded (small index case)
    return S;
  elif Length(u)>0 then
    S:=ClosureGroup(Sorig,u);
  else
    S:=Sorig;
  fi;

  # now build along composition series

  rad:=SolvableRadical(G);
  # composition series through perfect residuum seems to work better
  # Possible reason: Simple composition factors arise as inner
  # automorphisms. Moving the perfect residuum (if smaller) in the series,
  # thus increases the chance that much of the subgroup is found early (and
  # does not need to be found through search through complements)
  res:=PerfectResiduum(G);

  nr:=[Core(G,S),NormalClosure(G,S)];
  if Size(nr[1])=Size(nr[2]) then nr:=nr{[1]};fi;

  # refine with perfect residuum
  no:=Intersection(nr[1],res);
  if not no in nr then nr:=Concatenation([no],nr);fi;
  if Length(nr)>1 then
    no:=ClosureGroup(Last(nr),res);
    if not no in nr then Add(nr,no);fi;
  fi;

  cs:=CompositionSeriesThrough(G,nr);

  nr:=First([1..Length(cs)],x->IsSubset(S,cs[x]));
  uno:=false;
  while nr>1 do
    nr:=nr-1;
    if IsSubset(cs[nr+1],S) then

      u:=S;
      ise:=u;
      if uno=false then
        # if the group is huge use the radical-based normalizer
        if Size(rad)>10^13 and IndexNC(G,ise)>10^5 then
          Info(InfoMorph,4,"Radical-based Normalizer:",IndexNC(G,u));
          uno:=NormalizerViaRadical(G,ise);
        else
          Info(InfoMorph,4,"Ordinary Normalizer:",IndexNC(G,u));
          uno:=Normalizer(G,ise);
        fi;
      fi;
      no:=Intersection(uno,cs[nr]);
    else

      u:=Intersection(cs[nr],S);
      ise:=Intersection(cs[nr+1],u);

      # if the group is huge use the radical-based normalizer
      if Size(Intersection(rad,cs[nr]))>10^13 and IndexNC(cs[nr],ise)>10^5 then
        Info(InfoMorph,4,"Radical-based Normalizer:",IndexNC(cs[nr],ise),
          " of ",Index(cs[nr],cs[nr+1]));
        no:=NormalizerViaRadical(cs[nr],ise);
      else
        Info(InfoMorph,4,"Ordinary Normalizer:",IndexNC(cs[nr],ise),
          " of ",Index(cs[nr],cs[nr+1]));
        no:=Normalizer(cs[nr],ise);
      fi;
    fi;

    un:=Size(no);
    no:=Group(SmallGeneratingSet(no));
    SetSize(no,un);

    un:=SubgroupConditionAboveAux(no,cond,u,Intersection(no,cs[nr+1]));
    Info(InfoMorph,2,
      "Step ",nr,": ",IndexNC(cs[nr],cs[nr+1])," to ",IndexNC(un,u));
    if not IsSubset(S,un) then
      S:=ClosureGroup(S,un);
      uno:=false;
    fi;
  od;
  return S;
end);

# find classes of normal subgroups
BindGlobal("AGSRNormalSubgroupClasses",function(G)
local fp,n,pat,pools,i,sel;
  # fingerprint
  fp:=function(x)
  local l;
    if ID_AVAILABLE(Size(x)) <> fail
      and ValueOption(NO_PRECOMPUTED_DATA_OPTION)<>true then
      return IdGroup(x);
    fi;
    l:=[Size(x)];
    Add(l,Collected(List(ConjugacyClasses(x),
      y->[Order(Representative(y)),Size(y)])));
    Add(l,AbelianInvariants(x));
    return l;
  end;
  n:=ValueOption("directs");
  if n<>fail then
    # avoid large number of normals in direct product
    n:=Concatenation(List(n,
      x->Filtered(NormalSubgroups(x),y->Size(y)>1 and Size(y)<Size(x))));
  else
    n:=NormalSubgroups(G);
  fi;
  pat:=List(n,fp);
  pools:=[];
  for i in Set(pat) do
    sel:=Filtered([1..Length(n)],x->pat[x]=i);
    Add(pools,n{sel});
  od;
  return pools;
end);

# form a characterististic series through radical with elab factors
BindGlobal("AGSRCharacteristicSeries",function(G,r)
local somechar,d,i,j,u,v;
  d:=Filtered(StructuralSeriesOfGroup(G),x->IsSubset(r,x));
  # refine
  d:=RefinedSubnormalSeries(d,Centre(G));
  d:=RefinedSubnormalSeries(d,Centre(r));
  somechar:=ValueOption("someCharacteristics");
  if somechar<>fail then
    if IsRecord(somechar) then
      somechar:=somechar.subgroups;
    fi;
    for i in somechar do
      d:=RefinedSubnormalSeries(d,i);
    od;
  fi;

  if not ForAll([1..Length(d)-1],
    x->HasElementaryAbelianFactorGroup(d[x],d[x+1])) then
    for i in PrimeDivisors(Size(r)) do
      u:=PCore(r,i);
      if Size(u)>1 then
        d:=RefinedSubnormalSeries(d,u);
        j:=1;
        repeat
          v:=Agemo(u,i,j);
          if Size(v)>1 then
            d:=RefinedSubnormalSeries(d,v);
          fi;
          j:=j+1;
        until Size(v)=1;
        j:=1;
        repeat
          if Size(u)>=2^24 then
            v:=u; # bail out as method for `Omega` will do so.
          else
            v:=Omega(u,i,j);
            if Size(v)<Size(u) then
              d:=RefinedSubnormalSeries(d,v);
            fi;
            j:=j+1;
          fi;

      until Size(v)=Size(u);
    fi;
    od;
  fi;
  Assert(1,ForAll([1..Length(d)-1],x->Size(d[x])<>Size(d[x+1])));
  return d;
end);

# main automorphism method -- currently still using factor groups, but
# nevertheless faster..

# option somechar may be a list of characteristic subgroups, or a record with
# component subgroups, orbits
BindGlobal("AutomGrpSR",function(G)
local ff,r,d,ser,u,v,i,j,k,p,bd,e,gens,lhom,M,N,hom,Q,Mim,q,ocr,split,MPcgs,
      b,fratsim,AQ,OQ,Zm,D,innC,oneC,imgs,C,maut,innB,tmpAut,imM,a,A,B,
      cond,sub,AQI,AQP,AQiso,rf,res,resperm,proj,Aperm,Apa,precond,ac,
      comiso,extra,mo,rada,makeaqiso,ind,lastperm,actbase,somechar,stablim,
      scharorb,asAutom,jorb,jorpo,substb,isBadPermrep,ma,nosucl,nosuf,rlgf;

  # criterion for when to force degree reduction
  isBadPermrep:=function(g)
    return NrMovedPoints(g)^3>Size(g)*Index(g,DerivedSubgroup(g));
  end;

  asAutom:=function(sub,hom) return Image(hom,sub);end;

  actbase:=ValueOption("autactbase");
  nosucl:=fail;

  makeaqiso:=function()
  local a,b;
    Info(InfoMorph,3,"enter makeaqiso");
    if HasIsomorphismPermGroup(AQ) then
      AQiso:=IsomorphismPermGroup(AQ);
    elif HasNiceMonomorphism(AQ) and IsPermGroup(Range(NiceMonomorphism(AQ))) then
      AQiso:=NiceMonomorphism(AQ:autactbase:=fail);
    else
      a:=Filtered(GeneratorsOfGroup(AQ),HasConjugatorOfConjugatorIsomorphism);
      if Length(a)>2 then
        a:=List(a,ConjugatorOfConjugatorIsomorphism);
        b:=SmallGeneratingSet(Group(a));
        if Length(b)<Length(a) then
          a:=List(b,x->ConjugatorAutomorphism(Source(AQ.1),x));
          b:=Filtered(GeneratorsOfGroup(AQ),x->not HasConjugatorOfConjugatorIsomorphism(x));
          a:=Concatenation(a,b);
          b:=InnerAutomorphismsAutomorphismGroup(AQ);
          AQ:=Group(a,One(AQ));
          SetInnerAutomorphismsAutomorphismGroup(AQ,b);
          SetIsGroupOfAutomorphismsFiniteGroup(AQ,true);
        fi;
      fi;
      if actbase<>fail then
        AQiso:=IsomorphismPermGroup(AQ:autactbase:=List(actbase,x->Image(hom,x)));
      else
        AQiso:=IsomorphismPermGroup(AQ);
      fi;
    fi;
    AQP:=Image(AQiso,AQ);
    Info(InfoMorph,3,"Permrep of AQ ",Size(AQ),", deg:",NrMovedPoints(AQP));

    if Length(GeneratorsOfGroup(AQP))=Length(GeneratorsOfGroup(AQ)) then
      a:=AGSRReducedGens(AQP,AQP,AQ,AQiso);
      if a<>fail then
        AQP:=a[1];
        AQ:=a[2];
      fi;
    fi;

    # force degree down
    if isBadPermrep(AQP) then
      a:=SmallerDegreePermutationRepresentation(AQP:cheap);
      if NrMovedPoints(Image(a))<NrMovedPoints(AQP) then
        Info(InfoMorph,3,"Permdegree reduced ",
              NrMovedPoints(AQP),"->",NrMovedPoints(Image(a)));
        AQiso:=AQiso*a;
        b:=Image(a,AQP);
        if Length(GeneratorsOfGroup(b))>Length(GeneratorsOfGroup(AQP)) then
          b:=Group(List(GeneratorsOfGroup(AQP),x->ImagesRepresentative(a,x)));
          SetSize(b,Size(AQP));
        fi;
        AQP:=b;
      fi;
    fi;

  end;

  stablim:=function(gp,cond,lim)
  local no,same,sz,ac,i,sub;
    same:=true;
    repeat
      sz:=Size(Aperm);
      if Size(gp)/Size(Aperm)>lim then
        no:=Normalizer(gp,Aperm);
        if Size(no)>Size(Aperm) and Size(no)<Size(gp) then
          stablim(no,cond,lim);
        fi;
      else
        no:=Aperm;
      fi;
      if Size(gp)/Size(Aperm)>lim then
        ac:=AscendingChain(gp,Aperm);
        List(Union(List(ac,GeneratorsOfGroup)),cond); # try generators...
        if Size(Aperm)>sz then
          ac:=Unique(List(ac,x->ClosureGroup(Aperm,x)));
        fi;

        i:=First([Length(ac),Length(ac)-1..1],x->Size(ac[x])/sz<=lim);
        sub:=ac[i];
      else
        sub:=gp;
      fi;
      if Size(sub)>Size(Aperm) and not IsSubset(no,sub) then
        SubgroupProperty(sub,cond,Aperm);
      fi;
      same:=Size(Aperm)=sz;
      if not same then
        Info(InfoMorph,3,"stablim improves by ",Size(Aperm)/sz,
        " remaining ",Size(gp)/Size(Aperm));
      fi;
    until same;
    return sub=gp;
  end;

  ff:=FittingFreeLiftSetup(G);
  r:=ff.radical;
  rlgf:=LGFirst(SpecialPcgs(r));


  # find series through r
  somechar:=ValueOption("someCharacteristics");

  # derived and then primes and then elementary abelian
  d:=ValueOption("series");
  if d=fail then
    d:=AGSRCharacteristicSeries(G,r:someCharacteristics:=somechar);
    d:=Reversed(d);
  else
    d:=ShallowCopy(d);
    SortBy(d,Size); # in case reversed order....
  fi;

  scharorb:=fail;
  if somechar<>fail then
    if IsRecord(somechar) then
      if IsBound(somechar.orbits) then
        scharorb:=somechar.orbits;
      fi;
      somechar:=somechar.subgroups;
    fi;
  fi;

  # now go up in series if elementary abelian to avoid too many tiny steps
  u:=1; # last group in the series to be used
  i:=2;
  while i<Length(d) do
    p:=IndexNC(d[i+1],d[u]);
    # should we skip group i?
    if p<100 and IsPrimePowerInt(p) and
       HasElementaryAbelianFactorGroup(d[i+1],d[u]) then
       d:=Concatenation(d{[1..i-1]},d{[i+1..Length(d)]});
       # i stays the same, as it now is the next subgroup
    else
      u:=i;
      i:=i+1;
    fi;
  od;

  # avoid small central subgroups, as the factor will be hard to represent
  u:=Centre(G);
  if Size(u)>1 then
    p:=SmallestPrimeDivisor(Size(u));
    u:=SylowSubgroup(u,p);
    u:=Omega(u,p,1);
    i:=Position(d,u);
    if i<>fail and i>2 then
      d:=d{Union([1],[i..Length(d)])};
    fi;
  fi;

  ser:=[TrivialSubgroup(G)];
  for i in d{[2..Length(d)]} do
    u:=Last(ser);
    for p in PrimeDivisors(Size(i)/Size(u)) do
      bd:=PValuation(Size(i)/Size(u),p); # max p-exponent
      u:=ClosureSubgroup(u,SylowSubgroup(i,p));
      v:=Last(ser);
      while not HasElementaryAbelianFactorGroup(u,v) do
        gens:=Filtered(GeneratorsOfGroup(u),x->not x in v);
        e:=List(gens,x->First([1..bd],a->x^(p^a) in v));
        e:=p^(Maximum(e)-1);
        for j in gens do
          v:=ClosureSubgroup(v,j^e);
        od;
        Add(ser,v);
      od;
      Add(ser,u);
    od;
  od;

  rada:=fail;

  ser:=Reversed(ser);
  hom:=ff.factorhom;
  Q:=Image(hom,G);
  if IsPermGroup(Q) and NrMovedPoints(Q)/Size(Q)*Size(Socle(Q))
        >SufficientlySmallDegreeSimpleGroupOrder(Size(Q)) then
    # just in case the radical factor hom is inherited.
    Q:=SmallerDegreePermutationRepresentation(Q:cheap);
    Info(InfoMorph,3,"Radical factor degree reduced ",NrMovedPoints(Range(hom)),
              " -> ",NrMovedPoints(Range(Q)));
    hom:=hom*Q;
    Q:=Image(hom,G);
  fi;

  ma:=MaximalSubgroupClassesSol(G);

  AQ:=AutomorphismGroupFittingFree(Q:someCharacteristics:=fail);
  AQI:=InnerAutomorphismsAutomorphismGroup(AQ);
  lastperm:=fail;
  # preseed natural homs in ascending form (as the largest one might help
  # for smaller ones)
  for i in [Length(ser),Length(ser)-1..2] do
    lhom:=NaturalHomomorphismByNormalSubgroup(G,ser[i]);
  od;
  i:=1;
  while i<Length(ser) do
    Assert(2,ForAll(GeneratorsOfGroup(AQ),x->Size(Source(x))=Size(Q)));
    # ensure that the step is OK
    lhom:=hom;
    OQ:=Q;
    repeat
      Info(InfoMorph,4,List(ser,Size)," ",i);
      Info(InfoMorph,1,"Step ",i," ",Size(ser[i]),"->",Size(ser[i+1]));
      M:=ser[i];
      N:=ser[i+1];
      hom:=NaturalHomomorphismByNormalSubgroup(G,N);

      Q:=Image(hom,G);
      # degree reduction called for?
      if IsPermGroup(Q) and Size(N)>1
        and (isBadPermrep(Q) or NrMovedPoints(Q)>1000) then
        q:=SmallerDegreePermutationRepresentation(Q:cheap);
        if NrMovedPoints(Q)>1000
          and NrMovedPoints(Q)=NrMovedPoints(Image(q)) then
          q:=SmallerDegreePermutationRepresentation(Q);
        fi;
        if NrMovedPoints(Range(q))<NrMovedPoints(Q) then
          Info(InfoMorph,3,"reduced permrep Q ",NrMovedPoints(Q)," -> ",
              NrMovedPoints(Range(q)));
          hom:=hom*q;
          Q:=Image(hom,G);
        fi;
      fi;

      # inherit radical factor map
      q:=GroupHomomorphismByImagesNC(Q,Range(ff.factorhom),
        List(GeneratorsOfGroup(G),x->ImagesRepresentative(hom,x)),
        List(GeneratorsOfGroup(G),x->ImagesRepresentative(ff.factorhom,x)));
      b:=Image(hom,ff.radical);
      SetSolvableRadical(Q,b);
      AddNaturalHomomorphismsPool(Q,b,q);

      # Use known maximals for Frattini
      for j in ma do
        D:=Image(hom,j);
        if not IsSubset(D,b) then
          b:=Core(Q,NormalIntersection(b,D));
        fi;
      od;
      SetIsNilpotentGroup(b,true);
      SetFrattiniSubgroup(Q,b);

      # M-factor
      Mim:=Image(hom,M);
      MPcgs:=Pcgs(Mim);
      q:=GroupHomomorphismByImagesNC(Q,OQ,
        List(GeneratorsOfGroup(G),x->ImagesRepresentative(hom,x)),
        List(GeneratorsOfGroup(G),x->ImagesRepresentative(lhom,x)));
      AddNaturalHomomorphismsPool(Q,Mim,q);

      mo:=GModuleByMats(LinearActionLayer(GeneratorsOfGroup(Q),MPcgs),GF(RelativeOrders(MPcgs)[1]));
      # is the extension split?
      ocr:=OneCocycles(Q,Mim);
      split:=ocr.isSplitExtension;
      if not split then
        # test: Semisimple and Frattini
        b:=MTX.BasisRadical(mo);
        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;
          # insert in series
          for j in [Length(ser),Length(ser)-1..i+1] do
            ser[j+1]:=ser[j];
          od;
          ser[i+1]:=N;
          Info(InfoMorph,2,"insert1");
        else
          # Frattini?
          fratsim:=IsSubset(FrattiniSubgroup(Q),Mim);
          if not fratsim then
            N:=Intersection(FrattiniSubgroup(Q),Mim);
            # insert
            for j in [Length(ser),Length(ser)-1..i+1] do
              ser[j+1]:=ser[j];
            od;
            ser[i+1]:=PreImage(hom,N);
            Info(InfoMorph,2,"insert2");
          fi;
          N:=ser[i+1]; # the added normal
        fi;
        if rada<>fail
            and ForAny(GeneratorsOfGroup(rada),x->N<>Image(x,N)) then
          Info(InfoMorph,3,"radical automorphism stabilizer");
          SetIsGroupOfAutomorphismsFiniteGroup(rada,true);
          NiceMonomorphism(rada:autactbase:=fail,someCharacteristics:=fail);
          rada:=Stabilizer(rada,N,asAutom);
        fi;
      fi;
    until split or fratsim;

    # Use cocycles
    b:=BasisVectors(Basis(ocr.oneCocycles));

    # find D
    Zm:=PreImage(q,Centre(OQ));
    D:=Centralizer(Zm,Mim);

    innC:=List(GeneratorsOfGroup(D),d->InnerAutomorphism(Q,d));

    D:=List(innC,inn->List(ocr.generators,o->Image(inn,o)));
    D:=List(D,d->List([1..Length(ocr.generators)],i->ocr.generators[i]^-1*d[i]));
    D:=List(D,d->ocr.listToCocycle(d));
    TriangulizeMat(D);
    D:=Filtered(D,x->x<>0*x);

    b:=BaseSteinitzVectors(b,D).factorspace;

    C:=[];
    if Size(Group(ocr.generators))<Size(Q) then
      extra:=MPcgs;
    else
      extra:=[];
    fi;
    for j  in b  do
      oneC := ocr.cocycleToList( j );
      imgs:=List([1..Length(ocr.generators)],i->ocr.generators[i]*oneC[i]);
      oneC:=GroupHomomorphismByImagesNC(Q,Q,Concatenation(ocr.generators,extra),Concatenation(imgs,extra));
      Assert(2,IsBijective(oneC));
      Add(C,oneC);
    od;

    B:=[];

    if lastperm<>fail then
      AQiso:=lastperm;
      AQP:=Group(List(GeneratorsOfGroup(AQ),x->ImagesRepresentative(AQiso,x)));
    else
      makeaqiso();
    fi;

    if split then
      maut:=MTX.ModuleAutomorphisms(mo);
      # find noninner of B
      innB:=List(SmallGeneratingSet(Zm),z->InnerAutomorphism(Q,z));
      innB:=Group(One(DefaultFieldOfMatrixGroup(maut))*
                      List(innB,inn->List(MPcgs,m->ExponentsOfPcElement(MPcgs,Image(inn,m)))));

      tmpAut:=SubgroupNC(maut,Filtered(GeneratorsOfGroup(maut),aut->not aut in innB));

      gens:=GeneratorsOfGroup(ocr.complement);
      for a  in GeneratorsOfGroup(tmpAut)  do
        imM:=List(a,i->PcElementByExponents(MPcgs,i));
        imM:=GroupHomomorphismByImagesNC(Q,Q,Concatenation(MPcgs,gens),Concatenation(imM,gens));
        Assert(2,IsBijective(imM));
        Add(B,imM);
      od;

      # test condition for lifting, also add corresponding automorphism
      comiso:=GroupHomomorphismByImagesNC(ocr.complement,OQ,gens,List(gens,x->ImagesRepresentative(q,x)));

      precond:=fail;
      mo:=GModuleByMats(LinearActionLayer(gens,MPcgs),mo.field);
      cond:=function(perm)
      local aut,newgens,mo2,iso,a;
        if perm in Aperm then
          return true;
        fi;
        aut:=PreImagesRepresentative(AQiso,perm);
        newgens:=List(gens,x->PreImagesRepresentative(comiso,
          ImagesRepresentative(aut,ImagesRepresentative(comiso,x))));

        mo2:=GModuleByMats(LinearActionLayer(newgens,MPcgs),mo.field);
        iso:=MTX.IsomorphismModules(mo,mo2);
        if iso=fail then
          return false;
        else
          # build associated auto

          a:=GroupHomomorphismByImagesNC(Q,Q,Concatenation(gens,MPcgs),
                  Concatenation(newgens,
                   List(MPcgs,x->PcElementByExponents(MPcgs,
                     (ExponentsOfPcElement(MPcgs,x)*One(mo.field))*iso  ))));
         Assert(2,IsBijective(a));
         Add(A,a);
         Add(Apa,perm);
         Aperm:=ClosureGroup(Aperm,perm);
         return true;
        fi;
      end;

    else
      # there is no B in the nonsplit case
      B:=[];

      ocr:=AGSRPrepareAutomLift( Q, MPcgs, q );

      precond:=function(perm)
      local aut,newgens,mo2;
        if perm in Aperm then
          return true;
        fi;
        aut:=PreImagesRepresentative(AQiso,perm);
        newgens:=List(GeneratorsOfGroup(Q),
          x->PreImagesRepresentative(q,Image(aut,ImagesRepresentative(q,x))));
        mo2:=GModuleByMats(LinearActionLayer(newgens,MPcgs),mo.field);
        return MTX.IsomorphismModules(mo,mo2)<>fail;
      end;

      cond:=function(perm)
      local aut,newgens,mo2,iso,a;
        if perm in Aperm then
          return true;
        fi;
        aut:=PreImagesRepresentative(AQiso,perm);
        newgens:=List(GeneratorsOfGroup(Q),
          x->PreImagesRepresentative(q,Image(aut,ImagesRepresentative(q,x))));
        mo2:=GModuleByMats(LinearActionLayer(newgens,MPcgs),mo.field);
        iso:=MTX.IsomorphismModules(mo,mo2);
        if iso=fail then
          return false;
        else
          # build associated auto
          a:=AGSRAutomLift(ocr,q,aut,iso);
          if a=fail then
            #Print("test failed\n");
            return false;
          else
            Add(A,a);
            Add(Apa,perm);
            Aperm:=ClosureGroup(Aperm,perm);
            #Print("test succeeded\n");
            return true;
          fi;
        fi;
      end;

    fi;

    # find A using the set condition
    A:=[];
    Apa:=[];
    # note: we do not include AQI here, so might need to add later
    Aperm:=SubgroupNC(AQP,List(GeneratorsOfGroup(AQI),
            x->ImagesRepresentative(AQiso,x)));

    # try to find some further generators
    if Size(AQP)/Size(Aperm)>100 then
      for j in SpecialPcgs(SolvableRadical(AQP)) do
        cond(j);
      od;
      for j in GeneratorsOfGroup(AQP) do
        cond(j);
      od;
    fi;

    sub:=AQP;
    if precond<>fail and not ForAll(GeneratorsOfGroup(sub),precond) then
      # compatible pairs condition
      sub:=SubgroupProperty(sub,precond,Aperm);
    fi;

    if IndexNC(sub,Aperm)>10^6 then
      # try to find characteristic subgroups
      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))));
      nosuf:=Filtered(nosuf,x->Size(x[1])>1 and Size(x[1])<Size(OQ));
      SortBy(nosuf,Length);
      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)<Size(sub) then
          sub:=ac.stabilizer;
        fi;
      od;
    fi;

    j:=Size(sub);
    Info(InfoMorph,2,"start search ",IndexNC(sub,Aperm));
    sub:=SubgroupConditionAbove(sub,cond,Aperm);
    Info(InfoMorph,2,"end search ",j/Size(sub));

    # Note: Aperm is larger than what is generated by Apa
    j:=1;
    while Size(Aperm)<Size(sub) do
      ac:=InnerAutomorphism(OQ,Image(q,GeneratorsOfGroup(Q)[j]));
      k:=ImagesRepresentative(AQiso,ac);
      if not k in Aperm then
        Add(Apa,k);
        Aperm:=ClosureGroup(Aperm,k);
        Add(A,InnerAutomorphism(Q,GeneratorsOfGroup(Q)[j]));
      fi;
      j:=j+1;
    od;

    # remove redundant generators. Note that Aperm could be to big, thus
    # make group from Apa
    ac:=AGSRReducedGens(SubgroupNC(Parent(sub),Apa),Apa,A);
    if ac<>fail then
      Apa:=GeneratorsOfGroup(ac[1]);
      A:=GeneratorsOfGroup(ac[2]);
    fi;

    Info(InfoMorph,2,"Lift Index ",Size(AQP)/Size(sub));

    # now make the new automorphism group
    innB:=List(SmallGeneratingSet(Q),x->InnerAutomorphism(Q,x));
    gens:=ShallowCopy(innB);
    Info(InfoMorph,2,"|gens|=",Length(gens),"+",Length(C),
      "+",Length(B),"+",Length(A));
    Append(gens,C);
    Append(gens,B);
    Append(gens,A);

    Assert(2,ForAll(gens,IsBijective));
    for j in gens do
      SetIsBijective(j,true);
    od;
    A:=Group(gens);
    SetIsAutomorphismGroup(A,true);
    SetIsGroupOfAutomorphismsFiniteGroup(A,true);
    SetIsFinite(A,true);

    AQI:=SubgroupNC(A,innB);
    SetInnerAutomorphismsAutomorphismGroup(A,AQI);
    AQ:=A;
    if Size(KernelOfMultiplicativeGeneralMapping(hom))>1
      or ValueOption("delaypermrep")<>true then
      makeaqiso();
      if not IsIdenticalObj(A,AQ) then
        A:=AQ;
      fi;
    else
      A!.makeaqiso:=makeaqiso;
    fi;

    # do we use induced radical automorphisms to help next step?
    if Size(KernelOfMultiplicativeGeneralMapping(hom))>1 and
      Size(A)>10^8
      #(
      ## potentially large GL
      #Size(GL(Length(MPcgs),RelativeOrders(MPcgs)[1]))>10^10 and
      ## automorphism size really grew from B/C-bit
      ##Size(A)/Size(AQP)*Index(AQP,sub)>10^10) )
      ## do so for all factors
     and ForAll([2..Length(rlgf)],x->rlgf[x]-rlgf[x-1]<10)
     and ValueOption("noradicalaut")<>true then

      if rada=fail then
        if IsElementaryAbelian(r) and Size(r)>1 then
          B:=Pcgs(r);
          rf:=GF(RelativeOrders(B)[1]);
          ind:=Filtered(ser,x->IsSubset(r,x) and Size(x)>1 and Size(x)<Size(r));
          ind:=List(ind,x->List(GeneratorsOfGroup(x),y->ExponentsOfPcElement(B,y)));
          ind:=List(ind,x->x*One(rf));
          ind:=SpaceAndOrbitStabilizer(Length(B),rf,ind,[]);
          rada:=List(GeneratorsOfGroup(ind),x->
            GroupHomomorphismByImagesNC(r,r,B,List(x,y->PcElementByExponents(B,List(y,Int)))));
          rada:=Group(rada);
          SetIsGroupOfAutomorphismsFiniteGroup(rada,true);
          NiceMonomorphism(rada:autactbase:=fail,someCharacteristics:=fail);
        else
          ind:=IsomorphismPcGroup(r);
          rada:=AutomorphismGroup(Image(ind,r):someCharacteristics:=fail,autactbase:=fail);
          # we only consider those homomorphism that stabilize the series we use
          for k in List(ser,x->Image(ind,x)) do
            if ForAny(GeneratorsOfGroup(rada),x->Image(x,k)<>k) then
              Info(InfoMorph,3,"radical automorphism stabilizer");
              NiceMonomorphism(rada:autactbase:=fail,someCharacteristics:=fail);
              SetIsGroupOfAutomorphismsFiniteGroup(rada,true);
              rada:=Stabilizer(rada,k,asAutom);
            fi;
          od;
          # move back to bad degree
          rada:=Group(List(GeneratorsOfGroup(rada),
            x-> InducedAutomorphism(InverseGeneralMapping(ind),x)));
        fi;
      fi;

      rf:=Image(hom,r);
      Info(InfoMorph,2,"Use radical automorphisms for reduction");

      makeaqiso();
      B:=MappingGeneratorsImages(AQiso);
      res:=List(B[1],x->
        GroupHomomorphismByImagesNC(rf,rf,GeneratorsOfGroup(rf),
          List(GeneratorsOfGroup(rf),y->ImagesRepresentative(x,y))));

      ind:=[];
      for j in GeneratorsOfGroup(rada) do
        k:=GroupHomomorphismByImagesNC(rf,rf,
          GeneratorsOfGroup(rf),
          List(GeneratorsOfGroup(rf),
            y->ImagesRepresentative(hom,ImagesRepresentative(j,
                 PreImagesRepresentative(hom,y)))));
        Assert(2,IsBijective(k));
        Add(ind,k);
      od;

      C:=Group(Unique(Concatenation(res,ind))); # to guarantee common parent
      SetIsFinite(C,true);
      SetIsGroupOfAutomorphismsFiniteGroup(C,true);
      Size(C:autactbase:=fail,someCharacteristics:=fail); # disable autactbase transfer
      res:=SubgroupNC(C,res);
      ind:=SubgroupNC(C,ind);
      # this should now go via the niceo of C
      Size(ind:autactbase:=fail,someCharacteristics:=fail);
      Size(res:autactbase:=fail,someCharacteristics:=fail);
      ind:=Intersection(res,ind); # only those we care about

      if Size(ind)<Size(res) then
        # reduce to subgroup that induces valid automorphisms
        Info(InfoMorph,1,"Radical autos reduce by factor ",Size(res)/Size(ind));
        resperm:=IsomorphismPermGroup(C);
        proj:=GroupHomomorphismByImagesNC(AQP,Image(resperm),
          B[2],List(GeneratorsOfGroup(res),x->ImagesRepresentative(resperm,x)));
        C:=PreImage(proj,Image(resperm,ind));
        C:=List(SmallGeneratingSet(C),x->PreImagesRepresentative(AQiso,x));
        AQ:=Group(C);
        SetIsFinite(AQ,true);
        SetIsGroupOfAutomorphismsFiniteGroup(AQ,true);
        makeaqiso();
      fi;

      # # hook for using existing characteristics to reduce for next step
      if somechar<>fail then
        u:=Filtered(Unique(List(somechar,x->Image(hom,x))),x->Size(x)>1);
        u:=Filtered(u,s->ForAny(GeneratorsOfGroup(AQ),h->Image(h,s)<>s));
        SortBy(u,Size);
        Info(InfoMorph,1,"Forced characteristics ",List(u,Size));

        if scharorb<>fail then
          # these are subgroups for which certain orbits must be stabilized.
          C:=List(Reversed(scharorb),x->List(x,y->Image(hom,y)));
          C:=Filtered(C,x->Size(x[1])>1 and Size(x[1])<Size(Q));
          Info(InfoMorph,1,"Forced orbits ",List(C,x->Size(x[1])));
          Append(u,C);
        fi;

        if Length(u)>0 then
          C:=MappingGeneratorsImages(AQiso);
          if C[2]<>GeneratorsOfGroup(AQP) then
            C:=[List(GeneratorsOfGroup(AQP),
                     x->PreImagesRepresentative(AQiso,x)),
                 GeneratorsOfGroup(AQP)];
          fi;
          for j in u do
            if IsList(j) then
              # stabilizer set of subgroups
              jorb:=ShallowCopy(Orbit(AQP,j[1],C[2],C[1],asAutom));
              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));
            jorpo[2]:=Position(jorb,j[2]);
              fi;
              if Length(jorb)>Length(j) then
            B:=ActionHomomorphism(AQP,jorb,C[2],C[1],asAutom);
            substb:=Group(List(C[2],x->ImagesRepresentative(B,x)),());
            substb:=Stabilizer(substb,Set(jorpo),OnSets);
            substb:=PreImage(B,substb);
            Info(InfoMorph,2,"Stabilize characteristic orbit ",Size(j[1]),
              " :",Size(AQP)/Size(substb) );
              else
                substb:=AQP;
              fi;


        else
          substb:=Stabilizer(AQP,j,C[2],C[1],asAutom);
          Info(InfoMorph,2,"Stabilize characteristic subgroup ",Size(j),
        " :",Size(AQP)/Size(substb) );
        fi;
        if Size(substb)<Size(AQP) then
          B:=Size(substb);
          substb:=SmallGeneratingSet(substb);
          AQP:=Group(substb);
          SetSize(AQP,B);
          C:=[List(substb,x->PreImagesRepresentative(AQiso,x)),substb];
        fi;

      od;
      AQ:=Group(C[1]);
      SetIsFinite(AQ,true);
      SetIsGroupOfAutomorphismsFiniteGroup(AQ,true);
      SetSize(AQ,Size(AQP));
      #AQP:=Group(C[2]); # ensure small gen set
      #SetSize(AQP,Size(AQ));
      makeaqiso();
    fi;
      fi;

      lastperm:=AQiso;
    else
      lastperm:=fail;
    fi;

    i:=i+1;
  od;

  return AQ;

end);

# find characteristic subgroups by splitting into elementary abelian,
# homogeneous layers
BindGlobal("AGSRModuleLayerSeries",function(g)
local s,l,r,i,j,sy,hom,p,pcgs;
  s:=ShallowCopy(DerivedSeriesOfGroup(g));
  r:=SolvableRadical(Last(s));
  if Size(r)>1 then # cannot be last, as solvable
    Append(s,DerivedSeriesOfGroup(r));
  fi;
  i:=2;
  while i<=Length(s) do
    if HasAbelianFactorGroup(s[i-1],s[i]) then
      p:=Factors(IndexNC(s[i-1],s[i]))[1];
      # is it a single prime?
      if not IsPrimePowerInt(IndexNC(s[i-1],s[i])) then
        hom:=NaturalHomomorphismByNormalSubgroupNC(s[i-1],s[i]);
        sy:=SylowSystem(Image(hom));
        l:=[sy[1]];
        for j in [2..Length(sy)-1] do
          Add(l,ClosureGroup(Last(l),sy[j]));
        od;
        l:=Reversed(l);
        l:=List(l,x->PreImage(hom,x));
        Info(InfoMorph,6,"insert prime @",i);
        s:=Concatenation(s{[1..i-1]},l,s{[i..Length(s)]});
      elif not HasElementaryAbelianFactorGroup(s[i-1],s[i]) then
        # not elementary abelian -- pth powers suffice as abelian
        l:=s[i];
        for j in GeneratorsOfGroup(s[i-1]) do
          l:=ClosureGroup(l,j^p);
        od;
        Info(InfoMorph,6,"insert ppower @",i);
        s:=Concatenation(s{[1..i-1]},[l],s{[i..Length(s)]});
      else
        # make module
        pcgs:=ModuloPcgs(s[i-1],s[i]);
        l:=LinearActionLayer(g,pcgs);
        l:=GModuleByMats(l,GF(p));
        # check for characteristic submodules
        r:=MTX.BasisRadical(l);
        if Length(r)=0 then
          r:=MTX.BasisSocle(l);
          if Length(r)=l.dimension then
            # semisimple -- use homogeneous
            sy:=List(MTX.CollectedFactors(l),x->x[1]);
            if Length(sy)>1 then
              r:=Minimum(List(sy,x->x.dimension));
              sy:=Filtered(sy,x->x.dimension=r);
              r:=[];
              for j in sy do
                Append(r,MTX.Homomorphisms(j,l));
              od;
              r:=Concatenation(r);
              r:=Filtered(TriangulizedMat(r),x->not IsZero(x));
            else
              r:=fail;
            fi;
          fi;
        fi;

        if r=fail then
          i:=i+1;
        else
          l:=s[i];
          for j in r do;
            l:=ClosureGroup(l,PcElementByExponents(pcgs,j));
          od;
          if Size(l)<Size(s[i-1]) and Size(l)>Size(s[i]) then
            Info(InfoMorph,6,"insert module @",i);
            s:=Concatenation(s{[1..i-1]},[l],s{[i..Length(s)]});
          else
            i:=i+1;
          fi;
        fi;
      fi;
    else
      i:=i+1;
    fi;
  od;
  return s;
end);

# find corresponding characteristic subgroups
BindGlobal("AGSRMatchedCharacteristics",function(g,h)
local a,props,cg,ch,clg,clh,ng,nh,coug,couh,pg,ph,i,j,stop,coinc;

  props:=function(a)
  local p,b;

    if ID_AVAILABLE(Size(a))<>fail then
      p:=ShallowCopy(-IdGroup(a)); # negative avoids clash with others
    else
      p:=[Size(a)];
      b:=ShallowCopy(AbelianInvariants(a));
      Sort(b);
      Add(p,b);
      Add(p,List(DerivedSeriesOfGroup(a),Size));
    fi;
#    # intersections
#    for i in [1..Length(chars)] do
#      for j in AGSRModuleLayerSeries(chars[i]) do
#        der(Intersection(j,a));
#        der(ClosureGroup(j,a));
#      od;
#    od;
    return p;
  end;

  # Process that G-subgroup i matches H-subgroup j. Return true if there is
  # a problem.
  coinc:=function(i,j)
  local a,b,sa,sb,sel,p,q;
    if pg[i]<>ph[j] then Error("matching bug");fi;
    a:=cg[i];
    b:=ch[j];
    Add(ng,a);
    Add(nh,b);
    sel:=Difference([1..Length(cg)],[i]);
    cg:=cg{sel};
    pg:=pg{sel};
    sel:=Difference([1..Length(ch)],[j]);
    ch:=ch{sel};
    ph:=ph{sel};
    # find associated modules and further match
    sa:=AGSRModuleLayerSeries(a);
    sb:=AGSRModuleLayerSeries(b);
    if List(sa,Size)<>List(sb,Size) then return true;fi;
    for i in [1..Length(sa)] do
      p:=Position(cg,sa[i]);
      q:=Position(ch,sb[i]);
      if p=fail then
        if q<>fail then return true;fi;
      elif q=fail then return true;
      else
        if pg[p]<>ph[q] # but not same properties
         or coinc(p,q) then return true;fi;
      fi;
    od;
    return false;
  end;

  ng:=[];
  nh:=[];

  # get the characteristic subgroups
  cg:=ShallowCopy(CharacteristicSubgroups(g));
  ch:=ShallowCopy(CharacteristicSubgroups(h));
  SortBy(cg,x->-Size(x));
  SortBy(ch,x->-Size(x));

  # find list of properties, tryign to match them up
  pg:=List(cg,props);
  ph:=List(ch,props);

  stop:=false;
  while Length(cg)>0 and not stop do
    # test in loop as list changes
    if Collected(pg)<>Collected(ph) then return fail;fi;
    i:=First([1..Length(pg)],x->Number(pg,y->y=pg[x])=1);
    if i<>fail then
      # found a unique one -- process
      # because properties agree this must match
      j:=Position(ph,pg[i]);
      if coinc(i,j) then return fail;fi;
    else
      stop:=true; # give up, for the moment
      # try clusters
      j:=Set(pg);
      clg:=List(j,x->cg{Filtered([1..Length(cg)],y->pg[y]=x)});
      clh:=List(j,x->ch{Filtered([1..Length(ch)],y->ph[y]=x)});
      # also use classes of size 1 for compare
      Append(clg,List(ng,x->[x]));
      Append(clh,List(nh,x->[x]));
      # sort larger first
      SortParallel(clg,clh,function(a,b) return Size(a[1])>Size(b[1]);end);
      i:=1;
      while i<=Length(clg) do
        if Length(clg[i])>1 then
          j:=i+1;
          while j<=Length(clg) do
            coug:=List(clg[i],x->Number(clg[j],y->IsSubset(x,y)));
            couh:=List(clh[i],x->Number(clh[j],y->IsSubset(x,y)));
            if Collected(coug)<>Collected(couh) then return fail;fi;
            a:=First(Collected(coug),x->x[2]=1);
            if a<>fail then
              # unique number -- split
              a:=a[1];
              if coinc(Position(cg,clg[i][Position(coug,a)]),
                    Position(ch,clh[i][Position(couh,a)])) then return fail;fi;
              i:=Length(clg); j:=Length(clg); # break out of loops
              stop:=false;
            fi;
            j:=j+1;
          od;
        fi;
        i:=i+1;
      od;
    fi;
  od;

  j:=Set(pg);
  return rec(
    ng:=ng,nh:=nh,
    cg:=List(j,x->cg{Filtered([1..Length(cg)],y->pg[y]=x)}),
    ch:=List(j,x->ch{Filtered([1..Length(ch)],y->ph[y]=x)})
    );
end);

BindGlobal("AGBoundedOrbrep",function(G,from,to,act,bound)
local orb,rep,S,i,g,img,p;
  orb:=[from];
  rep:=[One(G)];
  S:=[];
  i:=1;
  while i<=Length(orb) do
    for g in GeneratorsOfGroup(G) do
      img:=act(orb[i],g);
      p:=Position(orb,img);
      if p=fail then
        if Length(orb)>=bound then return fail;fi; # length bailout
        Add(orb,img);
        Add(rep,rep[i]*g);
      else
        img:=rep[i]*g/rep[p];
        if not (img in S or img^-1 in S) then
          Add(S,img);
        fi;
      fi;
    od;
    i:=i+1;
  od;
  p:=Position(orb,to);
  if p=fail then return false;fi; # not in orbit
  return rec(rep:=rep[p],orblen:=Length(orb),stabgens:=S);
end);


# pathetic isomorphism test, based on the automorphism group of GxH. This is
# only of use as long as we don't yet have a Cannon/Holt version of
# isomorphism available and there are many generators
InstallGlobalFunction(PatheticIsomorphism,function(G,H)
local d,a,map,cG,nG,nH,i,j,u,v,asAutomorphism,K,L,conj,e1,e2,
      iso,api,gens,pre,aab,as,somechar;

  asAutomorphism:=function(sub,hom)
    return Image(hom,sub);
  end;

  # TODO: use matgrp package
  if not (IsPermGroup(G) or IsPcGroup(G))  then
    i:=IsomorphismPermGroup(G);
    iso:=PatheticIsomorphism(Image(i,G),H);
    if iso=fail then
      return iso;
    else
      return i*iso;
    fi;
  fi;

  # TODO: use matgrp package
  if not (IsPermGroup(H) or IsPcGroup(H)) then
    i:=IsomorphismPermGroup(H);
    iso:=PatheticIsomorphism(G,Image(i,H));
    if iso=fail then
      return iso;
    else
      return iso*InverseGeneralMapping(i);
    fi;
  fi;

  # go through factors of characteristic series to keep orbits short.
  AutomorphismGroup(G:someCharacteristics:=fail);
  AutomorphismGroup(H:someCharacteristics:=fail);

  d:=AGSRMatchedCharacteristics(G,H);
  if d=fail then return fail;fi; # characteristics do not match
  nG:=d.ng;
  nH:=d.nh;
  for i in [1..Length(d.cg)] do
    u:=TrivialSubgroup(G);
    for j in d.cg[i] do
      u:=ClosureGroup(u,j);
    od;
    if not u in nG then
      Add(nG,u);
      u:=TrivialSubgroup(H);
      for j in d.ch[i] do
        u:=ClosureGroup(u,j);
      od;
      Add(nH,u);
    fi;
  od;

  d:=DirectProduct(G,H);
  e1:=Embedding(d,1);
  e2:=Embedding(d,2);
  # combine images of characteristic factors, reverse order
  cG:=[];
  nG:=Reversed(nG);
  nH:=Reversed(nH);
  for i in [1..Length(nG)] do
    Add(cG,ClosureGroup(Image(e1,nG[i]),Image(e2,nH[i])));
  od;
  nG:=Concatenation([TrivialSubgroup(G)],nG);
  nH:=Concatenation([TrivialSubgroup(H)],nH);
  SortParallel(nG,nH,function(a,b) return Size(a)<Size(b);end);
  if List(nG,Size)<>List(nH,Size) then return fail;fi;

  for i in [2..Length(nG)] do
    K:=Filtered([1..Length(nG)],x->Size(nG[x])*2=Size(nG[i])
          and IsSubset(nG[i],nG[x]));
    if Length(K)>0 then
      K:=K[1];
      # We are seeking an isomorphism, not the full automorphism group of
      # GxG. It is thus sufficient, if we find the subgroup Aut(G)\wr 2.


      # We now found that G and H have two characteristic subgroups A<B with
      # [B:A]=2. An isomorphism swapping G and H will need to map B to B and
      # A to A. Furthermore, in the factor modulo A_G xA_H, a generator of
      # B_G must be swappes with a generator of B_H.
      # This implies that A_G\times A_H, together with the diagonal of B is
      # characteristic in Aut(A)\wr 2. We thus may add this subgroup as
      # ``characteristic'' to improve the series.

      Add(cG,ClosureGroup(
        ClosureGroup(Image(e1,nG[K]),Image(e2,nH[K])),
          Image(e1,First(GeneratorsOfGroup(nG[i]),x->not x in nG[K]))
         *Image(e2,First(GeneratorsOfGroup(nH[i]),x->not x in nH[K]))));

    fi;
  od;

  aab:=[Image(e1,G),Image(e2,H)];
  # we also fix the *pairs* of the characteristic subgroups as orbits. Again
  # this must happen in Aut(G)\wr 2, and reduces the size of the group.
  somechar:=rec(subgroups:=cG,
        orbits:=List([1..Length(nG)],x->[Image(e1,nG[x]),Image(e2,nH[x])]));
  a:=AutomorphismGroup(d:autactbase:=aab,someCharacteristics:=somechar,
    directs:=aab,
    delaypermrep:=true );
  for i in cG do
    if not ForAll(GeneratorsOfGroup(a),x->Image(x,i)=i) then
      a:=Stabilizer(a,i,asAutomorphism);
    fi;
  od;

  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);
  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));

    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
          map:=AGBoundedOrbrep(as,u,v,asAutomorphism,100);
        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);
          map:=map.rep;
          conj:=conj*map;
          K:=Image(map,K);
          as:=as^map;
        else
          if iso=fail then
            Info(InfoMorph,1,"Shortorb failed, get delayed permrep");
            if IsBound(a!.makeaqiso) then a!.makeaqiso();fi;
            iso:=IsomorphismPermGroup(a:autactbase:=aab);
            api:=Image(iso,as);
          fi;

          if IsSolvableGroup(api) then
            gens:=Pcgs(api);
          else
            gens:=SmallGeneratingSet(api);
          fi;
          pre:=List(gens,x->PreImagesRepresentative(iso,x));
          map:=RepresentativeAction(SubgroupNC(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;
    od;

  fi;

  return GroupHomomorphismByImagesNC(G,H,GeneratorsOfGroup(G),
    List(GeneratorsOfGroup(G),x->PreImagesRepresentative(e2,
         Image(conj,Image(e1,x)))));
end);
