// private List<Organism> Hive; // private OrganismBase TheBest; /// <summary> /// Attempts find an optimal solution to a sudoku puzzle with the a hive of organisms /// The organisms each have a total number of allowed Epochs (generations) to find the optimal solution. /// If after the max epochs is reached without an optimal solution, they are culled and another hive is started within the maximum number of specified extinctions /// NOTE: Combinatorial evolution doesn't guarantee and optimal solution will be found. /// </summary> /// <param name="problem">int[][]</param> /// <param name="numOrganisms">how many virtual organism are in the hive at a given time, each representing a possible solution</param> /// <param name="maxEpochs">number of iterations (generations) each organism can run through to find the optimal solution</param> /// <param name="maxExtinctions">if an optimal solution isn't found after the maxEpochs, all organisms are killed and the cycle is restarted if not more than maxExtinctions.</param> /// <returns>The best solution (int[][]) found within the specified time.</returns> public async Task <int[][]> SolveB4ExtinctCount_Async(int[][] problem, int numOrganisms, int maxEpochs, int maxExtinctions) { OriginalMatrix = SudokuTool.DuplicateMatrix(problem); // wrapper for SolveEvo() var err = int.MaxValue; var seed = 0; int[][] bestSolution = null; var extinctions = 0; int epochs = 0; // generate a new hive of organisms with a number of epochs to try for an error-free solution or until we've killed them off too often while (err != 0 && extinctions < maxExtinctions) { _rnd = new Random(seed); bestSolution = await SolveAsync(problem, numOrganisms, maxEpochs).ConfigureAwait(true); err = SudokuTool.Errors(bestSolution); extinctions++; // keep track of the attempts Debug.WriteLine($"{err} errors prior to {extinctions} extinction level events"); ++seed; // increment the seed } _stats.extinctions = extinctions; return(bestSolution); }
} // Error /// <summary> /// This creates a new random solution and maps that solution onto provided problem /// </summary> /// <param name="problem">int[][]</param> /// <param name="rand">pass in your random object if you got one</param> /// <returns></returns> public static int[][] GetRandomMatrix(int[][] problem, Random rand = null) { Random rnd = rand ?? new Random(); // make a copy of the current puzzle var newRandomMatrix = SudokuTool.DuplicateMatrix(problem); for (var block = 0; block < Constants.BoardSize; ++block) { // fill them with 1 through 9 var corners = SudokuTool.Corner(block); var vals = new List <int>(Constants.BoardSize); for (var i = 1; i <= Constants.BoardSize; ++i) { vals.Add(i); } // shuffle each cell with a value from another cell for (var k = 0; k < vals.Count; ++k) { var ri = rnd.Next(k, vals.Count); var tmp = vals[k]; vals[k] = vals[ri]; vals[ri] = tmp; } // remove the random cell values from the known cell values var r = corners[0]; var c = corners[1]; for (var i = r; i < r + 3; ++i) { for (var j = c; j < c + 3; ++j) { var v = problem[i][j]; if (v != 0) // a fixed starting number is assumed correct { vals.Remove(v); } } } // walk thru block and add the new values to the random solution output var ptr = 0; // pointer into List for (var i = r; i < r + 3; ++i) { for (var j = c; j < c + 3; ++j) { if (newRandomMatrix[i][j] == 0) // not occupied { var v = vals[ptr]; // get value from List newRandomMatrix[i][j] = v; ++ptr; // move to next value in List } } } } // each block, k return(newRandomMatrix); } // RandomMatrix
} // RandomMatrix /// <summary> /// Randomly swap the values in two cells of a random block of the supplied matrix /// </summary> /// <param name="problem">the original puzzle </param> /// <param name="matrix">the current working solution</param> /// <param name="rand">pass in your random object if you got one</param> /// <returns></returns> public static int[][] EvolveMatrixAsync(int[][] problem, int[][] matrix, Random rand = null) { Random rnd = rand ?? new Random(); // pick a random 3x3 block, // pick two random cells in block // swap values var result = SudokuTool.DuplicateMatrix(matrix); var block = rnd.Next(0, 9); // [0,8] //Console.WriteLine("block = " + block); var corners = SudokuTool.Corner(block); //Console.WriteLine("corners = " + corners[0] + " " + corners[1]); // look for at minimum 2 cells in the neighbor matrix that might be useful in the problem var cells = new List <int[]>(); for (var i = corners[0]; i < corners[0] + 3; ++i) { for (var j = corners[1]; j < corners[1] + 3; ++j) { //Console.WriteLine("problem " + i + " " + j + " = " + problem[i][j]); if (problem[i][j] == 0) // a non-fixed value { cells.Add(new[] { i, j }); } } } if (cells.Count < 2) { // _sb.AppendLine($"Found only {cells.Count} useful cell in the neighbor."); throw new Exception("block " + block + " doesn't have two values to swap!"); } // pick two. suppose there are 4 possible cells 0,1,2,3 var k1 = rnd.Next(0, cells.Count); // 0,1,2,3 var inc = rnd.Next(1, cells.Count); // 1,2,3 var k2 = (k1 + inc) % cells.Count; //Console.WriteLine("k1 k2 = " + k1 + " " + k2); var r1 = cells[k1][0]; var c1 = cells[k1][1]; var r2 = cells[k2][0]; var c2 = cells[k2][1]; //Console.WriteLine("r1 c1 = " + r1 + " " + c1); //Console.WriteLine("r2 c2 = " + r2 + " " + c2); var tmp = result[r1][c1]; result[r1][c1] = result[r2][c2]; result[r2][c2] = tmp; return(result); } // NeighborMatrix
} // NeighborMatrix /// <summary> /// for each block (3x3) in matrix2, there's a 50% chance to be added into matrix1 /// </summary> /// <param name="matrix1">mating partner 1</param> /// <param name="matrix2">mating partner 2</param> /// <param name="chance">0.50 or some percential double value</param> /// <param name="rand">pass in your random object if you got one</param> /// <returns>a single matrix with some relationships between both provided</returns> public static int[][] MatingResult(int[][] matrix1, int[][] matrix2, double chance = 0.50, Random rand = null) { Random rnd = rand ?? new Random(); var result = SudokuTool.DuplicateMatrix(matrix1); for (var block = 0; block < 9; ++block) { if (rnd.NextDouble() < chance) { var corners = SudokuTool.Corner(block); for (var i = corners[0]; i < corners[0] + 3; ++i) { for (var j = corners[1]; j < corners[1] + 3; ++j) { result[i][j] = matrix2[i][j]; } } } } return(result);// Task.FromResult(result); }
/// <summary> /// Core engine for finding a solution. Running this is going to spawn a hive of organisms (some percentage of workers, the rest explorers) /// /// </summary> /// <param name="problem"></param> /// <param name="numOrganisms"></param> /// <param name="maxEpochs"></param> /// <returns></returns> private async Task <int[][]> SolveAsync(int[][] problem, int numOrganisms, int maxEpochs) { // start fresh var Hive = new List <Organism>(new Organism[numOrganisms]); // the best organisms in each epoch will be stored here var TheBest = new OrganismBase(0, null, Int32.MaxValue, 0); // initialize combinatorial Organisms with 90% of them workers and the rest explorers // Explorers always get a new random matrix to test against. var numWorker = (int)(numOrganisms * 0.90); var numExplorer = numOrganisms - numWorker; // fill the hive with new organisms having a random puzzle and known error count and also if it was the best yet for (var i = 0; i < numOrganisms; ++i) { var orgType = i < numWorker ? OrganismTypes.Worker : OrganismTypes.Explorer; // get a new random puzzle and count the errors int[][] rndMatrix = await Task.FromResult(SudokuTool.GetRandomMatrix(problem, _rnd)); var currentErrors = await Task.FromResult(SudokuTool.Errors(rndMatrix)); // create a new organism, give it the random puzzle and errors Hive[i] = new Organism(orgType, rndMatrix, currentErrors, 0); _stats.organismsBorn++; // track the best so far puzzle and copy it to TheBest if (currentErrors < TheBest.Error) { TheBest.Error = currentErrors; TheBest.Matrix = await Task.FromResult(SudokuTool.DuplicateMatrix(rndMatrix)); _stats.randomWasTheBest++; // this should average out at some point? // Splash(); } } // main loop - start the generations (epochs) var epoch = 0; while (epoch < maxEpochs) { // keep a little log for every 1000 generations if (epoch % 1000 == 0) { Debug.WriteLine("epoch = " + epoch); Debug.WriteLine("best error = " + TheBest.Error); } // if we somehow have no errors (optimal solution!) then just stop now. if (TheBest.Error == 0) // solution found { break; } // process each organism and give explorers random solutions and evolve workers // (with for loop)...why not foreach? because if it ages out we want to put a new organism in place for (var i = 0; i < numOrganisms; ++i) { // we treat Explorers differently than Workers. Explorers always get a new random matrix to test against. Workers evolve, mutate, age +/- or die of old age switch (Hive[i].Type) { case OrganismTypes.Explorer: // give it a new random matrix int[][] rndMatrix = await Task.FromResult(SudokuTool.GetRandomMatrix(problem, _rnd)); Hive[i].Matrix = await Task.FromResult(SudokuTool.DuplicateMatrix(rndMatrix)); Hive[i].Error = await Task.FromResult(SudokuTool.Errors(rndMatrix)); if (Hive[i].Error < TheBest.Error) { TheBest.Error = Hive[i].Error; TheBest.Matrix = SudokuTool.DuplicateMatrix(rndMatrix); _stats.randomWasTheBest++; // Splash(); } break; case OrganismTypes.Worker: // mutate the matrix, pick a random block and pick two random cells and swap them. int[][] evolving = await Task.FromResult(SudokuTool.EvolveMatrixAsync(problem, Hive[i].Matrix, _rnd)); var evolutionErrors = await Task.FromResult(SudokuTool.Errors(evolving)); var probability = _rnd.NextDouble(); var rareMutation = probability < 0.001; bool evoWorked = evolutionErrors < Hive[i].Error; // if the evolution is better (natural selection) or if this is a rareMutation, take it if (evoWorked || rareMutation) // better, or a mistake { // positive result to evolution provides refreshed genepool and resets age if (evoWorked) { Hive[i].Age = 0; _stats.evolutionWorked++; } // if you didn't get the better end of the deal if (rareMutation && !evoWorked) { _stats.mutationFailed++; } Hive[i].Matrix = await Task.FromResult(SudokuTool.DuplicateMatrix(evolving)); // by value Hive[i].Error = evolutionErrors; if (Hive[i].Error < TheBest.Error) { TheBest.Error = Hive[i].Error; TheBest.Matrix = await Task.FromResult(SudokuTool.DuplicateMatrix(evolving)); _stats.bestMutationsEver++; // Splash(); } } else // evolved code is not better and there was no chance for forced mutation { // age this organism Hive[i].Age++; if (Hive[i].Age > 1000) // die - if so, get a random to replace { _stats.diedOfOldAge++; // create a new replacement for the hive. var m = await Task.FromResult(SudokuTool.GetRandomMatrix(problem, _rnd)); var me = await Task.FromResult(SudokuTool.Errors(m)); Hive[i] = new Organism(0, m, me, 0); _stats.organismsBorn++; } } break; } } // each organism // find the index of the best worker var bestWkrIndex = await Task.FromResult(SudokuTool.GetBestWorkerIndex(Hive, numWorker)); var bestWorker = Hive[bestWkrIndex]; // find the index of the best explorer var bestExpIndex = await Task.FromResult(SudokuTool.GetBestExplorerIndex(Hive, numWorker)); var bestExplorer = Hive[bestExpIndex]; // find the index of the worst worker var worstWkrIndex = await Task.FromResult(SudokuTool.GetWorstWorkerIndex(Hive, numWorker)); // have a 50/50 chance for each block in 2nd organism will be blended into 1st organism var babyOrg = await Task.FromResult(SudokuTool.MatingResult(bestWorker.Matrix, bestExplorer.Matrix, 0.50, _rnd)); _stats.matingPairs++; var babyErr = await Task.FromResult(SudokuTool.Errors(babyOrg)); var genNext = new Organism(OrganismTypes.Worker, babyOrg, babyErr, 0) { // generations are only incremented if one of the parents have a GeneMarker Generation = incrementGeneration(bestWorker, bestExplorer), // GeneMarker = "GenX" // might use this if you happened to want to see details later. }; _stats.organismsBorn++; // replace the worst worker with the next generation of worker Hive[worstWkrIndex] = genNext; _stats.culledCount++; if (babyErr < TheBest.Error) { TheBest.Error = babyErr; TheBest.Matrix = await Task.FromResult(SudokuTool.DuplicateMatrix(babyOrg)); _stats.bestBabies++; // Splash(); } ++epoch; _stats.epochsCompleted++; } // while return(TheBest.Matrix); } // SolveEvo