DynamicalSystems.jl icon indicating copy to clipboard operation
DynamicalSystems.jl copied to clipboard

basins_of_attraction fails at simple system (easy fix)

Open vitusbenson opened this issue 3 years ago • 21 comments

Describe the bug I have a very simple 3-dimensional dynamical systems for which I want to find the basins of attraction automatically. It is a combination of double-well potentials in each dimension - leading to 8 stable fixed points (+-1, +-1, +-1), which should each have one of the octant of 3d-space as their basin of attraction. Oddly, basins_of_attraction just returns all basins correctly in the lower half of the space, it misses the upper half completely and returns -1 instead.

Minimal Working Example

using DynamicalSystems 
using DifferentialEquations
using IntervalArithmetic

@inline @inbounds function f(u, p, t)
    du1 = -u[1]^3 + u[1]
    du2 = -u[2]^3 + u[2]
    du3 = -u[3]^3 + u[3]
    return SVector{3}(du1, du2, du3)
end
# Jacobian=>
@inline @inbounds function f_jac(u, p, t)
    J = @SMatrix [(-3*u[1]^2+1) 0. 0.;
    0. (-3*u[2]^2+1) 0.;
    0. 0.  (-3*u[3]^2+1)]
    return J
end

ds = ContinuousDynamicalSystem(f, [-1., -1., -1.], [0.], f_jac)
xg = yg = zg = range(-2.0, 2.0; length = 101)
mapper = AttractorsViaRecurrences(ds, (xg, yg, zg))
basins, attractors = basins_of_attraction(mapper, (xg, yg, zg))

x = interval(-2,2)
y = interval(-2,2)
z = interval(-2,2)
box = x × y × z
fp, eigs, stable = fixedpoints(ds, box)

In the above example, for instance i get the following output:

julia> unique(basins[:,:,52])
1-element Vector{Int16}:
 -1

Thank you in advance :)

Package versions Julia Version 1.7.2 (2022-02-06)

  [608a59af] ChaosTools v2.9.0
  [77a26b50] DiffEqNoiseProcess v5.12.0
  [0c46a032] DifferentialEquations v7.1.0
  [61744808] DynamicalSystems v2.3.0
  [587475ba] Flux v0.13.3
  [d1acc4aa] IntervalArithmetic v0.20.6

vitusbenson avatar Jun 27 '22 23:06 vitusbenson

Thatnks for the good report. What does fp, eigs, stable = fixedpoints(ds, box) return?

Datseris avatar Jun 28 '22 08:06 Datseris

Below comes the output.. I found a way to avoid the problem: If I set the length of the grid to an even number, e.g. xg = yg = zg = range(-2.0, 2.0; length = 100), then I get the correctly classified basins of the stable attractors. And no basins for the unstable fixpoints (as these lie exactly on the hyperplanes that are 0 in at least one dimension). But if there are gridcells centered on the unstable fixpoints as in the original post, then the basins algorithm fails for some basins of the stable fixpoints.

julia> fp
3-dimensional Dataset{Float64} with 27 points
  1.94063e-57   1.0           1.72462e-57
 -1.0          -1.0           1.0
  1.36518e-45  -1.0          -1.0
  1.94063e-57   1.0           1.0
  1.0           1.0          -1.0
  1.0          -1.0           1.75017e-41
  1.0          -1.0          -1.0
  1.36518e-45  -1.0           1.0
  1.0           1.36518e-45  -1.0
  1.94063e-57   1.72462e-57   1.0
  ⋮                          
 -1.0           1.36518e-45  -1.0
  1.0           1.0           1.0
  1.0          -1.0           1.0
 -1.0          -3.10652e-41  -3.10652e-41
 -1.0           1.0          -3.50035e-41
 -1.0           1.0          -1.0
  1.0           1.72462e-57   1.72462e-57
  1.36518e-45   1.49952e-46  -1.0
 -1.0          -1.0          -1.0
julia> stable
27-element Vector{Bool}:
 0
 1
 0
 0
 1
 0
 1
 0
 0
 0
 0
 ⋮
 0
 0
 0
 1
 1
 0
 0
 1
 0
 0
 1
julia> eigs
27-element Vector{Vector{ComplexF64}}:
 [-2.0 + 0.0im, 1.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, 1.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 ⋮
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, 1.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]
 [-2.0 + 0.0im, 1.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]
 [-2.0 + 0.0im, 1.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, 1.0 + 0.0im, 1.0 + 0.0im]
 [-2.0 + 0.0im, -2.0 + 0.0im, -2.0 + 0.0im]

vitusbenson avatar Jun 28 '22 09:06 vitusbenson

