Genetic Algorithm Sample
Blind Watch Maker 1
Download
Find Words
forth
Fractal Benchmark
Genetic Algorithm
Gtk Source Editor
Hex Dump
Notepad
Point
Shapes
Simple English Parser
Sizes
TPK
Word Count
"""
Uses the genetic algorithm (GA) to solve various problems.

Run with -turbo for maximum speed as in:

    cobra -turbo GeneticAlgorithm.cobra

Links

    http://en.wikipedia.org/wiki/Genetic_algorithm
        http://en.wikipedia.org/wiki/Tournament_selection
        http://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)
        http://en.wikipedia.org/wiki/Mutation_(genetic_algorithm)
    http://en.wikipedia.org/wiki/Travelling_salesman_problem

Credit

    Written by Charles Esterbrook
    Based in part on http://code.activestate.com/recipes/199121/

TODO

    [ ] Record with each member the generation it was created in
    [ ] implement the travelling salesman problem (TSP)
    [ ] Solve a "minimize/maximize function" problem where bitstring is broken
        into numeric args
"""

@number float


class BaseObject
    """
    The base class of all other classes in this library/program.
    Provides basics like serial numbers, .random (shared) and .innerString.
    """

    shared
    
        var _nextSerialNum = 1_000

        var _random as Random?
        
        pro random as Random
            """
            Shared random number generator.
            """
            get
                if _random is nil, _random = Random()
                return _random to !
            set
                _random = value

    cue init
        base.init
        __serialNum = _nextSerialNum
        _nextSerialNum += 1
    
    get serialNum from __serialNum as int

    def toString as String is override
        """ Invoke .toString; override .innerString. """
        return '[.typeOf.name]([.serialNum][.innerString])'
    
    def innerString as String
        """
        Subclasses can override to:
            return base.innerString + ', foo=[value1], bar=[value2]'
        """
        return ''

    def dump
        print this


class Params inherits BaseObject
    """
    Contains the parameters for a GA run.
    """

    shared
    
        get default as Params
            p = Params()
            p.populationSize = 1_000
            p.tournamentSize = 5
            p.crossoverRate = 0.90
            p.mutationRate = 0.03
            p.maxGenerations = 100
            p.randomSeed = 0
            return p

    invariant
        .populationSize >= 4
        .tournamentSize >= 1
        .crossoverRate  >= 0 and .crossoverRate <= 1
        .mutationRate   >= 0 and .mutationRate <= 1
        .maxGenerations >= 0
        .scoreGoal      >  0
    
    cue init
        base.init
        _populationSize = 10
        _tournamentSize = 2
        _scoreGoal = 1.0

    pro populationSize from var as int
    
    pro tournamentSize from var as int
    
    pro crossoverRate from var as number
    
    pro mutationRate from var as number
    
    pro maxGenerations from var as int
    
    pro randomSeed from var as int
        """
        A .randomSeed of zero is ignored in which the stream of random numbers
        will be different on each run.
        """

    pro memberType from var as Type?
        """
        The type of the members of the population.
        Used when creating a new members for an initial population.
        """

    pro scoreGoal from var as number
        """
        The score you want to achieve among the population. Once reached,
        evolution will stop.
        """


class Population inherits BaseObject
    """
    Holds a population of members and provides population-level operations such
    as reporting the best and worst member.
    """

    var _members = List<of Member>()

    cue init(size as int, memberType as Type)
        base.init
        for i in size, _members.add(memberType())

    cue init(member as Member)
        ensure
            .members.toList == [member]
            .count == 1
        body
            base.init
            _members.add(member)
        
    get count as int
        ensure result > 0
        return _members.count

    get members as Member*
        for m in _members, yield m

    get best from var as Member?

    get worst from var as Member?

    get averageScore from var as number
    
    def add(m as Member)
        _members.add(m)

    def examine
        """
        Examine the population for the best and worst members as well compute
        the average score.
        """
        require
            .count > 0
        ensure
            .best and .best.score >= .averageScore
            .worst and .worst.score <= .averageScore
        body
            total = 0.0
            best as Member?
            worst as Member?
            for m in .members
                if best is nil or best.score < m.score, best = m
                if worst is nil or worst.score > m.score, worst = m
                total += m.score
            _averageScore = total / .count
            _best, _worst = best, worst

    def randomMember(r as Random) as Member
        require .count > 0
        return _members.random(r)

    def removeLast
        _members.removeLast
        
    def dump
        base.dump
        for i, m in _members.numbered
            print '[i] ' stop
            m.dump

    def report
        print 'best  =', .best
        print 'worst =', .worst
        print 'avg   =', .averageScore


