1st non-trivial program - gentle criticism requested

Paul-Michael Agapow p.agapow at ic.ac.uk
Thu Apr 13 04:10:16 EDT 2000


Hi y'all,

I'm experimenting with Python for a number of purposes (an embedded
language for simulations, a tool for exploring experimental data, a good
first programming language) and so I've cobbled together my first
non-trivial program. With some trepidation, I post it here in the hopes
that experts will be able to coach me in "The Python Way". I realise it
looks like C++ (my usual langauge), but criticisms of style would be
useful too.

Basically, it's a recreation of the "Methinks it is a weasel" simulation
detailed by Richard Dawkins in "The Blind Watchmaker". A population of
strings is compared vs. a target string and the "fittest" (the one that
most resembles the target) is used to form the next generation, with a
small chance. So it's a simple genetic algorithm.

thanks

p-m


--- program follows

"""
weasel.py

A simple program exploring the possibilities of using Python, esp. in
simulations, modelling the 'methinks it is a weasel' problem from
Richard Dawkins' "The Blind Watchmaker". The program flow is:

1. Init population of random individuals
2. Pick fittest individual
3. If the individual is the target:
   3a. Return
   3b. Else, breed new population from individual (mutation)
4. Goto 2. 

"""

#--- Includes

import whrandom
import string


#--- Module constants & variables

kDefTarget = "METHINKS IT IS LIKE A WEASEL"
kDefPopSize = 10;
kDefStepSize = 50;

gPopulation = [];                         # the weasels, as it were
gRng = whrandom.whrandom()                # random source
gTarget                                   # what fitness is measured
against
gNumGenerations                           # how many cycles did it take?
gAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ " # all possible symbols
gMutationRate                             # chance of a symbol flipping
gStepSize                                 # how often to report stats


#--- Module functions -----------------------------------------------

#--- Run

# Setup the variables and run
def run (size=kDefPopSize, target=kDefTarget, step=kDefStepSize):
   global gTarget, gNumGenerations, gStepSize, gMutationRate
   # initialise
   gTarget = target
   gNumGenerations = 0;
   gStepSize = step
   gMutationRate = 1.0/ (len(gTarget) + 1)
   initPop (size);
   # tell user the settings
   print "WEASEL in action:"
   print "-----------------"
   printStartParams();
   print "-----------------"
   # evolve until the end is reached
   while (0 == evolvePop (gStepSize)):
      if (0 == (gNumGenerations % kDefStepSize)):
         print "At generation", gNumGenerations
   # print end results
   print "-----------------"
   print "Reached target after ", gNumGenerations, "generations"
   printPop();
   return


#--- Setup

def initPop (popsize = 10):
   # Initialise the contents of the population
   # Note: do this or gPop is local and temporary!
   global gPopulation
   gPopulation = [];
   for i in range (popsize):
      theNewMember = MakeNewMember (len(gTarget))
      gPopulation.append (theNewMember)
   return

def MakeNewMember (chromsize = len(gTarget)):
   # For initing the population, generate a random string of symbols
   # Note: seems inefficient creating strings this way
   theNewString = "";
   while (len(theNewString) < chromsize):
      theNewString = theNewString + GetRandomSymbol()
   return theNewString
   

#--- Mutation

def MutateIndividual (theTargetIndiv):
   """Iterates over symbols of individual changing their 'genes'"""
   # Must be a faster way of doing this
   theNewIndiv = ""
   for theSymbol in theTargetIndiv:
      if (gRng.random() < gMutationRate):
         theNewSymbol = GetRandomSymbol()
      else:
         theNewSymbol = theSymbol
      theNewIndiv = theNewIndiv + theNewSymbol
   return theNewIndiv


def GetRandomSymbol ():
   """For the purposes of mutation, this selects a random symbol from
the alphabet"""
   # The global declaration isn't necessary as we don't assign to these
   # vars, but I'm doing it as a reminder
   global gRng, gAlphabet
   theSymbolIndex = gRng.randint(0, len(gAlphabet) - 1)
   return gAlphabet[theSymbolIndex]
   
      
#--- Evolve

# Evolve for a given number of time steps
def evolvePop (iNumSteps):
   global gNumGenerations
   for i in range (iNumSteps):
      if (evolveStep()):
         return 1
      else:
         gNumGenerations = gNumGenerations + 1
   return 0
   

# Evolve a single step
def evolveStep ():
   global gPopulation
   if (gTarget in gPopulation):
      return 1
   else:
      # calculate fitness
      theFitness = []
      for theIndiv in gPopulation:
         theFitness.append (calcFitness (theIndiv))   
      # select winner
      theWinnerIndex = findMax (theFitness)
      theWinner = gPopulation[theWinnerIndex]
      assert (theWinnerIndex <> None)
      # breed from winner
      for i in range(len(gPopulation)):
         gPopulation[i] = MutateIndividual (theWinner)
      return 0


def calcFitness (anIndiv):
   theFitness = 0
   for i in range (len(gTarget)):
      if (gTarget[i] == anIndiv[i]):
         theFitness = theFitness + 1;
   return theFitness

def findMax (iCollection):
   # Returns the index of the largest member of collection
   # Just a little bullet-proofing
   if (len(iCollection) == 0):
      return
   # Setup initial sensible values
   theMaxIndex = 0
   theMaxVal = iCollection[0];
   # Iterate over remainder of container
   # Inefficient to look at the first element again, but it will do
   for i in range(len(iCollection)):
      if (theMaxVal < iCollection[i]):
         theMaxVal = iCollection[i]
         theMaxIndex = i
   return theMaxIndex
   

#--- Output

# Print out the contents of the population
def printStartParams ():
   print "The target is", gTarget
   print "The mutation rate is", gMutationRate
   print "The alphabet is '" + gAlphabet + "'"
   print "The population size is", len(gPopulation)
   printPop()
   return
   
   
# Print out the contents of the population
def printPop ():
   print "The population is currently:"
   gPopulation.sort()
   theCount = 0;
   for theIndividual in gPopulation:
      if (theIndividual == gTarget):
         thePrefix = "*"
      else:
         thePrefix = ""
      theCount = theCount + 1
      thePrefix = thePrefix + repr(theCount) + " :"
      print string.rjust(thePrefix,8), theIndividual


#--- if this is called as a standalone script   

if (__name__ == "__main__"):
   run()
   
   
-- 
Paul-Michael Agapow (p.agapow at ic.ac.uk), Biology, Silwood Park
"The world is made of fire."



More information about the Python-list mailing list