How to implement a Dynamic Chart using MESA library?

69 Views Asked by At

I have been learning how to use MESA Agent Based Modelling library in python to help my PhD student supervisor. I am simulating the activity and bone formation phase of the bone remodelling process. I specifically focus on osteoblasts cells and how they work together to deposit bone matrix.

I have finally understood how to actually use the agents and model commands from MESA, I will share my code so that it an be checked. My code basically shows osteoblast cells moving around bone matrix until reaching a white bone matrix (cell with density = 0). Once they locate themselves in a cell with density == 0 they stop and start secreting bone, making the density increase by 0.5 at each step. My problem is that I have tried to add a dynamic line graph so that I can see at each step the bone formation rate that would be measured by the increase in the density. Every time I import ChartModule and DataCollector and run my code everything in the server visualization goes blank. I would like a suggestion, not any solution of how to tackle this.

This first code is my working version without any graph added. I would like to implement to this code a data collector module with a ChartModule module from MESA to see a dynamic chart.

# -*- coding: utf-8 -*-
"""
Created on Thu Aug 31 15:46:01 2023

@author: alvar
"""
import mesa
from mesa.time import RandomActivation
from mesa.space import MultiGrid
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer


class Barrier(mesa.Agent): # Barrier agent is just the line that separates the soft tissue or outside bone are from bone matrix.
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        
        