class Member inherits BaseObject is abstract

    get age from var as int
        """
        Returns the age in number of generations
        that this member has survived.
        """
    
    pro score from var as number
        """
        The score represents how well this member performs
        or how "good" it is.
        """
    
    def evaluate as number is abstract
        """ Computes the score of this member. """
        ensure .score == result

    def copy as dynamic
        ensure result is not this
        return .memberwiseClone
    
    def crossover(other) as List<of Member> is abstract
        require
            other is not this
        ensure
            result.count == 2
            result[0].typeOf.isKindOf(.typeOf)
            result[1].typeOf.isKindOf(.typeOf)
            result[0] is not this and result[0] is not other
            result[1] is not this and result[1] is not other
    
    def incAge
        _age += 1
    
    def mutate(rate as number) as bool is abstract
        """ Potentially mutates the member. Returns true if member is mutated. """
        require rate >= 0
    
    def innerString as String
        return base.innerString + ', age=[.age], score=[.score]'


class DummyMember inherits Member
    """
    A DummyMember does nothing practical on its own, but can be used for simple
    unit tests.
    """

    def evaluate as number is override
        return 1
        
    def crossover(other) as List<of Member> is override
        t = List<of Member>()
        t.add(this)
        t.add(this)
        return t
    
    def mutate(rate as number) as bool is override
        return false


class BitstringMember inherits Member is abstract
    """
    A member whose genome is comprised of 1s and 0s that are subsequently
    translated/interpreted as one or more phenotypes.
    
    TODO: add util method to convert a range of the bitstring to an integer
    """

    shared
            
        def randomBits(length as int) as int[]
            bits = int[](length)
            for i in length, bits[i] = .random.next % 2
            return bits

    var _bits as int[]
    
    cue init(bits as int[])
        require bits.length >= 2
        ensure .bits is bits
        base.init
        _bits = bits

    cue init(length as int)
        """ Initializes the bitstring with `length` random bits. """
        require length >= 2
        .init(.randomBits(length))

    get bits from var
    
    get length as int
        ensure result >= 2
        return _bits.length

    def copy as dynamic
        and ensure
            result.bits is not .bits
        body
            copy = base.copy to BitstringMember
            n = .length
            bits = int[](n)
            for i in n, bits[i] = _bits[i]
            copy._bits = bits
            return copy

    def crossover(other) as List<of Member> is override
        """ Override this method to customize the crossover operation. """
        return .twoPointCrossover(other)
    
    def mutate(rate as number) as bool is override
        """ Override this method to customize the crossover operation. """
        return .randomMutation(rate)
    
    def twoPointCrossover(other as BitstringMember) as List<of Member>
        left, right = _pickPivots
        n = .length
        a, b = int[](n), int[](n)
        for i in left
            a[i], b[i] = _bits[i], other._bits[i]
        for i in left : right
            a[i], b[i] = other._bits[i], _bits[i]
        for i in right : n
            a[i], b[i] = _bits[i], other._bits[i]
        list = List<of Member>()
        type = .typeOf
        childA, childB = type(a), type(b)
        childA.repair(this, other)
        childB.repair(this, other)
        list.add(childA)
        list.add(childB)
        return list

    def randomMutation(rate as number) as bool
        didMutate = false
        for i in .length
            if .random.nextDouble < rate
                _bits[i] = if(_bits[i], 0, 1)
                didMutate = true
        return didMutate

    def _pickPivots as List<of int>
        ensure
            result.count == 2
            result[1] >= result[0]
        body
            left = .random.next(1, .length-2)
            right = .random.next(left, .length-1)
            return [left, right]

    def repair(parent1 as BitstringMember, parent2 as BitstringMember)
        """ Override this method to fix duplicated genes, if necessary. """
        pass


