package LBJ2.infer;
import java.util.*;
import LBJ2.classify.*;
import LBJ2.jni.GLPKHook;
/**
* Uses the GNU Linear Programming Kit library to perform Integer Linear
* Programming over the variables, maximizing the sum of all
* learner-object-value triples selected while respecting the constraints.
* The difference between this implementation and <code>NaiveGLPK</code> is
* that this implementation attempts to save the generation of a few
* constraints by directly translating a broader variety of propositional
* logic subexpressions into as few constraints as possible. For example,
* negated variables never need to be converted to positive variables with
* the addition of a new variable and constraint, like <code>NaiveGLPK</code>
* would generate.
*
* <p> This class assumes that the <code>constraint</code> variable inherited
* from class <code>Inference</code> is of type
* <code>FirstOrderConstraint</code>.
*
* @author Nick Rizzolo
* @deprecated As of LBJ release 2.0.12, it is preferrable to pass a
* {@link LBJ2.jni.GLPKHook} object to the
* {@link LBJ2.infer.ILPInference} constructor.
**/
public class GLPK extends Inference
{
/** Keeps the next ID number for objects of this class. */
protected static int nextID = 0;
/** The JNI to the GLPK library. */
protected GLPKHook solver;
/** Whether or not to write debug files when problems arise. */
protected boolean writeStatusFiles;
/** The identification number for this object, used in debug file names. */
protected int ID;
/** Whether or not to generate Gomory cuts. */
protected boolean generateCuts;
/** Debugging variable. */
private int PRINT_ILP;
/**
* Used during ILP constraint generation. When a propositional constraint
* finishes generating any ILP constraints that may be associated with it,
* it sets this variable to its own index.
**/
protected int returnIndex;
/**
* Used during ILP constraint generation. This flag is set iff the
* variable corresponding to <code>returnIndex</code> is negated in its
* current context.
**/
protected boolean returnNegation;
/**
* Used during ILP constraint generation. This map associates each
* variable index with a representation of the expression whose value is
* represented by the variable. The keys associated with indexes of
* variables that were originally part of the inference problem are
* <code>PropositionalVariable</code> objects. The keys associated with
* indexes of temporary variables created during constraint translation are
* strings.
**/
protected HashMap indexMap;
/**
* Used during ILP constraint generation. Constraints are treated
* differently if they are part of another constraint expression than if
* they are a term in the top level conjunction.
**/
protected boolean topLevel;
/**
* Creates an instance with the specified verbosity level.
*
* @param v Setting for the {@link #PRINT_ILP} variable.
**/
public GLPK(int v) { this(null, v); }
/**
* Creates an instance with the specified verbosity level.
*
* @param v Setting for the {@link #PRINT_ILP} variable.
**/
public GLPK(Object h, int v) {
this(h);
PRINT_ILP = v;
}
/** Default constructor. */
public GLPK() { this(null); }
/**
* Initializing constructor.
*
* @param g Whether or not to generate cuts.
**/
public GLPK(boolean g) { this(null, g); }
/**
* Initializing constructor.
*
* @param g Whether or not to generate cuts.
* @param w Whether or not to write debug files when problems arise.
**/
public GLPK(boolean g, boolean w) { this(null, g, w); }
/**
* Initializing constructor.
*
* @param h The head object.
**/
public GLPK(Object h) { this(h, true); }
/**
* Initializing constructor.
*
* @param h The head object.
* @param g Whether or not to generate cuts.
**/
public GLPK(Object h, boolean g) { this(h, g, false); }
/**
* Initializing constructor.
*
* @param h The head object.
* @param g Whether or not to generate cuts.
* @param w Whether or not to write debug files when problems arise.
**/
public GLPK(Object h, boolean g, boolean w) {
super(h);
ID = nextID++;
generateCuts = g;
writeStatusFiles = w;
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);
}
}
/**
* Adds a constraint to the inference.
*
* @param c The constraint to add.
**/
public void addConstraint(FirstOrderConstraint c) {
solver = null;
if (constraint == null) constraint = c;
else
constraint =
new FirstOrderConjunction((FirstOrderConstraint) constraint, c);
}
/**
* Uses the <code>lpx_intopt(LPX*)</code> C routine from the GLPK library
* to solve the ILP proglem if it hasn't already been solved.
**/
protected void infer() throws Exception {
if (solver != null) return;
constraint.consolidateVariables(variables);
indexMap = new HashMap();
if (writeStatusFiles)
solver = new GLPKHook("GLPKInference" + ID, generateCuts, PRINT_ILP);
else solver = new GLPKHook(generateCuts, PRINT_ILP);
solver.setMaximize(true);
for (Iterator I = variables.values().iterator(); I.hasNext(); ) {
FirstOrderVariable v = (FirstOrderVariable) I.next();
ScoreSet ss = getNormalizer(v.getClassifier()).normalize(v.getScores());
Score[] scores = null;
if (ss != null) scores = ss.toArray();
if (scores == null || scores.length == 0) {
System.err.println(
"LBJ ERROR: Classifier " + v.getClassifier()
+ " did not return any scores. GLPK Inference cannot be "
+ "performed.");
System.exit(1);
}
int[] indexes = new int[scores.length];
double[] coefficients = new double[scores.length];
Arrays.fill(coefficients, 1);
for (int j = 0; j < scores.length; ++j) {
indexes[j] = solver.addBooleanVariable(scores[j].score);
indexMap.put(
new PropositionalVariable(v.getClassifier(), v.getExample(),
scores[j].value),
new Integer(indexes[j]));
if (PRINT_ILP == ILPInference.VERBOSITY_HIGH)
System.out.println(
indexes[j] + "(" + scores[j].score + "): " + v.getClassifier()
+ "(" + v.getExample() + ") == " + scores[j].value);
}
solver.addEqualityConstraint(indexes, coefficients, 1);
}
PropositionalConstraint propositional =
((FirstOrderConstraint) constraint).propositionalize();
if (propositional instanceof PropositionalConjunction)
propositional =
((PropositionalConjunction) propositional).simplify(true);
else propositional = propositional.simplify();
if (propositional instanceof PropositionalConstant
&& !propositional.evaluate()) {
System.err.println("GLPK ERROR: Unsatisfiable constraints!");
solver.addEqualityConstraint(new int[]{ 0 }, new double[]{ 1 }, 2);
}
topLevel = true;
propositional.runVisit(this);
if (!solver.solve()) throw new InferenceNotOptimalException(solver, head);
int variableIndex = 0;
for (Iterator I = variables.values().iterator(); I.hasNext(); ) {
FirstOrderVariable v = (FirstOrderVariable) I.next();
Score[] scores = v.getScores().toArray();
for (int j = 0; j < scores.length; ++j, ++variableIndex)
if (solver.getBooleanValue(variableIndex))
v.setValue(scores[j].value);
}
}
/**
* Retrieves the value of the specified variable as identified by the
* classifier and the object that produce that variable.
*
* @param c The classifier producing the variable.
* @param o The object from which the variable is produced.
* @return The current value of the requested variable. If the variable
* does not exist in this inference, the result of the
* <code>Learner</code>'s <code>discreteValue(Object)</code> method
* applied to the <code>Object</code> is returned.
**/
public String valueOf(LBJ2.learn.Learner c, Object o) throws Exception {
infer();
return getVariable(new FirstOrderVariable(c, o)).getValue();
}
/**
* Two <code>Inference</code> objects are equal when they have the same
* run-time type and store the same head object. I.e., the <code>==</code>
* operator must return <code>true</code> when comparing the two head
* objects for this method to return <code>true</code>.
*
* @param o The object to compare to this object.
* @return <code>true</code> iff this object equals the argument object as
* defined above.
**/
public boolean equals(Object o) {
if (!(o instanceof GLPK)) return false;
return head == ((GLPK) o).head;
}
/**
* Used during ILP constraint generation, this method creates a new
* temporary propositional variable and adds the corresponding column to
* the solver.
*
* @param d A textual description of the subexpression whose value is
* represented by the new variable.
* @return The index of the new variable created.
**/
protected int createNewVariable(String d) {
int result = solver.addBooleanVariable(0);
if (PRINT_ILP == ILPInference.VERBOSITY_HIGH)
System.out.println(result + ": " + d);
return result;
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalDoubleImplication c) {
assert topLevel : "GLPK: PropositionalDoubleImplication encountered.";
topLevel = false;
int[] indexes = new int[2];
double[] coefficients = new double[2];
double bound = 0;
c.left.runVisit(this);
indexes[0] = returnIndex;
if (returnNegation) {
coefficients[0] = -1;
--bound;
}
else coefficients[0] = 1;
c.right.runVisit(this);
indexes[1] = returnIndex;
if (returnNegation) {
coefficients[1] = 1;
++bound;
}
else coefficients[1] = -1;
solver.addEqualityConstraint(indexes, coefficients, bound);
topLevel = true;
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalImplication c) {
assert false : "GLPK: PropositionalImplication encountered.";
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalConjunction c) {
PropositionalConstraint[] children =
(PropositionalConstraint[]) c.getChildren();
int[] indexes = null;
double[] coefficients = null;
double bound;
if (topLevel) {
PropositionalConstraint[] variables =
new PropositionalConstraint[children.length];
int size = 0;
for (int i = 0; i < children.length; ++i) {
if (children[i] instanceof PropositionalVariable
|| children[i] instanceof PropositionalNegation)
variables[size++] = children[i];
else children[i].runVisit(this);
}
if (size > 0) {
indexes = new int[size];
coefficients = new double[size];
bound = size;
for (int i = 0; i < size; ++i) {
variables[i].runVisit(this);
indexes[i] = returnIndex;
if (returnNegation) {
coefficients[i] = -1;
--bound;
}
else coefficients[i] = 1;
}
solver.addEqualityConstraint(indexes, coefficients, bound);
}
}
else {
indexes = new int[children.length + 1];
coefficients = new double[children.length + 1];
bound = 0;
for (int i = 0; i < children.length; ++i) {
children[i].runVisit(this);
indexes[i] = returnIndex;
if (returnNegation) {
coefficients[i] = -1;
--bound;
}
else coefficients[i] = 1;
}
String[] stringIndexes = new String[children.length];
for (int i = 0; i < children.length; ++i)
stringIndexes[i] = (coefficients[i] < 0 ? "!" : "") + indexes[i];
Arrays.sort(stringIndexes);
String key = stringIndexes[0];
for (int i = 1; i < stringIndexes.length; ++i)
key += "&" + stringIndexes[i];
Integer I = (Integer) indexMap.get(key);
if (I == null) {
I = new Integer(createNewVariable(key));
indexMap.put(key, I);
indexes[children.length] = I.intValue();
coefficients[children.length] = -children.length;
solver.addGreaterThanConstraint(indexes, coefficients, bound);
coefficients[children.length] = -1;
solver.addLessThanConstraint(indexes, coefficients,
bound + children.length - 1);
}
returnIndex = I.intValue();
returnNegation = false;
}
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalDisjunction c) {
PropositionalConstraint[] children =
(PropositionalConstraint[]) c.getChildren();
int[] indexes = null;
double[] coefficients = null;
double bound = 0;
if (topLevel) {
int subConstraintIndex = -1;
for (int i = 0; i < children.length && subConstraintIndex == -1; ++i) {
if (children[i] instanceof PropositionalVariable
|| children[i] instanceof PropositionalNegation)
continue;
if (children[i] instanceof PropositionalConjunction)
subConstraintIndex = i;
else if (children[i] instanceof PropositionalAtLeast)
subConstraintIndex = i;
}
if (subConstraintIndex > -1) {
PropositionalConstraint[] subChildren =
(PropositionalConstraint[])
children[subConstraintIndex].getChildren();
int multiplier =
children[subConstraintIndex] instanceof PropositionalConjunction
? subChildren.length
: ((PropositionalAtLeast) children[subConstraintIndex]).getM();
indexes = new int[subChildren.length + children.length - 1];
coefficients = new double[subChildren.length + children.length - 1];
bound = multiplier;
topLevel = false;
int j = 0;
for (int i = 0; i < children.length; ++i) {
if (i == subConstraintIndex) continue;
children[i].runVisit(this);
indexes[j] = returnIndex;
if (returnNegation) {
coefficients[j] = -multiplier;
bound -= multiplier;
}
else coefficients[j] = multiplier;
++j;
}
for (int i = 0; i < subChildren.length; ++i, ++j) {
subChildren[i].runVisit(this);
indexes[j] = returnIndex;
if (returnNegation) {
coefficients[j] = -1;
--bound;
}
else coefficients[j] = 1;
}
topLevel = true;
solver.addGreaterThanConstraint(indexes, coefficients, bound);
return;
}
}
if (topLevel) {
indexes = new int[children.length];
coefficients = new double[children.length];
bound = 1;
}
else {
indexes = new int[children.length + 1];
coefficients = new double[children.length + 1];
}
boolean saveTopLevel = topLevel;
topLevel = false;
for (int i = 0; i < children.length; ++i) {
children[i].runVisit(this);
indexes[i] = returnIndex;
if (returnNegation) {
coefficients[i] = -1;
--bound;
}
else coefficients[i] = 1;
}
topLevel = saveTopLevel;
if (topLevel)
solver.addGreaterThanConstraint(indexes, coefficients, bound);
else {
String[] stringIndexes = new String[children.length];
for (int i = 0; i < children.length; ++i)
stringIndexes[i] = (coefficients[i] < 0 ? "!" : "") + indexes[i];
Arrays.sort(stringIndexes);
String key = stringIndexes[0];
for (int i = 1; i < stringIndexes.length; ++i)
key += "|" + stringIndexes[i];
Integer I = (Integer) indexMap.get(key);
if (I == null) {
I = new Integer(createNewVariable(key));
indexMap.put(key, I);
indexes[children.length] = I.intValue();
coefficients[children.length] = -1;
solver.addGreaterThanConstraint(indexes, coefficients, bound);
coefficients[children.length] = -children.length;
solver.addLessThanConstraint(indexes, coefficients, bound);
}
returnIndex = I.intValue();
returnNegation = false;
}
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalAtLeast c) {
PropositionalConstraint[] children =
(PropositionalConstraint[]) c.getChildren();
int[] indexes = null;
double[] coefficients = null;
double bound = 0;
if (topLevel) {
indexes = new int[children.length];
coefficients = new double[children.length];
bound = c.getM();
}
else {
indexes = new int[children.length + 1];
coefficients = new double[children.length + 1];
}
boolean saveTopLevel = topLevel;
topLevel = false;
for (int i = 0; i < children.length; ++i) {
children[i].runVisit(this);
indexes[i] = returnIndex;
if (returnNegation) {
coefficients[i] = -1;
--bound;
}
else coefficients[i] = 1;
}
topLevel = saveTopLevel;
if (topLevel)
solver.addGreaterThanConstraint(indexes, coefficients, bound);
else {
String[] stringIndexes = new String[children.length];
for (int i = 0; i < children.length; ++i)
stringIndexes[i] = (coefficients[i] < 0 ? "!" : "") + indexes[i];
Arrays.sort(stringIndexes);
String key = "atl" + c.getM() + "of" + stringIndexes[0];
for (int i = 1; i < stringIndexes.length; ++i)
key += "&" + stringIndexes[i];
Integer I = (Integer) indexMap.get(key);
if (I == null) {
I = new Integer(createNewVariable(key));
indexMap.put(key, I);
indexes[children.length] = I.intValue();
coefficients[children.length] = -c.getM();
solver.addGreaterThanConstraint(indexes, coefficients, bound);
coefficients[children.length] = -children.length;
solver.addLessThanConstraint(indexes, coefficients,
bound + c.getM() - 1);
}
returnIndex = I.intValue();
returnNegation = false;
}
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalNegation c) {
assert c.constraint instanceof PropositionalVariable
: "GLPK: Negation of a " + c.constraint.getClass().getName()
+ " encountered.";
c.constraint.runVisit(this);
returnNegation = true;
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalVariable c) {
returnIndex = ((Integer) indexMap.get(c)).intValue();
returnNegation = false;
}
/**
* Derived classes override this method to do some type of processing on
* constraints of the parameter's type.
*
* @param c The constraint to process.
**/
public void visit(PropositionalConstant c) {
assert false : "GLPK: Constant encountered. (" + c.evaluate() + ")";
}
}