/** * */ package vroom.trsp.optimization.localSearch; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import vroom.common.heuristics.Move; import vroom.common.heuristics.NeighborhoodBase; import vroom.common.utilities.optimization.IInstance; import vroom.common.utilities.optimization.IMove; import vroom.common.utilities.optimization.IParameters; import vroom.trsp.datamodel.ITRSPTour; import vroom.trsp.datamodel.TRSPDetailedSolutionChecker; import vroom.trsp.datamodel.TRSPTour; import vroom.trsp.datamodel.TRSPTour.TRSPTourIterator; import vroom.trsp.optimization.localSearch.TRSPShiftLateness.TRSPShiftLatenessMove; import vroom.trsp.util.TRSPLogging; /** * <code>TRSPShift</code> is an implementation of the <code>shift</code> neighborhood for the TRSP problem and data * model. * <p> * Creation date: Mar 17, 2011 - 1:30:00 PM * </p> * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public class TRSPShiftLateness extends NeighborhoodBase<TRSPTour, TRSPShiftLatenessMove> { /** * <code>TRSPShiftLatenessMove</code> is a representation a <code>shift</code> move for the TRSP. * <p> * Creation date: Mar 17, 2011 - 1:45:02 PM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public static class TRSPShiftLatenessMove extends Move { /** The id of the shifted node */ private final int mNode; /** The id of the node new successor */ private final int mNewSucc; /** <code>true</code> if the shift is forward, <code>false</code> if it is backward */ private final boolean mForward; /** An optional field describing the change in updated lateness */ private Lateness mUpdatedLateness; /** * Creates a new <code>TRSPShiftLatenessMove</code>. * * @param improvement * the improvement resulting from this move * @param node * the first node * @param newSucc * the second node */ protected TRSPShiftLatenessMove(double improvement, int node, int newSucc, boolean forward) { super(improvement); mNode = node; mNewSucc = newSucc; mForward = forward; } /** * Getter for <code>updatedLateness</code> * * @return the updatedLateness */ public Lateness getUpdatedLateness() { return mUpdatedLateness; } /** * Setter for <code>updatedLateness</code> * * @param updatedLateness * the updatedLateness to set */ public void setUpdatedLateness(Lateness updatedLateness) { mUpdatedLateness = updatedLateness; } /** * Getter for <code>node</code> * * @return the node */ public int getNode() { return mNode; } /** * Getter for <code>newSucc</code> * * @return the newSucc */ public int getNewSucc() { return mNewSucc; } /** * Getter for <code>forward</code> * * @return the forward */ public boolean isForward() { return mForward; } @Override public String getMoveName() { return "shift"; } /** * Build the new sequence of node that will result from this <code>1-shift</code> move. Nodes which visit order * in unchanged are ignored, except the immediate predecessor of the first changed node, which will appear * first. * * @param tour * the considered tour * @return a list containing the changed sequence resulting from the execution of this <code>1-shift</code> move * on the given <code>tour</code>. */ public List<Integer> changedSequence(TRSPTour tour) { LinkedList<Integer> changedSequence = new LinkedList<Integer>(); if (isForward()) { changedSequence.add(tour.getPred(getNode())); TRSPTourIterator it = tour.iterator(tour.getSucc(getNode())); while (it.hasNext()) { int next = it.next(); if (next == getNewSucc()) // Insert the node changedSequence.add(getNode()); changedSequence.add(next); } // if newSucc==UNDEFINED it means the node is moved to the end of the tour if (getNewSucc() == ITRSPTour.UNDEFINED) changedSequence.add(getNode()); } else { // changedSequence.add(tour.getPred(node)); changedSequence.add(tour.getPred(getNewSucc())); changedSequence.add(getNode()); TRSPTourIterator it = tour.iterator(getNewSucc()); while (it.hasNext()) { int next = it.next(); if (next != getNode()) // Skip the node changedSequence.add(next); } } return changedSequence; } @Override public String toString() { return String.format("%s(%s:%s-%s,%.3f)", getMoveName(), mForward ? "F" : "B", mNode, mNewSucc, getImprovement()); } } @Override public boolean executeMove(TRSPTour tour, IMove move) { TRSPShiftLatenessMove m = (TRSPShiftLatenessMove) move; TRSPLogging.getNeighborhoodLogger().lowDebug("TRSPShift.executeMove: executing move %s", move); int node = m.mNode, newSucc = m.mNewSucc; // Check if the shift is valid if (!tour.isVisited(node)) throw new IllegalArgumentException("Node " + node + " is not present in this tour"); if (newSucc != ITRSPTour.UNDEFINED && !tour.isVisited(newSucc)) throw new IllegalArgumentException("New successor " + node + " is not present in this tour"); if (tour.getSucc(node) == newSucc) return false; // Node predecessor and successor int pred = tour.getPred(node); int succ = tour.getSucc(node); // New predecessor int newPred; if (newSucc != ITRSPTour.UNDEFINED) newPred = tour.getPred(newSucc); else // If newSucc is UNDEFINED then the node is appended to the end of the tour newPred = tour.getLastNode(); // Extract node if (succ != ITRSPTour.UNDEFINED) tour.setPred(succ, pred); else tour.setLast(pred); if (pred != ITRSPTour.UNDEFINED) tour.setSucc(pred, succ); else tour.setFirst(succ); // Insert node if (newPred != ITRSPTour.UNDEFINED) tour.setSucc(newPred, node); else tour.setFirst(node); if (newSucc != ITRSPTour.UNDEFINED) tour.setPred(newSucc, node); else tour.setLast(node); tour.setPred(node, newPred); tour.setSucc(node, newSucc); // Propagate tools and spare parts tour.propagateUpdate(m.mForward ? succ : node, m.mForward ? node : pred); tour.getCostDelegate().nodeShifted(tour, node, pred, m.mForward); // tour.setTotalCost(tour.getTotalCost() + m.getImprovement()); if (isCheckSolutionAfterMove()) { String check = TRSPDetailedSolutionChecker.INSTANCE.checkTour(tour); if (!check.isEmpty()) TRSPLogging.getNeighborhoodLogger() .warn("TRSPShift.executeMove: Incoherencies in tour after move %s (%s)", m, check); double maxLateness = tour.getMaxLateness(); if (m.mUpdatedLateness != null && Math.abs(m.mUpdatedLateness.getMaxLateness() - maxLateness) > 1e-3) { TRSPLogging .getNeighborhoodLogger() .warn("TRSPShift.executeMove: The maximum lateness has not changed as expected after move %s (expected %.3f, was %.3f)", m, m.mUpdatedLateness.getMaxLateness(), maxLateness); } } return true; } @Override public void pertub(IInstance instance, TRSPTour tour, IParameters params) { throw new UnsupportedOperationException("pertub is not implemented yet " + params); } @Override protected TRSPShiftLatenessMove randomNonImproving(TRSPTour tour, IParameters params) { throw new UnsupportedOperationException("randomNonImproving is not implemented yet " + params); } @Override protected TRSPShiftLatenessMove randomFirstImprovement(TRSPTour tour, IParameters params) { throw new UnsupportedOperationException("randomFirstImprovement is not implemented yet " + params); } @Override protected TRSPShiftLatenessMove deterministicBestImprovement(TRSPTour tour, IParameters params) { return deterministicExploration(tour, params, true); } @Override protected TRSPShiftLatenessMove deterministicFirstImprovement(TRSPTour tour, IParameters params) { return deterministicExploration(tour, params, true); } protected TRSPShiftLatenessMove deterministicExploration(TRSPTour tour, IParameters params, boolean first) { throw new UnsupportedOperationException("deterministicExploration is not implemented yet " + params); } @Override public boolean localSearch(TRSPTour tour, IParameters params) { /* * This local search procedure is inspired by the algorithm described in * Da Silva, R. F. & Urrutia, S. * A General VNS heuristic for the traveling salesman problem with time windows * Discrete Optimization, 2010, 7, 203 - 211 */ boolean improved = false; // The set of violated requests Set<Integer> violated = new HashSet<Integer>(); // The maximum lateness and associated request boolean explored = false; TRSPShiftLatenessMove move = null; explored = false; while (!explored) { // Evaluate the current maximum lateness double maxLateness = 0; int maxLatenessReq = ITRSPTour.UNDEFINED; if (move == null) { for (int node : tour) { if (!tour.getInstance().isDepot(node) && tour.getLateness(node) > 0) { violated.add(node); if (tour.getLateness(node) > maxLateness) { maxLateness = tour.getLateness(node); maxLatenessReq = node; } } } } else { if (move.getUpdatedLateness() != null) { maxLateness = move.getUpdatedLateness().getMaxLateness(); maxLatenessReq = move.getUpdatedLateness().getMaxLatenessReq(); } } // backward movements of violated requests // --------------------------------------------------- move = exploreBackwardViolated(tour, violated, maxLateness, maxLatenessReq, params); // forward movements of non-violated requests // --------------------------------------------------- if (move == null || !move.isImproving()) { move = exploreForwardNonViolated(tour, violated, maxLateness, maxLatenessReq, params); } // backward movements of non-violated requests // --------------------------------------------------- if (move == null || !move.isImproving()) { } // forward movements of violated requests // --------------------------------------------------- if (move == null || !move.isImproving()) { } if (move != null && move.isImproving()) { executeMove(tour, move); // maxLateness = move.mUpdatedLateness.maxLateness; // maxLatenessReq = move.mUpdatedLateness.maxLatenessReq; explored = false; } else { explored = true; } } return improved; } /** * Explore the sub-neighborhood composed of backward shifts of violated requests. * * @param tour * the optimized tour * @param violated * a set of violated request * @param maxLateness * the maximum lateness in the tour * @param maxLatenessReq * the request with the maximum lateness * @param params * local search parameters * @return the best/first improving backward shifts of violated request (depending on the * {@link IParameters#acceptFirstImprovement() params}) */ protected TRSPShiftLatenessMove exploreBackwardViolated(TRSPTour tour, Set<Integer> violated, final double maxLateness, final int maxLatenessReq, IParameters params) { if (tour.length() < 1) return null; // We start from the beginning of the tour // TODO start from the end of the tour? TRSPTourIterator mainIt = tour.iterator(); int node = ITRSPTour.UNDEFINED; int candSucc = ITRSPTour.UNDEFINED; TRSPShiftLatenessMove bestMove = null; boolean skip = false; if (mainIt.hasNext()) { node = mainIt.next(); // Skip depot(s) and the first request which cannot be shifted backward while (mainIt.hasNext() && tour.getInstance().isDepot(node)) node = mainIt.next(); } else // No backward shift can be found return null; while (mainIt.hasNext()) { // Main iterator node = mainIt.next(); // Ignore non-violated requests and depots skip = !violated.contains(node) || tour.getInstance().isDepot(node); TRSPTourIterator insIt = null; if (!skip) { // Insertion iterator insIt = tour.iterator(); // No candidate insertion point if (!insIt.hasNext()) skip = true; else { candSucc = insIt.next(); // Skip depot(s) while (insIt.hasNext() && tour.getInstance().isDepot(candSucc)) candSucc = insIt.next(); } } // Temporary values for lateness used to evaluate the new maximum lateness after a candidate move double maxLatenessTmp = Long.MIN_VALUE; int maxLatenessReqTmp = ITRSPTour.UNDEFINED; // Consider all nodes that are before the node while (!skip && insIt.hasNext() && candSucc != node) { // Evaluate the maximum lateness if node is shifted before candSucc TRSPShiftLatenessMove move = new TRSPShiftLatenessMove(Double.NaN, node, candSucc, false); tour.getCostDelegate().evaluateMove(move);// evaluateMaxLateness(tour, node, candSucc, false); // We found an improving move if (move.isImproving()) { // The new max lateness will appear before if (move.getUpdatedLateness().getMaxLateness() < maxLatenessTmp) { move.setUpdatedLateness(new Lateness(maxLatenessTmp, maxLatenessReqTmp)); } if (params.acceptFirstImprovement()) return move; else if (bestMove == null || move.getImprovement() > bestMove.getImprovement()) bestMove = move; } // Update the maximum lateness found so far if (tour.getLateness(candSucc) > maxLatenessTmp) { maxLatenessTmp = tour.getLateness(candSucc); maxLatenessReqTmp = candSucc; } // Move to next node candSucc = insIt.next(); } if (node == maxLatenessReq) // This was the node with the maximum lateness // Moving following nodes will not improve maximum lateness break; } return bestMove; } /** * Explore the sub-neighborhood composed of forward shifts of non-violated requests. * * @param tour * the optimized tour * @param violated * a set of violated request * @param maxLateness * the maximum lateness in the tour * @param maxLatenessReq * the request with the maximum lateness * @param params * local search parameters * @return the best/first improving forward shifts of non-violated request (depending on the * {@link IParameters#acceptFirstImprovement() params}) */ protected TRSPShiftLatenessMove exploreForwardNonViolated(TRSPTour tour, Set<Integer> violated, double maxLateness, int maxLatenessReq, IParameters params) { if (tour.length() < 1) return null; // We start from the beginning of the tour // TODO start from the end of the tour? TRSPTourIterator mainIt = tour.iterator(); int node = ITRSPTour.UNDEFINED; int candSucc = ITRSPTour.UNDEFINED; TRSPShiftLatenessMove bestMove = null; boolean skip = false; if (mainIt.hasNext()) { node = mainIt.next(); // Skip depot(s) and the first request which cannot be shifted backward while (mainIt.hasNext() && tour.getInstance().isDepot(node)) node = mainIt.next(); } else // No shift can be found return null; while (mainIt.hasNext()) { // Main iterator node = mainIt.next(); // Ignore violated requests, depots, and the last node skip = violated.contains(node) || tour.getInstance().isDepot(node) || tour.getSucc(node) == ITRSPTour.UNDEFINED || tour.getSucc(tour.getSucc(node)) == ITRSPTour.UNDEFINED; TRSPTourIterator insIt = null; if (!skip) { // Insertion iterator insIt = tour.iterator(tour.getSucc(tour.getSucc(node))); // No candidate insertion point if (!insIt.hasNext()) skip = true; else candSucc = insIt.next(); } // Temporary values for lateness used to evaluate the new maximum lateness after a candidate move double maxLatenessTmp = Long.MIN_VALUE; int maxLatenessReqTmp = ITRSPTour.UNDEFINED; // Consider all nodes that are after the node while (!skip) { // End the loop after this iteration if (candSucc == ITRSPTour.UNDEFINED) skip = true; // Evaluate the maximum lateness if node is shifted before candSucc TRSPShiftLatenessMove move = new TRSPShiftLatenessMove(Double.NaN, node, candSucc, true); tour.getCostDelegate().evaluateMove(move); // We found an improving move if (move.isImproving()) { // The new max lateness will appear before if (params.acceptFirstImprovement()) return move; else if (bestMove == null || move.getImprovement() > bestMove.getImprovement()) bestMove = move; } // Move to next node if (insIt.hasNext()) candSucc = insIt.next(); else // Append to the end of the tour candSucc = ITRSPTour.UNDEFINED; } // Update the maximum lateness found so far if (tour.getLateness(node) > maxLatenessTmp) { maxLatenessTmp = tour.getLateness(node); maxLatenessReqTmp = node; } } return bestMove; } @Override public String getShortName() { return "shiftLate"; } /** * <code>Lateness</code> is a simple container used when evaluating the max lateness in a tour. * <p> * Creation date: Mar 22, 2011 - 11:15:52 AM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public static class Lateness { private final double maxLateness; private final int maxLatenessReq; /** * Creates a new <code>Lateness</code> * * @param maxLateness * @param maxLatenessReq */ public Lateness(double maxLateness, int maxLatenessReq) { super(); this.maxLateness = maxLateness; this.maxLatenessReq = maxLatenessReq; } /** * Getter for <code>maxLateness</code> * * @return the maxLateness */ public double getMaxLateness() { return maxLateness; } /** * Getter for <code>maxLatenessReq</code> * * @return the maxLatenessReq */ public int getMaxLatenessReq() { return maxLatenessReq; } } }