Agent-based simulation in Python

In this article I demonstrate agent-based simulation in Python. Actually, I already introduced agent-based modeling in Python in previous posts. I e.g. introduced a battlefield model that contains agent groups that were located on a 2D grid. I coded that model in Python and used Matplotlib for visualization. In this article I will not only build a model but actually execute an entire agent-based simulation in Python.

Simple module for agent-based simulation in Python

To begin with I need to construct a simple module for agent-based simulations in Python. Firstly, much like in my previous introduction to agent-based modeling in Python, I define a custom agent datatype. This is done on the form of a custom class in Python.

# class, defining agents as abstract data types
class agent:
    # init-method, the constructor method for agents
    def __init__(self,x,y,group):
        self.life = 100 # agent's life score
        self.x = x
        self.y = y
        self.group = group

As can be seen from the constructor of the agent class an agent has a life score. Agents furthermore belong to a group as well as they have x- and y-coordinates. These coordinate attributes are used to describe the location of agents in a 2D grid.

Basically, a 2-dimensional array is used to contain all agents. Hence, populations of agents are allocated on a 2D grid. In this case this grid represents a battlefield. As can be seen below I create the battlefield with a list comprehension in Python.

# creating empty 100 x 100 list using list comprehension in python
battlefield = [[None for i in range(0,100)] for i in range(0,100)]

I now proceed by defining a helper function in Python for creating and allocating agents on the battlefield. I will use this function in order to setup my agent-based simulation in Python.

# define a function for creating agents and assigning them to grid
def agentCreator(size,group,groupList,field,n,m):
    # loop through entire group, i.e. in this case 1000 units
    for j in range(0,size):
        # select random available location 
        while True:
            # random x coordinate
            x = random.choice(range(0,n))
            # random y coordinate
            y = random.choice(range(0,m))
            # check if spot is available; if not then re-iterate 
            if field[x][y] == None:
                field[x][y] = agent(x=x,y=y,group=group)
                # append agent object reference to group list
                groupList.append(field[x][y])
                # exit while loop; spot on field is taken
                break

In short the agentCreator-function creates a specified amount of agents and allocates them on a field. Each agent created by one function call have the same type. As can be seen from the function definition the type is a function input parameter. Another key point to notice is that the field in which agents are allocated is passed on as a reference. I.e. in this case the field is the battlefield grid. It facilitates the canvas on which this agent-based simulation in Python takes place.

Initializing my agent-based simulation in Python

Using these model components I now initiate my model. To start my agent-based simulation in Python I create an initial battlefield. I furthermore add agents to it. All this is done by calling the agentCreator function as I have shown above. I call the function twice to add two groups of agents. The first group is type “A” while the second group is of type “B”.

# list with available x and y locations
locations = battlefield.copy() # using .copy prevents copying by reference
# create empty list for containing agent references in future, type A & B
agents_A = []
agents_B = []
# assigning random spots to agents of group A and B; 
import random
agentCreator(size = 1000,
                group = "A",
                groupList = agents_A,
                field = battlefield,
                n = 100,
                m = 100)
agentCreator(size = 1000,
                group = "B",
                groupList = agents_B,
                field = battlefield,
                n = 100,
                m = 100) 

While the data will now reflect a 2D grid with two competing agent populations I cannot see it visually. I will use Matplotlib to visualize this agent-based simulation in Python. I the following paragraphs I demonstrate how to do this.

Matplotlib visualizes agent-based simulation in Python

As shown by me in one of my previous blog posts Matplotlib can be used to visualize 2D grids in Python. I have placed a reference in the form of a link at the end of this article. Make sure to read in case you want to implement this kind of visualization from scratch on your own.

In the code displayed below I use Matplotlib to visualize every cell in the battlefield grid. I do so by creating 2D float array using a list comprehension in Python. I then iterate through every index-pair of the 2D grid. Due to every index-pair in the float array matching a index-pair in the battlefield grid I can identify cells with agents in them. I overwrite cells that contain an agent. In short, every cell with an agent of type A receives the float 1.0 while every cell with an agent of type B receives the float 2.0.

#.imshow() needs a matrix with float elements;
population = [[0.0 for i in range(0,100)] for i in range(0,100)]

# if agent is of type A, put a 1.0, if of type B, pyt a 2.0
for i in range(1,100):
    for j in range(1,100):
        if battlefield[i][j] == None: # empty
            pass # leave 0.0 in population cell
        elif battlefield[i][j].group == "A": # group A agents
            population[i][j] = 1.0 # 1.0 means "A"
        else: # group B agents
            population[i][j] = 2.0 # 2.0 means "B"

I can not visualize the battlefiend. In order to do so I import pyplot and colors from Matplotlib in Python. This enables me to create a ListedColormap object. ListedColormap objects can be bassed on as references to the pyplot.imshow()-method. I show this in the code below.


# import pyplot and colors from matplotlib
from matplotlib import pyplot, colors

# using colors from matplotlib, define a color map
colormap = colors.ListedColormap(["lightgrey","green","blue"])

# define figure size using pyplot
pyplot.figure(figsize = (12,12))
# using pyplot add a title
pyplot.title("battlefield before simulation run (green = A, blue = B)",
            fontsize = 24)
# using pyplot add x and y labels
pyplot.xlabel("x coordinates", fontsize = 20)
pyplot.ylabel("y coordinates", fontsize = 20)
# adjust x and y axis ticks, using pyplot
pyplot.xticks(fontsize = 16)
pyplot.yticks(fontsize = 16)
# use .imshow() method from pyplot to visualize agent locations
pyplot.imshow(X = population,
             cmap = colormap)

