package edu.berkeley.nlp.math; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import edu.berkeley.nlp.util.IOUtils; import edu.berkeley.nlp.util.StrUtils; import edu.berkeley.nlp.util.Utils; import edu.berkeley.nlp.util.Option; public class IntegerProgram { @Option(gloss = "Location of the lp_solve executable") public static String lpSolveLocation = "/opt/local/bin/lp_solve"; @Option(gloss = "Maximum time for the solver (disaster strikes if it doesn't finish.") public static double maxIntegerProgramTime = -1.0; @Option(gloss = "Relaxes integer constraint on variables.") public static boolean relaxIntegerConstraint = false; @Option(gloss = "Don't optimize; used for profiling.") public static boolean dontOptimize = false; @Option(gloss = "Don't delete ilp files; used for testing.") public static boolean dontDeleteFiles = false; @Option(gloss = "Branch and bound depth limit (0 = no limit).") public static int depthLimit = 0; private int numVars; private List<String> constraints; private List<String> integerVariables; private double[] objectiveCoefficients; private double[] lp_solve_solution; private double lp_solve_objective_value; private boolean maximize; private boolean optimized; public IntegerProgram() { constraints = new ArrayList<String>(); integerVariables = new ArrayList<String>(); } public void setToMaximize() { maximize = true; } public double objectiveValue() { if (!optimized) optimize(); return lp_solve_objective_value; } public double[] solution() { if (!optimized) optimize(); return lp_solve_solution; } public void suggestSolution(double[] solution) { } public void cleanUp() { } public void addObjectiveWeights(List<Integer> indices, List<Double> weights) { for (int i = 0; i < indices.size(); i++) { addObjectiveWeight(indices.get(i), weights.get(i)); } } private void addObjectiveWeight(int pos, double val) { if (objectiveCoefficients == null) objectiveCoefficients = new double[numVars]; if (objectiveCoefficients.length < numVars) { double[] oldCoef = objectiveCoefficients; objectiveCoefficients = new double[numVars]; System.arraycopy(oldCoef, 0, objectiveCoefficients, 0, oldCoef.length); } objectiveCoefficients[pos] = val; } public void addObjectiveWeights(int[] indices, double[] weights) { for (int i = 0; i < indices.length; i++) { addObjectiveWeight(indices[i], weights[i]); } } public void addEqualityConstraint(int var, double weight, double rhs) { int[] vars = new int[1]; double[] weights = new double[1]; vars[0] = var; weights[0] = weight; addEqualityConstraint(vars, weights, rhs); } public void addEqualityConstraint(int[] indices, double[] weights, double rhs) { addConstraint(indices, weights, rhs, "="); } public void addLessThanConstraint(int var, double weight, double rhs) { int[] vars = new int[1]; double[] weights = new double[1]; vars[0] = var; weights[0] = weight; addLessThanConstraint(vars, weights, rhs); } public void addLessThanConstraint(int[] indices, double[] weights, double rhs) { addConstraint(indices, weights, rhs, "<="); } public void addConstraint(int[] indices, double[] weights, double rhs, String op) { StringBuilder sb = new StringBuilder(); assert (indices.length == weights.length); for (int i = 0; i < indices.length; i++) { sb.append("+ "); sb.append(weights[i]); sb.append(" "); sb.append(var(indices[i])); sb.append(" "); } sb.append(op); sb.append(" "); sb.append(rhs); sb.append(";"); constraints.add(sb.toString()); } public void addBoundedVars(int k, double lower, double upper) { for (int i = numVars; i < k + numVars; i++) { integerVariables.add(var(i)); if (lower != 0) addLessThanConstraint(i, -1, lower); addLessThanConstraint(i, 1, upper); } numVars += k; } private static String var(int i) { return "x" + i; } /* Heavy lifting */ /** * Optimizes by writing to a file. */ public void optimize() { if (dontOptimize) { lp_solve_objective_value = 0; lp_solve_solution = new double[numVars]; optimized = true; } else { try { File temp = File.createTempFile("ilp-", ".mps"); if (dontDeleteFiles) System.err.println("[IntPgrm] " + temp.getPath()); writeProgram(temp); executeLPSolve(temp.getAbsolutePath()); if (!dontDeleteFiles) temp.delete(); optimized = true; } catch (IOException e) { throw new RuntimeException(e); } } } private void writeProgram(File temp) { PrintWriter out = IOUtils.openOutHard(temp); writeObjective(out); for (String c : constraints) { out.println(c); } String intCons = "int " + StrUtils.join(integerVariables, ", ") + ";"; if (!relaxIntegerConstraint) out.println(intCons); out.close(); } private void writeObjective(PrintWriter out) { out.print(maximize ? "max: " : "min: "); assert numVars == objectiveCoefficients.length; for (int i = 0; i < numVars; i++) { out.print("+ "); out.print(objectiveCoefficients[i]); out.print(" "); out.print(var(i)); out.print(" "); } out.print(";\n"); } /** * Solves the problem with lp-solve and store solution in this object. */ private void executeLPSolve(String problemPath) { StringBuilder command = new StringBuilder(); command.append(lpSolveLocation + " "); // command.append(" -fmps "); if (maxIntegerProgramTime > 0) { command.append("-timeout " + maxIntegerProgramTime + " "); } command.append("-depth " + depthLimit + " "); command.append(problemPath); StringWriter output = new StringWriter(); PrintWriter out = new PrintWriter(output); StringWriter error = new StringWriter(); PrintWriter err = new PrintWriter(error); Utils.systemHard(command.toString(), out, err); BufferedReader reader = new BufferedReader(new StringReader(output.toString())); lp_solve_solution = new double[numVars]; int var = 0; try { while (reader.ready()) { String next = reader.readLine(); if (next == null) return; if (next.startsWith("x")) { String[] parts = next.trim().split("\\s+"); if (parts[0].equals("x")) continue; // Weird output from lp_solve assert (parts[0].equals(var(var))); double val = Double.parseDouble(parts[1]); lp_solve_solution[var] = val; var++; } else if (next.startsWith("Value of objective function")) { String[] parts = next.split("\\s+"); lp_solve_objective_value = Double.parseDouble(parts[parts.length - 1]); } } } catch (IOException e) { throw new RuntimeException(e); } } /* Testing */ public static void main(String[] args) { dontDeleteFiles = true; IntegerProgram ip = new IntegerProgram(); ip.addBoundedVars(3, 0, 1); ip.addObjectiveWeight(0, 10); ip.addObjectiveWeight(1, -5); ip.addObjectiveWeight(2, 1); int[] cvars = { 0, 1, 2 }; double[] cweights = { 1, 0, 1 }; ip.setToMaximize(); ip.addEqualityConstraint(cvars, cweights, 1); double[] sol = ip.solution(); for (int i = 0; i < sol.length; i++) { System.out.println("x" + i + ":\t" + sol[i]); } System.out.println("obj:\t" + ip.objectiveValue()); } /** * combined = left * right */ public void addAndConstraint(int combined, int left, int right) { int[] vars1 = { combined, left }; int[] vars2 = { combined, right }; double[] weights = { 1.0, -1.0 }; addLessThanConstraint(vars1, weights, 0); addLessThanConstraint(vars2, weights, 0); } }