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!)