GroupSearch := function(generators, powers, target, targetOrder, maxLen, maxRep, goalLabel, checkOrder, minLen, prefixIndices, prefixFacesUsed, prefixPerm, prefixSlicePerm, prefixLastFace, knownBest, dbFileArg, genKeyArg, moveNamesDBArg) # rms-main7i: fixes early pruning bug from 7h # Old: pruned cores where coreLen*2 >= bestMoveCount (assumes N>=2) # Bug: when maxRep=1, minimum total moves is coreLen*1, not coreLen*2 # causing premature termination before searching N=1 solutions # Fix: prune cores where coreLen >= bestMoveCount (correct for any N>=1) # rms-main7j: writes improved solutions to database immediately on discovery # so that ctrl-c does not lose results from long runs. # rms-main7k: shows coreLen * n = totalMoves in IMPROVED SOLUTION FOUND output. local moves, moveNames, totalGenerators, moveFaces, WordToString, InvertMove, ReverseWordToString, GetFace, UsesAllFaces, EnumerateWords, found, bestMoveCount, bestSolution, i, j, gname, startTime, runtime, identity, targetInverse, checkInverse, isSliceMove, hasSliceMoves, centresFixed, centreFacelets, lengthStartTime, lengthEndTime, lengthElapsed, statesExplored, previousStatesExplored, orderChecks, orderPassed, slicePruneCount, prefixLen, effectiveMinLen, effectiveMaxLen; # --- Initialization --- if IsBound(goalLabel) then Print("\033[1;36mSearching for ", goalLabel, "\033[0m\n\n"); fi; if not IsBound(checkOrder) then checkOrder := true; fi; # --- Inverse checking setup --- targetInverse := target^-1; checkInverse := (target <> targetInverse); if checkInverse then Print("Goal order > 2: inverse checking ENABLED\n"); else Print("Goal order = 2: inverse checking DISABLED (goal = goal^-1)\n"); fi; # --- Centre facelets --- centreFacelets := [1, 2, 3, 4, 5, 6]; centresFixed := ForAll(centreFacelets, f -> f^target = f); if centresFixed then Print("Centres fixed in goal: slice pruning ENABLED (if slice moves present)\n"); else Print("Centres move in goal: slice pruning DISABLED\n"); fi; prefixLen := Length(prefixIndices); Print("Using maxRep = ", maxRep, ", maxLen = ", maxLen, " (total core), minLen = ", minLen, " (total core)"); if prefixLen > 0 then Print(", prefixLen = ", prefixLen); fi; if checkOrder then Print(" (order checking ENABLED)\n\n"); else Print(" (order checking DISABLED)\n\n"); fi; startTime := Runtime(); statesExplored := 0; previousStatesExplored := 0; orderChecks := 0; orderPassed := 0; slicePruneCount := 0; # --- Initialize bestMoveCount from database --- if knownBest <> fail then bestMoveCount := knownBest.totalMoves; bestSolution := []; Print("Database: initialized bestMoveCount = ", bestMoveCount, " from known solution\n\n"); else bestMoveCount := infinity; bestSolution := []; fi; # Build moves list moves := []; moveNames := []; moveFaces := []; totalGenerators := Length(generators); for i in [1..totalGenerators] do gname := generators[i]; for j in [1..powers[i]] do if j = 1 then Add(moves, EvalString(gname)); Add(moveNames, gname); else Add(moves, EvalString(Concatenation(gname, "^", String(j)))); Add(moveNames, Concatenation(gname, String(j))); fi; Add(moveFaces, i); od; od; identity := (); # --- Precompute slice moves --- isSliceMove := List(moveNames, name -> name[1] = '2'); hasSliceMoves := ForAny(isSliceMove, x -> x = true); if centresFixed and hasSliceMoves then Print("Slice moves detected: centre-return pruning active\n\n"); elif centresFixed then Print("No slice moves detected: centre pruning not applicable\n\n"); else Print("\n"); fi; # Both minLen and maxLen refer to TOTAL core length (prefix + free). # Convert to free move counts for the search loop. effectiveMinLen := Maximum(minLen - prefixLen, 1); effectiveMaxLen := maxLen - prefixLen; # --- Helper functions --- WordToString := function(word) local s, t; s := ""; for t in [1..Length(word)] do if t > 1 then s := Concatenation(s, " "); fi; s := Concatenation(s, moveNames[word[t]]); od; return s; end; InvertMove := function(moveName) local n; n := Length(moveName); if n > 0 and moveName[n] = '3' then return moveName{[1..n-1]}; elif n > 0 and moveName[n] = '2' then return moveName; else return Concatenation(moveName, "3"); fi; end; ReverseWordToString := function(word) local s, t, invName; s := ""; for t in [Length(word), Length(word)-1..1] do if t < Length(word) then s := Concatenation(s, " "); fi; invName := InvertMove(moveNames[word[t]]); s := Concatenation(s, invName); od; return s; end; GetFace := function(moveIdx) return moveFaces[moveIdx]; end; UsesAllFaces := function(facesUsed) return ForAll(facesUsed, x -> x = true); end; found := []; # --- Recursive enumerator --- # wordIndices is pre-loaded with prefix indices. # permSoFar starts as prefixPerm (prefix permutation). # slicePermSoFar starts as prefixSlicePerm. # facesUsed starts with prefix faces already marked. # lastFace starts as 0 (no restriction on first free move). # lengthRemaining = number of FREE moves still to place. # Full core = prefix + free moves; check permSoFar^n = target. EnumerateWords := function(wordIndices, permSoFar, slicePermSoFar, facesUsed, lastFace, lengthRemaining) local idx, newPerm, newSlicePerm, newFacesUsed, newLastFace, faceCount, possibleNewFaces, totalMoves, faceIdx, n, power; # Quick pruning: not enough free moves to cover remaining faces faceCount := Length(Filtered(facesUsed, x -> x = true)); possibleNewFaces := faceCount + lengthRemaining; if possibleNewFaces < totalGenerators then return; fi; # Early pruning by bestMoveCount: core*1 is minimum total (N>=1) if Length(wordIndices) >= bestMoveCount then return; fi; if lengthRemaining = 0 then if UsesAllFaces(facesUsed) then if checkOrder then orderChecks := orderChecks + 1; # Optimisation (7d): use permSoFar^targetOrder = identity # instead of full Order() which must find the complete order. if permSoFar^targetOrder <> identity then return; fi; orderPassed := orderPassed + 1; fi; # Optimisation (7d): incremental power multiplication. power := identity; for n in [1..maxRep] do power := power * permSoFar; # Centre-return pruning (7h fix): # Check actual centre positions in power^n, not S^n approximation. # S^n was incorrect because face moves between slice moves affect # how slice moves compose under repetition (non-abelian group). if centresFixed and hasSliceMoves then if ForAny(centreFacelets, f -> f^power <> f) then slicePruneCount := slicePruneCount + 1; continue; fi; fi; # Total moves = full core length * n totalMoves := Length(wordIndices) * n; if totalMoves >= bestMoveCount then break; fi; # Check goal: (prefix + free)^n = target if power = target then Add(found, [ShallowCopy(wordIndices), n, totalMoves, false]); bestMoveCount := totalMoves; bestSolution := [ShallowCopy(wordIndices), n, totalMoves, false]; runtime := (Runtime() - startTime)/1000; Print("\n*** IMPROVED SOLUTION FOUND ***\n"); Print("(", WordToString(wordIndices), ")", n, " = ", Length(wordIndices), " * ", n, " = ", totalMoves, " total moves (", Float(runtime), " s)\n"); if prefixLen > 0 then Print(" [first ", prefixLen, " moves are the fixed prefix]\n"); fi; Print(" [States explored so far: ", statesExplored, "]\n"); if checkOrder then Print(" [Order checks: ", orderChecks, ", passed: ", orderPassed, " (", Int(100.0 * orderPassed / orderChecks), "%)]\n"); fi; Print("*******************************\n"); # 7j: write immediately so ctrl-c does not lose result AppendRecord(dbFileArg, rec( pattern := goalLabel, generators := genKeyArg, result := true, coreLen := Length(wordIndices), n := n, totalMoves := totalMoves, solution := Concatenation("(", WordToString(wordIndices), ")", String(n)), maxLen := maxLen, maxRep := maxRep, date := "live", program := "rms-main7k.txt", seconds := runtime, states := statesExplored )); if n = 1 then Print("Exiting...\n"); QUIT_GAP(1); fi; if maxRep > n-1 then maxRep := n-1; Print("Reducing maxRep to ", maxRep, " to prune further repetitions\n"); fi; elif checkInverse and power = targetInverse then Add(found, [ShallowCopy(wordIndices), n, totalMoves, true]); bestMoveCount := totalMoves; bestSolution := [ShallowCopy(wordIndices), n, totalMoves, true]; runtime := (Runtime() - startTime)/1000; Print("\n*** IMPROVED SOLUTION FOUND (via inverse) ***\n"); Print("(", WordToString(wordIndices), ")", n, " = ", Length(wordIndices), " * ", n, " = ", totalMoves, " total moves (", Float(runtime), " s)\n"); Print(" Inverse-derived direct solution:\n"); Print(" (", ReverseWordToString(wordIndices), ")", n, "\n"); if prefixLen > 0 then Print(" [first ", prefixLen, " moves are the fixed prefix]\n"); fi; Print(" [States explored so far: ", statesExplored, "]\n"); if checkOrder then Print(" [Order checks: ", orderChecks, ", passed: ", orderPassed, " (", Int(100.0 * orderPassed / orderChecks), "%)]\n"); fi; Print("*******************************\n"); # 7j: write immediately so ctrl-c does not lose result AppendRecord(dbFileArg, rec( pattern := goalLabel, generators := genKeyArg, result := true, coreLen := Length(wordIndices), n := n, totalMoves := totalMoves, solution := Concatenation("(inv)(", ReverseWordToString(wordIndices), ")", String(n)), maxLen := maxLen, maxRep := maxRep, date := "live", program := "rms-main7k.txt", seconds := runtime, states := statesExplored )); if n = 1 then Print("Exiting...\n"); QUIT_GAP(1); fi; if maxRep > n-1 then maxRep := n-1; Print("Reducing maxRep to ", maxRep, " to prune further repetitions\n"); fi; fi; od; fi; return; fi; # Recursive expansion for idx in [1..Length(moves)] do faceIdx := GetFace(idx); if faceIdx <> lastFace then newPerm := permSoFar * moves[idx]; statesExplored := statesExplored + 1; newFacesUsed := ShallowCopy(facesUsed); newFacesUsed[faceIdx] := true; newLastFace := faceIdx; if isSliceMove[idx] then newSlicePerm := slicePermSoFar * moves[idx]; else newSlicePerm := slicePermSoFar; fi; Add(wordIndices, idx); EnumerateWords(wordIndices, newPerm, newSlicePerm, newFacesUsed, newLastFace, lengthRemaining-1); Remove(wordIndices, Length(wordIndices)); fi; od; end; # --- Main search loop --- Print("Starting search in <", JoinStringsWithSeparator(generators, ", "), ">...\n\n"); for i in [effectiveMinLen..effectiveMaxLen] do if (prefixLen + i) >= bestMoveCount then Print("Core length ", prefixLen + i, " cannot improve on best solution (", bestMoveCount, " moves), stopping.\n"); break; fi; lengthStartTime := Runtime(); if prefixLen > 0 then Print("Searching core length ", prefixLen + i, " (prefix=", prefixLen, " + free=", i, ")...\n"); else Print("Searching length ", i, " words...\n"); fi; EnumerateWords(ShallowCopy(prefixIndices), prefixPerm, prefixSlicePerm, ShallowCopy(prefixFacesUsed), prefixLastFace, i); lengthEndTime := Runtime(); lengthElapsed := (lengthEndTime - lengthStartTime) / 1000.0; Print(" Completed in ", Float(lengthElapsed), " s\n"); Print(" States explored this length: ", statesExplored - previousStatesExplored, "\n"); if centresFixed and hasSliceMoves then Print(" Slice pruned this length: ", slicePruneCount, "\n"); fi; if checkOrder then Print(" Order checks this length: ", orderChecks - orderPassed, " failed, ", orderPassed, " passed\n"); fi; Print("\n"); previousStatesExplored := statesExplored; od; # --- Results summary --- Print("\n=======================================\n"); if Length(found) = 0 then Print("No solutions found up to core length ", maxLen, " and ", maxRep, " repetitions.\n"); else Print("BEST SOLUTION:\n"); if bestSolution[4] then Print("(via inverse) (", ReverseWordToString(bestSolution[1]), ")", bestSolution[2], " = ", bestSolution[3], " total moves\n\n"); else Print("(", WordToString(bestSolution[1]), ")", bestSolution[2], " = ", bestSolution[3], " total moves\n\n"); fi; if prefixLen > 0 then Print("[Note: first ", prefixLen, " moves of core are the fixed prefix]\n\n"); fi; Print("All solutions found (", Length(found), " total):\n"); for i in found do if i[4] then Print("(inv) (", ReverseWordToString(i[1]), ")", i[2], " = ", i[3], " moves\n"); else Print("(", WordToString(i[1]), ")", i[2], " = ", i[3], " moves\n"); fi; od; fi; Print("=======================================\n"); Print("\nTotal states explored: ", statesExplored, "\n"); if centresFixed and hasSliceMoves then Print("Total slice pruned: ", slicePruneCount, "\n"); fi; if checkOrder then Print("Order checks performed: ", orderChecks, "\n"); Print("Order checks passed: ", orderPassed); if orderChecks > 0 then Print(" (", Int(100.0 * orderPassed / orderChecks), "%)"); fi; Print("\n"); fi; # Return results for database writing return rec(found := found, bestSolution := bestSolution, statesExplored := statesExplored, runtime := (Runtime() - startTime)/1000); end; # ───────────────────────────────────────────────────────────────────────────── # Database functions # ───────────────────────────────────────────────────────────────────────────── dbFile := "rms-best-known-solutions.txt"; # Normalize a generator string for use as a database key. # Sorts tokens alphabetically so that "U,D,F,B,L,R" and "U,D,L,R,F,B" # both map to the same key "B,D,F,L,R,U". Generator ordering does not # affect which group is generated, so this is safe. NormalizeGenerators := function(genStr) local tokens; tokens := SplitString(genStr, ","); tokens := List(tokens, NormalizedWhitespace); tokens := Filtered(tokens, t -> Length(t) > 0); Sort(tokens); return JoinStringsWithSeparator(tokens, ","); end; # Find the best known result for a given pattern+generators combination. # Returns the rec with the smallest totalMoves where result=true, or fail. # genKey must be the normalized generator string. FindBestKnown := function(dbList, pattern, genKey) local r, best; best := fail; for r in dbList do if r.pattern = pattern and r.generators = genKey then if r.result = true then if best = fail or r.totalMoves < best.totalMoves then best := r; fi; fi; fi; od; return best; end; # Print all known results for a given pattern+generators combination. # genString is displayed to the user; genKey is the normalized database key. PrintKnownResults := function(dbList, pattern, genString, genKey) local r, anyFound, dateStr, progStr, maxLenStr, maxRepStr, statesStr; anyFound := false; Print("════════════════════════════════════════════════════════════\n"); Print(" Known results for: ", pattern, " generators: ", genString, "\n"); if genKey <> genString then Print(" (canonical key: ", genKey, ")\n"); fi; Print("════════════════════════════════════════════════════════════\n"); for r in dbList do if r.pattern = pattern and r.generators = genKey then anyFound := true; if r.date = fail then dateStr := "unknown"; else dateStr := r.date; fi; if r.program = fail then progStr := "unknown"; else progStr := r.program; fi; if r.maxLen = fail then maxLenStr := "?"; else maxLenStr := String(r.maxLen); fi; if r.maxRep = fail then maxRepStr := "?"; else maxRepStr := String(r.maxRep); fi; if r.states = fail then statesStr := "?"; else statesStr := String(r.states); fi; if r.result = true then Print(" FOUND ", r.totalMoves, " moves ", r.solution, "\n"); Print(" coreLen=", r.coreLen, " n=", r.n, " maxLen=", maxLenStr, " maxRep=", maxRepStr, " [", dateStr, " ", progStr, "]\n"); else Print(" NONE maxLen=", maxLenStr, " maxRep=", maxRepStr, " states=", statesStr, " [", dateStr, " ", progStr, "]\n"); fi; fi; od; if not anyFound then Print(" (no prior results on record)\n"); fi; Print("════════════════════════════════════════════════════════════\n\n"); end; # Append a single record to the database file. # Uses genKey (normalized) for the generators field. AppendRecord := function(filename, r) local f; f := OutputTextFile(filename, true); # true = append SetPrintFormattingStatus(f, false); PrintTo(f, "Add(rmsDB, rec(\n"); PrintTo(f, " pattern := \"", r.pattern, "\",\n"); PrintTo(f, " generators := \"", r.generators, "\",\n"); PrintTo(f, " result := ", r.result, ",\n"); if r.result = true then PrintTo(f, " coreLen := ", r.coreLen, ",\n"); PrintTo(f, " n := ", r.n, ",\n"); PrintTo(f, " totalMoves := ", r.totalMoves, ",\n"); PrintTo(f, " solution := \"", r.solution, "\",\n"); else PrintTo(f, " coreLen := fail,\n"); PrintTo(f, " n := fail,\n"); PrintTo(f, " totalMoves := fail,\n"); PrintTo(f, " solution := fail,\n"); fi; PrintTo(f, " maxLen := ", r.maxLen, ",\n"); PrintTo(f, " maxRep := ", r.maxRep, ",\n"); PrintTo(f, " date := \"", r.date, "\",\n"); PrintTo(f, " program := \"", r.program, "\",\n"); PrintTo(f, " seconds := ", r.seconds, ",\n"); PrintTo(f, " states := ", r.states, "\n"); PrintTo(f, "));\n\n"); CloseStream(f); end; # ───────────────────────────────────────────────────────────────────────────── # Command line argument parsing # Usage: # gap 3x3x3-exp.txt patterns1c.txt rms-main7k.txt [maxLen] [maxRep] [minLen] [prefix] # # Arguments: # pattern - name of pattern defined in patterns1c.txt # generators - comma separated generator list (e.g. "U,D,L,R,B") # maxLen - max total core length (default 14) # maxRep - max repetitions (default 20) # minLen - min total core length (default = num generators) # prefix - space separated move names forming fixed start of core # # New in rms-main7e (vs rms-main7d): # - Loads rms-best-known-solutions.txt at startup # - Prints all known results for (pattern, generators) before searching # - Initializes bestMoveCount from best known result to prune search # - Appends result record to database on completion (found or not found) # - Generator string normalized alphabetically for database key matching, # so "U,D,F,B,L,R" and "U,D,L,R,F,B" are treated as the same search. # User-supplied ordering is preserved in all output display. # ───────────────────────────────────────────────────────────────────────────── Print("\n════════════════════════════════════════════════════════════\n"); Print(" RMS MAIN7k - Repeating Move Solver with Solution Database\n"); Print("════════════════════════════════════════════════════════════\n\n"); args := GAPInfo.SystemCommandLine; if Length(args) < 8 then Print("Usage: gap 3x3x3-exp.txt patterns1c.txt rms-main7k.txt [maxLen] [maxRep] [minLen] [prefix]\n\n"); Print("Examples:\n"); Print(" gap 3x3x3-exp.txt patterns1c.txt rms-main7e.txt snake1 \"U,D,L,R,B\" 14 20\n"); Print(" gap 3x3x3-exp.txt patterns1c.txt rms-main7e.txt snake1 \"U,D,L,R,B\" 10 20 5 \"U B2 L\"\n"); QUIT_GAP(1); fi; patternName := args[7]; genString := args[8]; maxLen := 14; maxRep := 20; if Length(args) >= 9 then maxLen := Int(args[9]); fi; if Length(args) >= 10 then maxRep := Int(args[10]); fi; # Load pattern goal := EvalString(patternName); if goal = fail or goal = 0 then Print("ERROR: Pattern '", patternName, "' not found.\n"); QUIT_GAP(1); fi; Print("Pattern: ", patternName, "\n"); Print("Generators: ", genString, "\n"); Print("maxLen: ", maxLen, " (maximum total core length)\n"); Print("maxRep: ", maxRep, "\n\n"); # Parse generators ParseGenerators := function(genStr) local tokens, gens, pows, token; tokens := SplitString(genStr, ","); gens := []; pows := []; for token in tokens do token := NormalizedWhitespace(token); if Length(token) = 0 then continue; fi; Add(gens, token); if token[Length(token)] = '2' then Add(pows, 1); else Add(pows, 3); fi; od; return [gens, pows]; end; parsed := ParseGenerators(genString); generators := parsed[1]; powers := parsed[2]; Print("Parsed generators: ", JoinStringsWithSeparator(generators, ", "), "\n"); Print("Powers: ", powers, "\n\n"); # --- Normalize generator string for database key --- # Alphabetical sort ensures "U,D,F,B,L,R" and "U,D,L,R,F,B" match the same records. genKey := NormalizeGenerators(genString); if genKey <> genString then Print("Canonical key: ", genKey, "\n\n"); fi; # --- Membership check --- Print("Checking if goal is in Group(generators)...\n"); genGroup := Group(List(generators, g -> EvalString(g))); if not goal in genGroup then Print("ERROR: Goal '", patternName, "' is NOT reachable with these generators. Exiting.\n"); QUIT_GAP(1); fi; Print("Goal confirmed in generator group. Proceeding.\n\n"); # --- minLen --- minLen := Length(generators); if Length(args) >= 11 then minLen := Int(args[11]); fi; Print("minLen: ", minLen, " (minimum total core length)\n\n"); # --- Build full move name list for prefix index lookup --- allMoveNames := []; allMoveFaces := []; pgi := 0; pgj := 0; for pgi in [1..Length(generators)] do for pgj in [1..powers[pgi]] do if pgj = 1 then Add(allMoveNames, generators[pgi]); else Add(allMoveNames, Concatenation(generators[pgi], String(pgj))); fi; Add(allMoveFaces, pgi); od; od; # --- Prefix --- prefixIndices := []; prefixFacesUsed := List([1..Length(generators)], x -> false); prefixPerm := (); prefixSlicePerm := (); prefixStr := ""; if Length(args) >= 12 then prefixStr := args[12]; fi; prefixTokens := []; prefixTok := ""; prefixMoveIdx := 0; prefixFound := false; prefixMv := (); if Length(prefixStr) > 0 then prefixTokens := SplitString(prefixStr, " "); for prefixTok in prefixTokens do prefixTok := NormalizedWhitespace(prefixTok); if Length(prefixTok) = 0 then continue; fi; prefixFound := false; for prefixMoveIdx in [1..Length(allMoveNames)] do if allMoveNames[prefixMoveIdx] = prefixTok then Add(prefixIndices, prefixMoveIdx); prefixFacesUsed[allMoveFaces[prefixMoveIdx]] := true; prefixMv := EvalString(prefixTok); prefixPerm := prefixPerm * prefixMv; if prefixTok[1] = '2' then prefixSlicePerm := prefixSlicePerm * prefixMv; fi; prefixFound := true; break; fi; od; if not prefixFound then Print("ERROR: Prefix move '", prefixTok, "' not in generator move list.\n"); Print("Valid moves: ", JoinStringsWithSeparator(allMoveNames, ", "), "\n"); QUIT_GAP(1); fi; od; Print("Prefix: \"", prefixStr, "\" (", Length(prefixIndices), " moves)\n"); Print("Prefix faces used: ", prefixFacesUsed, "\n\n"); else Print("Prefix: (none)\n\n"); fi; # --- prefixLastFace --- prefixLastFace := 0; if Length(prefixIndices) > 0 then prefixLastFace := allMoveFaces[prefixIndices[Length(prefixIndices)]]; Print("Prefix last face: ", generators[prefixLastFace], " (position ", Length(prefixIndices), " in prefix)", " - first free move will not repeat this face\n\n"); fi; # ───────────────────────────────────────────────────────────────────────────── # Load database and print known results # ───────────────────────────────────────────────────────────────────────────── rmsDB := []; # global list populated by Read(dbFile) if IsExistingFile(dbFile) then Print("Loading database: ", dbFile, "\n"); Read(dbFile); Print("Database loaded: ", Length(rmsDB), " record(s) total.\n\n"); else Print("Database file not found (", dbFile, ") - starting fresh.\n\n"); fi; PrintKnownResults(rmsDB, patternName, genString, genKey); knownBest := FindBestKnown(rmsDB, patternName, genKey); if knownBest <> fail then Print("Best known solution: ", knownBest.totalMoves, " moves\n"); Print(" ", knownBest.solution, "\n\n"); fi; # ───────────────────────────────────────────────────────────────────────────── # Build moveNamesForDB before search (needed by GroupSearch for live DB writes) # ───────────────────────────────────────────────────────────────────────────── moveNamesForDB := []; for pgi in [1..Length(generators)] do for pgj in [1..powers[pgi]] do if pgj = 1 then Add(moveNamesForDB, generators[pgi]); else Add(moveNamesForDB, Concatenation(generators[pgi], String(pgj))); fi; od; od; # ───────────────────────────────────────────────────────────────────────────── # Run the search # ───────────────────────────────────────────────────────────────────────────── searchResult := GroupSearch(generators, powers, goal, 8, maxLen, maxRep, patternName, false, minLen, prefixIndices, prefixFacesUsed, prefixPerm, prefixSlicePerm, prefixLastFace, knownBest, dbFile, genKey, moveNamesForDB); # ───────────────────────────────────────────────────────────────────────────── # Write result to database # ───────────────────────────────────────────────────────────────────────────── # Helper: build solution string from wordIndices and n WordToStringGlobal := function(wordIndices, moveNamesLocal, n, isInverse) local s, t, InvMove; InvMove := function(moveName) local ln; ln := Length(moveName); if ln > 0 and moveName[ln] = '3' then return moveName{[1..ln-1]}; elif ln > 0 and moveName[ln] = '2' then return moveName; else return Concatenation(moveName, "3"); fi; end; s := "("; if isInverse then for t in [Length(wordIndices), Length(wordIndices)-1..1] do if t < Length(wordIndices) then s := Concatenation(s, " "); fi; s := Concatenation(s, InvMove(moveNamesLocal[wordIndices[t]])); od; else for t in [1..Length(wordIndices)] do if t > 1 then s := Concatenation(s, " "); fi; s := Concatenation(s, moveNamesLocal[wordIndices[t]]); od; fi; s := Concatenation(s, ")", String(n)); return s; end; Exec("date +%Y-%m-%d > /tmp/gap_date.txt"); dateStream := InputTextFile("/tmp/gap_date.txt"); today := NormalizedWhitespace(ReadLine(dateStream)); CloseStream(dateStream); if Length(searchResult.found) > 0 then bs := searchResult.bestSolution; solStr := WordToStringGlobal(bs[1], moveNamesForDB, bs[2], bs[4]); dbRec := rec( pattern := patternName, generators := genKey, result := true, coreLen := Length(bs[1]), n := bs[2], totalMoves := bs[3], solution := solStr, maxLen := maxLen, maxRep := maxRep, date := today, program := "rms-main7k.txt", seconds := searchResult.runtime, states := searchResult.statesExplored ); Print("\nAppending FOUND result to database: ", dbFile, "\n"); else dbRec := rec( pattern := patternName, generators := genKey, result := fail, coreLen := fail, n := fail, totalMoves := fail, solution := fail, maxLen := maxLen, maxRep := maxRep, date := today, program := "rms-main7k.txt", seconds := searchResult.runtime, states := searchResult.statesExplored ); Print("\nAppending NONE result to database: ", dbFile, "\n"); fi; AppendRecord(dbFile, dbRec); Print("Done.\n"); QUIT_GAP(1); # ───────────────────────────────────────────────────────────────────────────── # Archived examples (for reference, not executed) # ───────────────────────────────────────────────────────────────────────────── #GroupSearch(["U","D","L","R","B"], [3,3,3,3,3], goal, 8, 14, 20, "snake1", false, 5, [], List([1..5],x->false), (), (), 0, fail);