Oh that's the classical mistake of having the unstable fixed points directly on the set of initial conditions, which means that they are identified as attractors. (this makes perfect sense by the way, it's not a problem with the algorithm).

So, shall I close this?

Datseris avatar Jun 28 '22 09:06 Datseris

Hi, regarding the -1 issue in the original post, it seemed related to a solver related issue. I used:

diffeq = (alg = Vern9(), reltol = 1e-9, abstol = 1e-9)
mapper = AttractorsViaRecurrences(ds, (xg, yg, zg); diffeq)

The algorithm found 27 fixed points (including those on the coinciding with the grid):


[0.0, 0.0, -1.0000000000209022]
[-0.9999999999990652, 0.9999999999790907, 0.0]
[0.0, -0.9999999999990652, 0.9999999999790907]
[0.9999999999790907, -0.9999999999990652, 0.0]
[0.9999999999851943, 0.0, 0.9999999999851943]
[0.0, 0.9999999999790907, -0.9999999999990652]
[0.0, 0.9999999999790583, 0.0]
[-1.0000000000120615, -1.0000000000120615, -1.0000000000120615]
[-0.9999999999977844, -0.9999999999977844, 0.9999999999792872]
[-0.9999999999990652, 0.0, 0.9999999999790907]
[0.0, 0.0, 0.9999999999790583]
[0.9999999999790907, 0.0, -0.9999999999990652]
[0.0, -1.0000000000209022, 0.0]
[0.9999999999852051, 0.9999999999852051, -1.0]
[0.0, 0.0, 0.0]
[0.9999999999792872, -0.9999999999977844, -0.9999999999977844]
[-0.9999999999977844, 0.9999999999792872, -0.9999999999977844]
[-1.0, 0.9999999999852051, 0.9999999999852051]
[-1.0000000000147977, 0.0, -1.0000000000147977]
[-1.0000000000209022, 0.0, 0.0]
[0.9999999999790583, 0.0, 0.0]
[0.0, -1.0000000000147977, -1.0000000000147977]
[-1.0000000000147977, -1.0000000000147977, 0.0]
[0.9999999999851943, 0.9999999999851943, 0.0]
[0.9999999999852051, -1.0, 0.9999999999852051]
[0.0, 0.9999999999851943, 0.9999999999851943]
[0.9999999999879023, 0.9999999999879023, 0.9999999999879023]

With the other grid I get 8 fixed points only as predicted:

[-0.9999999999985189, -0.9999999999985189, 0.9999999999791677]
[0.9999999999852051, 0.9999999999852051, -0.9999999999999356]
[0.9999999999852051, -0.9999999999999356, 0.9999999999852051]
[-0.9999999999999356, 0.9999999999852051, 0.9999999999852051]
[0.9999999999791677, -0.9999999999985189, -0.9999999999985189]
[0.9999999999879039, 0.9999999999879039, 0.9999999999879039]
[-0.9999999999985189, 0.9999999999791677, -0.9999999999985189]
[-1.000000000012085, -1.000000000012085, -1.000000000012085]

awage avatar Jun 28 '22 09:06 awage

Well this makes it clear that this was not a problem with our algorithm, but rather finetuning the parameters used.

Datseris avatar Jun 28 '22 09:06 Datseris

Actually, no, I want to think about this a bit when I have time. If the trajectory went to a fixed point anyways, why would we get -1? Is it really that the SimpleTsit5 solver would deviate the trajectory so strongly that we don't even end up at the correct fixed points? Sounds a bit unlikely... But could be true! Maybe @vitusbenson can analyze this a bit more, as it is their system we are using here, so they will have the most benefit by figuring this out!

Datseris avatar Jun 28 '22 09:06 Datseris

Well you also changed the tolerances here. What about with the SimpleATsit5? (SimpleTsit5 is non-adaptive so it could definitely happen there if you choose a high enough dt)

ChrisRackauckas avatar Jun 28 '22 09:06 ChrisRackauckas

The problem goes away with the change in tolerance:

diffeq = (reltol = 1e-9, abstol = 1e-9)

Still I don't know what happens when the default tolerances are set. I can dig that.

awage avatar Jun 28 '22 09:06 awage

@ChrisRackauckas Sorry I misstyped, the default solver is SimpleATsit5 not SimpleTsit5. The adaptive version is used by default.

Datseris avatar Jun 28 '22 10:06 Datseris

Thanks all, indeed it seems to be the solver SimpleATsit5 that introduces the issue. For instance

ds = ContinuousDynamicalSystem(f, [-1., -1., -1.], [0.], f_jac)
xg = yg = zg = range(-2.0, 2.0; length = 101)
mapper = AttractorsViaRecurrences(ds, (xg, yg, zg); diffeq = (alg = SimpleTsit5(), dt = 5.))
basins, attractors = basins_of_attraction(mapper, (xg, yg, zg))

