package vroom.optimization.pl.symphony.vrp; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import vroom.common.modeling.dataModel.ListRoute.ArrayListRoute; import vroom.common.modeling.dataModel.IRoute; import vroom.common.modeling.dataModel.IVRPInstance; import vroom.common.modeling.dataModel.IVRPRequest; import vroom.common.modeling.dataModel.IVRPSolution; import vroom.common.modeling.dataModel.Solution; import vroom.common.modeling.io.TSPLibPersistenceHelper; import vroom.common.modeling.util.SolutionChecker; import vroom.common.utilities.ProcessDestroyer; import vroom.common.utilities.Stopwatch; import vroom.common.utilities.logging.LoggerHelper; import vroom.common.utilities.lp.SolverStatus; import vroom.optimization.pl.IVRPSolver; import vroom.optimization.vrph.VRPHSolver; /** * The Class <code>CVRPSymphonySolver</code> implements {@link IVRPSolver} by using the Symphony VRP solver application. * Visit the <a href="https://projects.coin-or.org/SYMPHONY">symphony project home</a> to download and install the * latest version. * <p> * At this stage, this solver only supports instances with integral distances. * </p> * <p> * Creation date: Sep 15, 2010 - 10:30:38 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 CVRPSymphonySolver implements IVRPSolver { /** A lock used to prevent different threads to use the same file name */ protected static Lock sFileLock = new ReentrantLock(); protected static class SolverOutput { double obj = -1; Solution<ArrayListRoute> solution; SolverStatus status = SolverStatus.UNKNOWN_STATUS; int intobj; int numTrucks = -1; protected boolean isOptimal() { return status == SolverStatus.OPTIMAL; } } /** * Solver call command line format, arguments: * <p> * [solverCommand, instanceFile, numberOfTrucks, upperBound] * </p> */ public final static String COMMAND_FORMAT = "%s -F %s -N %s -u %s -v 0"; public static final LoggerHelper LOGGER = LoggerHelper.getLogger(CVRPSymphonySolver.class.getSimpleName()); private static String sSolverCommand = "/opt/coinor/symphony/SYMPHONY/Applications/VRP/vrp"; /** * Getter for the solver command string * * @return the solverCommand */ public static String getSolverCommand() { return sSolverCommand; } /** * Setter for the solver command string * * @param solverCommand * the solverCommand to set */ public static void setSolverCommand(String solverCommand) { sSolverCommand = solverCommand; } private double mPrecision = 1e-4; // private final VersatileLocalSearch<Solution<ArrayListRoute>> mHeurSolver; private boolean mInitialized; private IVRPInstance mInstance; private final TSPLibPersistenceHelper mPersistenceHelper; private Solution<ArrayListRoute> mSolution; private String mSolutionString; private SolverOutput mSolverOutput; /** a temporary folder for instance files **/ private String mTempFolder; private final Stopwatch mTimer; private double mUpperBound; private final VRPHSolver mVRPHSolver; public void setPrintOutput(boolean printOutput) { } /** * Creates a new <code>CVRPSymphonySolver</code> */ public CVRPSymphonySolver() { mTimer = new Stopwatch(); mPersistenceHelper = new TSPLibPersistenceHelper(); setTempFolder("./tmp"); mVRPHSolver = new VRPHSolver(); // VLSGlobalParameters parameters = new VLSGlobalParameters(); // parameters.set(VLSGlobalParameters.PARAM_LOCALSEARCH, new SimpleParameters(5000, 10000, false, true, false, // null)); // parameters.set(VLSGlobalParameters.PARAM_INIT, new SimpleParameters(1000, 1000, false, false, false, null)); // parameters.set(VLSGlobalParameters.ENABLE_CALLBACKS, false); // VLSParameters params = new VLSParameters(parameters, 1, 0, 0, 1000); // // ConstraintHandler<Solution<ArrayListRoute>> ctrHandler = new ConstraintHandler<Solution<ArrayListRoute>>(); // ctrHandler.addConstraint(new FixedNodesConstraint<Solution<ArrayListRoute>>()); // ctrHandler.addConstraint(new CapacityConstraint<Solution<ArrayListRoute>>()); // // CWParameters cwParams = new CWParameters(); // ClarkeAndWrightHeuristic<Solution<ArrayListRoute>> cw = new // ClarkeAndWrightHeuristic<Solution<ArrayListRoute>>( // cwParams, BasicSavingsHeuristic.class, ctrHandler); // CWInitialization<Solution<ArrayListRoute>> initialization = new // CWInitialization<Solution<ArrayListRoute>>(cw, // cwParams); // // @SuppressWarnings("unchecked") // VariableNeighborhoodSearch<Solution<ArrayListRoute>> ls = VariableNeighborhoodSearch.newVNS(VNSVariant.VND, // OptimizationSense.MINIMIZATION, null, new MRG32k3a(), new SwapNeighborhood<Solution<ArrayListRoute>>( // ctrHandler), new TwoOptNeighborhood<Solution<ArrayListRoute>>(ctrHandler), // new OrOptNeighborhood<Solution<ArrayListRoute>>(ctrHandler), // new StringExchangeNeighborhood<Solution<ArrayListRoute>>(ctrHandler)); // ((GenericNeighborhoodHandler<?>) ls.getNeighHandler()).setStrategy(Strategy.FREQUENCY_BASED); // ((GenericNeighborhoodHandler<?>) ls.getNeighHandler()).setResetStrategy(10000, 0.01); // // mHeurSolver = new VersatileLocalSearch<Solution<ArrayListRoute>>(parameters, params, VLSStateBase.class, // new SimpleAcceptanceCriterion(parameters), initialization, ls, // new Identity<Solution<ArrayListRoute>>(), ctrHandler); } @Override protected void finalize() throws Throwable { // mHeurSolver.destroy(); super.finalize(); } @Override public IVRPInstance getInstance() { return mInstance; } @Override public double getObjectiveValue() { return mSolverOutput != null ? mSolverOutput.obj : Double.NaN; } @Override public IVRPSolution<? extends IRoute<?>> getSolution() { return mSolution; } @Override public double getSolveTime() { return mTimer.readTimeMS(); } /** * Getter for a temporary folder for instance files * * @return the value of tempFOlder */ public String getTempFolder() { return mTempFolder; } @Override public boolean isInitialized() { return mInitialized; } @Override public boolean isSolutionFeasible() { return SolutionChecker.checkSolution(getSolution(), false, true, true) == null; } @Override public void printSolution(boolean printVariables) { System.out.println(mSolutionString); } @Override public synchronized void readInstance(IVRPInstance instance) { reset(); mUpperBound = Double.POSITIVE_INFINITY; mInstance = instance; mInitialized = true; } @SuppressWarnings("unused") protected SolverOutput readOutput(Process pr) throws NumberFormatException, IOException { SolverOutput output = new SolverOutput(); BufferedReader solverOutput = new BufferedReader(new InputStreamReader(pr.getInputStream())); String line = null; String currentRoute = null; mSolutionString = null; while ((line = solverOutput.readLine()) != null) { LOGGER.lowDebug(line); if (line.contains("Optimal Solution Found ")) { output.status = SolverStatus.OPTIMAL; LOGGER.info(" Otimal solution found"); } else if (line.contains("The problem is infeasible")) { output.obj = -2; output.status = SolverStatus.INFEASIBLE; LOGGER.info(" Problem is infeasible"); break; } else if (output.isOptimal() && line.startsWith("Solution Cost: ")) { line = line.replaceFirst("Solution Cost: ", ""); output.intobj = Double.valueOf(line).intValue(); output.obj = ((double) output.intobj) / mPersistenceHelper.getCoordinatesScaleFactor(); LOGGER.info(" Solution Cost: %s", output.obj); // break; } else if (output.isOptimal() && (line.startsWith("Route") || mSolutionString != null)) { // Read a route // TODO read and convert the solution mSolutionString = String.format("%s\n%s", mSolutionString, line); } } return output; } @Override public void reset() { mSolution = null; mSolutionString = null; mSolverOutput = null; mTimer.reset(); mInitialized = false; } /** * Setter for a temporary folder for instance files * * @param tempFOlder * the value to be set for a temporary folder for instance files */ public synchronized void setTempFolder(String tempFOlder) { mTempFolder = tempFOlder; } @Override public synchronized void setTimeLimit(int timeout) { mTimer.setTimout(timeout); } @Override public synchronized SolverStatus solve() throws InterruptedException, IOException { mTimer.reset(); mTimer.start(); mSolverOutput = null; sFileLock.lock(); // write the instance in a temporary file int idx = 0; File instanceFile = new File(String.format("%s/%s-%s.vrp", getTempFolder(), getInstance().getName(), idx++)); while (instanceFile.exists()) { instanceFile = new File(String.format("%s/%s-%s.vrp", getTempFolder(), getInstance().getName(), idx++)); } // Set the persistence helper to autodetect the best scaling factor mPersistenceHelper.setCoordinatesScaleFactor(TSPLibPersistenceHelper.calculateCoordFracScale(getInstance(), mPrecision)); mPersistenceHelper.setDemandsScaleFactor(TSPLibPersistenceHelper.calculateDemFracScale(getInstance(), mPrecision)); LOGGER.info("Coordinates scaling factor: %s", mPersistenceHelper.getCoordinatesScaleFactor()); LOGGER.info("Demands scaling factor: %s", mPersistenceHelper.getDemandsScaleFactor()); // Write the instance in file mPersistenceHelper.writeInstance(getInstance(), instanceFile, "Temporary instance"); LOGGER.info("Instance written to file: " + instanceFile.getAbsolutePath()); sFileLock.unlock(); // Estimate the minimum number of trucks required double load = 0; for (IVRPRequest r : getInstance().getRequests()) { load += r.getDemand(); } int minTrucks = (int) Math.ceil(load / getInstance().getFleet().getVehicle().getCapacity()); LOGGER.info("Minimum number of trucks: %s (%s/%s)", minTrucks, load, getInstance().getFleet().getVehicle() .getCapacity()); // VLS Heuristic Solution // -------------------------------------------------------------- // Run the heuristic to get an initial bound // mHeurSolver.setInstance(getInstance()); // mHeurSolver.run(); // mSolution = mHeurSolver.getBestSolution(); // // Remove empty routes // SolutionChecker.removeEmptyRoutes(getSolution()); // // Ensure that cost is coherent // SolutionChecker.checkSolution(getSolution(), true, true, true); // // // Number of trucks // int heurNumTrucks = getSolution().getRouteCount(); // double heurObj = getSolution().getCost(); // -------------------------------------------------------------- // VRPH Heuristic Solution // -------------------------------------------------------------- mVRPHSolver.solve(instanceFile, mPersistenceHelper); int heurNumTrucks = mVRPHSolver.getNumTrucks(); double heurObj = mVRPHSolver.getObjectiveValue(); // -------------------------------------------------------------- // int maxTrucks = Math.min(2 * heurNumTrucks, getInstance().getRequestCount()); int maxTrucks = getInstance().getRequestCount(); double doubleUB = heurObj; // Upper bound on the solution cost int ub = (int) Math.ceil((doubleUB + mPrecision) * mPersistenceHelper.getCoordinatesScaleFactor()); LOGGER.info("Heuristic solution found in %s ms: obj=%s trucks=%s", mTimer.readTimeMS(), heurObj, heurNumTrucks); int numTrucks = minTrucks; SolverOutput output = null; int exitVal = 0; boolean first = true; // while (!optimal && numTrucks <= maxTrucks) { while (numTrucks <= maxTrucks) { LOGGER.info(" Attempting to solve the problem with %s trucks, ub=%s", numTrucks, ub); // command String command = String.format(COMMAND_FORMAT, getSolverCommand(), instanceFile.getPath(), numTrucks, ub); // String command = String // .format("vrp -F %s -N %s", instanceFile.getName(), numTrucks); LOGGER.info(" Command line: " + command); Runtime rt = Runtime.getRuntime(); // Run the command Process pr = rt.exec(command); // Parse the output output = readOutput(pr); // Timeout process killer ProcessDestroyer dest = ProcessDestroyer.monitorProcess(pr, mTimer.getRemainingTime()); exitVal = pr.waitFor(); dest.cancel(); LOGGER.info(" Exited with error code " + exitVal); if (exitVal != 0) { break; } if (output.isOptimal()) { if (mSolverOutput == null || output.obj < mSolverOutput.obj) { mSolverOutput = output; } if (output.intobj < ub) { // Store upper bound ub = output.intobj; if (mSolverOutput == null || output.obj < mSolverOutput.obj) { // Store output as best solution mSolverOutput = output; mSolverOutput.numTrucks = numTrucks; } LOGGER.info(" New upper bound: " + ub); } // Try with more trucks numTrucks++; } else { if (first && doubleUB < heurObj) { // Revert upper bound but keep number of trucks doubleUB = heurObj; ub = (int) Math.ceil(doubleUB * 1.01 * mPersistenceHelper.getCoordinatesScaleFactor()); LOGGER.info(" Reverting upper bound"); } else if (numTrucks == heurNumTrucks) { // We know that a solution exist with this number of trucks // Relax upper bound for rounding issues ub = (int) Math.ceil(ub * 1.05); LOGGER.info(" Heuristic solution known, relaxing upper bound"); } else { // Increase number of trucks numTrucks++; LOGGER.info(" Increasing number of trucks"); } } first = false; pr.destroy(); } instanceFile.delete(); mTimer.stop(); LOGGER.info("Optimization terminated in %sms", mTimer.readTimeMS()); return mSolverOutput != null ? mSolverOutput.status : SolverStatus.UNKNOWN_STATUS; } /** * Define a user upper bound for the current instance * * @param ub * the upper bound value */ public void setUpperBound(double ub) { if (ub > 0) { mUpperBound = ub; } } /** * Getter for the number of trucks * * @return the number of trucks used in the solution */ public int getNumTrucks() { return mSolverOutput.numTrucks; } }