Search code examples
pythonnumpyhungarian-algorithm

Hungarian algorithm in Python for non-square cost matrices


I want to use the Hungarian assignment algorithm in python on a non-square numpy array.

My input matrix X looks like this:

X = np.array([[0.26, 0.64, 0.16, 0.46, 0.5 , 0.63, 0.29],
              [0.49, 0.12, 0.61, 0.28, 0.74, 0.54, 0.25],
              [0.22, 0.44, 0.25, 0.76, 0.28, 0.49, 0.89],
              [0.56, 0.13, 0.45, 0.6 , 0.53, 0.56, 0.05],
              [0.66, 0.24, 0.61, 0.21, 0.47, 0.31, 0.35],
              [0.4 , 0.85, 0.45, 0.14, 0.26, 0.29, 0.24]])

The desired result is the matrix ordered such as X becomes X_desired_output:

X_desired_output = np.array([[0.63, 0.5 , 0.29, 0.46, 0.26, 0.64, 0.16], 
                             [0.54, 0.74, 0.25, 0.28, 0.49, 0.12, 0.61], 
                             [[0.49, 0.28, 0.89, 0.76, 0.22, 0.44, 0.25], 
                             [[0.56, 0.53, 0.05, 0.6 , 0.56, 0.13, 0.45], 
                             [[0.31, 0.47, 0.35, 0.21, 0.66, 0.24, 0.61], 
                             [[0.29, 0.26, 0.24, 0.14, 0.4 , 0.85, 0.45]])

Here I would like to maximize the cost and not minimize so the input to the algorithm would be in theory either 1-X or simply X.

I have found https://software.clapper.org/munkres/ that leads to:

from munkres import Munkres

m = Munkres()
indices = m.compute(-X)

indices
[(0, 5), (1, 4), (2, 6), (3, 3), (4, 0), (5, 1)]

# getting the indices in list format
ii = [i for (i,j) in indices]
jj = [j for (i,j) in indices]

How can I use these to sort X ? jjonly contain 6 elements as opposed to the original 7 columns of X.

I am looking to actually get the matrix sorted.


Solution

  • After spending some hours working on it, I found a solution. The problem was due to the fact that X.shape[1] > X.shape[0], some columns are not assigned at all and this leads to the problem.

    The documentation states that

    "The Munkres algorithm assumes that the cost matrix is square. However, it’s possible to use a rectangular matrix if you first pad it with 0 values to make it square. This module automatically pads rectangular cost matrices to make them square."

    from munkres import Munkres
    
    m = Munkres()
    indices = m.compute(-X)
    
    indices
    [(0, 5), (1, 4), (2, 6), (3, 3), (4, 0), (5, 1)]
    
    # getting the indices in list format
    ii = [i for (i,j) in indices]
    jj = [j for (i,j) in indices]
    
    # re-order matrix
    X_=X[:,jj]  # re-order columns
    X_=X_[ii,:] # re-order rows
    
    # HERE IS THE TRICK: since the X is not diagonal, some columns are not assigned to the rows !
    not_assigned_columns = X[:, [not_assigned for not_assigned in np.arange(X.shape[1]).tolist() if not_assigned not in jj]].reshape(-1,1)
    
    X_desired = np.concatenate((X_, not_assigned_columns), axis=1)
    
    print(X_desired)
    
    array([[0.63, 0.5 , 0.29, 0.46, 0.26, 0.64, 0.16],
           [0.54, 0.74, 0.25, 0.28, 0.49, 0.12, 0.61],
           [0.49, 0.28, 0.89, 0.76, 0.22, 0.44, 0.25],
           [0.56, 0.53, 0.05, 0.6 , 0.56, 0.13, 0.45],
           [0.31, 0.47, 0.35, 0.21, 0.66, 0.24, 0.61],
           [0.29, 0.26, 0.24, 0.14, 0.4 , 0.85, 0.45]])