/** * */ 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; } } }