Genetic Algorithm
8
Genetic Algorithm implementation.
See also "Weighted random selection" for RandomItemSelector class source.
See also "Weighted random selection" for RandomItemSelector class source.
class Individual
attr_reader :dna, :fitness
def initialize(dna)
@dna = dna
@fitness = nil
end
def fit(fitnessFunc)
@fitness = fitnessFunc.call(dna)
end
def produce(secondParent, dnaCombinator)
Individual.new(dnaCombinator.call(@dna, secondParent.dna))
end
def to_s
"[#{@dna}: #{@fitness}]"
end
end
class Generation
def initialize(size, maxSurvivors, minFitness, fitnessFunc, dnaCombinator)
@size = size
@maxSurvivors = maxSurvivors
@minFitness = minFitness
@fitnessFunc = fitnessFunc
@dnaCombinator = dnaCombinator
@individuals = []
@survivors = nil
end
def add(individual)
raise "Generation overflow" unless has_more_space
ensure_fitness_defined individual
@individuals.push individual
end
def has_more_space
return @individuals.length + 1 < @size
end
def next
nextGeneration = Generation.new(@size, @maxSurvivors, @minFitness, @fitnessFunc, @dnaCombinator)
individualsLeft = @maxSurvivors
survivors.each { |individual| nextGeneration.add individual; break if (individualsLeft -= 1) <= 0 }
bestParents = RandomItemSelector.new(survivors, proc { |individual| individual.fitness })
while nextGeneration.has_more_space
firstParent = bestParents.next
secondParent = survivors[rand(survivors.length)]
child = firstParent.produce(secondParent, @dnaCombinator)
nextGeneration.add child
end
return nextGeneration
end
def survivors
if @survivors.nil?
@survivors = []
@individuals.each { |individual|
ensure_fitness_defined individual
@survivors.push individual if individual.fitness >= @minFitness
}
raise "No more survivors" if @survivors.length == 0
@survivors = best_first @survivors
end
return @survivors
end
def best
@individuals.max { |a,b| a.fitness <=> b.fitness }
end
def ensure_fitness_defined(individual)
individual.fit(@fitnessFunc) if individual.fitness.nil?
end
def best_first(items)
items.sort { |a,b| b.fitness <=> a.fitness }
end
def to_s
temp = "Generation(#{best.fitness}):"
samplesLeft = 10
sorted = best_first @individuals
sorted.each { |item| temp += " " + item.to_s; break if (samplesLeft -= 1) <= 0 }
return temp
end
end
class Universe
def initialize(firstGeneration)
@generation = firstGeneration
end
def next_generation
@generation = @generation.next
puts @generation
end
def run(count)
for i in 1..count
next_generation
end
end
def best
@generation.best
end
end
attr_reader :dna, :fitness
def initialize(dna)
@dna = dna
@fitness = nil
end
def fit(fitnessFunc)
@fitness = fitnessFunc.call(dna)
end
def produce(secondParent, dnaCombinator)
Individual.new(dnaCombinator.call(@dna, secondParent.dna))
end
def to_s
"[#{@dna}: #{@fitness}]"
end
end
class Generation
def initialize(size, maxSurvivors, minFitness, fitnessFunc, dnaCombinator)
@size = size
@maxSurvivors = maxSurvivors
@minFitness = minFitness
@fitnessFunc = fitnessFunc
@dnaCombinator = dnaCombinator
@individuals = []
@survivors = nil
end
def add(individual)
raise "Generation overflow" unless has_more_space
ensure_fitness_defined individual
@individuals.push individual
end
def has_more_space
return @individuals.length + 1 < @size
end
def next
nextGeneration = Generation.new(@size, @maxSurvivors, @minFitness, @fitnessFunc, @dnaCombinator)
individualsLeft = @maxSurvivors
survivors.each { |individual| nextGeneration.add individual; break if (individualsLeft -= 1) <= 0 }
bestParents = RandomItemSelector.new(survivors, proc { |individual| individual.fitness })
while nextGeneration.has_more_space
firstParent = bestParents.next
secondParent = survivors[rand(survivors.length)]
child = firstParent.produce(secondParent, @dnaCombinator)
nextGeneration.add child
end
return nextGeneration
end
def survivors
if @survivors.nil?
@survivors = []
@individuals.each { |individual|
ensure_fitness_defined individual
@survivors.push individual if individual.fitness >= @minFitness
}
raise "No more survivors" if @survivors.length == 0
@survivors = best_first @survivors
end
return @survivors
end
def best
@individuals.max { |a,b| a.fitness <=> b.fitness }
end
def ensure_fitness_defined(individual)
individual.fit(@fitnessFunc) if individual.fitness.nil?
end
def best_first(items)
items.sort { |a,b| b.fitness <=> a.fitness }
end
def to_s
temp = "Generation(#{best.fitness}):"
samplesLeft = 10
sorted = best_first @individuals
sorted.each { |item| temp += " " + item.to_s; break if (samplesLeft -= 1) <= 0 }
return temp
end
end
class Universe
def initialize(firstGeneration)
@generation = firstGeneration
end
def next_generation
@generation = @generation.next
puts @generation
end
def run(count)
for i in 1..count
next_generation
end
end
def best
@generation.best
end
end
def combine_random_bits(dna1, dna2, len)
mask = rand(1 << len)
return (dna1 & mask) | (dna2 & ~mask)
end
def mutate_random_bits(dna, len)
mask = 1 << rand(len)
return dna ^ mask
end
mask = rand(1 << len)
return (dna1 & mask) | (dna2 & ~mask)
end
def mutate_random_bits(dna, len)
mask = 1 << rand(len)
return dna ^ mask
end
seed = Generation.new(100, 70, 0.1,
proc { |dna|
1 - (dna - 80).abs / 256.0
},
proc { |dna1, dna2|
len = 8
dna = combine_random_bits(dna1, dna2, len)
dna = mutate_random_bits(dna, len) if rand(10) == 1
return dna
})
while seed.has_more_space
seed.add Individual.new(rand(256))
end
universe = Universe.new(seed)
universe.run 100
puts "Best indivdual: #{universe.best}"
proc { |dna|
1 - (dna - 80).abs / 256.0
},
proc { |dna1, dna2|
len = 8
dna = combine_random_bits(dna1, dna2, len)
dna = mutate_random_bits(dna, len) if rand(10) == 1
return dna
})
while seed.has_more_space
seed.add Individual.new(rand(256))
end
universe = Universe.new(seed)
universe.run 100
puts "Best indivdual: #{universe.best}"
Comments
Thu. Nov. 2nd, 2006 5:09 AM
SCoon
SCoon





