Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Potential issue in extending tipping_probabilities to attraction basins computed on non-uniform grid #36

Open
mdepitta opened this issue Jan 29, 2023 · 12 comments

Comments

@mdepitta
Copy link

@Datseris @awage , there is a potential issue with how tipping_probabilities are computed at the moment. The result is correct if the grid steps are unitary in all directions. If it is not, then the computation of the basin measure must consider the true grid size. Here is my temporary workaround:

function my_tipping_probabilities(basins_before, basins_after, grid)
    @assert size(basins_before) == size(basins_after)

    bid, aid = unique.((basins_before, basins_after))
    P = zeros(length(bid), length(aid))
    # Make -1 last entry in bid, aid, if it exists
    put_minus_1_at_end!(bid); put_minus_1_at_end!(aid)

    # Notice: the following loops could be optimized with smarter boolean operations,
    # however they are so fast that everything should be done within milliseconds even
    # on a potato
    for (i, ι) in enumerate(bid)
        B_i = findall(isequal(ι), basins_before)
        μ_B_i = 0
        for ind in B_i
            # μ = measure on non-unitary grid (in my specific case grid is from ranges/vectors of a state space of 4 variables)
            ## Maybe there is a way to directly sum from the vectors extracted from CartesianIndexes -- in python this would be immediate: but I do not know how to do it in Julia for now...
            μ_B_i += grid[1][ind[1]]*grid[2][ind[2]]*grid[2][ind[3]]*grid[4][ind[4]]
        end
#         μ_B_i = length(B_i) # μ = measure ## This was the original code instead
        for (j, ξ) in enumerate(aid)
           ## Directly compute the overlap
            overlap = findall(x->x.>0,(basins_pre.==ξ).&(basins_pst.==ξ))
            μ_overlap = 0
            for ind in overlap
                μ_overlap += grid[1][ind[1]]*grid[2][ind[2]]*grid[2][ind[3]]*grid[4][ind[4]]
            end           
            P[i, j] = μ_overlap/μ_B_i
        end
    end
    return P
end

function put_minus_1_at_end!(bid)
    if -1  bid
        sort!(bid)
        popfirst!(bid)
        push!(bid, -1)
    end
end
@awage
Copy link
Contributor

awage commented Jan 30, 2023

Hi! Do you have a reference of what you are trying to achieve? If I understand correctly you want to extend the computation to non-uniform grid steps.

What does this multiplication of grid points implies: grid[1][ind[1]]*grid[2][ind[2]]*grid[2][ind[3]]*grid[4][ind[4]] ? I don't see the relation with the grid step.

@mdepitta
Copy link
Author

mdepitta commented Jan 31, 2023

@awage You are right. Indeed the grid should have been replaced by the equivalent step. Here is a temporary edit. I need to test it yet. Unfortunately, I am running out of memory as mentioned before, and I won't have access to HPC resources before the next three weeks. In my case, I have 4D system, and I am calling the grid steps by d<var_name>. However, if correct, the code below should be easy to generalize to ND systems. Unfortunately, not by my knowledge of the language at the moment.

function my_tipping_probabilities(basins_before, basins_after, grid)
    @assert size(basins_before) == size(basins_after)

    bid, aid = unique.((basins_before, basins_after))
    P = zeros(length(bid), length(aid))
    # Make -1 last entry in bid, aid, if it exists
    put_minus_1_at_end!(bid); put_minus_1_at_end!(aid)
     
    ## We now compute all the differences across all elements of the grid, so that we can compute all voxels accordingly
    ## Compute incremental steps of grid
    dg = diff(grid[1])
    df = diff(grid[2])
    da = diff(grid[3])
    dc = diff(grid[4])
    
    # Notice: the following loops could be optimized with smarter boolean operations,
    # however they are so fast that everything should be done within milliseconds even
    # on a potato
    for (i, ι) in enumerate(bid)
        B_i = findall(isequal(ι), basins_before)
        μ_B_i = 0
        for ind in B_i
            # μ = measure on non-unitary grid
            ## The condition is required because diff is length-1 the original vectors for the state variables upon which grid is defined 
            if (ind[1]<length(dg))&&(ind[2]<length(df))&&(ind[3]<length(da))&&(ind[4]<length(dc))
                μ_B_i += dg[ind[1]]*df[ind[2]]*da[ind[3]]*dc[ind[4]]
            end
        end
#         μ_B_i = length(B_i) # μ = measure
        for (j, ξ) in enumerate(aid)
            overlap = findall(x->x.>0,(basins_pre.==ξ).&(basins_pst.==ξ))
            μ_overlap = 0
            for ind in overlap
                if (ind[1]<length(dg))&&(ind[2]<length(df))&&(ind[3]<length(da))&&(ind[4]<length(dc))
                    μ_overlap += dg[ind[1]]*df[ind[2]]*da[ind[3]]*dc[ind[4]]
                end
            end           
            P[i, j] = μ_overlap/μ_B_i
        end
    end
    return P
end

@awage
Copy link
Contributor

awage commented Feb 1, 2023

I see. I will think of a way to extend the computation to Ndim non uniform grids.

@awage
Copy link
Contributor

awage commented Feb 1, 2023

I have a solution but it is untested and will not work at first. Can you please think of a test unit? I leave the code.

The idea is to restrict the size of the basins (with view) instead of cheking the bounds. Also I have written the computation of the volume using broadcast on the array. Maybe we can discuss about having this feature in the codebase with @Datseris.

