imported hobo11 data... this is a fragment of vmware svn, whatever I had
[tues-crep.git] / sites / hobo11.nerdland.org / coursework / 20062 / achtung / src / pygene / population.py
blobc5d3712c37bd2772db4ce6d49f6719ed4f729b8b
1 """
2 pygene/population.py - Represents a population of organisms
3 """
5 import random
6 from random import randrange, choice
7 from math import sqrt
9 from organism import Organism, BaseOrganism
11 from xmlio import PGXmlMixin
13 class Population(PGXmlMixin):
14 """
15 Represents a population of organisms
17 You might want to subclass this
19 Overridable class variables:
21 - species - Organism class or subclass, being the 'species'
22 of organism comprising this population
24 - initPopulation - size of population to randomly create
25 if no organisms are passed in to constructor
27 - childCull - cull to this many children after each generation
29 - childCount - number of children to create after each generation
31 - incest - max number of best parents to mix amongst the
32 kids for next generation, default 10
34 - numNewOrganisms - number of random new orgs to add each
35 generation, default 0
37 - initPopulation - initial population size, default 10
39 - mutants - default 0.1 - if mutateAfterMating is False,
40 then this sets the percentage of mutated versions of
41 children to add to the child population; children to mutate
42 are selected based on fitness
44 Supports the following python operators:
46 - + - produces a new population instances, whose members are
47 an aggregate of the members of the values being added
49 - [] - int subscript - returns the ith fittest member
51 """
52 # cull to this many children after each generation
53 childCull = 20
55 # number of children to create after each generation
56 childCount = 100
58 # max number of best parents to mix amongst the kids for
59 # next generation
60 incest = 10
62 # parameters governing addition of random new organisms
63 numNewOrganisms = 0 # number of new orgs to add each generation
65 # set to initial population size
66 initPopulation = 10
68 # set to species of organism
69 species = Organism
71 # mutate this proportion of organisms
72 mutants = 0.1
74 # set this to true to mutate all progeny
75 mutateAfterMating = True
77 def __init__(self, *items, **kw):
78 """
79 Create a population with zero or more members
81 Arguments:
82 - any number of arguments and/or sequences of args,
83 where each arg is an instance of this population's
84 species. If no arguments are given, organisms are
85 randomly created and added automatically, according
86 to self.initPopulation and self.species
88 Keywords:
89 - init - size of initial population to randomly create.
90 Ignored if 1 or more constructor arguments are given.
91 if not given, value comes from self.initPopulation
92 - species - species of organism to create and add. If not
93 given, value comes from self.species
94 """
95 self.organisms = []
97 if kw.has_key('species'):
98 species = self.species = kw['species']
99 else:
100 species = self.species
102 if kw.has_key('init'):
103 init = self.initPopulation = kw['init']
104 else:
105 init = self.initPopulation
107 if not items:
108 for i in xrange(init):
109 self.add(species())
111 def add(self, *args):
113 Add an organism, or a population of organisms,
114 to this population
116 You can also pass lists or tuples of organisms and/or
117 populations, to any level of nesting
119 for arg in args:
120 if isinstance(arg, tuple) or isinstance(arg, list):
121 # got a list of things, add them one by one
122 self.add(*item)
124 if isinstance(arg, BaseOrganism):
125 # add single organism
126 self.organisms.append(arg)
128 elif isinstance(arg, Population):
129 # absorb entire population
130 self.organisms.extend(arg)
131 else:
132 raise TypeError(
133 "can only add Organism or Population objects")
135 self.sorted = False
137 def __add__(self, other):
139 Produce a whole new population consisting of an aggregate
140 of this population and the other population's members
142 return Population(self, other)
144 def getRandom(self, items=None):
146 randomly select one of the given items
147 (or one of this population's members, if items
148 not given).
150 Favours fitter members
152 if items == None:
153 items = self.organisms
155 nitems = len(items)
156 n2items = nitems * nitems
158 # pick one parent randomly, favouring fittest
159 idx = int(sqrt(randrange(n2items)))
160 return items[nitems - idx - 1]
162 def gen(self, nfittest=None, nchildren=None):
164 Executes a generation of the population.
166 This consists of:
167 - producing 'nchildren' children, parented by members
168 randomly selected with preference for the fittest
169 - culling the children to the fittest 'nfittest' members
170 - killing off the parents, and replacing them with the
171 children
173 Read the source code to study the method of probabilistic
174 selection.
176 if not nfittest:
177 nfittest = self.childCull
178 if not nchildren:
179 nchildren = self.childCount
181 children = []
183 # add in some new random organisms, if required
184 if self.numNewOrganisms:
185 #print "adding %d new organisms" % self.numNewOrganisms
186 for i in xrange(self.numNewOrganisms):
187 self.add(self.__class__())
189 # we use square root to skew the selection probability to
190 # the fittest
192 # get in order, if not already
193 self.sort()
194 nadults = len(self)
196 n2adults = nadults * nadults
198 # statistical survey
199 #stats = {}
200 #for j in xrange(nchildren):
201 # stats[j] = 0
203 # wild orgy, have lots of children
204 for i in xrange(nchildren):
205 # pick one parent randomly, favouring fittest
206 idx1 = idx2 = int(sqrt(randrange(n2adults)))
207 parent1 = self[-idx1]
209 # pick another parent, distinct from the first parent
210 while idx2 == idx1:
211 idx2 = int(sqrt(randrange(n2adults)))
212 parent2 = self[-idx2]
214 #print "picking items %s, %s of %s" % (
215 # nadults - idx1 - 1,
216 # nadults - idx2 - 1,
217 # nadults)
219 #stats[nadults - idx1 - 1] += 1
220 #stats[nadults - idx2 - 1] += 1
222 # get it on, and store the child
223 child1, child2 = parent1 + parent2
225 # mutate kids if required
226 if self.mutateAfterMating:
227 child1 = child1.mutate()
228 child2 = child2.mutate()
230 children.extend([child1, child2])
232 # if incestuous, add in best adults
233 if self.incest:
234 children.extend(self[:self.incest])
236 children.sort()
238 # and add in some mutants, a proportion of the children
239 # with a bias toward the fittest
240 if not self.mutateAfterMating:
241 nchildren = len(children)
242 n2children = nchildren * nchildren
243 mutants = []
244 numMutants = int(nchildren * self.mutants)
246 if 1:
247 for i in xrange(numMutants):
248 # pick one parent randomly, favouring fittest
249 idx = int(sqrt(randrange(n2children)))
250 #child = children[nchildren - idx - 1]
251 child = children[-idx]
252 mutants.append(child.mutate())
253 else:
254 for i in xrange(numMutants):
255 mutants.append(children[i].mutate())
257 children.extend(mutants)
259 #print "added %s mutants" % numMutants
261 # sort the children by fitness
262 children.sort()
264 # take the best 'nfittest', make them the new population
265 self.organisms[:] = children[:nfittest]
267 self.sorted = True
269 #return stats
270 def __repr__(self):
272 crude human-readable dump of population's members
274 return str(self.organisms)
276 def __getitem__(self, n):
278 Return the nth member of this population,
279 which we guarantee to be sorted in order from
280 fittest first
282 self.sort()
283 return self.organisms[n]
285 def __len__(self):
287 return the number of organisms in this population
289 return len(self.organisms)
291 def fitness(self):
293 returns the average fitness value for the population
295 fitnesses = map(lambda org: org.fitness(), self.organisms)
297 return sum(fitnesses)/len(fitnesses)
299 def best(self):
301 returns the fittest member of the population
303 self.sort()
304 return self[0]
306 def sort(self):
308 Sorts this population in order of fitness, with
309 the fittest first.
311 We keep track of whether this population is in order
312 of fitness, so we don't perform unnecessary and
313 costly sorting
315 if not self.sorted:
316 self.organisms.sort()
317 self.sorted = True
319 # methods for loading/saving to/from xml
321 def xmlDumpSelf(self, doc, parent):
323 Writes out the contents of this population
324 into the xml tree
326 # create population element
327 pop = doc.createElement("population")
328 parent.appendChild(pop)
330 # set population class details
331 pop.setAttribute("class", self.__class__.__name__)
332 pop.setAttribute("module", self.__class__.__module__)
334 # set population params as xml tag attributes
335 pop.setAttribute("childCull", str(self.childCull))
336 pop.setAttribute("childCount", str(self.childCount))
338 # dump out organisms
339 for org in self.organisms:
340 org.xmlDumpSelf(doc, pop)