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 ids 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.