Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

Please change this code to take a 6X6 SUDOKU board, to take in 36 variables, NOT a 9X9 board. import numpy import random random.seed() Nd

Please change this code to take a 6X6 SUDOKU board, to take in 36 variables, NOT a 9X9 board.

import numpy import random random.seed()

Nd = 6 # Number of digits (in the case of standard Sudoku puzzles, this is 6).

class Population(object): """ A set of candidate solutions to the Sudoku puzzle. These candidates are also known as the chromosomes in the population. """

def __init__(self): self.candidates = [] return

def seed(self, Nc, given): self.candidates = []

# Determine the legal values that each square can take. helper = Candidate() helper.values = [[[] for j in range(0, Nd)] for i in range(0, Nd)] for row in range(0, Nd): for column in range(0, Nd): for value in range(1, 10): if((given.values[row][column] == 0) and not (given.is_column_duplicate(column, value) or given.is_block_duplicate(row, column, value) or given.is_row_duplicate(row, value))): # Value is available. helper.values[row][column].append(value) elif(given.values[row][column] != 0): # Given/known value from file. helper.values[row][column].append(given.values[row][column]) break

# Seed a new population. for p in range(0, Nc): g = Candidate() for i in range(0, Nd): # New row in candidate. row = numpy.zeros(Nd)

# Fill in the givens. for j in range(0, Nd): # New column j value in row i.

# If value is already given, don't change it. if(given.values[i][j] != 0): row[j] = given.values[i][j] # Fill in the gaps using the helper board. elif(given.values[i][j] == 0): row[j] = helper.values[i][j][random.randint(0, len(helper.values[i][j])-1)]

# If we don't have a valid board, then try again. There must be no duplicates in the row. while(len(list(set(row))) != Nd): for j in range(0, Nd): if(given.values[i][j] == 0): row[j] = helper.values[i][j][random.randint(0, len(helper.values[i][j])-1)]

g.values[i] = row

self.candidates.append(g)

# Compute the fitness of all candidates in the population. self.update_fitness()

print("Seeding complete.")

return

def update_fitness(self): """ Update fitness of every candidate/chromosome. """ for candidate in self.candidates: candidate.update_fitness() return

def sort(self): """ Sort the population based on fitness. """ self.candidates.sort(self.sort_fitness) return

def sort_fitness(self, x, y): """ The sorting function. """ if(x.fitness < y.fitness): return 1 elif(x.fitness == y.fitness): return 0 else: return -1

class Candidate(object): """ A candidate solutions to the Sudoku puzzle. """ def __init__(self): self.values = numpy.zeros((Nd, Nd)) self.fitness = None return

def update_fitness(self): """ The fitness of a candidate solution is determined by how close it is to being the actual solution to the puzzle. The actual solution (i.e. the 'fittest') is defined as a 9x9 grid of numbers in the range [1, 9] where each row, column and 3x3 block contains the numbers [1, 9] without any duplicates (see e.g. http://www.sudoku.com/); if there are any duplicates then the fitness will be lower. """

column_count = numpy.zeros(Nd) block_count = numpy.zeros(Nd) column_sum = 0 block_sum = 0

for i in range(0, Nd): # For each column... for j in range(0, Nd): # For each number within the current column... column_count[self.values[i][j]-1] += 1 # ...Update list with occurrence of a particular number.

column_sum += (1.0/len(set(column_count)))/Nd column_count = numpy.zeros(Nd)

# For each block... for i in range(0, Nd, 3): for j in range(0, Nd, 3): block_count[self.values[i][j]-1] += 1 block_count[self.values[i][j+1]-1] += 1 block_count[self.values[i][j+2]-1] += 1

block_count[self.values[i+1][j]-1] += 1 block_count[self.values[i+1][j+1]-1] += 1 block_count[self.values[i+1][j+2]-1] += 1

block_count[self.values[i+2][j]-1] += 1 block_count[self.values[i+2][j+1]-1] += 1 block_count[self.values[i+2][j+2]-1] += 1

block_sum += (1.0/len(set(block_count)))/Nd block_count = numpy.zeros(Nd)

# Calculate overall fitness. if (int(column_sum) == 1 and int(block_sum) == 1): fitness = 1.0 else: fitness = column_sum * block_sum

self.fitness = fitness return

def mutate(self, mutation_rate, given): """ Mutate a candidate by picking a row, and then picking two values within that row to swap. """

r = random.uniform(0, 1.1) while(r > 1): # Outside [0, 1] boundary - choose another r = random.uniform(0, 1.1)

success = False if (r < mutation_rate): # Mutate. while(not success): row1 = random.randint(0, 8) row2 = random.randint(0, 8) row2 = row1

from_column = random.randint(0, 8) to_column = random.randint(0, 8) while(from_column == to_column): from_column = random.randint(0, 8) to_column = random.randint(0, 8)

