#region License Information /* HeuristicLab * Copyright (C) 2002-2016 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.Collections.Generic; using System.Linq; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.IntegerVectorEncoding; using HeuristicLab.Operators; using HeuristicLab.Optimization; using HeuristicLab.Parameters; using HeuristicLab.Persistence; namespace HeuristicLab.Problems.Orienteering { /// /// The used neighborhood operator is based on a two point exchange move. A move in /// the k-th neighborhood consists of removing k consecutive vertices from the tour, starting /// at a randomly selected position. Afterwards, a sorted list of all vertices not yet included /// in the current tour is built. The vertices are sorted in descending order with respect to the /// objective value increase using the current weights. Out of the first three entries with the /// highest ranking in this list, one randomly selected vertex is reinserted into the current tour /// at the same position as the removed vertices. This way, l new vertices are inserted into the /// tour. The largest neighborhood is a complete exchange of all vertices on the tour. /// The shaking procedure does not guarantee that the new tour does not exceed the cost /// limit Tmax. Therefore, in a repair step, a sorted list of all vertices in the tour is created. The /// vertices are sorted in descending order with respect to costs saved when removing the vertex /// from the tour. Vertices are removed as long as the cost limit is violated. /// (Schilde et. al. 2009) /// [Item("OrienteeringShakingOperator", @"Implements the shaking procedure described in Schilde M., Doerner K.F., Hartl R.F., Kiechle G. 2009. Metaheuristics for the bi-objective orienteering problem. Swarm Intelligence, Volume 3, Issue 3, pp 179-201.")] [StorableType("0f624eb9-cdc7-4109-96c6-1a306532ca54")] public sealed class OrienteeringShakingOperator : SingleSuccessorOperator, IMultiNeighborhoodShakingOperator, IStochasticOperator { #region Shaking Parameter Properties public IValueLookupParameter CurrentNeighborhoodIndexParameter { get { return (IValueLookupParameter)Parameters["CurrentNeighborhoodIndex"]; } } public ILookupParameter NeighborhoodCountParameter { get { return (ILookupParameter)Parameters["NeighborhoodCount"]; } } #endregion #region Parameter Properties public ILookupParameter IntegerVectorParameter { get { return (ILookupParameter)Parameters["IntegerVector"]; } } public ILookupParameter MaximumDistanceParameter { get { return (ILookupParameter)Parameters["MaximumDistance"]; } } public ILookupParameter StartingPointParameter { get { return (ILookupParameter)Parameters["StartingPoint"]; } } public ILookupParameter TerminalPointParameter { get { return (ILookupParameter)Parameters["TerminalPoint"]; } } public ILookupParameter DistanceMatrixParameter { get { return (ILookupParameter)Parameters["DistanceMatrix"]; } } public ILookupParameter ScoresParameter { get { return (ILookupParameter)Parameters["Scores"]; } } public ILookupParameter PointVisitingCostsParameter { get { return (ILookupParameter)Parameters["PointVisitingCosts"]; } } public ILookupParameter RandomParameter { get { return (ILookupParameter)Parameters["Random"]; } } #endregion [StorableConstructor] private OrienteeringShakingOperator(StorableConstructorFlag deserializing) : base(deserializing) { } private OrienteeringShakingOperator(OrienteeringShakingOperator original, Cloner cloner) : base(original, cloner) { } public OrienteeringShakingOperator() : base() { Parameters.Add(new ValueLookupParameter("CurrentNeighborhoodIndex", "The index of the operator that should be applied (k).")); Parameters.Add(new LookupParameter("NeighborhoodCount", "The number of operators that are available.")); Parameters.Add(new LookupParameter("IntegerVector", "The Orienteering Solution given in path representation.")); Parameters.Add(new LookupParameter("MaximumDistance", "The maximum distance constraint for a Orienteering solution.")); Parameters.Add(new LookupParameter("StartingPoint", "Index of the starting point.")); Parameters.Add(new LookupParameter("TerminalPoint", "Index of the ending point.")); Parameters.Add(new LookupParameter("DistanceMatrix", "The matrix which contains the distances between the points.")); Parameters.Add(new LookupParameter("Scores", "The scores of the points.")); Parameters.Add(new LookupParameter("PointVisitingCosts", "The costs for visiting a point.")); Parameters.Add(new LookupParameter("Random", "The random number generator that will be used.")); } public override IDeepCloneable Clone(Cloner cloner) { return new OrienteeringShakingOperator(this, cloner); } public override IOperation Apply() { var initialTour = IntegerVectorParameter.ActualValue; var distances = DistanceMatrixParameter.ActualValue; var scores = ScoresParameter.ActualValue; var startingPoint = StartingPointParameter.ActualValue.Value; var terminalPoint = TerminalPointParameter.ActualValue.Value; var pointVisitingCosts = PointVisitingCostsParameter.ActualValue.Value; double maxDistance = MaximumDistanceParameter.ActualValue.Value; int numPoints = scores.Length; if (NeighborhoodCountParameter.ActualValue == null) NeighborhoodCountParameter.ActualValue = new IntValue(initialTour.Length); else NeighborhoodCountParameter.ActualValue.Value = initialTour.Length; var random = RandomParameter.ActualValue; if (initialTour.Length > 2) { // Limit the neighborhood to the tour length int currentNeighborhood = CurrentNeighborhoodIndexParameter.ActualValue.Value + 1; int maximumNeighborhood = initialTour.Length - 2; // neighborhood limit within [0, changable tour length - 1) maximumNeighborhood = (maximumNeighborhood > currentNeighborhood) ? currentNeighborhood : maximumNeighborhood; int neighborhood = maximumNeighborhood; // Find all points that are not yet included in the tour and are // within the maximum distance allowed (ellipse) // and sort them with regard to their utility var visitablePoints = ( from point in Enumerable.Range(0, numPoints) // Calculate the distance when going from the starting point to this point and then to the end point let distance = distances[startingPoint, point] + distances[point, terminalPoint] + pointVisitingCosts // If this distance is feasible and the point is neither starting nor ending point, check the point where distance < maxDistance && point != startingPoint && point != terminalPoint // The point was not yet visited, so add it to the candidate list where !initialTour.Contains(point) // Calculate the utility of the point at this position let utility = scores[point] orderby utility select point ).ToList(); // Initialize the new tour var actualTour = new List { startingPoint }; // Perform the insertions according to the utility sorting InsertPoints(actualTour, initialTour, neighborhood, visitablePoints, random); // Bring the tour back to be feasible CleanupTour(actualTour, distances, maxDistance, pointVisitingCosts); // Set new Tour IntegerVectorParameter.ActualValue = new IntegerVector(actualTour.ToArray()); } return base.Apply(); } private void InsertPoints(List actualTour, IntegerVector initialTour, int neighborhood, List visitablePoints, IRandom random) { // Elect the starting index of the part to be replaced int tourSize = initialTour.Length; int randomPosition = random.Next(tourSize - neighborhood - 1) + 1; for (int position = 1; position < tourSize; position++) { if ((position < randomPosition) || (position > (randomPosition + neighborhood - 1))) { // Copy from initial tour when outside shaking range actualTour.Add(initialTour[position]); // Delete this point from the candidate list visitablePoints.Remove(initialTour[position]); } else { // Point from within shaking range if (visitablePoints.Count > 0) { // Add the point with the highest utility from the candidate list int randomFactor = random.Next(3); int insertionIndex = visitablePoints.Count - 1; if (visitablePoints.Count > 4) insertionIndex -= randomFactor; actualTour.Add(visitablePoints[insertionIndex]); // Delete this point from the candidate list visitablePoints.RemoveAt(insertionIndex); } else { // We don't have any points left that could be inserted so we can only re-insert // the removed and not already re-inserted points in a random order for (int reinsertPosition = randomPosition; reinsertPosition < randomPosition + neighborhood; reinsertPosition++) { bool alreadyReinserted = actualTour.Contains(initialTour[reinsertPosition]); if (!alreadyReinserted) visitablePoints.Add(initialTour[reinsertPosition]); } int randomIndex = random.Next(visitablePoints.Count); actualTour.Add(visitablePoints[randomIndex]); visitablePoints.Clear(); } } } } private void CleanupTour(List actualTour, DistanceMatrix distances, double maxDistance, double pointVisitingCosts) { // Sort the points on the tour according to their costs savings when removed var distanceSavings = ( from removePosition in Enumerable.Range(1, actualTour.Count - 2) let saving = distances.CalculateRemovementSaving(actualTour, removePosition, pointVisitingCosts) orderby saving descending select new SavingInfo { Index = removePosition, Saving = saving } ).ToList(); double tourLength = distances.CalculateTourLength(actualTour, pointVisitingCosts); // As long as the created path is infeasible, remove elements while (tourLength > maxDistance) { // Remove the point that frees the largest distance // Note, distance savings are not updated after removal tourLength -= distances.CalculateRemovementSaving(actualTour, distanceSavings[0].Index, pointVisitingCosts); actualTour.RemoveAt(distanceSavings[0].Index); // Shift indices due to removal of a point in the tour for (int i = 1; i < distanceSavings.Count; i++) { if (distanceSavings[i].Index > distanceSavings[0].Index) { distanceSavings[i].Index--; } } distanceSavings.RemoveAt(0); } } private class SavingInfo { public int Index; public double Saving; } } }