/// <summary> /// Calculates the weight for a part of the tour including the turns at first and last position. /// </summary> /// <param name="part">The part to calculate for.</param> /// <param name="timeBefore">The time when arriving at the first customer, excluding the turn at that first customer.</param> public int TimeAndViolationsForPart(IEnumerable <int> part, float timeBefore, out float time, out float waitTime, out float violatedTime, ref bool[] validFlags) { var times = this.Times; var windows = this.Windows; var turnPenalties = this.TurnPenalties; time = timeBefore; violatedTime = 0f; waitTime = 0f; var violated = 0; var previousFrom = int.MaxValue; foreach (var directedId in part) { // extract turns and stuff from directed id. int arrivalId, departureId, id, turn; DirectedHelper.ExtractAll(directedId, out arrivalId, out departureId, out id, out turn); // add the weight from the previous customer to the current one. var turnPenalty = 0f; if (previousFrom != int.MaxValue) { // there is a previous, add the travel time. time = time + times[previousFrom][arrivalId]; // and keep the turn penalty. turnPenalty += turnPenalties[turn]; } else { // first customer. // add turn penalty at first customer. time += turnPenalties[DirectedHelper.ExtractTurn(directedId)]; } // check the windows (before turn-penalties). var window = windows[id]; validFlags[id] = true; if (window.Max < (time + waitTime)) { // ok, unfeasible. violatedTime += (time + waitTime) - window.Max; validFlags[id] = false; violated++; } if (window.Min > (time + waitTime)) { // wait here! waitTime += (window.Min - (time + waitTime)); } // add the turn penalty. time += turnPenalty; previousFrom = departureId; } time = time - timeBefore; // correct the time again, the time returned is the time of only this part. return(violated); }
/// <summary> /// Calculates the fitness value of the given solution. /// </summary> public sealed override float Calculate(TSProblem problem, Tour solution) { if (solution.Count == 1) { return(0); } var isLoop = solution.Last == solution.First; var weights = problem.Weights; var weight = 0f; var previousFrom = int.MaxValue; var firstTo = int.MaxValue; var lastTurn = 0f; foreach (var directedId in solution) { // extract turns and stuff from directed id. int arrivalId, departureId, id, turn; DirectedHelper.ExtractAll(directedId, out arrivalId, out departureId, out id, out turn); // add the weight from the previous customer to the current one. if (previousFrom != int.MaxValue) { weight = weight + weights[previousFrom][arrivalId]; } else { firstTo = arrivalId; } // add turn penalty. if (isLoop || id != problem.First) { lastTurn = problem.TurnPenalties[turn]; weight += lastTurn; } previousFrom = departureId; } if (solution.Last == null) { weight -= lastTurn; } // add the weight between last and first. if (previousFrom != int.MaxValue && isLoop) { weight = weight + weights[previousFrom][firstTo]; } return(weight); }
/// <summary> /// Calculates the fitness value of the given solution. /// </summary> public sealed override STSPFitness Calculate(STSProblem problem, Tour solution) { // TODO: unittest this stuff! var fitness = new STSPFitness() { Customers = solution.Count, Weight = 0 }; var weights = problem.Weights; var weight = 0f; var previousFrom = int.MaxValue; var firstTo = int.MaxValue; foreach (var directedId in solution) { // extract turns and stuff from directed id. int arrivalId, departureId, id, turn; DirectedHelper.ExtractAll(directedId, out arrivalId, out departureId, out id, out turn); // add the weight from the previous customer to the current one. if (previousFrom != int.MaxValue) { weight = weight + weights[previousFrom][arrivalId]; } else { firstTo = arrivalId; } // add turn penalty. weight += problem.TurnPenalties[turn]; previousFrom = departureId; } // add the weight between last and first. if (previousFrom != int.MaxValue && solution.First == solution.Last) { weight = weight + weights[previousFrom][firstTo]; } fitness.Weight = weight; return(fitness); }
/// <summary> /// Calculates the fitness value of the given solution. /// </summary> public sealed override float Calculate(SequenceDirectedProblem problem, Tour solution) { var weights = problem.Weights; var weight = 0f; var previousFrom = int.MaxValue; var firstTo = int.MaxValue; foreach (var directedId in solution) { // extract turns and stuff from directed id. int arrivalId, departureId, id, turn; DirectedHelper.ExtractAll(directedId, out arrivalId, out departureId, out id, out turn); // add the weight from the previous customer to the current one. if (previousFrom != int.MaxValue) { weight = weight + weights[previousFrom][arrivalId]; } else { firstTo = arrivalId; } // add turn penalty. weight += problem.TurnPenalties[turn]; previousFrom = departureId; } // add the weight between last and first. if (previousFrom != int.MaxValue && solution.First == solution.Last) { weight = weight + weights[previousFrom][firstTo]; } return(weight); }
/// <summary> /// Applies this operator. /// </summary> public bool Apply(SequenceDirectedProblem problem, SequenceDirectedObjective objective, Tour solution, out float delta) { var weights = problem.Weights; delta = objective.Zero; var fitnessBefore = objective.Calculate(problem, solution); var enumerator = solution.GetEnumerator(); while (enumerator.MoveNext()) { var previous = enumerator.Current; var previousDepartureId = DirectedHelper.ExtractDepartureId(previous); var current = solution.GetNeigbour(previous); if (current == Constants.NOT_SET) { // last of open. continue; } var next = solution.GetNeigbour(current); var nextArrivalId = Constants.NOT_SET; if (next != Constants.NOT_SET) { nextArrivalId = DirectedHelper.ExtractArrivalId(next); } int currentTurn, currentId, currentArrivalId, currentDepartureId; DirectedHelper.ExtractAll(current, out currentArrivalId, out currentDepartureId, out currentId, out currentTurn); var weightBefore = weights[previousDepartureId][currentArrivalId]; var weightAfter = float.MaxValue; var newDirectedId = Constants.NOT_SET; if (next == Constants.NOT_SET) { // there is no next, only one option here: // 0 or 1: arrival id offset = 0 OR // 2 or 3: arrival id offset = 1 if (currentArrivalId % 2 == 0) { // check turn = 2. weightAfter = weights[previousDepartureId][currentArrivalId + 1]; if (weightAfter < weightBefore) { // do the switch. newDirectedId = DirectedHelper.BuildDirectedId(currentId, 2); } } else { // check turn = 0 weightAfter = weights[previousDepartureId][currentArrivalId - 1]; if (weightAfter < weightBefore) { // do the switch. newDirectedId = DirectedHelper.BuildDirectedId(currentId, 0); } } } else { // three options left, excluding the current one of 4. weightBefore += weights[currentDepartureId][nextArrivalId]; weightBefore += problem.TurnPenalties[currentTurn]; currentArrivalId = currentArrivalId - (currentArrivalId % 2); currentDepartureId = currentDepartureId - (currentDepartureId % 2); for (var i = 0; i < 4; i++) { if (i == currentTurn) { continue; } int arrivalOffset, departureOffset; DirectedHelper.ExtractOffset(i, out arrivalOffset, out departureOffset); var newWeightAfter = weights[previousDepartureId][currentArrivalId + arrivalOffset] + weights[currentDepartureId + departureOffset][nextArrivalId] + problem.TurnPenalties[i]; if (newWeightAfter < weightAfter && newWeightAfter < weightBefore) { newDirectedId = DirectedHelper.BuildDirectedId(currentId, i); weightAfter = newWeightAfter; } } } // switch if a new directed if was found. if (newDirectedId != Constants.NOT_SET) { solution.Replace(current, newDirectedId); } } var fitnessAfter = objective.Calculate(problem, solution); delta = objective.Subtract(problem, fitnessBefore, fitnessAfter); return(objective.CompareTo(problem, delta, objective.Zero) > 0); }
/// <summary> /// Returns true if there was an improvement, false otherwise. /// </summary> /// <returns></returns> public bool Apply(TSPTWProblem problem, TSPTWObjective objective, Tour tour, out float delta) { delta = 0; var fitness = objective.Calculate(problem, tour); var customers = new List <int>(problem.Times.Length + 1); customers.AddRange(tour); if (tour.Last == tour.First) { // add last customer at the end if it's the same as the first one. customers.Add(tour.Last.Value); } // select two edges with at least one edge between them at both sides of the tour. var weightBefore = 0f; if (problem.Windows[DirectedHelper.ExtractId(customers[0])].Min > weightBefore) { // wait here! weightBefore = problem.Windows[DirectedHelper.ExtractId(customers[0])].Min; } for (var edge1 = 0; edge1 < customers.Count - 3; edge1++) { // iterate over all from-edges. var edge11 = customers[edge1 + 0]; var edge12 = customers[edge1 + 1]; // extract directional information. int edge11arrivalId, edge11departureId, edge11id, edge11turn; DirectedHelper.ExtractAll(edge11, out edge11arrivalId, out edge11departureId, out edge11id, out edge11turn); int edge12arrivalId, edge12departureId, edge12id, edge12turn; DirectedHelper.ExtractAll(edge12, out edge12arrivalId, out edge12departureId, out edge12id, out edge12turn); for (var edge2 = edge1 + 2; edge2 < customers.Count - 1; edge2++) { // iterate over all possible to-edges given the from-edge. var edge21 = customers[edge2 + 0]; var edge22 = customers[edge2 + 1]; // at this point we have two edges 11->12->...->21->22->... // attempt to reverse the part in between. // 1: calculate the reverse part and check if feasible. // 2: if not feasible then stop. // 3: if feasible then calculate the forward part. // 4: if better then accept the result. // calculate het best possible reverse sequence by switching turns and departure and arrivel id's. // First start by creating a sequence with all turns and arrival and departure id's equal to zero. var part = new List <int>(); part.Add(DirectedHelper.UpdateDepartureOffset(edge11, 0)); for (var b = edge2; b > edge1; b--) { part.Add(DirectedHelper.BuildDirectedId(DirectedHelper.ExtractId(customers[b]), 0)); } part.Add(DirectedHelper.UpdateArrivalOffset(edge22, 0)); var localDelta = this.OptimizePart(problem, part); while (localDelta > 0.1) { localDelta = this.OptimizePart(problem, part); } int violated; float violatedTime, waitTime; var partTime = objective.CalculateTimeForPart(problem, part, weightBefore, out violated, out violatedTime, out waitTime); if (violated == 0) { // new part is feasible, this should be fine. var existingPart = new List <int>(); existingPart.Add(edge11); for (var c = edge1 + 1; c < edge2; c++) { existingPart.Add(customers[c]); } existingPart.Add(edge22); var existingPartTime = objective.CalculateTimeForPart(problem, existingPart, weightBefore, out violated, out violatedTime, out waitTime); if (existingPartTime > partTime) { // an improvment, add the new part. tour.Replace(edge11, part[0]); tour.Replace(edge22, part[part.Count - 1]); for (var c = 1; c < part.Count; c++) { tour.ReplaceEdgeFrom(part[c - 1], part[c]); } var newFitness = objective.Calculate(problem, tour); delta = fitness - newFitness; return(true); } } } // update weight before. weightBefore += problem.Times[edge11arrivalId][edge12arrivalId]; weightBefore += problem.TurnPenalties[edge11turn]; if (problem.Windows[DirectedHelper.ExtractId(customers[edge1])].Min > weightBefore) { // wait here! weightBefore = problem.Windows[DirectedHelper.ExtractId(customers[edge1])].Min; } } return(false); }
/// <summary> /// Optimizes the given part of the tour by choosing the best improvements in either the departureOffset or arrivalOffset of the first/last customers or the turns at any of the intermediate ones. /// </summary> private float OptimizePart(TSPTWProblem problem, List <int> part) { int turn1, arrivalId1, departureId1, id1; int turn2, arrivalId2, departureId2, id2; int turn3, arrivalId3, departureId3, id3; var delta = 0f; // the positive difference. var arrivalIdChange = -1; var departureIdChange = -1; var customerIdx = -1; var customerTurn = -1; // try changing the departureId. DirectedHelper.ExtractAll(part[0], out arrivalId1, out departureId1, out id1, out turn1); DirectedHelper.ExtractAll(part[1], out arrivalId2, out departureId2, out id2, out turn2); var weight = problem.TurnPenalties[turn1]; weight += problem.Times[departureId1][arrivalId2]; var new0 = DirectedHelper.SwitchDepartureOffset(part[0]); DirectedHelper.ExtractAll(new0, out arrivalId3, out departureId3, out id3, out turn3); var newWeight = problem.TurnPenalties[turn3]; newWeight += problem.Times[departureId3][arrivalId2]; if (newWeight < weight) { // there was an improvement found in changing the departure id. departureIdChange = DirectedHelper.ExtractDepartureId(new0); delta = newWeight - weight; } // try changing the arrivalId. DirectedHelper.ExtractAll(part[part.Count - 2], out arrivalId1, out departureId1, out id1, out turn1); DirectedHelper.ExtractAll(part[part.Count - 1], out arrivalId2, out departureId2, out id2, out turn2); weight = problem.TurnPenalties[turn2]; weight += problem.Times[departureId1][arrivalId2]; var newLast = DirectedHelper.SwitchArrivalOffset(part[part.Count - 1]); DirectedHelper.ExtractAll(newLast, out arrivalId3, out departureId3, out id3, out turn3); newWeight = problem.TurnPenalties[turn3]; newWeight += problem.Times[departureId1][arrivalId3]; if (newWeight < weight && delta < (newWeight - weight)) { // there was an improvement found in changing the arrival id. arrivalIdChange = DirectedHelper.ExtractDepartureId(newLast); departureIdChange = -1; delta = newWeight - weight; } for (var c = 1; c < part.Count - 1; c++) { var perviousDepartureId = DirectedHelper.ExtractDepartureId(part[c - 1]); DirectedHelper.ExtractAll(part[c], out arrivalId1, out departureId1, out id1, out turn1); var nextArrivalid = DirectedHelper.ExtractArrivalId(part[c + 1]); weight = problem.Times[perviousDepartureId][arrivalId1]; weight += problem.TurnPenalties[turn1]; weight += problem.Times[departureId1][nextArrivalid]; for (var t = 0; t < 3; t++) { if (t == turn1) { continue; } var newDirectedId = DirectedHelper.BuildDirectedId(id1, t); DirectedHelper.ExtractAll(newDirectedId, out arrivalId2, out departureId2, out id2, out turn2); newWeight = problem.Times[perviousDepartureId][arrivalId2]; newWeight += problem.TurnPenalties[turn2]; newWeight += problem.Times[departureId2][nextArrivalid]; if (newWeight < weight && delta < (newWeight - weight)) { // there was an improvement found in changing the turn. arrivalIdChange = -1; departureIdChange = -1; customerIdx = c; customerTurn = t; delta = newWeight - weight; } } } if (delta > 0) { if (departureIdChange != -1) { part[0] = DirectedHelper.SwitchDepartureOffset(part[0]); } else if (arrivalIdChange != -1) { part[part.Count - 1] = DirectedHelper.SwitchArrivalOffset(part[part.Count - 1]); } else if (customerIdx != -1) { part[customerIdx] = DirectedHelper.BuildDirectedId( DirectedHelper.ExtractId(part[customerIdx]), customerTurn); } } return(delta); }
/// <summary> /// Gets the minimum weight from customer1 -> customer3 while inserting customer2 and the best direction and turns to use. /// </summary> public static float CalculateCheapestInsert(this float[][] weights, float[] penalties, int directedId1, int id2, int directedId3, bool includeTurn1, bool includeTurn3, out int departureOffset1, out int arrivalOffset3, out int turn2) { // extract existing data. int id1, turn1, arrivalId1, departureId1, arrivalOffset1, temp; DirectedHelper.ExtractAll(directedId1, out arrivalId1, out departureId1, out id1, out turn1); DirectedHelper.ExtractOffset(turn1, out arrivalOffset1, out temp); int id3, turn3, arrivalId3, departureId3, departureOffset3; DirectedHelper.ExtractAll(directedId3, out arrivalId3, out departureId3, out id3, out turn3); DirectedHelper.ExtractOffset(turn3, out temp, out departureOffset3); // calculate current weight. var weightBefore = weights[departureId1][arrivalId3]; if (includeTurn1) { weightBefore += penalties[turn1]; } if (includeTurn3) { weightBefore += penalties[turn3]; } // evaluate all possibilities. var best = float.MaxValue; var base1 = id1 * 2; var base2 = id2 * 2; var base3 = id3 * 2; departureOffset1 = Constants.NOT_SET; arrivalOffset3 = Constants.NOT_SET; turn2 = Constants.NOT_SET; for (var do1 = 0; do1 < 2; do1++) { var d1 = base1 + do1; var turn1Weight = 0f; if (includeTurn1) { turn1 = DirectedHelper.BuildTurn(arrivalOffset1, do1); turn1Weight = penalties[turn1]; } for (var ao3 = 0; ao3 < 2; ao3++) { var a3 = base3 + ao3; var turn3Weight = 0f; if (includeTurn3) { turn3 = DirectedHelper.BuildTurn(departureOffset3, ao3); turn3Weight = penalties[turn3]; } for (var t = 0; t < 4; t++) { int ao2, do2; DirectedHelper.ExtractOffset(t, out ao2, out do2); var weight = weights[d1][base2 + ao2] + weights[base2 + do2][a3] + penalties[t] + turn1Weight + turn3Weight; if (weight < best) { best = weight; departureOffset1 = do1; arrivalOffset3 = ao3; turn2 = t; } } } } return best - weightBefore; }
/// <summary> /// Calculates total time and violations if the given id was moved after 'before'. Assumes the tour is closed. /// </summary> public int TimeAndViolations(IEnumerable <int> tour, out float time, out float waitTime, out float violatedTime, ref bool[] validFlags) { var times = this.Times; var windows = this.Windows; var turnPenalties = this.TurnPenalties; time = 0f; violatedTime = 0f; waitTime = 0f; var violated = 0; var previousFrom = int.MaxValue; var firstTo = int.MaxValue; var first = Constants.NOT_SET; foreach (var directedId in tour) { if (first == Constants.NOT_SET) { first = directedId; } // extract turns and stuff from directed id. int arrivalId, departureId, id, turn; DirectedHelper.ExtractAll(directedId, out arrivalId, out departureId, out id, out turn); // add the weight from the previous customer to the current one. var turnPenalty = 0f; if (previousFrom != int.MaxValue) { // there is a previous, add the travel time. time = time + times[previousFrom][arrivalId]; // and keep the turn penalty. turnPenalty += turnPenalties[turn]; } else { // first customer. firstTo = arrivalId; } // check the windows (before turn-penalties). var window = windows[id]; validFlags[id] = true; if (window.Max < (time + waitTime)) { // ok, unfeasible. violatedTime += (time + waitTime) - window.Max; validFlags[id] = false; violated++; } if (window.Min > (time + waitTime)) { // wait here! waitTime += (window.Min - (time + waitTime)); } // add the turn penalty. time += turnPenalty; previousFrom = departureId; } // add the weight between last and first. if (previousFrom != int.MaxValue) { time = time + times[previousFrom][firstTo]; // add turn penalty at first customer. time += turnPenalties[DirectedHelper.ExtractTurn(first)]; } return(violated); }
/// <summary> /// Applies this operator. /// </summary> public bool Apply(TSProblem problem, TSPObjective objective, Tour solution, out float delta) { if (problem.Weights.Length <= 2) { delta = 0; return(false); } var weights = problem.Weights; delta = 0; // test switching directions in random order. if (_pool == null || solution.Count != _pool.Size) { // create a new pool. _pool = new RandomPool(solution.Count); } else { // just reset the existing one. _pool.Reset(); } while (_pool.MoveNext()) { var previous = solution.GetDirectedId(_pool.Current); var previousDepartureId = DirectedHelper.ExtractDepartureId(previous); var current = solution.GetNeigbour(previous); if (current == Constants.NOT_SET) { // last of open. continue; } var next = solution.GetNeigbour(current); var nextArrivalId = Constants.NOT_SET; if (next != Constants.NOT_SET) { nextArrivalId = DirectedHelper.ExtractArrivalId(next); } int currentTurn, currentId, currentArrivalId, currentDepartureId; DirectedHelper.ExtractAll(current, out currentArrivalId, out currentDepartureId, out currentId, out currentTurn); var weightBefore = weights[previousDepartureId][currentArrivalId]; var weightAfter = float.MaxValue; var newDirectedId = Constants.NOT_SET; if (next == Constants.NOT_SET) { // there is no next, only one option here: // 0 or 1: arrival id offset = 0 OR // 2 or 3: arrival id offset = 1 if (currentArrivalId % 2 == 0) { // check turn = 2. weightAfter = weights[previousDepartureId][currentArrivalId + 1]; if (weightAfter < weightBefore) { // do the switch. newDirectedId = DirectedHelper.BuildDirectedId(currentId, 2); } } else { // check turn = 0 weightAfter = weights[previousDepartureId][currentArrivalId - 1]; if (weightAfter < weightBefore) { // do the switch. newDirectedId = DirectedHelper.BuildDirectedId(currentId, 0); } } } else { // three options left, excluding the current one of 4. weightBefore += weights[currentDepartureId][nextArrivalId]; weightBefore += problem.TurnPenalties[currentTurn]; currentArrivalId = currentArrivalId - (currentArrivalId % 2); currentDepartureId = currentDepartureId - (currentDepartureId % 2); for (var i = 0; i < 4; i++) { if (i == currentTurn) { continue; } int arrivalOffset, departureOffset; DirectedHelper.ExtractOffset(i, out arrivalOffset, out departureOffset); var newWeightAfter = weights[previousDepartureId][currentArrivalId + arrivalOffset] + weights[currentDepartureId + departureOffset][nextArrivalId] + problem.TurnPenalties[i]; if (newWeightAfter < weightAfter && newWeightAfter < weightBefore) { newDirectedId = DirectedHelper.BuildDirectedId(currentId, i); weightAfter = newWeightAfter; } } } // switch if a new directed if was found. if (newDirectedId != Constants.NOT_SET) { delta = weightAfter - weightBefore; // negative when improvement. solution.Replace(current, newDirectedId); } } return(delta < 0); }