#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);
}
}
}