/** * */ package vroom.trsp.optimization.rch; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import vroom.common.heuristics.ProcedureStatus; import vroom.common.utilities.IDisposable; import vroom.common.utilities.IObservable; import vroom.common.utilities.IObserver; import vroom.common.utilities.Update; import vroom.trsp.datamodel.ITRSPTour; import vroom.trsp.datamodel.TRSPInstance; import vroom.trsp.datamodel.Technician; import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate; import vroom.trsp.optimization.TRSPHeuristic; import vroom.trsp.optimization.constraints.TourConstraintHandler; import vroom.trsp.optimization.split.SplitTourArcBuilder; import vroom.trsp.optimization.split.TRSPSplit; import vroom.trsp.util.TRSPGlobalParameters; /** * <code>TRSPRndConstructiveHeuristic</code> * <p> * Creation date: Sep 22, 2011 - 1:48:19 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 abstract class TRSPRndConstructiveHeuristic extends TRSPHeuristic { /** * {@code true} if the heuristic will only be used to generate giant tours, {@code false} for feasible tours only */ private final boolean mGiantTours; /** * Returns {@code true} if the heuristic will only be used to generate giant tours, {@code false} for feasible tours * only * * @return {@code true} if the heuristic will only be used to generate giant tours, {@code false} for feasible tours * only */ public boolean isGiantTours() { return mGiantTours; } /** {@code true} if TW feasibility should be checked when building a giant tour */ private final boolean mCheckTWFeas; /** * Returns {@code true} if TW feasibility should be checked when building a giant tour * * @return {@code true} if TW feasibility should be checked when building a giant tour */ public boolean isCheckTWFeas() { return mCheckTWFeas; } private final int mKmax; /** * Returns the K<sub>max</sub> value * * @return the K<sub>max</sub> value */ public int getKmax() { return mKmax; } /** The collection containing the tours that were generated during last iteration */ private final Collection<ITRSPTour> mTourPool; /** * Returns the collection containing the tours that were generated during last iteration * * @return the collection containing the tours that were generated during last iteration */ public Collection<ITRSPTour> getTourPool() { return mTourPool; } /** The randomized factor, read at construction time for performance */ private final double mRndFact; /** The spliting procedure */ private final TRSPSplit mSplit; /** the observable node associated with each request */ private final ObservableNode[] mObsNodes; /** List of the compatible requests for each technician */ private final ObservableNode[][] mCompRequests; /** * Creates a new <code>TRSPRndConstructiveHeuristic</code> * * @param parameters * the global parameters used in this heuristic * @param constraintHandler * the constraints used in this heuristic * @param costDelegate * the cost delegate used to evaluate the tours * @param kMax * the K<sub>max</sub> value * @param giantTours * {@code true} if the heuristic will only be used to generate giant tours, {@code false} for feasible * tours only * @param revertSortOrder * <code>true</code> if the order of decisions has to be reversed (for eg for furthest insertion) */ public TRSPRndConstructiveHeuristic(TRSPInstance instance, TRSPGlobalParameters parameters, TourConstraintHandler constraintHandler, TRSPCostDelegate costDelegate, int kMax) { super(instance, parameters, constraintHandler, costDelegate); mKmax = kMax - 1; mGiantTours = getParameters().get(TRSPGlobalParameters.RCH_GIANT_SPLIT); mCheckTWFeas = getParameters().get(TRSPGlobalParameters.RCH_GIANT_TW_CHECK); mObsNodes = new ObservableNode[getInstance().getMaxId()]; mObsNodes[getInstance().getMainDepot().getID()] = new ObservableNode(getInstance().getMainDepot().getID()); for (Technician t : getInstance().getFleet()) mObsNodes[t.getHome().getID()] = new ObservableNode(t.getHome().getID()); mCompRequests = new ObservableNode[getInstance().getFleet().size()][]; mTourPool = new LinkedList<ITRSPTour>(); // The randomized factor, read at construction time for performance mRndFact = parameters.get(TRSPGlobalParameters.RCH_RND_FACTOR); mSplit = new TRSPSplit(new SplitTourArcBuilder(constraintHandler, costDelegate, parameters)); Collection<Integer> requests = getInstance().getReleasedRequests(); List<ObservableNode> requestsId = new ArrayList<ObservableNode>(requests.size()); for (int r : requests) { mObsNodes[r] = new ObservableNode(r); requestsId.add(mObsNodes[r]); } initialize(requestsId); } /** * Initialization of the precalculated data structures * * @param requestsId * a list of the requests id defined in the {@linkplain #getInstance() instance} */ protected void initialize(List<ObservableNode> requestsId) { initRequestLists(requestsId); } /** * Initialize the lists of compatible requests associated with each technician * * @param requestsId */ private void initRequestLists(List<ObservableNode> requestsId) { for (Technician t : getInstance().getFleet()) { LinkedList<ObservableNode> compReq = new LinkedList<ObservableNode>(); for (ObservableNode r : requestsId) { if (getInstance().isCompatible(t.getID(), r.getId())) compReq.add(r); } mCompRequests[t.getID()] = compReq.toArray(new ObservableNode[compReq.size()]); } } @Override public ProcedureStatus call() { setStatus(ProcedureStatus.RUNNING); reset(); if (isGiantTours()) { // Generate giant tours Collection<ITRSPTour> giantTours = generateGiantTours(); // TRSPLogging.getOptimizationLogger().lowDebug("%s.call: generated %s giant tours", // getClass().getSimpleName(), giantTours.size()); // Split giant tours for (ITRSPTour giantTour : giantTours) { getTourPool().addAll(mSplit.splitTour(giantTour)); } } else { // Generate one feasible tour per technician and add them to the pool getTourPool().addAll(generateFeasibleTours()); } setStatus(ProcedureStatus.TERMINATED); return getStatus(); } /** * Returns the {@link ObservableNode} instance corresponding to the home of a technician * * @param tech * the id of the considered technician * @return the {@link ObservableNode} instance corresponding to the home of a technician */ protected ObservableNode getHome(int tech) { return mObsNodes[tech + 1]; } /** * Returns the {@link ObservableNode} instance corresponding to the given request * * @param reqId * the id of the considered request * @return the {@link ObservableNode} instance corresponding to the given request */ protected ObservableNode getObsNode(int reqId) { return mObsNodes[reqId]; } /** * Returns an array containing the ids of the requests compatible with the specified technician * * @param tech * @return an array containing the ids of the requests compatible with the specified technician */ protected ObservableNode[] getCompatibleRequests(int tech) { return mCompRequests[tech]; } /** * Generate a set of giant tours. * <p> * This method will be called by {@link #call()} and is to be used by subclasses only. * </p> * <p> * Tours will automatically added to the {@link #getTourPool()tour pool} * </p> * * @return a set of tours */ protected Collection<ITRSPTour> generateGiantTours() { if (getParameters().isCVRPTW()) { // Generate a giant tour for the base vehicle return generateGiantTour(getInstance().getFleet().getVehicle()); } else { ArrayList<ITRSPTour> pool = new ArrayList<ITRSPTour>(getInstance().getFleet().size()); // For each technician for (Technician tech : getInstance().getFleet()) { // Generate a giant tour pool.addAll(generateGiantTour(tech)); } return pool; } } /** * Generate a giant tour for a specific technician. * <p> * By convention generated tours should start at the technician home, and end at the last served request * </p> * * @param technician * the technician * @return a giant tour for the given technician */ protected abstract Collection<ITRSPTour> generateGiantTour(Technician tech); /** * Generate a set of feasible tours (1 per technician). * <p> * This method will be called by {@link #call()} and is to be used by subclasses only. * </p> * <p> * Tours will automatically added to the {@link #getTourPool()tour pool} * </p> * * @return a set of feasible tours */ protected Collection<ITRSPTour> generateFeasibleTours() { ArrayList<ITRSPTour> pool = new ArrayList<ITRSPTour>(getInstance().getFleet().size()); // For each technician for (Technician tech : getInstance().getFleet()) { // Generate a giant tour pool.add(generateFeasibleTour(tech)); } return pool; } /** * Generate a feasible tour for a specific technician * <p> * By convention generated tours should start and end at the technician home * </p> * * @param technician * the technician * @return a feasible tour for the given technician */ protected abstract ITRSPTour generateFeasibleTour(Technician tech); /** * Reset the state of all nodes */ protected void resetNodes() { for (ObservableNode n : mObsNodes) if (n != null) n.reset(); } /** * Reset the state of all nodes that compatible with the technician <code>tech</code> * * @param tech * the id of the considered technician */ protected void resetNodes(int tech) { for (ObservableNode n : getCompatibleRequests(tech)) n.reset(); } /** * Generate a pseudo-random index between <code>0</code> and * <code>min(max,{@link #getKmax() K<sub>max</sub>})</code> * * @param max * the maximum value to be generated * @return a pseudo-random index between <code>0</code> and <code>min(max,{@link #getKmax() K<sub>max</sub>})</code> */ protected int nextIdx(int max) { if (max == 0) return 0; return getParameters().getRCHRndStream().nextInt(0, Math.min(max - 1, mKmax)); // double rnd = getRandomStream().nextDouble(); // return (int) (Math.pow(rnd, mRndFact) * max); } /** * Evaluates the cost of inserting {@code node} between {@code pred} and {@code succ} with {@code technician} * * @param node * the node to be inserted * @param pred * the predecessor * @param succ * the successor * @param technician * @param twPenalty * {@code true} if a penalty should be added if the insertion violates tw * @return the cost of inserting {@code node} between {@code pred} and {@code succ} with {@code technician} */ protected double evaluateInsertionCost(Integer node, Integer pred, Integer succ, Technician technician, boolean twPenalty) { double insCost = getInstance().getCostDelegate().getInsertionCost(node, pred, succ, technician); if (twPenalty && isCheckTWFeas()) { // Check the predecessor arc if (!getInstance().isArcTWFeasible(pred, node)) // We will have to split the tour at this point insCost += getInstance().getCostDelegate().getCost(pred, technician.getHome().getID(), technician) + getInstance().getCostDelegate().getCost(technician.getHome().getID(), node, technician); // Check the successor arc if (!getInstance().isArcTWFeasible(node, succ)) // We will have to split the tour at this point insCost += getInstance().getCostDelegate().getCost(node, technician.getHome().getID(), technician) + getInstance().getCostDelegate().getCost(technician.getHome().getID(), succ, technician); } return insCost; } private void reset() { mTourPool.clear(); } @Override public void dispose() { mTourPool.clear(); } /** * The class <code>ObservableNode</code> wraps a node to allow more efficient updates of the list of unserved * neighbors of a node * <p> * Creation date: Oct 5, 2011 - 4:24:09 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 */ protected static class ObservableNode implements IObservable, IDisposable { // private final ObserverManager mObservers; private final Integer mId; private boolean mServed; /** * Returns the id of the wrapped node * * @return the id of the wrapped node */ public Integer getId() { return mId; } /** * Returns <code>true</code> if this node is currently served, <code>false</code> otherwise * * @return <code>true</code> if this node is currently served, <code>false</code> otherwise */ public boolean isServed() { return mServed; } /** * Creates a new <code>ObservableNode</code> * * @param nodeId */ protected ObservableNode(Integer nodeId) { super(); mId = nodeId; // mObservers = new ObserverManager(this); mServed = false; } /** * Mark this node as served */ public void markAsServed() { mServed = true; // mObservers.notifyObservers(null); } /** * Reset this node, remove all observers and mark as unserved */ public void reset() { mServed = false; // mObservers.notifyObservers(null); } @Override public void addObserver(IObserver o) { // mObservers.addObserver(o); throw new UnsupportedOperationException("Removed for performance"); } @Override public void removeObserver(IObserver o) { // mObservers.removeObserver(o); } @Override public void removeAllObservers() { // mObservers.removeAllObservers(); } @Override public void dispose() { removeAllObservers(); } @Override public String toString() { return String.format("%s(%s)", getId(), isServed() ? "s" : "u"); } } /** * <code>Neighbor</code> is a wrapper class that contains a reference to the node and the distance to the reference * node * <p> * Creation date: Oct 4, 2011 - 3:31:11 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 */ protected static class Neighbor implements Comparable<Neighbor> { private final ObservableNode mNode; private ObservableNode mNeigh; private double mDistance; /** * Returns the node * * @return the node */ public ObservableNode getNode() { return mNode; } /** * Returns the neighbor node * * @return the neighbor node */ public ObservableNode getNeighbor() { return mNeigh; } /** * The distance to the reference node * * @return the distance to the reference node */ public double getDistance() { return mDistance; } /** * Creates a new <code>Neighbor</code> * * @param node * the considered node * @param neigh * the neighbor of the node * @param distance * the distance between the considered node and the neighbor */ public Neighbor(ObservableNode node, ObservableNode neigh, double distance) { mNode = node; mNeigh = neigh; mDistance = distance; } /** * Update the stored neighbor and associated distance * * @param newNeighbor * @param newDistance */ public void update(ObservableNode newNeighbor, double newDistance) { mNeigh = newNeighbor; mDistance = newDistance; } @Override public int compareTo(Neighbor o) { return Double.compare(getDistance(), o.getDistance()); } @Override public String toString() { return String.format("%s-%s (%.3f)", getNode(), getNeighbor(), getDistance()); } } /** * The class <code>NeighborListElement</code> is a simple implementation of a list of {@link ObservableNode} that * automatically removes nodes that were marked as served, it is intended to be used with {@link NeighborList} * <p> * Creation date: Oct 5, 2011 - 4:35:34 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 */ protected static class NeighborListElement implements IObserver, IDisposable { private final Neighbor mNeighbor; private final NeighborListElement mOriginalPrev; private NeighborListElement mOriginalNext; private NeighborListElement mPrev; private NeighborListElement mNext; /** * Creates a new <code>NeighborListElement</code> * * @param neighbor * @param prev * @param next * @param autoUpdate */ private NeighborListElement(Neighbor neighbor, NeighborListElement prev, NeighborListElement next, boolean autoUpdate) { super(); mNeighbor = neighbor; mOriginalPrev = prev; mOriginalNext = next; mPrev = prev; mNext = next; if (autoUpdate && mNeighbor != null) mNeighbor.getNeighbor().addObserver(this); } @Override public void update(IObservable source, Update update) { if (mNeighbor.getNeighbor().isServed()) { if (mPrev != null) this.mPrev.mNext = this.mNext; if (mNext != null) this.mNext.mPrev = this.mPrev; } else { this.mPrev = this.mOriginalPrev; this.mNext = this.mOriginalNext; if (this.mPrev != null) { this.mPrev.mNext = this; } if (this.mNext != null) { this.mNext.mPrev = this; } } } @Override public void dispose() { mNeighbor.getNeighbor().removeObserver(this); } @Override public String toString() { return mNeighbor != null ? mNeighbor.toString() : "null"; } } /** * The class <code>NeighborList</code> is a simple list implementation to store ordered lists of neighbors that * automatically update when nodes are marked as served * <p> * Creation date: Oct 5, 2011 - 5:15:05 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 */ protected static class NeighborList implements Iterable<Neighbor> { private final NeighborListElement mHead; private final TRSPInstance mInstance; protected NeighborList(TRSPInstance instance, List<Neighbor> nodes, boolean autoUpdate) { mInstance = instance; mHead = new NeighborListElement(null, null, null, autoUpdate); NeighborListElement prev = mHead; for (Neighbor node : nodes) { NeighborListElement e = new NeighborListElement(node, prev, null, autoUpdate); prev.mNext = e; prev.mOriginalNext = e; prev = e; } } /** * Returns the first neighbor of this list, or <code>null</code> if the list is empty * * @return the first neighbor of this list, or <code>null</code> if the list is empty */ protected Neighbor getFirst() { return mHead.mNext != null ? mHead.mNext.mNeighbor : null; } /** * Returns the neighbor at a specified position, the last neighbor if the index is out of range, or * <code>null</code> if the list is empty * * @param idx * the index of the neighbor * @param tech * the id of the technician * @return the neighbor at a specified position, the last neighbor if the index is out of range, or * <code>null</code> if the list is empty */ protected Neighbor get(int idx, int tech) { int i = -1; NeighborListIterator it = iterator(); Neighbor n = null; Neighbor ncomp = null; while (it.hasNext() && i < idx) { n = it.next(); if (mInstance.isCompatible(tech, n.getNeighbor().getId())) { i++; ncomp = n; } } return ncomp; } /** * Returns the unserved neighbor at a specified position, the last neighbor if the index is out of range, or * <code>null</code> if the list is empty * * @param idx * the index of the neighbor * @param tech * the id of the technician * @return the neighbor at a specified position, the last neighbor if the index is out of range, or * <code>null</code> if the list is empty */ protected Neighbor getUnserved(int idx, int tech) { int i = -1; NeighborListIterator it = iterator(); Neighbor n = null; Neighbor kuns = null; while (it.hasNext() && i < idx) { n = it.next(); if (!n.getNeighbor().isServed() && mInstance.isCompatible(tech, n.getNeighbor().getId())) { i++; kuns = n; } } return kuns; } /** * Returns the number of neighbors in this list, complexity is in <code>O(size)</code> * * @return the number of neighbors in this list, complexity is in <code>O(size)</code> */ public int size() { int size = 0; NeighborListIterator it = iterator(); while (it.hasNext()) { it.next(); size++; } return size; } /** * Returns the number of neighbors compatible with <code>tech</code> in this list, complexity is in * <code>O(size)</code> * * @param tech * the id of the considered technician * @return the number of neighbors compatible with <code>tech</code> in this list, complexity is in * <code>O(size)</code> */ public int sizeComp(int tech) { int size = 0; NeighborListIterator it = iterator(); while (it.hasNext()) { Neighbor n = it.next(); if (mInstance.isCompatible(tech, n.getNeighbor().getId())) size++; } return size; } /** * Returns the number of unserved neighbors compatible with <code>tech</code> in this list, complexity is in * <code>O(size)</code> * * @param tech * the id of the considered technician * @return the number of unserved neighbors compatible with <code>tech</code> in this list, complexity is in * <code>O(size)</code> */ public int sizeUnserved(int tech) { int size = 0; NeighborListIterator it = iterator(); while (it.hasNext()) { Neighbor n = it.next(); if (!n.getNeighbor().isServed() && mInstance.isCompatible(tech, n.getNeighbor().getId())) size++; } return size; } @Override public NeighborListIterator iterator() { return new NeighborListIterator(mHead); } @Override public String toString() { return vroom.common.utilities.Utilities.toShortString(this); } } protected static class NeighborListIterator implements Iterator<Neighbor> { private NeighborListElement mCurrent; /** * Creates a new <code>NeighborListIterator</code> * * @param current */ private NeighborListIterator(NeighborListElement current) { super(); mCurrent = current; } @Override public boolean hasNext() { return mCurrent.mNext != null; } @Override public Neighbor next() { if (!hasNext()) throw new NoSuchElementException(); mCurrent = mCurrent.mNext; return peek(); } /** * Returns the {@link Neighbor} that was returned by the last call to {@link #next()} * * @return the {@link Neighbor} that was returned by the last call to {@link #next()} */ public Neighbor peek() { return mCurrent.mNeighbor; } @Override public void remove() { throw new UnsupportedOperationException(); } } }