/**
*
*/
package vroom.trsp.optimization.matheuristic;
import gurobi.GRB;
import gurobi.GRB.DoubleAttr;
import gurobi.GRB.IntAttr;
import gurobi.GRB.StringAttr;
import gurobi.GRBException;
import gurobi.GRBModel;
import gurobi.GRBVar;
import java.io.File;
import java.text.DecimalFormat;
import java.util.concurrent.Callable;
import vroom.common.utilities.IDisposable;
import vroom.common.utilities.StatCollector;
import vroom.common.utilities.StatCollector.Label;
import vroom.common.utilities.gurobi.GRBStatCollector;
import vroom.common.utilities.gurobi.GRBUtilities;
import vroom.common.utilities.lp.SolverStatus;
import vroom.trsp.util.TRSPLogging;
/**
* <code>TRSPGRBStatCollector</code> contains instrumenting logic to monitor a {@link GRBModel gurobi model} for the
* TRSP
* <p>
* Creation date: Aug 25, 2011 - 4:48:45 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 TRSPGRBStatCollector implements Callable<SolverStatus>, IDisposable {
public static final double[] TARGET_GAPS = new double[] { 0.1, 0.05, 0.025, 0.01, 0.005, 0.0025, 0.001 };
public static final long[] TIME_SLICES = new long[] { 10, 30, 60, 300, 600, 1800 };
public static final int TT_LAB_OFFSET = 10;
public static final Label<?>[] TT_LABELS = new Label<?>[TARGET_GAPS.length + TT_LAB_OFFSET + TIME_SLICES.length];
static {
TT_LABELS[0] = new Label<String>("Instance", String.class);
TT_LABELS[1] = new Label<Boolean>("StartSol", Boolean.class);
TT_LABELS[2] = new Label<SolverStatus>("Status", SolverStatus.class);
TT_LABELS[3] = new Label<Integer>("Columns", Integer.class);
TT_LABELS[4] = new Label<Integer>("RemColumns", Integer.class);
TT_LABELS[5] = new Label<Integer>("Rows", Integer.class);
TT_LABELS[6] = new Label<Double>("Start", Double.class);
TT_LABELS[7] = new Label<Double>("Objective", Double.class);
TT_LABELS[8] = new Label<Double>("ObjBound", Double.class);
TT_LABELS[9] = new Label<Double>("Gap", Double.class);
for (int i = 0; i < TARGET_GAPS.length; i++) {
TT_LABELS[i + TT_LAB_OFFSET] = new Label<Long>(100 * TARGET_GAPS[i] + "%", Long.class);
}
for (int i = 0; i < TIME_SLICES.length; i++) {
TT_LABELS[i + TT_LAB_OFFSET + TARGET_GAPS.length] = new Label<Double>(TIME_SLICES[i] + "s", Double.class,
new DecimalFormat("###0.000000"));
}
}
private GRBModel mModel;
private GRBBenchCallback mCallback;
private SolverStatus mStatus;
private double mInitialObj;
private boolean mStartDefined;
private boolean mRunning;
private final StatCollector mTargetCollector;
private final String mDetailStatFile;
private final String mStatComment;
/**
* Creates a new <code>GurobiRun</code>
*
* @param statComment
* a comment for the time to target statistics file
* @param detStatFile
* the path of the detailed statistic file
* @param ttStatFile
* the path of the time to target statistics file
*/
public TRSPGRBStatCollector(String statComment, String detStatFile, String ttStatFile) {
this(null, statComment, detStatFile, ttStatFile);
}
/**
* Creates a new <code>GurobiRun</code>
*
* @param model
* the model to be instrumented
* @param statComment
* a comment for the time to target statistics file
* @param detStatFile
* the path of the detailed statistic file
* @param ttStatFile
* the path of the time to target statistics file
*/
public TRSPGRBStatCollector(GRBModel model, String statComment, String detStatFile, String ttStatFile) {
mTargetCollector = new StatCollector(new File(ttStatFile), true, true, statComment, TT_LABELS);
mDetailStatFile = detStatFile;
mStatComment = statComment;
setModel(model);
}
/**
* Sets the model and attach the corresponding callbacks to it
*
* @param model
* the model to be monitored
*/
public void setModel(GRBModel model) {
mModel = model;
mStatus = null;
if (mModel == null)
return;
// Read the initial solution value
double initObj = 0;
boolean startDef = false;
String name = "model";
try {
name = mModel.get(StringAttr.ModelName);
GRBVar vars[] = mModel.getVars();
double starts[] = mModel.get(DoubleAttr.Start, vars);
double costs[] = mModel.get(DoubleAttr.Obj, vars);
for (int i = 0; i < starts.length; i++) {
if (starts[i] != GRB.UNDEFINED) {
startDef = false;
initObj += costs[i] * starts[i];
}
}
} catch (GRBException e) {
TRSPLogging.getRunLogger().exception("GurobiBenchmarking.main", e);
}
mInitialObj = initObj;
mStartDefined = startDef;
mCallback = new GRBBenchCallback(mDetailStatFile, name, mStatComment, mStartDefined, TARGET_GAPS, TIME_SLICES,
mInitialObj);
mModel.setCallback(mCallback);
}
@Override
public SolverStatus call() throws Exception {
if (mRunning)
throw new IllegalStateException("Model is already beeing solved");
mRunning = true;
try {
mModel.optimize();
mRunning = false;
collectStats();
} catch (Exception e) {
mRunning = false;
throw e;
}
return mStatus;
}
/**
* Collect and write the statistics once the model was optimized
*
* @throws IllegalStateException
* if the optimization is not finished
*/
public synchronized void collectStats() {
if (mRunning)
throw new IllegalStateException("Model optimization has not terminated");
try {
mStatus = GRBUtilities.convertStatus(mModel.get(IntAttr.Status));
} catch (GRBException e1) {
mStatus = SolverStatus.UNKNOWN_STATUS;
TRSPLogging.getBaseLogger().exception("GurobiRun.collectStats", e1);
}
mCallback.solverStopped(mStatus);
// Collect stats
Object[] stats = new Object[TT_LABELS.length];
try {
stats[0] = mModel.get(StringAttr.ModelName);
double obj = mModel.get(DoubleAttr.ObjVal);
double bnd = mModel.get(DoubleAttr.ObjBound);
stats[7] = obj;
stats[8] = bnd;
stats[9] = Math.abs((obj - bnd) / obj);
} catch (GRBException e) {
TRSPLogging.getBaseLogger().exception("GurobiRun.collectStats", e);
}
stats[1] = mStartDefined;
stats[2] = mStatus;
stats[3] = mModel.getVars().length;
stats[4] = mCallback.getRemovedColumns();
stats[5] = mModel.getConstrs().length;
stats[6] = mInitialObj;
for (int j = 0; j < TARGET_GAPS.length; j++) {
stats[j + TT_LAB_OFFSET] = mCallback.getTimeToTarget(j);
}
for (int j = 0; j < TIME_SLICES.length; j++) {
stats[j + TT_LAB_OFFSET + TARGET_GAPS.length] = mCallback.getImpAtTime(j);
}
// TRSPLogging.getRunLogger().info(" Collecting stats: %s", Utilities.toShortString(stats));
mTargetCollector.collect(stats);
if (mCallback.getRemovedColumns() < mModel.getVars().length
&& (mStatus == SolverStatus.OPTIMAL || mStatus == SolverStatus.INTERRUPTED || mStatus == SolverStatus.TIME_LIMIT)) {
mCallback.flush();
}
}
@Override
public void dispose() {
mTargetCollector.close();
mCallback.closeCollector();
}
public static class GRBBenchCallback extends GRBStatCollector {
private final String mComment;
private final boolean mStart;
private final double[] mGapTargets;
private final long[] mTimesToTarget;
private int mCurrentTarget;
private final long[] mTimeSlices;
private final double[] mImpAtTime;
private final double mInitialSol;
private int mCurrentSlice;
private int mRemovedColumns;
/**
* Returns the number of columns removed by presolve
*
* @return the number of columns removed by presolve
*/
public int getRemovedColumns() {
return mRemovedColumns;
}
/**
* Creates a new <code>GRBBenchCallback</code>
*
* @param detailStatFile
* @param fileComment
* @param runComment
* @param start
* @param targets
* the target gap values, in descending order
*/
public GRBBenchCallback(String detailStatFile, String fileComment, String runComment, boolean start,
double[] targets, long[] timeSlices, double initialSol) {
super(detailStatFile, fileComment, true, false);
mComment = runComment;
mStart = start;
mGapTargets = targets;
mTimesToTarget = new long[mGapTargets.length];
for (int i = 0; i < mTimesToTarget.length; i++) {
mTimesToTarget[i] = -1;
}
mTimeSlices = timeSlices;
mImpAtTime = new double[mTimeSlices.length];
for (int i = 0; i < mImpAtTime.length; i++) {
mImpAtTime[i] = -2;
}
mInitialSol = initialSol;
mCurrentTarget = 0;
mCurrentSlice = 0;
}
@Override
protected void callback() {
super.callback();
switch (where) {
case GRB.Callback.PRESOLVE:
try {
mRemovedColumns = getIntInfo(GRB.Callback.PRE_COLDEL);
} catch (GRBException e) {
TRSPLogging.getRunLogger().exception("GRBBenchCallback.callback", e);
}
break;
case GRB.Callback.MIPNODE:
case GRB.Callback.MIPSOL:
long time = -1;
try {
time = (long) getDoubleInfo(GRB.Callback.RUNTIME);
} catch (GRBException e) {
TRSPLogging.getRunLogger().exception("GRBBenchCallback.callback", e);
}
while (mCurrentTarget < mTimesToTarget.length && getGap() < mGapTargets[mCurrentTarget]) {
mTimesToTarget[mCurrentTarget++] = time;
if (mCurrentTarget == mTimesToTarget.length)
// Abort the MIP search
abort();
}
while (mCurrentSlice < mTimeSlices.length && time >= mTimeSlices[mCurrentSlice]) {
mImpAtTime[mCurrentSlice++] = getCurrentImp();
}
}
}
/**
* Returns the current improvement with respect to the initial solution
*
* @return the current improvement with respect to the initial solution
*/
public double getCurrentImp() {
return (mInitialSol - getIncumbent()) / mInitialSol;
}
@Override
protected Label<?>[] getAdditionalLabels() {
return new Label<?>[] { new Label<String>("Instance", String.class),
new Label<Boolean>("StartSol", Boolean.class),
};
}
@Override
protected Object[] getAdditionalStatArray() throws GRBException {
return new Object[] { mComment, mStart };
}
/**
* Returns the time recorded to get to the specified target gap value
*
* @param target
* the index of the target gap value
* @return the time recorded to get to the specified target gap value
*/
public long getTimeToTarget(int target) {
return mTimesToTarget[target];
}
/**
* Returns the improvement on initial solution recorded at a specific time
*
* @param time
* the index of the target time
* @return the improvement on initial solution recorded at a specific time
*/
public double getImpAtTime(int time) {
return mImpAtTime[time];
}
/**
* Finalize the collection of statistics
*
* @param status
* the status of the solver
*/
public void solverStopped(SolverStatus status) {
if (status == SolverStatus.OPTIMAL) {
long finalTime = mTimesToTarget[0] >= 0 ? mTimesToTarget[0] : 0;
for (int i = 0; i < mTimesToTarget.length; i++) {
if (mTimesToTarget[i] >= 0)
finalTime = mTimesToTarget[i];
else
mTimesToTarget[i] = finalTime;
}
}
if (status == SolverStatus.OPTIMAL || status == SolverStatus.TIME_LIMIT
|| status == SolverStatus.INTERRUPTED) {
for (int i = 0; i < mImpAtTime.length; i++) {
if (mImpAtTime[i] < 0)
mImpAtTime[i] = getCurrentImp();
}
}
}
}
}