/**
*
*/
package vroom.trsp.optimization.alns;
import java.util.ConcurrentModificationException;
import vroom.common.heuristics.alns.IDestroy.IDestroyResult;
import vroom.common.heuristics.alns.IRepair;
import vroom.common.utilities.Utilities;
import vroom.common.utilities.optimization.IInstance;
import vroom.common.utilities.optimization.IParameters;
import vroom.trsp.datamodel.TRSPInstance;
import vroom.trsp.datamodel.TRSPSolution;
import vroom.trsp.datamodel.costDelegates.NoisyCostDelegate;
import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate;
import vroom.trsp.datamodel.costDelegates.TRSPTourBalance;
import vroom.trsp.optimization.InsertionMove;
import vroom.trsp.optimization.constraints.TourConstraintHandler;
import vroom.trsp.util.TRSPGlobalParameters;
import vroom.trsp.util.TRSPLogging;
/**
* <code>RepairBestInsertion</code> is a sequential implementation of the <em>regret-q insertion</em> presented in:
* <p>
* Ropke, S. & Pisinger, D.<br/>
* An adaptive large neighborhood search heuristic for the pickup and delivery problem with time windows<br/>
* Transportation Science, 2006, 40, 455-472
* </p>
* <p>
* Creation date: May 13, 2011 - 3:20:23 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 2.0
*/
public class RepairRegret implements IRepair<TRSPSolution> {
private final TRSPGlobalParameters mParams;
/** the regret level **/
private final int mLevel;
/**
* Getter for the regret level, e.g 2 for the regret-2.
*
* @return the regret level
*/
public int getLevel() {
return this.mLevel;
}
/** the noise flag **/
private final boolean mNoise;
private double mMaxNoise;
/**
* Getter for the noise flag
*
* @return {@code true} if noise is added to the objective function
*/
public boolean isNoiseEnabled() {
return this.mNoise;
}
private boolean mBusy = false;
/** The currently optimized solution */
TRSPSolution mSolution;
/** A matrix containing the insertion cost of each request in each tour */
InsertionMove[][] mInsMatrix;
/** An array containing the regret value of each request */
Double[] mRegretValues;
/** An array containing the best insertion of each request */
InsertionMove[] mBestIns;
/** The tour in which a request was inserted last */
int mLastInsTour;
/** A constraint handler for this repair component **/
private final TourConstraintHandler mConstraintHandler;
/**
* Getter for the constraint handler
*
* @return A constraint handler for this repair component
*/
public TourConstraintHandler getConstraintHandler() {
return this.mConstraintHandler;
}
/**
* Creates a new <code>RepairBestInsertion</code>
*
* @param params
* the global parameters
* @param constraintHandler
* the constraint handler to use
* @param level
* the level <em>q</em> of the <em>regret-q</em> heuristic
* @param noise
* {@code true} if noise should be added to the objective function
*/
public RepairRegret(TRSPGlobalParameters params, TourConstraintHandler constraintHandler,
int level, boolean noise) {
super();
mConstraintHandler = constraintHandler;
mLevel = level;
mParams = params;
mNoise = noise;
}
@Override
public void initialize(IInstance instance) {
if (isNoiseEnabled()) {
mMaxNoise = mParams.get(TRSPGlobalParameters.ALNS_REP_ETA)
* ((TRSPInstance) instance).getCostDelegate().getMaxDistance()
/ ((TRSPInstance) instance).getFleet().getVehicle().getSpeed();
} else {
mMaxNoise = 0;
}
}
@Override
public boolean repair(TRSPSolution solution, IDestroyResult<TRSPSolution> destroyResult,
IParameters params) {
if (mBusy)
throw new ConcurrentModificationException("This instance is already in use");
mBusy = true;
mSolution = solution;
mLastInsTour = -1;
mBestIns = new InsertionMove[solution.getInstance().getMaxId()];
mRegretValues = new Double[solution.getInstance().getMaxId()];
mInsMatrix = new InsertionMove[solution.getInstance().getMaxId()][];
for (int r : solution.getUnservedRequests())
mInsMatrix[r] = new InsertionMove[solution.getTourCount()];
while (!solution.getUnservedRequests().isEmpty()) {
// Evaluate the insertion cost matrix
evaluateInsCostMatrix(params);
InsertionMove bestInsOverall = selectBestInsertion();
// Execute the best insertion
if (bestInsOverall != null) {
boolean inserted = InsertionMove.executeMove(bestInsOverall);
if (inserted) {
TRSPLogging.getOptimizationLogger().lowDebug(
"RepairBestInsertion.repair: insertion successfull - %s",
bestInsOverall);
// Remove the request from the set of pending requests
solution.markAsServed(bestInsOverall.getNodeId());
// Store the id of the last modified tour
mLastInsTour = bestInsOverall.getTour().getTechnicianId();
// Clear stored data
mInsMatrix[bestInsOverall.getNodeId()] = null;
mBestIns[bestInsOverall.getNodeId()] = null;
mRegretValues[bestInsOverall.getNodeId()] = null;
} else {
TRSPLogging.getOptimizationLogger().lowDebug(
"RepairBestInsertion.repair: insertion failed - %s", bestInsOverall);
break;
}
} else {
break;
}
}
if (!solution.getUnservedRequests().isEmpty()) {
TRSPLogging
.getOptimizationLogger()
.lowDebug(
"RepairBestInsertion.repair: Unable to repair the solution, unfeasible requests: %s",
solution.getUnservedRequests());
}
mBusy = false;
return solution.getUnservedRequests().isEmpty();
}
/**
* Selects the best insertion among all candidates
*
* @return the best insertion among all candidates
*/
protected InsertionMove selectBestInsertion() {
InsertionMove bestIns = null;
double bestRegret = 0;
for (int r : mSolution.getUnservedRequests()) {
if (mRegretValues[r] == null) {
// Get the q best insertions
InsertionMove[] bests = Utilities.Math.max(mInsMatrix[r], getLevel());
if (bests.length == 0) {
// No feasible insertion was found
mBestIns[r] = null;
mRegretValues[r] = null;
continue;
}
// Store the best insertion
mBestIns[r] = bests[bests.length - 1];
// Evaluate the regret
if (getLevel() == 1)
mRegretValues[r] = -mBestIns[r].getCost();
else {
mRegretValues[r] = 0d;
for (int i = 0; i < bests.length - 1; i++) {
mRegretValues[r] += bests[i].getCost() - mBestIns[r].getCost();
}
}
}
// Select the best insertion overall
if (bestIns == null || mRegretValues[r] > bestRegret || // Better insertion found
(mRegretValues[r] == bestRegret && mBestIns[r].getCost() < bestIns.getCost())) {// Resolve ties
bestIns = mBestIns[r];
bestRegret = mRegretValues[r];
}
}
return bestIns;
}
/**
* Evaluates the insertion cost matrix stored in {@link #mInsMatrix} and updates the best insertion for each request
* {@link #mBestInsTour} and overall {@link #mBestInsOverall}
*/
void evaluateInsCostMatrix(IParameters params) {
for (int req : mSolution.getUnservedRequests()) {
// Select the tour cost delegate
// We do this to ensure we have good insertions within a tour
TRSPCostDelegate costDelegate = TRSPTourBalance.class.isInstance(mSolution
.getCostDelegate()) ? ((TRSPTourBalance) mSolution.getCostDelegate())
.getTourCostDelegate() : mSolution.getCostDelegate();
if (isNoiseEnabled())
costDelegate = new NoisyCostDelegate(costDelegate, params.getRandomStream(),
mMaxNoise);
// Evaluate all tours in the first iteration, and only the last modified tour otherwise
int tmin = mLastInsTour < 0 ? 0 : mLastInsTour;
int tmax = mLastInsTour < 0 ? mSolution.getTourCount() : mLastInsTour + 1;
for (int t = tmin; t < tmax; t++) {
// Evaluate the insertion cost of the considered request in tour t
InsertionMove ins = InsertionMove.findInsertion(req, mSolution.getTour(t),
costDelegate, getConstraintHandler(),
TRSPGlobalParameters.CTR_CHK_FWD_FEAS, true);
if (!ins.isFeasible()) {
if (mInsMatrix[req][t] != null) {
// The insertion used to be feasible, the best insertion and regret value may have changed
mBestIns[req] = null;
mRegretValues[req] = null;
}
// The move cannot be inserted in this tour
mInsMatrix[req][t] = null;
} else {
// Reevaluate the insertion if needed to evaluate the insertion for the whole solution
if (mSolution.getCostDelegate() instanceof TRSPTourBalance) {
double scdImp = ins.getImprovement();
mSolution.getCostDelegate().evaluateMove(ins);
// The initial improvement is stored as secondary improvement
ins.setSecondaryImprovement(scdImp);
}
// Store the value
mInsMatrix[req][t] = ins;
// The best insertion and regret value are likely to have changed
mBestIns[req] = null;
mRegretValues[req] = null;
}
}
}
}
@Override
public String toString() {
return String.format("%s[%s]", getName(), getLevel());
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
@Override
public String getName() {
return String.format("regret-%s%s", getLevel(), isNoiseEnabled() ? "-n" : "");
}
@Override
public void dispose() {
mInsMatrix = null;
mRegretValues = null;
mBestIns = null;
}
@Override
public RepairRegret clone() {
return new RepairRegret(mParams, mConstraintHandler, mLevel, false);
}
}