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())
		add_agent!( model, vel, 1.0)
	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! :smile:

1 Like

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

1 Like

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.

:open_mouth: Ooh - my Very First Pull Request.

:grinning: 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. :rainbow: