/*
*
*/
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.ListIterator;
import java.util.Map;
import vroom.common.heuristics.ConstraintHandler;
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.VRPRequest;
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;
/**
* The Class <code>VRPSDDetourRegret</code> is an implementation of the Regret algorithm that considers a subset of
* candidate requests and estimate their regret value by evaluating the detour cost and load improvement on each
* scenario.
* <p>
* <p>
* Creation date: Dec 3, 2010 - 10:19:55 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 VRPSDDetourRegret extends VRPSDSimpleRegret {
public static int sSampleSize = Integer.MAX_VALUE;
public static double sCostEpsilon = 0.01;
private final VariableNeighborhoodSearch<VRPScenario> mVNS;
private final VRPSDConsensus mConsensus;
public VRPSDDetourRegret(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();
CandidateList candidateList = selectKLowerInsCost();
ArrayList<VRPActualRequest> candidates = new ArrayList<VRPActualRequest>(candidateList.bestEvals.size());
// 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;
}
/**
* Selects a subset of {@link #sSampleSize} requests that have the lower insertion cost
*
* @return the corresponding candidate list
*/
protected CandidateList selectKLowerInsCost() {
CandidateList candidateList = new CandidateList();
INodeVisit depot = getInstance().getDepotsVisits().iterator().next();
new HashMap<Integer, Integer>(getInstance().getPendingRequests().size());
double avgScenCost = 0;
int count = 0;
for (VRPScenario s : getComponentManager().getParentMSAProxy().getScenarioPool()) {
avgScenCost += s.getCost();
count++;
for (VRPActualRequest n : getInstance().getPendingRequests()) {
VRPScenarioRoute r = s.getRoute(0);
candidateList.updateEval(n.getID(), -evaluateInsertionCost(depot, r, n));
}
}
if (count > 0) {
avgScenCost /= count;
}
// Filter out costly requests
ListIterator<CandidateList.Eval> it = (ListIterator<vroom.optimization.online.jmsa.vrp.vrpsd.VRPSDDetourRegret.CandidateList.Eval>) candidateList.bestEvals
.iterator();
while (it.hasNext()) {
if (it.next().eval > avgScenCost * sCostEpsilon) {
it.remove();
}
}
return candidateList;
}
protected double evaluateInsertionCost(INodeVisit depot, VRPScenarioRoute route, VRPRequest req) {
double insCost = 0;
if (route.length() > 2) {
INodeVisit next = route.getNodeAt(2);
INodeVisit cur = getInstance().getShrunkRequest(0);
if (next.getID() == req.getID()) {
// Already the first request
insCost = 0;
} else {
// Insertion cost
insCost = getInstance().getCost(cur, req) + getInstance().getCost(req, next)
- getInstance().getCost(cur, next);
}
}
// A route failure will occur in this scenario
if (!route.canAccommodateRequest(req)) {
// Add the cost of a replenishment trip
insCost += 2 * getInstance().getCost(depot, req);
}
return insCost;
}
/**
* Selects a subset of {@link #sSampleSize} candidate requests that appear first with the highest frequency
*
* @return the corresponding candidate list
*/
protected CandidateList selectKFirst() {
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());
VRPScenarioRoute route = scenario.getRoute(0);
double insRelCost = evaluateInsertionCost(depot, route, req) / scenario.getCost();
double loadRatio = (route.contains(req) ? 1 : 1 + req.getDemand() / route.getLoad());
return currentValue + insRelCost / loadRatio;
}
@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();
}
}
public void remove(int reqID) {
if (evals.containsKey(reqID)) {
bestEvals.remove(evals.get(reqID));
}
}
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();
}
}
}