# Combining ContinuousSpace with discrete grid property in InteractiveDynamics

Hi,

I’m writing simulations for a course on Agent-Based Modeling, and I’m struggling to combine discrete heatmaps with movement in a ContinuousSpace. In the following code, everything works fine until I add the final line `heatarray=foodmap` in the call to abmvideo:

``````module Test

using Agents, GLMakie, InteractiveDynamics
using Random:bitrand

@agent Turtle ContinuousAgent{2} begin
speed::Float64
end

function agent_step!( turtle, model)
cs,sn = (x->(cos(x),sin(x)))((2rand()-1)*pi/15)
turtle.vel = Tuple([cs sn;-sn cs]*collect(turtle.vel))
move_agent!( turtle, model, turtle.speed)
end

function demo()
dims = (30,30)
world = ContinuousSpace(dims, spacing=1.0)
model = ABM( Turtle, world; properties=Dict(:food => bitrand(dims)))
foodmap(modl) = modl.food

for _ in 1:50
vel = (x->(cos(x),sin(x)))(2π*rand())
end

abmvideo(
"Test.mp4", model, agent_step!;
framerate = 50, frames = 200,
ac=:blue, as=20, am=:circle,
heatarray=foodmap,
)
end

end
``````

I get a complaint about the use of a discrete `size()` in conjunction with `ContinuousSpace`, but can’t find any hints online about how to combine the two:

``````julia> Test.demo()
ERROR: MethodError: no method matching size(::Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)})
Closest candidates are:
size(::Union{LinearAlgebra.QR, LinearAlgebra.QRCompactWY, LinearAlgebra.QRPivoted}) at C:\Users\hswt136nia\AppData\Local\Programs\julia-1.8.4\share\julia\stdlib\v1.8\LinearAlgebra\src\qr.jl:581
size(::Union{LinearAlgebra.QR, LinearAlgebra.QRCompactWY, LinearAlgebra.QRPivoted}, ::Integer) at C:\Users\hswt136nia\AppData\Local\Programs\julia-1.8.4\share\julia\stdlib\v1.8\LinearAlgebra\src\qr.jl:580
size(::Union{LinearAlgebra.Cholesky, LinearAlgebra.CholeskyPivoted}) at C:\Users\hswt136nia\AppData\Local\Programs\julia-1.8.4\share\julia\stdlib\v1.8\LinearAlgebra\src\cholesky.jl:514
...
Stacktrace:
[1] abmplot_heatobs(model::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}, heatarray::Function)
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\InteractiveDynamics\EThtU\src\agents\lifting.jl:99
[2] (::InteractiveDynamics.var"#40#46")(arg1#327::Function, arg2#328::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG})
@ InteractiveDynamics .\none:0
[3] #map#13
@ C:\Users\hswt136nia\.julia\packages\Observables\PHGQ8\src\Observables.jl:564 [inlined]
[4] map
@ C:\Users\hswt136nia\.julia\packages\Observables\PHGQ8\src\Observables.jl:562 [inlined]
[5] lift_attributes(model::Observables.Observable{Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}}, ac::Observables.Observable{Any}, as::Observables.Observable{Any}, am::Observables.Observable{Any}, offset::Observables.Observable{Any}, heatarray::Observables.Observable{Any}, used_poly::Observables.Observable{Any})
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\InteractiveDynamics\EThtU\src\agents\lifting.jl:11
[6] plot!(abmplot::MakieCore.Combined{InteractiveDynamics._abmplot, Tuple{Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}}})
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\InteractiveDynamics\EThtU\src\agents\abmplot.jl:203
[7] plot!(scene::Makie.Scene, P::Type{MakieCore.Combined{InteractiveDynamics._abmplot, Tuple{Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}}}}, attributes::MakieCore.Attributes, input::Tuple{Observables.Observable{Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}}}, args::Observables.Observable{Tuple{Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}}})
@ Makie C:\Users\hswt136nia\.julia\packages\Makie\Ppzqh\src\interfaces.jl:417
[8] plot!(scene::Makie.Scene, P::Type{MakieCore.Combined{InteractiveDynamics._abmplot}}, attributes::MakieCore.Attributes, args::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}; kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ Makie C:\Users\hswt136nia\.julia\packages\Makie\Ppzqh\src\interfaces.jl:335
[9] plot!
@ C:\Users\hswt136nia\.julia\packages\Makie\Ppzqh\src\interfaces.jl:302 [inlined]
[10] plot!(la::Makie.Axis, P::Type{MakieCore.Combined{InteractiveDynamics._abmplot}}, attributes::MakieCore.Attributes, args::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}; kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ Makie C:\Users\hswt136nia\.julia\packages\Makie\Ppzqh\src\makielayout\blocks\axis.jl:773
[11] plot!
@ C:\Users\hswt136nia\.julia\packages\Makie\Ppzqh\src\makielayout\blocks\axis.jl:760 [inlined]
[12] #plot!#1292
@ C:\Users\hswt136nia\.julia\packages\Makie\Ppzqh\src\makielayout\blocks\axis.jl:790 [inlined]
[13] _abmplot!(::Makie.Axis, ::Vararg{Any}; attributes::Base.Pairs{Symbol, Any, NTuple{8, Symbol}, NamedTuple{(:ax, :abmobs, :add_controls, :_add_interaction, :ac, :as, :am, :heatarray), Tuple{Makie.Axis, InteractiveDynamics.ABMObservable{Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}, typeof(Main.Test.agent_step!), typeof(Agents.dummystep), Nothing, Nothing, Nothing, Nothing, Bool}, Bool, Bool, Symbol, Int64, Symbol, Main.Test.var"#foodmap#6"}}})
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\MakieCore\rLlRw\src\recipes.jl:38
[14] abmplot!(ax::Makie.Axis, model::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}; agent_step!::Function, model_step!::Function, adata::Nothing, mdata::Nothing, when::Bool, _add_interaction::Bool, add_controls::Bool, enable_inspection::Bool, kwargs::Base.Pairs{Symbol, Any, NTuple{4, Symbol}, NamedTuple{(:ac, :as, :am, :heatarray), Tuple{Symbol, Int64, Symbol, Main.Test.var"#foodmap#6"}}})
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\InteractiveDynamics\EThtU\src\agents\abmplot.jl:126
[15] abmplot(model::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}; figure::NamedTuple{(:resolution,), Tuple{Tuple{Int64, Int64}}}, axis::NamedTuple{(:title, :titlealign), Tuple{Observables.Observable{String}, Symbol}}, kwargs::Base.Pairs{Symbol, Any, NTuple{7, Symbol}, NamedTuple{(:add_controls, :agent_step!, :model_step!, :ac, :as, :am, :heatarray), Tuple{Bool, typeof(Main.Test.agent_step!), typeof(Agents.dummystep), Symbol, Int64, Symbol, Main.Test.var"#foodmap#6"}}})
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\InteractiveDynamics\EThtU\src\agents\abmplot.jl:104
[16] abmvideo(file::String, model::Agents.AgentBasedModel{Agents.ContinuousSpace{2, true, Float64, typeof(Agents.no_vel_update)}, Main.Test.Turtle, typeof(Agents.Schedulers.fastest), Dict{Symbol, BitMatrix}, Random.TaskLocalRNG}, agent_step!::typeof(Main.Test.agent_step!), model_step!::typeof(Agents.dummystep); spf::Int64, framerate::Int64, frames::Int64, title::String, showstep::Bool, figure::NamedTuple{(:resolution,), Tuple{Tuple{Int64,
Int64}}}, axis::NamedTuple{(), Tuple{}}, recordkwargs::NamedTuple{(:compression,), Tuple{Int64}}, kwargs::Base.Pairs{Symbol, Any, NTuple{4, Symbol}, NamedTuple{(:ac, :as, :am, :heatarray), Tuple{Symbol, Int64, Symbol, Main.Test.var"#foodmap#6"}}})
@ InteractiveDynamics C:\Users\hswt136nia\.julia\packages\InteractiveDynamics\EThtU\src\agents\convenience.jl:144
[17] demo()
@ Main.Test c:\Users\hswt136nia\OneDrive\Documents\Rock Dene Cottage\Courses\LEAP\Projects\Anatta\src\Development\DSM\Test.jl:27
[18] top-level scope
@ REPL[1]:1
``````

