def __init__(self, rows, cols, dplobj, counter, pflag): # Initialise the object members self.rows = rows self.cols = cols self.dplobj = dplobj self.chrom_start = 0 # Chromosome starting index self.permutations = counter self.pflag = pflag # Permutations calculation flag # # Calculate solution maximum number of holes. # (self.maxholes, self.maxdominoes) = cmn.calc_solution_holes(rows, cols) # OK taking a different approach so that we use a list of shapes, as per # the chromosome and then run all our tests on this list. This has the # benefit of not requiring us to manage the shape of the dominoes on a # grid, where we'd have to check for overlap and manage that. We also # have many of the functions that work on a list already developed and # tested. # # Create the chromosome and fill it with unassigned values. # Note: that this is maximum possible list size and is the equivalent of # filling the board with spaces only. self.chromosome = [cmn.CELL_UNASSIGNED for _ in range(rows * cols)] # Initialise the board size and fill with zeros self.board = np.zeros((self.rows, self.cols), 'uint8')
def ba_proc(rows, cols, chrom_seq, pnum, counter, permutations): """ BA Algorithm function for use in a multiprocessor process. :param rows: number of rows on the board. :param cols: number of columns on the board. :param chrom_seq: pre-initialised chromosome. :param pnum: number of processors. :param permutations: permutations calculation flag. :param counter: reference to counter object. :return: n/a """ # # Plot the domino grid solutions; saving to a each to a file. # print("Saving resultant board plots.") # Retrieve the expected number of holes and dominoes (holes, dominoes) = cmn.calc_solution_holes(rows, cols) dplobj = dpl.CPlot(rows, cols, "BA", holes, dominoes) dplobj.set_proc_num(pnum) # # Create the backtracking algorithm object with the specified board size. # baobj = CBacktracking(rows, cols, dplobj, counter, permutations) # Display the backtracking algorithm's properties if pnum == 0: print(baobj) # Only print it the once. # Now ready to solve the problem using the chromosome. print( "Executing the backtracking algorithm on processor[{}].".format(pnum)) baobj.runp(chrom_seq)
def main(): """ Main function entry point to calculating all the solutions to the (8 x 10) board, by using the solutions from lower dimensions. These being 2x slices of (3 x 8) solutions, plus 1x slice of (4 x 8) solutions. :param: n/a :return: n/a """ # # Check that the solution folders are present, no point continuing if # they are not there. # ok_to_proceed = True soln_folders = ["3x8__8_holes-8_dominoes", "4x8__10_holes-11_dominoes"] for folder in soln_folders: if not os.path.exists(os.path.join(cmn.BA_FOLDER, folder)): print("Error: {} doesn't exist.".format(folder)) ok_to_proceed = False else: print("Found solution folder '{}'".format(folder)) if not ok_to_proceed: print("Missing solution folder(s) so quitting.") sys.exit(-1) # # Load all the solutions into memory for speed and ease of processing. # solns = {"3x8": [], "4x8": []} for folder in soln_folders: path_str = os.path.join(cmn.BA_FOLDER, folder) for fname in os.listdir(path_str): # Identify a solution file and load it. if fname.endswith(".npy"): board = np.load(os.path.join(path_str, fname)) if folder == "3x8__8_holes-8_dominoes": solns["3x8"].append(board) else: solns["4x8"].append(board) # Need 3x nested for loops representing each of the 3 slices. # As the seed board solutions are different we need to execute the nested # loop three times to get full coverage, as follows: all_solns = [] # 1) slice1: 3x8, slice2: 3x8, slice3: 4x8 all_solns.extend(get_solns(solns["3x8"], solns["3x8"], solns["4x8"])) # 2) slice1: 3x8, slice2: 4x8, slice3: 3x8 all_solns.extend(get_solns(solns["3x8"], solns["4x8"], solns["3x8"])) # 3) slice1: 4x8, slice2: 3x8, slice3: 3x8 all_solns.extend(get_solns(solns["4x8"], solns["3x8"], solns["3x8"])) # Plot the domino board solutions; saving to a each to a file. # Note we need to pass in a numpy 2D array! max_holes, min_dominoes = cmn.calc_solution_holes(8, 10) dplobj = dpl.CPlot(8, 10, "pattern", max_holes, min_dominoes) for result in all_solns: dplobj.write_soln_to_file(result) dplobj.classify_solns() dplobj.plot_all()
def main(grid, algo): """ Main reclassification script. :param grid: grid dimensions in rows and columns. :param algo: algorithm to be reclassified. :return: execution time """ rows = int(grid[0]) cols = int(grid[1]) # Calculate solution maximum number of holes. (max_holes, max_dominoes) = cmn.calc_solution_holes(rows, cols) # # Instantiate the plotting object. # dplobj = dpl.CPlot(rows, cols, algo, max_holes, max_dominoes) # # Scan the results folder solutions and update the class members # print("Scanning the previous results.") dplobj.load_soln_info() # print(dplobj) # debug output # # Scan the results folder for image files and remove any it finds # print("Removing old image solution files.") for fname in os.listdir(dplobj.path_str): if fname.endswith(".png"): os.remove(os.path.join(dplobj.path_str, fname)) # # Parse the solution files; classify the solutions and plot them. # print("Re-classfying the results.") dplobj.classify_solns() print("Plotting all the results.") dplobj.plot_all() print("Found {} total solutions and {} fundamental solutions" \ .format(dplobj.total_all_solns, dplobj.total_fundamental)) # # Update the test information file with the correct number of fundamental solutions. # fname = os.path.join(dplobj.path_str, cmn.RESULTS_FILE) if os.path.exists(fname): print("Updating the number of fundamental solutions in {}".format(fname)) # The file exists so read in the entire contents with open(fname, "r") as fin: contents = fin.read() # Update the number of fundamental solutions repl = "Fundamental solutions: {}".format(dplobj.total_fundamental) # Had to use regex '(?!...) look behind so that regex it didn't consume # the 'Fundamental solutions: ' string. contents = re.sub(r"(Fundamental solutions:\s*[0-9]+)", repl, contents, re.IGNORECASE) # Write the file back again. with open(fname, "w") as fout: fout.write(contents)
def main(grid, calc_permutations, timed_execution): """ Main Bracktracking Algorithm implementation. :param grid: grid dimensions in rows and columns. :param timed_execution: flag to time algorithm execution. :return: execution time """ start = time.time() # Used to time script execution. rows = int(grid[0]) cols = int(grid[1]) # Set the number of parallel processes we want to run. # This is dependent on the number of processors available and the # initialisation values available and the board dimensions. num_processors = mp.cpu_count() print("Number of processors available: {}".format(num_processors)) # Create the chromosome initialisation list. if calc_permutations and num_processors >= 9 and rows > 4 and cols > 4: num_processors = 9 # Only use 9 processors # Note: when calculating the permutations we need ALL combinations! chrom_init_seq = [[cmn.CELL_SPACE, cmn.CELL_HDOMINO], [cmn.CELL_SPACE, cmn.CELL_VDOMINO], [cmn.CELL_SPACE, cmn.CELL_SPACE], [cmn.CELL_HDOMINO, cmn.CELL_SPACE], [cmn.CELL_HDOMINO, cmn.CELL_HDOMINO], [cmn.CELL_HDOMINO, cmn.CELL_VDOMINO], [cmn.CELL_VDOMINO, cmn.CELL_SPACE], [cmn.CELL_VDOMINO, cmn.CELL_HDOMINO], [cmn.CELL_VDOMINO, cmn.CELL_VDOMINO]] if not calc_permutations and num_processors >= 8 and rows > 4 and cols > 4: num_processors = 8 # Only use 8 processors chrom_init_seq = [[cmn.CELL_SPACE, cmn.CELL_HDOMINO], [cmn.CELL_SPACE, cmn.CELL_VDOMINO], [cmn.CELL_HDOMINO, cmn.CELL_SPACE], [cmn.CELL_HDOMINO, cmn.CELL_HDOMINO], [cmn.CELL_HDOMINO, cmn.CELL_VDOMINO], [cmn.CELL_VDOMINO, cmn.CELL_SPACE], [cmn.CELL_VDOMINO, cmn.CELL_HDOMINO], [cmn.CELL_VDOMINO, cmn.CELL_VDOMINO]] elif not calc_permutations and num_processors >= 6 and rows >= 4 and cols >= 4: num_processors = 6 # Only use 6 processors chrom_init_seq = [[cmn.CELL_SPACE, cmn.CELL_HDOMINO], [cmn.CELL_SPACE, cmn.CELL_VDOMINO], [cmn.CELL_HDOMINO], [cmn.CELL_VDOMINO, cmn.CELL_SPACE], [cmn.CELL_VDOMINO, cmn.CELL_HDOMINO], [cmn.CELL_VDOMINO, cmn.CELL_VDOMINO]] elif num_processors >= 3 and rows >= 3 and cols >= 3: num_processors = 3 # Only use 3 processors chrom_init_seq = [[cmn.CELL_SPACE], [cmn.CELL_HDOMINO], [cmn.CELL_VDOMINO]] else: num_processors = 1 # Only use 1 processor chrom_init_seq = [] # Initialise our permutations counter object. counter = Counter(0) # Setup the BA algorithm to run on num_processors value, if possible. if num_processors == 1: print("System not sufficient to run parallel processes.") ba_proc(rows, cols, chrom_init_seq, 0, counter, calc_permutations) else: print("Running {} parallel processes.".format(num_processors)) # Seeding each BA object with all the different possible combinations # for the first board squares. procs = [] for i in range(num_processors): proc = Process(target=ba_proc, args=(rows, cols, chrom_init_seq[i], i, counter, calc_permutations)) procs.append(proc) proc.start() # Block until all cores have finished. for proc in procs: proc.join() execution_time = time.time() - start # # Now display the number of permutations found. # if calc_permutations: msg = "\n\nNumber of permutations = {}\n".format(counter.value()) print(msg) fname = "{}x{}-permutations.txt".format(rows, cols) fname = os.path.join(cmn.PERMUTATIONS_FOLDER, fname) with open(fname, "w") as fout: fout.write(msg) # # Parse the solution files; classify the solutions and plot them. # else: # Retrieve the expected number of holes and dominoes (holes, dominoes) = cmn.calc_solution_holes(rows, cols) dplobj = dpl.CPlot(rows, cols, "BA", holes, dominoes) dplobj.load_soln_info() dplobj.classify_solns() dplobj.plot_all() # # Write the version information to the results folder. # - Python and script version information is recorded. # - this includes the execution time in seconds. # verstr = cmn.get_version_str(SCRIPTINFO) if dplobj.get_total_solns(): time_1st = float(execution_time / dplobj.get_total_solns()) else: # protect against divide by zero time_1st = execution_time dplobj.write_test_info(verstr, execution_time, time_1st) # Display the time taken if requested. if timed_execution: print("Script execution time:", execution_time, "seconds") # Return the execution time. return execution_time
def main(grid, timed_execution): """ Main Genetic Algorithm implementation. :param grid: the board dimensions in rows and columns. :param timed_execution: flag to time algorithm execution. :return: execution time and total number of solutions found. """ # Setup the GA configuration and show the user rows = int(grid[0]) cols = int(grid[1]) print('Running {} with board ({} x {}):'.format(SCRIPTNAME, rows, cols)) # # Calculate solution maximum number of holes. # (max_holes, max_dominoes) = cmn.calc_solution_holes(rows, cols) print("Maximum number of holes for {} x {} board is {}.".format( rows, cols, max_holes)) print("Maximum number of dominoes for {} x {} board is {}.".format( rows, cols, max_dominoes)) # Determine the chromosome size 'csize' from the maximum number of rows and # columns that are possible; plus add some padding. csize = int(max_holes + max_dominoes + 4) print("Chromosome size = {}".format(csize)) print( "Generations={}, Population={}, Crossover Probability={}, Mutation Rate={}" .format(GENERATIONS_MAX, POPULATION_SIZE, CX_PROBABILITY, MUTATION_RATE)) # Initialise the DEAP structure and evaluation object toolbox, pop, fits = init_DEAP_structures(csize, cols, rows, max_holes, max_dominoes) # # Run the evolution: # Stop if we find a solution or we reach the maximum number of generations. # # Variable keeping track of the number of generations generation = 0 start = time.time() # Used to time script execution. while max(fits) < 100 and generation < GENERATIONS_MAX: # Display the header at the regular intervals. if not generation % 50: print("\n {:<10}{:<10}{:<8}{:<8}{:<8}{:<8}".format( "Gen", "Eval", "Min", "Max", "Avg", "Std")) print("-" * 60) # A new generation random.seed(time.time()) # MUST ENSURE RANDOM SEED EACH RUN generation = generation + 1 # Select the next generation individuals offspring = toolbox.select(pop, len(pop)) # Clone the selected individuals offspring = list(map(toolbox.clone, offspring)) for mutant in offspring: # mutate an individual with probability MUTATION_RATE # range [0.0, 1.0) if random.random() < MUTATION_RATE: toolbox.mutate(mutant) del mutant.fitness.values # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit # The population is entirely replaced by the offspring pop[:] = offspring # Gather all the fitnesses in one list and print the stats fits = [ind.fitness.values[0] for ind in pop] # Calculate and print out the statistics length = len(pop) mean = sum(fits) / length sum2 = sum(x * x for x in fits) std = abs(sum2 / length - mean**2)**0.5 print(" {:<10}{:<10}{:<8.2f}{:<8.2f}{:<8.2f}{:<8.2f}".format( generation, len(invalid_ind), min(fits), max(fits), mean, std)) # Had to add time.sleep() to get around a weird bug, where times less than # 1 second are not recorded/recognised. Simple 1 second delay and then we # have to subtract the 1 second to get the actual time. time.sleep(1) execution_time = time.time() - start - 1 total_solns = int(0) best = tools.selBest(pop, 1)[0] # print("Best individual is %s, %s" % (best, best.fitness.values)) # debug # Let the user know the result. if best.fitness.values[0] >= 100: print("\nSolution(s) Found.") print("Saving resultant board plots.") dplobj = dpl.CPlot(rows, cols, "GA", max_holes, max_dominoes) solns = [] # Find all the solutions in the population as there may be more than 1 for ind in pop: if ind.fitness.values[0] == 100: result = cmn.convert2board(ind, rows, cols) # Only add if the solution is unique! unique = True for soln in solns: if np.array_equal(soln, result): unique = False break if unique: # print("ind={}".format(ind)) # debug solns.append(result) # Plot the domino board solutions; saving to a each to a file. # Note we need to pass in a numpy 2D array! dplobj.write_soln_to_file(result) dplobj.classify_solns() dplobj.plot_all() # # Write the version information to the results folder. # - Python and script version information is recorded. # - this includes the execution time in seconds. # total_solns = len(solns) verstr = cmn.get_version_str(SCRIPTINFO) time1st = execution_time / total_solns dplobj.write_test_info(verstr, execution_time, time1st) else: print("\nFailed to find a solution.") # Display the time taken if requested. if timed_execution: print("Script execution time:", execution_time, "seconds") # Return the execution time. return execution_time, total_solns
outstr += "title_str={}\n".format(self.title_str) outstr += "total_all_solns={}\n".format(self.total_all_solns) outstr += "total_fundamental={}\n".format(self.total_fundamental) outstr += "board={}\n".format(self.board) outstr += "rows={}\n".format(self.rows) outstr += "cols={}\n".format(self.cols) outstr += "files={}\n".format(self.files) return outstr if __name__ == '__main__': START = time.time() # Used to time script execution. ROWS = 3 COLS = 4 # Retrieve the expected number of holes and dominoes (HOLES, DOMINOES) = cmn.calc_solution_holes(ROWS, COLS) DPLOBJ = CPlot(ROWS, COLS, "domplotlib", HOLES, DOMINOES) # New solution # Fill the board up with holes # BOARD = np.zeros((ROWS, COLS), 'uint8') # Add a new fundamental shape to the test [row, col] BOARD[0, 1] = cmn.CELL_HDOMINO BOARD[0, 2] = cmn.CELL_HDOMINO BOARD[1, 0] = cmn.CELL_HDOMINO BOARD[1, 1] = cmn.CELL_HDOMINO BOARD[2, 1] = cmn.CELL_HDOMINO BOARD[2, 2] = cmn.CELL_HDOMINO BOARD[1, 3] = cmn.CELL_VDOMINO BOARD[2, 3] = cmn.CELL_VDOMINO # Generate all the solutions for this board, make life easier.