Advertisement
Guest User

Untitled

a guest
Nov 21st, 2019
809
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.41 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. ##############################################################################
  3. # EVOLIFE  http://evolife.telecom-paristech.fr         Jean-Louis Dessalles  #
  4. # Telecom Paris  2019                                      www.dessalles.fr  #
  5. # -------------------------------------------------------------------------- #
  6. # License:  Creative Commons BY-NC-SA                                        #
  7. ##############################################################################
  8.  
  9. ##############################################################################
  10. # Ants                                                                       #
  11. ##############################################################################
  12.  
  13. """ Collective foraging:
  14. Though individual agents follow erratic paths to find food,
  15. the collective may discover optimal paths.
  16. """
  17.  
  18. # In this story, 'ants' move in search for food
  19. # In the absence of pheromone, ants move randomly for some time,
  20. # and then head back toward the colony.
  21. # When they find food, they return to the colony while laying down pheromone.
  22. # If they find pheromone, ants tend to follow it.
  23.  
  24.  
  25. import sys
  26. from time import sleep
  27. import random
  28.        
  29. sys.path.append('..')
  30. sys.path.append('../../..')
  31. import Evolife.Scenarii.Parameters          as EPar
  32. import Evolife.Ecology.Observer             as EO
  33. import Evolife.QtGraphics.Evolife_Window    as EW
  34. import Evolife.Tools.Tools                  as ET
  35. import Landscapes
  36.  
  37. # two functions to convert from complex numbers into (x,y) coordinates
  38. c2t = lambda c: (int(round(c.real)),int(round(c.imag))) # converts a complex into a couple
  39. t2c = lambda P: complex(*P) # converts a couple into a complex
  40.  
  41. #################################################
  42. # Aspect of ants, food and pheromons on display
  43. #################################################
  44. AntAspect = (0, -3, 'shape=ant_stencil.gif')    # -3 = size in logical coordinates
  45. AntAspectWhenLaden = (180, -3, 'shape=ant_stencil_red.gif') # -3 = size in logical coordinates
  46. # AntAspectWhenLaden = ('red', 18)
  47. FoodAspect = ('yellow', 14)
  48. FoodDepletedAspect = ('brown', 11)
  49. ObstacleAspect = ('red', 11)
  50. ObstacleAspect2 = ('black', 11)
  51. #(0,-6, 'shape = anry.png')
  52. PPAspect = (17, 2)  # 17th colour
  53. NPAspect = ('blue', 1)
  54.    
  55.  
  56. class LandCell(Landscapes.LandCell):
  57.     """ Defines what's in one location on the ground
  58.     """
  59.  
  60.     # Cell content is defined as a triple  (Food, NegativePheromon, PositivePheromon)
  61.  
  62.     def __init__(self, F=0, NP=0, PP=0,OB = 0):
  63.         Landscapes.LandCell.__init__(self, (0, 0), VoidCell=(0, 0, 0))
  64.         self.setContent((F,NP,PP,OB))
  65.  
  66.     def clean(self):   
  67.         return self.setContent((self.Content()[0],0,0,0))
  68.  
  69.     def food(self, addendum=0):
  70.         (F,NP,PP,OB) = self.Content()
  71.         if addendum:    self.setContent((F + addendum, NP, PP,OB))
  72.         return F + addendum  
  73.    
  74.     def np(self, addendum=0):  
  75.         (F,NP,PP,OB) = self.Content()
  76.         if addendum:    self.setContent((F, self.limit(NP + addendum), PP,OB))
  77.         return NP + addendum
  78.  
  79.     def pp(self, addendum=0):  
  80.         (F,NP,PP, OB) = self.Content()
  81.         if addendum:    self.setContent((F, NP, self.limit(PP + addendum),OB))
  82.         return PP + addendum
  83.    
  84.     def ob(self, addendum=0):  
  85.         (F,NP,PP,OB) = self.Content()
  86.         if addendum:    self.setContent((F, self.limit(NP + addendum),PP, self.limit(OB + addendum)))
  87.         return OB + addendum
  88.  
  89.     def limit(self, Pheromone):
  90.         return min(Pheromone, Gbl.Parameter('Saturation'))
  91.        
  92.     # def __add__(self, Other):
  93.         # # redefines the '+' operation between cells
  94.         # return LandCell(self.food()+Other.food(),
  95.                         # self.limit(self.np() + Other.np()),
  96.                         # self.limit(self.pp() + Other.pp())
  97.  
  98.     def evaporate(self):
  99.         # Pheromone evaporation should be programmed about here
  100.         if self.np() > 0:
  101.             self.np(-Gbl.Parameter('Evaporation')) # Repulsive ('negative') pheromone
  102.         if self.pp() > 0:
  103.             self.pp(-Gbl.Parameter('Evaporation')) # Attractive ('positive') Pheromone
  104.         if self.np() <= 0 and self.pp() <= 0:
  105.             self.clean()
  106.             return True
  107.         return False
  108.  
  109. class FoodSource:
  110.     """ Location where food is available
  111.     """
  112.     def __init__(self, Name):
  113.         self.Name = Name
  114.         self.FoodAmount = 0
  115.         self.Location = (-1,-1)
  116.         self.Radius = (Gbl.Parameter('FoodSourceSize')+1)//2
  117.         self.Distribution = Gbl.Parameter('FoodQuantity') // ((2*self.Radius) ** 2)
  118.         self.Area = []
  119.  
  120.     def locate(self, Location = None):
  121.         if Location:
  122.             self.Location = Location
  123.         return self.Location
  124.  
  125.     def __str__(self):
  126.         return "[%s, %d, %s...]" % (self.Name, self.FoodAmount, str(self.Area)[:22])
  127.  
  128.  
  129. class Obstacle:
  130.     def __init__(self, Name):
  131.         self.Name = Name
  132.         self.Visited = 0
  133.         self.Location = (-1,-1)
  134.         #self.Radius = (Gbl.Parameter('FoodSourceSize')+1)//2
  135.         #self.Distribution = Gbl.Parameter('FoodQuantity') // ((2*self.Radius) ** 2)
  136.         self.Area = []
  137.        
  138.     def locate(self, Location = None):
  139.         if Location:
  140.             self.Location = Location
  141.         return self.Location
  142.        
  143.    
  144. class Landscape(Landscapes.Landscape):
  145.     """ A 2-D grid with cells that contains food or pheromone
  146.     """
  147.     def __init__(self, Size, NbFoodSources, NbObstacles):
  148.         Landscapes.Landscape.__init__(self, Size, CellType=LandCell)
  149.        
  150.         global Observer
  151.        
  152.         # Positioning Food Sources
  153.         self.FoodSourceNumber = NbFoodSources
  154.         self.FoodSources = []
  155.         self.ObstacleNumber = NbObstacles
  156.         self.Obstacles_m = []        
  157.                
  158.         for FSn in range(self.FoodSourceNumber):
  159.             FS = FoodSource('FS%d' % FSn)
  160.             FS.locate((random.randint(0,Size-1),random.randint(0,Size-1)))
  161.             self.FoodSources.append(FS)
  162.             for Pos in self.neighbours(FS.locate(), Radius=FS.Radius):
  163.                 FS.Area.append(Pos)
  164.                 self.food(Pos, FS.Distribution)  # Cell contains a certain amount of food
  165.             Observer.record((FS.Name, FS.locate() + FoodAspect))    # to display food source
  166.             #Observer.record((OB.Name, OB.locate() +  ObstacleAspect))
  167.            
  168.         for Obsn in range(self.ObstacleNumber):
  169.             OB = Obstacle('OB%d' % Obsn)
  170.             OB.locate((random.randint(0,Size-1),random.randint(0,Size-1)))
  171.             self.FoodSources.append(FS)
  172.             for Pos in self.neighbours(FS.locate(), Radius=FS.Radius):
  173.                 OB.Area.append(Pos)
  174.             Observer.record((OB.Name, OB.locate() +  ObstacleAspect))
  175.            
  176.     # def Modify(self, (x,y), Modification):
  177.         # self.Ground[x][y] += Modification   # uses addition as redefined in LandCell
  178.         # return self.Ground[x][y]
  179.  
  180.     # def FoodSourceConsistency(self):
  181.         # for FS in self.FoodSources:
  182.             # amount = 0
  183.             # for Pos in FS.Area:L
  184.                 # amount += self.food(Pos)
  185.             # if amount != FS.FoodAmount:
  186.                 # print('************ error consistency %s: %d %d' % (FS.Name, amount, FS.FoodAmount))
  187.                 # print [self.food(Pos) for Pos in FS.Area]
  188.                 # FS.FoodAmount = amount
  189.                        
  190.     def food(self, Pos, delta=0):
  191.         if delta:
  192.             # let the food source know
  193.             for FS in self.FoodSources:
  194.                 if Pos in FS.Area:
  195.                     FS.FoodAmount += delta
  196.                     if FS.FoodAmount <= 0:
  197.                         Observer.record((FS.Name, FS.locate() + FoodDepletedAspect))    # to display food sources
  198.         return self.Cell(Pos).food(delta)   # adds food
  199.    
  200.     def foodQuantity(self):
  201.         return sum([FS.FoodAmount for FS in self.FoodSources])
  202.    
  203.     def npheromone(self, Pos, delta=0):
  204.         if delta:  
  205.             self.ActiveCells.add(Pos)
  206.             Observer.record(('NP%d_%d' % Pos, Pos + NPAspect)) # for ongoing display of negative pheromone
  207.         return self.Cell(Pos).np(delta) # adds repulsive pheromone
  208.        
  209.     def ppheromone(self, Pos, delta=0):
  210.         if delta:
  211.             self.ActiveCells.add(Pos)
  212.             Observer.record(('PP%d_%d' % Pos, Pos + PPAspect)) # for ongoing display of positive pheromone
  213.         return self.Cell(Pos).pp(delta) # adds attractive pheromone
  214.    
  215.     def obstacles(self, Pos, delta=0)
  216.         if delta:  
  217.             for OS in self.Obstacles_m:
  218.                 if Pos in OS.Area:
  219.                     OS.Visited += delta
  220.                     if OS.FoodAmount < 0:
  221.                         Observer.record((OS.Name, OS.locate() + ObstacleAspect2))   # to display food sources
  222.                         #Observer.record((OS.Name, OS.locate() + ObstacleAspect2))  # to display food sources
  223.         return self.Cell(Pos).np(delta) # adds repulsive pheromone
  224.         #return self.Cell(Pos).ob(delta)    # adds attractive pheromone    
  225.  
  226.     def evaporation(self):
  227.         for Pos in list(self.ActiveCells):
  228.             if self.Cell(Pos).evaporate(): # no pheromone left
  229.                 # call 'erase' for updating display when there is no pheromone left
  230.                 self.erase(Pos) # for ongoing display
  231.                 self.ActiveCells.remove(Pos)
  232.  
  233.     def erase(self, Pos):
  234.         " says to Observer that there is no pheromon left at that location "
  235.         Observer.record(('NP%d_%d' % Pos, Pos + (-1,))) # negative colour means erase from display
  236.         Observer.record(('PP%d_%d' % Pos, Pos + (-1,))) # negative colour means erase from display
  237.        
  238.     def update_(self):
  239.         # scans ground for food and pheromone - May be used for statistics
  240.         Food = NPher = PPher = Obstc = []
  241.         for (Position, Cell) in Land.travel():
  242.             if Cell.Food:       Food.append((Pos, Cell.food()))
  243.             if Cell.NPheromone: NPher.append((Pos, Cell.np()))
  244.             if Cell.PPheromone: PPher.append((Pos, Cell.pp()))
  245.             if Cell.Obstacle: Obstc.append((Pos, Cell.ob()))            
  246.         return (Food, NPher, PPher, Obstc)
  247.        
  248.        
  249. class Ant:
  250.     """ Defines individual agents
  251.     """
  252.     def __init__(self, IdNb, InitialPosition):
  253.         self.ID = IdNb
  254.         self.Colony = InitialPosition # Location of the colony nest
  255.         self.location = InitialPosition
  256.         self.PPStock = Gbl.Parameter('PPMax')
  257.         self.Action = 'Move'
  258.         self.moves()
  259.  
  260.     def Sniff(self):
  261.         " Looks for the next place to go "
  262.         Neighbourhood = list(Land.neighbours(self.location, Gbl.Parameter('SniffingDistance')))
  263.         random.shuffle(Neighbourhood) # to avoid anisotropy
  264.         acceptable = None
  265.         best = -Gbl.Parameter('Saturation') # best == pheromone balance found so far
  266.         for NewPos in Neighbourhood:
  267.             # looking for position devoid of negative pheromon
  268.             if NewPos == self.location: continue
  269.             #if Land.obstacles(NewPos)>0: break
  270.             if Land.food(NewPos) > 0:
  271.                 # Food is always good to take
  272.                 acceptable = NewPos
  273.                 break
  274.             """if Land.obstacles(NewPos)>0:
  275.                 acceptable = None"""
  276.             found = Land.ppheromone(NewPos)   # attractiveness of positive pheromone
  277.             found -= Land.npheromone(NewPos)   # repulsiveness of negative pheromone
  278.             if found > best:             
  279.                 acceptable = NewPos
  280.                 best = found
  281.         return acceptable
  282.  
  283.     def returnHome(self):
  284.         " The ant heads toward the colony "
  285.         Direction = t2c(self.Colony) - t2c(self.location)   # complex number operation
  286.         Distance = abs(Direction)
  287.         if Distance >= Gbl.Parameter('SniffingDistance'):
  288.             # Negative pheromone
  289.             if Gbl.Parameter('NPDeposit'):
  290.                 Land.npheromone(self.location, Gbl.Parameter('NPDeposit')) # marking current position as visited with negative pheromone
  291.             # Positive pheromone
  292.             Land.ppheromone(self.location, self.PPStock) # marking current posiiton as interesting with positive pheromone
  293.             Direction /= Distance   # normed vector
  294.             # MaxSteps = int(Gbl.Parameter('LandSize') / 2 / Distance)  #
  295.             Decrease = int(self.PPStock / Distance) # Linear decrease
  296.             self.PPStock -= Decrease
  297.             # if self.PPStock <= Gbl.Parameter('PPMin'):   self.PPStock = Gbl.Parameter('PPMin')    # always lay some amount of positive pheromone
  298.             self.location = c2t(t2c(self.location) + 2 * Direction) # complex number operation
  299.             self.location = Land.ToricConversion(self.location)     # landscape is a tore
  300.             Observer.record((self.ID, self.location + AntAspectWhenLaden)) # for ongoing display of ants
  301.         else:
  302.             # Home reached
  303.             self.PPStock = Gbl.Parameter('PPMax')
  304.             self.Action = 'Move'
  305.        
  306.     def moves(self):
  307.         """ Basic behavior: move by looking for neighbouring unvisited cells.
  308.             If food is in sight, return straight back home.
  309.             Lay down negative pheromone on visited cells.
  310.             Lay down positive pheromone on returning home.
  311.         """
  312.         if self.Action == 'BackHome':
  313.             self.returnHome()
  314.         else:
  315.             NextPos = self.Sniff()
  316.             # print self.ID, 'in', self.location, 'sniffs', NextPos
  317.             if (NextPos is None) or random.randint(0,100) < Gbl.Parameter('Exploration'):
  318.                 # either all neighbouring cells have been visited or in the mood for exploration
  319.                 NextPos = c2t(t2c(self.location) + complex(random.randint(-1,1),random.randint(-1,1)))
  320.                 NextPos = Land.ToricConversion(NextPos)
  321.             # Marking the old location as visited
  322.             if Gbl.Parameter('NPDeposit'):
  323.                 Land.npheromone(self.location, Gbl.Parameter('NPDeposit'))
  324.             if Land.obstacles(self.location)>0:
  325.                 Land.obstacles(self.location, -1)   # consume one unit of food
  326.                 Land.npheromone(self.location, Gbl.Parameter('NPDeposit'))        
  327.                 # Observer.record(('NP%d_%d' % self.location, self.location + NPAspect)) # for ongoing display of negative pheromone
  328.             self.location = NextPos
  329.             Observer.record((self.ID, self.location + AntAspect)) # for ongoing display of ants
  330.             foundFood = False
  331.             if Land.food(self.location) > 0:   
  332.                 Land.food(self.location, -1)    # consume one unit of food
  333.                 foundFood = True
  334.                 # # # Observer.FoodCollected += 1
  335.                 self.Action = 'BackHome'    # return when having found food
  336.                
  337.             return foundFood
  338.        
  339.  
  340.                    
  341. class Population:
  342.     " defines the population of agents "
  343.    
  344.     def __init__(self, PopulationSize,  Observer, ColonyPosition):
  345.         " creates a population of ant agents "
  346.         self.ColonyPosition = ColonyPosition
  347.         self.Pop = []
  348.         self.PopulationSize = PopulationSize
  349.         for ID in range(self.PopulationSize):   self.Pop.append(Ant('A%d' % ID, ColonyPosition))
  350.         self.AllMoved = 0  # counts the number of times all agents have moved on average
  351.         self.SimulationGoes = 400 * self.PopulationSize
  352.         # allows to run on the simulation beyond stop condition
  353.         self.year = -1
  354.         self.FoodCollected = 0
  355.  
  356.     def selectIndividual(self):
  357.         if self.Pop:    return random.choice(self.Pop)
  358.         return None
  359.    
  360.     def One_Decision(self):
  361.         """ This function is repeatedly called by the simulation thread.
  362.             One ant is randomly chosen and decides what it does
  363.         """
  364.         # print('.', end="", flush=True)
  365.         self.year += 1
  366.         ant = self.selectIndividual()
  367.         if ant and ant.moves(): self.FoodCollected += 1
  368.         Moves = self.year // self.PopulationSize    # One step = all Walkers have moved once on average
  369.         Observer.season(year=Moves)
  370.         # print (self.year, self.AllMoved, Moves),
  371.         if Moves > self.AllMoved:
  372.             Land.evaporation()
  373.             self.AllMoved = Moves
  374.         if Observer.Visible():  Observer.curve('Food', (Moves, self.FoodCollected))
  375.         if (Land.foodQuantity() <= 0)self.SimulationGoes -= 1
  376.         return self.SimulationGoes > 0   # stops the simulation when False
  377.  
  378.        
  379. if __name__ == "__main__":
  380.     print(__doc__)
  381.  
  382.     #############################
  383.     # Global objects            #
  384.     #############################
  385.     Gbl = EPar.Parameters('_Params.evo')    # Loading global parameter values
  386.     if Gbl.Parameter('RandomSeed') > 0: random.seed(Gbl.Parameter('RandomSeed'))
  387.     Observer = EO.Observer(Gbl)   # Observer contains statistics
  388.     Land = Landscape(Gbl.Parameter('LandSize'), Gbl.Parameter('NbFoodSources'),7)
  389.     Pop = Population(Gbl.Parameter('PopulationSize'), Observer, (Gbl.Parameter('LandSize')//2, Gbl.Parameter('LandSize')//2))   # Ant colony
  390.  
  391.     Observer.recordInfo('Background', 'ants-1_pale.jpg')
  392.     Observer.recordInfo('FieldWallpaper', 'Grass1.jpg')
  393.     # Observer.recordInfo('DefaultViews', [('Field', Gbl.Parameter('LandSize')*12)])
  394.     Observer.recordInfo('DefaultViews', [('Field', Gbl.Parameter('WindowSize'))])
  395.     # Observer.recordInfo('FieldWallpaper', 'white')
  396.     Observer.curve(Name='Food', Color='green1', Legend='Year (each ant moves once a year on average)&nbsp;&nbsp;&nbsp;x&nbsp;&nbsp;&nbsp;Amount of food collected') # declaration of a curve
  397.  
  398.     EW.Start(Pop.One_Decision, Observer, Capabilities='RPC')
  399.  
  400.     print("Bye.......")
  401.     sleep(1.0)
  402.  
  403. __author__ = 'Dessalles'
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement