Evolife
Evolife has been developed to study Genetic algorithms, Natural evolution and behavioural ecology.
Landscape.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2""" @brief This class defines a 2D square grid on which agents can move.
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
21import random
22
23# class MySet(set):
24 # " set without duplicates "
25 # def append(self, Item): self.add(Item)
26
27 # def list(self): return list(self)
28
30 """ Defines what's in one location on the ground
31 """
32 def __init__(self, Content=None, VoidCell=None):
33 self.VoidCell = VoidCell
34 self.Present = None # actual content
35 self.Future = None # future content
36 self.Previous = None
37 self.setContent(Content, Future=False)
38
39 def Content(self, Future=False): return self.Future if Future else self.Present
40
41 def free(self): return (self.Present == self.VoidCell)
42
43 def clean(self): return self.setContent(self.VoidCell)
44
45 def setContent(self, Content=None, Future=False):
46 if Content is None: Content = self.VoidCell
47 self.Previous = (self.Present, self.Future)
48 if Future and Content != self.Future:
49 self.Future = Content # here Present and Future diverge
50 return True # content changed
51 elif Content != self.Present:
52 self.Present = Content
53 self.Future = Content
54 return True # content changed
55 return False # content unchanged
56
57 def activated(self, Future=False): # indicates relevant change
58 """ tells whether a cell is active
59 """
60 return self.Content(Future=Future) != self.Previous[1*Future]
61
62 def Update(self):
63 self.Present = self.Future # erases history
64 return self.free()
65
66 def __str__(self): return str(self.Content())
67
69 """ A 2-D square toric grid
70 """
71 def __init__(self, Width=100, Height=0, CellType=LandCell):
72 self.ContentType = None # may constrain what content can be
73 self.Size = Width
74 self.Width = Width
75 if Height > 0: self.Height = Height
76 else: self.Height = self.Size
77 self.Dimension = (self.Width, self.Height)
78 self.Ground = [[CellType() for y in range(self.Height)] for x in range(self.Width)]
79 # self.Ground = [[CellType() for y in range(self.Width)] for x in range(self.Height)]
80 self.ActiveCells = set() # list of Positions that have been modified
81 self.Statistics = {} # will contain lists of cells for each content type
82
83 def setAdmissible(self, ContentType):
84 self.ContentType = ContentType
85 self.statistics() # builds lists of cell per content
86
87 def Admissible(self, Content):
88 if self.ContentType: return (Content in self.ContentType)
89 return True
90
91 def ToricConversion(self, P):
92 # toric landscape
93 (x,y) = P
94 return (x % self.Width, y % self.Height)
95
96 def Modify(self, P, NewContent, check=False, Future=False):
97 """ Changes content at a location
98 """
99 # print 'Modyfying', (x,y), NewContent
100 (x,y) = P
101 Cell = self.Ground[x][y]
102 if check:
103 if NewContent is not None: # put only admissible content only at free location
104 if not self.Admissible(NewContent) or not Cell.free():
105 return False
106 else: # erase only when content is already there
107 if Cell.free(): return False
108 Cell.setContent(NewContent, Future=Future)
109 if Cell.activated(Future=Future):
110 # only activate cells that have actually changed state
111 self.ActiveCells.add((x,y))
112 return True
113
114 def Content(self, P, Future=False):
115 (x,y) = P
116 return self.Ground[x][y].Content(Future=Future)
117
118 def Cell(self, P):
119 (x,y) = P
120 try: return self.Ground[x][y]
121 except IndexError:
122 raise Exception('Bad coordinates for Cell (%d, %d)' % (x,y))
123
124 def __getitem__(self, P): return self.Cell(P)
125
126 def free(self, P):
127 (x,y) = P
128 return self.Ground[x][y].free() # checks whether cell is free
129
130 def neighbourhoodLength(self, Radius=1):
131 return (2*Radius+1) ** 2 - 5 # square minus self and four corners
132
133 def neighbours(self, P, Radius=1):
134 """ returns neighbouring cells
135 """
136 # N = []
137 (x,y) = P
138 for distx in range(-Radius, Radius+1):
139 for disty in range(-Radius, Radius+1):
140 if distx == 0 and disty == 0: continue # self not in neighbourhood
141 if abs(distx) + abs(disty) == 2 * Radius: continue # corners not in neighbourhood
142 # N.append(self.ToricConversion((x + distx, y + disty)))
143 yield self.ToricConversion((x + distx, y + disty))
144 # return N
145
146 def segment(self, P0, P1):
147 (x0,y0) = P0
148 (x1,y1) = P1
149 """ computes the shorter segment between two points on the tore
150 """
151 (vx,vy) = self.ToricConversion((x0-x1, y0-y1))
152 (wx,wy) = self.ToricConversion((x1-x0, y1-y0))
153 return (min(vx,wx), min(vy,wy))
154
155 def InspectNeighbourhood(self, Pos, Radius):
156 """ Makes statistics about local content
157 Returns a dictionary by Content.
158 The center position is omitted
159 """
160 Neighbourhood = self.neighbours(Pos, Radius)
161 if self.ContentType:
162 LocalStatistics = {x:0 for x in self.ContentType} # {Content_1:0, Content_2:0...}
163 else: LocalStatistics = dict()
164 for NPos in Neighbourhood:
165 if self.Content(NPos):
166 if self.Content(NPos) in LocalStatistics: LocalStatistics[self.Content(NPos)] += 1
167 else: LocalStatistics[self.Content(NPos)] = 1
168 return LocalStatistics
169
170 def statistics(self):
171 """ scans ground and builds lists of cells depending on Content
172 """
173 if self.ContentType:
174 self.Statistics = {x:[] for x in (self.ContentType + [None])} # {Content_1:[], Content_2:[]..., None:[]}
175 for (Pos, Cell) in self.travel():
176 if Cell.Content() in self.Statistics:
177 self.Statistics[Cell.Content()].append(Pos)
178 else:
179 self.Statistics[Cell.Content()] = [Pos]
180
181 def update(self):
182 """ updates the delayed effect of cells that have been modified
183 """
184 CurrentlyActiveCells = list(self.ActiveCells)
185 # print '\n%d active cells' % len(CurrentlyActiveCells)
186 # setting active cells to their last state
187 for Pos in CurrentlyActiveCells:
188 if self.Cell(Pos).Update(): # erases history, keeps last state - True if cell ends up empty
189 self.ActiveCells.remove(Pos) # cell is empty
190 # self.ActiveCells.reset()
191
192 def activation(self):
193 """ Active cells produce their effect
194 """
195 for Pos in list(self.ActiveCells):
196 self.activate(Pos) # may activate new cells
197
198 def activate(self, Pos):
199 """ Cell located at position 'Pos' has been modified and now produces its effect, possibly on neighbouring cells
200 """
201 pass # to be overloaded
202
203 def randomPosition(self, Content=None, check=False):
204 """ picks an element of the grid with 'Content' in it
205 """
206 if check and self.Statistics:
207 for ii in range(10): # as Statistics may not be reliable
208 if self.Statistics[Content]:
209 Pos = random.choice(self.Statistics[Content])
210 if self.Content(Pos) == Content: # should be equal if statistics up to date
211 return Pos
212 # at this point, no location found with Content in it - Need to update
213 self.statistics()
214 else: # blind search
215 for ii in range(10):
216 Row = random.randint(0,self.Height-1)
217 Col = random.randint(0,self.Width-1)
218 if check:
219 if self.Content((Col,Row)) == Content: return (Col, Row)
220 else: return (Col, Row)
221 return None
222
223 def travel(self):
224 """ Iteratively returns Cells of the grid
225 """
226 Pos = 0 # used to travel over the grid
227 for Col in range(self.Width):
228 for Row in range(self.Height):
229 try: yield ((Col,Row), self.Ground[Col][Row])
230 except IndexError:
231 print(self.Width, self.Height)
232 print(Col, Row, Pos)
233 raise
234
235
237 """ Same as LandCell, plus a third dimension
238 """
239 def __init__(self, Altitude=0, Content=None, VoidCell=None):
240 LandCell.__init__(self, Content=Content, VoidCell=VoidCell)
241 self.Altitude = Altitude
242
244 """ Same as Landscape, but stores a third dimension in cells
245 """
246 def __init__(self, Altitudes=[], AltitudeFile='', CellType=LandCell_3D):
247 Height = len(Altitudes) # number of rows
248 if Height == 0:
249 # Altitudes are retrieved from file
250 Rows = open(AltitudeFile).readlines()
251 # Altitudes = [Row.split() for Row in Rows if len(Row.split()) > 1 ]
252 Altitudes = [list(map(int,Row.split())) for Row in Rows]
253 Height = len(Altitudes) # number of rows
254 Width = len(Altitudes[0]) # assuming rectangular array correct
255 print('Ground = %d x %d' % (Width, Height))
256 Landscape.__init__(self, Width=Width, Height=Height, CellType=CellType)
257 for ((Col,Row), Cell) in self.travel():
258 try:
259 Cell.Altitude = Altitudes[Height-1-Row][Col]
260 except IndexError:
261 print(Width, Height)
262 print(Col, Row)
263 raise
264
265
266
267if __name__ == "__main__":
268 print(__doc__)
269
270
271
272__author__ = 'Dessalles'
Same as LandCell, plus a third dimension.
Definition: Landscape.py:236
def __init__(self, Altitude=0, Content=None, VoidCell=None)
Definition: Landscape.py:239
Defines what's in one location on the ground.
Definition: Landscape.py:29
def Content(self, Future=False)
Definition: Landscape.py:39
def setContent(self, Content=None, Future=False)
Definition: Landscape.py:45
def activated(self, Future=False)
tells whether a cell is active
Definition: Landscape.py:57
def __init__(self, Content=None, VoidCell=None)
Definition: Landscape.py:32
Same as Landscape, but stores a third dimension in cells.
Definition: Landscape.py:243
def __init__(self, Altitudes=[], AltitudeFile='', CellType=LandCell_3D)
Definition: Landscape.py:246
A 2-D square toric grid.
Definition: Landscape.py:68
def travel(self)
Iteratively returns Cells of the grid.
Definition: Landscape.py:223
def statistics(self)
scans ground and builds lists of cells depending on Content
Definition: Landscape.py:170
def setAdmissible(self, ContentType)
Definition: Landscape.py:83
def activation(self)
Active cells produce their effect.
Definition: Landscape.py:192
def update(self)
updates the delayed effect of cells that have been modified
Definition: Landscape.py:181
def neighbours(self, P, Radius=1)
returns neighbouring cells
Definition: Landscape.py:133
def Content(self, P, Future=False)
Definition: Landscape.py:114
def __init__(self, Width=100, Height=0, CellType=LandCell)
Definition: Landscape.py:71
def Modify(self, P, NewContent, check=False, Future=False)
Changes content at a location.
Definition: Landscape.py:96
def randomPosition(self, Content=None, check=False)
picks an element of the grid with 'Content' in it
Definition: Landscape.py:203
def activate(self, Pos)
Cell located at position 'Pos' has been modified and now produces its effect, possibly on neighbourin...
Definition: Landscape.py:198
def neighbourhoodLength(self, Radius=1)
Definition: Landscape.py:130
def InspectNeighbourhood(self, Pos, Radius)
Makes statistics about local content Returns a dictionary by Content.
Definition: Landscape.py:155