Exemplo n.º 1
0
        /// <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);
        }
Exemplo n.º 2
0
        /// <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);
        }
Exemplo n.º 3
0
        //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);
            }
        }
Exemplo n.º 4
0
        /// <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);
            }
        }