performancejuliaoperations-research

Finding overlapping areas in Julia grid system performance question


I am currently attempting to calculate a specific type of set of locations within a grid. The problem at hand is related to a 2D packing optimization problem.

When packing items, I need to select a position (i,j) in the grid for the item. Due to the item size, several other positions in the grid could also be covered. Therefore I am in need of a set Qijc, which is the set of squares were placing an item c may cover position (i,j). For example if item c is sizes 2x2 the Qijc contains {(i,j),(i-1,j-1),(i-1,j),(i,j-1)}. If item c is in any of these positions.. position (i,j) is also covered.

I have the current working code. However, it brute forces the calculation and is extremely slow when hitting grid sizes of 150x400. I have been trying to optimize the code, and I believe that I am missing something simple. Any suggestions would be appreciated.

Currently, I store the information in a dictionary in order to have a "one-to-many" relationship. Another caveat to the problem is that I only store positions for item c, that would cover position (i,j) if the position is available in the network for item c.

In the given example, if item c = 3, and we are looking at position (4,3) the positions that would cover is (4,2) and (4,3), however, position (5,3) would also usually cover (4,3), however, is not stored as item c cannot go to this position. ( I hope this makes sense)

I believe that I have several redundant loops, and that it could be done in a much faster fashion, however, I struggle to wrap my head around it.

function validpositions(UnusuableSpace::Matrix{Int64}, nCargoes::Int64, SquaresL::Vector{Int64}, SquaresW::Vector{Int64})
    # This function should return all position that are valid in the layout for
    # cargo c in C. This should be returned as a tuple e.g. pos = [(1,1),(4,3),..,(18,1)].
    N = []
    for c in 1:nCargoesM
        pos = Vector{Tuple{Int64,Int64}}()

        for i in SquaresL[c]:size(UnusuableSpace)[1] # skip some of the rows. If the cargo has dimensions > 1.
            for j in 1:size(UnusuableSpace)[2]
                # Check if position includes a pillar / object
                if UnusuableSpace[i,j] != 1
                    # Check if these is space for the cargo in the position.
                    if (i-SquaresL[c]) >= 0 && (j+SquaresW[c]-1) <= size(UnusuableSpace)[2]
                        # Check if position i,j covers an are with pillars in due to cargo
                        # dimensions.
                        if sum(UnusuableSpace[i-SquaresL[c]+1:i,j:j+SquaresW[c]-1]) == 0
                            push!(pos,(i,j))
                        end
                    end
                end
            end

        end
        push!(N,pos)
    end
    # return all valid positions of the cargo c.
    return N
end # end valid position function





nCargoesM = 3
SquaresL = [1,2,3]
SquaresW = [1,2,2]


UnusableSpace = [
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 1 1 0 1 0 0 0
0 0 1 1 1 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0]

N = validpositions(UnusableSpace, nCargoesM, SquaresL, SquaresW)


function coveringSet(UnusableSpace::Matrix{Int64}, nCargoes::Int64, SquaresL::Vector{Int64}, SquaresW::Vector{Int64}, N)
    Qijc = Dict{}()
    for c in 1:nCargoesM
        for i in 1:size(UnusableSpace)[1]
            for j in 1:size(UnusableSpace)[2]
                tmpset = []
                for (k, l) in N[c]
                    # Get the points/nodes of the areas to check
                    vec1 = [i, j]
                    vec2 = [k-SquaresL[c]+1:k, l:l+SquaresW[c]-1]

                    tmp = [[x, y] for x in vec1[1], y in vec1[2]]
                    tpm = [[x, y] for x in vec2[1], y in vec2[2]]

                    # Check for overlapping
                    if sum([tmp in tpm for tmp = tmp]) > 0
                        push!(tmpset, [k, l])
                    end
                end
                push!(Qijc, [i, j, c] => tmpset)
            end
        end
    end
    return Qijc
end



Qijc = coveringSet(UnusableSpace, nCargoesM, SquaresL, SquaresW, N)



Solution

  • I actually solved this problem myself after some time. You can precompute all the keys in the dictionary and thread the loop. Checking the positioning of the cargo and whether it is a spot that is allowable.

    function coveringSet_new_threaded(UnusableSpace, nCargoesM::Int64, SquaresL::Vector{Int64}, SquaresW::Vector{Int64})
        # Create dictionary
        Qijc4 = Dict{}()
    
        for c in 1:nCargoesM
            for i in 1:size(UnusableSpace)[1]
                for j in 1:size(UnusableSpace)[2]
                    push!(Qijc4, [i, j, c] => [])
                end
            end
        end
    
        # Finding the combinations of cargo size
        combinationsCargo = Vector{Tuple{Int64,Int64}}()
        for i in 1:length(SquaresL)
            push!(combinationsCargo, (SquaresL[i], SquaresW[i]))
        end
    
        # The unique combinations available
        CargoTypes = unique(combinationsCargo)
    
    
        Threads.@threads for c in 1:length(CargoTypes)
            typeindx = findall(x -> x == CargoTypes[c], combinationsCargo)
            # Index to control where to find information regarding squaresL and squaresW
            sizeidx = typeindx[1]
    
            #Threads.@threads for c in 1:nCargoesM
            for i in 1:size(UnusableSpace)[1]
                for j in 1:size(UnusableSpace)[2]
                    tmpset = []
    
                    # Generate the range of positions to check. (h,m) check if it also covers (i,j)
                    # due to cargo size.
                    for h in i:i+SquaresW[sizeidx]-1
                        for m in j-SquaresL[sizeidx]+1:j
                            # Check that the position is actually within the layout of the ship
                            if (h > 0 && m > 0 && h <= size(UnusableSpace)[1] && h <= size(UnusableSpace)[2])
                                # Check that the position is not unusable.
                                if UnusableSpace[h, m] != 1
                                    # Check that the position given cargo size is withon the layout of the ship.
                                    if (h - SquaresW[sizeidx]) >= 0 && (m + SquaresL[sizeidx] - 1) <= size(UnusableSpace)[2]
                                        # Check that the cargo, when in position (h,m) does not collide with any unusable space.
                                        if sum(UnusableSpace[h-SquaresW[sizeidx]+1:h, m:m+SquaresL[sizeidx]-1]) == 0
                                            # Only then is it a position for cargo c that covers (i,j).
                                            push!(tmpset, [h, m])
                                        end
                                    end
                                end
                            end
                        end
                    end
                    # Push in set for that position. It may be empty.
                    for k in typeindx
                        Qijc4[[i, j, k]] = tmpset
                    end
                end
            end
        end
    
        return Qijc4
    end