Evolife
Evolife has been developed to study Genetic algorithms, Natural evolution and behavioural ecology.
Population.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2""" @brief A population is a set of semi-permeable groups.
3 """
4
5#============================================================================#
6# EVOLIFE http://evolife.telecom-paris.fr Jean-Louis Dessalles #
7# Telecom Paris 2022-04-11 www.dessalles.fr #
8# -------------------------------------------------------------------------- #
9# License: Creative Commons BY-NC-SA #
10#============================================================================#
11# Documentation: https://evolife.telecom-paris.fr/Classes #
12#============================================================================#
13
14
15
18
19
20
21
22import sys
23if __name__ == '__main__': # for tests
24 sys.path.append('../..')
25 from Evolife.Scenarii.MyScenario import InstantiateScenario
26 InstantiateScenario('Cooperation','../Evolife')
27
28from random import randint, choice
29
30from Evolife.Tools.Tools import error
31from Evolife.Ecology.Group import Group, EvolifeGroup # definition of groups
32
34 """ List of Groups
35 Minimal version
36 """
37
38 def __init__(self, Scenario, Observer):
39 """ Creation of the groups - calls createGroup
40 """
41 self.Scenario = Scenario
42 self.popSize = self.Scenario.Parameter('PopulationSize')
43 self.groupMaxSize = self.popSize + 1
44 self.groups = []
45 self.year = -1 # to keep track of time
46 self.Observer = Observer # contains instantaneous data for statistics and display
47 self.best_score = 0
48 nb_groups = self.Scenario.Parameter('NumberOfGroups', Default=1)
49 group_size = self.popSize // nb_groups
50 self.groupMaxSize = 2 * group_size # groups beyond that size split
51 while (nb_groups > 0):
52 self.groups.append(self.createGroup(ID=nb_groups, Size=group_size))
53 nb_groups -= 1
54 self.statistics(Display=True) # updates popSize
55
56 def createGroup(self, ID=0, Size=0):
57 """ Calls class 'Group'
58 """
59 return Group(self.Scenario, ID=ID, Size=Size)
60
62 """ random selection of an individual in the population
63 """
64 (group, winner) = self.lottery()
65 return group.whoIs(winner)
66
67 def lottery(self):
68 """ random selection of an individual by number in the population
69 """
70 winner = randint(0,self.popSize-1)
71 for gr in self.groups:
72 if gr.size > winner: return (gr,winner)
73 else: winner -= gr.size
74 error(f"Population: wrong population size: {self.popSize}")
75
76 def season(self):
77 """ increments 'year' and calls Oberver's season and groups' season
78 """
79 self.year += 1 # keeps track of time
80 self.Observer.season(self.year)
81 for gr in self.groups: gr.season(self.year)
82
83 def migration(self):
84 """ migration between groups of some percentage of individuals
85 """
86 if len(self.groups) < 2 or self.Scenario.Parameter('MigrationRate', Default=0) == 0:
87 return # no migration if only one group
88 migrants = int(self.Scenario.Parameter('MigrationRate') * self.popSize/100.0 + 0.5)
89 while migrants:
90 (gr_out, migrant) = self.lottery() # choosing the migrant
91 (gr_in,dummy) = self.lottery() # choosing where to go
92 gr_in.receive(gr_out.remove_(migrant)) # symbolically murdered, and then born-again
93 migrants -= 1
94
95 def group_splitting(self):
96 """ groups that are too big are split in two,
97 and groups that are too small are dispersed
98 """
99
102 grps = self.groups[:] # copy of the list, necessary since 'groups' is modified within the loop
103 for gr in grps:
104 if gr.size > self.groupMaxSize:
105 effectif = int(gr.size/2.0 + .5)
106 newgroup = self.createGroup(ID=len(self.groups)+1) # create empty group
107 while effectif:
108 newgroup.receive(gr.remove_(randint(0,gr.size-1))) # symbolically murdered, and then born-again
109 effectif -= 1
110 newgroup.update_()
111 self.groups.append(newgroup)
112
113
116 if self.Scenario.Parameter('GroupMinSize', Default=0) ==0: return # No group minimum size
117 grps = self.groups[:] # copy of the list, necessary since 'groups' is modified within the loop
118 for gr in grps:
119 if gr.size < self.Scenario.Parameter('GroupMinSize'):
120 self.groups.remove(gr)
121 self.popSize -= gr.size # necessary for lottery()
122 # for dummy in gr.members:
123 for dummy in list(gr):
124 try:
125 gr_in = choice(self.groups) # dispersed members join groups independently of their size
126 except IndexError:
127 return # dying population
128
129 gr_in.receive(gr.remove_(0)) # symbolically murdered, and then born-again
130 self.popSize += 1
131
132 def limit(self):
133 """ randomly kills individuals until size is reached
134 """
135
136 self.update()
137 while self.popSize > self.Scenario.Parameter('PopulationSize'):
138 (gr,Unfortunate) = self.lottery()
139 if gr.kill(Unfortunate) is not None:
140 self.popSize -= 1
141 self.update(display=True)
142
143 def update(self, flagRanking = False, display=False):
144 """ updates groups and looks for empty groups
145 """
146 self.popSize = 0 # population size will be recomputed
147 toBeRemoved = []
148 for gr in self.groups:
149 gr.location = self.popSize # useful for separating groups when displaying them on an axis
150 grsize = gr.update_(flagRanking, display=display)
151 if grsize == 0: toBeRemoved.append(gr)
152 self.popSize += grsize
153 for gr in toBeRemoved: self.groups.remove(gr)
154 if self.popSize == 0: error("Population is empty")
155 self.best_score = max([gr.best_score for gr in self.groups])
156 return self.popSize
157
158 def statistics(self, Complete=True, Display=False):
159 """ Updates statistics about the population
160 """
161 self.update(display=Display) # updates facts
162 self.Observer.reset()
163 if Complete:
164 self.Observer.open_()
165 for gr in self.groups:
166 gr.statistics()
167 self.Observer.store(gr.Examiner)
168 self.Observer.close_() # computes statistics in Observer
169
170 def one_year(self):
171 """ One year of life.
172 Calls 'limit', 'migration', 'group_splitting', 'season', 'statistics'
173 """
174 if self.year < 0:
175 # just to get a snapshot of the initial situation
176 self.season() # annual resetting and time increment
177 self.statistics()
178 return True
179 try:
180 self.limit() # some individuals die to limit population size
181 self.migration() # some individuals change group
182 self.group_splitting() # big groups split and small groups are dissolved
183 self.season() # annual resetting and time increment
184 if self.Observer.Visible():
185 self.statistics(Complete=True, Display=True) # compute statistics before reproduction
186 # try: self.Observer.recordInfo('Best', self.groups[0].get_best())
187 # except (IndexError, AttributeError): pass # no record of best individual
188 return True
189 except Exception as Msg:
190 error("Population", str(Msg))
191 return False
192
193 def members(self):
194 """ iterates over all individuals in the population
195 """
196 for gr in self.groups:
197 for i in gr:
198 yield i
199
200 def display(self):
201 """ calling 'display' for all individuals in the population
202 """
203 for i in self.members(): i.display()
204
205 def __str__(self):
206 # printing global statistics
207 # and then a list of groups, one per line
208 return "\n Population Statistics:\n" + \
209 "> Popul: %d members\tbest: %d\tavg: %.02f\tyear: %d\n" \
210 % (self.Observer.Statistics['Properties']['length'],
211 self.Observer.Statistics['Properties']['best'][1],
212 self.Observer.Statistics['Properties']['average'][1], self.year) + \
213 "\n".join(["group %d: %d members\tbest: %d\tavg: %.02f" \
214 % (i, grObs.storages['Properties'].length, grObs.storages['Properties'].best[1],
215 grObs.storages['Properties'].average[1]) \
216 for (i,grObs) in enumerate(self.Observer.storage)]) + "\n"
217
218
219
220
222 """ Population + reproduction + call to Scenario life_game
223 """
224 def __init__(self, Scenario, Evolife_Obs):
225 """ Creation of groups """
226 Population.__init__(self, Scenario, Evolife_Obs)
227 # Possibility of intialiazing genomes from file
228 if self.Scenario.Parameter('StartFromFile', Default=0):
229 StartFile = open('EvoStart.gen','r')
230 self.Observer.TextDisplay('Retrieving population from EvoStart.gen\n')
231 Genomes = StartFile.readlines() # put lines in a list
232 StartFile.close()
233 self.popSizepopSize = len(Genomes) # priority over configuration file
234 for gr in self.groups: gr.uploadDNA(Genomes)
235 else:
236 Genomes = []
237 self.statistics() # updates popSize
238
239 def createGroup(self, ID=0, Size=0):
240 """ This version of 'createGroup' calls the 'EvolifeGroup' class instead of the 'Group' class
241 """
242 return EvolifeGroup(self.Scenario, ID=ID, Size=Size)
243
244 def reproduction(self):
245 """ launches reproduction in groups
246 """
247 for gr in self.groups:
248 gr.reproduction()
249 self.update()
250
251 def life_game(self):
252 """ Calls local 'life_game' in groups
253 """
254 for gr in self.groups:
255 gr.life_game()
256
257 def one_year(self):
258 """ Population's 'one_year' + calls to 'reproduction' and 'life_game'
259 """
260 if self.year >= 0:
261 self.reproduction() # reproduction depends on scores
262 self.life_game() # where individual earn their score
263 Res = Population.one_year(self)
264 return Res
265
266
267if __name__ == "__main__":
268 print(__doc__)
269 print(Population.__doc__ + '\n\nTest:\n')
270
271
272
275
276if __name__ == "__main__":
277 from Evolife.Ecology.Observer import Meta_Observer
278 Obs = Meta_Observer('PopObs')
279 Pop = Population(Obs)
280 print(Pop)
281
282 for ii in range(16):
283 Pop.one_year()
284 print(Pop)
285 raw_input('[Return]')
286
287__author__ = 'Dessalles'
class Group: list of individuals that interact and reproduce.
Definition: Group.py:162
A group is mainly a list of individuals.
Definition: Group.py:37
Population + reproduction + call to Scenario life_game.
Definition: Population.py:221
def one_year(self)
Population's 'one_year' + calls to 'reproduction' and 'life_game'.
Definition: Population.py:257
def life_game(self)
Calls local 'life_game' in groups.
Definition: Population.py:251
def reproduction(self)
launches reproduction in groups
Definition: Population.py:244
def createGroup(self, ID=0, Size=0)
This version of 'createGroup' calls the 'EvolifeGroup' class instead of the 'Group' class.
Definition: Population.py:239
def __init__(self, Scenario, Evolife_Obs)
Creation of groups.
Definition: Population.py:224
List of Groups Minimal version
Definition: Population.py:33
def group_splitting(self)
groups that are too big are split in two, and groups that are too small are dispersed
Definition: Population.py:95
def season(self)
increments 'year' and calls Oberver's season and groups' season
Definition: Population.py:76
def createGroup(self, ID=0, Size=0)
Calls class 'Group'.
Definition: Population.py:56
def lottery(self)
random selection of an individual by number in the population
Definition: Population.py:67
def migration(self)
migration between groups of some percentage of individuals
Definition: Population.py:83
def selectIndividual(self)
random selection of an individual in the population
Definition: Population.py:61
Groups are lists of Individuals.
Definition: Group.py:1
Gets data from various modules and stores them for display and statistics.
Definition: Observer.py:1
def one_year(self)
One year of life.
Definition: Population.py:170
def limit(self)
(gr_in,dummy) = self.lottery() # choosing where to go
Definition: Population.py:132
def update(self, flagRanking=False, display=False)
updates groups and looks for empty groups
Definition: Population.py:143
def members(self)
iterates over all individuals in the population
Definition: Population.py:193
def statistics(self, Complete=True, Display=False)
Updates statistics about the population.
Definition: Population.py:158
def display(self)
calling 'display' for all individuals in the population
Definition: Population.py:200
Various functions.
Definition: Tools.py:1
def error(ErrMsg, Explanation='')
Definition: Tools.py:182