org.sat4j.pb.tools.DependencyHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.sat4j.pb Show documentation
Show all versions of org.sat4j.pb Show documentation
The pb library contains algorithms for solving pseudo boolean optimization problems.
The newest version!
/*******************************************************************************
* SAT4J: a SATisfiability library for Java Copyright (C) 2004-2008 Daniel Le Berre
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU Lesser General Public License Version 2.1 or later (the
* "LGPL"), in which case the provisions of the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of the LGPL, and not to allow others to use your version of
* this file under the terms of the EPL, indicate your decision by deleting
* the provisions above and replace them with the notice and other provisions
* required by the LGPL. If you do not delete the provisions above, a recipient
* may use your version of this file under the terms of the EPL or the LGPL.
*******************************************************************************/
package org.sat4j.pb.tools;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.sat4j.core.Vec;
import org.sat4j.core.VecInt;
import org.sat4j.pb.IPBSolver;
import org.sat4j.pb.ObjectiveFunction;
import org.sat4j.specs.ContradictionException;
import org.sat4j.specs.IConstr;
import org.sat4j.specs.IVec;
import org.sat4j.specs.IVecInt;
import org.sat4j.specs.TimeoutException;
import org.sat4j.tools.GateTranslator;
/**
* Helper class intended to make life easier to people to feed a sat solver
* programmatically.
*
* @author daniel
*
* @param
* The class of the objects to map into boolean variables.
* @param
* The class of the object to map to each constraint.
*/
public class DependencyHelper {
public static final INegator NO_NEGATION = new INegator() {
public boolean isNegated(Object thing) {
return false;
}
public Object unNegate(Object thing) {
return thing;
}
};
public static final INegator BASIC_NEGATION = new INegator() {
public boolean isNegated(Object thing) {
return (thing instanceof Negation);
}
public Object unNegate(Object thing) {
return ((Negation) thing).getThing();
}
};
private static final class Negation {
private final Object thing;
Negation(Object thing) {
this.thing = thing;
}
Object getThing() {
return thing;
}
}
/**
*
*/
private static final long serialVersionUID = 1L;
private final Map mapToDimacs = new HashMap();
private final Map mapToDomain = new HashMap();
final Map descs = new HashMap();
private final XplainPB xplain;
private final GateTranslator gator;
final IPBSolver solver;
private INegator negator = BASIC_NEGATION;
private ObjectiveFunction objFunction;
private IVecInt objLiterals;
private IVec objCoefs;
private final boolean explanationEnabled;
private final boolean canonicalOptFunction;
/**
*
* @param solver
* the solver to be used to solve the problem.
*/
public DependencyHelper(IPBSolver solver) {
this(solver, true);
}
/**
*
* @param solver
* the solver to be used to solve the problem.
* @param explanationEnabled
* if true, will add one new variable per constraint to allow the
* solver to compute an explanation in case of failure. Default
* is true. Usually, this is set to false when one wants to check
* the encoding produced by the helper.
*/
public DependencyHelper(IPBSolver solver, boolean explanationEnabled) {
this(solver, explanationEnabled, true);
}
/**
*
* @param solver
* the solver to be used to solve the problem.
* @param explanationEnabled
* if true, will add one new variable per constraint to allow the
* solver to compute an explanation in case of failure. Default
* is true. Usually, this is set to false when one wants to check
* the encoding produced by the helper.
* @param canonicalOptFunctionEnabled
* when set to true, the objective function sum up all the
* coefficients for a given literal. The default is true. It is
* useful to set it to false when checking the encoding produced
* by the helper.
* @since 2.2
*/
public DependencyHelper(IPBSolver solver, boolean explanationEnabled,
boolean canonicalOptFunctionEnabled) {
if (explanationEnabled) {
this.xplain = new XplainPB(solver);
this.solver = this.xplain;
} else {
this.xplain = null;
this.solver = solver;
}
this.gator = new GateTranslator(this.solver);
this.explanationEnabled = explanationEnabled;
canonicalOptFunction = canonicalOptFunctionEnabled;
}
public void setNegator(INegator negator) {
this.negator = negator;
}
/**
* Translate a domain object into a dimacs variable.
*
* @param thing
* a domain object
* @return the dimacs variable (an integer) representing that domain object.
*/
protected int getIntValue(T thing) {
return getIntValue(thing, true);
}
/**
* Translate a domain object into a dimacs variable.
*
* @param thing
* a domain object
* @param create
* to allow or not the solver to create a new id if the object in
* unknown. If set to false, the method will throw an
* IllegalArgumentException if the object is unknown.
* @return the dimacs variable (an integer) representing that domain object.
*/
protected int getIntValue(T thing, boolean create) {
T myThing;
boolean negated = negator.isNegated(thing);
if (negated) {
myThing = (T) negator.unNegate(thing);
} else {
myThing = thing;
}
Integer intValue = mapToDimacs.get(myThing);
if (intValue == null) {
if (create) {
intValue = solver.nextFreeVarId(true);
mapToDomain.put(intValue, myThing);
mapToDimacs.put(myThing, intValue);
} else {
throw new IllegalArgumentException("" + myThing
+ " is unknown in the solver!");
}
}
if (negated) {
return -intValue;
}
return intValue;
}
/**
* Retrieve the solution found.
*
* Note that this method returns a Sat4j specific data structure (IVec).
* People willing to retrieve a more common data structure should use
* {@link #getASolution()} instead.
*
* THAT METHOD IS EXPECTED TO BE CALLED IF hasASolution() RETURNS TRUE.
*
* @return the domain object that must be satisfied to satisfy the
* constraints entered in the solver.
* @see {@link #hasASolution()}
* @see {@link #getASolution()}
*/
public IVec getSolution() {
int[] model = solver.model();
IVec toInstall = new Vec();
if (model != null) {
for (int i : model) {
if (i > 0) {
toInstall.push(mapToDomain.get(i));
}
}
}
return toInstall;
}
/**
* Retrieve a collection of objects satisfying the constraints.
*
* It behaves basically the same as {@link #getSolution()} but returns a
* common Collection instead of a Sat4j specific IVec.
*
* THAT METHOD IS EXPECTED TO BE CALLED IF hasASolution() RETURNS TRUE.
*
* @return the domain object that must be satisfied to satisfy the
* constraints entered in the solver.
* @see {@link #hasASolution()}
* @see {@link #getSolution()}
* @since 2.3.1
*/
public Collection getASolution() {
int[] model = solver.model();
Collection toInstall = new ArrayList();
if (model != null) {
for (int i : model) {
if (i > 0) {
toInstall.add(mapToDomain.get(i));
}
}
}
return toInstall;
}
public BigInteger getSolutionCost() {
return objFunction.calculateDegree(solver);
}
/**
* Retrieve the boolean value associated with a domain object in the
* solution found by the solver. THAT METHOD IS EXPECTED TO BE CALLED IF
* hasASolution() RETURNS TRUE.
*
* @param t
* a domain object
* @return true iff the domain object has been set to true in the current
* solution.
* @throws IllegalArgumentException
* If the argument of the method is unknown to the solver.
*/
public boolean getBooleanValueFor(T t) {
return solver.model(getIntValue(t, false));
}
/**
*
* @return true if the set of constraints entered inside the solver can be
* satisfied.
* @throws TimeoutException
*/
public boolean hasASolution() throws TimeoutException {
return solver.isSatisfiable();
}
/**
*
* @return true if the set of constraints entered inside the solver can be
* satisfied.
* @throws TimeoutException
*/
public boolean hasASolution(IVec assumps) throws TimeoutException {
IVecInt assumptions = new VecInt();
for (Iterator it = assumps.iterator(); it.hasNext();) {
assumptions.push(getIntValue(it.next()));
}
return solver.isSatisfiable(assumptions);
}
/**
*
* @return true if the set of constraints entered inside the solver can be
* satisfied.
* @throws TimeoutException
*/
public boolean hasASolution(Collection assumps) throws TimeoutException {
IVecInt assumptions = new VecInt();
for (T t : assumps) {
assumptions.push(getIntValue(t));
}
return solver.isSatisfiable(assumptions);
}
/**
* Explain the reason of the inconsistency of the set of constraints.
*
* THAT METHOD IS EXPECTED TO BE CALLED IF hasASolution() RETURNS FALSE.
*
* @return a set of objects used to "name" each constraint entered in the
* solver.
* @throws TimeoutException
* @see {@link #hasASolution()}
*/
public Set why() throws TimeoutException {
if (!explanationEnabled) {
throw new UnsupportedOperationException("Explanation not enabled!");
}
Collection explanation = xplain.explain();
Set ezexplain = new TreeSet();
for (IConstr constr : explanation) {
C desc = descs.get(constr);
if (desc != null) {
ezexplain.add(desc);
}
}
return ezexplain;
}
/**
* Explain a domain object has been set to true in a solution.
*
* @return a set of objects used to "name" each constraint entered in the
* solver.
* @throws TimeoutException
* @see {@link #hasASolution()}
*/
public Set why(T thing) throws TimeoutException {
IVecInt assumps = new VecInt();
assumps.push(-getIntValue(thing));
return why(assumps);
}
/**
* Explain a domain object has been set to false in a solution.
*
* @return a set of objects used to "name" each constraint entered in the
* solver.
* @throws TimeoutException
* @see {@link #hasASolution()}
*/
public Set whyNot(T thing) throws TimeoutException {
IVecInt assumps = new VecInt();
assumps.push(getIntValue(thing));
return why(assumps);
}
private Set why(IVecInt assumps) throws TimeoutException {
if (xplain.isSatisfiable(assumps)) {
return new TreeSet();
}
return why();
}
/**
* Add a constraint to set the value of a domain object to true.
*
* @param thing
* the domain object
* @param name
* the name of the constraint, to be used in an explanation if
* needed.
* @throws ContradictionException
* if the set of constraints appears to be trivially
* inconsistent.
*/
public void setTrue(T thing, C name) throws ContradictionException {
IConstr constr = gator.gateTrue(getIntValue(thing));
if (constr != null) {
descs.put(constr, name);
}
}
/**
* Add a constraint to set the value of a domain object to false.
*
* @param thing
* the domain object
* @param name
* the name of the constraint, to be used in an explanation if
* needed.
* @throws ContradictionException
* if the set of constraints appears to be trivially
* inconsistent.
*/
public void setFalse(T thing, C name) throws ContradictionException {
IConstr constr = gator.gateFalse(getIntValue(thing));
// constraints duplication detection may end up with null constraint
if (constr != null) {
descs.put(constr, name);
}
}
/**
* Create a logical implication of the form lhs -> rhs
*
* @param lhs
* some domain objects. They form a conjunction in the left hand
* side of the implication.
* @return the right hand side of the implication.
*/
public ImplicationRHS implication(T... lhs) {
IVecInt clause = new VecInt();
for (T t : lhs) {
clause.push(-getIntValue(t));
}
return new ImplicationRHS(this, clause);
}
public DisjunctionRHS disjunction(T... lhs) {
IVecInt literals = new VecInt();
for (T t : lhs) {
literals.push(-getIntValue(t));
}
return new DisjunctionRHS(this, literals);
}
/**
* Create a constraint stating that at most i domain object should be set to
* true.
*
* @param i
* the maximum number of domain object to set to true.
* @param things
* the domain objects.
* @return an object used to name the constraint. The constraint MUST BE
* NAMED.
* @throws ContradictionException
*/
public void atLeast(C name, int i, T... things)
throws ContradictionException {
IVecInt literals = new VecInt();
for (T t : things) {
literals.push(getIntValue(t));
}
descs.put(solver.addAtLeast(literals, i), name);
}
/**
* Create a constraint stating that at most i domain object should be set to
* true.
*
* @param i
* the maximum number of domain object to set to true.
* @param things
* the domain objects.
* @return an object used to name the constraint. The constraint MUST BE
* NAMED.
* @throws ContradictionException
*/
public ImplicationNamer atMost(int i, T... things)
throws ContradictionException {
IVec toName = new Vec();
IVecInt literals = new VecInt();
for (T t : things) {
literals.push(getIntValue(t));
}
toName.push(solver.addAtMost(literals, i));
return new ImplicationNamer(this, toName);
}
/**
* Create a constraint stating that at most i domain object should be set to
* true.
*
* @param i
* the maximum number of domain object to set to true.
* @param things
* the domain objects.
* @return an object used to name the constraint. The constraint MUST BE
* NAMED.
* @throws ContradictionException
*/
public void atMost(C name, int i, T... things)
throws ContradictionException {
IVecInt literals = new VecInt();
for (T t : things) {
literals.push(getIntValue(t));
}
descs.put(solver.addAtMost(literals, i), name);
}
/**
* Create a clause (thing1 or thing 2 ... or thingn)
*
* @param name
* @param things
*
* @throws ContradictionException
*/
public void clause(C name, T... things) throws ContradictionException {
IVecInt literals = new VecInt(things.length);
for (T t : things) {
literals.push(getIntValue(t));
}
IConstr constr = gator.addClause(literals);
// constr can be null if duplicated clauses are detected.
if (constr != null) {
descs.put(constr, name);
}
}
/**
* Create a constraint using equivalency chains thing <=> (thing1 <=> thing2
* <=> ... <=> thingn)
*
* @param thing
* a domain object
* @param things
* other domain objects.
* @throws ContradictionException
*/
public void iff(C name, T thing, T... otherThings)
throws ContradictionException {
IVecInt literals = new VecInt(otherThings.length);
for (T t : otherThings) {
literals.push(getIntValue(t));
}
IConstr[] constrs = gator.iff(getIntValue(thing), literals);
for (IConstr constr : constrs) {
if (constr != null) {
descs.put(constr, name);
}
}
}
/**
* Create a constraint of the form thing <=> (thing1 and thing 2 ... and
* thingn)
*
* @param name
* @param thing
* @param otherThings
* @throws ContradictionException
*/
public void and(C name, T thing, T... otherThings)
throws ContradictionException {
IVecInt literals = new VecInt(otherThings.length);
for (T t : otherThings) {
literals.push(getIntValue(t));
}
IConstr[] constrs = gator.and(getIntValue(thing), literals);
for (IConstr constr : constrs) {
if (constr != null) {
descs.put(constr, name);
}
}
}
/**
* Create a constraint of the form thing <=> (thing1 or thing 2 ... or
* thingn)
*
* @param name
* @param thing
* @param otherThings
* @throws ContradictionException
*/
public void or(C name, T thing, T... otherThings)
throws ContradictionException {
IVecInt literals = new VecInt(otherThings.length);
for (T t : otherThings) {
literals.push(getIntValue(t));
}
IConstr[] constrs = gator.or(getIntValue(thing), literals);
for (IConstr constr : constrs) {
if (constr != null) {
descs.put(constr, name);
}
}
}
/**
* Create a constraint of the form thing <= (thing1 or thing 2 ... or
* thingn)
*
* @param name
* @param thing
* @param otherThings
* @throws ContradictionException
*/
public void halfOr(C name, T thing, T... otherThings)
throws ContradictionException {
IVecInt literals = new VecInt(otherThings.length);
for (T t : otherThings) {
literals.push(getIntValue(t));
}
IConstr[] constrs = gator.halfOr(getIntValue(thing), literals);
for (IConstr constr : constrs) {
if (constr != null) {
descs.put(constr, name);
}
}
}
/**
* Create a constraint of the form thing <=> (if conditionThing then
* thenThing else elseThing)
*
* @param name
* @param thing
* @param otherThings
* @throws ContradictionException
*/
public void ifThenElse(C name, T thing, T conditionThing, T thenThing,
T elseThing) throws ContradictionException {
IConstr[] constrs = gator.ite(getIntValue(thing),
getIntValue(conditionThing), getIntValue(thenThing),
getIntValue(elseThing));
for (IConstr constr : constrs) {
if (constr != null) {
descs.put(constr, name);
}
}
}
/**
* Add an objective function to ask for a solution that minimize the
* objective function.
*
* @param wobj
* a set of weighted objects (pairs of domain object and
* BigInteger).
*/
public void setObjectiveFunction(WeightedObject... wobj) {
createObjectivetiveFunctionIfNeeded(wobj.length);
for (WeightedObject wo : wobj) {
addProperly(wo.thing, wo.getWeight());
}
}
private void addProperly(T thing, BigInteger weight) {
int lit = getIntValue(thing);
int index;
if (canonicalOptFunction && (index = objLiterals.indexOf(lit)) != -1) {
objCoefs.set(index, objCoefs.get(index).add(weight));
} else {
objLiterals.push(lit);
objCoefs.push(weight);
}
}
private void createObjectivetiveFunctionIfNeeded(int n) {
if (objFunction == null) {
objLiterals = new VecInt(n);
objCoefs = new Vec(n);
objFunction = new ObjectiveFunction(objLiterals, objCoefs);
solver.setObjectiveFunction(objFunction);
}
}
/**
* Add a weighted literal to the objective function.
*
* @param thing
* @param weight
*/
public void addToObjectiveFunction(T thing, int weight) {
addToObjectiveFunction(thing, BigInteger.valueOf(weight));
}
/**
* Add a weighted literal to the objective function.
*
* @param thing
* @param weight
*/
public void addToObjectiveFunction(T thing, BigInteger weight) {
createObjectivetiveFunctionIfNeeded(20);
addProperly(thing, weight);
}
/**
* Create a PB constraint of the form
* w1.l1 + w2.l2 + ... wn.ln >= degree
*
where wi are position integers and li are domain objects.
*
* @param degree
* @param wobj
* @throws ContradictionException
*/
public void atLeast(C name, BigInteger degree, WeightedObject... wobj)
throws ContradictionException {
IVecInt literals = new VecInt(wobj.length);
IVec coeffs = new Vec(wobj.length);
for (WeightedObject wo : wobj) {
literals.push(getIntValue(wo.thing));
coeffs.push(wo.getWeight());
}
descs.put(solver.addPseudoBoolean(literals, coeffs, true, degree), name);
}
/**
* Create a PB constraint of the form
* w1.l1 + w2.l2 + ... wn.ln <= degree
*
where wi are position integers and li are domain objects.
*
* @param degree
* @param wobj
* @throws ContradictionException
*/
public void atMost(C name, BigInteger degree, WeightedObject... wobj)
throws ContradictionException {
IVecInt literals = new VecInt(wobj.length);
IVec coeffs = new Vec(wobj.length);
for (WeightedObject wo : wobj) {
literals.push(getIntValue(wo.thing));
coeffs.push(wo.getWeight());
}
descs.put(solver.addPseudoBoolean(literals, coeffs, false, degree),
name);
}
public void atMost(C name, int degree, WeightedObject... wobj)
throws ContradictionException {
atMost(name, BigInteger.valueOf(degree), wobj);
}
/**
* Stop the SAT solver that is looking for a solution. The solver will throw
* a TimeoutException.
*/
public void stopSolver() {
solver.expireTimeout();
}
/**
* Stop the explanation computation. A TimeoutException will be thrown by
* the explanation algorithm.
*/
public void stopExplanation() {
if (!explanationEnabled) {
throw new UnsupportedOperationException("Explanation not enabled!");
}
xplain.cancelExplanation();
}
public void discard(IVec things) throws ContradictionException {
IVecInt literals = new VecInt(things.size());
for (Iterator it = things.iterator(); it.hasNext();) {
literals.push(-getIntValue(it.next()));
}
solver.addBlockingClause(literals);
}
public void discardSolutionsWithObjectiveValueGreaterThan(long value)
throws ContradictionException {
ObjectiveFunction obj = solver.getObjectiveFunction();
IVecInt literals = new VecInt(obj.getVars().size());
obj.getVars().copyTo(literals);
IVec coeffs = new Vec(obj.getCoeffs().size());
obj.getCoeffs().copyTo(coeffs);
solver.addPseudoBoolean(literals, coeffs, false,
BigInteger.valueOf(value));
}
public String getObjectiveFunction() {
ObjectiveFunction obj = solver.getObjectiveFunction();
StringBuffer stb = new StringBuffer();
for (int i = 0; i < obj.getVars().size(); i++) {
stb.append(obj.getCoeffs().get(i)
+ (obj.getVars().get(i) > 0 ? " " : "~")
+ mapToDomain.get(Math.abs(obj.getVars().get(i))) + " ");
}
return stb.toString();
}
public int getNumberOfVariables() {
return mapToDimacs.size();
}
public int getNumberOfConstraints() {
return descs.size();
}
public Map getMappingToDomain() {
return mapToDomain;
}
public Object not(T thing) {
return new Negation(thing);
}
/**
* @since 2.2
* @return the IPBSolver enclosed in the helper.
*/
public IPBSolver getSolver() {
if (explanationEnabled)
return xplain.decorated();
return solver;
}
/**
* Reset the state of the helper (mapping, objective function, etc).
*
* @since 2.3.1
*/
public void reset() {
mapToDimacs.clear();
mapToDomain.clear();
descs.clear();
solver.reset();
if (objLiterals != null) {
objLiterals.clear();
objCoefs.clear();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy