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