Selecting specific group in a multiagent simulation with agents.jl

Hello people! I am building a simulation model with agents.jl of a city where any moment, there is an area that can be affected by an emergency with a specific probability, so if this is the case, I want to move an ambulance to that point and come back to the started point. I am taking the example of zombies in a city for maps and location and predator for having more than one agent, so my start point is this:

using Agents
using Random
using Distributions

@agent Area OSMAgent begin
    emergency::Bool
    probability::Float64
end

mutable struct Area <: AbstractAgent
    id::Int
    pos::Tuple{Int, Int, Float64}
    emergency::Bool
    probability::Float64
end

@agent Ambulance OSMAgent begin
    speed::Float64
end

mutable struct Ambulance <: AbstractAgent
    id::Int
    pos::Tuple{Int, Int, Float64}
    speed::Float64
end

function initialise(seed = 1234, n_areas = 100, n_ambulances =2)
    #Set space
    map_path = OSM.test_map()
    properties = Dict(:dt => 1/60)
    model = ABM(
        Union{Area, Ambulance},
        OpenStreetMapSpace(map_path);
        properties = properties,
        rng = Random.MersenneTwister(seed)
    )
    #Develop initial states for each node
    for id in 1:n_areas
        start = random_position(model) # At an intersection
        probability = 0.001#rand(1:2) # Random probability
        emergency = Area(id, start, false, probability,)
        add_agent!(emergency, model)
    end
    #Allocate ambulance at random
    for id in 1:n_ambulances 
        start = random_position(model)
        speed = rand(model.rng) + 60.0
        ambulances = Ambulance(id+n_areas, start, speed)
        add_agent!(ambulances, model)
    end
    return model
end

if I initialize then I have this:

julia> initialise()
[ Info: Created OSMGraph object with kwargs: `network_type=drive`, `weight_type=distance`, `graph_type=static`, `precompute_dijkstra_states=false`, `largest_connected_component=true`
┌ Warning: AgentType is not concrete. If your agent is parametrically typed, you're probably
│ seeing this warning because you gave `Agent` instead of `Agent{Float64}`
│ (for example) to this function. You can also create an instance of your agent
│ and pass it to this function. If you want to use `Union` types for mixed agent
│ models, you can silence this warning.
└ @ Agents ~/.julia/packages/Agents/QtIUJ/src/core/model.jl:299
AgentBasedModel with 102 agents of type Union{Ambulance, Area}
 space: OpenStreetMapSpace with 598 ways and 2177 nodes
 scheduler: fastest
 properties: dt

But my problem start when I want to make specific actions with one of the two agents. This function shouldn’t do anything useful for now more than setting in every frame the emergency boolean

function agent_step!(agent::Area, model)
    #If every area nothing happen then launch a random bernoully distribution otherwise keep 
    #the emergency activated
    if agent.emergency == false
        agent.emergency = rand(Bernoulli(agent.probability))
    end
    return
end

If I want visualize the model:

using InteractiveDynamics
using CairoMakie
CairoMakie.activate!() # hide
ac(agent::Area) = agent.emergency ? :red : :black 
ac(agent::Ambulance) = :green  
as(agent) =  10
model = initialise()

abmvideo("emergency_system.mp4", model, agent_step!; 
title = "Emergency in a city", framerate = 5, frames = 300, as, ac)

I got this error:

┌ Warning: Since there are a lot of edges (3639 > 500), they will be drawn as straight lines even though they contain curvy edges. If you really want to plot them as bezier curves pass `edge_plottype=:beziersegments` explicitly. This will have much worse performance!
└ @ GraphMakie ~/.julia/packages/GraphMakie/wRCZ6/src/recipes.jl:409
ERROR: MethodError: no method matching agent_step!(::Ambulance, ::AgentBasedModel{Agents.OSM.OpenStreetMapSpace, Union{Ambulance, Area}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Float64}, MersenneTwister})
Closest candidates are:
  agent_step!(::Area, ::Any) at ~/Desktop/projects/data-analisis/02-emergency-sim/src/sim.jl:62
