/** * */ package vroom.trsp.optimization.biobj; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import vroom.common.heuristics.Move; import vroom.common.utilities.LevenshteinDistance; import vroom.common.utilities.LevenshteinDistance.Edit; import vroom.common.utilities.LevenshteinDistance.EditType; import vroom.common.utilities.Utilities; import vroom.common.utilities.optimization.IParameters; import vroom.common.utilities.optimization.IPathRelinking; import vroom.trsp.datamodel.TRSPSolution; import vroom.trsp.datamodel.TRSPSolutionChecker; import vroom.trsp.datamodel.TRSPTour; import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate; import vroom.trsp.util.TRSPGlobalParameters; import vroom.trsp.util.TRSPLogging; /** * The class <code>LevenshteinPR</code> contains an implementation of Path relinking that used the sequence of edits * found while evaluating the Levenshtein distance. * <p> * Creation date: Nov 29, 2011 - 4:45:55 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 class LevenshteinPR implements IPathRelinking<TRSPSolution> { private final TRSPCostDelegate mCostDelegate; public LevenshteinPR(TRSPGlobalParameters params) { mCostDelegate = params.newALNSCostDelegate(null); } @Override public List<TRSPSolution> pathRelinking(TRSPSolution start, TRSPSolution target, IParameters params) { List<PRMove> moveList = decomposeInMoves(start, target); // Evaluate moves for (PRMove prMove : moveList) { mCostDelegate.evaluateMove(prMove); } // Sort moves // Collections.sort(moveList, Collections.reverseOrder()); // Execute moves ArrayList<TRSPSolution> path = new ArrayList<TRSPSolution>(moveList.size() + 1); path.add(start); if (moveList.size() == 1) { path.add(target); return path; } for (PRMove prMove : moveList) { TRSPLogging.getOptimizationLogger().debug("LevenshteinPR.pathRelinking: PR Move %s", prMove); // Clone the last solution in the path TRSPSolution sol = path.get(path.size() - 1).clone(); path.add(sol); for (TRSPTour tour : sol) tour.setAutoUpdated(false); // Execute the move on the solution for (AtomicPRMove mve : prMove.getMoves()) executeMove(sol, mve); for (TRSPTour tour : sol) tour.setAutoUpdated(true); String err = TRSPSolutionChecker.INSTANCE.checkSolution(sol); if (err.contains("Unserved requests)") || err.contains("served twice")) TRSPLogging.getOptimizationLogger().info( "LevenshteinPR.pathRelinking: generated an invalid solution: %s (%s)", prMove, err); } return path; } /** * Execute an {@linkplain AtomicPRMove atomic move} * * @param solution * the solution to be modified * @param move * the move to execute */ protected void executeMove(TRSPSolution solution, AtomicPRMove move) { if (move.isExecuted()) return; TRSPTour tour = solution.getTour(move.getTour().getTechnicianId()); checkTour(tour); switch (move.getType()) { case INS: for (Integer node : move.getInsertedNodes()) tour.insertAfter(move.getEditedReq(), node); break; case DEL: tour.removeNode(move.getEditedReq()); break; case SUB: // FIXME Add a special case when both nodes are on the same tour tour.setNode(move.getEditedReq(), move.getNewReq()); break; default: throw new UnsupportedOperationException("Unsupported edit type :" + move.getType()); } checkTour(tour); move.setExecuted(); } private void checkTour(TRSPTour tour) { int last = 0; for (int n : tour) last = n; if (!tour.getSolution().getInstance().isDepot(last) || last != tour.getLastNode()) throw new IllegalStateException(); } /** * Decompose the differences between two solutions (in the sense of the Levenshtein distance) into a sequence of * independent {@link PRMove} * * @param start * the starting point * @param target * the target solution * @return a sequence of independent {@link PRMove} */ @SuppressWarnings("unchecked") public List<PRMove> decomposeInMoves(TRSPSolution start, TRSPSolution target) { List<?>[] edits = new List<?>[start.getInstance().getMaxId()]; int mveCount = 0; for (int techId = 0; techId < start.getTourCount(); techId++) { TRSPTour s = start.getTour(techId); TRSPTour t = target.getTour(techId); List<Edit<Integer>> editSequence = LevenshteinDistance.getEditSequence(s.asList(), t.asList()); ListIterator<Edit<Integer>> it = editSequence.listIterator(); while (it.hasNext()) { Edit<Integer> e = it.next(); // Special case for insert: check for sequential insertions List<Integer> newNodes = null; if (e.getType() == EditType.INS) { newNodes = new ArrayList<Integer>(3); int idx = e.getEditIndex(); newNodes.add(e.getNewElement()); Edit<Integer> next = null; // Step forward while we are on an sequence insertion boolean insSeq = true; while (it.hasNext() && insSeq) { next = it.next(); insSeq = next.getType() == EditType.INS && next.getEditIndex() == idx; if (insSeq) newNodes.add(next.getNewElement()); } // Step back if insertion sequence finished if (!insSeq) it.previous(); // Reverse the insertion order // Collections.reverse(newNodes); } else { newNodes = Collections.singletonList(e.getNewElement()); } AtomicPRMove mve = new AtomicPRMove(e.getType(), s, e.getEditedElement(), newNodes); for (Integer n : newNodes) getEditList(edits, n).add(mve); if (mve.getType() == EditType.SUB) getEditList(edits, e.getEditedElement()).add(mve); mveCount++; } } List<PRMove> moveList = new ArrayList<PRMove>(mveCount / 2); int lastIdx = -1; while (lastIdx < edits.length) { lastIdx++; LinkedList<Integer> relatedIds = new LinkedList<Integer>(); // Find the next atomic move to process for (int i = lastIdx; i < edits.length; i++) { lastIdx = i; if (edits[i] != null && !edits[i].isEmpty()) { relatedIds.add(i); break; } } // Build the complete move PRMove mve = new PRMove(); while (!relatedIds.isEmpty()) { int i = relatedIds.pop(); if (edits[i] != null && !edits[i].isEmpty()) { for (AtomicPRMove atMve : (Iterable<AtomicPRMove>) edits[i]) { relatedIds.addAll(affectedIds(atMve)); mve.getMoves().add(atMve); } edits[i] = null; } } if (!mve.getMoves().isEmpty()) { mve.sort(); moveList.add(mve); } } return moveList; } /** * Get the id of the requests affected by a move * * @param move * @return */ private Collection<Integer> affectedIds(AtomicPRMove move) { switch (move.getType()) { case INS: return move.getInsertedNodes(); case SUB: return Arrays.asList(move.getEditedReq(), move.getNewReq()); case DEL: return Collections.singleton(move.getEditedReq()); default: throw new UnsupportedOperationException("Unsupported edit type: " + move.getType()); } } @SuppressWarnings("unchecked") private List<AtomicPRMove> getEditList(List<?>[] edits, int id) { if (edits[id] == null) edits[id] = new ArrayList<AtomicPRMove>(2); return (List<AtomicPRMove>) edits[id]; } /** * The class <code>PRMove</code> contains a list of {@link AtomicPRMove}, it represent a sequence of edits that will * lead to a valid (but not necessarily feasible) solution. * <p> * In other words it describes a neighbor of the start solution * </p> * <p> * Creation date: Nov 30, 2011 - 10:46:35 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 PRMove extends Move { private final LinkedList<AtomicPRMove> mMoves; protected PRMove() { mMoves = new LinkedList<AtomicPRMove>(); } /** * Returns the actual list of atomic moves * * @return the actual list of atomic moves */ private LinkedList<AtomicPRMove> getMoves() { return mMoves; } /** * Returns a view of the atomic moves composing this PR move * * @return a view of the atomic moves composing this PR move */ public List<AtomicPRMove> getAtomicMoves() { return Collections.unmodifiableList(mMoves); } /** * Sort the atomic moves composing this PR move */ public void sort() { Collections.sort(mMoves, new Comparator<AtomicPRMove>() { @Override public int compare(AtomicPRMove o1, AtomicPRMove o2) { if (o1.getTour().getTechnicianId() != o2.getTour().getTechnicianId()) { return 10000 * Integer.compare(o1.getTour().getTechnicianId(), o2.getTour() .getTechnicianId()); } else if (o1.getType() != o2.getType()) { return 100 * o1.getType().compareTo(o2.getType()); } else if (o1.getType() == EditType.SUB) { if (o1.getTour().isVisited(o1.getNewReq())) return 10; else return -10; } else { return Integer.compare(o1.getEditedReq(), o2.getEditedReq()); } }; }); } @Override public String toString() { return String.format("PR(imp:%.3f a:%s)", getImprovement(), Utilities.toShortString(mMoves)); } @Override public String getMoveName() { return "PR"; } } /** * The class <code>AtomicPRMove</code> represent an atomic PR move, or in other words an edit in the Levenshtein * distance * <p> * Creation date: Nov 30, 2011 - 10:44:07 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 AtomicPRMove { private final EditType mType; private final TRSPTour mTour; private final Integer mEditedReq; private final List<Integer> mNewReqs; private boolean mExecuted = false; /** * Creates a new <code>AtomicPRMove</code> * * @param type * the type of move * @param tour * the affected tour * @param editedReq * the affected request * @param newReqs * the new requests */ protected AtomicPRMove(EditType type, TRSPTour tour, Integer editedReq, Integer newReq) { this(type, tour, editedReq, Collections.singletonList(newReq)); } /** * Creates a new <code>AtomicPRMove</code> * * @param type * the type of move * @param tour * the affected tour * @param editedReq * the affected request * @param newReqs * the inserted requests */ protected AtomicPRMove(EditType type, TRSPTour tour, Integer editedReq, List<Integer> insReq) { mType = type; mEditedReq = editedReq; mNewReqs = insReq; mTour = tour; } /** * Getter for <code>type</code> * * @return the type */ public EditType getType() { return mType; } /** * Getter for the tour to which this move applies * * @return the tour */ public TRSPTour getTour() { return mTour; } /** * Getter for the edited request in the reference sequence: the deleted request for {@link EditType#DEL}, the * request that was replaced for {@link EditType#SUB}, and the request after which an element was inserted for * {@link EditType#INS}. * * @return the edited request in the reference sequence */ public Integer getEditedReq() { return mEditedReq; } /** * Getter for the substituted request for {@link EditType#SUB} * * @return the substituted request */ public Integer getNewReq() { return mNewReqs.get(0); } /** * Getter for the inserted requests for {@link EditType#INS}. * * @return the inserted request */ public List<Integer> getInsertedNodes() { return mNewReqs; } /** * Getter for <code>executed</code> * * @return the executed */ public boolean isExecuted() { return mExecuted; } /** * Setter for <code>executed</code> * * @param executed * the executed to set */ public void setExecuted() { mExecuted = true; } @Override public String toString() { return String.format("%s[%s:%s/%s]", getType(), getTour().getTechnicianId(), getEditedReq(), getInsertedNodes().size() > 1 ? getInsertedNodes() : getNewReq()); } } }