#region License Information /* HeuristicLab * Copyright (C) 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.IO; using System.Linq; using System.Threading; using HEAL.Attic; using HeuristicLab.Analysis; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Encodings.IntegerVectorEncoding; using HeuristicLab.Optimization; using HeuristicLab.Optimization.Operators; using HeuristicLab.Parameters; using HeuristicLab.Problems.Instances; using HeuristicLab.Problems.Instances.Types; using HeuristicLab.Problems.TravelingSalesman; namespace HeuristicLab.Problems.Orienteering { [Item("Orienteering Problem (OP)", "Represents a single-objective Orienteering Problem.")] [Creatable(CreatableAttribute.Categories.CombinatorialProblems, Priority = 115)] [StorableType("0B8DB4A4-F183-4368-86C6-C51289B183D2")] public sealed class OrienteeringProblem : IntegerVectorProblem, IProblemInstanceConsumer, IProblemInstanceConsumer, IProblemInstanceConsumer { [Storable] public ValueParameter OrienteeringProblemDataParameter { get; private set; } [Storable] public OptionalValueParameter BestKnownSolutionParameter { get; private set; } [Storable] private IResultParameter BestOrienteeringSolutionParameter { get; set; } public IResultDefinition BestOrienteeringSolution => BestOrienteeringSolutionParameter; public IOrienteeringProblemData OrienteeringProblemData { get { return OrienteeringProblemDataParameter.Value; } set { OrienteeringProblemDataParameter.Value = value; } } public OrienteeringSolution BestKnownSolution { get { return BestKnownSolutionParameter.Value; } set { BestKnownSolutionParameter.Value = value; } } [StorableConstructor] private OrienteeringProblem(StorableConstructorFlag _) : base(_) { } private OrienteeringProblem(OrienteeringProblem original, Cloner cloner) : base(original, cloner) { OrienteeringProblemDataParameter = cloner.Clone(original.OrienteeringProblemDataParameter); BestKnownSolutionParameter = cloner.Clone(original.BestKnownSolutionParameter); BestOrienteeringSolutionParameter = cloner.Clone(original.BestOrienteeringSolutionParameter); } public override IDeepCloneable Clone(Cloner cloner) { return new OrienteeringProblem(this, cloner); } public OrienteeringProblem() : base(new IntegerVectorEncoding("Route")) { Parameters.Add(OrienteeringProblemDataParameter = new ValueParameter("OP Data", "The main parameters for the orienteering problem.", new OrienteeringProblemData())); Parameters.Add(BestKnownSolutionParameter = new OptionalValueParameter("BestKnownSolution", "The best known solution of this Orienteering instance.")); Parameters.Add(BestOrienteeringSolutionParameter = new ResultParameter("Best Orienteering Solution", "The best so far solution found.")); Maximization = true; InitializeOperators(); } public override ISingleObjectiveEvaluationResult Evaluate(IntegerVector solution, IRandom random, CancellationToken cancellationToken) { var data = OrienteeringProblemData; var score = CalculateScore(data, solution); var travelCosts = CalculateTravelCosts(data, solution); var quality = CalculateQuality(data, score, travelCosts); return new SingleObjectiveEvaluationResult(quality); } public static double CalculateQuality(IOrienteeringProblemData data, double score, double travelCosts) { if (travelCosts > data.MaximumTravelCosts) return data.MaximumTravelCosts - travelCosts; // negative excessive distance return score; } public static double CalculateScore(IOrienteeringProblemData data, IEnumerable solution) { return solution.Sum(t => data.GetScore(t)); } public static double CalculateTravelCosts(IOrienteeringProblemData data, IntegerVector solution) { var distance = data.GetPathDistance(solution, closed: false); distance += (solution.Length - 2) * data.PointVisitingCosts; return distance; } public static double CalculateTravelCosts(IOrienteeringProblemData data, IList solution) { var distance = data.GetPathDistance(solution, closed: false); distance += (solution.Count - 2) * data.PointVisitingCosts; return distance; } public override void Analyze(IntegerVector[] vectors, double[] qualities, ResultCollection results, IRandom random) { base.Analyze(vectors, qualities, results, random); var data = OrienteeringProblemData; var best = GetBestSolution(vectors, qualities).Item1; var score = CalculateScore(OrienteeringProblemData, best); var travelCosts = CalculateTravelCosts(OrienteeringProblemData, best); var quality = CalculateQuality(OrienteeringProblemData, score, travelCosts); if (double.IsNaN(BestKnownQuality) || IsBetter(quality, BestKnownQuality)) { BestKnownQuality = quality; BestKnownSolutionParameter.ActualValue = data.GetSolution((IntegerVector)best.Clone(), quality, score, travelCosts); } var bestSoFar = BestOrienteeringSolutionParameter.ActualValue; if (bestSoFar == null || IsBetter(quality, bestSoFar.Quality.Value)) { bestSoFar = data.GetSolution((IntegerVector)best.Clone(), quality, score, travelCosts); BestOrienteeringSolutionParameter.ActualValue = bestSoFar; } } public static double CalculateInsertionCosts(IOrienteeringProblemData data, IList path, int insertPosition, int point) { double detour = data.GetDistance(path[insertPosition - 1], point) + data.GetDistance(point, path[insertPosition]); detour += data.PointVisitingCosts; detour -= data.GetDistance(path[insertPosition - 1], path[insertPosition]); return detour; } public static double CalculateReplacementCosts(IOrienteeringProblemData data, IList path, int replacePosition, int point) { double detour = data.GetDistance(path[replacePosition - 1], point) + data.GetDistance(point, path[replacePosition + 1]); detour -= data.GetDistance(path[replacePosition - 1], path[replacePosition]) + data.GetDistance(path[replacePosition], path[replacePosition + 1]); return detour; } public static double CalculateRemovementSaving(IOrienteeringProblemData data, IList path, int removePosition) { double saving = data.GetDistance(path[removePosition - 1], path[removePosition]); saving += data.GetDistance(path[removePosition], path[removePosition + 1]); saving -= data.GetDistance(path[removePosition - 1], path[removePosition + 1]); saving += data.PointVisitingCosts; return saving; } protected override void OnEncodingChanged() { base.OnEncodingChanged(); ParameterizeOperators(); } protected override void OnEvaluatorChanged() { base.OnEvaluatorChanged(); ParameterizeOperators(); } private void InitializeOperators() { Encoding.SolutionCreator = new GreedyOrienteeringTourCreator() { OrienteeringProblemDataParameter = { ActualName = OrienteeringProblemDataParameter.Name } }; Operators.Add(new OrienteeringLocalImprovementOperator() { OrienteeringProblemDataParameter = { ActualName = OrienteeringProblemDataParameter.Name } }); Operators.Add(new OrienteeringShakingOperator() { OrienteeringProblemDataParameter = { ActualName = OrienteeringProblemDataParameter.Name } }); Operators.Add(new QualitySimilarityCalculator()); Operators.Add(new PopulationSimilarityAnalyzer(Operators.OfType())); ParameterizeOperators(); } private void ParameterizeOperators() { foreach (var op in Operators.OfType()) { op.IntegerVectorParameter.ActualName = Encoding.Name; op.QualityParameter.ActualName = Evaluator.QualityParameter.ActualName; } foreach (var op in Operators.OfType()) { op.IntegerVectorParameter.ActualName = Encoding.Name; } foreach (var similarityCalculator in Operators.OfType()) { similarityCalculator.SolutionVariableName = Encoding.Name; similarityCalculator.QualityVariableName = Evaluator.QualityParameter.ActualName; } } #region Instance consuming public void Load(OPData data) { if (data.Coordinates == null && data.Distances == null) throw new InvalidDataException("The given instance specifies no coordinates or distance matrix!"); if (data.Coordinates != null && data.Coordinates.GetLength(1) != 2) throw new InvalidDataException("The coordinates of the given instance are not in the right format, there need to be one row for each customer and two columns for the x and y coordinates."); // Clear old solutions BestKnownSolution = null; Name = data.Name; Description = data.Description; var tsp = TSP.GetDataFromInstance(data); OrienteeringProblemData = new OrienteeringProblemData(tsp, data.StartingPoint, data.TerminalPoint, data.Scores, data.MaximumDistance, data.PointVisitingCosts); } public void Load(TSPData data) { if (data.Coordinates == null && data.Distances == null) throw new InvalidDataException("The given instance specifies no coordinates or distance matrix!"); if (data.Coordinates != null && data.Coordinates.GetLength(1) != 2) throw new InvalidDataException("The coordinates of the given instance are not in the right format, there need to be one row for each customer and two columns for the x and y coordinates."); // Clear old solutions BestKnownSolution = null; Name = data.Name; Description = data.Description; var tsp = TSP.GetDataFromInstance(data); var avgDist = 0.0; for (var i = 0; i < data.Dimension - 1; i++) for (var j = i + 1; i < data.Dimension; j++) avgDist += tsp.GetDistance(i, j); avgDist /= (data.Dimension - 1) * data.Dimension / 2.0; OrienteeringProblemData = new OrienteeringProblemData(tsp, 0, data.Dimension - 1, Enumerable.Repeat(1.0, data.Dimension).ToArray(), 5 * avgDist, 0); } public void Load(CVRPData data) { if (data.Coordinates == null && data.Distances == null) throw new InvalidDataException("The given instance specifies no coordinates or distance matrix!"); if (data.Coordinates != null && data.Coordinates.GetLength(1) != 2) throw new InvalidDataException("The coordinates of the given instance are not in the right format, there need to be one row for each customer and two columns for the x and y coordinates."); // Clear old solutions BestKnownSolution = null; Name = data.Name; Description = data.Description; var tsp = TSP.GetDataFromInstance(data); OrienteeringProblemData = new OrienteeringProblemData(tsp, 0, 0, data.Demands, data.Capacity * 2, 0); } #endregion } }