#------------------------------------------------------------- #************************************************************# # FALL 2008 :: COMP522 FINAL PROJECT # # Agent-based Modelling with Statecharts # # Studying the behaviour of spatially distributed # # systems using collections of interacting Statecharts # # # # Nabeelah Ali # # nabeelah.ali@mail.mcgill.ca # # December 2008 # #************************************************************# #------------------------------------------------------------- # The website for this project contains more details on the motivation behind # this project, how to use this file as well as create new Statecharts, and details # on simulatin results. # http://msdl.cs.mcgill.ca/people/hv/teaching/MS/projects/material/Nabeelah.Ali/ # The landscape for my cellular automaton is a square grid. The grid size can # be specified by altering the grid_size variable below. The grid is represented as an array, # with values meaning the following things: # * 0 = empty grid spot # * 1 = healty occupant # * 2 = diseased occupant # * 3 = dead occupant #------------------------IMPORTING MODULES------------------------------------ # Model compiled from AToM3. # For more information on AToM3, go here -> http://atom3.cs.mcgill.ca/ #from simpleDisease_MDL_Compiled import simpleDisease_MDL from complexModel_MDL_Compiled import complexModel_MDL as simpleDisease_MDL from random import * from time import * # The numpy module is needed for array-handling. # For more information, go here -> http://numpy.scipy.org/ from numpy import * # For the GUI, the Pygame module is imported. # Can be downloaded here: http://www.pygame.org/ import pygame, sys,os from pygame.locals import * #------------------------VARIABLE DEFINITIONS---------------------------------- """ The following variables can all be adjusted to alter the model. """ print "---------Starting the disease simulation.---------" # The grid_size variable dictates how many units across and down will be created. # For example, if grid_size = 30, the grid will be 30 x 30 units (900 in total). grid_size = input("The grid is an n x n array. What value of n would you like to simulate? ") grid_size = int(grid_size) # How many agents should be created? people = input("How many agents would you like to simulate? ") people = int(people) prob = input("What is the probability that a newly-created agent is infected? ") prob = float(prob) simulationTime = input("How long would you like the simulation to run for? ") simulationTime = int(simulationTime) #--------------------------CLASS DEFINTIONS------------------------------------ class simpleDisease: def __init__(self): self.statechart = simpleDisease_MDL() self.statechart.initModel() self.controller = simpleDisease_Controller(self.statechart) self.statechart.event("start", self.controller) class simpleDisease_Controller: def __init__(self, statechart): self.statechart = None self.state = 1 self.statechart = statechart # Set a random location for this new agent. found = False # Make sure the grid spot is empty. while (found == False): self.location = (randint(0,grid_size - 1),randint(0,grid_size - 1)) if landscape[self.location] == 0: found = True # Mark the grid. landscape[self.location] = self.state self.numBuried = 0 def infection(self): self.statechart.event('infection') def immunized(self): self.statechart.event('immunized') def healthy(self): self.statechart.event('healthy') def dead(self): self.statechart.event('dead') def infected(self): self.statechart.event('infected') def makeInfected(self): self.state = 2 landscape[self.location] = self.state updateGUI(None,self.location,self.state) self.statechart.event('infected') def makeHealthy(self): self.state = 1 landscape[self.location] = self.state updateGUI(None,self.location,self.state) self.statechart.event("healthy") def bury(self): landscape[self.location] = 0 updateGUI(None,self.location,0) def die(self): self.state = 3 landscape[self.location] = self.state updateGUI(None,self.location,self.state) self.statechart.event("dead") def move(self): oldLocation = self.location empty = False # Move in a random direction while ensuring it is not occupied. # What if all grid spots around a particular agent are occupied? # The next interrupt from a time-advance will stop the processing # and so the agent will stay in the same place. """ Movement directions. 1 | 2 | 3 __|___|__ | | 4 | O | 5 __|___|__ | | 6 | 7 | 8 """ while (empty == False): # Select a direction in which to randomly move. newLoc = randint(1,8) if (newLoc == 1): # Move to top-left grid squre. newLocation = (self.location[0] - 1, self.location[1] - 1) elif (newLoc == 2): # Move to top grid square. newLocation = (self.location[0], self.location[1] - 1) elif (newLoc == 3): # Move to top-right grid square. newLocation = (self.location[0] + 1, self.location[1] - 1) elif (newLoc == 4): # Move to left grid square. newLocation = (self.location[0] - 1, self.location[1]) elif (newLoc == 5): # Move to right grid square. newLocation = (self.location[0] + 1, self.location[1]) elif (newLoc == 6): # Move to bottom-left grid square. newLocation = (self.location[0] - 1, self.location[1] + 1) elif (newLoc == 7): # Move to bottom grid square. newLocation = (self.location[0], self.location[1] + 1) elif (newLoc == 8): # Move to bottom-right grid square. newLocation = (self.location[0] + 1, self.location[1] + 1) # Checks to make sure location does not exceed grid size. The grid # 'wraps around' like a globe - there are no edges so agents that disappear # off one end should reappear on the other side. if (newLocation[0] == grid_size): newLocation = (0, newLocation[1]) elif (newLocation[0] < 0): newLocation = (grid_size - 1, newLocation[1]) if (newLocation[1] == grid_size): newLocation = (newLocation[0], 0) elif (newLocation[1] < 0): newLocation = (newLocation[0], grid_size - 1) if landscape[newLocation] != 0: empty = False else: empty = True self.location = newLocation # Erase old location and mark new location. landscape[oldLocation] = 0 landscape[newLocation] = self.state updateGUI(oldLocation, newLocation, self.state) # Now check out who's in the vicinity, and store the states of occupants in an array. stateArray = zeros(8) x = self.location[0] y = self.location[1] minus1x = x - 1 minus1y = y - 1 add1x = x + 1 add1y = y + 1 if ((x + 1) == grid_size): add1x = 0 if ((y + 1) == grid_size): add1y = 0 if ((x - 1) < 0): minus1x = grid_size - 1 if ((y - 1) < 0): minus1y = grid_size - 1 stateArray[0] = landscape[(minus1x, minus1y)] stateArray[1] = landscape[(x, minus1y)] stateArray[2] = landscape[(add1x, minus1y)] stateArray[3] = landscape[(minus1x, y)] stateArray[4] = landscape[(add1x, y)] stateArray[5] = landscape[(minus1x, add1y)] stateArray[6] = landscape[(x, add1y)] stateArray[7] = landscape[(add1x, add1y)] # Iterate through array. # If next to an infected person, trigger the 'infection' event. # If next to a dead person, trigger the 'infection' event. for i in stateArray: if i == 1: pass elif i == 2: self.statechart.event("infection") elif i == 3: self.statechart.event("infection") def updateGUI(oldLoc, newLoc, state): """ This script uses PyGame to handle the screen that displays the current state and location of agents. This following function handles updating the screen. """ if (state == 1): icon = healthyIcon elif (state == 2): icon = infectedIcon elif (state == 3): icon = deadIcon elif (state == 0): icon = emptyIcon numInfected = 0 numDead = 0 numHealthy = 0 # Iterate through the landscape array and tally up totals. for agentRow in landscape: for agent in agentRow: if agent == 1: numHealthy += 1 elif agent == 2: numInfected += 1 elif agent == 3: numDead += 1 numBuried = people - (numHealthy + numInfected + numDead) # Mark the screen. Keep track of what rectangles are modified, and only update # those to prevent unnecessary refreshing of other parts of the screen. screen.blit(icon, (newLoc[0] * iconSize, newLoc[1] * iconSize)) rectList1 = (newLoc[0] * iconSize, newLoc[1] * iconSize, (newLoc[0] + iconSize) * iconSize, (newLoc[1] + iconSize) * iconSize) displayText = " Healthy: " + str(numHealthy) + " Infected: " + str(numInfected) + " Dead: " + str(numDead) + " Buried: " + str(numBuried) #displayText += "Time: " + str(time() - startTime) text = font.render(displayText, 1, (10, 10, 10)) textpos = (0,windowSize + padding) screen.fill(0xFFFFFF, (0, windowSize + padding, windowSize, windowSize + textSize + padding)) screen.blit(text, textpos) rectList3 = (0, windowSize + padding, windowSize, windowSize + textSize + padding) if (oldLoc != None): screen.blit(emptyIcon, (oldLoc[0] * iconSize, oldLoc[1] * iconSize)) rectList2 = (oldLoc[0] * iconSize, oldLoc[1] * iconSize, (oldLoc[0] + iconSize) * iconSize, (oldLoc[1] + iconSize) * iconSize) pygame.display.update((rectList1, rectList2, rectList3)) else: pygame.display.update((rectList1, rectList3)) if __name__=="__main__": # How many pixels across and down is the icon? (Must be a square icon.) iconSize = 16 # How big should the text be (in pixels)? textSize = 30 # Padding is decorative, prevents text from being 'stuck' to screen's walls. padding = 10 numBuried = 0 numInfected = 0 numDead = 0 numHealthy = 0 landscape = zeros((grid_size, grid_size)) windowSize = grid_size * iconSize pygame.init() window = pygame.display.set_mode((windowSize, windowSize + textSize + (padding * 2))) pygame.display.set_caption('Disease Cellular Automata + Statechart Model') pygame.mouse.set_visible(0) screen = pygame.display.get_surface() screen.fill((255, 255, 255)) # Icons used here are under a Creative Commons license. # Creator: http://www.famfamfam.com/lab/icons/silk/ healthyIconFile = os.path.join("images","healthy.png") infectedIconFile = os.path.join("images","infected.png") emptyIconFile = os.path.join("images","empty.png") deadIconFile = os.path.join("images","dead.png") healthyIcon = pygame.image.load(healthyIconFile).convert() infectedIcon = pygame.image.load(infectedIconFile).convert() emptyIcon = pygame.image.load(emptyIconFile).convert() deadIcon = pygame.image.load(deadIconFile).convert() for xC in xrange(0,grid_size): for yC in xrange(0,grid_size): screen.blit(emptyIcon, (xC * iconSize, yC * iconSize)) #sleep(0.2) font = pygame.font.Font(None, textSize) pygame.display.update() startTime = time() # Initialize all the agents in the model. for j in xrange(0,people): person = simpleDisease() inf = uniform(0,1) if inf < prob: person.controller.statechart.event("infection") # Keep checking events that occur in the GUI. # If the ESCAPE key is hit, end the simulation. while True: if time() - startTime > simulationTime: for agentRow in landscape: for agent in agentRow: if agent == 1: numHealthy += 1 elif agent == 2: numInfected += 1 elif agent == 3: numDead += 1 numBuried = people - (numHealthy + numInfected + numDead) print "The simulation ran for ", simulationTime, "seconds." print "At the end, the number of healty agents was", numHealthy, ",the number of infected " print "agents was", numInfected, ", the number of dead agents was", numDead + numBuried, "." raise SystemExit for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit if (event.type == KEYDOWN): if( event.key == K_ESCAPE ): raise SystemExit