JuliaDynamics / Attractors.jl

Find attractors (and their basins) of dynamical systems. Perform global continuation. Study global stability (a.k.a. non-local, or resilience). Also tipping points functionality.
MIT License
32 stars 6 forks source link

AttractorsViaRecurrences `mx_chk_loc_att` and `:att_found` state #102

Closed Datseris closed 11 months ago

Datseris commented 1 year ago

Alright, @awage I think we should discuss this. At the moment, the mode of operation :att_found of the Finite State Machine (FSM) is as follows:

    # This state can only be reached from `:att_search`. It means we have
    # enough recurrences to claim we have found an attractor.
    # We then locate the attractor by recording enough cells.
    if bsn_nfo.state == :att_found
        if ic_label == 0 || ic_label == bsn_nfo.visited_cell_label
            # label this cell as part of an attractor
            bsn_nfo.basins[n] = bsn_nfo.current_att_label
            bsn_nfo.consecutive_match = 1
            store_attractor!(bsn_nfo, u)
        elseif iseven(ic_label) && (bsn_nfo.consecutive_match <  mx_chk_loc_att)
            # We make sure we hit the attractor another `mx_chk_loc_att` consecutive times
            # just to be sure that we have the complete attractor
            bsn_nfo.consecutive_match += 1
            store_once_per_cell || store_attractor!(bsn_nfo, u)
        elseif iseven(ic_label) && bsn_nfo.consecutive_match ≥ mx_chk_loc_att
            # We have recorded the attractor with sufficient accuracy.
            # We now set the empty counters for the new attractor.
            cleanup_visited_cells!(bsn_nfo)
            bsn_nfo.visited_cell_label += 2
            bsn_nfo.current_att_label += 2
            reset_basins_counters!(bsn_nfo)
            # We return the label corresponding to the *basin* of the attractor
            return ic_label + 1
        end
        return 0
    end

This means that, once we enter :att_found, we start labelling all encountered cells as "attractor cells". However, the counter is incremented only the second time we encounter the attractor cells. And each time we do not encounter an attractor cell, we reset the counter and start again from scratch.

For a long time now I thought that the mode of operation is a second option: "every subsequent visited cell is labelled an attractor cell, and each time the counter is incremented." That's not what we have.

But my argument is: shouldn't we do the second option...? The way things are right now we are searching for recurrences twice: We first search for recurrences once to trigger the :att_found phase. But then we search for recurrences again during the :att_found phase, as only when a second recurrence occurs, we increment the counter.

So, isn't it better if we do the second option? This way, we only search for recurrences once. Additionally, this way, the user has much better clarity of what value to give to mx_chk_fnd_att. It is the only parameter that relates with recurrences in state space.

Now, if we do go with the second option, it is not clear to me what mx_chk_loc_att should do... My guess is that it simply counts how many steps to take after having found an attractor to label cells as attractor cells.

Anyways, I am wondering, why did we do things this way. We search for recurrences twice. But it doesn't "improve the accuracy" of the algorithm if you think about it, because :att_found is a state one can never escape from.

I am not sure. I hope you know this better and remember things better so you can tell me why the code is the way 1 instead of the way 2...

awage commented 1 year ago

This is very good question. Right now the mode of operation is number 1: you keep collecting points of the attractor until you have a number mx_chk_loc_att of consecutive recurrences on the attractor.

The problem is the stopping criterion of the algorithm. When do we stop collecting points? So the idea is to continue on the attractor enough consecutive steps without finding an new cell. If there is a newly found cell, then we store the point and reset the counter. This is especially important for chaotic attractors.

The other option is to set another stopping criterion. It can be a fixed amount of steps: the algorithm goes on for a sufficiently long time and stops. This is OK when combined with store_once_per_cell = false to store more points of the attractor if needed. Since attractors are processed only once, the mx_chk_loc_att can be a big number like 1e5 iterations by default. It should be compared with basics benchmarks.

There are tradeofs, the first method guarantees to have a good description of the attractor but it can be stuck in some situations. Especially in high dimension (I got some problems with the coupled Kuramotos at the time). The second is more intuitive and direct. But I think it leaves the number mx_chk_loc_att to the user.

This is a breaking change, but it is worth thinking about it.

Datseris commented 12 months ago

@awage I think we should do this change. But first, let's organize ourselves.

  1. Method 1. What source code does now. First phase (search mode) is collect enough recurrences. Second phase (locate mode) is collect attractor cells, but reset counter when meeting a non-previously-visited cell. The counter is reset, but the collected cells are not reset.
  2. Method 2. First phase is identical. Second phase is: algorithm proceeds for a user-specified amount of steps, and every encountered cell becomes an attractor cell. No questions asked.

I am arguing we should go for the second method, because it is more intuitive. First method is unintuitive for me. It is unintuitive to me that the algorithm resets in second phase; we have already collected enough recurrences to claim that we are on an attractor, so why do it again...? The algorithm resets already in the first phase, why do it in the second as well? Additionally, even in the limit of extremely high mx_chk_fnd_att, the algorithm, where every single cell the attractor covers has already been visited, even then, the first method needs to make several more "loops" around the attractor, to first label the cells as attractor cells, and then start incrementing the counter. Isn't this second loop a waste?

My other problem with method 1 is: both mx_chk_fnd_att and mx_chk_loc_att keywords tune the precision with which we find attractors. While in method 2 only mx_chk_fnd_att does. The loc keyword is really only about how many atttractor cells we will record, and has nothing to do with recurrences. I just find this more intuitive.

So the idea is to continue on the attractor enough consecutive steps without finding an new cell. If there is a newly found cell, then we store the point and reset the counter. This is especially important for chaotic attractors.

Why is this important for chaotic attractors? I don't see the argument. We have already ensured that there are enough recurrences in the first phase of the algorithm. We can increase accuracy by demanding more recurrences before going into storing attractor points. I just don't see how searching for recurrences a second time makes things more accurate in this scenario, or why this would be important for chaotic attractors.

The second is more intuitive and direct. But I think it leaves the number mx_chk_loc_att to the user.

But this number is anyways left to the user. It is a keyword argument. Doesn't matter if we change things or not.

This is a breaking change, but it is worth thinking about it.

Technically, you are right, this is techinically breaking. I am not sure if this is actually worth a breaking release though. In principle, the two methods have equivalent results by adjusting their keywords. And I believe that changing keyword arguments should not count as a breaking change. And indeed this is declared in the way DynamicalSystems.jl handles versioning: https://juliadynamics.github.io/DynamicalSystemsDocs.jl/dynamicalsystems/stable/#Version-numbers

Besides, Method 1 becomes method 2 in the limit of increasing the recurrence threshold mx_chk_fnd_att.

Lastly, for a long long time, I thought the algorithm was doing method 2 !!!!! Only now it is perfectly clear to me that we do method 1.


I will do a PR that changes to method 2, and we will see how it goes. How many tests it breaks without adjusting keyword arguments.

awage commented 12 months ago

I think the method 2 is better also. I am convinced that it will be clearer for the user. I believe that in 90% of the cases it will not make any difference in the basins found. The other 10% would need some meta-parameter tuning.