RSOC

Mesa Tutorial

[1]:
from mesa import Agent, Model
from mesa.time import RandomActivation
from mesa.space import MultiGrid
import numpy as np
from mesa.datacollection import DataCollector
from mesa.batchrunner import BatchRunner

# For a jupyter notebook add the following line:
%matplotlib inline

# The below is needed for both notebooks and scripts
import matplotlib.pyplot as plt
[2]:
class MoneyAgent(Agent):
    """Agent with fixed intial wealth"""

    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        self.wealth = 1

    def move(self):
        possible_steps = self.model.grid.get_neighborhood(
            self.pos,
            moore = True,
            include_center = False
        )
        new_position = self.random.choice(possible_steps)
        self.model.grid.move_agent(self, new_position)

    def give_money(self):
        cellmates = self.model.grid.get_cell_list_contents([self.pos])
        if len(cellmates) > 1:
            self.wealth -= 1
            other = self.random.choice(cellmates)
            other.wealth += 1

    def step(self):
        self.move()
        if self.wealth > 0:
            self.give_money()
[5]:
def compute_gini(model):
    agent_wealths = [agent.wealth for agent in model.schedule.agents]
    x = sorted(agent_wealths)
    N = model.num_of_agents
    B = sum( xi * (N-i) for i, xi in enumerate(x) ) / (N*sum(x))
    return (1 + (1/N) - 2*B)

class MoneyModel(Model):
    """A model with some number of agents"""

    def __init__(self, N, width, height):
        super().__init__()
        self.num_of_agents = N
        self.grid = MultiGrid(width, height, True)
        self.schedule = RandomActivation(self)
        self.running = True

        for i in range(self.num_of_agents):
            a = MoneyAgent(i, self)
            self.schedule.add(a)

            x = self.random.randrange(self.grid.width)
            y = self.random.randrange(self.grid.height)
            self.grid.place_agent(a, (x, y))

        self.datacollector = DataCollector(
            model_reporters = {"Gini": compute_gini},
            agent_reporters = {"Wealth": "wealth"})

    def plot_state(self):
        agent_counts = np.zeros((self.grid.width, self.grid.height))
        for cell in self.grid.coord_iter():
            cell_content, x, y = cell
            agent_count = len(cell_content)
            agent_counts[x][y] = agent_count
        plt.imshow(agent_counts, interpolation='nearest')
        plt.colorbar()

    def plot_histogram(self):
        agent_wealth = [a.wealth for a in self.schedule.agents]
        plt.hist(agent_wealth)

    def step(self):
        """advance the model by one step"""
        self.datacollector.collect(self)
        self.schedule.step()
[23]:
mdl = MoneyModel(50, 10, 10)
for i in range(1000):
    if i%500 == 0:
        print(i)
    mdl.step()
0
500
[24]:
mdl.plot_state()
_images/RSOC_6_0.svg
[25]:
mdl.plot_histogram()
_images/RSOC_7_0.svg
[26]:
gini = mdl.datacollector.get_model_vars_dataframe()
gini.plot()
[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x1f3f3b32438>
_images/RSOC_8_1.svg
[27]:
agent_wealth = mdl.datacollector.get_agent_vars_dataframe()
agent_wealth.head()
[27]:
Wealth
Step AgentID
0 0 1
1 1
2 1
3 1
4 1
[29]:
end_wealth = agent_wealth.xs(99, level="Step")["Wealth"]
end_wealth.hist(bins=range(agent_wealth.Wealth.max() + 1))
[29]:
<matplotlib.axes._subplots.AxesSubplot at 0x1f3f0cb9eb8>
_images/RSOC_10_1.svg
[30]:
one_agent_wealth = agent_wealth.xs(5, level="AgentID")
one_agent_wealth.Wealth.plot()
[30]:
<matplotlib.axes._subplots.AxesSubplot at 0x1f3f1fedd68>
_images/RSOC_11_1.svg

Batch Runner

[13]:
T fixed_params = {
    "width": 10,
    "height": 10
}

variable_params = {"N": range(10, 500, 10)}

# The variables parameters will be invoke along with the fixed parameters allowing for either or both to be honored.
batch_run = BatchRunner(
    MoneyModel,
    variable_params,
    fixed_params,
    iterations = 5,
    max_steps = 100,
    model_reporters = {"Gini": compute_gini}
)

batch_run.run_all()
245it [04:17,  1.05s/it]
[14]:
run_data = batch_run.get_model_vars_dataframe()
run_data.head()
plt.scatter(run_data.N, run_data.Gini)
[14]:
<matplotlib.collections.PathCollection at 0x1f3f08ae898>
_images/RSOC_14_1.svg

Visualization

[1]:
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer
from mesa.visualization.modules import ChartModule
[54]:
def agent_portrayal(agent):
    portrayal = {"Shape": "circle",
                 "Filled": "true",
                 "r": 0.5}

    if agent.wealth > 0:
        portrayal["Color"] = "red"
        portrayal["Layer"] = 0
    else:
        portrayal["Color"] = "grey"
        portrayal["Layer"] = 1
        portrayal["r"] = 0.2
    return portrayal
[55]:
grid = CanvasGrid(agent_portrayal, 100, 100, 500, 500)
chart = ChartModule([{"Label": "Gini",
                      "Color": "Black"}],
                    data_collector_name='datacollector')
[50]:
server = ModularServer(MoneyModel,
                       [grid, chart],
                       "Money Model",
                       {"N": 30, "width": 100, "height": 100})
server.port = 8529 # The default
server.launch()
Interface starting at http://127.0.0.1:8528
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-50-cbeb805557c5> in <module>
      4                        {"N": 30, "width": 100, "height": 100})
      5 server.port = 8528 # The default
