/* ==========================================
 * JGraphT : a free Java graph-theory library
 * ==========================================
 *
 * Project Info:  http://org.org.jgrapht.sourceforge.net/
 * Project Creator:  Barak Naveh (http://sourceforge.net/users/barak_naveh)
 *
 * (C) Copyright 2003-2013, by Barak Naveh and Contributors.
 *
 * This program and the accompanying materials are dual-licensed under
 * either
 *
 * (a) the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation, or (at your option) any
 * later version.
 *
 * or (per the licensee's choosing)
 *
 * (b) the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation.
 */
/* -------------------------
 * KuhnMunkresMinimalWeightBipartitePerfectMatching.java
 * -------------------------
 *
 * Original Author:  Alexey Kudinkin
 * Contributor(s):
 *
 */
package org.jgrapht.alg;

import java.util.*;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;


/**
 * Kuhn-Munkres algorithm (named in honor of Harold Kuhn and James Munkres)
 * solving <i>assignment problem</i> also known as <a
 * href=http://en.wikipedia.org/wiki/Hungarian_algorithm>hungarian algorithm</a>
 * (in the honor of hungarian mathematicians Dénes K?nig and Jen? Egerváry).
 * It's running time O(V^3).
 *
 * <p><i>Assignment problem</i> could be set as follows:
 *
 * <p>Given <a href=http://en.wikipedia.org/wiki/Complete_bipartite_graph>
 * complete bipartite graph</a> G = (S, T; E), such that |S| = |T|, and each
 * edge has <i>non-negative</i> cost <i>c(i, j)</i>, find <i>perfect</i>
 * matching of <i>minimal cost<i/>.</p>
 * </p>
 *
 * @author Alexey Kudinkin
 */
