Skip to content

Commit 37d3a13

Browse files
authored
Use a mutable struct for the state of GridSpaceIdIterator (#936)
1 parent de82efd commit 37d3a13

File tree

2 files changed

+24
-18
lines changed

2 files changed

+24
-18
lines changed

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,22 @@ _We tried to deprecate every major change, resulting in practically no breakage
1010
- Allows us to develop new types of models that may have rules that are defined differently, without being based on e.g., two particular functions.
1111
- Allows us to develop (in the future) a new model type that is optimized for multi-agent simulations.
1212
- 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.
13-
- 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.
14-
- Due to this, the `nextid` function is no longer public API.
13+
- 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.
1514
- 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.
1615

1716
## New features
1817

18+
- Grid and continuous spaces support boundaries with mixed periodicity, specified by tuples with a `Bool` value for each dimension, e.g. `GridSpace((5,5); periodic=(true,false))` is periodic along the first dimension but not along the second.
1919
- 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).
2020
- A new function `swap_agents` can be used to swap an agents couple in a discrete space.
21+
22+
## Performance Improvements
23+
2124
- 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.
2225
- The `random_agent` function is now much faster than before. The functions `random_nearby_position`, `random_nearby_id` and `random_nearby_agent` are up to 2 times faster thanks to a faster sampling function.
26+
- The `nearby_agents` function for `ContinuousSpace` and `GridSpace` is now 1.5x faster than before.
2327
- The `sample!` function is up to 2x faster than before.
24-
- Grid and continuous spaces support boundaries with mixed periodicity, specified by tuples with a `Bool` value for each dimension, e.g. `GridSpace((5,5); periodic=(true,false))` is periodic along the first dimension but not along the second.
28+
2529

2630
# v5.17
2731

src/spaces/grid_multi.jl

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,11 @@ function nearby_ids(pos::NTuple{D, Int}, space::GridSpace{D,P}, r::Real = 1) whe
120120
return GridSpaceIdIterator{P, D}(stored_ids, nindices, pos, L, space_size, nocheck)
121121
end
122122

123-
# Iterator struct. State is `(pos_i, inner_i)` with `pos_i` the index to the nearby indices
124-
# P is Boolean, and means "periodic".
123+
mutable struct IdIteratorState
124+
pos_i::Int
125+
inner_i::Int
126+
ids_in_pos::Vector{Int}
127+
end
125128
struct GridSpaceIdIterator{P,D}
126129
stored_ids::Array{Vector{Int},D} # Reference to array in grid space
127130
indices::Vector{NTuple{D,Int}} # Result of `offsets_within_radius` pretty much
@@ -136,10 +139,9 @@ Base.IteratorSize(::Type{<:GridSpaceIdIterator}) = Base.SizeUnknown()
136139
# Initialize iteration
137140
function Base.iterate(iter::GridSpaceIdIterator)
138141
@inbounds begin
139-
stored_ids, indices, L, origin, nocheck = getproperty.(
140-
Ref(iter), (:stored_ids, :indices, :L, :origin, :nocheck))
141-
combine, invalid = nocheck ? (combine_positions_nocheck, invalid_access_nocheck) :
142-
(combine_positions, invalid_access)
142+
indices, L, origin = iter.indices, iter.L, iter.origin
143+
combine, invalid = iter.nocheck ? (combine_positions_nocheck, invalid_access_nocheck) :
144+
(combine_positions, invalid_access)
143145
pos_i = 1
144146
pos_index = combine(indices[pos_i], origin, iter)
145147
# First, check if the position index is valid (bounds checking)
@@ -151,22 +153,21 @@ function Base.iterate(iter::GridSpaceIdIterator)
151153
pos_index = combine(indices[pos_i], origin, iter)
152154
end
153155
# We have a valid position index and a non-empty position
154-
ids_in_pos = stored_ids[pos_index...]
156+
ids_in_pos = iter.stored_ids[pos_index...]
155157
id = ids_in_pos[1]
156158
end
157-
return (id, (pos_i, 2, ids_in_pos))
159+
return (id, IdIteratorState(pos_i, 2, ids_in_pos))
158160
end
159161

160162
# For performance we need a different method of starting the iteration
161163
# and another one that continues iteration. Second case uses the explicitly
162164
# known knowledge of `pos_i` being a valid position index.
163165
function Base.iterate(iter::GridSpaceIdIterator, state)
164166
@inbounds begin
165-
stored_ids, indices, L, origin, nocheck = getproperty.(
166-
Ref(iter), (:stored_ids, :indices, :L, :origin, :nocheck))
167-
combine, invalid = nocheck ? (combine_positions_nocheck, invalid_access_nocheck) :
168-
(combine_positions, invalid_access)
169-
pos_i, inner_i, ids_in_pos = state
167+
indices, L, origin = iter.indices, iter.L, iter.origin
168+
combine, invalid = iter.nocheck ? (combine_positions_nocheck, invalid_access_nocheck) :
169+
(combine_positions, invalid_access)
170+
pos_i, inner_i, ids_in_pos = state.pos_i, state.inner_i, state.ids_in_pos
170171
if inner_i > length(ids_in_pos)
171172
# we have exhausted IDs in current position, so we reset and go to next
172173
pos_i += 1
@@ -180,11 +181,12 @@ function Base.iterate(iter::GridSpaceIdIterator, state)
180181
pos_i > L && return nothing
181182
pos_index = combine(indices[pos_i], origin, iter)
182183
end
183-
ids_in_pos = stored_ids[pos_index...]
184+
ids_in_pos = iter.stored_ids[pos_index...]
184185
end
185186
# We reached the next valid position and non-empty position
186187
id = ids_in_pos[inner_i]
187-
return (id, (pos_i, inner_i + 1, ids_in_pos))
188+
state.pos_i, state.inner_i, state.ids_in_pos = pos_i, inner_i + 1, ids_in_pos
189+
return (id, state)
188190
end
189191
end
190192

0 commit comments

Comments
 (0)