# Check if the two places are free... if(given.values[row1][from_column] == 0 and given.values[row1][to_column] == 0): # ...and that we are not causing a duplicate in the rows' columns. if(not given.is_column_duplicate(to_column, self.values[row1][from_column]) and not given.is_column_duplicate(from_column, self.values[row2][to_column]) and not given.is_block_duplicate(row2, to_column, self.values[row1][from_column]) and not given.is_block_duplicate(row1, from_column, self.values[row2][to_column])):

# Swap values. temp = self.values[row2][to_column] self.values[row2][to_column] = self.values[row1][from_column] self.values[row1][from_column] = temp success = True

return success

class Given(Candidate): """ The grid containing the given/known values. """

def __init__(self, values): self.values = values return

def is_row_duplicate(self, row, value): """ Check whether there is a duplicate of a fixed/given value in a row. """ for column in range(0, Nd): if(self.values[row][column] == value): return True return False

def is_column_duplicate(self, column, value): """ Check whether there is a duplicate of a fixed/given value in a column. """ for row in range(0, Nd): if(self.values[row][column] == value): return True return False

def is_block_duplicate(self, row, column, value): """ Check whether there is a duplicate of a fixed/given value in a 3 x 3 block. """ i = 3*(int(row/3)) j = 3*(int(column/3))

if((self.values[i][j] == value) or (self.values[i][j+1] == value) or (self.values[i][j+2] == value) or (self.values[i+1][j] == value) or (self.values[i+1][j+1] == value) or (self.values[i+1][j+2] == value) or (self.values[i+2][j] == value) or (self.values[i+2][j+1] == value) or (self.values[i+2][j+2] == value)): return True else: return False

class Tournament(object): """ The crossover function requires two parents to be selected from the population pool. The Tournament class is used to do this.

Two individuals are selected from the population pool and a random number in [0, 1] is chosen. If this number is less than the 'selection rate' (e.g. 0.85), then the fitter individual is selected; otherwise, the weaker one is selected. """

def __init__(self): return

def compete(self, candidates): """ Pick 2 random candidates from the population and get them to compete against each other. """ c1 = candidates[random.randint(0, len(candidates)-1)] c2 = candidates[random.randint(0, len(candidates)-1)] f1 = c1.fitness f2 = c2.fitness

# Find the fittest and the weakest. if(f1 > f2): fittest = c1 weakest = c2 else: fittest = c2 weakest = c1

selection_rate = 0.85 r = random.uniform(0, 1.1) while(r > 1): # Outside [0, 1] boundary. Choose another. r = random.uniform(0, 1.1) if(r < selection_rate): return fittest else: return weakest

class CycleCrossover(object): """ Crossover relates to the analogy of genes within each parent candidate mixing together in the hopes of creating a fitter child candidate. Cycle crossover is used here (see e.g. A. E. Eiben, J. E. Smith. Introduction to Evolutionary Computing. Springer, 2007). """

def __init__(self): return

def crossover(self, parent1, parent2, crossover_rate): """ Create two new child candidates by crossing over parent genes. """ child1 = Candidate() child2 = Candidate()

# Make a copy of the parent genes. child1.values = numpy.copy(parent1.values) child2.values = numpy.copy(parent2.values)

r = random.uniform(0, 1.1) while(r > 1): # Outside [0, 1] boundary. Choose another. r = random.uniform(0, 1.1)

# Perform crossover. if (r < crossover_rate): # Pick a crossover point. Crossover must have at least 1 row (and at most Nd-1) rows. crossover_point1 = random.randint(0, 8) crossover_point2 = random.randint(1, 9) while(crossover_point1 == crossover_point2): crossover_point1 = random.randint(0, 8) crossover_point2 = random.randint(1, 9)

if(crossover_point1 > crossover_point2): temp = crossover_point1 crossover_point1 = crossover_point2 crossover_point2 = temp

for i in range(crossover_point1, crossover_point2): child1.values[i], child2.values[i] = self.crossover_rows(child1.values[i], child2.values[i])

return child1, child2

def crossover_rows(self, row1, row2): child_row1 = numpy.zeros(Nd) child_row2 = numpy.zeros(Nd)

remaining = range(1, Nd+1) cycle = 0

while((0 in child_row1) and (0 in child_row2)): # While child rows not complete... if(cycle % 2 == 0): # Even cycles. # Assign next unused value. index = self.find_unused(row1, remaining) start = row1[index] remaining.remove(row1[index]) child_row1[index] = row1[index] child_row2[index] = row2[index] next = row2[index]

while(next != start): # While cycle not done... index = self.find_value(row1, next) child_row1[index] = row1[index] remaining.remove(row1[index]) child_row2[index] = row2[index] next = row2[index]

