1. Basics of specialized workflows
  2. Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl
  3. Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl
  • (Just enough) Julia for scientific informatics, modeling, and reasoning
  • Introduction
  • Basic frameworks and mechanisms
    • Orientation
    • Basics of setting up and running Julia
    • Basics of visualizing mathematical models
    • Basics of working with randomness and probabilities
    • Basics of working with data tables
  • Basics of specialized workflows
    • Basics of paleobiological fossil collection analyses
    • Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl
      • Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl
    • Basics of species distribution modeling
  • Primers
    • Bernoulli trial
    • Pathogen fitness as a function of virulence (Frank, 1996)
    • Virulence-transmission trade-off (Frank, 1996)
    • Julia – Environments – Global vs project
    • Julia: Functions, methods, and signatures
    • Markov property
    • Probabilty distributions–Essential concepts
    • Pseudo-random number generators
    • Pseudo-random number generators: best practices
    • Pseudo-random number generators: continuous values from discrete machines

On this page

  • 1 Setting up the environment
  • 2 Defining the agent
  • 3 Model parameters and initialization
  • 4 The stepping function
  • 5 Running the model
    • 5.1 Single step
    • 5.2 Running for many steps and collecting data
  • 6 Visualizing the epidemic time series
  • 7 Visualizing the spatial state
    • 7.1 Multi-panel snapshot sequence
  • 8 Parameter sweeps with paramscan
  • 9 Extending the model: heterogeneous transmission
  1. Basics of specialized workflows
  2. Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl
  3. Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl

Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl

Author

Jeet Sukumaran

1 Setting up the environment

using Pkg
Pkg.add(["Agents", "CairoMakie", "DataFrames", "Random", "StatsBase"])
using Agents
using CairoMakie
using DataFrames
using Random
using StatsBase: sample, Weights
NoteAgents.jl version

This chapter targets Agents.jl v6.x.

In v6, stepping functions are attached to the model when constructing the StandardABM, using the agent_step! (and optionally model_step!) keyword arguments. After that, step!(model, n) and run!(model, n) advance the model for n steps.

2 Defining the agent

@agent struct Host(GridAgent{2})
    status::Symbol
    days_infected::Int
end

3 Model parameters and initialization

function initialize_sir(;
    grid_size          = (50, 50),
    n_initial_infected = 5,
    infection_prob     = 0.06,
    recovery_time      = 14,
    seed               = 42
)
    rng   = Random.Xoshiro(seed)
    space = GridSpace(grid_size; periodic = false, metric = :chebyshev)

    properties = (
        infection_prob = infection_prob,
        recovery_time  = recovery_time,
        rng            = rng
    )

    model = StandardABM(
        Host, space;
        properties  = properties,
        rng         = rng,
        scheduler   = Schedulers.Randomly(),
        agent_step! = sir_step!,
    )

    for pos in positions(model)
        add_agent!(pos, model, :S, 0)
    end

    initial_infected = sample(
        rng,
        collect(positions(model)),
        n_initial_infected;
        replace = false
    )

    for pos in initial_infected
        agent = model[ids_in_position(pos, model)[1]]
        agent.status = :I
        agent.days_infected = 1
    end

    return model
end

4 The stepping function

In Agents.jl v6.x, the stepping function must have signature

agent_step!(agent, model)

and is attached to the model via the agent_step! keyword in the StandardABM constructor.

function sir_step!(agent::Host, model)
    if agent.status == :S
        for neighbor in nearby_agents(agent, model, 1)
            if neighbor.status == :I
                if rand(model.rng) < model.infection_prob
                    agent.status = :I
                    agent.days_infected = 1
                    break
                end
            end
        end

    elseif agent.status == :I
        agent.days_infected += 1
        if agent.days_infected >= model.recovery_time
            agent.status = :R
        end
    end

    return nothing
end

5 Running the model

5.1 Single step

model = initialize_sir(seed = 42)

step!(model, 1)

5.2 Running for many steps and collecting data

n_susceptible(agent) = agent.status == :S
n_infected(agent)    = agent.status == :I
n_recovered(agent)   = agent.status == :R

adata = [
    (n_susceptible, count),
    (n_infected,    count),
    (n_recovered,   count)
]

model = initialize_sir(seed = 42)

agent_data, _ = run!(model, 200; adata)

first(agent_data, 8)

6 Visualizing the epidemic time series

fig = Figure(size = (800, 380))
ax  = Axis(
    fig[1, 1];
    xlabel = "time step",
    ylabel = "number of agents",
    title  = "SIR epidemic dynamics on a 50 × 50 spatial grid"
)

