#region License Information /* HeuristicLab * Copyright (C) 2002-2012 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 System; using System.Collections.Generic; using System.Linq; using System.Threading; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.IntegerVectorEncoding; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.Random; namespace HeuristicLab.Problems.GeneralizedQuadraticAssignment { [Item("SlackMinimizationSolutionCreator", "A heuristic that creates a solution to the Generalized Quadratic Assignment Problem by minimizing the amount of slack.")] [StorableClass] public class SlackMinimizationSolutionCreator : GQAPStochasticSolutionCreator, IEvaluatorAwareGQAPOperator { public IValueLookupParameter MaximumTriesParameter { get { return (IValueLookupParameter)Parameters["MaximumTries"]; } } public IValueLookupParameter CreateMostFeasibleSolutionParameter { get { return (IValueLookupParameter)Parameters["CreateMostFeasibleSolution"]; } } public IValueLookupParameter DepthParameter { get { return (IValueLookupParameter)Parameters["Depth"]; } } public IValueLookupParameter RandomWalkLengthParameter { get { return (IValueLookupParameter)Parameters["RandomWalkLength"]; } } public IValueLookupParameter EvaluatorParameter { get { return (IValueLookupParameter)Parameters["Evaluator"]; } } [StorableConstructor] protected SlackMinimizationSolutionCreator(bool deserializing) : base(deserializing) { } protected SlackMinimizationSolutionCreator(SlackMinimizationSolutionCreator original, Cloner cloner) : base(original, cloner) { } public SlackMinimizationSolutionCreator() : base() { Parameters.Add(new ValueLookupParameter("MaximumTries", "The maximum number of tries to create a feasible solution after which an exception is thrown. If it is set to 0 or a negative value there will be an infinite number of attempts to create a feasible solution.", new IntValue(100000))); Parameters.Add(new ValueLookupParameter("CreateMostFeasibleSolution", "If this is set to true the operator will always succeed, and outputs the solution with the least violation instead of throwing an exception.", new BoolValue(false))); Parameters.Add(new ValueLookupParameter("Depth", "How deep the algorithm should look forward.", new IntValue(3))); Parameters.Add(new ValueLookupParameter("RandomWalkLength", "The length of the random walk in the feasible region that is used to diversify the found assignments.", new IntValue(10))); Parameters.Add(new ValueLookupParameter("Evaluator", "The evaluator that is used to evaluate GQAP solutions.")); } public override IDeepCloneable Clone(Cloner cloner) { return new SlackMinimizationSolutionCreator(this, cloner); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { if (!Parameters.ContainsKey("Depth")) { Parameters.Add(new ValueLookupParameter("Depth", "How deep the algorithm should look forward.", new IntValue(3))); } if (!Parameters.ContainsKey("RandomWalkLength")) { Parameters.Add(new ValueLookupParameter("RandomWalkLength", "The length of the random walk in the feasible region that is used to diversify the found assignments.", new IntValue(10))); } } public static IntegerVector CreateSolution(IRandom random, DoubleArray demands, DoubleArray capacities, IGQAPEvaluator evaluator, int depth, int maximumTries, bool createMostFeasibleSolution, int randomWalkLength, CancellationToken cancel) { IntegerVector result = null; bool isFeasible = false; int counter = 0; double minViolation = double.MaxValue; var slack = new DoubleArray(capacities.Length); var assignment = new Dictionary(demands.Length); while (!isFeasible) { cancel.ThrowIfCancellationRequested(); if (maximumTries > 0) { counter++; if (counter > maximumTries) { if (createMostFeasibleSolution) break; else throw new InvalidOperationException("A feasible solution could not be obtained after " + maximumTries + " attempts."); } } assignment.Clear(); for (int i = 0; i < capacities.Length; i++) slack[i] = capacities[i]; var remainingEquipment = new HashSet(Enumerable.Range(0, demands.Length)); while (remainingEquipment.Any()) { var minimumDemand = remainingEquipment.Min(x => demands[x]); var possibleLocations = Enumerable.Range(0, capacities.Length).Where(x => slack[x] >= minimumDemand); if (!possibleLocations.Any()) break; foreach (var location in possibleLocations.Shuffle(random)) { var group = FindBestGroup(location, slack[location], remainingEquipment, demands, depth); foreach (var eq in group) { remainingEquipment.Remove(eq); assignment[eq] = location; slack[location] -= demands[eq]; } } } if (assignment.Count != demands.Length) { // complete the solution while (remainingEquipment.Any()) { var f = remainingEquipment.MaxItems(x => demands[x]).SampleRandom(random); var l = Enumerable.Range(0, capacities.Length).MaxItems(x => slack[x]).SampleRandom(random); remainingEquipment.Remove(f); assignment.Add(f, l); slack[l] -= demands[f]; } } else RandomFeasibleWalk(random, assignment, demands, slack, randomWalkLength); double violation = evaluator.EvaluateOverbooking(slack, capacities); isFeasible = violation == 0; if (isFeasible || violation < minViolation) { result = new IntegerVector(assignment.OrderBy(x => x.Key).Select(x => x.Value).ToArray()); minViolation = violation; } } return result; } private static IEnumerable FindBestGroup(int location, double slack, HashSet remainingEquipment, DoubleArray demands, int depth = 3) { var feasibleEquipment = remainingEquipment.Where(x => demands[x] <= slack).ToArray(); if (!feasibleEquipment.Any()) yield break; if (depth == 0) { var e = feasibleEquipment.MaxItems(x => demands[x]).First(); yield return e; yield break; } double bestSlack = slack; int bestEquipment = -1; int[] bestColleagues = new int[0]; foreach (var e in feasibleEquipment) { remainingEquipment.Remove(e); var colleagues = FindBestGroup(location, slack - demands[e], remainingEquipment, demands, depth - 1).ToArray(); var slackWithColleagues = slack - demands[e] - colleagues.Sum(x => demands[x]); if (bestSlack > slackWithColleagues || (bestSlack == slackWithColleagues && colleagues.Length < bestColleagues.Length)) { bestSlack = slackWithColleagues; bestEquipment = e; bestColleagues = colleagues; } remainingEquipment.Add(e); } yield return bestEquipment; foreach (var a in bestColleagues) yield return a; } private static void RandomFeasibleWalk(IRandom random, Dictionary assignment, DoubleArray demands, DoubleArray slack, int walkLength) { for (int i = 0; i < walkLength; i++) { var equipments = Enumerable.Range(0, demands.Length).Shuffle(random); foreach (var e in equipments) { var partners = Enumerable.Range(0, demands.Length) .Where(x => slack[assignment[x]] + demands[x] - demands[e] >= 0 && slack[assignment[e]] + demands[e] - demands[x] >= 0); if (!partners.Any()) continue; var f = partners.SampleRandom(random); int h = assignment[e]; assignment[e] = assignment[f]; assignment[f] = h; slack[assignment[e]] += demands[f] - demands[e]; slack[assignment[f]] += demands[e] - demands[f]; break; } } } protected override IntegerVector CreateRandomSolution(IRandom random, DoubleArray demands, DoubleArray capacities) { return CreateSolution(random, demands, capacities, EvaluatorParameter.ActualValue, DepthParameter.ActualValue.Value, MaximumTriesParameter.ActualValue.Value, CreateMostFeasibleSolutionParameter.ActualValue.Value, RandomWalkLengthParameter.ActualValue.Value, CancellationToken); } } }