cycle += 1

else: # Odd cycle - flip values. index = self.find_unused(row1, remaining) start = row1[index] remaining.remove(row1[index]) child_row1[index] = row2[index] child_row2[index] = row1[index] next = row2[index]

while(next != start): # While cycle not done... index = self.find_value(row1, next) child_row1[index] = row2[index] remaining.remove(row1[index]) child_row2[index] = row1[index] next = row2[index]

cycle += 1

return child_row1, child_row2

def find_unused(self, parent_row, remaining): for i in range(0, len(parent_row)): if(parent_row[i] in remaining): return i

def find_value(self, parent_row, value): for i in range(0, len(parent_row)): if(parent_row[i] == value): return i

class Sudoku(object): """ Solves a given Sudoku puzzle using a genetic algorithm. """

def __init__(self): self.given = None return

def load(self, path): # Load a configuration to solve. with open(path, "r") as f: values = numpy.loadtxt(f).reshape((Nd, Nd)).astype(int) self.given = Given(values) return

def save(self, path, solution): # Save a configuration to a file. with open(path, "w") as f: numpy.savetxt(f, solution.values.reshape(Nd*Nd), fmt='%d') return

def solve(self): Nc = 100 # Number of candidates (i.e. population size). Ne = int(0.05*Nc) # Number of elites. Ng = 1000 # Number of generations. Nm = 0 # Number of mutations.

# Mutation parameters. phi = 0 sigma = 1 mutation_rate = 0.06

# Create an initial population. self.population = Population() self.population.seed(Nc, self.given)

# For up to 10000 generations... stale = 0 for generation in range(0, Ng):

print("Generation %d" % generation)

# Check for a solution. best_fitness = 0.0 for c in range(0, Nc): fitness = self.population.candidates[c].fitness if(fitness == 1): print("Solution found at generation %d!" % generation) print(self.population.candidates[c].values) return self.population.candidates[c]

# Find the best fitness. if(fitness > best_fitness): best_fitness = fitness

print("Best fitness: %f" % best_fitness)

# Create the next population. next_population = []

# Select elites (the fittest candidates) and preserve them for the next generation. self.population.sort() elites = [] for e in range(0, Ne): elite = Candidate() elite.values = numpy.copy(self.population.candidates[e].values) elites.append(elite)

# Create the rest of the candidates. for count in range(Ne, Nc, 2): # Select parents from population via a tournament. t = Tournament() parent1 = t.compete(self.population.candidates) parent2 = t.compete(self.population.candidates)

## Cross-over. cc = CycleCrossover() child1, child2 = cc.crossover(parent1, parent2, crossover_rate=1.0)

# Mutate child1. old_fitness = child1.fitness success = child1.mutate(mutation_rate, self.given) child1.update_fitness() if(success): Nm += 1 if(child1.fitness > old_fitness): # Used to calculate the relative success rate of mutations. phi = phi + 1

# Mutate child2. old_fitness = child2.fitness success = child2.mutate(mutation_rate, self.given) child2.update_fitness() if(success): Nm += 1 if(child2.fitness > old_fitness): # Used to calculate the relative success rate of mutations. phi = phi + 1

# Add children to new population. next_population.append(child1) next_population.append(child2)

# Append elites onto the end of the population. These will not have been affected by crossover or mutation. for e in range(0, Ne): next_population.append(elites[e])

# Select next generation. self.population.candidates = next_population self.population.update_fitness()

# Calculate new adaptive mutation rate (based on Rechenberg's 1/5 success rule). This is to stop too much mutation as the fitness progresses towards unity. if(Nm == 0): phi = 0 # Avoid divide by zero. else: phi = phi / Nm

if(phi > 0.2): sigma = sigma/0.998 elif(phi < 0.2): sigma = sigma*0.998

mutation_rate = abs(numpy.random.normal(loc=0.0, scale=sigma, size=None))

# Check for stale population. self.population.sort() if(self.population.candidates[0].fitness != self.population.candidates[1].fitness): stale = 0 else: stale += 1

# Re-seed the population if 100 generations have passed with the fittest two candidates always having the same fitness. if(stale >= 100): print("The population has gone stale. Re-seeding...") self.population.seed(Nc, self.given) stale = 0 sigma = 1 phi = 0 mutations = 0 mutation_rate = 0.06

print("No solution found.") return None

s = Sudoku() s.load("testPuzzle2.txt") solution = s.solve() #if(solution): # s.save("solution.txt", solution)

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

Harness The Power Of Big Data The IBM Big Data Platform

Authors: Paul Zikopoulos, David Corrigan James Giles Thomas Deutsch Krishnan Parasuraman Dirk DeRoos Paul Zikopoulos

1st Edition

0071808183, 9780071808187

More Books

Students also viewed these Databases questions