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
end
The 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,
extent = (100, 100),
spacing = visual_distance / 1.5,
)
space2d = ContinuousSpace(extent, spacing)
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
return model
end
Defining 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
neighbor_ids = nearby_ids(bird, model, bird.visual_distance)
N = 0
match = separate = cohere = (0.0, 0.0)
# Calculate behaviour properties based on neighbors
for id in neighbor_ids
N += 1
neighbor = model[id].pos
heading = neighbor .- bird.pos
# `cohere` computes the average position of neighboring birds
cohere = cohere .+ heading
if edistance(bird.pos, neighbor, model) < bird.separation
# `separate` repels the bird away from neighboring birds
separate = separate .- heading
end
# `match` computes the average trajectory of neighboring birds
match = match .+ model[id].vel
end
N = max(N, 1)
# Normalise results based on model input and neighbor count
cohere = cohere ./ N .* bird.cohere_factor
separate = separate ./ N .* bird.separate_factor
match = match ./ N .* bird.match_factor
# Compute velocity based on rules defined above
bird.vel = (bird.vel .+ cohere .+ separate .+ match) ./ 2
bird.vel = bird.vel ./ norm(bird.vel)
# Move bird according to new velocity and speed
move_agent!(bird, model, bird.speed)
end
agent_step! (generic function with 1 method)
Plotting the flock
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)
end
And here is the animation
using Plots
model = initialize_model()
e = model.space.extent
anim = @animate for i in 0:100
i > 0 && step!(model, agent_step!, 1)
p1 = plotabm(
model;
am = bird_triangle,
as = 7,
showaxis = false,
grid = false,
xlims = (0, e[1]),
ylims = (0, e[2]),
)
title!(p1, "step $(i)")
end
gif(anim, "flock.gif", fps = 30)