Another solution is to have a convention for the element of the grid step missing. In some application the last step is repeated (in filtering for example).

function my_tipping_probabilities(basins_before, basins_after, grid)
    @assert size(basins_before) == size(basins_after)

    bid, aid = unique.((basins_before, basins_after))
    P = zeros(length(bid), length(aid))
    # Make -1 last entry in bid, aid, if it exists
    put_minus_1_at_end!(bid); put_minus_1_at_end!(aid)
     
    ## We now compute all the differences across all elements of the grid, so that we can compute all voxels accordingly
    ## Compute incremental steps of grid
    dg = diff.(grid)
    ng = ntuple(i -> 1:length(grid[i])-1, length(grid))
    bb = view(basins_before, ng...)
    ba = biew(basins_after, ng...)
    # Notice: the following loops could be optimized with smarter boolean operations,
    # however they are so fast that everything should be done within milliseconds even
    # on a potato
    for (i, ι) in enumerate(bid)
        B_i = findall(isequal(ι), bb)
        μ_B_i = 0
        for ind in B_i
            μ_B_i += prod(getindex.(dg, Tuple(ind))) 
        end
        for (j, ξ) in enumerate(aid)
            overlap = findall(x->x.>0,(bb.==ξ).&(ba.==ξ))
            μ_overlap = 0
            for ind in overlap
                    μ_overlap += prod(getindex.(dg, Tuple(ind))) 
            end           
            P[i, j] = μ_overlap/μ_B_i
        end
    end
    return P
end

@awage
Copy link
Contributor

awage commented Feb 2, 2023

Corrected some typos and put a dumb example:

function my_tipping_probabilities(basins_before, basins_after, grid)
    @assert size(basins_before) == size(basins_after)

    bid, aid = unique.((basins_before, basins_after))
    P = zeros(length(bid), length(aid))
    # Make -1 last entry in bid, aid, if it exists
    put_minus_1_at_end!(bid); put_minus_1_at_end!(aid)
     
    ## We now compute all the differences across all elements of the grid, so that we can compute all voxels accordingly
    ## Compute incremental steps of grid
    dg = diff.(grid)
    ng = ntuple(i -> 1:length(grid[i])-1, length(grid))
    bb = view(basins_before, ng...)
    ba = view(basins_after, ng...)
    # Notice: the following loops could be optimized with smarter boolean operations,
    # however they are so fast that everything should be done within milliseconds even
    # on a potato
    for (i, ι) in enumerate(bid)
        B_i = findall(isequal(ι), bb)
        μ_B_i = 0
        for ind in B_i
            μ_B_i += prod(getindex.(dg, Tuple(ind))) 
        end
        for (j, ξ) in enumerate(aid)
            overlap = findall(x->x.>0,(bb.==ξ).&(ba.==ξ))
            μ_overlap = 0
            for ind in overlap
                    μ_overlap += prod(getindex.(dg, Tuple(ind))) 
            end           
            P[i, j] = μ_overlap/μ_B_i
        end
    end
    return P
end


function put_minus_1_at_end!(bid)
    if -1  bid
        sort!(bid)
        popfirst!(bid)
        push!(bid, -1)
    end
end

# Random basins 3 dim grid 
bas1 = rand(1:4,(10,10,10))
bas2 = rand(1:4,(10,10,10))
grid = (1:10, 1:10, 1:10) 

P = my_tipping_probabilities(bas1, bas2, grid)

@mdepitta
Copy link
Author

mdepitta commented Feb 2, 2023

@awage thanks. I need to test it on my data sets to be able to give you consistent feedback. I am currently building the data, but, as I mentioned earlier, I will have access to reasonable computing resources only in a couple of weeks. Thanks for your patience.

@mdepitta
Copy link
Author

@awage Thank you for your patience. I got my laptop stolen and had to rewrite the code from scratch. I am testing your code on dummy data, and it works so far. Unfortunately, I am waiting to build the real attractors for my problem. But I am experiencing some issue that I am reporting in a separate thread.

@mdepitta
Copy link
Author

@awage There is a typo in the computation of the overlapping basin. The overlapping indexes were originally computed by:

overlap = findall(x->x.>0,(bb.==ξ).&(ba.==ξ))

should instead be estimated by:

overlap = findall(x->x.>0,(bb.==ι).&(ba.==ξ))

based on the definition of tipping probabilities by Kaszás, Feudel & Tél. Tipping phenomena in typical dynamical systems subjected to parameter drift. Scientific Reports, 9(1).

@Datseris
Copy link
Member

Yes, you are right, please do a PR that fixes it?

@mdepitta
Copy link
Author

@Datseris I am not sure how to do a PR nor what a PR is. This code is not in the current Attractors.jl since it implements the tipping_probabilities that allows for basins defined on irregularly-spaced grids.

@Datseris
Copy link
Member

okay no worries, I now understand you were referring to the code Alex pasted here, not to the code in the package.

@awage
Copy link
Contributor

awage commented Jun 16, 2023

Thanks! Do you have an example that can be tested?

Also to submit a Pull Request (PR) you need to:

  • fork the repository in your github environment.
  • clone the repository locally on your computer.
  • create a new branch in this repository.
  • push your changes.
  • Submit the PR in the github environment.

There are many youtube videos that will guide you through the process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants