Can't retrieve agent.id even though it definitely exists

Hi everyone,

I’m trying to adapt the Schelling segregation model to run on a network graph rather than a grid space. I’ve been working from the Schelling and
Schoolyard tutorials. Apologies in advance, I’m sure there’s multiple things wrong with my code, this is just the one I’m stuck on right now.

As part of this, I have to retrieve the agent.id in order to match that with the node on the graph to make the updates to the edges. My code seems to initialise fine, and I’ve confirmed that the ID field has a number in it, but when I go to step the agent I get the following:

KeyError: key :id not found
Stacktrace:
  [1] getindex
    @ .\dict.jl:498 [inlined]
  [2] getproperty
    @ C:\Users\Admin\.julia\packages\Agents\EOn0z\src\core\model_abstract.jl:128 [inlined]
  [3] agent_step!(agent::StandardABM{GridSpaceSingle{2, false}, SchellingAgent, Vector{SchellingAgent}, Tuple{DataType}, typeof(agent_step!), typeof(dummystep), Agents.Schedulers.Randomly, Dict{Symbol, SimpleWeightedGraph{Int64, Float64}}, Xoshiro}, model::Int64)
    @ Main c:\Users\Admin\Documents\GitHub\ABM_polarisation\schelling-final.jl:48
  [4] until(t1::Int64, t0::Int64, f::typeof(agent_step!), model::StandardABM{GridSpaceSingle{2, false}, SchellingAgent, Vector{SchellingAgent}, Tuple{DataType}, typeof(agent_step!), typeof(dummystep), Agents.Schedulers.Randomly, Dict{Symbol, SimpleWeightedGraph{Int64, Float64}}, Xoshiro})
    @ Agents C:\Users\Admin\.julia\packages\Agents\EOn0z\src\simulations\step.jl:38
  [5] step_ahead!(model::StandardABM{GridSpaceSingle{2, false}, SchellingAgent, Vector{SchellingAgent}, Tuple{DataType}, typeof(agent_step!), typeof(dummystep), Agents.Schedulers.Randomly, Dict{Symbol, SimpleWeightedGraph{Int64, Float64}}, Xoshiro}, agent_step!::typeof(agent_step!), model_step!::typeof(dummystep), n::Function, t::Base.RefValue{Int64})
    @ Agents C:\Users\Admin\.julia\packages\Agents\EOn0z\src\simulations\step_standard.jl:12
  [6] step!(model::StandardABM{GridSpaceSingle{2, false}, SchellingAgent, Vector{SchellingAgent}, Tuple{DataType}, typeof(agent_step!), typeof(dummystep), Agents.Schedulers.Randomly, Dict{Symbol, SimpleWeightedGraph{Int64, Float64}}, Xoshiro}, n::typeof(agent_step!))
    @ Agents C:\Users\Admin\.julia\packages\Agents\EOn0z\src\simulations\step_standard.jl:5
  [7] top-level scope
    @ REPL[4]:1
  [8] eval
    @ .\boot.jl:385 [inlined]
  [9] eval
    @ .\Base.jl:88 [inlined]
 [10] repleval(m::Module, code::Expr, ::String)
    @ VSCodeServer c:\Users\Admin\.vscode\extensions\julialang.language-julia-1.127.2\scripts\packages\VSCodeServer\src\repl.jl:229
 [11] (::VSCodeServer.var"#112#114"{Module, Expr, REPL.LineEditREPL, REPL.LineEdit.Prompt})()
    @ VSCodeServer c:\Users\Admin\.vscode\extensions\julialang.language-julia-1.127.2\scripts\packages\VSCodeServer\src\repl.jl:192
 [12] with_logstate(f::Function, logstate::Any)
    @ Base.CoreLogging .\logging.jl:515
 [13] with_logger
    @ .\logging.jl:627 [inlined]
 [14] (::VSCodeServer.var"#111#113"{Module, Expr, REPL.LineEditREPL, REPL.LineEdit.Prompt})()
    @ VSCodeServer c:\Users\Admin\.vscode\extensions\julialang.language-julia-1.127.2\scripts\packages\VSCodeServer\src\repl.jl:193
 [15] #invokelatest#2
    @ Base .\essentials.jl:887 [inlined]
 [16] invokelatest(::Any)
    @ Base .\essentials.jl:884
 [17] (::VSCodeServer.var"#64#65")()
    @ VSCodeServer c:\Users\Admin\.vscode\extensions\julialang.language-julia-1.127.2\scripts\packages\VSCodeServer\src\eval.jl:34

Here’s my full code, I think there’s been a new version of Agents.jl since I was last working on it, but it seems to match the tutorials I was working from.

#Model parameters
is_happy = false #starting mood for the agent, false = unhappy and will "move" on next agent_step
seg_tolerance = 0.375 #the minimum fraction of the agent's neighbours that must belong to the same group as them for them to become happy
total_agents = 50

import Pkg

Pkg.activate(".")
Pkg.instantiate()

using Agents
using CairoMakie # choosing a plotting backend
using SimpleWeightedGraphs 
using Graphs
using GraphMakie
using SparseArrays: findnz
using Random # for reproducibility
#using InteractiveDynamics -- no longer needed, abmplot is in Agents

function randomExcluded(min, max, excluded)
      
    n = rand(min:max)
    if (n ≥ excluded) 
        n += 1
   else
        n += 0
    end

    return n

end #function needed for generating random graph edges without node selecting itself


@agent struct SchellingAgent(GridAgent{2}) begin
    mood::Bool # whether the agent is happy in its position. (true = happy)
    group::Int # The group of the agent, determines mood as it interacts with neighbors
    seg::Float64 #the number of neighbours in the same group that the agent needs to be happy
end
end

for (name, type) in zip(fieldnames(SchellingAgent), fieldtypes(SchellingAgent))
    println(name, "::", type)
end

function agent_step!(agent, model)
    count_neighbours_same_group = 0
    count_neighbours = 0
    which_agent = agent.id
    print(which_agent)
    
    neigh = Graphs.neighbors(model.social, which_agent)
    neighbours_same_group = []
    neighbours_other_group = []
    for i in neigh
        count_neighbours += 1
        if model[which_agent].group == model[i].group
            count_neighbours_same_group += 1
            push!(neighbours_same_group,model[i].id)
        else 
            push!(neighbours_other_group,model[i].id)
        end
    end #keeping track of the agent's same and different group links to select from later

    if count_neighbours_same_group/count_neighbours ≥ agent.seg
        agent.mood = true
    else
        agent.mood = false
        cutoff = rand(neighbours_other_group)
        rem_edge!(model.social, which_agent, cutoff)
        count_neighbours -=1
    end #if unhappy, cut off a link from a different group

    while count_neighbours ≤ 50 #each node should have at least 50 friends, this can be disrupted if links are broken by other agents
        if length(neighbours_same_group) > 0
            network_link = rand(neighbours_same_group)
            friends_of_friend = Graphs.neighbors(model.social, network_link) 
            friends_of_friend = setdiff(friends_of_friend,which_agent)
            new_friend = rand(friends_of_friend) 
            add_edge!(model.social,which_agent,new_friend)
            count_neighbours +=1    #if there are friends in the same group, select new freind from their friends at random
        else
            random_friend = randomExcluded(1,49,which_agent)
            add_edge!(model.social,which_agent,random_friend)
            count_neighbours +=1    #else select a friend from the whole graph at random
        end
    end
    
    return
end

function initialize(; total_agents = total_agents, gridsize = (20, 20), seed = 125)
    space = GridSpaceSingle(gridsize; periodic = false)
    properties = Dict(:social => SimpleWeightedGraph(total_agents))
    rng = Xoshiro(seed)
    model = StandardABM(
        SchellingAgent, space;
        agent_step! = agent_step!, properties, rng,
        container = Vector, # agents are not removed, so we use this
        scheduler = Schedulers.Randomly() # all agents are activated once at random
    )
    # populate the model with agents, adding equal amount of the two types of agents
    # at random positions in the model. At the start all agents are unhappy.
    for n in 1:total_agents
        agent = SchellingAgent(n, (1, 1), false, n < total_agents / 2 ? 1 : 2, 0.375)
        add_agent_single!(agent, model)
        print(agent.id)
    end

    return model

   

    
end

model = initialize()

for n in 1:(total_agents-1) #populate the model with graph edges
    starter_agent = n
    for n in 1:8
        friend = randomExcluded(1,(total_agents-1),starter_agent)
        add_edge!(model.social, starter_agent, friend)
    end
end

#step!(model, agent_step!)