Global continuation

Attractors.global_continuationFunction
global_continuation(gca::GlobalContinuationAlgorithm, pcurve, icsampler; kwargs...)

Find and continue attractors (or representations of attractors) and properties of their basins across a parameter curve pcurve according to algorithm gca and by sampling initial conditions using icsampler.

Possible subtypes of a GlobalContinuationAlgorithm are:

pcurve is a Vector of dictionaries, each dictionary mapping parameter indices to values. This defines an arbitrary curve in the parameter space of the dynamical system.

icsampler is a subtype of InitialConditionsSampler and provides instructions for how to sample initial conditions to explore the state space during the continuation.

Return an instance of GlobalContinuationOutput that contains the continued attractors, their basins fractions, and any other additional information.

There is no difference between single or multi parameter global continuation. Use hilbert_pcurve to cover multiparameter spaces.

Keyword arguments

  • show_progress = true: display a progress bar of the computation.

Description

global_continuation is the central function for performing global continuation as outlined in our article (Datseris et al., 2026).

The global continuation algorithm typically references a BasinMap which is used to find basins and corresponding attractors of a dynamical system. Additional arguments that control how to continue/track/match attractors across a parameter range are given when creating gca.

The basin properties and the attractors (or some representation of them) are continued across the parameter curve, whose elements are given to DynamicalSystems.set_parameters! to update system parameters. Thus, global continuation operates on a prescribed parameter curve.

source
Attractors.continuation_seriesFunction
continuation_series(continuation_info, fillval = NaN)

Transform a continuation quantity (a vector of dictionaries, each dictionary mapping basin IDs to values of same type as fillval) to a dictionary of vectors where the k dictionary entry is the series of the continuation quantity corresponding to basin with ID k. fillval denotes the value to assign in the series if the basin with ID k does not exist at this particular series index. If the continuation_info is the attractors themselves, you likely want to use as fillval some empty state space set such as StateSpaceSet{D}(), or just the missing value (in which case the output will be of Union type).

This function is typically used on the output of global_continuation.

source

General seeding-based continuation (ASCM)

Attractors.AttractorSeedContinueMatchType
AttractorSeedContinueMatch(bmap, matcher = MatchBySSSetDistance(); seeding)

A global continuation method for global_continuation. bmap is any subtype of BasinMap which implements extract_attractors, i.e., it finds the actual attractors. matcher is a configuration of how to match attractor IDs, see IDMatcher for more options.

Description

This is a general and composable global continuation method outlined in our article (Datseris et al., 2026) and based on a 4-step process:

  1. Seed initial conditions from previously found attractors
  2. Propagate those forwards to "continue" previous attractors
  3. Estimate basin fractions and potentially find new attractors
  4. Match attractors

Step 0 - Finding initial attractors

At the first parameter slice of the global continuation process, attractors and their fractions are found using the given bmap and basins_fractions. See the bmap documentation and BasinMap for details on how this works. Then, from the second parameter onwards the continuation occurs.

Step 1 - Seeding initial conditions

Initial conditions can be seeded from previously found attractors. This is controlled by the seeding keyword, which must be a function that given a StateSpaceSet (an attractor), it returns an iterator of initial conditions. By default the first point of an attractor is provided as the only seed.

Seeding can be turned off by providing the dummy function seeding = A -> [], i.e., it always returns an empty iterator and hence no seeds and we skip to step 2.

Step 2 - Continuing the seeds

The dynamical system referenced by the bmap is now set to the new parameter value. The seeds are run through the bmap to converge to attractors at the new parameter value. Seeding initial conditions close to previous attractors increases the probability that if an attractor continues to exist in the new parameter, it is found. Additionally, for some mappers this seeding process improves the accuracy as well as performance of finding attractors, see e.g. discussion in (Datseris et al., 2023).

This seeding works for any bmap, regardless of if they can map individual initial conditions with the bmap(u0) syntax! If this syntax isn't supported, steps 2 and 3 are done together.

Step 3 - Estimate basins fractions

After the special seeded initial conditions are mapped to attractors, attractor basin fractions are computed by sampling additional initial conditions using the provided ics in global_continuation. I.e., exactly as in basins_fractions. Naturally, during this step new attractors may be found, besides those found using the "seeding from previous attractors".

