Agent-based segregation model (Python)

I previously introduced the first sprint of an agent-based modeling and simulation framework that I am working on. In this blog post I implement an agent-based segregation model in Python. Similar models, with some adjustments and deviations, are e.g. used for understanding social segregation. The primary objective of this blog post is to deliver another demonstration of agent-based modeling and simulation in Python. The framework used is the abm_framework in Python.

Framework for agent-based modeling in Python

I develop a segregation model as another application example of the agent-based modeling framework that I am working on in Python. You can download the framework on GitHub here:

The GitHub repository contains a demo folder. Exemplary applications are available here. At the time of the publication of this article there are not that many demo models available yet. I will add more demo models in the weeks to come, and the existing modules will be expanded to support a wider range of agent-based modeling applications.

In the abm_framework the important modules are the following ones:

  • framework.py for modeling e.g. agents, agent populations and the simulation environment
  • data.py for managing database operations for writing and reading simulation results
  • stats.py for plotting simulation results (also independently of simulation run execution)
  • config.py for e.g. specifying relevant paths, e.g. the database file path
  • animation.py for animating simulation results

Below figure summarizes the content of the abm_framework repository on GitHub.

framework for agent-based modeling and agent-based simulation. Used for agent-based segregation model implementation.

The file model.py is the simulation model itself. In case of the example described in the article at hand I e.g. named the file segregation.py. The model file implements a specific model. The framework (with all modules described above) serves as a toolbox. In other words, the framework is meant to make it easier to implement a specific simulation model.

Conceptual agent-based segregation model description

As I am using the agent-based modeling framework presented by me in previous articles, I am developing a grid-based agent-based simulation model. That is, I assign agent to a grid. Agents thus have locations. Agents also have a neighbourhood with a specified radius. Interaction between agents take place within this neighbourhood. Below figure, taken from my previous post on agent-based SIR model implementation in Python, illustrates this concept.

In this examplary agent-based segragation model I facilitate segregation mechanisms by implementing below flow chart:

flow chart for conceptual model description of agent-based segregation-model implementation

For every iteration I pick one agent randomly. I then calculate the utility impact in the agent’s current cell. This utility impact is calculated by iterating over all neighbouring agents. If the neighbour is of “same type” as the agent itself, the utility impact is increased (positive impact). If the neighbour is not of “same type” the utility impact is decreased (negative impact).

Next, a random free cell on the grid is selected and the hypothetical utility impact in that cell is calculated. For this calculation the same logic as described above applies. If the hypothetical utility impact in that cell is positive the agent relocates to that location and updates its utility again. If not, another free cell is selected and the same test applies. The process repeats itself up to a maximum search limit, i.e. a specified maximum number of repititions.

Agent-based segregation model implementation in Python

Below is the code that implements the agent-based segregation model.

__author__ = "Linnart Felkl"
__email__ = "LinnartSF@gmail.com"

