avatarThe Pragmatic Programmers

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

3139

Abstract

ize 8 representing the location of each queen on the board. For example, the solution [0, 2, 1, 3, 6, 4, 7, 5] represents the following configuration on the chess board:</p><figure id="4f51"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*RonefP7jWrjLs7p_iki1dA.png"><figcaption></figcaption></figure><p id="0e6c">To create a permutation genotype, add the following function to NQueens:</p><div id="f15b"><pre><span class="hljs-variable">@impl</span> <span class="hljs-literal">true</span> ​ ​<span class="hljs-function"><span class="hljs-keyword">def</span><span class="hljs-title">genotype</span></span><span class="hljs-keyword">do</span>​ ​ genes = Enum.shuffle(<span class="hljs-number">0</span>..<span class="hljs-number">7</span>) ​ %Chromosome{​<span class="hljs-symbol">genes:</span>​ genes, ​<span class="hljs-symbol">size:</span><span class="hljs-number">8</span>} ​ ​<span class="hljs-keyword">end</span></pre></div><p id="803c">Enum.shuffle/1 scrambles the given enumerable. In this function, you pass the range 0..7 to represent a permutation of size 8.</p><p id="078d">Next, you need to define a fitness function.</p><h1 id="8716">Implementing the Fitness Function</h1><p id="a5be">Remember, the objective is to configure a chess board so that there are no conflicts between any of the pieces on the board. Your objective is to minimize these conflicts; therefore, your fitness function should measure the number of conflicts in some way.</p><p id="c861">To do this, you need to check solutions across each row and diagonally. Your genotype restricts queens from being in the same column; however, they can still be in the same row.</p><p id="c3f9">Your fitness function should look like this:</p><div id="2545"><pre>​ @impl <span class="hljs-literal">true</span> ​ ​def​ fitness_function(chromosome) ​<span class="hljs-built_in">do</span>​ ​ diag_clashes = ​ <span class="hljs-keyword">for</span> i <- <span class="hljs-number">0.</span><span class="hljs-number">.7</span>, j <- <span class="hljs-number">0.</span><span class="hljs-number">.7</span><span class="hljs-built_in">do</span>​ ​ ​<span class="hljs-keyword">if</span>​ i != j ​<span class="hljs-built_in">do</span>​ ​ dx = <span class="hljs-built_in">abs</span>(i - j) ​ dy = ​ <span class="hljs-built_in">abs</span>( ​ chromosome.genes ​ |> Enum.<span class="hljs-keyword">at</span>(i) ​ |> Kernel.-(Enum.<span class="hljs-keyword">at</span>(chromosome.genes), j) ​ ) ​ ​<span class="hljs-keyword">if</span>​ dx == dy ​<span class="hljs-built_in">do</span>​ ​ <span class="hljs-number">1</span> ​ ​<span class="hljs-keyword">else</span>​ ​ <span class="hljs-number">0</span> ​ ​<span class="hljs-keyword">end</span>​ ​ ​<span class="hljs-keyword">else</span>​ ​ <span class="hljs-number">0</span> ​ ​<span class="hljs-keyword">end</span>​ ​ ​<span class="hljs-keyword">end</span>​ ​ <span class="hljs-built_in">length</span>(Enum.uniq(chromosome.genes)) - Enum.<s

Options

pan class="hljs-built_in">sum</span>(diag_clashes) ​ ​<span class="hljs-keyword">end</span></pre></div><p id="183a">First, you calculate the number of diagonal clashes in the solution. Next, you filter out duplicate values in the chromosome because those represent row clashes. Finally, you return the difference. The fitness function will return the number of non-conflicts or the number of pieces that don’t conflict with any others.</p><p id="7b59">The final step is to define your termination criteria.</p><h1 id="319a">Defining Termination Criteria and Running</h1><p id="eb4f">Because the fitness function returns the number of non-conflicts, your algorithm is complete when the maximum fitness of the population is 8. In other words, your algorithm is complete when there are no conflicts on the board.</p><p id="7ba9">Your termination criteria is:</p><div id="16ab"><pre><span class="hljs-symbol">@impl</span> <span class="hljs-literal">true</span> ​ ​def​ terminate?(population, _generation), ​<span class="hljs-keyword">do</span>​: ​ <span class="hljs-keyword">Enum</span>.max_by(population, &NQueens.fitness_function/<span class="hljs-number">1</span>).fitness == <span class="hljs-number">8</span></pre></div><p id="9f74">To run your algorithm, add the following lines beneath your module definition:</p><div id="9544"><pre>​ soln = Genetic<span class="hljs-selector-class">.run</span>(NQueens) ​ ​ IO<span class="hljs-selector-class">.write</span>(​<span class="hljs-string">"​​\n"</span>​) ​ IO<span class="hljs-selector-class">.inspect</span>(soln)</pre></div><p id="8326">Next, open a terminal and run mix run scripts/n_queens.exs:</p><div id="bca2"><pre>​ ​$​ mix <span class="hljs-keyword">run</span><span class="language-bash"> scripts/n_queens.exs</span> ​ Current ​Best:​ <span class="hljs-number">5</span></pre></div><p id="8a68">What’s going on here? Your solution stagnates before ever reaching the maximum possible fitness.</p><p id="34a6">The problem is your crossover function creates invalid chromosomes. Right now, your framework uses single-point crossover. Single-point crossover doesn’t preserve the integrity of the permutation — you need to implement a crossover strategy that works for permutations.</p><p id="83ac"><i>👈 <a href="https://readmedium.com/chapter-6-generating-new-solutions-11ae2f380e55">Chapter 6 Generating New Solutions</a> | <a href="https://readmedium.com/table-of-contents-879fc8614df">TOC</a> | <a href="https://readmedium.com/solving-n-queens-with-order-one-crossover-fe4af2a4a4a">Solving N-Queens with Order-One Crossover</a> 👉</i></p><p id="36b9"><i>Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats <a href="https://pragprog.com/titles/smgaelixir">directly from the Pragmatic Programmers</a>. If you notice a code error or formatting mistake, please let us know <a href="https://readmedium.com/how-to-report-errata-4e164674347a">here</a> so that we can fix it.</i></p><figure id="0787"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*U2E2a23SuM4520w9CUXSWw.jpeg"><figcaption></figcaption></figure></article></body>

