/** * */ package vroom.trsp.datamodel; import java.util.ArrayList; import java.util.Collections; 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 { /** The technician which is executing this tour */ private final Technician mTechnician; /** The id of the main depot duplicate associated with this tour */ private final int mMainDepotId; /** The First Node ID. */ private int mFirst; /** The Last Node ID. */ private int mLast; /** The Length. */ private int mLength = 0; /** * 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); 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; mMainDepotId = solution.getInstance().getMainDepotDuplicate(getTechnicianId()); mFirst = ITRSPTour.UNDEFINED; mLast = ITRSPTour.UNDEFINED; mLength = 0; setTotalCost(0); setAutoUpdated(true); } // /** // * Creates a new <code>TRSPTour</code> by cloning the given instance. // * // * @param original // * the original tour // */ // private TRSPTour(TRSPTour original) { // super(original.getSolution()); // this.mMainDepotId = original.mMainDepotId; // // 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); // } // // 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; // } /** * 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(); } /** * Returns the id of the main depot associated with this tour * * @return the id of the main depot associated with this tour */ public int getMainDepotId() { return mMainDepotId; } /** * 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 cumulative cost at a given node * * @param nodeId * the id of the considered node * @return the cumulative cost at the specified node */ public double getCumulativeCost(int nodeId) { return getPermutation().getCumulativeCost(nodeId); } /** * Sets the cumulative cost at a given node * * @param nodeId * the id of the considered node * @param value * the cumulative cost at the specified node */ public void setCumulativeCost(int nodeId, double value) { getPermutation().setCumulativeCost(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 getPermutation().getEarliestArrivalTime(nodeId); } /** * 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); } /** * Returns the latest recommended departure time considering that the technician should wait at {@code node} instead * of waiting at its sucessor * * @param node * @return the latest recommended departure time at {@code node} */ public double getWaitDepartureTime(int node) { double wait = getSucc(node) != UNDEFINED ? getWaitingTime(getSucc(node)) : 0; return getEarliestDepartureTime(node) + wait; } /** * 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) { getPermutation().setEarliestArrivalTime(nodeId, time); } /** * Returns the latest time at which the tour can be started so that it ends at the earliest date possible and no * time window is violated * * @return the latest time at which the tour can be started */ public double getLatestStartTime() { if (length() < 2) return getEarliestStartTime(); else if (getPermutation().isFwdSlackTimeDefined()) return getEarliestStartTime() + Math.min(getFwdSlackTime(getFirstNode()), getWaitingTime(getFirstNode(), getLastNode())); else return getEarliestStartTime(); } /** * The minimal tour duration, taking into account the {@linkplain #getLatestStartTime() latest start time} * * @return the minimal tour duration * @see #getMinimalDuration() */ public double getMinimalDuration() { if (getLastNode() == ITRSPTour.UNDEFINED) return 0; else return getEarliestDepartureTime(getLastNode()) - getLatestStartTime(); } /** * Returns the total duration of this tour. * * @return the total duration of this tour. */ public double getDuration() { if (getLastNode() == ITRSPTour.UNDEFINED) return 0; else return getEarliestDepartureTime(getLastNode()); } /** * 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 getPermutation().getWaitingTime(nodeId); } /** * Gets the cumulative waiting time between two nodes, i.e. the total time the vehicle will spend waiting between * nodes {@code i} and {@code j} * <p> * Note that the waiting time at nodes {@code i} and {@code j} are not included in the returned value * </p> * * @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) { return getPermutation().getWaitingTime(i, j); } /** * 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) { getPermutation().setWaitingTime(nodeId, 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) { getPermutation().setWaitingTime(i, j, time); } /** * Returns the forward slack time at node {@code i} indicating how far the <em>departure</em> time can be shifted * without violating TW constraints. * * @param i * a node of this tour * @return the forward slack time at node {@code i} * @see #getFwdSlackTime(int, int) */ public double getFwdSlackTime(int i) { return getFwdSlackTime(i, getLastNode()); } /** * Returns the forward slack time at node {@code i} relative to the path {@code (i,...,j)} indicating how far the * <em>departure</em> time can be shifted without violating TW constraints on the subtour {@code (i,...,j)} . * * @param i * the first node of the subtour * @param j * the last node of the subtour * @return the forward slack time at node {@code i} relative to the path {@code (i,...,j)} */ public double getFwdSlackTime(int i, int j) { return getPermutation().getFwdSlackTime(i, j); } /** * Sets the forward slack time at node {@code i} relative to the path {@code (i,...,j)} * * @param i * @param j * @param slack * the forward slack time at node {@code i} relative to the path {@code (i,...,j)} */ public void setFwdSlackTime(int i, int j, double slack) { getPermutation().setFwdSlackTime(i, j, slack); } /** * 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 getPermutation().getLatestFeasibleArrivalTime(nodeId); } /** * Gets the latest departure 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 departure time at node with id <code>nodeId</code> */ public double getLatestFeasibleDepartureTime(int nodeId) { return getEarliestDepartureTime(nodeId) + getFwdSlackTime(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) { getPermutation().setLatestFeasibleTime(nodeId, time); } /** * 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(getMainDepotId()); } /** * 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 getPermutation().isMainDepotVisited(node); } /** * Sets the main depot visited flag * * @param node * @param visited */ private void setMainDepotVisited(int node, boolean visited) { getPermutation().setMainDepotVisited(node, visited); } /** * 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(getRequiredSpareParts(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 * @param arrivalTime * the time at which the {@code node} will be visited * @return true, if successful */ public void appendNode(int node, double arrivalTime) { appendNode(node); // Set the earliest arrival setEarliestArrivalTime(node, arrivalTime); // Set the waiting time setWaitingTime(node, getTimeWindow(node).getWaiting(arrivalTime)); // Set the fwd slack time propagateFwdSlackTime(node, true); } /** * Append a single node to this tour. * * @param node * the node */ public void 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; } getPermutation().setVisitingTechnician(node, getTechnicianId()); mLength++; this.mLast = node; propagateUpdate(getLastNode(), getLastNode()); getCostDelegate().nodeInserted(this, getPred(node), node, ITRSPTour.UNDEFINED); } /** * Append nodes. * * @param nodes * the nodes * @return true, if successful */ public void appendNodes(List<Integer> nodes) { for (int n : nodes) { appendNode(n); } } @Override public TRSPTour clone() { throw new UnsupportedOperationException("Can only clone an entire solution"); } /** * Internal cloning of a tour, should be overridden by subclasses * * @param solution * @return a new tour instance attached to the given {@code solution} */ protected TRSPTour cloneInternal(TRSPSolution solution) { return new TRSPTour(solution, getTechnician()); } /** * Clone this tour and associate it to a new solution * * @param solution * the solution with which the new tour will be associated * @return a clone of this tour */ public TRSPTour clone(TRSPSolution solution) { TRSPTour clone = cloneInternal(solution); clone.mAutoUpdated = this.mAutoUpdated; clone.mFirst = this.mFirst; clone.mLast = this.mLast; clone.mLength = this.mLength; clone.setTotalCost(this.getTotalCost()); return clone; } /** * Copy the sequence of nodes from {@code tour} in this tour * * @param tour */ public void importTour(ITRSPTour tour) { boolean prev = isAutoUpdated(); setAutoUpdated(false); this.clear(); final boolean importArrival = TRSPTour.class.isAssignableFrom(tour.getClass()); for (int i : tour) { if (getSolution().getInstance().isMainDepot(i)) i = getMainDepotId(); if (importArrival) this.appendNode(i, ((TRSPTour) tour).getEarliestArrivalTime(i)); else this.appendNode(i); } // Update all data at once setAutoUpdated(true); // Revert to previous value setAutoUpdated(prev); } /** * 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); getCostDelegate().nodeRemoved(this, pred, node, succ); return pred != ITRSPTour.UNDEFINED || succ != ITRSPTour.UNDEFINED; } /** * Truncate this tour at a given node. * <p> * All the nodes from this tour that are after {@code node} will be removed, excluding {@code node} itself * </p> * * @param node * the truncation point * @return a list containing the removed nodes */ public List<Integer> truncate(int node) { if (!isVisited(node) || getLastNode() == node) return Collections.emptyList(); ArrayList<Integer> removedNodes = new ArrayList<>(); TRSPTourIterator it = iterator(node); it.next(); while (it.hasNext()) { int next = it.next(); if (next == getLastNode()) break; removedNodes.add(next); } setSucc(node, getLastNode()); setPred(getLastNode(), node); this.mLength -= removedNodes.size(); propagateUpdate(getLastNode(), node); getCostDelegate().evaluateTour(this, true); for (Integer n : removedNodes) resetNodeData(n); return removedNodes; } /** * Remove all nodes from this tour and reset all stored information */ public void clear() { for (int i : this) { resetNodeData(i); } setFirst(UNDEFINED); setLast(UNDEFINED); mLength = 0; setTotalCost(0); } /** * 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) { getPermutation().resetNodeData(node); } /** * 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); 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(String.format( "The specified predecessor %s is not present in this tour (tec:%s)", pred, getTechnicianId())); 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); if (succ != UNDEFINED) setPred(succ, node); setSucc(node, succ); // Update length this.mLength++; getPermutation().setVisitingTechnician(node, getTechnicianId()); propagateUpdate(node, node); 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(String.format( "The specified successor %s is not present in this tour (tec:%s)", succ, getTechnicianId())); 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++; getPermutation().setVisitingTechnician(node, getTechnicianId()); propagateUpdate(node, node); 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); getPermutation().setVisitingTechnician(pred, getTechnicianId()); pred = succ; } last = succ; getPermutation().setVisitingTechnician(last, getTechnicianId()); setSucc(getPred(current), first); setPred(first, getPred(current)); setPred(current, last); setSucc(last, current); // Update length this.mLength += subtour.size(); propagateUpdate(predecessor, current); 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); getPermutation().setVisitingTechnician(node, getTechnicianId()); 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; if (first != UNDEFINED) getPermutation().setVisitingTechnician(first, getTechnicianId()); } /** * Set the length of this tour, for internal use only * * @param length */ protected void setLength(int length) { mLength = length; } /** * 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; if (last != UNDEFINED) getPermutation().setVisitingTechnician(last, getTechnicianId()); } /** * 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 getPermutation().getPred(node); } /** * Sets the predecessor of a node. * * @param node * the node */ public void setPred(int node, int pred) { getPermutation().setPred(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 getPermutation().getSucc(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) { getPermutation().setSucc(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; } /** * Update the time related information in the whole tour. This includes arrival times, waiting times, forward slack * times. */ public void updateTimeInformation() { propagateEarliestTime(getFirstNode(), true); propagateWaitingTime(getFirstNode()); propagateFwdSlackTime(getFirstNode(), true); propagateLatestFeasibleArrivalTime(getLastNode(), true); } /** * 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 */ public void propagateUpdate(int fwdNode, int bwdNode) { // Bugfix 2012/03/06 We cannot assure that previous data was erased so we must force the update final boolean force = true; // Ignore if auto update is disabled if (!isAutoUpdated()) return; // Ignore tour is empty if (length() == 0) return; if (fwdNode == ITRSPTour.UNDEFINED) fwdNode = getFirstNode(); if (bwdNode == ITRSPTour.UNDEFINED) bwdNode = getLastNode(); propagateEarliestTime(fwdNode, force); propagateLatestFeasibleArrivalTime(bwdNode, force); propagateWaitingTime(fwdNode); propagateFwdSlackTime(fwdNode, force); propagateTools(fwdNode, force); propagateSpareParts(fwdNode, force); propagateDepotVisitedUpdateFwd(fwdNode, force); propagateRequiredSpareParts(bwdNode, force); } /** * Returns {@code true} if the stored data for {@code node} is allowed, {@code false} otherwise * * @param node * @return {@code true} if the stored data for {@code node} is allowed, {@code false} otherwise */ protected boolean isUpdateAllowed(int node) { return true; } /** * Propagate all the time-related information after {@code node} was changed * * @param node * the changed node */ public void propagateTime(int node) { if (node == UNDEFINED) throw new IllegalStateException(node + " is not a valid node id"); propagateEarliestTime(node, true); propagateLatestFeasibleArrivalTime(node, true); propagateWaitingTime(node); propagateFwdSlackTime(node, true); } /** * 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 = isMainDepotVisited(node); if (node == getMainDepotId()) // Node is a depot setMainDepotVisited(node, true); else if (pred == ITRSPTour.UNDEFINED) // Start of the tour (and node is not the main depot) setMainDepotVisited(node, false); else // Propagate change setMainDepotVisited(node, isMainDepotVisited(pred)); // Recursive call if (force || old != isMainDepotVisited(node) || node == getMainDepotId()) propagateDepotVisitedUpdateFwd(getSucc(node), force); } /** * Sets the waiting 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 (length() == 0) // Nothing to do return; int i, j; int pred = getPred(node); TRSPTourIterator it = pred == UNDEFINED ? iterator() : iterator(pred); // 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 cumulative waiting time of all nodes if (!getInstance().isCVRPTW() || getInstance().isForceFwdSlackTime()) { 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(); } } } /** * 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 = 0; int pred = getPred(node); do { if (isUpdateAllowed(node)) { old = getEarliestArrivalTime(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)); } // Move to next node pred = node; node = getSucc(node); } while (node != UNDEFINED && (force || old != getEarliestArrivalTime(node))); } /** * 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 = 0; int succ = getSucc(node); do { if (isUpdateAllowed(node)) { old = getLatestFeasibleArrivalTime(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))); } } // Move to next node succ = node; node = getPred(node); } while (node != UNDEFINED && (force || old != getLatestFeasibleArrivalTime(node))); } /** * Sets the forward slack time for <code>node</code> by propagating the slack time * * @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 propagateFwdSlackTime(int node, boolean force) { if (!getPermutation().isFwdSlackTimeDefined()) return; // Nothing to do final int predNode = getPred(node); // Update fwd slack time between any two nodes TRSPTourIterator outerIt = iterator(); // Fwd slack time is unchanged between start and node, unless node is the first node boolean jumpToChanged = predNode != UNDEFINED; int predI = getFirstNode(); while (outerIt.hasNext()) { int i = outerIt.next(); // We reached the changed node, inner loop will start at node succ[i] if (i == predNode) jumpToChanged = false; // Set fw slk time for (i...i) if (!jumpToChanged) // setFwdSlackTime(i, i, getTimeWindow(i).endAsDouble() + getServiceTime(i) // - getEarliestDepartureTime(i)); setFwdSlackTime(i, i, Double.POSITIVE_INFINITY); // The time window is on the arrival time if (getSucc(i) == UNDEFINED) continue; int firstInnerIt = jumpToChanged ? predNode : getSucc(i); TRSPTourIterator innerIt = iterator(firstInnerIt); int j = getPred(firstInnerIt); double prevSlackTime = getFwdSlackTime(i, j); while (innerIt.hasNext()) { int nextJ = innerIt.next(); // j+1 // We use the recurrence function // F_i^(i..j+1) = min{F_i^(i...j}, b_{j+1} -t_{j+1} +\sum_{i<p<=j} W_p,) prevSlackTime = Math.min(prevSlackTime,// getTimeWindow(nextJ).endAsDouble() - getEarliestArrivalTime(nextJ) // + getWaitingTime(i, nextJ)); setFwdSlackTime(i, nextJ, prevSlackTime); } } } /** * 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 || getInstance().getToolCount() == 0) return; // Nothing to do boolean changed = false; if (isMainDepot(node)) // Main depot: assumes all tools are available for (int t = 0; t < getInstance().getToolCount(); t++) { if (!isToolAvailable(node, t)) { setToolAvailability(node, t, true); changed = true; } } else if (getPred(node) == ITRSPTour.UNDEFINED) // No predecessor: assumes start of the tour for (int t = 0; t < getInstance().getToolCount(); t++) { if (isToolAvailable(node, t) != getTechnician().getToolSet().hasAttribute(t)) { setToolAvailability(node, t, getTechnician().getToolSet().hasAttribute(t)); changed = true; } } else // Propagate the tools available at the predecessor for (int t = 0; t < getInstance().getToolCount(); t++) { if (isToolAvailable(node, t) != isToolAvailable(getPred(node), t)) { setToolAvailability(node, t, isToolAvailable(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 || getInstance().getSpareCount() == 0) 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 (getAvailableSpareParts(node, p) != Integer.MAX_VALUE) { setAvailableSpareParts(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 (getAvailableSpareParts(node, p) != av) { setAvailableSpareParts(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 (getAvailableSpareParts(node, p) != av) { setAvailableSpareParts(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 || getInstance().getSpareCount() == 0) 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 ? getRequiredSpareParts(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 = getRequiredSpareParts(node, p); setRequiredSpareParts(node, p, succReq[p] + nodeReq[p]); changed |= old != getRequiredSpareParts(node, p); } // Recursive call if (force || changed) propagateRequiredSpareParts(getPred(node), force); } @Override public boolean isVisited(int node) { return node != UNDEFINED && getPermutation().getVisitingTechnician(node) == getTechnicianId(); } @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(), getMinimalDuration(), 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(n); if (it.hasNext()) sb.append(","); } sb.append(">"); return sb.toString(); } @Override public String toDetailedString() { StringBuilder sb = new StringBuilder(length() * 3); sb.append(String.format("t:%s c:%.2f d:%.2f l:%s <", getTechnician().getID(), getTotalCost(), getMinimalDuration(), length())); TRSPTourIterator it = iterator(); while (it.hasNext()) { int n = it.next(); if (getInstance().isCVRPTW()) sb.append(String.format("%s {q:%s,tw:%s@%s}", n, // Utilities.toShortString(getPermutation().getAvailableSpareParts(n)),// getEarliestArrivalTime(n), getTimeWindow(n))); else sb.append(String.format( "%s {t:%s,p:%s,tw:%.1f@%s,s:%.1f}", n, // isMainDepotVisited(n) ? "all" : Utilities.toShortString(getPermutation() .getAvailableTools(n)), // isMainDepotVisited(n) ? "all" : Utilities.toShortString(getPermutation() .getAvailableSpareParts(n)),// getEarliestArrivalTime(n), getTimeWindow(n),// getFwdSlackTime(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 getPermutation().isToolAvailable(node, tool); } /** * Set the tool availability * * @param node * @param tool * @param available */ private void setToolAvailability(int node, int tool, boolean available) { getPermutation().setToolAvailability(node, tool, available); } /** * 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 getPermutation().getAvailableSpareParts(node, type); } /** * Sets the number of spare parts available after servicing a given node * * @param node * the considered node * @param type * the considered spare part type * @param num * 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> */ private void setAvailableSpareParts(int node, int type, int num) { getPermutation().setAvailableSpareParts(node, type, num); } /** * Gets the number of spare parts required to serve the tour starting at 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> 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 getPermutation().getRequiredSpareParts(node, type); } /** * Sets 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 * @param num * 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> */ private void setRequiredSpareParts(int node, int type, int num) { getPermutation().setRequiredSpareParts(node, type, num); } /** * 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 * @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) { if (node == ITRSPTour.UNDEFINED) return new int[getInstance().getSpareCount()]; return getPermutation().getRequiredSpareParts(node); } /** * 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)); if (getInstance().isMainDepot(node) && node != getMainDepotId()) throw new IllegalArgumentException( String.format("Inserting the wrong main depot id (is:%s expected:%s)", node, getMainDepotId())); } /** * 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(); } @Override public int hashSolution() { return getSolution() != null ? getSolution().getInstance().getSolutionHasher().hash(this) : super.hashCode(); } /** * This method adds the technician home to the start and end of this tour, and set the {@link #isAutoUpdated() * autoUpdate} flag to {@code true} * * @throws IllegalStateException * if this tour is not empty */ public void initialize() { if (this.length() > 0) throw new IllegalStateException("This tour is not empty"); appendNode(getTechnician().getHome().getID()); appendNode(getInstance().getHomeDuplicate(getTechnician().getHome().getID())); setAutoUpdated(true); } }