Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/run-flocking.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
echo "Benchmarking Agents.jl"
julia --project=@. ./Flocking/Agents/benchmark_flocking.jl

echo "Benchmarking Ark.jl"
julia --project=@. ./Flocking/Ark/benchmark_flocking.jl

echo "Benchmarking Mason"
bash ./Flocking/Mason/benchmark_flocking.sh

Expand Down
3 changes: 3 additions & 0 deletions .github/run-forestfire.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
echo "Benchmarking Agents.jl"
julia --project=@. ./ForestFire/Agents/benchmark_forestfire.jl

echo "Benchmarking Ark.jl"
julia --project=@. ./ForestFire/Ark/benchmark_forestfire.jl

echo "Benchmarking Mason"
bash ./ForestFire/Mason/benchmark_forestfire.sh

Expand Down
3 changes: 3 additions & 0 deletions .github/run-schelling.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
echo "Benchmarking Agents.jl"
julia --project=@. ./Schelling/Agents/benchmark_schelling.jl

echo "Benchmarking Ark.jl"
julia --project=@. ./Schelling/Ark/benchmark_schelling.jl

echo "Benchmarking Mason"
bash ./Schelling/Mason/benchmark_schelling.sh

Expand Down
3 changes: 3 additions & 0 deletions .github/run-wolfsheep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
echo "Benchmarking Agents.jl"
julia --project=@. ./WolfSheep/Agents/benchmark_wolfsheep.jl

echo "Benchmarking Ark.jl"
julia --project=@. ./WolfSheep/Ark/benchmark_wolfsheep.jl

echo "Benchmarking Mason"
bash ./WolfSheep/Mason/benchmark_wolfsheep.sh

Expand Down
167 changes: 167 additions & 0 deletions Flocking/Ark/Flocking.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using Ark, LinearAlgebra, StaticArrays, Random

struct Position
p::SVector{2, Float64}
end

struct Velocity
v::SVector{2, Float64}
end

struct BirdParams
speed::Float64
cohere_factor::Float64
separation::Float64
separate_factor::Float64
match_factor::Float64
visual_distance::Float64
end

struct SpatialGrid
entities::Array{Vector{Entity}, 2}
extent::SVector{2, Float64}
cell_size::Float64
rows::Int
cols::Int
end

function SpatialGrid(extent, cell_size)
width, height = extent
rows = max(1, ceil(Int, height / cell_size))
cols = max(1, ceil(Int, width / cell_size))
entities = [Entity[] for _ in 1:rows, _ in 1:cols]
return SpatialGrid(entities, extent, cell_size, rows, cols)
end

function get_direction(from, to, extent)
direct_dir = to .- from
inverse_dir = direct_dir .- sign.(direct_dir) .* extent
return map((x, y) -> abs(x) <= abs(y) ? x : y, direct_dir, inverse_dir)
end

function get_cell(grid::SpatialGrid, p::SVector{2, Float64})
row = floor(Int, p[2] / grid.cell_size) + 1
col = floor(Int, p[1] / grid.cell_size) + 1
return clamp(row, 1, grid.rows), clamp(col, 1, grid.cols)
end

struct FlockingBuffers
entities::Vector{Entity}
end

struct Range
offsets::Vector{Vector{Tuple{Int, Int}}}
end

function compute_offsets(range, r)
if isassigned(range.offsets, r)
return range.offsets[r]
else
resize!(range.offsets, r)
return range.offsets[r] = [(x, y) for x in -r:r for y in -r:r if x^2 + y^2 <= r^2]
end
end

function flocking_model(rng, extent, n_birds, visual_distance;
speed = 1.0, cohere_factor = 0.03, separation = 1.0, separate_factor = 0.015,
match_factor = 0.05, spacing = visual_distance / 1.5,)

world = World(Position, Velocity, BirdParams)

params = BirdParams(speed, cohere_factor, separation, separate_factor, match_factor, visual_distance)

all_entities = Entity[]
resize!(all_entities, n_birds)
for i in 1:n_birds
pos = SVector{2, Float64}(rand(rng, Float64) * extent[1], rand(rng, Float64) * extent[2])
vel = SVector{2, Float64}(rand(rng, Float64) * 2 - 1, rand(rng, Float64) * 2 - 1)
entity = new_entity!(world, (Position(pos), Velocity(vel), params))
all_entities[i] = entity
end
add_resource!(world, FlockingBuffers(all_entities))

