I am using Agents.jl
and I want to create for each agent
a list of (the ids) other agents that are his friends. I am working on a GridSpace
(not in a GraphSpace
).
For each agent, the list of friends has a random size between [a,b]
and age
between [ agent.age - c, agent.age + d]
.
I have tried the following:
mutable struct Person <: AbstractAgent
id::Int
pos::NTuple{2, Int}
age::Int
friends::Array ## list of ids of other agents
end
but when initializing the model, I should have the ages of all agents before defining the friends of each one. How can I do that? Using a sample
for each agent?
So if I understood you correctly, each agent should have a list of friends with a length between a
and b
and the age of the friends inside that list should be between agent.age - c
and agent.age - d
.
There are definitely multiple ways to do it. Hm, I think I would do the following:
using Random
using Pipe
mutable struct Person <: AbstractAgent
id::Int
pos::NTuple{2, Int}
age::Int
friends::Vector{Int} # use vector of integers because typeof(agent.id) == Int
end
# inside the model initialisation function:
for n in 1:numagents
agent = Person(
n, # id
(1, 1), # position
rand(model.rng, 0:80), # random age between 0 and 80
fill(n, rand(model.rng, a:b)) # placeholder vector of friends with length between a and b
)
add_agent!(agent, model) # place agents at a random position on the grid
end
for agent in allagents(model)
# first retrieve a list of possible friends in the correct age range
possible_friends = @pipe collect(allids(model)) |> # collect all existing ids
filter!(id -> id != agent.id, _) |> # remove self from id list
filter!(id -> agent.age - c < model[id].age < agent.age + d, _) |> # remove ids of agents that are not in the correct age range
shuffle!(model.rng, _) # shuffle the list to randomise the draw
# then replace values in agent.friends with a random subset from possible_friends
agent.friends .= possible_friends[begin:length(agent.friends)] #
end
Couldn’t test it because I don’t have your full model but hopefully you got some ideas out of my write-up. It’s probably not the most performant way to do things but it should work.
I’ve also tried to be a bit liberal with the comments, since I don’t know about your current level of coding skills. If you didn’t understand something, feel free to ask for more info.
1 Like
Thank you for your great help. I am an untalented amateur in coding but I understood almost all your code, without reading your helpful comments (except for the fill
line ).
I tried to run your code in my (almost) complete model but I got a bounds error that I must debug.
I have changed the age
bounds and also gave values to the variables a, b, c, d
.
I am sending now the complete code and error message:
using Agents
using Random
using Pipe
mutable struct Person <: AbstractAgent
id::Int
pos::NTuple{2, Int}
age::Int
friends::Vector{Int} # use vector of integers because typeof(agent.id) == Int
end
function initialize(; numagents = 81, griddims = (81, 100), seed = 125)
space = GridSpace(griddims, periodic = false)
rng = Random.MersenneTwister(seed)
model = ABM(
Person, space;
rng, scheduler = Schedulers.randomly
)
a = 10; b = 25; c = 5; d = 5
# inside the model initialisation function:
for n in 1:numagents
agent = Person(
n, # id
(1, 1), # position
rand(model.rng, 20:100), # random age between 20 and 100
fill(n, rand(model.rng, a:b)) # placeholder vector of friends with length between a and b
)
add_agent!(agent, model) # place agents at a random position on the grid
end
for agent in allagents(model)
# first retrieve a list of possible friends in the correct age range
possible_friends = @pipe collect(allids(model)) |> # collect all existing ids
filter!(id -> id != agent.id, _) |> # remove self from id list
filter!(id -> agent.age - c < model[id].age < agent.age + d, _) |> # remove ids of agents that are not in the correct age range
shuffle!(model.rng, _) # shuffle the list to randomise the draw
# then replace values in agent.friends with a random subset from possible_friends
agent.friends .= possible_friends[begin:length(agent.friends)] #
end
return model
end
model = initialize()
The error message is:
ERROR: LoadError: BoundsError: attempt to access 7-element Vector{Int64} at index [1:16]
Stacktrace:
[1] throw_boundserror(A::Vector{Int64}, I::Tuple{UnitRange{Int64}})
@ Base .\abstractarray.jl:691
[2] checkbounds
@ .\abstractarray.jl:656 [inlined]
[3] getindex
@ .\array.jl:867 [inlined]
[4] initialize(; numagents::Int64, griddims::Tuple{Int64, Int64}, seed::Int64)
@ Main c:\Users\sbac\Dropbox\JULIA\coordTransSocNets\disc.jl:38
[5] initialize()
@ Main c:\Users\sbac\Dropbox\JULIA\coordTransSocNets\disc.jl:13
[6] top-level scope
@ c:\Users\sbac\Dropbox\JULIA\coordTransSocNets\disc.jl:44
in expression starting at c:\Users\sbac\Dropbox\JULIA\coordTransSocNets\disc.jl:44
The problem most likely lies in this statement possible_friends[begin:length(agent.friends)
.
If your list of possible_friends is too small (e.g. only 7 id
s in there) but your agent should have more friends than that (e.g. the random draw between a
and b
was 16), then you will encounter such a BoundsError
.
So what you’re facing here is the fact that you don’t know about the final length of the friends vector during the initialisation process (because it depends dynamically on a
,b
, age
, c
, and d
). The preallocation that I’ve tried with the fill
function will likely not work properly then.
Here’s how you could solve it:
# adjust the agent struct to save the maximum number of friends an agent can have
mutable struct Person <: AbstractAgent
id::Int
pos::NTuple{2, Int}
age::Int
max_friends::Int
friends::Vector{Int}
end
# and inside the init function:
for n in 1:numagents
agent = Person(
n,
(1, 1),
rand(model.rng, 20:100),
rand(model.rng, a:b), # random draw for maximum number of friends
[] # initialise an empty vector of friends
)
add_agent!(agent, model)
end
for agent in allagents(model)
possible_friends = @pipe collect(allids(model)) |>
filter!(id -> id != agent.id, _) |>
filter!(id -> agent.age - c < model[id].age < agent.age + d, _) |>
shuffle!(model.rng, _) # shuffle the list to randomise the draw
for id in possible_friends
push!(agent.friends, id) # add this id to the friends vector
if length(agent.friends) == agent.max_friends # if friends vector has reached desired length
break # break the for-loop
end
end
end
1 Like
Thank you for your help. Now the script works fine. The only issue is that max_friends
is in fact number_of_friends
, as I wanted.
I have yet a long way to finish my project, but I learned a lot from your code.