5
\$\begingroup\$

This is supposed to be an evolutionary algorithmic program, and I'm not sure if it does what it's supposed to do.

It is supposed to take a random list of numbers (containing at least one 5), and over many generations, increase the probability of 5's reproducing, thus having the 5's eventually taking over the output.

Is this an accurate representation of an evolutionary algorithm? If so, how can I make it better, and if not, how can I make it one?

import random as r
import os
import sys
import time
from subprocess import call
#call('color a', shell=True)
class Entity(object):
    def __init__(self, number):
        self.number = number
        self.chance = 10
        self.age = 0
class Fitness(object):
    def __init__(self, population):
        self.population = population

    def StartPopulation(self, pop, entities):
        for x in range(pop):
            entity = Entity(r.randint(1, 10))
            entities.append(entity)

    def FitnessMethod(self, entities):

        for x in entities:
            if x.number == 5:
                x.chance += 10
            else: 
                x.chance += 0

            if x.chance > 100:
                x.chance = 100

            else: 
                x.chance += 0


    def Reproduce(self,entities):
        x = entities[r.randint(0, (len(entities) - 1))]
        if r.randint(0, 100) < x.chance:    
            random_children = r.randint(1, 3)
            for a in range(random_children):    
                entity = Entity(x.number)
                entity.chance = x.chance
                entities.append(entity)
            x.age += 1

    def Aging(self, entities):
        for x in entities:
            if x.age >= 1:
                entities.remove(x)          

pop = r.randint(5, 10)
entities = []
Fitness = Fitness(pop)
Fitness.StartPopulation(pop, entities)
for x in entities:
    print x.number, x.chance
print "~~~~~~~~~~~~~\n"
raw_input()


for x in range(200):
    Fitness.FitnessMethod(entities)
    Fitness.Reproduce(entities)
    Fitness.Aging(entities)
    for x in entities:
        print x.number, x.age, x.chance
    print "Generation_Mutation_Complete"    
raw_input()
\$\endgroup\$
3
  • \$\begingroup\$ It would be preferred to post your new code as a new follow-up question. We no longer encourage extended reviews of newer code in the same question. \$\endgroup\$
    – Jamal
    May 16, 2014 at 15:21
  • 1
    \$\begingroup\$ Ok, editing and creating new question(feel free to edit it, I know you love that job). \$\endgroup\$
    – FrigidDev
    May 16, 2014 at 15:22
  • \$\begingroup\$ I submitted a new question, here it is: (codereview.stackexchange.com/questions/50927/…) \$\endgroup\$
    – FrigidDev
    May 16, 2014 at 15:28

2 Answers 2

6
\$\begingroup\$

Picking up where Josay left off:

Simulating evolution

The current algorithm has some flaws as an evolution simulation:

  • Entities inherit their parents' fitness. Fitness should be determined by the entity's traits; it isn't a trait in its own right.
  • Entities only age when they reproduce, and the number of offspring doesn't depend on the fitness. This means all entities reproduce eventually; fitter ones just do it faster! Furthermore, the number of offspring doesn't depend on the fitness, so all entities have the same reproductive success! In an evolution simulation, fitter organisms should have more reproductive success.

Some other oddities:

  • Only one organism per step can reproduce. Why not let them all reproduce at once?
  • Randomness is introduced in too many places: in particular, the fitness is random, but it's also compared to a random number. You only need one of these. It's better to keep randomness out of the fitness function entirely, so its results are more meaningful and it's easier to debug.
  • The population grows without bound. It should probably be limited, perhaps with random.sample.

It's not necessary to model aging at all in a simple evolution simulator. You can simply have every organism die in every generation. Or you can have them die randomly, without keeping track of age.

There are two important parts of a genetic algorithm:

  1. The fitness function: given an organism, how successful is it?

  2. The breeding function: Given a population and a fitness function, produce the next generation. There are a lot of options here: you can choose parents randomly weighted by fitness, or let everything reproduce a few times and keep the fittest children, or let everything reproduce according to its fitness and keep random children.

Design

Fitness does not make much sense as a class, because its instances don't represent anything. If each instance represents a population, it could be a Population. But it's simpler to represent a population as a list of organisms, with no separate class. (Not everything needs to be a class!)

Some of the methods of Fitness should be operations on a single Entity, not a whole population.

Several functions modify objects unnecessarily. In particular, fitness_method should simply return a fitness rather than modifying the Entity. (This causes a bug: the fitness of 5 increases over time!)

Minor style issues

Some names could be improved:

  • fitness_method is fitness. Method names should say what they do, not the fact that they're methods.
  • aging should be removeOldOrganisms.
  • Entity is vague. Organism is more specific. Populations can be called population, not entities.

