Skip to content

Commit 1aa1f52

Browse files
authored
Dedicated Basin of Attraction types (#189)
* Implemented BasinsOfAttraction types + modified load order Implemented BasinsOfAttraction types + modified load order - Implemented BasinsOfAttraction type and ArrayBasinsOfAttraction, SampledBasinsOfAttraction subtypes and "pretty printing" for them - Added several constructors for the new types -Modified load order of type Grid and BasinsOfAttraction: In future changes, the BasinInfo type in will require ArrayBasinOfAttraction but this is loaded after BasinInfo. Also ArrayBasinsOfAttraction requires Grid to be defined so we must load the grids.jl file before both. Also basin_utilities.jl requires AttractorMapper so we must load this after attractor_mapping * Added BasinsOfAttraction wrapper functions - Created ArrayBasinsOfAttraction wrapper functions for exported functions that take array "basins" as inputs. Replaced return types with ArrayBasinsOfAttraction if they also return "basins". - Cannot find a way to incorporate ArrayBasinsOfAttraction type return of convergence_and_basins_of_attraction - will write note in docstring - Created wrappers for the plotting functions - Fixed type error in Basin overlap * Added SampledBasinsOfAttraction constructor to attractor_mapping.jl - Changed basins_fractions in basins_utilities.jl to apply to all BoA not just ArrayBoA * Modified BasinsInfo type combining basins, attractors and grid. - Updated all references to those fields with new fields in both attractor_mapping_recurrences.jl and finite_state_machine.jl * Modified docstrings, added new docstrings for the BasinOfAttraction types - Minor refactoring of exporting and locations of some basin functions - Changed some of the bsn_nfo.grid_nfo references in the docs examples * Adding map_to_basin feature for basin interpolation * Reorganised includes - New file `basins/basin_types.jl` containing the structure definitions and only basic, non-constuctor functions - All constructors moved to `basin_utilities.jl` as this allows the problematic dependency on `AttractorMapper` and `grids.jl` structures to be solved - Load order in `Attractors.jl` now returned to original - `basins.jl` returned to original - Instantiation of abstract `Grid` type done now in `attractor_mapping.jl` just before `basin_types.jl` is imported which allows `attractor_mapping_recurrences.jl` to be included without conflict * Fixed test issue caused by BasinsOfAttraction attractor type being too specific * Most of the doc errors caused by last test issue, fixed some minor referencing problems * Implemented requested changes * Updated changelog and bumped project version * Implementing further changes, fixed NearestNeighbors `always_false` error
1 parent 8d03203 commit 1aa1f52

19 files changed

+421
-54
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# v1.31
2+
3+
- New `BasinsOfAttraction` type and subtypes, containing "basins" as an array or vector, attractors, and a "spatial domain" i.e. a `Grid` or sampled points.
4+
- All functions using "basins" can now also be called with these new types.
5+
- Type is decomposible such that `basins, attractors = boa` which ensures backwards compatibility.
6+
- `map_to_basin` function allows interpolating points to basins.
7+
18
# v1.30
29

310
- `animate_attractors_continuation` has been enhanced to allow animating a

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "Attractors"
22
uuid = "f3fd9213-ca85-4dba-9dfd-7fc91308fec7"
33
authors = ["George Datseris <[email protected]>", "Kalel Rossi", "Alexandre Wagemakers"]
44
repo = "https://github.com/JuliaDynamics/Attractors.jl.git"
5-
version = "1.30.2"
5+
version = "1.31"
66

77
[deps]
88
BlackBoxOptim = "a134a8b2-14d6-55f6-9291-3336d3ab0209"

docs/src/api.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,26 @@ extract_features
6464

6565

6666
## Basins of attraction
67+
The basins of attraction are often represented as an array or vector. We also provide a convenient extendable structure
68+
that contains the basins themselves, attractors, and the domains on which the basins are defined. All standard basin-related
69+
functions are compatible with this alternate representation.
70+
71+
```@docs
72+
BasinsOfAttraction
73+
ArrayBasinsOfAttraction
74+
SampledBasinsOfAttraction
75+
```
76+
77+
The `map_to_basin` function provides simple interpolation of a point in state space to determine which basin of attraction it
78+
is likely to belong to:
79+
80+
```@docs
81+
map_to_basin
82+
```
6783

6884
Calculating basins of attraction, or their state space fractions, can be done with the functions:
6985
- [`basins_fractions`](@ref)
70-
- [`basins_of_attraction`](@ref).
86+
- [`basins_of_attraction`](@ref)
7187

7288
```@docs
7389
basins_fractions

docs/src/recurrences_animation.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function animate_attractors_via_recurrences(
5151
filename = "recurrence_algorithm.mp4",
5252
)
5353

54-
grid_nfo = mapper.bsn_nfo.grid_nfo
54+
grid_nfo = mapper.bsn_nfo.BoA.grid
5555

5656
fig = Figure()
5757
ax = Axis(fig[1,1])
@@ -170,7 +170,7 @@ function animate_attractors_via_recurrences(
170170
u = current_state(ds)
171171

172172
# update FSM
173-
n = Attractors.basin_cell_index(u, bsn_nfo.grid_nfo)
173+
n = Attractors.basin_cell_index(u, bsn_nfo.BoA.grid)
174174
cell_label = Attractors.finite_state_machine!(bsn_nfo, n, u; mapper.kwargs...)
175175

176176
state = bsn_nfo.state
@@ -180,7 +180,7 @@ function animate_attractors_via_recurrences(
180180

181181
# color-code initial condition if we converged to attractor
182182
# or to basin (even or odd cell label)
183-
u0n = Attractors.basin_cell_index(u0, bsn_nfo.grid_nfo)
183+
u0n = Attractors.basin_cell_index(u0, bsn_nfo.BoA.grid)
184184

185185
basidx = (cell_label - 1)
186186
color_obs[u0n][] = COLORS[3+basidx]

docs/src/tutorial.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ plot_attractors(attractors)
209209
# each attractors attracts. The search is probabilistic, so "all" attractors means those
210210
# that at least one initial condition converged to.
211211

212-
# We can provide explicitly initial conditions to [`basins_fraction`](@ref),
212+
# We can provide explicitly initial conditions to [`basins_fractions`](@ref),
213213
# however it is typically simpler to provide it with with a state space sampler instead:
214214
# a function that generates random initial conditions in the region of the
215215
# state space that we are interested in. Here this region coincides with `grid`,

ext/plotting.jl

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ function Attractors.heatmap_basins_attractors(grid, basins::AbstractArray, attra
7979
return fig
8080
end
8181

82+
function Attractors.heatmap_basins_attractors(BoA::ArrayBasinsOfAttraction; kwargs...)
83+
return Attractors.heatmap_basins_attractors(BoA.grid, BoA.basins, BoA.attractors; kwargs...)
84+
end
85+
8286
function Attractors.heatmap_basins_attractors!(ax, grid, basins, attractors;
8387
ukeys = unique(basins), # internal argument just for other keywords
8488
colors = colors_from_keys(ukeys),
@@ -110,6 +114,25 @@ function Attractors.heatmap_basins_attractors!(ax, grid, basins, attractors;
110114
return ax
111115
end
112116

117+
function Attractors.heatmap_basins_attractors!(ax, BoA::ArrayBasinsOfAttraction;
118+
ukeys = unique(BoA.basins), # internal argument just for other keywords
119+
colors = colors_from_keys(ukeys),
120+
markers = markers_from_keys(ukeys),
121+
labels = Dict(ukeys .=> ukeys),
122+
add_legend = length(ukeys) < 7,
123+
access = SVector(1, 2),
124+
sckwargs = (strokewidth = 1.5, strokecolor = :white,)
125+
)
126+
return Attractors.heatmap_basins_attractors!(ax, BoA.grid, BoA.basins, BoA.attractors;
127+
ukeys = ukeys, # internal argument just for other keywords
128+
colors = colors,
129+
markers = markers,
130+
labels = labels,
131+
add_legend = add_legend,
132+
access = access,
133+
sckwargs = sckwargs
134+
)
135+
end
113136
##########################################################################################
114137
# Shaded basins
115138
##########################################################################################
@@ -126,6 +149,15 @@ function Attractors.shaded_basins_heatmap(grid, basins::AbstractArray, attractor
126149
return fig
127150
end
128151

152+
function Attractors.shaded_basins_heatmap(BoA::ArrayBasinsOfAttraction, iterations;
153+
show_attractors = true,
154+
maxit = maximum(iterations),
155+
kwargs...
156+
)
157+
return Attractors.shaded_basins_heatmap(BoA.grid, BoA.basins, BoA.attractors, iterations;
158+
show_attractors = show_attractors, maxit = maxit, kwargs...)
159+
end
160+
129161
function Attractors.shaded_basins_heatmap!(ax, grid, basins, iterations, attractors;
130162
ukeys = unique(basins),
131163
show_attractors = true,
@@ -176,7 +208,13 @@ function Attractors.shaded_basins_heatmap!(ax, grid, basins, iterations, attract
176208
# Add legend using colors only
177209
add_legend && axislegend(ax)
178210
end
179-
return ax
211+
return ax
212+
end
213+
214+
function Attractors.shaded_basins_heatmap!(ax, BoA::ArrayBasinsOfAttraction, iterations;
215+
ukeys = unique(basins), show_attractors = true, maxit = maximum(iterations))
216+
return Attractors.shaded_basins_heatmap!(ax, BoA.grid, BoA.basins, iterations, BoA.attractors;
217+
ukeys = ukeys, show_attractors = show_attractors, maxit = maxit)
180218
end
181219

182220
function custom_colormap_shaded(ukeys)

src/basins/basins.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
include("basins_utilities.jl")
22
include("fractality_of_basins.jl")
3-
include("wada_test.jl")
3+
include("wada_test.jl")

src/basins/basins_types.jl

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using Neighborhood
2+
export BasinsOfAttraction,
3+
ArrayBasinsOfAttraction,
4+
SampledBasinsOfAttraction,
5+
extract_basins,
6+
extract_basins,
7+
extract_domain
8+
9+
#########################################################################################
10+
# Basins of Attraction structure definition
11+
#########################################################################################
12+
"""
13+
BasinsOfAttraction
14+
15+
A subtype of `BasinsOfAttraction` is a convenient structure that stores a representation
16+
of the `basins` of attraction, their associated `attractors`, and a representation of the domain
17+
over which the basin is defined. For example, this domain could be a `Grid` subtype matching
18+
the size of the basins as an array or a set of points sampled from the state space. These fields
19+
can be accessed using the [`extract_basins`](@ref), [`extract_attractors`](@ref),
20+
and [`extract_domain`](@ref) functions respectively.
21+
22+
Currently available subtypes:
23+
24+
* [`ArrayBasinsOfAttraction`](@ref)
25+
* [`SampledBasinsOfAttraction`](@ref)
26+
27+
All `BasinsOfAttraction` subtypes can be used with [`basins_fractions`](@ref) provided
28+
that the basins are represented as subtypes of `AbstractArray`. Additionally, all
29+
`BasinsOfAttraction` subtypes are iterable in the sense: `basins, attractors = BoA`, this
30+
was done to ensure backwards compatibility for functions whose original return format was
31+
`basins, attractors` but has since been replaced with a `BasinsOfAttraction` type.
32+
33+
The [`map_to_basin`](@ref) function provides simple interpolation of a point in state space
34+
to determine which basin of attraction it is likely to belong to.
35+
"""
36+
abstract type BasinsOfAttraction{ID} end
37+
38+
39+
"""
40+
ArrayBasinsOfAttraction(basins, attractors, grid)
41+
42+
A subtype of [`BasinsOfAttraction`](@ref) whose `basins` of attraction are represented by an
43+
`array::AbstractArray`, that has `D` number of dimensions. The `attractors` take the form of a
44+
dictionary mapping attractor labels to `StateSpaceSet`'s with the points of each set being of
45+
length `D`. The `grid` represents the spatial domain, and can be anything given to [`AttractorsViaRecurrences`](@ref)
46+
as a grid, i.e., a tuple of ranges or a `Grid` type.
47+
48+
"""
49+
struct ArrayBasinsOfAttraction{ID, D, B <: AbstractArray{ID,D}, T, V, G <: Grid, AK, S <: StateSpaceSet{D, T, V}} <: BasinsOfAttraction{ID}
50+
basins::B
51+
attractors::Dict{AK, S}
52+
grid::G
53+
54+
function ArrayBasinsOfAttraction(basins::B, attractors::Dict{AK, S}, grid::G) where {ID, D, B <: AbstractArray{ID,D}, T, V, G <: Grid, AK, S <: StateSpaceSet{D, T, V}}
55+
# Dimensionality checks
56+
length(grid.grid) != ndims(basins) && error("The basins and the grid must have the same number of dimensions")
57+
# Attractor state space sets have the same type so same dimensions, can compare grid with any of them
58+
if !isempty(attractors) #
59+
length(grid.grid) != length(valtype(collect(values(attractors))[1])) && error("The attractor points and the grid must have the same number of dimensions")
60+
end
61+
new{ID,D,B,T,V,G,AK,S}(basins, attractors, grid)
62+
end
63+
end
64+
# The definition of other constructors can be found in `basins/basins_utilities.jl`.
65+
66+
"""
67+
SampledBasinsOfAttraction(basins, attractors, sampled_points)
68+
69+
A subtype of [`BasinsOfAttraction`](@ref) whose `basins` of attraction are represented by a `vector::AbstractVector`.
70+
The `attractors` take the form of a dictionary mapping attractor labels to `StateSpaceSet`'s with
71+
the points of each set being of equal length. The spatial domain of this basin type is `sampled_points` which
72+
can be a `StateSpaceSet` with the same dimensionality, element type, and vector type as those
73+
used to represent the attractors or alternatively a vector of points with the aforementioned requirements.
74+
75+
Additional keyword arguments may be specified for use in the construction of a search structure which [`map_to_basin`](@ref)
76+
uses to interpolate state space points to their nearest basin. These arguments are:
77+
78+
* `tree`: search tree constructor (e.g. `KDTree`, `BallTree`)
79+
* `metric`: distance metric (e.g. `Euclidean()`, `Chebyshev()`)
80+
* `searchstructure_kwargs...`: additional keyword arguments passed to `searchstructure`
81+
"""
82+
struct SampledBasinsOfAttraction{ID, D, T, V <: AbstractVector, AK, S <: StateSpaceSet{D, T, V}, ss <: Neighborhood.SearchType} <: BasinsOfAttraction{ID}
83+
points_ids::Vector{ID}
84+
attractors::Dict{AK, S}
85+
sampled_points::S
86+
search_struct::ss
87+
88+
function SampledBasinsOfAttraction(basins::Vector{ID}, attractors::Dict{AK, S}, sampled_points::S; tree = KDTree, metric = Euclidean(), ss_kwargs...) where
89+
{ID, D, T, V <: AbstractVector, AK, S <: StateSpaceSet{D, T, V}}
90+
# Dimensionality checks
91+
length(basins) != sampled_points && error("The basins and the sampled points must have equal length")
92+
search_struct = searchstructure(tree, BoA.sampled_points, metric, ss_kwargs...)
93+
new{ID,D,T,V,AK,S,typeof(search_struct)}(basins, attractors, sampled_points, search_struct)
94+
end
95+
end
96+
# The definition of other constructors can be found in `basins/basins_utilities.jl`.
97+
98+
#########################################################################################
99+
# Basins of Attraction Convenience functions
100+
#########################################################################################
101+
Base.iterate(BoA::BasinsOfAttraction, state=1) = state == 1 ? (extract_basins(BoA), 2) : state == 2 ? (extract_attractors(BoA), 3) : nothing
102+
103+
"""
104+
extract_basins(BoA::BasinsOfAttraction) → basins
105+
106+
Returns the basins component of a `BasinsOfAttraction` object.
107+
"""
108+
extract_basins(BoA::ArrayBasinsOfAttraction) = BoA.basins
109+
extract_basins(BoA::SampledBasinsOfAttraction) = BoA.points_ids
110+
111+
"""
112+
extract_attractors(BoA::BasinsOfAttraction) → attractors
113+
114+
Returns the attractors component of a `BasinsOfAttraction` object. Which is a dictionary mapping
115+
attractor labels to attractors represented as `StateSpaceSet`'s.
116+
"""
117+
extract_attractors(BoA::ArrayBasinsOfAttraction) = BoA.attractors
118+
extract_attractors(BoA::SampledBasinsOfAttraction) = BoA.attractors
119+
120+
"""
121+
extract_domain(BoA::BasinsOfAttraction) → domain
122+
123+
Returns the domain component of a `BasinsOfAttraction` object.
124+
"""
125+
extract_domain(BoA::SampledBasinsOfAttraction) = BoA.sampled_points
126+
extract_domain(BoA::ArrayBasinsOfAttraction) = BoA.grid
127+
128+
#####################################################################################
129+
# Pretty printing
130+
#####################################################################################
131+
function Base.show(io::IO, BoA::ArrayBasinsOfAttraction)
132+
ps = 14
133+
println(io, "$(nameof(typeof(BoA)))")
134+
println(io, rpad(" ID type: ", ps), typeof(BoA).parameters[1])
135+
println(io, rpad(" basin size: ", ps), size(BoA.basins))
136+
println(io, rpad(" grid: ", ps), extract_domain(BoA))
137+
attstrings = split(sprint(show, MIME"text/plain"(), extract_attractors(BoA)), '\n')
138+
println(io, rpad(" attractors: ", ps), attstrings[1])
139+
for j in 2:size(attstrings,1)
140+
println(io, rpad(" ", ps), attstrings[j])
141+
end
142+
return
143+
end
144+
145+
# Add first few vectors to printing followed by ...
146+
function Base.show(io::IO, BoA::SampledBasinsOfAttraction)
147+
ps = 14
148+
println(io, "$(nameof(typeof(BoA)))")
149+
println(io, rpad(" ID type: ", ps), typeof(BoA).parameters[1])
150+
println(io, rpad(" basin length: ", ps), length(BoA))
151+
attstrings = split(sprint(show, MIME"text/plain"(), extract_attractors(BoA)), '\n')
152+
println(io, rpad(" attractors: ", ps), attstrings[1])
153+
for j in 2:size(attstrings,1)
154+
println(io, rpad(" ", ps), attstrings[j])
155+
end
156+
return
157+
end

0 commit comments

Comments
 (0)