package vroom.trsp.datamodel; import java.math.RoundingMode; import vroom.common.modeling.dataModel.attributes.ILocation; import vroom.common.utilities.IntegerSet; import vroom.common.utilities.Utilities; import vroom.common.utilities.dataModel.ISolutionChecker; import vroom.trsp.datamodel.TRSPTour.TRSPTourIterator; /** * The Interface <code>TRSPSolutionCheckerBase</code> defines classes used to check the feasibility of a * {@link TRSPSolution} and {@link TRSPTour} * <p> * Creation date: Jun 15, 2011 - 9:28:35 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 abstract class TRSPSolutionCheckerBase implements ISolutionChecker<TRSPSolution> { private final boolean mCheckUnserved; /** * Creates a new <code>TRSPSolutionCheckerBase</code> */ public TRSPSolutionCheckerBase() { this(false); } /** * Creates a new <code>TRSPSolutionCheckerBase</code> * * @param checkUnserved * <code>true</code> if unserved reauests should be checked, <code>false</code> otherwise */ public TRSPSolutionCheckerBase(boolean checkUnserved) { super(); mCheckUnserved = checkUnserved; } /** * Check the feasibility of a solution * * @param solution * the solution to be checked * @return a string describing the infeasibility of <code>solution</code>, or an empty string if the solution is * feasible */ @Override public String checkSolution(TRSPSolution solution) { if (solution == null) return "null solution"; StringBuilder err = new StringBuilder(); // --------------------------------------------------------------- // Check for unserved requests IntegerSet unservedRequests = new IntegerSet(solution.getInstance().getMaxId()); for (int r : solution.getInstance().getReleasedRequests()) if (solution.getInstance().getSimulator() == null || !solution.getInstance().getSimulator().isRejected(r)) unservedRequests.add(r); for (ITRSPTour tour : solution) for (int node : tour) { unservedRequests.remove(node); } if (mCheckUnserved && !unservedRequests.isEmpty() && !solution.getInstance().isUnservedReqAllowed()) err.append(String.format("Unserved requests: %s", Utilities.toShortString(unservedRequests))); if (!unservedRequests.equals(solution.getUnservedRequests())) append(err, "Wrong unserved requests (is:%s expected:%s)", Utilities.toShortString(solution.getUnservedRequests()), Utilities.toShortString(unservedRequests)); // --------------------------------------------------------------- // --------------------------------------------------------------- // Check for doubly served requests IntegerSet servedRequests = new IntegerSet(solution.getInstance().getMaxId()); for (ITRSPTour tour : solution) for (int node : tour) { if (solution.getInstance().isRequest(node) && !servedRequests.add(node)) { err.append(String.format("Request %s is served twice (second tour: %s)", node, tour.getTechnicianId())); } } // --------------------------------------------------------------- // Check each tour individually // --------------------------------------------------------------- for (TRSPTour tour : solution) { String e = checkTour(tour); if (!e.isEmpty()) { append(err, "[%s: %s]", tour.getTechnician().getID(), e); } } // --------------------------------------------------------------- // Check for the served nodes // --------------------------------------------------------------- if (solution.getInstance().isDynamic()) { for (TRSPTour tour : solution) { String e = checkServedAssignedRequests(tour); if (!e.isEmpty()) { append(err, "[%s: %s]", tour.getTechnician().getID(), e); } } } // --------------------------------------------------------------- return err.toString(); } /** * Check the feasibility of a tour * * @param tour * the tour to be checked * @return a string describing the infeasibility of <code>tour</code>, or an empty string if the tour is feasible */ public abstract String checkTour(ITRSPTour tour); /** * Append a message to the string builder, inserting commas when required * * @param err * the error string builder * @param format * the format string of the message * @param args * the argument of the format string */ protected static void append(StringBuilder err, String format, Object... args) { if (err.length() > 0) err.append(","); err.append(String.format(format, args)); } /** * Returns the total distance traveled in the given {@code solution} * * @param solution * the solution to be evaluated * @param precision * the precision for possible rounding, a negative value means no rounding * @return the total distance traveled */ public static double evaluateTotalEuclidianDistance(TRSPSolution solution, int precision) { double dist = 0; for (TRSPTour t : solution) { dist += evaluateTotalEuclidianDistance(t, precision); } return dist; } /** * Returns the total distance traveled in the given {@code tour} * * @param tour * the tour to be evaluated * @param precision * the precision for possible rounding, a negative value means no rounding * @return the total distance traveled */ public static double evaluateTotalEuclidianDistance(TRSPTour tour, int precision) { if (tour.length() < 3) return 0; double totalDist = 0; TRSPTourIterator it = tour.iterator(); ILocation pred = tour.getInstance().getNode(it.next()).getLocation(); while (it.hasNext()) { ILocation node = tour.getInstance().getNode(it.next()).getLocation(); double dist = Math.sqrt(Math.pow(pred.getX() - node.getX(), 2) + Math.pow(pred.getY() - node.getY(), 2)); if (precision >= 0) dist = Utilities.Math.round(dist, precision, RoundingMode.FLOOR); totalDist += dist; pred = node; } return totalDist; } /** * Returns the total duration of the given {@code tour} * * @param tour * the tour to be evaluated * @param precision * the precision for possible rounding, a negative value means no rounding * @return the total duration of {@code tour} */ public static double evaluateTotalDuration(TRSPTour tour, int precision) { if (tour.length() < 3) return 0; double time = 0; double waiting = 0; TRSPTourIterator it = tour.iterator(); int pred = it.next(); ILocation predNode = tour.getInstance().getNode(pred).getLocation(); while (it.hasNext()) { int n = it.next(); ILocation node = tour.getInstance().getNode(n).getLocation(); // Travel time double travel = Math.sqrt(Math.pow(predNode.getX() - node.getX(), 2) + Math.pow(predNode.getY() - node.getY(), 2)) / tour.getTechnician().getSpeed(); if (precision >= 0) travel = Utilities.Math.round(travel, precision, RoundingMode.FLOOR); // -- waiting += tour.getTimeWindow(pred).getWaiting(time); time = tour.getTimeWindow(pred).getEarliestStartOfService(time) + tour.getServiceTime(pred) + travel; pred = n; predNode = node; } waiting += tour.getTimeWindow(pred).getWaiting(time); time = tour.getTimeWindow(pred).getEarliestStartOfService(time) + tour.getServiceTime(pred); double fwdSlack = evaluateFwdSlackTime(tour, tour.getFirstNode(), tour.getLastNode()); return time - (tour.getEarliestStartTime() + Math.min(waiting, fwdSlack)); } /** * Evaluates the forward slack time between node {@code i} and {@code j} in {@code tour} * * @param tour * @param i * @param j * @return the forward slack time between node {@code i} and {@code j} in {@code tour} */ public static double evaluateFwdSlackTime(TRSPTour tour, int i, int j) { if (j == i) return Double.POSITIVE_INFINITY; double Di = tour.getEarliestDepartureTime(i); TRSPTourIterator it = tour.iterator(tour.getSucc(i)); return evaluateFwdSlackTime(tour, it, Di, i, j); } /** * Evaluates the forward slack time between node {@code i} and {@code j} in {@code tour} * * @param tour * @param earlyDepi * the earliest departure time at node {@code i} * @param i * @param j * @return the forward slack time between node {@code i} and {@code j} in {@code tour} */ public static double evaluateFwdSlackTime(ITRSPTour tour, double earlyDepi, int i, int j) { if (j == i) return Double.POSITIVE_INFINITY; ITourIterator it = tour.iterator(); int k = it.next(); while (k != i) k = it.next(); return evaluateFwdSlackTime(tour, it, earlyDepi, i, j); } private static double evaluateFwdSlackTime(ITRSPTour tour, ITourIterator it, double earlyDep, int i, int j) { if (j == i) return Double.POSITIVE_INFINITY; double Fij = Double.POSITIVE_INFINITY; double sumT = 0, sumS = 0; double Di = earlyDep; int p = i; int q; do { q = it.next(); sumT += tour .getInstance() .getCostDelegate() .getTravelTime(p, q, tour.getInstance().getFleet().getVehicle(tour.getTechnicianId())); double bq = tour.getInstance().getTimeWindow(q).endAsDouble(); // Earliest arrival at q: departure from i + sum of travel times up to q + sum of service times up to p double eaq = Di + sumT + sumS; double Fiq = bq - eaq; if (Fiq < Fij) Fij = Fiq; // Sum of service times up to q sumS += tour.getInstance().getServiceTime(q); p = q; } while (q != j); return Fij; } /** * Check the feasibility of a tour regarding requests that have already been served or assigned * * @param tour * the tour to be checked * @return a string describing the infeasibility of <code>tour</code>, or an empty string if the tour is feasible */ public static String checkServedAssignedRequests(ITRSPTour tour) { if (tour.getInstance().getSimulator().isStaticSetting()) return ""; StringBuilder sb = new StringBuilder(); ITourIterator tIt = tour.iterator(); TRSPTourIterator refIt = tour.getInstance().getSimulator().getCurrentSolution() .getTour(tour.getTechnicianId()).iterator(); while (refIt.hasNext()) { int refn = refIt.next(); if (!tIt.hasNext()) { append(sb, "%s:%s@%s", tour.getTechnicianId(), "null", refn); break; } int tn = tIt.next(); if (tn != refn) append(sb, "%s:%s@%s", tour.getTechnicianId(), tn, refn); } return sb.toString(); } /** * Check the feasibility of a solution regarding requests that have already been served or assigned * * @param solution * the solution to be checked * @return a string describing the infeasibility of <code>solution</code>, or an empty string if the solution is * feasible */ public static String checkServedAssignedRequests(TRSPSolution solution) { StringBuilder sb = new StringBuilder(); for (TRSPTour t : solution) { String e = checkServedAssignedRequests(t); if (e.length() > 0) append(sb, e); } return sb.toString(); } }