The loop to print a population is repeated and should be factored out.

raw_input can print a prompt, so the separate print before it is unnecessary. But there's no need to wait for user input at all.

It's not necessary to rename random.

How I'd do it

Here's a version with most of the above changes:

import random

class Organism:
    def __init__(self, number):
        self.number = number
    def fitness(self):
        "How many children can this organism have?"
        return 3 if self.number == 5 else 2
    def reproduce(self):
        "Make a child. Currently children are just copies of the parent."
        return Organism(self.number)

max_population = 100

def nextGeneration(population):
    children = []
    for x in population:
        n_children = random.randint(0, x.fitness())
        children += [x.reproduce() for _ in range(n_children)]
    return random.sample(children, min(len(children), max_population))

def printPopulation(population):
    for x in population:
        print x.number, x.fitness()

population = [Organism(random.randint(1, 10)) for _ in range(10)]
printPopulation(population)

for gen in range(10):
    print "Generation", gen, "population", len(population)
    population = nextGeneration(population)
    printPopulation(population)

This uses fitness to determine the number of children rather than which children survive; either is reasonable.

\$\endgroup\$
3
  • \$\begingroup\$ I'm glad I wasn't able to finish my answer this morning because of the freeze, this is much better than what I would have done. \$\endgroup\$
    – SylvainD
    May 16, 2014 at 14:40
  • \$\begingroup\$ Thank Anonymous for that very good answer. I actually changed my code yesterday, before reading this, so I will edit my question to show you where I currently am. As of now, it is working splendidly, and with some simple changes, I can have it evolve into complete sentences. Thanks for your help, and the new code is in the question. \$\endgroup\$
    – FrigidDev
    May 16, 2014 at 15:17
  • \$\begingroup\$ I'm creating a new question, it will be closely linked to this one. \$\endgroup\$
    – FrigidDev
    May 16, 2014 at 15:20
5
\$\begingroup\$

The formatting of your code is a bit unusual : no line break between classes, line breaks in the middle of the functions, local variable names starting with an upper-case letter, etc. To be fair, your code does't even run on my setup because of the Fitness variable.

A few tools exist to perform some checks on your Python code. Let's run then and see what they tell us :

pep8

fitness.py:11:1: E302 expected 2 blank lines, found 0
fitness.py:16:1: E302 expected 2 blank lines, found 0
fitness.py:30:18: W291 trailing whitespace
fitness.py:36:18: W291 trailing whitespace
fitness.py:40:5: E303 too many blank lines (2)
fitness.py:40:23: E231 missing whitespace after ','
fitness.py:42:41: W291 trailing whitespace
fitness.py:44:45: W291 trailing whitespace
fitness.py:53:35: W291 trailing whitespace
fitness.py:55:1: E302 expected 2 blank lines, found 1
fitness.py:66:5: E303 too many blank lines (2)
fitness.py:72:45:W291 trailing whitespace

pychecker

fitness.py:6: Imported module (os) not used
fitness.py:7: Imported module (sys) not used
fitness.py:8: Imported module (time) not used
fitness.py:21: Local variable (x) not used
fitness.py:44: Local variable (a) not used
fitness.py:58: Variable (Fitness) used before being set
fitness.py:59: Invalid arguments to (StartPopulation), got 2, expected
fitness.py:59: self is not first method argument
fitness.py:67: Invalid arguments to (FitnessMethod), got 1, expected 2 fitness.py:67: self is not first method argument
fitness.py:68: Invalid arguments to (Reproduce), got 1, expected 2 fitness.py:68: self is not first method argument
fitness.py:69: Invalid arguments to (Aging), got 1, expected 2
fitness.py:69: self is not first method argument

pyflakes

fitness.py:6: 'os' imported but unused
fitness.py:7: 'sys' imported but unused
fitness.py:8: 'time' imported but unused
fitness.py:9: 'call' imported but unused
fitness.py:58: local variable 'Fitness' (defined in enclosing scope on line 16) referenced before assignment

After fixing the different issues, your code looks like :

#!/usr/bin/python
"""Docstring for Python module"""

import random as r


class Entity(object):
    def __init__(self, number):
        self.number = number
        self.chance = 10
        self.age = 0


class Fitness(object):
    def __init__(self, population):
        self.population = population

    def StartPopulation(self, pop, entities):
        for _ in range(pop):
            entity = Entity(r.randint(1, 10))
            entities.append(entity)

    def FitnessMethod(self, entities):
        for x in entities:
            if x.number == 5:
                x.chance += 10
            else:
                x.chance += 0

            if x.chance > 100:
                x.chance = 100
            else:
                x.chance += 0

    def Reproduce(self, entities):
        x = entities[r.randint(0, (len(entities) - 1))]
        if r.randint(0, 100) < x.chance:
            random_children = r.randint(1, 3)
            for _ in range(random_children):
                entity = Entity(x.number)
                entity.chance = x.chance
                entities.append(entity)
            x.age += 1

    def Aging(self, entities):
        for x in entities:
            if x.age >= 1:
                entities.remove(x)


