/* * */ package vroom.optimization.online.jmsa.vrp.vrpsd; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import vroom.common.heuristics.ConstraintHandler; import vroom.common.heuristics.vns.VNSParameters; import vroom.common.heuristics.vns.VariableNeighborhoodSearch; import vroom.common.heuristics.vns.VariableNeighborhoodSearch.VNSVariant; import vroom.common.heuristics.vrp.SwapNeighborhood; import vroom.common.heuristics.vrp.TwoOptNeighborhood; import vroom.common.heuristics.vrp.constraints.CapacityConstraint; import vroom.common.modeling.dataModel.INodeVisit; import vroom.common.utilities.Utilities; import vroom.common.utilities.optimization.INeighborhood; import vroom.common.utilities.optimization.OptimizationSense; import vroom.optimization.online.jmsa.IActualRequest; import vroom.optimization.online.jmsa.IDistinguishedSolution; import vroom.optimization.online.jmsa.components.ComponentManager; import vroom.optimization.online.jmsa.components.ISolutionBuilderParam; import vroom.optimization.online.jmsa.vrp.MSAVRPInstance; import vroom.optimization.online.jmsa.vrp.VRPActualRequest; import vroom.optimization.online.jmsa.vrp.VRPScenario; import vroom.optimization.online.jmsa.vrp.VRPScenarioRoute; import vroom.optimization.online.jmsa.vrp.VRPShrunkRequest; import vroom.optimization.online.jmsa.vrp.optimization.MSAFixedNodeConstraint; /** * <code>VRPSDSampledRegret</code> is an implementation of the Regret algorithm that only considers a subset of * candidate requests and estimate their regret value by performing a fast local search on each scenario. * <p> * Creation date: Aug 25, 2010 - 1:44:46 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 VRPSDSampledRegret extends VRPSDSimpleRegret { public static int sSampleSize = 5; private final VariableNeighborhoodSearch<VRPScenario> mVNS; private final VRPSDConsensus mConsensus; public VRPSDSampledRegret(ComponentManager<VRPScenario, ?> componentManager) { super(componentManager); mConsensus = new VRPSDConsensus(componentManager); List<INeighborhood<VRPScenario, ?>> neighborhoods = new LinkedList<INeighborhood<VRPScenario, ?>>(); ConstraintHandler<VRPScenario> ctr = new ConstraintHandler<VRPScenario>(); ctr.addConstraint(new CapacityConstraint<VRPScenario>()); ctr.addConstraint(new MSAFixedNodeConstraint<VRPScenario>()); neighborhoods.add(new SwapNeighborhood<VRPScenario>(ctr)); neighborhoods.add(new TwoOptNeighborhood<VRPScenario>(ctr)); mVNS = VariableNeighborhoodSearch.newVNS(VNSVariant.VND, OptimizationSense.MINIMIZATION, null, getMSAProxy() .getDecisionRandomStream(), neighborhoods); } @Override public IDistinguishedSolution buildDistinguishedPlan(ISolutionBuilderParam param) { // Special case when vehicle is at the depot, use consensus if (getInstance().getShrunkRequest(0).isDepot()) { return mConsensus.buildDistinguishedPlan(param); } else { return super.buildDistinguishedPlan(param); } } @Override protected Collection<? extends IActualRequest> selectCandidateRequests() { CandidateList candidateList = selectCandidateRequestsKNearest(); ArrayList<VRPActualRequest> candidates = new ArrayList<VRPActualRequest>(sSampleSize); // Retreive the best candidates for (CandidateList.Eval e : candidateList.bestEvals) { candidates.add(getInstance().getNodeVisit(e.id)); } // Add the depot // if (candidates.isEmpty()) { candidates.add(new VRPActualRequest(getInstance().getDepotsVisits().iterator().next())); // } return candidates; } protected CandidateList selectCandidateRequestsKNearest() { CandidateList candidateList = new CandidateList(); INodeVisit depot = getInstance().getDepotsVisits().iterator().next(); for (VRPActualRequest n : getInstance().getPendingRequests()) { for (VRPScenario s : getComponentManager().getParentMSAProxy().getScenarioPool()) { double insCost = 0; VRPScenarioRoute r = s.getRoute(0); if (r.length() > 2) { INodeVisit next = r.getNodeAt(2); INodeVisit cur = getInstance().getShrunkRequest(0); if (next.getID() == n.getID()) { insCost = 0; } else { insCost = getInstance().getCost(cur, n) + getInstance().getCost(n, next) - getInstance().getCost(cur, next); } } if (!r.canAccommodateRequest(n)) { insCost += getInstance().getCost(depot, n); } candidateList.updateEval(n.getID(), -insCost); } } return candidateList; } /** * Selects a subset of {@link #sSampleSize} candidate requests that are the most frequently present in the first * {@link #sSampleSize} nodes of any scenario. */ protected CandidateList selectCandidateRequestsKFirsts() { CandidateList candidateList = new CandidateList(); // Check whether the vehicle is currently at the depot boolean atDepot = getInstance().getShrunkRequest(0).getNodeVisit().isDepot(); double resCap = getInstance().getFleet().getVehicle().getCapacity() - getInstance().getShrunkRequest(0).getDemand(); // Iterate over all scenarios for (VRPScenario s : getComponentManager().getParentMSAProxy().getScenarioPool()) { // Number of candidates examined int count = 0; // Current route int route = 0; // Iterate over all routes until sSampleSize nodes were examined while (route < s.getRouteCount()) { count = 0; s.acquireLock(); Iterator<VRPActualRequest> it = Utilities.castIterator(s.getOrderedActualRequests(route).iterator()); s.releaseLock(); // Safe-remove the first node (depot) if (!it.next().isDepot()) { s.acquireLock(); it = Utilities.castIterator(s.getOrderedActualRequests(route).iterator()); s.releaseLock(); } // Iterate over the route nodes while (it.hasNext() && count < sSampleSize) { VRPActualRequest req = it.next(); // Do not consider depot if already at the depot // or requests violating the remaining capacity if (req.isDepot() && atDepot || req.getDemand() > resCap) { break; } candidateList.updateEval(req.getID(), 1); count++; } route++; } } return candidateList; } @Override protected IDistinguishedSolution defaultDecision(ISolutionBuilderParam param) { return mConsensus.buildDistinguishedPlan(param); } /* * (non-Javadoc) * @see vroom.optimization.online.jmsa.components.RegretSolutionBuilderBase# evaluateRegret(vroom.optimization.online.jmsa.IActualRequest, * vroom.optimization.online.jmsa.IScenario, double) */ @Override protected double evaluateRegret(IActualRequest request, VRPScenario scenario, double currentValue) { // Ignore empty scenarios or invalid requests if (scenario.getRouteCount() <= 0 || request == null) { return currentValue; } // If the request is already the first, keep the value unchanged INodeVisit first = scenario.getRoute(0).getNodeAt(2); VRPActualRequest req = (VRPActualRequest) request; if (first == null || first.getID() == request.getID() || first.isDepot() && req.isDepot() || !(scenario.getRoute(0).getNodeAt(1) instanceof VRPShrunkRequest)) { return currentValue; } // Depot VRPActualRequest depot = new VRPActualRequest(getInstance().getDepotsVisits().iterator().next()); // The actual request with sampled demands of the scenario req = req.isDepot() ? req : scenario.getActualRequest(request.getID()); // Clone the scenario VRPScenario tmp = scenario.clone(); VRPScenarioRoute firstRoute = tmp.getRoute(0); double insCost; double cap = firstRoute.getVehicle().getCapacity() - tmp.getRoute(0).getLoad(); int len = firstRoute.length(); int last = len - 1; double delta = 0; boolean prevFixState = req.isFixed(); if (!req.isDepot()) { // Insertion cost insCost = -scenario.getCost(); // Remove the request from its current route for (VRPScenarioRoute r : tmp) { if (r.remove(req)) { break; } } // Insert the request at after the shrunk node firstRoute.insertNode(2, req); req.fix(); // Fix the node for the VNS insCost += tmp.getCost(); // Repair solution // Try to remove nodes while (cap < 0 && last > 4) { last--; cap += firstRoute.getNodeAt(last).getDemand(); } } else { // Special case for replenishment trip // remove the shrunk node from the route VRPShrunkRequest shrunk = (VRPShrunkRequest) firstRoute.extractNode(1); delta = 2 * getInstance().getCost(depot, shrunk); insCost = 0; } if (cap >= 0) { // Feasibility can be repaired if (last < len - 1) { // Remove nodes to ensure feasibility VRPScenarioRoute newRoute = firstRoute.extractSubroute(last, len - 2); newRoute.insertNode(0, depot); newRoute.appendNode(depot); // Add a new route tmp.addRoute(newRoute); } // Reoptimize the resulting scenario tmp = mVNS.localSearch(getInstance(), tmp, new VNSParameters(200, 100, true, 50, 10, null, null)); delta += tmp.getCost() - scenario.getCost(); } else { // Route will necessarily fail // Estimate the failure cost as the insertion cost of req in first // position // plus twice the distance from the depot to req delta = insCost + 2 * getInstance().getCost(depot, req); } if (prevFixState) { req.fix(); } else { req.free(); } return currentValue + delta; } @Override protected MSAVRPInstance getInstance() { return (MSAVRPInstance) getComponentManager().getParentMSAProxy().getInstance(); } public static class CandidateList { final Map<Integer, Eval> evals = new HashMap<Integer, Eval>(); final LinkedList<Eval> bestEvals = new LinkedList<Eval>(); public void updateEval(int reqID, double deltaEval) { // Update the request evaluation Eval e; if (!evals.containsKey(reqID)) { e = new Eval(reqID, deltaEval); evals.put(reqID, e); } else { e = evals.get(reqID); e.eval += deltaEval; } if (!bestEvals.contains(e)) { bestEvals.add(e); Collections.sort(bestEvals); } // Remove exceeding candidates while (bestEvals.size() > sSampleSize) { bestEvals.removeLast(); } } private static class Eval implements Comparable<Eval> { int id; double eval; public Eval(int id, double eval) { super(); this.id = id; this.eval = eval; } @Override public int hashCode() { return id; } @Override public int compareTo(Eval o) { return Double.compare(this.eval, o.eval); } @Override public String toString() { return String.format("%s:%s", id, eval); } } @Override public String toString() { return bestEvals.toString(); } } }