package vroom.trsp.optimization;
import vroom.common.heuristics.NeighborhoodBase;
import vroom.common.utilities.optimization.IMove;
import vroom.trsp.datamodel.ITRSPTour;
import vroom.trsp.datamodel.TRSPDetailedSolutionChecker;
import vroom.trsp.datamodel.TRSPSolution;
import vroom.trsp.datamodel.TRSPTour;
import vroom.trsp.datamodel.TRSPTour.TRSPTourIterator;
import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate;
import vroom.trsp.optimization.constraints.TourConstraintHandler;
import vroom.trsp.optimization.constraints.TourConstraintHandler.FeasibilityState;
import vroom.trsp.util.TRSPLogging;
/**
* The class <code>InsertionMove</code> is a representation of the insertion of a node in a {@linkplain TRSPTour tour}.
* <p>
* When evaluating an {@link InsertionMove} the cost of an eventual trip to the depot, as defined by
* {@link InsertionMove#isDepotTrip()} and {@link InsertionMove#getDepotSucc()} should be considered
* </p>
* <p>
* Creation date: Mar 24, 2011 - 1:49:51 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 InsertionMove extends TRSPMove {
private final int mNodeId;
private final int mInsertionPred;
private final int mInsertionSucc;
private final boolean mDepotTrip;
private final int mDepotSucc;
private double mSecondaryImprovement;
/**
* Returns <code>true</code> if this move is feasible, <code>false</code> otherwise
*
* @return <code>true</code> if this move is feasible, <code>false</code> otherwise
*/
public boolean isFeasible() {
return getInsertionPred() != ITRSPTour.UNDEFINED
|| getInsertionSucc() != ITRSPTour.UNDEFINED;
}
/**
* Creates a new infeasible <code>InsertionMove</code>
*
* @param node
* the infeasible node
*/
public InsertionMove(int node, TRSPTour tour) {
this(node, tour, Double.POSITIVE_INFINITY, ITRSPTour.UNDEFINED, ITRSPTour.UNDEFINED);
}
/**
* Creates a new <code>InsertionMove</code>
*
* @param node
* the inserted node
* @param tour
* the tour in which the node should be inserted
* @param cost
* the cost of the insertion
* @param insertionPred
* the node after which the <code>node</code> should be inserted
* @param insertionSucc
* the node before which the <code>node</code> should be inserted
* @param move
* the inserted move
*/
public InsertionMove(int node, TRSPTour tour, double cost, int insertionPred, int insertionSucc) {
this(node, tour, cost, insertionPred, insertionSucc, ITRSPTour.UNDEFINED, false);
}
/**
* Creates a new <code>InsertionMove</code>
*
* @param node
* the inserted node
* @param tour
* the tour in which the node should be inserted
* @param cost
* the cost of the insertion
* @param insertionPred
* the node after which the <code>node</code> should be inserted
* @param insertionSucc
* the node before which the <code>move</code> should be inserted
* @param depotSucc
* the node before which a trip to the central depot will be planned
* @param move
* the inserted move
*/
public InsertionMove(int node, TRSPTour tour, double cost, int insertionPred,
int insertionSucc, int depotSucc) {
this(node, tour, cost, insertionPred, insertionSucc, depotSucc, true);
}
/**
* Creates a new <code>InsertionMove</code>
*
* @param node
* the inserted node
* @param tour
* the tour in which the node should be inserted
* @param cost
* the cost of the insertion
* @param insertionPred
* the node after which the <code>node</code> should be inserted
* @param insertionSucc
* the node before which the <code>move</code> should be inserted
* @param depotSucc
* the node before which a trip to the central depot will be planned
* @param move
* the inserted move
* @param depotTrip
* {@code true} if a trip to the central depot should be planned
*/
private InsertionMove(int node, TRSPTour tour, double cost, int insertionPred,
int insertionSucc, int depotSucc, boolean depotTrip) {
super(-cost, tour);
mNodeId = node;
mInsertionPred = insertionPred;
mInsertionSucc = insertionSucc;
mDepotTrip = depotTrip;
mDepotSucc = depotSucc;
}
@Override
public String toString() {
return String.format("ins(%3s@%3s:(%3s,%3s) (%3s),%.3f)", //
getNodeId(), getTour().getTechnicianId(),//
getInsertionPred(), getInsertionSucc(), isDepotTrip() ? getDepotSucc() : "-", //
getImprovement()// , getSecondaryImprovement()
);
}
@Override
public String getMoveName() {
return "ins";
}
/**
* Getter for the insertion <code>evaluation</code> (cost)
*
* @return the evaluation
*/
public double getCost() {
return -getImprovement();
}
/**
* Getter for the improvement relative to a possible second objective
*
* @return the secondary improvement
*/
public double getSecondaryImprovement() {
return mSecondaryImprovement;
}
/**
* Setter for the improvement relative to a possible second objective
*
* @param secondaryImprovement
* the secondary improvement to set
*/
public void setSecondaryImprovement(double secondaryImprovement) {
mSecondaryImprovement = secondaryImprovement;
}
/**
* Returns the node to be inserted
*
* @return the node to be inserted
*/
public int getNodeId() {
return mNodeId;
}
/**
* Returns the move after which the move is to be inserted, or {@link ITRSPTour#UNDEFINED} if the move should be
* inserted at the beginning of the tour
*
* @return the move after which the move is to be inserted, or {@link ITRSPTour#UNDEFINED} if the move should be
* inserted at the beginning of the tour
*/
public int getInsertionPred() {
return mInsertionPred;
}
/**
* Returns the node before which the move is to be inserted, or {@link ITRSPTour#UNDEFINED} if the node should be
* appended
*
* @return the node before which the move is to be inserted, or {@link ITRSPTour#UNDEFINED} if the node should be
* appended
*/
public int getInsertionSucc() {
return mInsertionSucc;
}
/**
* Returns <code>true</code> if a trip to the depot has to be added
*
* @return <code>true</code> if a trip to the depot has to be added
*/
public boolean isDepotTrip() {
return mDepotTrip;
}
/**
* Returns the node before which the trip to the depot has to be added
*
* @return the node before which the trip to the depot has to be added
*/
public int getDepotSucc() {
return mDepotSucc;
}
/**
* Returns the node after which the trip to the depot has to be inserted
*
* @return the node after which the trip to the depot has to be inserted
*/
public int getDepotPred() {
return getDepotSucc() == getNodeId() ? getInsertionPred() : ((TRSPTour) getTour())
.getPred(getDepotSucc());
}
/**
* Find a feasible insertion of a node in a solution.
*
* @param node
* the node which insertion is to be evaluated
* @param costDelegate
* the cost delegate to evaluate the cost of insertion
* @param constraintHandler
* the constraint handler
* @param pruneSearch
* <code>true</code> if the search should be pruned when possible
* @param bestInsertion
* {@code true} if the method should return the best insertion
* @param tour
* the tour in which the node should be inserted
* @return the insertion move
*/
public static InsertionMove findInsertion(int node, TRSPSolution solution,
TRSPCostDelegate costDelegate, TourConstraintHandler constraintHandler,
boolean pruneSearch, boolean bestInsertion) {
InsertionMove best = null;
for (TRSPTour tour : solution) {
if (tour.isVisited(node))
throw new IllegalStateException(String.format("Tour %s already visits node %s",
tour.getTechnicianId(), node));
InsertionMove mve = findInsertion(node, tour, costDelegate, constraintHandler,
pruneSearch, bestInsertion);
if (!bestInsertion && mve != null)
return mve;
if (mve != null && best == null || best.compareTo(mve) < 0)
best = mve;
}
return best;
}
/**
* Find a feasible insertion of a node in a tour.
*
* @param node
* the node which insertion is to be evaluated
* @param tour
* the tour in which the node should be inserted
* @param costDelegate
* the cost delegate to evaluate the cost of insertion
* @param constraintHandler
* the constraint handler
* @param pruneSearch
* <code>true</code> if the search should be pruned when possible
* @param bestInsertion
* {@code true} for the best insertion, {@code false} for the first feasible solution
* @return the insertion move
*/
public static InsertionMove findInsertion(int node, TRSPTour tour,
TRSPCostDelegate costDelegate, TourConstraintHandler constraintHandler,
boolean pruneSearch, boolean bestInsertion) {
if (tour.isVisited(node))
throw new IllegalStateException("The tour " + tour + " already visits the node " + node);
if (!tour.getInstance().isCompatible(tour.getTechnicianId(), node))
// The technician does not have the skills to serve this move
return new InsertionMove(node, tour);
if (tour.length() == 0)
return new InsertionMove(node, tour, 0, ITRSPTour.UNDEFINED, ITRSPTour.UNDEFINED);
if (tour.length() == 1)
return new InsertionMove(node, tour, 0, tour.getFirstNode(), ITRSPTour.UNDEFINED);
InsertionMove best = null;
// ---------------------------------------------------------------
// Look for a feasible insertion point with minimal cost
// ---------------------------------------------------------------
if (tour.isMainDepotVisited()
|| tour.getInstance().hasRequiredTools(tour.getTechnician().getID(), node)
|| tour.getInstance().hasRequiredSpareParts(tour.getTechnician().getID(), node)) {
TRSPTourIterator it = tour.iterator();
// Skip the first node (depot)
int pred = it.next();
while (it.hasNext()) {
int succ = it.next();
InsertionMove move = new InsertionMove(node, tour, 0, pred, succ);
// We assume that the cost evaluation is faster than the feasibility check
double cost = -costDelegate.evaluateMove(move);
if ((best == null || best.getCost() > cost)) {
FeasibilityState feasibility = constraintHandler.checkFeasibility(tour, move,
null);
if (feasibility.isFeasible()) {
if (!bestInsertion)
return move;
best = move;
} else if (pruneSearch
&& !constraintHandler.checkFeasibility(tour, move, feasibility)
.isForwardFeasible()) {
break;
}
}
pred = succ;
}
}
if (best != null)
return best;
if (tour.isMainDepotVisited() || node == tour.getMainDepotId())
// The main depot is already visited, no feasible insertion could be found, or the inserted node was the
// main depot
return new InsertionMove(node, tour);
// ---------------------------------------------------------------
// Look for an insertion point with minimal cost allowing visit to depot
// ---------------------------------------------------------------
if (tour.getInstance().isMainDepotTripAllowed()) {
TRSPTourIterator it = tour.iterator();
// Skip the first node (depot)
int pred = it.next();
boolean pruned = false;
while (it.hasNext() && !pruned) {
int succ = it.next();
TRSPTourIterator depotIt = tour.iterator();
// Skip the first node (depot)
depotIt.next();
int depotSucc = ITRSPTour.UNDEFINED;
boolean abort = false;
while (depotIt.hasNext() && !abort && !pruned) {
depotSucc = depotIt.next();
if (depotSucc == succ) {
depotSucc = node;
// Bugfix: Artificially abort the loop if the depot successor is the inserted node
abort = true;
}
InsertionMove move = new InsertionMove(node, tour, 0, pred, succ, depotSucc);
// We assume that the cost evaluation is faster than the feasibility check
double cost = -costDelegate.evaluateMove(move);
if ((best == null || best.getCost() > cost)) {
FeasibilityState feasibility = constraintHandler.checkFeasibility(tour,
move, null);
if (feasibility.isFeasible()) {
if (!bestInsertion)
return move;
best = move;
} else if (pruneSearch
&& !constraintHandler.checkFeasibility(tour, move, feasibility)
.isForwardFeasible()) {
pruned = true;
break;
}
}
}
pred = succ;
}
}
if (best != null)
return best;
return new InsertionMove(node, tour);
}
/**
* Execute an insertion move
*
* @param move
* the insertion to be executed
* @return <code>true</code> if the insertion was executed successfully, <code>false</code> otherwise
*/
public static boolean executeMove(InsertionMove move) {
TRSPTour tour = (TRSPTour) move.getTour();
if (!move.isFeasible())
// This assignment is not feasible
return false;
if (!move.isDepotTrip()) {
if (tour.insertBefore(move.getInsertionSucc(), move.getNodeId())) {
tour.getSolution().markAsServed(move.getNodeId());
checkAfterMove(move);
return true;
}
} else {
if (tour.insertBefore(move.getInsertionSucc(), move.getNodeId())) {
tour.insertBefore(move.getDepotSucc(), tour.getMainDepotId());
tour.getSolution().markAsServed(move.getNodeId());
checkAfterMove(move);
return true;
}
}
return false;
}
private static void checkAfterMove(InsertionMove move) {
if (move.getTour().getSolution().getVisitingTour(move.getNodeId()) == null)
throw new IllegalStateException("The request has not been inserted properly");
if (NeighborhoodBase.isCheckSolutionAfterMove()) {
String err = TRSPDetailedSolutionChecker.INSTANCE.checkSolution(move.getTour()
.getSolution());
if (!err.isEmpty())
TRSPLogging.getOptimizationLogger().warn(
"InsertionMove.executeMove: Incoherencies after move %s : %s", move, err);
}
}
@Override
public int compareTo(IMove o) {
if (o instanceof InsertionMove) {
int comp = Double.compare(getImprovement(), o.getImprovement());
// Resolve ties using secondary improvement
return comp != 0 ? comp : Double.compare(getSecondaryImprovement(),
((InsertionMove) o).getSecondaryImprovement());
} else
return super.compareTo(o);
}
}