grid = SpatialGrid(SVector{2, Float64}(extent), spacing)
for (entities, positions) in Query(world, (Position,))
for i in eachindex(entities)
row, col = get_cell(grid, positions[i].p)
push!(grid.entities[row, col], entities[i])
end
end
add_resource!(world, grid)
add_resource!(world, Range(Vector{Tuple{Int, Int}}[]))

return world
end

function swap_remove!(v::Vector{Entity}, x::Entity)
idx = findfirst(==(x), v)
if idx !== nothing
v[idx] = v[end]
pop!(v)
end
end

function flocking_step!(world::World, rng)
grid = get_resource(world, SpatialGrid)
range = get_resource(world, Range)
buffers = get_resource(world, FlockingBuffers)

entities = buffers.entities
shuffle!(rng, entities)

for entity in entities
pos_comp, vel_comp, param = get_components(world, entity, (Position, Velocity, BirdParams))
pos, vel = pos_comp.p, vel_comp.v

match = separate = cohere = SVector{2, Float64}(0.0, 0.0)
N = 0

row, col = get_cell(grid, pos)
radius = ceil(Int, param.visual_distance / grid.cell_size)

for (dr, dc) in compute_offsets(range, radius)
r, c = mod1(row + dr, grid.rows), mod1(col + dc, grid.cols)
for neighbor_entity in grid.entities[r, c]
if neighbor_entity == entity
continue
end

n_pos_comp, n_vel_comp = get_components(world, neighbor_entity, (Position, Velocity))
n_pos, n_vel = n_pos_comp.p, n_vel_comp.v

heading = get_direction(pos, n_pos, grid.extent)

N += 1
cohere += heading
match += n_vel
if sum(heading .^ 2) < param.separation^2
separate -= heading
end
end
end

cohere *= param.cohere_factor
separate *= param.separate_factor
match *= param.match_factor
vel += (cohere + separate + match) / max(N, 1)
vel /= norm(vel)

new_pos = pos + vel * param.speed
new_pos = mod1.(new_pos, grid.extent)

old_row, old_col = get_cell(grid, pos)
new_row, new_col = get_cell(grid, new_pos)
if (old_row, old_col) != (new_row, new_col)
swap_remove!(grid.entities[old_row, old_col], entity)
push!(grid.entities[new_row, new_col], entity)
end

set_components!(world, entity, (Position(new_pos), Velocity(vel)))
end
end

function step!(world::World, rng, n::Int = 1)
for _ in 1:n
flocking_step!(world, rng)
end
end
25 changes: 25 additions & 0 deletions Flocking/Ark/benchmark_flocking.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Ark
using BenchmarkTools
using Random
using StaticArrays

include("Flocking.jl")

rng_seed = MersenneTwister(42)

rng_model() = Xoshiro(rand(rng_seed, 1:10000))

function run_model(rng, extent, n_birds, visual_distance)
world = flocking_model(rng, extent, n_birds, visual_distance)
step!(world, rng, 100)
end

n_run = 100

a = @benchmark run_model(rng, (100.0, 100.0), 200, 5.0) setup=(rng = rng_model()) evals=1 samples=n_run seconds=1e6
median_time = sort(a.times)[n_run ÷ 2 + n_run % 2]
println("Ark.jl Flocking-small (ms): ", median_time * 1e-6)

a = @benchmark run_model(rng, (150.0, 150.0), 400, 15.0) setup=(rng = rng_model()) evals=1 samples=n_run seconds=1e6
median_time = sort(a.times)[n_run ÷ 2 + n_run % 2]
println("Ark.jl Flocking-large (ms): ", median_time * 1e-6)
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
[deps]
Agents = "46ada45e-f475-11e8-01d0-f70cc89e6671"
Ark = "56664e29-41e4-4ea5-ab0e-825499acc647"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
LightXML = "9c8b4983-aa76-5018-a973-4c85ecc9e179"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
Expand Down
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# Agent based modelling frameworks comparison


