package vroom.optimization.vrph; 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.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; /** * The Class <code>VRPHSolver</code> implements {@link IVRPSolver} by using the VRPH library. Visit the <a * href="http://www.coin-or.org/projects/VRPH.xml">VRPH project home</a> to download and install the latest version. * <p> * Creation date: Feb 21, 2011 - 1:30:50 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 VRPHSolver 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"; public static final LoggerHelper LOGGER = LoggerHelper.getLogger(VRPHSolver.class.getSimpleName()); private static String sSolverCommand = "/opt/coinor/vrph/bin/vrp_rtr"; /** * 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 boolean mInitialized; private IVRPInstance mInstance; private 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; public void setPrintOutput(boolean printOutput) { } /** * Creates a new <code>VRPHSolver</code> */ public VRPHSolver() { mTimer = new Stopwatch(); mPersistenceHelper = new TSPLibPersistenceHelper(); setTempFolder("./tmp"); } @Override protected void finalize() throws Throwable { 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(); 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 currentRoute = null; mSolutionString = null; // Comments: // -------------------------------------------- // VRPH, version 1.0 // Copyright 2010 Chris Groer // Distributed under Common Public License 1.0 // -------------------------------------------- // String line; for (int c = 0; c < 6; c++) { line = solverOutput.readLine(); LOGGER.lowDebug(line); } // Solution summary // 5 524.611 0.43 1.000 // numtrucks obj ? ? line = solverOutput.readLine(); LOGGER.lowDebug(line); String[] values = line.split("\\s+"); // Num trucks output.numTrucks = Integer.valueOf(values[0]); // Objective output.intobj = Double.valueOf(values[1]).intValue(); output.obj = ((double) output.intobj) / mPersistenceHelper.getCoordinatesScaleFactor(); output.status = SolverStatus.HEURISTIC; 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); } public synchronized SolverStatus solve(File instanceFile, TSPLibPersistenceHelper persistenceHelper) throws InterruptedException, IOException { if (!mTimer.isStarted()) { mTimer.reset(); mTimer.start(); } mSolverOutput = null; mPersistenceHelper = persistenceHelper; // write the instance in a temporary file LOGGER.info(" Running the heuristic"); // command String command = String.format(COMMAND_FORMAT, getSolverCommand(), instanceFile.getPath()); LOGGER.info(" Command line: " + command); Runtime rt = Runtime.getRuntime(); // Run the command Process pr = rt.exec(command); // Parse the output mSolverOutput = readOutput(pr); // Timeout process killer ProcessDestroyer dest = ProcessDestroyer.monitorProcess(pr, mTimer.getRemainingTime()); int exitVal = pr.waitFor(); dest.cancel(); LOGGER.info(" Exited with error code " + exitVal); mTimer.stop(); LOGGER.info("Optimization terminated in %sms, solution: %s", mTimer.readTimeMS(), getObjectiveValue()); return mSolverOutput != null ? mSolverOutput.status : SolverStatus.UNKNOWN_STATUS; } @Override public synchronized SolverStatus solve() throws InterruptedException, IOException { mTimer.reset(); mTimer.start(); 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); sFileLock.unlock(); SolverStatus r = solve(instanceFile, mPersistenceHelper); instanceFile.delete(); return r; } /** * Getter for the number of trucks * * @return the number of trucks used in the solution */ public int getNumTrucks() { return mSolverOutput.numTrucks; } }