Step 4 - Matching

Normally the ID an attractor gets assigned is somewhat a random integer. Therefore, to ensure a logical output of the global continuation process, attractors need to be "matched". This means: attractor and fractions must have their IDs changed, so that attractors that are "similar" to those at a previous parameter get assigned the same ID.

What is "similar enough" is controlled by the matcher input. The default matcherMatchBySSSetDistance matches sets which have small distance in state space. The matching algorithm itself can be quite involved, so read the documentation of the matcher for how matching works.

A note on matching: the MatchBySSSetDistance can also be used after the continuation is completed, as it only requires as input the state space sets (attractors), without caring at which parameter each attractor exists at. If you don't like the final matching output, you may use a different instance of MatchBySSSetDistance and call match_sequentially! again on the output, without having to recompute the whole global continuation!

Step 5 - Finish

After matching the parameter(s) is incremented. Steps 1-4 repeat until all parameter values are exhausted.

Further note

This global continuation method is a generalization of the "RAFM" continuation described in (Datseris et al., 2023). This continuation method is still exported as RecurrencesFindAndMatch.

source

Recurrences continuation

Attractors.RecurrencesFindAndMatchFunction
RecurrencesFindAndMatch <: GlobalContinuationAlgorithm
RecurrencesFindAndMatch(bmap::BasinMapRecurrences; kwargs...)

A method for global_continuation as in (Datseris et al., 2023) that is based on the recurrences algorithm for finding attractors (BasinMapRecurrences) and then matching them according to their state space distance.

Keyword arguments

  • distance = Centroid(), threshold = Inf: passed to MatchBySSSetDistance.
  • seeds_from_attractor: A function that takes as an input an attractor and returns an iterator of initial conditions to be seeded from the attractor for the next parameter slice. By default, we sample only the first stored point on the attractor.

Description

RecurrencesFindAndMatch is a wrapper type. It is has been generalized by AttractorSeedContinueMatch. It is still exported for backwards compatibility and to have a clear reference to the original algorithm developed in (Datseris et al., 2023).

The source code of RecurrencesFindAndMatch is trival: it takes the given bmap, it initializes a MatchBySSSetDistance, and along with seeds_from_attractor it makes the AttractorSeedContinueMatch instance. This is the process described in (Datseris et al., 2023), whereby attractors are found using the recurrences algorithm BasinMapRecurrences and they are then matched by their distance in state space MatchBySSSetDistance.

source

Grouping continuation (FGAP)

Attractors.FeaturizeGroupAcrossParameterType
FeaturizeGroupAcrossParameter <: GlobalContinuationAlgorithm
FeaturizeGroupAcrossParameter(bmap::BasinMapFeaturizeGroup [, info_extraction]; kw...)

A method for global_continuation that featurizes and groups trajectories across the whole parameter axis to establish a continuation of the groups.

info_extraction::Function is a function that takes as an input a vector of feature-vectors (corresponding to a cluster) and returns a description of the cluster. By default, the centroid of the cluster is used as its description. This output becomes the attractors representation in GlobalContinuationOutput.

If the keyword store_features is true (default), then this method adds some information into the other field of GlobalContinuationOutput:

  • "features" contains the features per parameter step
  • "labels" contains their corresponding labels (both containers are sorted)

which allows subsequent analysis of the grouped features.

Description

It uses the featurizing approach discussed in BasinMapFeaturizeGroup and hence requires an instance of that bmap as an input. When used in global_continuation, features are extracted and then grouped across a parameter range. Said differently, all features of all initial conditions across all parameter values are put into the same "pool" and then grouped as dictated by the group_config of the bmap. After the grouping is finished the feature label fractions are distributed to each parameter value they came from.

This continuation method is based on, but strongly generalizes, the approaches in the papers (Gelbrecht et al., 2020) and (Stender and Hoffmann, 2021).

source

Aggregating attractors

Attractors.aggregate_attractorsFunction
aggregate_attractors(attractors::Dict, featurizer, group_config) → agg_attractors, centroids, members

Aggregate the attractors of a single parameter (a dictionary mapping IDs to StateSpaceSets, as returned by basins_fractions or one slice of a global_continuation output) by merging similar ones into groups.