[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.8016506.svg)](https://doi.org/10.5281/zenodo.8016506)



This repository contains code used to compare performance and features between various agent based modelling **(ABM)** frameworks. Currently, frameworks compared are [Agents.jl](https://github.com/JuliaDynamics/Agents.jl), [NetLogo](https://github.com/NetLogo/NetLogo), [MASON](https://github.com/eclab/mason) and [Mesa](https://github.com/projectmesa/mesa). We happily welcome more frameworks to join the comparison.
This repository contains code used to compare performance and features between various agent based modelling **(ABM)** frameworks. Currently, frameworks compared are [Agents.jl](https://github.com/JuliaDynamics/Agents.jl), [Ark.jl](https://github.com/ark-ecs/Ark.jl), [NetLogo](https://github.com/NetLogo/NetLogo), [MASON](https://github.com/eclab/mason) and [Mesa](https://github.com/projectmesa/mesa). We happily welcome more frameworks to join the comparison.

**The performance benchmark comparison is run automatically during continuous integration, and hence the comparison is updated after every pull request to this repo.**

Expand All @@ -15,17 +12,17 @@ This repository has been initiated and maintained by the developers of Agents.jl

These are the results of the latest comparison:

| Model/Framework | Agents.jl 6.2.10 | MASON 22.0 | Netlogo 6.4.0 | Mesa 3.2.0 |
|:------------------|:---------------:|:------------:|:------------:|:---------------:|
| WolfSheep (Time-Ratio, Small-Version) | 1 | 5.3x | 12.8x | 12.4x |
| WolfSheep (Time-Ratio, Large-Version) | 1 | 6.0x | 6.1x | 4.9x |
| WolfSheep (Lines of Code) | 73 | 202 | 137 (871) | 118 |
| Flocking (Time-Ratio, Small-Version) | 1 | 1.4x | 17.1x | 184.1x |
| Flocking (Time-Ratio, Large-Version) | 1 | 0.6x | 19.0x | 62.7x |
| Flocking (Lines of Code) | 42 | 159 | 82 (689) | 94 |
| Schelling (Time-Ratio, Small-Version) | 1 | 1.0x | 12.5x | 32.3x |
| Schelling (Time-Ratio, Large-Version) | 1 | 1.3x | 13.5x | 23.4x |
| Schelling (Lines of Code) | 26 | 129 | 54 (739) | 33 |
| Model/Framework | Agents.jl 6.2.10 | Ark.jl 0.3.2 | MASON 22.0 | Netlogo 6.4.0 | Mesa 3.2.0 |
|:------------------:|:------------------:|:--------------:|:------------:|:---------------:|:------------:|
| WolfSheep-small (Time-Ratio) | 1.0 | 0.39 | 5.29 | 9.94 | 9.38 |
| WolfSheep-large (Time-Ratio) | 1.0 | 0.24 | 7.8 | 4.81 | 3.28 |
| WolfSheep (Lines of Code) | 73 | 149 | 202 | 137 (871) | 118 |
| Flocking-small (Time-Ratio) | 1.0 | 0.75 | 1.42 | 15.37 | 159.29 |
| Flocking-large (Time-Ratio) | 1.0 | 0.4 | 0.61 | 19.14 | 59.5 |
| Flocking (Lines of Code) | 42 | 137 | 159 | 82 (689) | 94 |
| Schelling-small (Time-Ratio) | 1.0 | 0.67 | 1.19 | 11.39 | 29.73 |
| Schelling-large (Time-Ratio) | 1.0 | 0.68 | 1.51 | 14.33 | 26.66 |
| Schelling (Lines of Code) | 26 | 78 | 129 | 54 (739) | 33 |

## How it works

Expand Down Expand Up @@ -79,7 +76,7 @@ sudo apt install python3-pip
pip install mesa==3.2.0

# install netlogo
sudo wget http://ccl.northwestern.edu/netlogo/6.4.0/NetLogo-6.4.0-64.tgz
sudo wget https://downloads.netlogo.org/6.4.0/NetLogo-6.4.0-64.tgz
sudo tar -xzf NetLogo-6.4.0-64.tgz

# move netlogo inside repository
Expand All @@ -96,3 +93,7 @@ cd ABM_Framework_Comparisons
If you are using WSL make sure that you move to a folder inside the subsystem before running these commands.






2 changes: 1 addition & 1 deletion Schelling/Agents/Schelling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function agent_step!(agent, model)
count_neighbors_same_group += 1
end
end
if count_neighbors_same_group model.min_to_be_happy
if count_neighbors_same_group < model.min_to_be_happy
move_agent_single!(agent, model)
end
return
Expand Down
Loading