----> 6 server.launch()

~\AppData\Local\Programs\Python\Python37\lib\site-packages\mesa\visualization\ModularVisualization.py in launch(self, port)
    322         webbrowser.open(url)
    323         tornado.autoreload.start()
--> 324         tornado.ioloop.IOLoop.current().start()

~\AppData\Local\Programs\Python\Python37\lib\site-packages\tornado\platform\asyncio.py in start(self)
    146             self._setup_logging()
    147             asyncio.set_event_loop(self.asyncio_loop)
--> 148             self.asyncio_loop.run_forever()
    149         finally:
    150             asyncio.set_event_loop(old_loop)

~\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py in run_forever(self)
    524         self._check_closed()
    525         if self.is_running():
--> 526             raise RuntimeError('This event loop is already running')
    527         if events._get_running_loop() is not None:
    528             raise RuntimeError(

RuntimeError: This event loop is already running

Ok, Ants

[3]:
from mesa import Agent, Model
from mesa.time import RandomActivation
from mesa.space import MultiGrid
import numpy as np
from mesa.datacollection import DataCollector
from mesa.batchrunner import BatchRunner
# For a jupyter notebook add the following line:
%matplotlib inline

# The below is needed for both notebooks and scripts
import matplotlib.pyplot as plt
[15]:
class Ant(Agent):
    """Ant with fixed intial hunger"""

    def __init__(self, unique_id, model, initial_hunger_value = 460):
        super().__init__(unique_id, model)
        self.hunger = initial_hunger_value
        self.state = 'wandering'#feeding, satiated, disturbed
        #intially we have 'eat_seeking' if ant finds food, then he starts 'eating' until he will be 'satisfied'

    def is_satiated(self):
        return self.hunger == 0

    def is_food_found(self):
        return self.model.food_array[self.pos[0], self.pos[1]] > 0

    def eat(self):
        if self.hunger > 0:
            self.hunger -= 1

    def move(self):
        possible_steps = self.model.grid.get_neighborhood(
            self.pos,
            moore = False,
            include_center = False
        )
        new_position = self.random.choice(possible_steps)
        self.model.grid.move_agent(self, new_position)

    def disturb(self):
        cellmates = self.model.grid.get_cell_list_contents([self.pos])
        for antmate in cellmates:
            if antmate.state == 'feeding':
                antmate.state = 'disturbed'

    def step(self):
        #movement
        if self.state == 'disturbed':
            self.state = 'wandering'
            self.move()
        elif not self.is_satiated() and not self.is_food_found():
            self.state = 'wandering'
            self.move()
        elif not self.is_satiated() and self.is_food_found():
            self.disturb()
            self.state = 'feeding'
            self.eat()
        elif self.is_satiated():
            self.state = 'satiated'
            self.move()
[12]:
class AntModel(Model):
    """A model with some number of ants"""

    def __init__(self, av_number_of_drive_ants = 1, number_of_food = 20, width = 20, height = 20):
        super().__init__()

        self.global_agent_index = 1
        self.width = width
        self.height = height

        self.av_number_of_drive_ants = av_number_of_drive_ants

        self.initialize_food_grid(number_of_food)
        self.grid = MultiGrid(width, height, False)
        self.schedule = RandomActivation(self)
        self.running = True
        self.datacollector = DataCollector(
            agent_reporters = {"Hunger": "hunger"})

    def initialize_food_grid(self, number_of_food):
        self.number_of_food = number_of_food
        food_array = np.zeros((self.width*self.height), dtype=int)
        assert number_of_food <= self.width*self.height
        food_array[:number_of_food] = 1
        food_array = np.random.permutation(food_array)
        self.food_array = food_array.reshape((self.width, self.height))

    def add_ant(self, pos):
        assert pos[0] >= 0 and pos[0] <= self.width
        assert pos[1] >= 0 and pos[1] <= self.height
        ant = Ant(self.global_agent_index, self)
        self.schedule.add(ant)
        x,y = pos
        self.grid.place_agent(ant, (x, y))
        self.global_agent_index += 1

    def drive(self):
        number_of_ants_to_add = np.random.poisson(self.av_number_of_drive_ants)

        for i in range(number_of_ants_to_add):

            coordHoriz = (
                np.random.choice([0, self.width - 1]),
                np.random.randint(self.height))

            coordVert = (
                np.random.randint(self.width),
                np.random.choice([0, self.height - 1]))

            index = np.random.choice([0, 1])
            coord = np.array([coordHoriz, coordVert])[index]

            self.add_ant(coord)

    def topple(self):
        pass

    def satiated_ants_on_boundary(self):
        ants = []
        for ant in self.schedule.agents:
            if ant.is_satiated():
                pos = ant.pos
                if pos[0] == 0 or pos[0] == self.width - 1 or pos[1] == 0 or pos[1] == self.height - 1:
                    ants.append(ant)
        return ants

    def dissipate(self):
        for ant in self.satiated_ants_on_boundary():
            self.schedule.remove(ant)
            self.grid.remove_agent(ant)

    def step(self):
        """advance the model by one step"""
        self.drive()
        self.topple()
        self.dissipate()
        self.datacollector.collect(self)
        self.schedule.step()

    def plot_state(self):
        agent_counts = np.zeros((self.grid.width, self.grid.height))
        for cell in self.grid.coord_iter():
            cell_content, x, y = cell
            agent_count = len(cell_content)
            agent_counts[x][y] = agent_count
        plt.imshow(agent_counts, interpolation = 'nearest')
        plt.colorbar()

    def plot_food(self):
        plt.imshow(self.food_array)
[16]:
mdl = AntModel(av_number_of_drive_ants = 10, number_of_food = 10,  width = 20, height = 20)

mdl.step()

mdl.plot_state()
_images/RSOC_24_0.svg
[18]:
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer

def agent_portrayal(agent):
    portrayal = {
        "Shape": "circle",
        "Filled": "true",
        "r": 1,
        "Color": "red",
        "Layer": 0,
        "text": agent.hunger,
        "text_color": "black"}

    if agent.state == 'wandering':
        portrayal["Color"] = "red"
        portrayal["Layer"] = 0
    elif agent.state == 'feeding':
        portrayal["Color"] = "yellow"
        portrayal["Layer"] = 1
    elif agent.state == 'satiated':
        portrayal["Color"] = "green"
        portrayal["Layer"] = 2
    elif agent.state == 'disturbed':
        portrayal["Color"] = "black"
        portrayal["Layer"] = 3

    return portrayal

grid = CanvasGrid(agent_portrayal, 30, 30, 700, 700)
server = ModularServer(AntModel,
                       [grid],
                       "Rapid Self-Organized Criticality",
                       {"av_number_of_drive_ants": 0.05, "number_of_food" : 30, "width": 30, "height": 30})
server.port = 8551 # The default
server.launch()
Interface starting at http://127.0.0.1:8551
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-18-525ae9a27079> in <module>
     33                        {"av_number_of_drive_ants": 0.05, "number_of_food" : 30, "width": 30, "height": 30})
     34 server.port = 8551 # The default
---> 35 server.launch()

~\AppData\Local\Programs\Python\Python37\lib\site-packages\mesa\visualization\ModularVisualization.py in launch(self, port)
    322         webbrowser.open(url)
    323         tornado.autoreload.start()
--> 324         tornado.ioloop.IOLoop.current().start()

~\AppData\Local\Programs\Python\Python37\lib\site-packages\tornado\platform\asyncio.py in start(self)
    146             self._setup_logging()
    147             asyncio.set_event_loop(self.asyncio_loop)
--> 148             self.asyncio_loop.run_forever()
    149         finally:
    150             asyncio.set_event_loop(old_loop)

~\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py in run_forever(self)
    524         self._check_closed()
    525         if self.is_running():
--> 526             raise RuntimeError('This event loop is already running')
    527         if events._get_running_loop() is not None:
    528             raise RuntimeError(

RuntimeError: This event loop is already running