View Javadoc

1   /*******************************************************************************
2    * SAT4J: a SATisfiability library for Java Copyright (C) 2004-2008 Daniel Le Berre
3    *
4    * All rights reserved. This program and the accompanying materials
5    * are made available under the terms of the Eclipse Public License v1.0
6    * which accompanies this distribution, and is available at
7    * http://www.eclipse.org/legal/epl-v10.html
8    *
9    * Alternatively, the contents of this file may be used under the terms of
10   * either the GNU Lesser General Public License Version 2.1 or later (the
11   * "LGPL"), in which case the provisions of the LGPL are applicable instead
12   * of those above. If you wish to allow use of your version of this file only
13   * under the terms of the LGPL, and not to allow others to use your version of
14   * this file under the terms of the EPL, indicate your decision by deleting
15   * the provisions above and replace them with the notice and other provisions
16   * required by the LGPL. If you do not delete the provisions above, a recipient
17   * may use your version of this file under the terms of the EPL or the LGPL.
18   *******************************************************************************/
19  
20  package org.sat4j.pb.tools;
21  
22  import java.math.BigInteger;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.TreeSet;
30  
31  import org.sat4j.core.Vec;
32  import org.sat4j.core.VecInt;
33  import org.sat4j.pb.IPBSolver;
34  import org.sat4j.pb.ObjectiveFunction;
35  import org.sat4j.specs.ContradictionException;
36  import org.sat4j.specs.IConstr;
37  import org.sat4j.specs.IVec;
38  import org.sat4j.specs.IVecInt;
39  import org.sat4j.specs.TimeoutException;
40  import org.sat4j.tools.GateTranslator;
41  
42  /**
43   * Helper class intended to make life easier to people to feed a sat solver
44   * programmatically.
45   * 
46   * @author daniel
47   * 
48   * @param <T>
49   *            The class of the objects to map into boolean variables.
50   * @param <C>
51   *            The class of the object to map to each constraint.
52   */
53  public class DependencyHelper<T, C> {
54  
55  	public final INegator<T> NO_NEGATION = new INegator<T>() {
56  
57  		public boolean isNegated(T thing) {
58  			return false;
59  		}
60  
61  		public T unNegate(T thing) {
62  			return thing;
63  		}
64  	};
65  
66  	/**
67  	 * 
68  	 */
69  	private static final long serialVersionUID = 1L;
70  
71  	private final Map<T, Integer> mapToDimacs = new HashMap<T, Integer>();
72  	private final Map<Integer, T> mapToDomain = new HashMap<Integer, T>();
73  	final Map<IConstr, C> descs = new HashMap<IConstr, C>();
74  
75  	private final XplainPB xplain;
76  	private final GateTranslator gator;
77  	final IPBSolver solver;
78  	private INegator<T> negator = NO_NEGATION;
79  
80  	private ObjectiveFunction objFunction;
81  	private IVecInt objLiterals;
82  	private IVec<BigInteger> objCoefs;
83  
84  	public boolean explanationEnabled = true;
85  
86  	/**
87  	 * 
88  	 * @param solver
89  	 *            the solver to be used to solve the problem.
90  	 */
91  	public DependencyHelper(IPBSolver solver) {
92  		this(solver, true);
93  	}
94  
95  	public DependencyHelper(IPBSolver solver, boolean explanationEnabled) {
96  		if (explanationEnabled) {
97  			this.xplain = new XplainPB(solver);
98  			this.solver = this.xplain;
99  		} else {
100 			this.xplain = null;
101 			this.solver = solver;
102 		}
103 		this.gator = new GateTranslator(this.solver);
104 	}
105 
106 	public void setNegator(INegator<T> negator) {
107 		this.negator = negator;
108 	}
109 
110 	/**
111 	 * Translate a domain object into a dimacs variable.
112 	 * 
113 	 * @param thing
114 	 *            a domain object
115 	 * @return the dimacs variable (an integer) representing that domain object.
116 	 */
117 	int getIntValue(T thing) {
118 		T myThing;
119 		boolean negated = negator.isNegated(thing);
120 		if (negated) {
121 			myThing = negator.unNegate(thing);
122 		} else {
123 			myThing = thing;
124 		}
125 		Integer intValue = mapToDimacs.get(myThing);
126 		if (intValue == null) {
127 			intValue = solver.nextFreeVarId(true);
128 			mapToDomain.put(intValue, thing);
129 			mapToDimacs.put(thing, intValue);
130 		}
131 		if (negated) {
132 			return -intValue;
133 		}
134 		return intValue;
135 	}
136 
137 	/**
138 	 * Retrieve the solution found.
139 	 * 
140 	 * THAT METHOD IS EXPECTED TO BE CALLED IF hasASolution() RETURNS TRUE.
141 	 * 
142 	 * @return the domain object that must be satisfied to satisfy the
143 	 *         constraints entered in the solver.
144 	 * @see {@link #hasASolution()}
145 	 */
146 	public IVec<T> getSolution() {
147 		int[] model = solver.model();
148 		IVec<T> toInstall = new Vec<T>();
149 		for (int i : model) {
150 			if (i > 0) {
151 				toInstall.push(mapToDomain.get(i));
152 			}
153 		}
154 		return toInstall;
155 	}
156 
157 	public BigInteger getSolutionCost() {
158 		return objFunction.calculateDegree(solver.model());
159 	}
160 
161 	/**
162 	 * Retrieve the boolean value associated with a domain object in the
163 	 * solution found by the solver. THAT METHOD IS EXPECTED TO BE CALLED IF
164 	 * hasASolution() RETURNS TRUE.
165 	 * 
166 	 * @param t
167 	 *            a domain object
168 	 * @return true iff the domain object has been set to true in the current
169 	 *         solution.
170 	 */
171 	public boolean getBooleanValueFor(T t) {
172 		return solver.model(getIntValue(t));
173 	}
174 
175 	/**
176 	 * 
177 	 * @return true if the set of constraints entered inside the solver can be
178 	 *         satisfied.
179 	 * @throws TimeoutException
180 	 */
181 	public boolean hasASolution() throws TimeoutException {
182 		return solver.isSatisfiable();
183 	}
184 
185 	/**
186 	 * 
187 	 * @return true if the set of constraints entered inside the solver can be
188 	 *         satisfied.
189 	 * @throws TimeoutException
190 	 */
191 	public boolean hasASolution(IVec<T> assumps) throws TimeoutException {
192 		IVecInt assumptions = new VecInt();
193 		for (Iterator<T> it = assumps.iterator(); it.hasNext();) {
194 			assumptions.push(getIntValue(it.next()));
195 		}
196 		return solver.isSatisfiable(assumptions);
197 	}
198 
199 	/**
200 	 * 
201 	 * @return true if the set of constraints entered inside the solver can be
202 	 *         satisfied.
203 	 * @throws TimeoutException
204 	 */
205 	public boolean hasASolution(Collection<T> assumps) throws TimeoutException {
206 		IVecInt assumptions = new VecInt();
207 		for (T t : assumps) {
208 			assumptions.push(getIntValue(t));
209 		}
210 		return solver.isSatisfiable(assumptions);
211 	}
212 
213 	/**
214 	 * Explain the reason of the inconsistency of the set of constraints.
215 	 * 
216 	 * THAT METHOD IS EXPECTED TO BE CALLED IF hasASolution() RETURNS FALSE.
217 	 * 
218 	 * @return a set of objects used to "name" each constraint entered in the
219 	 *         solver.
220 	 * @throws TimeoutException
221 	 * @see {@link #hasASolution()}
222 	 */
223 	public Set<C> why() throws TimeoutException {
224 		if (!explanationEnabled) {
225 			throw new UnsupportedOperationException("Explanation not enabled!");
226 		}
227 		Collection<IConstr> explanation = xplain.explain();
228 		Set<C> ezexplain = new TreeSet<C>();
229 		for (IConstr constr : explanation) {
230 			C desc = descs.get(constr);
231 			if (desc != null) {
232 				ezexplain.add(desc);
233 			}
234 		}
235 		return ezexplain;
236 	}
237 
238 	/**
239 	 * Explain a domain object has been set to true in a solution.
240 	 * 
241 	 * @return a set of objects used to "name" each constraint entered in the
242 	 *         solver.
243 	 * @throws TimeoutException
244 	 * @see {@link #hasASolution()}
245 	 */
246 	public Set<C> why(T thing) throws TimeoutException {
247 		IVecInt assumps = new VecInt();
248 		assumps.push(-getIntValue(thing));
249 		return why(assumps);
250 	}
251 
252 	/**
253 	 * Explain a domain object has been set to false in a solution.
254 	 * 
255 	 * @return a set of objects used to "name" each constraint entered in the
256 	 *         solver.
257 	 * @throws TimeoutException
258 	 * @see {@link #hasASolution()}
259 	 */
260 	public Set<C> whyNot(T thing) throws TimeoutException {
261 		IVecInt assumps = new VecInt();
262 		assumps.push(getIntValue(thing));
263 		return why(assumps);
264 	}
265 
266 	private Set<C> why(IVecInt assumps) throws TimeoutException {
267 		if (xplain.isSatisfiable(assumps)) {
268 			return Collections.emptySet();
269 		}
270 		return why();
271 	}
272 
273 	/**
274 	 * Add a constraint to set the value of a domain object to true.
275 	 * 
276 	 * @param thing
277 	 *            the domain object
278 	 * @param name
279 	 *            the name of the constraint, to be used in an explanation if
280 	 *            needed.
281 	 * @throws ContradictionException
282 	 *             if the set of constraints appears to be trivially
283 	 *             inconsistent.
284 	 */
285 	public void setTrue(T thing, C name) throws ContradictionException {
286 		descs.put(gator.gateTrue(getIntValue(thing)), name);
287 	}
288 
289 	/**
290 	 * Add a constraint to set the value of a domain object to false.
291 	 * 
292 	 * @param thing
293 	 *            the domain object
294 	 * @param name
295 	 *            the name of the constraint, to be used in an explanation if
296 	 *            needed.
297 	 * @throws ContradictionException
298 	 *             if the set of constraints appears to be trivially
299 	 *             inconsistent.
300 	 */
301 	public void setFalse(T thing, C name) throws ContradictionException {
302 		descs.put(gator.gateFalse(getIntValue(thing)), name);
303 	}
304 
305 	/**
306 	 * Create a logical implication of the form lhs -> rhs
307 	 * 
308 	 * @param lhs
309 	 *            some domain objects. They form a conjunction in the left hand
310 	 *            side of the implication.
311 	 * @return the right hand side of the implication.
312 	 */
313 	public ImplicationRHS<T, C> implication(T... lhs) {
314 		IVecInt clause = new VecInt();
315 		for (T t : lhs) {
316 			clause.push(-getIntValue(t));
317 		}
318 		return new ImplicationRHS<T, C>(this, clause);
319 	}
320 
321 	public DisjunctionRHS<T, C> disjunction(T... lhs) {
322 		IVecInt literals = new VecInt();
323 		for (T t : lhs) {
324 			literals.push(-getIntValue(t));
325 		}
326 		return new DisjunctionRHS<T, C>(this, literals);
327 	}
328 
329 	/**
330 	 * Create a constraint stating that at most i domain object should be set to
331 	 * true.
332 	 * 
333 	 * @param i
334 	 *            the maximum number of domain object to set to true.
335 	 * @param things
336 	 *            the domain objects.
337 	 * @return an object used to name the constraint. The constraint MUST BE
338 	 *         NAMED.
339 	 * @throws ContradictionException
340 	 */
341 	public ImplicationNamer<T, C> atMost(int i, T... things)
342 			throws ContradictionException {
343 		IVec<IConstr> toName = new Vec<IConstr>();
344 		IVecInt literals = new VecInt();
345 		for (T t : things) {
346 			literals.push(getIntValue(t));
347 		}
348 		toName.push(solver.addAtMost(literals, i));
349 		return new ImplicationNamer<T, C>(this, toName);
350 	}
351 
352 	/**
353 	 * Create a clause (thing1 or thing 2 ... or thingn)
354 	 * 
355 	 * @param name
356 	 * @param things
357 	 * 
358 	 * @throws ContradictionException
359 	 */
360 	public void clause(C name, T... things) throws ContradictionException {
361 		IVecInt literals = new VecInt(things.length);
362 		for (T t : things) {
363 			literals.push(getIntValue(t));
364 		}
365 		descs.put(gator.addClause(literals), name);
366 	}
367 
368 	/**
369 	 * Create a constraint using equivalency chains thing <=> (thing1 <=> thing2
370 	 * <=> ... <=> thingn)
371 	 * 
372 	 * @param thing
373 	 *            a domain object
374 	 * @param things
375 	 *            other domain objects.
376 	 * @throws ContradictionException
377 	 */
378 	public void iff(C name, T thing, T... otherThings)
379 			throws ContradictionException {
380 		IVecInt literals = new VecInt(otherThings.length);
381 		for (T t : otherThings) {
382 			literals.push(getIntValue(t));
383 		}
384 		IConstr[] constrs = gator.iff(getIntValue(thing), literals);
385 		for (IConstr constr : constrs) {
386 			descs.put(constr, name);
387 		}
388 	}
389 
390 	/**
391 	 * Create a constraint of the form thing <=> (thing1 and thing 2 ... and
392 	 * thingn)
393 	 * 
394 	 * @param name
395 	 * @param thing
396 	 * @param otherThings
397 	 * @throws ContradictionException
398 	 */
399 	public void and(C name, T thing, T... otherThings)
400 			throws ContradictionException {
401 		IVecInt literals = new VecInt(otherThings.length);
402 		for (T t : otherThings) {
403 			literals.push(getIntValue(t));
404 		}
405 		IConstr[] constrs = gator.and(getIntValue(thing), literals);
406 		for (IConstr constr : constrs) {
407 			descs.put(constr, name);
408 		}
409 	}
410 
411 	/**
412 	 * Create a constraint of the form thing <=> (thing1 or thing 2 ... or
413 	 * thingn)
414 	 * 
415 	 * @param name
416 	 * @param thing
417 	 * @param otherThings
418 	 * @throws ContradictionException
419 	 */
420 	public void or(C name, T thing, T... otherThings)
421 			throws ContradictionException {
422 		IVecInt literals = new VecInt(otherThings.length);
423 		for (T t : otherThings) {
424 			literals.push(getIntValue(t));
425 		}
426 		IConstr[] constrs = gator.or(getIntValue(thing), literals);
427 		for (IConstr constr : constrs) {
428 			descs.put(constr, name);
429 		}
430 	}
431 
432 	/**
433 	 * Create a constraint of the form thing <=> (if conditionThing then
434 	 * thenThing else elseThing)
435 	 * 
436 	 * @param name
437 	 * @param thing
438 	 * @param otherThings
439 	 * @throws ContradictionException
440 	 */
441 	public void ifThenElse(C name, T thing, T conditionThing, T thenThing,
442 			T elseThing) throws ContradictionException {
443 		IConstr[] constrs = gator.ite(getIntValue(thing),
444 				getIntValue(conditionThing), getIntValue(thenThing),
445 				getIntValue(elseThing));
446 		for (IConstr constr : constrs) {
447 			descs.put(constr, name);
448 		}
449 	}
450 
451 	/**
452 	 * Add an objective function to ask for a solution that minimize the
453 	 * objective function.
454 	 * 
455 	 * @param wobj
456 	 *            a set of weighted objects (pairs of domain object and
457 	 *            BigInteger).
458 	 */
459 	public void setObjectiveFunction(WeightedObject<T>... wobj) {
460 		createObjectivetiveFunctionIfNeeded(wobj.length);
461 		for (WeightedObject<T> wo : wobj) {
462 			objLiterals.push(getIntValue(wo.thing));
463 			objCoefs.push(wo.getWeight());
464 		}
465 
466 	}
467 
468 	private void createObjectivetiveFunctionIfNeeded(int n) {
469 		if (objFunction == null) {
470 			objLiterals = new VecInt(n);
471 			objCoefs = new Vec<BigInteger>(n);
472 			objFunction = new ObjectiveFunction(objLiterals, objCoefs);
473 			solver.setObjectiveFunction(objFunction);
474 		}
475 	}
476 
477 	/**
478 	 * Add a weighted literal to the objective function.
479 	 * 
480 	 * @param thing
481 	 * @param weight
482 	 */
483 	public void addToObjectiveFunction(T thing, int weight) {
484 		addToObjectiveFunction(thing, BigInteger.valueOf(weight));
485 	}
486 
487 	/**
488 	 * Add a weighted literal to the objective function.
489 	 * 
490 	 * @param thing
491 	 * @param weight
492 	 */
493 	public void addToObjectiveFunction(T thing, BigInteger weight) {
494 		createObjectivetiveFunctionIfNeeded(20);
495 		objLiterals.push(getIntValue(thing));
496 		objCoefs.push(weight);
497 	}
498 
499 	/**
500 	 * Stop the SAT solver that is looking for a solution. The solver will throw
501 	 * a TimeoutException.
502 	 */
503 	public void stopSolver() {
504 		solver.expireTimeout();
505 	}
506 
507 	/**
508 	 * Stop the explanation computation. A TimeoutException will be thrown by
509 	 * the explanation algorithm.
510 	 */
511 	public void stopExplanation() {
512 		if (!explanationEnabled) {
513 			throw new UnsupportedOperationException("Explanation not enabled!");
514 		}
515 		xplain.cancelExplanation();
516 	}
517 
518 	public void discard(IVec<T> things) throws ContradictionException {
519 		IVecInt literals = new VecInt(things.size());
520 		for (Iterator<T> it = things.iterator(); it.hasNext();) {
521 			literals.push(-getIntValue(it.next()));
522 		}
523 		solver.addClause(literals);
524 	}
525 
526 	public String getObjectiveFunction() {
527 		ObjectiveFunction obj = solver.getObjectiveFunction();
528 		StringBuffer stb = new StringBuffer();
529 		for (int i = 0; i < obj.getVars().size(); i++) {
530 			stb.append(obj.getCoeffs().get(i)
531 					+ (obj.getVars().get(i) > 0 ? " " : "~")
532 					+ mapToDomain.get(Math.abs(obj.getVars().get(i))) + " ");
533 		}
534 		return stb.toString();
535 	}
536 
537 	public int getNumberOfVariables() {
538 		return mapToDimacs.size();
539 	}
540 
541 	public int getNumberOfConstraints() {
542 		return descs.size();
543 	}
544 }