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