/** * */ package vrp2013.algorithms; import ilog.concert.IloColumnArray; import ilog.concert.IloException; import ilog.concert.IloNumVar; import ilog.concert.IloNumVarType; import ilog.concert.IloObjectiveSense; import ilog.concert.IloRange; import ilog.cplex.IloCplex; import ilog.cplex.IloCplex.UnknownObjectException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import vroom.common.modeling.dataModel.INodeVisit; import vroom.common.modeling.dataModel.IRoute; import vroom.common.modeling.dataModel.IVRPInstance; import vroom.common.modeling.dataModel.IVRPRequest; import vroom.common.modeling.dataModel.ImmutableRoute; import vroom.common.modeling.dataModel.RouteBase; import vroom.common.modeling.util.HashRoutePool; import vroom.common.modeling.util.IRoutePool; import vroom.common.modeling.util.ISolutionFactory; import vroom.common.utilities.Utilities; import vroom.common.utilities.Utilities.AbsolutePrecision; import vrp2013.util.VRPLogging; import vrp2013.util.VRPSolution; /** * The class <code>HeuristicConcentration</code> is an implementation of an heuristic concentration that builds a * solution to a VRP problem using a pool of routes and solving a set covering problem with the CPLEX solver. * <p> * Creation date: May 4, 2013 - 11:06:37 AM * * @author Victor Pillac, <a href="http://www.nicta.com.au">National ICT Australia</a>, <a * href="http://www.victorpillac.com">www.victorpillac.com</a> * @version 1.0 */ public class HeuristicConcentration implements IVRPOptimizationAlgorithm { private final IVRPInstance mInstance; private List<ImmutableRoute<INodeVisit>> mRoutes; private HashSet<Integer> mIncumbentRoutes; private IloNumVar[] mVars; private final IloRange[] mCoverCtrs; private IloCplex mCplex; private VRPSolution mSolution; private IRoutePool<INodeVisit> mRoutePool; private final ISolutionFactory mSolutionFactory; /** * Return the solution found by the heuristic concentration * * @return the solution found by the heuristic concentration */ @Override public VRPSolution getBestSolution() { return mSolution; } /** * Creates a new <code>HeuristicConcentration</code> * * @param instance */ public HeuristicConcentration(IVRPInstance instance, ISolutionFactory soluitonFactory) { mInstance = instance; mSolutionFactory = soluitonFactory; mCoverCtrs = new IloRange[Utilities.getMaxId(instance.getRequests()) + 1]; } /** * Initialize the heuristic concentration with the given {@code routePool} and {@code incumbent} * * @param routePool * the pool of routes defining the Set Covering model * @param incumbent * an incumbent (starting solution) for the MIP solver (can be {@code null}) * @throws IloException */ public void initialize(IRoutePool<INodeVisit> routePool, VRPSolution incumbent) throws IloException { // Protect the list of routes from any direct modification mRoutePool = routePool; mRoutes = new ArrayList<>(routePool.getAllRoutes()); // Store the hash code of the routes from the incumbent solution mIncumbentRoutes = new HashSet<>(); if (incumbent != null) { if (routePool instanceof HashRoutePool) { for (RouteBase r : incumbent) { mIncumbentRoutes.add(((HashRoutePool<?>) routePool).getHasher().hash(r)); } } else { for (RouteBase r : incumbent) { ImmutableRoute<INodeVisit> ir = new ImmutableRoute<>(r, r.hashCode()); mRoutes.add(ir); mIncumbentRoutes.add(ir.hashCode()); } } } mRoutes = Collections.unmodifiableList(mRoutes); mSolution = incumbent; if (!mRoutes.isEmpty()) buildModel(); } @Override public VRPSolution call() throws IloException { if (mCplex != null) { mCplex.solve(); buildSolution(); } return getBestSolution(); } /** * Build the model * * @throws IloException */ private void buildModel() throws IloException { mCplex = new IloCplex(); mCplex.setOut(null); createAndAddCoverCtrs(); createAndAddVars(); } /** * Create and add to the model the decision variables defining which routes are selected * * @throws IloException */ private void createAndAddVars() throws IloException { // Variable definition // lower bound double xlb = 0; // upper bound double xub = 1; // type IloNumVarType xt = IloNumVarType.Bool; // name String xnames[] = new String[mRoutes.size()]; // coefficient in the objective function double xobj[] = new double[mRoutes.size()]; // starting value from the incumbent double xstart[] = new double[mRoutes.size()]; // coefficients of the variables in the cover constraints double[][] matrixCoef = new double[mCoverCtrs.length][mRoutes.size()]; // Definition of each variable for (int i = 0; i < mRoutes.size(); i++) { xobj[i] = mRoutes.get(i).getCost(); xnames[i] = "x_" + i; // Add a 1 coefficient in the covering constraint of each visited request for (INodeVisit n : mRoutes.get(i)) { if (n.getParentRequest() != null) { matrixCoef[n.getParentRequest().getID()][i] = 1; } } // Set the start value of the variable if the route is present in the incumbent if (mIncumbentRoutes.contains(mRoutes.get(i).hashCode())) { xstart[i] = 1; } } // Definition of the columns objective mCplex.addObjective(IloObjectiveSense.Minimize); IloColumnArray cols = mCplex.columnArray(mCplex.getObjective(), xobj); // Definition of the columns coefficients in the cover constraints for (IVRPRequest r : mInstance.getRequests()) { cols = cols.and(mCplex.columnArray(mCoverCtrs[r.getID()], matrixCoef[r.getID()])); } // Add the variables to the model mVars = mCplex.numVarArray(cols, xlb, xub, xt, xnames); // Sets the incumbent mCplex.addMIPStart(mVars, xstart); } /** * Create and add to the model the covering constraints that ensure that all requests are served * * @throws IloException */ private void createAndAddCoverCtrs() throws IloException { for (IVRPRequest r : mInstance.getRequests()) { mCoverCtrs[r.getID()] = mCplex.addGe(mCplex.constant(0), 1, "cover_" + r.getID()); } } /** * Builds a solution to the original VRP problem from the set covering solution * * @throws UnknownObjectException * @throws IloException */ private void buildSolution() throws UnknownObjectException, IloException { double[] vals = mCplex.getValues(mVars); List<ImmutableRoute<INodeVisit>> selectedRoutes = new LinkedList<>(); for (int i = 0; i < vals.length; i++) { if (AbsolutePrecision.isStrictlyPositive(vals[i])) selectedRoutes.add(mRoutes.get(i)); } // Check that each request is visited exactly once Map<IRoute<?>, HashSet<INodeVisit>> removedNodes = new HashMap<>(); IRoute<?>[] visitingRoutes = new IRoute<?>[mCoverCtrs.length]; IRoute<?> prevRoute = null; for (IRoute<?> route : selectedRoutes) { removedNodes.put(route, new HashSet<INodeVisit>()); int i = 0; for (INodeVisit n : route) { if (n.getParentRequest() != null) { if ((prevRoute = visitingRoutes[n.getParentRequest().getID()]) != null) { VRPLogging .getOptLogger() .info("HeuristicConcentration.buildSolution: request %3s is served more than once, fixing the solution", n.getParentRequest().getID()); // The request is covered by another route // Compare the removal profit of both routes if (remProfit(route, i, mInstance) > remProfit(prevRoute, prevRoute.getNodePosition(n), mInstance)) { removedNodes.get(route).add(n); } else { removedNodes.get(prevRoute).add(n); visitingRoutes[n.getParentRequest().getID()] = route; } } else { visitingRoutes[n.getParentRequest().getID()] = route; } } i++; } } mSolution = (VRPSolution) mSolutionFactory.newSolution(getInstance()); for (ImmutableRoute<INodeVisit> r : selectedRoutes) { RouteBase route = (RouteBase) mSolutionFactory.newRoute(mSolution, r.getVehicle()); for (INodeVisit n : r) { if (!removedNodes.get(r).contains(n)) route.appendNode(n); } mSolution.addRoute(route); } } /** * Evaluate the decrease in cost following the removal of the node at position {@code pos} in {@code route} * * @param route * @param pos * @param instance * @return the decrease in cost following the removal of the node at position {@code pos} in {@code route} */ private double remProfit(IRoute<?> route, int pos, IVRPInstance instance) { double profit = 0; INodeVisit pred = route.getNodeAt(pos - 1); INodeVisit node = route.getNodeAt(pos); INodeVisit succ = route.getNodeAt(pos + 1); profit += instance.getCost(pred, node, route.getVehicle()); profit += instance.getCost(node, succ, route.getVehicle()); profit -= instance.getCost(pred, succ, route.getVehicle()); return profit; } /** * Exports the model to a file * * @param file * @throws IloException */ public void exportModel(String file) throws IloException { if (mCplex != null) mCplex.exportModel(file); } @Override public IRoutePool<INodeVisit> getRoutePool() { return mRoutePool; } @Override public IVRPInstance getInstance() { return mInstance; } @Override public int getIterations() { return 1; } @Override public ISolutionFactory getSolutionFactory() { return mSolutionFactory; } @Override public void dispose() { mRoutePool.dispose(); } }