juliasimulationmodelingagents.jl

How can I merge agents into a different type agent using multiagents?


Starting from the example Continuous space social distancing, I'm trying to create a model that involves merging each pair of nearby agents into an agent of a different type, with other properties.

using Agents

@agent struct Single(ContinuousAgent{2, Float64})
  mass :: Float64
  propertyA :: Integer
end

@agent struct Double(ContinuousAgent{2, Float64})
  mass :: Float64
  propertyB :: Float64
  propertyC :: Integer
end

using Random

agent_step!(agent, model) = move_agent!(agent, model, model.dt)

function merge_agents!(a1, a2, model)
    new_mass = a1.mass + a2.mass
    new_pos = ((a1.pos[1]+a2.pos[1])/2.0, (a1.pos[2]+a2.pos[2])/2.0)
    new_vel = sincos(2π * rand(abmrng(model))) .* 0.0
    add_agent!(Double, model; pos = new_pos, vel = new_vel, mass = new_mass, propertyB = 20.0, propertyC = 5)
    remove_agent!(a1, model)
    remove_agent!(a2, model)
end

function model_step!(model)
    for (a1, a2) in interacting_pairs(model, 0.01, :nearest)
        if isa(a1, Single) && isa(a2, Single)
            merge_agents!(a1, a2, model)
        end
    end
end

function ball_model(; speed = 0.002)
    space2d = ContinuousSpace((1, 1); spacing = 0.02)
    model = StandardABM(Union{Single, Double}, space2d; agent_step!, model_step!, properties = Dict(:dt => 1.0),rng = MersenneTwister(42))
    for ind in 1:100
        new_pos = Tuple(rand(abmrng(model), 2))
        new_vel = sincos(2π * rand(abmrng(model))) .* speed
        add_agent!(Single, model; pos = new_pos, vel = new_vel, mass = 1.0, propertyA = 10)
    end
    return model   
end

model = ball_model()

using CairoMakie

abmvideo(
    "mergingAgents-Union.mp4",
    model;
    title = "Merging agents (Union)",
    frames = 500,
    dt = 1,
    framerate = 25,
    agent_size = agent -> isa(agent, Double) ? 20 : 10,
    agent_color = agent -> isa(agent, Double) ? :blue : :green
)

The code above works as desired.

Agents of the types Single (smaller blue disks) and Double (larger green disks).

However, I've seen that the Agents.jl documentation recommends using @multiagent instead of Union, so I tried to adapt the code accordingly:

using Agents

@agent struct Single(ContinuousAgent{2, Float64})
  mass :: Float64
  propertyA :: Integer
end

@agent struct Double(ContinuousAgent{2, Float64})
  mass :: Float64
  propertyB :: Float64
  propertyC :: Integer
end

@multiagent Nucleus(Single, Double) <: AbstractAgent

using Random

agent_step!(agent, model) = move_agent!(agent, model, model.dt)

function merge_agents!(a1, a2, model)
    new_mass = a1.mass + a2.mass
    new_pos = ((a1.pos[1]+a2.pos[1])/2.0, (a1.pos[2]+a2.pos[2])/2.0)
    new_vel = sincos(2π * rand(abmrng(model))) .* 0.0
    new_agent = Nucleus(Double(model, new_pos, new_vel, new_mass, 20.0, 5))
    add_agent!(new_agent, model)
    remove_agent!(a1, model)
    remove_agent!(a2, model)
end

function model_step!(model)
    for (a1, a2) in interacting_pairs(model, 0.01, :nearest)
        if isa(a1, Single) && isa(a2, Single)
            merge_agents!(a1, a2, model)
        end
    end
end

function ball_model(; speed = 0.002)
    space2d = ContinuousSpace((1, 1); spacing = 0.02)
    model = StandardABM(Nucleus, space2d; agent_step!, model_step!, properties = Dict(:dt => 1.0),rng = MersenneTwister(42))
    for ind in 1:100
        new_pos = Tuple(rand(abmrng(model), 2))
        new_vel = sincos(2π * rand(abmrng(model))) .* speed
        new_agent = Nucleus(Single(model, new_pos, new_vel, 1.0, 10))
        add_agent_own_pos!(new_agent, model)
    end
    return model
end

model = ball_model()

using CairoMakie

abmvideo(
    "mergingAgents-multiagent.mp4",
    model;
    title = "Merging agents (multiagent)",
    frames = 500,
    dt = 1,
    framerate = 25,
    agent_size = agent -> isa(agent, Double) ? 20 : 10,
    agent_color = agent -> isa(agent, Double) ? :blue : :green
)

Now, the agents don't merge anymore.

Agents of the type Single (smaller blue disks) that don't merge into agents of the type anymore.

What am I doing wrong?


Solution

  • The Agents.@multiagent (currently v6.1) documentation says

    @multiagent YourAgentType(AgentTypesToMerge...) [<: OptionalSupertype]

    Define multiple agent "subtypes", which are variants of a unique type YourAgentType. This means that all "subtypes" are enclosed in the overarching type. Then, You cannot distinguish them on the basis of typeof, but need to use instead the variantof function. [...]

    The way to retrieve the variant of the agent is through the function variantof. [...]

    You can also access the enclosed variant instance with the variant function. [...]

    Using your agents constructed with a Nucleus wrapping a Single or a Double, the problem is that expressions like isa(anAgent, Single) and isa(anAgent, Double) are false.

    anAgent = Nucleus(Single(id=-1; pos=[-2.0,-3.0], vel=[-0.2,-0.3], mass=-4.0, propertyA=-5))
    
    typeof(anAgent) # Nucleus
    
    isa(anAgent, Nucleus) # true
    
    isa(anAgent, Single) # false
    

    Solutions are to unwrap the Nucleus with variant(anAgent) or variantof(anAgent).

    isa(variant(anAgent), Single) # true
    
    variantof(anAgent) == Single  # true
    

    For example

    function model_step!(model)
        for (a1, a2) in interacting_pairs(model, 0.01, :nearest)
            if isa(variant(a1), Single) && isa(variant(a2), Single)          # modified
                merge_agents!(a1, a2, model)
            end
        end
    end
    
    abmvideo(
        "mergingAgents-multiagent.mp4",
        model;
        title = "Merging agents (multiagent)",
        frames = 500,
        dt = 1,
        framerate = 25,
        agent_size = agent -> isa(variant(agent), Double) ? 20 : 10,         # modified
        agent_color = agent -> isa(variant(agent), Double) ? :blue : :green  # modified
    )