package LBJ2.infer;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import LBJ2.classify.Score;
import LBJ2.classify.ScoreSet;
/**
* This class employs an {@link ILPSolver} to solve a constrained inference
* problem. When constructing an instance of this class in an LBJ source
* file, use one of the constructors that does <i>not</i> specify a head
* object. The generated code will fill in the head object automatically.
* The other constructor parameters are used to specify the ILP algorithm and
* enable textual output of ILP variable descriptions to <code>STDOUT</code>.
* Textual output of the ILP problem itself is controlled by the
* {@link ILPSolver}.
*
* @author Nick Rizzolo
**/
public class ILPInference extends Inference
{
/** A possible setting for {@link #verbosity}. */
public static final int VERBOSITY_NONE = 0;
/** A possible setting for {@link #verbosity}. */
public static final int VERBOSITY_LOW = 1;
/** A possible setting for {@link #verbosity}. */
public static final int VERBOSITY_HIGH = 2;
/** Keeps the next ID number for objects of this class. */
protected static int nextID = 0;
/** The identification number for this object, used in debug file names. */
protected int ID;
/** The ILP algorithm. */
protected ILPSolver solver;
/** This flag is set if the constraints turn out to be true in all cases. */
protected boolean tautology;
/**
* 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;
/**
* Verbosity level. {@link ILPInference#VERBOSITY_NONE} produces no
* incidental output. If set to {@link ILPInference#VERBOSITY_LOW}, only
* timing information is printed on <code>STDOUT</code>. If set to
* {@link ILPInference#VERBOSITY_HIGH}, information mapping the generated
* ILP variables to the first order variables they were generated from and
* their settings in the ILP problem's solution is printed to
* <code>STDOUT</code>.
**/
protected int verbosity;
/** Don't use this constructor, since it doesn't set an ILP algorithm. */
public ILPInference() { this(null); }
/**
* Initializes the ILP algorithm, but not the head object.
*
* @param a The ILP algorithm.
**/
public ILPInference(ILPSolver a) { this(null, a); }
/**
* Initializes the ILP algorithm, but not the head object.
*
* @param a The ILP algorithm.
* @param v Sets the value of {@link #verbosity}.
**/
public ILPInference(ILPSolver a, int v) { this(null, a, v); }
/** Don't use this constructor, since it doesn't set an ILP algorithm. */
public ILPInference(Object h) { this(h, null); }
/**
* Sets the head object and the ILP algorithm.
*
* @param h The head object.
* @param a The ILP algorithm.
**/
public ILPInference(Object h, ILPSolver a) { this(h, a, VERBOSITY_NONE); }
/**
* Sets the head object and the ILP algorithm.
*
* @param h The head object.
* @param a The ILP algorithm.
* @param v Sets the value of {@link #verbosity}.
**/
public ILPInference(Object h, ILPSolver a, int v) {
super(h);
solver = a;
verbosity = v;
ID = nextID++;
}
/**
* Adds a constraint to the inference.
*
* @param c The constraint to add.
**/
public void addConstraint(FirstOrderConstraint c) {
solver.reset();
if (constraint == null) constraint = c;
else
constraint =
new FirstOrderConjunction((FirstOrderConstraint) constraint, c);
}
/**
* Uses the provided ILP algorithm to solve the ILP proglem if it hasn't
* already been solved.
**/
protected void infer() throws Exception {
if (tautology || solver.isSolved()) return;
solver.setMaximize(true);
constraint.consolidateVariables(variables);
indexMap = new HashMap();
if (verbosity > VERBOSITY_NONE)
System.out.println("variables: (" + new Date() + ")");
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. ILP inference cannot be "
+ "performed.");
System.exit(1);
}
int[] indexes = solver.addDiscreteVariable(scores);
for (int j = 0; j < scores.length; ++j) {
indexMap.put(
new PropositionalVariable(v.getClassifier(), v.getExample(),
scores[j].value),
new Integer(indexes[j]));
if (verbosity >= VERBOSITY_HIGH) {
StringBuffer toPrint = new StringBuffer();
toPrint.append("x_");
toPrint.append(indexes[j]);
while (toPrint.length() < 8) toPrint.insert(0, ' ');
toPrint.append(" (");
toPrint.append(scores[j].score);
toPrint.append("): ");
toPrint.append(v.getClassifier());
toPrint.append("(");
toPrint.append(Inference.exampleToString(v.getExample()));
toPrint.append(") == ");
toPrint.append(scores[j].value);
System.out.println(toPrint);
}
}
}
if (verbosity > VERBOSITY_NONE)
System.out.println("propositionalization: (" + new Date() + ")");
PropositionalConstraint propositional =
((FirstOrderConstraint) constraint).propositionalize();
if (verbosity > VERBOSITY_NONE)
System.out.println("simplification: (" + new Date() + ")");
if (propositional instanceof PropositionalConjunction)
propositional =
((PropositionalConjunction) propositional).simplify(true);
else propositional = propositional.simplify();
if (propositional instanceof PropositionalConstant) {
if (propositional.evaluate()) {
tautology = true;
return;
}
else {
System.err.println("ILP ERROR: Unsatisfiable constraints!");
solver.addEqualityConstraint(new int[]{ 0 }, new double[]{ 1 }, 2);
}
}
if (verbosity > VERBOSITY_NONE)
System.out.println("translation: (" + new Date() + ")");
topLevel = true;
propositional.runVisit(this);
if (verbosity > VERBOSITY_NONE)
System.out.println("solution: (" + new Date() + ")");
if (!solver.solve()) throw new InferenceNotOptimalException(solver, head);
int variableIndex = 0;
if (verbosity > VERBOSITY_NONE)
System.out.println("variables set true in solution: (" + new Date()
+ ")");
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);
if (verbosity >= VERBOSITY_HIGH) {
StringBuffer toPrint = new StringBuffer();
toPrint.append("x_");
toPrint.append(variableIndex);
while (toPrint.length() < 8) toPrint.insert(0, ' ');
toPrint.append(": ");
toPrint.append(v);
System.out.println(toPrint);
}
}
}
}
/**
* 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 ILPInference)) return false;
return head == ((ILPInference) o).head;
}
/**
* Simply returns the <code>head</code>'s hash code.
*
* @see java.lang.Object#hashCode()
**/
public int hashCode() { return head.hashCode(); }
/**
* Creates a new Boolean variable to represent the value of a subexpression
* of some constraint.
*
* @param d A textual description of the subexpression whose value is
* represented by the new variable.
* @return The index of the new variable.
**/
protected int createVariable(String d) {
int result = solver.addBooleanVariable(0);
if (verbosity >= 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 : "ILP: 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 : "ILP: 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(createVariable(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(createVariable(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(createVariable(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
: "ILP: 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 : "ILP: Constant encountered. (" + c.evaluate() + ")";
}
}