Introducing N-Queens

Genetic Algorithms in Elixir — by Sean Moriarity (49 / 101)

👈 Chapter 6 Generating New Solutions | TOC | Solving N-Queens with Order-One Crossover 👉

Imagine your friend challenges you to array eight queens on a standard chess board so that none of the queens conflict with another. This problem, known as N-queens, is a fundamental constraint satisfaction problem, similar to the knapsack problem introduced in Chapter 4, Evaluating Solutions and Populations. In N-queens, the objective is to configure N queens on a chess board so that no queen threatens another. In chess, a piece is “threatened” when another piece can move to the square it occupies to “capture” it. The queen is permitted to move horizontally, vertically, and diagonally any number of spaces on the board. Because queens can move in any direction horizontally or vertically, it’s only possible to create a correct configuration of N queens on an NxN chess board.

The following image illustrates a correct solution to the N-queens problem with eight queens on an 8x8 chess board:

N-queens is a combinatorial optimization problem — which, as you already know, genetic algorithms are well-suited for. You’ll attempt to solve N-queens to demonstrate the importance of using a good crossover strategy in your algorithms.

Start by creating a new file called n_queens.exs in your scripts folder. In this file, you’ll write an encoding of the N-queens problem and run your algorithm to obtain a solution.

In n_queens.exs, define the module and specify the Problem behaviour, like this:

​ ​defmoduleNQueens@behaviour Problemalias Types.Chromosome
​ 
​   ​# Rest goes here​
​ ​end

Implementing the Genotype

Remember, you need to define three functions to correctly implement the Problem behaviour. The first function is genotype/0, which returns the genotype your algorithm will use. At first, you might be tempted to encode solutions using a binary genotype where each index represents whether a queen is present or not. However, it’s smarter to keep track of a permutation of size 8 representing the location of each queen on the board. For example, the solution [0, 2, 1, 3, 6, 4, 7, 5] represents the following configuration on the chess board:

To create a permutation genotype, add the following function to NQueens:

@impl true
​ ​defgenotypedo​
​   genes = Enum.shuffle(0..7)
​   %Chromosome{​genes:​ genes, ​size:8}
​ ​end

Enum.shuffle/1 scrambles the given enumerable. In this function, you pass the range 0..7 to represent a permutation of size 8.

Next, you need to define a fitness function.

Implementing the Fitness Function

Remember, the objective is to configure a chess board so that there are no conflicts between any of the pieces on the board. Your objective is to minimize these conflicts; therefore, your fitness function should measure the number of conflicts in some way.

To do this, you need to check solutions across each row and diagonally. Your genotype restricts queens from being in the same column; however, they can still be in the same row.

Your fitness function should look like this:

​ @impl true
​ ​def​ fitness_function(chromosome) ​do​
​   diag_clashes =
​     for i <- 0..7, j <- 0..7do​
​       ​if​ i != j ​do​
​         dx = abs(i - j)
​         dy =
​           abs(
​             chromosome.genes
​             |> Enum.at(i)
​             |> Kernel.-(Enum.at(chromosome.genes), j)
​           )
​         ​if​ dx == dy ​do​
​           1
​         ​else​
​           0
​         ​end​
​       ​else​
​         0
​       ​end​
​     ​end​
​   length(Enum.uniq(chromosome.genes)) - Enum.sum(diag_clashes)
​ ​end

First, you calculate the number of diagonal clashes in the solution. Next, you filter out duplicate values in the chromosome because those represent row clashes. Finally, you return the difference. The fitness function will return the number of non-conflicts or the number of pieces that don’t conflict with any others.

The final step is to define your termination criteria.

Defining Termination Criteria and Running

Because the fitness function returns the number of non-conflicts, your algorithm is complete when the maximum fitness of the population is 8. In other words, your algorithm is complete when there are no conflicts on the board.

Your termination criteria is:

@impl true
​ ​def​ terminate?(population, _generation), ​do​:
​   Enum.max_by(population, &NQueens.fitness_function/1).fitness == 8

To run your algorithm, add the following lines beneath your module definition:

​ soln = Genetic.run(NQueens)
​ 
​ IO.write(​"​​\n"​)
​ IO.inspect(soln)

Next, open a terminal and run mix run scripts/n_queens.exs:

​ ​$​ mix run scripts/n_queens.exs
​ Current ​Best:​ 5

What’s going on here? Your solution stagnates before ever reaching the maximum possible fitness.

The problem is your crossover function creates invalid chromosomes. Right now, your framework uses single-point crossover. Single-point crossover doesn’t preserve the integrity of the permutation — you need to implement a crossover strategy that works for permutations.

👈 Chapter 6 Generating New Solutions | TOC | Solving N-Queens with Order-One Crossover 👉

Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats directly from the Pragmatic Programmers. If you notice a code error or formatting mistake, please let us know here so that we can fix it.

Smgaelixir
Recommended from ReadMedium