#region License Information /* HeuristicLab * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.PermutationEncoding; using HeuristicLab.Operators; using HeuristicLab.Optimization; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; namespace HeuristicLab.Problems.LinearAssignment { [Item("LinearAssignmentProblemSolver", "Uses the hungarian algorithm to solve linear assignment problems.")] [StorableType("9AE29C0F-02CB-4906-AA61-1E0896CA007E")] public sealed class LinearAssignmentProblemSolver : SingleSuccessorOperator, ISingleObjectiveOperator { private const int UNASSIGNED = -1; public IValueLookupParameter MaximizationParameter { get { return (IValueLookupParameter)Parameters["Maximization"]; } } public ILookupParameter CostsParameter { get { return (ILookupParameter)Parameters["Costs"]; } } public ILookupParameter AssignmentParameter { get { return (ILookupParameter)Parameters["Assignment"]; } } public ILookupParameter QualityParameter { get { return (ILookupParameter)Parameters["Quality"]; } } [StorableConstructor] private LinearAssignmentProblemSolver(bool deserializing) : base(deserializing) { } private LinearAssignmentProblemSolver(LinearAssignmentProblemSolver original, Cloner cloner) : base(original, cloner) { } public LinearAssignmentProblemSolver() : base() { Parameters.Add(new ValueLookupParameter("Maximization", "Whether the costs should be maximized or minimized.")); Parameters.Add(new LookupParameter("Costs", LinearAssignmentProblem.CostsDescription)); Parameters.Add(new LookupParameter("Assignment", "The assignment solution to create.")); Parameters.Add(new LookupParameter("Quality", "The quality value of the solution.")); } public override IDeepCloneable Clone(Cloner cloner) { return new LinearAssignmentProblemSolver(this, cloner); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { // BackwardsCompatibility3.3 #region Backwards compatible code, remove with 3.4 if (!Parameters.ContainsKey("Maximization")) Parameters.Add(new ValueLookupParameter("Maximization", "Whether the costs should be maximized or minimized.")); #endregion } public override IOperation Apply() { var costs = CostsParameter.ActualValue; var maximization = MaximizationParameter.ActualValue.Value; if (maximization) { costs = (DoubleMatrix)costs.Clone(); for (int i = 0; i < costs.Rows; i++) for (int j = 0; j < costs.Rows; j++) costs[i, j] = -costs[i, j]; } double quality; var solution = Solve(costs, out quality); AssignmentParameter.ActualValue = new Permutation(PermutationTypes.Absolute, solution); if (maximization) quality = -quality; QualityParameter.ActualValue = new DoubleValue(quality); return base.Apply(); } /// /// Uses the Hungarian algorithm to solve the linear assignment problem (LAP). /// The LAP is defined as minimize f(p) = Sum(i = 1..N, c_{i, p(i)}) for a permutation p and an NxN cost matrix. /// /// The runtime complexity of the algorithm is O(n^3). The algorithm is deterministic and terminates /// returning one of the optimal solutions and the corresponding quality. /// /// /// The algorithm is written similar to the fortran implementation given in http://www.seas.upenn.edu/qaplib/code.d/qapglb.f /// /// An NxN costs matrix. /// The quality value of the optimal solution. /// The optimal solution. public static int[] Solve(DoubleMatrix costs, out double quality) { int length = costs.Rows; // solve the linear assignment problem f(p) = Sum(i = 1..|p|, c_{i, p(i)}) int[] rowAssign = new int[length], colAssign = new int[length]; double[] dualCol = new double[length], dualRow = new double[length]; for (int i = 0; i < length; i++) { // mark all positions as untouched rowAssign[i] = UNASSIGNED; colAssign[i] = UNASSIGNED; } for (int i = 0; i < length; i++) { // find the minimum (base) level for each row double min = costs[i, 0]; int minCol = 0; dualCol[0] = min; for (int j = 1; j < length; j++) { if (costs[i, j] <= min) { min = costs[i, j]; minCol = j; } if (costs[i, j] > dualCol[j]) dualCol[j] = costs[i, j]; } dualRow[i] = min; // this will be the value of our dual variable if (colAssign[minCol] == UNASSIGNED) { colAssign[minCol] = i; rowAssign[i] = minCol; } } for (int j = 0; j < length; j++) { // calculate the second dual variable if (colAssign[j] != UNASSIGNED) dualCol[j] = 0; else { int minRow = 0; for (int i = 0; i < length; i++) { if (dualCol[j] > 0 && costs[i, j] - dualRow[i] < dualCol[j]) { dualCol[j] = costs[i, j] - dualRow[i]; // the value is the original costs minus the first dual value minRow = i; } } if (rowAssign[minRow] == UNASSIGNED) { colAssign[j] = minRow; rowAssign[minRow] = j; } } } // at this point costs_ij - dualRow_i - dualColumn_j results in a matrix that has at least one zero in every row and every column for (int i = 0; i < length; i++) { // try to make the remaining assignments if (rowAssign[i] == UNASSIGNED) { double min = dualRow[i]; for (int j = 0; j < length; j++) { if (colAssign[j] == UNASSIGNED && (costs[i, j] - min - dualCol[j]).IsAlmost(0.0)) { rowAssign[i] = j; colAssign[j] = i; break; } } } } bool[] marker = new bool[length]; double[] dplus = new double[length], dminus = new double[length]; int[] rowMarks = new int[length]; for (int u = 0; u < length; u++) { if (rowAssign[u] == UNASSIGNED) { for (int i = 0; i < length; i++) { rowMarks[i] = u; marker[i] = false; dplus[i] = double.MaxValue; dminus[i] = costs[u, i] - dualRow[u] - dualCol[i]; } dplus[u] = 0; int index = -1; double minD = double.MaxValue; while (true) { minD = double.MaxValue; for (int i = 0; i < length; i++) { if (!marker[i] && dminus[i] < minD) { minD = dminus[i]; index = i; } } if (colAssign[index] == UNASSIGNED) break; marker[index] = true; dplus[colAssign[index]] = minD; for (int i = 0; i < length; i++) { if (marker[i]) continue; double compare = minD + costs[colAssign[index], i] - dualCol[i] - dualRow[colAssign[index]]; if (dminus[i] > compare) { dminus[i] = compare; rowMarks[i] = colAssign[index]; } } } // while(true) while (true) { colAssign[index] = rowMarks[index]; var ind = rowAssign[rowMarks[index]]; rowAssign[rowMarks[index]] = index; if (rowMarks[index] == u) break; index = ind; } for (int i = 0; i < length; i++) { if (dplus[i] < double.MaxValue) dualRow[i] += minD - dplus[i]; if (dminus[i] < minD) dualCol[i] += dminus[i] - minD; } } } quality = 0; for (int i = 0; i < length; i++) { quality += costs[i, rowAssign[i]]; } return rowAssign; } } }