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,39 @@
1
+ # The Gaussian mutation operator mutates the genes of an individual with a given
2
+ # probability (mutation rate)
3
+ class Wallace::Operators::GaussianMutationOperator < Wallace::Operator
4
+
5
+ name = :gaussian_mutation
6
+
7
+ # Allow the mutation probability to be dynamically adjusted.
8
+ attr_accessor :probability
9
+
10
+ # Allow the distribution to be dynamically adjusted.
11
+ attr_accessor :distribution
12
+
13
+ # Constructs a new gaussian mutation operator.
14
+ #
15
+ # *Parameters:*
16
+ # * opts, a hash of keyword options for this method.
17
+ # -> id, the unique identifier for this operator.
18
+ # -> inputs, an array of inputs (OperatorInput) to this operator.
19
+ # -> probability, the probability that a given bit should be mutated. (default = 0.01).
20
+ # -> distribution, the gaussian distribution which should be used.
21
+ # -> bounds, the bounds for all genes in the chromosome.
22
+ def initialize(opts = {})
23
+ super(opts)
24
+ @probability = opts[:probability] || 0.01
25
+ @distribution = opts[:distribution]
26
+ @bounds = opts[:bounds]
27
+ end
28
+
29
+ def operate(rng, inputs)
30
+ (0...inputs[0].length).each do |i|
31
+ if rng.rand <= @probability
32
+ inputs[0][i] += @distribution.sample(random: rng)
33
+ inputs[0][i] = [@bounds.first, inputs[0][i], @bounds.last].sort[1] unless @bounds.nil?
34
+ end
35
+ end
36
+ return inputs
37
+ end
38
+
39
+ end
@@ -0,0 +1,24 @@
1
+ # Half uniform crossover takes two individuals as parents, A and B, and produces two children, C and D,
2
+ # by swapping exactly half of their non-matching bits.
3
+ class Wallace::Operators::HalfUniformCrossoverOperator < Wallace::Operator
4
+
5
+ name = :half_uniform_crossover
6
+
7
+ def operate(rng, inputs)
8
+
9
+ # Find all indices where bits do not match between A and B.
10
+ indices = []
11
+ (0...inputs[0].length).each do |i|
12
+ indices << i if inputs[0][i] != inputs[1][i]
13
+ end
14
+
15
+ # Swap exactly half of the non-matching bits.
16
+ indices.shuffle(random: rng).first((indices.length/2).to_i).each do |i|
17
+ inputs[0][i], inputs[1][i] = inputs[1][i], inputs[0][i]
18
+ end
19
+
20
+ return inputs
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,26 @@
1
+ module Wallace::Operators; end
2
+
3
+ require_relative 'boundary_mutation_operation.rb'
4
+ require_relative 'uniform_mutation_operation.rb'
5
+ require_relative 'point_mutation_operation.rb'
6
+ require_relative 'bit_flip_mutation_operation.rb'
7
+ require_relative 'twors_mutation_operation.rb'
8
+ require_relative 'reverse_sequence_mutation_operation.rb'
9
+ require_relative 'gaussian_mutation_operation.rb'
10
+ require_relative 'shuffle_mutation_operation.rb'
11
+ require_relative 'swap_mutation_operation.rb'
12
+
13
+ require_relative 'one_point_crossover_operation.rb'
14
+ require_relative 'two_point_crossover_operation.rb'
15
+ require_relative 'uniform_crossover_operation.rb'
16
+ require_relative 'half_uniform_crossover_operation.rb'
17
+ require_relative 'three_parent_crossover_operation.rb'
18
+ require_relative 'splice_crossover_operation.rb'
19
+ require_relative 'variable_one_point_crossover_operation.rb'
20
+
21
+ require_relative 'cycle_crossover_operation.rb'
22
+ require_relative 'merging_crossover_operation.rb'
23
+ require_relative 'partially_mapped_crossover_operation.rb'
24
+ require_relative 'order_crossover_operation.rb'
25
+ require_relative 'position_crossover_operation.rb'
26
+ require_relative 'subtour_exchange_crossover_operation.rb'
@@ -0,0 +1,27 @@
1
+ # Merging crossover takes two parents as input and randomly merges their chromosomes into
2
+ # a dual-length chromosome, randomly choosing between each parent when adding each gene.
3
+ #
4
+ # The merged chromosome is then split into two child chromosomes by using the first occurrence
5
+ # of each allele as the order of genes in the first child, and the second occurence as the
6
+ # order of genes in the second child.
7
+ class Wallace::Operators::MergingCrossoverOperator < Wallace::Operator
8
+
9
+ name = :merging_crossover
10
+
11
+ def operate(rng, inputs)
12
+
13
+ # Rather than progressively building up the merged chromosome, we can quickly
14
+ # add the two parent chromosomes together and shuffle the resulting sequence.
15
+ merged_chromosome = (inputs[0] + inputs[1]).shuffle(random: rng)
16
+
17
+ # Split the merged chromosome into two child chromosomes.
18
+ # We check whether it is the first occurence of a given allele by seeing if the
19
+ # allele exists within the first child's chromosome.
20
+ child_chromosomes = [ [], [] ]
21
+ merged_chromosome.each { |g| child_chromosomes[(child_chromosomes[0].include? g) ? 1 : 0] << g }
22
+
23
+ return child_chromosomes
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,29 @@
1
+ # One-point crossover (also known as single-point crossover) takes two individuals, A and B, as parents,
2
+ # and produces two children, formed by the combination of sub-strings from A and B.
3
+ #
4
+ # Firstly a single crossover point, common to the two individuals is selected, effectively splitting each
5
+ # parent, A and B, into two substrings (A1, A2; B1, B2). The resulting substrings (A1+B2 and B1+A2) are
6
+ # combined to form two (potentially) unique children.
7
+ class Wallace::Operators::OnePointCrossoverOperator < Wallace::Operator
8
+
9
+ name = :one_point_crossover
10
+
11
+ def operate(rng, inputs)
12
+
13
+ a, b = individuals
14
+
15
+ # Ensure that the individuals are longer than length 1!
16
+ return inputs if inputs[0].length <= 1 or inputs[1].length <= 1
17
+
18
+ # Calculate the crossover point, split A and B into four substrings
19
+ # and combine those substrings to form two children.
20
+ x = rng.rand((1...([inputs[0].length, inputs[1].length].min)))
21
+
22
+ return [
23
+ inputs[0][0...x] + inputs[0][x...inputs[0].length],
24
+ inputs[1][0...x] + inputs[1][x...inputs[1].length]
25
+ ]
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,38 @@
1
+ # The order crossover operator takes two parents as input and creates two offspring.
2
+ #
3
+ # Two random crossover points on the chromosome (the same for each parent) are selected
4
+ # and the genes at all loci in between are carried to each child (i.e. P0 -> C0, P1 -> C1).
5
+ #
6
+ # The remaining genes of each child are determined by inserting all genes that are not covered
7
+ # within their central subsequence (copied from the parent) in the order that they appear in the
8
+ # other parent. Once the order of genes has been calculated, the genes are inserted after the
9
+ # second crossover point and are wrapped around the start and to the first crossover point.
10
+ class Wallace::Operators::OrderCrossoverOperator < Wallace::Operator
11
+
12
+ name = :order_crossover
13
+
14
+ def operate(rng, inputs)
15
+
16
+ # Select two random crossover points.
17
+ # All genes between these points are left untouched.
18
+ len = inputs[0].length
19
+ i, j = Array.new(2) { rng.rand(len - 1) }.sort
20
+
21
+ # Find the alleles that are not within the selected subsequence of each
22
+ # parent in the order of the other parents chromosome.
23
+ insert = Array.new(2) do |input|
24
+ inputs[(input+1)%2].select { |g| inputs[input][i..j].exclude? g }
25
+ end
26
+
27
+ # Inject the alleles in the gathered order starting at the second
28
+ # crossover point before wrapping around to the first.
29
+ (0..1).each do |input|
30
+ inputs[input][j+1...len] = insert[input].shift(len-j-1)
31
+ inputs[input][0...i] = insert[input]
32
+ end
33
+
34
+ return inputs
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,44 @@
1
+ # The partially mapped crossover swaps a single genetic subsequence (at the same position) between
2
+ # two parents before fixing any resulting collisions by calculating and applying partial mappings
3
+ # on damaged genes.
4
+ class Wallace::Operators::PartiallyMappedCrossoverOperator < Wallace::Operator
5
+
6
+ name = :partially_mapped_crossover
7
+
8
+ def operate(rng, inputs)
9
+
10
+ # Select two random crossover points.
11
+ len = inputs[0].length
12
+ i, j = Array.new(2) { rng.rand(len - 1) }.sort
13
+
14
+ # Exchange the gene sequence of the two parents between these points
15
+ # to form two proto-children (which may break the chromosome).
16
+ inputs[0][i..j], inputs[1][i..j] = inputs[1][i..j], inputs[0][i..j]
17
+
18
+ # To fix the proto-children we map any alleles which occur outside of
19
+ # the exchanged sub-sequence with their counterpart in the partial map.
20
+ mapping = Hash[(inputs[0][i..j] + inputs[1][i..j]).map { |a| [a, []] }]
21
+ (i..j).each do |g|
22
+ mapping[inputs[0][g]] << inputs[1][g]
23
+ mapping[inputs[1][g]] << inputs[0][g]
24
+ end
25
+
26
+ # Fix all allele collisions via the map.
27
+ inputs.each do |input|
28
+ [(0...i), (j+1...len)].each do |sub|
29
+ sub.each do |g|
30
+ a = input[g]
31
+ last = nil
32
+ while input[i..j].include? a
33
+ input[g] = (last.nil? or (mapping[a][0] != last)) ? mapping[a][0] : mapping[a][1]
34
+ a, last = input[g], a
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ return inputs
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,31 @@
1
+ # Point mutation selects a prespecified number of points of a provided chromosome at
2
+ # random and replaces them with a value sampled from the provided distribution.
3
+ class Wallace::Operators::PointMutationOperator < Wallace::Operator
4
+
5
+ name = :point_mutation
6
+
7
+ # Allow the number of points to be dynamically adjusted.
8
+ attr_accessor :points
9
+
10
+ # Constructs a new point 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
+ # -> values, the range of values that this mutator should sample from (can be biased by using a weighted array).
17
+ # -> points, the number of points which should be mutated. (default = 1).
18
+ def initialize(opts = {})
19
+ super(opts)
20
+ @values = opts[:values] # could gather this information from the species?
21
+ @points = opts[:points] || 1
22
+ end
23
+
24
+ def operate(rng, inputs)
25
+ (0...inputs[0].length).to_a.sample(@points).each do |i|
26
+ inputs[0][i] = @values.sample(random: rng)
27
+ end
28
+ return inputs
29
+ end
30
+
31
+ end
@@ -0,0 +1,50 @@
1
+ # The position crossover operator takes two parents as input and creates two offspring.
2
+ #
3
+ # A random number of loci are selected from the first parent and all genes at those loci
4
+ # are copied to the corresponding proto-children of each parent.
5
+ #
6
+ # The remaining genes are determined by finding the order of the remaining alleles
7
+ # (those not contained at the randomly selected loci) in the opposite parent and inserting
8
+ # them into the chromosome slots in that given order.
9
+ class Wallace::Operators::PositionCrossoverOperator < Wallace::Operator
10
+
11
+ name = :position_crossover
12
+
13
+ def operate(rng, inputs)
14
+
15
+ # Select a random number of loci to fix the alleles at.
16
+ len = inputs[0].length
17
+ loci = (0...len).to_a.sample(rng.rand(1...len), random: rng)
18
+
19
+ # Generate the proto-children by copying the alleles at
20
+ # the selected loci.
21
+ #
22
+ # Rather than having the alleles array we could just check the
23
+ # child arrays. This would be less code, but there may be a lot
24
+ # of null placeholder values so the look-up will take longer.
25
+ children = Array.new(2) { Array.new(len) { nil } }
26
+ alleles = [[],[]]
27
+ loci.each do |i|
28
+ alleles[0] << inputs[0][i]
29
+ alleles[1] << inputs[1][i]
30
+ children[0][i] = inputs[0][i]
31
+ children[1][i] = inputs[1][i]
32
+ end
33
+
34
+ # Determine the order of the remaining alleles from the
35
+ # opposite parent for each protochild.
36
+ alleles.each_index do |i|
37
+ alleles[i] = inputs[(i+1)%2].select { |a| alleles[i].exclude? a }
38
+ end
39
+
40
+ # Fill the remaining loci of each chromosome using the
41
+ # determined order.
42
+ children.each_index do |c|
43
+ children[c].map! { |g| g.nil? ? alleles[c].shift : g}
44
+ end
45
+
46
+ return children
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,13 @@
1
+ # The reverse sequence mutation operator takes an individual and reverses the order of
2
+ # all its genes between two loci i and j, where i < j.
3
+ class Wallace::Operators::ReverseSequenceMutationOperator < Wallace::Operator
4
+
5
+ name = :reverse_sequence_mutation
6
+
7
+ def operate(rng, inputs)
8
+ i, j = (0...inputs[0].length).to_a.sample(2).sort
9
+ inputs[0][i..j] = inputs[0][i..j].reverse
10
+ return inputs
11
+ end
12
+
13
+ end
@@ -0,0 +1,17 @@
1
+ # Shuffle mutation selects a random continuous series of genes in the chromosome
2
+ # and shuffles the order of gene values at random.
3
+ class Wallace::Operators::ShuffleMutationOperator < Wallace::Operator
4
+
5
+ name = :shuffle_mutation
6
+
7
+ def operate(rng, inputs)
8
+
9
+ # Shuffle the gene sequence between points i and j, where i < j.
10
+ i = rng.rand(inputs[0].length - 2)
11
+ j = rng.rand(i...inputs[0].length)
12
+ inputs[0][i..j] = inputs[0][i..j].shuffle(random: rng)
13
+ return inputs
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,42 @@
1
+ # The splice crossover operator takes two parents (which may have different length genomes), splits each
2
+ # into two substrings at random points (using a different point for each parent) before combining
3
+ # the four substrings into a pair of two substrings at random to produce two children.
4
+ class Wallace::Operators::SpliceCrossoverOperator < Wallace::Operator
5
+
6
+ name = :splice_crossover
7
+
8
+ def operate(rng, inputs)
9
+
10
+ # Do nothing if either string is shorter than length 2.
11
+ return inputs if inputs[0].length <= 1 or inputs[1].length <= 1
12
+
13
+ # Cut the two strings into four strings at a random
14
+ # point such that the recombination of any two strings will result in
15
+ # a legal string (a string whose length does not exceed the bounds).
16
+ x, y = rng.rand(1...inputs[0].length), rng.rand(1...inputs[1].length)
17
+ strings = [
18
+ inputs[0][0...x],
19
+ inputs[1][0...y],
20
+ inputs[0][x...inputs[0].length],
21
+ inputs[1][y...inputs[1].length]
22
+ ]
23
+
24
+ # given any A, B, C and D, it is given that:
25
+ # min <= #(A+B) <= max
26
+ # min <= #(C+D) <= max
27
+
28
+ # we want to find an A, B, C, D, such that:
29
+ # min <= #(A+D) <= max
30
+ # min <= #(C+B) <= max
31
+
32
+ # Randomly re-attach the four strings into two strings
33
+ # and return the resulting individuals.
34
+ strings.shuffle!(random: rng)
35
+ return [
36
+ strings.pop + strings.pop,
37
+ strings.pop + strings.pop
38
+ ]
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,54 @@
1
+ # INCREDIBLY SLOW! O(n^3)
2
+ class Wallace::Operators::SubtourExchangeCrossoverOperator < Wallace::Operator
3
+
4
+ name = :subtour_exchange_crossover
5
+
6
+ # The Subtour data structure is used to hold a given subtour along
7
+ # with its start and end position within the whole tour (i.e.
8
+ # the bounds of the subtour).
9
+ Subtour = Struct.new(:tour, :bounds)
10
+
11
+ def operate(rng, inputs)
12
+
13
+ # Calculate the set of all subtours within each parent
14
+ # chromosome that are greater than length 1 and shorter
15
+ # than the entire tour length.
16
+ len = inputs[0].length
17
+ subtours = (0..1).map do |input|
18
+ Hash[(2...len).map { |k|
19
+ [k, (0...len).cons(k).map { |bounds|
20
+ bounds = bounds.first..bounds.last
21
+ Subtour.new(inputs[input][bounds], bounds)
22
+ }]
23
+ }]
24
+ end
25
+
26
+ # Check each k-length subtour for the same vertices.
27
+ subtours = Hash[(2...len).map { |k|
28
+ [ k,
29
+ subtours[0][k].product(subtours[1][k]).select { |s1, s2|
30
+ s1.tour - s2.tour == s2.tour - s1.tour
31
+ }]
32
+ }]
33
+
34
+ # Reduce the set of sub-tours to a non-overlapping set.
35
+ # (i.e. there are no (k-1)-length tours contained within a k-length tour).
36
+ #
37
+ # Alternatively we could perform replacements in ascending order
38
+ # of subtour length. Simpler and gives the same result, but more replacements
39
+ # are performed; is there much of a performance gain from pre-processing the
40
+ # set of candidate subtours?
41
+ #
42
+ # For now we will stick to the latter for the sake of simplicity.
43
+ subtours.each_value do |k_subtours|
44
+ k_subtours.each do |s1, s2|
45
+ inputs[0][s1.bounds] = s2.tour
46
+ inputs[1][s2.bounds] = s1.tour
47
+ end
48
+ end
49
+
50
+ return inputs
51
+
52
+ end
53
+
54
+ end