mtortonesi/ruby-mhl

View on GitHub
lib/mhl/bitstring_genotype_space.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'bitstring'

module MHL

  # This class implements a genotype with bitstring representation
  class BitstringGenotypeSpace
    def initialize(opts)
      @bitstring_length = opts[:bitstring_length].to_i
      @mutation_threshold = opts[:mutation_threshold] || 0.5
      @recombination_threshold = opts[:recombination_threshold] || 0.5
      unless @bitstring_length and @bitstring_length > 0
        raise ArgumentError, 'Must have positive integer bitstring_length'
      end

      @random_func = opts[:random_func] || default_random_func(opts[:random_one_to_zero_ratio] || 1.0)
    end

    def get_random
      @random_func.call
    end

    # reproduction with bitflip mutation and one-point crossover
    def reproduce_from(p1, p2, mutation_rv, recombination_rv)
      # make copies of p1 and p2
      # (we're only interested in the :genotype key)
      c1 = { :genotype => p1[:genotype].dup }
      c2 = { :genotype => p2[:genotype].dup }

      # mutation comes first
      bitflip_mutation(c1[:genotype], mutation_rv)
      bitflip_mutation(c2[:genotype], mutation_rv)

      # and then recombination
      c1[:genotype], c2[:genotype] =
        onepoint_crossover(c1[:genotype], c2[:genotype], recombination_rv)

      return c1, c2
    end

    private

      def bitflip_mutation(bitstring, mutation_rv)
        # TODO: disable this check in non-debugging mode
        unless bitstring.length == @bitstring_length
          raise 'Error! Different bit string sizes!'
        end

        @bitstring_length.times do |i|
          if mutation_rv.next < @mutation_threshold
            bitval       = bitstring[i]
            bitstring[i] = (bitval == 1 ? '0' : '1')
          end
        end
      end

      def onepoint_crossover(bitstring1, bitstring2, recombination_rv)
        # TODO: disable this check in non-debugging mode
        unless bitstring1.length == @bitstring_length and bitstring2.length == @bitstring_length
          raise 'Error! Different bit string sizes!'
        end

        if recombination_rv.next < @recombination_threshold
          size     = bitstring1.length
          point    = 1 + rand(size - 2)
          hi_mask  = bitstring1.mask(point, BitString::LOW_END) # lowest point bits
          low_mask = bitstring1.mask(size - point, BitString::HIGH_END) # highest size-point bits
          new_b1   = (bitstring1 & hi_mask) | (bitstring2 & low_mask)
          new_b2   = (bitstring2 & hi_mask) | (bitstring1 & low_mask)
          return new_b1, new_b2
        end
        return bitstring1, bitstring2
      end

      def default_random_func(one_to_zero_ratio)
        random_percentage_of_ones = one_to_zero_ratio / (1.0 + one_to_zero_ratio)
        lambda do
          str = (0...@bitstring_length).inject("") do |s,i|
            s << ((rand < random_percentage_of_ones) ? '1' : '0')
          end
          BitString.new(str, @bitstring_length)
        end
      end

  end

end