wallace 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. data/Gemfile +14 -0
  2. data/README.md +12 -0
  3. data/Rakefile +52 -0
  4. data/VERSION +1 -0
  5. data/bin/.gitkeep +0 -0
  6. data/lib/analysers/fitness_distribution_analyser.rb +28 -0
  7. data/lib/analysers/init.rb +1 -0
  8. data/lib/core/analyser.rb +63 -0
  9. data/lib/core/breeder.rb +68 -0
  10. data/lib/core/breeding_graph.rb +59 -0
  11. data/lib/core/breeding_graph/init.rb +3 -0
  12. data/lib/core/breeding_graph/input_node.rb +55 -0
  13. data/lib/core/breeding_graph/node.rb +68 -0
  14. data/lib/core/breeding_graph/node_input.rb +6 -0
  15. data/lib/core/evaluator.rb +52 -0
  16. data/lib/core/evolver.rb +47 -0
  17. data/lib/core/exceptions.rb +6 -0
  18. data/lib/core/experiment.rb +22 -0
  19. data/lib/core/fitness.rb +8 -0
  20. data/lib/core/fraction.rb +7 -0
  21. data/lib/core/individual.rb +65 -0
  22. data/lib/core/logger.rb +5 -0
  23. data/lib/core/migrator.rb +6 -0
  24. data/lib/core/operator.rb +54 -0
  25. data/lib/core/population.rb +56 -0
  26. data/lib/core/selector.rb +43 -0
  27. data/lib/core/species.rb +52 -0
  28. data/lib/core/state.rb +29 -0
  29. data/lib/core/subpopulation.rb +53 -0
  30. data/lib/core/termination.rb +39 -0
  31. data/lib/distributions/gaussian_distribution.rb +60 -0
  32. data/lib/distributions/init.rb +3 -0
  33. data/lib/fitness/init.rb +1 -0
  34. data/lib/fitness/raw_fitness.rb +30 -0
  35. data/lib/loggers/csv_logger.rb +20 -0
  36. data/lib/loggers/init.rb +1 -0
  37. data/lib/loggers/mongo_logger.rb +5 -0
  38. data/lib/loggers/sqlite_logger.rb +5 -0
  39. data/lib/modules/ge/backus_naur_form.rb +125 -0
  40. data/lib/modules/ge/grammar_derivation.rb +30 -0
  41. data/lib/modules/ge/grammar_species.rb +29 -0
  42. data/lib/modules/ge/init.rb +5 -0
  43. data/lib/modules/init.rb +2 -0
  44. data/lib/modules/koza/builder.rb +48 -0
  45. data/lib/modules/koza/builder/full_builder.rb +92 -0
  46. data/lib/modules/koza/builder/grow_builder.rb +103 -0
  47. data/lib/modules/koza/builder/half_builder.rb +70 -0
  48. data/lib/modules/koza/builder/init.rb +3 -0
  49. data/lib/modules/koza/ephemeral.rb +33 -0
  50. data/lib/modules/koza/init.rb +12 -0
  51. data/lib/modules/koza/koza_node.rb +108 -0
  52. data/lib/modules/koza/koza_node_value.rb +72 -0
  53. data/lib/modules/koza/koza_node_value_set.rb +51 -0
  54. data/lib/modules/koza/koza_species.rb +48 -0
  55. data/lib/modules/koza/koza_tree.rb +159 -0
  56. data/lib/modules/koza/operators/init.rb +4 -0
  57. data/lib/modules/koza/operators/subtree_crossover_operation.rb +43 -0
  58. data/lib/modules/koza/operators/subtree_mutation_operation.rb +32 -0
  59. data/lib/operators/bit_flip_mutation_operation.rb +29 -0
  60. data/lib/operators/boundary_mutation_operation.rb +28 -0
  61. data/lib/operators/cycle_crossover_operation.rb +77 -0
  62. data/lib/operators/gaussian_mutation_operation.rb +39 -0
  63. data/lib/operators/half_uniform_crossover_operation.rb +24 -0
  64. data/lib/operators/init.rb +26 -0
  65. data/lib/operators/merging_crossover_operation.rb +27 -0
  66. data/lib/operators/one_point_crossover_operation.rb +29 -0
  67. data/lib/operators/order_crossover_operation.rb +38 -0
  68. data/lib/operators/partially_mapped_crossover_operation.rb +44 -0
  69. data/lib/operators/point_mutation_operation.rb +31 -0
  70. data/lib/operators/position_crossover_operation.rb +50 -0
  71. data/lib/operators/reverse_sequence_mutation_operation.rb +13 -0
  72. data/lib/operators/shuffle_mutation_operation.rb +17 -0
  73. data/lib/operators/splice_crossover_operation.rb +42 -0
  74. data/lib/operators/subtour_exchange_crossover_operation.rb +54 -0
  75. data/lib/operators/swap_mutation_operation.rb +29 -0
  76. data/lib/operators/three_parent_crossover_operation.rb +16 -0
  77. data/lib/operators/two_point_crossover_operation.rb +31 -0
  78. data/lib/operators/twors_mutation_operation.rb +18 -0
  79. data/lib/operators/uniform_crossover_operation.rb +30 -0
  80. data/lib/operators/uniform_mutation_operation.rb +31 -0
  81. data/lib/operators/variable_one_point_crossover_operation.rb +80 -0
  82. data/lib/patches/enumerable.rb +85 -0
  83. data/lib/patches/init.rb +5 -0
  84. data/lib/patches/range.rb +13 -0
  85. data/lib/selectors/init.rb +5 -0
  86. data/lib/selectors/random_selector.rb +14 -0
  87. data/lib/selectors/roulette_selector.rb +23 -0
  88. data/lib/selectors/tournament_selector.rb +36 -0
  89. data/lib/species/array_species.rb +40 -0
  90. data/lib/species/bit_string_species.rb +18 -0
  91. data/lib/species/init.rb +4 -0
  92. data/lib/species/permutation_species.rb +22 -0
  93. data/lib/species/string_species.rb +29 -0
  94. data/lib/utility/init.rb +4 -0
  95. data/lib/utility/scaled_array.rb +88 -0
  96. data/lib/utility/sorted_array.rb +39 -0
  97. data/lib/wallace.rb +40 -0
  98. data/test/.gitkeep +0 -0
  99. metadata +248 -0