class MaximizeOnesMember inherits BitstringMember
    """
    A problem-specific member to maximize the number of ones (1s)
    that appear in the genome.
    """
    
    cue init
        base.init(50)

    cue init(bits as int[])
        base.init(bits)

    cue init(length as int)
        base.init(length)

    def evaluate as number is override
        total = 0
        for b in _bits, total += b
        _score = total
        return total

    def innerString as String
        return base.innerString + ', ' + (for b in .bits get '01'[b]).joined('')


class Engine inherits BaseObject
    """
    An engine runs the GA using parameters and a population.
    It pushes the population from one generation to the next,
    applying genetic operators.
    """

    var _params as Params?
    var _pop as Population
    var _generation as int

    cue init(params as Params)
        .init(params, nil)

    cue init(params as Params, pop as Population?)
        require
            params.memberType
            params.populationSize > 0 or (pop and pop.count > 0)
        body
            base.init
            _params = params
            BaseObject.random = if(params.randomSeed, Random(params.randomSeed), Random())
            _pop = pop ? Population(params.populationSize, params.memberType)
            .evaluate

    get generation as int
        ensure result >= 0
        return _generation

    get params from var
    
    get population from _pop

    def run
        while not .isDone, .step

    def isDone as bool
        return .generation >= .params.maxGenerations or (
            .population.best and .population.best.score >= .params.scoreGoal)

    def evaluate
        for m in _pop.members, m.evaluate
        .population.examine

    def step
        .generateNextPopulation
        for member in _pop.members, member.incAge
        _generation += 1
        .population.examine
        .report

    def generateNextPopulation
        thisPop = .population
        nextPop = Population(.population.best)
        while nextPop.count < thisPop.count
            mate1 = .select
            if _random.nextDouble < .params.crossoverRate
                while true
                    mate2 = .select
                    if mate2 is not mate1, break
                offspring = mate1.crossover(mate2)
            else
                offspring = [mate1.copy to Member]
            for member in offspring
                member.mutate(.params.mutationRate)
                member.evaluate
                nextPop.add(member)
        while nextPop.count > thisPop.count
            nextPop.removeLast
        _pop = nextPop

    def select as Member
        """
        Calls .tournamentSelect by default.
        A subclass can override this to implement a different selection method.
        """
        return .tournamentSelect

    def tournamentSelect as Member
        require .population.count > 0
        # This is an interesting method because for quality control,
        # it's not clear what useful `ensure` could be added to it,
        # nor is it clear what kind of unit test would prove that
        # tournament selection was working as expected.
        # Have ideas? Post them to
        # http://cobra-language.com/forums/viewforum.php?f=4
        best as Member?
        for i in .params.tournamentSize
            competitor = .population.randomMember(.random)
            if best is nil or best.score < competitor.score
                best = competitor
        return best to !
    
    def report
        print 'gen   =', .generation
        .population.report
        print


class Program

    def main
        .maximizeOnesExample

    def maximizeOnesExample
        params = Params.default
        params.maxGenerations = 100
        params.memberType = MaximizeOnesMember
        params.scoreGoal = 50
        engine = Engine(params)
        engine.run
        # testing:
        assert engine.generation < 100
        assert engine.population.best.score == 50
        print 'done.'


extend Type

    def isKindOf(t as Type) as bool
        """
        Returns true if this type is the same as or a subclass of the given type.
        TODO: Should this return true if t is an interface that
              this type implements?
        """
        return t is this or .isSubclassOf(t)


extend IList<of T>

    def joined(sep as String) as String
        sb = StringBuilder()
        sep2 = ''
        for item in this
            sb.append(sep2)
            sb.append(CobraCore.printStringMaker.makeString(item))
            sep2 = sep
        return sb.toString