diff --git a/TODO-mandarins.md b/TODO-mandarins.md new file mode 100644 index 000000000..dc4f09e39 --- /dev/null +++ b/TODO-mandarins.md @@ -0,0 +1,136 @@ +## Make clear where things can be improved +- comments and how to reproduce + +## TODO +Wenn z.b. in einem Blatt ein SLP berechnet wurde, heißt das in einem inneren +Knoten `node` nicht, dass der input auch element von Grp(node) ist. +Insbesondere müssen wir immer das SLP für ein Mandarin auswerten und checken, +dass der Mandarin rausgekommen ist? + +FDPM test: + Soll ich mal ein profile anlegen wo ich constructive membership test für An + und Sn vergleiche? + Ergebnis: ist einfach langsam + +There is a regression in bugfix.tst. Clean up and then check again. + + ########> Diff in /home/sergio/projects/pkg/recog/tst/working/slow/bugfix.\ + tst:25 + # Input is: + for i in [1..50] do + ri := RECOG.TestGroup(GL(8,27), false, Size(GL(8,27))); + od; + # Expected output: + # But found: + Immediate verification: found extra kernel element(s)! + Error, no method found! For debugging hints type ?Recovery from NoMethodFound + Error, no 1st choice method found for `Length' on 1 arguments + The 1st argument is 'fail' which might point to an earlier problem + + ######## + +- also use gens of input group as mandarins + +- make ImmVer + send 100 elements + would this be faster? If so, do a hybrid approach? Namely only work with 10 + or 20 mandarins during recognition, but with 100 once the tree is finished. + +- set mandarin info msgs to level 2 +- revisit 1c19d12 fine tune imprimitive +- test whether we can make the standard arguments RECOG_FindKernelFastNormalClosureStandardArgumentValues for findgensNmeth smaller and + get away with it since we use mandarins. + +Could it be that immediate verification doesn't do anything once mandarins are +enabled? See logs/\*.logs. With master immediate verification prints "found +extra kernel element". +ImmVer does something when encountering trivial kernels? Double check this and +see if performance deteriorates if we disable that. + +gap> SetInfoLevel(InfoRecog, 1); +gap> TestMatDiagonal := function(F, n) +> local gens, l, i, m, j, g, ri; +> gens := []; +> l := ShallowCopy(Elements(F)); +> RemoveSet(l,Zero(F)); +> for i in [1..5] do +> m := IdentityMat(7,F); +> for j in [1..7] do +> m[j,j] := Random(l); +> od; +> Add(gens,m); +> od; +> g := GroupWithGenerators(gens); +> return RecogniseGroup(g); +> end;; +gap> while true do TestMatDiagonal(GF(9), 2);; od; + +^ there is a call which takes forever when I let it run + +Super slow due to crisis: +- MatDiagonal.tst +Slow due to crisis: +- bugfix + +## Option to enable/disable mandarins +set num_mands to 0 + +## Docs: +Differences to magma/CompositionTree: +- We add levels and small/big crises. +- They during a crisis, the generators of the chopped off is doubled. + Then, they claim that nrGens of a kernel is always at least nrGens of the + parent assures that all descendants of the new kernel also have a big + generatings set. + We also do the former. Instead of doing the latter, + + +## Kernel-safe methods, deterministicKernelCreation +Let methods say, that they create safe kernels if the current node is safe. +Try this e.g. with "ThrowAwayFixedPoints" + + +## Performance + +Something is weird with the current profiling system. Membership-checking takes +forever for some groups. But profiling shows that the extra time is spent in +the SLP functions. Here we can find difference between membership testing and +mandarins. + + gap-master tst/testall-120-inTests-10-mandarins.g + gap-master tst/testall-30-inTests-100-mandarins.g + + +Test performance by manually reducing the arguments for findkernelmeth of +BalTreeForBlocks and Imprimitive? Or maybe even of the whole +`RECOG_Find..StandardArguments`? +- Setting BAL_CONST to 0.1 and running recognition of the wreath product + g := WreathProduct(SymmetricGroup(5),SymmetricGroup(32)); + triggers lvl 3 crises and works fine. Time for recognition is roughly 5s with + no extreme outliers. + BUT See how this behaves if I only reduce the "conjugations" + argument for findgensNmeth + +When crises are raised very often, that may be a hint, that the kernel +generating size is too small. To an extent, it should be fine to rely on the +mandarins to catch that. + +- ce1f513 master + total 591680 ms (34347 ms GC) and 57.3GB allocated +- 61adc09 but without handling crises and erroring out instead + total 982249 ms (78561 ms GC) and 88.7GB allocated +- b7c0cf0 Mandarins with SLPs but without checking equality + total 1043838 ms (84736 ms GC) and 89.6GB allocated +- faf71d4 Mandarins with SLPs + total 3370850 ms (505873 ms GC) and 153GB allocated +- 65539bf Mandarins without SLPs + total 2866155 ms (365469 ms GC) and 188GB allocated + +## Performance improvements + +According to a few profiles I made a lot of time is spent in the GAP library +functions in lib/straight.gi: +- RewriteStraightLineProgram +- ResultOfLineOfStraightLineProgram +- IntermediateResultsOfSLPWithoutOverwriteInner +Also, the SLPs seem to use up a lot of storage. That should be reduced by +implementing an SLP data-type on the kernel level. diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..7513ca1e5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,14 @@ +- IsReady: only is not set when no recognition method was able to recognise a + node in the tree. +- make in the package RecognizeGeneric only be called with RecogNodes + this would enable us to get rid of `depthstring` +- IsSafeForMandarins: recog method should know whether there is a deterministic + function to compute its kernel. Name isFindGensNMethDeterministic? + => operation with two methods + - IsRecogNode: go check on successmethod + - IsRecogMethod: take its component + => findGensNmeth: is it ever set by anything but recognition methods? If not, + we can store it in the recog method. +- ValidateHomomInput: everybody must set hasvalidatehomominput! Can we make + this a required component of the recognition method? +- Enforce that every recog method provides a way to SetNiceGens? diff --git a/examples/challenges-by-frank-luebeck/README.md b/examples/challenges-by-frank-luebeck/README.md new file mode 100644 index 000000000..d311169ee --- /dev/null +++ b/examples/challenges-by-frank-luebeck/README.md @@ -0,0 +1,5 @@ +These challenges were taken from Frank Lübeck's homepage: + + http://www.math.rwth-aachen.de/~Frank.Luebeck/data/MatrixChallenges/index.html?LANG=en + +Progress can be found in the file `solutions` in the directory above. diff --git a/examples/example1.g.gz b/examples/challenges-by-frank-luebeck/example1.g.gz similarity index 100% rename from examples/example1.g.gz rename to examples/challenges-by-frank-luebeck/example1.g.gz diff --git a/examples/example10.g.gz b/examples/challenges-by-frank-luebeck/example10.g.gz similarity index 100% rename from examples/example10.g.gz rename to examples/challenges-by-frank-luebeck/example10.g.gz diff --git a/examples/example11.g.gz b/examples/challenges-by-frank-luebeck/example11.g.gz similarity index 100% rename from examples/example11.g.gz rename to examples/challenges-by-frank-luebeck/example11.g.gz diff --git a/examples/example12.g.gz b/examples/challenges-by-frank-luebeck/example12.g.gz similarity index 100% rename from examples/example12.g.gz rename to examples/challenges-by-frank-luebeck/example12.g.gz diff --git a/examples/example2.g.gz b/examples/challenges-by-frank-luebeck/example2.g.gz similarity index 100% rename from examples/example2.g.gz rename to examples/challenges-by-frank-luebeck/example2.g.gz diff --git a/examples/example3.g.gz b/examples/challenges-by-frank-luebeck/example3.g.gz similarity index 100% rename from examples/example3.g.gz rename to examples/challenges-by-frank-luebeck/example3.g.gz diff --git a/examples/example4.g.gz b/examples/challenges-by-frank-luebeck/example4.g.gz similarity index 100% rename from examples/example4.g.gz rename to examples/challenges-by-frank-luebeck/example4.g.gz diff --git a/examples/example5.g.gz b/examples/challenges-by-frank-luebeck/example5.g.gz similarity index 100% rename from examples/example5.g.gz rename to examples/challenges-by-frank-luebeck/example5.g.gz diff --git a/examples/example6.g.gz b/examples/challenges-by-frank-luebeck/example6.g.gz similarity index 100% rename from examples/example6.g.gz rename to examples/challenges-by-frank-luebeck/example6.g.gz diff --git a/examples/example7.g.gz b/examples/challenges-by-frank-luebeck/example7.g.gz similarity index 100% rename from examples/example7.g.gz rename to examples/challenges-by-frank-luebeck/example7.g.gz diff --git a/examples/example8.g.gz b/examples/challenges-by-frank-luebeck/example8.g.gz similarity index 100% rename from examples/example8.g.gz rename to examples/challenges-by-frank-luebeck/example8.g.gz diff --git a/examples/example9.g.gz b/examples/challenges-by-frank-luebeck/example9.g.gz similarity index 100% rename from examples/example9.g.gz rename to examples/challenges-by-frank-luebeck/example9.g.gz diff --git a/gap/base/kernel.gi b/gap/base/kernel.gi index b5ad92588..79cca0ce8 100644 --- a/gap/base/kernel.gi +++ b/gap/base/kernel.gi @@ -74,6 +74,7 @@ InstallGlobalFunction( ImmediateVerification, fi; if verified = true then return true; fi; # Now, verified = false. + Print("Immediate verification: found extra kernel element(s)!\n"); Info(InfoRecog,2, "Immediate verification: found extra kernel element(s)!"); if FindKernelFastNormalClosure(ri,5,5) = fail then @@ -166,6 +167,7 @@ InstallGlobalFunction( FastNormalClosure , function( G, list, n ) if IsEmpty(list) then return []; fi; + Info(InfoRecog, 2, "Do FastNormalClosure with n=", n, "."); list2 := ShallowCopy(list); if IsGroup(G) then grpgens := GeneratorsOfGroup(G); diff --git a/gap/base/recognition.gd b/gap/base/recognition.gd index ca6cbfea1..57b6a88ce 100644 --- a/gap/base/recognition.gd +++ b/gap/base/recognition.gd @@ -470,6 +470,30 @@ BindGlobal( "FindHomMethodsGeneric", rec() ); ## <#/GAPDoc> BindGlobal( "SLPforElementFuncsGeneric", rec() ); +# TODO Document mandarins +# Refer to overview paper by Baarnhielm, Holt, Charles, Eamonn, sections "5.2 +# The main algorithm" and "5.4 Crisis management". +# Explain safe and unsafe nodes. +# Explain in which situations a crisis can be evoked: +# - only in a leaf: mandarin can't be written as SLPs in nice gens, this does +# not happen in non-leaf nodes +# - mandarins can't be mapped under reduction homomorphism +# - +# Note that when implementing verification via presentations, that can also +# evoke crises. Then I'd make sense to remove the "MANDARIN_" prefix from the +# name. +# TODO Wait a second, if the generators of the root can be expressed as SLPs in +# the nice gens, then the kernels must be correct, right?! +# Crisis objects +DeclareCategory("IsRecogCrisis", IsObject); +BindGlobal("RecogCrisisFamily", NewFamily("RecogCrisisFamily", IsRecogCrisis)); +BindGlobal("RecogCrisisType", NewType(RecogCrisisFamily, IsRecogCrisis)); +DeclareOperation( "RecogCrisis", [IsRecogNode]); +DeclareFilter( "KernelGeneratorsAlreadyEnlargedByCrisis" ); +# TODO remove this: +BindGlobal("MANDARIN_CRISIS", MakeImmutable("MANDARIN_CRISIS")); +BindGlobal("NUM_MANDARINS_DEFAULT_VALUE", 100); +DeclareFilter( "IsSafeForMandarins" ); # Our global functions for the main recursion: @@ -575,7 +599,9 @@ DeclareSynonym("RecognizeGroup", RecogniseGroup); ## ## ## <#/GAPDoc> -DeclareGlobalFunction( "RecogniseGeneric" ); +# TODO: change documentation and update references from func to oper +DeclareOperation( "RecogniseGeneric", + [ IsRecogNode, IsObject, IsString, IsObject, IsBool ] ); DeclareSynonym("RecognizeGeneric", RecogniseGeneric); DeclareGlobalFunction( "PrintTreePos" ); diff --git a/gap/base/recognition.gi b/gap/base/recognition.gi index 6db8eed00..56c105426 100644 --- a/gap/base/recognition.gi +++ b/gap/base/recognition.gi @@ -18,7 +18,8 @@ BindGlobal("RECOG_FindKernelFastNormalClosureStandardArgumentValues", Immutable([6, 3])); -BindConstant("RECOG_NrElementsInImmediateVerification", 10); +#BindConstant("RECOG_NrElementsInImmediateVerification", 10); +RECOG_NrElementsInImmediateVerification := 10; # a nice view method: RECOG_ViewObj := function( level, ri ) @@ -51,6 +52,9 @@ RECOG_ViewObj := function( level, ri ) elif HasIsRecogInfoForAlmostSimpleGroup(ri) and IsRecogInfoForAlmostSimpleGroup(ri) then Print(" AlmostSimple"); fi; + if ri!.crisisLevel > 0 then + Print(" crisisLevel=", ri!.crisisLevel); + fi; if HasSize(ri) then Print(" Size=",Size(ri)); fi; @@ -91,25 +95,25 @@ InstallMethod( ViewObj, "for recognition nodes", [IsRecogNode], InstallGlobalFunction( RecognisePermGroup, function(G) - return RecogniseGeneric(G, FindHomDbPerm, "", rec()); + return RecogniseGeneric(G, FindHomDbPerm, "", rec(), fail, true); end); InstallGlobalFunction( RecogniseMatrixGroup, function(G) - return RecogniseGeneric(G, FindHomDbMatrix, "", rec()); + return RecogniseGeneric(G, FindHomDbMatrix, "", rec(), fail, true); end); InstallGlobalFunction( RecogniseProjectiveGroup, function(G) - return RecogniseGeneric(G, FindHomDbProjective, "", rec()); + return RecogniseGeneric(G, FindHomDbProjective, "", rec(), fail, true); end); InstallGlobalFunction( RecogniseGroup, function(G) if IsPermGroup(G) then - return RecogniseGeneric(G, FindHomDbPerm, "", rec()); + return RecogniseGeneric(G, FindHomDbPerm, "", rec(), fail, true); elif IsMatrixGroup(G) then - return RecogniseGeneric(G, FindHomDbMatrix, "", rec()); + return RecogniseGeneric(G, FindHomDbMatrix, "", rec(), fail, true); else ErrorNoReturn("Only matrix and permutation groups are supported"); fi; @@ -149,13 +153,23 @@ InstallMethod( RecogNode, ri := ShallowCopy(r); Objectify( RecogNodeType, ri ); SetGrp(ri,H); + # Used by RecogCrisis + if not IsBound(ri!.crisisLevel) then + ri!.crisisLevel := 0; + fi; Setslpforelement(ri,SLPforElementGeneric); SetgensN(ri,[]); # this will grow over time Setimmediateverification(ri,false); - SetInitialDataForKernelRecogNode(ri,rec(hints := [])); - # this is eventually handed down to the kernel - SetInitialDataForImageRecogNode(ri,rec(hints := [])); - # this is eventually handed down to the image + # this is eventually handed down to the kernel + SetInitialDataForKernelRecogNode( + ri, + rec(hints := [], crisisLevel := ri!.crisisLevel) + ); + # this is eventually handed down to the image + SetInitialDataForImageRecogNode( + ri, + rec(hints := [], crisisLevel := ri!.crisisLevel) + ); if projective then Setisone(ri,IsOneProjective); Setisequal(ri,IsEqualProjective); @@ -228,6 +242,7 @@ InstallOtherMethod( RecogNode, return RecogNode(H, projective, rec()); end ); + # Sets the stamp used by RandomElm, RandomElmOrd, and related functions. RECOG.SetPseudoRandomStamp := function(g,st) if IsBound(g!.pseudorandomfunc) then @@ -451,35 +466,244 @@ InstallGlobalFunction( PrintTreePos, fi; end ); -InstallGlobalFunction( RecogniseGeneric, - function(H, methoddb, depthString, knowledge) +BindGlobal("TryToEnlargeKernelGeneratingSetAndUpdateSLPsDuringMandarinCrisis", +function(ri, crisis) + local gensNWasEmpty, targetNrGensN, kernelGenerationSuccess; + Print("Handling Mandarin Crisis!\n"); + gensNWasEmpty := IsEmpty(gensN(ri)); + if gensNWasEmpty then + # The following value was chosen arbitrarily. It gets reduced during + # each iteration to a minimal value of 1, to account for the case where + # the kernel only has two elements. The findgensNmeth might discard + # trivial or duplicate generators. In that case we can never find more + # than one generator, if the kernel has size two. + targetNrGensN := 5; + else + # The goal is to obtain twice as many generators as were in gensN. The + # kernel generation methods write directly into gensN and don't provide + # a means to specify how many generators should be created. Also, they + # might not add a newly created generator, if that one already is in + # gensN. If that happens when enlarging a kernel generating set for the + # first time and we set the factor below to 2, we might call the + # findgensNmeth a second time, tripling instead of doubling the size of + # the generating set. + targetNrGensN := Int(1.9 * Length(gensN(ri))); + fi; + Info(InfoRecog, 2, + "Enlarging the kernel's generating set due to a mandarin crisis."); + repeat + # It might happen that we enlarge the wrong kernel. If in addition the + # kernel is very small, we might never stop looking for new generators, + # unless we successively reduce targetNrGensN. + targetNrGensN := targetNrGensN - 1; + # Create additional kernel generators with the stored method: + # kernelGenerationSuccess is either true or fail. + kernelGenerationSuccess := + CallFuncList(findgensNmeth(ri).method, + Concatenation([ri],findgensNmeth(ri).args)); + if kernelGenerationSuccess = fail then + return false; + fi; + until Length(gensN(ri)) >= targetNrGensN; + SetgensNslp(ri,SLPOfElms(gensN(ri))); + SlotUsagePattern(gensNslp(ri)); + ri!.crisisLevel := crisis!.level; + InitialDataForImageRecogNode(ri).crisisLevel := crisis!.level; + InitialDataForKernelRecogNode(ri).crisisLevel := crisis!.level; + return true; +end); + +# Create an IsRecogCrisis object. The argument `ri` must have a +# ParentRecogNode. A crisis can have level 1 or level 2. +# The higher the level, the more we chop off of the tree. +# How is the level of a new crisis determined? If on the path to the root there +# is no node marked KernelGeneratorsAlreadyEnlargedByCrisis, then we throw a +# level 1 crisis. Otherwise we throw a level 2 crisis. +# Why is it sufficient to only check the path to the root? If we were too +# conservative by not backtracking all the way to the first IsSafeForMandarins +# node and instead only backtracked to a node X, then recognising X must again +# throw a crisis. Thus node X will be on the path to the root. +InstallMethod(RecogCrisis, +"for an IsRecogNode", +[IsRecogNode], +function(ri) + local isKernelNode, unsafeKernelsOnPathToRoot, node, highLevel, lowLevel, + isSmallCrisis, level, i, kernelToChop, result; + # This is never called on the root: + isKernelNode := + x -> HasKernelRecogNode(ParentRecogNode(x)) + and IsIdenticalObj(x, KernelRecogNode(ParentRecogNode(x))); + # Find unsafe kernel nodes on the path to root + unsafeKernelsOnPathToRoot := []; + node := ri; + repeat + if isKernelNode(node) then + Add(unsafeKernelsOnPathToRoot, node); + fi; + node := ParentRecogNode(node); + # The root is always safe, so this loop terminates. + until IsSafeForMandarins(node); + # Now unsafeKernelsOnPathToRoot must be non-empty + Print(List(unsafeKernelsOnPathToRoot, y -> ParentRecogNode(y)!.crisisLevel), "\n"); + # Determine whether to return a small crisis, that is whether to cut low. + # If unsafeKernelsOnPathToRoot has length one, there is no difference + # between a small and a big crisis, so we don't need to special case it. + highLevel := unsafeKernelsOnPathToRoot[Length(unsafeKernelsOnPathToRoot)]!.crisisLevel; + lowLevel := ri!.crisisLevel; + isSmallCrisis := lowLevel = highLevel; + # TODO: fix description how levels are determined. + # We distinguish two types of crises. Either the level is one, or the level + # is greater or equal than two. We call the crisis of the latter kind a big + # crisis. In case of such a big crisis the recognition algorithm backtracks + # all the way to the topmost unsafe kernel on the way to the root and + # chopping it off. A level one crisis only backtracks part of the way and + # thus chops off a smaller subtree than big crises. + # TODO: describe that this level is propagated downwards to all *newly* + # created nodes. but only on the kernel side of the marked node! Thus it + # might be, that on a path to the root, the level is not strictly + # increasing. + # TODO TODO actually, I need to propagate it to everything? I had an + # example run taking 135 s instead of 2 s. Also, if a node is marked, then + # its kernel node is chopped off and that tree is recomputed, the tree + # rooted in the image node must have been completely recognized. + # TODO thus say above, + # that each node is initialized with a "crisisLevelSeen". + # The parent of the kernel which is chopped off stores the crisis level. + # Note that for a fixed node, these are strictly increasing. + # TODO: explain that + # the crisis level of a node determines how often findgensNmeth is called, + # namely 2 ^ crisisLevel many times. + # If no node on the way to the root was marked by a level one crisis, we + # always throw a level one crisis. Otherwise we take the highest crisis + # level seen by the topmost unsafe kernel and increase it by one. Note that + # once a node is marked by a big crisis, the next crisis triggered in the + # new subtree rooted in its newly constructed kernel node will again be a + # level one crisis, since no node will have been marked by a level one + # crisis yet. + # In a newly constructed subtree we always first spawn a level one + # crisis, to conservatively chop off part of the recognition tree. + # should small crises increase level? + #0 + #1 + #2 + # + #0 0 0 0 + #-> 1, low + # + #0 0 1 1 + #-> 1, high + # + #1 1 1 1 + #-> 2, low + # + #1 1 2 2 + #-> 2, high + # + #2 2 2 2 + level := highLevel + 1; + Info(InfoRecog, 1, "Crisis level=", level, ", small=", isSmallCrisis, "."); + # Determine kernelToChop + if isSmallCrisis then + i := Maximum(1, QuoInt(1 + Length(unsafeKernelsOnPathToRoot), 2)); + else + i := Length(unsafeKernelsOnPathToRoot); + fi; + kernelToChop := unsafeKernelsOnPathToRoot[i]; + result := rec(kernelToChop := kernelToChop, level := level); + Objectify(RecogCrisisType, result); + return result; +end); + +# This is a hack to handle the case where the kernel is trivial and thus +# instead of a RecogNode, a "fail" is put into the tree. +InstallOtherMethod(RecogCrisis, +"for a boolean and an IsRecogNode", +[IsBool, IsRecogNode], +function(trivialKernel, parent) + local fakeNode, result; + if not trivialKernel = fail then + ErrorNoReturn(" must be fail but is ", trivialKernel); + fi; + fakeNode := RecogNode(Group(())); + SetParentRecogNode(fakeNode, parent); + SetKernelRecogNode(parent, fakeNode); + result := RecogCrisis(fakeNode); + SetKernelRecogNode(parent, fail); + if IsIdenticalObj(result!.kernelToChop, fakeNode) then + result!.kernelToChop := fail; + fi; + return result; +end); + +# TODO, disable this to run test suite. +InstallOtherMethod( RecogniseGeneric, +"compatibility method", +[ IsGroup, IsObject, IsString, IsRecord, IsObject, IsBool ], +function(H, methoddb, depthString, knowledge, mandarins, isSafeForMandarins) + local ri; + # RecogNode calls ProductReplacer, which needs to have at least one + # generator. + if Length(GeneratorsOfGroup(H)) = 0 then + H := Group([One(H)]); + fi; + ri := RecogNode( + H, + IsIdenticalObj( methoddb, FindHomDbProjective ), + knowledge + ); + RecogniseGeneric(ri, methoddb, depthString, + mandarins, isSafeForMandarins); + return ri; +end); + +# TODO: Update docs: new return value: crisis or true +# TODO: Rename this into RecogniseRecogNode +# TODO: isSafeForMandarins could be put into the RecogNode call, then we can +# remove it here. +InstallMethod( RecogniseGeneric, +"TODO", +[ IsRecogNode, IsObject, IsString, IsObject, IsBool ], +function(ri, methoddb, depthString, mandarins, isSafeForMandarins) + local depth, H, allmethods, mandarinSLPs, s, imageMandarins, y, + counter_image, rifac, imageStatus, kernelGenerationSuccess, l, ll, + kernelMandarins, crisis, kernelMandarinSuccess, + immediateVerificationSuccess, N, riker, kernelStatus, x, + nrNiceGensOfImageNode, i; # Assume all the generators have no memory! - local N,depth,done,i,l,ll,allmethods, - hint, - proj1,proj2,ri,rifac,riker,s,x,y,z,succ,counter; depth := Length(depthString); + H := Grp(ri); PrintTreePos("E",depthString,H); Info(InfoRecog,4,"Recognising: ",H); if Length(GeneratorsOfGroup(H)) = 0 then + # FIXME: shouldn't we just, like, finish here immediately? H := Group([One(H)]); fi; - # Set up the record and the group object: - ri := RecogNode( - H, - IsIdenticalObj( methoddb, FindHomDbProjective ), - knowledge - ); + if mandarins = fail then + Assert(0, depth = 0); + # HACK: We don't want the mandarins to be reused by any computation. + # Since PseudoRandom is hacked, it is important that we generate the + # mandarins before calling RecogNode. Otherwise the mandarins would be + # reused by RandomElm and RandomElmOrd. + # TODO: enable passing NUM_MANDARINS as optional arg + mandarins := List([1..NUM_MANDARINS_DEFAULT_VALUE], i -> PseudoRandom(H)); + fi; + ri!.mandarins := mandarins; + if isSafeForMandarins then + SetFilterObj(ri, IsSafeForMandarins); + fi; + # was here earlier: Setcalcnicegens(ri,CalcNiceGensGeneric); + Setmethodsforimage(ri,methoddb); # Combine database of find homomorphism methods with hints allmethods := methoddb; - if IsBound(knowledge.hints) then - allmethods := Concatenation(allmethods, knowledge.hints); + if IsBound(ri!.hints) then + allmethods := Concatenation(allmethods, ri!.hints); SortBy(allmethods, a -> -a.rank); fi; # verify no rank occurs more than once @@ -500,7 +724,7 @@ InstallGlobalFunction( RecogniseGeneric, Info(InfoRecog, 1, "RecogNode could not be recognised,", " IsReady() is not set, recognition aborts"); - return ri; + return true; fi; # Handle the leaf case: @@ -523,48 +747,111 @@ InstallGlobalFunction( RecogniseGeneric, SetNiceGens(ri,GeneratorsOfGroup(H)); fi; fi; + + # Check mandarins and compute their SLPs in the nice generators of ri. + Info(InfoRecog, 3, "Check mandarins (leaf, depth=", depth,")."); + mandarinSLPs := []; + for x in mandarins do + s := SLPforElement(ri, x); + if s = fail then + Info(InfoRecog, 1, + "Enter mandarin crisis (leaf, depth=", depth, ")."); + return RecogCrisis(ri); + fi; + Add(mandarinSLPs, s); + od; + ri!.mandarinSLPs := mandarinSLPs; + if InfoLevel(InfoRecog) = 1 and depth = 0 then Print("\n"); fi; # StopStoringRandEls(ri); SetFilterObj(ri,IsReady); - return ri; + return true; fi; # The non-leaf case: # In that case we know that ri now knows: homom plus additional data. - # Try to recognise the image a few times, then give up: - counter := 0; + if ForAny(GeneratorsOfGroup(H), x->not ValidateHomomInput(ri, x)) then + # We just computed the homomorphism using exactly those generators. + ErrorNoReturn("Can't map generators of under the homomorphism", + " Homom(): ValidateHomomInput failed"); + fi; + + # Check that the mandarins can be mapped under the homomorphism. If this + # fails, then somewhere higher up in the recognition tree, a kernel must + # have been too small. + if ForAny(mandarins, x->not ValidateHomomInput(ri, x)) then + Info(InfoRecog, 1, + "Enter mandarin crisis (depth=", depth, "), ", + "ValidateHomomInput failed."); + return RecogCrisis(ri); + fi; + # Compute the mandarins of the factor + imageMandarins := []; + for x in mandarins do + y := ImageElm(Homom(ri), x); + Assert(2, y <> fail); + Add(imageMandarins, y); + od; + # TODO: sort (?) the imageMandarins and handle duplicates and trivials + + # Recognise the image and build the kernel. + counter_image := 1; repeat - counter := counter + 1; - if counter > 10 then + if counter_image > 2 then + # We enter here, if kernelGenerationSuccess was fail twice in + # a row. The value of kernelGenerationSuccess is fail, if + # writing of a random element of the image as an SLP in its nice + # generators failed and thus somewhere below the image node rifac + # there is a too small kernel, which the Mandarins didn't catch. + # This should be super seldom. + # Maybe this can happen more often when, deep in the tree, the + # Mandarins are very few? if InfoLevel(InfoRecog) = 1 and depth = 0 then Print("\n"); fi; Info(InfoRecog, 1, - "ImageRecogNode of RecogNode could not be recognised,", + "Kernel generation for RecogNode failed ", + counter_image, " times, ", " IsReady() is not set, recognition aborts"); - return ri; + return true; fi; if IsMatrixGroup(Image(Homom(ri))) then Info(InfoRecog,2,"Going to the image (depth=",depth,", try=", - counter,", dim=",DimensionOfMatrixGroup(Image(Homom(ri))), + counter_image,", dim=",DimensionOfMatrixGroup(Image(Homom(ri))), ", field=",Size(FieldOfMatrixGroup(Image(Homom(ri)))),")."); else Info(InfoRecog,2,"Going to the image (depth=",depth,", try=", - counter,")."); - fi; - if ForAny(GeneratorsOfGroup(H), x->not ValidateHomomInput(ri, x)) then - # Our group fails to contain some of the generators of H! - return fail; + counter_image,")."); fi; - Add(depthString,'F'); - rifac := RecogniseGeneric( + rifac := RecogNode( Group(List(GeneratorsOfGroup(H), x->ImageElm(Homom(ri),x))), - methodsforimage(ri), depthString, InitialDataForImageRecogNode(ri) ); - Remove(depthString); - PrintTreePos("F",depthString,H); + IsIdenticalObj(methodsforimage(ri), FindHomDbProjective), + InitialDataForImageRecogNode(ri)); SetImageRecogNode(ri,rifac); SetParentRecogNode(rifac,ri); + Add(depthString,'F'); + # TODO Change return value + imageStatus := RecogniseGeneric( + rifac, + methodsforimage(ri), + depthString, + imageMandarins, + IsSafeForMandarins(ri)); + Remove(depthString); + PrintTreePos("F",depthString,H); + if IsRecogCrisis(imageStatus) then + # According to the mandarins, somewhere higher up in the recognition + # tree, a kernel must have been too small. + Info(InfoRecog, 2, + "Backtrack to the last safe node (depth=", depth, ")."); + return imageStatus; + fi; + if not IsReady(rifac) then + # IsReady was not set, thus abort the whole computation. + if InfoLevel(InfoRecog) = 1 and depth = 0 then Print("\n"); fi; + return true; + fi; if IsMatrixGroup(H) then Info(InfoRecog,2,"Back from image (depth=",depth, @@ -574,21 +861,31 @@ InstallGlobalFunction( RecogniseGeneric, Info(InfoRecog,2,"Back from image (depth=",depth,")."); fi; - if not IsReady(rifac) then - # IsReady was not set, thus abort the whole computation. - if InfoLevel(InfoRecog) = 1 and depth = 0 then Print("\n"); fi; - return ri; - fi; - # Now we want to have preimages of the new generators in the image: Info(InfoRecog,2,"Calculating preimages of nice generators."); ri!.pregensfacwithmem := CalcNiceGens(rifac, ri!.gensHmem); Setpregensfac(ri, StripMemory(ri!.pregensfacwithmem)); # Now create the kernel generators with the stored method: - succ := CallFuncList(findgensNmeth(ri).method, - Concatenation([ri],findgensNmeth(ri).args)); - until succ = true; + # The value of kernelGenerationSuccess is either true or fail. + # A non-zero crisisLevel means that somewhere above us a kernel + # had to be chopped off due to a crisis. Thus we create more kernel + # generators. + # We tested running findgensNmeth 2 ^ ri!.crisisLevel times on + # the "big imprimitive wreath product" test case (TODO explain what + # that is). + # Using 2 ^ level was inferior to using 2 * level. + # TODO, drop the 2 *: Using 2 ^ level was inferior to using level. + # TODO: test running findgensNmeth 2 ^ ri!.crisisLevel times + # with improved findgensNmeth args. + # TODO: why doesn't this throw a crisis? + kernelGenerationSuccess := true; + for i in [1 .. 1 + ri!.crisisLevel] do + kernelGenerationSuccess := kernelGenerationSuccess + and (true = CallFuncList(findgensNmeth(ri).method, + Concatenation([ri],findgensNmeth(ri).args))); + od; + until kernelGenerationSuccess = true; # If nobody has set how we produce preimages of the nicegens: if not Hascalcnicegens(ri) then @@ -601,7 +898,7 @@ InstallGlobalFunction( RecogniseGeneric, Sort(l,SortFunctionWithMemory); # this favours "shorter" memories! # FIXME: For projective groups different matrices might stand # for the same element, we might overlook this here! - # remove duplicates: + # remove duplicates and trivial entries: ll := []; for i in [1..Length(l)] do if not isone(ri)(l[i]) and @@ -611,57 +908,182 @@ InstallGlobalFunction( RecogniseGeneric, od; SetgensN(ri,ll); fi; + + # Build kernel mandarins + kernelMandarins := List( + [1..Length(mandarins)], + i -> mandarins[i] + # Divide by a preimage of its image under Homom(ri). + / ResultOfStraightLineProgram(rifac!.mandarinSLPs[i], pregensfac(ri)) + ); + # TODO: sort(?) the kernelMandarins and handle duplicates and trivials + if IsEmpty(gensN(ri)) and immediateverification(ri) then # Special case, for an explanation see the source of the called function. RECOG_HandleSpecialCaseKernelTrivialAndMarkedForImmediateVerification(ri); fi; + # If no kernel generators were found, check that the mandarins agree that + # the kernel is trivial. + if IsEmpty(gensN(ri)) + # HACK: something is suuper iffy about the method BlocksModScalars, + # see the comment at the "check mandarins" part of the non-leaf case. + and not fhmethsel(ri).successMethod in ["BlocksModScalars", "BlockScalar"] + and ForAny(kernelMandarins, x -> not ri!.isone(x)) then + Info(InfoRecog, 1, + "Enter mandarin crisis (depth=", depth, "), ", + "kernel can't be trivial."); + # We handle this in the same way as if recognition of the + # kernel threw a crisis. + crisis := RecogCrisis(fail, ri); + if crisis!.kernelToChop <> fail then + return crisis; + else + Info(InfoRecog, 2, + "Handle the mandarin crisis (depth=", depth, ")."); + if not TryToEnlargeKernelGeneratingSetAndUpdateSLPsDuringMandarinCrisis(ri, crisis) then + # TODO: discard and re-recognise the image. + ErrorNoReturn("TODO"); + fi; + fi; + fi; if IsEmpty(gensN(ri)) then - # We found out that N is the trivial group! - # In this case we do nothing, kernel is fail indicating this. + # We only enter this case, if the mandarins agreed that the kernel is + # trivial. Set the kernel to fail to indicate that it is trivial. Info(InfoRecog,2,"Found trivial kernel (depth=",depth,")."); SetKernelRecogNode(ri,fail); # We have to learn from the image, what our nice generators are: SetNiceGens(ri,pregensfac(ri)); + # Since the kernel is trivial, evaluating the image's mandarinSLPs in + # pregensfac(ri) must yield the mandarins of the current node. + # Since the mandarins agree that the kernel is trivial, the current + # node can't be IsSafeForMandarins and we can ignore that case. + ri!.mandarinSLPs := ShallowCopy(rifac!.mandarinSLPs); + for i in [1..Length(mandarins)] do + x := mandarins[i]; + s := ri!.mandarinSLPs[i]; + if not isequal(ri)(x, ResultOfStraightLineProgram(s, NiceGens(ri))) + # HACK: something is suuper iffy about the method BlocksModScalars, + # see the comment at the "check mandarins" part of the non-leaf case. + and fhmethsel(ri).successMethod <> "BlocksModScalars" then + # TODO remove + return MANDARIN_CRISIS; + fi; + od; if InfoLevel(InfoRecog) = 1 and depth = 0 then Print("\n"); fi; # StopStoringRandEls(ri); SetFilterObj(ri,IsReady); - return ri; + return true; fi; - Info(InfoRecog,2,"Going to the kernel (depth=",depth,")."); + # Due to mandarins or immediate verification we may have to enlarge gensN + # and then recognise the kernel again. repeat - # Now we go on as usual: + kernelMandarinSuccess := false; + immediateVerificationSuccess := false; + SetgensNslp(ri,SLPOfElms(gensN(ri))); SlotUsagePattern(gensNslp(ri)); # This is now in terms of the generators of H! N := Group(StripMemory(gensN(ri))); + Info(InfoRecog,2,"Going to the kernel (depth=",depth,")."); + riker := RecogNode(N, + # TODO!! safeguard this against changed method dbs + # TODO custom_decide_whether_db_is_projectiv + IsIdenticalObj(methoddb, FindHomDbProjective), + InitialDataForKernelRecogNode(ri)); + SetKernelRecogNode(ri,riker); + SetParentRecogNode(riker,ri); Add(depthString,'K'); - riker := RecogniseGeneric( N, methoddb, depthString, InitialDataForKernelRecogNode(ri) ); + kernelStatus := RecogniseGeneric( riker, methoddb, depthString, + kernelMandarins, + # TODO: extend this such that riker can also + # be IsSafeForMandarins, if the responsible + # findgensNmeth is guaranteed to find the + # generators for the whole kernel. + false); Remove(depthString); PrintTreePos("K",depthString,H); - SetKernelRecogNode(ri,riker); - SetParentRecogNode(riker,ri); + if IsRecogCrisis(kernelStatus) then + # According to the mandarins, there was an error in the kernel + # generation of the current node or higher up in the recognition + # tree. + if not IsIdenticalObj(kernelStatus!.kernelToChop, riker) then + Info(InfoRecog, 2, + "Backtrack further (depth=", depth, ")."); + return kernelStatus; + fi; + Info(InfoRecog, 2, + "Handle the mandarin crisis (depth=", depth, ")."); + # We are the first safe node on the way to the root and thus need to + # handle the crisis ourselves. + if not TryToEnlargeKernelGeneratingSetAndUpdateSLPsDuringMandarinCrisis(ri, kernelStatus) then + # TODO: discard and re-recognise the image. + ErrorNoReturn("TODO"); + fi; + # This restarts the loop, since kernelMandarinSuccess is false. + continue; + fi; + kernelMandarinSuccess := true; Info(InfoRecog,2,"Back from kernel (depth=",depth,")."); if not IsReady(riker) then # IsReady is not set, thus the whole computation aborts. - return ri; + return true; fi; - done := true; - if immediateverification(ri) then - Info(InfoRecog,2,"Doing immediate verification (depth=", + if not immediateverification(ri) then + immediateVerificationSuccess := true; + else + Info(InfoRecog,3,"Doing immediate verification (depth=", depth,")."); - done := ImmediateVerification(ri); + immediateVerificationSuccess := ImmediateVerification(ri); fi; - until done; + until kernelMandarinSuccess and immediateVerificationSuccess; SetNiceGens(ri,Concatenation(pregensfac(ri), NiceGens(riker))); + + Info(InfoRecog, 3, "Check mandarins (depth=", depth,")."); + mandarinSLPs := []; + for i in [1..Length(mandarins)] do + x := mandarins[i]; + # Build an SLP for the mandarin x and check that it evaluates to x. + # We get that SLP by multiplying the results of the corresponding + # kernel and image node SLPs. We also need to adjust for the fact that + # the nice generators of our node are the concatenation of the image + # and kernel node nice generators. + # Note that the case with trivial kernel was handled above. + nrNiceGensOfImageNode := Length(NiceGens(rifac)); + s := NewProductOfStraightLinePrograms(riker!.mandarinSLPs[i], [nrNiceGensOfImageNode + 1 .. Length(NiceGens(ri))], + rifac!.mandarinSLPs[i], [1..nrNiceGensOfImageNode], + Length(NiceGens(ri))); + # Now check that the SLP evaluates to x. But first we need to do a + # hack: something is suuper iffy about the method BlocksModScalars, + # which is called by BlockDiagonal. Groups recognized by + # BlocksModScalars are to be understood neither as a projective nor as + # a matrix group, but rather as a "all block-scalars being trivial" + # group. To be able to check the mandarins we would thus need special + # functions handling isone and isequal, but these do not exist. + # Note that this also poses a problem for immediate verification, which + # we circumvent by hacking SLPforElementGeneric. + if not isequal(ri)(x, ResultOfStraightLineProgram(s, NiceGens(ri))) + and fhmethsel(ri).successMethod <> "BlocksModScalars" then + # TODO: with the master branch rewriting the gens as slps never + # fails. at least we never enter a second iteration of the + # "recognise image" loop. + Info(InfoRecog, 2, + "Enter Mandarin crisis (non-leaf, depth=", depth, ")."); + # TODO remove + return MANDARIN_CRISIS; + fi; + Add(mandarinSLPs, s); + od; + ri!.mandarinSLPs := mandarinSLPs; + if InfoLevel(InfoRecog) = 1 and depth = 0 then Print("\n"); fi; # StopStoringRandEls(ri); SetFilterObj(ri,IsReady); - return ri; + return true; end); InstallGlobalFunction( ValidateHomomInput, @@ -785,7 +1207,7 @@ InstallOtherMethod( Size, "for a recognition node", InstallOtherMethod( Size, "for a failed recognition node", [IsRecogNode], function(ri) - ErrorNoReturn("the recognition described by this recognition node has failed!"); + ErrorNoReturn("Can't compute size since is not ready (see IsReady)"); end); InstallOtherMethod( \in, "for a group element and a recognition node", @@ -807,7 +1229,7 @@ InstallOtherMethod( \in, "for a group element and a recognition node", InstallOtherMethod( \in, "for a group element and a recognition node", [IsObject, IsRecogNode], function( el, ri ) - ErrorNoReturn("the recognition described by this recognition node has failed!"); + ErrorNoReturn("Can't decide membership since is not ready (see IsReady)"); end); InstallGlobalFunction( "DisplayCompositionFactors", function(arg) @@ -1066,42 +1488,3 @@ RECOG.testAllSubgroups := function(g, options...) CallFuncList(RECOG.TestGroup, Concatenation([sub, false, Size(sub)],options)); od; end; - - -RECOG.TestRecognitionNode := function(ri,stop,recurse) - local err, grp, x, slp, y, ef, ek, i; - err := 0; - grp := Grp(ri); - for i in [1..100] do - x := PseudoRandom(grp); - slp := SLPforElement(ri,x); - if slp <> fail then - y := ResultOfStraightLineProgram(slp,NiceGens(ri)); - fi; - if slp = fail or not ri!.isone(x/y) then - if stop then ErrorNoReturn("ErrorNoReturn found, look at x, slp and y"); fi; - err := err + 1; - Print("X\c"); - else - Print(".\c"); - fi; - od; - Print("\n"); - if err > 0 and recurse then - if IsLeaf(ri) then - return rec(err := err, badnode := ri); - fi; - ef := RECOG.TestRecognitionNode(ImageRecogNode(ri),stop,recurse); - if IsRecord(ef) then - return ef; - fi; - if KernelRecogNode(ri) <> fail then - ek := RECOG.TestRecognitionNode(KernelRecogNode(ri),stop,recurse); - if IsRecord(ek) then - return ek; - fi; - fi; - return rec( err := err, badnode := ri, factorkernelok := true ); - fi; - return err; -end; diff --git a/gap/obsolete.gi b/gap/obsolete.gi new file mode 100644 index 000000000..0b9ac5e3f --- /dev/null +++ b/gap/obsolete.gi @@ -0,0 +1,30 @@ +############################################################################# +## +## This file is part of recog, a package for the GAP computer algebra system +## which provides a collection of methods for the constructive recognition +## of groups. +## +## This files's authors include Sergio Siccha. +## +## Copyright of recog belongs to its developers whose names are too numerous +## to list here. Please refer to the COPYRIGHT file for details. +## +## SPDX-License-Identifier: GPL-3.0-or-later +## +## +## Code to stay backwards-compatible. +## +############################################################################# + +RECOG_DidPrintObsoleteWarningForEmptyRecognitionInfoRecord := false; +BindGlobal("EmptyRecognitionInfoRecord", +function(r, H, projective) + if not RECOG_DidPrintObsoleteWarningForEmptyRecognitionInfoRecord then + Info(InfoObsolete, 1, + "'EmptyRecognitionInfoRecord' is obsolete.", + "\n#I It may be removed in a future release of GAP.", + "\n#I Use RecogNode instead."); + RECOG_DidPrintObsoleteWarningForEmptyRecognitionInfoRecord := true; + fi; + return RecogNode(H, projective, r); +end); diff --git a/gap/perm.gi b/gap/perm.gi index ec1dcbd5b..06340b051 100644 --- a/gap/perm.gi +++ b/gap/perm.gi @@ -128,7 +128,7 @@ function(ri, G) InitialDataForKernelRecogNode(ri).blocks := blocks; AddMethod(InitialDataForKernelRecogNode(ri).hints, FindHomMethodsPerm.PcgsForBlocks, 400); AddMethod(InitialDataForKernelRecogNode(ri).hints, FindHomMethodsPerm.BalTreeForBlocks, 200); - findgensNmeth(ri).args[1] := Length(blocks)+3; + findgensNmeth(ri).args[1] := 2 * Length(blocks)+3; findgensNmeth(ri).args[2] := 5; return Success; end); @@ -158,6 +158,11 @@ function(ri, G) return NeverApplicable; end); +# This variable is used in BalTreeForBlocks to determine how many kernel +# generators to compute with findgensNmeth(ri). It is temporarily set to zero +# by the mandarin test suite to provoke lots of crises while recognising an +# imprimitive permutation group. +BindGlobal("RECOG_FactorForBalTreeForBlocks", 1); #! @BeginChunk BalTreeForBlocks #! This method creates a balanced composition tree for the kernel of an #! imprimitive group. This is guaranteed as the method is just called @@ -192,8 +197,8 @@ function(ri, G) Setvalidatehomominput(ri, {ri,p} -> ForAll(o, x -> x^p in seto)); SetHomom(ri,hom); Setimmediateverification(ri,true); - findgensNmeth(ri).args[1] := 3+cut; - findgensNmeth(ri).args[2] := 5; + findgensNmeth(ri).args[1] := 3 + RECOG_FactorForBalTreeForBlocks * cut; + findgensNmeth(ri).args[2] := 1; if nrblocks - cut > 1 then l := Length(upperhalf[1]); n := Length(upperhalf); @@ -430,7 +435,7 @@ function(ri, G) return NeverApplicable; fi; fi; - o := MovedPoints(G); + o := Set(MovedPoints(G)); hom := ActionHomomorphism(G,o); SetHomom(ri,hom); Setvalidatehomominput(ri, {ri,p} -> IsSubset(o, MovedPoints(p))); diff --git a/helper.md b/helper.md new file mode 100644 index 000000000..5d4c9b406 --- /dev/null +++ b/helper.md @@ -0,0 +1,8 @@ +``` +Conformal := ConformalUnitaryGroup(3, 3); +maximalInConformal := [g`subgroup : g in MaximalSubgroups(Conformal)]; +groupsWithOrders := [ g : g in maximalInConformal | #g mod (7) eq 0 ]; + +maximalInOmega := ClassicalMaximals("O+", d, q); +groupsWithOrders := [ g : g in maximalInOmega | #g mod (3*7*13*31) eq 0 ]; +``` diff --git a/profiling.g b/profiling.g new file mode 100644 index 000000000..6068e70bf --- /dev/null +++ b/profiling.g @@ -0,0 +1,38 @@ +testFDPM := function(deg, field, alternating) +local old_info_level, g, gens, mgens, m, cf, dims, max, pos, x, new_gens, + h, result; +old_info_level := InfoLevel(InfoRecog); +if alternating then + g := AlternatingGroup(deg); +else + g := SymmetricGroup(deg); +fi; +repeat + gens := List([1..5],x->PseudoRandom(g)); +until Size(Group(gens)) = Size(g); +mgens := List(gens,x->PermutationMat(x,deg,field)); +m := GModuleByMats(mgens,field); +cf := MTX.CompositionFactors(m); +dims := List(cf,x->x.dimension); +max := Maximum(dims); +pos := Position(dims,max); +x := PseudoRandom(GL(max,field)); +new_gens := List(cf[pos].generators,y->y^x); +h := Group(new_gens); +return RecogniseGroup(h); +return RECOG.TestGroup(h,false,Size(g)); +end;; + +MakeReadWriteGlobal("NUM_MANDARINS_DEFAULT_VALUE"); +NUM_MANDARINS_DEFAULT_VALUE := 0; +ri := testFDPM(88, GF(3), false); +riAlt := testFDPM(88, GF(3), true); +ProfileLineByLine("FDPM-dim-87-gf3-sym-membershiptests-10.gz"); +List([1..10], i -> SLPforElement(ri, PseudoRandom(Grp(ri)))); +UnprofileLineByLine(); +ProfileLineByLine("FDPM-dim-87-gf3-alt-membershiptests-10.gz"); +List([1..10], i -> SLPforElement(riAlt, PseudoRandom(Grp(riAlt)))); +UnprofileLineByLine(); +LoadPackage("profiling"); +OutputAnnotatedCodeCoverageFiles("FDPM-dim-87-gf3-sym-membershiptests-10.gz","FDPM-dim-87-gf3-sym-membershiptests-10"); +OutputAnnotatedCodeCoverageFiles("FDPM-dim-87-gf3-alt-membershiptests-10.gz","FDPM-dim-87-gf3-alt-membershiptests-10"); diff --git a/tst/working/quick/RecogCrisis.tst b/tst/working/quick/RecogCrisis.tst new file mode 100644 index 000000000..3e2437fce --- /dev/null +++ b/tst/working/quick/RecogCrisis.tst @@ -0,0 +1,75 @@ +# TODO KernelGeneratorsAlreadyEnlargedByCrisis +# Build the following tree: +# gap> root; +# >>> +# K:> +gap> G := Group((1,2,3));; +gap> newNode := RecogNode(G);; SetFilterObj(newNode, IsSafeForMandarins); +gap> root := newNode;; +gap> # We mostly add lots of kernels. +gap> r := newNode;; newNode := RecogNode(G);; +gap> SetKernelRecogNode(r, newNode); SetParentRecogNode(newNode, r); +gap> r := newNode;; newNode := RecogNode(G);; +gap> # Here we add an image node and a trivial kernel +gap> SetImageRecogNode(r, newNode); SetParentRecogNode(newNode, r); +gap> SetKernelRecogNode(r, fail); +gap> nodeWithTrivialKernel := r;; +gap> r := newNode;; newNode := RecogNode(G);; +gap> SetKernelRecogNode(r, newNode); SetParentRecogNode(newNode, r); +gap> r := newNode;; newNode := RecogNode(G);; +gap> SetKernelRecogNode(r, newNode); SetParentRecogNode(newNode, r); +gap> r := newNode;; newNode := RecogNode(G);; +gap> SetKernelRecogNode(r, newNode); SetParentRecogNode(newNode, r); +gap> leaf := newNode;; SetFilterObj(leaf, IsLeaf); + +# Level 1 +gap> crisis := RecogCrisis(leaf);; +gap> crisis!.level; +1 +gap> IsIdenticalObj(crisis!.kernelToChop, ParentRecogNode(leaf)); +true + +# Level 1 with trivial kernel +gap> crisis := RecogCrisis(fail, nodeWithTrivialKernel);; +gap> crisis!.level; +1 +gap> crisis!.kernelToChop = fail; +true + +# Level 2 +gap> SetFilterObj(ParentRecogNode(leaf), KernelGeneratorsAlreadyEnlargedByCrisis); +gap> crisis := RecogCrisis(leaf);; +gap> crisis!.level; +2 +gap> IsIdenticalObj(crisis!.kernelToChop, KernelRecogNode(root)); +true + +# Now make the top most kernel safe +gap> SetFilterObj(KernelRecogNode(root), IsSafeForMandarins); +gap> crisis := RecogCrisis(leaf);; +gap> crisis!.level; +2 +gap> IsIdenticalObj(crisis!.kernelToChop, +> KernelRecogNode(ImageRecogNode(KernelRecogNode(root)))); +true + +# Crisis at depth 1 +gap> newNode := RecogNode(G);; SetFilterObj(newNode, IsSafeForMandarins); +gap> root := newNode;; +gap> r := newNode;; newNode := RecogNode(G);; +gap> SetKernelRecogNode(r, newNode); SetParentRecogNode(newNode, r); +gap> leaf := newNode;; SetFilterObj(leaf, IsLeaf); +gap> crisis := RecogCrisis(leaf);; +gap> crisis!.level; +1 +gap> IsIdenticalObj(crisis!.kernelToChop, leaf); +true