/**
*
*/
package vroom.common.modeling.dataModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import vroom.common.utilities.ArrayIterator;
import vroom.common.utilities.ExtendedReentrantLock;
import vroom.common.utilities.ObjectWithIdSet;
import vroom.common.utilities.Utilities;
/**
* <code>TRSPSolution</code> is a class representing a solution for the TRSP. It contains a set of
* {@link GiantPermRoute}, each associated with a {@link Vehicle}
* <p>
* Creation date: Mar 24, 2011 - 2:14:41 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 TRSPSolution extends Solution<GiantPermRoute> {
/** The main data structure for all tours */
private final GiantPermutation mGiantPermutation;
/**
* Returns the giant permutation in which all tour data is stored
*
* @return the giant permutation in which all tour data is stored
*/
protected GiantPermutation getGiantPermutation() {
return mGiantPermutation;
}
// Arrays shared across all tours
/** the set of unserved requests **/
private final Set<IVRPRequest> mUnservedRequests;
/**
* Returns the set of unserved requests <br/>
* This set is initialized with all requests but should be maintained externally
*
* @return the set of unserved requests
*/
public Set<IVRPRequest> getUnservedRequests() {
return this.mUnservedRequests;
}
/** The tours contained in this solution */
private final GiantPermRoute[] mTours;
/**
* Returns the tour associated with a given vehicle
*
* @param techId
* the id of the vehicle
* @return the tour associated with the vehicle with id <code>techID</code>
*/
public GiantPermRoute getTour(int techId) {
return mTours[techId];
}
/**
* Returns the tour that visits a given request
*
* @param reqId
* the considered request
* @return the tour that visits the request with id <code>reqId</code>
*/
public GiantPermRoute getVisitingTour(int reqId) {
int tech = getGiantPermutation().getVisitingVehicle(reqId);
return tech >= 0 ? getTour(getGiantPermutation().getVisitingVehicle(reqId)) : null;
}
// /**
// * Add the tour to this solution, replacing possible previous values
// *
// * @param tour
// * the tour to be added
// * @return the tour previously associated with the same vehicle
// */
// public TRSPTour importTour(TRSPTour tour) {
// TRSPTour t = getTour(tour.getVehicleId());
// TRSPSimpleTour prev = new TRSPSimpleTour(t);
// t.importTour(tour);
// for (int i : prev)
// markAsUnserved(i);
// for (int i : tour)
// markAsServed(i);
//
// return prev;
// }
//
// /**
// * Import a solution into this solution. Will replace all the tours from this solution with the one present in
// * {@code solution}
// *
// * @param solution
// * the solution to import
// */
// public void importSolution(TRSPSolution solution) {
// if (solution != this) {
// getGiantPermutation().clear();
// mUnservedRequests.clear();
// mUnservedRequests.addAll(solution.getUnservedRequests());
// for (TRSPTour tour : solution) {
// TRSPTour thisTour = getTour(tour.getVehicleId());
// thisTour.importTour(tour);
// }
//
// getGiantPermutation().importPermutationInternal(solution.getGiantPermutation());
// }
// }
/**
* Creates a new <code>TRSPSolution</code>
*
* @param instance
* the parent instance
* @param costDelegate
* the cost delegate used to evaluate tours
* @see #freeze()
* @see #unfreeze()
*/
public TRSPSolution(IVRPInstance instance) {
super(instance);
mLock = new ExtendedReentrantLock();
mTours = new GiantPermRoute[instance.getFleet().size()];
mGiantPermutation = newGiantPermutation();
mUnservedRequests = new ObjectWithIdSet<>(Utilities.getMaxId(getParentInstance()
.getNodeVisits()));
for (IVRPRequest r : instance.getRequests())
markAsUnserved(r);
for (int i = 0; i < mTours.length; i++) {
mTours[i] = newTour(getParentInstance().getFleet().getVehicle(i));
}
}
/**
* Instanciates a new giant permutation, used in constructor
*
* @return
*/
protected GiantPermutation newGiantPermutation() {
return new GiantPermutation(this);
}
/**
* Factory method used in constructor {@link #TRSPSolution(IVRPInstance, TRSPCostDelegate, boolean)} to instanciate
* the tours
*
* @param t
* the vehicle to associate with the returned tour
* @return a new tour associated with the given vehicle
*/
protected GiantPermRoute newTour(Vehicle t) {
return new GiantPermRoute(this, t);
}
/**
* Creates a new <code>TRSPSolution</code> by cloning
*
* @param parent
* the solution to be cloned
*/
protected TRSPSolution(TRSPSolution parent) {
super(parent.getParentInstance());
mLock = new ExtendedReentrantLock();
mTours = new GiantPermRoute[getParentInstance().getFleet().size()];
mGiantPermutation = clonePermutation(parent.getGiantPermutation());
mUnservedRequests = new ObjectWithIdSet<IVRPRequest>(Utilities.getMaxId(getParentInstance()
.getNodeVisits()));
mUnservedRequests.addAll(parent.getUnservedRequests());
for (int i = 0; i < mTours.length; i++) {
mTours[i] = cloneTour(parent.getTour(i));
}
}
/**
* clone a permutation, used internally in {@link #TRSPSolution(TRSPSolution)}
*
* @param perm
* @return
*/
protected GiantPermutation clonePermutation(GiantPermutation perm) {
return perm.clone(this);
}
/**
* Clone a tour, used internally in {@link #TRSPSolution(TRSPSolution)}
*
* @param tour
* the tour to be cloned
* @return a clone of {@code tour}
*/
protected GiantPermRoute cloneTour(GiantPermRoute tour) {
return tour.clone(this);
}
/**
* Returns the number of tours in this solution
*
* @return the number of tours in this solution
*/
public int getTourCount() {
return mTours.length;
}
@Override
public Comparable<?> getObjective() {
return getObjectiveValue();
}
@Override
public double getObjectiveValue() {
double cost = 0;
for (int r = 0; r < getRouteCount(); r++) {
cost += getRoute(r).getCost();
}
return cost;
}
/**
* Returns the number of requests that are not served by this solution
*
* @return the number of requests that are not served by this solution
*/
public int getUnservedCount() {
return getUnservedRequests().size();
}
/**
* Mark a request as served, or does nothing if {@code id} is not a valid request id
*
* @param id
* an id
*/
public void markAsServed(IVRPRequest r) {
getUnservedRequests().remove(r);
}
/**
* Mark all requests as served, in other words clear the {@link #getUnservedRequests() unserved request}
*/
public void markAllAsServed() {
mUnservedRequests.clear();
}
/**
* Mark a request as unserved, or does nothing if {@code id} is not a valid request id
*
* @param r
* an id
*/
public void markAsUnserved(IVRPRequest r) {
getUnservedRequests().add(r);
}
@Override
public TRSPSolution clone() {
return new TRSPSolution(this);
}
@Override
public ListIterator<GiantPermRoute> iterator() {
return new ArrayIterator<GiantPermRoute>(mTours);
}
// ------------------------------------
// ILockable interface implementation
// ------------------------------------
/** A lock to be used by this instance */
private final ExtendedReentrantLock mLock;
private boolean mSelfLock = false;
@Override
public boolean tryLock(long timeout) {
try {
return getLockInstance().tryLock(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}
@Override
public void acquireLock() {
try {
if (!getLockInstance().tryLock(TRY_LOCK_TIMOUT, TRY_LOCK_TIMOUT_UNIT)) {
throw new IllegalStateException(
String.format(
"Unable to acquire lock on this instance of %s (%s) after %s %s, owner: %s",
this.getClass().getSimpleName(), hashCode(), TRY_LOCK_TIMOUT,
TRY_LOCK_TIMOUT_UNIT, getLockInstance().getOwnerName()));
}
} catch (InterruptedException e) {
throw new IllegalStateException(String.format(
"Unable to acquire lock on this instance of %s (%s)", this.getClass()
.getSimpleName(), hashCode()), e);
}
;
}
@Override
public void releaseLock() {
if (mLock.isLocked()) {
this.mLock.unlock();
}
}
@Override
public void internalReleaseLock() {
if (mSelfLock) {
releaseLock();
mSelfLock = false;
}
}
@Override
public boolean isLockOwnedByCurrentThread() {
return this.mLock.isHeldByCurrentThread();
}
@Override
public ExtendedReentrantLock getLockInstance() {
return this.mLock;
}
/**
* Check the lock state of this object
*
* @return <code>true</code> if there was no previous lock and lock was acquired, <code>false</code> if the lock was
* already owned by the current thread
* @throws ConcurrentModificationException
*/
@Override
public boolean checkLock() throws ConcurrentModificationException {
if (!isLockOwnedByCurrentThread() && mLock.isLocked()) {
throw new ConcurrentModificationException(
String.format(
"The current thread (%s) does not have the lock on this instance of %s, owner: %s",
Thread.currentThread(), this.getClass().getSimpleName(),
getLockInstance().getOwnerName()));
} else if (!mLock.isLocked()) {
acquireLock();
mSelfLock = true;
return true;
} else {
mSelfLock = false;
return false;
}
}
// ------------------------------------
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(String.format("Cost:%.2f, Unserved:%s Tours [", getObjectiveValue(),
Utilities.toShortString(getUnservedRequests())));
Iterator<GiantPermRoute> it = iterator();
while (it.hasNext()) {
b.append(it.next().toString());
if (it.hasNext())
b.append(',');
}
b.append(']');
return b.toString();
}
// @Override
// public int hashCode() {
// return getParentInstance().getSolutionHasher().hash(this);
// }
//
// @Override
// public int hashSolution() {
// return getParentInstance().getSolutionHasher().hash(this);
// }
/**
* Returns the hash code of this instance as defined in {@link Object#hashCode()}
*
* @return the hash code of this instance as defined in {@link Object#hashCode()}
*/
public int defaultHashCode() {
return super.hashCode();
}
/**
* Convert a solution to a giant tour
*
* @return a list containing all the tours concatenated into a giant tour
*/
public List<INodeVisit> toGiantTour() {
ArrayList<INodeVisit> tour = new ArrayList<INodeVisit>(getParentInstance().getNodeVisits()
.size());
for (GiantPermRoute t : this) {
for (INodeVisit node : t) {
tour.add(node);
}
}
return tour;
}
/**
* Convert a solution to a short string of the form: <br/>
* <code>"<1,11,12,0,13,21|2,14,15,16,22>"</code>
*
* @return a short string describing this solution
*/
public String toShortString() {
StringBuilder sb = new StringBuilder();
sb.append("<");
for (GiantPermRoute tour : this) {
if (sb.length() > 1)
sb.append("|");
int idx = 0;
for (INodeVisit node : tour) {
if (idx > 0)
sb.append(",");
sb.append(node.toString());
idx++;
}
}
sb.append(">");
return sb.toString();
}
/**
* <code>GiantPermutation</code>
* <p>
* Creation date: Apr 26, 2012 - 4:56:09 PM
*
* @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a
* href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a
* href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a>
* @version 1.0
*/
public static class GiantPermutation {
/** The cumulative cost at each node. */
private final double[] mCumulativeCost;
/** 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 forward time slack between any two nodes */
private final double[][] mFwdTimeSlack;
/** The available spare parts at each node */
private final double[][] mCapacity;
/** 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 Predecessor of each node. */
private final int[] mPred;
/** The Successor of each node. */
private final int[] mSucc;
/** The vehicle visiting each node */
private final int[] mTechId;
private final TRSPSolution mSolution;
/**
* 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;
private IVRPInstance getParentInstance() {
return mSolution.getParentInstance();
}
protected GiantPermutation(TRSPSolution solution) {
mSolution = solution;
int maxID = Utilities.getMaxId(getParentInstance().getNodeVisits());
mCumulativeCost = new double[maxID];
mEarliestArrivalTime = new double[maxID];
mLatestFeasibleTime = new double[maxID];
mWaitingTime = new double[maxID][];
boolean defFwdSlck = false; // FIXME set depending on the probleme at hand
mFwdTimeSlack = defFwdSlck ? new double[maxID][] : null;
for (int i = 0; i < mWaitingTime.length; i++) {
mWaitingTime[i] = new double[defFwdSlck ? mWaitingTime.length - i : 1];
if (defFwdSlck)
mFwdTimeSlack[i] = new double[mFwdTimeSlack.length - i];
}
mCapacity = new double[maxID][getParentInstance().getFleet().getVehicle()
.getCompartmentCount()];
mRequiredSpareParts = new int[maxID][getParentInstance().getFleet().getVehicle()
.getCompartmentCount()];
mDepotVisited = new boolean[maxID];
mPred = new int[maxID];
mSucc = new int[maxID];
mTechId = new int[maxID];
for (int id = 0; id < mTechId.length; id++) {
mTechId[id] = GiantPermRoute.UNDEFINED;
mPred[id] = GiantPermRoute.UNDEFINED;
mSucc[id] = GiantPermRoute.UNDEFINED;
}
}
/**
* Import {@code giantPermutation} in this permutation
*
* @param giantPermutation
*/
protected void importPermutationInternal(GiantPermutation giantPermutation) {
// Nothing to do here - the work is delegated to TRSPTour.importTour
}
/**
* Used internally in {@link #clone()} to instantiate a new permutation, should be overriden by subclasses
*
* @param solution
* @return
*/
protected GiantPermutation cloneInternal(TRSPSolution solution) {
return new GiantPermutation(solution);
}
/**
* Clone this permutation and associate it to a possibly different solution
*
* @param solution
* @return a clone of this permutation
*/
private GiantPermutation clone(TRSPSolution solution) {
GiantPermutation clone = cloneInternal(solution);
clone.importPermutation(this);
return clone;
}
/**
* Clone this permutation and associate it to a possibly different solution
*
* @param solution
* @param clone
* an empty instance of {@link GiantPermutation}
* @return a clone of this permutation
*/
public void importPermutation(GiantPermutation clone) {
for (int i = 0; i < mCumulativeCost.length; i++) {
this.mCumulativeCost[i] = clone.mCumulativeCost[i];
this.mEarliestArrivalTime[i] = clone.mEarliestArrivalTime[i];
this.mLatestFeasibleTime[i] = clone.mLatestFeasibleTime[i];
for (int j = 0; j < this.mWaitingTime[i].length; j++)
this.mWaitingTime[i][j] = clone.mWaitingTime[i][j];
if (this.mFwdTimeSlack != null)
for (int j = 0; j < this.mFwdTimeSlack[i].length; j++)
this.mFwdTimeSlack[i][j] = clone.mFwdTimeSlack[i][j];
for (int j = 0; j < this.mCapacity[i].length; j++)
this.mCapacity[i][j] = clone.mCapacity[i][j];
for (int j = 0; j < this.mRequiredSpareParts[i].length; j++)
this.mRequiredSpareParts[i][j] = clone.mRequiredSpareParts[i][j];
this.mDepotVisited[i] = clone.mDepotVisited[i];
this.mPred[i] = clone.mPred[i];
this.mSucc[i] = clone.mSucc[i];
this.mTechId[i] = clone.mTechId[i];
}
}
/**
* Returns {@code true} if the forward slack time is defined in this instance
*
* @return {@code true} if the forward slack time is defined in this instance
*/
public boolean isFwdSlackTimeDefined() {
return mFwdTimeSlack != null;
}
/**
* Returns the id of the vehicle visiting the given node
*
* @param node
* @return the id of the vehicle visiting the given node
*/
public int getVisitingVehicle(int node) {
return mTechId[node];
}
/**
* Sets the id of the vehicle visiting the given node
*
* @param node
* @param techId
*/
public void setVisitingVehicle(int node, int techId) {
if (mTechId[node] != GiantPermRoute.UNDEFINED && techId != GiantPermRoute.UNDEFINED
&& techId != mTechId[node])
throw new IllegalStateException(String.format(
"Cannot affect %s to vehicle %s (already visited by %s)", node, techId,
mTechId[node]));
mTechId[node] = techId;
}
/**
* 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 mCumulativeCost[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) {
mCumulativeCost[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 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
* @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;
}
try {
mWaitingTime[i][j - i] = time;
} catch (ArrayIndexOutOfBoundsException e) {
// FIXME remove
e.printStackTrace();
throw e;
}
}
/**
* Returns the forward slack time at node {@code i} relative to the path {@code (i,...,j)}
*
* @param i
* @param j
* @return the forward slack time at node {@code i} relative to the path {@code (i,...,j)}
*/
public double getFwdSlackTime(int i, int j) {
if (j < i) {
int a = j;
j = i;
i = a;
}
return mFwdTimeSlack[i][j - i];
}
/**
* 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) {
if (j < i) {
int a = j;
j = i;
i = a;
}
mFwdTimeSlack[i][j - i] = 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 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;
}
/**
* 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;
}
protected void resetNodeData(int node) {
setPred(node, GiantPermRoute.UNDEFINED);
setSucc(node, GiantPermRoute.UNDEFINED);
setVisitingVehicle(node, GiantPermRoute.UNDEFINED);
// FIXME find why inconsistencies appear if this is not reset
setCumulativeCost(node, NA);
setEarliestArrivalTime(node, GiantPermutation.NA);
setLatestFeasibleTime(node, GiantPermutation.NA);
Arrays.fill(this.mCapacity[node], 0);
Arrays.fill(this.mRequiredSpareParts[node], 0);
setWaitingTime(node, NA);
// if (!getParentInstance().isCVRPTW()) { FIXME implement this case
// for (INodeVisit n : getParentInstance().getNodeVisits()) {
// setWaitingTime(n.getID(), node, NA);
// }
// }
}
/**
* Reset all the information stored in this instance
*/
public void clear() {
for (int n = 0; n < mPred.length; n++)
resetNodeData(n);
}
/**
* Check this giant permutation for cycles
*
* @return the first cycle found in the permutation, or <code>null</code> if none was found
*/
public List<Integer> checkForCycles() {
boolean[] touched = new boolean[mPred.length];
int start = 0;
while (start < mPred.length) {
if (!touched[start]) {
LinkedList<Integer> cycle = new LinkedList<>();
// start was not touched before
int n = start;
cycle.add(n);
touched[n] = true;
while (mSucc[n] != GiantPermRoute.UNDEFINED) {
cycle.add(n);
if (touched[n]) // We found a cycle
return cycle;
touched[n] = true;
n = mSucc[n];
}
} else {
start++; // start was already touched: move to the next node
}
}
return null;
}
public boolean isFrozen(int node) {
return false;
}
}
/** Reset this solution to its initial state */
@Override
public void clear() {
getGiantPermutation().clear();
for (GiantPermRoute t : this) {
t.clear();
}
for (IVRPRequest r : getParentInstance().getRequests())
getUnservedRequests().add(r);
}
}