Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further clarify mandatory API ABM #900

Merged
merged 16 commits into from
Oct 6, 2023
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ _We tried to deprecate every major change, resulting in practically no breakage
- Allows us to develop (in the future) a new model type that is optimized for multi-agent simulations.
- The `@agent` macro has been rewritten to support fields with default and const values. It has a new usage syntax now that parallelizes more Julia's native `struct` declaration. The old macro version still works but it's deprecated. Since now the macro supports these features, using `@agent` is the only supported way to create agent types for Agents.jl.
- Manually setting or altering the ids of agents is no longer allowed. The agent id is now considered a read-only field, and is set internally by Agents.jl to enable hidden optimizations in the future. As a consequence, `add_agent!(agent::AbstractAgent, pos::ValidPos, model::ABM)` and `add_agent!(agent::AbstractAgent, model::ABM)` have been deprecated. See issue #861 for more.
- Due to this, the `nextid` function is no longer public API.
- Agent types in `ContinuousSpace` now use `SVector` for their `pos` and `vel` fields rather than `NTuple`. `NTuple` usage 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, e.g., doing `agent.pos == (0.5, 0.5)`. This will always be `false` in v6 as `agent.pos` is an `SVector`. The rest of the functionality should all work without problems, such as moving agents to tuple-based positions etc.

## New features

- 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 function `swap_agents` can be used to swap an agents couple in a discrete space.
- A new function `swap_agents` can be used to swap an agents couple in a discrete space.
- 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 `random_agent` function is now much faster than before.
- The `sample!` function is up to 2x faster than before.
Expand Down
18 changes: 9 additions & 9 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ end

#### API -> GRAPH ####

graph_model = ABM(GraphAgent, GraphSpace(complete_digraph(200)))
graph_union_model = ABM(
graph_model = StandardABM(GraphAgent, GraphSpace(complete_digraph(200)))
graph_union_model = StandardABM(
Union{GraphAgentOne,GraphAgentTwo,GraphAgentThree,GraphAgentFour,GraphAgentFive},
GraphSpace(complete_digraph(200)),
warn = false,
Expand All @@ -88,7 +88,7 @@ SUITE["graph"]["add_union"]["agent_pos"] =
SUITE["graph"]["add_union"]["agent_single"] =
@benchmarkable add_agent_single!($GraphAgent, $graph_union_model, 6.5, false) samples = 100

graph_model = ABM(GraphAgent, GraphSpace(complete_digraph(100)))
graph_model = StandardABM(GraphAgent, GraphSpace(complete_digraph(100)))
for position in 1:100
for _ in 1:4
add_agent!(position, graph_model, 6.5, false)
Expand Down Expand Up @@ -121,8 +121,8 @@ SUITE["graph"]["position"]["positions"] = @benchmarkable positions($graph_model)

##### API -> GRID ####

grid_model = ABM(GridAgent, GridSpace((15, 15)))
grid_union_model = ABM(
grid_model = StandardABM(GridAgent, GridSpace((15, 15)))
grid_union_model = StandardABM(
Union{GridAgentOne,GridAgentTwo,GridAgentThree,GridAgentFour,GridAgentFive},
GridSpace((15, 15));
warn = false,
Expand All @@ -138,7 +138,7 @@ SUITE["grid"]["add_union"]["agent_pos"] =
SUITE["grid"]["add_union"]["agent_fill"] =
@benchmarkable fill_space!(GridAgent, $grid_union_model, 6.5, false) samples = 100

grid_model = ABM(GridAgent, GridSpace((50, 50)))
grid_model = StandardABM(GridAgent, GridSpace((50, 50)))
for x in 1:50
for y in 1:50
for _ in 1:4
Expand Down Expand Up @@ -176,18 +176,18 @@ SUITE["graph"]["position"]["positions"] = @benchmarkable positions($graph_model)

#### API -> CONTINUOUS ####

continuous_model = ABM(ContinuousAgent{3,Float64}, ContinuousSpace((10.0, 10.0, 10.0); spacing = 0.5))
continuous_model = StandardABM(ContinuousAgent{3,Float64}, ContinuousSpace((10.0, 10.0, 10.0); spacing = 0.5))

# We must use setup create the model inside some benchmarks here, otherwise we hit the issue from #226.
# For tuning, this is actually impossible. So until ContinuousSpace is implemented, we drop these tests.
SUITE["continuous"]["add"]["agent_pos"] =
@benchmarkable add_agent!((2.2, 1.9, 7.5), $ContinuousAgent, cmodel, (0.5, 1.0, 0.01), 6.5, false) setup =
(cmodel = ABM(ContinuousAgent, ContinuousSpace((10.0, 10.0, 10.0); spacing = 0.5))) samples =
(cmodel = StandardABM(ContinuousAgent, ContinuousSpace((10.0, 10.0, 10.0); spacing = 0.5))) samples =
100

SUITE["continuous"]["add_union"]["agent_pos"] =
@benchmarkable add_agent!((2.2, 1.9, 7.5), $ContinuousAgent, cmodel, (0.5, 1.0, 0.01), 6.5, false) setup = (
cmodel = ABM(
cmodel = StandardABM(
Union{
ContinuousAgentOne,
ContinuousAgentTwo,
Expand Down
2 changes: 1 addition & 1 deletion benchmark/ensembles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function ensemble_benchmark(f, parallel, nreplicates)
space = GridSpace(griddims, periodic = false)
properties = Dict(:min_to_be_happy => min_to_be_happy)

model = ABM(SchellingAgent, space;
model = StandardABM(SchellingAgent, space;
properties = properties, scheduler = Schedulers.randomly)

for n in 1:numagents
Expand Down
4 changes: 2 additions & 2 deletions benchmark/schedulers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ end
end

function fake_model(; nagents, scheduler)
model = ABM(FakeAgent; scheduler)
model = StandardABM(FakeAgent; scheduler)
for i in 1:nagents
add_agent!(model, i % 2, 0)
end
model
end

function fake_model_multi(; nagents, scheduler)
model = ABM(Union{FakeAgent,OtherFakeAgent}; scheduler, warn = false)
model = StandardABM(Union{FakeAgent,OtherFakeAgent}; scheduler, warn = false)
for i in 1:nagents
if i % 2 == 0
add_agent!(FakeAgent, model, 0, 0)
Expand Down
2 changes: 1 addition & 1 deletion benchmark/simple_grid/continuous_space_impact.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ r = 0.1
space = ContinuousSpace(extent, spacing)
@agent struct Agent(ContinuousAgent{2,Float64})
end
model = ABM(Agent, space; rng = StableRNG(42))
model = StandardABM(Agent, space; rng = StableRNG(42))

# fill with random agents
N = 1000
Expand Down
2 changes: 1 addition & 1 deletion benchmark/simple_grid/gridspace_based.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function initialize_gridspace()
space = GridSpace(grid_size; periodic = false)
properties = Dict(:min_to_be_happy => min_to_be_happy)
rng = Random.Xoshiro(rand(UInt))
model = ABM(GridSpaceAgent, space; properties, rng)
model = StandardABM(GridSpaceAgent, space; properties, rng)
N = grid_size[1]*grid_size[2]*grid_occupation
for n in 1:N
group = n < N / 2 ? 1 : 2
Expand Down
2 changes: 1 addition & 1 deletion benchmark/simple_grid/simple_grid_space.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function initialize_sologridspace()
space = GridSpaceSingle(grid_size; periodic = false)
properties = Dict(:min_to_be_happy => min_to_be_happy)
rng = Random.Xoshiro(rand(UInt))
model = ABM(SoloGridSpaceAgent, space; properties, rng)
model = StandardABM(SoloGridSpaceAgent, space; properties, rng)
N = grid_size[1]*grid_size[2]*grid_occupation
for n in 1:N
group = n < N / 2 ? 1 : 2
Expand Down
14 changes: 7 additions & 7 deletions docs/logo/example_zoo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function flocking_model(;
space2d = ContinuousSpace(extent; spacing = visual_distance/1.5)
rng = Random.MersenneTwister(seed)

model = ABM(Bird, space2d; rng, scheduler = Schedulers.Randomly())
model = StandardABM(Bird, space2d; rng, scheduler = Schedulers.Randomly())
for _ in 1:n_birds
vel = rand(abmrng(model), SVector{2}) * 2 .- 1
add_agent!(
Expand Down Expand Up @@ -156,7 +156,7 @@ end
function initialise_zombies(; seed = 1234)
map_path = OSM.test_map()
properties = Dict(:dt => 1 / 60)
model = ABM(
model = StandardABM(
Zombie,
OpenStreetMapSpace(map_path);
properties = properties,
Expand Down Expand Up @@ -293,7 +293,7 @@ function transform_forces(agent::SimpleCell)
return fsym, compression, torque
end

bacteria_model = ABM(
bacteria_model = StandardABM(
SimpleCell,
ContinuousSpace((14, 9); spacing = 1.0, periodic = false);
properties = Dict(:dt => 0.005, :hardness => 1e2, :mobility => 1.0),
Expand Down Expand Up @@ -339,7 +339,7 @@ function initialize_runners(map_url; goal = (128, 409), seed = 88)
heightmap = floor.(Int, convert.(Float64, load(download(map_url))) * 255)
space = GridSpace(size(heightmap); periodic = false)
pathfinder = AStar(space; cost_metric = PenaltyMap(heightmap, MaxDistance{2}()))
model = ABM(
model = StandardABM(
Runner,
space;
rng = MersenneTwister(seed),
Expand Down Expand Up @@ -383,7 +383,7 @@ function forest_fire(; density = 0.7, griddims = (100, 100), seed = 2)
rng = Random.MersenneTwister(seed)
## The `trees` field is coded such that
## Empty = 0, Green = 1, Burning = 2, Burnt = 3
forest = ABM(GridAgent{2}, space; rng, properties = (trees = zeros(Int, griddims),))
forest = StandardABM(GridAgent{2}, space; rng, properties = (trees = zeros(Int, griddims),))
for I in CartesianIndices(forest.trees)
if rand(abmrng(forest)) < density
## Set the trees at the left edge on fire
Expand Down Expand Up @@ -689,7 +689,7 @@ function initialize_fractal(;
)
## space is periodic to allow particles going off one edge to wrap around to the opposite
space = ContinuousSpace(space_extents; spacing = 1.0, periodic = true)
model = ABM(FractalParticle, space; properties, rng = Random.MersenneTwister(seed))
model = StandardABM(FractalParticle, space; properties, rng = Random.MersenneTwister(seed))
center = space_extents ./ 2.0
for i in 1:initial_particles
p_r = particle_radius(min_radius, max_radius, abmrng(model))
Expand Down Expand Up @@ -788,7 +788,7 @@ function socialdistancing_init(;
dt,
)
space = ContinuousSpace((1,1); spacing = 0.02)
model = ABM(PoorSoul, space, properties = properties, rng = MersenneTwister(seed))
model = StandardABM(PoorSoul, space, properties = properties, rng = MersenneTwister(seed))

## Add initial individuals
for ind in 1:N
Expand Down
2 changes: 1 addition & 1 deletion docs/logo/logo_model_def.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function sir_logo_initiation(;
)
rng = Random.Xoshiro(seed)
space = ContinuousSpace(logo_dims ./ 100)
model = ABM(PoorSoul, space; rng, properties)
model = StandardABM(PoorSoul, space; rng, properties)

## Add pre-defined static individuals
#--------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Specifically, imagine the scenario
using Agents
# We don't need to make a new agent type here,
# we use the minimal agent for 4-dimensional grid spaces
model = ABM(GridAgent{4}, GridSpace((5, 5, 5, 5)))
model = StandardABM(GridAgent{4}, GridSpace((5, 5, 5, 5)))
add_agent!((1, 1, 1, 1), model)
add_agent!((1, 1, 1, 1), model)
add_agent!((2, 1, 1, 1), model)
Expand Down
4 changes: 2 additions & 2 deletions docs/src/performance_tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ using Agents
@agent struct MyAgent(NoSpaceAgent) <: AbstractAgent
end
properties = Dict(:par1 => 1, :par2 => 1.0, :par3 => "Test")
model = ABM(MyAgent; properties = properties)
model = StandardABM(MyAgent; properties = properties)
model_step!(model) = begin
a = model.par1 * model.par2
end
Expand Down Expand Up @@ -103,7 +103,7 @@ The solution is to use a Dictionary for model properties only when all values ar
end

properties = Parameters()
model = ABM(MyAgent; properties = properties)
model = StandardABM(MyAgent; properties = properties)
```

## Don't use agents to represent a spatial property
Expand Down
2 changes: 1 addition & 1 deletion examples/celllistmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function initialize_bouncing(;
number_of_particles=number_of_particles,
system=system,
)
model = ABM(Particle,
model = StandardABM(Particle,
space2d,
properties=properties
)
Expand Down
8 changes: 4 additions & 4 deletions examples/diffeq.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function initialise(;
min_threshold = 60.0, # Regulate fishing if population drops below this value
nagents = 50,
)
model = ABM(
model = StandardABM(
Fisher;
properties = Dict(
:stock => stock,
Expand Down Expand Up @@ -153,7 +153,7 @@ function initialise(;
min_threshold = 60.0, # Regulate fishing if population drops below this value
nagents = 50,
)
model = ABM(
model = StandardABM(
Fisher;
properties = Dict(
:stock => stock,
Expand Down Expand Up @@ -253,7 +253,7 @@ function initialise_diffeq(;
OrdinaryDiffEq.ODEProblem(fish_stock!, [stock], (0.0, Inf), [max_population, 0.0])
integrator = OrdinaryDiffEq.init(prob, OrdinaryDiffEq.Tsit5(); advance_to_tstop = true)

model = ABM(
model = StandardABM(
Fisher;
properties = Dict(
:stock => stock,
Expand Down Expand Up @@ -361,7 +361,7 @@ function agent_cb_step!(agent, model)
end

function initialise_cb(; min_threshold = 60.0, nagents = 50)
model = ABM(Fisher; properties = Dict(:min_threshold => min_threshold))
model = StandardABM(Fisher; properties = Dict(:min_threshold => min_threshold))

for _ in 1:nagents
add_agent!(model, floor(rand(abmrng(model), truncated(LogNormal(), 1, 6))), 0.0)
Expand Down
2 changes: 1 addition & 1 deletion examples/flock.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function initialize_model(;
space2d = ContinuousSpace(extent; spacing = visual_distance/1.5)
rng = Random.MersenneTwister(seed)

model = ABM(Bird, space2d; rng, scheduler = Schedulers.Randomly())
model = StandardABM(Bird, space2d; rng, scheduler = Schedulers.Randomly())
for _ in 1:n_birds
vel = rand(abmrng(model), SVector{2}) * 2 .- 1
add_agent!(
Expand Down
2 changes: 1 addition & 1 deletion examples/measurements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function daisyworld(;
properties = @dict max_age surface_albedo solar_luminosity solar_change scenario
properties[:tick] = 0
daisysched(model) = [a.id for a in allagents(model) if a isa Daisy]
model = ABM(
model = StandardABM(
Union{Daisy,Land},
space;
scheduler = daisysched,
Expand Down
2 changes: 1 addition & 1 deletion examples/predator_prey.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function initialize_model(;
countdown = zeros(Int, dims),
regrowth_time = regrowth_time,
)
model = ABM(Union{Sheep, Wolf}, space;
model = StandardABM(Union{Sheep, Wolf}, space;
properties, rng, scheduler = Schedulers.randomly, warn = false
)
## Add agents
Expand Down
2 changes: 1 addition & 1 deletion examples/rabbit_fox_hawk.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function initialize_model(
dt = dt,
)

model = ABM(Animal, space; rng, properties)
model = StandardABM(Animal, space; rng, properties)

## spawn each animal at a random walkable position according to its pathfinder
for _ in 1:n_rabbits
Expand Down
4 changes: 2 additions & 2 deletions examples/schelling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ end
# We also want to include a property `min_to_be_happy` in our model, and so we have:

properties = Dict(:min_to_be_happy => 3)
schelling = ABM(SchellingAgent, space; properties, agent_step!)
schelling = StandardABM(SchellingAgent, space; properties, agent_step!)

# Since we opted to use an `agent_step!` function, the scheduler of the model matters.
# Here we used the default scheduler (which is also the fastest one) to create
# the model. We could instead try to activate the agents according to their
# property `:group`, so that all agents of group 1 act first.
# We would then use the scheduler [`Schedulers.ByProperty`](@ref) like so:

schelling2 = ABM(
schelling2 = StandardABM(
SchellingAgent,
space;
properties,
Expand Down
2 changes: 1 addition & 1 deletion examples/schoolyard.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function schoolyard(;
seed = 6998,
velocity = (0, 0),
)
model = ABM(
model = StandardABM(
Student,
ContinuousSpace((100, 100); spacing=spacing, periodic=false);
properties = Dict(
Expand Down
2 changes: 1 addition & 1 deletion examples/sir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function model_initiation(;
death_rate
)
space = GraphSpace(complete_graph(C))
model = ABM(PoorSoul, space; properties, rng)
model = StandardABM(PoorSoul, space; properties, rng)

## Add initial individuals
for city in 1:C, n in 1:Ns[city]
Expand Down
2 changes: 1 addition & 1 deletion examples/zombies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ end
function initialise_zombies(; seed = 1234)
map_path = OSM.test_map()
properties = Dict(:dt => 1 / 60)
model = ABM(
model = StandardABM(
Zombie,
OpenStreetMapSpace(map_path);
agent_step! = zombie_step!,
Expand Down
18 changes: 9 additions & 9 deletions src/Agents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ using Random
using StaticArrays: SVector
export SVector
import ProgressMeter
import Base.length # TODO: This should not be imported!!!
import LinearAlgebra

# Core structures of Agents.jl
include("core/agents.jl")
include("core/model_abstract.jl")
include("core/model_concrete.jl")
include("core/for_free_extensions.jl")
include("core/model_single_container.jl")
include("core/space_interaction_API.jl")
include("core/higher_order_iteration.jl")

Expand Down Expand Up @@ -80,20 +80,20 @@ Breaking changes:
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
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. The macro has been rewritten to make it
possible to declare fields as constants. The old version still works but it's deprecated.
possible to declare fields as constants. The old version still works but it's deprecated.
Refer to the documentation of the macro for the new syntax.
- Manually setting or altering the ids of agents is no longer allowed. The agent id is now considered
a read-only field, and is set internally by Agents.jl to enable hidden optimizations in the future.
As a consequence, `add_agent!(agent::AbstractAgent, pos::ValidPos, model::ABM)` and
- Manually setting or altering the ids of agents is no longer allowed. The agent id is now considered
a read-only field, and is set internally by Agents.jl to enable hidden optimizations in the future.
As a consequence, `add_agent!(agent::AbstractAgent, pos::ValidPos, model::ABM)` and
`add_agent!(agent::AbstractAgent, model::ABM)` have been 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.
Agents in `ContinuousSpace` now require `SVector` for their `pos` and `vel` fields instead of `NTuple`.
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.
- Several performance improvements all across the board.
Expand Down
Loading
Loading