/**
*
*/
package vroom.trsp.datamodel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import vroom.common.utilities.Utilities;
import vroom.common.utilities.optimization.ISolution;
import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate;
/**
* <code>TRSPTour</code> is a doubly linked list representation of a tour using only nodes id. It is largely based on
* {@link DoublyLinkedTour}
* <p>
* This class implements {@link ISolution} as it can be used to represent a solution of the single technician TRSP
* </p>
* <p>
* Creation date: Feb 23, 2011 - 3:55:00 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 TRSPTour extends TRSPTourBase {
// TODO TRSPTour redesign: migrate all arrays to TRSPSolution and use getters/setters
/** The Constant NA to represent unknown data, for instance for cumulative cost of a node that is not in the tour */
public final static double NA = Double.NaN;
/** The technician which is executing this tour */
private final Technician mTechnician;
/** The cumulative cost at each node. */
private final double[] mCumulatedCost;
/** The earliest time of visit at each node. */
private final double[] mEarliestArrivalTime;
/** The latest feasible arrival time at each node. */
private final double[] mLatestFeasibleTime;
/** The total waiting time at between any two nodes */
private final double[][] mWaitingTime;
/** The maximum lateness in this tour */
private double mMaxLateness = 0;
/** The available tools at each node */
private final boolean[][] mTools;
/** The available spare parts at each node */
private final int[][] mSpareParts;
/** The number of spare parts required at each node and for the rest of the tour */
private final int[][] mRequiredSpareParts;
/** <code>true</code> if the depot is visited prior to the corresponding node */
private final boolean[] mDepotVisited;
/** The First Node ID. */
private int mFirst;
/** The Last Node ID. */
private int mLast;
/** The Length. */
private int mLength = 0;
/** The Predecessor of each node. */
private final int[] mPred;
/** The Successor of each node. */
private final int[] mSucc;
/**
* the autoupdated flag: <code>true</code> if stored information such as cost should be automatically updated,
* <code>false</code> otherwise
**/
private boolean mAutoUpdated;
/**
* Getter for the auto-updated flag:.
*
* @return <code>true</code> if stored information such as cost should be automatically updated, <code>false</code>
* otherwise
*/
public boolean isAutoUpdated() {
return this.mAutoUpdated;
}
/**
* Setter for the auto-updated flag.
*
* @param <code>true</code> if stored information such as cost should be automatically updated, <code>false</code>
* otherwise
*/
public void setAutoUpdated(boolean autoUpdated) {
boolean prev = this.mAutoUpdated;
this.mAutoUpdated = autoUpdated;
if (autoUpdated && !prev) {
// Update the tour
propagateUpdate(ITRSPTour.UNDEFINED, ITRSPTour.UNDEFINED, true);
getCostDelegate().evaluateTour(this, true);
}
}
/**
* Creates a new <code>TRSPTour</code> based on the given <code>instance</code> and <code>technician</code>.
*
* @param solution
* @param technician
*/
public TRSPTour(TRSPSolution solution, Technician technician) {
super(solution);
mTechnician = technician;
this.mPred = new int[getInstance().getMaxId()];
this.mSucc = new int[getInstance().getMaxId()];
this.mCumulatedCost = new double[getInstance().getMaxId()];
this.mEarliestArrivalTime = new double[getInstance().getMaxId()];
this.mLatestFeasibleTime = new double[getInstance().getMaxId()];
// this.mLateness = new double[getInstance().getMaxId()];
this.mWaitingTime = new double[getInstance().getMaxId()][];
for (int i = 0; i < mWaitingTime.length; i++) {
mWaitingTime[i] = new double[mWaitingTime.length - i];
}
this.mTools = new boolean[getInstance().getMaxId()][getInstance().getToolCount()];
this.mSpareParts = new int[getInstance().getMaxId()][getInstance().getSpareCount()];
this.mRequiredSpareParts = new int[getInstance().getMaxId()][getInstance().getSpareCount()];
this.mDepotVisited = new boolean[getInstance().getMaxId()];
// Set to default all data
// TODO TRSPTour redesign: remove this initialization
for (int id = 0; id < getInstance().getMaxId(); id++) {
mPred[id] = UNDEFINED;
mSucc[id] = UNDEFINED;
// We ignore this part as it is redundant with the fact that no node is present in the tour
// mCumulatedCost[id]=NA;
// mArrivalTime[id] = NA;
// for (int i = 0; i < mSlackTime.length; i++) {
// setSlackTime(i, id, NA);
// }
// mLatestFeasibleTime[id]=NA;
}
mDepotVisited[TRSPInstance.MAIN_DEPOT] = true;
mFirst = ITRSPTour.UNDEFINED;
mLast = ITRSPTour.UNDEFINED;
mLength = 0;
setTotalCost(0);
}
/**
* Creates a new <code>TRSPTour</code> by cloning the given instance.
*
* @param original
* the original tour
*/
private TRSPTour(TRSPTour original) {
super(original.getSolution());
this.mPred = Arrays.copyOf(original.mPred, original.mPred.length);
this.mSucc = Arrays.copyOf(original.mSucc, original.mSucc.length);
this.mCumulatedCost = Arrays
.copyOf(original.mCumulatedCost, original.mCumulatedCost.length);
this.mEarliestArrivalTime = Arrays.copyOf(original.mEarliestArrivalTime,
original.mEarliestArrivalTime.length);
this.mLatestFeasibleTime = Arrays.copyOf(original.mLatestFeasibleTime,
original.mLatestFeasibleTime.length);
// this.mLateness = Arrays.copyOf(original.mLateness, original.mLateness.length);
this.mWaitingTime = new double[original.mWaitingTime.length][];
for (int i = 0; i < mWaitingTime.length; i++) {
this.mWaitingTime[i] = Arrays.copyOf(original.mWaitingTime[i],
original.mWaitingTime[i].length);
}
this.mMaxLateness = original.mMaxLateness;
setTotalCost(original.getTotalCost());
this.mTools = new boolean[original.mTools.length][original.getInstance().getToolCount()];
this.mSpareParts = new int[original.mSpareParts.length][original.getInstance()
.getSpareCount()];
this.mRequiredSpareParts = new int[original.mRequiredSpareParts.length][original
.getInstance().getSpareCount()];
for (int i = 0; i < mTools.length; i++) {
this.mTools[i] = Arrays.copyOf(original.mTools[i], original.mTools[i].length);
this.mSpareParts[i] = Arrays.copyOf(original.mSpareParts[i],
original.mSpareParts[i].length);
this.mRequiredSpareParts[i] = Arrays.copyOf(original.mRequiredSpareParts[i],
original.mRequiredSpareParts[i].length);
}
this.mDepotVisited = Arrays.copyOf(original.mDepotVisited, original.mDepotVisited.length);
this.mFirst = original.mFirst;
this.mLast = original.mLast;
this.mLength = original.mLength;
this.mTechnician = original.mTechnician;
// setAutoUpdated(original.isAutoUpdated());
this.mAutoUpdated = original.mAutoUpdated;
}
/**
* Creates a new <code>TRSPTour</code> by copying an existing tour and possibly changing the associated technician
*
* @param solution
* the parent solution
* @param tour
* the tour to be copied
* @param technicianId
* the id of the new technician to be associated with this tour
*/
public TRSPTour(TRSPSolution solution, ITRSPTour tour, int technicianId) {
this(solution, solution.getInstance().getFleet().getVehicle(technicianId));
for (Integer n : tour) {
appendNode(n);
}
}
/**
* Getter for the parent instance
*
* @return the parent instance of this tour
*/
@Override
public TRSPInstance getInstance() {
return getSolution().getInstance();
}
@Override
public Technician getTechnician() {
return mTechnician;
}
@Override
public int getTechnicianId() {
return getTechnician().getID();
}
/**
* Getter for the cost calculation delegate used to evaluate this tour.
*
* @return the cost calculation delegate used to evaluate this tour.
*/
public TRSPCostDelegate getCostDelegate() {
return getSolution().getCostDelegate();
}
/**
* Gets the cumulated cost at a given node
*
* @param nodeId
* the id of the considered node
* @return the cumulated cost at the specified node
*/
public double getCumulatedCost(int nodeId) {
return mCumulatedCost[nodeId];
}
/**
* Sets the cumulated cost at a given node
*
* @param nodeId
* the id of the considered node
* @param value
* the cumulated cost at the specified node
*/
public void setCumulatedCost(int nodeId, double value) {
mCumulatedCost[nodeId] = value;
}
/**
* Gets the earliest arrival time at a given node, independently of the node time window.
*
* @param nodeId
* the id of the considered node
* @return the earliest arrival time at node with id <code>nodeId</code>
*/
public double getEarliestArrivalTime(int nodeId) {
return mEarliestArrivalTime[nodeId];
}
/**
* Sets the earliest arrival time at a given node
*
* @param nodeId
* the id of the considered node
* @param time
* the earliest arrival time at node with id <code>nodeId</code>
*/
public void setEarliestArrivalTime(int nodeId, double time) {
mEarliestArrivalTime[nodeId] = time;
}
/**
* Gets the waiting time at a given node, i.e. the time that the vehicle will be waiting before the start of the
* time window.
*
* @param nodeId
* the id of the considered node
* @return the waiting time at node with id <code>nodeId</code>
*/
public double getWaitingTime(int nodeId) {
return mWaitingTime[nodeId][0];
}
/**
* Gets the cumulated waiting time between two nodes, i.e. the total time the vehicle will spend waiting between
* nodes <code>i</code> and <code>j</code>
*
* @param i
* the first node
* @param j
* the second node
* @return the waiting time at node with id <code>nodeId</code>
*/
public double getWaitingTime(int i, int j) {
if (j < i) {
int a = j;
j = i;
i = a;
}
return mWaitingTime[i][j - i];
}
/**
* Sets the waiting time at a given node, i.e. the time that the vehicle will be waiting before the start of the
* time window.
*
* @param nodeId
* the id of the considered node
* @param time
* the waiting time at node with id <code>nodeId</code>
*/
public void setWaitingTime(int nodeId, double time) {
mWaitingTime[nodeId][0] = time;
}
/**
* Sets the cumulative waiting time between two nodes, i.e. the total time the vehicle will spend waiting between
* nodes <code>i</code> and <code>j</code>
*
* @param i
* the first node
* @param j
* the second node
* @param time
* the cumulative waiting time between <code>i</code> and <code>j</code>
* @return the waiting time at node with id <code>nodeId</code>
*/
public void setWaitingTime(int i, int j, double time) {
if (j < i) {
int a = j;
j = i;
i = a;
}
mWaitingTime[i][j - i] = time;
}
/**
* Gets the earliest departure time at a given node, depending on its time window and service time: <br/>
* <code>max(ea[nodeId],a[nodeId])+s[nodeId]</code>
*
* @param nodeId
* the id of the considered node
* @return the earliest departure time at node with id <code>nodeId</code>
* @see #getEarliestArrivalTime(int)
*/
public double getEarliestDepartureTime(int nodeId) {
return getTimeWindow(nodeId).getEarliestStartOfService(getEarliestArrivalTime(nodeId))
+ getServiceTime(nodeId);
}
/**
* Gets the latest arrival time at a node so that the remaining of the route can be executed.
*
* @param nodeId
* the id of the considered node
* @return the latest feasible arrival time at node with id <code>nodeId</code>
*/
public double getLatestFeasibleArrivalTime(int nodeId) {
return mLatestFeasibleTime[nodeId];
}
/**
* Sets the latest arrival time at a node so that the remaining of the route can be executed.
*
* @param nodeId
* the id of the considered node
* @param time
* the latest feasible arrival time at node with id <code>nodeId</code>
*/
public void setLatestFeasibleTime(int nodeId, double time) {
mLatestFeasibleTime[nodeId] = time;
}
/**
* Returns the total duration of this tour.
*
* @return the total duration of this tour.
*/
public double getTotalDuration() {
if (getLastNode() == ITRSPTour.UNDEFINED)
return 0;
else
return getEarliestDepartureTime(getLastNode());
}
/**
* Gets the lateness at a given node
*
* @param node
* the id of the considered node
* @return the lateness at node with id <code>nodeId</code>
*/
public double getLateness(int node) {
// We assume that tws[node] + service[node] < twe[node], therefore getting to a node earlier ensures that there
// will be no lateness
return getTimeWindow(node).getViolation(getEarliestArrivalTime(node));
// Lateness was disabled for performance
// return mLateness[nodeId];
}
/**
* Gets the maximum lateness in this tour
*
* @return the maximum lateness in this tour
*/
public double getMaxLateness() {
// return Utilities.max(mLateness);
throw new UnsupportedOperationException("Maximum lateness was disabled for performance");
// return mMaxLateness;
}
/**
* Returns <code>true</code> if the main depot is visited in this tour.
*
* @return <code>true</code> if the main depot is visited in this tour.
*/
public boolean isMainDepotVisited() {
return isVisited(TRSPInstance.MAIN_DEPOT);
}
/**
* Returns <code>true</code> if the main depot is visited in this tour before visiting <code>node</code>.
*
* @param node
* the id of the considered node
* @return <code>true</code> if the main depot is visited in this tour before visiting <code>node</code>.
*/
public boolean isMainDepotVisited(int node) {
return mDepotVisited[node];
}
/**
* Returns <code>true</code> if a visit to the main depot is required to ensure the feasibility of this tour.
*
* @return <code>true</code> if a visit to the main depot is required to ensure the feasibility of this tour.
*/
public boolean isVisitToMainDepotRequired() {
if (length() == 0)
return false;
if (Utilities.compare(mRequiredSpareParts[getFirstNode()], getTechnicianSpareParts()) > 0) {
return true;
}
for (int node : this) {
if (!getInstance().hasRequiredTools(getTechnician().getID(), node))
return true;
}
return false;
}
/**
* Append a single node to this tour.
*
* @param node
* the node
* @return true, if successful
*/
public boolean appendNode(int node) {
checkNode(node);
if (mLast != ITRSPTour.UNDEFINED) {// Tour is not empty
setSucc(mLast, node);
setPred(node, mLast);
} else { // Tour is empty
// Initialize the tour
this.mFirst = node;
}
mLength++;
this.mLast = node;
propagateUpdate(getLastNode(), getLastNode(), false);
getCostDelegate().nodeInserted(this, getPred(node), node, ITRSPTour.UNDEFINED);
return true;
}
/**
* Append nodes.
*
* @param nodes
* the nodes
* @return true, if successful
*/
public boolean appendNodes(List<Integer> nodes) {
for (int n : nodes) {
appendNode(n);
}
return true;
}
@Override
public TRSPTour clone() {
// TODO TRSPTour redesign: throw an exception here - cloning should only supported at solution level
return new TRSPTour(this);
}
/**
* Extract node at a specific index.
* <p>
* Complexity is in <code>O(n/2)</code> for the actual removal, and <code>O(n)</code> for the update
* </p>
*
* @param index
* the index in the tour of the node to be removed
* @return the id of the node that was at <code>index</code>
*/
public int extractNode(int index) {
int node = getNodeAt(index);
removeNode(node);
return node;
}
/**
* Remove a given node from this tour
* <p>
* Runs in constant time for the actual removal, and <code>O(n)</code> for the update
* </p>
*
* @param node
* the node to be removed
* @return <code>true</code> if the node was present in the tour
*/
public boolean removeNode(int node) {
if (!isVisited(node))
return false;
int pred = getPred(node);
int succ = getSucc(node);
if (pred != ITRSPTour.UNDEFINED)
setSucc(pred, succ);
else
this.mFirst = succ;
if (succ != ITRSPTour.UNDEFINED)
setPred(succ, pred);
else
this.mLast = pred;
// Erase information stored for the removed node
resetNodeData(node);
// Update length
this.mLength--;
// Propagate from the predecessor
propagateUpdate(succ, pred, false);
getCostDelegate().nodeRemoved(this, pred, node, succ);
return pred != ITRSPTour.UNDEFINED || succ != ITRSPTour.UNDEFINED;
}
/**
* Remove all nodes from this tour and reset all stored information
*/
public void clear() {
for (int i = 0; i < mPred.length; i++) {
resetNodeData(i);
}
}
/**
* Reset to default values the data stored for a node
*
* @param node
* the id of the node to be erased
*/
private void resetNodeData(int node) {
setPred(node, ITRSPTour.UNDEFINED);
setSucc(node, ITRSPTour.UNDEFINED);
Arrays.fill(this.mTools[node], false);
Arrays.fill(this.mSpareParts[node], 0);
Arrays.fill(this.mRequiredSpareParts[node], 0);
setCumulatedCost(node, NA);
setEarliestArrivalTime(node, NA);
for (int i = 0; i < mWaitingTime.length; i++) {
setWaitingTime(i, node, NA);
}
setLatestFeasibleTime(node, NA);
mDepotVisited[node] = node == TRSPInstance.MAIN_DEPOT;
}
/**
* Extract subtour.
*
* @param start
* the start
* @param end
* the end
* @return the list
*/
public List<Integer> extractSubtour(int start, int end) {
List<Integer> subtour = new ArrayList<Integer>(end - start + 1);
int node = getNodeAt(start);
int pred = getPred(node);
int succ = getSucc(node);
int index = start;
while (index <= end) {
subtour.add(node);
succ = getSucc(node);
// Erase the node info
resetNodeData(node);
// Update length
this.mLength--;
node = succ;
index++;
}
if (pred != ITRSPTour.UNDEFINED)
setSucc(pred, succ);
if (succ != ITRSPTour.UNDEFINED)
setPred(succ, pred);
propagateUpdate(succ, pred, false);
getCostDelegate().subtourRemoved(this, pred, subtour, succ);
return subtour;
}
/**
* Insert a node before a request of this tour.
*
* @param pred
* the request after which the <code>node</code> will be inserted, if equal to
* {@link ITRSPTour#UNDEFINED} the <code>node</code> will be inserted at the head
* @param node
* the node to be inserted
* @return true, if successful
*/
public boolean insertAfter(int pred, int node) {
checkNode(node);
if (!isVisited(pred))
throw new IllegalArgumentException("The insertion successor " + pred
+ " is not visited by this tour");
int succ;
if (pred == ITRSPTour.UNDEFINED) {
if (length() == 0) {
appendNode(node);
return true;
} else {
succ = getFirstNode();
setFirst(node);
}
} else {
succ = getSucc(pred);
setSucc(pred, node);
}
setPred(node, pred);
setPred(succ, node);
setSucc(node, succ);
// Update length
this.mLength++;
propagateUpdate(node, node, false);
getCostDelegate().nodeInserted(this, getPred(node), node, getSucc(node));
return true;
}
/**
* Insert a node before a request of this tour.
*
* @param succ
* the request before which the <code>node</code> will be inserted, if equal to
* {@link ITRSPTour#UNDEFINED} the <code>node</code> will be appended
* @param node
* the node to be inserted
* @return true, if successful
*/
public boolean insertBefore(int succ, int node) {
checkNode(node);
if (!isVisited(succ))
throw new IllegalArgumentException("The insertion successor " + succ
+ " is not visited by this tour");
if (succ == ITRSPTour.UNDEFINED) {
appendNode(node);
return true;
}
int pred = getPred(succ);
if (pred != ITRSPTour.UNDEFINED)
setSucc(pred, node);
else
setFirst(node);
setPred(node, pred);
setPred(succ, node);
setSucc(node, succ);
// Update length
this.mLength++;
propagateUpdate(node, node, false);
getCostDelegate().nodeInserted(this, getPred(node), node, getSucc(node));
return true;
}
/**
* Insert nodes.
*
* @param index
* the index
* @param subtour
* the subtour
* @return true, if successful
*/
public boolean insertNodes(int index, List<Integer> subtour) {
if (subtour.isEmpty())
return true;
final int current = getNodeAt(index);
final int predecessor = getPred(current);
Iterator<? extends Integer> it = subtour.iterator();
int first = ITRSPTour.UNDEFINED, last = ITRSPTour.UNDEFINED;
int pred = it.next();
int succ = pred;
first = pred;
while (it.hasNext()) {
checkNode(pred);
succ = it.next();
setPred(succ, pred);
setSucc(pred, succ);
pred = succ;
}
last = succ;
setSucc(getPred(current), first);
setPred(first, getPred(current));
setPred(current, last);
setSucc(last, current);
// Update length
this.mLength += subtour.size();
propagateUpdate(predecessor, current, false);
getCostDelegate().tourInserted(this, predecessor, subtour, current);
return true;
}
/**
* Reverse subtour.
*
* @param start
* the start
* @param end
* the end
* @return true, if successful
*/
public boolean reverseSubtour(int start, int end) {
if (start == end)
return true;
int first = getNodeAt(start), last = first;
int pred = getPred(first);
int node = first, succ = first;
int index = start;
// Reverse the subtour
while (index <= end) {
succ = getSucc(node);
setSucc(node, getPred(node));
setPred(node, succ);
last = node;
node = succ;
index++;
}
// Relink the subtour extremities
setSucc(first, succ);
setPred(succ, first);
setSucc(last, pred);
setPred(pred, last);
getCostDelegate().subtourReversed(this, pred, first, last, succ);
return true;
}
/**
* Sets the node at a specific index.
*
* @param index
* the index
* @param node
* the node
* @return the node that was previously at {@code index}
*/
public int setNodeAt(int index, int node) {
int old = getNodeAt(index);
setNode(old, node);
return old;
}
/**
* Replace a node by another
*
* @param old
* the node to be replaced
* @param node
* the new node
* @throws IllegalStateException
* if {@code node} is node visited by this tour
*/
public void setNode(int old, int node) {
if (!isVisited(old))
throw new IllegalStateException("The specified node is not visited by this tour");
checkNode(node);
int pred = getPred(old);
int succ = getSucc(old);
if (pred != ITRSPTour.UNDEFINED)
setSucc(pred, node);
if (succ != ITRSPTour.UNDEFINED)
setPred(succ, node);
setPred(node, pred);
setSucc(node, succ);
resetNodeData(old);
getCostDelegate().nodeReplaced(this, getPred(node), old, node, getSucc(node));
}
/**
* Gets the first node.
*
* @return the first node
*/
@Override
public int getFirstNode() {
return mFirst;
}
/**
* Sets the first node.
*
* @param first
* the first node of the tour
*/
public void setFirst(int first) {
mFirst = first;
}
/**
* Gets the last node.
*
* @return the last node
*/
@Override
public int getLastNode() {
return mLast;
}
/**
* Sets the last node.
*
* @param last
* the last node of the tour
*/
public void setLast(int last) {
mLast = last;
}
/**
* Gets the node id at.
*
* @param index
* the index
* @return the node id at
*/
@Override
public int getNodeAt(int index) {
if (index < 0 || index >= length())
throw new IllegalArgumentException(String.format("Index if out of range: %s [%s]",
index, length()));
if (index < length() / 2) {
int cursor = 0;
int node = mFirst;
while (cursor < index) {
cursor++;
node = getSucc(node);
}
return node;
} else {
int cursor = length() - 1;
int node = mLast;
while (cursor > index) {
cursor--;
node = getPred(node);
}
return node;
}
}
/**
* Gets the node position.
*
* @param node
* the node
* @return the node position
*/
public int getNodePosition(int node) {
// Complexity : O(n)
int cursor = 0;
int pred = node;
while (getPred(pred) != ITRSPTour.UNDEFINED) {
pred = getPred(pred);
cursor++;
}
return cursor;
}
/**
* Gets the predecessor of a node.
*
* @param node
* the node
* @return the predecessor of <code>node</code>
*/
public int getPred(int node) {
return this.mPred[node];
}
/**
* Sets the predecessor of a node.
*
* @param node
* the node
*/
public void setPred(int node, int pred) {
this.mPred[node] = pred;
}
/**
* Gets the successor of a node.
*
* @param node
* the node
* @return the successor of <code>node</code>
*/
public int getSucc(int node) {
return this.mSucc[node];
}
/**
* Sets the predecessor of a node.
*
* @param node
* the node
* @param succ
* the successor of <code>node</code>
*/
public void setSucc(int node, int succ) {
this.mSucc[node] = succ;
}
/*
* (non-Javadoc)
* @see vroom.trsp.datamodel.ITRSPTour#iterator()
*/
@Override
public TRSPTourIterator iterator() {
return new TRSPTourIterator();
}
/**
* Iterator starting from a given node
*
* @param nodeId
* the id of the first considered node (if {@link ITRSPTour#UNDEFINED} then the iterator will start at
* the beginning of the tour)
* @return a list iterator starting at the node <code>nodeId</code>
*/
public TRSPTourIterator iterator(int nodeId) {
return new TRSPTourIterator(nodeId);
}
/**
* Return the tour length, in number of visited nodes, including depots.
*
* @return the tour length, in number of visited nodes, including depots
*/
@Override
public int length() {
return mLength;
}
/**
* Propagates the available tools, spare parts, earliest arrival time and lateness from the predecessor of
* <code>fwdNode</code> recursively to all successors, and the latest feasible arrival time and required spare parts
* from the successor of <code>bwdNode</code> recursively to all its predecessors
*
* @param fwdNode
* the node from which update will be propagated forward, if equal to {@link ITRSPTour#UNDEFINED} the
* first node will be used
* @param bwdNode
* the node from which update will be propagated backward, if equal to {@link ITRSPTour#UNDEFINED} the
* last node will be used
* @param force
* set to <code>true</code> to force the propagation to the all the tour (for example when tour was
* changed in more than one point), set to <code>false</code> to stop propagation as soon as stored
* values equal calculated values (for example when tour was changed only at one point).
*/
public void propagateUpdate(int fwdNode, int bwdNode, boolean force) {
// Ignore if auto update is disabled
if (!isAutoUpdated())
return;
// Ignore tour is empty
if (length() == 0)
return;
if (fwdNode == ITRSPTour.UNDEFINED)
fwdNode = getFirstNode();
propagateEarliestTime(fwdNode, force);
propagateWaitingTime(fwdNode);
propagateTools(fwdNode, force);
propagateSpareParts(fwdNode, force);
propagateDepotVisitedUpdateFwd(fwdNode, force);
if (bwdNode == ITRSPTour.UNDEFINED)
bwdNode = getLastNode();
propagateLatestFeasibleArrivalTime(bwdNode, force);
propagateRequiredSpareParts(bwdNode, force);
}
/**
* Sets the depot visited flag for <code>node</code> by propagating the flag from its predecessor, and recursively
* call the method on its successor.
*
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateDepotVisitedUpdateFwd(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
int pred = getPred(node);
boolean old = mDepotVisited[node];
if (node == TRSPInstance.MAIN_DEPOT)
// Node is a depot
mDepotVisited[node] = true;
else if (pred == ITRSPTour.UNDEFINED)
// Start of the tour (and node is not the main depot)
mDepotVisited[node] = false;
else
// Propagate change
mDepotVisited[node] = mDepotVisited[pred];
// Recursive call
if (force || old != isMainDepotVisited(node) || node == TRSPInstance.MAIN_DEPOT)
propagateDepotVisitedUpdateFwd(getSucc(node), force);
}
/**
* Sets the depot visited flag for <code>node</code> by propagating the flag from its predecessor, and recursively
* call the method on its successor.
*
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateDepotVisitedUpdateBwd(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
int succ = getSucc(node);
boolean old = mDepotVisited[node];
if (node == TRSPInstance.MAIN_DEPOT)
// Node is a depot
mDepotVisited[node] = true;
else if (succ == ITRSPTour.UNDEFINED)
// End of the tour
mDepotVisited[node] = node == TRSPInstance.MAIN_DEPOT;
else
// Propagate change
mDepotVisited[node] = succ != TRSPInstance.MAIN_DEPOT && mDepotVisited[succ];
// Recursive call
if (force || old != isMainDepotVisited(node) || node == TRSPInstance.MAIN_DEPOT)
propagateDepotVisitedUpdateBwd(getPred(node), force);
}
/**
* Sets the waiting time time for <code>node</code> by propagating the time from its predecessor, and recursively
* call the method on its successor. This method also updates lateness
* <p>
* Please note that this method assumes that the {@linkplain #getEarliestArrivalTime(int) earliest arrival} is
* properly defined
* </p>
*
* @param node
* the considered node
*/
protected void propagateWaitingTime(int node) {
if (node == UNDEFINED || length() == 0)
// Nothing to do
return;
int i, j;
TRSPTourIterator it = iterator(node);
// Update the waiting time of node and its successors
while (it.hasNext()) {
i = it.next();
/*
* The waiting time at node is equal to max(0,tws[node] - early[node])
*/
setWaitingTime(i,
Math.max(0, getTimeWindow(i).startAsDouble() - getEarliestArrivalTime(i)));
}
// Update the cumulated waiting time of all nodes
it = iterator();
double cumWait = 0;
// If true then the sub-loop will start at the changed node
boolean jumpToNode = true;
i = getFirstNode();
while (it.hasNext() && getSucc(i) != UNDEFINED) {
if (i == node)
jumpToNode = false;
// The start node for the sub-loop
int startNode = jumpToNode ? node : getSucc(i);
int predStartNode = getPred(startNode);
if (predStartNode == UNDEFINED || predStartNode == i)
cumWait = 0;
else
cumWait = getWaitingTime(i, predStartNode) + getWaitingTime(predStartNode);
TRSPTourIterator succ = iterator(startNode);
while (succ.hasNext()) {
j = succ.next();
setWaitingTime(i, j, cumWait);
cumWait += getWaitingTime(j);
}
i = it.next();
}
// Recursive call
propagateWaitingTime(getSucc(node));
}
/**
* Sets the earliest visit time for <code>node</code> by propagating the time from its predecessor, and recursively
* call the method on its successor. This method also updates lateness
*
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateEarliestTime(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
double old = getEarliestArrivalTime(node);
int pred = getPred(node);
if (pred == ITRSPTour.UNDEFINED)
// Start of the tour
setEarliestArrivalTime(node, getEarliestStartTime());
else
/*
* The earliest time at node is equal to max(early[pred],tws[pred]) + service[pred] + travel[pred,node] This
* means that we consider that the service of the predecessor cannot be started before the start of its time
* window
*/
setEarliestArrivalTime(node, getEarliestDepartureTime(pred) + getTravelTime(pred, node));
// We assume that tws[node] + service[node] < twe[node], therefore getting to a node earlier ensures that there
// will be no lateness
// double lateness = getTimeWindow(node).getViolation(getArrivalTime(node));
// if (lateness > getMaxLateness())
// mMaxLateness = lateness;
// mLateness[node] = lateness;
// Recursive call
if (force || old != getEarliestArrivalTime(node))
propagateEarliestTime(getSucc(node), force);
}
/**
* Sets the latest feasible arrival time for <code>node</code> by propagating the time at <code>succ</code>
*
* @param succ
* the <code>node</code> successor
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateLatestFeasibleArrivalTime(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
double old = getLatestFeasibleArrivalTime(node);
int succ = getSucc(node);
if (succ == ITRSPTour.UNDEFINED)
// End of the tour: twe[node]
setLatestFeasibleTime(node, getTimeWindow(node).endAsDouble());
else {
/*
* The latest feasible arrival time is equal to min(twe[node],late[succ]-service[node]-travel[node,succ])
*/
setLatestFeasibleTime(
node,
Math.min(getTimeWindow(node).endAsDouble(), getLatestFeasibleArrivalTime(succ)
- getServiceTime(node) - getTravelTime(node, succ)));
}
// Recursive call
if (force || old != getLatestFeasibleArrivalTime(node))
propagateLatestFeasibleArrivalTime(getPred(node), force);
}
/**
* Sets the available tools for <code>node</code> by propagating the available tools from its predecessor, and
* recursively call the method on its successor.
*
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateTools(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
boolean changed = false;
if (isMainDepot(node))
// Main depot: assumes all tools are available
for (int t = 0; t < this.mTools[node].length; t++) {
if (this.mTools[node][t] != true) {
this.mTools[node][t] = true;
changed = true;
}
}
else if (getPred(node) == ITRSPTour.UNDEFINED)
// No predecessor: assumes start of the tour
for (int t = 0; t < this.mTools[node].length; t++) {
if (this.mTools[node][t] != getTechnician().getToolSet().hasAttribute(t)) {
this.mTools[node][t] = getTechnician().getToolSet().hasAttribute(t);
changed = true;
}
}
else
// Propagate the tools available at the predecessor
for (int t = 0; t < this.mTools[node].length; t++) {
if (this.mTools[node][t] != this.mTools[getPred(node)][t]) {
this.mTools[node][t] = this.mTools[getPred(node)][t];
changed = true;
}
}
// Recursive call
if (force || changed)
propagateTools(getSucc(node), force);
}
/**
* Sets the available spare parts for <code>node</code> by propagating the available spare parts from its
* predecessor, and recursively call the method on its successor.
*
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateSpareParts(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
boolean changed = false;
if (isMainDepot(node))
// Main depot: assumes all spare parts are available
for (int p = 0; p < getInstance().getSpareCount(); p++) {
if (this.mSpareParts[node][p] != Integer.MAX_VALUE) {
this.mSpareParts[node][p] = Integer.MAX_VALUE;
changed = true;
}
}
else if (getPred(node) == ITRSPTour.UNDEFINED)
// No predecessor: assumes start of the tour
for (int p = 0; p < getInstance().getSpareCount(); p++) {
// Available spare parts, ignoring requirements if a visit to the depot was made before
int av = getTechnician().getAvailableSpareParts(p) == Integer.MAX_VALUE ? Integer.MAX_VALUE
: getTechnician().getAvailableSpareParts(p)
- getInstance().getSparePartReq(node, p);
if (this.mSpareParts[node][p] != av) {
this.mSpareParts[node][p] = av;
changed = true;
}
}
else
// Propagate the spare parts available at the predecessor
for (int p = 0; p < getInstance().getSpareCount(); p++) {
// Available spare parts, ignoring requirements is a visit to the depot was made before
int av = getAvailableSpareParts(getPred(node), p) == Integer.MAX_VALUE ? Integer.MAX_VALUE
: getAvailableSpareParts(getPred(node), p)
- getInstance().getSparePartReq(node, p);
if (this.mSpareParts[node][p] != av) {
this.mSpareParts[node][p] = av;
changed = true;
}
}
// Recursive call
if (force || changed)
propagateSpareParts(getSucc(node), force);
}
/**
* Sets the required spare parts for <code>node</code> by propagating the required spare parts from its successor,
* and recursively call the method on its predecessor.
*
* @param node
* the considered node
* @param force
* set to <code>true</code> to force the propagation to the all the tour, set to <code>false</code> to
* stop propagation as soon as stored values equal calculated values
*/
protected void propagateRequiredSpareParts(int node, boolean force) {
if (node == ITRSPTour.UNDEFINED)
return; // Nothing to do
boolean changed = false;
// if (getInstance().isMainDepot(node)) {
// // Reset the requirements
// Arrays.fill(mRequiredSpareParts[node], 0);
// } else
// The requirements of the successor
int[] succReq = getSucc(node) != ITRSPTour.UNDEFINED ? mRequiredSpareParts[getSucc(node)]
: new int[getInstance().getSpareCount()];
// The requirements for this node
int[] nodeReq = getInstance().isRequest(node) ? getInstance().getRequest(node)
.getSparePartRequirements() : new int[getInstance().getSpareCount()];
// Update the stored requirements
for (int p = 0; p < nodeReq.length; p++) {
int old = mRequiredSpareParts[node][p];
mRequiredSpareParts[node][p] = succReq[p] + nodeReq[p];
changed |= old != mRequiredSpareParts[node][p];
}
// Recursive call
if (force || changed)
propagateRequiredSpareParts(getPred(node), force);
}
@Override
public boolean isVisited(int node) {
return node != ITRSPTour.UNDEFINED
&& (getPred(node) != ITRSPTour.UNDEFINED || getSucc(node) != ITRSPTour.UNDEFINED || (node == mFirst && node == mLast));
}
@Override
public Comparable<?> getObjective() {
return getTotalCost();
}
@Override
public double getObjectiveValue() {
return getTotalCost();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(length() * 3);
sb.append(String.format("t:%s c:%.2f d:%.2f l:%s <", getTechnician().getID(),
getTotalCost(), getTotalDuration(), length()));
TRSPTourIterator it = iterator();
int[] spreq = new int[getTechnician().getCompartmentCount()];
while (it.hasNext()) {
int n = it.next();
for (int s = 0; s < spreq.length; s++)
if (getInstance().isRequest(n))
spreq[s] += getInstance().getSparePartReq(n, s);
else
spreq[s] = 0;
// sb.append(String.format("%s[et:%.2f,lft:%.2f,l:%.2f]", n, getEarliestArrivalTime(n),
// getLatestFeasibleArrivalTime(n), getLateness(n)));
// if (getLateness(n) > 0)
// sb.append(String.format("%s[l:%.1f]", n, getLateness(n)));
// else
sb.append(n);
// sb.append(String.format("%s%s", n, Utilities.toShortString(spreq)));
// sb.append(String.format("%s%s", n, isMainDepotVisited(n) ? "+" : ""));
if (it.hasNext())
sb.append(",");
}
sb.append(">");
return sb.toString();
}
@Override
public String getNodeSeqString() {
StringBuilder sb = new StringBuilder(length() * 3);
sb.append("<");
Iterator<Integer> it = iterator();
while (it.hasNext()) {
int n = it.next();
sb.append(n);
if (it.hasNext())
sb.append(",");
}
sb.append(">");
return sb.toString();
}
/**
* Checks if a tool is available at a given node
*
* @param node
* the considered node
* @param tool
* the considered tool
* @return <code>true</code> if the <code>tool</code> is available at the given <code>node</code>
*/
public boolean isToolAvailable(int node, int tool) {
if (node == ITRSPTour.UNDEFINED)
return getTechnician().getToolSet().hasAttribute(tool);
return mTools[node][tool];
}
/**
* Gets the number of spare parts available after servicing a given node
*
* @param node
* the considered node
* @param type
* the considered spare part type
* @return the number of spare parts of <code>type</code> available after servicing the given <code>node</code>, or
* at the start of the tour if <code>node={@link ITRSPTour#UNDEFINED}</code>
*/
public int getAvailableSpareParts(int node, int type) {
if (node == ITRSPTour.UNDEFINED)
return getTechnician().getAvailableSpareParts(type);
return mSpareParts[node][type];
}
/**
* Gets the number of spare parts required to serve the tour starting at a given node.
* <p>
* The returned value ignores possible trips to the depot in the following nodes.
* </p>
*
* @param node
* the considered node
* @param type
* the considered spare part type
* @return the number of spare parts of <code>type</code> required to serve the given <code>node</code> and all
* subsequent nodes, or 0 if <code>node={@link ITRSPTour#UNDEFINED}</code>
*/
public int getRequiredSpareParts(int node, int type) {
if (node == ITRSPTour.UNDEFINED)
return 0;
return mRequiredSpareParts[node][type];
}
/**
* Check if a node can be inserted/added/appended to this tour
*
* @param node
* the node to check
* @throws IllegalArgumentException
* if the node is already present in the tour.
*/
protected void checkNode(int node) throws IllegalArgumentException {
if (isVisited(node))
throw new IllegalArgumentException(String.format(
"Node %s is already visited in this tour", node));
}
/**
* The Class <code>TRSPTourIterator</code> is an implementation of {@link ListIterator} to iterate over a.
* {@link TRSPTour}
* <p>
* Creation date: Feb 16, 2011 - 5:15:56 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 TRSPTourIterator implements ITourIterator {
/** <code>true</code> if the value of <code>cursor</code> cannot be trusted */
private final boolean noCursor;
/** The current node, which will be returned at the next call to {@link #next()}. */
private int current;
/** The cursor. */
private int cursor;
/**
* Instantiates a new tRSP tour iterator.
*/
protected TRSPTourIterator() {
current = getFirstNode();
cursor = 0;
noCursor = false;
}
/**
* Creates a new <code>TRSPTourIterator</code> starting at the given node.
* <p>
* Note that the first call to {@link #next()} of {@link #previous()} will return <code>nodeId</code>
* </p>
*
* @param nodeId
* the id of the current node. If equal to {@link ITRSPTour#UNDEFINED} then the iterator will be
* initialized with the first node
*/
public TRSPTourIterator(int nodeId) {
this(nodeId != ITRSPTour.UNDEFINED ? nodeId : getFirstNode(), 0,
nodeId != ITRSPTour.UNDEFINED);
}
/**
* Creates a new <code>TRSPTourIterator</code>
*
* @param noCursor
* @param current
* @param cursor
*/
private TRSPTourIterator(int current, int cursor, boolean noCursor) {
super();
this.noCursor = noCursor;
this.current = current;
this.cursor = cursor;
}
@Override
public void add(Integer e) {
TRSPTour.this.insertBefore(current, e);
}
@Override
public boolean hasNext() {
return current != ITRSPTour.UNDEFINED;
}
@Override
public boolean hasPrevious() {
return current != ITRSPTour.UNDEFINED;
}
@Override
public Integer next() {
if (hasNext()) {
cursor++;
int c = current;
current = TRSPTour.this.getSucc(current);
return c;
} else
throw new NoSuchElementException();
}
@Override
public int nextIndex() {
if (noCursor)
throw new IllegalStateException(
"Cannot trust the index position when the iterator was started from an initial node different from the tour start");
return cursor + 1;
}
@Override
public Integer previous() {
if (hasPrevious()) {
cursor--;
int c = current;
current = TRSPTour.this.getPred(current);
return c;
} else
throw new NoSuchElementException();
}
@Override
public int previousIndex() {
if (noCursor)
throw new IllegalStateException(
"Cannot trust the index position when the iterator was started from an initial node different from the tour start");
return cursor - 1;
}
@Override
public void remove() {
TRSPTour.this.removeNode(getPred(current));
}
@Override
public void set(Integer e) {
throw new UnsupportedOperationException(
"set operation is not supported by this iterator");
}
@Override
protected ITourIterator clone() {
return new TRSPTourIterator(current, cursor, noCursor);
}
@Override
public ITourIterator subIterator() {
return clone();
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TRSPTour))
return false;
TRSPTour tour = (TRSPTour) obj;
if (getTechnician().getID() != tour.getTechnician().getID())
return false;
if (length() != tour.length())
return false;
TRSPTourIterator thisIt = iterator();
TRSPTourIterator tourIt = tour.iterator();
while (thisIt.hasNext()) {
if (thisIt.next() != tourIt.next())
return false;
}
return true;
}
@Override
public int hashCode() {
return getSolution() != null ? getSolution().getInstance().getSolutionHasher().hash(this)
: super.hashCode();
}
}