This is the single-parameter building block of aggregate_continuation; use it directly when you only want to group the attractors found at one parameter value and there is nothing to continue.

Each attractor is turned into a feature vector with featurizer and the attractors are partitioned into groups with group_config (a GroupingConfig). The members of a group are merged into a single StateSpaceSet.

Return

  1. agg_attractors: like attractors, but mapping each group ID to the merged StateSpaceSet of its members.
  2. centroids: a dictionary mapping each group ID to the centroid (mean) of its members' feature vectors. This summarises where each group sits in feature space; the per-step version of this is the second output of aggregate_continuation.
  3. members: a dictionary mapping each group ID to the vector of original attractor IDs (the keys of attractors) that were merged into it.

To compute stability quantifiers for the merged groups, pass agg_attractors to stability_quantifiers_along_continuation (wrapping it and the parameter in length-1 vectors if you have a single parameter).

source
Attractors.aggregate_continuationFunction
aggregate_continuation(attractors_cont, featurizer, group_config; kw...)

Aggregate the attractors of a global_continuation result.

This is the post-processing companion of global_continuation. After a global_continuation with AttractorSeedContinueMatch, call this function to "group" attractors that have similar features into a single merged attractor. The merged attractors carry IDs that stay consistent along the parameter curve.

Arguments

  1. attractors_cont: a vector of dictionaries mapping IDs to attractors (StateSpaceSets), exactly as returned by global_continuation (or basins_fractions).
  2. featurizer: a 1-argument function mapping an attractor to a feature vector. Features expected by GroupingConfig are typically SVectors.
  3. group_config: a subtype of GroupingConfig.

All keyword arguments are propagated into MatchBySSSetDistance. This matcher is used to match feature groups along the continuation. This is possible because a group of features is a vector of vectors, just like a StateSpaceSet is.

Return

  1. agg_attractors_cont: like attractors_cont, but mapping each group ID to the merged StateSpaceSet of its members. (merged attractors are unions of the individual ones)
  2. centroids_cont: a vector of dictionaries mapping each group ID to the centroid (mean) of its members' feature vectors at that step.
  3. members_cont: a vector of dictionaries mapping each group ID to the vector of original attractor IDs (the keys of attractors_cont[i]) merged into it at that step.

All three share consistent group IDs along the parameter axis.

Description

At each parameter step the attractors are turned into feature vectors with featurizer and partitioned into groups with group_config (a GroupingConfig); the members of a group are merged into a single StateSpaceSet. To keep group IDs consistent along the parameter curve, groups are matched between consecutive parameter steps in feature space: the set of member feature vectors of each group is collected into a StateSpaceSet and these are matched with MatchBySSSetDistance using the distance keyword. By default distance = Centroid(), i.e. groups are matched by the distance between their feature-vector centroids; any other set-distance accepted by setsofsets_distances (e.g. Hausdorff, StrictlyMinimumDistance, or a custom f(A, B)) instead compares the full feature-vector sets. Because matching is in feature space, featurizer must return numeric feature vectors (e.g. SVectors).

Aggregating stability quantifiers

To obtain stability quantifiers for the aggregated groups, pass agg_attractors_cont to stability_quantifiers_along_continuation. Each merged group is then treated as a single attractor, so every quantifier — including those that need the raw basin data, such as medians and critical shock magnitudes — is computed correctly for the group.

See the aggregation example for an illustration.

Aggregating basin fractions only

If you only care about aggregating the basin fractions, there is no reason to go through the route of stability_quantifiers_along_continuation. Simply give the returned members_cont, together with the fractions_cont from the continuation, to aggregate_fractions. The denser the sampling for the original continuation was, the more accurate the aggregated fractions will be, and there is no reason to re-run a continuation.

source

Dict utils

Attractors.unique_keysFunction
unique_keys(v::Iterator{<:AbstractDict})

Given a vector of dictionaries, return a sorted vector of the unique keys that are present across all dictionaries.

source
Attractors.swap_dict_keys!Function
swap_dict_keys!(d::Dict, matching_map::Dict)

Swap the keys of a dictionary d given a matching_map which maps old keys to new keys. Also ensure that a swap can happen at most once, e.g., if input d has a key 4, and rmap = Dict(4 => 3, 3 => 2), then the key 4 will be transformed to 3 and not further to 2.

