001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.math.combinatorics.optimisation;
031
032import java.util.Arrays;
033
034import Jama.Matrix;
035
036/* This code is adapted from Kevin L. Stern's implementation of the hungarian 
037 * algorithm, available from:
038 * 
039 * https://github.com/KevinStern/software-and-algorithms/blob/master/src/main/java/blogspot/software_and_algorithms/stern_library/optimization/HungarianAlgorithm.java
040 * 
041 * The original copyright statement is as follows:
042 * 
043 * Copyright (c) 2012 Kevin L. Stern
044 * 
045 * Permission is hereby granted, free of charge, to any person obtaining a copy
046 * of this software and associated documentation files (the "Software"), to deal
047 * in the Software without restriction, including without limitation the rights
048 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
049 * copies of the Software, and to permit persons to whom the Software is
050 * furnished to do so, subject to the following conditions:
051 * 
052 * The above copyright notice and this permission notice shall be included in
053 * all copies or substantial portions of the Software.
054 * 
055 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
056 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
057 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
058 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
059 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
060 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
061 * SOFTWARE.
062 */
063
064/**
065 * An implementation of the Hungarian algorithm for solving the assignment
066 * problem. An instance of the assignment problem consists of a number of
067 * workers along with a number of jobs and a cost matrix which gives the cost of
068 * assigning the i'th worker to the j'th job at position (i, j). The goal is to
069 * find an assignment of workers to jobs so that no job is assigned more than
070 * one worker and so that no worker is assigned to more than one job in such a
071 * manner so as to minimize the total cost of completing the jobs.
072 * <p>
073 * An assignment for a cost matrix that has more workers than jobs will
074 * necessarily include unassigned workers, indicated by an assignment value of
075 * -1; in no other circumstance will there be unassigned workers. Similarly, an
076 * assignment for a cost matrix that has more jobs than workers will necessarily
077 * include unassigned jobs; in no other circumstance will there be unassigned
078 * jobs. For completeness, an assignment for a square cost matrix will give
079 * exactly one unique worker to each job.
080 * <p>
081 * This version of the Hungarian algorithm runs in time O(n^3), where n is the
082 * maximum among the number of workers and the number of jobs.
083 * 
084 * @author Kevin L. Stern
085 */
086public class HungarianAlgorithm {
087        private final double[][] costMatrix;
088        private final int rows, cols, dim;
089        private final double[] labelByWorker, labelByJob;
090        private final int[] minSlackWorkerByJob;
091        private final double[] minSlackValueByJob;
092        private final int[] matchJobByWorker, matchWorkerByJob;
093        private final int[] parentWorkerByCommittedJob;
094        private final boolean[] committedWorkers;
095
096        /**
097         * Construct an instance of the algorithm.
098         * 
099         * @param costMatrix
100         *            the cost matrix, where matrix[i][j] holds the cost of
101         *            assigning worker i to job j, for all i, j. The cost matrix
102         *            must not be irregular in the sense that all rows must be the
103         *            same length.
104         */
105        public HungarianAlgorithm(Matrix costMatrix) {
106                this(costMatrix.getArray());
107        }
108
109        /**
110         * Construct an instance of the algorithm.
111         * 
112         * @param costMatrix
113         *            the cost matrix, where matrix[i][j] holds the cost of
114         *            assigning worker i to job j, for all i, j. The cost matrix
115         *            must not be irregular in the sense that all rows must be the
116         *            same length.
117         */
118        public HungarianAlgorithm(double[][] costMatrix) {
119                this.dim = Math.max(costMatrix.length, costMatrix[0].length);
120                this.rows = costMatrix.length;
121                this.cols = costMatrix[0].length;
122                this.costMatrix = new double[this.dim][this.dim];
123                for (int w = 0; w < this.dim; w++) {
124                        if (w < costMatrix.length) {
125                                if (costMatrix[w].length != this.cols) {
126                                        throw new IllegalArgumentException("Irregular cost matrix");
127                                }
128                                this.costMatrix[w] = Arrays.copyOf(costMatrix[w], this.dim);
129                        } else {
130                                this.costMatrix[w] = new double[this.dim];
131                        }
132                }
133                labelByWorker = new double[this.dim];
134                labelByJob = new double[this.dim];
135                minSlackWorkerByJob = new int[this.dim];
136                minSlackValueByJob = new double[this.dim];
137                committedWorkers = new boolean[this.dim];
138                parentWorkerByCommittedJob = new int[this.dim];
139                matchJobByWorker = new int[this.dim];
140                Arrays.fill(matchJobByWorker, -1);
141                matchWorkerByJob = new int[this.dim];
142                Arrays.fill(matchWorkerByJob, -1);
143        }
144
145        /**
146         * Compute an initial feasible solution by assigning zero labels to the
147         * workers and by assigning to each job a label equal to the minimum cost
148         * among its incident edges.
149         */
150        protected void computeInitialFeasibleSolution() {
151                for (int j = 0; j < dim; j++) {
152                        labelByJob[j] = Double.POSITIVE_INFINITY;
153                }
154                for (int w = 0; w < dim; w++) {
155                        for (int j = 0; j < dim; j++) {
156                                if (costMatrix[w][j] < labelByJob[j]) {
157                                        labelByJob[j] = costMatrix[w][j];
158                                }
159                        }
160                }
161        }
162
163        /**
164         * Execute the algorithm.
165         * 
166         * @return the minimum cost matching of workers to jobs based upon the
167         *         provided cost matrix. A matching value of -1 indicates that the
168         *         corresponding worker is unassigned.
169         */
170        public int[] execute() {
171                /*
172                 * Heuristics to improve performance: Reduce rows and columns by their
173                 * smallest element, compute an initial non-zero dual feasible solution
174                 * and create a greedy matching from workers to jobs of the cost matrix.
175                 */
176                reduce();
177                computeInitialFeasibleSolution();
178                greedyMatch();
179
180                int w = fetchUnmatchedWorker();
181                while (w < dim) {
182                        initializePhase(w);
183                        executePhase();
184                        w = fetchUnmatchedWorker();
185                }
186                final int[] result = Arrays.copyOf(matchJobByWorker, rows);
187                for (w = 0; w < result.length; w++) {
188                        if (result[w] >= cols) {
189                                result[w] = -1;
190                        }
191                }
192                return result;
193        }
194
195        /**
196         * Execute a single phase of the algorithm. A phase of the Hungarian
197         * algorithm consists of building a set of committed workers and a set of
198         * committed jobs from a root unmatched worker by following alternating
199         * unmatched/matched zero-slack edges. If an unmatched job is encountered,
200         * then an augmenting path has been found and the matching is grown. If the
201         * connected zero-slack edges have been exhausted, the labels of committed
202         * workers are increased by the minimum slack among committed workers and
203         * non-committed jobs to create more zero-slack edges (the labels of
204         * committed jobs are simultaneously decreased by the same amount in order
205         * to maintain a feasible labeling).
206         * <p>
207         * 
208         * The runtime of a single phase of the algorithm is O(n^2), where n is the
209         * dimension of the internal square cost matrix, since each edge is visited
210         * at most once and since increasing the labeling is accomplished in time
211         * O(n) by maintaining the minimum slack values among non-committed jobs.
212         * When a phase completes, the matching will have increased in size.
213         */
214        protected void executePhase() {
215                while (true) {
216                        int minSlackWorker = -1, minSlackJob = -1;
217                        double minSlackValue = Double.POSITIVE_INFINITY;
218                        for (int j = 0; j < dim; j++) {
219                                if (parentWorkerByCommittedJob[j] == -1) {
220                                        if (minSlackValueByJob[j] < minSlackValue) {
221                                                minSlackValue = minSlackValueByJob[j];
222                                                minSlackWorker = minSlackWorkerByJob[j];
223                                                minSlackJob = j;
224                                        }
225                                }
226                        }
227                        if (minSlackValue > 0) {
228                                updateLabeling(minSlackValue);
229                        }
230                        parentWorkerByCommittedJob[minSlackJob] = minSlackWorker;
231                        if (matchWorkerByJob[minSlackJob] == -1) {
232                                /*
233                                 * An augmenting path has been found.
234                                 */
235                                int committedJob = minSlackJob;
236                                int parentWorker = parentWorkerByCommittedJob[committedJob];
237                                while (true) {
238                                        final int temp = matchJobByWorker[parentWorker];
239                                        match(parentWorker, committedJob);
240                                        committedJob = temp;
241                                        if (committedJob == -1) {
242                                                break;
243                                        }
244                                        parentWorker = parentWorkerByCommittedJob[committedJob];
245                                }
246                                return;
247                        } else {
248                                /*
249                                 * Update slack values since we increased the size of the
250                                 * committed workers set.
251                                 */
252                                final int worker = matchWorkerByJob[minSlackJob];
253                                committedWorkers[worker] = true;
254                                for (int j = 0; j < dim; j++) {
255                                        if (parentWorkerByCommittedJob[j] == -1) {
256                                                final double slack = costMatrix[worker][j]
257                                                                - labelByWorker[worker] - labelByJob[j];
258                                                if (minSlackValueByJob[j] > slack) {
259                                                        minSlackValueByJob[j] = slack;
260                                                        minSlackWorkerByJob[j] = worker;
261                                                }
262                                        }
263                                }
264                        }
265                }
266        }
267
268        /**
269         * 
270         * @return the first unmatched worker or {@link #dim} if none.
271         */
272        protected int fetchUnmatchedWorker() {
273                int w;
274                for (w = 0; w < dim; w++) {
275                        if (matchJobByWorker[w] == -1) {
276                                break;
277                        }
278                }
279                return w;
280        }
281
282        /**
283         * Find a valid matching by greedily selecting among zero-cost matchings.
284         * This is a heuristic to jump-start the augmentation algorithm.
285         */
286        protected void greedyMatch() {
287                for (int w = 0; w < dim; w++) {
288                        for (int j = 0; j < dim; j++) {
289                                if (matchJobByWorker[w] == -1
290                                                && matchWorkerByJob[j] == -1
291                                                && costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0)
292                                {
293                                        match(w, j);
294                                }
295                        }
296                }
297        }
298
299        /**
300         * Initialize the next phase of the algorithm by clearing the committed
301         * workers and jobs sets and by initializing the slack arrays to the values
302         * corresponding to the specified root worker.
303         * 
304         * @param w
305         *            the worker at which to root the next phase.
306         */
307        protected void initializePhase(int w) {
308                Arrays.fill(committedWorkers, false);
309                Arrays.fill(parentWorkerByCommittedJob, -1);
310                committedWorkers[w] = true;
311                for (int j = 0; j < dim; j++) {
312                        minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w]
313                                        - labelByJob[j];
314                        minSlackWorkerByJob[j] = w;
315                }
316        }
317
318        /**
319         * Helper method to record a matching between worker w and job j.
320         */
321        protected void match(int w, int j) {
322                matchJobByWorker[w] = j;
323                matchWorkerByJob[j] = w;
324        }
325
326        /**
327         * Reduce the cost matrix by subtracting the smallest element of each row
328         * from all elements of the row as well as the smallest element of each
329         * column from all elements of the column. Note that an optimal assignment
330         * for a reduced cost matrix is optimal for the original cost matrix.
331         */
332        protected void reduce() {
333                for (int w = 0; w < dim; w++) {
334                        double min = Double.POSITIVE_INFINITY;
335                        for (int j = 0; j < dim; j++) {
336                                if (costMatrix[w][j] < min) {
337                                        min = costMatrix[w][j];
338                                }
339                        }
340                        for (int j = 0; j < dim; j++) {
341                                costMatrix[w][j] -= min;
342                        }
343                }
344                final double[] min = new double[dim];
345                for (int j = 0; j < dim; j++) {
346                        min[j] = Double.POSITIVE_INFINITY;
347                }
348                for (int w = 0; w < dim; w++) {
349                        for (int j = 0; j < dim; j++) {
350                                if (costMatrix[w][j] < min[j]) {
351                                        min[j] = costMatrix[w][j];
352                                }
353                        }
354                }
355                for (int w = 0; w < dim; w++) {
356                        for (int j = 0; j < dim; j++) {
357                                costMatrix[w][j] -= min[j];
358                        }
359                }
360        }
361
362        /**
363         * Update labels with the specified slack by adding the slack value for
364         * committed workers and by subtracting the slack value for committed jobs.
365         * In addition, update the minimum slack values appropriately.
366         */
367        protected void updateLabeling(double slack) {
368                for (int w = 0; w < dim; w++) {
369                        if (committedWorkers[w]) {
370                                labelByWorker[w] += slack;
371                        }
372                }
373                for (int j = 0; j < dim; j++) {
374                        if (parentWorkerByCommittedJob[j] != -1) {
375                                labelByJob[j] -= slack;
376                        } else {
377                                minSlackValueByJob[j] -= slack;
378                        }
379                }
380        }
381}