1 module nxt.hungarian;
2 
3 version (none):					/+ TODO: activate +/
4 
5 /// Origin: http://fantascienza.net/leonardo/so/hungarian.d.
6 
7 /* Copyright (c) 2012 Kevin L. Stern
8  *
9  * Permission is hereby granted, free of charge, to any person obtaining a copy
10  * of this software and associated documentation files (the "Software"), to deal
11  * in the Software without restriction, including without limitation the rights
12  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the Software is
14  * furnished to do so, subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be included in
17  * all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25  * SOFTWARE.
26  */
27 
28 /**
29  * An implementation of the Hungarian algorithm for solving the assignment
30  * problem. An instance of the assignment problem consists of a number of
31  * workers along with a number of jobs and a cost matrix which gives the cost of
32  * assigning the i'th worker to the j'th job at position (i, j). The goal is to
33  * find an assignment of workers to jobs so that no job is assigned more than
34  * one worker and so that no worker is assigned to more than one job in such a
35  * manner so as to minimize the total cost of completing the jobs.
36  * <p>
37  *
38  * An assignment for a cost matrix that has more workers than jobs will
39  * necessarily include unassigned workers, indicated by an assignment value of
40  * -1; in no other circumstance will there be unassigned workers. Similarly, an
41  * assignment for a cost matrix that has more jobs than workers will necessarily
42  * include unassigned jobs; in no other circumstance will there be unassigned
43  * jobs. For completeness, an assignment for a square cost matrix will give
44  * exactly one unique worker to each job.
45  *
46  * This version of the Hungarian algorithm runs in time O(n^3), where n is the
47  * maximum among the number of workers and the number of jobs.
48  *
49  * @author Kevin L. Stern
50  *
51  * Ported to D language, Oct 8 2012 V.1.2, by leonardo maffi.
52  */
53 struct HungarianAlgorithm(T) {
54 	import std.algorithm: max;
55 	import std.traits: isFloatingPoint;
56 
57 	static if (isFloatingPoint!T)
58 		enum T infinity = T.infinity;
59 	else
60 		enum T infinity = T.max; // Doesn't work with Bigint.
61 
62 	private T[][] costMatrix;
63 	private int rows, cols, dim;
64 	private T[] labelByWorker, labelByJob, minSlackValueByJob;
65 	private int[] minSlackWorkerByJob, matchJobByWorker,
66 				  matchWorkerByJob, parentWorkerByCommittedJob;
67 	private bool[] committedWorkers;
68 
69 	/**
70 	 * Construct an instance of the algorithm and Execute the algorithm.
71 	 *
72 	 * @param costMatrix
73 	 *			the cost matrix, where matrix[i][j] holds the cost of
74 	 *			assigning worker i to job j, for all i, j. The cost matrix
75 	 *			must not be irregular in the sense that all rows must be the
76 	 *			same length.
77 	 *
78 	 * @return the minimum cost matching of workers to jobs based upon the
79 	 *		 provided cost matrix. A matching value of -1 indicates that the
80 	 *		 corresponding worker is unassigned.
81 	 */
82 	public static int[] opCall(in T[][] costMatrix_)
83 	pure /*nothrow*/
84 	in(costMatrix_.length > 0)
85 	in
86 	{
87 		foreach (row; costMatrix_) // is rectangular
88 			assert(row.length == costMatrix_[0].length);
89 	}
90 	do
91 	{
92 		HungarianAlgorithm self;
93 		self.dim = max(costMatrix_.length, costMatrix_[0].length);
94 		self.rows = costMatrix_.length;
95 		self.cols = costMatrix_[0].length;
96 		self.costMatrix = new T[][](self.dim, self.dim);
97 
98 		foreach (w; 0 .. self.dim)
99 			if (w < costMatrix_.length)
100 				self.costMatrix[w] = copyOf(costMatrix_[w], self.dim);
101 			else
102 				self.costMatrix[w][] = 0; // For Java semantics.
103 
104 		self.labelByWorker.length = self.dim;
105 		self.labelByWorker[] = 0; // Necessary to follow Java semantics.
106 		self.labelByJob.length = self.dim;
107 		self.minSlackWorkerByJob.length = self.dim;
108 		self.minSlackValueByJob.length = self.dim;
109 		self.committedWorkers.length = self.dim;
110 		self.parentWorkerByCommittedJob.length = self.dim;
111 		self.matchJobByWorker.length = self.dim;
112 		self.matchJobByWorker[] = -1;
113 		self.matchWorkerByJob.length = self.dim;
114 		self.matchWorkerByJob[] = -1;
115 
116 		/*
117 		 * Heuristics to improve performance: Reduce rows and columns by their
118 		 * smallest element, compute an initial non-zero dual feasible solution
119 		 * and create a greedy matching from workers to jobs of the cost matrix.
120 		 */
121 		self.reduce();
122 		self.computeInitialFeasibleSolution();
123 		self.greedyMatch();
124 
125 		auto w = self.fetchUnmatchedWorker();
126 		while (w < self.dim) {
127 			self.initializePhase(w);
128 			self.executePhase();
129 			w = self.fetchUnmatchedWorker();
130 		}
131 		auto result = copyOf(self.matchJobByWorker, self.rows);
132 		foreach (ref r; result)
133 			if (r >= self.cols)
134 				r = -1;
135 		return result;
136 	}
137 
138 	static T[] copyOf(T)(in T[] input, in size_t size) pure /*nothrow*/ {
139 		if (size <= input.length)
140 			return input[0 .. size].dup;
141 		else {
142 			auto result = new T[size];
143 			result[0 .. input.length] = input[];
144 			result[input.length .. $] = 0; // Necessary to follow Java semantics.
145 			return result;
146 		}
147 	}
148 
149 	/**
150 	 * Compute an initial feasible solution by assigning zero labels to the
151 	 * workers and by assigning to each job a label equal to the minimum cost
152 	 * among its incident edges.
153 	 */
154 	void computeInitialFeasibleSolution() pure nothrow {
155 		labelByJob[] = infinity;
156 		foreach (w, row; costMatrix)
157 			foreach (j, rj; row)
158 				if (rj < labelByJob[j])
159 					labelByJob[j] = rj;
160 	}
161 
162 	/**
163 	 * Execute a single phase of the algorithm. A phase of the Hungarian
164 	 * algorithm consists of building a set of committed workers and a set of
165 	 * committed jobs from a root unmatched worker by following alternating
166 	 * unmatched/matched zero-slack edges. If an unmatched job is encountered,
167 	 * then an augmenting path has been found and the matching is grown. If the
168 	 * connected zero-slack edges have been exhausted, the labels of committed
169 	 * workers are increased by the minimum slack among committed workers and
170 	 * non-committed jobs to create more zero-slack edges (the labels of
171 	 * committed jobs are simultaneously decreased by the same amount in order
172 	 * to maintain a feasible labeling).
173 	 *
174 	 * The runtime of a single phase of the algorithm is O(n^2), where n is the
175 	 * dimension of the internal square cost matrix, since each edge is visited
176 	 * at most once and since increasing the labeling is accomplished in time
177 	 * O(n) by maintaining the minimum slack values among non-committed jobs.
178 	 * When a phase completes, the matching will have increased in size.
179 	 */
180 	void executePhase() pure nothrow {
181 		while (true) {
182 			int minSlackWorker = -1, minSlackJob = -1;
183 			auto minSlackValue = infinity;
184 			foreach (j, pj; parentWorkerByCommittedJob)
185 				if (pj == -1)
186 					if (minSlackValueByJob[j] < minSlackValue) {
187 						minSlackValue = minSlackValueByJob[j];
188 						minSlackWorker = minSlackWorkerByJob[j];
189 						minSlackJob = j;
190 					}
191 
192 			if (minSlackValue > 0)
193 				updateLabeling(minSlackValue);
194 			parentWorkerByCommittedJob[minSlackJob] = minSlackWorker;
195 			if (matchWorkerByJob[minSlackJob] == -1) {
196 				// An augmenting path has been found.
197 				int committedJob = minSlackJob;
198 				int parentWorker = parentWorkerByCommittedJob[committedJob];
199 				while (true) {
200 					immutable temp = matchJobByWorker[parentWorker];
201 					match(parentWorker, committedJob);
202 					committedJob = temp;
203 					if (committedJob == -1)
204 						break;
205 					parentWorker = parentWorkerByCommittedJob[committedJob];
206 				}
207 				return;
208 			} else {
209 				// Update slack values since we increased the size of the
210 				// committed workers set.
211 				immutable int worker = matchWorkerByJob[minSlackJob];
212 				committedWorkers[worker] = true;
213 				for (int j = 0; j < dim; j++) { // Performance-critical.
214 					if (parentWorkerByCommittedJob[j] == -1) {
215 						immutable slack = cast(T)(costMatrix[worker][j] -
216 												  labelByWorker[worker] -
217 												  labelByJob[j]);
218 						if (minSlackValueByJob[j] > slack) {
219 							minSlackValueByJob[j] = slack;
220 							minSlackWorkerByJob[j] = worker;
221 						}
222 					}
223 				}
224 			}
225 		}
226 	}
227 
228 	/**
229 	 *
230 	 * @return the first unmatched worker or {@link #dim} if none.
231 	 */
232 	int fetchUnmatchedWorker() const pure nothrow {
233 		foreach (w, mw; matchJobByWorker)
234 			if (mw == -1)
235 				return w;
236 		return dim;
237 	}
238 
239 	/**
240 	 * Find a valid matching by greedily selecting among zero-cost matchings.
241 	 * This is a heuristic to jump-start the augmentation algorithm.
242 	 */
243 	void greedyMatch() pure nothrow {
244 		foreach (w, row; costMatrix)
245 			foreach (j, rj; row)
246 				if (matchJobByWorker[w] == -1
247 						&& matchWorkerByJob[j] == -1
248 						&& rj - labelByWorker[w] - labelByJob[j] == 0)
249 					match(w, j);
250 	}
251 
252 	/**
253 	 * Initialize the next phase of the algorithm by clearing the committed
254 	 * workers and jobs sets and by initializing the slack arrays to the values
255 	 * corresponding to the specified root worker.
256 	 *
257 	 * @param w
258 	 *			the worker at which to root the next phase.
259 	 */
260 	void initializePhase(in int w) pure nothrow {
261 		committedWorkers[] = false;
262 		parentWorkerByCommittedJob[] = -1;
263 		committedWorkers[w] = true;
264 		foreach (j, ref mj; minSlackValueByJob) {
265 			mj = cast(T)(costMatrix[w][j] - labelByWorker[w] - labelByJob[j]);
266 			minSlackWorkerByJob[j] = w;
267 		}
268 	}
269 
270 	/**
271 	 * Helper method to record a matching between worker w and job j.
272 	 */
273 	void match(in int w, in int j) pure nothrow {
274 		matchJobByWorker[w] = j;
275 		matchWorkerByJob[j] = w;
276 	}
277 
278 	/**
279 	 * Reduce the cost matrix by subtracting the smallest element of each row
280 	 * from all elements of the row as well as the smallest element of each
281 	 * column from all elements of the column. Note that an optimal assignment
282 	 * for a reduced cost matrix is optimal for the original cost matrix.
283 	 */
284 	void reduce() pure /*nothrow*/ {
285 		foreach (ref row; costMatrix) {
286 			auto min = infinity;
287 			foreach (r; row)
288 				if (r < min)
289 					min = r;
290 			row[] -= min;
291 		}
292 		auto min = new T[dim];
293 		min[] = infinity;
294 		foreach (row; costMatrix)
295 			foreach (j, rj; row)
296 				if (rj < min[j])
297 					min[j] = rj;
298 		foreach (row; costMatrix)
299 			row[] -= min[];
300 	}
301 
302 	/**
303 	 * Update labels with the specified slack by adding the slack value for
304 	 * committed workers and by subtracting the slack value for committed jobs.
305 	 * In addition, update the minimum slack values appropriately.
306 	 */
307 	void updateLabeling(in T slack) pure nothrow {
308 		foreach (w, cw; committedWorkers)
309 			if (cw)
310 				labelByWorker[w] += slack;
311 		foreach (j, pj; parentWorkerByCommittedJob)
312 			if (pj != -1)
313 				labelByJob[j] -= slack;
314 			else
315 				minSlackValueByJob[j] -= slack;
316 	}
317 }
318 
319 
320 //void main() {
321 unittest {
322 	import std.stdio, std.random, std.datetime, std.exception, core.exception,
323 		   std.typecons, std.range, std.algorithm, std.typetuple;
324 
325 //	{
326 //		version (assert) {
327 //			const double[][] mat0 = [];
328 //			assertThrown!(AssertError)(HungarianAlgorithm!double(mat0));
329 //		}
330 //	}
331 
332 	{
333 		const double[][] mat0 = [[], []];
334 		const r0 = HungarianAlgorithm!double(mat0);
335 		assert(r0 == [-1, -1]);
336 	}
337 
338 	{
339 		const double[][] mat0 = [[1]];
340 		const r0 = HungarianAlgorithm!double(mat0);
341 		assert(r0 == [0]);
342 	}
343 
344 	{
345 		const double[][] mat0 = [[1], [1]];
346 		const r0 = HungarianAlgorithm!double(mat0);
347 		assert(r0 == [0, -1]);
348 	}
349 
350 	{
351 		const double[][] mat0 = [[1, 1]];
352 		const r0 = HungarianAlgorithm!double(mat0);
353 		assert(r0 == [0]);
354 	}
355 
356 	{
357 		const double[][] mat0 = [[1, 1], [1, 1]];
358 		const r0 = HungarianAlgorithm!double(mat0);
359 		assert(r0 == [0, 1]);
360 	}
361 
362 	{
363 		const double[][] mat0 = [[1, 1], [1, 1], [1, 1]];
364 		const r0 = HungarianAlgorithm!double(mat0);
365 		assert(r0 == [0, 1, -1]);
366 	}
367 
368 
369 	{
370 		const double[][] mat0 = [[1, 2, 3], [6, 5, 4]];
371 		const r0 = HungarianAlgorithm!double(mat0);
372 		assert(r0 == [0, 2]);
373 	}
374 
375 	{
376 		const double[][] mat0 = [[1, 2, 3], [6, 5, 4], [1, 1, 1]];
377 		const r0 = HungarianAlgorithm!double(mat0);
378 		assert(r0 == [0, 2, 1]);
379 	}
380 
381 
382 	{
383 		const int[][] mat0 = [[], []];
384 		const r0 = HungarianAlgorithm!int(mat0);
385 		assert(r0 == [-1, -1]);
386 	}
387 
388 	{
389 		const int[][] mat0 = [[1]];
390 		const r0 = HungarianAlgorithm!int(mat0);
391 		assert(r0 == [0]);
392 	}
393 
394 	{
395 		const int[][] mat0 = [[1], [1]];
396 		const r0 = HungarianAlgorithm!int(mat0);
397 		assert(r0 == [0, -1]);
398 	}
399 
400 	{
401 		const int[][] mat0 = [[1, 1]];
402 		const r0 = HungarianAlgorithm!int(mat0);
403 		assert(r0 == [0]);
404 	}
405 
406 	{
407 		const int[][] mat0 = [[1, 1], [1, 1]];
408 		const r0 = HungarianAlgorithm!int(mat0);
409 		assert(r0 == [0, 1]);
410 	}
411 
412 	{
413 		const int[][] mat0 = [[1, 1], [1, 1], [1, 1]];
414 		const r0 = HungarianAlgorithm!int(mat0);
415 		assert(r0 == [0, 1, -1]);
416 	}
417 
418 
419 	{
420 		const int[][] mat0 = [[1, 2, 3], [6, 5, 4]];
421 		const r0 = HungarianAlgorithm!int(mat0);
422 		assert(r0 == [0, 2]);
423 	}
424 
425 	{
426 		const int[][] mat0 = [[1, 2, 3], [6, 5, 4], [1, 1, 1]];
427 		const r0 = HungarianAlgorithm!int(mat0);
428 		assert(r0 == [0, 2, 1]);
429 	}
430 
431 	{
432 		int[][] mat1 = [[  7,  53, 183, 439, 863],
433 						[497, 383, 563,  79, 973],
434 						[287,  63, 343, 169, 583],
435 						[627, 343, 773, 959, 943],
436 						[767, 473, 103, 699, 303]];
437 		foreach (row; mat1)
438 			row[] *= -1;
439 		const r1 = HungarianAlgorithm!int(mat1);
440 		// Currently doesn't work with -inline:
441 		//r1.length.iota().map!(r => -mat1[r][r1[r]])().reduce!q{a + b}().writeln();
442 		int tot1 = 0;
443 		foreach (i, r1i; r1)
444 			tot1 -= mat1[i][r1i];
445 		assert(tot1 == 3315);
446 	}
447 
448 	{
449 		// Euler Problem 345:
450 		// http://projecteuler.net/index.php?section=problems&id=345
451 		int [][] mat2 = [
452 		[  7,  53, 183, 439, 863, 497, 383, 563,  79, 973, 287,  63, 343, 169, 583],
453 		[627, 343, 773, 959, 943, 767, 473, 103, 699, 303, 957, 703, 583, 639, 913],
454 		[447, 283, 463,  29,  23, 487, 463, 993, 119, 883, 327, 493, 423, 159, 743],
455 		[217, 623,   3, 399, 853, 407, 103, 983,  89, 463, 290, 516, 212, 462, 350],
456 		[960, 376, 682, 962, 300, 780, 486, 502, 912, 800, 250, 346, 172, 812, 350],
457 		[870, 456, 192, 162, 593, 473, 915,  45, 989, 873, 823, 965, 425, 329, 803],
458 		[973, 965, 905, 919, 133, 673, 665, 235, 509, 613, 673, 815, 165, 992, 326],
459 		[322, 148, 972, 962, 286, 255, 941, 541, 265, 323, 925, 281, 601,  95, 973],
460 		[445, 721,  11, 525, 473,  65, 511, 164, 138, 672,  18, 428, 154, 448, 848],
461 		[414, 456, 310, 312, 798, 104, 566, 520, 302, 248, 694, 976, 430, 392, 198],
462 		[184, 829, 373, 181, 631, 101, 969, 613, 840, 740, 778, 458, 284, 760, 390],
463 		[821, 461, 843, 513,  17, 901, 711, 993, 293, 157, 274,  94, 192, 156, 574],
464 		[ 34, 124,   4, 878, 450, 476, 712, 914, 838, 669, 875, 299, 823, 329, 699],
465 		[815, 559, 813, 459, 522, 788, 168, 586, 966, 232, 308, 833, 251, 631, 107],
466 		[813, 883, 451, 509, 615,  77, 281, 613, 459, 205, 380, 274, 302,  35, 805]];
467 		foreach (row; mat2)
468 			row[] *= -1;
469 		const r2 = HungarianAlgorithm!int(mat2);
470 		int tot2 = 0;
471 		foreach (i, r2i; r2)
472 			tot2 -= mat2[i][r2i];
473 		assert(tot2 == 13938); // Euler Problem 345 solution.
474 	}
475 
476 	static T[][] genMat(T)(in int N, in int M, in int seed) {
477 		rndGen.seed(seed);
478 		auto mat = new T[][](N, M);
479 		foreach (row; mat)
480 			foreach (ref x; row)
481 				static if (is(T == short))
482 					x = uniform(cast(T)10, cast(T)500);
483 				else
484 					x = uniform(cast(T)10, cast(T)5_000);
485 		return mat;
486 	}
487 
488 	// Steinhaus-Johnson-Trotter permutations algorithm
489 	static struct Spermutations {
490 		const int n;
491 		alias Tuple!(int[], int) TResult;
492 
493 		int opApply(int delegate(ref TResult) dg) {
494 			int result;
495 			TResult aux;
496 
497 			int sign = 1;
498 			alias Tuple!(int, int) Pair;
499 			//auto p = iota(n).map!(i => Pair(i, i ? -1 : 0))().array();
500 			auto p = array(map!(i => Pair(i, i ? -1 : 0))(iota(n)));
501 
502 			aux = tuple(array(map!(pp => pp[0])(p)), sign);
503 			result = dg(aux); if (result) goto END;
504 
505 			while (canFind!(pp => pp[1])(p)) {
506 				// Failed using std.algorithm here, too much complex
507 				auto largest = Pair(-100, -100);
508 				int i1 = -1;
509 				foreach (i, pp; p)
510 					if (pp[1]) {
511 						if (pp[0] > largest[0]) {
512 							i1 = i;
513 							largest = pp;
514 						}
515 					}
516 				int n1 = largest[0], d1 = largest[1];
517 
518 				sign *= -1;
519 				int i2;
520 				if (d1 == -1) {
521 					i2 = i1 - 1;
522 					swap(p[i1], p[i2]);
523 					if (i2 == 0 || p[i2 - 1][0] > n1)
524 						p[i2][1] = 0;
525 				} else if (d1 == 1) {
526 					i2 = i1 + 1;
527 					swap(p[i1], p[i2]);
528 					if (i2 == n - 1 || p[i2 + 1][0] > n1)
529 						p[i2][1] = 0;
530 				}
531 				aux = tuple(array(map!(pp => pp[0])(p)), sign);
532 				result = dg(aux); if (result) goto END;
533 
534 				foreach (i3, ref pp; p) {
535 					auto n3 = pp[0], d3 = pp[1];
536 					if (n3 > n1)
537 						pp[1] = (i3 < i2) ? 1 : -1;
538 				}
539 			}
540 
541 			END: return result;
542 		}
543 	}
544 
545 	static T bruteForceSolver(T)(in T[][] mat) /*pure nothrow*/
546 	in(mat.length > 0)
547 	in
548 	{
549 		// Currently works only with square matrices.
550 		foreach (row; mat) // Is square.
551 			assert(row.length == mat.length);
552 	}
553 	do
554 	{
555 		auto maxTotal = -T.max;
556 		foreach (p; Spermutations(mat.length)) {
557 			T total = 0;
558 			foreach (i, pi; p[0])
559 				total += mat[i][pi];
560 			maxTotal = max(maxTotal, total);
561 		}
562 		return maxTotal;
563 	}
564 
565 	foreach (T; TypeTuple!(double, int, float, short, long)) {
566 		// Fuzzy test.
567 		foreach (test; 0 .. 10) {
568 			auto mat3 = genMat!T(2 + test % 8, 2 + test % 8, test);
569 			foreach (row; mat3)
570 				row[] *= -1;
571 			const r3 = HungarianAlgorithm!T(mat3);
572 			T tot3 = 0;
573 			foreach (i, r3i; r3)
574 				tot3 -= mat3[i][r3i];
575 			foreach (row; mat3)
576 				row[] *= -1;
577 			assert(tot3 == bruteForceSolver(mat3));
578 		}
579 	}
580 
581 	version (hungarian_benchmark) {
582 		foreach (T; TypeTuple!(double, int, float, short, long)) {
583 			auto mat4 = genMat!T(2_000, 2_000, 0);
584 			StopWatch sw;
585 			sw.start();
586 			const r4 = HungarianAlgorithm!T(mat4);
587 			sw.stop();
588 			writefln("Type %s, milliseconds: %d",
589 					 T.stringof, sw.peek().msecs);
590 		}
591 		/*
592 		Type double, milliseconds: 1032
593 		Type int, milliseconds: 834
594 		Type float, milliseconds: 859
595 		Type short, milliseconds: 9221
596 		Type long, milliseconds: 1306
597 		*/
598 	}
599 }
600 
601 //void main() {}