Flock model

The flock model illustrates how flocking behavior can emerge when each bird follows three simple rules:
- maintain a minimum distance from other birds to avoid collision
- fly towards the average position of neighbors
- fly in the average direction of neighbors
It is also available from the Models module as Models.flocking.
Defining the core structures
We begin by calling the required packages and defining an agent type representing a bird.
using Agents, LinearAlgebra
mutable struct Bird <: AbstractAgent
id::Int
pos::NTuple{2,Float64}
vel::NTuple{2,Float64}
speed::Float64
cohere_factor::Float64
separation::Float64
separate_factor::Float64
match_factor::Float64
visual_distance::Float64
endThe fields id and pos are required for every agent. The field vel is required for using move_agent! in ContinuousSpace. speed defines how far the bird travels in the direction defined by vel per step. seperation defines the minimum distance a bird must maintain from its neighbors. visual_distance refers to the distance a bird can see and defines a radius of neighboring birds. The contribution of each rule defined above recieves an importance weight: cohere_factor is the importance of maintaining the average position of neighbors, match_factor is the importance of matching the average trajectory of neighboring birds, and separate_factor is the importance of maining the minimum distance from neighboring birds.
The function initialize_model generates birds and returns a model object using default values.
function initialize_model(;
n_birds = 100,
speed = 1.0,
cohere_factor = 0.25,
separation = 4.0,
separate_factor = 0.25,
match_factor = 0.01,
visual_distance = 5.0,
dims = (100, 100),
)
space2d = ContinuousSpace(2; periodic = true, extend = dims)
model = ABM(Bird, space2d, scheduler = random_activation)
for _ in 1:n_birds
vel = Tuple(rand(2) * 2 .- 1)
add_agent!(
model,
vel,
speed,
cohere_factor,
separation,
separate_factor,
match_factor,
visual_distance,
)
end
index!(model)
return model
endDefining the agent_step!
agent_step! is the primary function called for each step and computes velocity according to the three rules defined above.
function agent_step!(bird, model)
# Obtain the ids of neighbors within the bird's visual distance
ids = space_neighbors(bird, model, bird.visual_distance)
# Compute velocity based on rules defined above
bird.vel =
(
bird.vel .+ cohere(bird, model, ids) .+ separate(bird, model, ids) .+
match(bird, model, ids)
) ./ 2
bird.vel = bird.vel ./ norm(bird.vel)
# Move bird according to new velocity and speed
move_agent!(bird, model, bird.speed)
end
distance(a1, a2) = sqrt(sum((a1.pos .- a2.pos) .^ 2))
get_heading(a1, a2) = a1.pos .- a2.poscohere computes the average position of neighboring birds, weighted by importance
function cohere(bird, model, ids)
N = max(length(ids), 1)
birds = model.agents
coherence = (0.0, 0.0)
for id in ids
coherence = coherence .+ get_heading(birds[id], bird)
end
return coherence ./ N .* bird.cohere_factor
endseparate repels the bird away from neighboring birds
function separate(bird, model, ids)
seperation_vec = (0.0, 0.0)
N = max(length(ids), 1)
birds = model.agents
for id in ids
neighbor = birds[id]
if distance(bird, neighbor) < bird.separation
seperation_vec = seperation_vec .- get_heading(neighbor, bird)
end
end
return seperation_vec ./ N .* bird.separate_factor
endmatch computes the average trajectory of neighboring birds, weighted by importance
function match(bird, model, ids)
match_vector = (0.0, 0.0)
N = max(length(ids), 1)
birds = model.agents
for id in ids
match_vector = match_vector .+ birds[id].vel
end
return match_vector ./ N .* bird.match_factor
endRunning the model
n_steps = 500
model = initialize_model()
step!(model, agent_step!, n_steps)Plotting the birds
The great thing about plotabm is its flexibility. We can incorporate the direction of the birds when plotting them, by making the "marker" function am create a Shape: a triangle with same orientation as the bird's velocity. It is as simple as defining the following function:
function bird_triangle(b::Bird)
φ = atan(b.vel[2], b.vel[1])
xs = [(i ∈ (0, 3) ? 2 : 1) * cos(i * 2π / 3 + φ) for i in 0:3]
ys = [(i ∈ (0, 3) ? 2 : 1) * sin(i * 2π / 3 + φ) for i in 0:3]
Shape(xs, ys)
endAnd here is the animation
using AgentsPlots
model = initialize_model()
e = model.space.extend
anim = @animate for i in 0:100
i > 0 && step!(model, agent_step!, 1)
p1 = plotabm(
model;
am = bird_triangle,
as = 10,
showaxis = false,
grid = false,
xlims = (0, e[1]),
ylims = (0, e[2]),
)
title!(p1, "step $(i)")
end
gif(anim, "flock.gif", fps = 30)