/** * */ package vroom.common.modeling.util; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import vroom.common.modeling.dataModel.INodeVisit; import vroom.common.modeling.dataModel.IRoute; import vroom.common.modeling.dataModel.IVRPInstance; import vroom.common.modeling.dataModel.IVRPSolution; import vroom.common.utilities.ILockable; import vroom.common.utilities.Utilities; /** * <code>SolutionChecker</code> is a class which function is to check if a solution routes internal costs and loads are * consistent with the visited nodes * <p> * Creation date: Jun 29, 2010 - 4:16:32 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 SolutionChecker { /** A tolerance for the zero value */ public static double ZERO_TOLERANCE = 10E-6; /** * Checking of a route * * @param route * the route to be checked * @param autoRepair * <code>true</code> if the route should be automatically repaired * @param startAtDeopt * <code>true</code> if the routes should start at the depot * @param endAtDepot * <code>true</code> if the routes should end at the depot * @return a string describing the incoherences in the route internal cost and load, or <code>null</code> if the * internal data is coherent. * @see IRoute#calculateCost(boolean) * @see IRoute#calculateLoad(boolean) */ public static <N extends INodeVisit, R extends IRoute<N>> String checkRoute(R route, boolean autoRepair, boolean startAtDepot, boolean endAtDepot) { StringBuilder sb = new StringBuilder(); boolean error = false; if (route.length() <= 0) { sb.append("Route is empty"); error = true; } else { if (startAtDepot && !route.getNodeAt(0).isDepot()) { sb.append("Route does not start at the depot"); error = true; } if (endAtDepot && !route.getNodeAt(route.length() - 1).isDepot()) { sb.append("Route does not end at the depot"); error = true; } } if (error) { sb.append(" ("); sb.append(route.toString()); sb.append(")"); } double cost = calculateCost(route); double[] loads = calculateLoads(route); if (Math.abs(cost - route.getCost()) > ZERO_TOLERANCE) { sb.append(String.format("Route %s: Inconsistent cost (expected:%.3f, stored:%.3f))", route.hashCode(), cost, route.getCost())); if (autoRepair) { route.updateCost(-route.getCost() + cost); } } if (route instanceof ILockable) { ((ILockable) route).acquireLock(); } for (int p = 0; p < loads.length; p++) { if (Math.abs(loads[p] - route.getLoad(p)) > ZERO_TOLERANCE) { if (sb.length() > 0) { sb.append(", "); } sb.append(String.format( "Route %s: Inconsistent load (prod:%s, expected:%.3f, stored:%.3f))", route.hashCode(), p, loads[p], route.getLoad(p))); if (autoRepair) { route.updateLoad(p, -route.getLoad(p) + loads[p]); } } } if (route instanceof ILockable) { ((ILockable) route).releaseLock(); } return sb.length() > 0 ? sb.toString() : null; } /** * Calculates the load of the route * * @param route * @return an array containing the aggregated load for each product */ public static double[] calculateLoads(IRoute<?> route) { double[] loads = new double[route.getVehicle().getCompartmentCount()]; if (route instanceof ILockable) { ((ILockable) route).acquireLock(); } Iterator<INodeVisit> it = Utilities.castIterator(route.iterator()); if (it.hasNext()) { INodeVisit pred = it.next(); for (int p = 0; p < loads.length; p++) { loads[p] += pred.getDemand(p); } INodeVisit succ = null; while (it.hasNext()) { succ = it.next(); for (int p = 0; p < loads.length; p++) { loads[p] += succ.getDemand(p); } pred = succ; } } if (route instanceof ILockable) { ((ILockable) route).releaseLock(); } return loads; } /** * Calculate the cost of a route * * @param route * the route which cost has to be calculated * @return the total cost of the given route */ public static double calculateCost(IRoute<?> route) { double cost = 0; if (route instanceof ILockable) { ((ILockable) route).acquireLock(); } Iterator<INodeVisit> it = Utilities.castIterator(route.iterator()); if (it.hasNext()) { INodeVisit pred = it.next(); INodeVisit succ = null; while (it.hasNext()) { succ = it.next(); cost += route.getParentSolution().getParentInstance().getCostDelegate() .getCost(pred, succ, route.getVehicle()); pred = succ; } } if (route instanceof ILockable) { ((ILockable) route).releaseLock(); } return cost; } /** * Calculate the cost of a solution * * @param solution * the solution which cost has to be calculated * @return the total cost of the given solution */ public static double calculateCost(IVRPSolution<?> solution) { double cost = 0; for (IRoute<?> route : solution) { cost += calculateCost(route); } return cost; } /** * Check a solution for missing customers * * @param solution * the solution to be checked * @return a collection containing the {@link INodeVisit} present in the instance but not served by any route * @see IVRPInstance#getNodeVisits() */ public static Collection<INodeVisit> checkUnservedCustomers(IVRPSolution<?> solution) { HashMap<Integer, INodeVisit> missingCustomers = new HashMap<Integer, INodeVisit>(); for (INodeVisit visit : solution.getParentInstance().getNodeVisits()) { missingCustomers.put(visit.getID(), visit); } for (IRoute<?> route : solution) { for (INodeVisit n : route) { missingCustomers.remove(n.getID()); } } return missingCustomers.values(); } /** * Checking of a route * * @param solution * the solution to be checked * @param autorepair * <code>true</code> if the solution should be automatically repaired * @param startAtDeopt * <code>true</code> if the routes should start at the depot * @param endAtDepot * <code>true</code> if the routes should end at the depot * @return a string describing the incoherences in the solution's routes internal cost and load, or * <code>null</code> if the internal data is coherent. * @see #checkRoute(IRoute, boolean, boolean, boolean) */ public static String checkSolution(IVRPSolution<?> solution, boolean autorepair, boolean startAtDeopt, boolean endAtDepot) { StringBuilder sb = new StringBuilder(); for (IRoute<?> r : solution) { String checkR = checkRoute(r, autorepair, startAtDeopt, endAtDepot); if (checkR != null) { if (sb.length() > 0) { sb.append(","); } sb.append('['); sb.append(checkR); sb.append(']'); } } Collection<INodeVisit> unservedVisits = checkUnservedCustomers(solution); if (!unservedVisits.isEmpty()) { sb.append("Unserved customers:{"); for (INodeVisit n : unservedVisits) { sb.append(n); sb.append(','); } sb.setCharAt(sb.length() - 1, '}'); } return sb.length() > 0 ? sb.toString() : null; } public static void removeEmptyRoutes(IVRPSolution<?> solution) { Iterator<?> routes = solution.iterator(); while (routes.hasNext()) { IRoute<?> r = (IRoute<?>) routes.next(); if (r.length() == 0 || (r.length() <= 2 && r.getFirstNode().isDepot() && r.getLastNode().isDepot())) { routes.remove(); } } } }