Skip to content

Commit

Permalink
Faster sample! function (#858)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tortar authored Aug 26, 2023
1 parent 360eb04 commit f824cbb
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 24 deletions.
13 changes: 5 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
# v6
- The `@agent` macro now supports fields with default and const values (through the special `constants` field). Since now the macro supports these features, using `@agent` is the only supported way to create agent types for Agents.jl.
- The `add_agent!` function supports adding an agent propagating keywords arguments.
- Agent types in `ContinuousSpace` now require `SVector` for their `pos` and `vel` fields rather than `NTuple`.
- Two new functions `random_id_in_position` and `random_agent_in_position` can be used to select a random id/agent in a position in discrete spaces (even with filtering).
- A new argument `alloc` can be used to select a more performant version in relation to the expensiveness of the filtering for all random methods selecting ids/agents/positions.
- The `sample!` function is up to 2x faster than before.

## BREAKING
- `NTuple` in `ContinuousSpace` is officially deprecated, but backward compatibility is *mostly* maintained. Known breakages include the comparison of agent position and/or velocity with user-defined tuples (which will silently fail).

# main

- The `@agent` macro now supports fields with default and const values (through the special `constants` field). Since now the macro supports these features, using `@agent` is the only supported way to create agent types for Agents.jl.
- The `add_agent!` function supports adding an agent propagating keywords arguments.
- Two new functions `random_id_in_position` and `random_agent_in_position` can be used to select a random id/agent in a position in discrete spaces (even with filtering).
- A new argument `alloc` can be used to select a more performant version in relation to the expensiveness of the filtering for all
random methods selecting ids/agents/positions.

# v5.17

- New function `replicate!` allows to create a new instance of a given agent at the same position with the possibility to specify some
Expand Down
25 changes: 25 additions & 0 deletions benchmark/sample.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Agents, BenchmarkTools

@agent FakeAgent NoSpaceAgent begin end

function fake_model(; nagents)
model = StandardABM(FakeAgent)
for i in 1:nagents
add_agent!(model)
end
return model
end

@benchmark sample!(model, 10^5) setup=(model = fake_model(; nagents = 10^2)) evals=1
@benchmark sample!(model, 10^4) setup=(model = fake_model(; nagents = 10^2)) evals=1
@benchmark sample!(model, 10^3) setup=(model = fake_model(; nagents = 10^2)) evals=1
@benchmark sample!(model, 10^2) setup=(model = fake_model(; nagents = 10^2)) evals=1
@benchmark sample!(model, 10^1) setup=(model = fake_model(; nagents = 10^2)) evals=1
@benchmark sample!(model, 10^0) setup=(model = fake_model(; nagents = 10^2)) evals=1

@benchmark sample!(model, 10^5) setup=(model = fake_model(; nagents = 10^5)) evals=1
@benchmark sample!(model, 10^4) setup=(model = fake_model(; nagents = 10^5)) evals=1
@benchmark sample!(model, 10^3) setup=(model = fake_model(; nagents = 10^5)) evals=1
@benchmark sample!(model, 10^2) setup=(model = fake_model(; nagents = 10^5)) evals=1
@benchmark sample!(model, 10^1) setup=(model = fake_model(; nagents = 10^5)) evals=1
@benchmark sample!(model, 10^0) setup=(model = fake_model(; nagents = 10^5)) evals=1
26 changes: 19 additions & 7 deletions src/Agents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,26 @@ Welcome to this new update of Agents.jl!
Breaking changes:
- Agents in `ContinuousSpace` now require `SVector` for their `pos`
and `vel` fields instead of `NTuple`.
- Agents.jl moved to Julia 1.9+, and now exports visualization
and interactive applications automatically once Makie (or Makie backends
such as GLMakie) come into scope, using the new package extension system.
The only downside of this is that now to visualize ABMs on open street
maps, the package OSMMakie.jl must be explicitly loaded as well.
InteractiveDynamics.jl is now obsolete.
- We have created an objective fully automated framework for comparing open source
agent based modelling software. It shows that Agents.jl is much faster
than competing alternatives (MASON, NetLogo, Mesa).
- The `@agent` macro is now THE way to create agent types for Agents.jl simulations since
now supports declaring default and constant fields. Directly creating structs by hand is
no longer mentioned in the documentation at all. This will allow us in the future to utilize
additional fields that the user does not have to know about, which may bring new features or
performance gains by being part of the agent structures.
- `ContinuousAgent{D}` is not a concrete type anymore. The new interface requires two parameters
`ContinuousAgent{D,T}` where `T` is any `AbstractFloat` type. If you want to use a type different
from `Float64`, you will also need to change the type of the `ContinuousSpace` extent accordingly.
Agents in `ContinuousSpace` now require `SVector` for their `pos` and `vel` fields instead of `NTuple`.
Using `NTuple`s in `ContinuousSpace` is now deprecated.
- `ContinuousAgent{D}` is not a concrete type anymore.
The new interface requires two parameters `ContinuousAgent{D,T}`
where `T` is any `AbstractFloat` type.
If you want to use a type different from `Float64`, you will also
need to change the type of the `ContinuousSpace` extent accordingly.
- Several performance improvements all across the board.
See the online documentation for more!
"""
Expand Down
24 changes: 15 additions & 9 deletions src/simulations/sample.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,34 @@ function sample!(
org_ids = collect(allids(model))
if weight !== nothing
weights = Weights([get_data(a, weight, identity) for a in allagents(model)])
newids = sample(abmrng(model), org_ids, weights, n, replace = replace)
new_ids = sample(abmrng(model), org_ids, weights, n, replace = replace)
else
newids = sample(abmrng(model), org_ids, n, replace = replace)
new_ids = sample(abmrng(model), org_ids, n, replace = replace)
end
add_newids!(model, org_ids, newids)
add_newids!(model, org_ids, new_ids)
end

#Used in sample!
function add_newids!(model, org_ids, newids)
# `counter` counts the number of occurencies for each item, it comes from DataStructure.jl
count_newids = counter(newids)
function add_newids!(model, org_ids, new_ids)
sort!(org_ids)
sort!(new_ids)
i, L = 1, length(new_ids)
sizehint!(agent_container(model), L)
id_new = new_ids[1]
for id in org_ids
noccurances = count_newids[id]
agent = model[id]
if noccurances == 0
if id_new != id
remove_agent!(agent, model)
else
for _ in 2:noccurances
i += 1
while i <= L && new_ids[i] == id
replicate!(agent, model)
i += 1
end
i <= L && (id_new = new_ids[i])
end
end
return
end

"""
Expand Down
19 changes: 19 additions & 0 deletions test/randomness_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ using Agents, Test
end

@testset "sample!" begin
rng = StableRNG(50)
model4 = ABM(Agent1, GridSpace((2, 2)); rng = rng)
agents = model4.agents
add_agent_pos!(Agent1(1, (1,1)), model4)
add_agent_pos!(Agent1(2, (2,2)), model4)
sample!(model4, 4)
res = Dict{Int64, Agent1}(4 => Agent1(4, (2, 2)), 2 => Agent1(2, (2, 2)),
3 => Agent1(3, (2, 2)), 1 => Agent1(1, (1, 1)))
res_fields = [getfield(res[k], f) for f in fieldnames(Agent1) for k in keys(res)]
agents_fields = [getfield(agents[k], f) for f in fieldnames(Agent1) for k in keys(model4.agents)]
@test keys(model4.agents) == keys(res)
@test res_fields == agents_fields
sample!(model4, 2)
res = Dict{Int64, Agent1}(4 => Agent1(4, (2, 2)), 1 => Agent1(1, (1, 1)))
res_fields = [getfield(res[k], f) for f in fieldnames(Agent1) for k in keys(res)]
agents_fields = [getfield(agents[k], f) for f in fieldnames(Agent1) for k in keys(model4.agents)]
@test keys(model4.agents) == keys(res)
@test res_fields == agents_fields

rng = StableRNG(42)
model = ABM(Agent2; rng = rng)
for i in 1:20
Expand Down

0 comments on commit f824cbb

Please sign in to comment.