Can anyone help?
Thanks!

Hm, I think this can’t be done automatically with the current recipe. You’d have to make a Pull Request that allows this feature. I’d suggest opening a feature request at Agents.jl or InteractiveDynamics.jl.

Oh. Thanks, that’s useful information - I had assumed that I just hadn’t understood the documentation properly. I was planning on the students creating agents that operate on an environment subject to reaction-diffusion dynamics. I’ll have to consider whether I have time to implement it myself, so any workarounds that anyone can offer would be very welcome.

The workaround is to have 2 plots side by side, one the continuous space aBM animation and the other the heatmap of hte thing you are interested. Just like it is done here: Sugarscape · Agents.jl Example Zoo

The best way forwards I think is to make a Pull Request at InteractiveDynamics.jl that allows this heatmap out of the box. Makie.jl supports a heatmap with arbitrary coordinates. So first one makes the coordinates, `nbinx, nbiny = size(property)` . Then `coordx = range(0, 1; length = nbinx)` and same for `y` . Then, `heatmap!(ax, coordx, coordy, property; ...)` . Shouldn’t be too hard.

Ooh - my Very First Pull Request.

Thanks - I’ll see how I get on.

Nope. Thanks very much for the help, but it would take too much time right now - I have to prepare the course. I’ll make a note of it and come back to it later in the year.

Best wishes,
Niall.

Hah! I think I’ve found a workaround: Simply insert the following definition:
Base.size(cs::Agents.ContinuousSpace) = cs.dims

At present, everything seems to work just fine.