| Home | Trees | Indices | Help |
|
|---|
|
|
1 """
2 Internal implementation of a SAT solver, used by L{solver.SATSolver}.
3 This is not part of the public API.
4 """
5
6 from __future__ import print_function
7
8 # Copyright (C) 2010, Thomas Leonard
9 # See the README file for details, or visit http://0install.net.
10
11 # The design of this solver is very heavily based on the one described in
12 # the MiniSat paper "An Extensible SAT-solver [extended version 1.2]"
13 # http://minisat.se/Papers.html
14 #
15 # The main differences are:
16 #
17 # - We care about which solution we find (not just "satisfiable" or "not").
18 # - We take care to be deterministic (always select the same versions given
19 # the same input). We do not do random restarts, etc.
20 # - We add an AtMostOneClause (the paper suggests this in the Excercises, and
21 # it's very useful for our purposes).
22
23 -def debug(msg, *args):
27
28 # variables are numbered from 0
29 # literals have the same number as the corresponding variable,
30 # except they for negatives they are (-1-v):
31 #
32 # Variable Literal not(Literal)
33 # 0 0 -1
34 # 1 1 -2
35 -def neg(lit):
39
46
49 """Preferred literals come first.
50 @type solver: L{SATProblem}"""
51 self.solver = solver
52 self.lits = lits
53
54 # The single literal from our set that is True.
55 # We store this explicitly because the decider needs to know quickly.
56 self.current = None
57
59 # Re-add ourselves to the watch list.
60 # (we we won't get any more notifications unless we backtrack,
61 # in which case we'd need to get back on the list anyway)
62 self.solver.watch_lit(lit, self)
63
64 # value[lit] has just become True
65 assert self.solver.lit_value(lit) == True
66 assert lit >= 0
67
68 #debug("%s: noticed %s has become True" % (self, self.solver.name_lit(lit)))
69
70 # If we already propagated successfully when the first
71 # one was set then we set all the others to False and
72 # anyone trying to set one True will get rejected. And
73 # if we didn't propagate yet, current will still be
74 # None, even if we now have a conflict (which we'll
75 # detect below).
76 assert self.current is None
77
78 self.current = lit
79
80 # If we later backtrace, call our undo function to unset current
81 self.solver.get_varinfo_for_lit(lit).undo.append(self)
82
83 for l in self.lits:
84 value = self.solver.lit_value(l)
85 #debug("Value of %s is %s" % (self.solver.name_lit(l), value))
86 if value is True and l is not lit:
87 # Due to queuing, we might get called with current = None
88 # and two versions already selected.
89 debug("CONFLICT: already selected %s" % self.solver.name_lit(l))
90 return False
91 if value is None:
92 # Since one of our lits is already true, all unknown ones
93 # can be set to False.
94 if not self.solver.enqueue(neg(l), self):
95 debug("CONFLICT: enqueue failed for %s", self.solver.name_lit(neg(l)))
96 return False # Conflict; abort
97
98 return True
99
101 debug("(backtracking: no longer selected %s)" % self.solver.name_lit(lit))
102 assert lit == self.current
103 self.current = None
104
105 # Why is lit True?
106 # Or, why are we causing a conflict (if lit is None)?
108 if lit is None:
109 # Find two True literals
110 trues = []
111 for l in self.lits:
112 if self.solver.lit_value(l) is True:
113 trues.append(l)
114 if len(trues) == 2: return trues
115 else:
116 for l in self.lits:
117 if l is not lit and self.solver.lit_value(l) is True:
118 return [l]
119 # Find one True literal
120 assert 0 # don't know why!
121
123 debug("best_undecided: %s" % (self.solver.name_lits(self.lits)))
124 for lit in self.lits:
125 #debug("%s = %s" % (self.solver.name_lit(lit), self.solver.lit_value(lit)))
126 if self.solver.lit_value(lit) is None:
127 return lit
128 return None
129
132
138
139 # Try to infer new facts.
140 # We can do this only when all of our literals are False except one,
141 # which is undecided. That is,
142 # False... or X or False... = True => X = True
143 #
144 # To get notified when this happens, we tell the solver to
145 # watch two of our undecided literals. Watching two undecided
146 # literals is sufficient. When one changes we check the state
147 # again. If we still have two or more undecided then we switch
148 # to watching them, otherwise we propagate.
149 #
150 # Returns False on conflict.
152 # value[get(lit)] has just become False
153
154 #debug("%s: noticed %s has become False" % (self, self.solver.name_lit(neg(lit))))
155
156 # For simplicity, only handle the case where self.lits[1]
157 # is the one that just got set to False, so that:
158 # - value[lits[0]] = None | True
159 # - value[lits[1]] = False
160 # If it's the other way around, just swap them before we start.
161 if self.lits[0] == neg(lit):
162 self.lits[0], self.lits[1] = self.lits[1], self.lits[0]
163
164 if self.solver.lit_value(self.lits[0]) == True:
165 # We're already satisfied. Do nothing.
166 self.solver.watch_lit(lit, self)
167 return True
168
169 assert self.solver.lit_value(self.lits[1]) == False
170
171 # Find a new literal to watch now that lits[1] is resolved,
172 # swap it with lits[1], and start watching it.
173 for i in range(2, len(self.lits)):
174 value = self.solver.lit_value(self.lits[i])
175 if value != False:
176 # Could be None or True. If it's True then we've already done our job,
177 # so this means we don't get notified unless we backtrack, which is fine.
178 self.lits[1], self.lits[i] = self.lits[i], self.lits[1]
179 self.solver.watch_lit(neg(self.lits[1]), self)
180 return True
181
182 # Only lits[0], is now undefined.
183 self.solver.watch_lit(lit, self)
184 return self.solver.enqueue(self.lits[0], self)
185
187
188 # Why is lit True?
189 # Or, why are we causing a conflict (if lit is None)?
191 assert lit is None or lit is self.lits[0]
192
193 # The cause is everything except lit.
194 return [neg(l) for l in self.lits if l is not lit]
195
198
199 # Using an array of VarInfo objects is less efficient than using multiple arrays, but
200 # easier for me to understand.
201 -class VarInfo(object):
202 __slots__ = ['value', 'reason', 'level', 'undo', 'obj']
204 self.value = None # True/False/None
205 self.reason = None # The constraint that implied our value, if True or False
206 self.level = -1 # The decision level at which we got a value (when not None)
207 self.undo = [] # Constraints to update if we become unbound (by backtracking)
208 self.obj = obj # The object this corresponds to (for our caller and for debugging)
209
211 return '%s=%s' % (self.name, self.value)
212
213 @property
215 return str(self.obj)
216
219 # Propagation
220 self.watches = [] # watches[2i,2i+1] = constraints to check when literal[i] becomes True/False
221 self.propQ = [] # propagation queue
222
223 # Assignments
224 self.assigns = [] # [VarInfo]
225 self.trail = [] # order of assignments [lit]
226 self.trail_lim = [] # decision levels (len(trail) at each decision)
227
228 self.toplevel_conflict = False
229
233
235 """@rtype: int"""
236 debug("add_variable('%s')", obj)
237 index = len(self.assigns)
238
239 self.watches += [[], []] # Add watch lists for X and not(X)
240 self.assigns.append(VarInfo(obj))
241 return index
242
243 # lit is now True
244 # reason is the clause that is asserting this
245 # Returns False if this immediately causes a conflict.
247 """@type lit: int
248 @rtype: bool"""
249 debug("%s => %s" % (reason, self.name_lit(lit)))
250 old_value = self.lit_value(lit)
251 if old_value is not None:
252 if old_value is False:
253 # Conflict
254 return False
255 else:
256 # Already set (shouldn't happen)
257 return True
258
259 if lit < 0:
260 var_info = self.assigns[neg(lit)]
261 var_info.value = False
262 else:
263 var_info = self.assigns[lit]
264 var_info.value = True
265 var_info.level = self.get_decision_level()
266 var_info.reason = reason
267
268 self.trail.append(lit)
269 self.propQ.append(lit)
270
271 return True
272
273 # Pop most recent assignment from self.trail
275 lit = self.trail[-1]
276 debug("(pop %s)", self.name_lit(lit))
277 var_info = self.get_varinfo_for_lit(lit)
278 var_info.value = None
279 var_info.reason = None
280 var_info.level = -1
281 self.trail.pop()
282
283 while var_info.undo:
284 var_info.undo.pop().undo(lit)
285
287 n_this_level = len(self.trail) - self.trail_lim[-1]
288 debug("backtracking from level %d (%d assignments)" %
289 (self.get_decision_level(), n_this_level))
290 while n_this_level != 0:
291 self.undo_one()
292 n_this_level -= 1
293 self.trail_lim.pop()
294
299
300 # Process the propQ.
301 # Returns None when done, or the clause that caused a conflict.
303 #debug("propagate: queue length = %d", len(self.propQ))
304 while self.propQ:
305 lit = self.propQ[0]
306 del self.propQ[0]
307 wi = watch_index(lit)
308 watches = self.watches[wi]
309 self.watches[wi] = []
310
311 debug("%s -> True : watches: %s" % (self.name_lit(lit), watches))
312
313 # Notifiy all watchers
314 for i in range(len(watches)):
315 clause = watches[i]
316 if not clause.propagate(lit):
317 # Conflict
318
319 # Re-add remaining watches
320 self.watches[wi] += watches[i+1:]
321
322 # No point processing the rest of the queue as
323 # we'll have to backtrack now.
324 self.propQ = []
325
326 return clause
327 return None
328
331
333 """@type lit: int
334 @rtype: L{VarInfo}"""
335 if lit >= 0:
336 return self.assigns[lit]
337 else:
338 return self.assigns[neg(lit)]
339
341 """@type lit: int
342 @rtype: bool | None"""
343 if lit >= 0:
344 value = self.assigns[lit].value
345 return value
346 else:
347 v = -1 - lit
348 value = self.assigns[v].value
349 if value is None:
350 return None
351 else:
352 return not value
353
354 # Call cb when lit becomes True
356 #debug("%s is watching for %s to become True" % (cb, self.name_lit(lit)))
357 """@type lit: int"""
358 self.watches[watch_index(lit)].append(cb)
359
360 # Returns the new clause if one was added, True if none was added
361 # because this clause is trivially True, or False if the clause is
362 # False.
364 """@type lits: [int]
365 @type learnt: bool
366 @type reason: str
367 @rtype: L{zeroinstall.injector.sat.UnionClause} | bool"""
368 if not lits:
369 assert not learnt
370 self.toplevel_conflict = True
371 return False
372 elif len(lits) == 1:
373 # A clause with only a single literal is represented
374 # as an assignment rather than as a clause.
375 return self.enqueue(lits[0], reason)
376
377 clause = UnionClause(self, lits)
378
379 if learnt:
380 # lits[0] is None because we just backtracked.
381 # Start watching the next literal that we will
382 # backtrack over.
383 best_level = -1
384 best_i = 1
385 for i in range(1, len(lits)):
386 level = self.get_varinfo_for_lit(lits[i]).level
387 if level > best_level:
388 best_level = level
389 best_i = i
390 lits[1], lits[best_i] = lits[best_i], lits[1]
391
392 # Watch the first two literals in the clause (both must be
393 # undefined at this point).
394 for lit in lits[:2]:
395 self.watch_lit(neg(lit), clause)
396
397 return clause
398
403
404 # For nicer debug messages
406 """@type lit: int
407 @rtype: str"""
408 if lit >= 0:
409 return self.assigns[lit].name
410 return "not(%s)" % self.assigns[neg(lit)].name
411
413 # Public interface. Only used before the solve starts.
414 """@type lits: [int]
415 @rtype: L{zeroinstall.injector.sat.UnionClause} | bool"""
416 assert lits
417
418 debug("add_clause([%s])" % ', '.join(self.name_lits(lits)))
419
420 if any(self.lit_value(l) == True for l in lits):
421 # Trivially true already.
422 return True
423 lit_set = set(lits)
424 for l in lits:
425 if neg(l) in lit_set:
426 # X or not(X) is always True.
427 return True
428 # Remove duplicates and values known to be False
429 lits = [l for l in lit_set if self.lit_value(l) != False]
430
431 retval = self._add_clause(lits, learnt = False, reason = "input fact")
432 if not retval:
433 self.toplevel_conflict = True
434 return retval
435
437 """@type lits: [int]
438 @rtype: L{zeroinstall.injector.sat.AtMostOneClause}"""
439 assert lits
440
441 debug("at_most_one(%s)" % ', '.join(self.name_lits(lits)))
442
443 # If we have zero or one literals then we're trivially true
444 # and not really needed for the solve. However, Zero Install
445 # monitors these objects to find out what was selected, so
446 # keep even trivial ones around for that.
447 #
448 #if len(lits) < 2:
449 # return True # Trivially true
450
451 # Ensure no duplicates
452 assert len(set(lits)) == len(lits), lits
453
454 # Ignore any literals already known to be False.
455 # If any are True then they're enqueued and we'll process them
456 # soon.
457 lits = [l for l in lits if self.lit_value(l) != False]
458
459 clause = AtMostOneClause(self, lits)
460
461 for lit in lits:
462 self.watch_lit(lit, clause)
463
464 return clause
465
467 # After trying some assignments, we've discovered a conflict.
468 # e.g.
469 # - we selected A then B then C
470 # - from A, B, C we got X, Y
471 # - we have a rule: not(A) or not(X) or not(Y)
472 #
473 # The simplest thing to do would be:
474 # 1. add the rule "not(A) or not(B) or not(C)"
475 # 2. unassign C
476 #
477 # Then we we'd deduce not(C) and we could try something else.
478 # However, that would be inefficient. We want to learn a more
479 # general rule that will help us with the rest of the problem.
480 #
481 # We take the clause that caused the conflict ("cause") and
482 # ask it for its cause. In this case:
483 #
484 # A and X and Y => conflict
485 #
486 # Since X and Y followed logically from A, B, C there's no
487 # point learning this rule; we need to know to avoid A, B, C
488 # *before* choosing C. We ask the two variables deduced at the
489 # current level (X and Y) what caused them, and work backwards.
490 # e.g.
491 #
492 # X: A and C => X
493 # Y: C => Y
494 #
495 # Combining these, we get the cause of the conflict in terms of
496 # things we knew before the current decision level:
497 #
498 # A and X and Y => conflict
499 # A and (A and C) and (C) => conflict
500 # A and C => conflict
501 #
502 # We can then learn (record) the more general rule:
503 #
504 # not(A) or not(C)
505 #
506 # Then, in future, whenever A is selected we can remove C and
507 # everything that depends on it from consideration.
508
509
510 learnt = [None] # The general rule we're learning
511 btlevel = 0 # The deepest decision in learnt
512 p = None # The literal we want to expand now
513 seen = set() # The variables involved in the conflict
514
515 counter = 0
516
517 while True:
518 # cause is the reason why p is True (i.e. it enqueued it).
519 # The first time, p is None, which requests the reason
520 # why it is conflicting.
521 if p is None:
522 debug("Why did %s make us fail?" % cause)
523 p_reason = cause.calc_reason(p)
524 debug("Because: %s => conflict" % (' and '.join(self.name_lits(p_reason))))
525 else:
526 debug("Why did %s lead to %s?" % (cause, self.name_lit(p)))
527 p_reason = cause.calc_reason(p)
528 debug("Because: %s => %s" % (' and '.join(self.name_lits(p_reason)), self.name_lit(p)))
529
530 # p_reason is in the form (A and B and ...)
531 # p_reason => p
532
533 # Check each of the variables in p_reason that we haven't
534 # already considered:
535 # - if the variable was assigned at the current level,
536 # mark it for expansion
537 # - otherwise, add it to learnt
538
539 for lit in p_reason:
540 var_info = self.get_varinfo_for_lit(lit)
541 if var_info not in seen:
542 seen.add(var_info)
543 if var_info.level == self.get_decision_level():
544 # We deduced this var since the last decision.
545 # It must be in self.trail, so we'll get to it
546 # soon. Remember not to stop until we've processed it.
547 counter += 1
548 elif var_info.level > 0:
549 # We won't expand lit, just remember it.
550 # (we could expand it if it's not a decision, but
551 # apparently not doing so is useful)
552 learnt.append(neg(lit))
553 btlevel = max(btlevel, var_info.level)
554 # else we already considered the cause of this assignment
555
556 # At this point, counter is the number of assigned
557 # variables in self.trail at the current decision level that
558 # we've seen. That is, the number left to process. Pop
559 # the next one off self.trail (as well as any unrelated
560 # variables before it; everything up to the previous
561 # decision has to go anyway).
562
563 # On the first time round the loop, we must find the
564 # conflict depends on at least one assignment at the
565 # current level. Otherwise, simply setting the decision
566 # variable caused a clause to conflict, in which case
567 # the clause should have asserted not(decision-variable)
568 # before we ever made the decision.
569 # On later times round the loop, counter was already >
570 # 0 before we started iterating over p_reason.
571 assert counter > 0
572
573 while True:
574 p = self.trail[-1]
575 var_info = self.get_varinfo_for_lit(p)
576 cause = var_info.reason
577 self.undo_one()
578 if var_info in seen:
579 break
580 debug("(irrelevant)")
581 counter -= 1
582
583 if counter <= 0:
584 assert counter == 0
585 # If counter = 0 then we still have one more
586 # literal (p) at the current level that we
587 # could expand. However, apparently it's best
588 # to leave this unprocessed (says the minisat
589 # paper).
590 break
591
592 # p is the literal we decided to stop processing on. It's either
593 # a derived variable at the current level, or the decision that
594 # led to this level. Since we're not going to expand it, add it
595 # directly to the learnt clause.
596 learnt[0] = neg(p)
597
598 debug("Learnt: %s" % (' or '.join(self.name_lits(learnt))))
599
600 return learnt, btlevel
601
603 """@rtype: bool"""
604
605 # Check whether we detected a trivial problem
606 # during setup.
607 if self.toplevel_conflict:
608 debug("FAIL: toplevel_conflict before starting solve!")
609 return False
610
611 while True:
612 # Use logical deduction to simplify the clauses
613 # and assign literals where there is only one possibility.
614 conflicting_clause = self.propagate()
615 if not conflicting_clause:
616 debug("new state: %s", self.assigns)
617 if all(info.value != None for info in self.assigns):
618 # Everything is assigned without conflicts
619 debug("SUCCESS!")
620 return True
621 else:
622 # Pick a variable and try assigning it one way.
623 # If it leads to a conflict, we'll backtrack and
624 # try it the other way.
625 lit = decide()
626 #print "TRYING:", self.name_lit(lit)
627 assert lit is not None, "decide function returned None!"
628 assert self.lit_value(lit) is None
629 self.trail_lim.append(len(self.trail))
630 r = self.enqueue(lit, reason = "considering")
631 assert r is True
632 else:
633 if self.get_decision_level() == 0:
634 debug("FAIL: conflict found at top level")
635 return False
636 else:
637 # Figure out the root cause of this failure.
638 learnt, backtrack_level = self.analyse(conflicting_clause)
639
640 self.cancel_until(backtrack_level)
641
642 c = self._add_clause(learnt, learnt = True, reason = conflicting_clause)
643
644 if c is not True:
645 # Everything except the first literal in learnt is known to
646 # be False, so the first must be True.
647 e = self.enqueue(learnt[0], c)
648 assert e is True
649
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Mar 26 18:14:11 2013 | http://epydoc.sourceforge.net |