├── 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 | 6 | Clever Algorithms 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/bibtex/error.rb: -------------------------------------------------------------------------------- 1 | module BibTeX 2 | 3 | # 4 | # Represents a lexical or syntactical error. 5 | # 6 | class Error < Element 7 | 8 | attr_reader :trace 9 | 10 | def initialize(trace=[]) 11 | @trace = trace 12 | end 13 | 14 | def trace=(trace) 15 | raise(ArgumentError, "BibTeX::Error trace must be of type Array; was: #{trace.class.name}.") unless trace.kind_of?(Array) 16 | @trace = trace 17 | end 18 | 19 | def content 20 | @trace.map { |e| e[1] }.join 21 | end 22 | 23 | # Called when the element was added to a bibliography. 24 | def added_to_bibliography(bibliography) 25 | super(bibliography) 26 | bibliography.errors << self 27 | self 28 | end 29 | 30 | # Called when the element was removed from a bibliography. 31 | def removed_from_bibliography(bibliography) 32 | super(bibliography) 33 | bibliography.errors.delete(self) 34 | self 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /book/f_title_page.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 | % Title Page 7 | 8 | 9 | % def 10 | \makeatletter 11 | \def\thickhrulefill{\leavevmode \leaders \hrule height 1pt\hfill \kern \z@} 12 | \renewcommand{\maketitle}{\begin{titlepage}% 13 | \let\footnotesize\small 14 | \let\footnoterule\relax 15 | \parindent \z@ 16 | \reset@font 17 | \null 18 | \vskip 10\p@ 19 | \hbox{\mbox{\hspace{3em}}% 20 | \vrule depth 0.6\textheight% 21 | \mbox{\hspace{2em}} 22 | \vbox{ 23 | \vskip 40\p@ 24 | \begin{flushleft} 25 | \Large \@author \par 26 | \end{flushleft} 27 | \vskip 70\p@ 28 | \begin{flushleft} 29 | \huge \bfseries \@title \par 30 | \end{flushleft} 31 | \vfil 32 | }} 33 | \null 34 | \end{titlepage}% 35 | \setcounter{footnote}{0}% 36 | } 37 | \makeatother 38 | \author{\mybookauthor} 39 | \title{\mybooktitle\\{\large\mybooksubtitle}} 40 | \date{\mybookdate} -------------------------------------------------------------------------------- /src/algorithms/evolutionary/tests/tc_genetic_algorithm2.rb: -------------------------------------------------------------------------------- 1 | # Unit tests for genetic_algorithm.rb 2 | # additional tests, because tc_genetic_algorithm.rb is used as an example in the book. 3 | 4 | # The Clever Algorithms Project: http://www.CleverAlgorithms.com 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__)) + "/../genetic_algorithm" 10 | 11 | class TC_GeneticAlgorithm2 < Test::Unit::TestCase 12 | 13 | # helper for turning off STDOUT 14 | # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 39 15 | def silence_stream(stream) 16 | old_stream = stream.dup 17 | stream.reopen('/dev/null') 18 | stream.sync = true 19 | yield 20 | ensure 21 | stream.reopen(old_stream) 22 | end 23 | 24 | # test that the algorithm can solve the problem 25 | def test_search 26 | best = nil 27 | silence_stream(STDOUT) do 28 | best = search(100, 64, 100, 0.95, 1.0/64.0) 29 | end 30 | assert_not_nil(best[:fitness]) 31 | assert_equal(64, best[:fitness]) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /book/f_foreword.tex: -------------------------------------------------------------------------------- 1 | % 2 | % Foreword 3 | % 4 | \chapter*{Foreword\markboth{Foreword}{}} 5 | \addcontentsline{toc}{chapter}{Foreword} 6 | 7 | I am delighted to write this foreword. This book, a reference where one can look up the details of most any algorithm to find a clear unambiguous description, has long been needed and here it finally is. A concise reference that has taken many hours to write but which has the capacity to save vast amounts of time previously spent digging out original papers. 8 | 9 | I have known the author for several years and have had experience of his amazing capacity for work and the sheer quality of his output, so this book comes as no surprise to me. But I hope it will be a surprise and delight to you, the reader for whom it has been written. 10 | 11 | But useful as this book is, it is only a beginning. There are so many algorithms that no one author could hope to cover them all. So if you know of an algorithm that is not yet here, how about contributing it using the same clear and lucid style? 12 | 13 | \begin{flushright} 14 | \vspace{0.5in} 15 | Professor Tim Hendtlass \\ 16 | Complex Intelligent Systems Laboratory \\ 17 | Faculty of Information and Communication Technologies \\ 18 | Swinburne University of Technology 19 | \end{flushright} 20 | 21 | \begin{flushleft} 22 | \vspace{0.2in} 23 | Melbourne, Australia \\ 24 | 2010 25 | \end{flushleft} 26 | -------------------------------------------------------------------------------- /src/algorithms/stochastic/random_search.rb: -------------------------------------------------------------------------------- 1 | # 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 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 search(search_space, max_iter) 18 | best = nil 19 | max_iter.times do |iter| 20 | candidate = {} 21 | candidate[:vector] = random_vector(search_space) 22 | candidate[:cost] = objective_function(candidate[:vector]) 23 | best = candidate if best.nil? or candidate[:cost] < best[:cost] 24 | puts " > iteration=#{(iter+1)}, best=#{best[:cost]}" 25 | end 26 | return best 27 | end 28 | 29 | if __FILE__ == $0 30 | # problem configuration 31 | problem_size = 2 32 | search_space = Array.new(problem_size) {|i| [-5, +5]} 33 | # algorithm configuration 34 | max_iter = 100 35 | # execute the algorithm 36 | best = search(search_space, max_iter) 37 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].inspect}" 38 | end 39 | -------------------------------------------------------------------------------- /book/f_acknowledgments.tex: -------------------------------------------------------------------------------- 1 | % 2 | % Acknowledgments 3 | % 4 | \chapter*{Acknowledgments\markboth{Acknowledgments}{}} 5 | % general 6 | This book could not have been completed without the commitment, passion, and hard work from a large group of editors and supporters. 7 | 8 | % Steve 9 | A special thanks to Steve Dower for his incredible attention to detail in providing technical and copy edits for large portions of this book, and for his enthusiasm for the subject area. 10 | % Dan 11 | Also, a special thanks to Daniel Angus for the discussions around the genesis of the project, his continued support with the idea of an `algorithms atlas' and for his attention to detail in providing technical and copy edits for key chapters. 12 | 13 | % helpers and technical editors 14 | In no particular order, thanks to: 15 | Juan Ojeda, 16 | Martin Goddard, 17 | David Howden, 18 | Sean Luke, 19 | David Zappia, 20 | Jeremy Wazny, 21 | Andrew Murray, 22 | John Wise, 23 | Patrick Boehnke, 24 | Martin-Louis Bright, 25 | Leif Wickland, 26 | Andrew Myers, 27 | Paul Chinnery, 28 | Donald Doherty, 29 | Brook Tamir, 30 | Zach Scott, 31 | Diego Noble, 32 | Jason Davies, 33 | Mark Chenoweth, 34 | Markus Stokmaier, 35 | Stefan Pauleweit, 36 | Lai Yu-Hsuan, 37 | and 38 | Stephan Williams. 39 | 40 | % cover voting 41 | Thanks to the hundreds of machine learning enthusiasts who voted on potential covers and helped shape what this book became. You know who you are! 42 | 43 | % support 44 | Finally, I would like to thank my beautiful wife Ying Liu for her unrelenting support and patience throughout the project. 45 | -------------------------------------------------------------------------------- /book/book.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 | \documentclass[twoside, openright]{book} 6 | \include{definitions} 7 | \include{f_title_page} 8 | \makeindex 9 | % paperback only 10 | \geometry{bindingoffset=1cm, twoside, paperwidth=6in, paperheight=9in, top=.9in, bottom=.6667in, outer=0.667in} 11 | 12 | % \includeonly{f_copyright} 13 | 14 | \begin{document} 15 | \defaultbibliography{book/bibtex} 16 | \frontmatter 17 | \maketitle 18 | \include{f_copyright} 19 | \cleardoublepage\include{f_toc} 20 | \addtocontents{toc}{\protect\markboth{Contents}{}} 21 | \cleardoublepage\include{f_foreword} 22 | \cleardoublepage\include{f_preface} 23 | \cleardoublepage\include{f_acknowledgments} 24 | \mainmatter 25 | \part{Background} 26 | \include{c_introduction} 27 | \part{Algorithms} 28 | \include{c_stochastic} 29 | \include{c_evolution} 30 | \include{c_physical} 31 | \include{c_probabilistic} 32 | \include{c_swarm} 33 | \include{c_immune} 34 | \include{c_neural} 35 | \part{Extensions} 36 | \include{c_advanced} 37 | \part{Appendix} 38 | \appendix 39 | \include{b_appendix1} 40 | \cleardoublepage 41 | \include{b_errata} 42 | % correct handling of the index (new odd page) 43 | \cleardoublepage 44 | \phantomsection 45 | \addcontentsline{toc}{part}{Index} 46 | {\footnotesize \printindex} 47 | % blank page 48 | \blanknonumber\cleardoublepage 49 | \end{document} 50 | -------------------------------------------------------------------------------- /src/algorithms/stochastic/stochastic_hill_climbing.rb: -------------------------------------------------------------------------------- 1 | # Stochastic Hill Climbing 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.0){|sum, v| sum + ((v=="1") ? 1 : 0)} 9 | end 10 | 11 | def random_bitstring(num_bits) 12 | return Array.new(num_bits){|i| (rand<0.5) ? "1" : "0"} 13 | end 14 | 15 | def random_neighbor(bitstring) 16 | mutant = Array.new(bitstring) 17 | pos = rand(bitstring.size) 18 | mutant[pos] = (mutant[pos]=='1') ? '0' : '1' 19 | return mutant 20 | end 21 | 22 | def search(max_iterations, num_bits) 23 | candidate = {} 24 | candidate[:vector] = random_bitstring(num_bits) 25 | candidate[:cost] = onemax(candidate[:vector]) 26 | max_iterations.times do |iter| 27 | neighbor = {} 28 | neighbor[:vector] = random_neighbor(candidate[:vector]) 29 | neighbor[:cost] = onemax(neighbor[:vector]) 30 | candidate = neighbor if neighbor[:cost] >= candidate[:cost] 31 | puts " > iteration #{(iter+1)}, best=#{candidate[:cost]}" 32 | break if candidate[:cost] == num_bits 33 | end 34 | return candidate 35 | end 36 | 37 | if __FILE__ == $0 38 | # problem configuration 39 | num_bits = 64 40 | # algorithm configuration 41 | max_iterations = 1000 42 | # execute the algorithm 43 | best = search(max_iterations, num_bits) 44 | puts "Done. Best Solution: c=#{best[:cost]}, v=#{best[:vector].join}" 45 | end 46 | -------------------------------------------------------------------------------- /web/bibtex/string_replacement.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 | 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 | --------------------------------------------------------------------------------