/* This file is part of JOP, the Java Optimized Processor see <http://www.jopdesign.com/> Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.wcet.ipet; import com.jopdesign.common.graphutils.IDProvider; import com.jopdesign.wcet.ipet.LinearConstraint.ConstraintType; import lpsolve.LpSolve; import lpsolve.LpSolveException; import org.apache.log4j.Logger; import java.io.File; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; /** * Simple, typed API for invoking LpSolve. * * @param <T> type of variables. If you don't want typed variables, use {@link java.lang.Object} * * @author Benedikt Huber <benedikt.huber@gmail.com> */ public class LpSolveWrapper<T> { /* FIXME: do know yet how to get correct information on variables after presolving. * Therefore, presolving is only enabled if you just need a objective value */ private static final int PRESOLVE_OPTIONS = 0 // | LpSolve.PRESOLVE_COLS // | LpSolve.PRESOLVE_ROWS // | LpSolve.PRESOLVE_LINDEP | LpSolve.PRESOLVE_SOS | LpSolve.PRESOLVE_ELIMEQ2 // doc says may lead to numerical stability problems; // this is the presolver that brings significant speedup // | LpSolve.PRESOLVE_PROBEFIX // try to fix binary variables (little speedup) // | LpSolve.PRESOLVE_PROBEREDUCE | LpSolve.PRESOLVE_ROWDOMINATE // | LpSolve.PRESOLVE_COLDOMINATE ; /** * Status of the lp solver (typed copy of basic LP solve status numbers) */ public enum SolverStatus { NOMEMORY(LpSolve.NOMEMORY), OPTIMAL(0), SUBOPTIMAL(1), INFEASIBLE(2), UNBOUNDED(3), DEGENERATE(4), NUMFAILURE(5), USERABORT(6), TIMEOUT(7), PRESOLVED(9),PROCFAIL(10),PROCBREAK(11),FEASFOUND(12),NOFEASFOUND(13), UNKNOWN(-1); int statusCode; SolverStatus(int c) { this.statusCode = c; } } private static final long LP_SOLVE_SEC_TIMEOUT = 1200; private static long solverTime = 0, resetSolverTime = 0; /** * Get time spend in the solver since the last call to {@link #resetSolverTime()} * * @return the time spend in the solver in seconds */ public static double getSolverTime() { return solverTime/1.0E9; } public static double getTotalSolverTime() { return (resetSolverTime+solverTime)/1.0E9; } /** * Reset the solver time statistic to 0 (does not affect totalSolverTime). */ public static void resetSolverTime() { resetSolverTime += solverTime; solverTime = 0; } private static Map<Integer,SolverStatus> readMap = null; /** * Wrap the return code of lp_solve into a {@link SolverStatus} variable. * @param code the code returned by the solver * @return */ public static SolverStatus getSolverStatus(int code) { if(readMap == null) { readMap = new TreeMap<Integer,SolverStatus>(); for(SolverStatus ss : SolverStatus.values()) { readMap.put(ss.statusCode, ss); } } SolverStatus status = readMap.get(code); if(status == null) return SolverStatus.UNKNOWN; else return status; } private class RawVector { int count; int[] ixs; double[] coeffs; } private RawVector buildRawVector(LinearVector<? extends T> inputVector) throws LpSolveException { RawVector vec = new RawVector(); vec.count = inputVector.size(); vec.ixs = new int[vec.count]; vec.coeffs = new double[vec.count]; int i = 0; for(Entry<? extends T,Long> e : inputVector.getCoeffs().entrySet()) { int objId = idProvider.getID(e.getKey()); if(objId < 1 || objId > numVars) { throw new LpSolveException("Bad id: "+e+"has id "+objId+" not in [1.."+numVars+"]"); } vec.ixs[i] = objId; long val = e.getValue(); double dval; // FIXME: The Big M method is extremely sensitive to numeric instabilities // 1E7 works fine in practice, but may fail on arbitrary problems // Should do some research to find whether there are solutions to this problem, // but in general better avoid Big M and use statically derived constants if(val == Long.MAX_VALUE) { dval = 1.0E7; } else if (val == Long.MIN_VALUE) { dval = - (1.0E7); } else { dval = (double)val; } vec.coeffs[i] = dval; i++; } return vec; } private LpSolve lpsolve; private int numVars; private IDProvider<T> idProvider; private boolean isILP; /** * Create a new (I)LP problem with the given number of variables. Note that * variables are per default considered to be non-negative. * @param numVars number of variables * @param idProvider mapping variables to ids. The id of a variable has to be in the range * [1..numVars]. * @param isILP if true, solves the ILP problem, otherwise the relaxed problem * @throws LpSolveException */ public LpSolveWrapper(int numVars, boolean isILP, IDProvider<T> idProvider) throws LpSolveException { this.numVars = numVars; this.idProvider = idProvider; this.lpsolve = LpSolve.makeLp(0,numVars); this.isILP = isILP; lpsolve.setPrintSol(LpSolve.FALSE); lpsolve.setTrace(false); lpsolve.setDebug(false); lpsolve.setVerbose(LpSolve.SEVERE); for(int i = 1; i <= numVars; i++) { lpsolve.setInt(i, isILP ? true : false); } lpsolve.setAddRowmode(true); } /** * add a linear constraint to the the problem * @param linearConstraint the linear constraint * @throws LpSolveException */ public void addConstraint(LinearConstraint<? extends T> linearConstraint) throws LpSolveException { LinearVector<? extends T> row = linearConstraint.getLinearVectorOnLHS(); RawVector rawVector = buildRawVector(row); int constrType = mapConstraintType(linearConstraint.getConstraintType()); double rh = linearConstraint.getInhomogenousTermOnRHS(); this.lpsolve.addConstraintex(rawVector.count, rawVector.coeffs, rawVector.ixs, constrType , rh); } /** * Set the objective of the (I)LP problem. * @param <T> Type of variables * @param objVector the objective vector * @param doMax whether to maximize (if false, minimize) * @throws LpSolveException */ public void setObjective(LinearVector<? extends T> objVector, boolean doMax) throws LpSolveException { RawVector rawVec = buildRawVector(objVector); this.lpsolve.setObjFnex(rawVec.count, rawVec.coeffs, rawVec.ixs); if(doMax) lpsolve.setMaxim(); else lpsolve.setMinim(); } /** * Turn row mode off - changes are expensive now, * but possible to dump ILP */ public void freeze() { this.lpsolve.setAddRowmode(false); } private class SolverThread extends Thread { int result = -1; long solverTime = 0; LpSolveException exception = null; public SolverThread() {} @Override public void run() { long start = System.nanoTime(); try { result = lpsolve.solve(); } catch (LpSolveException e) { exception = e; } long stop = System.nanoTime(); solverTime = stop-start; } } /** * Solve the I(LP) * @param objVec if non-null, write the solution into this array * @return the objective value * @throws LpSolveException */ public double solve(double[] objVec) throws LpSolveException { return solve(objVec, false); } /** * Solve the I(LP) * @param presolve whether to use presolving * @return the objective value * @throws LpSolveException */ public double solve(boolean preSolve) throws LpSolveException { return solve(null, preSolve); } /* either presolving, or a solution vector (at the moment) */ private double solve(double[] objVec, boolean preSolve) throws LpSolveException { freeze(); /* Presolving gives a speedup of factor 5 on the 'min-cache-blocks' problem for StartKfl */ /* But, the flow in the variables seems to be wrong; need to check whether this can be fixed */ if(preSolve) { lpsolve.setPresolve(PRESOLVE_OPTIONS,lpsolve.getPresolveloops()); } lpsolve.setTimeout(LP_SOLVE_SEC_TIMEOUT); SolverThread thr = new SolverThread(); thr.start(); int cnt=0; while(true) { boolean interrupted = false; try { thr.join(1000); } catch (InterruptedException e) { interrupted = true; } if(! thr.isAlive()) { break; } if(!interrupted) { if(++cnt == 1) { System.err.print("LP Solve: Hard Problem, calculating: ."); System.err.flush(); } else { System.err.print("."); System.err.flush(); } } } if(cnt>0) System.err.println(cnt+" seconds"); LpSolveWrapper.solverTime += (thr.solverTime); SolverStatus st = getSolverStatus(thr.result); if(objVec != null) this.lpsolve.getVariables(objVec); if(st != SolverStatus.OPTIMAL) { if(objVec != null) { int i = 0; for(double obj : objVec) { System.out.println(String.format("Objective entry %d: %.2f",i++,obj)); } } throw new LpSolveException("Failed to solve LP problem: status="+st+", exc="+thr.exception); // (objVec != null ? Arrays.toString(objVec) : " no info ")); } return this.lpsolve.getObjective(); } private int mapConstraintType(ConstraintType constraintType) { switch(constraintType) { case Equal : return LpSolve.EQ; case GreaterEqual : return LpSolve.GE; case LessEqual : return LpSolve.LE; default: throw new AssertionError("unexpected constraint type: "+constraintType); } } /** * Dump the (I)LP problem to the given file * @param outFile * @throws LpSolveException */ public void dumpToFile(File outFile) throws LpSolveException { outFile.delete(); try { this.lpsolve.writeLp(outFile.getPath()); } catch(LpSolveException ex) { Logger.getLogger(this.getClass()).error("Failed to dump LP Solve Problem to "+outFile.getPath()+": "+ex); } } /** * Mark the given variable (after adding it) as being binary * @param dv */ public void setBinary(T dv) { if(isILP) { try { this.lpsolve.setBinary(this.idProvider.getID(dv), true); } catch (LpSolveException e) { throw new AssertionError("setBinary failed for dv "+dv); } } } }