Stacktrace:
 [1] step!(model::AgentBasedModel{Agents.OSM.OpenStreetMapSpace, Union{Ambulance, Area}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Float64}, MersenneTwister}, agent_step!::typeof(agent_step!), model_step!::typeof(dummystep), n::Int64, agents_first::Bool)
   @ Agents ~/.julia/packages/Agents/QtIUJ/src/simulations/step.jl:55
 [2] step!
   @ ~/.julia/packages/Agents/QtIUJ/src/simulations/step.jl:48 [inlined]
 [3] step!(abmobs::ABMObservable{AgentBasedModel{Agents.OSM.OpenStreetMapSpace, Union{Ambulance, Area}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Float64}, MersenneTwister}, typeof(agent_step!), typeof(dummystep), Nothing, Nothing, Nothing, Nothing, Bool}, n::Int64; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ InteractiveDynamics ~/.julia/packages/InteractiveDynamics/EThtU/src/agents/model_observable.jl:53
 [4] step!
   @ ~/.julia/packages/InteractiveDynamics/EThtU/src/agents/model_observable.jl:51 [inlined]
 [5] (::InteractiveDynamics.var"#93#96"{Int64, Int64, ABMObservable{AgentBasedModel{Agents.OSM.OpenStreetMapSpace, Union{Ambulance, Area}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Float64}, MersenneTwister}, typeof(agent_step!), typeof(dummystep), Nothing, Nothing, Nothing, Nothing, Bool}, Observable{Int64}})(io::VideoStream)
   @ InteractiveDynamics ~/.julia/packages/InteractiveDynamics/EThtU/src/agents/convenience.jl:152
 [6] Record(func::InteractiveDynamics.var"#93#96"{Int64, Int64, ABMObservable{AgentBasedModel{Agents.OSM.OpenStreetMapSpace, Union{Ambulance, Area}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Float64}, MersenneTwister}, typeof(agent_step!), typeof(dummystep), Nothing, Nothing, Nothing, Nothing, Bool}, Observable{Int64}}, figlike::Figure; framerate::Int64)
   @ Makie ~/.julia/packages/Makie/Ppzqh/src/display.jl:583
 [7] #record#957
   @ ~/.julia/packages/Makie/Ppzqh/src/display.jl:577 [inlined]
 [8] abmvideo(file::String, model::AgentBasedModel{Agents.OSM.OpenStreetMapSpace, Union{Ambulance, Area}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Float64}, MersenneTwister}, agent_step!::typeof(agent_step!), model_step!::typeof(dummystep); spf::Int64, framerate::Int64, frames::Int64, title::String, showstep::Bool, figure::NamedTuple{(:resolution,), Tuple{Tuple{Int64, Int64}}}, axis::NamedTuple{(), Tuple{}}, recordkwargs::NamedTuple{(:compression,), Tuple{Int64}}, kwargs::Base.Pairs{Symbol, Function, Tuple{Symbol, Symbol}, NamedTuple{(:as, :ac), Tuple{typeof(as), typeof(ac)}}})
   @ InteractiveDynamics ~/.julia/packages/InteractiveDynamics/EThtU/src/agents/convenience.jl:149
 [9] top-level scope
   @ ~/Desktop/projects/data-analisis/02-emergency-sim/src/sim.jl:90

Why in agent_step! select also the ambulance agent? It shouldn’t only select the Area if I specify in the arguments of the function?

1 Like

It is trying to step the Ambulance agent because you’ve defined the model scheduler to be fastest which iterates over all agents in the model. Either you use a scheduler by type or you simply add another stepping function for Ambulance agent defining that it should do nothing for now, e.g. agent_step!(agent::Ambulance, model) = nothing. Since you probably want to do something with the ambulances later on anyway, I would suggest the latter approach.

On a side note, you don’t have to define the agent type with both @agent and mutable struct. These are redundant. Just use the @agent macro and that should do the trick.

2 Likes

thank you fbanning that solves the main problem!