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,29 @@
1
+ # The swap mutation operator swaps pairs of consecutive genes according to
2
+ # an associated probability.
3
+ class Wallace::Operators::SwapMutationOperator < Wallace::Operator
4
+
5
+ name = :swap_mutation
6
+
7
+ # Allow the swap probability to be dynamically adjusted.
8
+ attr_accessor :probability
9
+
10
+ # Constructs a new swap mutation operator.
11
+ #
12
+ # *Parameters:*
13
+ # * opts, a hash of keyword options for this method.
14
+ # -> id, the unique identifier for this operator.
15
+ # -> inputs, an array of inputs (OperatorInput) to this operator.
16
+ # -> probability, the probability that a given pair (of consecutive genes) should be swapped. (default = 0.05).
17
+ def initialize(opts = {})
18
+ super(opts)
19
+ @probability = opts[:probability] || 0.05
20
+ end
21
+
22
+ def operate(rng, inputs)
23
+ (0...inputs[0].length).each_cons(2) do |g1, g2|
24
+ inputs[0][g1], inputs[0][g2] = inputs[0][g2], inputs[0][g1] if rng.rand <= @probability
25
+ end
26
+ return inputs
27
+ end
28
+
29
+ end
@@ -0,0 +1,16 @@
1
+ # Three parent crossover takes three individuals as parents, A, B and C, and
2
+ # combines them to form a single child D. At each point in the chromosome, the
3
+ # values of A and B are compared; if they are the same then that value is used,
4
+ # otherwise the value at C is used.
5
+ class Wallace::Operators::ThreeParentCrossoverOperator < Wallace::Operator
6
+
7
+ name = :three_parent_crossover
8
+
9
+ def operate(rng, inputs)
10
+ (0...inputs[0].length).each do |i|
11
+ inputs[0][i] = inputs[2][i] if inputs[0][i] != inputs[1][i]
12
+ end
13
+ return [inputs[0]]
14
+ end
15
+
16
+ end
@@ -0,0 +1,31 @@
1
+ # Two-point crossover takes two individuals, A and B, as parents, and produces two children by
2
+ # swapping the substrings between loci, X and Y.
3
+ #
4
+ # Two crossover points, X and Y, are chosen such that X and Y both lie within the bounds of A and B
5
+ # and that X < Y. The substrings A[X...Y] and B[X...Y] are swapped to produce two (potentially) new
6
+ # children strings.
7
+ class Wallace::Operators::TwoPointCrossoverOperator < Wallace::Operator
8
+
9
+ name = :two_point_crossover
10
+
11
+ def operate(rng, inputs)
12
+
13
+ # Ensure that the individuals are longer than length 2!
14
+ return inputs if inputs[0].length <= 2 or inputs[1].length <= 2
15
+
16
+ # Calculate the crossover points X and Y then swap the substrings
17
+ # between A and B at those loci.
18
+ x = rng.rand((1...([inputs[0].length, inputs[1].length].min-1)))
19
+ y = rng.rand((x...([inputs[0].length, inputs[1].length].min)))
20
+
21
+ # Unfortunately []= can not be performed in parallel so we must
22
+ # find and store a substring before swapping them.
23
+ t = inputs[0][x...y]
24
+ inputs[0][x...y] = inputs[1][x...y]
25
+ inputs[1][x...y] = t
26
+
27
+ return inputs
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,18 @@
1
+ # The Twors Mutation operator takes an individual and swaps the values
2
+ # of two genes selected at random.
3
+ #
4
+ # Found in:
5
+ # => Analyzing the Performance of Mutation Operators to Solve the Travelling Salesman Problem
6
+ # => Otman ABDOUN, Jaafar ABOUCHABAKA, Chakir TAJANI
7
+
8
+ class Wallace::Operators::TworsMutationOperator < Wallace::Operator
9
+
10
+ name = :twors_mutation
11
+
12
+ def operate(rng, inputs)
13
+ i, j = (0...inputs[0].length).to_a.sample(2)
14
+ inputs[0][i], inputs[0][j] = inputs[0][j], inputs[0][i]
15
+ return inputs
16
+ end
17
+
18
+ end
@@ -0,0 +1,30 @@
1
+ # Uniform crossover takes two individuals as parents, A and B, and produces two children, C and D,
2
+ # by swapping genes according to the mixing ratio probability. Using a mixing ratio of 0.5 should
3
+ # result in individuals which share half of their genes with a given parent.
4
+ class Wallace::Operators::UniformCrossoverOperator < Wallace::Operator
5
+
6
+ name = :uniform_crossover
7
+
8
+ # Allow the mixing ratio to be dynamically adjusted.
9
+ attr_accessor :ratio
10
+
11
+ # Constructs a new uniform crossover operator.
12
+ #
13
+ # *Parameters:*
14
+ # * opts, a hash of keyword options for this method.
15
+ # -> id, the unique identifier for this operator.
16
+ # -> inputs, an array of inputs (OperatorInput) to this operator.
17
+ # -> ratio, the mixing ratio to use during crossover. (default = 0.5).
18
+ def initialize(opts = {})
19
+ super(opts)
20
+ @ratio = opts[:ratio] || 0.5
21
+ end
22
+
23
+ def operate(rng, inputs)
24
+ (0...inputs[0].length).each do |i|
25
+ inputs[0][i], inputs[1][i] = inputs[1][i], inputs[0][i] if rng.rand <= @ratio
26
+ end
27
+ return inputs
28
+ end
29
+
30
+ end
@@ -0,0 +1,31 @@
1
+ class Wallace::Operators::UniformMutationOperator < Wallace::Operator
2
+
3
+ name = :uniform_mutation
4
+
5
+ # Allow the mutation probability to be dynamically adjusted.
6
+ attr_accessor :probability
7
+
8
+ # Constructs a new uniform mutation operator.
9
+ #
10
+ # *Parameters:*
11
+ # * opts, a hash of keyword options for this method.
12
+ # -> id, the unique identifier for this operator.
13
+ # -> inputs, an array of inputs (OperatorInput) to this operator.
14
+ # -> values, the range of values that this mutator should sample from (can be biased by using a weighted array).
15
+ # -> probability, the probability that a given bit should be mutated. (default = 0.01).
16
+ def initialize(opts = {})
17
+ super(opts)
18
+ @values = opts[:values]
19
+ @probability = opts[:probability] || 0.01
20
+ end
21
+
22
+ def operate(rng, inputs)
23
+ (0...inputs[0].length).each do |i|
24
+ if rng.rand <= @probability
25
+ inputs[0][i] = @values.sample(random: rng)
26
+ end
27
+ end
28
+ return inputs
29
+ end
30
+
31
+ end
@@ -0,0 +1,80 @@
1
+ # The variable one point crossover operator was designed to address the problem of bloat
2
+ # resulting from the crossover on variable length chromosomes using the splice crossover
3
+ # operator.
4
+ #
5
+ # This operator takes two chromosomes as its input, X and Y, and slices each into two
6
+ # subsequences, A and B, and C and D, respectively. The first subsequence of X (A) is
7
+ # combined with the last subsequence of Y (D) to form one of the child chromosomes.
8
+ # The second child is formed by combining the first subsequence of Y (C) with the last
9
+ # subsequence of X (B).
10
+ #
11
+ # The subsequences are chosen at random from the set of legal sub-sequences. More
12
+ # details on the process are given in "operate".
13
+ #
14
+ # The key feature of this operator is that it guarantees that the length of the child
15
+ # chromosomes will fall within some given bounds. This feature helps to control the
16
+ # problem of bloat in problems using variable length chromosomes and can significantly
17
+ # improve performance in certain cases.
18
+ #
19
+ # *WARNING:* Operates on the assumption that the input individuals obey the length constraints.
20
+ # *WARNING:* The length of each input chromosome must be at least 2, failing this the original
21
+ # inputs are simply returned.
22
+ #
23
+ # *Author:* Chris Timperley
24
+ class Wallace::Operators::VariableOnePointCrossoverOperator < Wallace::Operator
25
+
26
+ attr_accessor :bounds
27
+
28
+ # Constructs a new variable one point crossover operator.
29
+ #
30
+ # *Parameters:*
31
+ # * opts, a hash of keyword options for this constructor.
32
+ # -> id, the unique identifier for this operator.
33
+ # -> bounds, the bounds upon the length of child chromosomes produced by this operator.
34
+ def initialize(opts = {})
35
+ super(opts)
36
+ @bounds = opts[:bounds]
37
+ end
38
+
39
+ # Produces two child chromosomes using two parent chromosomes according to the
40
+ # rules of this genetic operator.
41
+ #
42
+ # *Parameters:*
43
+ # * rng, the RNG to use to select the crossover point.
44
+ # * parents, the parent chromosomes used in the operation.
45
+ #
46
+ # *Returns:*
47
+ # An array containing two child chromosomes formed using data from the parent chromosomes.
48
+ def operate(rng, parents)
49
+
50
+ # Calculate the length of each parent.
51
+ x, y = parents[0].length, parents[1].length
52
+
53
+ # If either parent chromosome is shorter than length 2 then return them as the child
54
+ # chromosomes (else the operation will fail).
55
+ return parents if x < 2 or y < 2
56
+
57
+ # Pseudo-randomly select the first sub-sequence of X (A) such that
58
+ # 1 <= |A| < |X|. Let B be the remaining part of X.
59
+ a = rng.rand(1...x)
60
+ b = x - a
61
+
62
+ # Using the chosen A, find all the possible values of |D| such that
63
+ # MIN <= |A| + |D| <= MAX. Restrict the set of possible values for |D|
64
+ # to those which produce a legal |C| (i.e. MIN <= |B| + |C| <= MAX)
65
+ # before sampling a value uniformly at random.
66
+ d = ([1, @bounds.min - a].max..[y, @bounds.max - a].min).to_a.select{ |v|
67
+ @bounds.cover?(b + y - v)
68
+ }.sample(random: rng)
69
+ c = y - d
70
+
71
+ # Gather and combine the subsequences into the child chromosomes using
72
+ # their calculated lengths.
73
+ return [
74
+ parents[0].first(a) + parents[1].last(d),
75
+ parents[1].first(c) + parents[0].last(b)
76
+ ]
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,85 @@
1
+ module Enumerable
2
+
3
+ # Returns an array of all consecutive k-tuples in the collection.
4
+ #
5
+ # *Parameters:*
6
+ # * k, the size of the tuples.
7
+ def cons(k)
8
+ k_tuples = []
9
+ each_cons(k) { |tuple| k_tuples << tuple }
10
+ return k_tuples
11
+ end
12
+
13
+ # Checks whether this collection does not contain a given value V.
14
+ #
15
+ # *Parameters:*
16
+ # * v, the value to check for.
17
+ #
18
+ # *Returns:*
19
+ # true if not contained, false if contained.
20
+ def exclude?(v)
21
+ not include?(v)
22
+ end
23
+
24
+ # Calculates and returns the N highest values from this collection.
25
+ #
26
+ # *Parameters:*
27
+ # * n, number of values to return.
28
+ def nmax(n)
29
+ result = [] # lowest to highest
30
+ each do |v|
31
+ result = result.sorted_insert!(v).last(n) if result.length < n or v > result.first
32
+ end
33
+ return result
34
+ end
35
+
36
+ # Calculates and returns the N lowest values from this collection.
37
+ #
38
+ # *Parameters:*
39
+ # * n, number of values to return.
40
+ def nmin(n)
41
+ result = [] # lowest to highest
42
+ each do |v|
43
+ result = result.sorted_insert!(v).last(n) if result.length < n or v < result.first
44
+ end
45
+ return result
46
+ end
47
+
48
+ # Inserts a given value into this collection, assuming that the
49
+ # collection is sorted.
50
+ #
51
+ # *Parameters:*
52
+ # * value, the value to insert into the collection.
53
+ #
54
+ # *Returns:*
55
+ # The resulting collection.
56
+ def sorted_insert!(value)
57
+ index = calculate_sorted_index(value)
58
+ insert(index, value)
59
+ return self
60
+ end
61
+
62
+ private
63
+
64
+ # Calculates the index of a given value within this sorted collection.
65
+ #
66
+ # *Parameter:*
67
+ # * value, the value to calculate the index of.
68
+ #
69
+ # *Returns:*
70
+ # * the index of that value within this collection.
71
+ #
72
+ # *TODO:*
73
+ # * This is a rubbish O(n) implementation!
74
+ # * A RB-binary tree or a better insertion sort method should
75
+ # be implemented.
76
+ def calculate_sorted_index(value)
77
+ i = 0
78
+ each do |v|
79
+ break if v > value
80
+ i += 1
81
+ end
82
+ return i
83
+ end
84
+
85
+ end
@@ -0,0 +1,5 @@
1
+ # This directory contains monkey patches that extend functionality
2
+ # within the built-in Ruby classes and modules.
3
+
4
+ require_relative 'enumerable.rb'
5
+ require_relative 'range.rb'
@@ -0,0 +1,13 @@
1
+ class Range
2
+
3
+ # Samples a value from within this range.
4
+ #
5
+ # *Parameters:*
6
+ # * opts, keyword options for this method.
7
+ # * => random, the RNG to use to sample from this range.
8
+ def sample(opts = {})
9
+ random = opts[:random] || Random.new
10
+ random.rand(self)
11
+ end
12
+
13
+ end
@@ -0,0 +1,5 @@
1
+ module Wallace::Selectors; end
2
+
3
+ require_relative 'random_selector.rb'
4
+ require_relative 'roulette_selector.rb'
5
+ require_relative 'tournament_selector.rb'
@@ -0,0 +1,14 @@
1
+ class Wallace::Selectors::RandomSelector < Wallace::Selector
2
+
3
+ name = :random
4
+
5
+ # Selects an individual at random from the list of candidates.
6
+ #
7
+ # *Arguments*
8
+ # * rng, random number generator to use to select index of individual.
9
+ # * candidates, the list of candidates to select from.
10
+ def produce(rng, candidates)
11
+ candidates.sample(random: rng)
12
+ end
13
+
14
+ end
@@ -0,0 +1,23 @@
1
+ # NOT READY!
2
+ class Wallace::Selectors::RouletteSelector < Wallace::Selector
3
+
4
+ name = :roulette
5
+
6
+ # Precomputes the probability distribution prior to selection.
7
+ def prepare(rng, candidates)
8
+ sum_fitness = candidates.reduce(0) {|sum, ind| sum += 1.0 / ind.fitness.value.to_f }
9
+ distribution = Array.new(candidates.length) do |i|
10
+ (1.0 / candidates[i].fitness.value.to_f) / sum_fitness
11
+ end
12
+ @candidates = Wallace::Utility::ScaledArray.new(candidates, distribution)
13
+ end
14
+
15
+ # Selects an individual at random from the list of candidates.
16
+ #
17
+ # *Arguments*
18
+ # * rng, random number generator to use to select index of individual.
19
+ def select(rng)
20
+ return @candidates.sample(random: rng)
21
+ end
22
+
23
+ end
@@ -0,0 +1,36 @@
1
+ class Wallace::Selectors::TournamentSelector < Wallace::Selector
2
+
3
+ name = :tournament
4
+
5
+ # Constructs a new tournament selector.
6
+ #
7
+ # *Arguments*
8
+ # * opts, a hash of keyword options for this method.
9
+ # -> size, the size of the tournament. (default = 3).
10
+ # -> pick_best, flag indicating if the best individual in the tournament should win.
11
+ def initialize(opts = {})
12
+ @size = opts[:size] || 3
13
+ pick_best = opts[:pick_best] || true
14
+ @winning_condition = pick_best ? -1 : 1
15
+ raise ArgumentError, "Tournament size must be greater than 1." if @size < 2
16
+ end
17
+
18
+ # Random samples a number (equal to the tournament size) of individuals
19
+ # from the list of candidates, and returns the best or worst (depending on
20
+ # the selector parameters).
21
+ #
22
+ # *Arguments*
23
+ # * rng, random number generator to use to select index of individual.
24
+ #
25
+ # *Parameters:*
26
+ # * rng, random number generator to use to select index of individual.
27
+ # * candidates, the list of candidates for inclusion within the tournament.
28
+ #
29
+ # *Returns:*
30
+ # The winning individual of the tournament (whether best or worst).
31
+ def produce(rng, candidates)
32
+ tournament = candidates.sample(@size, random: rng)
33
+ return @winning_condition ? tournament.min : tournament.max
34
+ end
35
+
36
+ end
@@ -0,0 +1,40 @@
1
+ # Array-based species model their members as arrays of set values.
2
+ class Wallace::Species::ArraySpecies < Wallace::Species
3
+
4
+ name = :array
5
+
6
+ # Constructs a new array-based species.
7
+ #
8
+ # *Parameters:*
9
+ # * opts, hash of keyword options used by this method.
10
+ # -> id, the unique identifier for this species.
11
+ # -> length, the length constraints imposed on individuals of this species.
12
+ # -> values, the possible values of array elements (can be either a range or an array).
13
+ def initialize(opts = {})
14
+ super(opts)
15
+ @length = opts[:length]
16
+ @values = opts[:values]
17
+ end
18
+
19
+ # Check that a given individual meets the length constraints of the species.
20
+ def valid?(individual)
21
+ return false unless super(individual)
22
+ return true if @length.nil?
23
+ return false if @length.is_a? Integer and individual.data.length != @length
24
+ return false if @length.is_a? Range and (not @length.include? individual.data.length)
25
+ end
26
+
27
+ # Creates a new array-based individual using the set of values and length
28
+ # constraints of the species.
29
+ #
30
+ # *Parameters:*
31
+ # * opts, hash of keyword options used by this method.
32
+ # -> random, the RNG to use when spawning individuals.
33
+ def spawn(opts = {})
34
+ opts[:random] ||= Random.new
35
+ size = @length.is_a?(Range) ? opts[:random].rand(@length) : @length
36
+ data = Array.new(size) { @values.sample(opts) }
37
+ return Wallace::Individual.new(self, data)
38
+ end
39
+
40
+ end