def main():
    pop = r.randint(5, 10)
    entities = []
    fitness = Fitness(pop)
    fitness.StartPopulation(pop, entities)
    for x in entities:
        print x.number, x.chance
    print "~~~~~~~~~~~~~\n"
    raw_input()

    for x in range(200):
        fitness.FitnessMethod(entities)
        fitness.Reproduce(entities)
        fitness.Aging(entities)
        for x in entities:
            print x.number, x.age, x.chance
        print "Generation_Mutation_Complete"


if __name__ == "__main__":
    main()

Now, x.chance += 0 is probably not useful : let's get rid of it. Also, you can use min if you want to be sure not to go further than 100:

def FitnessMethod(self, entities):
    for x in entities:
        if x.number == 5:
            x.chance = min(100, x.chance+5)

Then, it seems like all method from Fitness take entities as a parameter. This is a good indicator that maybe it should be a member of the class. I also took this chance to move fitness_method + reproduce + aging in a single method. Also, the StartPopulation is a bit weird : maybe this should be in the __init__ method. Once integrated, one can easily realise that self.population is never used.

Once re-simplified, your code becomes :

#!/usr/bin/python
"""Docstring for Python module"""

import random as r


class Entity(object):
    def __init__(self, number):
        self.number = number
        self.chance = 10
        self.age = 0


class Fitness(object):
    def __init__(self, population):
        self.entities = []
        for _ in range(population):
            self.entities.append(Entity(r.randint(1, 10)))

    def fitness_method(self):
        for x in self.entities:
            if x.number == 5:
                x.chance = min(100, x.chance+5)

    def reproduce(self):
        x = self.entities[r.randint(0, (len(self.entities) - 1))]
        if r.randint(0, 100) < x.chance:
            random_children = r.randint(1, 3)
            for _ in range(random_children):
                entity = Entity(x.number)
                entity.chance = x.chance
                self.entities.append(entity)
            x.age += 1

    def aging(self):
        for x in self.entities:
            if x.age >= 1:
                self.entities.remove(x)

    def run_step(self):
        self.fitness_method()
        self.reproduce()
        self.aging()


def main():
    fitness = Fitness(r.randint(5, 10))
    for x in fitness.entities:
        print x.number, x.chance
    print "~~~~~~~~~~~~~\n"
    raw_input()

    for x in range(200):
        fitness.run_step()
        for x in fitness.entities:
            print x.number, x.age, x.chance
        print "Generation_Mutation_Complete"


if __name__ == "__main__":
    main()

Now, a few points about list :

  • you shouldn't alter a container while looping over it. You'll find the explanation here and some information about the right way to do this here about what you should be doing in your aging function. (Btw, aging is not a very descriptive name for what the method does, the aging process is more the x.age += 1 part).

  • the constructor can be re-written using list comprehension

It would look like this :

   def __init__(self, population):
        self.entities = [Entity(r.randint(1, 10)) for _ in range(population)]

I have to go before I reach the interesting part but I am not convinced your code performs a proper evolutionnary algorithm. I suggest you have an additional look at ressources online and go back to your code.

Edit :

Anonymous' answer is probably the kind of answer you were expecting. However, I'll share with you a few other findings that I was about to post before the freeze.

  • You could add a default parameter chance in the constructor of Entity. This allows you to make Reproduce more concise as it doesn't need to update chance after the creation. You can even take this chance to use list comprehension again.

  • You do not need to keep track of the age of each entity : as soon as you were about to "make it older", you can remove it alltogether.

This is what you get afterwards :

class Entity(object):
    def __init__(self, number, chance=10):
        self.number = number
        self.chance = chance


class Fitness(object):
    def __init__(self, population):
        self.entities = [Entity(r.randint(1, 10)) for _ in range(population)]

    def fitness_method(self):
        for x in self.entities:
            if x.number == 5:
                x.chance = min(100, x.chance+5)

    def reproduce(self):
        x = r.choice(self.entities)
        if r.randint(0, 100) < x.chance:
            # removing aging entity
            self.entities.remove(x)
            self.entities.extend(Entity(x.number, x.chance) for _ in range(r.randint(1, 3)))

    def run_step(self):
        self.fitness_method()
        self.reproduce()
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for pointing out some errors, but please, what's the interesting part! \$\endgroup\$
    – FrigidDev
    May 15, 2014 at 17:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.