if __name__ == "__main__":

    print("demo starts")

    import sys
    from pathlib import Path
    file = Path(__file__).resolve()
    parent, root = file.parent, file.parents[1]
    sys.path.append(str(root))

    # remove the current file's directory from sys.path, unless already removed
    try:
        sys.path.remove(str(parent))
    except ValueError:
        pass

    import data
    import stats
    import config
    import framework
    import random
    import animation

    # setup database manager and connection
    db = data.Database("sqlite3", config.path_databasefile)
    db_manager = data.Manager(db)
    
    # create an empty environment
    env = framework.Environment(1, True, 20, 20, db_manager)

    # create initial population of healthy humans
    attrs = ["utility","type"]
    datatypes = ["REAL","TEXT"]
    pops = framework.Populations(amount = 2, env = env, db_manager = db_manager, attributes = attrs, datatypes = datatypes)
    pops.add_population(name = "natives", 
                        size = 50, 
                        attributes = attrs, 
                        datatypes = datatypes, 
                        initialvals = [100, "native"]
                        )
    pops.add_population(name = "immigrants",
                        size = 50,
                        attributes = attrs,
                        datatypes = datatypes,
                        initialvals = [100, "immigrant"]
                        )
    
    # setup simulation
    sim = framework.Simulation(1000)

    # make sure that environment and agents tables in database are set
    pops.write_env_to_db(sim.Iteration)
    pops.write_agents_to_db(sim.Iteration)
    
    agents = pops.get_agents()

    # other model specific global settings
    _max_search = 10
    _impactarea = 1

    # execute simulation run
    while sim.run():
        
        # select one random agent
        agent = random.choice(agents)

        # get that agents neighbourhood
        neighbours = env.get_neighbourhood(agent, mode = "moore", radius = _impactarea)

        util_is = 0.0
        
        # if there are neighbours, then recalculate the utility of the agent
        for o in neighbours:
            
            if o.get_attr_value("type") == agent.get_attr_value("type"):

                util_is += 10
            
            else:

                util_is += -10
        
        # update agent utility 
        agent.increase_attr_value("utility",util_is)
        
        # for search up to maximum limit of random free cells
        cells = env.get_freecells(n = _max_search)

        for c in cells:
            
            util_new = 0.0

            neighbours = env.get_neighbourhood(c, "moore", radius = _impactarea)

            for o in neighbours:

                if o.get_attr_value("type") == agent.get_attr_value("type"):
            
                    util_new += 10

                else:

                    util_new += -10
            
            if util_new > util_is:

                # relocate agent, then break loop
                env.relocate(agent, c)
                agent.increase_attr_value("utility",util_new)
                break
                
        # update results in database, for agents and for environment
        if (sim.Iteration % 10) == 0:
            pops.write_agents_to_db(sim.Iteration)
            pops.write_env_to_db(sim.Iteration)
            pops.write_density_to_db(sim.Iteration)
    
    # get dataframes with simulation results 
    agents_df = db_manager.get_agentsdf()
    env_df = db_manager.get_environmentdf()
    density_df = db_manager.get_densitydf()
    
    # visualize simulation data
    stats.set_fontsizes(8,10,12)

    stats.plot_grid_occupation(env_df, ["natives","immigrants"], colors = ["#F52D2D","#4A87F1"], maxtime=0, markersize = 150.0)
    stats.save_plot("segplt_early_ia1_50agents_1000it")

    stats.plot_grid_occupation(env_df, ["natives","immigrants"], colors = ["#F52D2D","#4A87F1"], maxtime=500, markersize = 150.0)
    stats.save_plot("segplt_intermediate_ia1_50agents_1000it")

    stats.plot_grid_occupation(env_df, ["natives","immigrants"], colors = ["#F52D2D","#4A87F1"], maxtime=1000, markersize = 150.0)
    stats.save_plot("segplt_late_ia1_50agents_1000it")

    stats.plot_avgattr_lines(["utility"], agents_df)
    stats.save_plot("avgutil_ia1_50agents_1000it")

    animation.animate_grid_occupation(
                            df = env_df,
                            filename = "segvid_ia1_50agents_1000it",
                            population = ["natives","immigrants"],
                            colors = ["#F52D2D","#4A87F1"],
                            tpf = 0.20, # time per frame
                            mintime = 0,
                            maxtime = 1000, 
                            markersize = 150.0
                        )

    animation.animate_density(
                            df = density_df,
                            filename = "segdens_ia1_50agents_1000it",
                            attr = "utility",
                            defaultsize = 150,
                            color = "#F52D2D",
                            tpf = 0.20,
                            maxtime = 1000
    )
    
    # end program
    db.close()
    print("demo ends")

As this is just an exemplary article, demonstrating how you can use the grid-based agent-based modeling framework for agent-based segregation models, I execute 1000 iterations only. For a limited number of iterations I can write the current system states (agent attribute values, grid occupation etc.) into my results database. If I have a much higher number of iterations I should however only write a subset of iterations into my database. E.g. every 10th iteration. Likewise, to make animation creation faster I can e.g. subset results by only pulling every nth simulation time from the database. When using matplotlib for animations I should furthermore avoid using loc = “best” for legend location selection.

Agent-based segregation model simulation results

In this section I present visualizations generated by above simulation run. The visualizations come in a standardized form. I created them by making use of my GitHub repository (abm_framework). That is, the framework supports database management for storage of simulation results, and visualization as well as animation of simulation results.

Below plot visualizes grid occupation at simulation start.

And lastly, a plot that visualizes grid occupation after 1000 iterations.

To complete this section I also want to look at average agent utility throughout simulation time. I can visualize this using the plot_avgattr_lines() function provided stats.py of the abm_framework.

The average utility line keeps increasing. That indicates that agents on average are in a state in which they accumulate more utility. This state is most likely not stationary, and there are segregation models that e.g. limit the maximum utility of an agent, or only consider changes in utility when the environment changes. There are also agent-based segregation models that only allow an agent to relocate if the respective agent’s situation is “bad enough” (i.e. agents can tolerate utility losses up to a certain limit).

As stated, the stats.py module also supports animations. In the following section two animations of the agent-based segragation model will be presented.

Animation of agent-based segregation model

For this exemplary framework implementation I made use of two standardized animations. The first one shows changes in agent locations throughout time.

The second animation shows how utility density changes throughout time. Clusters with very high utility are formed. At the same time other areas suffer from very low utility (when compared to other agents).

If I wanted to I could now e.g. proceed by having a closer look at the distributions of agent utility etc. However, this blog post was meant to demonstrate how my agent-based framework (currently in its early stages) can be applied to segregation model development. I have done so now. I will document other exemplary applications of the ABM framework in future blog posts.

Related agent-based modeling content

Additional content related to agent-based modeling and agent-based simulation is available on our blog. Here are some related articles that might be of interest to you:

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.