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

Commit

Permalink
(NOT WORKING) Add heatmap for Continuous Spaces (#127)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
tatakof and Datseris committed Feb 26, 2023
1 parent f57be8f commit b91e9c7
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "InteractiveDynamics"
uuid = "ec714cd0-5f51-11eb-0b6e-452e7367ff84"
repo = "https://github.com/JuliaDynamics/InteractiveDynamics.jl.git"
version = "0.22.0"
version = "0.22.1"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Expand Down
89 changes: 85 additions & 4 deletions examples/agents/agents_flocking.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,80 @@
using Agents
using GLMakie
using InteractiveDynamics
using LinearAlgebra: norm
using Random

model, flocking_agent_step!, flocking_model_step! = Models.flocking()
Bird = valtype(model.agents)
# Create flocking model
@agent Bird ContinuousAgent{2} begin
speed::Float64
cohere_factor::Float64
separation::Float64
separate_factor::Float64
match_factor::Float64
visual_distance::Float64
end

function flocking(;
n_birds = 100,
speed = 1.0,
cohere_factor = 0.25,
separation = 4.0,
separate_factor = 0.25,
match_factor = 0.01,
visual_distance = 5.0,
extent = (100, 100),
spacing = visual_distance / 1.5,
spatial_field_size = (20, 20),
)
space2d = ContinuousSpace(extent; spacing)
properties = (spatial_field = rand(spatial_field_size...),)
model = ABM(Bird, space2d; properties, scheduler = Schedulers.Randomly())
for _ in 1:n_birds
vel = Tuple(rand(model.rng, 2) * 2 .- 1)
add_agent!(
model,
vel,
speed,
cohere_factor,
separation,
separate_factor,
match_factor,
visual_distance,
)
end
return model
end

function flocking_agent_step!(bird, model)
neighbor_ids = nearby_ids(bird, model, bird.visual_distance)
N = 0
match = separate = cohere = (0.0, 0.0)
for id in neighbor_ids
N += 1
neighbor = model[id].pos
heading = neighbor .- bird.pos
cohere = cohere .+ heading
if euclidean_distance(bird.pos, neighbor, model) < bird.separation
separate = separate .- heading
end
match = match .+ model[id].vel
end
N = max(N, 1)
cohere = cohere ./ N .* bird.cohere_factor
separate = separate ./ N .* bird.separate_factor
match = match ./ N .* bird.match_factor
bird.vel = (bird.vel .+ cohere .+ separate .+ match) ./ 2
bird.vel = bird.vel ./ norm(bird.vel)
move_agent!(bird, model, bird.speed)
end

function flocking_model_step!(model)
Random.shuffle!(model.spatial_field)
end

model = flocking()

# %% plot
const bird_polygon = Polygon(Point2f[(-0.5, -0.5), (1, 0), (-0.5, 0.5)])
function bird_marker(b::Bird)
φ = atan(b.vel[2], b.vel[1]) #+ π/2 + π
Expand All @@ -19,7 +89,18 @@ fig
fig, ax, abmobs = abmplot(model;
axis = (; title = "Flocking"),
agent_step! = flocking_agent_step!,
model_step! = flocking_model_step!,
am = bird_marker,
model_step! = flocking_model_step!,
am = bird_marker,
)
fig

# %% Test plot with a heatmap in continuous space
fig, ax, abmobs = abmplot(model;
axis = (; title = "Flocking"),
agent_step! = flocking_agent_step!,
model_step! = flocking_model_step!,
am = bird_marker,
heatarray = :spatial_field,
heatkwargs = (colormap = :grays,),
)
fig
49 changes: 31 additions & 18 deletions src/agents/abmplot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Requires `Agents`. See also [`abmvideo`](@ref) and [`abmexploration`](@ref).
### Agent related
* `ac, as, am` : These three keywords decide the color, size, and marker, that
each agent will be plotted as. They can each be either a constant or a *function*,
which takes as an input a single agent and outputs the corresponding value. If the model
which takes as an input a single agent and outputs the corresponding value. If the model
uses a `GraphSpace`, `ac, as, am` functions instead take an *iterable of agents* in each
position (i.e. node of the graph).
Expand All @@ -43,27 +43,28 @@ Requires `Agents`. See also [`abmvideo`](@ref) and [`abmexploration`](@ref).
* `scatterkwargs = ()` : Additional keyword arguments propagated to the `scatter!` call.
### Preplot related
* `heatarray = nothing` : A keyword that plots a heatmap over the space.
* `heatarray = nothing` : A keyword that plots a model property (that is a matrix)
as a heatmap over the space.
Its values can be standard data accessors given to functions like `run!`, i.e.
either a symbol (directly obtain model property) or a function of the model.
The returned data must be a matrix of the same size as the underlying space.
If the space is `AbstractGridSpace` then matrix must be the same size as the underlying
space. For `ContinuousSpace` any size works and will be plotted over the space extent.
For example `heatarray = :temperature` is used in the Daisyworld example.
But you could also define `f(model) = create_matrix_from_model...` and set
`heatarray = f`. The heatmap will be updated automatically during model evolution
in videos and interactive applications.
It is strongly recommended to use `abmplot` instead of the `abmplot!` method if
you use `heatarray`, so that a colorbar can be placed naturally.
* `heatkwargs = NamedTuple()` : Keywords given to `Makie.heatmap` function
if `heatarray` is not nothing.
* `add_colorbar = true` : Whether or not a Colorbar should be added to the right side of the
heatmap if `heatarray` is not nothing.
heatmap if `heatarray` is not nothing. It is strongly recommended to use `abmplot`
instead of the `abmplot!` method if you use `heatarray`, so that a colorbar can be
placed naturally.
* `static_preplot!` : A function `f(ax, model)` that plots something after the heatmap
but before the agents.
* `osmkwargs = NamedTuple()` : keywords directly passed to `OSMMakie.osmplot!`
* `osmkwargs = NamedTuple()` : keywords directly passed to `OSMMakie.osmplot!`
if model space is `OpenStreetMapSpace`.
* `graphplotkwargs = NamedTuple()` : keywords directly passed to
[`GraphMakie.graphplot!`](https://graph.makie.org/stable/#GraphMakie.graphplot)
* `graphplotkwargs = NamedTuple()` : keywords directly passed to
[`GraphMakie.graphplot!`](https://graph.makie.org/stable/#GraphMakie.graphplot)
if model space is `GraphSpace`.
The stand-alone function `abmplot` also takes two optional `NamedTuple`s named `figure` and
Expand All @@ -82,7 +83,7 @@ The stand-alone function `abmplot` also takes two optional `NamedTuple`s named `
1. "run": starts/stops the continuous evolution of the model.
1. "reset model": resets the model to its initial state from right after starting the
interactive application.
1. Two sliders control the animation speed: "spu" decides how many model steps should be
1. Two sliders control the animation speed: "spu" decides how many model steps should be
done before the plot is updated, and "sleep" the `sleep()` time between updates.
* `enable_inspection = add_controls`: If `true`, enables agent inspection on mouse hover.
* `spu = 1:50`: The values of the "spu" slider.
Expand Down Expand Up @@ -132,12 +133,13 @@ end
abmplot(abmobs::ABMObservable; kwargs...) → fig, ax, abmobs
abmplot!(ax::Axis/Axis3, abmobs::ABMObservable; kwargs...) → abmobs
Same functionality as `abmplot(model; kwargs...)`/`abmplot!(ax, model; kwargs...)`
Same functionality as `abmplot(model; kwargs...)`/`abmplot!(ax, model; kwargs...)`
but allows to link an already existing `ABMObservable` to the created plots.
"""
function abmplot(abmobs::ABMObservable;
figure = NamedTuple(),
axis = NamedTuple(),

function abmplot(abmobs::ABMObservable;
figure = NamedTuple(),
axis = NamedTuple(),
kwargs...)
fig = Figure(; figure...)
ax = fig[1,1][1,1] = agents_space_dimensionality(abmobs.model[]) == 3 ?
Expand Down Expand Up @@ -218,6 +220,8 @@ const SUPPORTED_SPACES = Union{
Agents.GraphSpace,
}



function Makie.plot!(abmplot::_ABMPlot)
model = abmplot.abmobs[].model[]
if !(model.space isa SUPPORTED_SPACES)
Expand All @@ -232,8 +236,8 @@ function Makie.plot!(abmplot::_ABMPlot)

# Following attributes are all lifted from the recipe observables (specifically,
# the model), see lifting.jl for source code.
pos, color, marker, markersize, heatobs =
lift_attributes(abmplot.abmobs[].model, abmplot.ac, abmplot.as, abmplot.am,
pos, color, marker, markersize, heatobs =
lift_attributes(abmplot.abmobs[].model, abmplot.ac, abmplot.as, abmplot.am,
abmplot.offset, abmplot.heatarray, abmplot._used_poly)

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

# Heatmap
if !isnothing(heatobs[])
hmap = heatmap!(abmplot, heatobs; colormap = JULIADYNAMICS_CMAP, abmplot.heatkwargs...)
if !(model.space isa Agents.ContinuousSpace)
hmap = heatmap!(abmplot, heatobs; colormap = JULIADYNAMICS_CMAP, abmplot.heatkwargs...)
else # need special version for continuous space
nbinx, nbiny = size(heatobs[])
extx, exty = Agents.abmspace(model).extent
coordx = range(0, extx; length = nbinx)
coordy = range(0, exty; length = nbiny)
hmap = heatmap!(abmplot, coordx, coordy, heatobs; colormap = JULIADYNAMICS_CMAP, abmplot.heatkwargs...)
end

if abmplot.add_colorbar[]
Colorbar(fig[1, 1][1, 2], hmap, width = 20)
# TODO: Set colorbar to be "glued" to axis
Expand Down
13 changes: 8 additions & 5 deletions src/agents/lifting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ agents_space_dimensionality(::Agents.GraphSpace) = 2
#####

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

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

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


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

0 comments on commit b91e9c7

Please # to comment.