Agents.jl periodic=true still gives space extent errors

Hey all, I have a continuous, periodic space ABM. My ABM declaration looks like this:

    space2d = ContinuousSpace(extent;
                              periodic=true,
                              );
    model = AgentBasedModel(Particle, space2d;
                            agent_step! = agent_step!,
                            model_step! = (args...) -> (@timeit to "model_step!" model_step!(args...; to=to)),
                            properties=Dict(:attraction_matrix => properties, :time_scale => 1.0, :hitbox => 1.0, :viscosity => 0.9, :max_distance => 80.0)
                            )

However running this for a sufficient amount of steps results in this error:

Error in callback:
position is outside space extent!
Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:44
  [2] move_agent!(agent::Main.ParticleLife.Particle, pos::StaticArraysCore.SVector{2, Float64}, model::Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG})
    @ Agents ~/.julia/packages/Agents/WuWeG/src/spaces/continuous.jl:153
  [3] walk!(agent::Main.ParticleLife.Particle, direction::StaticArraysCore.SVector{2, Float64}, model::Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG})
    @ Agents ~/.julia/packages/Agents/WuWeG/src/spaces/walk.jl:64
  [4] move_agent!(agent::Main.ParticleLife.Particle, model::Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG}, dt::Float64)
    @ Agents ~/.julia/packages/Agents/WuWeG/src/spaces/continuous.jl:187
  [5] agent_step!(agent::Main.ParticleLife.Particle, model::Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG})
    @ Main.ParticleLife ~/Documents/Programming/particle-life/particle.jl:115
  [6] step_ahead!(model::Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG}, agent_step!::typeof(Main.ParticleLife.agent_step!), model_step!::Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, n::Int64, t::Base.RefValue{Int64})
    @ Agents ~/.julia/packages/Agents/WuWeG/src/simulations/step_standard.jl:17
  [7] step!
    @ ~/.julia/packages/Agents/WuWeG/src/simulations/step_standard.jl:5 [inlined]
  [8] step!(abmobs::Agents.ABMObservable{Observables.Observable{Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG}}, Nothing, Nothing, Nothing, Nothing, Bool, Observables.Observable{Int64}, Observables.Observable{Tuple{Base.RefValue{Int64}, Vector{Int64}}}}, t::Int64)
    @ AgentsVisualizations ~/.julia/packages/Agents/WuWeG/ext/AgentsVisualizations/src/model_observable.jl:37
  [9] (::AgentsVisualizations.var"#add_controls!##2#add_controls!##3"{Agents.ABMObservable{Observables.Observable{Agents.StandardABM{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.ParticleLife.Particle, Dict{Int64, Main.ParticleLife.Particle}, Tuple{DataType}, typeof(Main.ParticleLife.agent_step!), Main.ParticleLife.var"#make_model##0#make_model##1"{TimerOutputs.TimerOutput}, typeof(Agents.Schedulers.fastest), Dict{Symbol, Any}, Random.TaskLocalRNG}}, Nothing, Nothing, Nothing, Nothing, Bool, Observables.Observable{Int64}, Observables.Observable{Tuple{Base.RefValue{Int64}, Vector{Int64}}}}, Observables.Observable{Any}})(c::Int64)
    @ AgentsVisualizations ~/.julia/packages/Agents/WuWeG/ext/AgentsVisualizations/src/interaction.jl:47
 [10] notify
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
 [11] setindex!(observable::Observables.Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:123
 [12] (::Makie.var"#initialize_block!##179#initialize_block!##180"{Makie.Button})(::Makie.MouseEvent)
    @ Makie ~/.julia/packages/Makie/FUAHr/src/makielayout/blocks/button.jl:72
 [13] (::Makie.var"#1313#1314"{Makie.var"#initialize_block!##179#initialize_block!##180"{Makie.Button}})(event::Makie.MouseEvent)
    @ Makie ~/.julia/packages/Makie/FUAHr/src/makielayout/mousestatemachine.jl:94
 [14] notify
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
 [15] setindex!
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:123 [inlined]
 [16] (::Makie.var"#_addmouseevents!##2#_addmouseevents!##3"{Makie.Scene, Base.RefValue{Bool}, Base.RefValue{Union{Nothing, Makie.Mouse.Button}}, Base.RefValue{Float64}, Base.RefValue{Float64}, Base.RefValue{Bool}, Base.RefValue{GeometryBasics.Point{2, Float64}}, Base.RefValue{Bool}, Base.RefValue{Union{Nothing, Makie.Mouse.Button}}, Base.RefValue{Bool}, Base.RefValue{GeometryBasics.Point{2, Float32}}, Base.RefValue{GeometryBasics.Point{2, Float64}}, Base.RefValue{Makie.Mouse.Action}, Observables.Observable{Makie.MouseEvent}, Float64, Module})(event::Makie.MouseButtonEvent)
    @ Makie ~/.julia/packages/Makie/FUAHr/src/makielayout/mousestatemachine.jl:365
 [17] notify
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
 [18] setindex!
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:123 [inlined]
 [19] (::GLMakie.var"#mousebuttons#mousebuttons##0"{Observables.Observable{Makie.MouseButtonEvent}})(window::GLFW.Window, button::GLFW.MouseButton, action::GLFW.Action, mods::Int32)
    @ GLMakie ~/.julia/packages/GLMakie/1dQSN/src/events.jl:104
 [20] _MouseButtonCallbackWrapper(window::GLFW.Window, button::GLFW.MouseButton, action::GLFW.Action, mods::Int32)
    @ GLFW ~/.julia/packages/GLFW/Zwulj/src/callback.jl:46
 [21] macro expansion
    @ ~/.julia/packages/GLFW/Zwulj/src/GLFW.jl:34 [inlined]
 [22] PollEvents
    @ ~/.julia/packages/GLFW/Zwulj/src/glfw3.jl:753 [inlined]
 [23] pollevents(screen::GLMakie.Screen{GLFW.Window}, frame_state::Makie.TickState)
    @ GLMakie ~/.julia/packages/GLMakie/1dQSN/src/screen.jl:546
 [24] on_demand_renderloop(screen::GLMakie.Screen{GLFW.Window})
    @ GLMakie ~/.julia/packages/GLMakie/1dQSN/src/screen.jl:1038
 [25] renderloop(screen::GLMakie.Screen{GLFW.Window})
    @ GLMakie ~/.julia/packages/GLMakie/1dQSN/src/screen.jl:1066
 [26] (::GLMakie.var"#start_renderloop!##0#start_renderloop!##1"{GLMakie.Screen{GLFW.Window}})()
    @ GLMakie ~/.julia/packages/GLMakie/1dQSN/src/screen.jl:927

I’m very confused by this. From what I understand, a periodic space lets the agents loop around from one edge to another. I’m pretty sure that this error message, however, is saying that the agent did not loop around, it instead exceeded the space boundaries.

What am I missing?
Thanks in advance!

Impossible to say without your stepping function, but my guess is you manually move the agent outside the space boundaries. You should use only the API functions for moving agents. How do you move agents in your model?

In model_step, my code updates each particle’s velocity (like a.vel = ...). In agent_step, it uses move_agent. Specifically:

function agent_step!(agent, model)
    move_agent!(agent, model, abmproperties(model)[:time_scale])
end

I ran into the same error once when some cells of the forcefield I used to update agent velocities were NaN. Agents ending up on these fields were technically still inside the space boundaries in my case but the error thrown was also position is outside space extent!.

I can’t say of course, but maybe something similar is happening in your model?

The way you do it is correct from an API point of view, this function respects periodic spaces. Unfortunatley I cannot help more without a Minimal Working Example.

I figured it out. In case it helps anybody else: my velocity code was dividing by zero, creating a velocity that was equal to [NaN, NaN].