<matplotlib.image.AxesImage at 0x22495922ac8>

Initial model status of agent-based simulation in Python

We can now execute and visualize an agent-based simulation in Python. While I only show one iteration of the simulation in this article I did place a linked reference to another article in which I use execute an entire simulation experiment.

Implementing strategies for agent-interaction

For this we implement two attack strategies:

Group A has the strategy of always hitting the same agent in each round
Group B has a random and independent strategy for attacking enemies. This means that each agent of type B will attack a randomly selected enemy within that agent’s reach.

The simulation is now conducted under the following conditions:

1) Each round is one iteration
2) In each round, each agent can attack one agent within his reach
3) The reach of an agent is defined at the start of the simulation and defaults to 10
4) If an agent dies he will no longer be located on the battle field
5) An agent dies when his life score equals or goes below zero
6) Each agent has a randomly distributed attack damage, ranging from 10 to 60
7) In each round all agents get to launch an attack

With these rules in place I will now iterate through 50 rounds of fighting. At the end I will print a plot of the remaining agents on the battlefield. The implementation follows below:

for counter in range(0,50): # in this case I am conducting 50 iterations 
    # iterating through all cells on the battlefield
    for i in range(0,len(battlefield)):
        for j in range(0,len(battlefield)):
            #print("top tier iteration, i: "+str(i)+", j: "+str(j))
            # check if there is an agent in the respective cell
            if battlefield[i][j] != None:
                # depending on the type: execute respective attack strategy
                if battlefield[i][j].group == "A":
                    found_i = None
                    found_j = None
                    # look in neigbouring cells in same order for each iteration
                    for k in range(i-10,i+11):
                        for l in range(j-10,j+11):
                            # check for negative index values; if so - break!
                            if k < 0 or l < 0:
                                break
                            # check for index values above 99, if so break!
                            if k > 99 or l > 99:
                                break
                            if battlefield[k][l]:
                                if battlefield[k][l].group == "B": # then this is an enemy
                                    if found_i == None:
                                        found_i = k
                                        found_j = l
                    # deal damage to identified enemy
                    if found_i:
                        battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)
                else: 
                    # first check if there even is an enemy in one of the surrounding cells
                    enemy_found = False
                    for k in range(i-10,i+11):
                        for l in range(j-10,j+11):
                            # check for negative index, if so break to next iteration
                            if k < 0 or l < 0:
                                break
                            # check for index values above 99, if so break
                            if k > 99 or l > 99:
                                break
                            if battlefield[k][l] != None:
                                if battlefield[k][l].group == "A":
                                    enemy_found = True
                    # select a random row and a random column
                    found_i = None
                    found_j = None
                    while enemy_found and found_i == None:
                        k = random.randint(i-10,i+10)
                        l = random.randint(j-10,j+10)
                        # check for negative index, if so continue to next iteration
                        if k < 0 or l < 0:
                            continue
                        # check for index value > 99, if so continue
                        if k > 99 or l > 99:
                            continue
                        if k != i:
                            if battlefield[k][l]: 
                                if battlefield[k][l].group == "A":
                                    found_i = k
                                    found_j = l
                        else:
                            if l != j:
                                if battlefield[k][l]:
                                    if battlefield[k][l].group == "A":
                                        found_i = k
                                        found_j = l
                    # deal damage to identified enemy
                    if found_i:
                        battlefield[found_i][found_j].life = battlefield[found_i][found_j].life - random.randint(10,60)
    # identifying agents with life score of score or below - and removing them from the grid
    for i in range(0,len(battlefield)):
        for j in range(0,len(battlefield)):
            if battlefield[i][j]:
                if battlefield[i][j].life <= 0:
                    battlefield[i][j] = None
# producing a plot of all battlefield locations, 10 iterations after
population = [[0.0 for i in range(0,100)] for i in range(0,100)]
# if agent is of type A, put a 1.0, if of type B, pyt a 2.0
for i in range(1,100):
    for j in range(1,100):
        if battlefield[i][j] == None: # empty
            pass # leave 0.0 in population cell
        elif battlefield[i][j].group == "A": # group A agents
            population[i][j] = 1.0 # 1.0 means "A"
        else: # group B agents
            population[i][j] = 2.0 # 2.0 means "B"
# import pyplot and colors from matplotlib
from matplotlib import pyplot, colors
# using colors from matplotlib, define a color map
colormap = colors.ListedColormap(["lightgrey","green","blue"])
# define figure size using pyplot
pyplot.figure(figsize = (12,12))
# using pyplot add a title
pyplot.title("battlefield after 50 iterations (green = A, blue = B)",
            fontsize = 24)
# using pyplot add x and y labels
pyplot.xlabel("x coordinates", fontsize = 20)
pyplot.ylabel("y coordinates", fontsize = 20)
# adjust x and y axis ticks, using pyplot
pyplot.xticks(fontsize = 16)
pyplot.yticks(fontsize = 16)
# use .imshow() method from pyplot to visualize agent locations
pyplot.imshow(X = population,
             cmap = colormap)
<matplotlib.image.AxesImage at 0x22495be1848>
Final state at end time of agent-based simulation in Python

Concluding remarks and references to related content

In some following posts I will clean the code and pack its functionality into re-usable functions. I will then conduct a more comprehensive simulation study in which various parameters are varied to investigate their impact on battle outcome.

In this example I used Python lists as my agent containers of choice. However, similar models can be constructed using e.g. NumPy arrays.

If you are interested in agent-based simulation you might be interested in the following content:

You May Also Like

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.