source
Attractors.next_free_idFunction
next_free_id(new::Dict, old::Dict)

Return the minimum key of the "new" dictionary that doesn't exist in the "old" dictionary. If one of the two dictionaries are empty, return its maximum key + 1. If both are empty, return 1.

The function assumes tha the dictionary keys are integers.

source

Matching attractors

Matching attractors follow an extendable interface based on IDMatcher. The available matchers are:

Attractors.MatchBySSSetDistanceType
MatchBySSSetDistance(; distance = Centroid(), threshold = Inf, use_vanished = false)

A matcher type that matches IDs by the distance of their corresponding state space sets.

Keyword arguments

  • distance = Centroid(): distance to match by, given to setsofsets_distances.
  • threshold = Inf: sets with distance larger than the threshold are guaranteed to not be mapped to each other.
  • use_vanished = !isinf(threshold): value of the keyword use_vanished when used in match_sequentially!.

Description

In this matcher the values compared are StateSpaceSets which in most cases represent attractors in the state space, but may also represent any other set such as a group of features.

Here is how this matcher works: (recall in this conversation that sets/attractors are stored in dictionaries, mapping keys/IDs to the sets, and we want to match keys in the "new" dictionary (a₊) to those in the "old" dictionary (a₋)).

The distance between all possible pairs of sets between the "old" sets and "new" sets is computed as a formal distance between sets. This is controlled by the distance option, itself given to the lower-level setsofsets_distances function, so distance can be whatever that function accepts. That is, one of Centroid, Hausdorff, StrictlyMinimumDistance, or any arbitrary user-provided function f that given two sets f(A, B) it returns a positive number (their distance).

Sets (in particular, their corresponding IDs) are then matched according to this distance. First, all possible ID pairs (old, new) are sorted according to the distance of their corresponding sets. The pair with smallest distance is matched. IDs in matched pairs are removed from the matching pool to ensure a unique mapping. Then, the next pair with least remaining distance is matched, and the process repeats until all pairs are exhausted.

Additionally, you can provide a threshold value. If the distance between two sets is larger than this threshold, then it is guaranteed that the two sets will get assigned different ID in the replacement map, and hence, the set in a₊ gets the next available integer as its ID.

source
Attractors.MatchByBasinEnclosureType
MatchByBasinEnclosure(; kw...) <: IDMatcher

A matcher that matches attractors by whether they are enclosed in the basin of a new attractor or not.

Keyword arguments

  • ε = nothing: distance threshold given to BasinMapProximity. If nothing, it is estimated as a quarter of the minimum distance of centroids (in contrast to the default more accurate estimation in BasinMapProximity).
  • Δt = 1, consecutive_lost_steps = 1000: also given to BasinMapProximity. We have not yet decided what should happen to attractors that did not converge to one of the current attractors within this number of steps. At the moment they get assigned the next available free ID but this may change in future releases.
  • distance = Centroid(): metric to estimate distances between state space sets in case there are co-flowing attractors, see below.
  • seeding = A -> A[end]: how to select a point from the attractor to see if it is enclosed in the basin of a new attractor.

Description

An attractor A₋ is a set in a state space that occupies a particular region (or, a single point, if it is a fixed point). This region is always within the basin of attraction of said attractor. When the parameter of the dynamical system is incremented, the attractors A₊ in the new parameter have basins that may have changed in shape and size.

The new attractor A₊ is "matched" (i.e., has its ID changed) to the old attractor A₋ attractor if A₋ is located inside the basin of attraction of A₊. To see if A₋ is in the basin of A₊, we first pick a point from A₋ using the seeding keyword argument. By default this is the last point on the attractor, but it could be anything else, including the centroid of the attractor (mean(A)). This point is given as an initial condition to an BasinMapProximity bmap that maps initial conditions to the attractors when the trajectories from the initial conditions are ε-close to the attractors.

There can be the situation where multiple attractors converge to the same attractor, which we call "coflowing attractors". In this scenario matching is prioritized for the attractor that is closest to the in terms of state space set distance, which is estimated with the distance keyword, which can be anything MatchBySSSetDistance accepts. The closest attractor gets the ID of the closest attractor that converge to it.

Basin enclosure is a concept similar to "basin (in)stability" in (Ritchie et al., 2023): attractors that quantify as "basin stable" are matched.

source
Attractors.MatchByBasinOverlapType
MatchByBasinOverlap(threshold = Inf)

A matcher that matches IDs given full basins of attraction.

Description

This matcher cannot be used in with the generic global continuation method of AttractorSeedContinueMatch. This matcher matches IDs of attractors whose basins of attraction before and after b₋, b₊ have the most overlap (in pixels). This overlap is normalized in 0-1 (with 1 meaning 100% of a basin in b₋ is overlaping with some other basin in b₊). Therefore, the values this matcher compares are full basins of attraction, not attractors themselves (hence why it can't be given to AttractorSeedContinueMatch). Rather, you may use this matcher with matching_map.

The threshold can dissallow matching between basins that do not have enough overlap. Basins whose overlap is less than 1/threshold are guaranteed to get assined different IDs. For example: for threshold = 2 basins that have ≤ 50% overlap get different IDs guaranteed. By default, there is no threshold.

The information of the basins of attraction is typically an Array, or a ArrayBasinsOfAttraction type , i.e. the direct output of basins_of_attraction. For convenience, as well as backwards compatibility, when using matching_map with this bmap you may provide two Arrays b₊, b₋ representing basins of attraction after and before, and the conversion to dictionaries will happen internally as it is supposed to. Similarly two ArrayBasinsOfAttraction types for before and after can be used, BoA₊ and BoA₋. To replace the IDs in b₊ given the replacement map just call replace!(b₊, rmap...), or equivalently replace!(BoA₊.basins, rmap...), or use the in-place version matching_map! directly.

A lower-level input for this matcher in matching_map can be dictionaries mapping IDs to vectors of cartesian indices, where the indices mean which parts of the state space belong to which ID

source

Matching interface

Attractors.IDMatcherType
IDMatcher

Supertype of all "matchers" that match can IDs labelling attractors. Currently available matchers:

Matchers implement an extendable interface based on the function matching_map. This function is used by the higher level function match_sequentially!, which can be called after any call to a global continuation to match attractors differently, if the matching used originally during the continuation was not the best.

source
Attractors.matching_mapFunction
matching_map(
    a₊::Dict, a₋::Dict, matcher;
    ds::DynamicalSystem, p, pprev, next_id
) → rmap

Given dictionaries a₊, a₋ mapping IDs to values, return a replacement map: a dictionary mapping the IDs (keys) in dictionary a₊ to IDs (keys) in dictionary a₋, so that so that values in a₊ that are the "closest" to values in a₋ get assigned the same key as in a₋. In this way keys of a₊ are "matched" to keys of a₋. Use swap_dict_keys! to apply rmap to a₊ or to other dictionaries with same keys as a₊.

How matching happens, i.e., how "closeness" is defined, depends on the algorithm matcher.

The values contained in a₊, a₋ can be anything supported by matcher. Within Attractors.jl they are typically StateSpaceSets representing attractors. Typically the +,- mean after and before some change of parameter of a dynamical system.

matching_map always returns an empty dictionary of either a₊, a₋ is empty.

Keyword arguments

  • ds: the dynamical system that generated a₊, a₋.
  • p, pprev: the parameters corresponding to a₊, a₋. Both need to be iterables mapping parameter index to parameter value (such as Dict, Vector{Pair}, etc., so whatever can be given as input to DynamicalSystems.set_parameters!).
  • next_id = next_free_id(a₊, a₋): the ID to give to values of a₊ that cannot be matched to a₋ and hence must obtain a new unique ID.

Some matchers like MatchBySSSetDistance do not utilize ds, p, pprev in any way while other matchers like MatchByBasinEnclosure do, and those require expliticly giving values to ds, p, pprev as their default values is just nothing.

source
matching_map(BoA₊::ArrayBasinsOfAttraction, BoA₋::ArrayBasinsOfAttraction, matcher::MatchByBasinOverlap)
matching_map(b₊::AbstractArray, b₋::AbstractArray, matcher::MatchByBasinOverlap)

Special case of matching_map where instead of having as input dictionaries mapping IDs to values, we have Arrays which represent basins of attraction and whose elements are the IDs.

The second function signature exists for backwards compatibility.

See MatchByBasinOverlap for how matching works.

source
Attractors.match_sequentially!Function
match_sequentially!(dicts::Vector{Dict{Int, Any}}, matcher::IDMatcher; kw...)

Match the dicts, a vector of dictionaries mapping IDs (integers) to values, according to the given matcher by sequentially applying the matching_map function to all elements of dicts besides the first one.

In the context of Attractors.jl dicts are typically dictionaries mapping IDs to attractors (StateSpaceSets), however the function is generic and would work for any values that matcher works with.

Return rmaps, which is a vector of dictionaries. rmaps[i] contains the matching_map for dicts[i+1], i.e., the pairs of old => new IDs.

Keyword arguments

  • pcurve = nothing: the curve of parameters along which the continuation occured, from which to extract the p, pprev values given to matching_map. See global_continuation if you are unsure what this means.
  • ds = nothing: dynamical system propagated to matching_map.
  • retract_keys::Bool = true: If true at the end the function will "retract" keys (i.e., make the integers smaller integers) so that all unique IDs are the 1-incremented positive integers. E.g., if the IDs where 1, 6, 8, they will become 1, 2, 3. The special ID -1 is unaffected by this.
  • use_vanished = false: If use_vanised = true, then IDs (and their corresponding sets) that existed before but have vanished are kept in "memory" when it comes to matching: the current dictionary values (the attractor sets) are compared to the latest instance of all values that have ever existed, each with a unique ID, and get matched to their closest ones. The value of this keyword is obtained from the matcher.
source
match_sequentially!(continuation_quantity::Vector{Dict}, rmaps::Vector{Dict})

Do the same as in match_sequentially! above, now given the vector of matching maps, and for any arbitrary quantity that has been tracked in the global continuation. continuation_quantity can for example be fractions_cont from global_continuation.

source

Low-level distance functions

StateSpaceSets.CentroidType
Centroid(metric = Euclidean())

A distance that can be used in set_distance. The Centroid method returns the distance (according to metric) between the centroids (a.k.a. centers of mass) of the sets.

metric can be any function that takes in two static vectors are returns a positive definite number to use as a distance (and typically is a Metric from Distances.jl).

source
StateSpaceSets.HausdorffType
Hausdorff(metric = Euclidean())

A distance that can be used in set_distance. The Hausdorff distance is the greatest of all the distances from a point in one set to the closest point in the other set. The distance is calculated with the metric given to Hausdorff which defaults to Euclidean.

Hausdorff is 2x slower than StrictlyMinimumDistance, however it is a proper metric in the space of sets of state space sets.

This metric only works for StateSpaceSets whose elements are SVectors.

For developers: set_distance can take keywords tree1, tree2 that are the KDTrees of the first and second sets respectively.

source
StateSpaceSets.StrictlyMinimumDistanceType
StrictlyMinimumDistance([brute = false,] [metric = Euclidean(),])

A distance that can be used in set_distance. The StrictlyMinimumDistance returns the minimum distance of all the distances from a point in one set to the closest point in the other set. The distance is calculated with the given metric.

The brute::Bool argument switches the computation between a KDTree-based version, or brute force (i.e., calculation of all distances and picking the smallest one). Brute force performs better for sets that are either large dimensional or have a small amount of points. Deciding a cutting point is not trivial, and is recommended to simply benchmark the set_distance function to make a decision.

If brute = false this metric only works for StateSpaceSets whose elements are SVectors.

For developers: set_distance can take a keyword tree2 that is the KDTree of the second set.

source
StateSpaceSets.set_distanceFunction
set_distance(ssset1, ssset2 [, distance])

Calculate a distance between two StateSpaceSets, i.e., a distance defined between sets of points, as dictated by distance.

Possible distance types are:

source
StateSpaceSets.setsofsets_distancesFunction
setsofsets_distances(a₊, a₋ [, distance]) → distances

Calculate distances between sets of StateSpaceSets. Here a₊, a₋ are containers of StateSpaceSets, and the returned distances are dictionaries of distances. Specifically, distances[i][j] is the distance of the set in the i key of a₊ to the j key of a₋. Distances from a₋ to a₊ are not computed at all, assumming symmetry in the distance function.

The distance can be anything valid for set_distance.

Containers a₊, a₋ can be empty but they must be concretely typed.

source