Evolife
Evolife has been developed to study Genetic algorithms, Natural evolution and behavioural ecology.
Default_Scenario.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2""" @brief Determines how individuals acquire their score, either by themselves or through interactions.
3
4 Evolife scenarii may rewrite several functions defined here :
5 (those marked with '+' are called from 'Group.py')
6 (those marked with 'o' are called from 'Observer.py')
7
8 - initialization(self): allows to define local variables
9 - genemap(self): initialises the genes on the gene map (see 'Genetic_map.py')
10 - phenemap(self): defines a list of phenotypic character names (see 'Phenotype.py')
11 + season(self, year, members): makes periodic actions like resetting parameters
12 + behaviour(self, BestIndiv, AvgIndiv): defines a behaviour to be displayed
13 + life_game(self, members): defines a round of interactions - calls the five following functions
14 - start_game(self, members): group-level initialization before starting interactions
15 - prepare(self, indiv): individual initialization before starting interactions
16 - interaction(self, Indiv, Partner): defines a single interaction
17 - partner(self, Indiv, members): select a partner among 'members' that will interact with 'Indiv'
18 - end_game(self, members): an occasion for a closing round after all interactions
19 - evaluation(self, Indiv): defines how the score of an individual is computed
20 - lives(self, members): converts scores into life points
21 + couples(self, members): returns a list of couples for procreation (individuals may appear in several couples!)- Calls the following functions:
22 - parenthood(self, RankedCandidates, Def_Nb_Children): Determines the number of children depending on rank
23 - parents(self, candidates): selects two parents from a list of candidates (candidate = (indiv, NbOfPotentialChildren))
24 + new_agent(self, child, parents): initializes newborns
25 + remove_agent(self, agent): action to be performed when an agent dies
26 + update_positions(self, members, groupID): assigns a position to agents
27 o default_view(self): says which windows should be open at start up
28 o legends(self): returns a string to be displayed at the bottom ot the Legend window.
29 o display_(self): says which statistics are displayed each year
30 o def Field_grid(self): initial draw in the Field window
31 o def Trajectory_grid(self): initial draw in the Trajectory window
32 o wallpaper(self, Window): if one wants to display different backgrounds in windows
33
34 ************
35"""
36
37#============================================================================#
38# EVOLIFE http://evolife.telecom-paris.fr Jean-Louis Dessalles #
39# Telecom Paris 2022-04-11 www.dessalles.fr #
40# -------------------------------------------------------------------------- #
41# License: Creative Commons BY-NC-SA #
42#============================================================================#
43# Documentation: https://evolife.telecom-paris.fr/Classes #
44#============================================================================#
45
46
47
50
51 # This file shouldn't be edited - Rather edit particular scenarios #
52
53
54import sys
55if __name__ == '__main__': sys.path.append('../..') # for tests
56
57import random
58
59from Evolife.Scenarii.Parameters import Parameters
60from Evolife.Genetics.Genetic_map import Genetic_map
61from Evolife.Tools.Tools import decrease, chances
62
64 """ All functions defined here can be
65 overloaded in specific scenarii (see module doc)
66 """
67 def __init__(self, Name='Default scenario', CfgFile=''):
68 """ Loads parameters, sets gene map and calls local initialization.
69 """
70 self.Name = Name
71 # loading parameter values
72 if CfgFile == '': CfgFile = self.Name + '.evo'
73 try: Parameters.__init__(self,CfgFile)
74 except IOError:
75 print("%s -- File not found." % CfgFile)
76 CfgFile = 'Evolife.evo'
77 print("Loading parameters from %s" % CfgFile)
78 Parameters.__init__(self,CfgFile)
79
80 # option for deterministic evolution
81 if self.Parameter('RandomSeed', Default=0) > 0: random.seed(Gbl.Parameter('RandomSeed'))
82
83 # creating the genetic map
84 Genetic_map.__init__(self, self.genemap())
85 self.initialization()
86
87 def initialization(self):
88 """ local initialization, to be overloaded
89 """
90 self.ALocalQuantity = 42 # a quantity that can be modified anywhere within the scenario
91
92 def genemap(self):
93 """ Defines the name of genes and their position on the DNA.
94 Accepted syntax:
95 - ['genename1', 'genename2',...]: lengths and coding are retrieved from configuration.
96 - [('genename1', 8), ('genename2', 4),...]: numbers give lengths in bits; coding is retrieved from configuration.
97 - [('genename1', 8, 'Weighted'), ('genename2', 4, 'Unweighted'),...]: coding can be 'Weighted', 'Unweighted', 'Gray', 'NoCoding'.
98 Note that 'Unweighted' is unsuitable to explore large space.
99 """
100 return [('gene1',16),('gene2',4)]
101
102 def phenemap(self):
103 """ Defines the set of non inheritable characteristics
104 """
105 # return ['Feature']
106 return []
107
108 def behaviour(self, best_individual, avg_individual):
109 """ returns information about the phenotype of a given individual
110 (best individual or fictitious individual with average genome)
111 for display purposes (e.g. a trajectory in a maze)
112 """
113 return 0
114
115
119 def prepare(self, indiv):
120 """ defines what is to be done at the individual level before interactions
121 occur - Used in 'start_game'
122 """
123 pass
124
125 def start_game(self, members):
126 """ defines what is to be done at the group level each year
127 before interactions occur - Used in 'life_game'
128 """
129 for indiv in members: self.prepare(indiv)
130
131 def evaluation(self, indiv):
132 """ Implements the computation of individuals' scores - - Used in 'life_game'
133 """
134 #Typically: indiv.score(SOME VALUE, FlagSet=True) (FlagSet=False --> value is added to score instead of replacing it)
135 # Note: scores should always be kept positive
136 pass
137
138 def partner(self, indiv, members):
139 """ Decides whom to interact with - Used in 'life_game'
140 """
141 # By default, a partner is randomly chosen
142 partners = members[:]
143 partners.remove(indiv)
144 if partners != []:
145 return random.choice(partners)
146 else:
147 return None
148
149 def interaction(self, indiv, partner):
150 """ Nothing by default - Used in 'life_game'
151 """
152 pass
153
154 def end_game(self, members):
155 """ defines what to do at the group level once all interactions
156 have occurred - Used in 'life_game'
157 """
158 pass
159
160
161 def life_game(self, members):
162 """ Life games (or their components) are defined in specific scenarii
163 life_games calls:
164 - start_game (which calls 'prepare')
165 - interaction (which calls 'partner')
166 - end_game
167 - evaluation
168 - lives
169 """
170 # First: make initializations
171 self.start_game(members)
172 # Then: play multipartite games
173 for play in range(self.Parameter('Rounds', Default=1)):
174 players = members[:] # ground copy
175 random.shuffle(players)
176 # Individuals engage in several interactions successively
177 for indiv in players:
178 Partner = self.partner(indiv, players)
179 if Partner is not None:
180 self.interaction(indiv, Partner)
181 # Lastly: work out
182 self.end_game(members)
183 # Alternatively (or successively): play individual games
184 for indiv in members:
185 self.evaluation(indiv)
186 # scores are translated into life points
187 self.lives(members)
188
189 def lives(self, members):
190 """ converts scores into life points
191 """
192 if self.Parameter('SelectionPressure') == 0:
193 return
194 if len(members) == 0:
195 return
196 BestScore = max([i.score() for i in members])
197 MinScore = min([i.score() for i in members])
198 if BestScore == MinScore: return
199 for indiv in members:
200 # translating scores to zero and above
201 indiv.LifePoints = (self.Parameter('SelectionPressure') \
202 * (indiv.score() - MinScore))/float(BestScore - MinScore)
203 return
204
205
206 def season(self, year, members):
207 """ This function is called at the beginning of each year
208 """
209 pass
210
211 def parenthood(self, RankedCandidates, Def_Nb_Children):
212 """ Determines the number of children that would-be parents may have
213 depending on their rank
214 """
215 candidates = [[m,0] for m in RankedCandidates]
216 # parenthood is distributed as a function of the rank
217 # it is the responsibility of the caller to rank members appropriately
218 # Note: reproduction_rate has to be doubled, as it takes two parents to beget a child
219 for ParentID in enumerate(RankedCandidates):
220 candidates[ParentID[0]][1] = chances(decrease(ParentID[0],len(RankedCandidates),
221 self.Parameter('Selectivity')), 2 * Def_Nb_Children)
222 # print(self.Parameter('Selectivity'))
223 # print([chances(decrease(ii,len(RankedCandidates), self.Parameter('Selectivity')), 2 * Def_Nb_Children) for ii in range(20)])
224 return candidates
225
226 def parents(self, candidates):
227 """ Selects one couple from candidates.
228 Candidates are (indiv, NbChildren) pairs, where NbChildren indicates the number of
229 children that indiv can still have
230 """
231 try:
232 return random.sample(candidates, 2)
233 except ValueError: return None
234
235 def couples(self, members, nb_children=-1):
236 """ Returns a set of couples that will beget newborns.
237 Note that a given individual may appear several times.
238 By default, the probability for an individual to be in a
239 couple (and thus to have a child) decreases with its rank
240 in 'members'
241 """
242
243 if nb_children < 0: # the number of children may be imposed (e.g. in s_gazelle)
244 nb_children = chances(self.Parameter('ReproductionRate') / 100.0, len(members))
245
246 candidates = self.parenthood(members, nb_children)
247 # print(candidates)
248
249 Couples = []
250 # print candidates[:10]
251 for ii in range(nb_children):
252 Couple = self.parents([p for p in candidates if p[1] > 0]) # selects two parents from the list of candidates
253 if Couple:
254 (mother, father) = Couple
255 Couples.append((mother[0],father[0]))
256 mother[1] -= 1
257 father[1] -= 1
258 else: break
259 return Couples
260
261 def new_agent(self, child, parents):
262 """ initializes newborns - parents==None when the population is created
263 """
264 return True
265
266
267 def remove_agent(self, agent):
268 """ action to be performed when an agent dies
269 """
270 pass
271
272 def update_positions(self, members, groupLocation):
273 """ Allows to define spatial coordinates for individuals.
274 These positions are displayed in the Field window.
275 Coordinates are typically (x,y,c) where c (optional)
276 is the colour representing the agent
277 """
278 for nbr, indiv in enumerate(members):
279 indiv.location = (groupLocation + nbr, 17, 'red')
280
281 def default_view(self):
282 """ Defines which windows should be open when the program starts
283 Example: ['Genomes', 'Field', ('Trajectories', 320), ('Network', 500, 200)]
284 optional numbers provide width and height
285 """
286 return []
287
288 def legends(self):
289 """ The returned string will be displayed at the bottom ot the Legend window.
290 Useful to describe what is to be seen in the various windows.
291 """
292 L = '<u>Genomes</u>:<P>'
293 L += 'genes from left to right: %s<br>' % (', '.join([g[0] if isinstance(g, tuple) else g for g in self.genemap()]))
294 L += 'Each horizontal line represents the genome of an individual.'
295 return L
296
297 def display_(self):
298 """ Defines what is to be displayed. It offers the possibility
299 of plotting the evolution through time of the best score,
300 the average score, any locally defined value,
301 and the average value of the various genes and phenes.
302 It should return a list of pairs (C, X) or triples (C, X, L)
303 where C is the curve colour (colour name or number)
304 and X can be 'best', 'average', 'ALocalQuantity'
305 (where ALocalQuantity is a local variable)
306 or any gene name defined in genemap
307 or any phene defined in phenemap.
308 L (optional) is a legend string
309 """
310 # The default behaviour is to display all genes of GeneMap
311 disp = [(i+1,G.name) for (i,G) in enumerate(self.GeneMap)]
312
313 # and all phenes of phenemap
314 L = len(disp)
315 disp += [(L+i+1,G) for (i,G) in enumerate(self.phenemap())]
316
317 # and a locally defined quantity
318 # L = len(disp)
319 # disp += [(L+1,'ALocalQuantity')]
320 return disp
321
322 def wallpaper(self, Window):
323 """ Displays background image or colour when the window is created.
324 """
325 # Possible windows are: 'Field', 'Curves', 'Genome', 'Log', 'Help', 'Trajectories', 'Network'
326 if Window == 'Help': return 'Graphics/EvolifeBG.png'
327 return None
328
329 def Field_grid(self):
330 """ returns a list of graphic orders for initial display on the Field window
331 """
332 return []
333
334 def Trajectory_grid(self):
335 """ returns a list of graphic orders for initial display on the Trajectory window
336 """
337 return []
338
339 def __str__(self):
340 return self.Name
341
342
343
344
347
348if __name__ == "__main__":
349 print(__doc__ + '\n')
350 input('[Return]')
351
352
353__author__ = 'Dessalles'
a Genetic_map is a series of genes, located one after the other
Definition: Genetic_map.py:51
All functions defined here can be overloaded in specific scenarii (see module doc)
def end_game(self, members)
defines what to do at the group level once all interactions have occurred - Used in 'life_game'
def new_agent(self, child, parents)
initializes newborns - parents==None when the population is created
def default_view(self)
Defines which windows should be open when the program starts Example: ['Genomes', 'Field',...
def couples(self, members, nb_children=-1)
Returns a set of couples that will beget newborns.
def life_game(self, members)
Life games (or their components) are defined in specific scenarii life_games calls:
def Trajectory_grid(self)
returns a list of graphic orders for initial display on the Trajectory window
def remove_agent(self, agent)
action to be performed when an agent dies
def wallpaper(self, Window)
Displays background image or colour when the window is created.
def legends(self)
The returned string will be displayed at the bottom ot the Legend window.
def update_positions(self, members, groupLocation)
Allows to define spatial coordinates for individuals.
def genemap(self)
Defines the name of genes and their position on the DNA.
def behaviour(self, best_individual, avg_individual)
returns information about the phenotype of a given individual (best individual or fictitious individu...
def start_game(self, members)
defines what is to be done at the group level each year before interactions occur - Used in 'life_gam...
def interaction(self, indiv, partner)
Nothing by default - Used in 'life_game'.
def display_(self)
Defines what is to be displayed.
def parents(self, candidates)
Selects one couple from candidates.
def prepare(self, indiv)
The following functions are used # internally, called from 'life_game' #.
def Field_grid(self)
returns a list of graphic orders for initial display on the Field window
def lives(self, members)
converts scores into life points
def parenthood(self, RankedCandidates, Def_Nb_Children)
Determines the number of children that would-be parents may have depending on their rank.
def season(self, year, members)
This function is called at the beginning of each year.
def initialization(self)
local initialization, to be overloaded
def evaluation(self, indiv)
Implements the computation of individuals' scores - - Used in 'life_game'.
def partner(self, indiv, members)
Decides whom to interact with - Used in 'life_game'.
def phenemap(self)
Defines the set of non inheritable characteristics.
Definition of genes as DNA segment having semantics.
Definition: Genetic_map.py:1
Various functions.
Definition: Tools.py:1
def chances(proba, N)
computes what one gets from a maximum of N with probability proba
Definition: Tools.py:62
def decrease(x, M, Selection)
Computes a decreasing function of x in [0,M] which sums to 1 1/(x+M) normalized for x in [0,...
Definition: Tools.py:32