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,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