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?

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.

thank you fbanning that solves the main problem!