#region License Information
/* HeuristicLab
* Copyright (C) 2002-2018 Joseph Helm and 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.PermutationEncoding;
using HeuristicLab.Optimization;
using HeuristicLab.Parameters;
using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
namespace HeuristicLab.Problems.BinPacking3D {
public enum SortingMethod { All, Given, VolumeHeight, HeightVolume, AreaHeight, HeightArea, ClusteredAreaHeight, ClusteredHeightArea }
public enum FittingMethod { All, FirstFit, ResidualSpaceBestFit, FreeVolumeBestFit }
[Item("Extreme-point-based Bin Packing (3d)", "An implementation of the extreme-point based packing described in Crainic, T. G., Perboli, G., & Tadei, R. (2008). Extreme point-based heuristics for three-dimensional bin packing. Informs Journal on computing, 20(3), 368-384.")]
[StorableClass]
[Creatable(CreatableAttribute.Categories.SingleSolutionAlgorithms, Priority = 180)]
public sealed class ExtremePointAlgorithm : BasicAlgorithm {
public override Type ProblemType {
get { return typeof(PermutationProblem); }
}
public new PermutationProblem Problem {
get { return (PermutationProblem)base.Problem; }
set { base.Problem = value; }
}
public override bool SupportsPause {
get { return false; }
}
[Storable]
private readonly IValueParameter> sortingMethodParameter;
public IValueParameter> SortingMethodParameter {
get { return sortingMethodParameter; }
}
[Storable]
private readonly IValueParameter> fittingMethodParameter;
public IValueParameter> FittingMethodParameter {
get { return fittingMethodParameter; }
}
[Storable]
private readonly IValueParameter deltaParameter;
public IValueParameter DeltaParameter {
get { return deltaParameter; }
}
[StorableConstructor]
private ExtremePointAlgorithm(bool deserializing) : base(deserializing) { }
private ExtremePointAlgorithm(ExtremePointAlgorithm original, Cloner cloner)
: base(original, cloner) {
sortingMethodParameter = cloner.Clone(original.sortingMethodParameter);
fittingMethodParameter = cloner.Clone(original.fittingMethodParameter);
deltaParameter = cloner.Clone(original.deltaParameter);
}
public ExtremePointAlgorithm() {
Parameters.Add(sortingMethodParameter = new ValueParameter>("SortingMethod", "In which order the items should be packed.", new EnumValue(SortingMethod.All)));
Parameters.Add(fittingMethodParameter = new ValueParameter>("FittingMethod", "Which method to fit should be used.", new EnumValue(FittingMethod.All)));
Parameters.Add(deltaParameter = new ValueParameter("Delta", "[1;100]% Clustered sorting methods use a delta parameter to determine the clusters.", new PercentValue(.1)));
Problem = new PermutationProblem();
}
public override IDeepCloneable Clone(Cloner cloner) {
return new ExtremePointAlgorithm(this, cloner);
}
[StorableHook(HookType.AfterDeserialization)]
private void AfterDeserialization() {
}
protected override void Run(CancellationToken token) {
var items = Problem.Items;
var bin = Problem.BinShape;
var sorting = new[] { SortingMethodParameter.Value.Value };
if (sorting[0] == SortingMethod.All) {
sorting = Enum.GetValues(typeof(SortingMethod)).Cast().Where(x => x != SortingMethod.All).ToArray();
}
var fitting = new[] { fittingMethodParameter.Value.Value };
if (fitting[0] == FittingMethod.All) {
fitting = Enum.GetValues(typeof(FittingMethod)).Cast().Where(x => x != FittingMethod.All).ToArray();
}
var result = GetBest(bin, items, sorting, fitting, token);
if (result == null) throw new InvalidOperationException("No result obtained!");
Results.Add(new Result("Best Solution",
"The best found solution",
result.Item1));
Results.Add(new Result("Best Solution Quality",
"The quality of the best found solution according to the evaluator",
new DoubleValue(result.Item2)));
var binUtil = new BinUtilizationEvaluator();
var packRatio = new PackingRatioEvaluator();
Results.Add(new Result("Best Solution Bin Count",
"The number of bins in the best found solution",
new IntValue(result.Item1.NrOfBins)));
Results.Add(new Result("Best Solution Bin Utilization",
"The utilization given in percentage as calculated by the BinUtilizationEvaluator (total used space / total available space)",
new PercentValue(Math.Round(binUtil.Evaluate(result.Item1), 3))));
if (result.Item3.HasValue && sorting.Length > 1)
Results.Add(new Result("Best Sorting Method",
"The sorting method that found the best solution",
new EnumValue(result.Item3.Value)));
if (result.Item4.HasValue && fitting.Length > 1)
Results.Add(new Result("Best Fitting Method",
"The fitting method that found the best solution",
new EnumValue(result.Item4.Value)));
}
private Tuple GetBest(PackingShape bin, IList items, SortingMethod[] sortings, FittingMethod[] fittings, CancellationToken token) {
SortingMethod? bestSorting = null;
FittingMethod? bestFitting = null;
var best = double.NaN;
Solution bestSolution = null;
foreach (var fit in fittings) {
foreach (var sort in sortings) {
var result = Optimize(bin, items, sort, fit, DeltaParameter.Value.Value, Problem.UseStackingConstraints, Problem.SolutionEvaluator, token);
if (double.IsNaN(result.Item2) || double.IsInfinity(result.Item2)) continue;
if (double.IsNaN(best)
|| Problem.Maximization && result.Item2 > best
|| !Problem.Maximization && result.Item2 < best) {
bestSolution = result.Item1;
best = result.Item2;
bestSorting = sort;
bestFitting = fit;
}
if (token.IsCancellationRequested) return Tuple.Create(bestSolution, best, bestSorting, bestFitting);
}
}
if (double.IsNaN(best)) return null;
return Tuple.Create(bestSolution, best, bestSorting, bestFitting);
}
private static Tuple Optimize(PackingShape bin, IList items, SortingMethod sorting, FittingMethod fitting, double delta, bool stackingConstraints, IEvaluator evaluator, CancellationToken token) {
Permutation sorted = null;
switch (sorting) {
case SortingMethod.Given:
sorted = new Permutation(PermutationTypes.Absolute, Enumerable.Range(0, items.Count).ToArray());
break;
case SortingMethod.VolumeHeight:
sorted = new Permutation(PermutationTypes.Absolute,
items.Select((v, i) => new { Index = i, Item = v })
.OrderByDescending(x => x.Item.Depth * x.Item.Width * x.Item.Height)
.ThenByDescending(x => x.Item.Height)
.Select(x => x.Index).ToArray());
break;
case SortingMethod.HeightVolume:
sorted = new Permutation(PermutationTypes.Absolute,
items.Select((v, i) => new { Index = i, Item = v })
.OrderByDescending(x => x.Item.Height)
.ThenByDescending(x => x.Item.Depth * x.Item.Width * x.Item.Height)
.Select(x => x.Index).ToArray());
break;
case SortingMethod.AreaHeight:
sorted = new Permutation(PermutationTypes.Absolute,
items.Select((v, i) => new { Index = i, Item = v })
.OrderByDescending(x => x.Item.Depth * x.Item.Width)
.ThenByDescending(x => x.Item.Height)
.Select(x => x.Index).ToArray());
break;
case SortingMethod.HeightArea:
sorted = new Permutation(PermutationTypes.Absolute,
items.Select((v, i) => new { Index = i, Item = v })
.OrderByDescending(x => x.Item.Height)
.ThenByDescending(x => x.Item.Depth * x.Item.Width)
.Select(x => x.Index).ToArray());
break;
case SortingMethod.ClusteredAreaHeight:
double clusterRange = bin.Width * bin.Depth * delta;
sorted = new Permutation(PermutationTypes.Absolute,
items.Select((v, i) => new { Index = i, Item = v, ClusterId = (int)(Math.Ceiling(v.Width * v.Depth / clusterRange)) })
.GroupBy(x => x.ClusterId)
.Select(x => new { Cluster = x.Key, Items = x.OrderByDescending(y => y.Item.Height).ToList() })
.OrderByDescending(x => x.Cluster)
.SelectMany(x => x.Items)
.Select(x => x.Index).ToArray());
break;
case SortingMethod.ClusteredHeightArea:
double clusterRange2 = bin.Height * delta;
sorted = new Permutation(PermutationTypes.Absolute,
items.Select((v, i) => new { Index = i, Item = v, ClusterId = (int)(Math.Ceiling(v.Height / clusterRange2)) })
.GroupBy(x => x.ClusterId)
.Select(x => new { Cluster = x.Key, Items = x.OrderByDescending(y => y.Item.Depth * y.Item.Width).ToList() })
.OrderByDescending(x => x.Cluster)
.SelectMany(x => x.Items)
.Select(x => x.Index).ToArray());
break;
default: throw new ArgumentException("Unknown sorting method: " + sorting);
}
ExtremePointPermutationDecoderBase decoder = null;
switch (fitting) {
case FittingMethod.FirstFit:
decoder = new ExtremePointPermutationDecoder();
break;
case FittingMethod.FreeVolumeBestFit:
decoder = new FreeVolumeBestFitExtremePointPermutationDecoder();
break;
case FittingMethod.ResidualSpaceBestFit:
decoder = new ResidualSpaceBestFitExtremePointPermutationDecoder();
break;
default: throw new ArgumentException("Unknown fitting method: " + fitting);
}
var sol = decoder.Decode(sorted, bin, items, stackingConstraints);
var fit = evaluator.Evaluate(sol);
return Tuple.Create(sol, fit);
}
}
}