/**
*
*/
package vroom.optimization.online.jmsa.vrp.vrpsd;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import vroom.common.heuristics.ConstraintHandler;
import vroom.common.heuristics.vrp.TwoOptNeighborhood;
import vroom.common.heuristics.vrp.constraints.CapacityConstraint;
import vroom.common.modeling.dataModel.INodeVisit;
import vroom.common.modeling.dataModel.NodeInsertion;
import vroom.common.modeling.util.SolutionChecker;
import vroom.common.utilities.optimization.IParameters.LSStrategy;
import vroom.common.utilities.optimization.SimpleParameters;
import vroom.optimization.online.jmsa.IActualRequest;
import vroom.optimization.online.jmsa.IScenario;
import vroom.optimization.online.jmsa.components.ComponentManager;
import vroom.optimization.online.jmsa.utils.MSALogging;
import vroom.optimization.online.jmsa.vrp.MSAVRPInstance;
import vroom.optimization.online.jmsa.vrp.MSAVRPSolutionFactory;
import vroom.optimization.online.jmsa.vrp.VRPActualRequest;
import vroom.optimization.online.jmsa.vrp.VRPParameterKeys;
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.VRPScenarioRoute.State;
import vroom.optimization.online.jmsa.vrp.VRPScenarioUpdaterBase;
import vroom.optimization.online.jmsa.vrp.VRPShrunkRequest;
import vroom.optimization.online.jmsa.vrp.optimization.MSAFixedNodeConstraint;
/**
* <code>VRPSDScenarioUpdater</code> is a specialization of {@link VRPScenarioUpdaterBase} for the VRPSD problem.
* <p>
* Creation date: May 7, 2010 - 11:46: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 class VRPSDScenarioUpdater extends VRPScenarioUpdaterBase {
private final MSAVRPSolutionFactory mSolutionFactory;
private final TwoOptNeighborhood<VRPScenario> mTwoOptNeigh;
/**
* Creates a new <code>VRPSDScenarioUpdater</code>
*
* @param componentManager
*/
public VRPSDScenarioUpdater(ComponentManager<?, ?> componentManager) {
super(componentManager);
mSolutionFactory = getComponentManager().getParentMSAProxy().getParameters()
.newInstance(VRPParameterKeys.SCENARIO_FACTORY_CLASS);
ConstraintHandler<VRPScenario> constraintHandler = new ConstraintHandler<VRPScenario>();
constraintHandler.addConstraint(new MSAFixedNodeConstraint<VRPScenario>());
constraintHandler.addConstraint(new CapacityConstraint<VRPScenario>());
mTwoOptNeigh = new TwoOptNeighborhood<VRPScenario>(constraintHandler);
}
@Override
public boolean startOfServiceUpdate(IScenario scenario, int resourceId, IActualRequest request) {
VRPScenario scen = (VRPScenario) scenario;
boolean b = repairCapacity(scen);
SolutionChecker.checkSolution(scen, true, true, true);
return b;
}
@Override
public boolean endOfServiceUpdate(IScenario scenario, int resourceId, IActualRequest servedRequest) {
boolean r = false;
VRPScenario s = (VRPScenario) scenario;
s.acquireLock();
// Check that the last fixed request is the good one
if (s.getLastFixedRequest(resourceId) == null
// Newly generated scenarios are assumed to be coherent with the system
// state
|| s.getLastFixedRequest(resourceId).getID() == servedRequest.getID()) {
// Mark the last request as served
s.markLastVisitAsServed(resourceId);
r = true;
}
s.releaseLock();
return r;
}
@Override
public boolean startServicingUpdate(IScenario scenario, int resourceId) {
VRPScenario scen = (VRPScenario) scenario;
scen.acquireLock();
if (!(scen.getRoute(resourceId).getNodeAt(1) instanceof VRPShrunkRequest)) {
repairSolution(scen);
}
boolean b = false;
if (scen.getRoute(resourceId).getFirstNode().isDepot()) {
// Fix the first request (depot)
// scen.fixFirstActualRequest(resourceId);
// Mark the first request as served as the vehicle left the
// depot
scen.markLastVisitAsServed(resourceId);
scen.getRoute(resourceId).setCurrentState(State.STARTED);
b = true;
}
scenario.releaseLock();
return b;
}
@Override
public boolean stopServicingUpdate(IScenario scenario, int resourceId) {
if (scenario instanceof VRPScenario) {
scenario.acquireLock();
boolean b = ((VRPScenario) scenario).getRoute(resourceId).length() == 0
|| ((VRPScenario) scenario).getRoute(resourceId).length() == 1
&& ((VRPScenario) scenario).getRoute(resourceId).getFirstNode().isDepot();
scenario.releaseLock();
return b;
} else {
return false;
}
}
@Override
public boolean enforceDecision(IScenario scenario, IActualRequest request, int resourceId) {
boolean b = false;
scenario.acquireLock();
VRPScenario scen = (VRPScenario) scenario;
VRPScenarioRoute firstRoute = scen.getRoute(0);
VRPActualRequest req = (VRPActualRequest) request;
VRPShrunkRequest shrunk = getInstance().getShrunkRequest(0);
if (!(firstRoute.getNodeAt(1) instanceof VRPShrunkRequest)) {
if (!repairSolution(scen)) {
MSALogging
.getComponentsLogger()
.warn("VRPSDScenarioUpdater.enforceDecision: Incoherent scenario detected while enforcing request %s: %s",
request, scen);
return false;
} else {
firstRoute = scen.getRoute(0);
}
}
if (req.isDepot()) {
// Remove first route when request is a depot
// reinsert remaining requests in other routes or remove scenario
List<VRPActualRequest> actualRequests = firstRoute.getOrderedActualRequests();
for (VRPActualRequest r : actualRequests) {
if (!r.isDepot()) {
int bestRoute = -1;
NodeInsertion ins = null, bestIns = null;
// Find the best insertion
for (int rte = 1; rte < scen.getRouteCount(); rte++) {
ins = scen.getRoute(rte).getBestRequestInsertion(r);
if (ins != null && (bestIns == null || ins.getCost() < bestIns.getCost())) {
bestIns = ins;
bestRoute = rte;
}
}
if (bestIns != null) {
scen.getRoute(bestRoute).insertNode(bestIns, r);
} else {
// The request cannot be reinserted, discard this
// scenario
return false;
}
}
}
scen.removeRoute(firstRoute);
if (scen.getRouteCount() > 0 && !scen.getRoute(0).containsShrunkNode()) {
b = scen.getRoute(0).insertNode(1, scen.getParentInstance().getShrunkRequest(resourceId));
}
} else if (firstRoute.getFirstActualRequest() == null) {
// There is no first actual request (empty route)
if (request == null) {
return true;
} else {
return false;
}
}
// The first request is already the good one
else if (firstRoute.getFirstActualRequest().getID() == request.getID() && firstRoute.containsShrunkNode()) {
if (!firstRoute.isLastFixedRequestServed()) {
firstRoute.markLastRequestAsServed();
}
b = req.getID() == scen.fixFirstActualRequest(resourceId).getID();
}
// The last served request is a depot
// Find if the request is the first or last of any route
else if (shrunk.size() > 1
&& shrunk.getShrunkNodes().get(getInstance().getShrunkRequest(0).size() - 2).isDepot()) {
Iterator<VRPScenarioRoute> it = scen.iterator();
boolean found = false;
VRPScenarioRoute reqRoute = null;
VRPScenarioRoute shrunkRoute = null;
int i = 0;
int routeIdx = -1;
while (it.hasNext() && (!found || shrunkRoute == null)) {
VRPScenarioRoute route = it.next();
if (route.containsShrunkNode()) {
shrunkRoute = route;
}
if (!found && route.length() > 2 && (!route.containsShrunkNode() || route.length() > 3)) {
int first = route.containsShrunkNode() ? 2 : 1;
VRPRequest firstReq = route.getNodeAt(first);
VRPRequest lastReq = route.getNodeAt(route.length() - 2);
found = firstReq.getID() == request.getID();
if (found) {
reqRoute = route;
}
if (!found && lastReq.getID() == request.getID()) {
reqRoute = route;
reqRoute.reverseRoute();
found = true;
routeIdx = i;
}
// Temporarily remove the route from the scenario
if (found && routeIdx != 0) {
it.remove();
}
}
i++;
}
if (found) {
if (routeIdx != 0) {
scen.addRoute(reqRoute, 0);
firstRoute = reqRoute;
}
if (shrunkRoute != reqRoute || !(shrunkRoute.getNodeAt(1) instanceof VRPShrunkRequest)) {
shrunkRoute.remove(shrunk);
if (shrunkRoute.length() < 3) {
scen.removeRoute(shrunkRoute);
}
firstRoute.insertNode(1, shrunk);
// Repair the scenario if route capacity is exceeded
b = repairCapacity(scen);
}
if (!firstRoute.isLastFixedRequestServed()) {
firstRoute.markLastRequestAsServed();
}
b &= req.getID() == scen.fixFirstActualRequest(resourceId).getID();
} else {
b = false;
}
}
// Attempt to repair the scenario
else {
VRPScenarioRoute reqRoute = null;
int reqIndex = -1;
// Find the route in which the request appears
for (VRPScenarioRoute route : scen) {
int idx = route.getNodePosition(req);
if (idx >= 0) {
reqIndex = idx;
reqRoute = route;
break;
}
}
if (reqRoute != null) {
reqRoute.extractNode(reqIndex);
firstRoute.insertNode(2, req);
if (!firstRoute.isLastFixedRequestServed()) {
firstRoute.markLastRequestAsServed();
}
b = req.getID() == scen.fixFirstActualRequest(resourceId).getID();
if (b) {
// Fast reopt
VRPScenario newScen = mTwoOptNeigh.localSearch(getInstance(), scen, new SimpleParameters(
LSStrategy.DET_BEST_IMPROVEMENT, 200, 200, getMSAProxy().getOptimizationRandomStream()));
b = repairSolution(newScen);
if (b) {
scen.clear();
scen.importScenario(newScen);
firstRoute = scen.getRoute(0);
}
}
} else {
b = false;
}
}
SolutionChecker.checkSolution(scen, true, true, true);
scenario.releaseLock();
return b;
// Breakpoint condition
// !b
// || scen.getRoute(0) != firstRoute
// || firstRoute.getFirstActualRequest().getID() == req.getID()
// || firstRoute.isLastFixedRequestServed()
// || !firstRoute.containsShrunkNode()
// || firstRoute.getLastFixedRequest() == null
// || firstRoute.getLastFixedRequest().getID() != req.getID()
}
/**
* Repair a scenario by finding all routes exceeding vehicle capacity and reinserting nodes in other existing or new
* routes
*
* @param scenario
* the scenario to be repaired
* @return <code>true</code> if the scenario was successfully repaired
*/
public boolean repairCapacity(VRPScenario scenario) {
// Find routes that violate capacity and remove exceeding nodes
LinkedList<VRPRequest> pendingRequests = new LinkedList<VRPRequest>();
for (VRPScenarioRoute r : scenario) {
r.calculateLoad(true);
for (int p = 0; p < r.getVehicle().getCompartmentCount(); p++) {
int end = r.length() - 2;
int start = r.length();
double load = r.getLoad(p);
ListIterator<VRPRequest> it = r.iterator();
while (load > r.getVehicle().getCapacity(p) && it.hasPrevious()) {
load -= it.previous().getDemand(p);
start--;
}
if (start <= end) {
pendingRequests.addAll(r.extractNodes(start, end));
}
}
}
if (pendingRequests.isEmpty()) {
return true;
}
Iterator<VRPRequest> it = pendingRequests.iterator();
// Attempt to reinsert requests in existing routes of the scenario
while (it.hasNext()) {
if (insertRequest(scenario, it.next())) {
it.remove();
}
}
boolean error = false;
while (!pendingRequests.isEmpty() && !error) {
it = pendingRequests.iterator();
// Create a new route
VRPScenarioRoute r = mSolutionFactory.newRoute(scenario, getInstance().getFleet().getVehicle());
// Add remaining requests to a new route
error = true;
while (it.hasNext()) {
VRPRequest req = it.next();
if (r.canAccommodateRequest(req)) {
r.bestInsertion(req);
it.remove();
error = false; // Prevent infinite loop
}
}
// Add route to scenario
scenario.addRoute(r);
}
return pendingRequests.isEmpty();
}
/**
* Reorganize the scenario so that the route containing the shrunk node is in first position, and the shrunk node
* appears in first position in the route itself.
* <p>
* Additionally this method tries to improve consensus by reordering routes
* </p>
*
* @param scenario
* the scenario to be repaired
* @return <code>true</code> if the scenario is coherent
*/
public static boolean repairSolution(VRPScenario scenario) {
if (scenario == null) {
return false;
}
scenario.acquireLock();
VRPShrunkRequest shReq = scenario.getParentInstance().getShrunkRequest(0);
if (scenario.getRouteCount() == 0 || shReq.getParentRequest() == null) {
return true;
}
boolean b = true;
boolean shrunkNodeFound = false;
List<VRPScenarioRoute> removedRoutes = new LinkedList<VRPScenarioRoute>();
for (int r = 0; r < scenario.getRouteCount(); r++) {
VRPScenarioRoute route = scenario.getRoute(r);
if (route.length() <= 2) {
removedRoutes.add(route);
} else if (route.containsShrunkNode()) {
if (shrunkNodeFound) {
// The mSolution already contains a shrunk node
return false;
} else {
shrunkNodeFound = true;
}
if (r != 0) {
// Put the route in first position
scenario.removeRoute(route);
scenario.addRoute(route, 0);
}
b &= route.ensureRouteSequence();
if (!b) {
return false;
}
}
}
for (VRPScenarioRoute route : removedRoutes) {
scenario.removeRoute(route);
}
// Try to improve consensus by re-ordering route when the vehicle is at
// a depot
if (b && (shReq.isDepot() || shReq.getParentRequest() == null)) {
List<VRPScenarioRoute> routes = new LinkedList<VRPScenarioRoute>();
for (VRPScenarioRoute route : scenario) {
// Empty route
if (route.containsShrunkNode() && route.length() > 3 || !route.containsShrunkNode()
&& route.length() > 2) {
if (route.containsShrunkNode()) {
route.extractNode(1);
}
reorderRoute(route);
routes.add(route);
}
}
Collections.sort(routes, new Comparator<VRPScenarioRoute>() {
@Override
public int compare(VRPScenarioRoute o1, VRPScenarioRoute o2) {
MSAVRPInstance ins = o1.getParentSolution().getParentInstance();
ins.getDepotsVisits().iterator().next();
// return o1.getNodeAt(1).getID() - o2.getNodeAt(1).getID();
// return (int) Math.round((ins.getCost(depot,
// o2.getNodeAt(1)) - ins.getCost(
// depot, o1.getNodeAt(1))) * 10000);
return (int) Math.round((o2.getLoad() - o1.getLoad()) * 1000);
}
});
if (!routes.isEmpty()) {
routes.get(0).insertNode(1, shReq);
}
scenario.clear();
for (VRPScenarioRoute route : routes) {
scenario.addRoute(route);
}
} else {
}
scenario.releaseLock();
return b && shrunkNodeFound;
}
/**
* Reorder the route: will ensure that the first node has a lower id than the last.
*
* @param route
*/
public static void reorderRoute(VRPScenarioRoute route) {
int first = route.containsShrunkNode() ? 2 : 1;
if (route.length() > 2 + first) {
// if (route.getNodeAt(first).getID() >
// route.getNodeAt(route.length() - 2).getID()) {
MSAVRPInstance instance = route.getParentSolution().getParentInstance();
INodeVisit dep = instance.getDepotsVisits().iterator().next();
if (instance.getCost(dep, route.getNodeAt(route.length() - 2)) > instance.getCost(dep,
route.getNodeAt(first))) {
if (route.containsShrunkNode()) {
route.reverseSubRoute(2, route.length() - 2);
} else {
route.reverseRoute();
}
}
}
}
}