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.
- data/Gemfile +14 -0
- data/README.md +12 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/bin/.gitkeep +0 -0
- data/lib/analysers/fitness_distribution_analyser.rb +28 -0
- data/lib/analysers/init.rb +1 -0
- data/lib/core/analyser.rb +63 -0
- data/lib/core/breeder.rb +68 -0
- data/lib/core/breeding_graph.rb +59 -0
- data/lib/core/breeding_graph/init.rb +3 -0
- data/lib/core/breeding_graph/input_node.rb +55 -0
- data/lib/core/breeding_graph/node.rb +68 -0
- data/lib/core/breeding_graph/node_input.rb +6 -0
- data/lib/core/evaluator.rb +52 -0
- data/lib/core/evolver.rb +47 -0
- data/lib/core/exceptions.rb +6 -0
- data/lib/core/experiment.rb +22 -0
- data/lib/core/fitness.rb +8 -0
- data/lib/core/fraction.rb +7 -0
- data/lib/core/individual.rb +65 -0
- data/lib/core/logger.rb +5 -0
- data/lib/core/migrator.rb +6 -0
- data/lib/core/operator.rb +54 -0
- data/lib/core/population.rb +56 -0
- data/lib/core/selector.rb +43 -0
- data/lib/core/species.rb +52 -0
- data/lib/core/state.rb +29 -0
- data/lib/core/subpopulation.rb +53 -0
- data/lib/core/termination.rb +39 -0
- data/lib/distributions/gaussian_distribution.rb +60 -0
- data/lib/distributions/init.rb +3 -0
- data/lib/fitness/init.rb +1 -0
- data/lib/fitness/raw_fitness.rb +30 -0
- data/lib/loggers/csv_logger.rb +20 -0
- data/lib/loggers/init.rb +1 -0
- data/lib/loggers/mongo_logger.rb +5 -0
- data/lib/loggers/sqlite_logger.rb +5 -0
- data/lib/modules/ge/backus_naur_form.rb +125 -0
- data/lib/modules/ge/grammar_derivation.rb +30 -0
- data/lib/modules/ge/grammar_species.rb +29 -0
- data/lib/modules/ge/init.rb +5 -0
- data/lib/modules/init.rb +2 -0
- data/lib/modules/koza/builder.rb +48 -0
- data/lib/modules/koza/builder/full_builder.rb +92 -0
- data/lib/modules/koza/builder/grow_builder.rb +103 -0
- data/lib/modules/koza/builder/half_builder.rb +70 -0
- data/lib/modules/koza/builder/init.rb +3 -0
- data/lib/modules/koza/ephemeral.rb +33 -0
- data/lib/modules/koza/init.rb +12 -0
- data/lib/modules/koza/koza_node.rb +108 -0
- data/lib/modules/koza/koza_node_value.rb +72 -0
- data/lib/modules/koza/koza_node_value_set.rb +51 -0
- data/lib/modules/koza/koza_species.rb +48 -0
- data/lib/modules/koza/koza_tree.rb +159 -0
- data/lib/modules/koza/operators/init.rb +4 -0
- data/lib/modules/koza/operators/subtree_crossover_operation.rb +43 -0
- data/lib/modules/koza/operators/subtree_mutation_operation.rb +32 -0
- data/lib/operators/bit_flip_mutation_operation.rb +29 -0
- data/lib/operators/boundary_mutation_operation.rb +28 -0
- data/lib/operators/cycle_crossover_operation.rb +77 -0
- data/lib/operators/gaussian_mutation_operation.rb +39 -0
- data/lib/operators/half_uniform_crossover_operation.rb +24 -0
- data/lib/operators/init.rb +26 -0
- data/lib/operators/merging_crossover_operation.rb +27 -0
- data/lib/operators/one_point_crossover_operation.rb +29 -0
- data/lib/operators/order_crossover_operation.rb +38 -0
- data/lib/operators/partially_mapped_crossover_operation.rb +44 -0
- data/lib/operators/point_mutation_operation.rb +31 -0
- data/lib/operators/position_crossover_operation.rb +50 -0
- data/lib/operators/reverse_sequence_mutation_operation.rb +13 -0
- data/lib/operators/shuffle_mutation_operation.rb +17 -0
- data/lib/operators/splice_crossover_operation.rb +42 -0
- data/lib/operators/subtour_exchange_crossover_operation.rb +54 -0
- data/lib/operators/swap_mutation_operation.rb +29 -0
- data/lib/operators/three_parent_crossover_operation.rb +16 -0
- data/lib/operators/two_point_crossover_operation.rb +31 -0
- data/lib/operators/twors_mutation_operation.rb +18 -0
- data/lib/operators/uniform_crossover_operation.rb +30 -0
- data/lib/operators/uniform_mutation_operation.rb +31 -0
- data/lib/operators/variable_one_point_crossover_operation.rb +80 -0
- data/lib/patches/enumerable.rb +85 -0
- data/lib/patches/init.rb +5 -0
- data/lib/patches/range.rb +13 -0
- data/lib/selectors/init.rb +5 -0
- data/lib/selectors/random_selector.rb +14 -0
- data/lib/selectors/roulette_selector.rb +23 -0
- data/lib/selectors/tournament_selector.rb +36 -0
- data/lib/species/array_species.rb +40 -0
- data/lib/species/bit_string_species.rb +18 -0
- data/lib/species/init.rb +4 -0
- data/lib/species/permutation_species.rb +22 -0
- data/lib/species/string_species.rb +29 -0
- data/lib/utility/init.rb +4 -0
- data/lib/utility/scaled_array.rb +88 -0
- data/lib/utility/sorted_array.rb +39 -0
- data/lib/wallace.rb +40 -0
- data/test/.gitkeep +0 -0
- 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
|