public class KuhnMunkresMinimalWeightBipartitePerfectMatching<V, E>
    implements WeightedMatchingAlgorithm<V, E>
{
    

    /////////////////////////////////////////////////////////////////////////////////////////////////

    private final WeightedGraph<V, E> graph;

    private final List<? extends V> firstPartition;
    private final List<? extends V> secondPartition;

    private final int [] matching;

    

    /**
     * @param G target weighted bipartite graph to find matching in
     * @param S first vertex partition of the target bipartite graph
     * @param T second vertex partition of the target bipartite graph
     */
    public KuhnMunkresMinimalWeightBipartitePerfectMatching(
        final WeightedGraph<V, E> G,
        List<? extends V> S,
        List<? extends V> T)
    {
        // Validate graph being complete bipartite with equally-sized partitions

        if (S.size() != T.size()) {
            throw new IllegalArgumentException(
                "Graph supplied isn't complete bipartite with equally sized partitions!");
        }

        int partition = S.size(), edges = G.edgeSet().size();

        if (edges != (partition * partition)) {
            throw new IllegalArgumentException(
                "Graph supplied isn't complete bipartite with equally sized partitions!");
        }

        graph = G;
        firstPartition = S;
        secondPartition = T;

        matching =
            new KuhnMunkresMatrixImplementation<V, E>(G, S, T).buildMatching();
    }

    

    @Override public Set<E> getMatching()
    {
        Set<E> edges = new HashSet<E>();

        for (int i = 0; i < matching.length; ++i) {
            edges.add(
                graph.getEdge(
                    firstPartition.get(i),
                    secondPartition.get(matching[i])));
        }

        return edges;
    }

    @Override public double getMatchingWeight()
    {
        double weight = 0.;

        for (E edge : getMatching()) {
            weight += graph.getEdgeWeight(edge);
        }

        return weight;
    }

    

    /**
     * ...
     */
    protected static class KuhnMunkresMatrixImplementation<V, E>
    {
        /**
         * Cost matrix
         */
        private double [][] costMatrix;

        /**
         * Excess matrix
         */
        private double [][] excessMatrix;

        /**
         * Rows coverage bit-mask
         */
        boolean [] rowsCovered;

        /**
         * Columns coverage bit-mask
         */
        boolean [] columnsCovered;

        /**
         * ``columnMatched[i]'' is the column # of the ZERO matched at the i-th
         * row
         */
        private int [] columnMatched;

        /**
         * ``rowMatched[j]'' is the row # of the ZERO matched at the j-th column
         */
        private int [] rowMatched;

        public KuhnMunkresMatrixImplementation(
            final WeightedGraph<V, E> G,
            final List<? extends V> S,
            final List<? extends V> T)
        {
            int partition = S.size();

            // Build an excess-matrix corresponding to the supplied weighted
            // complete bipartite graph

            costMatrix = new double[partition][];

            for (int i = 0; i < S.size(); ++i) {
                V source = S.get(i);
                costMatrix[i] = new double[partition];
                for (int j = 0; j < T.size(); ++j) {
                    V target = T.get(j);
                    if (source.equals(target)) {
                        continue;
                    }
                    costMatrix[i][j] =
                        G.getEdgeWeight(G.getEdge(source, target));
                }
            }
        }

        /**
         * Gets costs-matrix as input and returns assignment of tasks
         * (designated by the columns of cost-matrix) to the workers (designated
         * by the rows of the cost-matrix) so that to MINIMIZE total
         * tasks-tackling costs
         */
        protected int [] buildMatching()
        {
            int height = costMatrix.length, width = costMatrix[0].length;

            // Make an excess-matrix
            excessMatrix = makeExcessMatrix();

            // Build row/column coverage
            rowsCovered = new boolean[height];
            columnsCovered = new boolean[width];

            // Cached values of zeroes' indices in particular columns/rows
            columnMatched = new int[height];
            rowMatched = new int[width];

            Arrays.fill(columnMatched, -1);
            Arrays.fill(rowMatched, -1);

            // Find perfect matching corresponding to the optimal assignment

            while (buildMaximalMatching() < width) {
                buildVertexCoverage();
                extendEqualityGraph();
            }

            // Almost done!

            return Arrays.copyOf(columnMatched, height);
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Composes excess-matrix corresponding to the given cost-matrix
         */
        double [][] makeExcessMatrix()
        {
            double [][] excessMatrix = new double[costMatrix.length][];

            for (int i = 0; i < excessMatrix.length; ++i) {
                excessMatrix[i] =
                    Arrays.copyOf(costMatrix[i], costMatrix[i].length);
            }

            // Subtract minimal costs across the rows

            for (int i = 0; i < excessMatrix.length; ++i) {
                double cheapestTaskCost = Double.MAX_VALUE;

                for (int j = 0; j < excessMatrix[i].length; ++j) {
                    if (cheapestTaskCost > excessMatrix[i][j]) {
                        cheapestTaskCost = excessMatrix[i][j];
                    }
                }

                for (int j = 0; j < excessMatrix[i].length; ++j) {
                    excessMatrix[i][j] -= cheapestTaskCost;
                }
            }

            // Subtract minimal costs across the columns
            //
            // NOTE:  Makes nothing if there is any worker that can more
            // (cost-)effectively tackle this task than any other, i.e. there
            // is any row having zero in this column. However, if there is no
            // one, reduce the cost-demands of each worker to the size of the
            // min-cost (we can easily do this, since we have particular
            // interest of the relative values only), i.e. subtract the value
            // of the minimum cost-demands to highlight (with zero) the
            // worker that can tackle this task demanding lowest reward.

            for (int j = 0; j < excessMatrix[0].length; ++j) {
                double cheapestWorkerCost = Double.MAX_VALUE;

                for (int i = 0; i < excessMatrix.length; ++i) {
                    if (cheapestWorkerCost > excessMatrix[i][j]) {
                        cheapestWorkerCost = excessMatrix[i][j];
                    }
                }

                for (int i = 0; i < excessMatrix.length; ++i) {
                    excessMatrix[i][j] -= cheapestWorkerCost;
                }
            }

            return excessMatrix;
        }

        /**
         * Builds maximal matching corresponding to the given excess-matrix
         *
         * @return size of a maximal matching built
         */
        int buildMaximalMatching()
        {
            // Match all zeroes non-staying in the same column/row

            int matchingSizeLowerBound = 0;

            for (int i = 0; i < columnMatched.length; ++i) {
                if (columnMatched[i] != -1) {
                    ++matchingSizeLowerBound;
                }
            }

            // Compose first-approximation by matching zeroes in a greed fashion

            for (int j = 0; j < excessMatrix[0].length; ++j) {
                if (rowMatched[j] == -1) {
                    for (int i = 0; i < excessMatrix.length; ++i) {
                        if ((excessMatrix[i][j] == 0)
                            && (columnMatched[i] == -1))
                        {
                            ++matchingSizeLowerBound;
                            columnMatched[i] = j;
                            rowMatched[j] = i;
                            break;
                        }
                    }
                }
            }

            // Check whether perfect matching is found

            if (matchingSizeLowerBound == excessMatrix[0].length) {
                return matchingSizeLowerBound;
            }

            //
            //As to E. Burge: Matching is maximal iff bipartite graph doesn't
            //contain any matching-augmenting paths.
            //
            //Try to find any match-augmenting path

            boolean [] rowsVisited = new boolean[excessMatrix.length];
            boolean [] colsVisited = new boolean[excessMatrix[0].length];

            int matchingSize = 0;

            boolean extending = true;

            while (extending && (matchingSize < excessMatrix.length)) {
                Arrays.fill(rowsVisited, false);
                Arrays.fill(colsVisited, false);

                extending = false;

                for (int j = 0; j < excessMatrix.length; ++j) {
                    if ((rowMatched[j] == -1) && !colsVisited[j]) {
                        extending |=
                            new MatchExtender(rowsVisited, colsVisited).extend(
                                j); /* Try to extend matching */
                    }
                }

                matchingSize = 0;

                for (int j = 0; j < rowMatched.length; ++j) {
                    if (rowMatched[j] != -1) {
                        ++matchingSize;
                    }
                }
            }

            return matchingSize;
        }

        /**
         * Builds vertex-cover given built up matching
         */
        void buildVertexCoverage()
        {
            Arrays.fill(columnsCovered, false);
            Arrays.fill(rowsCovered, false);

            boolean [] invertible = new boolean[rowsCovered.length];

            for (int i = 0; i < excessMatrix.length; ++i) {
                if (columnMatched[i] != -1) {
                    invertible[i] = true;
                    continue;
                }

                for (int j = 0; j < excessMatrix[i].length; ++j) {
                    if (Double.valueOf(excessMatrix[i][j]).compareTo(0.) == 0) {
                        rowsCovered[i] = invertible[i] = true;
                        break;
                    }
                }
            }

            boolean cont = true;

            while (cont) {
                for (int i = 0; i < excessMatrix.length; ++i) {
                    if (rowsCovered[i]) {
                        for (int j = 0; j < excessMatrix[i].length; ++j) {
                            if ((Double.valueOf(excessMatrix[i][j]).compareTo(
                                        0.) == 0)
                                && !columnsCovered[j])
                            {
                                columnsCovered[j] = true;
                            }
                        }
                    }
                }

                // Shall we continue?

                cont = false;

                for (int j = 0; j < columnsCovered.length; ++j) {
                    if (columnsCovered[j] && (rowMatched[j] != -1)) {
                        if (!rowsCovered[rowMatched[j]]) {
                            cont = true;
                            rowsCovered[rowMatched[j]] = true;
                        }
                    }
                }
            }

            // Invert covered rows selection

            for (int i = 0; i < rowsCovered.length; ++i) {
                if (invertible[i]) {
                    rowsCovered[i] ^= true;
                }
            }

            assert uncovered(excessMatrix, rowsCovered, columnsCovered) == 0;
            assert minimal(rowMatched, rowsCovered, columnsCovered);
        }

        /**
         * Extends equality-graph subtracting minimal excess from all the
         * COLUMNS UNCOVERED and adding it to the all ROWS COVERED
         */
        void extendEqualityGraph()
        {
            double minExcess = Double.MAX_VALUE;

            for (int i = 0; i < excessMatrix.length; ++i) {
                if (rowsCovered[i]) {
                    continue;
                }
                for (int j = 0; j < excessMatrix[i].length; ++j) {
                    if (columnsCovered[j]) {
                        continue;
                    }
                    if (minExcess > excessMatrix[i][j]) {
                        minExcess = excessMatrix[i][j];
                    }
                }
            }

            // Add up to all elements of the COVERED ROWS

            for (int i = 0; i < excessMatrix.length; ++i) {
                if (!rowsCovered[i]) {
                    continue;
                }
                for (int j = 0; j < excessMatrix[i].length; ++j) {
                    excessMatrix[i][j] += minExcess;
                }
            }

            // Subtract from all elements of the UNCOVERED COLUMNS

            for (int j = 0; j < excessMatrix[0].length; ++j) {
                if (columnsCovered[j]) {
                    continue;
                }
                for (int i = 0; i < excessMatrix.length; ++i) {
                    excessMatrix[i][j] -= minExcess;
                }
            }
        }

        /**
         * Assures given column-n-rows-coverage/zero-matching to be
         * minimal/maximal
         *
         * @param match zero-matching to check
         * @param rowsCovered rows coverage to check
         * @param colsCovered columns coverage to check
         *
         * @return true if given matching and coverage are maximal and minimal
         * respectively
         */
        private static boolean minimal(
            final int [] match,
            final boolean [] rowsCovered,
            final boolean [] colsCovered)
        {
            int matched = 0;
            for (int i = 0; i < match.length; ++i) {
                if (match[i] != -1) {
                    ++matched;
                }
            }

            int covered = 0;
            for (int i = 0; i < rowsCovered.length; ++i) {
                if (rowsCovered[i]) {
                    ++covered;
                }
                if (colsCovered[i]) {
                    ++covered;
                }
            }

            return matched == covered;
        }

        /**
         * Accounts for zeroes being uncovered
         *
         * @param excessMatrix target excess-matrix
         * @param rowsCovered rows coverage to check
         * @param colsCovered columns coverage to check
         */
        private static int uncovered(
            final double [][] excessMatrix,
            final boolean [] rowsCovered,
            final boolean [] colsCovered)
        {
            int uncoveredZero = 0;

            for (int i = 0; i < excessMatrix.length; ++i) {
                if (rowsCovered[i]) {
                    continue;
                }
                for (int j = 0; j < excessMatrix[i].length; ++j) {
                    if (colsCovered[j]) {
                        continue;
                    }
                    if (Double.valueOf(excessMatrix[i][j]).compareTo(0.) == 0) {
                        ++uncoveredZero;
                    }
                }
            }

            return uncoveredZero;
        }

        /**
         * Aggregates utilities to extend matching
         */
        protected class MatchExtender
        {
            private final boolean [] rowsVisited;
            private final boolean [] colsVisited;

            private MatchExtender(
                final boolean [] rowsVisited,
                final boolean [] colsVisited)
            {
                this.rowsVisited = rowsVisited;
                this.colsVisited = colsVisited;
            }

            /**
             * Performs DFS to seek after matching-augmenting path starting at
             * the initial-vertex
             *
             * @return true when some augmenting-path found, false otherwise
             */
            public boolean extend(int initialCol)
            {
                return extendMatchingEL(initialCol);
            }

            /**
             * DFS helper #1 (applicable for ODD-LENGTH paths ONLY)
             *
             * @param pathTailRow row # of tail of the matching-augmenting path
             * @param pathTailCol column # of tail of the matching-augmenting
             * path
             *
             * @return true if matching-augmenting path found, false otherwise
             */
            private boolean extendMatchingOL(int pathTailRow, int pathTailCol)
            {
                // Seek after already matched zero

                // Check whether row occupied by the 'tail' vertex isn't matched

                if (columnMatched[pathTailRow] == -1) {
                    // There is no way to continue: mark zero as matched,
                    // trigger along-side flipping
                    columnMatched[pathTailRow] = pathTailCol;
                    rowMatched[pathTailCol] = pathTailRow;

                    return true;
                } else {
                    // Add next matched zero: mark current vertex as "visited"
                    rowsVisited[pathTailRow] = true;

                    // Check whether we can proceed
                    if (colsVisited[columnMatched[pathTailRow]]) {
                        return false;
                    }

                    boolean extending =
                        extendMatchingEL(
                            columnMatched[pathTailRow]);

                    if (extending) {
                        columnMatched[pathTailRow] = pathTailCol;
                        rowMatched[pathTailCol] = pathTailRow;
                    }

                    return extending;
                }
            }

            /**
             * DFS helper #1 (applicable for ODD-LENGTH paths ONLY)
             *
             * @param pathTailCol column # of tail of the matching-augmenting
             * path
             *
             * @return true if matching-augmenting path found, false otherwise
             */
            private boolean extendMatchingEL(int pathTailCol)
            {
                colsVisited[pathTailCol] = true;

                for (int i = 0; i < excessMatrix.length; ++i) {
                    if ((excessMatrix[i][pathTailCol] == 0)
                        && !rowsVisited[i])
                    {
                        boolean extending =
                            extendMatchingOL(
                                i, // New tail to continue
                                pathTailCol //
                                );
                        if (extending) {
                            return true;
                        }
                    }
                }

                return false;
            }
        }
    }
}

// End KuhnMunkresMinimalWeightBipartitePerfectMatching.java