gives me the expected output

julia> unique(basins[:,:,52])
9-element Vector{Int16}:
 19
 20
 21
 22
 23
 24
 25
 26
 27

Also working is diffeq = (alg = Vern9(), reltol = 1e-9, abstol = 1e-9). But diffeq = (reltol = 1e-9, abstol = 1e-9) is not working.

vitusbenson avatar Jun 28 '22 10:06 vitusbenson

But @awage said that the problem goes away with high tolerances even in the default solver. In any case, let's please simplify and not use basins_of_attraction, but rather directly evolve an initial condition that you know should go to the missed fixed point, and see what this initial condition is doing? It is clear by now that the "problem" is not with basins_of_attraction but with the time evolution, so the more we use basins_of_attraction the more we complicate things but in an unecessary manner.

Problem in quotes, because this is not a real problem.

Datseris avatar Jun 28 '22 11:06 Datseris

Actually I am not so sure about that. With my original code:

julia> attractors[19]
3-dimensional Dataset{Float64} with 1 points
 -1.0  -1.0  1.0
julia> basins[40,40,52]
-1
julia> tr = trajectory(ds, 10000, [xg[40], yg[40], zg[52]])
3-dimensional Dataset{Float64} with 1000001 points
 -0.44      -0.44      0.04
 -0.443556  -0.443556  0.0404014
 -0.447126  -0.447126  0.0408067
 -0.45071   -0.45071   0.0412161
 -0.454309  -0.454309  0.0416297
 -0.457921  -0.457921  0.0420473
 -0.461547  -0.461547  0.0424691
 -0.465185  -0.465185  0.0428952
 -0.468837  -0.468837  0.0433255
 -0.472501  -0.472501  0.0437601
  ⋮                    
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0
 -1.0       -1.0       1.0

So the initial condition at indices 40,40,52 should go to attractor 19. And in the docs it is stated that trajectory also uses SimpleATsit5 as default.

With "problem" in quotes I agree, as for all practical manners it is solved for me now - so feel free to close! :)

vitusbenson avatar Jun 28 '22 11:06 vitusbenson

Ah god damn it. So this is indeed related to the recurrences algorithm. So, how much did you play around with the parameters of the algorithm like basin hit thresholds, leaving off to infinity thresholds, etc? Can you repeat with increasing eg. mx_chk_lost ??

Datseris avatar Jun 28 '22 11:06 Datseris

I did play around a little bit with the parameters, but it never changed. mx_chk_lost = 10000 gives the same results.

vitusbenson avatar Jun 28 '22 11:06 vitusbenson

I have a theory. When the recurrence algorithm hits a unstable fixed point the SimpleATsit5 increases the step dt. I found a value dt=5.864383502047231e15 after some time. So for the next initial condition, the solver goes nuts and spits NaN. The step size is not updated after each initial condition.

I still have to test this theory!

awage avatar Jun 28 '22 12:06 awage

Yes, theory confirmed:

integ = integrator(ds, [0. ,0. ,0.])
for k=1:10; step!(integ); end
integ.dt = 2.2876792454960986e12

awage avatar Jun 28 '22 12:06 awage

I wonder if we should warn of this behavior somewhere.

awage avatar Jun 28 '22 12:06 awage

It seems like a bug that reinit! doesn't reset dt. We should do a PR at SimpleDiffEq.jl that ensures that reinit! also resets dt.

Datseris avatar Jun 28 '22 12:06 Datseris

There is a keyword argument for it. https://diffeq.sciml.ai/stable/basics/integrator/#SciMLBase.reinit!

reset_dt: Set whether to reset the current value of dt using the automatic dt determination algorithm. Default is (integrator.dtcache == zero(integrator.dt)) && integrator.opts.adaptive

And no, it should not be the default because the most common use cases (reinitialization of DAEs) don't necessarily want to do this.

ChrisRackauckas avatar Jun 28 '22 12:06 ChrisRackauckas

Okay, then someone needs to do a PR in ChaosTools.jl so that our calls to reinit! use the keyword reset_dt = true. @vitusbenson this could be your first pr here!

code: https://github.com/JuliaDynamics/ChaosTools.jl/blob/master/src/basins/attractor_mapping_recurrences.jl

Datseris avatar Jun 28 '22 12:06 Datseris

I have made the necessary PR.

IBArbitrary avatar Jul 20 '22 12:07 IBArbitrary

I am assuming this to not be an issue in DS v3 and closing it. Re-open a new issue if problems remain!

Datseris avatar Jan 15 '24 14:01 Datseris