lines!(ax, agent_data.step, agent_data.count_n_susceptible;
    color = :steelblue3, linewidth = 2, label = "Susceptible (S)")

lines!(ax, agent_data.step, agent_data.count_n_infected;
    color = :firebrick3, linewidth = 2, label = "Infected (I)")

lines!(ax, agent_data.step, agent_data.count_n_recovered;
    color = :seagreen, linewidth = 2, label = "Recovered (R)")

axislegend(ax; position = :rc)

fig

7 Visualizing the spatial state

model_snap = initialize_sir(seed = 42)

step!(model_snap, 40)

status_color(agent) =
    agent.status == :S ? :steelblue3 :
    agent.status == :I ? :firebrick3 :
    :seagreen

fig_snap, ax_snap, abmobs = abmplot(
    model_snap;
    agent_color = status_color,
    agent_size  = 6,
    figure      = (; size = (520, 520)),
    axis        = (; title = "Spatial state at step 40")
)

fig_snap

7.1 Multi-panel snapshot sequence

snapshots = [5, 20, 40, 80, 120, 200]

fig_seq = Figure(size = (1050, 700))

for (i, t) in enumerate(snapshots)
    row = div(i - 1, 3) + 1
    col = mod(i - 1, 3) + 1

    m = initialize_sir(seed = 42)
    step!(m, t)

    ax = Axis(fig_seq[row, col];
        title = "t = $t",
        aspect = DataAspect())

    hidedecorations!(ax)

    xs = [a.pos[1] for a in allagents(m)]
    ys = [a.pos[2] for a in allagents(m)]
    cs = [status_color(a) for a in allagents(m)]

    scatter!(ax, xs, ys; color = cs, markersize = 4)
end

fig_seq

8 Parameter sweeps with paramscan

parameters = Dict(
    :infection_prob  => [0.02, 0.04, 0.06, 0.10],
    :recovery_time   => [7, 14, 21],
    :seed            => [1, 2, 3]
)

scan_data, _ = paramscan(
    parameters,
    initialize_sir;
    n     = 200,
    adata = [(n_recovered, count)]
)

final_sizes = combine(
    groupby(
        filter(:step => s -> s == 200, scan_data),
        [:infection_prob, :recovery_time]
    ),
    :count_n_recovered => mean => :mean_recovered,
    :count_n_recovered => std  => :std_recovered
)

final_sizes

9 Extending the model: heterogeneous transmission

@agent struct VariableHost(GridAgent{2})
    status::Symbol
    days_infected::Int
    susceptibility::Float64
end
function variable_sir_step!(agent::VariableHost, model)
    if agent.status == :S
        for neighbor in nearby_agents(agent, model, 1)
            if neighbor.status == :I
                if rand(model.rng) <
                   model.infection_prob * agent.susceptibility
                    agent.status = :I
                    agent.days_infected = 1
                    break
                end
            end
        end

    elseif agent.status == :I
        agent.days_infected += 1
        if agent.days_infected >= model.recovery_time
            agent.status = :R
        end
    end

    return nothing
end
function initialize_variable_sir(;
    grid_size           = (50, 50),
    n_initial_infected  = 5,
    infection_prob      = 0.08,
    recovery_time       = 14,
    susceptibility_mean = 0.8,
    susceptibility_sd   = 0.2,
    seed                = 42
)
    rng   = Random.Xoshiro(seed)
    space = GridSpace(grid_size; periodic = false, metric = :chebyshev)

    properties = (
        infection_prob = infection_prob,
        recovery_time  = recovery_time,
        rng            = rng
    )

    model = StandardABM(
        VariableHost, space;
        properties  = properties,
        rng         = rng,
        scheduler   = Schedulers.Randomly(),
        agent_step! = variable_sir_step!,
    )

    for pos in positions(model)
        s = clamp(
            susceptibility_mean +
            susceptibility_sd * randn(rng),
            0.0, 1.0
        )
        add_agent!(pos, model, :S, 0, s)
    end

    initial_infected = sample(
        rng,
        collect(positions(model)),
        n_initial_infected;
        replace = false
    )

    for pos in initial_infected
        agent = model[ids_in_position(pos, model)[1]]
        agent.status = :I
        agent.days_infected = 1
    end

    return model
end
Back to top
Basics of agent-based modeling: spatial epidemic dynamics with Agents.jl
Basics of species distribution modeling
  • © Jeet Sukumaran

Please share or adapt under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0).