# How to simulate friends on a GridSpace in Agents.jl

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:
 throw_boundserror(A::Vector{Int64}, I::Tuple{UnitRange{Int64}})
@ Base .\abstractarray.jl:691
 checkbounds
@ .\abstractarray.jl:656 [inlined]
 getindex
@ .\array.jl:867 [inlined]
 initialize(; numagents::Int64, griddims::Tuple{Int64, Int64}, seed::Int64)
@ Main c:\Users\sbac\Dropbox\JULIA\coordTransSocNets\disc.jl:38
 initialize()
@ Main c:\Users\sbac\Dropbox\JULIA\coordTransSocNets\disc.jl:13
 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
)
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.