Evolife
Evolife has been developed to study Genetic algorithms, Natural evolution and behavioural ecology.
GraphicExample.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2""" @brief This example shows how to use Evolife's graphic system (based on PyQT)
3to run simulations that display images,dots and lines, and curves.
4
5 -----------------------------------------------------------------------------------
6 Evolife provides a window system that you can use for you own simulation.
7
8 * Simulation:
9 - Re-implement the function 'One_Run' in the class 'Population'.
10 This function is repeatedly called by Evolife. It should perform the simulation.
11 - This function typically executes individual behaviour: in this example,
12 function 'move'. You may just re-write the latter.
13
14 * Display
15 Evolife gets instructions for display through the class 'Observer'.
16 Observer should return appropriate data to Evolife's requests, as indicated.
17 This means that the simulation must keep Observer informed of relevant changes.
18
19 * Starting Evolife
20 Evolife can be started with various capabilities for display
21 (curves, dots and lines, genomes, links...) as indicated.
22 -----------------------------------------------------------------------------------
23
24"""
25
26#============================================================================#
27# EVOLIFE http://evolife.telecom-paris.fr Jean-Louis Dessalles #
28# Telecom Paris 2022-04-11 www.dessalles.fr #
29# -------------------------------------------------------------------------- #
30# License: Creative Commons BY-NC-SA #
31#============================================================================#
32# Documentation: https://evolife.telecom-paris.fr/Classes #
33#============================================================================#
34
35
36
39
40
41
42 # Evolife's graphic system recognizes two forms of vectors
43 #
44 # - ((Agent1Id, Coordinates1), (Agent2Id, Coordinates2), ...)
45 # - (Coordinates1, Coordinates2, ...)
46 # The first format allows to move and erase agents
47 # The second format is merely used to draw permanent blobs and lines
48 # Coordinates have the following form:
49 # (x, y, colour, size, ToX, ToY, segmentColour, segmentThickness, 'shape=<form>')
50 # (shorter tuples are automatically continued with default values - 'shape=...' can be inserted anywhere)
51 # The effect is that an object of size 'size' is drawn at location (x,y) (your coordinates, not pixels)
52 # and a segment starting from that blob is drawn to (ToX, ToY) (if these values are given)
53 # If you change the coordinates of an agent in the next call, it will be moved.
54 # 'size' is in pixels and is not resized in case of zoom. However, if negative, it is interpreted in your coordinates and it will be resized/
55 # 'size' may be a fractional number. It is then understood as a fraction of the window size.
56 # The value assigned to 'shape' in the string 'shape=...' can be 'ellipse' (=default) or 'rectangle' or
57 # any image. If it is an image, 'colour' is interpreted as an angle. The image is scaled to fit 'size' (but aspect ratio is preserved)
58 #
59 # These two forms of vectors can be used to draw in two windows: 'Trajectories' and 'Field'.
60 # Use the order: record(vector, Window=<'Field'|'Trajectories'>)
61 # or: record([vectors], Window=<'Field'|'Trajectories'>)
62 # 'Field' is default. The latter order is used to send a list of vectors.
63 #
64 # The 'Field' window comes in two modes, 'F' and 'R' (see below option F and R at Evolife's start)
65 # - In the 'F' mode, all agents should be given positions at each call.
66 # Missing agents are destroyed from display.
67 # - In the 'R' ('Region') mode, you may indicates positions only for relevant agents
68 # To destroy an agent from display, give a negative value to its colour.
69
70
71
72
73from time import sleep
74from random import randint
75
76import sys
77sys.path.append('../..')
78
79from Evolife.Graphics import Evolife_Window
80from Evolife.Ecology import Observer
81from Evolife.Graphics import Curves
82
83
84
85class GrObserver(Observer.Generic_Observer):
86 """ Stores all values that should be displayed
87 May also store general information
88 """
89
90 def __init__(self, TimeLimit):
91 Observer.Generic_Observer.__init__(self, TimeLimit=TimeLimit)
92 # declaration of a curve
93 # Color should be one of Evolife's colours - See Curves.py
94 self.curve(Name='Time', Color='blue', Legend='time step + noise') # declaration of a curve
95 # Then, to add a point to the curve, call:
96 # curve('Time', y)
97 # This adds a point (t, y) to the curve, where t is the current time step
98
99 # the following information will be displayed in the "Legend" window
100 self.recordInfo('WindowLegends', """The "field" window shows moving vectors.<br>The "Trajectories" window shows one rotating segment""")
101
102 def Field_grid(self):
103 """ initial draw: here a green square
104 """
105 return [(5, 5, 'green', 5, 95, 5, 'green', 5),
106 (95, 5, 'green', 5, 95, 95, 'green', 15),
107 (95, 95, 'green', 5 , 5, 95, 'green', 5),
108 (5, 95, 'green', 4, 5, 5, 'green', 5), ] # 'green' becomes a curve, and '4' (last point size) here will be its eventual thickness in case of redraw
109 # to draw lines with various dot sizes and segment thickness, name the segments
110 # [(segm1, (5, 5, 'green', 8, 95, 5, 'green', 2)),
111 # (segm2, (95, 5, 'green', 5, 95, 95, 'green', 3),]
112 # and be sure to use 'Region' instead of 'Field' when calling Evolife 'Start'
113
114 def get_data(self, Slot):
115 """ This function is called each time the window wants to update display
116 """
117
118 #--------------------------------------------------------------------------#
119 # Displaying genomes (option G when starting Evolife) #
120 #--------------------------------------------------------------------------#
121 if Slot == 'DNA':
122 # Should return a list (or tuple) of genomes
123 # Each genome is a tuple like (0,1,1,0,1,...)
124 # All genomes should have same length
125 pass
126
127 #--------------------------------------------------------------------------#
128 # Displaying social links (option N (== network) when starting Evolife) #
129 #--------------------------------------------------------------------------#
130 elif Slot == 'Network':
131 # Should return a list (or tuple) of friends
132 # ((Agent1Id, Friends1), (Agent2Id, Friends2), ...)
133 # Agents' Ids should be consistent with agents' positions sent to 'Field'
134 # Friends = list of agents Ids to which the agent is connected
135 # Currently, only links to best friends are displayed
136 pass
137
138 return Observer.Generic_Observer.get_data(self, Slot) # default behaviour
139
140 #--------------------------------------------------------------------------#
141 # Displaying images (option F when starting Evolife) #
142 #--------------------------------------------------------------------------#
143 if Slot == 'Image':
144 # Should return the path to an image file
145 return default
146
147 #--------------------------------------------------------------------------#
148 # Displaying patterns (option T when starting Evolife) #
149 # (same as slot 'Image', but for the 'Trajectories' window) #
150 #--------------------------------------------------------------------------#
151 elif Slot == 'Pattern':
152 # Should return the path to an image file
153 return default
154
155 else: return Observer.Generic_Observer.get_info(self, Slot, default=default) # basic behaviour
156
157
158class Agent:
159 """ class Agent: defines what an individual consists of
160 """
161 def __init__(self, IdNb):
162 self.ID = "A%d" % IdNb # Identity number
163 # basic colours are displayed at the right of Evolife curve display, from bottom up. Otherwise, use 'Shade'
164 if IdNb % 2: self.colour = Curves.Shade(IdNb, BaseColour='red', Max=100, darkToLight=False)
165 else: self.colour = Curves.Shade(IdNb, BaseColour='blue', Max=100)
166 self.Size = 5
167 self.Location = (IdNb, randint(0, IdNb), self.colour, self.Size) # (x, y, colour, size)
168
169 def move(self):
170 # Just a brownian vertical movement
171 # Coordinates: (x, y, colour, size, toX, toY, segmentColour, segmentThickness)
172 self.Location = (self.Location[0], max(10, self.Location[1] + randint(-1, 1)), self.colour, self.Size, 50, 10, self.colour, 1)
173
175 """ defines the population of agents
176 """
177 def __init__(self, NbAgents, Observer):
178 """ creates a population of agents
179 """
180 self.Pop = [Agent(IdNb) for IdNb in range(NbAgents)]
181 self.Obs = Observer
182 self.MouseEvent = None # remembers mouse clicks
183
184 def __iter__(self): return iter(self.Pop) # allows to loop over Population
185
186 def positions(self):
187 return {A.ID:A.Location for A in self.Pop}
188
189 #----------------------------------------------#
190 # This function is run at each simulation step #
191 #----------------------------------------------#
192 def one_year(self):
193 self.Obs.StepId += 1 # updates simulation step in Observer
194 for agent in self.Pop: agent.move() # whatever behaviour one wants to simulate
195
196 # Storing agents' positions for display
197 self.Obs.record(list(self.positions().items()), Window='Field') # let Observer know position changes
198 # Storing the ant's position for display
199 self.Obs.record(('ant', (self.Obs.StepId % 80, 105, 0, 0.13, 'shape=ant.gif')), Window='Field') # 0.13 == 13% of window size
200
201 # display in window Trajectory
202 self.Obs.record(('s1',(40 + 20*(((1+self.Obs.StepId) % 4)//2), 60 - 20 * ((self.Obs.StepId % 4)//2), 'brown', 1, 50, 50, 'brown', 3)), Window='Trajectories')
203
204
205 # Displaying curve
206 self.Obs.curve('Time', self.Obs.StepId + randint(0,20)) # draws noisy increasing line
207 # Other syntax with explicit x-value:
208 # self.Obs.curve('Time', (self.Obs.StepId, self.Obs.StepId + randint(0,20))) # draws noisy increasing line
209
210 MC = self.Obs.get_info('MouseClick', erase=True)
211 if MC:
212 self.MouseEvent = MC
213 print(MC)
214 self.Obs.record(('mouse', MC[1] + ('blue', 10)),
215 Window='Field' if MC[0] == 'Field_window' else 'Trajectories')
216
217
218 return True
219
220
221def Start():
222 Obs = GrObserver(TimeLimit=10000) # Observer stores graphic orders and performs statistics
223 Obs.setOutputDir('___Results') # curves, average values and screenshots will be stored there
224 Obs.recordInfo('Background', 'yellow') # windows will have this background by default
225 # Background could be 'yellow' or 'toto.jpg' or '11' or '#F0B554'
226 Obs.recordInfo('CurvesWallpaper', '../Graphics/EvolifeBG.png')
227 Obs.recordInfo('TrajectoriesWallpaper', '../Graphics/EvolifeBG.png')
228 Obs.recordInfo('TrajectoriesTitle', 'Trajectory window')
229 Obs.recordInfo('FieldTitle', 'Field window')
230 Obs.recordInfo('DefaultViews', ['Field', ('Trajectories', 300, 240)]) # Evolife should start with these windows open - these sizes are in pixels
231 # other syntax for default views: ('Trajectories', 400, 600, 300, 240)
232
233
234 Pop = Population(NbAgents=100, Observer=Obs) # population of agents
235
236 #--------------------------------------------------------------------------#
237 # Start: launching Evolife window system #
238 #--------------------------------------------------------------------------#
239 # Start(Callback function, Observer, Capabilities, Options)
240 # Callback function: this locally defined function is called by Evolife at each time step
241 # Observer: locally defined
242 # Capabilities: string containing any of the following letters
243 # C = Curves
244 # F = Field (2D seasonal display) (excludes R)
245 # G = Genome display
246 # L = Log Terminal (not implemented)
247 # N = social network display
248 # P = Photo (screenshot)
249 # R = Region (2D ongoing display) (excludes F)
250 # T = Trajectory display (requires additional implementation in each case)
251
252 Evolife_Window.Start(
253 Pop.one_year,
254 Obs,
255 Capabilities='RCPT',
256 Options = {'Run':True}
257 )
258
259
260
261if __name__ == "__main__":
262 print(__doc__)
263 Start()
264 print("Bye.......")
265 sleep(1.1)
266
267
268__author__ = 'Dessalles'
class Agent: defines what an individual consists of
Stores all values that should be displayed May also store general information.
def get_data(self, Slot)
This function is called each time the window wants to update display.
def Field_grid(self)
initial draw: here a green square
defines the population of agents
def __init__(self, NbAgents, Observer)
creates a population of agents