Skip to content
This repository was archived by the owner on Jun 21, 2024. It is now read-only.

Commit b91e9c7

Browse files
tatakofDatseris
andauthored
(NOT WORKING) Add heatmap for Continuous Spaces (#127)
* create .devcontainer.json * (NOT WORKING) Add heatmap for Continuous Spaces * (NOT WORKING) Add heatmap for continous spaces * wip * make runnable flocking with spatial field * add if clause for continous heatmap; use correct extent * revert commit for lifting * only check the size of heatmap if gridspace * fix typo: there is no "property"... * add a plotting example that shows altering heatmap * visually distinctive colormap * delete wrongly commited .json file Please put these things in the .gitignore * bump patch version * add the news in the docstring and improve the docstring --------- Co-authored-by: Datseris <datseris.george@gmail.com>
1 parent f57be8f commit b91e9c7

File tree

4 files changed

+125
-28
lines changed

4 files changed

+125
-28
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "InteractiveDynamics"
22
uuid = "ec714cd0-5f51-11eb-0b6e-452e7367ff84"
33
repo = "https://github.com/JuliaDynamics/InteractiveDynamics.jl.git"
4-
version = "0.22.0"
4+
version = "0.22.1"
55

66
[deps]
77
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"

examples/agents/agents_flocking.jl

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,80 @@
11
using Agents
22
using GLMakie
33
using InteractiveDynamics
4+
using LinearAlgebra: norm
5+
using Random
46

5-
model, flocking_agent_step!, flocking_model_step! = Models.flocking()
6-
Bird = valtype(model.agents)
7+
# Create flocking model
8+
@agent Bird ContinuousAgent{2} begin
9+
speed::Float64
10+
cohere_factor::Float64
11+
separation::Float64
12+
separate_factor::Float64
13+
match_factor::Float64
14+
visual_distance::Float64
15+
end
16+
17+
function flocking(;
18+
n_birds = 100,
19+
speed = 1.0,
20+
cohere_factor = 0.25,
21+
separation = 4.0,
22+
separate_factor = 0.25,
23+
match_factor = 0.01,
24+
visual_distance = 5.0,
25+
extent = (100, 100),
26+
spacing = visual_distance / 1.5,
27+
spatial_field_size = (20, 20),
28+
)
29+
space2d = ContinuousSpace(extent; spacing)
30+
properties = (spatial_field = rand(spatial_field_size...),)
31+
model = ABM(Bird, space2d; properties, scheduler = Schedulers.Randomly())
32+
for _ in 1:n_birds
33+
vel = Tuple(rand(model.rng, 2) * 2 .- 1)
34+
add_agent!(
35+
model,
36+
vel,
37+
speed,
38+
cohere_factor,
39+
separation,
40+
separate_factor,
41+
match_factor,
42+
visual_distance,
43+
)
44+
end
45+
return model
46+
end
47+
48+
function flocking_agent_step!(bird, model)
49+
neighbor_ids = nearby_ids(bird, model, bird.visual_distance)
50+
N = 0
51+
match = separate = cohere = (0.0, 0.0)
52+
for id in neighbor_ids
53+
N += 1
54+
neighbor = model[id].pos
55+
heading = neighbor .- bird.pos
56+
cohere = cohere .+ heading
57+
if euclidean_distance(bird.pos, neighbor, model) < bird.separation
58+
separate = separate .- heading
59+
end
60+
match = match .+ model[id].vel
61+
end
62+
N = max(N, 1)
63+
cohere = cohere ./ N .* bird.cohere_factor
64+
separate = separate ./ N .* bird.separate_factor
65+
match = match ./ N .* bird.match_factor
66+
bird.vel = (bird.vel .+ cohere .+ separate .+ match) ./ 2
67+
bird.vel = bird.vel ./ norm(bird.vel)
68+
move_agent!(bird, model, bird.speed)
69+
end
70+
71+
function flocking_model_step!(model)
72+
Random.shuffle!(model.spatial_field)
73+
end
74+
75+
model = flocking()
776

77+
# %% plot
878
const bird_polygon = Polygon(Point2f[(-0.5, -0.5), (1, 0), (-0.5, 0.5)])
979
function bird_marker(b::Bird)
1080
φ = atan(b.vel[2], b.vel[1]) #+ π/2 + π
@@ -19,7 +89,18 @@ fig
1989
fig, ax, abmobs = abmplot(model;
2090
axis = (; title = "Flocking"),
2191
agent_step! = flocking_agent_step!,
22-
model_step! = flocking_model_step!,
23-
am = bird_marker,
92+
model_step! = flocking_model_step!,
93+
am = bird_marker,
2494
)
2595
fig
96+
97+
# %% Test plot with a heatmap in continuous space
98+
fig, ax, abmobs = abmplot(model;
99+
axis = (; title = "Flocking"),
100+
agent_step! = flocking_agent_step!,
101+
model_step! = flocking_model_step!,
102+
am = bird_marker,
103+
heatarray = :spatial_field,
104+
heatkwargs = (colormap = :grays,),
105+
)
106+
fig

src/agents/abmplot.jl

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Requires `Agents`. See also [`abmvideo`](@ref) and [`abmexploration`](@ref).
1818
### Agent related
1919
* `ac, as, am` : These three keywords decide the color, size, and marker, that
2020
each agent will be plotted as. They can each be either a constant or a *function*,
21-
which takes as an input a single agent and outputs the corresponding value. If the model
21+
which takes as an input a single agent and outputs the corresponding value. If the model
2222
uses a `GraphSpace`, `ac, as, am` functions instead take an *iterable of agents* in each
2323
position (i.e. node of the graph).
2424
@@ -43,27 +43,28 @@ Requires `Agents`. See also [`abmvideo`](@ref) and [`abmexploration`](@ref).
4343
* `scatterkwargs = ()` : Additional keyword arguments propagated to the `scatter!` call.
4444
4545
### Preplot related
46-
* `heatarray = nothing` : A keyword that plots a heatmap over the space.
46+
* `heatarray = nothing` : A keyword that plots a model property (that is a matrix)
47+
as a heatmap over the space.
4748
Its values can be standard data accessors given to functions like `run!`, i.e.
4849
either a symbol (directly obtain model property) or a function of the model.
49-
The returned data must be a matrix of the same size as the underlying space.
50+
If the space is `AbstractGridSpace` then matrix must be the same size as the underlying
51+
space. For `ContinuousSpace` any size works and will be plotted over the space extent.
5052
For example `heatarray = :temperature` is used in the Daisyworld example.
5153
But you could also define `f(model) = create_matrix_from_model...` and set
5254
`heatarray = f`. The heatmap will be updated automatically during model evolution
5355
in videos and interactive applications.
54-
55-
It is strongly recommended to use `abmplot` instead of the `abmplot!` method if
56-
you use `heatarray`, so that a colorbar can be placed naturally.
5756
* `heatkwargs = NamedTuple()` : Keywords given to `Makie.heatmap` function
5857
if `heatarray` is not nothing.
5958
* `add_colorbar = true` : Whether or not a Colorbar should be added to the right side of the
60-
heatmap if `heatarray` is not nothing.
59+
heatmap if `heatarray` is not nothing. It is strongly recommended to use `abmplot`
60+
instead of the `abmplot!` method if you use `heatarray`, so that a colorbar can be
61+
placed naturally.
6162
* `static_preplot!` : A function `f(ax, model)` that plots something after the heatmap
6263
but before the agents.
63-
* `osmkwargs = NamedTuple()` : keywords directly passed to `OSMMakie.osmplot!`
64+
* `osmkwargs = NamedTuple()` : keywords directly passed to `OSMMakie.osmplot!`
6465
if model space is `OpenStreetMapSpace`.
65-
* `graphplotkwargs = NamedTuple()` : keywords directly passed to
66-
[`GraphMakie.graphplot!`](https://graph.makie.org/stable/#GraphMakie.graphplot)
66+
* `graphplotkwargs = NamedTuple()` : keywords directly passed to
67+
[`GraphMakie.graphplot!`](https://graph.makie.org/stable/#GraphMakie.graphplot)
6768
if model space is `GraphSpace`.
6869
6970
The stand-alone function `abmplot` also takes two optional `NamedTuple`s named `figure` and
@@ -82,7 +83,7 @@ The stand-alone function `abmplot` also takes two optional `NamedTuple`s named `
8283
1. "run": starts/stops the continuous evolution of the model.
8384
1. "reset model": resets the model to its initial state from right after starting the
8485
interactive application.
85-
1. Two sliders control the animation speed: "spu" decides how many model steps should be
86+
1. Two sliders control the animation speed: "spu" decides how many model steps should be
8687
done before the plot is updated, and "sleep" the `sleep()` time between updates.
8788
* `enable_inspection = add_controls`: If `true`, enables agent inspection on mouse hover.
8889
* `spu = 1:50`: The values of the "spu" slider.
@@ -132,12 +133,13 @@ end
132133
abmplot(abmobs::ABMObservable; kwargs...) → fig, ax, abmobs
133134
abmplot!(ax::Axis/Axis3, abmobs::ABMObservable; kwargs...) → abmobs
134135
135-
Same functionality as `abmplot(model; kwargs...)`/`abmplot!(ax, model; kwargs...)`
136+
Same functionality as `abmplot(model; kwargs...)`/`abmplot!(ax, model; kwargs...)`
136137
but allows to link an already existing `ABMObservable` to the created plots.
137138
"""
138-
function abmplot(abmobs::ABMObservable;
139-
figure = NamedTuple(),
140-
axis = NamedTuple(),
139+
140+
function abmplot(abmobs::ABMObservable;
141+
figure = NamedTuple(),
142+
axis = NamedTuple(),
141143
kwargs...)
142144
fig = Figure(; figure...)
143145
ax = fig[1,1][1,1] = agents_space_dimensionality(abmobs.model[]) == 3 ?
@@ -218,6 +220,8 @@ const SUPPORTED_SPACES = Union{
218220
Agents.GraphSpace,
219221
}
220222

223+
224+
221225
function Makie.plot!(abmplot::_ABMPlot)
222226
model = abmplot.abmobs[].model[]
223227
if !(model.space isa SUPPORTED_SPACES)
@@ -232,8 +236,8 @@ function Makie.plot!(abmplot::_ABMPlot)
232236

233237
# Following attributes are all lifted from the recipe observables (specifically,
234238
# the model), see lifting.jl for source code.
235-
pos, color, marker, markersize, heatobs =
236-
lift_attributes(abmplot.abmobs[].model, abmplot.ac, abmplot.as, abmplot.am,
239+
pos, color, marker, markersize, heatobs =
240+
lift_attributes(abmplot.abmobs[].model, abmplot.ac, abmplot.as, abmplot.am,
237241
abmplot.offset, abmplot.heatarray, abmplot._used_poly)
238242

239243
# OpenStreetMapSpace preplot
@@ -247,7 +251,16 @@ function Makie.plot!(abmplot::_ABMPlot)
247251

248252
# Heatmap
249253
if !isnothing(heatobs[])
250-
hmap = heatmap!(abmplot, heatobs; colormap = JULIADYNAMICS_CMAP, abmplot.heatkwargs...)
254+
if !(model.space isa Agents.ContinuousSpace)
255+
hmap = heatmap!(abmplot, heatobs; colormap = JULIADYNAMICS_CMAP, abmplot.heatkwargs...)
256+
else # need special version for continuous space
257+
nbinx, nbiny = size(heatobs[])
258+
extx, exty = Agents.abmspace(model).extent
259+
coordx = range(0, extx; length = nbinx)
260+
coordy = range(0, exty; length = nbiny)
261+
hmap = heatmap!(abmplot, coordx, coordy, heatobs; colormap = JULIADYNAMICS_CMAP, abmplot.heatkwargs...)
262+
end
263+
251264
if abmplot.add_colorbar[]
252265
Colorbar(fig[1, 1][1, 2], hmap, width = 20)
253266
# TODO: Set colorbar to be "glued" to axis

src/agents/lifting.jl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ agents_space_dimensionality(::Agents.GraphSpace) = 2
5858
#####
5959

6060
abmplot_colors(model::Agents.ABM{<:SUPPORTED_SPACES}, ac, ids) = to_color(ac)
61-
abmplot_colors(model::Agents.ABM{<:SUPPORTED_SPACES}, ac::Function, ids) =
61+
abmplot_colors(model::Agents.ABM{<:SUPPORTED_SPACES}, ac::Function, ids) =
6262
to_color.([ac(model[i]) for i in ids])
6363
# in GraphSpace we iterate over a list of agents (not agent ids) at a graph node position
64-
abmplot_colors(model::Agents.ABM{<:Agents.GraphSpace}, ac::Function, ids) =
64+
abmplot_colors(model::Agents.ABM{<:Agents.GraphSpace}, ac::Function, ids) =
6565
to_color.(ac(model[id] for id in model.space.stored_ids[idx]) for idx in ids)
6666

6767
#####
@@ -107,7 +107,7 @@ abmplot_markersizes(model::Agents.ABM{<:SUPPORTED_SPACES}, as::Function, ids) =
107107
[as(model[i]) for i in ids]
108108

109109
abmplot_markersizes(model::Agents.ABM{<:Agents.GraphSpace}, as, ids) = as
110-
abmplot_markersizes(model::Agents.ABM{<:Agents.GraphSpace}, as::Function, ids) =
110+
abmplot_markersizes(model::Agents.ABM{<:Agents.GraphSpace}, as::Function, ids) =
111111
[as(model[id] for id in model.space.stored_ids[idx]) for idx in ids]
112112

113113

@@ -124,8 +124,11 @@ function abmplot_heatobs(model, heatarray)
124124
#
125125
# TODO: use surface!(heatobs) here?
126126
matrix = Agents.get_data(model, heatarray, identity)
127-
if !(matrix isa AbstractMatrix) || size(matrix) size(model.space)
128-
error("The heat array property must yield a matrix of same size as the grid!")
127+
# Check for correct size for discrete space
128+
if Agents.abmspace(model) isa Agents.AbstractGridSpace
129+
if !(matrix isa AbstractMatrix) || size(matrix) size(Agents.abmspace(model))
130+
error("The heat array property must yield a matrix of same size as the grid!")
131+
end
129132
end
130133
matrix
131134
else

0 commit comments

Comments
 (0)