2 pygene/population.py - Represents a population of organisms
6 from random
import randrange
, choice
9 from organism
import Organism
, BaseOrganism
11 from xmlio
import PGXmlMixin
13 class Population(PGXmlMixin
):
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
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
52 # cull to this many children after each generation
55 # number of children to create after each generation
58 # max number of best parents to mix amongst the kids for
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
68 # set to species of organism
71 # mutate this proportion of organisms
74 # set this to true to mutate all progeny
75 mutateAfterMating
= True
77 def __init__(self
, *items
, **kw
):
79 Create a population with zero or more members
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
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
97 if kw
.has_key('species'):
98 species
= self
.species
= kw
['species']
100 species
= self
.species
102 if kw
.has_key('init'):
103 init
= self
.initPopulation
= kw
['init']
105 init
= self
.initPopulation
108 for i
in xrange(init
):
111 def add(self
, *args
):
113 Add an organism, or a population of organisms,
116 You can also pass lists or tuples of organisms and/or
117 populations, to any level of nesting
120 if isinstance(arg
, tuple) or isinstance(arg
, list):
121 # got a list of things, add them one by one
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
)
133 "can only add Organism or Population objects")
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
150 Favours fitter members
153 items
= self
.organisms
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.
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
173 Read the source code to study the method of probabilistic
177 nfittest
= self
.childCull
179 nchildren
= self
.childCount
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
192 # get in order, if not already
196 n2adults
= nadults
* nadults
200 #for j in xrange(nchildren):
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
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,
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
234 children
.extend(self
[:self
.incest
])
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
244 numMutants
= int(nchildren
* self
.mutants
)
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())
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
264 # take the best 'nfittest', make them the new population
265 self
.organisms
[:] = children
[:nfittest
]
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
283 return self
.organisms
[n
]
287 return the number of organisms in this population
289 return len(self
.organisms
)
293 returns the average fitness value for the population
295 fitnesses
= map(lambda org
: org
.fitness(), self
.organisms
)
297 return sum(fitnesses
)/len(fitnesses
)
301 returns the fittest member of the population
308 Sorts this population in order of fitness, with
311 We keep track of whether this population is in order
312 of fitness, so we don't perform unnecessary and
316 self
.organisms
.sort()
319 # methods for loading/saving to/from xml
321 def xmlDumpSelf(self
, doc
, parent
):
323 Writes out the contents of this population
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
))
339 for org
in self
.organisms
:
340 org
.xmlDumpSelf(doc
, pop
)