/** * */ package vroom.optimization.online.jmsa.vrp.vrpsd; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import vroom.common.modeling.dataModel.NodeInsertion; import vroom.common.modeling.dataModel.NodeVisit; import vroom.common.modeling.dataModel.Request; import vroom.common.utilities.Constants; import vroom.common.utilities.Stopwatch; import vroom.common.utilities.events.EventHandlingException; import vroom.optimization.online.jmsa.MSABase.MSAProxy; import vroom.optimization.online.jmsa.ScenarioPool; import vroom.optimization.online.jmsa.components.ScenarioOptimizerParam; import vroom.optimization.online.jmsa.events.MSACallbackEvent.EventTypes; import vroom.optimization.online.jmsa.events.ResourceEvent; import vroom.optimization.online.jmsa.events.ResourceHandler; import vroom.optimization.online.jmsa.utils.MSALogging; import vroom.optimization.online.jmsa.vrp.MSAVRPInstance; import vroom.optimization.online.jmsa.vrp.VRPActualRequest; import vroom.optimization.online.jmsa.vrp.VRPRequest; import vroom.optimization.online.jmsa.vrp.VRPScenario; import vroom.optimization.online.jmsa.vrp.VRPScenarioRoute; import vroom.optimization.online.jmsa.vrp.VRPShrunkRequest; /** * <code>VRPSDResourceHandler</code> is a specialization of {@link ResourceHandler} for the VRPSD. * <p> * Creation date: May 10, 2010 - 8:58:15 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 VRPSDResourceHandler extends ResourceHandler<VRPScenario, MSAVRPInstance> { /** * Creates a new <code>VRPSDResourceHandler</code> * * @param parentMSA */ public VRPSDResourceHandler(MSAProxy<VRPScenario, MSAVRPInstance> parentMSA) { super(parentMSA); } /* * (non-Javadoc) * @see vroom.optimization.online.jmsa.events.ResourceHandler#handleEvent(edu .uniandes .copa.jMSA.events.ResourceEvent) */ @Override public boolean handleEvent(ResourceEvent event) throws EventHandlingException { MSAVRPInstance instance = getParentMSAProxy().getInstance(); boolean result; switch (event.getType()) { case START: result = super.handleEvent(event); break; case STOP: result = super.handleEvent(event); break; case REQUEST_ASSIGNED: result = super.handleEvent(event); break; case END_OF_SERVICE: result = super.handleEvent(event); // 0. Check if the request is a depot and the route has been started if (event.getRequest() != null && ((VRPRequest) event.getRequest()).isDepot() && getParentMSAProxy().getInstance().isResourceStarted(event.getResourceId()) && getParentMSAProxy().getInstance().getShrunkRequest(event.getResourceId()) .size() > 1) { result = handleReplenishmentTrip(event); } break; case START_OF_SERVICE: // Update the request demand if (event.getRequest() instanceof VRPSDActualRequest) { ((VRPSDActualRequest) event.getRequest()).setActualDemands((double[]) event .getAdditionalInformation()); } int veh = event.getResourceId(); double excLoad = instance.getCurrentLoad(veh, 0) - instance.getFleet().getVehicle(veh).getCapacity(); // 0. Check if the route capacity is violated if (excLoad > 0) { if (!(event.getRequest() instanceof VRPActualRequest)) { throw new IllegalArgumentException( "Route failure occured at a node that is not a VRP actual"); } // Handle the failure result = handleFailure(event, excLoad, instance.getFleet().getVehicle(veh) .getCapacity()); getParentMSAProxy().callbacks(EventTypes.EVENTS_RESOURCE, new Object[] { event, result, excLoad }); } else { result = super.handleEvent(event); // Reset all scenarios // for (VRPScenario s : getParentMSAProxy().getScenarioPool()) { // s.clear(); // getParentMSAProxy().getComponentManager().getScenarioOptimizer() // .initialize(s, Integer.MAX_VALUE); // } // Force optimization of the pool Stopwatch t = new Stopwatch(); t.start(); int reinitCount = 0; MSALogging.getEventsLogger().debug( "VRPSDResourceHandler.handleEvent: reoptimizing the pool"); for (VRPScenario s : getParentMSAProxy().getScenarioPool()) { boolean reinit = false; // Check if scenario is feasible for (VRPScenarioRoute r : s) { if (r.getLoad() > r.getVehicle().getCapacity()) { reinit = true; break; } } if (reinit) { s.clear(); getParentMSAProxy() .getComponentManager() .getScenarioOptimizer() .initialize(s, new ScenarioOptimizerParam(Integer.MAX_VALUE, 100, false)); reinitCount++; } else { // getParentMSAProxy().getComponentManager().optimize(s, // new ScenarioOptimizerParam(60, 2, false)); } } t.stop(); MSALogging .getEventsLogger() .debug("VRPSDResourceHandler.handleEvent: pool reoptimized in %sms, %s scenarios were reinitialized out of %s", t.readTimeMS(), reinitCount, getParentMSAProxy().getScenarioPool().size()); } break; default: result = super.handleEvent(event); break; } return result; } /** * Handle a replenishment trip * * @param event * @return <code>true</code> */ protected boolean handleReplenishmentTrip(ResourceEvent event) { ScenarioPool<VRPScenario> pool = getParentMSAProxy().getScenarioPool(); LinkedList<VRPScenario> removedScenarios = new LinkedList<VRPScenario>(); // 1 Update the scenario pool for (VRPScenario scenario : pool) { scenario.acquireLock(); VRPScenarioRoute firstRoute = scenario.getRoute(0); // 2 Remove the first route of all scenarios if it is of length // inferior to 2 // or equal to 3 ending with a depot // nodeI.e. <depot,shrunkNode[,depot]> if (firstRoute.length() <= 2 || firstRoute.length() == 3 && firstRoute.getLastNode().isDepot()) { scenario.removeRoute(scenario.getRoute(0)); // 3 Try to repair } else { if (reinsertRequests(scenario, 0, 2, firstRoute.length() - 1)) { // 3.a Repair successful, remove the first route scenario.removeRoute(firstRoute); } else { // 3.b Repair failed, remove the scenario removedScenarios.add(scenario); } } scenario.releaseLock(); } pool.removeScenarios(removedScenarios); return true; } /** * Handle a failure * * @param event * @param excLoad * @param vehicleCap * @return <code>true</code> * @throws EventHandlingException */ protected boolean handleFailure(ResourceEvent event, double excLoad, double vehicleCap) throws EventHandlingException { boolean result; VRPActualRequest req = (VRPActualRequest) event.getRequest(); // 1. Change the given request so that it only has the feasible demand double feasibleDem = req.getDemand() - excLoad; if (Constants.isPositive(feasibleDem)) { if (req instanceof VRPSDActualRequest) { ((VRPSDActualRequest) req).setActualDemands(feasibleDem); } else { req.getParentRequest().setDemands(feasibleDem); } } else { throw new IllegalStateException(String.format( "The feasible demand is negative (%s): failure occured before", feasibleDem)); } MSALogging .getEventsLogger() .info("VRPSDResourceHandler.handleEvent: a route failure has occured, updating the pool (request:%s exceeding capacity:%s)", req, excLoad); result = super.handleEvent(event); // 2. Update all the scenarios by forcing the closure of the current // route, reinsert remaining nodes LinkedList<VRPScenario> removedScenarios = new LinkedList<VRPScenario>(); for (VRPScenario scenario : getParentMSAProxy().getScenarioPool()) { scenario.acquireLock(); // Assume that the first route is the current route VRPScenarioRoute firstRoute = scenario.getRoute(0); // The route should have the sequence: <depot,shrunkNode,...,depot> // where the shrunkNode includes the failure node if (firstRoute.length() > 3) { // There are subsequent node visits // Reinsert them in the other routes if (!reinsertRequests(scenario, 0, 2, firstRoute.length() - 2)) { removedScenarios.add(scenario); } else { firstRoute.calculateLoad(true); } } scenario.releaseLock(); } // 2.b Remove scenarios that could not be repaired getParentMSAProxy().getScenarioPool().removeScenarios(removedScenarios); // 3. Create and insert in the instance and scenarios new requests with // the remaining demand int numAdVisit = (int) Math.ceil(excLoad / vehicleCap); VRPActualRequest[] newReqs = new VRPActualRequest[numAdVisit]; for (int v = 0; v < numAdVisit; v++) { double dem = Math.min(excLoad, vehicleCap); excLoad -= dem; Request request = new Request((v + 1) * 10000 + req.getID(), req.getNode()); request.setDemands(dem); newReqs[v] = new VRPActualRequest(NodeVisit.createNodeVisits(request)[0]); getParentMSAProxy().getInstance().acquireLock(); getParentMSAProxy().getInstance().requestReleased(newReqs[v]); getParentMSAProxy().getInstance().releaseLock(); } getParentMSAProxy().getComponentManager().insertRequest(newReqs); // TODO 4. Raise callback event? return result; } /** * Check and intent to restore capacity feasibility of the first route of each scenario * * @param event * @return <code>true</code> */ protected boolean restoreFeasibility(ResourceEvent event) { LinkedList<VRPScenario> removedScenarios = new LinkedList<VRPScenario>(); for (VRPScenario scenario : getParentMSAProxy().getScenarioPool()) { VRPScenarioRoute firstRoute = scenario.getRoute(0); // 0. Force load recalculation firstRoute.calculateLoad(true); // 1. Check route feasibility double cap = getParentMSAProxy().getInstance().getFleet() .getVehicle(event.getResourceId()).getCapacity(); if (firstRoute.getLoad() > cap) { // 2. Find the failure double load = 0; int failure = 0; Iterator<VRPRequest> it = firstRoute.iterator(); while (it.hasNext() && load <= cap) { failure++; load += it.next().getDemand(); } // 3. Try to reinsert subsequent nodes if (!reinsertRequests(scenario, 0, failure, firstRoute.length() - 2)) { // 3.a Failed: remove the scenario removedScenarios.add(scenario); } } } getParentMSAProxy().getScenarioPool().removeScenarios(removedScenarios); return true; } /** * Request extraction and re-insertion. * <p> * This method will remove the nodes in the range <code>min...max</code> (inclusive) from the given * <code>route</code> and attempt to reinsert them in the other routes of the scenario. * </p> * <p> * Note that this method will only reinsert instances of {@link VRPActualRequest} and ignore depots * </p> * * @param scenario * the scenario to modify * @param route * the index of the route from which nodes will be extracted * @param start * the index of the first removed node * @param end * the index of the last removed node * @return <code>true</code> if all nodes were extracted and reinserted, <code>false</code> otherwise */ protected boolean reinsertRequests(VRPScenario scenario, int route, int start, int end) { // Extract the remaining nodes Collection<VRPRequest> requests = scenario.getRoute(route).extractNodes(start, end); boolean b = true; for (VRPRequest movedReq : requests) { // Cannot reinsert a shrunk request if (movedReq instanceof VRPShrunkRequest) { return false; } // Only reinsert actual requests if (movedReq instanceof VRPActualRequest && !movedReq.isDepot()) { // Get the best insertion in all subsequent routes NodeInsertion ins = scenario.getBestInsertion(movedReq, route); if (ins != null) { // insert the node in the best position b &= ((VRPScenarioRoute) ins.getRoute()).insertNode(ins, movedReq); } else { // No feasible re-insertion, remove the scenario return false; } } } return b; } }