/** * */ package vroom.trsp.optimization.constraints; import java.util.List; import java.util.ListIterator; import vroom.common.utilities.optimization.IConstraint; import vroom.common.utilities.optimization.IMove; import vroom.trsp.datamodel.ITRSPTour; import vroom.trsp.datamodel.ITourIterator; import vroom.trsp.datamodel.TRSPDistanceMatrix; import vroom.trsp.datamodel.TRSPTour; import vroom.trsp.datamodel.TRSPTour.TRSPTourIterator; import vroom.trsp.datamodel.Technician; import vroom.trsp.optimization.InsertionMove; import vroom.trsp.optimization.localSearch.TRSPShift.TRSPShiftMove; import vroom.trsp.optimization.localSearch.TRSPTwoOpt.TRSPTwoOptMove; import vroom.trsp.util.TRSPLogging; /** * <code>TWConstraint</code> is an implementation of {@link IConstraint} that checks if moves are feasible in terms of * time window constraints. * <p> * Creation date: Mar 28, 2011 - 10:49:11 AM * * @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 TWConstraint extends TourConstraintBase { @Override protected boolean isTwoOptFeasible(ITRSPTour itour, TRSPTwoOptMove move) { if (!TRSPTour.class.isAssignableFrom(itour.getClass())) { TRSPLogging.getOptimizationLogger().warn( String.format("TWConstraint.isTwoOptFeasible: unsupported tour class (%s)", itour.getClass())); return true; } TRSPTour tour = (TRSPTour) itour; final int i = move.getFirst(); final int j = tour.getSucc(i); final int m = move.getSecond(); final int n = tour.getSucc(m); // The arrival time at the head of the first arc double arrival = tour.getEarliestArrivalTime(i); // Check the reversed subtour // We start with (i,m) then iterates to (j+1,j) int pred = i; TRSPTourIterator it = tour.iterator(m); while (pred != j && it.hasPrevious()) { int node = it.previous(); // Update the arrival time arrival = tour.getTimeWindow(pred).getEarliestStartOfService(arrival) + tour.getServiceTime(pred) + tour.getTravelTime(pred, node); if (!tour.getTimeWindow(node).isFeasible(arrival)) // Found a violated request return false; pred = node; } // Check (j,n) // Earliest arrival time at node n arrival = tour.getTimeWindow(j).getEarliestStartOfService(arrival) + tour.getServiceTime(j) + tour.getTravelTime(j, n); // compare it against the latest feasible time if (arrival > tour.getLatestFeasibleArrivalTime(n)) // The arrival time will not allow to visit the remaining nodes return false; else // The TW constraint is satisfied return true; } /** * Check if a {@linkplain TRSPShiftMove shift move} is feasible for the given <code>solution</code> * * @param solution * the base solution * @param move * the {@link TRSPShiftMove} to be tested * @return <code>true</code> if applying <code>move</code> to the given <code>solution</code> will result in a * feasible mSolution */ @Override protected boolean isShiftFeasible(ITRSPTour itour, TRSPShiftMove move) { if (!TRSPTour.class.isAssignableFrom(itour.getClass())) { TRSPLogging.getOptimizationLogger().warn( String.format("TWConstraint.isShiftFeasible: unsupported tour class (%s)", itour.getClass())); return true; } TRSPTour tour = (TRSPTour) itour; if (!tour.isAutoUpdated()) throw new IllegalStateException( "The tour must have its autoupdate flag set to true for proper working"); // Build the changed sequence List<Integer> changedSequence = move.getChangedSequence(); // The changed sequence predecessor (unchanged) ListIterator<Integer> it = changedSequence.listIterator(); // The last node that is unchanged int lastUnchanged = it.next(); // Departure time at the last unchanged node double newArrivalTime = lastUnchanged != ITRSPTour.UNDEFINED ? tour .getEarliestDepartureTime(lastUnchanged) : tour.getEarliestStartTime(); // If the move is backward we always allow skip boolean allowSkip = false; int pred = lastUnchanged; while (it.hasNext()) { int n = it.next(); if (pred != ITRSPTour.UNDEFINED) // Add the service time and travel time from predecessor newArrivalTime = tour.getTimeWindow(pred).getEarliestStartOfService(newArrivalTime) + tour.getServiceTime(pred) + tour.getTravelTime(pred, n); if (!tour.getTimeWindow(n).isFeasible(newArrivalTime)) return false; if (allowSkip && newArrivalTime <= tour.getEarliestDepartureTime(n)) // We can skip the evaluation of the rest of the tour as we are sure that we will arrive earlier at each // node return true; // We allow skip after we checked the moved node allowSkip |= n == move.getNode(); pred = n; } return true; } /** * Check if a {@linkplain InsertionMove shift move} is feasible for the given <code>solution</code> * * @param solution * the base solution * @param move * the {@link InsertionMove} to be tested * @return <code>true</code> if applying <code>move</code> to the given <code>solution</code> will result in a * feasible mSolution */ @Override public boolean isInsFeasible(ITRSPTour tour, InsertionMove move) { return checkInsFeasibility(tour, move) == 1; } /** * Check if a {@linkplain InsertionMove shift move} is feasible for the given <code>solution</code> * * @param solution * the base solution * @param move * the {@link InsertionMove} to be tested * @return 1 if move is feasible, -1 if there is no arc with successor, -2 if there is no arc with predecessor, -3 * if the node tw is violated, -4 if the tw of a successor is violated, and -5 if the tw of an intermediary * node is violated */ protected int checkInsFeasibility(ITRSPTour itour, InsertionMove move) { if (!TRSPTour.class.isAssignableFrom(itour.getClass())) { TRSPLogging.getOptimizationLogger().warn( String.format("TWConstraint.isShiftFeasible: unsupported tour class (%s)", itour.getClass())); return 1; } TRSPTour tour = (TRSPTour) itour; final int node = move.getNodeId(); // Check if arc exists if ((move.getInsertionSucc() != ITRSPTour.UNDEFINED && !tour.getInstance().isArcTWFeasible( move.getInsertionPred(), node))// || (move.getInsertionPred() != ITRSPTour.UNDEFINED && !tour.getInstance() .isArcTWFeasible(node, move.getInsertionSucc()))) return -1; final int pred = move.getInsertionPred(); double arrivalTime = 0; if (pred == ITRSPTour.UNDEFINED) { // Earliest arrival time at the inserted request (equal to the start of shift of the technician) arrivalTime = tour.getEarliestStartTime(); if (move.isDepotTrip() && move.getDepotSucc() == node) arrivalTime += tour.getTravelTime(tour.getMainDepotId(), node) + tour.getServiceTime(tour.getMainDepotId()); } else { // Check if arc exists if (!tour.getInstance().isArcTWFeasible(pred, node)) return -2; // Earliest arrival time at the inserted request depending on the previous request and possible depot trip if (move.isDepotTrip()) { if (move.getDepotSucc() == node) { // The depot is visited immediately before arrivalTime = tour.getEarliestDepartureTime(pred) + tour.getTravelTime(pred, tour.getMainDepotId()) + tour.getServiceTime(tour.getMainDepotId()) + tour.getTravelTime(tour.getMainDepotId(), node); } else { final int depotSucc = move.getDepotSucc(); final int depotPred = tour.getPred(move.getDepotSucc()); // The depot is visited earlier in the tour // Waiting times method // ----------------------------------- // Extra time required to visit depot (we assume there is no waiting at the depot) final double depotTrip = tour.getTravelTime(depotPred, tour.getMainDepotId()) + tour.getServiceTime(tour.getMainDepotId()) + tour.getTravelTime(tour.getMainDepotId(), depotSucc); // Check that the depot insertion is feasible if (tour.getEarliestDepartureTime(depotPred) + depotTrip > tour .getLatestFeasibleArrivalTime(depotSucc)) return -5; // Delta in the arrival time at the depot successor final double delta = depotTrip - tour.getTravelTime(depotPred, depotSucc); double predArrivalTime = tour.getEarliestArrivalTime(pred); // New arrival time at the insertion predecessor predArrivalTime = Math.max(predArrivalTime, predArrivalTime + delta - tour.getWaitingTime(depotPred, pred)); // Earliest arrival time at the inserted node arrivalTime = tour.getTimeWindow(pred).getEarliestStartOfService( predArrivalTime) + tour.getServiceTime(pred) + tour.getTravelTime(pred, node); } } else { // The depot is not visited arrivalTime = tour.getEarliestDepartureTime(pred) + tour.getTravelTime(pred, node); } } // Check if the earliest arrival time at the request is compatible with its time window if (!tour.getTimeWindow(node).isFeasible(arrivalTime)) return -3; else if (move.getInsertionSucc() == ITRSPTour.UNDEFINED) // The insertion consists in appending the node, nothing else to check return 1; // Estimate the earliest arrival time to the insertionSucc request arrivalTime = tour.getTimeWindow(node).getEarliestStartOfService(arrivalTime) + tour.getServiceTime(node) + tour.getTravelTime(node, move.getInsertionSucc()); // Check if the earliest arrival is compatible with the latest feasible arrival time if (arrivalTime > tour.getLatestFeasibleArrivalTime(move.getInsertionSucc())) return -4; else return 1; } @Override protected FeasibilityState checkFeasibility(ITRSPTour itour) { if (itour.length() == 0) return null; else if (TRSPTour.class.isAssignableFrom(itour.getClass())) { // Fast check for instances of TRSPTour using stored info TRSPTour tour = (TRSPTour) itour; for (int n : tour) { if (!tour.getTimeWindow(n).isFeasible(tour.getEarliestArrivalTime(n))) { return new FeasibilityState(n, "%s:TW violated (twe:%s ea:%.1f)", n, tour .getTimeWindow(n).endAsDouble(), tour.getEarliestArrivalTime(n)); } } return new FeasibilityState(); } else { TRSPDistanceMatrix cd = itour.getInstance().getCostDelegate(); Technician tech = itour.getInstance().getFleet().getVehicle(itour.getTechnicianId()); double time = itour.getInstance().getTimeWindow(itour.getFirstNode()).startAsDouble(); ITourIterator it = itour.iterator(); int pred = it.next(); while (it.hasNext()) { int n = it.next(); time = itour.getInstance().getTimeWindow(pred).getEarliestStartOfService(time); time += itour.getInstance().getServiceTime(pred); time += cd.getTravelTime(pred, n, tech); if (!itour.getInstance().getTimeWindow(n).isFeasible(time)) { return new FeasibilityState(n, "%s:TW violated (twe:%s ea:%.1f)", n, itour .getInstance().getTimeWindow(n).endAsDouble(), time); } pred = n; } return new FeasibilityState(); } } @Override public int checkFeasibility(ITRSPTour tour, IMove move) { if (InsertionMove.class.isAssignableFrom(move.getClass())) { InsertionMove mve = (InsertionMove) move; int feasibility = checkInsFeasibility(tour, mve); int fwdFeas; if (mve.isDepotTrip()) { // No deduction can be done fwdFeas = 2; } else { // The move is not fwd feasible if: // -2 there is no arc with predecessor // -3 the node tw is violated fwdFeas = (feasibility != -3 && feasibility != -2) ? 2 : 0; } return feasibility == 1 ? 1 + fwdFeas : fwdFeas; } else { return isFeasible(tour, move) ? 3 : 2; } } }