class BoneMatrix(mesa.Agent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        self.density = 5
        
    def step(self):
        print(f"bone_matrix({self.unique_id}) is taking a big steppy")

class Osteocyte(mesa.Agent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        
    def step(self):
        print(f"osteocyte{self.unique_id} is taking a big steppy")
    
class Osteoblast(mesa.Agent):
    def __init__(self, unique_id, model, white_cells,white_cell_colors):
        super().__init__(unique_id,model)
        self.white_cells = white_cells
        self.white_cell_colors = white_cell_colors
        self.mobile = True 
         
    
    def move(self):
        while True:
            #look for move
            possible_steps = self.model.grid.get_neighborhood(
                self.pos, moore = True, include_center = False
            )
            new_position = self.random.choice(possible_steps)
            cell_contents = self.model.grid.get_cell_list_contents(new_position)

            no_osteocyte_present = not any(isinstance(agent, Osteocyte) for agent in cell_contents)
            
            if no_osteocyte_present:
                break
        
        self.model.grid.move_agent(self,new_position)
        

        white_bone_matrix_present = any(
            isinstance(agent, BoneMatrix) and agent.density == 0 for agent in cell_contents
        )

        osteo_neighbour_present = False
        for nx, ny in self.model.grid.get_neighborhood(new_position, moore=True, include_center = False):
                        neighbour_contents = self.model.grid.get_cell_list_contents((nx,ny))
                        cell_density_max = False
                        embedded_osteoblast = False
                        if (any(isinstance(agent,Osteoblast) for agent in neighbour_contents)):
                            for n in neighbour_contents:
                                if isinstance(n, BoneMatrix):
                                    if (n.density == 5):
                                        cell_density_max = True
                                if isinstance(n, Osteoblast):
                                    if (not n.mobile):
                                        embedded_osteoblast = True
                        condition1 = any(isinstance(agent,Osteocyte) for agent in neighbour_contents)
                        condition2 = cell_density_max and embedded_osteoblast
                        if (condition1 or condition2):
                            osteo_neighbour_present = True
                            break
        if (white_bone_matrix_present and osteo_neighbour_present):
            self.mobile = False

    def differentiation(self):
        return 0
    
    def apoptosis(self):
        return 0
        
    def step(self):
        if (self.mobile):
            self.move()


class BoneModel(mesa.Model):
    def __init__(self,width, height):
        self.grid = MultiGrid(width, height, False)
        self.schedule = RandomActivation(self)

        # Loop to create Barrier agents
        for r in range(width):
            for s in range(height-3, height-2):
                barrier = Barrier(f'barrier_r{r}_{s}', self)
                self.grid.place_agent(barrier, (r,s))
                self.schedule.add(barrier)
        
        white_cells = set()
        
        while len(white_cells) < 12:
            x, y = self.random.randrange(3,7), self.random.randrange(height - 6, height - 3)
            white_cells.add((x, y))
        
        white_cell_colors = {cell: 0 for cell in white_cells}

        # Loop to create BoneMatrix agents
        for x in range(width):
            for y in range(height - 3):
                bm = BoneMatrix(f' bone_matrix_{x}_{y}', self)
                if (x, y) in white_cells:
                    bm.density = 0
                self.grid.place_agent(bm, (x, y))
                self.schedule.add(bm)
                
              
        
        # Loop to create Osteocyte agents and place them in the bottom 4 rows
        for p in range(width):
            for q in range(height-6):
                osc = Osteocyte(f' osteocyte_{p}_{q}', self)
                self.grid.place_agent(osc, (p, q))
                self.schedule.add(osc)

        # Loop to create Osteoblast agents and place them in the top 2 rows in different gridcells
        unique_positions = set()  # To keep track of unique positions
        for _ in range(12):
            while True:
                x, y = self.random.randrange(width), self.random.randrange(height-2, height)
                if (x, y) not in unique_positions:  # Check if the position is unique
                    osb = Osteoblast(f' osteoblast_{_}', self, white_cells, white_cell_colors)
                    self.grid.place_agent(osb, (x, y))
                    self.schedule.add(osb)
                    unique_positions.add((x, y))  
                    break
                   
                          
    def step(self):
        # Iterate through all grid cells
        self.schedule.step()
        
        
        for x in range(width):
            for y in range(height):
                cell_contents = self.grid.get_cell_list_contents((x, y))
                osteoblast_present = any(isinstance(agent, Osteoblast) for agent in cell_contents)
                
                
                neighbours = self.grid.get_neighborhood((x,y), moore=True, include_center=False)
                
                osteo_neighbour_present = False
                for nx, ny in neighbours:
                    neighbour_contents = self.grid.get_cell_list_contents((nx,ny))
                    cell_density_max = False
                    embedded_osteoblast = False
                    if (any(isinstance(agent, Osteoblast) for agent in neighbour_contents)):   
                        for n in neighbour_contents:
                                if isinstance(n, BoneMatrix):
                                    if (n.density == 5):
                                        cell_density_max=True
                                if isinstance(n, Osteoblast):
                                    if (not n.mobile):
                                        embedded_osteoblast = True
                    condition1 = any(isinstance(agent,Osteocyte) for agent in neighbour_contents) # osteocyte neighbyr
                    condition2 = cell_density_max and embedded_osteoblast# osteoblast fully formed neighbor
                    if (condition1 or condition2):
                        osteo_neighbour_present = True
                        break

                for agent in cell_contents:
                    if isinstance(agent, BoneMatrix):
                        if osteoblast_present and osteo_neighbour_present:
                            if agent.density < 5:
                                agent.density += 0.25
                osteo_neighbour_present = False
                      
def agent_portrayal(agent):
    portrayal = {
        "Shape": "rect",
        "w": 1,
        "h": 1, 
    }

    if isinstance(agent, BoneMatrix):
        # Map density to shades of blue between 0 and 10
        max_density = 10  # Set the maximum density
        min_density = 0  # Set the minimum density
        density = agent.density
        normalized_density = (density - min_density) / (max_density - min_density)
        color = f"rgba(0, 0, 255, {normalized_density})"
        portrayal["Color"] = color
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 0
        portrayal["text"] = "BM"
        portrayal["text_color"] = "white"
    elif isinstance(agent, Osteocyte):
        portrayal["Shape"] = "circle"
        portrayal["Color"] = "salmon"
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 1
        portrayal["text"] = "Osc"
        portrayal["r"] = 0.45
    elif isinstance(agent, Barrier):
        portrayal["Color"] = "rgba(245, 200, 245, 1)"
        
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 2
    elif isinstance(agent, Osteoblast):
        portrayal["Shape"] = "rect"
        portrayal["Color"] = "green"
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 3
        portrayal["text"] = "Osb"
        portrayal["w"] = 0.5
        portrayal["h"] = 0.3
        portrayal["x"] = -0.1
        portrayal["y"] = 0
        portrayal["r"] = 3
        if agent.pos in agent.white_cells:
            portrayal["Color"] = "yellow" 

    return portrayal


width = 10 # Grid width
height =  10 # Grid height

canvas_grid = CanvasGrid(agent_portrayal, width, height, 500, 500)
# Create a DataCollector object to collect data

server = ModularServer(
    BoneModel,
    [canvas_grid],
    "Bone Model",
    {"width": width, "height": height}
)

server.port = 8521  # Set the port for the web interface
server.launch()

This second code was my attempt of adding one. I still do not understand very well how to implement it and where to put the data collector function. I want to collect at the agents level which is just the increase of density and in the model i want to calculate the bone_formation_rate which will be measured by the increase of density at each step.

# -*- coding: utf-8 -*-
"""
Created on Thu Aug 31 15:46:01 2023

@author: alvar
"""
import mesa
from mesa.time import RandomActivation
from mesa.space import MultiGrid
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer
from mesa.datacollection import DataCollector
from mesa.visualization.modules import ChartModule


def bone_formation_rate(self):
    white_bone_matrix_agents = [agent for agent in self.schedule.agents if isinstance(agent, BoneMatrix) and agent.density == 0]
    total_density_increase = sum(1 for agent in white_bone_matrix_agents if agent.density > 0)
    return total_density_increase

class Barrier(mesa.Agent): # Barrier agent is just the line that separates the soft tissue or outside bone are from bone matrix.
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        
        
class BoneMatrix(mesa.Agent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        self.density = 5

        
     
        
    def step(self):
        print(f"bone_matrix({self.unique_id}) is taking a big steppy")

class Osteocyte(mesa.Agent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        
        
    def step(self):
        print(f"osteocyte{self.unique_id} is taking a big steppy")
    
class Osteoblast(mesa.Agent):
    def __init__(self, unique_id, model, white_cells,white_cell_colors):
        super().__init__(unique_id,model)
        self.white_cells = white_cells
        self.white_cell_colors = white_cell_colors
        self.mobile = True 
         
    
    def move(self):
        while True:
            #look for move
            possible_steps = self.model.grid.get_neighborhood(
                self.pos, moore = True, include_center = False
            )
            new_position = self.random.choice(possible_steps)
            cell_contents = self.model.grid.get_cell_list_contents(new_position)

            no_osteocyte_present = not any(isinstance(agent, Osteocyte) for agent in cell_contents)
            
            if no_osteocyte_present:
                break
 
     
        
        
        self.model.grid.move_agent(self,new_position)
        

        white_bone_matrix_present = any(
            isinstance(agent, BoneMatrix) and agent.density == 0 for agent in cell_contents
        )

        osteo_neighbour_present = False
        for nx, ny in self.model.grid.get_neighborhood(new_position, moore=True, include_center = False):
                        neighbour_contents = self.model.grid.get_cell_list_contents((nx,ny))
                        cell_density_max = False
                        embedded_osteoblast = False
                        if (any(isinstance(agent,Osteoblast) for agent in neighbour_contents)):
                            for n in neighbour_contents:
                                if isinstance(n, BoneMatrix):
                                    if (n.density == 5):
                                        cell_density_max = True
                                if isinstance(n, Osteoblast):
                                    if (not n.mobile):
                                        embedded_osteoblast = True
                        condition1 = any(isinstance(agent,Osteocyte) for agent in neighbour_contents)
                        condition2 = cell_density_max and embedded_osteoblast
                        if (condition1 or condition2):
                            osteo_neighbour_present = True
                            break
        if (white_bone_matrix_present and osteo_neighbour_present):
            self.mobile = False

    def differentiation(self):
        return 0
    
    def apoptosis(self):
        return 0
        
    def step(self):
        if (self.mobile):
            self.move()


class BoneModel(mesa.Model):
    def __init__(self,width, height):
        self.grid = MultiGrid(width, height, False)
        self.schedule = RandomActivation(self)
        
        self.datacollector = DataCollector(
            model_reporters={"Density": "density"},
            agent_reporters={"WhiteBoneMatrixDensity": lambda a: a.density if isinstance(a, BoneMatrix) and a.density == 0 else None},
        )

        # Loop to create Barrier agents
        for r in range(width):
            for s in range(height-3, height-2):
                barrier = Barrier(f'barrier_r{r}_{s}', self)
                self.grid.place_agent(barrier, (r,s))
                self.schedule.add(barrier)
        
        white_cells = set()
        
        while len(white_cells) < 12:
            x, y = self.random.randrange(3,7), self.random.randrange(height - 6, height - 3)
            white_cells.add((x, y))
        
        white_cell_colors = {cell: 0 for cell in white_cells}

        # Loop to create BoneMatrix agents
        for x in range(width):
            for y in range(height - 3):
                bm = BoneMatrix(f' bone_matrix_{x}_{y}', self)
                if (x, y) in white_cells:
                    bm.density = 0
                self.grid.place_agent(bm, (x, y))
                self.schedule.add(bm)
                
             
        
        # Loop to create Osteocyte agents and place them in the bottom 4 rows
        for p in range(width):
            for q in range(height-6):
                osc = Osteocyte(f' osteocyte_{p}_{q}', self)
                self.grid.place_agent(osc, (p, q))
                self.schedule.add(osc)

        # Loop to create Osteoblast agents and place them in the top 2 rows in different gridcells
        unique_positions = set()  # To keep track of unique positions
        for _ in range(12):
            while True:
                x, y = self.random.randrange(width), self.random.randrange(height-2, height)
                if (x, y) not in unique_positions:  # Check if the position is unique
                    osb = Osteoblast(f' osteoblast_{_}', self, white_cells, white_cell_colors)
                    self.grid.place_agent(osb, (x, y))
                    self.schedule.add(osb)
                    unique_positions.add((x, y))  
                    break
                   
                          
    def step(self):
        # Iterate through all grid cells
        self.datacollector.collect(self)
        self.schedule.step()
        
        
        for x in range(width):
            for y in range(height):
                cell_contents = self.grid.get_cell_list_contents((x, y))
                osteoblast_present = any(isinstance(agent, Osteoblast) for agent in cell_contents)
                
                
                neighbours = self.grid.get_neighborhood((x,y), moore=True, include_center=False)
                
                osteo_neighbour_present = False
                for nx, ny in neighbours:
                    neighbour_contents = self.grid.get_cell_list_contents((nx,ny))
                    cell_density_max = False
                    embedded_osteoblast = False
                    if (any(isinstance(agent, Osteoblast) for agent in neighbour_contents)):   
                        for n in neighbour_contents:
                                if isinstance(n, BoneMatrix):
                                    if (n.density == 5):
                                        cell_density_max=True
                                if isinstance(n, Osteoblast):
                                    if (not n.mobile):
                                        embedded_osteoblast = True
                    condition1 = any(isinstance(agent,Osteocyte) for agent in neighbour_contents) # osteocyte neighbyr
                    condition2 = cell_density_max and embedded_osteoblast# osteoblast fully formed neighbor
                    if (condition1 or condition2):
                        osteo_neighbour_present = True
                        break

                for agent in cell_contents:
                    if isinstance(agent, BoneMatrix):
                        if osteoblast_present and osteo_neighbour_present:
                            if agent.density < 5:
                                agent.density += 0.25
                osteo_neighbour_present = False
        

    
       
def agent_portrayal(agent):
    portrayal = {
        "Shape": "rect",
        "w": 1,
        "h": 1, 
    }

    if isinstance(agent, BoneMatrix):
        # Map density to shades of blue between 0 and 10
        max_density = 10  # Set the maximum density
        min_density = 0  # Set the minimum density
        density = agent.density
        normalized_density = (density - min_density) / (max_density - min_density)
        color = f"rgba(0, 0, 255, {normalized_density})"
        portrayal["Color"] = color
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 0
        portrayal["text"] = "BM"
        portrayal["text_color"] = "white"
    elif isinstance(agent, Osteocyte):
        portrayal["Shape"] = "circle"
        portrayal["Color"] = "salmon"
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 1
        portrayal["text"] = "Osc"
        portrayal["r"] = 0.45
    elif isinstance(agent, Barrier):
        portrayal["Color"] = "rgba(245, 200, 245, 1)"
        
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 2
    elif isinstance(agent, Osteoblast):
        portrayal["Shape"] = "rect"
        portrayal["Color"] = "green"
        portrayal["Filled"] = "true"
        portrayal["Layer"] = 3
        portrayal["text"] = "Osb"
        portrayal["w"] = 0.5
        portrayal["h"] = 0.3
        portrayal["x"] = -0.1
        portrayal["y"] = 0
        portrayal["r"] = 3
        if agent.pos in agent.white_cells:
            portrayal["Color"] = "yellow" 

    return portrayal


width = 10 # Grid width
height =  10 # Grid height

canvas_grid = CanvasGrid(agent_portrayal, width, height, 500, 500)
chart = ChartModule([{"Label": "Density", "Color": "blue"}, {"Label": "WhiteBoneMatrixDensity", "Color": "red"}])

server = ModularServer(
    BoneModel,
    [canvas_grid, chart],  # Include the chart module
    "Bone Model",
    {"width": width, "height": height}
)

server.port = 8521  # Set the port for the web interface
server.launch()
0

There are 0 best solutions below