├── web
├── epub_assets
│ ├── main.css
│ ├── cover.jpg
│ └── cover.html
├── bibtex
│ ├── error.rb
│ └── string_replacement.rb
└── bibtex.rb
├── book
├── f_toc.tex
├── graphics
│ ├── pso1.pdf
│ └── ga3.tex
├── styles
│ └── algorithm2e.sty
├── f_title_page.tex
├── f_foreword.tex
├── f_acknowledgments.tex
├── book.tex
├── f_copyright.tex
├── c_advanced.tex
├── c_physical.tex
├── c_stochastic.tex
├── b_errata.tex
├── c_swarm.tex
├── definitions.tex
└── f_preface.tex
├── .gitignore
├── src
├── algorithms
│ ├── evolutionary
│ │ ├── tests
│ │ │ ├── tc_genetic_algorithm2.rb
│ │ │ ├── tc_genetic_algorithm.rb
│ │ │ ├── tc_evolutionary_programming.rb
│ │ │ └── tc_evolution_strategies.rb
│ │ ├── genetic_algorithm.rb
│ │ ├── differential_evolution.rb
│ │ ├── evolutionary_programming.rb
│ │ └── evolution_strategies.rb
│ ├── stochastic
│ │ ├── random_search.rb
│ │ ├── stochastic_hill_climbing.rb
│ │ ├── tests
│ │ │ ├── tc_random_search.rb
│ │ │ ├── tc_stochastic_hill_climbing.rb
│ │ │ ├── tc_variable_neighborhood_search.rb
│ │ │ ├── tc_adaptive_random_search.rb
│ │ │ ├── tc_grasp.rb
│ │ │ ├── tc_tabu_search.rb
│ │ │ └── tc_iterated_local_search.rb
│ │ ├── adaptive_random_search.rb
│ │ ├── grasp.rb
│ │ ├── variable_neighborhood_search.rb
│ │ ├── iterated_local_search.rb
│ │ ├── tabu_search.rb
│ │ └── guided_local_search.rb
│ ├── probabilistic
│ │ ├── compact_genetic_algorithm.rb
│ │ ├── pbil.rb
│ │ ├── umda.rb
│ │ ├── tests
│ │ │ ├── tc_compact_genetic_algorithm.rb
│ │ │ ├── tc_pbil.rb
│ │ │ ├── tc_umda.rb
│ │ │ └── tc_cross_entropy_method.rb
│ │ └── cross_entropy_method.rb
│ ├── neural
│ │ ├── perceptron.rb
│ │ ├── lvq.rb
│ │ ├── tests
│ │ │ └── tc_perceptron.rb
│ │ ├── som.rb
│ │ └── hopfield.rb
│ ├── physical
│ │ ├── harmony_search.rb
│ │ ├── simulated_annealing.rb
│ │ ├── tests
│ │ │ ├── tc_simulated_annealing.rb
│ │ │ └── tc_harmony_search.rb
│ │ ├── cultural_algorithm.rb
│ │ └── memetic_algorithm.rb
│ ├── swarm
│ │ ├── bees_algorithm.rb
│ │ ├── pso.rb
│ │ ├── tests
│ │ │ ├── tc_bees_algorithm.rb
│ │ │ └── tc_bfoa.rb
│ │ └── ant_system.rb
│ └── immune
│ │ ├── negative_selection_algorithm.rb
│ │ ├── clonal_selection_algorithm.rb
│ │ └── optainet.rb
└── programming_paradigms
│ ├── tests
│ └── tc_flow.rb
│ └── oop.rb
└── Makefile
/web/epub_assets/main.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/book/f_toc.tex:
--------------------------------------------------------------------------------
1 | % toc
2 |
3 | \setcounter{tocdepth}{1}
4 | \tableofcontents
--------------------------------------------------------------------------------
/book/graphics/pso1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddii/CleverAlgorithms/master/book/graphics/pso1.pdf
--------------------------------------------------------------------------------
/web/epub_assets/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddii/CleverAlgorithms/master/web/epub_assets/cover.jpg
--------------------------------------------------------------------------------
/book/styles/algorithm2e.sty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddii/CleverAlgorithms/master/book/styles/algorithm2e.sty
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | book/*.idx
3 | book/*.ilg
4 | book/*.ind
5 | book/*.bbl
6 | book/*.blg
7 | book/*.aux
8 | book/*.pdf
9 | book/*.toc
10 | book/*.out
11 | book/*.log
12 | book/*.synctex.gz
13 | src/algorithms/*.log
14 | web/docs
15 | web/epub_temp
16 | *.epub
17 | **/*~
18 | **/*/*~
19 |
--------------------------------------------------------------------------------
/web/epub_assets/cover.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #++
18 |
19 | module BibTeX
20 | # This module contains functions to manipulate BibTeX
21 | # string literals.
22 | module StringReplacement
23 |
24 | # Returns a string representation of the literal.
25 | def self.to_s(value,options={})
26 | return if value.nil?
27 | options[:delimiter] ||= ['"','"']
28 | #options[:delimiter] ||= ['{','}']
29 |
30 | if value.empty? || (value.length == 1 && !value[0].kind_of?(Symbol))
31 | [options[:delimiter][0],value,options[:delimiter][1]].join
32 | else
33 | value.map { |s| s.kind_of?(Symbol) ? s.to_s : s.inspect}.join(' # ')
34 | end
35 | end
36 |
37 | # Replaces all string constants in +value+ which are defined in +hsh+.
38 | def self.replace(value,hsh)
39 | return if value.nil?
40 | value.map { |s| s.kind_of?(Symbol) && hsh.has_key?(s) ? hsh[s] : s }.flatten
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/book/f_copyright.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 | % copyright.tex
6 |
7 | \vspace*{\fill}
8 | \begin{flushleft}
9 | \begin{small}
10 |
11 | % The Cover
12 | \subsubsection*{Jason Brownlee, PhD}
13 | Jason Brownlee studied Applied Science at Swinburne University in Melbourne, Australia, going on to complete a Masters in Information Technology focusing on Niching Genetic Algorithms, and a PhD in the field of Artificial Immune Systems. Jason has worked for a number of years as a Consultant and Software Engineer for a range of Corporate and Government organizations. When not writing books, Jason likes to compete in Machine Learning competitions.
14 |
15 | % The Cover
16 | \subsubsection*{Cover Image}
17 | \copyright\ Copyright \mybookdate\ \mybookauthor. All Reserved. \\
18 | \vspace{0.5cm}
19 |
20 | % The Book
21 | \subsubsection*{\mybooktitle: \mybooksubtitle}
22 | \copyright\ Copyright \mybookdate\ \mybookauthor. Some Rights Reserved. \\
23 | \vspace{0.5cm}
24 |
25 | Revision 2. 16 June 2012 \\
26 | ISBN: 978-1-4467-8506-5 \\
27 | \vspace{0.5cm}
28 |
29 | This work is licensed under a Creative Commons Attribution\--Noncommercial\--Share Alike 2.5 Australia License. \\
30 | The full terms of the license are located online at \url{http://creativecommons.org/licenses/by-nc-sa/2.5/au/legalcode} \\
31 | \vspace{0.5cm}
32 |
33 | \subsubsection*{Webpage}
34 | Source code and additional resources can be downloaded from the books companion website online at \url{http://www.CleverAlgorithms.com}
35 |
36 | \end{small}
37 | \end{flushleft}
38 |
39 |
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/compact_genetic_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Compact Genetic Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def onemax(vector)
8 | return vector.inject(0){|sum, value| sum + value}
9 | end
10 |
11 | def generate_candidate(vector)
12 | candidate = {}
13 | candidate[:bitstring] = Array.new(vector.size)
14 | vector.each_with_index do |p, i|
15 | candidate[:bitstring][i] = (rand() c2[:cost] ? [c1,c2] : [c2,c1])
40 | best = winner if best.nil? or winner[:cost]>best[:cost]
41 | update_vector(vector, winner, loser, pop_size)
42 | puts " >iteration=#{iter}, f=#{best[:cost]}, s=#{best[:bitstring]}"
43 | break if best[:cost] == num_bits
44 | end
45 | return best
46 | end
47 |
48 | if __FILE__ == $0
49 | # problem configuration
50 | num_bits = 32
51 | # algorithm configuration
52 | max_iterations = 200
53 | pop_size = 20
54 | # execute the algorithm
55 | best = search(num_bits, max_iterations, pop_size)
56 | puts "done! Solution: f=#{best[:cost]}/#{num_bits}, s=#{best[:bitstring]}"
57 | end
--------------------------------------------------------------------------------
/web/bibtex.rb:
--------------------------------------------------------------------------------
1 | #--
2 | # BibTeX-Ruby
3 | # Copyright (C) 2010 Sylvester Keil
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #++
18 | #
19 | # = BibTeX
20 | #
21 | # This module encompasses a parser for BibTeX files and
22 | # auxiliary classes to model the individual
23 | # BibTeX objects: +String+, +Preamble+, +Comment+, and
24 | # +Entry+.
25 | #
26 | # Author:: {Sylvester Keil}[http://sylvester.keil.or.at]
27 | # Copyright:: Copyright (c) 2010 Sylvester Keil
28 | # License:: GNU GPL 3.0
29 | #
30 | module BibTeX
31 | require 'logger'
32 |
33 | # The current library version.
34 | VERSION = '0.0.1'
35 |
36 | #
37 | # An instance of the Ruby core class +Logger+.
38 | # Used for logging by BibTeX-Ruby.
39 | #
40 | Log = Logger.new(STDERR)
41 | Log.level = ENV.has_key?('DEBUG') ? Logger::DEBUG : Logger::WARN
42 | Log.datetime_format = "%Y-%m-%d %H:%M:%S"
43 |
44 | end
45 |
46 | require File.expand_path(File.dirname(__FILE__)) + '/bibtex/string_replacement'
47 | require File.expand_path(File.dirname(__FILE__)) + '/bibtex/elements'
48 | require File.expand_path(File.dirname(__FILE__)) + '/bibtex/entry'
49 | require File.expand_path(File.dirname(__FILE__)) + '/bibtex/error'
50 | require File.expand_path(File.dirname(__FILE__)) + '/bibtex/parser'
51 | require File.expand_path(File.dirname(__FILE__)) + '/bibtex/bibliography'
52 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_random_search.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for random_search.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../random_search"
9 |
10 | class TC_RandomSearch < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | # optima
21 | assert_equal(0, objective_function([0,0]))
22 | end
23 |
24 | # test the generation of random vectors
25 | def test_random_vector
26 | bounds, trials, size = [-3,3], 300, 20
27 | minmax = Array.new(size) {bounds}
28 | trials.times do
29 | vector, sum = random_vector(minmax), 0.0
30 | assert_equal(size, vector.size)
31 | vector.each do |v|
32 | assert_operator(v, :>=, bounds[0])
33 | assert_operator(v, :<, bounds[1])
34 | sum += v
35 | end
36 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
37 | end
38 | end
39 |
40 | # helper for turning off STDOUT
41 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
42 | def silence_stream(stream)
43 | old_stream = stream.dup
44 | stream.reopen('/dev/null')
45 | stream.sync = true
46 | yield
47 | ensure
48 | stream.reopen(old_stream)
49 | end
50 |
51 | # test that the algorithm can solve the problem
52 | def test_search
53 | best = nil
54 | silence_stream(STDOUT) do
55 | best = search([[-5,5],[-5,5]], 100)
56 | end
57 | assert_not_nil(best[:cost])
58 | assert_in_delta(0.0, best[:cost], 1.0)
59 | end
60 |
61 | end
62 |
--------------------------------------------------------------------------------
/book/c_advanced.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 | % This is a chapter
6 |
7 | \renewcommand{\bibsection}{\subsection{\bibname}}
8 | \chapter{Advanced Topics}
9 | \label{ch:advanced}
10 | This chapter discusses a number of advanced topics that may be considered once one or more of the algorithms described in this book have been mastered.
11 |
12 | The topics in this section consider some practical concerns such as:
13 |
14 | \begin{itemize}
15 | \item How to implement an algorithm using a different programming paradigm (Section~\ref{advanced:sec:paradigms}).
16 | \item How to devise and investigate a new biologically-inspired algorithm (Section~\ref{advanced:sec:new_algorithms}).
17 | \item How to test algorithm implementations to ensure they are implemented correctly (Section~\ref{advanced:sec:testing_algorithms}).
18 | \item How to visualize problems, algorithm behavior and candidate solutions (Section~\ref{advanced:sec:visualizing_algorithms}).
19 | \item How to direct these algorithms toward practical problem solving (Section~\ref{advanced:sec:problem_solving}).
20 | \item Issues to consider when benchmarking and comparing the capabilities of algorithms (Section~\ref{advanced:sec:racing_algorithms}).
21 | \end{itemize}
22 |
23 | The objective of this chapter is to illustrate the concerns and skills necessary for taking the algorithms described in this book into the real-world.
24 |
25 | % sections
26 | \newpage\begin{bibunit}\input{book/c_advanced/paradigms}\putbib\end{bibunit}
27 | \newpage\begin{bibunit}\input{book/c_advanced/new_algorithms}\putbib\end{bibunit}
28 | \newpage\begin{bibunit}\input{book/c_advanced/testing_algorithms}\putbib\end{bibunit}
29 | \newpage\begin{bibunit}\input{book/c_advanced/visualizing_algorithms}\putbib\end{bibunit}
30 | \newpage\begin{bibunit}\input{book/c_advanced/problem_solving}\putbib\end{bibunit}
31 | \newpage\begin{bibunit}\input{book/c_advanced/racing_algorithms}\putbib\end{bibunit}
32 |
33 |
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/pbil.rb:
--------------------------------------------------------------------------------
1 | # Population-Based Incremental Learning in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def onemax(vector)
8 | return vector.inject(0){|sum, value| sum + value}
9 | end
10 |
11 | def generate_candidate(vector)
12 | candidate = {}
13 | candidate[:bitstring] = Array.new(vector.size)
14 | vector.each_with_index do |p, i|
15 | candidate[:bitstring][i] = (rand()current[:cost]
43 | best = candidate if best.nil? or candidate[:cost]>best[:cost]
44 | end
45 | update_vector(vector, current, l_rate)
46 | mutate_vector(vector, current, mut_factor, p_mutate)
47 | puts " >iteration=#{iter}, f=#{best[:cost]}, s=#{best[:bitstring]}"
48 | break if best[:cost] == num_bits
49 | end
50 | return best
51 | end
52 |
53 | if __FILE__ == $0
54 | # problem configuration
55 | num_bits = 64
56 | # algorithm configuration
57 | max_iter = 100
58 | num_samples = 100
59 | p_mutate = 1.0/num_bits
60 | mut_factor = 0.05
61 | l_rate = 0.1
62 | # execute the algorithm
63 | best=search(num_bits, max_iter, num_samples, p_mutate, mut_factor, l_rate)
64 | puts "done! Solution: f=#{best[:cost]}/#{num_bits}, s=#{best[:bitstring]}"
65 | end
66 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_stochastic_hill_climbing.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for stochastic_hill_climbing.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../stochastic_hill_climbing"
9 |
10 | class TC_StochasticHillClimbing < Test::Unit::TestCase
11 |
12 | # test that the objective function behaves as expected
13 | def test_onemax
14 | assert_equal(0, onemax(["0","0","0","0"]))
15 | assert_equal(4, onemax(["1","1","1","1"]))
16 | assert_equal(2, onemax(["1","0","1","0"]))
17 | end
18 |
19 | # test basic construction of random bitstrings
20 | def test_random_bitstring
21 | assert_equal(10, random_bitstring(10).size)
22 | assert_equal(10, random_bitstring(10).select{|x| x=='0' or x=='1'}.size)
23 | end
24 |
25 | # test the approximate proportion of 1's and 0's
26 | def test_random_bitstring_ratio
27 | s = random_bitstring(1000)
28 | assert_in_delta(0.5, (s.select{|x| x=='0'}.size/1000.0), 0.05)
29 | assert_in_delta(0.5, (s.select{|x| x=='1'}.size/1000.0), 0.05)
30 | end
31 |
32 | # test the construction of a random neighbour
33 | def test_random_neighbor
34 | parent = [0,0,0,0,0]
35 | 100.times do
36 | rs = random_neighbor(parent)
37 | assert_equal(parent.size, rs.size)
38 | assert_not_equal(parent, rs)
39 | assert_not_same(parent, rs)
40 | diffs = 0
41 | parent.each_index {|i| diffs += 1 if parent[i]!=rs[i]}
42 | assert(1, diffs)
43 | end
44 | end
45 |
46 | # helper for turning off STDOUT
47 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
48 | def silence_stream(stream)
49 | old_stream = stream.dup
50 | stream.reopen('/dev/null')
51 | stream.sync = true
52 | yield
53 | ensure
54 | stream.reopen(old_stream)
55 | end
56 |
57 | # test that the algorithm can solve the problem
58 | def test_search
59 | best = nil
60 | silence_stream(STDOUT) do
61 | best = search(100, 20)
62 | end
63 | assert_not_nil(best[:cost])
64 | assert_in_delta(20, best[:cost],3)
65 | end
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/book/c_physical.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 | % This is a chapter
6 |
7 | \renewcommand{\bibsection}{\subsection{\bibname}}
8 | \begin{bibunit}
9 |
10 | \chapter{Physical Algorithms}
11 | \label{ch:physical}
12 | \index{Physical Algorithms}
13 |
14 | \section{Overview}
15 | This chapter describes Physical Algorithms.
16 |
17 |
18 | % biological
19 | \subsection{Physical Properties}
20 | Physical algorithms are those algorithms inspired by a physical process. The described physical algorithm generally belong to the fields of Metaheustics and Computational Intelligence, although do not fit neatly into the existing categories of the biological inspired techniques (such as Swarm, Immune, Neural, and Evolution). In this vein, they could just as easily be referred to as nature inspired algorithms.
21 |
22 | The inspiring physical systems range from metallurgy, music, the interplay between culture and evolution, and complex dynamic systems such as avalanches. They are generally stochastic optimization algorithms with a mixtures of local (neighborhood-based) and global search techniques.
23 |
24 | %
25 | % Extensions
26 | %
27 | \subsection{Extensions}
28 | There are many other algorithms and classes of algorithm that were not described inspired by natural systems, not limited to:
29 |
30 | \begin{itemize}
31 | \item \textbf{More Annealing}: Extensions to the classical Simulated Annealing algorithm, such as Adaptive Simulated Annealing (formally Very Fast Simulated Re-annealing) \cite{Ingber1989, Ingber1996}, and Quantum Annealing \cite{Apolloni1989, Das2005}.
32 | \item \textbf{Stochastic tunneling}: based on the physical idea of a particle tunneling through structures \cite{Wenzel1999}.
33 | \end{itemize}
34 |
35 | \putbib
36 | \end{bibunit}
37 |
38 | \newpage\begin{bibunit}\input{book/a_physical/simulated_annealing}\putbib\end{bibunit}
39 | \newpage\begin{bibunit}\input{book/a_physical/extremal_optimization}\putbib\end{bibunit}
40 | \newpage\begin{bibunit}\input{book/a_physical/harmony_search}\putbib\end{bibunit}
41 | \newpage\begin{bibunit}\input{book/a_physical/cultural_algorithm}\putbib\end{bibunit}
42 | \newpage\begin{bibunit}\input{book/a_physical/memetic_algorithm}\putbib\end{bibunit}
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/umda.rb:
--------------------------------------------------------------------------------
1 | # Univariate Marginal Distribution Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def onemax(vector)
8 | return vector.inject(0){|sum, value| sum + value}
9 | end
10 |
11 | def random_bitstring(size)
12 | return Array.new(size){ ((rand()<0.5) ? 1 : 0) }
13 | end
14 |
15 | def binary_tournament(pop)
16 | i, j = rand(pop.size), rand(pop.size)
17 | j = rand(pop.size) while j==i
18 | return (pop[i][:fitness] > pop[j][:fitness]) ? pop[i] : pop[j]
19 | end
20 |
21 | def calculate_bit_probabilities(pop)
22 | vector = Array.new(pop.first[:bitstring].length, 0.0)
23 | pop.each do |member|
24 | member[:bitstring].each_with_index {|v, i| vector[i] += v}
25 | end
26 | vector.each_with_index {|f, i| vector[i] = (f.to_f/pop.size.to_f)}
27 | return vector
28 | end
29 |
30 | def generate_candidate(vector)
31 | candidate = {}
32 | candidate[:bitstring] = Array.new(vector.size)
33 | vector.each_with_index do |p, i|
34 | candidate[:bitstring][i] = (rand()
random_bitstring(num_bits)}
42 | end
43 | pop.each{|c| c[:fitness] = onemax(c[:bitstring])}
44 | best = pop.sort{|x,y| y[:fitness] <=> x[:fitness]}.first
45 | max_iter.times do |iter|
46 | selected = Array.new(select_size) { binary_tournament(pop) }
47 | vector = calculate_bit_probabilities(selected)
48 | samples = Array.new(pop_size) { generate_candidate(vector) }
49 | samples.each{|c| c[:fitness] = onemax(c[:bitstring])}
50 | samples.sort!{|x,y| y[:fitness] <=> x[:fitness]}
51 | best = samples.first if samples.first[:fitness] > best[:fitness]
52 | pop = samples
53 | puts " >iteration=#{iter}, f=#{best[:fitness]}, s=#{best[:bitstring]}"
54 | end
55 | return best
56 | end
57 |
58 | if __FILE__ == $0
59 | # problem configuration
60 | num_bits = 64
61 | # algorithm configuration
62 | max_iter = 100
63 | pop_size = 50
64 | select_size = 30
65 | # execute the algorithm
66 | best = search(num_bits, max_iter, pop_size, select_size)
67 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:bitstring]}"
68 | end
69 |
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/tests/tc_compact_genetic_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for compact_genetic_algorithm.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../compact_genetic_algorithm"
9 |
10 | class TC_CompactGeneticAlgorithm < Test::Unit::TestCase
11 |
12 | # test that the objective function behaves as expected
13 | def test_onemax
14 | assert_equal(0, onemax([0,0,0,0]))
15 | assert_equal(4, onemax([1,1,1,1]))
16 | assert_equal(2, onemax([1,0,1,0]))
17 | end
18 |
19 | # generate a candidate solution
20 | def test_generate_candidate
21 | # all 0
22 | s = generate_candidate(Array.new(1000){0})
23 | assert_not_nil(s)
24 | assert_not_nil(s[:cost])
25 | assert_equal(0, s[:cost])
26 | assert_equal(1000, s[:bitstring].length)
27 | # all 1
28 | s = generate_candidate(Array.new(1000){1})
29 | assert_not_nil(s)
30 | assert_not_nil(s[:cost])
31 | assert_equal(1000, s[:cost])
32 | assert_equal(1000, s[:bitstring].length)
33 | # all 50/50
34 | s = generate_candidate(Array.new(1000){0.5})
35 | assert_not_nil(s)
36 | assert_not_nil(s[:cost])
37 | assert_in_delta(500, s[:cost],50)
38 | assert_equal(1000, s[:bitstring].length)
39 | end
40 |
41 | # test vector updates
42 | def test_update_vector
43 | # update all bits
44 | vector = [0.5,0.5,0.5]
45 | update_vector(vector, {:bitstring=>[1,1,1]}, {:bitstring=>[0,0,0]}, 10)
46 | vector.each{|i| assert_equal(0.5+(0.1), vector[i])}
47 | # update no bits
48 | vector = [0.5,0.5,0.5]
49 | update_vector(vector, {:bitstring=>[1,1,1]}, {:bitstring=>[1,1,1]}, 10)
50 | vector.each{|i| assert_equal(0.5, vector[i])}
51 | end
52 |
53 | # helper for turning off STDOUT
54 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
55 | def silence_stream(stream)
56 | old_stream = stream.dup
57 | stream.reopen('/dev/null')
58 | stream.sync = true
59 | yield
60 | ensure
61 | stream.reopen(old_stream)
62 | end
63 |
64 | # test that the algorithm can solve the problem
65 | def test_search
66 | best = nil
67 | silence_stream(STDOUT) do
68 | best = search(20, 200, 20)
69 | end
70 | assert_not_nil(best[:cost])
71 | assert_in_delta(20, best[:cost],5)
72 | end
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/book/c_stochastic.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 | % This is a chapter
6 |
7 | \renewcommand{\bibsection}{\subsection{\bibname}}
8 | % \begin{bibunit}
9 |
10 | \chapter{Stochastic Algorithms}
11 | \label{ch:stochastic}
12 | \index{Stochastic Algorithms}
13 | \index{Stochastic Global Optimization}
14 |
15 | %
16 | % Overview
17 | %
18 | \section{Overview}
19 | % high level
20 | This chapter describes Stochastic Algorithms.
21 |
22 | \subsection{Stochastic Optimization}
23 | % differences
24 | The majority of the algorithms to be described in this book are comprised of probabilistic and stochastic processes. What differentiates the `stochastic algorithms' in this chapter from the remaining algorithms is the specific lack of 1) an inspiring system, and 2) a metaphorical explanation. Both `inspiration' and `metaphor' refer to the descriptive elements in the standardized algorithm description.
25 |
26 | % features
27 | These described algorithms are predominately global optimization algorithms and metaheuristics that manage the application of an embedded neighborhood exploring (local) search procedure. As such, with the exception of `Stochastic Hill Climbing' and `Random Search' the algorithms may be considered extensions of the multi-start search (also known as multi-restart search). This set of algorithms provide various different strategies by which `better' and varied starting points can be generated and issued to a neighborhood searching technique for refinement, a process that is repeated with potentially improving or unexplored areas to search.
28 |
29 | %
30 | % Algorithms, one per section
31 | %
32 | \newpage\begin{bibunit}\input{book/a_stochastic/random_search}\putbib\end{bibunit}
33 | \newpage\begin{bibunit}\input{book/a_stochastic/adaptive_random_search}\putbib\end{bibunit}
34 | \newpage\begin{bibunit}\input{book/a_stochastic/hill_climbing_search}\putbib\end{bibunit}
35 | \newpage\begin{bibunit}\input{book/a_stochastic/iterated_local_search}\putbib\end{bibunit}
36 | \newpage\begin{bibunit}\input{book/a_stochastic/guided_local_search}\putbib\end{bibunit}
37 | \newpage\begin{bibunit}\input{book/a_stochastic/variable_neighborhood_search}\putbib\end{bibunit}
38 | \newpage\begin{bibunit}\input{book/a_stochastic/grasp}\putbib\end{bibunit}
39 | \newpage\begin{bibunit}\input{book/a_stochastic/scatter_search}\putbib\end{bibunit}
40 | \newpage\begin{bibunit}\input{book/a_stochastic/tabu_search}\putbib\end{bibunit}
41 | \newpage\begin{bibunit}\input{book/a_stochastic/reactive_tabu_search}\putbib\end{bibunit}
42 |
--------------------------------------------------------------------------------
/src/algorithms/neural/perceptron.rb:
--------------------------------------------------------------------------------
1 | # Perceptron Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def random_vector(minmax)
8 | return Array.new(minmax.size) do |i|
9 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
10 | end
11 | end
12 |
13 | def initialize_weights(problem_size)
14 | minmax = Array.new(problem_size + 1) {[-1.0,1.0]}
15 | return random_vector(minmax)
16 | end
17 |
18 | def update_weights(num_inputs, weights, input, out_exp, out_act, l_rate)
19 | num_inputs.times do |i|
20 | weights[i] += l_rate * (out_exp - out_act) * input[i]
21 | end
22 | weights[num_inputs] += l_rate * (out_exp - out_act) * 1.0
23 | end
24 |
25 | def activate(weights, vector)
26 | sum = weights[weights.size-1] * 1.0
27 | vector.each_with_index do |input, i|
28 | sum += weights[i] * input
29 | end
30 | return sum
31 | end
32 |
33 | def transfer(activation)
34 | return (activation >= 0) ? 1.0 : 0.0
35 | end
36 |
37 | def get_output(weights, vector)
38 | activation = activate(weights, vector)
39 | return transfer(activation)
40 | end
41 |
42 | def train_weights(weights, domain, num_inputs, iterations, lrate)
43 | iterations.times do |epoch|
44 | error = 0.0
45 | domain.each do |pattern|
46 | input = Array.new(num_inputs) {|k| pattern[k].to_f}
47 | output = get_output(weights, input)
48 | expected = pattern.last.to_f
49 | error += (output - expected).abs
50 | update_weights(num_inputs, weights, input, expected, output, lrate)
51 | end
52 | puts "> epoch=#{epoch}, error=#{error}"
53 | end
54 | end
55 |
56 | def test_weights(weights, domain, num_inputs)
57 | correct = 0
58 | domain.each do |pattern|
59 | input_vector = Array.new(num_inputs) {|k| pattern[k].to_f}
60 | output = get_output(weights, input_vector)
61 | correct += 1 if output.round == pattern.last
62 | end
63 | puts "Finished test with a score of #{correct}/#{domain.size}"
64 | return correct
65 | end
66 |
67 | def execute(domain, num_inputs, iterations, learning_rate)
68 | weights = initialize_weights(num_inputs)
69 | train_weights(weights, domain, num_inputs, iterations, learning_rate)
70 | test_weights(weights, domain, num_inputs)
71 | return weights
72 | end
73 |
74 | if __FILE__ == $0
75 | # problem configuration
76 | or_problem = [[0,0,0], [0,1,1], [1,0,1], [1,1,1]]
77 | inputs = 2
78 | # algorithm configuration
79 | iterations = 20
80 | learning_rate = 0.1
81 | # execute the algorithm
82 | execute(or_problem, inputs, iterations, learning_rate)
83 | end
--------------------------------------------------------------------------------
/src/algorithms/physical/harmony_search.rb:
--------------------------------------------------------------------------------
1 | # Harmony Search in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def rand_in_bounds(min, max)
12 | return min + ((max-min) * rand())
13 | end
14 |
15 | def random_vector(search_space)
16 | return Array.new(search_space.size) do |i|
17 | rand_in_bounds(search_space[i][0], search_space[i][1])
18 | end
19 | end
20 |
21 | def create_random_harmony(search_space)
22 | harmony = {}
23 | harmony[:vector] = random_vector(search_space)
24 | harmony[:fitness] = objective_function(harmony[:vector])
25 | return harmony
26 | end
27 |
28 | def initialize_harmony_memory(search_space, mem_size, factor=3)
29 | memory = Array.new(mem_size*factor){create_random_harmony(search_space)}
30 | memory.sort!{|x,y| x[:fitness]<=>y[:fitness]}
31 | return memory.first(mem_size)
32 | end
33 |
34 | def create_harmony(search_space, memory, consid_rate, adjust_rate, range)
35 | vector = Array.new(search_space.size)
36 | search_space.size.times do |i|
37 | if rand() < consid_rate
38 | value = memory[rand(memory.size)][:vector][i]
39 | value = value + range*rand_in_bounds(-1.0, 1.0) if rand() search_space[i][1]
42 | vector[i] = value
43 | else
44 | vector[i] = rand_in_bounds(search_space[i][0], search_space[i][1])
45 | end
46 | end
47 | return {:vector=>vector}
48 | end
49 |
50 | def search(bounds, max_iter, mem_size, consid_rate, adjust_rate, range)
51 | memory = initialize_harmony_memory(bounds, mem_size)
52 | best = memory.first
53 | max_iter.times do |iter|
54 | harm = create_harmony(bounds, memory, consid_rate, adjust_rate, range)
55 | harm[:fitness] = objective_function(harm[:vector])
56 | best = harm if harm[:fitness] < best[:fitness]
57 | memory << harm
58 | memory.sort!{|x,y| x[:fitness]<=>y[:fitness]}
59 | memory.delete_at(memory.size-1)
60 | puts " > iteration=#{iter}, fitness=#{best[:fitness]}"
61 | end
62 | return best
63 | end
64 |
65 | if __FILE__ == $0
66 | # problem configuration
67 | problem_size = 3
68 | bounds = Array.new(problem_size) {|i| [-5, 5]}
69 | # algorithm configuration
70 | mem_size = 20
71 | consid_rate = 0.95
72 | adjust_rate = 0.7
73 | range = 0.05
74 | max_iter = 500
75 | # execute the algorithm
76 | best = search(bounds, max_iter, mem_size, consid_rate, adjust_rate, range)
77 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:vector].inspect}"
78 | end
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/genetic_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Genetic Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def onemax(bitstring)
8 | sum = 0
9 | bitstring.size.times {|i| sum+=1 if bitstring[i].chr=='1'}
10 | return sum
11 | end
12 |
13 | def random_bitstring(num_bits)
14 | return (0...num_bits).inject(""){|s,i| s<<((rand<0.5) ? "1" : "0")}
15 | end
16 |
17 | def binary_tournament(pop)
18 | i, j = rand(pop.size), rand(pop.size)
19 | j = rand(pop.size) while j==i
20 | return (pop[i][:fitness] > pop[j][:fitness]) ? pop[i] : pop[j]
21 | end
22 |
23 | def point_mutation(bitstring, rate=1.0/bitstring.size)
24 | child = ""
25 | bitstring.size.times do |i|
26 | bit = bitstring[i].chr
27 | child << ((rand()=rate
34 | point = 1 + rand(parent1.size-2)
35 | return parent1[0...point]+parent2[point...(parent1.size)]
36 | end
37 |
38 | def reproduce(selected, pop_size, p_cross, p_mutation)
39 | children = []
40 | selected.each_with_index do |p1, i|
41 | p2 = (i.modulo(2)==0) ? selected[i+1] : selected[i-1]
42 | p2 = selected[0] if i == selected.size-1
43 | child = {}
44 | child[:bitstring] = crossover(p1[:bitstring], p2[:bitstring], p_cross)
45 | child[:bitstring] = point_mutation(child[:bitstring], p_mutation)
46 | children << child
47 | break if children.size >= pop_size
48 | end
49 | return children
50 | end
51 |
52 | def search(max_gens, num_bits, pop_size, p_crossover, p_mutation)
53 | population = Array.new(pop_size) do |i|
54 | {:bitstring=>random_bitstring(num_bits)}
55 | end
56 | population.each{|c| c[:fitness] = onemax(c[:bitstring])}
57 | best = population.sort{|x,y| y[:fitness] <=> x[:fitness]}.first
58 | max_gens.times do |gen|
59 | selected = Array.new(pop_size){|i| binary_tournament(population)}
60 | children = reproduce(selected, pop_size, p_crossover, p_mutation)
61 | children.each{|c| c[:fitness] = onemax(c[:bitstring])}
62 | children.sort!{|x,y| y[:fitness] <=> x[:fitness]}
63 | best = children.first if children.first[:fitness] >= best[:fitness]
64 | population = children
65 | puts " > gen #{gen}, best: #{best[:fitness]}, #{best[:bitstring]}"
66 | break if best[:fitness] == num_bits
67 | end
68 | return best
69 | end
70 |
71 | if __FILE__ == $0
72 | # problem configuration
73 | num_bits = 64
74 | # algorithm configuration
75 | max_gens = 100
76 | pop_size = 100
77 | p_crossover = 0.98
78 | p_mutation = 1.0/num_bits
79 | # execute the algorithm
80 | best = search(max_gens, num_bits, pop_size, p_crossover, p_mutation)
81 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:bitstring]}"
82 | end
83 |
--------------------------------------------------------------------------------
/src/algorithms/swarm/bees_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Bees Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_vector(minmax)
12 | return Array.new(minmax.size) do |i|
13 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
14 | end
15 | end
16 |
17 | def create_random_bee(search_space)
18 | return {:vector=>random_vector(search_space)}
19 | end
20 |
21 | def create_neigh_bee(site, patch_size, search_space)
22 | vector = []
23 | site.each_with_index do |v,i|
24 | v = (rand()<0.5) ? v+rand()*patch_size : v-rand()*patch_size
25 | v = search_space[i][0] if v < search_space[i][0]
26 | v = search_space[i][1] if v > search_space[i][1]
27 | vector << v
28 | end
29 | bee = {}
30 | bee[:vector] = vector
31 | return bee
32 | end
33 |
34 | def search_neigh(parent, neigh_size, patch_size, search_space)
35 | neigh = []
36 | neigh_size.times do
37 | neigh << create_neigh_bee(parent[:vector], patch_size, search_space)
38 | end
39 | neigh.each{|bee| bee[:fitness] = objective_function(bee[:vector])}
40 | return neigh.sort{|x,y| x[:fitness]<=>y[:fitness]}.first
41 | end
42 |
43 | def create_scout_bees(search_space, num_scouts)
44 | return Array.new(num_scouts) do
45 | create_random_bee(search_space)
46 | end
47 | end
48 |
49 | def search(max_gens, search_space, num_bees, num_sites, elite_sites, patch_size, e_bees, o_bees)
50 | best = nil
51 | pop = Array.new(num_bees){ create_random_bee(search_space) }
52 | max_gens.times do |gen|
53 | pop.each{|bee| bee[:fitness] = objective_function(bee[:vector])}
54 | pop.sort!{|x,y| x[:fitness]<=>y[:fitness]}
55 | best = pop.first if best.nil? or pop.first[:fitness] < best[:fitness]
56 | next_gen = []
57 | pop[0...num_sites].each_with_index do |parent, i|
58 | neigh_size = (i it=#{gen+1}, patch_size=#{patch_size}, f=#{best[:fitness]}"
65 | end
66 | return best
67 | end
68 |
69 | if __FILE__ == $0
70 | # problem configuration
71 | problem_size = 3
72 | search_space = Array.new(problem_size) {|i| [-5, 5]}
73 | # algorithm configuration
74 | max_gens = 500
75 | num_bees = 45
76 | num_sites = 3
77 | elite_sites = 1
78 | patch_size = 3.0
79 | e_bees = 7
80 | o_bees = 2
81 | # execute the algorithm
82 | best = search(max_gens, search_space, num_bees, num_sites, elite_sites, patch_size, e_bees, o_bees)
83 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:vector].inspect}"
84 | end
85 |
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/differential_evolution.rb:
--------------------------------------------------------------------------------
1 | # Differential Evolution in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_vector(minmax)
12 | return Array.new(minmax.size) do |i|
13 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
14 | end
15 | end
16 |
17 | def de_rand_1_bin(p0, p1, p2, p3, f, cr, search_space)
18 | sample = {:vector=>Array.new(p0[:vector].size)}
19 | cut = rand(sample[:vector].size-1) + 1
20 | sample[:vector].each_index do |i|
21 | sample[:vector][i] = p0[:vector][i]
22 | if (i==cut or rand() < cr)
23 | v = p3[:vector][i] + f * (p1[:vector][i] - p2[:vector][i])
24 | v = search_space[i][0] if v < search_space[i][0]
25 | v = search_space[i][1] if v > search_space[i][1]
26 | sample[:vector][i] = v
27 | end
28 | end
29 | return sample
30 | end
31 |
32 | def select_parents(pop, current)
33 | p1, p2, p3 = rand(pop.size), rand(pop.size), rand(pop.size)
34 | p1 = rand(pop.size) until p1 != current
35 | p2 = rand(pop.size) until p2 != current and p2 != p1
36 | p3 = rand(pop.size) until p3 != current and p3 != p1 and p3 != p2
37 | return [p1,p2,p3]
38 | end
39 |
40 | def create_children(pop, minmax, f, cr)
41 | children = []
42 | pop.each_with_index do |p0, i|
43 | p1, p2, p3 = select_parents(pop, i)
44 | children << de_rand_1_bin(p0, pop[p1], pop[p2], pop[p3], f, cr, minmax)
45 | end
46 | return children
47 | end
48 |
49 | def select_population(parents, children)
50 | return Array.new(parents.size) do |i|
51 | (children[i][:cost]<=parents[i][:cost]) ? children[i] : parents[i]
52 | end
53 | end
54 |
55 | def search(max_gens, search_space, pop_size, f, cr)
56 | pop = Array.new(pop_size) {|i| {:vector=>random_vector(search_space)}}
57 | pop.each{|c| c[:cost] = objective_function(c[:vector])}
58 | best = pop.sort{|x,y| x[:cost] <=> y[:cost]}.first
59 | max_gens.times do |gen|
60 | children = create_children(pop, search_space, f, cr)
61 | children.each{|c| c[:cost] = objective_function(c[:vector])}
62 | pop = select_population(pop, children)
63 | pop.sort!{|x,y| x[:cost] <=> y[:cost]}
64 | best = pop.first if pop.first[:cost] < best[:cost]
65 | puts " > gen #{gen+1}, fitness=#{best[:cost]}"
66 | end
67 | return best
68 | end
69 |
70 | if __FILE__ == $0
71 | # problem configuration
72 | problem_size = 3
73 | search_space = Array.new(problem_size) {|i| [-5, +5]}
74 | # algorithm configuration
75 | max_gens = 200
76 | pop_size = 10*problem_size
77 | weightf = 0.8
78 | crossf = 0.9
79 | # execute the algorithm
80 | best = search(max_gens, search_space, pop_size, weightf, crossf)
81 | puts "done! Solution: f=#{best[:cost]}, s=#{best[:vector].inspect}"
82 | end
83 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/adaptive_random_search.rb:
--------------------------------------------------------------------------------
1 | # Adaptive Random Search in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def rand_in_bounds(min, max)
12 | return min + ((max-min) * rand())
13 | end
14 |
15 | def random_vector(minmax)
16 | return Array.new(minmax.size) do |i|
17 | rand_in_bounds(minmax[i][0], minmax[i][1])
18 | end
19 | end
20 |
21 | def take_step(minmax, current, step_size)
22 | position = Array.new(current.size)
23 | position.size.times do |i|
24 | min = [minmax[i][0], current[i]-step_size].max
25 | max = [minmax[i][1], current[i]+step_size].min
26 | position[i] = rand_in_bounds(min, max)
27 | end
28 | return position
29 | end
30 |
31 | def large_step_size(iter, step_size, s_factor, l_factor, iter_mult)
32 | return step_size * l_factor if iter>0 and iter.modulo(iter_mult) == 0
33 | return step_size * s_factor
34 | end
35 |
36 | def take_steps(bounds, current, step_size, big_stepsize)
37 | step, big_step = {}, {}
38 | step[:vector] = take_step(bounds, current[:vector], step_size)
39 | step[:cost] = objective_function(step[:vector])
40 | big_step[:vector] = take_step(bounds,current[:vector],big_stepsize)
41 | big_step[:cost] = objective_function(big_step[:vector])
42 | return step, big_step
43 | end
44 |
45 | def search(max_iter, bounds, init_factor, s_factor, l_factor, iter_mult, max_no_impr)
46 | step_size = (bounds[0][1]-bounds[0][0]) * init_factor
47 | current, count = {}, 0
48 | current[:vector] = random_vector(bounds)
49 | current[:cost] = objective_function(current[:vector])
50 | max_iter.times do |iter|
51 | big_stepsize = large_step_size(iter, step_size, s_factor, l_factor, iter_mult)
52 | step, big_step = take_steps(bounds, current, step_size, big_stepsize)
53 | if step[:cost] <= current[:cost] or big_step[:cost] <= current[:cost]
54 | if big_step[:cost] <= step[:cost]
55 | step_size, current = big_stepsize, big_step
56 | else
57 | current = step
58 | end
59 | count = 0
60 | else
61 | count += 1
62 | count, step_size = 0, (step_size/s_factor) if count >= max_no_impr
63 | end
64 | puts " > iteration #{(iter+1)}, best=#{current[:cost]}"
65 | end
66 | return current
67 | end
68 |
69 | if __FILE__ == $0
70 | # problem configuration
71 | problem_size = 2
72 | bounds = Array.new(problem_size) {|i| [-5, +5]}
73 | # algorithm configuration
74 | max_iter = 1000
75 | init_factor = 0.05
76 | s_factor = 1.3
77 | l_factor = 3.0
78 | iter_mult = 10
79 | max_no_impr = 30
80 | # execute the algorithm
81 | best = search(max_iter, bounds, init_factor, s_factor, l_factor, iter_mult, max_no_impr)
82 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
83 | end
84 |
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/cross_entropy_method.rb:
--------------------------------------------------------------------------------
1 | # Cross-Entropy Method algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_variable(minmax)
12 | min, max = minmax
13 | return min + ((max - min) * rand())
14 | end
15 |
16 | def random_gaussian(mean=0.0, stdev=1.0)
17 | u1 = u2 = w = 0
18 | begin
19 | u1 = 2 * rand() - 1
20 | u2 = 2 * rand() - 1
21 | w = u1 * u1 + u2 * u2
22 | end while w >= 1
23 | w = Math.sqrt((-2.0 * Math.log(w)) / w)
24 | return mean + (u2 * w) * stdev
25 | end
26 |
27 | def generate_sample(search_space, means, stdevs)
28 | vector = Array.new(search_space.size)
29 | search_space.size.times do |i|
30 | vector[i] = random_gaussian(means[i], stdevs[i])
31 | vector[i] = search_space[i][0] if vector[i] < search_space[i][0]
32 | vector[i] = search_space[i][1] if vector[i] > search_space[i][1]
33 | end
34 | return {:vector=>vector}
35 | end
36 |
37 | def mean_attr(samples, i)
38 | sum = samples.inject(0.0) do |s,sample|
39 | s + sample[:vector][i]
40 | end
41 | return (sum / samples.size.to_f)
42 | end
43 |
44 | def stdev_attr(samples, mean, i)
45 | sum = samples.inject(0.0) do |s,sample|
46 | s + (sample[:vector][i] - mean)**2.0
47 | end
48 | return Math.sqrt(sum / samples.size.to_f)
49 | end
50 |
51 | def update_distribution!(samples, alpha, means, stdevs)
52 | means.size.times do |i|
53 | means[i] = alpha*means[i] + ((1.0-alpha)*mean_attr(samples, i))
54 | stdevs[i] = alpha*stdevs[i]+((1.0-alpha)*stdev_attr(samples,means[i],i))
55 | end
56 | end
57 |
58 | def search(bounds, max_iter, num_samples, num_update, learning_rate)
59 | means = Array.new(bounds.size){|i| random_variable(bounds[i])}
60 | stdevs = Array.new(bounds.size){|i| bounds[i][1]-bounds[i][0]}
61 | best = nil
62 | max_iter.times do |iter|
63 | samples = Array.new(num_samples){generate_sample(bounds, means, stdevs)}
64 | samples.each {|samp| samp[:cost] = objective_function(samp[:vector])}
65 | samples.sort!{|x,y| x[:cost]<=>y[:cost]}
66 | best = samples.first if best.nil? or samples.first[:cost] < best[:cost]
67 | selected = samples.first(num_update)
68 | update_distribution!(selected, learning_rate, means, stdevs)
69 | puts " > iteration=#{iter}, fitness=#{best[:cost]}"
70 | end
71 | return best
72 | end
73 |
74 | if __FILE__ == $0
75 | # problem configuration
76 | problem_size = 3
77 | search_space = Array.new(problem_size) {|i| [-5, 5]}
78 | # algorithm configuration
79 | max_iter = 100
80 | num_samples = 50
81 | num_update = 5
82 | l_rate = 0.7
83 | # execute the algorithm
84 | best = search(search_space, max_iter, num_samples, num_update, l_rate)
85 | puts "done! Solution: f=#{best[:cost]}, s=#{best[:vector].inspect}"
86 | end
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/tests/tc_pbil.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for pbil.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../pbil"
9 |
10 | class TC_PBIL < Test::Unit::TestCase
11 |
12 | # test that the objective function behaves as expected
13 | def test_onemax
14 | assert_equal(0, onemax([0,0,0,0]))
15 | assert_equal(4, onemax([1,1,1,1]))
16 | assert_equal(2, onemax([1,0,1,0]))
17 | end
18 |
19 | # generate a candidate solution
20 | def test_generate_candidate
21 | # all 0
22 | s = generate_candidate(Array.new(1000){0})
23 | assert_not_nil(s)
24 | assert_equal(1000, s[:bitstring].length)
25 | s[:bitstring].each{|x| assert_equal(0, x)}
26 | # all 1
27 | s = generate_candidate(Array.new(1000){1})
28 | assert_not_nil(s)
29 | assert_equal(1000, s[:bitstring].length)
30 | s[:bitstring].each{|x| assert_equal(1, x)}
31 | # all 50/50
32 | s = generate_candidate(Array.new(1000){0.5})
33 | assert_not_nil(s)
34 | assert_equal(1000, s[:bitstring].length)
35 | assert_in_delta(500, s[:bitstring].inject(0){|sum,x| sum+x}, 50)
36 | end
37 |
38 | # test updating the vector
39 | def test_update_vector
40 | # no update, no change
41 | vector = Array.new(1000){0.5}
42 | update_vector(vector, {:bitstring=>Array.new(1000){0}}, 0.0)
43 | vector.each{|x| assert_equal(0.5, x)}
44 | # no update, decay
45 | vector = Array.new(1000){0.5}
46 | update_vector(vector, {:bitstring=>Array.new(1000){0}}, 0.5)
47 | vector.each{|x| assert_equal(0.5*0.5, x)}
48 | # update
49 | vector = Array.new(1000){0.5}
50 | update_vector(vector, {:bitstring=>Array.new(1000){0.8}}, 0.5)
51 | vector.each{|x| assert_equal(0.5*0.5+0.8*0.5, x)}
52 | end
53 |
54 | # test mutating the vector
55 | def test_mutate_vector
56 | # no change
57 | vector = Array.new(1000){0.5}
58 | mutate_vector(vector, {:bitstring=>Array.new(1000){0}}, 0.5, 0.0)
59 | vector.each{|x| assert_equal(0.5, x)}
60 | # all change
61 | vector = Array.new(1000){0.5}
62 | mutate_vector(vector, {:bitstring=>Array.new(1000){0}}, 0.5, 1.0)
63 | vector.each do |x|
64 | assert_operator(x, :<=, 1.0)
65 | assert_operator(x, :>, 0.0)
66 | end
67 | end
68 |
69 | # helper for turning off STDOUT
70 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
71 | def silence_stream(stream)
72 | old_stream = stream.dup
73 | stream.reopen('/dev/null')
74 | stream.sync = true
75 | yield
76 | ensure
77 | stream.reopen(old_stream)
78 | end
79 |
80 | # test that the algorithm can solve the problem
81 | def test_search
82 | best = nil
83 | silence_stream(STDOUT) do
84 | best = search(20, 100, 100, 1.0/20.0, 0.05, 0.1)
85 | end
86 | assert_not_nil(best[:cost])
87 | assert_equal(20, best[:cost])
88 | end
89 |
90 | end
91 |
--------------------------------------------------------------------------------
/book/b_errata.tex:
--------------------------------------------------------------------------------
1 | %
2 | % Errata
3 | %
4 |
5 | \chapter*{Errata\markboth{Errata}{}}
6 | \addcontentsline{toc}{part}{Errata}
7 |
8 | \section*{Revision 2}
9 | \begin{small}
10 |
11 | \begin{description}
12 | \item[page 43--47] Typo's of Multi-Restart. Thanks to Stephan Williams.
13 | \end{description}
14 |
15 | \end{small}
16 |
17 |
18 | \section*{Revision 1}
19 |
20 | \begin{small}
21 |
22 | \begin{description}
23 | \item[page 9] Typo in Metaheuristics section of the Introduction. Thanks to Leif Wickland.
24 | \item[page 11] Typo in Function Optimization section of the Introduction. Thanks to John Wise and Patrick Boehnke.
25 | \item[page 11] Typo's in the Function Approximation section of the Introduction. Thanks to Patrick Boehnke.
26 | \item[page 13] Typo in the Function Approximation section of the Introduction. Thanks to Patrick Boehnke.
27 | \item[page 32] Typo in References section of Random Search.
28 | \item[page 37] Fixed bug with \texttt{step\_size} in Adaptive Random Search implementation. Thanks to Zach Scott.
29 | \item[page 43] Typo in Taxonomy section of Iterated Local Search. Thanks to Diego Noble.
30 | \item[page 69] Bug in \texttt{recombine} function of the Scatter Search algorithm. Thanks to Markus Stokmaier.
31 | \item[page 111] Bug in the \texttt{init\_population} function of the Evolution Strategies algorithm. Thanks to Lai Yu-Hsuan.
32 | \item[page 129] Bug in the \texttt{one\_point\_crossover} function of the Grammatical Evolution implementation. Thanks to Mark Chenoweth.
33 | \item[page 234] Fixed ambiguous pseudo code description of Particle Swarm Optimization. Thanks to Stefan Pauleweit.
34 | \item[page 235] Fixed a bug in the \texttt{get\_global\_best} function of the Particle Swarm Optimization implementation. Thanks to Paul Chinnery.
35 | \item[page 237] Improved reference 3 for Particle Swarm Optimization. Thanks to Diego Noble.
36 | \item[page 242] Fixed a bug in the \texttt{search} function of the Ant System implementation. Thanks to Andrew Myers.
37 | \item[page 330] Typo in taxonomy of LVQ algorithm. Thanks to Jason Davies.
38 | \item[page 393] Typo in Function Approximation section. Thanks to Diego Noble.
39 | \item[page 400] Typo in subsection 9.6.1. Thanks to Diego Noble.
40 | \item[page 402] Typo in subsection 9.6.2. Thanks to Diego Noble.
41 | \item[page 415] Changed equality to assignment in Ruby flow control example in Appendix A. Thanks to Donald Doherty.
42 | \item[page 413] Typo in Overview section in Appendix A. Thanks to Martin-Louis Bright.
43 | \item[page 413] Typo in Ruby Files section in Appendix A. Thanks to Brook Tamir.
44 | \item[page 414] Typos in Variables section in Appendix A. Thanks to Brook Tamir.
45 | \item[page 415] Typos in Flow Control and Arrays sections in Appendix A. Thanks to Brook Tamir.
46 | \item[page 416] Typos in Arrays and Function and Blocks sections in Appendix A. Thanks to Brook Tamir.
47 | \item[page 417] Typos in Function and Blocks section in Appendix A. Thanks to Brook Tamir.
48 | \item[page 418] Typos in Enumerating section in Appendix A. Thanks to Brook Tamir.
49 | \item[page 419] Typo in Conclusions section in Appendix A. Thanks to Brook Tamir.
50 | \end{description}
51 |
52 | \end{small}
53 |
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/tests/tc_genetic_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for genetic_algorithm.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../genetic_algorithm"
9 |
10 | class TC_GeneticAlgorithm < Test::Unit::TestCase
11 |
12 | # test that the objective function behaves as expected
13 | def test_onemax
14 | assert_equal(0, onemax("0000"))
15 | assert_equal(4, onemax("1111"))
16 | assert_equal(2, onemax("1010"))
17 | end
18 |
19 | # test the creation of random strings
20 | def test_random_bitstring
21 | assert_equal(10, random_bitstring(10).size)
22 | assert_equal(0, random_bitstring(10).delete('0').delete('1').size)
23 | end
24 |
25 | # test the approximate proportion of 1's and 0's
26 | def test_random_bitstring_ratio
27 | s = random_bitstring(1000)
28 | assert_in_delta(0.5, (s.delete('1').size/1000.0), 0.05)
29 | assert_in_delta(0.5, (s.delete('0').size/1000.0), 0.05)
30 | end
31 |
32 | # test that members of the population are selected
33 | def test_binary_tournament
34 | pop = Array.new(10) {|i| {:fitness=>i} }
35 | 10.times {assert(pop.include?(binary_tournament(pop)))}
36 | end
37 |
38 | # test point mutations at the limits
39 | def test_point_mutation
40 | assert_equal("0000000000", point_mutation("0000000000", 0))
41 | assert_equal("1111111111", point_mutation("1111111111", 0))
42 | assert_equal("1111111111", point_mutation("0000000000", 1))
43 | assert_equal("0000000000", point_mutation("1111111111", 1))
44 | end
45 |
46 | # test that the observed changes approximate the intended probability
47 | def test_point_mutation_ratio
48 | changes = 0
49 | 100.times do
50 | s = point_mutation("0000000000", 0.5)
51 | changes += (10 - s.delete('1').size)
52 | end
53 | assert_in_delta(0.5, changes.to_f/(100*10), 0.05)
54 | end
55 |
56 | # test cloning with crossover
57 | def test_crossover_clone
58 | p1, p2 = "0000000000", "1111111111"
59 | 100.times do
60 | s = crossover(p1, p2, 0)
61 | assert_equal(p1, s)
62 | assert_not_same(p1, s)
63 | end
64 | end
65 |
66 | # test recombination with crossover
67 | def test_crossover_recombine
68 | p1, p2 = "0000000000", "1111111111"
69 | 100.times do
70 | s = crossover(p1, p2, 1)
71 | assert_equal(p1.size, s.size)
72 | assert_not_equal(p1, s)
73 | assert_not_equal(p2, s)
74 | s.size.times {|i| assert( (p1[i]==s[i]) || (p2[i]==s[i]) ) }
75 | end
76 | end
77 |
78 | # test odd sized population
79 | def test_reproduce_odd
80 | pop = Array.new(9) {|i| {:fitness=>i,:bitstring=>"0000000000"} }
81 | children = reproduce(pop, pop.size, 0, 1)
82 | assert_equal(9, children.size)
83 | end
84 |
85 | # test reproduce size mismatch
86 | def test_reproduce_mismatch
87 | pop = Array.new(10) {|i| {:fitness=>i,:bitstring=>"0000000000"} }
88 | children = reproduce(pop, 9, 0, 0)
89 | assert_equal(9, children.size)
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/src/algorithms/immune/negative_selection_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Negative Selection Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def random_vector(minmax)
8 | return Array.new(minmax.length) do |i|
9 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
10 | end
11 | end
12 |
13 | def euclidean_distance(c1, c2)
14 | sum = 0.0
15 | c1.each_index {|i| sum += (c1[i]-c2[i])**2.0}
16 | return Math.sqrt(sum)
17 | end
18 |
19 | def contains?(vector, space)
20 | vector.each_with_index do |v,i|
21 | return false if vspace[i][1]
22 | end
23 | return true
24 | end
25 |
26 | def matches?(vector, dataset, min_dist)
27 | dataset.each do |pattern|
28 | dist = euclidean_distance(vector, pattern[:vector])
29 | return true if dist <= min_dist
30 | end
31 | return false
32 | end
33 |
34 | def generate_detectors(max_detectors, search_space, self_dataset, min_dist)
35 | detectors = []
36 | begin
37 | detector = {:vector=>random_vector(search_space)}
38 | if !matches?(detector[:vector], self_dataset, min_dist)
39 | detectors << detector if !matches?(detector[:vector], detectors, 0.0)
40 | end
41 | end while detectors.size < max_detectors
42 | return detectors
43 | end
44 |
45 | def generate_self_dataset(num_records, self_space, search_space)
46 | self_dataset = []
47 | begin
48 | pattern = {}
49 | pattern[:vector] = random_vector(search_space)
50 | next if matches?(pattern[:vector], self_dataset, 0.0)
51 | if contains?(pattern[:vector], self_space)
52 | self_dataset << pattern
53 | end
54 | end while self_dataset.length < num_records
55 | return self_dataset
56 | end
57 |
58 | def apply_detectors(detectors, bounds, self_dataset, min_dist, trials=50)
59 | correct = 0
60 | trials.times do |i|
61 | input = {:vector=>random_vector(bounds)}
62 | actual = matches?(input[:vector], detectors, min_dist) ? "N" : "S"
63 | expected = matches?(input[:vector], self_dataset, min_dist) ? "S" : "N"
64 | correct += 1 if actual==expected
65 | puts "#{i+1}/#{trials}: predicted=#{actual}, expected=#{expected}"
66 | end
67 | puts "Done. Result: #{correct}/#{trials}"
68 | return correct
69 | end
70 |
71 | def execute(bounds, self_space, max_detect, max_self, min_dist)
72 | self_dataset = generate_self_dataset(max_self, self_space, bounds)
73 | puts "Done: prepared #{self_dataset.size} self patterns."
74 | detectors = generate_detectors(max_detect, bounds, self_dataset, min_dist)
75 | puts "Done: prepared #{detectors.size} detectors."
76 | apply_detectors(detectors, bounds, self_dataset, min_dist)
77 | return detectors
78 | end
79 |
80 | if __FILE__ == $0
81 | # problem configuration
82 | problem_size = 2
83 | search_space = Array.new(problem_size) {[0.0, 1.0]}
84 | self_space = Array.new(problem_size) {[0.5, 1.0]}
85 | max_self = 150
86 | # algorithm configuration
87 | max_detectors = 300
88 | min_dist = 0.05
89 | # execute the algorithm
90 | execute(search_space, self_space, max_detectors, max_self, min_dist)
91 | end
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | # (c) Copyright 2011 Jason Brownlee. Some Rights Reserved.
3 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 | # Project Makefile
6 |
7 | # constants
8 | BOOK=$(CURDIR)/book
9 | WEB=$(CURDIR)/web
10 |
11 | # Build the PDF for the paperback for development
12 | r:
13 | pdflatex -halt-on-error -interaction=errorstopmode -output-directory ${BOOK} book.tex 1> ${BOOK}/book.log 2>&1;true
14 | makeindex ${BOOK}/book;true
15 | pdflatex -halt-on-error -interaction=errorstopmode -output-directory ${BOOK} book.tex 1> ${BOOK}/book.log 2>&1;true
16 | pdflatex -halt-on-error -interaction=errorstopmode -output-directory ${BOOK} book.tex 1> ${BOOK}/book.log 2>&1;true
17 | for file in ${BOOK}/bu*.aux ; do \
18 | bibtex $$file 2>&1 1>${BOOK}/book.log ; \
19 | done
20 | pdflatex -halt-on-error -interaction=errorstopmode -output-directory ${BOOK} book.tex 1> ${BOOK}/book.log 2>&1;true
21 | pdflatex -halt-on-error -interaction=errorstopmode -output-directory ${BOOK} book.tex 1> ${BOOK}/book.log 2>&1;true
22 | grep -i "undefined" ${BOOK}/book.log;true
23 | # grep -i "warning" ${BOOK}/book.log;true
24 | grep -i "error" ${BOOK}/book.log;true
25 |
26 | # Build the PDF for lulu
27 | lulu: r
28 | ps2pdf13 -dPDFSETTINGS=/prepress ${BOOK}/book.pdf ${BOOK}/book-lulu.pdf
29 |
30 | # clean the project
31 | clean:
32 | rm -rf ${BOOK}/*.pdf ${BOOK}/*.aux ${BOOK}/*.log ${BOOK}/*.out ${BOOK}/*.toc \
33 | ${BOOK}/*.idx ${BOOK}/*.ilg ${BOOK}/*.ind ${BOOK}/*.bak ${BOOK}/*.bbl ${BOOK}/*.blg
34 | rm -rf ${WEB}/docs ${WEB}/epub_temp
35 | rm -rf *.epub
36 | rm -rf tests.log
37 |
38 | # View the development PDF on Linux
39 | vl:
40 | acroread ${BOOK}/book.pdf 2>&1 1>/dev/null &
41 |
42 | # View the development PDF on Mac
43 | vm:
44 | open -a Preview ${BOOK}/book.pdf
45 |
46 | # run jabref on my linux workstation
47 | jl:
48 | java -jar /opt/jabref/JabRef-2.7.2.jar 2>1 1>/dev/null &
49 |
50 | # create the webpage version for CleverAlgorithms.com
51 | web: ${WEB}/generate.rb
52 | ruby ${WEB}/generate.rb
53 |
54 | # create epub version for iphone/ipad and friends
55 | epub: ${WEB}/generate_epub.rb
56 | ruby ${WEB}/generate_epub.rb
57 |
58 | # unit test ruby source code - DRY this up a bit
59 | test:
60 | rm -rf tests.log
61 | echo "testing..."
62 | for file in src/algorithms/evolutionary/tests/* ; do \
63 | ruby $$file | tee -a tests.log ; \
64 | done
65 | for file in src/algorithms/immune/tests/* ; do \
66 | ruby $$file | tee -a tests.log ; \
67 | done
68 | for file in src/algorithms/neural/tests/* ; do \
69 | ruby $$file | tee -a tests.log ; \
70 | done
71 | for file in src/algorithms/physical/tests/* ; do \
72 | ruby $$file | tee -a tests.log ; \
73 | done
74 | for file in src/algorithms/probabilistic/tests/* ; do \
75 | ruby $$file | tee -a tests.log ; \
76 | done
77 | for file in src/algorithms/stochastic/tests/* ; do \
78 | ruby $$file | tee -a tests.log ; \
79 | done
80 | for file in src/algorithms/swarm/tests/* ; do \
81 | ruby $$file | tee -a tests.log ; \
82 | done
83 | for file in src/programming_paradigms/tests/* ; do \
84 | ruby $$file | tee -a tests.log ; \
85 | done
86 | echo "DONE"
87 | cat tests.log | grep -E ' Error:| Failure:|No such file or directory'
88 |
--------------------------------------------------------------------------------
/src/algorithms/physical/simulated_annealing.rb:
--------------------------------------------------------------------------------
1 | # Simulated Annealing in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def cost(permutation, cities)
12 | distance =0
13 | permutation.each_with_index do |c1, i|
14 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
15 | distance += euc_2d(cities[c1], cities[c2])
16 | end
17 | return distance
18 | end
19 |
20 | def random_permutation(cities)
21 | perm = Array.new(cities.size){|i| i}
22 | perm.each_index do |i|
23 | r = rand(perm.size-i) + i
24 | perm[r], perm[i] = perm[i], perm[r]
25 | end
26 | return perm
27 | end
28 |
29 | def stochastic_two_opt!(perm)
30 | c1, c2 = rand(perm.size), rand(perm.size)
31 | exclude = [c1]
32 | exclude << ((c1==0) ? perm.size-1 : c1-1)
33 | exclude << ((c1==perm.size-1) ? 0 : c1+1)
34 | c2 = rand(perm.size) while exclude.include?(c2)
35 | c1, c2 = c2, c1 if c2 < c1
36 | perm[c1...c2] = perm[c1...c2].reverse
37 | return perm
38 | end
39 |
40 | def create_neighbor(current, cities)
41 | candidate = {}
42 | candidate[:vector] = Array.new(current[:vector])
43 | stochastic_two_opt!(candidate[:vector])
44 | candidate[:cost] = cost(candidate[:vector], cities)
45 | return candidate
46 | end
47 |
48 | def should_accept?(candidate, current, temp)
49 | return true if candidate[:cost] <= current[:cost]
50 | return Math.exp((current[:cost] - candidate[:cost]) / temp) > rand()
51 | end
52 |
53 | def search(cities, max_iter, max_temp, temp_change)
54 | current = {:vector=>random_permutation(cities)}
55 | current[:cost] = cost(current[:vector], cities)
56 | temp, best = max_temp, current
57 | max_iter.times do |iter|
58 | candidate = create_neighbor(current, cities)
59 | temp = temp * temp_change
60 | current = candidate if should_accept?(candidate, current, temp)
61 | best = candidate if candidate[:cost] < best[:cost]
62 | if (iter+1).modulo(10) == 0
63 | puts " > iteration #{(iter+1)}, temp=#{temp}, best=#{best[:cost]}"
64 | end
65 | end
66 | return best
67 | end
68 |
69 | if __FILE__ == $0
70 | # problem configuration
71 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
72 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
73 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
74 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
75 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
76 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
77 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
78 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
79 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
80 | # algorithm configuration
81 | max_iterations = 2000
82 | max_temp = 100000.0
83 | temp_change = 0.98
84 | # execute the algorithm
85 | best = search(berlin52, max_iterations, max_temp, temp_change)
86 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
87 | end
88 |
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/evolutionary_programming.rb:
--------------------------------------------------------------------------------
1 | # Evolutionary Programming algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_vector(minmax)
12 | return Array.new(minmax.size) do |i|
13 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
14 | end
15 | end
16 |
17 | def random_gaussian(mean=0.0, stdev=1.0)
18 | u1 = u2 = w = 0
19 | begin
20 | u1 = 2 * rand() - 1
21 | u2 = 2 * rand() - 1
22 | w = u1 * u1 + u2 * u2
23 | end while w >= 1
24 | w = Math.sqrt((-2.0 * Math.log(w)) / w)
25 | return mean + (u2 * w) * stdev
26 | end
27 |
28 | def mutate(candidate, search_space)
29 | child = {:vector=>[], :strategy=>[]}
30 | candidate[:vector].each_with_index do |v_old, i|
31 | s_old = candidate[:strategy][i]
32 | v = v_old + s_old * random_gaussian()
33 | v = search_space[i][0] if v < search_space[i][0]
34 | v = search_space[i][1] if v > search_space[i][1]
35 | child[:vector] << v
36 | child[:strategy] << s_old + random_gaussian() * s_old.abs**0.5
37 | end
38 | return child
39 | end
40 |
41 | def tournament(candidate, population, bout_size)
42 | candidate[:wins] = 0
43 | bout_size.times do |i|
44 | other = population[rand(population.size)]
45 | candidate[:wins] += 1 if candidate[:fitness] < other[:fitness]
46 | end
47 | end
48 |
49 | def init_population(minmax, pop_size)
50 | strategy = Array.new(minmax.size) do |i|
51 | [0, (minmax[i][1]-minmax[i][0]) * 0.05]
52 | end
53 | pop = Array.new(pop_size, {})
54 | pop.each_index do |i|
55 | pop[i][:vector] = random_vector(minmax)
56 | pop[i][:strategy] = random_vector(strategy)
57 | end
58 | pop.each{|c| c[:fitness] = objective_function(c[:vector])}
59 | return pop
60 | end
61 |
62 | def search(max_gens, search_space, pop_size, bout_size)
63 | population = init_population(search_space, pop_size)
64 | population.each{|c| c[:fitness] = objective_function(c[:vector])}
65 | best = population.sort{|x,y| x[:fitness] <=> y[:fitness]}.first
66 | max_gens.times do |gen|
67 | children = Array.new(pop_size) {|i| mutate(population[i], search_space)}
68 | children.each{|c| c[:fitness] = objective_function(c[:vector])}
69 | children.sort!{|x,y| x[:fitness] <=> y[:fitness]}
70 | best = children.first if children.first[:fitness] < best[:fitness]
71 | union = children+population
72 | union.each{|c| tournament(c, union, bout_size)}
73 | union.sort!{|x,y| y[:wins] <=> x[:wins]}
74 | population = union.first(pop_size)
75 | puts " > gen #{gen}, fitness=#{best[:fitness]}"
76 | end
77 | return best
78 | end
79 |
80 | if __FILE__ == $0
81 | # problem configuration
82 | problem_size = 2
83 | search_space = Array.new(problem_size) {|i| [-5, +5]}
84 | # algorithm configuration
85 | max_gens = 200
86 | pop_size = 100
87 | bout_size = 5
88 | # execute the algorithm
89 | best = search(max_gens, search_space, pop_size, bout_size)
90 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:vector].inspect}"
91 | end
92 |
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/evolution_strategies.rb:
--------------------------------------------------------------------------------
1 | # Evolution Strategies algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_vector(minmax)
12 | return Array.new(minmax.size) do |i|
13 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
14 | end
15 | end
16 |
17 | def random_gaussian(mean=0.0, stdev=1.0)
18 | u1 = u2 = w = 0
19 | begin
20 | u1 = 2 * rand() - 1
21 | u2 = 2 * rand() - 1
22 | w = u1 * u1 + u2 * u2
23 | end while w >= 1
24 | w = Math.sqrt((-2.0 * Math.log(w)) / w)
25 | return mean + (u2 * w) * stdev
26 | end
27 |
28 | def mutate_problem(vector, stdevs, search_space)
29 | child = Array(vector.size)
30 | vector.each_with_index do |v, i|
31 | child[i] = v + stdevs[i] * random_gaussian()
32 | child[i] = search_space[i][0] if child[i] < search_space[i][0]
33 | child[i] = search_space[i][1] if child[i] > search_space[i][1]
34 | end
35 | return child
36 | end
37 |
38 | def mutate_strategy(stdevs)
39 | tau = Math.sqrt(2.0*stdevs.size.to_f)**-1.0
40 | tau_p = Math.sqrt(2.0*Math.sqrt(stdevs.size.to_f))**-1.0
41 | child = Array.new(stdevs.size) do |i|
42 | stdevs[i] * Math.exp(tau_p*random_gaussian() + tau*random_gaussian())
43 | end
44 | return child
45 | end
46 |
47 | def mutate(par, minmax)
48 | child = {}
49 | child[:vector] = mutate_problem(par[:vector], par[:strategy], minmax)
50 | child[:strategy] = mutate_strategy(par[:strategy])
51 | return child
52 | end
53 |
54 | def init_population(minmax, pop_size)
55 | strategy = Array.new(minmax.size) do |i|
56 | [0, (minmax[i][1]-minmax[i][0]) * 0.05]
57 | end
58 | pop = Array.new(pop_size) { Hash.new }
59 | pop.each_index do |i|
60 | pop[i][:vector] = random_vector(minmax)
61 | pop[i][:strategy] = random_vector(strategy)
62 | end
63 | pop.each{|c| c[:fitness] = objective_function(c[:vector])}
64 | return pop
65 | end
66 |
67 | def search(max_gens, search_space, pop_size, num_children)
68 | population = init_population(search_space, pop_size)
69 | best = population.sort{|x,y| x[:fitness] <=> y[:fitness]}.first
70 | max_gens.times do |gen|
71 | children = Array.new(num_children) do |i|
72 | mutate(population[i], search_space)
73 | end
74 | children.each{|c| c[:fitness] = objective_function(c[:vector])}
75 | union = children+population
76 | union.sort!{|x,y| x[:fitness] <=> y[:fitness]}
77 | best = union.first if union.first[:fitness] < best[:fitness]
78 | population = union.first(pop_size)
79 | puts " > gen #{gen}, fitness=#{best[:fitness]}"
80 | end
81 | return best
82 | end
83 |
84 | if __FILE__ == $0
85 | # problem configuration
86 | problem_size = 2
87 | search_space = Array.new(problem_size) {|i| [-5, +5]}
88 | # algorithm configuration
89 | max_gens = 100
90 | pop_size = 30
91 | num_children = 20
92 | # execute the algorithm
93 | best = search(max_gens, search_space, pop_size, num_children)
94 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:vector].inspect}"
95 | end
96 |
--------------------------------------------------------------------------------
/src/algorithms/neural/lvq.rb:
--------------------------------------------------------------------------------
1 | # Learning Vector Quantization Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def random_vector(minmax)
8 | return Array.new(minmax.size) do |i|
9 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
10 | end
11 | end
12 |
13 | def generate_random_pattern(domain)
14 | classes = domain.keys
15 | selected_class = rand(classes.size)
16 | pattern = {:label=>classes[selected_class]}
17 | pattern[:vector] = random_vector(domain[classes[selected_class]])
18 | return pattern
19 | end
20 |
21 | def initialize_vectors(domain, num_vectors)
22 | classes = domain.keys
23 | codebook_vectors = []
24 | num_vectors.times do
25 | selected_class = rand(classes.size)
26 | codebook = {}
27 | codebook[:label] = classes[selected_class]
28 | codebook[:vector] = random_vector([[0,1],[0,1]])
29 | codebook_vectors << codebook
30 | end
31 | return codebook_vectors
32 | end
33 |
34 | def euclidean_distance(c1, c2)
35 | sum = 0.0
36 | c1.each_index {|i| sum += (c1[i]-c2[i])**2.0}
37 | return Math.sqrt(sum)
38 | end
39 |
40 | def get_best_matching_unit(codebook_vectors, pattern)
41 | best, b_dist = nil, nil
42 | codebook_vectors.each do |codebook|
43 | dist = euclidean_distance(codebook[:vector], pattern[:vector])
44 | best,b_dist = codebook,dist if b_dist.nil? or dist iter=#{iter}, got=#{bmu[:label]}, exp=#{pat[:label]}"
67 | end
68 | update_codebook_vector(bmu, pat, lrate)
69 | end
70 | end
71 |
72 | def test_network(codebook_vectors, domain, num_trials=100)
73 | correct = 0
74 | num_trials.times do
75 | pattern = generate_random_pattern(domain)
76 | bmu = get_best_matching_unit(codebook_vectors, pattern)
77 | correct += 1 if bmu[:label] == pattern[:label]
78 | end
79 | puts "Done. Score: #{correct}/#{num_trials}"
80 | return correct
81 | end
82 |
83 | def execute(domain, iterations, num_vectors, learning_rate)
84 | codebook_vectors = initialize_vectors(domain, num_vectors)
85 | train_network(codebook_vectors, domain, iterations, learning_rate)
86 | test_network(codebook_vectors, domain)
87 | return codebook_vectors
88 | end
89 |
90 | if __FILE__ == $0
91 | # problem configuration
92 | domain = {"A"=>[[0,0.4999999],[0,0.4999999]],"B"=>[[0.5,1],[0.5,1]]}
93 | # algorithm configuration
94 | learning_rate = 0.3
95 | iterations = 1000
96 | num_vectors = 20
97 | # execute the algorithm
98 | execute(domain, iterations, num_vectors, learning_rate)
99 | end
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/tests/tc_umda.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for umda.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../umda"
9 |
10 | class TC_UMDA < Test::Unit::TestCase
11 |
12 | # test that the objective function behaves as expected
13 | def test_onemax
14 | assert_equal(0, onemax([0,0,0,0]))
15 | assert_equal(4, onemax([1,1,1,1]))
16 | assert_equal(2, onemax([1,0,1,0]))
17 | end
18 |
19 | # test basic construction of random bitstrings
20 | def test_random_bitstring
21 | assert_equal(10, random_bitstring(10).size)
22 | assert_equal(10, random_bitstring(10).select{|x| x==0 or x==1}.size)
23 | end
24 |
25 | # test the approximate proportion of 1's and 0's
26 | def test_random_bitstring_ratio
27 | s = random_bitstring(1000)
28 | assert_in_delta(0.5, (s.select{|x| x==0}.size/1000.0), 0.05)
29 | assert_in_delta(0.5, (s.select{|x| x==1}.size/1000.0), 0.05)
30 | end
31 |
32 | # test that members of the population are selected
33 | def test_binary_tournament
34 | pop = Array.new(10) {|i| {:fitness=>i} }
35 | 10.times {assert(pop.include?(binary_tournament(pop)))}
36 | end
37 |
38 | # test the reduction of a pop to a probability vector
39 | def test_calculate_bit_probabilities
40 | # all zeros
41 | pop = [{:bitstring=>Array.new(1000){0}}, {:bitstring=>Array.new(1000){0}}]
42 | v = calculate_bit_probabilities(pop)
43 | assert_equal(1000, v.size)
44 | v.each{|x| assert_equal(0, x)}
45 | # all ones
46 | pop = [{:bitstring=>Array.new(1000){1}}, {:bitstring=>Array.new(1000){1}}]
47 | v = calculate_bit_probabilities(pop)
48 | assert_equal(1000, v.size)
49 | v.each{|x| assert_equal(1, x)}
50 | # 50/50
51 | pop = [{:bitstring=>Array.new(1000){1}}, {:bitstring=>Array.new(1000){0}}]
52 | v = calculate_bit_probabilities(pop)
53 | assert_equal(1000, v.size)
54 | v.each{|x| assert_equal(0.5, x)}
55 | end
56 |
57 | # generate a candidate solution
58 | def test_generate_candidate
59 | # all 0
60 | s = generate_candidate(Array.new(1000){0})
61 | assert_not_nil(s)
62 | assert_equal(1000, s[:bitstring].length)
63 | s[:bitstring].each{|x| assert_equal(0, x)}
64 | # all 1
65 | s = generate_candidate(Array.new(1000){1})
66 | assert_not_nil(s)
67 | assert_equal(1000, s[:bitstring].length)
68 | s[:bitstring].each{|x| assert_equal(1, x)}
69 | # all 50/50
70 | s = generate_candidate(Array.new(1000){0.5})
71 | assert_not_nil(s)
72 | assert_equal(1000, s[:bitstring].length)
73 | assert_in_delta(500, s[:bitstring].inject(0){|sum,x| sum+x}, 50)
74 | end
75 |
76 | # helper for turning off STDOUT
77 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
78 | def silence_stream(stream)
79 | old_stream = stream.dup
80 | stream.reopen('/dev/null')
81 | stream.sync = true
82 | yield
83 | ensure
84 | stream.reopen(old_stream)
85 | end
86 |
87 | # test that the algorithm can solve the problem
88 | def test_search
89 | best = nil
90 | silence_stream(STDOUT) do
91 | best = search(20, 100, 50, 30)
92 | end
93 | assert_not_nil(best[:fitness])
94 | assert_equal(20, best[:fitness])
95 | end
96 |
97 | end
98 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/grasp.rb:
--------------------------------------------------------------------------------
1 | # Greedy Randomized Adaptive Search Procedure in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def cost(perm, cities)
12 | distance =0
13 | perm.each_with_index do |c1, i|
14 | c2 = (i==perm.size-1) ? perm[0] : perm[i+1]
15 | distance += euc_2d(cities[c1], cities[c2])
16 | end
17 | return distance
18 | end
19 |
20 | def stochastic_two_opt(permutation)
21 | perm = Array.new(permutation)
22 | c1, c2 = rand(perm.size), rand(perm.size)
23 | exclude = [c1]
24 | exclude << ((c1==0) ? perm.size-1 : c1-1)
25 | exclude << ((c1==perm.size-1) ? 0 : c1+1)
26 | c2 = rand(perm.size) while exclude.include?(c2)
27 | c1, c2 = c2, c1 if c2 < c1
28 | perm[c1...c2] = perm[c1...c2].reverse
29 | return perm
30 | end
31 |
32 | def local_search(best, cities, max_no_improv)
33 | count = 0
34 | begin
35 | candidate = {:vector=>stochastic_two_opt(best[:vector])}
36 | candidate[:cost] = cost(candidate[:vector], cities)
37 | count = (candidate[:cost] < best[:cost]) ? 0 : count+1
38 | best = candidate if candidate[:cost] < best[:cost]
39 | end until count >= max_no_improv
40 | return best
41 | end
42 |
43 | def construct_randomized_greedy_solution(cities, alpha)
44 | candidate = {}
45 | candidate[:vector] = [rand(cities.size)]
46 | allCities = Array.new(cities.size) {|i| i}
47 | while candidate[:vector].size < cities.size
48 | candidates = allCities - candidate[:vector]
49 | costs = Array.new(candidates.size) do |i|
50 | euc_2d(cities[candidate[:vector].last], cities[i])
51 | end
52 | rcl, max, min = [], costs.max, costs.min
53 | costs.each_with_index do |c,i|
54 | rcl << candidates[i] if c <= (min + alpha*(max-min))
55 | end
56 | candidate[:vector] << rcl[rand(rcl.size)]
57 | end
58 | candidate[:cost] = cost(candidate[:vector], cities)
59 | return candidate
60 | end
61 |
62 | def search(cities, max_iter, max_no_improv, alpha)
63 | best = nil
64 | max_iter.times do |iter|
65 | candidate = construct_randomized_greedy_solution(cities, alpha);
66 | candidate = local_search(candidate, cities, max_no_improv)
67 | best = candidate if best.nil? or candidate[:cost] < best[:cost]
68 | puts " > iteration #{(iter+1)}, best=#{best[:cost]}"
69 | end
70 | return best
71 | end
72 |
73 | if __FILE__ == $0
74 | # problem configuration
75 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
76 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
77 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
78 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
79 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
80 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
81 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
82 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
83 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
84 | # algorithm configuration
85 | max_iter = 50
86 | max_no_improv = 50
87 | greediness_factor = 0.3
88 | # execute the algorithm
89 | best = search(berlin52, max_iter, max_no_improv, greediness_factor)
90 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
91 | end
92 |
--------------------------------------------------------------------------------
/src/algorithms/swarm/pso.rb:
--------------------------------------------------------------------------------
1 | # Particle Swarm Optimization in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_vector(minmax)
12 | return Array.new(minmax.size) do |i|
13 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
14 | end
15 | end
16 |
17 | def create_particle(search_space, vel_space)
18 | particle = {}
19 | particle[:position] = random_vector(search_space)
20 | particle[:cost] = objective_function(particle[:position])
21 | particle[:b_position] = Array.new(particle[:position])
22 | particle[:b_cost] = particle[:cost]
23 | particle[:velocity] = random_vector(vel_space)
24 | return particle
25 | end
26 |
27 | def get_global_best(population, current_best=nil)
28 | population.sort!{|x,y| x[:cost] <=> y[:cost]}
29 | best = population.first
30 | if current_best.nil? or best[:cost] <= current_best[:cost]
31 | current_best = {}
32 | current_best[:position] = Array.new(best[:position])
33 | current_best[:cost] = best[:cost]
34 | end
35 | return current_best
36 | end
37 |
38 | def update_velocity(particle, gbest, max_v, c1, c2)
39 | particle[:velocity].each_with_index do |v,i|
40 | v1 = c1 * rand() * (particle[:b_position][i] - particle[:position][i])
41 | v2 = c2 * rand() * (gbest[:position][i] - particle[:position][i])
42 | particle[:velocity][i] = v + v1 + v2
43 | particle[:velocity][i] = max_v if particle[:velocity][i] > max_v
44 | particle[:velocity][i] = -max_v if particle[:velocity][i] < -max_v
45 | end
46 | end
47 |
48 | def update_position(part, bounds)
49 | part[:position].each_with_index do |v,i|
50 | part[:position][i] = v + part[:velocity][i]
51 | if part[:position][i] > bounds[i][1]
52 | part[:position][i]=bounds[i][1]-(part[:position][i]-bounds[i][1]).abs
53 | part[:velocity][i] *= -1.0
54 | elsif part[:position][i] < bounds[i][0]
55 | part[:position][i]=bounds[i][0]+(part[:position][i]-bounds[i][0]).abs
56 | part[:velocity][i] *= -1.0
57 | end
58 | end
59 | end
60 |
61 | def update_best_position(particle)
62 | return if particle[:cost] > particle[:b_cost]
63 | particle[:b_cost] = particle[:cost]
64 | particle[:b_position] = Array.new(particle[:position])
65 | end
66 |
67 | def search(max_gens, search_space, vel_space, pop_size, max_vel, c1, c2)
68 | pop = Array.new(pop_size) {create_particle(search_space, vel_space)}
69 | gbest = get_global_best(pop)
70 | max_gens.times do |gen|
71 | pop.each do |particle|
72 | update_velocity(particle, gbest, max_vel, c1, c2)
73 | update_position(particle, search_space)
74 | particle[:cost] = objective_function(particle[:position])
75 | update_best_position(particle)
76 | end
77 | gbest = get_global_best(pop, gbest)
78 | puts " > gen #{gen+1}, fitness=#{gbest[:cost]}"
79 | end
80 | return gbest
81 | end
82 |
83 | if __FILE__ == $0
84 | # problem configuration
85 | problem_size = 2
86 | search_space = Array.new(problem_size) {|i| [-5, 5]}
87 | # algorithm configuration
88 | vel_space = Array.new(problem_size) {|i| [-1, 1]}
89 | max_gens = 100
90 | pop_size = 50
91 | max_vel = 100.0
92 | c1, c2 = 2.0, 2.0
93 | # execute the algorithm
94 | best = search(max_gens, search_space, vel_space, pop_size, max_vel, c1,c2)
95 | puts "done! Solution: f=#{best[:cost]}, s=#{best[:position].inspect}"
96 | end
97 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/variable_neighborhood_search.rb:
--------------------------------------------------------------------------------
1 | # Variable Neighborhood Search in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def cost(perm, cities)
12 | distance =0
13 | perm.each_with_index do |c1, i|
14 | c2 = (i==perm.size-1) ? perm[0] : perm[i+1]
15 | distance += euc_2d(cities[c1], cities[c2])
16 | end
17 | return distance
18 | end
19 |
20 | def random_permutation(cities)
21 | perm = Array.new(cities.size){|i| i}
22 | perm.each_index do |i|
23 | r = rand(perm.size-i) + i
24 | perm[r], perm[i] = perm[i], perm[r]
25 | end
26 | return perm
27 | end
28 |
29 | def stochastic_two_opt!(perm)
30 | c1, c2 = rand(perm.size), rand(perm.size)
31 | exclude = [c1]
32 | exclude << ((c1==0) ? perm.size-1 : c1-1)
33 | exclude << ((c1==perm.size-1) ? 0 : c1+1)
34 | c2 = rand(perm.size) while exclude.include?(c2)
35 | c1, c2 = c2, c1 if c2 < c1
36 | perm[c1...c2] = perm[c1...c2].reverse
37 | return perm
38 | end
39 |
40 | def local_search(best, cities, max_no_improv, neighborhood)
41 | count = 0
42 | begin
43 | candidate = {}
44 | candidate[:vector] = Array.new(best[:vector])
45 | neighborhood.times{stochastic_two_opt!(candidate[:vector])}
46 | candidate[:cost] = cost(candidate[:vector], cities)
47 | if candidate[:cost] < best[:cost]
48 | count, best = 0, candidate
49 | else
50 | count += 1
51 | end
52 | end until count >= max_no_improv
53 | return best
54 | end
55 |
56 | def search(cities, neighborhoods, max_no_improv, max_no_improv_ls)
57 | best = {}
58 | best[:vector] = random_permutation(cities)
59 | best[:cost] = cost(best[:vector], cities)
60 | iter, count = 0, 0
61 | begin
62 | neighborhoods.each do |neigh|
63 | candidate = {}
64 | candidate[:vector] = Array.new(best[:vector])
65 | neigh.times{stochastic_two_opt!(candidate[:vector])}
66 | candidate[:cost] = cost(candidate[:vector], cities)
67 | candidate = local_search(candidate, cities, max_no_improv_ls, neigh)
68 | puts " > iteration #{(iter+1)}, neigh=#{neigh}, best=#{best[:cost]}"
69 | iter += 1
70 | if(candidate[:cost] < best[:cost])
71 | best, count = candidate, 0
72 | puts "New best, restarting neighborhood search."
73 | break
74 | else
75 | count += 1
76 | end
77 | end
78 | end until count >= max_no_improv
79 | return best
80 | end
81 |
82 | if __FILE__ == $0
83 | # problem configuration
84 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
85 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
86 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
87 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
88 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
89 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
90 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
91 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
92 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
93 | # algorithm configuration
94 | max_no_improv = 50
95 | max_no_improv_ls = 70
96 | neighborhoods = 1...20
97 | # execute the algorithm
98 | best = search(berlin52, neighborhoods, max_no_improv, max_no_improv_ls)
99 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
100 | end
101 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/iterated_local_search.rb:
--------------------------------------------------------------------------------
1 | # Iterated Local Search algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def cost(permutation, cities)
12 | distance =0
13 | permutation.each_with_index do |c1, i|
14 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
15 | distance += euc_2d(cities[c1], cities[c2])
16 | end
17 | return distance
18 | end
19 |
20 | def random_permutation(cities)
21 | perm = Array.new(cities.size){|i| i}
22 | perm.each_index do |i|
23 | r = rand(perm.size-i) + i
24 | perm[r], perm[i] = perm[i], perm[r]
25 | end
26 | return perm
27 | end
28 |
29 | def stochastic_two_opt(permutation)
30 | perm = Array.new(permutation)
31 | c1, c2 = rand(perm.size), rand(perm.size)
32 | exclude = [c1]
33 | exclude << ((c1==0) ? perm.size-1 : c1-1)
34 | exclude << ((c1==perm.size-1) ? 0 : c1+1)
35 | c2 = rand(perm.size) while exclude.include?(c2)
36 | c1, c2 = c2, c1 if c2 < c1
37 | perm[c1...c2] = perm[c1...c2].reverse
38 | return perm
39 | end
40 |
41 | def local_search(best, cities, max_no_improv)
42 | count = 0
43 | begin
44 | candidate = {:vector=>stochastic_two_opt(best[:vector])}
45 | candidate[:cost] = cost(candidate[:vector], cities)
46 | count = (candidate[:cost] < best[:cost]) ? 0 : count+1
47 | best = candidate if candidate[:cost] < best[:cost]
48 | end until count >= max_no_improv
49 | return best
50 | end
51 |
52 | def double_bridge_move(perm)
53 | pos1 = 1 + rand(perm.size / 4)
54 | pos2 = pos1 + 1 + rand(perm.size / 4)
55 | pos3 = pos2 + 1 + rand(perm.size / 4)
56 | p1 = perm[0...pos1] + perm[pos3..perm.size]
57 | p2 = perm[pos2...pos3] + perm[pos1...pos2]
58 | return p1 + p2
59 | end
60 |
61 | def perturbation(cities, best)
62 | candidate = {}
63 | candidate[:vector] = double_bridge_move(best[:vector])
64 | candidate[:cost] = cost(candidate[:vector], cities)
65 | return candidate
66 | end
67 |
68 | def search(cities, max_iterations, max_no_improv)
69 | best = {}
70 | best[:vector] = random_permutation(cities)
71 | best[:cost] = cost(best[:vector], cities)
72 | best = local_search(best, cities, max_no_improv)
73 | max_iterations.times do |iter|
74 | candidate = perturbation(cities, best)
75 | candidate = local_search(candidate, cities, max_no_improv)
76 | best = candidate if candidate[:cost] < best[:cost]
77 | puts " > iteration #{(iter+1)}, best=#{best[:cost]}"
78 | end
79 | return best
80 | end
81 |
82 | if __FILE__ == $0
83 | # problem configuration
84 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
85 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
86 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
87 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
88 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
89 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
90 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
91 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
92 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
93 | # algorithm configuration
94 | max_iterations = 100
95 | max_no_improv = 50
96 | # execute the algorithm
97 | best = search(berlin52, max_iterations, max_no_improv)
98 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
99 | end
100 |
--------------------------------------------------------------------------------
/src/algorithms/physical/tests/tc_simulated_annealing.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for simulated_annealing.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../simulated_annealing"
9 |
10 | class TC_SimulatedAnnealing < Test::Unit::TestCase
11 |
12 | # test the rounding in the euclidean distance
13 | def test_euc_2d
14 | assert_equal(0, euc_2d([0,0], [0,0]))
15 | assert_equal(0, euc_2d([1.1,1.1], [1.1,1.1]))
16 | assert_equal(1, euc_2d([1,1], [2,2]))
17 | assert_equal(3, euc_2d([-1,-1], [1,1]))
18 | end
19 |
20 | # test tour cost includes return to origin
21 | def test_cost
22 | cities = [[0,0], [1,1], [2,2], [3,3]]
23 | assert_equal(1*2, cost([0,1], cities))
24 | assert_equal(3+4, cost([0,1,2,3], cities))
25 | assert_equal(4*2, cost([0, 3], cities))
26 | end
27 |
28 | # test the construction of a random permutation
29 | def test_random_permutation
30 | cities = Array.new(10)
31 | 100.times do
32 | p = random_permutation(cities)
33 | assert_equal(cities.size, p.size)
34 | [0,1,2,3,4,5,6,7,8,9].each {|x| assert(p.include?(x), "#{x}") }
35 | end
36 | end
37 |
38 | # test the two opt procedure
39 | def test_stochastic_two_opt
40 | perm = Array.new(10){|i| i}
41 | 200.times do
42 | other = stochastic_two_opt!(perm)
43 | assert_equal(perm.size, other.size)
44 | assert_same(perm, other)
45 | other.each {|x| assert(perm.include?(x), "#{x}") }
46 | end
47 | end
48 |
49 | # test the construction of a neighbour
50 | def test_create_neighbor
51 | cities = [[0,0],[3,3],[1,1],[2,2],[4,4]]
52 | 100.times do
53 | c = {:vector=>[0,1,2,3,4]}
54 | rs = create_neighbor(c, cities)
55 | assert_not_nil(rs[:cost])
56 | assert_not_nil(rs[:vector])
57 | assert_not_same(c[:vector], rs[:vector])
58 | assert_not_equal(c[:vector], rs[:vector])
59 | end
60 | end
61 |
62 | # test the acceptance criteria
63 | def test_should_accept
64 | # accept lower cost
65 | assert_equal(true, should_accept?({:cost=>1}, {:cost=>2}, 0))
66 | # accept same cost
67 | assert_equal(true, should_accept?({:cost=>1}, {:cost=>1}, 0))
68 | # TODO can we even test the temp meaningfuly?
69 | end
70 |
71 | # helper for turning off STDOUT
72 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
73 | def silence_stream(stream)
74 | old_stream = stream.dup
75 | stream.reopen('/dev/null')
76 | stream.sync = true
77 | yield
78 | ensure
79 | stream.reopen(old_stream)
80 | end
81 |
82 | # test that the algorithm can solve the problem
83 | def test_search
84 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
85 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
86 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
87 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
88 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
89 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
90 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
91 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
92 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
93 | best = nil
94 | silence_stream(STDOUT) do
95 | best = search(berlin52, 5000, 100000.0, 0.98)
96 | end
97 | # better than a NN solution's cost
98 | assert_not_nil(best[:cost])
99 | assert_in_delta(7542, best[:cost], 3000)
100 | end
101 |
102 | end
103 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_variable_neighborhood_search.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for variable_neighborhood_search.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../variable_neighborhood_search"
9 |
10 | class TC_VariableNeighborhoodSearch < Test::Unit::TestCase
11 |
12 | # test the rounding in the euclidean distance
13 | def test_euc_2d
14 | assert_equal(0, euc_2d([0,0], [0,0]))
15 | assert_equal(0, euc_2d([1.1,1.1], [1.1,1.1]))
16 | assert_equal(1, euc_2d([1,1], [2,2]))
17 | assert_equal(3, euc_2d([-1,-1], [1,1]))
18 | end
19 |
20 | # test tour cost includes return to origin
21 | def test_cost
22 | cities = [[0,0], [1,1], [2,2], [3,3]]
23 | assert_equal(1*2, cost([0,1], cities))
24 | assert_equal(3+4, cost([0,1,2,3], cities))
25 | assert_equal(4*2, cost([0, 3], cities))
26 | end
27 |
28 | # test the construction of a random permutation
29 | def test_random_permutation
30 | cities = Array.new(10)
31 | 100.times do
32 | p = random_permutation(cities)
33 | assert_equal(cities.size, p.size)
34 | [0,1,2,3,4,5,6,7,8,9].each {|x| assert(p.include?(x), "#{x}") }
35 | end
36 | end
37 |
38 | # test the two opt procedure
39 | def test_stochastic_two_opt
40 | perm = Array.new(10){|i| i}
41 | 200.times do
42 | other = stochastic_two_opt!(perm)
43 | assert_equal(perm.size, other.size)
44 | assert_same(perm, other)
45 | other.each {|x| assert(perm.include?(x), "#{x}") }
46 | end
47 | end
48 |
49 | # test the local search
50 | def test_local_search
51 | # improvement
52 | best = {:vector=>[0,1,2,3,4]}
53 | cities = [[0,0],[3,3],[1,1],[2,2],[4,4]]
54 | best[:cost] = cost(best[:vector], cities)
55 | rs = local_search(best, cities, 20, 3)
56 | assert_not_nil(rs)
57 | assert_not_nil(rs[:vector])
58 | assert_not_nil(rs[:cost])
59 | assert_not_same(best, rs)
60 | assert_not_equal(best[:vector], rs[:vector])
61 | assert_not_equal(best[:cost], rs[:cost])
62 | # no improvement
63 | best = {:vector=>[0,2,3,1,4]}
64 | best[:cost] = cost(best[:vector], cities)
65 | rs = local_search(best, cities, 20, 1)
66 | assert_not_nil(rs)
67 | assert_equal(best[:cost], rs[:cost])
68 | end
69 |
70 | # helper for turning off STDOUT
71 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
72 | def silence_stream(stream)
73 | old_stream = stream.dup
74 | stream.reopen('/dev/null')
75 | stream.sync = true
76 | yield
77 | ensure
78 | stream.reopen(old_stream)
79 | end
80 |
81 | # test that the algorithm can solve the problem
82 | def test_search
83 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
84 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
85 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
86 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
87 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
88 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
89 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
90 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
91 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
92 | best = nil
93 | silence_stream(STDOUT) do
94 | best = search(berlin52, 1...20, 50, 70)
95 | end
96 | # better than a NN solution's cost
97 | assert_not_nil(best[:cost])
98 | assert_in_delta(7542, best[:cost], 4000)
99 | end
100 |
101 | end
102 |
--------------------------------------------------------------------------------
/src/algorithms/physical/cultural_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Cultural Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def rand_in_bounds(min, max)
12 | return min + ((max-min) * rand())
13 | end
14 |
15 | def random_vector(minmax)
16 | return Array.new(minmax.size) do |i|
17 | rand_in_bounds(minmax[i][0], minmax[i][1])
18 | end
19 | end
20 |
21 | def mutate_with_inf(candidate, beliefs, minmax)
22 | v = Array.new(candidate[:vector].size)
23 | candidate[:vector].each_with_index do |c,i|
24 | v[i]=rand_in_bounds(beliefs[:normative][i][0],beliefs[:normative][i][1])
25 | v[i] = minmax[i][0] if v[i] < minmax[i][0]
26 | v[i] = minmax[i][1] if v[i] > minmax[i][1]
27 | end
28 | return {:vector=>v}
29 | end
30 |
31 | def binary_tournament(pop)
32 | i, j = rand(pop.size), rand(pop.size)
33 | j = rand(pop.size) while j==i
34 | return (pop[i][:fitness] < pop[j][:fitness]) ? pop[i] : pop[j]
35 | end
36 |
37 | def initialize_beliefspace(search_space)
38 | belief_space = {}
39 | belief_space[:situational] = nil
40 | belief_space[:normative] = Array.new(search_space.size) do |i|
41 | Array.new(search_space[i])
42 | end
43 | return belief_space
44 | end
45 |
46 | def update_beliefspace_situational!(belief_space, best)
47 | curr_best = belief_space[:situational]
48 | if curr_best.nil? or best[:fitness] < curr_best[:fitness]
49 | belief_space[:situational] = best
50 | end
51 | end
52 |
53 | def update_beliefspace_normative!(belief_space, acc)
54 | belief_space[:normative].each_with_index do |bounds,i|
55 | bounds[0] = acc.min{|x,y| x[:vector][i]<=>y[:vector][i]}[:vector][i]
56 | bounds[1] = acc.max{|x,y| x[:vector][i]<=>y[:vector][i]}[:vector][i]
57 | end
58 | end
59 |
60 | def search(max_gens, search_space, pop_size, num_accepted)
61 | # initialize
62 | pop = Array.new(pop_size) { {:vector=>random_vector(search_space)} }
63 | belief_space = initialize_beliefspace(search_space)
64 | # evaluate
65 | pop.each{|c| c[:fitness] = objective_function(c[:vector])}
66 | best = pop.sort{|x,y| x[:fitness] <=> y[:fitness]}.first
67 | # update situational knowledge
68 | update_beliefspace_situational!(belief_space, best)
69 | max_gens.times do |gen|
70 | # create next generation
71 | children = Array.new(pop_size) do |i|
72 | mutate_with_inf(pop[i], belief_space, search_space)
73 | end
74 | # evaluate
75 | children.each{|c| c[:fitness] = objective_function(c[:vector])}
76 | best = children.sort{|x,y| x[:fitness] <=> y[:fitness]}.first
77 | # update situational knowledge
78 | update_beliefspace_situational!(belief_space, best)
79 | # select next generation
80 | pop = Array.new(pop_size) { binary_tournament(children + pop) }
81 | # update normative knowledge
82 | pop.sort!{|x,y| x[:fitness] <=> y[:fitness]}
83 | acccepted = pop[0...num_accepted]
84 | update_beliefspace_normative!(belief_space, acccepted)
85 | # user feedback
86 | puts " > generation=#{gen}, f=#{belief_space[:situational][:fitness]}"
87 | end
88 | return belief_space[:situational]
89 | end
90 |
91 | if __FILE__ == $0
92 | # problem configuration
93 | problem_size = 2
94 | search_space = Array.new(problem_size) {|i| [-5, +5]}
95 | # algorithm configuration
96 | max_gens = 200
97 | pop_size = 100
98 | num_accepted = (pop_size*0.20).round
99 | # execute the algorithm
100 | best = search(max_gens, search_space, pop_size, num_accepted)
101 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:vector].inspect}"
102 | end
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tabu_search.rb:
--------------------------------------------------------------------------------
1 | # Tabu Search algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def cost(perm, cities)
12 | distance = 0
13 | perm.each_with_index do |c1, i|
14 | c2 = (i==perm.size-1) ? perm[0] : perm[i+1]
15 | distance += euc_2d(cities[c1], cities[c2])
16 | end
17 | return distance
18 | end
19 |
20 | def random_permutation(cities)
21 | perm = Array.new(cities.size){|i| i}
22 | perm.each_index do |i|
23 | r = rand(perm.size-i) + i
24 | perm[r], perm[i] = perm[i], perm[r]
25 | end
26 | return perm
27 | end
28 |
29 | def stochastic_two_opt(parent)
30 | perm = Array.new(parent)
31 | c1, c2 = rand(perm.size), rand(perm.size)
32 | exclude = [c1]
33 | exclude << ((c1==0) ? perm.size-1 : c1-1)
34 | exclude << ((c1==perm.size-1) ? 0 : c1+1)
35 | c2 = rand(perm.size) while exclude.include?(c2)
36 | c1, c2 = c2, c1 if c2 < c1
37 | perm[c1...c2] = perm[c1...c2].reverse
38 | return perm, [[parent[c1-1], parent[c1]], [parent[c2-1], parent[c2]]]
39 | end
40 |
41 | def is_tabu?(permutation, tabu_list)
42 | permutation.each_with_index do |c1, i|
43 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
44 | tabu_list.each do |forbidden_edge|
45 | return true if forbidden_edge == [c1, c2]
46 | end
47 | end
48 | return false
49 | end
50 |
51 | def generate_candidate(best, tabu_list, cities)
52 | perm, edges = nil, nil
53 | begin
54 | perm, edges = stochastic_two_opt(best[:vector])
55 | end while is_tabu?(perm, tabu_list)
56 | candidate = {:vector=>perm}
57 | candidate[:cost] = cost(candidate[:vector], cities)
58 | return candidate, edges
59 | end
60 |
61 | def search(cities, tabu_list_size, candidate_list_size, max_iter)
62 | current = {:vector=>random_permutation(cities)}
63 | current[:cost] = cost(current[:vector], cities)
64 | best = current
65 | tabu_list = Array.new(tabu_list_size)
66 | max_iter.times do |iter|
67 | candidates = Array.new(candidate_list_size) do |i|
68 | generate_candidate(current, tabu_list, cities)
69 | end
70 | candidates.sort! {|x,y| x.first[:cost] <=> y.first[:cost]}
71 | best_candidate = candidates.first[0]
72 | best_candidate_edges = candidates.first[1]
73 | if best_candidate[:cost] < current[:cost]
74 | current = best_candidate
75 | best = best_candidate if best_candidate[:cost] < best[:cost]
76 | best_candidate_edges.each {|edge| tabu_list.push(edge)}
77 | tabu_list.pop while tabu_list.size > tabu_list_size
78 | end
79 | puts " > iteration #{(iter+1)}, best=#{best[:cost]}"
80 | end
81 | return best
82 | end
83 |
84 | if __FILE__ == $0
85 | # problem configuration
86 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
87 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
88 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
89 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
90 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
91 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
92 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
93 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
94 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
95 | # algorithm configuration
96 | max_iter = 100
97 | tabu_list_size = 15
98 | max_candidates = 50
99 | # execute the algorithm
100 | best = search(berlin52, tabu_list_size, max_candidates, max_iter)
101 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
102 | end
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_adaptive_random_search.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for adaptive_random_search.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../adaptive_random_search"
9 |
10 | class TC_AdaptiveRandomSearch < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | # optima
21 | assert_equal(0, objective_function([0,0]))
22 | end
23 |
24 | # test the uniform sampling within bounds
25 | def test_rand_in_bounds
26 | # positive, zero offset
27 | x = rand_in_bounds(0, 20)
28 | assert_operator(x, :>=, 0)
29 | assert_operator(x, :<, 20)
30 | # negative
31 | x = rand_in_bounds(-20, -1)
32 | assert_operator(x, :>=, -20)
33 | assert_operator(x, :<, -1)
34 | # both
35 | x = rand_in_bounds(-10, 20)
36 | assert_operator(x, :>=, -10)
37 | assert_operator(x, :<, 20)
38 | end
39 |
40 | # test the generation of random vectors
41 | def test_random_vector
42 | bounds, trials, size = [-3,3], 300, 20
43 | minmax = Array.new(size) {bounds}
44 | trials.times do
45 | vector, sum = random_vector(minmax), 0.0
46 | assert_equal(size, vector.size)
47 | vector.each do |v|
48 | assert_operator(v, :>=, bounds[0])
49 | assert_operator(v, :<, bounds[1])
50 | sum += v
51 | end
52 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
53 | end
54 | end
55 |
56 | # test the construction of a step
57 | def test_take_step
58 | # step within stepsize
59 | p = take_step([[0, 100]], [50], 3.3)
60 | assert_operator(p[0], :>=, 50-3.3)
61 | assert_operator(p[0], :<=, 50+3.3)
62 | # snap to bounds
63 | p = take_step([[0, 1]], [0], 3.3)
64 | assert_operator(p[0], :>=, 0)
65 | assert_operator(p[0], :<, 1)
66 | end
67 |
68 | # test the calculation of the large step size
69 | def test_large_step_size
70 | # test use small factor
71 | s = large_step_size(0, 1, 2, 3, 100)
72 | assert_equal(1*2, s)
73 | # test use large factor
74 | s = large_step_size(100, 1, 2, 3, 100)
75 | assert_equal(1*3, s)
76 | end
77 |
78 | # test the construction of steps
79 | def test_take_steps
80 | 20.times do
81 | step1, step2 = take_steps([[0,10]], {:vector=>[5]}, 1, 3)
82 | # small
83 | assert_not_nil(step1[:vector])
84 | assert_not_nil(step1[:cost])
85 | assert_operator(step1[:vector][0], :>=, 5-1)
86 | assert_operator(step1[:vector][0], :<, 5+1)
87 | # large
88 | assert_not_nil(step2[:vector])
89 | assert_not_nil(step2[:cost])
90 | assert_operator(step2[:vector][0], :>=, 5-3)
91 | assert_operator(step2[:vector][0], :<, 5+3)
92 | end
93 | end
94 |
95 | # helper for turning off STDOUT
96 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
97 | def silence_stream(stream)
98 | old_stream = stream.dup
99 | stream.reopen('/dev/null')
100 | stream.sync = true
101 | yield
102 | ensure
103 | stream.reopen(old_stream)
104 | end
105 |
106 | # test that the algorithm can solve the problem
107 | def test_search
108 | best = nil
109 | silence_stream(STDOUT) do
110 | best = search(1000, [[-5,5],[-5,5]], 0.05, 1.3, 3.0, 10, 30)
111 | end
112 | assert_not_nil(best[:cost])
113 | assert_in_delta(0.0, best[:cost], 0.1)
114 | end
115 |
116 | end
117 |
--------------------------------------------------------------------------------
/src/algorithms/neural/tests/tc_perceptron.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for perceptron.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../perceptron"
9 |
10 | class TC_Perceptron < Test::Unit::TestCase
11 |
12 | # test the generation of random vectors
13 | def test_random_vector
14 | bounds, trials, size = [-3,3], 300, 20
15 | minmax = Array.new(size) {bounds}
16 | trials.times do
17 | vector, sum = random_vector(minmax), 0.0
18 | assert_equal(size, vector.size)
19 | vector.each do |v|
20 | assert_operator(v, :>=, bounds[0])
21 | assert_operator(v, :<, bounds[1])
22 | sum += v
23 | end
24 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
25 | end
26 | end
27 |
28 | # test weight initialization
29 | def test_initialize_weights
30 | w = initialize_weights(10)
31 | assert_equal(11, w.size)
32 | w.each do |v|
33 | assert_operator(v, :>=, -1)
34 | assert_operator(v, :<, 1)
35 | end
36 | end
37 |
38 | # test weight updates
39 | def test_update_weights
40 | # no error, no change, one inputs
41 | w = [0.5,0.5,0.5]
42 | update_weights(2, w, [1,1], 1.0, 1.0, 0.9)
43 | w.each{|x| assert_equal(0.5, x)}
44 | # no error, no change, zero inputs
45 | w = [0.5,0.5,0.5]
46 | update_weights(2, w, [1,1], 0.0, 0.0, 0.9)
47 | w.each{|x| assert_equal(0.5, x)}
48 | # an update
49 | w = [0.5,0.5,0.5]
50 | update_weights(2, w, [1,1], 1.0, 0.0, 0.9)
51 | w.each{|x| assert_equal(1.4, x)}
52 | end
53 |
54 | # test weighted sum function
55 | def test_activate
56 | assert_equal(5.0, activate([1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0]))
57 | assert_equal(2.5, activate([0.5, 0.5, 0.5, 0.5, 0.5], [1.0, 1.0, 1.0, 1.0]))
58 | assert_equal(-6.062263, activate([-6.072185,2.454509,-6.062263], [0, 0]))
59 | end
60 |
61 | # test the transfer function
62 | def test_transfer
63 | assert_equal(0, transfer(-1))
64 | assert_equal(1, transfer(0))
65 | assert_equal(1, transfer(1))
66 | end
67 |
68 | # test activation + transfer
69 | def test_get_output
70 | assert_equal(1, get_output([1,1,1], [1,1]))
71 | assert_equal(0, get_output([-1,-1,-1], [1,1]))
72 | end
73 |
74 | # helper for turning off STDOUT
75 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
76 | def silence_stream(stream)
77 | old_stream = stream.dup
78 | stream.reopen('/dev/null')
79 | stream.sync = true
80 | yield
81 | ensure
82 | stream.reopen(old_stream)
83 | end
84 |
85 | # test the training of weights
86 | def test_train_weights
87 | domain = [[0,0,0], [0,1,1], [1,0,1], [1,1,1]]
88 | w = [-1,-1,-1]
89 | silence_stream(STDOUT) do
90 | train_weights(w, domain, 2, 10, 0.5)
91 | end
92 | w.each {|x| assert_not_equal(-1, x) }
93 | end
94 |
95 | # test the testing of weights
96 | def test_test_weights
97 | rs = nil
98 | domain = [[0,0,0], [0,1,1], [1,0,1], [1,1,1]]
99 | w = [0.5,0.5,-0.5]
100 | silence_stream(STDOUT) do
101 | rs = test_weights(w, domain, 2)
102 | end
103 | assert_equal(4, rs)
104 | end
105 |
106 | # test that the algorithm can solve the problem
107 | def test_search
108 | domain = [[0,0,0], [0,1,1], [1,0,1], [1,1,1]]
109 | weights = nil
110 | silence_stream(STDOUT) do
111 | weights = execute(domain, 2, 20, 0.1)
112 | end
113 | assert_equal(3, weights.length)
114 | rs = nil
115 | silence_stream(STDOUT) do
116 | rs = test_weights(weights, domain, 2)
117 | end
118 | assert_equal(4, rs)
119 | end
120 |
121 | end
122 |
--------------------------------------------------------------------------------
/src/algorithms/neural/som.rb:
--------------------------------------------------------------------------------
1 | # Self-Organizing Map Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def random_vector(minmax)
8 | return Array.new(minmax.size) do |i|
9 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
10 | end
11 | end
12 |
13 | def initialize_vectors(domain, width, height)
14 | codebook_vectors = []
15 | width.times do |x|
16 | height.times do |y|
17 | codebook = {}
18 | codebook[:vector] = random_vector(domain)
19 | codebook[:coord] = [x,y]
20 | codebook_vectors << codebook
21 | end
22 | end
23 | return codebook_vectors
24 | end
25 |
26 | def euclidean_distance(c1, c2)
27 | sum = 0.0
28 | c1.each_index {|i| sum += (c1[i]-c2[i])**2.0}
29 | return Math.sqrt(sum)
30 | end
31 |
32 | def get_best_matching_unit(codebook_vectors, pattern)
33 | best, b_dist = nil, nil
34 | codebook_vectors.each do |codebook|
35 | dist = euclidean_distance(codebook[:vector], pattern)
36 | best,b_dist = codebook,dist if b_dist.nil? or disttraining: neighbors=#{neighbors.size}, bmu_dist=#{dist}"
69 | end
70 | end
71 |
72 | def summarize_vectors(vectors)
73 | minmax = Array.new(vectors.first[:vector].size){[1,0]}
74 | vectors.each do |c|
75 | c[:vector].each_with_index do |v,i|
76 | minmax[i][0] = v if vminmax[i][1]
78 | end
79 | end
80 | s = ""
81 | minmax.each_with_index {|bounds,i| s << "#{i}=#{bounds.inspect} "}
82 | puts "Vector details: #{s}"
83 | return minmax
84 | end
85 |
86 | def test_network(codebook_vectors, shape, num_trials=100)
87 | error = 0.0
88 | num_trials.times do
89 | pattern = random_vector(shape)
90 | bmu,dist = get_best_matching_unit(codebook_vectors, pattern)
91 | error += dist
92 | end
93 | error /= num_trials.to_f
94 | puts "Finished, average error=#{error}"
95 | return error
96 | end
97 |
98 | def execute(domain, shape, iterations, l_rate, neigh_size, width, height)
99 | vectors = initialize_vectors(domain, width, height)
100 | summarize_vectors(vectors)
101 | train_network(vectors, shape, iterations, l_rate, neigh_size)
102 | test_network(vectors, shape)
103 | summarize_vectors(vectors)
104 | return vectors
105 | end
106 |
107 | if __FILE__ == $0
108 | # problem configuration
109 | domain = [[0.0,1.0],[0.0,1.0]]
110 | shape = [[0.3,0.6],[0.3,0.6]]
111 | # algorithm configuration
112 | iterations = 100
113 | l_rate = 0.3
114 | neigh_size = 5
115 | width, height = 4, 5
116 | # execute the algorithm
117 | execute(domain, shape, iterations, l_rate, neigh_size, width, height)
118 | end
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_grasp.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for grasp.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../grasp"
9 |
10 | class TC_GRASP < Test::Unit::TestCase
11 |
12 | # test the rounding in the euclidean distance
13 | def test_euc_2d
14 | assert_equal(0, euc_2d([0,0], [0,0]))
15 | assert_equal(0, euc_2d([1.1,1.1], [1.1,1.1]))
16 | assert_equal(1, euc_2d([1,1], [2,2]))
17 | assert_equal(3, euc_2d([-1,-1], [1,1]))
18 | end
19 |
20 | # test tour cost includes return to origin
21 | def test_cost
22 | cities = [[0,0], [1,1], [2,2], [3,3]]
23 | assert_equal(1*2, cost([0,1], cities))
24 | assert_equal(3+4, cost([0,1,2,3], cities))
25 | assert_equal(4*2, cost([0, 3], cities))
26 | end
27 |
28 | # test the two opt procedure
29 | def test_stochastic_two_opt
30 | perm = Array.new(10){|i| i}
31 | 200.times do
32 | other = stochastic_two_opt(perm)
33 | assert_equal(perm.size, other.size)
34 | assert_not_equal(perm, other)
35 | assert_not_same(perm, other)
36 | other.each {|x| assert(perm.include?(x), "#{x}") }
37 | end
38 | end
39 |
40 | # test the local search procedure
41 | def test_local_search
42 | # improvement
43 | best = {:vector=>[0,1,2,3,4]}
44 | cities = [[0,0],[3,3],[1,1],[2,2],[4,4]]
45 | best[:cost] = cost(best[:vector], cities)
46 | rs = local_search(best, cities, 20)
47 | assert_not_nil(rs)
48 | assert_not_nil(rs[:vector])
49 | assert_not_nil(rs[:cost])
50 | assert_not_same(best, rs)
51 | assert_not_equal(best[:vector], rs[:vector])
52 | assert_not_equal(best[:cost], rs[:cost])
53 | # no improvement
54 | best = {:vector=>[0,2,3,1,4]}
55 | best[:cost] = cost(best[:vector], cities)
56 | rs = local_search(best, cities, 10)
57 | assert_not_nil(rs)
58 | assert_equal(best[:cost], rs[:cost])
59 | end
60 |
61 | # test the construction of a greedy solution
62 | def test_construct_randomized_greedy_solution
63 | cities = [[0,0],[3,3],[1,1],[2,2],[4,4]]
64 | rs = construct_randomized_greedy_solution(cities, 0.3)
65 | assert_not_nil(rs)
66 | assert_not_nil(rs[:vector])
67 | assert_not_nil(rs[:cost])
68 | assert_equal(cities.size, rs[:vector].size)
69 | rs[:vector].each {|x| assert([0,1,2,3,4].include?(x), "#{x}") }
70 | # TODO test if the created solution is greedy (NN connections?)
71 | end
72 |
73 | # helper for turning off STDOUT
74 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
75 | def silence_stream(stream)
76 | old_stream = stream.dup
77 | stream.reopen('/dev/null')
78 | stream.sync = true
79 | yield
80 | ensure
81 | stream.reopen(old_stream)
82 | end
83 |
84 | # test that the algorithm can solve the problem
85 | def test_search
86 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
87 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
88 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
89 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
90 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
91 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
92 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
93 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
94 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
95 | best = nil
96 | silence_stream(STDOUT) do
97 | best = search(berlin52, 50, 70, 0.3)
98 | end
99 | # better than a NN solution's cost
100 | assert_not_nil(best[:cost])
101 | assert_in_delta(7542, best[:cost], 3000)
102 | end
103 |
104 | end
105 |
--------------------------------------------------------------------------------
/src/algorithms/physical/tests/tc_harmony_search.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for harmony_search.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../harmony_search"
9 |
10 | class TC_HarmontySearch < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | # optima
21 | assert_equal(0, objective_function([0,0]))
22 | end
23 |
24 | # test the uniform sampling within bounds
25 | def test_rand_in_bounds
26 | # positive, zero offset
27 | x = rand_in_bounds(0, 20)
28 | assert_operator(x, :>=, 0)
29 | assert_operator(x, :<, 20)
30 | # negative
31 | x = rand_in_bounds(-20, -1)
32 | assert_operator(x, :>=, -20)
33 | assert_operator(x, :<, -1)
34 | # both
35 | x = rand_in_bounds(-10, 20)
36 | assert_operator(x, :>=, -10)
37 | assert_operator(x, :<, 20)
38 | end
39 |
40 | # test the generation of random vectors
41 | def test_random_vector
42 | bounds, trials, size = [-3,3], 300, 20
43 | minmax = Array.new(size) {bounds}
44 | trials.times do
45 | vector, sum = random_vector(minmax), 0.0
46 | assert_equal(size, vector.size)
47 | vector.each do |v|
48 | assert_operator(v, :>=, bounds[0])
49 | assert_operator(v, :<, bounds[1])
50 | sum += v
51 | end
52 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
53 | end
54 | end
55 |
56 | # test the creation of a random candidate
57 | def test_create_random_harmony
58 | s = create_random_harmony([[0,1],[0,1]])
59 | assert_not_nil(s)
60 | assert_not_nil(s[:vector])
61 | assert_not_nil(s[:fitness])
62 | s[:vector].each do |x|
63 | assert_operator(x, :>=, 0)
64 | assert_operator(x, :<=, 1)
65 | end
66 | end
67 |
68 | # test initializing memory
69 | def test_initialize_harmony_memory
70 | m = initialize_harmony_memory([[0,1],[0,1]], 10)
71 | assert_equal(10, m.size)
72 | end
73 |
74 | # test the creation of a harmony
75 | def test_create_harmony
76 | memory = [{:vector=>[0,0]}, {:vector=>[0,0]}, {:vector=>[0,0]}]
77 | # consideration, no adjustment
78 | rs = create_harmony([[0,1],[0,1]], memory, 1.0, 0.0, 0.005)
79 | assert_equal(2, rs[:vector].size)
80 | rs[:vector].each{|x| assert_equal(0, x)}
81 | # consideration, all adjustment
82 | rs = create_harmony([[0,1],[0,1]], memory, 1.0, 1.0, 0.005)
83 | assert_equal(2, rs[:vector].size)
84 | rs[:vector].each do |x|
85 | assert_operator(x, :>=, 0)
86 | assert_operator(x, :<=, 1)
87 | end
88 | # no consideration
89 | rs = create_harmony([[0,1],[0,1]], memory, 0.0, 0.0, 0.005)
90 | assert_equal(2, rs[:vector].size)
91 | rs[:vector].each do |x|
92 | assert_operator(x, :>=, 0)
93 | assert_operator(x, :<=, 1)
94 | end
95 | end
96 |
97 | # helper for turning off STDOUT
98 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
99 | def silence_stream(stream)
100 | old_stream = stream.dup
101 | stream.reopen('/dev/null')
102 | stream.sync = true
103 | yield
104 | ensure
105 | stream.reopen(old_stream)
106 | end
107 |
108 | # test that the algorithm can solve the problem
109 | def test_search
110 | best = nil
111 | silence_stream(STDOUT) do
112 | best = search([[-5,5],[-5,5]], 100, 20, 0.95, 0.7, 0.5)
113 | end
114 | assert_not_nil(best[:fitness])
115 | assert_in_delta(0.0, best[:fitness], 0.1)
116 | end
117 |
118 | end
119 |
--------------------------------------------------------------------------------
/src/algorithms/neural/hopfield.rb:
--------------------------------------------------------------------------------
1 | # Hopfield Network Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def random_vector(minmax)
8 | return Array.new(minmax.size) do |i|
9 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
10 | end
11 | end
12 |
13 | def initialize_weights(problem_size)
14 | minmax = Array.new(problem_size) {[-0.5,0.5]}
15 | return random_vector(minmax)
16 | end
17 |
18 | def create_neuron(num_inputs)
19 | neuron = {}
20 | neuron[:weights] = initialize_weights(num_inputs)
21 | return neuron
22 | end
23 |
24 | def transfer(activation)
25 | return (activation >= 0) ? 1 : -1
26 | end
27 |
28 | def propagate_was_change?(neurons)
29 | i = rand(neurons.size)
30 | activation = 0
31 | neurons.each_with_index do |other, j|
32 | activation += other[:weights][i]*other[:output] if i!=j
33 | end
34 | output = transfer(activation)
35 | change = output != neurons[i][:output]
36 | neurons[i][:output] = output
37 | return change
38 | end
39 |
40 | def get_output(neurons, pattern, evals=100)
41 | vector = pattern.flatten
42 | neurons.each_with_index {|neuron,i| neuron[:output] = vector[i]}
43 | evals.times { propagate_was_change?(neurons) }
44 | return Array.new(neurons.size){|i| neurons[i][:output]}
45 | end
46 |
47 | def train_network(neurons, patters)
48 | neurons.each_with_index do |neuron, i|
49 | for j in ((i+1)...neurons.size) do
50 | next if i==j
51 | wij = 0.0
52 | patters.each do |pattern|
53 | vector = pattern.flatten
54 | wij += vector[i]*vector[j]
55 | end
56 | neurons[i][:weights][j] = wij
57 | neurons[j][:weights][i] = wij
58 | end
59 | end
60 | end
61 |
62 | def to_binary(vector)
63 | return Array.new(vector.size){|i| ((vector[i]==-1) ? 0 : 1)}
64 | end
65 |
66 | def print_patterns(provided, expected, actual)
67 | p, e, a = to_binary(provided), to_binary(expected), to_binary(actual)
68 | p1, p2, p3 = p[0..2].join(', '), p[3..5].join(', '), p[6..8].join(', ')
69 | e1, e2, e3 = e[0..2].join(', '), e[3..5].join(', '), e[6..8].join(', ')
70 | a1, a2, a3 = a[0..2].join(', '), a[3..5].join(', '), a[6..8].join(', ')
71 | puts "Provided Expected Got"
72 | puts "#{p1} #{e1} #{a1}"
73 | puts "#{p2} #{e2} #{a2}"
74 | puts "#{p3} #{e3} #{a3}"
75 | end
76 |
77 | def calculate_error(expected, actual)
78 | sum = 0
79 | expected.each_with_index do |v, i|
80 | sum += 1 if expected[i]!=actual[i]
81 | end
82 | return sum
83 | end
84 |
85 | def perturb_pattern(vector, num_errors=1)
86 | perturbed = Array.new(vector)
87 | indicies = [rand(perturbed.size)]
88 | while indicies.size < num_errors do
89 | index = rand(perturbed.size)
90 | indicies << index if !indicies.include?(index)
91 | end
92 | indicies.each {|i| perturbed[i] = ((perturbed[i]==1) ? -1 : 1)}
93 | return perturbed
94 | end
95 |
96 | def test_network(neurons, patterns)
97 | error = 0.0
98 | patterns.each do |pattern|
99 | vector = pattern.flatten
100 | perturbed = perturb_pattern(vector)
101 | output = get_output(neurons, perturbed)
102 | error += calculate_error(vector, output)
103 | print_patterns(perturbed, vector, output)
104 | end
105 | error = error / patterns.size.to_f
106 | puts "Final Result: avg pattern error=#{error}"
107 | return error
108 | end
109 |
110 | def execute(patters, num_inputs)
111 | neurons = Array.new(num_inputs) { create_neuron(num_inputs) }
112 | train_network(neurons, patters)
113 | test_network(neurons, patters)
114 | return neurons
115 | end
116 |
117 | if __FILE__ == $0
118 | # problem configuration
119 | num_inputs = 9
120 | p1 = [[1,1,1],[-1,1,-1],[-1,1,-1]] # T
121 | p2 = [[1,-1,1],[1,-1,1],[1,1,1]] # U
122 | patters = [p1, p2]
123 | # execute the algorithm
124 | execute(patters, num_inputs)
125 | end
126 |
--------------------------------------------------------------------------------
/book/c_swarm.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 | % This is a chapter
6 |
7 | \renewcommand{\bibsection}{\subsection{\bibname}}
8 | \begin{bibunit}
9 |
10 | \chapter{Swarm Algorithms}
11 | \label{ch:swarm}
12 | \index{Swarm Algorithms}
13 | \index{Swarm Intelligence}
14 | \index{Collective Intelligence}
15 | \index{Ant Colony Optimization}
16 | \index{Particle Swarm Optimization}
17 |
18 | \section{Overview}
19 | This chapter describes Swarm Algorithms.
20 |
21 | \subsection{Swarm Intelligence}
22 | Swarm intelligence is the study of computational systems inspired by the `collective intelligence'. Collective Intelligence emerges through the cooperation of large numbers of homogeneous agents in the environment. Examples include schools of fish, flocks of birds, and colonies of ants. Such intelligence is decentralized, self-organizing and distributed through out an environment. In nature such systems are commonly used to solve problems such as effective foraging for food, prey evading, or colony re-location. The information is typically stored throughout the participating homogeneous agents, or is stored or communicated in the environment itself such as through the use of pheromones in ants, dancing in bees, and proximity in fish and birds.
23 |
24 | The paradigm consists of two dominant sub-fields 1) \emph{Ant Colony Optimization} that investigates probabilistic algorithms inspired by the stigmergy and foraging behavior of ants, and 2) \emph{Particle Swarm Optimization} that investigates probabilistic algorithms inspired by the flocking, schooling and herding. Like evolutionary computation, swarm intelligence `algorithms' or `strategies' are considered adaptive strategies and are typically applied to search and optimization domains.
25 |
26 | % References
27 | \subsection{References}
28 | % classical
29 | Seminal books on the field of Swarm Intelligence include ``\emph{Swarm Intelligence}'' by Kennedy, Eberhart and Shi \cite{Kennedy2001}, and ``\emph{Swarm Intelligence: From Natural to Artificial Systems}'' by Bonabeau, Dorigo, and Theraulaz \cite{Bonabeau1999}. Another excellent text book on the area is ``\emph{Fundamentals of Computational Swarm Intelligence}'' by Engelbrecht \cite{Engelbrecht2006}. The seminal book reference for the field of Ant Colony Optimization is ``\emph{Ant Colony Optimization}'' by Dorigo and St\"utzle \cite{Dorigo2004}.
30 |
31 | %
32 | % Extensions
33 | %
34 | \subsection{Extensions}
35 | There are many other algorithms and classes of algorithm that were not described from the field of Swarm Intelligence, not limited to:
36 |
37 | \begin{itemize}
38 | \item \textbf{Ant Algorithms}: such as Max-Min Ant Systems \cite{Stutzle2000} Rank-Based Ant Systems \cite{Bullnheimer1999}, Elitist Ant Systems \cite{Dorigo1996}, Hyper Cube Ant Colony Optimization \cite{Blum2001} Approximate Nondeterministic Tree-Search (ANTS) \cite{Maniezzo1999} and Multiple Ant Colony System \cite{Gambardella1999}.
39 | \item \textbf{Bee Algorithms}: such as Bee System and Bee Colony Optimization \cite{Lucic2001}, the Honey Bee Algorithm \cite{Tovey2004}, and Artificial Bee Colony Optimization \cite{Karaboga2005, Basturk2006}.
40 | \item \textbf{Other Social Insects}: algorithms inspired by other social insects besides ants and bees, such as the Firefly Algorithm \cite{Yang2008} and the Wasp Swarm Algorithm \cite{Pinto2007}.
41 | \item \textbf{Extensions to Particle Swarm}: such as Repulsive Particle Swarm Optimization \cite{Urfalioglu2004}.
42 | \item \textbf{Bacteria Algorithms}: such as the Bacteria Chemotaxis Algorithm \cite{Muller2002}.
43 | \end{itemize}
44 |
45 | \putbib
46 | \end{bibunit}
47 |
48 |
49 | \newpage\begin{bibunit}\input{book/a_swarm/pso}\putbib\end{bibunit}
50 | \newpage\begin{bibunit}\input{book/a_swarm/ant_system}\putbib\end{bibunit}
51 | \newpage\begin{bibunit}\input{book/a_swarm/ant_colony_system}\putbib\end{bibunit}
52 | \newpage\begin{bibunit}\input{book/a_swarm/bees_algorithm}\putbib\end{bibunit}
53 | \newpage\begin{bibunit}\input{book/a_swarm/bfoa}\putbib\end{bibunit}
54 |
--------------------------------------------------------------------------------
/src/algorithms/immune/clonal_selection_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Clonal Selection Algorithm (CLONALG) in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x**2.0)}
9 | end
10 |
11 | def decode(bitstring, search_space, bits_per_param)
12 | vector = []
13 | search_space.each_with_index do |bounds, i|
14 | off, sum = i*bits_per_param, 0.0
15 | param = bitstring[off...(off+bits_per_param)].reverse
16 | param.size.times do |j|
17 | sum += ((param[j].chr=='1') ? 1.0 : 0.0) * (2.0 ** j.to_f)
18 | end
19 | min, max = bounds
20 | vector << min + ((max-min)/((2.0**bits_per_param.to_f)-1.0)) * sum
21 | end
22 | return vector
23 | end
24 |
25 | def evaluate(pop, search_space, bits_per_param)
26 | pop.each do |p|
27 | p[:vector] = decode(p[:bitstring], search_space, bits_per_param)
28 | p[:cost] = objective_function(p[:vector])
29 | end
30 | end
31 |
32 | def random_bitstring(num_bits)
33 | return (0...num_bits).inject(""){|s,i| s<<((rand<0.5) ? "1" : "0")}
34 | end
35 |
36 | def point_mutation(bitstring, rate)
37 | child = ""
38 | bitstring.size.times do |i|
39 | bit = bitstring[i].chr
40 | child << ((rand()y[:cost]}
55 | range = pop.last[:cost] - pop.first[:cost]
56 | if range == 0.0
57 | pop.each {|p| p[:affinity] = 1.0}
58 | else
59 | pop.each {|p| p[:affinity] = 1.0-(p[:cost]/range)}
60 | end
61 | end
62 |
63 | def clone_and_hypermutate(pop, clone_factor)
64 | clones = []
65 | num_clones = num_clones(pop.size, clone_factor)
66 | calculate_affinity(pop)
67 | pop.each do |antibody|
68 | m_rate = calculate_mutation_rate(antibody)
69 | num_clones.times do
70 | clone = {}
71 | clone[:bitstring] = point_mutation(antibody[:bitstring], m_rate)
72 | clones << clone
73 | end
74 | end
75 | return clones
76 | end
77 |
78 | def random_insertion(search_space, pop, num_rand, bits_per_param)
79 | return pop if num_rand == 0
80 | rands = Array.new(num_rand) do |i|
81 | {:bitstring=>random_bitstring(search_space.size*bits_per_param)}
82 | end
83 | evaluate(rands, search_space, bits_per_param)
84 | return (pop+rands).sort{|x,y| x[:cost]<=>y[:cost]}.first(pop.size)
85 | end
86 |
87 | def search(search_space, max_gens, pop_size, clone_factor, num_rand, bits_per_param=16)
88 | pop = Array.new(pop_size) do |i|
89 | {:bitstring=>random_bitstring(search_space.size*bits_per_param)}
90 | end
91 | evaluate(pop, search_space, bits_per_param)
92 | best = pop.min{|x,y| x[:cost]<=>y[:cost]}
93 | max_gens.times do |gen|
94 | clones = clone_and_hypermutate(pop, clone_factor)
95 | evaluate(clones, search_space, bits_per_param)
96 | pop = (pop+clones).sort{|x,y| x[:cost]<=>y[:cost]}.first(pop_size)
97 | pop = random_insertion(search_space, pop, num_rand, bits_per_param)
98 | best = (pop + [best]).min{|x,y| x[:cost]<=>y[:cost]}
99 | puts " > gen #{gen+1}, f=#{best[:cost]}, s=#{best[:vector].inspect}"
100 | end
101 | return best
102 | end
103 |
104 | if __FILE__ == $0
105 | # problem configuration
106 | problem_size = 2
107 | search_space = Array.new(problem_size) {|i| [-5, +5]}
108 | # algorithm configuration
109 | max_gens = 100
110 | pop_size = 100
111 | clone_factor = 0.1
112 | num_rand = 2
113 | # execute the algorithm
114 | best = search(search_space, max_gens, pop_size, clone_factor, num_rand)
115 | puts "done! Solution: f=#{best[:cost]}, s=#{best[:vector].inspect}"
116 | end
117 |
--------------------------------------------------------------------------------
/src/programming_paradigms/tests/tc_flow.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for flow.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../flow"
9 |
10 | class TC_GeneticAlgorithm < Test::Unit::TestCase
11 |
12 | # test that the objective function behaves as expected
13 | def test_onemax
14 | o = nil
15 | silence_stream(STDOUT){o = EvalFlowUnit.new() }
16 | assert_equal(0, o.onemax("0000"))
17 | assert_equal(4, o.onemax("1111"))
18 | assert_equal(2, o.onemax("1010"))
19 | end
20 |
21 | # TODO consider testing the stop condition
22 |
23 | # test that members of the population are selected
24 | def test_binary_tournament
25 | o = nil
26 | silence_stream(STDOUT){o = SelectFlowUnit.new(Queue.new,Queue.new) }
27 | pop = Array.new(10) {|i| {:fitness=>i} }
28 | 10.times {assert(pop.include?(o.binary_tournament(pop)))}
29 | end
30 |
31 | # test uniform crossover
32 | def test_uniform_crossover
33 | p1 = "0000000000"
34 | p2 = "1111111111"
35 | o = nil
36 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,0.0) }
37 | assert_equal(p1, o.uniform_crossover(p1,p2))
38 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,0.0) }
39 | assert_not_same(p1, o.uniform_crossover(p1,p2))
40 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,1.0) }
41 | s = o.uniform_crossover(p1,p2)
42 | s.size.times {|i| assert( (p1[i]==s[i]) || (p2[i]==s[i]) ) }
43 | end
44 |
45 | # test point mutations at the limits
46 | def test_point_mutation
47 | o = nil
48 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,0.0,0.0) }
49 | assert_equal("0000000000", o.point_mutation("0000000000"))
50 | assert_equal("1111111111", o.point_mutation("1111111111"))
51 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,0.0,1.0) }
52 | assert_equal("1111111111", o.point_mutation("0000000000"))
53 | assert_equal("0000000000", o.point_mutation("1111111111"))
54 | end
55 |
56 | # test that the observed changes approximate the intended probability
57 | def test_point_mutation_ratio
58 | o = nil
59 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,0.0,0.5) }
60 | changes = 0
61 | 100.times do
62 | s = o.point_mutation("0000000000")
63 | changes += (10 - s.delete('1').size)
64 | end
65 | assert_in_delta(0.5, changes.to_f/(100*10), 0.05)
66 | end
67 |
68 | # test the reproduction
69 | def test_reproduce
70 | o = nil
71 | silence_stream(STDOUT){o = VariationFlowUnit.new(Queue.new,Queue.new,0.0,0.0) }
72 | c = o.reproduce({:bitstring=>"0000000000"}, {:bitstring=>"1111111111"})
73 | assert_not_nil(c[:bitstring])
74 | assert_equal(10, c[:bitstring].size)
75 | end
76 |
77 | # test the creation of random strings
78 | def test_random_bitstring
79 | assert_equal(10, random_bitstring(10).size)
80 | assert_equal(0, random_bitstring(10).delete('0').delete('1').size)
81 | end
82 |
83 | # test the approximate proportion of 1's and 0's
84 | def test_random_bitstring_ratio
85 | s = random_bitstring(1000)
86 | assert_in_delta(0.5, (s.delete('1').size/1000.0), 0.05)
87 | assert_in_delta(0.5, (s.delete('0').size/1000.0), 0.05)
88 | end
89 |
90 | # helper for turning off STDOUT
91 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
92 | def silence_stream(stream)
93 | old_stream = stream.dup
94 | stream.reopen('/dev/null')
95 | stream.sync = true
96 | yield
97 | ensure
98 | stream.reopen(old_stream)
99 | end
100 |
101 | # test that the algorithm can solve the problem
102 | def test_search
103 | best = nil
104 | silence_stream(STDOUT) do
105 | best = search()
106 | end
107 | assert_not_nil(best[:fitness])
108 | assert_equal(64, best[:fitness])
109 | end
110 |
111 | end
112 |
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/tests/tc_evolutionary_programming.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for evolutionary_programming.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../evolutionary_programming"
9 |
10 | class TC_EvolutionaryProgramming < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | # optima
21 | assert_equal(0, objective_function([0,0]))
22 | end
23 |
24 | # test the generation of random vectors
25 | def test_random_vector
26 | bounds, trials, size = [-3,3], 300, 20
27 | minmax = Array.new(size) {bounds}
28 | trials.times do
29 | vector, sum = random_vector(minmax), 0.0
30 | assert_equal(size, vector.size)
31 | vector.each do |v|
32 | assert_operator(v, :>=, bounds[0])
33 | assert_operator(v, :<, bounds[1])
34 | sum += v
35 | end
36 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
37 | end
38 | end
39 |
40 | # test default rand gaussian
41 | def test_random_gaussian_default
42 | mean, stdev = 0.0, 1.0
43 | all = []
44 | 1000.times do
45 | all << random_gaussian(mean, stdev)
46 | assert_in_delta(mean, all.last, 6*stdev)
47 | end
48 | m = all.inject(0){|sum,x| sum + x} / all.size.to_f
49 | assert_in_delta(mean, m, 0.1)
50 | end
51 |
52 | # test rand gaussian in different range
53 | def test_random_gaussian_non_default
54 | mean, stdev = 50, 10
55 | all = []
56 | 1000.times do
57 | all << random_gaussian(mean, stdev)
58 | assert_in_delta(mean, all.last, 6*stdev)
59 | end
60 | m = all.inject(0){|sum,x| sum + x} / all.size.to_f
61 | assert_in_delta(m, mean, 1.0)
62 | end
63 |
64 | # test mutation
65 | def test_mutate
66 | rs = mutate({:vector=>[0.5,0.5],:strategy=>[0.5,0.5]}, [[0,1],[0,1]])
67 | assert_not_nil(rs)
68 | assert_not_nil(rs[:vector])
69 | assert_not_nil(rs[:strategy])
70 | rs[:vector].each do |v|
71 | assert_operator(v, :>=, 0)
72 | assert_operator(v, :<=, 1)
73 | end
74 | end
75 |
76 | # test tournament
77 | def test_tournament
78 | # win
79 | candidate = {:fitness=>0}
80 | tournament(candidate, [{:fitness=>1}], 1)
81 | assert_not_nil(candidate[:wins])
82 | assert_equal(1, candidate[:wins])
83 | # loss
84 | candidate = {:fitness=>1}
85 | tournament(candidate, [{:fitness=>0}], 1)
86 | assert_not_nil(candidate[:wins])
87 | assert_equal(0, candidate[:wins])
88 | end
89 |
90 | # test initializing a new pop
91 | def test_init_population
92 | pop = init_population( [[0,1],[0,1]], 1000)
93 | assert_equal(1000, pop.size)
94 | pop.each do |p|
95 | assert_not_nil(p[:vector])
96 | assert_not_nil(p[:strategy])
97 | assert_not_nil(p[:fitness])
98 | assert_equal(2, p[:vector].size)
99 | assert_equal(2, p[:strategy].size)
100 | p[:vector].each do |v|
101 | assert_operator(v, :>=, 0)
102 | assert_operator(v, :<=, 1)
103 | end
104 | end
105 | end
106 |
107 | # helper for turning off STDOUT
108 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
109 | def silence_stream(stream)
110 | old_stream = stream.dup
111 | stream.reopen('/dev/null')
112 | stream.sync = true
113 | yield
114 | ensure
115 | stream.reopen(old_stream)
116 | end
117 |
118 | # test that the algorithm can solve the problem
119 | def test_search
120 | best = nil
121 | silence_stream(STDOUT) do
122 | best = search(50, [[-5,5],[-5,5]], 50, 3)
123 | end
124 | assert_not_nil(best[:fitness])
125 | assert_in_delta(0.0, best[:fitness], 0.001)
126 | end
127 |
128 | end
129 |
--------------------------------------------------------------------------------
/src/algorithms/immune/optainet.rb:
--------------------------------------------------------------------------------
1 | # Optimization Artificial Immune Network (opt-aiNet) in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x**2.0)}
9 | end
10 |
11 | def random_vector(minmax)
12 | return Array.new(minmax.size) do |i|
13 | minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
14 | end
15 | end
16 |
17 | def random_gaussian(mean=0.0, stdev=1.0)
18 | u1 = u2 = w = 0
19 | begin
20 | u1 = 2 * rand() - 1
21 | u2 = 2 * rand() - 1
22 | w = u1 * u1 + u2 * u2
23 | end while w >= 1
24 | w = Math.sqrt((-2.0 * Math.log(w)) / w)
25 | return mean + (u2 * w) * stdev
26 | end
27 |
28 | def clone(parent)
29 | v = Array.new(parent[:vector].size) {|i| parent[:vector][i]}
30 | return {:vector=>v}
31 | end
32 |
33 | def mutation_rate(beta, normalized_cost)
34 | return (1.0/beta) * Math.exp(-normalized_cost)
35 | end
36 |
37 | def mutate(beta, child, normalized_cost)
38 | child[:vector].each_with_index do |v, i|
39 | alpha = mutation_rate(beta, normalized_cost)
40 | child[:vector][i] = v + alpha * random_gaussian()
41 | end
42 | end
43 |
44 | def clone_cell(beta, num_clones, parent)
45 | clones = Array.new(num_clones) {clone(parent)}
46 | clones.each {|clone| mutate(beta, clone, parent[:norm_cost])}
47 | clones.each{|c| c[:cost] = objective_function(c[:vector])}
48 | clones.sort!{|x,y| x[:cost] <=> y[:cost]}
49 | return clones.first
50 | end
51 |
52 | def calculate_normalized_cost(pop)
53 | pop.sort!{|x,y| x[:cost]<=>y[:cost]}
54 | range = pop.last[:cost] - pop.first[:cost]
55 | if range == 0.0
56 | pop.each {|p| p[:norm_cost] = 1.0}
57 | else
58 | pop.each {|p| p[:norm_cost] = 1.0-(p[:cost]/range)}
59 | end
60 | end
61 |
62 | def average_cost(pop)
63 | sum = pop.inject(0.0){|sum,x| sum + x[:cost]}
64 | return sum / pop.size.to_f
65 | end
66 |
67 | def distance(c1, c2)
68 | sum = 0.0
69 | c1.each_index {|i| sum += (c1[i]-c2[i])**2.0}
70 | return Math.sqrt(sum)
71 | end
72 |
73 | def get_neighborhood(cell, pop, aff_thresh)
74 | neighbors = []
75 | pop.each do |p|
76 | neighbors << p if distance(p[:vector], cell[:vector]) < aff_thresh
77 | end
78 | return neighbors
79 | end
80 |
81 | def affinity_supress(population, aff_thresh)
82 | pop = []
83 | population.each do |cell|
84 | neighbors = get_neighborhood(cell, population, aff_thresh)
85 | neighbors.sort!{|x,y| x[:cost] <=> y[:cost]}
86 | pop << cell if neighbors.empty? or cell.equal?(neighbors.first)
87 | end
88 | return pop
89 | end
90 |
91 | def search(search_space, max_gens, pop_size, num_clones, beta, num_rand, aff_thresh)
92 | pop = Array.new(pop_size) {|i| {:vector=>random_vector(search_space)} }
93 | pop.each{|c| c[:cost] = objective_function(c[:vector])}
94 | best = nil
95 | max_gens.times do |gen|
96 | pop.each{|c| c[:cost] = objective_function(c[:vector])}
97 | calculate_normalized_cost(pop)
98 | pop.sort!{|x,y| x[:cost] <=> y[:cost]}
99 | best = pop.first if best.nil? or pop.first[:cost] < best[:cost]
100 | avgCost, progeny = average_cost(pop), nil
101 | begin
102 | progeny=Array.new(pop.size){|i| clone_cell(beta, num_clones, pop[i])}
103 | end until average_cost(progeny) < avgCost
104 | pop = affinity_supress(progeny, aff_thresh)
105 | num_rand.times {pop << {:vector=>random_vector(search_space)}}
106 | puts " > gen #{gen+1}, popSize=#{pop.size}, fitness=#{best[:cost]}"
107 | end
108 | return best
109 | end
110 |
111 | if __FILE__ == $0
112 | # problem configuration
113 | problem_size = 2
114 | search_space = Array.new(problem_size) {|i| [-5, +5]}
115 | # algorithm configuration
116 | max_gens = 150
117 | pop_size = 20
118 | num_clones = 10
119 | beta = 100
120 | num_rand = 2
121 | aff_thresh = (search_space[0][1]-search_space[0][0])*0.05
122 | # execute the algorithm
123 | best = search(search_space, max_gens, pop_size, num_clones, beta, num_rand, aff_thresh)
124 | puts "done! Solution: f=#{best[:cost]}, s=#{best[:vector].inspect}"
125 | end
--------------------------------------------------------------------------------
/src/algorithms/evolutionary/tests/tc_evolution_strategies.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for evolution_strategies.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../evolution_strategies"
9 |
10 | class TC_EvolutionStrategies < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | # optima
21 | assert_equal(0, objective_function([0,0]))
22 | end
23 |
24 | # test the generation of random vectors
25 | def test_random_vector
26 | bounds, trials, size = [-3,3], 300, 20
27 | minmax = Array.new(size) {bounds}
28 | trials.times do
29 | vector, sum = random_vector(minmax), 0.0
30 | assert_equal(size, vector.size)
31 | vector.each do |v|
32 | assert_operator(v, :>=, bounds[0])
33 | assert_operator(v, :<, bounds[1])
34 | sum += v
35 | end
36 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
37 | end
38 | end
39 |
40 | # test default rand gaussian
41 | def test_random_gaussian_default
42 | mean, stdev = 0.0, 1.0
43 | all = []
44 | 1000.times do
45 | all << random_gaussian(mean, stdev)
46 | assert_in_delta(mean, all.last, 6*stdev)
47 | end
48 | m = all.inject(0){|sum,x| sum + x} / all.size.to_f
49 | assert_in_delta(mean, m, 0.1)
50 | end
51 |
52 | # test rand gaussian in different range
53 | def test_random_gaussian_non_default
54 | mean, stdev = 50, 10
55 | all = []
56 | 1000.times do
57 | all << random_gaussian(mean, stdev)
58 | assert_in_delta(mean, all.last, 6*stdev)
59 | end
60 | m = all.inject(0){|sum,x| sum + x} / all.size.to_f
61 | assert_in_delta(m, mean, 1.0)
62 | end
63 |
64 | # test mutating the strategy vars
65 | def test_mutate_problem
66 | 100.times do
67 | rs = mutate_problem([rand(), rand()], [1,1], [[0,1],[0,1]])
68 | assert_equal(2, rs.size)
69 | rs.each do |v|
70 | assert_operator(v, :>=, 0)
71 | assert_operator(v, :<=, 1)
72 | end
73 | end
74 | end
75 |
76 | # test mutating the prob vars
77 | def test_mutate_strategy
78 | rs = mutate_strategy([rand(), rand()])
79 | assert_equal(2, rs.size)
80 | # TODO can we test this more meaningfully?
81 | end
82 |
83 | # test mutation
84 | def test_mutate
85 | rs = mutate({:vector=>[0,0],:strategy=>[0.5,0.5]}, [[0,1],[0,1]])
86 | assert_not_nil(rs)
87 | assert_not_nil(rs[:vector])
88 | assert_not_nil(rs[:strategy])
89 | assert_equal(2, rs[:vector].size)
90 | assert_equal(2, rs[:strategy].size)
91 | end
92 |
93 | # test initializing a new pop
94 | def test_init_population
95 | pop = init_population( [[0,1],[0,1]], 1000)
96 | assert_equal(1000, pop.size)
97 | pop.each do |p|
98 | assert_not_nil(p[:vector])
99 | assert_not_nil(p[:strategy])
100 | assert_not_nil(p[:fitness])
101 | assert_equal(2, p[:vector].size)
102 | assert_equal(2, p[:strategy].size)
103 | p[:vector].each do |v|
104 | assert_operator(v, :>=, 0)
105 | assert_operator(v, :<=, 1)
106 | end
107 | end
108 | end
109 |
110 | # helper for turning off STDOUT
111 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
112 | def silence_stream(stream)
113 | old_stream = stream.dup
114 | stream.reopen('/dev/null')
115 | stream.sync = true
116 | yield
117 | ensure
118 | stream.reopen(old_stream)
119 | end
120 |
121 | # test that the algorithm can solve the problem
122 | def test_search
123 | best = nil
124 | silence_stream(STDOUT) do
125 | best = search(50, [[-5,5],[-5,5]], 30, 20)
126 | end
127 | assert_not_nil(best[:fitness])
128 | assert_in_delta(0.0, best[:fitness], 0.001)
129 | end
130 |
131 | end
132 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_tabu_search.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for tabu_search.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../tabu_search"
9 |
10 | class TC_TabuSearch < Test::Unit::TestCase
11 |
12 | # test the rounding in the euclidean distance
13 | def test_euc_2d
14 | assert_equal(0, euc_2d([0,0], [0,0]))
15 | assert_equal(0, euc_2d([1.1,1.1], [1.1,1.1]))
16 | assert_equal(1, euc_2d([1,1], [2,2]))
17 | assert_equal(3, euc_2d([-1,-1], [1,1]))
18 | end
19 |
20 | # test tour cost includes return to origin
21 | def test_cost
22 | cities = [[0,0], [1,1], [2,2], [3,3]]
23 | assert_equal(1*2, cost([0,1], cities))
24 | assert_equal(3+4, cost([0,1,2,3], cities))
25 | assert_equal(4*2, cost([0, 3], cities))
26 | end
27 |
28 | # test the construction of a random permutation
29 | def test_random_permutation
30 | cities = Array.new(10)
31 | 100.times do
32 | p = random_permutation(cities)
33 | assert_equal(cities.size, p.size)
34 | [0,1,2,3,4,5,6,7,8,9].each {|x| assert(p.include?(x), "#{x}") }
35 | end
36 | end
37 |
38 | # test the two opt procedure
39 | def test_stochastic_two_opt
40 | perm = Array.new(10){|i| i}
41 | 200.times do
42 | other, edges = stochastic_two_opt(perm)
43 | assert_equal(perm.size, other.size)
44 | assert_not_equal(perm, other)
45 | assert_not_same(perm, other)
46 | other.each {|x| assert(perm.include?(x), "#{x}") }
47 | # TODO test the edges
48 | assert_equal(2, edges.size)
49 | end
50 | end
51 |
52 | # test tabu testing
53 | def test_is_tabu
54 | # no tabu
55 | assert_equal(false, is_tabu?([0,1,2,3,4], []))
56 | # one tabu
57 | assert_equal(true, is_tabu?([0,1,2,3,4], [[2,3]]))
58 | assert_equal(true, is_tabu?([0,1,2,3,4], [[4,0]]))
59 | # two tabu
60 | assert_equal(true, is_tabu?([0,1,2,3,4], [[2,3],[1,2]]))
61 | end
62 |
63 | # test the generation of permutations without tabu edges
64 | def test_generate_candidate
65 | cities = [[0,0], [1,1], [2,2], [3,3], [4,4]]
66 | # empty list
67 | rs, edges = generate_candidate({:vector=>[0,1,2,3,4]}, [], cities)
68 | assert_not_nil(rs)
69 | assert_not_nil(rs[:vector])
70 | assert_not_nil(rs[:cost])
71 | assert_equal(5, rs[:vector].size)
72 | assert_equal(2, edges.size)
73 | # non-empty list
74 | 100.times do
75 | rs, edges = generate_candidate({:vector=>[0,1,2,3,4]}, [[0,1]], cities)
76 | assert_not_nil(rs)
77 | assert_not_nil(rs[:vector])
78 | assert_not_nil(rs[:cost])
79 | assert_equal(5, rs[:vector].size)
80 | assert_equal(2, edges.size)
81 | assert_equal(false, is_tabu?(rs[:vector], [0,1]))
82 | end
83 | end
84 |
85 | # helper for turning off STDOUT
86 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
87 | def silence_stream(stream)
88 | old_stream = stream.dup
89 | stream.reopen('/dev/null')
90 | stream.sync = true
91 | yield
92 | ensure
93 | stream.reopen(old_stream)
94 | end
95 |
96 | # test that the algorithm can solve the problem
97 | def test_search
98 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
99 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
100 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
101 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
102 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
103 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
104 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
105 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
106 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
107 | best = nil
108 | silence_stream(STDOUT) do
109 | best = search(berlin52, 15, 50, 50)
110 | end
111 | # better than a NN solution's cost
112 | assert_not_nil(best[:cost])
113 | assert_in_delta(7542, best[:cost], 4000)
114 | end
115 |
116 | end
117 |
--------------------------------------------------------------------------------
/src/algorithms/swarm/tests/tc_bees_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for bees_algorithm.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 David Howden. Some Rights Reserved.
5 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
6 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
7 |
8 | require "test/unit"
9 | require File.expand_path(File.dirname(__FILE__)) + "/../bees_algorithm"
10 |
11 | class TC_BeesAlgorithm < Test::Unit::TestCase
12 |
13 | # test the objective function
14 | def test_objective_function
15 | # integer
16 | assert_equal(99**2, objective_function([99]))
17 | # float
18 | assert_equal(0.1**2.0, objective_function([0.1]))
19 | # vector
20 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
21 | # optima
22 | assert_equal(0, objective_function([0,0]))
23 | end
24 |
25 | # test the generation of random vectors
26 | def test_random_vector
27 | bounds, trials, size = [-3,3], 300, 20
28 | minmax = Array.new(size) {bounds}
29 | trials.times do
30 | vector, sum = random_vector(minmax), 0.0
31 | assert_equal(size, vector.size)
32 | vector.each do |v|
33 | assert_operator(v, :>=, bounds[0])
34 | assert_operator(v, :<, bounds[1])
35 | sum += v
36 | end
37 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
38 | end
39 | end
40 |
41 | # test that create_random_bee returns a correctly initialised bee
42 | def test_create_random_bee
43 | problem_size = 3
44 | search_space = Array.new(problem_size) {|i| [-1, 3]}
45 | pop = Array.new(1000) {|i| create_random_bee(search_space)}
46 | sum = pop.inject(0){|sum, bee| sum += bee[:vector].inject(0){|sum, dimension| sum+= dimension}}
47 | assert_in_delta (sum / (pop.size * problem_size)), 1, 0.1
48 | end
49 |
50 | # test that create_neighbourhood_bee centres bee on the correct site
51 | def test_create_neighbourhood_bee
52 | problem_size = 5
53 | search_space = Array.new(problem_size) {|i| [-10, 10]}
54 | site = Array.new(problem_size){|i| i}
55 | pop = Array.new(10000) {|i| create_neigh_bee(site, 4.5, search_space)}
56 | problem_size.times{|i| assert_in_delta pop.inject(0){|sum, bee| sum += bee[:vector][i]} / pop.size, i, 0.1}
57 | end
58 |
59 | # test that create_neighbourhood_bee stays within patch_size and does not exceed search_space boundary
60 | def test_create_neighbourhood_bee_bounds
61 | problem_size = 5
62 | patch_size = 2
63 | search_space = Array.new(problem_size) {|i| [0, 5]}
64 | site = Array.new(problem_size){|i| i}
65 | pop = Array.new(1000) {|i| create_neigh_bee(site, patch_size, search_space)}
66 | pop.each do |bee|
67 | bee[:vector].each_with_index do |dimension, i|
68 | (i-patch_size<0) ? (assert_operator dimension, :>=, 0) : (assert_operator dimension, :>=, i-patch_size)
69 | (i+patch_size>5) ? (assert_operator dimension, :<=, 5) : (assert_operator dimension, :<=, i+patch_size)
70 | end
71 | end
72 | end
73 |
74 | # test search neighbourhood
75 | def test_search_neigh
76 | rs = search_neigh({:vector=>[0.5,0.5]}, 10, 0.005, [[0,1],[0,1]])
77 | assert_not_nil(rs)
78 | assert_not_nil(rs[:vector])
79 | assert_not_nil(rs[:fitness])
80 | assert_equal(2, rs[:vector].size)
81 | end
82 |
83 | # test create scoute bees
84 | def test_create_scout_bees
85 | rs = create_scout_bees([[0,1],[0,1]], 10)
86 | assert_equal(10, rs.size)
87 | rs.each do |x|
88 | x[:vector].each do |v|
89 | assert_operator(v, :>=, 0)
90 | assert_operator(v, :<=, 1)
91 | end
92 | end
93 | end
94 |
95 | # helper for turning off STDOUT
96 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
97 | def silence_stream(stream)
98 | old_stream = stream.dup
99 | stream.reopen('/dev/null')
100 | stream.sync = true
101 | yield
102 | ensure
103 | stream.reopen(old_stream)
104 | end
105 |
106 | # test that the algorithm can solve the problem
107 | def test_search
108 | best = nil
109 | silence_stream(STDOUT) do
110 | best = search(50, [[-5,5],[-5,5]], 50, 5, 2, 3, 7, 2)
111 | end
112 | assert_not_nil(best[:fitness])
113 | assert_in_delta(0.0, best[:fitness], 0.01)
114 | end
115 |
116 | end
117 |
--------------------------------------------------------------------------------
/book/graphics/ga3.tex:
--------------------------------------------------------------------------------
1 | % GNUPLOT: LaTeX picture
2 | \setlength{\unitlength}{0.240900pt}
3 | \ifx\plotpoint\undefined\newsavebox{\plotpoint}\fi
4 | \sbox{\plotpoint}{\rule[-0.200pt]{0.400pt}{0.400pt}}%
5 | \begin{picture}(1200,900)(0,0)
6 | \sbox{\plotpoint}{\rule[-0.200pt]{0.400pt}{0.400pt}}%
7 | \put(130.0,82.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
8 | \put(110,82){\makebox(0,0)[r]{ 160}}
9 | \put(1119.0,82.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
10 | \put(130.0,212.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
11 | \put(110,212){\makebox(0,0)[r]{ 180}}
12 | \put(1119.0,212.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
13 | \put(130.0,341.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
14 | \put(110,341){\makebox(0,0)[r]{ 200}}
15 | \put(1119.0,341.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
16 | \put(130.0,471.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
17 | \put(110,471){\makebox(0,0)[r]{ 220}}
18 | \put(1119.0,471.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
19 | \put(130.0,600.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
20 | \put(110,600){\makebox(0,0)[r]{ 240}}
21 | \put(1119.0,600.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
22 | \put(130.0,730.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
23 | \put(110,730){\makebox(0,0)[r]{ 260}}
24 | \put(1119.0,730.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
25 | \put(130.0,859.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
26 | \put(110,859){\makebox(0,0)[r]{ 280}}
27 | \put(1119.0,859.0){\rule[-0.200pt]{4.818pt}{0.400pt}}
28 | \put(130.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
29 | \put(130,41){\makebox(0,0){-1}}
30 | \put(130.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
31 | \put(256.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
32 | \put(256,41){\makebox(0,0){-0.5}}
33 | \put(256.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
34 | \put(382.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
35 | \put(382,41){\makebox(0,0){ 0}}
36 | \put(382.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
37 | \put(508.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
38 | \put(508,41){\makebox(0,0){ 0.5}}
39 | \put(508.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
40 | \put(635.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
41 | \put(635,41){\makebox(0,0){ 1}}
42 | \put(635.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
43 | \put(761.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
44 | \put(761,41){\makebox(0,0){ 1.5}}
45 | \put(761.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
46 | \put(887.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
47 | \put(887,41){\makebox(0,0){ 2}}
48 | \put(887.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
49 | \put(1013.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
50 | \put(1013,41){\makebox(0,0){ 2.5}}
51 | \put(1013.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
52 | \put(1139.0,82.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
53 | \put(1139,41){\makebox(0,0){ 3}}
54 | \put(1139.0,839.0){\rule[-0.200pt]{0.400pt}{4.818pt}}
55 | \put(130.0,82.0){\rule[-0.200pt]{0.400pt}{187.179pt}}
56 | \put(130.0,82.0){\rule[-0.200pt]{243.068pt}{0.400pt}}
57 | \put(1139.0,82.0){\rule[-0.200pt]{0.400pt}{187.179pt}}
58 | \put(130.0,859.0){\rule[-0.200pt]{243.068pt}{0.400pt}}
59 | \put(382.0,671.0){\rule[-0.200pt]{0.400pt}{15.658pt}}
60 | \put(382.0,768.0){\rule[-0.200pt]{0.400pt}{17.345pt}}
61 | \put(307.0,736.0){\rule[-0.200pt]{36.135pt}{0.400pt}}
62 | \put(457.0,736.0){\rule[-0.200pt]{0.400pt}{7.709pt}}
63 | \put(307.0,768.0){\rule[-0.200pt]{36.135pt}{0.400pt}}
64 | \put(307.0,736.0){\rule[-0.200pt]{0.400pt}{7.709pt}}
65 | \put(344.0,840.0){\rule[-0.200pt]{18.308pt}{0.400pt}}
66 | \put(344.0,671.0){\rule[-0.200pt]{18.308pt}{0.400pt}}
67 | \put(635.0,432.0){\rule[-0.200pt]{0.400pt}{6.263pt}}
68 | \put(635.0,496.0){\rule[-0.200pt]{0.400pt}{15.658pt}}
69 | \put(560.0,458.0){\rule[-0.200pt]{36.135pt}{0.400pt}}
70 | \put(710.0,458.0){\rule[-0.200pt]{0.400pt}{9.154pt}}
71 | \put(560.0,496.0){\rule[-0.200pt]{36.135pt}{0.400pt}}
72 | \put(560.0,458.0){\rule[-0.200pt]{0.400pt}{9.154pt}}
73 | \put(597.0,561.0){\rule[-0.200pt]{18.308pt}{0.400pt}}
74 | \put(597.0,432.0){\rule[-0.200pt]{18.308pt}{0.400pt}}
75 | \put(887.0,186.0){\rule[-0.200pt]{0.400pt}{6.263pt}}
76 | \put(887.0,237.0){\rule[-0.200pt]{0.400pt}{12.527pt}}
77 | \put(812.0,212.0){\rule[-0.200pt]{36.135pt}{0.400pt}}
78 | \put(962.0,212.0){\rule[-0.200pt]{0.400pt}{6.022pt}}
79 | \put(812.0,237.0){\rule[-0.200pt]{36.135pt}{0.400pt}}
80 | \put(812.0,212.0){\rule[-0.200pt]{0.400pt}{6.022pt}}
81 | \put(849.0,289.0){\rule[-0.200pt]{18.308pt}{0.400pt}}
82 | \put(849.0,186.0){\rule[-0.200pt]{18.308pt}{0.400pt}}
83 | \put(130.0,82.0){\rule[-0.200pt]{0.400pt}{187.179pt}}
84 | \put(130.0,82.0){\rule[-0.200pt]{243.068pt}{0.400pt}}
85 | \put(1139.0,82.0){\rule[-0.200pt]{0.400pt}{187.179pt}}
86 | \put(130.0,859.0){\rule[-0.200pt]{243.068pt}{0.400pt}}
87 | \end{picture}
88 |
--------------------------------------------------------------------------------
/src/programming_paradigms/oop.rb:
--------------------------------------------------------------------------------
1 | # Genetic Algorithm in the Ruby Programming Language: Object-Oriented Programming
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | # A problem template
8 | class Problem
9 | def assess(candidate_solution)
10 | raise "A problem has not been defined"
11 | end
12 |
13 | def is_optimal?(candidate_solution)
14 | raise "A problem has not been defined"
15 | end
16 | end
17 |
18 | # An strategy template
19 | class Strategy
20 | def execute(problem)
21 | raise "A strategy has not been defined!"
22 | end
23 | end
24 |
25 | # An implementation of the OneMax problem using the problem template
26 | class OneMax < Problem
27 |
28 | attr_reader :num_bits
29 |
30 | def initialize(num_bits=64)
31 | @num_bits = num_bits
32 | end
33 |
34 | def assess(candidate_solution)
35 | if candidate_solution[:bitstring].length != @num_bits
36 | raise "Expected #{@num_bits} in candidate solution."
37 | end
38 | sum = 0
39 | candidate_solution[:bitstring].size.times do |i|
40 | sum += 1 if candidate_solution[:bitstring][i].chr =='1'
41 | end
42 | return sum
43 | end
44 |
45 | def is_optimal?(candidate_solution)
46 | return candidate_solution[:fitness] == @num_bits
47 | end
48 | end
49 |
50 | # An implementation of the Genetic algorithm using the strategy template
51 | class GeneticAlgorithm < Strategy
52 |
53 | attr_reader :max_generations, :population_size, :p_crossover, :p_mutation
54 |
55 | def initialize(max_gens=100, pop_size=100, crossover=0.98, mutation=1.0/64.0)
56 | @max_generations = max_gens
57 | @population_size = pop_size
58 | @p_crossover = crossover
59 | @p_mutation = mutation
60 | end
61 |
62 | def random_bitstring(num_bits)
63 | return (0...num_bits).inject(""){|s,i| s<<((rand<0.5) ? "1" : "0")}
64 | end
65 |
66 | def binary_tournament(pop)
67 | i, j = rand(pop.size), rand(pop.size)
68 | j = rand(pop.size) while j==i
69 | return (pop[i][:fitness] > pop[j][:fitness]) ? pop[i] : pop[j]
70 | end
71 |
72 | def point_mutation(bitstring)
73 | child = ""
74 | bitstring.size.times do |i|
75 | bit = bitstring[i].chr
76 | child << ((rand()<@p_mutation) ? ((bit=='1') ? "0" : "1") : bit)
77 | end
78 | return child
79 | end
80 |
81 | def uniform_crossover(parent1, parent2)
82 | return ""+parent1 if rand()>=@p_crossover
83 | child = ""
84 | parent1.length.times do |i|
85 | child << ((rand()<0.5) ? parent1[i].chr : parent2[i].chr)
86 | end
87 | return child
88 | end
89 |
90 | def reproduce(selected)
91 | children = []
92 | selected.each_with_index do |p1, i|
93 | p2 = (i.modulo(2)==0) ? selected[i+1] : selected[i-1]
94 | p2 = selected[0] if i == selected.size-1
95 | child = {}
96 | child[:bitstring] = uniform_crossover(p1[:bitstring], p2[:bitstring])
97 | child[:bitstring] = point_mutation(child[:bitstring])
98 | children << child
99 | break if children.size >= @population_size
100 | end
101 | return children
102 | end
103 |
104 | def execute(problem)
105 | population = Array.new(@population_size) do |i|
106 | {:bitstring=>random_bitstring(problem.num_bits)}
107 | end
108 | population.each{|c| c[:fitness] = problem.assess(c)}
109 | best = population.sort{|x,y| y[:fitness] <=> x[:fitness]}.first
110 | @max_generations.times do |gen|
111 | selected = Array.new(population_size){|i| binary_tournament(population)}
112 | children = reproduce(selected)
113 | children.each{|c| c[:fitness] = problem.assess(c)}
114 | children.sort!{|x,y| y[:fitness] <=> x[:fitness]}
115 | best = children.first if children.first[:fitness] >= best[:fitness]
116 | population = children
117 | puts " > gen #{gen}, best: #{best[:fitness]}, #{best[:bitstring]}"
118 | break if problem.is_optimal?(best)
119 | end
120 | return best
121 | end
122 | end
123 |
124 | if __FILE__ == $0
125 | # problem configuration
126 | problem = OneMax.new
127 | # algorithm configuration
128 | strategy = GeneticAlgorithm.new
129 | # execute the algorithm
130 | best = strategy.execute(problem)
131 | puts "done! Solution: f=#{best[:fitness]}, s=#{best[:bitstring]}"
132 | end
133 |
--------------------------------------------------------------------------------
/book/definitions.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 |
6 | %
7 | % Definitions
8 | %
9 |
10 | % The main title of the book
11 | \newcommand{\mybooktitle}{Clever Algorithms}
12 | % The sub title of the book
13 | \newcommand{\mybooksubtitle}{Nature-Inspired Programming Recipes}
14 | % title
15 | \newcommand{\mybookauthor}{Jason Brownlee}
16 | % date
17 | \newcommand{\mybookdate}{2011}
18 |
19 |
20 | % new macro for starting a new page and changing the style to empty
21 | % \newpage == ends the current page.
22 | % \thispagestyle == works in the same manner as the \pagestyle, except that it changes the style for the current page only.
23 | % empty == Produces empty heads and feet - no page numbers
24 | \newcommand{\blanknonumber}{\newpage\thispagestyle{empty}}
25 |
26 |
27 | %
28 | % Packages
29 | %
30 |
31 |
32 | % a replacement for fancyheadings
33 | % http://www.ctan.org/tex-archive/macros/latex/contrib/fancyhdr/
34 | \usepackage{fancyhdr}
35 | \fancyhead[LO]{\slshape\nouppercase{\leftmark}}
36 | \fancyhead[RE]{\slshape\nouppercase{\rightmark}}
37 | \fancyhead[LE,RO]{\thepage}
38 | \renewcommand{\headrulewidth}{0.4pt}
39 | \renewcommand{\sectionmark}[1]{\markright{\thesection.\ #1}}
40 | \renewcommand{\chaptermark}[1]{\markboth{\chaptername\ \thechapter.\ #1}{}}
41 |
42 |
43 |
44 |
45 | % add an index
46 | % http://ctan.unsw.edu.au/macros/latex/contrib/index/index.pdf
47 | \usepackage{index}
48 |
49 | % Flexible and easy interface to page dimensions
50 | % http://www.ctan.org/tex-archive/macros/latex/contrib/geometry/
51 | % also, bigger pages by default
52 | \usepackage[pdftex]{geometry}
53 |
54 | % Supports the Text Companion fonts which provide many text symbols (such as baht, bullet, copyright, musicalnote, onequarter, section, and yen) in the TS1 encoding.
55 | % http://www.ctan.org/tex-archive/help/Catalogue/entries/textcomp.html
56 | % needed for listings
57 | \usepackage{textcomp}
58 |
59 | % http://www.maths.adelaide.edu.au/anthony.roberts/LaTeX/ltxusecol.html
60 | % needed for listings - lots of pretty colors
61 | \usepackage[usenames,dvipsnames]{color}
62 |
63 | % bold in ttfamily
64 | \usepackage{bold-extra}
65 |
66 | % better spacing
67 | % http://ctan.unsw.edu.au/macros/latex/contrib/microtype/microtype.pdf
68 | \usepackage{microtype}
69 |
70 | % code listings (lots of languages)
71 | % http://mirror.aarnet.edu.au/pub/CTAN/macros/latex/contrib/listings/
72 | \usepackage{listings}
73 |
74 | % http://mirror.aarnet.edu.au/pub/CTAN/macros/latex/contrib/listings/
75 | \lstset{language=ruby,
76 | basicstyle=\footnotesize\ttfamily,
77 | numbers=left,
78 | numberstyle=\tiny,
79 | keywordstyle=\bfseries\ttfamily,
80 | frame=single,
81 | columns=flexible,
82 | upquote=true,
83 | showstringspaces=false,
84 | tabsize=2,
85 | captionpos=b,
86 | breaklines=true,
87 | breakatwhitespace=true}
88 |
89 | % for ebooks, turn cross references into links
90 | % http://www.tug.org/applications/hyperref/manual.html
91 | \usepackage[pdftex,
92 | breaklinks=true,
93 | colorlinks=true,
94 | urlcolor=blue,
95 | linkcolor=blue,
96 | citecolor=blue]{hyperref}
97 |
98 | % modifies the widths of certain columns, rather than the inter column space, to set a table with the requested total width
99 | % http://www.cs.brown.edu/system/software/latex/doc/tabularx.pdf
100 | \usepackage{tabularx}
101 |
102 | % This package provide some additional commands to enhance the quality of tables in LaTeX.
103 | % http://www.ctan.org/tex-archive/macros/latex/contrib/booktabs/
104 | \usepackage{booktabs}
105 |
106 | % a form of verbatim command that allows linebreaks at certain characters or combinations of characters
107 | % http://www.tex.ac.uk/tex-archive/help/Catalogue/entries/url.html
108 | % works well with hyperref
109 | \usepackage{url}
110 |
111 | % Algorithm2e is an environment for writing algorithms in LaTeX2e
112 | % http://www.ctan.org/tex-archive/macros/latex/contrib/algorithm2e/
113 | \usepackage[algoruled, linesnumbered, algosection]{styles/algorithm2e}
114 |
115 | % for adding in bib for each section or chapter
116 | % http://merkel.zoneo.net/Latex/natbib.php
117 | \usepackage[numbers, sort&compress]{natbib}
118 | \usepackage{styles/bibunits}
119 |
120 | % maths
121 | \usepackage{amsmath}
122 | \usepackage{latexsym}
123 |
124 | % graphics
125 | \usepackage{graphicx}
126 |
127 |
--------------------------------------------------------------------------------
/src/algorithms/probabilistic/tests/tc_cross_entropy_method.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for cross_entropy_method.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../cross_entropy_method"
9 |
10 | class TC_CompactGeneticAlgorithm < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | end
21 |
22 | # test the creation of random vars
23 | def test_random_variable
24 | # positive, zero offset
25 | x = random_variable([0, 20])
26 | assert_operator(x, :>=, 0)
27 | assert_operator(x, :<, 20)
28 | # negative
29 | x = random_variable([-20, -1])
30 | assert_operator(x, :>=, -20)
31 | assert_operator(x, :<, -1)
32 | # both
33 | x = random_variable([-10, 20])
34 | assert_operator(x, :>=, -10)
35 | assert_operator(x, :<, 20)
36 | end
37 |
38 | # test default rand gaussian
39 | def test_random_gaussian_default
40 | mean, stdev = 0.0, 1.0
41 | all = []
42 | 1000.times do
43 | all << random_gaussian(mean, stdev)
44 | assert_in_delta(mean, all.last, 6*stdev)
45 | end
46 | m = all.inject(0){|sum,x| sum + x} / all.size.to_f
47 | assert_in_delta(mean, m, 0.1)
48 | end
49 |
50 | # test rand gaussian in different range
51 | def test_random_gaussian_non_default
52 | mean, stdev = 50, 10
53 | all = []
54 | 1000.times do
55 | all << random_gaussian(mean, stdev)
56 | assert_in_delta(mean, all.last, 6*stdev)
57 | end
58 | m = all.inject(0){|sum,x| sum + x} / all.size.to_f
59 | assert_in_delta(m, mean, 1.0)
60 | end
61 |
62 | # test the generation of new samples in the search space
63 | def test_generate_sample
64 | # middle
65 | s = generate_sample([[-1,1],[-1,1]], [0,0], [1,1])
66 | assert_equal(2, s[:vector].size)
67 | s[:vector].each do |x|
68 | assert_operator(x, :>=, -1)
69 | assert_operator(x, :<=, 1)
70 | end
71 | # side
72 | s = generate_sample([[-1,1],[-1,1]], [0.9,0.9], [1,1])
73 | assert_equal(2, s[:vector].size)
74 | s[:vector].each do |x|
75 | assert_operator(x, :>=, -1)
76 | assert_operator(x, :<=, 1)
77 | end
78 | end
79 |
80 | # test taking the mean of an attribute across vectors
81 | def test_mean_parameter
82 | samples = [{:vector=>[0,5,0,0,0]}, {:vector=>[0,10,10,0,0]}]
83 | # zero
84 | assert_equal(0, mean_attr(samples, 0))
85 | # value
86 | assert_equal(7.5, mean_attr(samples, 1))
87 | assert_equal(5, mean_attr(samples, 2))
88 | end
89 |
90 | # test the standard dev of an atttribute
91 | def test_stdev_parameter
92 | samples = [{:vector=>[0,0,0,0,0]}, {:vector=>[0,10,0,0,0]}]
93 | # zero
94 | assert_equal(0, stdev_attr(samples, 0, 0))
95 | # value
96 | assert_equal(5, stdev_attr(samples, 5, 1))
97 | end
98 |
99 | # test updating the distribution
100 | def test_update_distribution
101 | samples = [{:vector=>[0,0,0,0,0]}, {:vector=>[0,1,-1,0.5,-0.5]}]
102 | means, stdvs = [0,0], [1,1]
103 | update_distribution!(samples, 1.0, means, stdvs)
104 | # TODO this is weak, rewrite
105 | means.each_index do |i|
106 | assert_operator(means[i], :>=, -1)
107 | assert_operator(means[i], :<=, 1)
108 | assert_operator(stdvs[i], :>=, 0)
109 | assert_operator(stdvs[i], :<=, 1)
110 | end
111 | end
112 |
113 | # helper for turning off STDOUT
114 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
115 | def silence_stream(stream)
116 | old_stream = stream.dup
117 | stream.reopen('/dev/null')
118 | stream.sync = true
119 | yield
120 | ensure
121 | stream.reopen(old_stream)
122 | end
123 |
124 | # test that the algorithm can solve the problem
125 | def test_search
126 | best = nil
127 | silence_stream(STDOUT) do
128 | best = search([[-5,5],[-5,5]], 100, 50, 5, 0.7)
129 | end
130 | assert_not_nil(best[:cost])
131 | assert_in_delta(0.0, best[:cost], 0.001)
132 | end
133 |
134 | end
135 |
--------------------------------------------------------------------------------
/book/f_preface.tex:
--------------------------------------------------------------------------------
1 | % The Clever Algorithms Project: http://www.CleverAlgorithms.com
2 | % (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
3 | % This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
4 |
5 |
6 | % A preface generally covers the story of how the book came into being, or how the idea for the book was developed; this is often followed by thanks and acknowledgments to people who were helpful to the author during the time of writing.
7 | \chapter*{Preface\markboth{Preface}{}}
8 | \addcontentsline{toc}{chapter}{Preface}
9 |
10 | %
11 | % About the book
12 | %
13 | \section*{About the book}
14 | % premise
15 | The need for this project was born of frustration while working towards my PhD. I was investigating optimization algorithms and was implementing a large number of them for a software platform called the Optimization Algorithm Toolkit (OAT)\footnote{OAT located at \url{http://optalgtoolkit.sourceforge.net}}. Each algorithm required considerable effort to locate the relevant source material (from books, papers, articles, and existing implementations), decipher and interpret the technique, and finally attempt to piece together a working implementation.
16 |
17 | % problem summary
18 | Taking a broader perspective, I realized that the communication of algorithmic techniques in the field of Artificial Intelligence was clearly a difficult and outstanding open problem. Generally, algorithm descriptions are:
19 |
20 | \begin{itemize}
21 | \item \emph{Incomplete}: many techniques are ambiguously described, partially described, or not described at all.
22 | \item \emph{Inconsistent}: a given technique may be described using a variety of formal and semi-formal methods that vary across different techniques, limiting the transferability of background skills an audience requires to read a technique (such as mathematics, pseudocode, program code, and narratives). An inconsistent representation for techniques means that the skills used to understand and internalize one technique may not be transferable to realizing different techniques or even extensions of the same technique.
23 | \item \emph{Distributed}: the description of data structures, operations, and parameterization of a given technique may span a collection of papers, articles, books, and source code published over a number of years, the access to which may be restricted and difficult to obtain.
24 | \end{itemize}
25 |
26 | % result
27 | For the practitioner, a badly described algorithm may be simply frustrating, where the gaps in available information are filled with intuition and `best guess'. At the other end of the spectrum, a badly described algorithm may be an example of bad science and the failure of the scientific method, where the inability to understand and implement a technique may prevent the replication of results, the application, or the investigation and extension of a technique.
28 |
29 | % solution summary
30 | The software I produced provided a first step solution to this problem: a set of working algorithms implemented in a (somewhat) consistent way and downloaded from a single location (features likely provided by any library of artificial intelligence techniques). The next logical step needed to address this problem is to develop a methodology that anybody can follow. The strategy to address the open problem of poor algorithm communication is to present complete algorithm descriptions (rather than just implementations) in a consistent manner, and in a centralized location.
31 | % book
32 | This book is the outcome of developing such a strategy that not only provides a methodology for standardized algorithm descriptions, but provides a large corpus of complete and consistent algorithm descriptions in a single centralized location.
33 |
34 | % goal
35 | The algorithms described in this work are practical, interesting, and fun, and the goal of this project was to promote these features by making algorithms from the field more accessible, usable, and understandable.
36 | % project
37 | This project was developed over a number years through a lot of writing, discussion, and revision. This book has been released under a permissive license that encourages the reader to explore new and creative ways of further communicating its message and content.
38 |
39 | % take away
40 | I hope that this project has succeeded in some small way and that you too can enjoy applying, learning, and playing with Clever Algorithms.
41 |
42 | \begin{flushright}
43 | \vspace{1in}
44 | Jason Brownlee
45 | \end{flushright}
46 |
47 | \begin{flushleft}
48 | \vspace{0.2in}
49 | Melbourne, Australia \\
50 | 2011
51 | \end{flushleft}
52 |
--------------------------------------------------------------------------------
/src/algorithms/physical/memetic_algorithm.rb:
--------------------------------------------------------------------------------
1 | # Memetic Algorithm in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def objective_function(vector)
8 | return vector.inject(0.0) {|sum, x| sum + (x ** 2.0)}
9 | end
10 |
11 | def random_bitstring(num_bits)
12 | return (0...num_bits).inject(""){|s,i| s<<((rand<0.5) ? "1" : "0")}
13 | end
14 |
15 | def decode(bitstring, search_space, bits_per_param)
16 | vector = []
17 | search_space.each_with_index do |bounds, i|
18 | off, sum = i*bits_per_param, 0.0
19 | param = bitstring[off...(off+bits_per_param)].reverse
20 | param.size.times do |j|
21 | sum += ((param[j].chr=='1') ? 1.0 : 0.0) * (2.0 ** j.to_f)
22 | end
23 | min, max = bounds
24 | vector << min + ((max-min)/((2.0**bits_per_param.to_f)-1.0)) * sum
25 | end
26 | return vector
27 | end
28 |
29 | def fitness(candidate, search_space, param_bits)
30 | candidate[:vector]=decode(candidate[:bitstring], search_space, param_bits)
31 | candidate[:fitness] = objective_function(candidate[:vector])
32 | end
33 |
34 | def binary_tournament(pop)
35 | i, j = rand(pop.size), rand(pop.size)
36 | j = rand(pop.size) while j==i
37 | return (pop[i][:fitness] < pop[j][:fitness]) ? pop[i] : pop[j]
38 | end
39 |
40 | def point_mutation(bitstring, rate=1.0/bitstring.size)
41 | child = ""
42 | bitstring.size.times do |i|
43 | bit = bitstring[i].chr
44 | child << ((rand()=rate
51 | child = ""
52 | parent1.size.times do |i|
53 | child << ((rand()<0.5) ? parent1[i].chr : parent2[i].chr)
54 | end
55 | return child
56 | end
57 |
58 | def reproduce(selected, pop_size, p_cross, p_mut)
59 | children = []
60 | selected.each_with_index do |p1, i|
61 | p2 = (i.modulo(2)==0) ? selected[i+1] : selected[i-1]
62 | p2 = selected[0] if i == selected.size-1
63 | child = {}
64 | child[:bitstring] = crossover(p1[:bitstring], p2[:bitstring], p_cross)
65 | child[:bitstring] = point_mutation(child[:bitstring], p_mut)
66 | children << child
67 | break if children.size >= pop_size
68 | end
69 | return children
70 | end
71 |
72 | def bitclimber(child, search_space, p_mut, max_local_gens, bits_per_param)
73 | current = child
74 | max_local_gens.times do
75 | candidate = {}
76 | candidate[:bitstring] = point_mutation(current[:bitstring], p_mut)
77 | fitness(candidate, search_space, bits_per_param)
78 | current = candidate if candidate[:fitness] <= current[:fitness]
79 | end
80 | return current
81 | end
82 |
83 | def search(max_gens, search_space, pop_size, p_cross, p_mut, max_local_gens,
84 | p_local, bits_per_param=16)
85 | pop = Array.new(pop_size) do |i|
86 | {:bitstring=>random_bitstring(search_space.size*bits_per_param)}
87 | end
88 | pop.each{|candidate| fitness(candidate, search_space, bits_per_param) }
89 | gen, best = 0, pop.sort{|x,y| x[:fitness] <=> y[:fitness]}.first
90 | max_gens.times do |gen|
91 | selected = Array.new(pop_size){|i| binary_tournament(pop)}
92 | children = reproduce(selected, pop_size, p_cross, p_mut)
93 | children.each{|cand| fitness(cand, search_space, bits_per_param)}
94 | pop = []
95 | children.each do |child|
96 | if rand() < p_local
97 | child = bitclimber(child, search_space, p_mut, max_local_gens,
98 | bits_per_param)
99 | end
100 | pop << child
101 | end
102 | pop.sort!{|x,y| x[:fitness] <=> y[:fitness]}
103 | best = pop.first if pop.first[:fitness] <= best[:fitness]
104 | puts ">gen=#{gen}, f=#{best[:fitness]}, b=#{best[:bitstring]}"
105 | end
106 | return best
107 | end
108 |
109 | if __FILE__ == $0
110 | # problem configuration
111 | problem_size = 3
112 | search_space = Array.new(problem_size) {|i| [-5, +5]}
113 | # algorithm configuration
114 | max_gens = 100
115 | pop_size = 100
116 | p_cross = 0.98
117 | p_mut = 1.0/(problem_size*16).to_f
118 | max_local_gens = 20
119 | p_local = 0.5
120 | # execute the algorithm
121 | best = search(max_gens, search_space, pop_size, p_cross, p_mut, max_local_gens, p_local)
122 | puts "done! Solution: f=#{best[:fitness]}, b=#{best[:bitstring]}, v=#{best[:vector].inspect}"
123 | end
124 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/tests/tc_iterated_local_search.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for iterated_local_search.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../iterated_local_search"
9 |
10 | class TC_IteratedLocalSearch < Test::Unit::TestCase
11 |
12 | # test the rounding in the euclidean distance
13 | def test_euc_2d
14 | assert_equal(0, euc_2d([0,0], [0,0]))
15 | assert_equal(0, euc_2d([1.1,1.1], [1.1,1.1]))
16 | assert_equal(1, euc_2d([1,1], [2,2]))
17 | assert_equal(3, euc_2d([-1,-1], [1,1]))
18 | end
19 |
20 | # test tour cost includes return to origin
21 | def test_cost
22 | cities = [[0,0], [1,1], [2,2], [3,3]]
23 | assert_equal(1*2, cost([0,1], cities))
24 | assert_equal(3+4, cost([0,1,2,3], cities))
25 | assert_equal(4*2, cost([0, 3], cities))
26 | end
27 |
28 | # test the construction of a random permutation
29 | def test_random_permutation
30 | cities = Array.new(10)
31 | 100.times do
32 | p = random_permutation(cities)
33 | assert_equal(cities.size, p.size)
34 | [0,1,2,3,4,5,6,7,8,9].each {|x| assert(p.include?(x), "#{x}") }
35 | end
36 | end
37 |
38 | # test the two opt procedure
39 | def test_stochastic_two_opt
40 | perm = Array.new(10){|i| i}
41 | 200.times do
42 | other = stochastic_two_opt(perm)
43 | assert_equal(perm.size, other.size)
44 | assert_not_equal(perm, other)
45 | assert_not_same(perm, other)
46 | other.each {|x| assert(perm.include?(x), "#{x}") }
47 | end
48 | end
49 |
50 | # test the local search procedure
51 | def test_local_search
52 | # improvement
53 | best = {:vector=>[0,1,2,3,4]}
54 | cities = [[0,0],[3,3],[1,1],[2,2],[4,4]]
55 | best[:cost] = cost(best[:vector], cities)
56 | rs = local_search(best, cities, 20)
57 | assert_not_nil(rs)
58 | assert_not_nil(rs[:vector])
59 | assert_not_nil(rs[:cost])
60 | assert_not_same(best, rs)
61 | assert_not_equal(best[:vector], rs[:vector])
62 | assert_not_equal(best[:cost], rs[:cost])
63 | # no improvement
64 | best = {:vector=>[0,2,3,1,4]}
65 | best[:cost] = cost(best[:vector], cities)
66 | rs = local_search(best, cities, 10)
67 | assert_not_nil(rs)
68 | assert_equal(best[:cost], rs[:cost])
69 | end
70 |
71 | # test a double bridge move
72 | def test_double_bridge_move
73 | perm = Array.new(100){|i| i}
74 | 100.times do
75 | rs = double_bridge_move(perm)
76 | assert_equal(perm.size, rs.size)
77 | perm.each{|x| assert(rs.include?(x)) }
78 | end
79 | # TODO test the offsets used internally
80 | end
81 |
82 | # test perturbation
83 | def test_perturbation
84 | cities = Array.new(100){[rand(), rand()]}
85 | best = {:vector=>Array.new(100){|i| i}}
86 | rs = perturbation(cities, best)
87 | assert_not_nil(rs)
88 | assert_not_nil(rs[:vector])
89 | assert_not_nil(rs[:cost])
90 | assert_equal(100, rs[:vector].length)
91 | end
92 |
93 | # helper for turning off STDOUT
94 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
95 | def silence_stream(stream)
96 | old_stream = stream.dup
97 | stream.reopen('/dev/null')
98 | stream.sync = true
99 | yield
100 | ensure
101 | stream.reopen(old_stream)
102 | end
103 |
104 | # test that the algorithm can solve the problem
105 | def test_search
106 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
107 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
108 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
109 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
110 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
111 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
112 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
113 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
114 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
115 | best = nil
116 | silence_stream(STDOUT) do
117 | best = search(berlin52, 100, 50)
118 | end
119 | # better than a NN solution's cost
120 | assert_not_nil(best[:cost])
121 | assert_in_delta(7542, best[:cost], 3000)
122 | end
123 |
124 | end
125 |
--------------------------------------------------------------------------------
/src/algorithms/swarm/tests/tc_bfoa.rb:
--------------------------------------------------------------------------------
1 | # Unit tests for bfoa.rb
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | require "test/unit"
8 | require File.expand_path(File.dirname(__FILE__)) + "/../bfoa"
9 |
10 | class TC_BFOA < Test::Unit::TestCase
11 |
12 | # test the objective function
13 | def test_objective_function
14 | # integer
15 | assert_equal(99**2, objective_function([99]))
16 | # float
17 | assert_equal(0.1**2.0, objective_function([0.1]))
18 | # vector
19 | assert_equal(1**2+2**2+3**2, objective_function([1,2,3]))
20 | # optima
21 | assert_equal(0, objective_function([0,0]))
22 | end
23 |
24 | # test the generation of random vectors
25 | def test_random_vector
26 | bounds, trials, size = [-3,3], 300, 20
27 | minmax = Array.new(size) {bounds}
28 | trials.times do
29 | vector, sum = random_vector(minmax), 0.0
30 | assert_equal(size, vector.size)
31 | vector.each do |v|
32 | assert_operator(v, :>=, bounds[0])
33 | assert_operator(v, :<, bounds[1])
34 | sum += v
35 | end
36 | assert_in_delta(bounds[0]+((bounds[1]-bounds[0])/2.0), sum/trials.to_f, 0.1)
37 | end
38 | end
39 |
40 | # test generating a random direction
41 | def test_generate_random_direction
42 | rs = generate_random_direction(1000)
43 | assert_equal(1000, rs.size)
44 | rs.each do |x|
45 | assert_operator(x, :>=, -1)
46 | assert_operator(x, :<=, 1)
47 | end
48 | end
49 |
50 | # test cell interactions
51 | def test_compute_cell_interaction
52 | # same
53 | rs = compute_cell_interaction({:vector=>[0,0]}, [{:vector=>[0,0]}], 1.0, 1.0)
54 | assert_equal(1.0, rs)
55 | # different
56 | rs = compute_cell_interaction({:vector=>[0,0]}, [{:vector=>[1,1]}], 1.0, 1.0)
57 | assert_in_delta(7.3, rs, 0.1)
58 | end
59 |
60 | # calculate combined forces
61 | def test_attract_repel
62 | # same
63 | rs = attract_repel({:vector=>[0,0]}, [{:vector=>[0,0]}], 1.0, 1.0, 1.0, 1.0)
64 | assert_equal(0.0, rs)
65 | # different
66 | rs = attract_repel({:vector=>[0,0]}, [{:vector=>[1,1]}], 1.0, 1.0, 1.0, 1.0)
67 | assert_equal(0.0, rs)
68 | # TODO test different attract repel scenarios
69 | end
70 |
71 | # test candidate evaluation
72 | def test_evaluate
73 | cell = {:vector=>[0,0]}
74 | evaluate(cell, [{:vector=>[0,0]},{:vector=>[1,1]}], 1.0, 1.0, 1.0, 1.0)
75 | assert_not_nil(cell[:cost])
76 | assert_not_nil(cell[:inter])
77 | assert_not_nil(cell[:fitness])
78 | assert_equal(cell[:cost]+cell[:inter], cell[:fitness])
79 | end
80 |
81 | # test cell tumble
82 | def test_tumble_cell
83 | # in bounds
84 | cell = tumble_cell([[0,1],[0,1]], {:vector=>[0.5,0.5]}, 0.1)
85 | assert_equal(2, cell[:vector].size)
86 | cell[:vector].each_index do |i|
87 | assert_operator(cell[:vector][i], :>=, 0)
88 | assert_operator(cell[:vector][i], :<=, 1)
89 | end
90 | # really large step size
91 | cell = tumble_cell([[0,1],[0,1]], {:vector=>[0.5,0.5]}, 100.0)
92 | assert_equal(2, cell[:vector].size)
93 | cell[:vector].each_index do |i|
94 | assert_operator(cell[:vector][i], :>=, 0)
95 | assert_operator(cell[:vector][i], :<=, 1)
96 | end
97 | end
98 |
99 | # helper for turning off STDOUT
100 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39
101 | def silence_stream(stream)
102 | old_stream = stream.dup
103 | stream.reopen('/dev/null')
104 | stream.sync = true
105 | yield
106 | ensure
107 | stream.reopen(old_stream)
108 | end
109 |
110 | # test chemotaxis
111 | def test_chemotaxis
112 | rs = nil
113 | silence_stream(STDOUT) do
114 | rs = chemotaxis([{:vector=>[0.5,0.5]},{:vector=>[1,1]}], [[0,1],[0,1]], 5, 10, 0.005,1.0, 1.0, 1.0, 1.0)
115 | end
116 | assert_not_nil(rs)
117 | assert_equal(2, rs.size)
118 | # best
119 | assert_not_nil(rs[0][:cost])
120 | # pop
121 | assert_equal(2, rs[1].size)
122 | end
123 |
124 | # test that the algorithm can solve the problem
125 | def test_search
126 | best = nil
127 | silence_stream(STDOUT) do
128 | best = search([[-5,5],[-5,5]], 20, 1, 4, 30, 4, 0.1, 0.1, 0.2, 0.1, 10, 0.25)
129 | end
130 | assert_not_nil(best[:cost])
131 | assert_in_delta(0.0, best[:cost], 0.01)
132 | end
133 |
134 | end
135 |
--------------------------------------------------------------------------------
/src/algorithms/stochastic/guided_local_search.rb:
--------------------------------------------------------------------------------
1 | # Guided Local Search in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def random_permutation(cities)
12 | perm = Array.new(cities.size){|i| i}
13 | perm.each_index do |i|
14 | r = rand(perm.size-i) + i
15 | perm[r], perm[i] = perm[i], perm[r]
16 | end
17 | return perm
18 | end
19 |
20 | def stochastic_two_opt(permutation)
21 | perm = Array.new(permutation)
22 | c1, c2 = rand(perm.size), rand(perm.size)
23 | exclude = [c1]
24 | exclude << ((c1==0) ? perm.size-1 : c1-1)
25 | exclude << ((c1==perm.size-1) ? 0 : c1+1)
26 | c2 = rand(perm.size) while exclude.include?(c2)
27 | c1, c2 = c2, c1 if c2 < c1
28 | perm[c1...c2] = perm[c1...c2].reverse
29 | return perm
30 | end
31 |
32 | def augmented_cost(permutation, penalties, cities, lambda)
33 | distance, augmented = 0, 0
34 | permutation.each_with_index do |c1, i|
35 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
36 | c1, c2 = c2, c1 if c2 < c1
37 | d = euc_2d(cities[c1], cities[c2])
38 | distance += d
39 | augmented += d + (lambda * (penalties[c1][c2]))
40 | end
41 | return [distance, augmented]
42 | end
43 |
44 | def cost(cand, penalties, cities, lambda)
45 | cost, acost = augmented_cost(cand[:vector], penalties, cities, lambda)
46 | cand[:cost], cand[:aug_cost] = cost, acost
47 | end
48 |
49 | def local_search(current, cities, penalties, max_no_improv, lambda)
50 | cost(current, penalties, cities, lambda)
51 | count = 0
52 | begin
53 | candidate = {:vector=> stochastic_two_opt(current[:vector])}
54 | cost(candidate, penalties, cities, lambda)
55 | count = (candidate[:aug_cost] < current[:aug_cost]) ? 0 : count+1
56 | current = candidate if candidate[:aug_cost] < current[:aug_cost]
57 | end until count >= max_no_improv
58 | return current
59 | end
60 |
61 | def calculate_feature_utilities(penal, cities, permutation)
62 | utilities = Array.new(permutation.size,0)
63 | permutation.each_with_index do |c1, i|
64 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
65 | c1, c2 = c2, c1 if c2 < c1
66 | utilities[i] = euc_2d(cities[c1], cities[c2]) / (1.0 + penal[c1][c2])
67 | end
68 | return utilities
69 | end
70 |
71 | def update_penalties!(penalties, cities, permutation, utilities)
72 | max = utilities.max()
73 | permutation.each_with_index do |c1, i|
74 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
75 | c1, c2 = c2, c1 if c2 < c1
76 | penalties[c1][c2] += 1 if utilities[i] == max
77 | end
78 | return penalties
79 | end
80 |
81 | def search(max_iterations, cities, max_no_improv, lambda)
82 | current = {:vector=>random_permutation(cities)}
83 | best = nil
84 | penalties = Array.new(cities.size){ Array.new(cities.size, 0) }
85 | max_iterations.times do |iter|
86 | current=local_search(current, cities, penalties, max_no_improv, lambda)
87 | utilities=calculate_feature_utilities(penalties,cities,current[:vector])
88 | update_penalties!(penalties, cities, current[:vector], utilities)
89 | best = current if best.nil? or current[:cost] < best[:cost]
90 | puts " > iter=#{(iter+1)}, best=#{best[:cost]}, aug=#{best[:aug_cost]}"
91 | end
92 | return best
93 | end
94 |
95 | if __FILE__ == $0
96 | # problem configuration
97 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
98 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
99 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
100 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
101 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
102 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
103 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
104 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
105 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
106 | # algorithm configuration
107 | max_iterations = 150
108 | max_no_improv = 20
109 | alpha = 0.3
110 | local_search_optima = 12000.0
111 | lambda = alpha * (local_search_optima/berlin52.size.to_f)
112 | # execute the algorithm
113 | best = search(max_iterations, berlin52, max_no_improv, lambda)
114 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
115 | end
116 |
--------------------------------------------------------------------------------
/src/algorithms/swarm/ant_system.rb:
--------------------------------------------------------------------------------
1 | # Ant System in the Ruby Programming Language
2 |
3 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com
4 | # (c) Copyright 2010 Jason Brownlee. Some Rights Reserved.
5 | # This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Australia License.
6 |
7 | def euc_2d(c1, c2)
8 | Math.sqrt((c1[0] - c2[0])**2.0 + (c1[1] - c2[1])**2.0).round
9 | end
10 |
11 | def cost(permutation, cities)
12 | distance =0
13 | permutation.each_with_index do |c1, i|
14 | c2 = (i==permutation.size-1) ? permutation[0] : permutation[i+1]
15 | distance += euc_2d(cities[c1], cities[c2])
16 | end
17 | return distance
18 | end
19 |
20 | def random_permutation(cities)
21 | perm = Array.new(cities.size){|i| i}
22 | perm.each_index do |i|
23 | r = rand(perm.size-i) + i
24 | perm[r], perm[i] = perm[i], perm[r]
25 | end
26 | return perm
27 | end
28 |
29 | def initialise_pheromone_matrix(num_cities, naive_score)
30 | v = num_cities.to_f / naive_score
31 | return Array.new(num_cities){|i| Array.new(num_cities, v)}
32 | end
33 |
34 | def calculate_choices(cities, last_city, exclude, pheromone, c_heur, c_hist)
35 | choices = []
36 | cities.each_with_index do |coord, i|
37 | next if exclude.include?(i)
38 | prob = {:city=>i}
39 | prob[:history] = pheromone[last_city][i] ** c_hist
40 | prob[:distance] = euc_2d(cities[last_city], coord)
41 | prob[:heuristic] = (1.0/prob[:distance]) ** c_heur
42 | prob[:prob] = prob[:history] * prob[:heuristic]
43 | choices << prob
44 | end
45 | choices
46 | end
47 |
48 | def select_next_city(choices)
49 | sum = choices.inject(0.0){|sum,element| sum + element[:prob]}
50 | return choices[rand(choices.size)][:city] if sum == 0.0
51 | v = rand()
52 | choices.each_with_index do |choice, i|
53 | v -= (choice[:prob]/sum)
54 | return choice[:city] if v <= 0.0
55 | end
56 | return choices.last[:city]
57 | end
58 |
59 | def stepwise_const(cities, phero, c_heur, c_hist)
60 | perm = []
61 | perm << rand(cities.size)
62 | begin
63 | choices = calculate_choices(cities,perm.last,perm,phero,c_heur,c_hist)
64 | next_city = select_next_city(choices)
65 | perm << next_city
66 | end until perm.size == cities.size
67 | return perm
68 | end
69 |
70 | def decay_pheromone(pheromone, decay_factor)
71 | pheromone.each do |array|
72 | array.each_with_index do |p, i|
73 | array[i] = (1.0 - decay_factor) * p
74 | end
75 | end
76 | end
77 |
78 | def update_pheromone(pheromone, solutions)
79 | solutions.each do |other|
80 | other[:vector].each_with_index do |x, i|
81 | y=(i==other[:vector].size-1) ? other[:vector][0] : other[:vector][i+1]
82 | pheromone[x][y] += (1.0 / other[:cost])
83 | pheromone[y][x] += (1.0 / other[:cost])
84 | end
85 | end
86 | end
87 |
88 | def search(cities, max_it, num_ants, decay_factor, c_heur, c_hist)
89 | best = {:vector=>random_permutation(cities)}
90 | best[:cost] = cost(best[:vector], cities)
91 | pheromone = initialise_pheromone_matrix(cities.size, best[:cost])
92 | max_it.times do |iter|
93 | solutions = []
94 | num_ants.times do
95 | candidate = {}
96 | candidate[:vector] = stepwise_const(cities, pheromone, c_heur, c_hist)
97 | candidate[:cost] = cost(candidate[:vector], cities)
98 | best = candidate if candidate[:cost] < best[:cost]
99 | solutions << candidate
100 | end
101 | decay_pheromone(pheromone, decay_factor)
102 | update_pheromone(pheromone, solutions)
103 | puts " > iteration #{(iter+1)}, best=#{best[:cost]}"
104 | end
105 | return best
106 | end
107 |
108 | if __FILE__ == $0
109 | # problem configuration
110 | berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
111 | [880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
112 | [1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
113 | [415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
114 | [835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
115 | [410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
116 | [685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
117 | [95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
118 | [830,610],[605,625],[595,360],[1340,725],[1740,245]]
119 | # algorithm configuration
120 | max_it = 50
121 | num_ants = 30
122 | decay_factor = 0.6
123 | c_heur = 2.5
124 | c_hist = 1.0
125 | # execute the algorithm
126 | best = search(berlin52, max_it, num_ants, decay_factor, c_heur, c_hist)
127 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}"
128 | end
129 |
--------------------------------------------------------------------------------