wallace 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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