package LBJ2.infer; import java.util.Arrays; import LBJ2.parse.LineByLine; import LBJ2.util.IVector; import LBJ2.util.IVector2D; import LBJ2.util.DVector; import LBJ2.util.DVector2D; import LBJ2.util.Sort; /** * Can be used to represent an ILP problem, assuming all variables are 0-1. * * @author Nick Rizzolo **/ public class ZeroOneILPProblem { /** Represents the constraint type "equality". */ public static final int EQUALITY = 0; /** Represents the constraint type "less than or equal to". */ public static final int LESS_THAN = 1; /** Represents the constraint type "greater than or equal to". */ public static final int GREATER_THAN = 2; /** Maps from the three constraint types to their operator symbols. */ public static final String[] boundTypeSymbols = { "=", "<=", ">=" }; /** Used to mitigate floating point error in (in)equality comparisons. */ public static final double TOLERANCE = 1e-10; /** * Remembers whether the objective function should be maximized or * minimzed. **/ protected boolean maximize; /** * Represents the coefficients of all inference variables in the objective * function. **/ protected DVector objectiveCoefficients; /** * Half of a sparse matrix representation of the constraints; this half * contains the variable indexes corresponding to the coefficients in * {@link #Ac}. **/ protected IVector2D Av; /** * Half of a sparse matrix representation of the constraints; this half * contains the coefficients on the variables whose indexes appear in * {@link #Av}. **/ protected DVector2D Ac; /** Contains the types of the constraints. */ protected IVector boundTypes; /** The vector of constraint bounds. */ protected DVector bounds; /** Default constructor. */ public ZeroOneILPProblem() { reset(); } /** * Reads a textual representation of a 0-1 ILP problem from the specified * file. * * @param name The name of the file from which to read the ILP problem's * representation. **/ public ZeroOneILPProblem(String name) { reset(); LineByLine parser = new LineByLine(name) { public Object next() { String line = readLine(); while (line != null && line.matches("\\s*")) line = readLine(); return line; } }; String line = (String) parser.next(); String[] a = line.split(" "); maximize = a[0].startsWith("max"); for (int i = 1; i < a.length; i += 2) { double c = Double.parseDouble(a[i]); int v = Integer.parseInt(a[i+1].substring(2)); objectiveCoefficients.set(v, c); } line = (String) parser.next(); if (line.indexOf("subject") != -1) line = (String) parser.next(); for (int i = 0; line != null; line = (String) parser.next(), ++i) { a = line.substring(2).split(" "); int[] variables = new int[a.length / 2 - 1]; double[] coefficients = new double[variables.length]; for (int j = 0; j < a.length - 2; j += 2) { coefficients[j / 2] = Double.parseDouble(a[j]); variables[j / 2] = Integer.parseInt(a[j+1].substring(2)); } int type = EQUALITY; if (a[a.length - 2].charAt(0) == '>') type = GREATER_THAN; else if (a[a.length - 2].charAt(0) == '<') type = LESS_THAN; double bound = Double.parseDouble(a[a.length - 1]); addConstraint(variables, coefficients, type, bound); } } /** * This method clears the all constraints and variables out of the problem * representation, bringing it back to the state it was in when first * constructed. **/ public void reset() { maximize = false; objectiveCoefficients = new DVector(); Av = new IVector2D(); Ac = new DVector2D(); boundTypes = new IVector(); bounds = new DVector(); } /** * 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; } /** * Returns <code>true</code> iff the objective function is to be maximized. **/ public boolean getMaximize() { return maximize; } /** Returns the number of constraints in the ILP problem. */ public int rows() { return bounds.size(); } /** Returns the number of variables in the ILP problem. */ public int columns() { return objectiveCoefficients.size(); } /** * Sets the specified coefficient in the objective function. * * @param j The index of the variable whose coefficient will be set. * @param cj The new value of the coefficient. **/ public void setObjectiveCoefficient(int j, double cj) { objectiveCoefficients.set(j, cj); } /** Returns the specified objective coefficient. */ public double getObjectiveCoefficient(int j) { return objectiveCoefficients.get(j); } /** * Sets the specified coefficient in the constraint matrix. * * @param i The index of the constraint. * @param j The index of the variable. * @param aij The new value of the coefficient. **/ public void setConstraintCoefficient(int i, int j, double aij) { int index = Av.binarySearch(i, j); if (index < 0) { index = -index - 1; for (int k = Av.size(i) - 1; k >= index; --k) { Av.set(i, k + 1, Av.get(i, k)); Ac.set(i, k + 1, Ac.get(i, k)); } Av.set(i, index, j); } Ac.set(i, index, aij); } /** Returns the specified constraint coefficient. */ public double getConstraintCoefficient(int i, int j) { int index = Av.binarySearch(i, j); if (index < 0) return 0; return Ac.get(i, index); } /** * Sets the bound type for the specified constraint. * * @param i The constraint whose bound type will be set. * @param t The new type for the constraint's bound. **/ public void setBoundType(int i, int t) { boundTypes.set(i, t); } /** Returns the type of the specified constraint's bound. */ public int getBoundType(int i) { return boundTypes.get(i); } /** * Sets the bound on the specified constraint. * * @param i The constraint whose bound will be set. * @param bi The new value for the bound. **/ public void setConstraintBound(int i, double bi) { bounds.set(i, bi); } /** Returns the bound of the specified constraint. */ public double getConstraintBound(int i) { return bounds.get(i); } /** * Determines whether all constraints are satisfied by the given solution. * * @param x The settings of the variables. * @return <code>true</code> iff all constraints are satisfied. **/ public boolean constraintsSatisfied(int[] x) { int constraints = Av.size(); for (int i = 0; i < constraints; ++i) { double a = 0; int variables = Av.size(i); for (int j = 0; j < variables; ++j) a += Ac.get(i, j) * x[Av.get(i, j)]; if (boundTypes.get(i) == EQUALITY && a != bounds.get(i) || boundTypes.get(i) == LESS_THAN && a > bounds.get(i) || boundTypes.get(i) == GREATER_THAN && a < bounds.get(i)) return false; } return true; } /** * 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. * @return The index of the created variable. **/ public int addBooleanVariable(double c) { objectiveCoefficients.add(c); return objectiveCoefficients.size() - 1; } /** * 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. * * @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 s = objectiveCoefficients.size(); int[] result = new int[c.length]; for (int i = 0; i < c.length; ++i) { objectiveCoefficients.add(c[i]); result[i] = s + i; } double[] a = new double[c.length]; Arrays.fill(a, 1); addEqualityConstraint(result, a, 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. * * @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) { double[] d = new double[c.length]; for (int i = 0; i < c.length; ++i) d[i] = c[i].score; return addDiscreteVariable(d); } /** * Adds a typeless constraint to the problem. No need to waste space * storing the types of constraints if they are implied or assumed. * Otherwise, this method does the same thing as * {@link #addConstraint(int[],double[],int,double)}. * * @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 void addConstraint(final int[] I, double[] a, double b) { int[] indexes = new int[I.length]; for (int i = 0; i < I.length; ++i) indexes[i] = i; Sort.sort(indexes, new Sort.IntComparator() { public int compare(int i1, int i2) { return I[i1] - I[i2]; } }); int s = Av.size(); for (int i = 0; i < I.length; ++i) { Av.set(s, i, I[indexes[i]]); double c = a[indexes[i]]; double rounded = Math.round(c); if (Math.abs(c - rounded) < TOLERANCE) c = rounded; Ac.set(s, i, c); } bounds.add(b); } /** * Adds a new constraint of the specified type 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>, * <code>*</code> represents dot product, and ?= stands for the type of the * constraint. * * <p> This method is called by the other constraint adding methods in this * class. It sorts the variables and their coefficients so that the * presence of a given variable can be determined with * {@link LBJ2.util.IVector2D#binarySearch(int,int)}. * * @param i The indexes of the variables with non-zero coefficients. * @param a The coefficients of the variables with the given indexes. * @param t The type of comparison in this constraint. * @param b The new constraint will enforce equality with this constant. **/ protected void addConstraint(int[] i, double[] a, int t, double b) { addConstraint(i, a, b); boundTypes.add(t); } /** * 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) { addConstraint(i, a, EQUALITY, 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) { addConstraint(i, a, GREATER_THAN, 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) { addConstraint(i, a, LESS_THAN, b); } /** * This method evaluates the objective function on a potential (not * necessarily feasible) solution. * * @param x The current settings of the inference variables. * @return The value of the objective function with these variable * settings. **/ public double evaluate(int[] x) { double result = 0; for (int i = 0; i < x.length; ++i) result += x[i] * objectiveCoefficients.get(i); return result; } /** * Creates a textual representation of the ILP problem in an algebraic * notation. * * @param buffer The created textual representation will be appended here. **/ public void write(StringBuffer buffer) { if (maximize) buffer.append("max"); else buffer.append("min"); int variables = objectiveCoefficients.size(); for (int i = 0; i < variables; ++i) { double c = objectiveCoefficients.get(i); buffer.append(" "); if (c >= 0) buffer.append("+"); buffer.append(c); buffer.append(" x_"); buffer.append(i); } buffer.append("\n"); int constraints = Ac.size(); for (int i = 0; i < constraints; ++i) { int constraintSize = Ac.size(i); buffer.append(" "); for (int j = 0; j < constraintSize; ++j) { double c = Ac.get(i, j); buffer.append(" "); if (c >= 0) buffer.append("+"); buffer.append(c); buffer.append(" x_"); buffer.append(Av.get(i, j)); } buffer.append(" "); buffer.append(boundTypeSymbols[boundTypes.get(i)]); buffer.append(" "); buffer.append(bounds.get(i)); buffer.append("\n"); } } /** Returns the representation created by {@link #write(StringBuffer)}. */ public String toString() { StringBuffer buffer = new StringBuffer(); write(buffer); return buffer.toString(); } }