/**
*
*/
package vroom.trsp.optimization.rch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import umontreal.iro.lecuyer.rng.RandomPermutation;
import vroom.common.modeling.dataModel.Depot;
import vroom.trsp.datamodel.ITRSPTour;
import vroom.trsp.datamodel.TRSPInstance;
import vroom.trsp.datamodel.TRSPSimpleTour;
import vroom.trsp.datamodel.TRSPSolution;
import vroom.trsp.datamodel.TRSPTour;
import vroom.trsp.datamodel.Technician;
import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate;
import vroom.trsp.optimization.InsertionMove;
import vroom.trsp.optimization.constraints.TourConstraintHandler;
import vroom.trsp.util.TRSPGlobalParameters;
import vroom.trsp.util.TRSPLogging;
/**
* <code>RndNearestNeighbor</code> is an implementation of the random nearest neighbor heuristic for the TRSP.
* <p>
* Complexity of the {@link #generateGiantTours()} is in O(Kn²), where K is the number of technicians and n the number
* of requests.
* </p>
* <p>
* Creation date: Sep 22, 2011 - 1:45:11 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 RndNearestNeighbor extends TRSPRndConstructiveHeuristic {
/** A matrix containing the neighbors of a node sorted in increasing distance order */
private NeighborList[] mNeighbors;
/**
* Creates a new <code>RndNearestNeighbor</code>
*
* @param instance
* @param parameters
* @param constraintHandler
* @param costDelegate
* @param kMax
* the K<sub>max</sub> value
* @param giantTours
* {@code true} if the heuristic will only be used to generate giant tours, {@code false} for feasible
* tours only
*/
public RndNearestNeighbor(TRSPInstance instance, TRSPGlobalParameters parameters,
TourConstraintHandler constraintHandler, TRSPCostDelegate costDelegate, int kMax) {
super(instance, parameters, constraintHandler, costDelegate, kMax);
}
@Override
protected void initialize(List<ObservableNode> requestsId) {
super.initialize(requestsId);
// Add the main depot
if (!isGiantTours() && !getParameters().isCVRPTW())
requestsId.add(getObsNode(getInstance().getMainDepot().getID()));
mNeighbors = new NeighborList[getInstance().getMaxId()];
initNeighborMatrix(requestsId);
}
/**
* Initialize the matrix containing the nearest neighbors of all nodes
*
* @param requestsId
*/
private void initNeighborMatrix(List<ObservableNode> requestsId) {
for (Depot dep : getInstance().getDepots()) {
mNeighbors[dep.getID()] = initNeighborsList(dep.getID(), requestsId);
}
for (ObservableNode req : requestsId) {
mNeighbors[req.getId()] = initNeighborsList(req.getId(), requestsId);
}
}
/**
* Returns the sorted list of neighbors of a node
*
* @param node
* @param requestsId
* @return the sorted list of neighbors of a node
*/
private NeighborList initNeighborsList(Integer node, List<ObservableNode> requestsId) {
// Initialize the list of possible successors
ArrayList<Neighbor> neigh = new ArrayList<Neighbor>(getInstance().getRequestCount());
for (ObservableNode succ : requestsId) {
neigh.add(new Neighbor(getObsNode(node), succ, getInstance().getCostDelegate()
.getDistance(node, succ.getId())));
}
// Sort the list of successors
Collections.sort(neigh);
// Store the list of successors
return new NeighborList(getInstance(), neigh, false);
}
/**
* Returns an array containing the neighbors, sorted according to their distance to the specified node
*
* @param node
* @return an array containing the neighbors, sorted according to their distance to the specified node
*/
protected NeighborList getNeighbors(int node) {
return mNeighbors[node];
}
@Override
protected Collection<ITRSPTour> generateGiantTour(Technician technician) {
int tech = technician.getID();
// Initialize the set of unserved requests
// boolean[] isUnserved = new boolean[getInstance().getMaxId()];
int unserved = getCompatibleRequests(tech).length;
resetNodes(tech);
Collection<ITRSPTour> tours = new LinkedList<ITRSPTour>();
boolean abort = false;
while (unserved > 0 && !abort) {
abort = true;
// Initialize the tour
List<Integer> tour = initTour(tech);
// Until all compatible requests have been visited
while (unserved > 0) {
// ObservableNode node = selectNextNode(tour, tech, isUnserved, unserved);
ObservableNode node = selectNextNode(tour, tech, unserved);
if (node == null)
break;
// if (node.isServed())
// throw new IllegalStateException("Attempting to add a node that is already present");
insertNextNode(tour, node, tech);
node.markAsServed();
// uns.remove(node.getId());
unserved--;
abort = false;
}
tours.add(new TRSPSimpleTour(tech, getInstance(), tour));
}
if (unserved > 0)
TRSPLogging.getOptimizationLogger().warn(
"RndNearestNeighbor.generateGiantTour: %s request(s) left unserved", unserved);
return tours;
}
@Override
protected Collection<ITRSPTour> generateFeasibleTours() {
// Reset all nodes
resetNodes();
int unserved = getInstance().getRequestCount();
// Initialization of the solution
// ----------------
TRSPSolution solution = new TRSPSolution(getInstance(), getCostDelegate());
TRSPTour[] clone = new TRSPTour[getInstance().getFleet().size()];
boolean[] mainDepotVisited = new boolean[getInstance().getFleet().size()];
Integer[] last = new Integer[getInstance().getFleet().size()];
boolean[] finished = new boolean[getInstance().getFleet().size()];
for (Technician technician : getInstance().getFleet()) {
int tech = technician.getID();
TRSPTour tour = solution.getTour(tech);
tour.appendNode(technician.getHome().getID());
tour.appendNode(getInstance().getHomeDuplicate(technician.getHome().getID()));
last[tech] = technician.getHome().getID();
tour.setAutoUpdated(true);
}
// ----------------
// Shuffle the list of technicians
Technician[] fleet = new Technician[getInstance().getFleet().size()];
int v = 0;
for (Technician technician : getInstance().getFleet()) {
fleet[v++] = technician;
}
RandomPermutation.shuffle(fleet, getParameters().getRCHRndStream());
// We abort the heuristic if all tours are "finished"
boolean abort = false;
// Until all compatible requests have been visited
while (unserved > 0 && !abort) {
abort = true;
// Insert a request into each of the tours
for (Technician technician : fleet) {
int tech = technician.getID();
if (finished[tech])
continue; // The tour cannot be build any further
abort = false;
// ObservableNode node = selectNextNode(tour, tech, isUnserved, unserved);
ObservableNode node = selectNextFeasibleNode(solution.getTour(tech), last[tech],
tech, unserved);
if (node == null) {
if (mainDepotVisited[tech])
finished[tech] = true; // Nothing else to do
else {
// Try to insert a visit to the main depot
clone[tech] = solution.getTour(tech).clone();
InsertionMove ins = InsertionMove.findInsertion(solution.getTour(tech)
.getMainDepotId(), solution.getTour(tech), getCostDelegate(),
getTourConstraintHandler(), true, true);
if (ins.isFeasible()) {
InsertionMove.executeMove(ins);
mainDepotVisited[tech] = true;
} else {
finished[tech] = true; // Nothing else to do
}
}
} else {
// if (node.isServed())
// throw new IllegalStateException("Attempting to add a node that is already present");
// insertNextNode(tour, node, tech);
solution.getTour(tech).insertBefore(solution.getTour(tech).getLastNode(),
node.getId());
last[tech] = node.getId();
node.markAsServed();
// uns.remove(node.getId());
if (getInstance().isRequest(node.getId()))
unserved--;
else if (node.getId() == solution.getTour(tech).getMainDepotId())
mainDepotVisited[tech] = true;
}
}
}
for (Technician technician : getInstance().getFleet()) {
int tech = technician.getID();
if (clone[tech] != null && solution.getTour(tech).length() == clone[tech].length() + 1) {
solution.importTour(clone[tech]);
}
}
if (unserved > 0)
TRSPLogging.getOptimizationLogger().lowDebug(
"RndNearestNeighbor.generateFeasibleTour: %s request(s) left unserved",
unserved);
ArrayList<ITRSPTour> tours = new ArrayList<ITRSPTour>(solution.getTourCount());
for (TRSPTour trspTour : solution) {
tours.add(trspTour);
}
return tours;
}
@Override
protected ITRSPTour generateFeasibleTour(Technician technician) {
int tech = technician.getID();
int unserved = getCompatibleRequests(tech).length;
resetNodes(tech);
TRSPSolution solution = new TRSPSolution(getInstance(), getCostDelegate());
TRSPTour tour = new TRSPTour(solution, technician);
Integer last = technician.getHome().getID();
tour.appendNode(last);
tour.appendNode(getInstance().getHomeDuplicate(last));
tour.setAutoUpdated(true);
boolean mainDepotVisited = false;
TRSPTour clone = null;
// Until all compatible requests have been visited
while (unserved > 0) {
// ObservableNode node = selectNextNode(tour, tech, isUnserved, unserved);
ObservableNode node = selectNextFeasibleNode(tour, last, tech, unserved);
if (node == null)
if (mainDepotVisited)
break; // Nothing else to do
else {
// Try to insert a visit to the main depot
clone = tour.clone();
InsertionMove ins = InsertionMove.findInsertion(tour.getMainDepotId(), tour,
getCostDelegate(), getTourConstraintHandler(), true, true);
if (ins.isFeasible()) {
InsertionMove.executeMove(ins);
mainDepotVisited = true;
continue;
} else {
break;
}
}
// if (node.isServed())
// throw new IllegalStateException("Attempting to add a node that is already present");
// insertNextNode(tour, node, tech);
tour.insertBefore(tour.getLastNode(), node.getId());
last = node.getId();
node.markAsServed();
// uns.remove(node.getId());
if (getInstance().isRequest(node.getId()))
unserved--;
else if (node.getId() == tour.getMainDepotId())
mainDepotVisited = true;
}
if (clone != null && tour.length() == clone.length() + 1) {
tour = clone;
TRSPLogging
.getOptimizationLogger()
.lowDebug(
"RndNearestNeighbor.generateFeasibleTour: the visit to the main depot did not allowed further improvements");
}
if (unserved > 0)
TRSPLogging.getOptimizationLogger().lowDebug(
"RndNearestNeighbor.generateFeasibleTour: %s request(s) left unserved",
unserved);
return tour;
}
/**
* Creates a new empty tour using the most efficient data structure
*
* @param tech
* the technician id
* @return a new empty tour using the most efficient data structure
*/
protected List<Integer> initTour(int tech) {
List<Integer> tour = new ArrayList<Integer>(getCompatibleRequests(tech).length);
tour.add(getInstance().getTechnician(tech).getHome().getID());
return tour;
}
/**
* Select the next node to be inserted
*
* @param tour
* the current tour
* @param tech
* the technician id
* @param unserved
* the number of unserved requests
* @param seed
* the previously inserted node
* @return the next node to be inserted, or <code>null</code> if no suitable node was found
*/
protected ObservableNode selectNextNode(List<Integer> tour, int tech, int unserved) {
int k = nextIdx(unserved);
int itwfeas = -1;
int itwinfeas = -1;
Integer last = tour.get(tour.size() - 1);
NeighborListIterator it = getNeighbors(last).iterator();
Neighbor n = null;
Neighbor kUnsTWFeas = null;
Neighbor kUnsTWInf = null;
while (it.hasNext() && itwfeas < k) {
n = it.next();
Integer cand = n.getNeighbor().getId();
if (!n.getNeighbor().isServed() // Unserved node
&& getInstance().isCompatible(tech, cand)) {
if (!isCheckTWFeas() || getInstance().isArcTWFeasible(last, cand)) {
itwfeas++;
kUnsTWFeas = n;
} else if (itwinfeas < k) {
itwinfeas++;
kUnsTWInf = n;
}
}
}
if (kUnsTWFeas != null)
// We found an unserved request that is TW feasible
return kUnsTWFeas.getNeighbor();
else
// We did not find a TW feasible unserved request, return the k-th TW infeasible unserved request
return kUnsTWInf != null ? kUnsTWInf.getNeighbor() : null;
}
/**
* Select the next node to be inserted
*
* @param tour
* the current tour
* @param tech
* the technician id
* @param unserved
* the number of unserved requests
* @param seed
* the previously inserted node
* @return the next node to be inserted, or <code>null</code> if no suitable node was found
*/
protected ObservableNode selectNextFeasibleNode(TRSPTour tour, Integer last, int tech,
int unserved) {
int k = nextIdx(unserved);
int i = -1;
NeighborListIterator it = getNeighbors(last).iterator();
Neighbor n = null;
Neighbor kuns = null;
while (it.hasNext() && i < k) {
n = it.next();
if (!n.getNeighbor().isServed() // Unserved node
&& getTourConstraintHandler().isFeasible(
tour,
new InsertionMove(n.getNeighbor().getId(), tour, 0, last, tour
.getLastNode()))) {
i++;
kuns = n;
}
}
return kuns != null ? kuns.getNeighbor() : null;
}
/**
* Insert the <code>node</code> in the given <code>tour</code>
*
* @param tour
* the tour in which <code>node</code> will be inserted
* @param node
* the node to be inserted
* @param tech
* TODO
*/
protected void insertNextNode(List<Integer> tour, ObservableNode node, int tech) {
tour.add(node.getId());
}
@Override
public String toString() {
return "RNN";
}
}