Attractors, Basins, Tipping Points
Finding Attractors
DynamicalSystems.jl defines a generic interface for finding attractors of dynamical systems. One first decides the instance of GeneralizedDynamicalSystem
they need. Then, an instance of AttractorMapper
is created from this dynamical system. This mapper
instance can be used to compute e.g., basins_of_attraction
, and the output can be further analyzed to get e.g., the basin_entropy
.
The example 2D basins of 4D system contains most of the functionality documented in this page.
ChaosTools.AttractorMapper
— TypeAttractorMapper(ds::GeneralizedDynamicalSystem, args...; kwargs...) → mapper
Subtypes of AttractorMapper
are structures that map initial conditions of ds
to attractors. Currently available mapping methods:
All AttractorMapper
subtypes can be used with basins_fractions
or basins_of_attraction
.
In addition, some mappers can be called as a function of an initial condition:
label = mapper(u0)
and this will on the fly compute and return the label of the attractor u0
converges at. The mappers that can do this are:
ChaosTools.AttractorsViaProximity
— TypeAttractorsViaProximity(ds::DynamicalSystem, attractors::Dict [, ε]; kwargs...)
Map initial conditions to attractors based on whether the trajectory reaches ε
-distance close to any of the user-provided attractors
. They have to be in a form of a dictionary mapping attractor labels to Dataset
s containing the attractors.
The system gets stepped, and at each step the minimum distance to all attractors is computed. If any of these distances is < ε
, then the label of the nearest attractor is returned. If an ε::Real
is not provided by the user, a value is computed automatically as half of the minimum distance between all attractors. This operation can be expensive for large attractor datasets.
Because in this method the attractors are already known to the user, the method can also be called supervised.
Keywords
Ttr = 100
: Transient time to first evolve the system for before checking for proximity.Δt = 1
: Integration step time (only valid for continuous systems).horizon_limit = 1e3
: If the maximum distance of the trajectory from any of the given attractors exceeds this limit, it is assumed that the trajectory diverged (gets labelled as-1
).mx_chk_lost = 1000
: If the integrator has been stepped this many times without comingε
-near to any attractor, it is assumed that the trajectory diverged (gets labelled as-1
).diffeq = NamedTuple()
: Keywords propagated to DifferentialEquations.jl (only valid for continuous systems).
ChaosTools.AttractorsViaRecurrences
— TypeAttractorsViaRecurrences(ds::GeneralizedDynamicalSystem, grid::Tuple; kwargs...)
Map initial conditions to attractors by identifying attractors on the fly based on recurrences in the state space, as outlined by Datseris & Wagemakers[Datseris2022]. Works for any case encapsulated by GeneralizedDynamicalSystem
.
grid
is a tuple of ranges partitioning the state space so that a finite state machine can operate on top of it. For example grid = (xg, yg)
where xg = yg = range(-5, 5; length = 100)
for a two-dimensional system. The grid has to be the same dimensionality as the state space, use a projected_integrator
if you want to search for attractors in a lower dimensional subspace.
Keyword Arguments
Δt
: Approximate time step of the integrator, which is1
for discrete systems. For continuous systems, an automatic value is calculated usingautomatic_Δt_basins
.diffeq = NamedTuple()
: Keyword arguments propagated tointegrator
. Only valid forContinuousDynamicalSystem
. It is recommended to choose high accuracy solvers for this application, e.g.diffeq = (alg=Vern9(), reltol=1e-9, abstol=1e-9)
.mx_chk_att = 2
: A parameter that sets the maximum checks of consecutives hits of an attractor before deciding the basin of the initial condition.mx_chk_hit_bas = 10
: Maximum check of consecutive visits of the same basin of attraction. This number can be increased for higher accuracy.mx_chk_fnd_att = 100
: Maximum check of unnumbered cell before considering we have an attractor. This number can be increased for higher accuracy.mx_chk_loc_att = 100
: Maximum check of consecutive cells marked as an attractor before considering that we have all the available pieces of the attractor.mx_chk_lost = 20
: Maximum check of iterations outside the defined grid before we consider the orbit lost outside. This number can be increased for higher accuracy.horizon_limit = 1e6
: If the norm of the integrator state reaches this limit we consider that the orbit diverges.
Description
An initial condition given to an instance of AttractorsViaRecurrences
is iterated based on the integrator corresponding to ds
. A recurrence in the state space means that the trajectory has converged to an attractor. This is the basis for finding attractors.
A finite state machine (FSM) follows the trajectory in the state space, and constantly maps it to the given grid
. The FSM decides when an initial condition has successfully converged into an attractor. An array, internally called "basins", stores the state of the FSM on the grid, according to the indexing system described in [Datseris2022]. As the system is integrated more and more, the information of the "basins" becomes richer and richer with more identified attractors or with grid cells that belong to basins of already found attractors. Notice that only in the special method basins_of_attraction(mapper::AttractorsViaRecurrences)
the information of the attraction or exit basins is utilized. In other functions like basins_fractions
only the attractor locations are utilized.
The iteration of a given initial condition continues until one of the following happens:
- The trajectory hits
mx_chk_fnd_att
times in a row grid cells previously visited: it is considered that an attractor is found and is labelled with a new number. - The trajectory hits an already identified attractor
mx_chk_att
consecutive times: the initial condition is numbered with the attractor's number. - The trajectory hits a known basin
mx_chk_hit_bas
times in a row: the initial condition belongs to that basin and is numbered accordingly. - The trajectory spends
mx_chk_lost
steps outside the defined grid or the norm of the integrator state becomes > thanhorizon_limit
: the initial condition is set to -1.
ChaosTools.automatic_Δt_basins
— Functionautomatic_Δt_basins(integ, grid; N = 5000) → Δt
Calculate an optimal Δt
value for basins_of_attraction
. This is done by evaluating the dynamic rule f
(vector field) at N
randomly chosen points of the grid. The average f
is then compared with the diagonal length of a grid cell and their ratio provides Δt
.
Notice that Δt
should not be too small which happens typically if the grid resolution is high. It is okay for basins_of_attraction
if the trajectory skips a few cells. But if Δt
is too small the default values for all other keywords such as mx_chk_hit_bas
need to be increased drastically.
Also, Δt
that is smaller than the internal step size of the integrator will cause a performance drop.
ChaosTools.AttractorsViaFeaturizing
— TypeAttractorsViaFeaturizing(ds::DynamicalSystem, featurizer::Function; kwargs...) → mapper
Initialize a mapper
that maps initial conditions to attractors using the featurizing and clustering method of [Stender2021]. See AttractorMapper
for how to use the mapper
.
featurizer
is a function that takes as an input an integrated trajectory A::Dataset
and the corresponding time vector t
and returns a Vector{<:Real}
of features describing the trajectory.
Keyword arguments
Integration
T=100, Ttr=100, Δt=1, diffeq=NamedTuple()
: Propagated totrajectory
.
Feature extraction and classification
attractors_ic = nothing
Enables supervised version, see below. If given, must be aDataset
of initial conditions each leading to a different attractor.min_neighbors = 10
: (unsupervised method only) minimum number of neighbors (i.e. of similar features) each feature needs to have in order to be considered in a cluster (fewer than this, it is labeled as an outlier,-1
).clust_method_norm=Euclidean()
: metric to be used in the clustering.clustering_threshold = 0.0
: Maximum allowed distance between a feature and the cluster center for it to be considered inside the cluster. Only used whenclust_method = "kNN_thresholded"
.clust_method = clustering_threshold > 0 ? "kNN_thresholded" : "kNN"
: (supervised method only) which clusterization method to apply. If"kNN"
, the first-neighbor clustering is used. If"kNN_thresholded"
, a subsequent step is taken, which considers as unclassified (label-1
) the features whose distance to the nearest template is above theclustering_threshold
.rescale_features = true
: (unsupervised method): if true, rescale each dimension of the
extracted features separately into the range [0,1]
.
optimal_radius_method = silhouettes
(unsupervised method): the method used to determine
the optimal radius for clustering features in the unsupervised method. The silhouettes
method chooses the radius that maximizes the average silhouette values of clusters, and is an iterative optimization procedure that may take some time to execute. The elbow
method chooses the the radius according to the elbow (knee, highest-derivative method) (see optimal_radius_dbscan_elbow
for details), and is quicker though possibly leads to worse clustering.
Description
The trajectory X
of each initial condition is transformed into a vector of features. Each feature is a number useful in characterizing the attractor the initial condition ends up at, and distinguishing it from other attrators. Example features are the mean or standard deviation of one of the of the timeseries of the trajectory, the entropy of the first two dimensions, the fractal dimension of X
, or anything else you may fancy. The vectors of features are then used to identify to which attractor each trajectory belongs (i.e. in which basin of attractor each initial condition is in). The method thus relies on the user having at least some basic idea about what attractors to expect in order to pick the right features, in contrast to AttractorsViaRecurrences
.
The algorithm of[Stender2021] that we use has two versions to do this. If the attractors are not known a-priori the unsupervised versions should be used. Here, the vectors of features of each initial condition are mapped to an attractor by analysing how the features are clustered in the feature space. Using the DBSCAN algorithm, we identify these clusters of features, and consider each cluster to represent an attractor. Features whose attractor is not identified are labeled as -1
. If each feature spans different scales of magnitude, rescaling them into the same [0,1]
interval can bring significant improvements in the clustering in case the Euclidean
distance metric is used.
In the supervised version, the attractors are known to the user, who provides one initial condition for each attractor using the attractors_ic
keyword. The algorithm then evolves these initial conditions, extracts their features, and uses them as templates representing the attrators. Each trajectory is considered to belong to the nearest template (based on the distance in feature space). Notice that the functionality of this version is similar to AttractorsViaProximity
. Generally speaking, the AttractorsViaProximity
is superior. However, if the dynamical system has extremely high-dimensionality, there may be reasons to use the supervised method of this featurizing algorithm instead.
Parallelization note
The trajectories in this method are integrated in parallel using Threads
. To enable this, simply start Julia with the number of threads you want to use.
Basins of attraction
Calculating basins of attraction, or their state space fractions, can be done with the functions basins_fractions
and basins_of_attraction
. See also the convenience statespace_sampler
and match_attractors
.
ChaosTools.basins_fractions
— Functionbasins_fractions(mapper::AttractorMapper, ics::Union{Dataset, Function}; kwargs...)
Approximate the state space fractions fs
of the basins of attraction of a dynamical stystem by mapping initial conditions to attractors using mapper
(which contains a reference to a GeneralizedDynamicalSystem
). The fractions are simply the ratios of how many initial conditions ended up at each attractor.
Initial conditions to use are defined by ics
. It can be:
- a
Dataset
of initial conditions, in which case all are used. - a 0-argument function
ics()
that spits out random initial conditions. ThenN
random initial conditions are chosen. Seestatespace_sampler
to generate such functions.
The returned arguments are fs
. If ics
is a Dataset
then the labels
of each initial and roughly approximated attractors are also returned: fs, labels, attractors
.
The output fs
is a dictionary whose keys are the labels given to each attractor (always integers enumerating the different attractors), and the values are their respective fractions. The label -1
is given to any initial condition where mapper
could not match to an attractor (this depends on the mapper
type). attractors
has the same structure, mapping labels to Dataset
s.
See AttractorMapper
for all possible mapper
types.
Keyword arguments
N = 1000
: Number of random initial conditions to generate in caseics
is a function.show_progress = true
: Display a progress bar of the process.
basins_fractions(basins::Array) → fs::Dict
Calculate the state space fraction of the basins of attraction encoded in basins
. The elements of basins
are integers, enumerating the attractor that the entry of basins
converges to (i.e., like the output of basins_of_attraction
). Return a dictionary that maps attractor IDs to their relative fractions.
In[Menck2013] the authors use these fractions to quantify the stability of a basin of attraction, and specifically how it changes when a parameter is changed.
ChaosTools.basins_of_attraction
— Functionbasins_of_attraction(mapper::AttractorMapper, grid::Tuple) → basins, attractors
Compute the full basins of attraction as identified by the given mapper
, which includes a reference to a GeneralizedDynamicalSystem
and return them along with (perhaps approximated) found attractors.
grid
is a tuple of ranges defining the grid of initial conditions that partition the state space into boxes with size the step size of each range. For example, grid = (xg, yg)
where xg = yg = range(-5, 5; length = 100)
. The grid has to be the same dimensionality as the state space expected by the integrator/system used in mapper
. E.g., a projected_integrator
could be used for lower dimensional projections, etc. A special case here is a poincaremap
with plane
being Tuple{Int, <: Real}
. In this special scenario the grid can be one dimension smaller than the state space, in which case the partitioning happens directly on the hyperplane the Poincaré map operates on.
basins_of_attraction
function is a convenience 5-lines-of-code wrapper which uses the labels
returned by basins_fractions
and simply assings them to a full array corresponding to the state space partitioning indicated by grid
.
basins_of_attraction(mapper::AttractorsViaRecurrences; show_progress = true)
This is a special method of basins_of_attraction
that using recurrences does exactly what is described in the paper by Datseris & Wagemakers[Datseris2022]. By enforcing that the internal grid of mapper
is the same as the grid of initial conditions to map to attractors, the method can further utilize found exit and attraction basins, making the computation faster as the grid is processed more and more.
ChaosTools.statespace_sampler
— Functionstatespace_sampler(rng = Random.GLOBAL_RNG; kwargs...) → sampler, isinside
Convenience function that creates two functions. sampler
is a 0-argument function that generates random points inside a state space region defined by the keywords. isinside
is a 1-argument function that decides returns true
if the given state space point is inside that region.
The regions can be:
- Rectangular box, with edges
min_bounds
andmax_bounds
. The sampling of the points inside the box is decided by the keywordmethod
which can be either"uniform"
or"multgauss"
. - Sphere, of
spheredims
dimensions, radiusradius
and centered oncenter
.
ChaosTools.match_attractors!
— Functionmatch_attractors!(b₋, a₋, b₊, a₊, [, method = :distance])
Attempt to match the attractors in basins/attractors b₊, a₊
with those at b₋, a₋
. b
is an array whose values encode the attractor ID, while a
is a dictionary mapping IDs to Dataset
s containing the attractors (i.e, output of basins_of_attraction
). Typically the +,- mean after and before some change of parameter for a system.
In basins_of_attraction
different attractors get assigned different IDs, however which attractor gets which ID is somewhat arbitrary, and computing the basins of the same system for slightly different parameters could label the "same" attractors (at the different parameters) with different IDs. match_attractors!
tries to "match" them by modifying the attractor IDs.
The modification of IDs is always done on the b, a
that have less attractors.
method
decides the matching process:
method = :overlap
matches attractors whose basins before and after have the most overlap (in pixels).method = :distance
matches attractors whose state space distance the smallest.
Final state sensitivity / fractal boundaries
Several functions are provided related with analyzing the fractality of the boundaries of the basins of attraction:
ChaosTools.basins_fractal_dimension
— Functionbasins_fractal_dimension(basins; kwargs...) -> V_ε, N_ε ,d
Estimate the Fractal Dimension d
of the boundary between basins of attraction using the box-counting algorithm.
The output N_ε
is a vector with the number of the balls of radius ε
(in pixels) that contain at least two initial conditions that lead to different attractors. V_ε
is a vector with the corresponding size of the balls. The ouput d
is the estimation of the box-counting dimension of the boundary by fitting a line in the log.(N_ε)
vs log.(1/V_ε)
curve. However it is recommended to analyze the curve directly for more accuracy.
Keyword arguments
range_ε = 2:maximum(size(basins))÷20
is the range of sizes of the ball to test (in pixels).
Description
It is the implementation of the popular algorithm of the estimation of the box-counting dimension. The algorithm search for a covering the boundary with N_ε
boxes of size ε
in pixels.
ChaosTools.basin_entropy
— Functionbasin_entropy(basins, ε = 20) -> Sb, Sbb
This algorithm computes the basin entropy Sb
of the basins of attraction. First, the input basins
is divided regularly into n-dimensional boxes of side ε
(along all dimensions). Then Sb
is simply the average of the Gibbs entropy computed over these boxes. The function returns the basin entropy Sb
as well as the boundary basin entropy Sbb
. The later is the average of the entropy only for boxes that contains at least two different basins, that is, for the boxes on the boundary.
The basin entropy is a measure of the uncertainty on the initial conditions of the basins. It is maximum at the value log(n_att)
being n_att
the number of attractors. In this case the boundary is intermingled: for a given initial condition we can find another initial condition that lead to another basin arbitriraly close. It provides also a simple criterion for fractality: if the boundary basin entropy Sbb
is above log(2)
then we have a fractal boundary. It doesn't mean that basins with values below cannot have a fractal boundary, for a more precise test see basins_fractal_test
. An important feature of the basin entropy is that it allows comparisons between different basins using the same box size ε
.
ChaosTools.basins_fractal_test
— Functionbasins_fractal_test(basins; ε = 20, Ntotal = 1000) -> test_res, Sbb
This is an automated test to decide if the boundary of the basins has fractal structures. The bottom line is to look at the basins with a magnifier of size ε
at random in basins
. If what we see in the magnifier looks like a smooth boundary (in average) we decide that the boundary is smooth. If it is not smooth we can say that at the scale ε
we have structures, i.e., it is fractal.
In practice the algorithm computes the boundary basin entropy Sbb
basin_entropy
for Ntotal
random balls of radius ε
. If the computed value is equal to theoretical value of a smooth boundary (taking into account statistical errors and biases) then we decide that we have a smooth boundary. Notice that the response test_res
may depend on the chosen ball radius ε
. For larger size, we may observe structures for smooth boundary and we obtain a different answer.
The output test_res
is a symbol describing the nature of the basin and the output Sbb
is the estimated value of the boundary basin entropy with the sampling method.
[Puy2021] Andreu Puy, Alvar Daza, Alexandre Wagemakers, Miguel A. F. Sanjuán. A test for fractal boundaries based on the basin entropy. Commun Nonlinear Sci Numer Simulat, 95, 105588, 2021.
Keyword arguments
ε = 20
: size of the ball for the test of basin. The result of the test may change with the size.Ntotal = 1000
: number of balls to test in the boundary for the computation ofSbb
ChaosTools.uncertainty_exponent
— Functionuncertainty_exponent(basins; kwargs...) -> ε, N_ε ,α
Estimate the uncertainty exponent[Grebogi1983] of the basins of attraction. This exponent is related to the final state sensitivity of the trajectories in the phase space. An exponent close to 1
means basins with smooth boundaries whereas an exponent close to 0
represent completely fractalized basins, also called riddled basins.
The output N_ε
is a vector with the number of the balls of radius ε
(in pixels) that contain at least two initial conditions that lead to different attractors. The ouput α
is the estimation of the uncertainty exponent using the box-counting dimension of the boundary by fitting a line in the log.(N_ε)
vs log.(1/ε)
curve. However it is recommended to analyze the curve directly for more accuracy.
Keyword arguments
range_ε = 2:maximum(size(basins))÷20
is the range of sizes of the ball to test (in pixels).
Description
A phase space with a fractal boundary may cause a uncertainty on the final state of the dynamical system for a given initial condition. A measure of this final state sensitivity is the uncertainty exponent. The algorithm probes the basin of attraction with balls of size ε
at random. If there are a least two initial conditions that lead to different attractors, a ball is tagged "uncertain". f_ε
is the fraction of "uncertain balls" to the total number of tries in the basin. In analogy to the fractal dimension, there is a scaling law between, f_ε ~ ε^α
. The number that characterizes this scaling is called the uncertainty exponent α
.
Notice that the uncertainty exponent and the box counting dimension of the boundary are related. We have Δ₀ = D - α
where Δ₀
is the box counting dimension computed with basins_fractal_dimension
and D
is the dimension of the phase space. The algorithm first estimates the box counting dimension of the boundary and returns the uncertainty exponent.
An obstruction to predictability, Physics Letters A, 99, 9, 1983
Tipping points
ChaosTools.tipping_probabilities
— Functiontipping_probabilities(basins_before, basins_after) → P
Return the tipping probabilities of the computed basins before and after a change in the system parameters (or time forcing), according to the definition of[Kaszás2019].
The input basins
are integer-valued arrays, where the integers enumerate the attractor, e.g. the output of basins_of_attraction
.
Description
Let $\mathcal{B}_i(p)$ denote the basin of attraction of attractor $A_i$ at parameter(s) $p$. Kaszás et al[Kaszás2019] define the tipping probability from $A_i$ to $A_j$, given a parameter change in the system of $p_- \to p_+$, as
\[P(A_i \to A_j | p_- \to p_+) = \frac{|\mathcal{B}_j(p_+) \cap \mathcal{B}_i(p_-)|}{|\mathcal{B}_i(p_-)|}\]
where $|\cdot|$ is simply the volume of the enclosed set. The value of $P(A_i \to A_j | p_- \to p_+)$ is P[i, j]
. The equation describes something quite simple: what is the overlap of the basin of attraction of $A_i$ at $p_-$ with that of the attractor $A_j$ at $p_+$. If basins_before, basins_after
contain values of -1
, corresponding to trajectories that diverge, this is considered as the last attractor of the system in P
.
Discrete system example
using DynamicalSystems
function newton_map(z, p, n)
z1 = z[1] + im*z[2]
dz1 = newton_f(z1, p[1])/newton_df(z1, p[1])
z1 = z1 - dz1
return SVector(real(z1), imag(z1))
end
newton_f(x, p) = x^p - 1
newton_df(x, p)= p*x^(p-1)
ds = DiscreteDynamicalSystem(newton_map, [0.1, 0.2], [3.0])
xg = yg = range(-1.5, 1.5; length = 400)
mapper = AttractorsViaRecurrences(ds, (xg, yg))
basins, attractors = basins_of_attraction(mapper; show_progress = false)
basins
400×400 Matrix{Int16}:
1 1 1 1 1 1 1 1 1 1 1 1 1 … 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 … 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
⋮ ⋮ ⋮ ⋱ ⋮ ⋮
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 … 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
attractors
Dict{Int16, Dataset{2, Float64}} with 3 entries:
2 => 2-dimensional Dataset{Float64} with 1 points
3 => 2-dimensional Dataset{Float64} with 1 points
1 => 2-dimensional Dataset{Float64} with 1 points
Now let's plot this as a heatmap
using CairoMakie
# Set up some code for plotting attractors
function scatter_attractors!(ax, attractors)
for k ∈ keys(attractors)
x, y = columns(attractors[k])
scatter!(ax, attractors[k].data;
color = Cycled(k),
strokewidth = 3, strokecolor = :white
)
end
end
generate_cmap(n) = cgrad(Main.COLORS[1:n], n; categorical = true)
ids = sort!(unique(basins))
cmap = generate_cmap(length(ids))
fig, ax = heatmap(xg, yg, basins;
colormap = cmap, colorrange = (ids[1] - 0.5, ids[end]+0.5),
)
scatter_attractors!(ax, attractors)
fig
Stroboscopic map example
This example targets periodically driven 2D continuous dynamical systems, like the Duffing oscillator:
using DynamicalSystems
ω=1.0; f = 0.2
ds = Systems.duffing([0.1, 0.25]; ω, f, d = 0.15, β = -1)
using OrdinaryDiffEq
diffeq = (alg = Vern9(), reltol = 1e-9)
smap = stroboscopicmap(ds, 2π/ω; diffeq)
Iterator of the stroboscopic map
rule f: duffing_rule
Period: 6.283185307179586
For stroboscopic maps, we strongly recommend using a higher precision integrator from OrdinaryDiffEq.jl. Now we can compute the basins of smap
just like before.
xg = yg = range(-2.2, 2.2; length = 200)
mapper = AttractorsViaRecurrences(smap, (xg, yg); diffeq)
basins, attractors = basins_of_attraction(mapper; show_progress = false)
basins
200×200 Matrix{Int16}:
1 2 1 1 1 1 1 1 1 2 2 1 2 … 1 1 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 2 1 2 2 1 1 1 2 1 2 2 2 1 1 2 2 2 2 1 2
1 1 2 1 1 1 1 2 2 1 2 1 2 1 1 1 1 1 1 2 2 2 1 1 2
1 1 1 2 2 2 1 1 1 2 1 2 2 2 2 2 1 1 1 1 2 2 2 2 2
2 2 1 2 2 2 1 2 2 2 2 2 2 1 1 1 1 2 2 1 1 1 1 2 2
2 2 2 2 2 2 2 2 1 1 1 1 2 … 1 1 1 1 1 1 1 2 2 1 1 1
2 2 2 1 1 2 1 1 2 2 2 2 1 1 1 1 1 2 2 1 1 1 1 1 1
2 2 2 2 1 2 2 2 1 1 2 1 2 2 1 1 1 1 1 2 1 1 1 2 1
2 2 2 2 1 2 2 2 1 2 2 2 1 1 2 2 1 1 1 1 1 2 2 1 2
1 1 2 2 1 1 2 1 1 1 1 1 1 1 1 1 2 2 1 2 1 1 1 2 1
⋮ ⋮ ⋮ ⋱ ⋮ ⋮
2 1 2 1 1 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2
1 2 2 1 2 2 2 2 2 2 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1
1 2 1 1 2 2 2 1 1 2 2 2 2 1 1 1 2 2 2 1 2 2 1 2 2
1 1 2 2 2 1 1 2 2 2 2 1 1 1 1 1 2 1 2 1 1 2 1 1 1
2 2 2 2 2 1 2 1 1 1 2 1 2 … 1 1 1 2 2 2 2 2 2 1 2 2
2 1 1 2 2 1 1 1 2 2 2 1 1 1 1 2 1 2 2 2 2 2 1 2 1
2 2 2 2 1 2 2 1 2 1 2 2 2 2 2 2 2 2 2 2 2 2 1 2 1
1 2 2 2 2 2 1 1 2 1 2 2 1 2 2 2 1 1 1 2 2 1 1 1 2
2 1 2 2 2 2 2 2 2 2 2 1 2 1 1 1 2 1 1 1 2 1 2 1 2
And visualize the result as a heatmap, scattering the found attractors via scatter.
using CairoMakie
ids = sort!(unique(basins))
cmap = generate_cmap(length(ids))
fig, ax = heatmap(xg, yg, basins;
colormap = cmap, colorrange = (ids[1] - 0.5, ids[end]+0.5),
)
scatter_attractors!(ax, attractors)
fig
2D basins of 4D system
In this section we will calculate the basins of attraction of the four-dimensional magnetic pendulum. We know that the attractors of this system are all individual fixed points on the (x, y) plane so we will only compute the basins there. We can also use this opportunity to highlight a different method, the AttractorsViaProximity
which works when we already know where the attractors are.
Computing the basins
ds = Systems.magnetic_pendulum(d=0.2, α=0.2, ω=0.8, N=3)
psys = projected_integrator(ds, [1, 2], [0.0, 0.0])
Integrator of a projected system
rule f: MagneticPendulum
projection: [1, 2]
dimension: 2
complete state: [0.0, 0.0]
For this systems we know the attractors are close to the magnet positions, so we can just do
attractors = Dict(i => Dataset([ds.f.magnets[i]]) for i in 1:3)
mapper = AttractorsViaProximity(psys, attractors)
AttractorsViaProximity
rule f: MagneticPendulum
type: ProjectedIntegrator
ε: 0.8660254037844386
Δt: 1
Ttr: 100
attractors: Dict{Int64, Dataset{2, Float64}} with 3 entries:
2 => 2-dimensional Dataset{Float64} with 1 points
3 => 2-dimensional Dataset{Float64} with 1 points
1 => 2-dimensional Dataset{Float64} with 1 points
and as before, get the basins of attraction
xg = yg = range(-4, 4; length=150)
basins, = basins_of_attraction(mapper, (xg, yg); show_progress = false)
ids = sort!(unique(basins))
cmap = generate_cmap(length(ids))
fig, ax = heatmap(xg, yg, basins;
colormap = cmap, colorrange = (ids[1] - 0.5, ids[end]+0.5),
)
scatter_attractors!(ax, attractors)
fig
Computing the uncertainty exponent
Let's now calculate the uncertainty_exponent
for this system as well. The calculation is straightforward:
ε, f_ε, α = uncertainty_exponent(basins)
fig, ax = lines(log.(ε), log.(f_ε))
ax.title = "α = $(round(α; digits=3))"
fig
The actual uncertainty exponent is the slope of the curve (α) and indeed we get an exponent near 0 as we know a-priory the basins have fractal boundaries for the magnetic pendulum.
Computing the tipping probabilities
We will compute the tipping probabilities using the magnetic pendulum's example as the "before" state. For the "after" state we will change the γ
parameter of the third magnet to be so small, its basin of attraction will virtually disappear. As we don't know when the basin of the third magnet will disappear, we switch the attractor finding algorithm back to AttractorsViaRecurrences
.
ds = Systems.magnetic_pendulum(d=0.2, α=0.2, ω=0.8, N=3, γs = [1.0, 1.0, 0.1])
psys = projected_integrator(ds, [1, 2], [0.0, 0.0]; diffeq = (reltol = 1e-9,))
mapper = AttractorsViaRecurrences(psys, (xg, yg); Δt = 1)
basins_after, attractors_after = basins_of_attraction(
mapper; show_progress = false
)
# matching attractors is important!
match_attractors!(basins, attractors, basins_after, attractors_after)
# now plot
ids = sort!(unique(basins_after))
cmap = generate_cmap(length(ids))
fig, ax = heatmap(xg, yg, basins_after;
colormap = cmap, colorrange = (ids[1] - 0.5, ids[end]+0.5),
)
scatter_attractors!(ax, attractors_after)
fig
P = tipping_probabilities(basins, basins_after)
3×2 Matrix{Float64}:
0.5 0.5
0.450115 0.549885
0.550155 0.449845
As you can see P
has size 3×2, as after the change only 2 attractors have been identified in the system (3 still exist but our state space discretization isn't accurate enough to find the 3rd because it has such a small basin). Also, the first row of P
is 50% probability to each other magnet, as it should be due to the system's symmetry.
Computing the basin fractions via featurizing
We can also compute the fraction of initial conditions that go to each of the three attractors for the magnetic pendulum via featurizing using AttractorsViaFeaturizing
because here it is trivial to find the appropriate features that distinguish the attractors: simply the final value of the x and y coordinates of the trajectory.
Let's also just use basins_fractions
with random sampling instead of computing a full array of basins of attractions, just for some variety.
So let's define a random sampler and the featurizing function
using Random
rng = Random.MersenneTwister(1234)
sampler, _ = statespace_sampler(rng; min_bounds=[-4, -4], max_bounds=[4, 4])
featurizer(A, t) = A[end]
featurizer (generic function with 1 method)
which gives
ds = Systems.magnetic_pendulum(d=0.2, α=0.2, ω=0.8, N=3)
psys = projected_integrator(ds, [1, 2], [0.0, 0.0]; diffeq = (reltol = 1e-9,))
mapper = AttractorsViaFeaturizing(psys, featurizer)
fs = basins_fractions(mapper, sampler; N = 1000, show_progress = false)
Dict{Int64, Float64} with 3 entries:
2 => 0.341
3 => 0.306
1 => 0.353
As it should, we get about 33% fraction for each attractor.
3D basins
To showcase the true power of AttractorsViaRecurrences
we need to use a system whose attractors span higher-dimensional space. An example is
ds = Systems.thomas_cyclical(b = 0.1665)
3-dimensional continuous dynamical system
state: [1.0, 0.0, 0.0]
rule f: thomas_rule
in-place? false
jacobian: thomas_jacob
parameters: [0.1665]
which, for this parameter, contains 5 coexisting attractors. 3 of them are entangled periodic orbits that span across all three dimensions, and the remaining 2 are fixed points.
To compute the basins we define a three-dimensional grid and call on it basins_of_attraction
.
# This computation takes about an hour
xg = yg = zg = range(-6.0, 6.0; length = 251)
mapper = AttractorsViaRecurrences(ds, (xg, yg, zg))
basins, attractors = basins_of_attraction(mapper)
attractors
Dict{Int16, Dataset{3, Float64}} with 5 entries:
5 => 3-dimensional Dataset{Float64} with 1 points
4 => 3-dimensional Dataset{Float64} with 379 points
6 => 3-dimensional Dataset{Float64} with 1 points
2 => 3-dimensional Dataset{Float64} with 538 points
3 => 3-dimensional Dataset{Float64} with 537 points
1 => 3-dimensional Dataset{Float64} with 1 points
The basins of attraction are very complicated. We can try to visualize them by animating the 2D slices at each z value, to obtain:
Then, we visualize the attractors to obtain:
In the animation above, the scattered points are the attractor values the function AttractorsViaRecurrences
found by itself. Of course, for the periodic orbits these points are incomplete. Once the function's logic understood we are on an attractor, it stops computing. However, we also simulated lines, by evolving initial conditions colored appropriately with the basins output.
The animation was produced with the code:
using GLMakie
fig = Figure()
display(fig)
ax = fig[1,1] = Axis3(fig; title = "found attractors")
cmap = cgrad(:dense, 6; categorical = true)
for i in keys(attractors)
tr = attractors[i]
markersize = length(attractors[i]) > 10 ? 2000 : 6000
marker = length(attractors[i]) > 10 ? :circle : :rect
scatter!(ax, columns(tr)...; markersize, marker, transparency = true, color = cmap[i])
j = findfirst(isequal(i), basins)
x = xg[j[1]]
y = yg[j[2]]
z = zg[j[3]]
tr = trajectory(ds, 100, SVector(x,y,z); Ttr = 100)
lines!(ax, columns(tr)...; linewidth = 1.0, color = cmap[i])
end
a = range(0, 2π; length = 200) .+ π/4
record(fig, "cyclical_attractors.mp4", 1:length(a)) do i
ax.azimuth = a[i]
end
Poincaré map example
In the previous example we saw that this system has periodic attractors. In the Poincaré map these periodic attractors become points. We can use any instance of AttractorMapper
and poincaremap
to find basins of attraction on the Poincaré surface of section.
ds = Systems.thomas_cyclical(b = 0.1665)
xg = yg = range(-6.0, 6.0; length = 100)
pmap = poincaremap(ds, (3, 0.0);
rootkw = (xrtol = 1e-8, atol = 1e-8), diffeq = (reltol=1e-9,)
)
mapper = AttractorsViaRecurrences(pmap, (xg, yg))
basins, attractors = basins_of_attraction(mapper; show_progress = false)
attractors
Dict{Int16, Dataset{3, Float64}} with 3 entries:
2 => 3-dimensional Dataset{Float64} with 1 points
3 => 3-dimensional Dataset{Float64} with 3 points
1 => 3-dimensional Dataset{Float64} with 1 points
attractors[1]
3-dimensional Dataset{Float64} with 1 points
1.83899 -4.15575 -2.10294e-11
Looks good so far, but let's plot it as well:
ids = sort!(unique(basins))
cmap = generate_cmap(length(ids))
fig, ax = heatmap(xg, yg, basins;
colormap = cmap, colorrange = (ids[1] - 0.5, ids[end]+0.5),
)
scatter_attractors!(ax, attractors)
fig
This aligns perfectly with the video we produced above.
- Datseris2022G. Datseris and A. Wagemakers, Effortless estimation of basins of attraction, Chaos 32, 023104 (2022)
- Stender2021Stender & Hoffmann, bSTAB: an open-source software for computing the basin stability of multi-stable dynamical systems
- Menck2013Menck, Heitzig, Marwan & Kurths. How basin stability complements the linear stability paradigm. Nature Physics, 9(2), 89–92
- Datseris2022G. Datseris and A. Wagemakers, Effortless estimation of basins of attraction, Chaos 32, 023104 (2022)
- Daza2016A. Daza, A. Wagemakers, B. Georgeot, D. Guéry-Odelin and M. A. F. Sanjuán, Basin entropy: a new tool to analyze uncertainty in dynamical systems, Sci. Rep., 6, 31416, 2016.
- Grebogi1983C. Grebogi, S. W. McDonald, E. Ott and J. A. Yorke, Final state sensitivity:
- Kaszás2019Kaszás, Feudel & Tél. Tipping phenomena in typical dynamical systems subjected to parameter drift. Scientific Reports, 9(1)