/**
*
*/
package vroom.trsp;
import gurobi.GRB.DoubleAttr;
import gurobi.GRBException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import umontreal.iro.lecuyer.rng.RandomStream;
import vroom.common.heuristics.ProcedureStatus;
import vroom.common.utilities.BestKnownSolutions;
import vroom.common.utilities.BatchThreadPoolExecutor;
import vroom.common.utilities.ProgressMonitor;
import vroom.common.utilities.StatCollector;
import vroom.common.utilities.StatCollector.Label;
import vroom.common.utilities.Stopwatch;
import vroom.common.utilities.Stopwatch.ReadOnlyStopwatch;
import vroom.common.utilities.Utilities;
import vroom.common.utilities.lp.SolverStatus;
import vroom.common.utilities.optimization.OptimizationSense;
import vroom.trsp.datamodel.HashTourPool;
import vroom.trsp.datamodel.ITRSPSolutionHasher;
import vroom.trsp.datamodel.ITRSPTour;
import vroom.trsp.datamodel.ITRSPTourPool;
import vroom.trsp.datamodel.TRSPInstance;
import vroom.trsp.datamodel.TRSPSolution;
import vroom.trsp.datamodel.TRSPSolutionChecker;
import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate;
import vroom.trsp.datamodel.costDelegates.TRSPDistance;
import vroom.trsp.datamodel.costDelegates.TRSPTourBalance;
import vroom.trsp.datamodel.costDelegates.TRSPWorkingTime;
import vroom.trsp.optimization.matheuristic.SCGurobiSolver;
import vroom.trsp.optimization.rch.RndBestIns;
import vroom.trsp.optimization.rch.RndClarkeWright;
import vroom.trsp.optimization.rch.RndNearestFurthestIns;
import vroom.trsp.optimization.rch.RndNearestNeighbor;
import vroom.trsp.optimization.rch.TRSPRndConstructiveHeuristic;
import vroom.trsp.util.TRSPGlobalParameters;
import vroom.trsp.util.TRSPLogging;
/**
* <code>RCHSCSolver</code> is an implementation of {@link TRSPSolver} that generates tours using randomized
* constructive heuristics and then builds a solution by solving a set covering problem.
* <p>
* Creation date: Sep 22, 2011 - 4:47: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 RCHSCSolver extends TRSPSolver {
public static boolean sParallel = true;
/** The labels that will be used in {@link #collectStats(StatCollector, BestKnownSolutions)} */
public static final Label<?>[] LABELS = new Label<?>[] {
new Label<String>("name", String.class), // name
new Label<String>("group", String.class), // group
new Label<Integer>("size", Integer.class), // size
new Label<Integer>("crew", Integer.class), // crew
new Label<Integer>("run", Integer.class), // run #
new Label<String>("comment", String.class), // comment
new Label<Long>("pool_gen_time", Long.class), // time spent generating the pool
new Label<Long>("sc_time", Long.class), // time to solve the set covering
new Label<Integer>("sol_unserved", Integer.class), // sol_unserved
new Label<Double>("sol_wt", Double.class, COST_FORMAT), // sol_wt
new Label<Double>("sol_wt_dev", Double.class, COST_FORMAT), // sol_wt_dev
new Label<Double>("sol_bks", Double.class, COST_FORMAT), // sol_bks
new Label<Double>("sol_gap", Double.class, PERC_FORMAT), // sol_gap_bks
new Label<Integer>("sol_bks_K", Integer.class), // sol_bks_K
new Label<Integer>("sol_K", Integer.class), // sol_K
new Label<String>("sol_checksol", String.class), // sol_checksol
new Label<Double>("sc_bbgap", Double.class), // sc_bbgap
new Label<SolverStatus>("sc_status", SolverStatus.class), // sc_status
new Label<Integer>("pool_size", Integer.class), // pool_size
new Label<String>("seeds", String.class), // seeds
new Label<String>("final_sol", String.class), // the detailed solution
};
/** the pool of tours generated by this solver */
private final ITRSPTourPool mTourPool;
ITRSPTourPool getTourPool() {
return mTourPool;
}
/** the randomized constructive heuristics used in this solver */
private final Collection<TRSPRndConstructiveHeuristic> mHeuristics;
Collection<TRSPRndConstructiveHeuristic> getHeuristics() {
return mHeuristics;
}
/** the hasher used in this solver */
private final ITRSPSolutionHasher mHasher;
/** the set-covering solver used in this solver */
private SCGurobiSolver mSCSolver;
/** the main cost delegate used in this solver **/
private final TRSPCostDelegate mCostDelegate;
/** a timer for the tour pool generation */
private final Stopwatch mGenTimer;
/** a timer for the set-covering solver */
private final Stopwatch mSCTimer;
/** a progress monitor for the method */
private ProgressMonitor mMonitor;
ProgressMonitor getMonitor() {
return mMonitor;
}
/**
* Returns the timer used to measure the time spent on the SC model
*
* @return the timer used to measure the time spent on the SC model
*/
public ReadOnlyStopwatch getSCTimer() {
return mSCTimer.getReadOnlyStopwatch();
}
/**
* Returns the timer used to measure the time spent on the generation of tours
*
* @return the timer used to measure the time spent on the generation of tours
*/
public ReadOnlyStopwatch getGenTimer() {
return mGenTimer.getReadOnlyStopwatch();
}
/**
* Getter for the main cost delegate used in this solver
*
* @return the main cost delegate used in this solver
*/
public TRSPCostDelegate getCostDelegate() {
return this.mCostDelegate;
}
public RCHSCSolver(TRSPInstance instance, TRSPGlobalParameters params) {
super(instance, params);
mHasher = params.newInstanceSafe(TRSPGlobalParameters.RCH_POOL_HASHER, new Class<?>[] {
TRSPInstance.class, RandomStream.class }, instance, getParams().getHashRndStream());
mTourPool = new HashTourPool(instance.getFleet().size(),
params.get(TRSPGlobalParameters.RCH_MAX_IT), mHasher);
mCostDelegate = getParams().newInstance(TRSPGlobalParameters.RCH_COST_DELEGATE);
mHeuristics = generateHeuristics();
mSCTimer = new Stopwatch();
mGenTimer = new Stopwatch();
}
protected Collection<TRSPRndConstructiveHeuristic> generateHeuristics() {
String[] list = getParams().get(TRSPGlobalParameters.RCH_HEURISTICS).split(",");
ArrayList<TRSPRndConstructiveHeuristic> heuristics = new ArrayList<TRSPRndConstructiveHeuristic>(
list.length);
for (String h : list) {
int par = h.indexOf("(");
String name = h.substring(0, par);
int kmax = Integer.valueOf(h.substring(par + 1, h.length() - 1));
if (name.equals("RNN")) {
heuristics.add(new RndNearestNeighbor(getInstance(), getParams(), getTourCtrHandler(),
getCostDelegate(), kmax));
} else if (name.equals("RNI")) {
heuristics.add(new RndNearestFurthestIns(getInstance(), getParams(),
getTourCtrHandler(), getCostDelegate(), kmax, false));
} else if (name.equals("RFI")) {
heuristics.add(new RndNearestFurthestIns(getInstance(), getParams(),
getTourCtrHandler(), getCostDelegate(), kmax, true));
} else if (name.equals("RBI")) {
heuristics.add(new RndBestIns(getInstance(), getParams(), getTourCtrHandler(),
getCostDelegate(), kmax));
} else if (name.equals("RCW")) {
heuristics.add(new RndClarkeWright(getInstance(), getParams(), getTourCtrHandler(),
getCostDelegate(), kmax));
} else {
throw new IllegalArgumentException("Unknown heuristic: " + name);
}
}
return heuristics;
}
@Override
public TRSPSolution call() {
getTimerInternal().reset();
mGenTimer.reset();
mSCTimer.reset();
getTimerInternal().start();
mMonitor = new ProgressMonitor(getParams().get(TRSPGlobalParameters.RCH_MAX_IT), false);
// Generate the tours
mGenTimer.reset();
mGenTimer.start();
generateTours();
mGenTimer.stop();
TRSPLogging.getOptimizationLogger().info("RCHSC %s: Pool generated in %ss (size: %s)",
mMonitor, mGenTimer.readTimeS(), mTourPool.size());
// Setup the SC model
setupSetCovering(false);
// Freeup memory
mTourPool.clear();
System.gc();
// Solve the SC model
mSCTimer.start();
solveSetCovering();
mSCTimer.stop();
// Get the final solution
setFinalSolution(mSCSolver.getSolution());
getTimerInternal().stop();
TRSPLogging.getOptimizationLogger().info("RCHSC %s: Set covering model solved in %ss",
mMonitor, mSCTimer.readTimeS());
return getFinalSolution();
}
/**
* Generate the tour pool using the constructive heuristics
*/
void generateTours() {
if (sParallel)
generateToursParallel();
else
generateToursSequential();
// Freeup memory by removing tours stored in the heuristics pools
for (TRSPRndConstructiveHeuristic h : mHeuristics) {
h.dispose();
}
System.gc();
}
/**
* Generate the tour pool using the constructive heuristics in the parent thread
*/
void generateToursSequential() {
final double maxIt = getParams().get(TRSPGlobalParameters.RCH_MAX_IT);
mMonitor.start();
int samplesPerHeur = Math.max(mHeuristics.size(),
(int) Math.ceil(maxIt / mHeuristics.size()));
for (TRSPRndConstructiveHeuristic h : mHeuristics) {
for (int it = 0; it < samplesPerHeur; it++) {
ProcedureStatus status = ProcedureStatus.INITIALIZED;
// try {
// Generate the tours
status = h.call();
// } catch (Exception e) {
// TRSPLogging.getOptimizationLogger().exception("RCHSCSolver.generateTours", e);
// }
if (status == ProcedureStatus.TERMINATED) {
// Add the generated tours to the pool
int addedTours = mTourPool.add(h.getTourPool());
TRSPLogging.getOptimizationLogger().debug(
"RCHSC %s: %s added %s/%s tours to the pool, new size:%s", mMonitor, h,
addedTours, h.getTourPool().size(), mTourPool.size());
} else {
throw new IllegalStateException(
"Constructive heuristic returned an unsupported state: " + status);
}
mMonitor.iterationFinished();
}
}
}
/**
* Generate the tour pool using the constructive heuristics in parallel
*/
void generateToursParallel() {
final double maxIt = getParams().get(TRSPGlobalParameters.RCH_MAX_IT);
mMonitor.start();
int samplesPerHeur = Math.max(mHeuristics.size(),
(int) Math.ceil(maxIt / mHeuristics.size()));
BatchThreadPoolExecutor executor = new BatchThreadPoolExecutor(
getParams().getThreadCount(), "RCH");
LinkedList<Future<Collection<ITRSPTour>>> futures = new LinkedList<Future<Collection<ITRSPTour>>>();
for (TRSPRndConstructiveHeuristic h : mHeuristics) {
futures.add(executor.submit(new RCHJob(h, samplesPerHeur)));
}
for (Future<Collection<ITRSPTour>> future : futures) {
try {
Collection<ITRSPTour> pool = future.get();
int addedTours = mTourPool.add(pool);
TRSPLogging.getOptimizationLogger().debug(
"RCHSC %s: %s/%s tours to the pool, new size:%s", mMonitor, addedTours,
pool.size(), mTourPool.size());
} catch (InterruptedException e) {
TRSPLogging.getBaseLogger().exception("RCHSCSolver.generateToursParallel", e);
} catch (ExecutionException e) {
TRSPLogging.getBaseLogger().exception("RCHSCSolver.generateToursParallel", e);
}
}
executor.shutdown();
}
/**
* Setup the set covering model
*
* @param twoPhases
* <code>true</code> if the problem should be solved in two-phases minimizing first the number of
* unserved requests
*/
boolean setupSetCovering(boolean twoPhases) {
mSCSolver = new SCGurobiSolver(getInstance(), getParams(), mHasher, twoPhases);
// Add stat writer
if (getGRBStatCollector() != null)
getGRBStatCollector().setModel(mSCSolver.getModel());
boolean r = mSCSolver.addColumns(mTourPool.getAllTours());
// Generate an initial solution to speed up the SC
// TRSPRepairConsHeur init = new TRSPRepairConsHeur(getInstance(), getParams(), getCtrHandler(), getCostDelegate(),
// new RepairRegret(getCtrHandler(), 2));
// try {
// TRSPLogging.getOptimizationLogger().debug("RCHSCSolver.call: Generating an initial solution for SC");
// init.call();
// TRSPLogging.getOptimizationLogger().debug(
// "RCHSCSolver.call: Initial solution for SC generated in %sms (sol:%s)",
// init.getTimer().readTimeMS(), init.getSolution());
// mSCSolver.setIncumbent(init.getSolution());
// } catch (Exception e) {
// TRSPLogging.getOptimizationLogger().exception("RCHSCSolver.call", e);
// }
return r;
}
/**
* Solve the set covering model
*
* @return the status of the SC solver
*/
SolverStatus solveSetCovering() {
mSCTimer.reset();
mSCTimer.start();
mSCSolver.checkModel();
// mSCSolver.logRemovedRows();
SolverStatus status = mSCSolver.solve();
if (status == SolverStatus.INFEASIBLE) {
setupSetCovering(true);
mSCSolver.checkModel();
mSCSolver.logRemovedRows();
status = mSCSolver.solve();
}
mSCSolver.repairSolution();
mSCTimer.stop();
return status;
}
@Override
public void dispose() {
mSCSolver.dispose();
mHeuristics.clear();
mTourPool.dispose();
}
@Override
public Label<?>[] getLabels() {
return LABELS;
}
@Override
public Object[] getStats(BestKnownSolutions bks, int runId, int runNum) {
String group = getInstance().getName().substring(0,
(getInstance().getName().contains("RC") ? 3 : 2));
String instanceName = getInstance().getName().replace(".txt", "");
TRSPCostDelegate wtDel;
// sCVRPTW
if (getParams().isCVRPTW())
wtDel = new TRSPDistance();
else
wtDel = new TRSPWorkingTime();
TRSPTourBalance tbDel = new TRSPTourBalance(wtDel, getParams().get(
TRSPGlobalParameters.BALANCE_COST_DELEGATE_MEASURE));
double sol_wt = getFinalSolution() != null ? wtDel.evaluateSolution(getFinalSolution(),
true, true) : Double.NaN;
double sol_wt_dev = getFinalSolution() != null ? tbDel.evaluateSolution(getFinalSolution(),
true, true) : Double.NaN;
double sol_bbgap = Double.NaN;
try {
double mipObj = mSCSolver.getModel().get(DoubleAttr.ObjVal);
double mipLB = mSCSolver.getModel().get(DoubleAttr.ObjBound);
sol_bbgap = mipLB != 0 ? (mipObj - mipLB) / mipObj : mipObj / 100;
} catch (GRBException e) {
TRSPLogging.getRunLogger().exception("ALNSSCSolver.collectStats", e);
}
Object[] stats = new Object[] { instanceName, // name
group, // group
getInstance().getRequestCount(), // size
getInstance().getFleet().size(), // crew count
runNum, // Run #
getComment(), // comment
mGenTimer.readTimeS(), // pool gen time
mSCTimer.readTimeS(), // sc time
getFinalSolution() != null ? getFinalSolution().getUnservedCount() : 0, // sol_unserved
sol_wt, // sol_wt
sol_wt_dev, // sol_wt_dev
bks.getBKS(instanceName), // sol_bks
bks.getGapToBKS(instanceName, sol_wt, OptimizationSense.MINIMIZATION), // sol_bks_gap
bks.getIntValue(instanceName, "K"), // BKS - K
getFinalSolution() != null ? getFinalSolution().getActualTourCount() : 0, // sol_K
getChecker().checkSolution(getFinalSolution()), // sol_checksol
sol_bbgap, // sol_gap
mSCSolver.getStatus(), // sol_status
mSCSolver.getColumnCount(), // sol_pool_size
Utilities.toShortString(getParams().get(TRSPGlobalParameters.RUN_SEEDS)),// seeds
getFinalSolution() != null ? getFinalSolution().toShortString() : "null" };
return stats;
}
/**
* Log a sample of the pool
*
* @param sampleSize
* an approximative size of the sample
*/
public void logPoolSample(int sampleSize) {
double p = Math.min(((double) sampleSize) / mTourPool.size(), 1);
Random r = new Random(0);
for (ITRSPTour t : mTourPool) {
if (r.nextDouble() < p)
TRSPLogging.getBaseLogger().debug("RCHSCSolver.logPoolSample: Tour %s Check: %s",
t, TRSPSolutionChecker.INSTANCE.checkTour(t));
}
}
public static class RCHJob implements Callable<Collection<ITRSPTour>> {
private final TRSPRndConstructiveHeuristic mHeuristic;
private final int mSampleSize;
/**
* Creates a new <code>RCHJob</code>
*
* @param heuristic
* @param sampleSize
*/
public RCHJob(TRSPRndConstructiveHeuristic heuristic, int sampleSize) {
mHeuristic = heuristic;
mSampleSize = sampleSize;
}
@Override
public Collection<ITRSPTour> call() throws Exception {
Collection<ITRSPTour> pool = new LinkedList<ITRSPTour>();
for (int it = 0; it < mSampleSize; it++) {
ProcedureStatus status = ProcedureStatus.INITIALIZED;
status = mHeuristic.call();
if (status == ProcedureStatus.TERMINATED) {
// Add the generated tours to the pool
pool.addAll(mHeuristic.getTourPool());
mHeuristic.dispose();
} else {
throw new IllegalStateException(
"Constructive heuristic returned an unsupported state: " + status);
}
}
return pool;
}
}
}