package LBJ2.jni; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import LBJ2.infer.ILPInference; import LBJ2.infer.ILPSolver; /** * Native interface to the GNU Linear Programming Kit (GLPK), designed for * version 4.14. In order to use this class, GLPK 4.14 must be installed * such that the following statement would work in a <code>C</code> source * file: <br><br> * * <code>#include <glpk.h></code> <br><br> * * and such that that <code>C</code> file could be compiled and linked with * <code>-lglpk</code> on the command line. Furthermore, the * <code>GLPKHook</code> library distributed with LBJ must be accessible in a * location where the JVM will search for it. * * <p> An object of this class represents a single <i>integer</i> linear * programming problem. All columns added are assumed to represent Boolean * variables (i.e., integer variables that may take either the value 0 or the * value 1). **/ public class GLPKHook implements ILPSolver { static { System.loadLibrary("LBJGLPKHook"); } /** Indicates whether this problem instance has been solved already. */ private boolean solved; /** A <code>C</code> pointer to the <code>C</code> problem structure. */ private long problemPointer; /** Whether or not to generate Gomory cuts. */ private boolean generateCuts; /** * Diagnostic messages are written to this file when an optimal solution * cannot be found. **/ private String debugFileName; /** * Verbosity level. {@link ILPInference#VERBOSITY_NONE} produces no * incidental output. If set to {@link ILPInference#VERBOSITY_LOW}, only * variable and constraint counts are reported on <code>STDOUT</code>. If * set to {@link ILPInference#VERBOSITY_HIGH}, a textual representation of * the entire optimization problem is also generated on * <code>STDOUT</code>. **/ protected int verbosity; /** * If the appropriate methods are called below, this list will contain all * the constraints that have been added to this solver. **/ private LinkedList constraints; /** * If the appropriate methods are called below, this string will represent * the objective function. **/ private String objectiveFunction; /** Represents the number of variables in the optimization problem. */ private int objectiveCoefficients; /** * If the appropriate methods are called below, this variable will be set * iff this solver is solving a maximization. **/ private boolean maximize; /** * Creates the problem object with a call to <code>createProblem()</code>. * * @see #createProblem() **/ public GLPKHook() { this(""); } /** * Creates the problem object with a call to <code>createProblem()</code>. * * @param g Whether or not to generate cuts. * @see #createProblem() **/ public GLPKHook(boolean g) { this("", g); } /** * Creates the problem object with a call to <code>createProblem()</code>. * * @param g Whether or not to generate cuts. * @param v Setting for the {@link #verbosity} level. * @see #createProblem() **/ public GLPKHook(boolean g, int v) { this("", g, v); } /** * Creates the problem object with a call to <code>createProblem()</code>. * * @param s A file name for the "debug" file, in which diagnostic messages * are written when an optimal solution cannot be found. * @see #createProblem() **/ public GLPKHook(String s) { this(s, false); } /** * Creates the problem object with a call to <code>createProblem()</code>. * * @param s A file name for the "debug" file, in which diagnostic messages * are written when an optimal solution cannot be found. * @param g Whether or not to generate cuts. * @see #createProblem() **/ public GLPKHook(String s, boolean g) { this(s, g, ILPInference.VERBOSITY_NONE); } /** * Creates the problem object with a call to <code>createProblem()</code>. * * @param s A file name for the "debug" file, in which diagnostic messages * are written when an optimal solution cannot be found. * @param g Whether or not to generate cuts. * @param v Setting for the {@link #verbosity} level. * @see #createProblem() **/ public GLPKHook(String s, boolean g, int v) { if (!LBJ2.Configuration.GLPKLinked) { System.err.println( "LBJ ERROR: LBJ has not been configured correctly to invoke GLPK " + "inference. Install GLPK 4.14 or better and re-configure."); System.exit(1); } debugFileName = s; generateCuts = g; verbosity = v; reset(); } /** * This method clears the all constraints and variables out of the ILP * solver's problem representation, bringing the <code>ILPSolver</code> * instance back to the state it was in when first constructed. **/ public void reset() { constraints = new LinkedList(); objectiveFunction = ""; if (problemPointer != 0) deleteProblem(); problemPointer = createProblem(); solved = false; } /** * Tests whether the problem represented by this <code>ILPSolver</code> * instance has been solved already. **/ public boolean isSolved() { return solved; } /** * Deletes the problem object with a call to <code>deleteProblem()</code>. * * @see #deleteProblem() **/ protected void finalize() throws Throwable { deleteProblem(); } /** * Instantiates the problem object in the <code>C</code> world. * * @return A pointer to the structure created. **/ protected native long createProblem(); /** Frees the problem object's memory in the <code>C</code> world. */ protected native void deleteProblem(); /** * Sets the direction of the objective function. * * @param d <code>true</code> if the objective function is to be * maximized. **/ public void setMaximize(boolean d) { maximize = d; if (d) setMaximize(); else setMinimize(); } /** Sets the direction of the objective function to maximization. */ protected native void setMaximize(); /** Sets the direction of the objective function to minimization. */ protected native void setMinimize(); /** Returns the number of variables in the ILP problem. */ public native int numberOfVariables(); /** Returns the number of constraints in the ILP problem. */ public native int numberOfConstraints(); /** * Adds a general, multi-valued discrete variable, which is implemented as * a set of Boolean variables, one per value of the discrete variable, with * exactly one of those variables set <code>true</code> at any given time. * Since GLPK does not handle these sets of Boolean variables specially, * this method simply calls {@link #addBooleanVariable(double)} repeatedly * and then adds an equality constraint. * * @param c The objective function coefficients for the new Boolean * variables. * @return The indexes of the newly created variables. **/ public int[] addDiscreteVariable(double[] c) { int[] result = new int[c.length]; for (int i = 0; i < c.length; ++i) result[i] = addBooleanVariable(c[i]); double[] coefficients = new double[c.length]; Arrays.fill(coefficients, 1); addEqualityConstraint(result, coefficients, 1); return result; } /** * Adds a general, multi-valued discrete variable, which is implemented as * a set of Boolean variables, one per value of the discrete variable, with * exactly one of those variables set <code>true</code> at any given time. * Since GLPK does not handle these sets of Boolean variables specially, * this method simply calls {@link #addBooleanVariable(double)} repeatedly * and then adds an equality constraint. * * @param c An array of {@link LBJ2.classify.Score}s containing the * objective function coefficients for the new Boolean variables. * @return The indexes of the newly created variables. **/ public int[] addDiscreteVariable(LBJ2.classify.Score[] c) { int[] result = new int[c.length]; for (int i = 0; i < c.length; ++i) result[i] = addBooleanVariable(c[i].score); double[] coefficients = new double[c.length]; Arrays.fill(coefficients, 1); addEqualityConstraint(result, coefficients, 1); return result; } /** * Adds a new Boolean variable (an integer variable constrained to take * either the value 0 or the value 1) with the specified coefficient in the * objective function to the problem. * * @param c The objective function coefficient for the new Boolean * variable. **/ public int addBooleanVariable(double c) { if (verbosity == ILPInference.VERBOSITY_HIGH) { objectiveFunction += " "; if (c >= 0) objectiveFunction += "+"; objectiveFunction += c + " x_" + objectiveCoefficients; } addObjectiveCoefficient(c); return objectiveCoefficients++; } /** * Adds a new Boolean variable (an integer variable constrained to take * either the value 0 or the value 1) with the specified coefficient in the * objective function to the problem. * * @param c The objective function coefficient for the new Boolean * variable. **/ protected native void addObjectiveCoefficient(double c); /** * Adds a new fixed constraint to the problem. The two array arguments * must be the same length, as their elements correspond to each other. * Variables whose coefficients are zero need not be mentioned. Variables * that are mentioned must have previously been added via * {@link #addBooleanVariable(double)} or * {@link #addDiscreteVariable(double[])}. The resulting constraint has * the form: * <blockquote> <code>x<sub>i</sub> * a = b</code> </blockquote> * where <code>x<sub>i</sub></code> represents the inference variables * whose indexes are contained in the array <code>i</code> and * <code>*</code> represents dot product. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param b The new constraint will enforce equality with this constant. **/ public void addEqualityConstraint(int[] i, double[] a, double b) { if (verbosity == ILPInference.VERBOSITY_HIGH) { StringBuffer constraint = new StringBuffer(); constraint.append(" "); for (int j = 0; j < i.length; ++j) { constraint.append(" "); if (a[j] >= 0) constraint.append("+"); constraint.append(a[j]); constraint.append(" x_"); constraint.append(i[j]); } constraint.append(" = "); constraint.append(b); constraints.add(constraint.toString()); } addFixedConstraint(i, a, b); } /** * Adds a new fixed constraint to the problem. The two array arguments * must be the same length, as their elements correspond to each other. * Variables whose coefficients are zero need not be mentioned. Variables * that are mentioned must have previously been added via * {@link #addBooleanVariable(double)} or * {@link #addDiscreteVariable(double[])}. The resulting constraint has * the form: * <blockquote> <code>x<sub>i</sub> * a = b</code> </blockquote> * where <code>x<sub>i</sub></code> represents the inference variables * whose indexes are contained in the array <code>i</code> and * <code>*</code> represents dot product. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param b The new constraint will enforce equality with this constant. **/ protected native void addFixedConstraint(int[] i, double[] a, double b); /** * Adds a new lower bounded constraint to the problem. The two array * arguments must be the same length, as their elements correspond to each * other. Variables whose coefficients are zero need not be mentioned. * Variables that are mentioned must have previously been added via * {@link #addBooleanVariable(double)} or * {@link #addDiscreteVariable(double[])}. The resulting constraint has * the form: * <blockquote> <code>x<sub>i</sub> * a >= b</code> </blockquote> * where <code>x<sub>i</sub></code> represents the inference variables * whose indexes are contained in the array <code>i</code> and * <code>*</code> represents dot product. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param b The lower bound for the new constraint. **/ public void addGreaterThanConstraint(int[] i, double[] a, double b) { if (verbosity == ILPInference.VERBOSITY_HIGH) { StringBuffer constraint = new StringBuffer(); constraint.append(" "); for (int j = 0; j < i.length; ++j) { constraint.append(" "); if (a[j] >= 0) constraint.append("+"); constraint.append(a[j]); constraint.append(" x_"); constraint.append(i[j]); } constraint.append(" >= "); constraint.append(b); constraints.add(constraint.toString()); } addLowerBoundedConstraint(i, a, b); } /** * Adds a new lower bounded constraint to the problem. The two array * arguments must be the same length, as their elements correspond to each * other. Variables whose coefficients are zero need not be mentioned. * Variables that are mentioned must have previously been added via * {@link #addBooleanVariable(double)} or * {@link #addDiscreteVariable(double[])}. The resulting constraint has * the form: * <blockquote> <code>x<sub>i</sub> * a >= b</code> </blockquote> * where <code>x<sub>i</sub></code> represents the inference variables * whose indexes are contained in the array <code>i</code> and * <code>*</code> represents dot product. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param b The lower bound for the new constraint. **/ protected native void addLowerBoundedConstraint(int[] i, double[] a, double b); /** * Adds a new upper bounded constraint to the problem. The two array * arguments must be the same length, as their elements correspond to each * other. Variables whose coefficients are zero need not be mentioned. * Variables that are mentioned must have previously been added via * {@link #addBooleanVariable(double)} or * {@link #addDiscreteVariable(double[])}. The resulting constraint has * the form: * <blockquote> <code>x<sub>i</sub> * a <= b</code> </blockquote> * where <code>x<sub>i</sub></code> represents the inference variables * whose indexes are contained in the array <code>i</code> and * <code>*</code> represents dot product. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param b The upper bound for the new constraint. **/ public void addLessThanConstraint(int[] i, double[] a, double b) { if (verbosity == ILPInference.VERBOSITY_HIGH) { StringBuffer constraint = new StringBuffer(); constraint.append(" "); for (int j = 0; j < i.length; ++j) { constraint.append(" "); if (a[j] >= 0) constraint.append("+"); constraint.append(a[j]); constraint.append(" x_"); constraint.append(i[j]); } constraint.append(" <= "); constraint.append(b); constraints.add(constraint.toString()); } addUpperBoundedConstraint(i, a, b); } /** * Adds a new upper bounded constraint to the problem. The two array * arguments must be the same length, as their elements correspond to each * other. Variables whose coefficients are zero need not be mentioned. * Variables that are mentioned must have previously been added via * {@link #addBooleanVariable(double)} or * {@link #addDiscreteVariable(double[])}. The resulting constraint has * the form: * <blockquote> <code>x<sub>i</sub> * a <= b</code> </blockquote> * where <code>x<sub>i</sub></code> represents the inference variables * whose indexes are contained in the array <code>i</code> and * <code>*</code> represents dot product. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param b The upper bound for the new constraint. **/ protected native void addUpperBoundedConstraint(int[] i, double[] a, double b); /** * Simply calls {@link #nativeSolve()}, saving the result in * {@link #solved}. **/ public boolean solve() throws Exception { if (verbosity > ILPInference.VERBOSITY_NONE) { System.out.println(" variables: " + numberOfVariables()); System.out.println(" constraints: " + numberOfConstraints()); } if (verbosity == ILPInference.VERBOSITY_HIGH) { StringBuffer buffer = new StringBuffer(); write(buffer); System.out.print(buffer); } return (solved = nativeSolve()); } /** * Invokes the <code>lpx_intopt</code> GLPK library function to solve the * integer linear program. To produce diagnostic messages regarding a * return value of <code>false</code> from this method, use the constructor * of this class that takes a <code>String</code> argument. * * @return <code>true</code> iff an optimal integer solution was found and * no problems were encountered while running the algorithms. **/ protected native boolean nativeSolve(); /** * When the problem has been solved, use this method to retrieve the value * of any Boolean inference variable. The result of this method is * undefined when the problem has not yet been solved. * * @param index The index of the variable whose value is requested. * @return The value of the variable. **/ public boolean getBooleanValue(int index) { return columnPrimalValueOf(index) > 0.5; } /** * When the problem has been solved, use this method to retrieve the value * of the objective function at the solution. The result of this method is * undefined when the problem has not yet been solved. If the problem had * no feasible solutions, negative (positive, respectively) infinity will * be returned if maximizing (minimizing). * * @return The value of the objective function at the solution. **/ public double objectiveValue() { if (solved) return nativeObjectiveValue(); if (maximize) return Double.NEGATIVE_INFINITY; return Double.POSITIVE_INFINITY; } /** * When the problem has been solved, use this method to retrieve the value * of the objective function at the solution. * * @return The value of the objective function at the solution. **/ public native double nativeObjectiveValue(); /** * Returns the value of the specified variable in the primal solution. * * @param i The index of the variable whose value is to be returned. * @return The value of the specified variable in the primal solution. **/ public native double columnPrimalValueOf(int i); /** * Writes the optimization problem that this solver represents into the * specified buffer, assuming the appropriate member methods were called to * remember this information. * * @param buffer The buffer to write in. **/ public void write(StringBuffer buffer) { if (maximize) buffer.append("max"); else buffer.append("min"); buffer.append(objectiveFunction); buffer.append("\n"); for (Iterator I = constraints.iterator(); I.hasNext(); ) { buffer.append(I.next()); buffer.append("\n"); } } }