Genetic Algorithm





8
Date Submitted Thu. Nov. 2nd, 2006 3:51 AM
Revision 1 of 1
Scripter SCoon
Tags Algorithm | Genetic | Random | Ruby
Comments 1 comments
Genetic Algorithm implementation.

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
 
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
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}"

Vladislav Zlobin

Comments

Comments More advanced example
Thu. Nov. 2nd, 2006 5:09 AM    Scripter SCoon

Voting