com.sri.ai.grinder.sgdpllt.api.Constraint Maven / Gradle / Ivy
package com.sri.ai.grinder.sgdpllt.api;
import static com.sri.ai.expresso.helper.Expressions.FALSE;
import static com.sri.ai.grinder.sgdpllt.library.boole.And.getConjuncts;
import static com.sri.ai.grinder.sgdpllt.library.boole.And.isConjunction;
import static com.sri.ai.util.Util.myAssert;
import java.util.List;
import com.sri.ai.expresso.api.Expression;
import com.sri.ai.grinder.sgdpllt.tester.SGDPLLTTester;
/**
* An {@link Expression} with efficient internal representation
* for satisfiability.
*
* As of August 2016, only implementations are conjunctive clauses,
* but eventually it should be expanded to arbitrary formulas.
*
* @author braz
*
*/
public interface Constraint extends Expression {
/**
* Returns the general theory used in the application.
*
* TODO
* This method should probably be eliminated.
* It used to be "the constraint's theory", but that meaning got blurred when compound constraint theories got introduced,
* because then the application theory could be a compound theory, whereas a single-variable constraint
* could be specific to just one of the sub-constraint theories.
* This caused problems because throughout the code we rely on this method to obtain the application's theory;
* when dealing with a single-variable constraint, this would instead give us its specific sub-theory.
* We changed it by making the method hold the general application theory even for single-variable constraints.
* This solved the problem but rendered the method misleading, since it does not represent a property of the constraint itself.
* It is now just a convenience method that spares us the trouble of passing the theory everywhere
* but, like stated above, at the cost of being misleading.
*
* @return the application's theory.
*/
Theory getTheory();
/**
* Returns an {@link SGDPLLTTester} representing the conjunction of this constraint and
* a given literal, or null if they are contradictory.
*
* At this point, the formula should be either a literal, or a {@link Constraint}.
*
* @param literal the literal to be conjoined.
* @param context the context
* @return the application result or null
if contradiction.
*/
Constraint conjoinWithLiteral(Expression literal, Context context);
/**
* Returns an {@link SGDPLLTTester} representing the conjunction of this constraint and
* a given formula, or null if they are contradictory.
*
* At this point, the formula should be either a literal, or a {@link Constraint}.
*
* Extensions may want to override this method if there are more efficient ways
* of conjoining with (certain types of) constraints than simply treating them as a formula
*
* @param formula the formula to be conjoined.
* @param context the context
* @return the application result or null
if contradiction.
*/
default Constraint conjoin(Expression formula, Context context) {
myAssert(
() -> isValidConjoinant(formula, context),
() -> this.getClass() + " currently only supports conjoining with literals, conjunctive clauses, and constraints, but received " + formula);
Constraint result;
if (isContradiction(formula)) {
result = makeContradiction();
}
// Warning: tempting, but inefficient because equals(TRUE) forces constraint's expression to be generated, which may be expensive if it's not being generated anywhere else. If going this route, may make sense to define a isTautology method that tests the same thing without generating expression
// else if (formula instanceof Constraint && this.equals(TRUE)) {
// result = (Constraint) formula;
// }
else if (formula instanceof Constraint || isConjunction(formula)) {
result = conjoinWithConjunctiveClause(formula, context); // for now, all Constraints are conjunctions. This will probably change in the future.
}
else {
result = conjoinWithLiteral(formula, context);
}
return result;
}
/**
* Tests whether an expression is a contradiction (that is, equal to false),
* by first checking if it is a {@link Constraint} and using {@link #isContradiction()},
* and only if that fails, using equals(FALSE)
.
* This avoids the unnecessary generation of an {@link Expression} form for {@link Constraint}s
* that only make it under demand.
* @param formula
* @return
*/
default boolean isContradiction(Expression formula) {
return (formula instanceof Constraint && ((Constraint)formula).isContradiction()) || formula.equals(FALSE);
}
/**
* @param formula
* @param context
* @return
*/
default boolean isValidConjoinant(Expression formula, Context context) {
boolean result =
formula == null
|| formula instanceof Constraint
|| getTheory().isConjunctiveClause(formula, context);
return result;
}
/**
* Returns the result of conjoining this constraint with all literal conjuncts of a given conjunctive clause
* (note that if conjunction
is not an application of and
,
* it will be considered a unit conjunction with itself the only conjunct.
* @param conjunctiveClause
* @param context
* @return the result of conjoining this constraint with all conjuncts of a given conjunction
*/
default Constraint conjoinWithConjunctiveClause(Expression conjunctiveClause, Context context) {
Constraint result;
List conjuncts = getConjuncts(conjunctiveClause);
if (conjuncts.size() == 1) { // this is necessary to avoid an infinite loop
result = conjoinWithLiteral(conjuncts.get(0), context);
}
else {
result = this;
for (Expression literal : conjuncts) {
result = result.conjoin(literal, context);
if (result == null) {
break;
}
}
}
return result;
}
/**
* Returns an expression to which the given variable is bound to
* under this constraint, if there is such a value and it can be determined by the implementation.
* @param variable
* @return an expression to which variable is bound, or null if there is no such value
*/
Expression binding(Expression variable);
/**
* Indicates whether constraint is contradiction.
* @return
*/
boolean isContradiction();
/**
* Make contradictory version of this constraint;
* this means the contradiction is of the same "type" as this contradiction
* (for example, same theory, among other details),
* but is a contradiction.
* @return
*/
Constraint makeContradiction();
// /**
// * Returns, in time constant in the size of the constraint,
// * a pair of {@link Constraint}s whose conjunction is equivalent to this constraint,
// * such that the given variables only occur in the second one.
// *
// * Note that there may be multiple such decompositions,
// * and that Pair(new {@link ExpressionConstraint}(TRUE), this)
is such a decomposition.
// * Implementations must seek to minimize the size of the second constraint while keeping
// * time constant in the size of the original constraint.
// *
// * The point of this operation is to isolate the variables in the second constraint,
// * while preserving as much internal efficient representation about the remaining variables
// * in the first constraint.
// *
// * For example, suppose we have a complex, efficiently represented constraint
// * C
on variables X,Y
, which gets conjoined with an
// * also efficiently represented constraint C'
in Z
// * (which could involve X
or Y
or both),
// * producing a new constraint C''
.
// * Ideally, the internal representation of C''
preserves
// * the original efficient representations.
// * If now we want to compute, say, there exists Z : C''
// * @return
// */
// PairOf decomposeInConstantTime(Collection variables);
//
// /**
// * Given a sub-set of supported indices, projects the constraint onto the remaining ones.
// * Resulting constraint still supports all original indices.
// * Default implementation uses symbolic satisfiability through {@link SGDPLLT}.
// * Specific constraint implementations will typically have more efficient ways to do it.
// */
// default Constraint project(Collection eliminatedIndices, Context context) {
// Expression resultExpression =
// SymbolicSolver.solve(
// new Conjunction(),
// eliminatedIndices,
// condition,
// body,
// getTheory().makeSingleVariableConstraint(null),
// context);
// // note that solvers should be aware that their input or part of their input may be a Constraint, and take advantage of the internal representations already present in them, instead of simply converting them to an Expression and redoing all the work.
// Collection remainingSupportedIndices = Util.subtract(getSupportedIndices(), eliminatedIndices);
// Constraint result = ExpressionConstraint.wrap(getTheory(), remainingSupportedIndices, resultExpression);
// return result;
// }
}