/* 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.
*/
package org.jgrapht.alg;
import junit.framework.TestCase;
import org.jgrapht.EdgeFactory;
import org.jgrapht.WeightedGraph;
import org.jgrapht.generate.SimpleWeightedBipartiteGraphMatrixGenerator;
import org.jgrapht.generate.WeightedGraphGeneratorAdapter;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleWeightedGraph;
import org.jgrapht.util.VertexPair;
import org.junit.Assert;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("unchecked")
public class KuhnMunkresMinimalWeightBipartitePerfectMatchingTest extends TestCase {
interface V {}
/**
* First partition
*/
enum FIRST_PARTITION implements V {
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V
}
static List<? extends V> firstPartition = Arrays.asList(FIRST_PARTITION.values());
/**
* Second partition
*/
enum SECOND_PARTITION implements V {
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V
}
static List<? extends V> secondPartition = Arrays.asList(SECOND_PARTITION.values());
static class WeightedEdge extends DefaultWeightedEdge {
class _ extends VertexPair<V> {
public _(V _1, V _2) {
super(_1, _2);
}
}
WeightedEdge(V _1, V _2) {
__ = new _(_1, _2);
}
static WeightedEdge make(V source, V target) {
return new WeightedEdge(source, target);
}
@Override
public boolean equals(Object edge) {
return (edge instanceof WeightedEdge) && __.equals(((WeightedEdge) edge).__);
}
@Override
public int hashCode() {
return __.hashCode();
}
@Override
public String toString() {
return __.toString() + " : " + getWeight();
}
_ __;
}
static KuhnMunkresMinimalWeightBipartitePerfectMatching<V, WeightedEdge>
match(final double[][] costMatrix, final int partitionCardinality) {
List<? extends V> first = firstPartition.subList(0, partitionCardinality);
List<? extends V> second = secondPartition.subList(0, partitionCardinality);
WeightedGraph<V, WeightedEdge> target =
new SimpleWeightedGraph<V, WeightedEdge>(new EdgeFactory<V, WeightedEdge>() {
@Override
public WeightedEdge createEdge(V sourceVertex, V targetVertex) {
return WeightedEdge.make(sourceVertex, targetVertex);
}
});
WeightedGraphGeneratorAdapter<V, WeightedEdge, V> generator =
new SimpleWeightedBipartiteGraphMatrixGenerator<V, WeightedEdge>()
.first (first)
.second (second)
.weights(costMatrix);
generator.generateGraph(target, null, null);
return new KuhnMunkresMinimalWeightBipartitePerfectMatching<V, WeightedEdge>(target, first, second);
}
public void test3x3SimpleAssignmentTask() {
// Obvious case:
// Optimal selection being disposed on the diagonal of the given matrix
double[][] costMatrix = new double[][] {
{ 1, 2, 3 },
{ 5, 4, 6 },
{ 8, 9, 7 }
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 12);
}
public void test3x3SimpleAssignmentTaskNo2() {
// Simple case:
// Every selection gives the same value of 15
double[][] costMatrix = new double[][] {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 15);
}
public void test5x5AssignmentTask() {
// Not so obvious case
double[][] costMatrix = new double[][] {
{ 1, 2, 3, 4, 5 },
{ 6, 7, 8, 7, 2 },
{ 1, 3, 4, 4, 5 },
{ 3, 6, 2, 8, 7 },
{ 4, 1, 3, 5, 4 }
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 10);
}
public void test5x5InvertedAssignmentTask() {
// Assignment minimizing total cost according to given cost-matrix
// maximizes total-cost according to the following cost-matrix:
//
// { 1, 2, 3, 4, 5 }
// { 6, 7, 8, 7, 2 }
// { 1, 3, 4, 4, 5 }
// { 3, 6, 2, 8, 7 }
// { 4, 1, 3, 5, 4 }
//
// NOTE:
// Cost-matrix being under test derived from the listed above
// by subtraction from the maximal element
double[][] costMatrix = new double[][] {
{ 7, 6, 5, 4, 3 },
{ 2, 1, 0, 1, 6 },
{ 7, 5, 4, 4, 3 },
{ 5, 2, 6, 0, 1 },
{ 4, 7, 5, 3, 4 }
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 12);
}
public void test6x6DegeneratedAssignmentTask() {
// First DEGENERATED case:
// Degenerated worker and degenerated task added
//
// NOTE:
// Answer have to stay the same as in previous case #4
double[][] costMatrix = new double[][] {
{ 7, 6, 5, 4, 3, 9 },
{ 2, 1, 0, 1, 6, 9 },
{ 7, 5, 4, 4, 3, 9 },
{ 5, 2, 6, 0, 1, 9 },
{ 4, 7, 5, 3, 4, 9 },
{ 9, 9, 9, 9, 9, 9 }
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 21);
}
public void test6x6DegeneratedAssignmentTaskNo2() {
// Second DEGENERATED case:
//
// |Workers| > |Tasks|
//
// degenerated task added
double[][] costMatrix = new double[][] {
{ 7, 6, 5, 4, 3, 9 },
{ 2, 1, 0, 1, 6, 9 },
{ 7, 5, 4, 4, 3, 9 },
{ 5, 2, 6, 0, 1, 9 },
{ 4, 7, 5, 3, 4, 9 },
{ 3, 5, 8, 7, 1, 9 }
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 19);
}
public void test5x5DegeneratedAssignmentTask() {
// Third DEGENERATED case:
//
// Task #1 can't be performed by the worker #1 (designated by the MAX + 1 value (9))
// Task #3 can't be performed by the worker #3 (designated by the MAX + 1 value (9))
// Task #4 can't be performed by the worker #1 (designated by the MAX + 1 value (9))
// Task #5 can't be performed by the workers #2, #4 (designated by the MAX + 1 value (9))
//
// degenerated task added
double[][] costMatrix = new double[][] {
{ 9, 6, 5, 9, 3 },
{ 2, 1, 0, 1, 6 },
{ 7, 5, 9, 4, 3 },
{ 9, 2, 6, 0, 1 },
{ 4, 9, 5, 9, 4 },
};
double w = match(costMatrix, costMatrix.length).getMatchingWeight();
Assert.assertTrue(w == 12);
}
public void test8x8BulkyAssignmentTask() {
double[][] costMatrix = new double[][] {
{233160, 1485901, 3245737, 25965896, 25965896, 25965896, 25965896, 25965896},
{238594, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896},
{242403, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896},
{233408, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896},
{233160, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896},
{258074, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896},
{233160, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896},
{233625, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896}
};
// Case entailing set-cover algo drastically degenerating, therefore need just to pass
match(costMatrix, costMatrix.length);
}
public void test21x21BulkyAssignmentTask() {
double[][] costMatrix = new double[][] {
{284169900,16680,27111,0,25914,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,16680,27305,0,25914,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,16834,60173,0,25981,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,16680,43679,0,32979,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{16656,270745874,270739560,270769776,270686589,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,271120238,271113924,271144140,271060953,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364,374364},
{0,270959812,270953498,270983714,270900527,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938,213938},
{284182260,0,33241,12360,0,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484,13412484},
{284169900,16680,27305,0,25914,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,17630,25747,0,31348,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,34668,28398,0,35157,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,17503,20151,0,0,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,18099,77279,0,26162,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,16804,27869,0,25914,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284175472,6700,0,5572,24108,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696,13405696},
{284169900,31543,22343,0,50828,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,16623,27215,0,25830,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,16680,27305,0,14463,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284174196,0,4296,4296,30210,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420,13404420},
{284169900,17377,27999,0,25914,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124},
{284169900,76034,27305,0,26379,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124,13400124}
};
// Case entailing set-cover algo drastically degenerating, therefore need just to pass
match(costMatrix, costMatrix.length);
}
public void test20x20BulkyAssignmentTask() {
double[][] costMatrix = new double[][] {
{284309466,162348,179093,121766,230867,175501,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162348,179287,121766,230867,133304,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162502,212155,121766,230934,192658,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162348,195661,121766,237932,175347,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{13538546,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{13147526,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{13307952,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,133308,172863,121766,192593,145039,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162348,179287,121766,230867,175287,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,163298,177729,121766,236301,184972,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,180336,180380,121766,240110,181736,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,163171,172133,121766,204953,175649,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,163767,229261,121766,231115,205427,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162472,179851,121766,230867,221341,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,146796,146410,121766,223489,156487,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,177211,174325,121766,255781,235876,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162291,179197,121766,230783,175287,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,162348,179287,121766,219416,175460,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,141372,151982,121766,230867,161161,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,163045,179981,121766,230867,175740,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466},
{284309466,221702,179287,121766,231332,175287,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466,284309466}
};
// Case entailing set-cover algo drastically degenerating, therefore need just to pass
match(costMatrix, costMatrix.length);
}
}