@@ -0,0 +1,53 @@
1
+ # This class is used to hold sub-populations of individuals. For at least
2
+ # some length of time, subpopulations evolve independently, only interacting
3
+ # with the rest of the population (other subpopulations) during migration.
4
+ class Wallace::Subpopulation
5
+
6
+ attr_accessor :contents
7
+ attr_reader :capacity,
8
+ :species
9
+
10
+ # Constructs a new subpopulation.
11
+ #
12
+ # *Arguments*
13
+ # * opts, a hash of keyword options for this method.
14
+ # -> size, the size of this subpopulation.
15
+ # -> species, the species of individuals within this sub-population.
16
+ def initialize(opts = {})
17
+ @capacity = opts[:size]
18
+ @contents = []
19
+ @species = opts[:species]
20
+ end
21
+
22
+ # Initialises a fresh sub-population of individuals.
23
+ #
24
+ # *Parameters:*
25
+ # * rng, the random number generator to use.
26
+ def fresh!(rng)
27
+ @contents = Array.new(@capacity) { @species.spawn(random: rng) }
28
+ end
29
+
30
+ # Clears the contents of this sub-population.
31
+ def clear!
32
+ @contents = []
33
+ end
34
+
35
+ # Returns the best individual within this sub-population.
36
+ def best
37
+ @contents.min
38
+ end
39
+ alias :min :best
40
+
41
+ # Returns the worst individual within this sub-population.
42
+ def worst
43
+ @contents.max
44
+ end
45
+ alias :max :worst
46
+
47
+ # Returns the actual number of individuals within this population.
48
+ def size
49
+ @contents.size
50
+ end
51
+ alias_method :length, :size
52
+
53
+ end
@@ -0,0 +1,39 @@
1
+ # This class is used to store and check against the termination criteria of the evolutionary
2
+ # process.
3
+ class Wallace::Termination
4
+
5
+ attr_reader :generation_limit,
6
+ :stagnation_limit,
7
+ :fitness_limit,
8
+ :error
9
+
10
+ # Constructs a new termination criteria.
11
+ #
12
+ # *Parameters:*
13
+ # * opts, a hash of keyword options for this method.
14
+ # -> generations, limit on the total number of generations to perform.
15
+ # -> stagnation, limit on the successive number of generations without change to the best solution.
16
+ # -> fitness, target fitness value.
17
+ # -> error, the maximum allowed error in the target fitness value.
18
+ def initialize(opts = {})
19
+ @generation_limit = opts[:generations] || 200
20
+ @stagnation_limit = opts[:stagnation]
21
+ @fitness_limit = opts[:fitness]
22
+ @error = opts[:error] || 0
23
+ end
24
+
25
+ # Determines whether the evolutionary process has reached its termination
26
+ # criteria or not.
27
+ #
28
+ # *Parameters:*
29
+ # * state, the current state of the evolution.
30
+ #
31
+ # Returns true if it has finished and false if otherwise.
32
+ def finished?(state)
33
+ return true if not @generation_limit.nil? and state.generations == @generation_limit
34
+ return true if not @fitness_limit.nil? and state.best.fitness.value <= @fitness_limit + @error
35
+ return true if not @stagnation_limit.nil? and state.stagnation == @stagnation_limit
36
+ return false
37
+ end
38
+
39
+ end
@@ -0,0 +1,60 @@
1
+ # Used to represent and sample values from a given Gaussian distribution.
2
+ #
3
+ # Uses the popular Box-Muller transformation to generate two values,
4
+ # one of which is returned, the other which is cached (and returned
5
+ # upon the next request).
6
+ #
7
+ # Since the Box-Muller transform samples two values from the distribution,
8
+ # rather than discarding the extra value, we cache it and return it
9
+ # upon the next request.
10
+ #
11
+ # *Credit to:*
12
+ # http://stackoverflow.com/questions/5825680/code-to-generate-gaussian-normally-distributed-random-numbers-in-ruby
13
+ class Wallace::Distributions::GaussianDistribution
14
+
15
+ # Constructs a new Gaussian distribution.
16
+ #
17
+ # *Parameters:*
18
+ # * mean, the mean of this distribution.
19
+ # * std_dev, the standard deviation of this distribution.
20
+ def initialize(mean, std_dev)
21
+ @mean = mean
22
+ @std_dev = std_dev
23
+ @cached = nil
24
+ end
25
+
26
+ # Samples a value from this distribution.
27
+ #
28
+ # Uses the popular Box-Muller transformation to generate two values,
29
+ # one of which is returned, the other which is cached (and returned
30
+ # upon the next request).
31
+ #
32
+ # *Parameters:*
33
+ # * opts, keyword arguments for this method.
34
+ # * -> random, the random number generation to use to seed the selection process.
35
+ def sample(opts = {})
36
+
37
+ # Create a RNG if one isn't already provided.
38
+ random = opts[:random] || Random.new
39
+
40
+ # Return a cached value if there is one.
41
+ # We could collapse these 4 lines into a much nicer:
42
+ # return @values.pop if @values.empty?
43
+ # However .empty? is much more expensive!
44
+ unless @cached.nil?
45
+ value = @cached
46
+ @cached = nil
47
+ return value
48
+ end
49
+
50
+ # Otherwise calculate two new values, caching one and
51
+ # returning the other.
52
+ theta = 2 * Math::PI * random.rand
53
+ rho = Math.sqrt(-2 * Math.log(1 - random.rand))
54
+ scale = @std_dev * rho
55
+ @cached = @mean + scale * Math.cos(theta)
56
+ return @mean + scale * Math.sin(theta)
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,3 @@
1
+ module Wallace::Distributions; end
2
+
3
+ require_relative 'gaussian_distribution.rb'
@@ -0,0 +1 @@
1
+ require_relative 'raw_fitness.rb'
@@ -0,0 +1,30 @@
1
+ # The raw fitness model uses a single numeric type to describe the fitness of a given individual.
2
+ # Ordering of fitnesses, and thus of individuals, is achieved via ordering on these simple numeric
3
+ # types.
4
+ class Wallace::Fitness::RawFitness < Wallace::Fitness
5
+
6
+ # Allow the fitness value to be read but not changed.
7
+ attr_reader :value
8
+
9
+ # Constructs a new raw fitness.
10
+ #
11
+ # *Parameters:*
12
+ # * value, the raw fitness value.
13
+ def initialize(value)
14
+ @value = value
15
+ end
16
+
17
+ # Compares this raw fitness to another.
18
+ #
19
+ # *Parameters:*
20
+ # * other, the fitness to compare against.
21
+ #
22
+ # *Warning:*
23
+ # It is assumed that a RawFitness object is provided for comparison.
24
+ def <=>(other)
25
+ return -1 if other.value.is_a? Float and other.value.nan?
26
+ return 1 if @value.is_a? Float and @value.nan?
27
+ return @value <=> other.value
28
+ end
29
+
30
+ end
@@ -0,0 +1,20 @@
1
+ # The CSV logger records information in a
2
+ class Wallace::Loggers::CsvLogger < Wallace::Logger
3
+
4
+ # Constructs a new CSV logger.
5
+ #
6
+ # *Parameters:*
7
+ # * file, the CSV file to log to.
8
+ # * opts, a hash of keyword options for this logger.
9
+ # -> columns, an ordered list of columns in this CSV.
10
+ # -> headers, an optional hash of headers for the columns in the CSV file.
11
+ def initialize(file)
12
+
13
+ end
14
+
15
+
16
+ def write(data)
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1 @@
1
+ module Wallace::Loggers; end
@@ -0,0 +1,5 @@
1
+ class Wallace::Loggers::MongoLogger < Wallace::Logger
2
+
3
+
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ class Wallace::Loggers::SQLiteLogger < Wallace::Logger
2
+
3
+
4
+
5
+ end
@@ -0,0 +1,125 @@
1
+ # Used to hold token values.
2
+ Wallace::GE::Literal = Struct.new(:value)
3
+ Wallace::GE::Symbol = Struct.new(:value)
4
+
5
+ # This class holds the details of a BNF grammar and provides methods for producing
6
+ # a derivation from a given sequence of integers.
7
+ #
8
+ # Rather than having a special class for grammar sequences, we simply use arrays to
9
+ # store grammar sequences, ensuring the best possible compatibility with existing
10
+ # array-based operators.
11
+ class Wallace::GE::BackusNaurForm
12
+
13
+ # The root symbol for this grammar (where all derivations begin).
14
+ attr_reader :root
15
+
16
+ # Constructs a new BNF Grammar.
17
+ #
18
+ # *Parameters:*
19
+ # * root, the root symbol in the grammar (at which all derivations start).
20
+ # * rules, the rules of this grammar, given as a hash, indexed by symbol.
21
+ def initialize(root, rules)
22
+
23
+ # Convert each rule into a sequence of symbol derivations.
24
+ # Transform each string-based derivation into a sequence of tokens.
25
+ @rules = Hash[rules.map { |rule, derivations|
26
+ derivations.map! { |derivation|
27
+ tokens = []
28
+ until derivation.empty?
29
+ left, tag, right = derivation.partition(/{\w+}/)
30
+ tokens << Wallace::GE::Literal.new(left) unless left.empty?
31
+ tokens << Wallace::GE::Symbol.new(tag[1...tag.length-1]) unless tag.empty?
32
+ derivation = right
33
+ end
34
+ tokens
35
+ }
36
+ [rule.to_s, derivations]
37
+ }].freeze
38
+
39
+ # Could ensure that the root is a valid symbol.
40
+ @root = root.to_s.freeze
41
+
42
+ end
43
+
44
+ # Returns the number of symbols in the BNF.
45
+ def size
46
+ @rules.size
47
+ end
48
+ alias :length :size
49
+
50
+ # Returns the list of possible derivations for a given symbol.
51
+ def [](symbol)
52
+ @rules[symbol]
53
+ end
54
+ alias :derivations :[]
55
+
56
+ # Produces a grammar derivation from a given sequence of integers.
57
+ #
58
+ # *Parameters:*
59
+ # * sequence, the sequence of integers to produce a derivation from.
60
+ # * opts, a hash of keyword options for this method.
61
+ # -> random, the RNG to use during the derivation process.
62
+ # -> wrap, flag indicating whether the sequence should be wrapped when
63
+ # all genetic material has been used.
64
+ # -> max_wraps, the maximum number of times the sequence should be
65
+ # wrapped.
66
+ #
67
+ # *Returns:*
68
+ # A grammar derivation object for the produced derivation.
69
+ def derive(sequence, opts = {})
70
+
71
+ # For now wrapping is forced.
72
+ opts[:wrap] = true
73
+ opts[:max_wraps] ||= 1 if opts[:wrap]
74
+
75
+ derivation = Wallace::GE::GrammarDerivation.new
76
+
77
+ # Keep processing the sequence of tokens until none remain.
78
+ #
79
+ # * Literal tokens are appended to the end of the derivation.
80
+ # * Symbol tokens are converted into a given symbol derivation,
81
+ # using the next codon as the index if there is more than a single
82
+ # choice.
83
+ # * When there are no codons left to consume then we either:
84
+ # a) Add a new codon to the sequence (until the limit is reached). [DISABLED]
85
+ # b) Reset the codon index to zero, wrapping the sequence round.
86
+ queue = [Wallace::GE::Symbol.new(@root)]
87
+ codon_index = 0
88
+ sequence_length = sequence.length
89
+ num_wraps = 0
90
+
91
+ until queue.empty?
92
+ token = queue.shift
93
+ if token.is_a? Wallace::GE::Literal
94
+ derivation << token.value
95
+ else
96
+ options = @rules[token.value]
97
+ if options.length == 1
98
+ queue += options[0]
99
+ else
100
+
101
+ # Check if there are no remaining codons in the sequence.
102
+ if (codon_index >= sequence_length)
103
+
104
+ # If wrapping is enabled, reset the codon pointer to the
105
+ # start of the sequence, unless the maximum number of wraps
106
+ # has been encountered.
107
+ if opts[:wrap]
108
+ raise Wallace::Errors::GenotypeExhaustedError if num_wraps != opts[:max_wraps] # throw error?
109
+ codon_index = 0
110
+ num_wraps += 1
111
+ end
112
+
113
+ end
114
+
115
+ queue = options[sequence[codon_index] % options.length] + queue
116
+ codon_index += 1
117
+ end
118
+ end
119
+ end
120
+
121
+ return derivation.freeze
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,30 @@
1
+ # The GrammarDerivation class is used to hold a derivation of a grammar produced by some provided
2
+ # sequence of integers. This class implements methods for transforming the derivation into other
3
+ # forms.
4
+ class Wallace::GE::GrammarDerivation < String
5
+
6
+ # Produces a lambda function from a given grammar derivation and a set
7
+ # of arguments.
8
+ #
9
+ # *Parameters:*
10
+ # * args, an ordered array of the arguments to the lambda function.
11
+ # * derivation, the grammar derivation to use as the body of the function.
12
+ #
13
+ # *Returns:*
14
+ # The corresponding lambda function.
15
+ def self.to_lambda(args, derivation)
16
+ eval("lambda { |#{args.join(',')}| #{derivation} }")
17
+ end
18
+
19
+ # Converts this derivation into a usable lambda function.
20
+ #
21
+ # *Parameters:*
22
+ # * args, an ordered array of the arguments to the lambda function.
23
+ #
24
+ # *Returns:*
25
+ # The corresponding lambda function.
26
+ def to_lambda(args)
27
+ Wallace::GE::GrammarDerivation.to_lambda(args, self)
28
+ end
29
+
30
+ end
@@ -0,0 +1,29 @@
1
+ # The grammar species represents each individual in the species as an array of integers which
2
+ # map to a derivation of the grammar attached to the species.
3
+ class Wallace::GE::GrammarSpecies < Wallace::Species::ArraySpecies
4
+
5
+ name = :grammar
6
+
7
+ attr_reader :grammar
8
+
9
+ # Constructs a new grammar-based species.
10
+ #
11
+ # *Parameters:*
12
+ # * opts, hash of keyword options used by this method.
13
+ # -> id, the unique identifier for this species.
14
+ # -> grammar, the grammar used by individuals of this species.
15
+ # -> length, the length constraints on individuals of the species.
16
+ def initialize(opts = {})
17
+
18
+ # By default grammar derivations are between 10 and 200 symbols and contain
19
+ # integers in the range 0 to 2147483647 inclusive.
20
+ opts[:length] ||= 10..200
21
+ opts[:values] ||= 0..2147483647
22
+
23
+ super(opts)
24
+
25
+ @grammar = opts[:grammar]
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,5 @@
1
+ module Wallace::GE; end
2
+
3
+ require_relative 'backus_naur_form.rb'
4
+ require_relative 'grammar_species.rb'
5
+ require_relative 'grammar_derivation.rb'
@@ -0,0 +1,2 @@
1
+ require_relative 'koza/init.rb'
2
+ require_relative 'ge/init.rb'
@@ -0,0 +1,48 @@
1
+ # The base class used by all Koza tree builders.
2
+ class Wallace::Koza::Builder
3
+
4
+ # The range of depths that trees within this species may have.
5
+ attr_accessor :depth_limits
6
+
7
+ attr_reader :terminals,
8
+ :non_terminals
9
+
10
+ # Prepares the builder.
11
+ #
12
+ # *Parameters:*
13
+ # * terminals, the list of terminals.
14
+ # * non_terminals, the list of non-terminals.
15
+ # * depth_limits, the range of depths that trees within the species may have.
16
+ def prepare(terminals, non_terminals, depth_limits)
17
+ @terminals = terminals
18
+ @non_terminals = non_terminals
19
+ @depth_limits = depth_limits
20
+ end
21
+
22
+ # Builds a new tree according to this build method that meets the depth
23
+ # constraints of the species.
24
+ #
25
+ # *Parameters:*
26
+ # * opts, a hash of keyword options for this method.
27
+ # -> random, the RNG to use for building the tree.
28
+ #
29
+ # *Returns:*
30
+ # The built tree.
31
+ def build_tree(opts = {})
32
+ raise NotImplementedError, 'No "build_tree" function was implemented by this builder.'
33
+ end
34
+
35
+ # Builds a new subtree of a maximum given depth.
36
+ #
37
+ # *Parameters:*
38
+ # * max_depth, the maximum depth of the sub-tree.
39
+ # * opts, a hash of keyword options for this method.
40
+ # -> random, the RNG to use for building the subtree.
41
+ #
42
+ # *Returns:*
43
+ # The root node of the built sub-tree.
44
+ def build_subtree(max_depth, opts = {})
45
+ raise NotImplementedError, 'No "build_subtree" function was implemented by this builder.'
46
+ end
47
+
48
+ end