/// <summary> /// Create a pruning table using corner permutation and equator order. /// Every table entry contains the exact number of moves required to /// solve the position represented by said coordinates. /// </summary> /// <returns> /// Create a pruning table using corner permutation equator order. /// </returns> public static byte[] CreatePhase2CornerEquatorTable() { byte invalid = 255; byte[] pruningTable = Enumerable .Repeat(invalid, PruningTableConstants.CornerEquatorPruningTableSizePhase2) .ToArray(); TableController.InitializeCornerPermutationMoveTable(); TableController.InitializeEquatorOrderMoveTable(); pruningTable[0] = 0; //solved int done = 1; int depth = 0; string outputFormat = "depth: {0}, done: {1}/" + PruningTableConstants.CornerEquatorPruningTableSizePhase2 + " ({2:P})"; Console.WriteLine(string.Format(outputFormat, depth, done, done / (double)PruningTableConstants.CornerEquatorPruningTableSizePhase2)); while (done < PruningTableConstants.CornerEquatorPruningTableSizePhase2) { for (int cornerPermutation = 0; cornerPermutation < NumCornerPermutations; cornerPermutation++) { for (int equatorPermutation = 0; equatorPermutation < NumEquatorOrders; equatorPermutation++) { int index = NumEquatorOrders * cornerPermutation + equatorPermutation; if (pruningTable[index] == depth) { foreach (int move in TwoPhaseConstants.Phase2Moves) { int newCornerPermutation = TableController.CornerPermutationMoveTable[cornerPermutation, move]; int newEquatorOrder = TableController.EquatorOrderMoveTable[equatorPermutation, move]; int newIndex = NumEquatorOrders * newCornerPermutation + newEquatorOrder; if (pruningTable[newIndex] == invalid) { pruningTable[newIndex] = (byte)(depth + 1); done++; } } } } } depth++; Console.WriteLine(string.Format(outputFormat, depth, done, done / (double)PruningTableConstants.CornerEquatorPruningTableSizePhase2)); } return(pruningTable); }
/// <summary> /// Create a weighted pruning table using corner permutation and /// equator edge order. Every table entry contains the minimum cost /// required to solve the position represented by said coordinates. /// </summary> /// <param name="weights">The weights to use.</param> /// <returns> /// A weighted pruning table using the corner permutation and equator /// edge order. /// </returns> /// <exception cref="InvalidWeightsException"> /// Thrown if <paramref name="weights"/> is invalid. /// </exception> public static float[] CreateWeightedPhase2CornerEquatorTable(float[] weights) { MoveWeightsUtils.ValidateWeights(weights); float invalid = float.NaN; float[] pruningTable = Enumerable .Repeat(invalid, PruningTableConstants.CornerEquatorPruningTableSizePhase2) .ToArray(); TableController.InitializeCornerPermutationMoveTable(); TableController.InitializeEquatorOrderMoveTable(); pruningTable[0] = 0f; int numChanged = -1; while (numChanged != 0) { numChanged = 0; for (int cornerPermutation = 0; cornerPermutation < NumCornerPermutations; cornerPermutation++) { for (int equatorOrder = 0; equatorOrder < NumEquatorOrders; equatorOrder++) { int index = NumEquatorOrders * cornerPermutation + equatorOrder; if (pruningTable[index] != invalid) { foreach (int move in TwoPhaseConstants.Phase2Moves) { int newCornerPermutation = TableController.CornerPermutationMoveTable[cornerPermutation, move]; int newEquatorOrder = TableController.EquatorOrderMoveTable[equatorOrder, move]; int newIndex = NumEquatorOrders * newCornerPermutation + newEquatorOrder; float newPruningValue = pruningTable[index] + weights[move]; if (pruningTable[newIndex] == invalid || pruningTable[newIndex] > newPruningValue) { pruningTable[newIndex] = newPruningValue; numChanged++; } } } } } } return(pruningTable); }
//TEST with impossible length /// <summary> /// Find a near-optimal solution for a cube in respect to the number of /// moves. If no matching solution is found, the return value is null. /// </summary> /// <param name="cubeToSolve">The cube to solve.</param> /// <param name="timeout"> /// Interrupt the search after that much time has passed. /// </param> /// <param name="returnLength"> /// The search stops as soon as a solution of the specified length is /// found. Be careful with setting <paramref name="returnLength"/> to a /// value smaller than 20, beacuse there might not be a solution /// shorter than or matching that length. In which case the algorithm /// will run for a long time. /// </param> /// <param name="requiredLength"> /// Ignore the timeout until a solution of a length less than or equal to /// <paramref name="requiredLength"/> is found. If the value of /// <paramref name="requiredLength"/> is negative, this parameter is /// ignored. If <paramref name="requiredLength"/> is positive, it must /// be larger than or equal to <paramref name="returnLength"/>. Setting /// the value to 30 or higher will cause the first solution found after /// timeout to be returned. /// </param> /// <param name="searchDifferentOrientations"> /// Start three threads each solving a 120° offset of the cube. /// </param> /// <param name="searchInverse"> /// For every orientation solved, start another thread that solves its /// inverse. /// </param> /// <returns> /// A near-optimal solution for the specified cube in respect to ist /// length. Null, if not matching solution is found. /// </returns> /// <exception cref="ArgumentNullException"> /// Thrown if <paramref name="cubeToSolve"/> is null. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown if <paramref name="returnLength"/> is negative or if /// <paramref name="requiredLength"/> is positive but smaller than /// <paramref name="returnLength"/>. /// </exception> public static Alg FindSolution(CubieCube cubeToSolve, TimeSpan timeout, int returnLength, int requiredLength, bool searchDifferentOrientations = true, bool searchInverse = true) { #region parameter checks if (cubeToSolve is null) { throw new ArgumentNullException(nameof(cubeToSolve) + " is null."); } if (timeout < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(timeout) + " cannot be negative: " + timeout); } if (returnLength < 0) { throw new ArgumentOutOfRangeException(nameof(returnLength) + " cannot be negative: " + returnLength); } if ((uint)requiredLength < returnLength) { throw new ArgumentOutOfRangeException(nameof(requiredLength) + " must either be negative or >= " + nameof(returnLength) + ": " + requiredLength + " (" + nameof(requiredLength) + ") < " + returnLength + " (" + nameof(returnLength) + ")"); } #endregion paramter checks //initialize move tables TableController.InitializeCornerOrientationMoveTable(); TableController.InitializeCornerPermutationMoveTable(); TableController.InitializeDEdgePermutationMoveTable(); TableController.InitializeEdgeOrientationMoveTable(); TableController.InitializeEquatorPermutationMoveTable(); TableController.InitializeUdEdgeOrderMoveTable(); TableController.InitializeUEdgePermutationMoveTable(); //initialize pruning tables TableController.InitializePhase1PruningTable(); TableController.InitializePhase2CornerEquatorPruningTable(); TableController.InitializePhase2CornerUdPruningTable(); Stopwatch timePassed = new Stopwatch(); timePassed.Start(); SyncedBoolWrapper isTerminated = new SyncedBoolWrapper() { Value = false }; SyncedIntWrapper shortestSolutionLength = new SyncedIntWrapper() { Value = 30 }; SyncedIntWrapper shortestSolutionIndex = new SyncedIntWrapper() { Value = -1 }; List <Alg> solutions = new List <Alg>(); int numThreads = (searchDifferentOrientations ? 3 : 1) * (searchInverse ? 2 : 1); int increment = searchDifferentOrientations ? 1 : 2; Thread[] solvers = new Thread[numThreads]; for (int orientation = 0; orientation < numThreads; orientation += increment) { TwoPhaseSolver solver = new TwoPhaseSolver() { _notRotatedCube = cubeToSolve.Clone(), //create a copy to avoid issues caused by multithreading _timePassed = timePassed, _timeout = timeout, _returnLength = returnLength, _requiredLength = requiredLength, IsTerminated = isTerminated, _solutions = solutions, _shortestSolutionLength = shortestSolutionLength, _shortestSolutionIndex = shortestSolutionIndex, _rotation = orientation % 3, _inversed = orientation / 3 == 1 }; Thread solverThread = new Thread(new ThreadStart(solver.StartSearch)); solvers[orientation] = solverThread; solverThread.Start(); } foreach (Thread solverThread in solvers) { solverThread.Join(); } timePassed.Stop(); //not required, only for the sake of completeness if (solutions.Count > 0) { return(solutions[shortestSolutionIndex.Value]); } else { return(null); } }
/// <summary> /// Create a pruning table using corner permutation and U- and D-edge /// order. Every table entry contains the exact number of moves /// required to solve the position represented by said coordinates. /// </summary> /// <returns> /// Create a pruning table using corner permutation and U- and D-edge /// order. /// </returns> public static byte[] CreatePhase2CornerUdTable() { byte invalid = 0xFF; byte[] pruningTable = Enumerable .Repeat(invalid, PruningTableConstants.CornerUdPruningTableSizePhase2) .ToArray(); TableController.InitializeCornerPermutationMoveTable(); TableController.InitializeUdEdgeOrderMoveTable(); pruningTable[0] = 0; //solved int done = 1; int depth = 0; string outputFormat = "depth: {0}, done: {1}/" + PruningTableConstants.CornerUdPruningTableSizePhase2 + " ({2:P})"; Console.WriteLine(string.Format(outputFormat, depth, done, done / (double)PruningTableConstants.CornerUdPruningTableSizePhase2)); while (done < PruningTableConstants.CornerUdPruningTableSizePhase2) { for (int reducedCornerPermutation = 0; reducedCornerPermutation < SymmetryReduction.NumCornerPermutationSymmetryClasses; reducedCornerPermutation++) { for (int udEdgeOrder = 0; udEdgeOrder < NumUdEdgeOrders; udEdgeOrder++) { int index = NumUdEdgeOrders * reducedCornerPermutation + udEdgeOrder; if (pruningTable[index] == depth) { int expandedCornerPermutation = SymmetryReduction.ExpandCornerPermutationCoordinate[reducedCornerPermutation]; for (int phase2Move = 0; phase2Move < TwoPhaseConstants.NumMovesPhase2; phase2Move++) { //apply move int newUdEdgeOrder = TableController.UdEdgeOrderMoveTable[udEdgeOrder, phase2Move]; int newCornerPermutation = TableController.CornerPermutationMoveTable[expandedCornerPermutation, MoveTables.Phase2IndexToPhase1Index[phase2Move]]; //same as calling GetPhase2CornerUdPruningIndex, //but the reduced edge orientation and equator //distribution coordinate is required. int newReducedCornerPermutation = SymmetryReduction.ReduceCornerPermutationCoordinate[newCornerPermutation]; int reductionSymmetry = SymmetryReduction.CornerPermutationReductionSymmetry[newCornerPermutation]; newUdEdgeOrder = SymmetryReduction.ConjugateUdEdgeOrderCoordinate[newUdEdgeOrder, reductionSymmetry]; int newIndex = NumUdEdgeOrders * newReducedCornerPermutation + newUdEdgeOrder; //store depth if (pruningTable[newIndex] == invalid) { pruningTable[newIndex] = (byte)(depth + 1); done++; int flags = SymmetryReduction.CornerPermutationSymmetries[newReducedCornerPermutation]; for (int symmetry = 1; symmetry < Symmetries.NumSymmetriesDh4; symmetry++) { flags >>= 1; if ((flags & 0b1) == 1) { int rotatedUdEdgePermutation = SymmetryReduction.ConjugateUdEdgeOrderCoordinate[newUdEdgeOrder, symmetry]; int rotatedIndex = NumUdEdgeOrders * newReducedCornerPermutation + rotatedUdEdgePermutation; if (pruningTable[rotatedIndex] == invalid) { pruningTable[rotatedIndex] = (byte)(depth + 1); done++; } } } } } } } } depth++; Console.WriteLine(string.Format(outputFormat, depth, done, done / (double)PruningTableConstants.CornerUdPruningTableSizePhase2)); } return(pruningTable); }
/// <summary> /// Find a near-optimal solution for a cube in respect to the cost of /// the moves. If no matching solution is found, the return value is /// null. /// </summary> /// <param name="cubeToSolve">The cube to solve.</param> /// <param name="timeout"> /// Interrupt the search after that much time has passed. /// </param> /// <param name="returnCost"> /// The search stops as soon as a solution of the specified cost is /// found. Be careful with setting <paramref name="returnCost"/> to a /// value too small, because there might not be a solution cheaper than /// or matching that cost. In which case the algorithm will run for a /// long time. /// </param> /// <param name="requiredCost"> /// Ignore the timeout until a solution of a cost less than or equal to /// <paramref name="requiredCost"/> is found. If the value of /// <paramref name="requiredCost"/> is negative, this parameter is /// ignored. If <paramref name="requiredCost"/> is positive, it must be /// larger than or equal to <paramref name="returnCost"/>. Setting the /// value to a large value will cause the first solution found after /// timeout to be returned. /// </param> /// <param name="weights">The weights to use.</param> /// <param name="weightedCornerEquatorPruningTable"> /// The weighted corner permutation and equator order pruning table /// created by /// <see cref="WeightedPruningTables.CreateWeightedPhase2CornerEquatorTable(float[])"/>. /// The weights used to create this table must be equatl to /// <paramref name="weights"/>. /// </param> /// <param name="deltaMin"> /// The minimum difference a newly found solution has to exceed the /// cheapest solution found. Only used to experiment. Set to 0 for /// optimal solutions. /// </param> /// <param name="searchDifferentOrientations"> /// Start three threads each solving a 120° offset of the cube. /// </param> /// <param name="searchInverse"> /// For every orientation solved, start another thread that solves its /// inverse. /// </param> /// <returns> /// A near-optimal solution for the specified cube in respect to its /// cost. Null, if no matching solution is found. /// </returns> /// <exception cref="ArgumentNullException"> /// Thrown if <paramref name="cubeToSolve"/> or /// <paramref name="weightedCornerEquatorPruningTable"/> is null. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown if <paramref name="timeout"/>, /// <paramref name="returnCost"/> or <paramref name="deltaMin"/> is /// negative. Also thrown if <paramref name="requiredCost"/> is /// positive and less than <paramref name="returnCost"/>. /// </exception> /// <exception cref="ArgumentException"> /// Thrown if <paramref name="weightedCornerEquatorPruningTable"/>.Length /// is not of the expected size. /// </exception> /// <exception cref="InvalidWeightsException"> /// Thrown if <paramref name="weights"/> is invalid. /// </exception> public static Alg FindSolution(CubieCube cubeToSolve, TimeSpan timeout, float returnCost, float requiredCost, float[] weights, float[] weightedCornerEquatorPruningTable, float deltaMin = 0f, bool searchDifferentOrientations = true, bool searchInverse = true) { #region parameter checks if (cubeToSolve is null) { throw new ArgumentNullException(nameof(cubeToSolve) + " is null."); } if (timeout < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(timeout) + " cannot be negative: " + timeout); } if (returnCost < 0d) { throw new ArgumentOutOfRangeException(nameof(returnCost) + " cannot be negative: " + returnCost); } if (requiredCost > 0d && requiredCost < returnCost) { throw new ArgumentOutOfRangeException(nameof(requiredCost) + " must either be negative or >= " + nameof(returnCost) + ": " + requiredCost + " (" + nameof(requiredCost) + ") < " + returnCost + " (" + nameof(returnCost) + ")"); } MoveWeightsUtils.ValidateWeights(weights); if (weightedCornerEquatorPruningTable is null) { throw new ArgumentNullException(nameof(weightedCornerEquatorPruningTable) + " is null."); } if (weightedCornerEquatorPruningTable.Length != PruningTableConstants.CornerEquatorPruningTableSizePhase2) { throw new ArgumentException(nameof(weightedCornerEquatorPruningTable) + " must be of size " + PruningTableConstants.CornerEquatorPruningTableSizePhase2); } if (deltaMin < 0d) { throw new ArgumentOutOfRangeException(nameof(deltaMin) + " cannot be negative: " + deltaMin); } #endregion paramter checks //initialize move tables TableController.InitializeCornerOrientationMoveTable(); TableController.InitializeCornerPermutationMoveTable(); TableController.InitializeDEdgePermutationMoveTable(); TableController.InitializeEdgeOrientationMoveTable(); TableController.InitializeEquatorPermutationMoveTable(); TableController.InitializeUdEdgeOrderMoveTable(); TableController.InitializeUEdgePermutationMoveTable(); //initialize pruning tables TableController.InitializePhase1PruningTable(); TableController.InitializePhase2CornerEquatorPruningTable(); TableController.InitializePhase2CornerUdPruningTable(); Stopwatch timePassed = new Stopwatch(); timePassed.Start(); SyncedBoolWrapper isTerminated = new SyncedBoolWrapper() { Value = false }; SyncedFloatWrapper bestSolutionCost = new SyncedFloatWrapper() { Value = float.MaxValue }; SyncedIntWrapper bestSolutionIndex = new SyncedIntWrapper() { Value = -1 }; List <Alg> solutions = new List <Alg>(); int numThreads = (searchDifferentOrientations ? 3 : 1) * (searchInverse ? 2 : 1); int increment = searchDifferentOrientations ? 1 : 2; Thread[] solvers = new Thread[numThreads]; for (int orientation = 0; orientation < numThreads; orientation += increment) { WeightedTwoPhaseSolver solver = new WeightedTwoPhaseSolver() { _notRotatedCube = cubeToSolve.Clone(), //create a copy to avoid issues caused by multithreading _timePassed = timePassed, _timeout = timeout, _returnCost = returnCost, _requiredCost = requiredCost, IsTerminated = isTerminated, _solutions = solutions, _bestSolutionCost = bestSolutionCost, _bestSolutionIndex = bestSolutionIndex, _rotation = orientation % 3, _inversed = orientation / 3 == 1, _nonRotatedWeights = (float[])weights.Clone(), //create a copy to avoid issues caused by multithreading _weightedCornerEquatorPruningTable = weightedCornerEquatorPruningTable, _deltaMin = deltaMin }; Thread solverThread = new Thread(new ThreadStart(solver.StartSearch)); solvers[orientation] = solverThread; solverThread.Start(); } foreach (Thread solverThread in solvers) { solverThread.Join(); } timePassed.Stop(); //not required, only for the sake of completeness if (solutions.Count > 0) { return(solutions[bestSolutionIndex.Value]); } else { return(null); } }