ABC: A System for Sequential Synthesis and Verification
 
Loading...
Searching...
No Matches
cadical_subsume.cpp
Go to the documentation of this file.
1#include "global.h"
2
3#include "internal.hpp"
4
6
7namespace CaDiCaL {
8
9/*------------------------------------------------------------------------*/
10
11// This file implements a global forward subsumption algorithm, which is run
12// frequently during search. It works both on original (irredundant)
13// clauses and on 'sticky' learned clauses which are likely to be kept.
14// This is abstracted away in the 'likely_to_be_kept_clause' function, which
15// implicitly relies on 'opts.reducetier1glue' (glucose level of clauses
16// which are not reduced) as well as dynamically determined size and glucose
17// level ('lim.keptglue' and 'lim.keptsize') of clauses kept in 'reduce'.
18//
19// Note, that 'forward' means that the clause from which the subsumption
20// check is started is checked for being subsumed by other (smaller or equal
21// size) clauses. Since 'vivification' is an extended version of subsume,
22// more powerful, but also slower, we schedule 'vivify' right after
23// 'subsume', which in contrast to 'subsume' is not run until to completion.
24//
25// This implementation is inspired by Bayardo's SDM'11 analysis of our
26// subsumption algorithm in our SATeLite preprocessor in the context of
27// finding extremal sets in data mining and his suggested improvements.
28
29// Our original subsumption algorithm in 'Quantor' and 'SATeLite' (and in
30// MiniSAT and descendants) is based on backward subsumption. It uses the
31// observation that only the occurrence list of one literal of a clause has
32// to be traversed in order to find all potential clauses which are subsumed
33// by the candidate. Thus the literal with the smallest number of
34// occurrences is used. However, that scheme requires to connect all
35// literals of surviving clauses, while forward algorithms only need to
36// connect one literal. On the other hand forward checking requires to
37// traverse the occurrence lists of all literals of the candidate clause to
38// find subsuming clauses. During connecting the single literal (similar to
39// the one-watch scheme by Lintao Zhang) one can connect a literal with a
40// minimal number of occurrence so far, which implicitly also reduces future
41// occurrence list traversal time.
42
43// Also the actual subsumption check is cheaper since during backward
44// checking the short subsuming candidate clause is marked and all the
45// literals in the larger subsume candidate clauses have to be traversed,
46// while for our forward approach the long subsumed candidate clause is only
47// marked once, while the literals of the shorter subsuming clauses have to
48// be checked. We also use a fixed special more cache friendly data
49// structure for binary clauses, to avoid traversing them directly.
50
51// In our forward scheme it is still possible to skip occurrence lists of
52// literals which were not added since the last subsumption round, since
53// only those can contain subsuming candidates. Actually, clauses which
54// contain at least one literal, which was not added since the last
55// subsumption round do not have to be connected at all, even though they
56// might still be subsumed them self.
57
58// Bayardo suggests to sort the literals in clauses and perform some kind of
59// partial merge-sort, while we mark literals, but do sort literals during
60// connecting a clause w.r.t. the number of occurrences, in order to find
61// literals which do not occur in the subsumed candidate fast with high
62// probability (less occurring literals have a higher chance).
63
64// This is the actual subsumption and strengthening check. We assume that
65// all the literals of the candidate clause to be subsumed or strengthened
66// are marked, so we only have to check that all the literals of the
67// argument clause 'subsuming', which is checked for subsuming the candidate
68// clause 'subsumed', has all its literals marked (in the correct phase).
69// If exactly one is in the opposite phase we can still strengthen the
70// candidate clause by this single literal which occurs in opposite phase.
71//
72// The result is INT_MIN if all literals are marked and thus the candidate
73// clause can be subsumed. It is zero if neither subsumption nor
74// strengthening is possible. Otherwise the candidate clause can be
75// strengthened and as a result the negation of the literal which can be
76// removed is returned.
77
78inline int Internal::subsume_check (Clause *subsuming, Clause *subsumed) {
79#ifdef CADICAL_NDEBUG
80 (void) subsumed;
81#endif
82 // Only use 'subsumed' for these following CADICAL_assertion checks. Otherwise we
83 // only require that 'subsumed' has all its literals marked.
84 //
85 CADICAL_assert (!subsumed->garbage);
86 CADICAL_assert (!subsuming->garbage);
87 CADICAL_assert (subsuming != subsumed);
88 CADICAL_assert (subsuming->size <= subsumed->size);
89
90 stats.subchecks++;
91 if (subsuming->size == 2)
92 stats.subchecks2++;
93
94 int flipped = 0, prev = 0;
95 bool failed = false;
96 const auto eoc = subsuming->end ();
97 for (auto i = subsuming->begin (); !failed && i != eoc; i++) {
98 int lit = *i;
99 *i = prev;
100 prev = lit;
101 const int tmp = marked (lit);
102 if (!tmp)
103 failed = true;
104 else if (tmp > 0)
105 continue;
106 else if (flipped)
107 failed = true;
108 else
109 flipped = lit;
110 }
111 CADICAL_assert (prev);
112 CADICAL_assert (!subsuming->literals[0]);
113 subsuming->literals[0] = prev;
114 if (failed)
115 return 0;
116
117 if (!flipped)
118 return INT_MIN; // subsumed!!
119 else if (!opts.subsumestr)
120 return 0;
121 else
122 return flipped; // strengthen!!
123}
124
125/*------------------------------------------------------------------------*/
126
127// Candidate clause 'subsumed' is subsumed by 'subsuming'.
128
129inline void Internal::subsume_clause (Clause *subsuming, Clause *subsumed) {
130 stats.subsumed++;
131 CADICAL_assert (subsuming->size <= subsumed->size);
132 LOG (subsumed, "subsumed");
133 if (subsumed->redundant)
134 stats.subred++;
135 else
136 stats.subirr++;
137 if (subsumed->redundant || !subsuming->redundant) {
138 mark_garbage (subsumed);
139 return;
140 }
141 LOG ("turning redundant subsuming clause into irredundant clause");
142 subsuming->redundant = false;
143 if (proof)
144 proof->strengthen (subsuming->id);
145 mark_garbage (subsumed);
146 stats.current.irredundant++;
147 stats.added.irredundant++;
148 stats.irrlits += subsuming->size;
149 CADICAL_assert (stats.current.redundant > 0);
150 stats.current.redundant--;
151 CADICAL_assert (stats.added.redundant > 0);
152 stats.added.redundant--;
153 // ... and keep 'stats.added.total'.
154}
155
156/*------------------------------------------------------------------------*/
157
158// Candidate clause 'c' is strengthened by removing 'lit'.
159
161 if (opts.check && is_external_forgettable (c->id))
163 stats.strengthened++;
164 CADICAL_assert (c->size > 2);
165 LOG (c, "removing %d in", lit);
166 if (proof) {
167 LOG (lrat_chain, "strengthening clause with chain");
168 proof->strengthen_clause (c, lit, lrat_chain);
169 }
170 if (!c->redundant)
172 auto new_end = remove (c->begin (), c->end (), lit);
173 CADICAL_assert (new_end + 1 == c->end ()), (void) new_end;
174 (void) shrink_clause (c, c->size - 1);
175 // bump_clause2 (c);
176 LOG (c, "strengthened");
177 external->check_shrunken_clause (c);
178}
179
180/*------------------------------------------------------------------------*/
181
182// Find clauses connected in the occurrence lists 'occs' which subsume the
183// candidate clause 'c' given as first argument. If this is the case the
184// clause is subsumed and the result is positive. If the clause was
185// strengthened the result is negative. Otherwise the candidate clause
186// can not be subsumed nor strengthened and zero is returned.
187
189 vector<Clause *> &shrunken) {
190
191 stats.subtried++;
193 LOG (c, "trying to subsume");
194
195 mark (c); // signed!
196
197 Clause *d = 0;
198 int flipped = 0;
199
200 for (const auto &lit : *c) {
201
202 // Only clauses which have a variable which has recently been added are
203 // checked for being subsumed. The idea is that all these newly added
204 // clauses are candidates for subsuming the clause. Then we also only
205 // need to check occurrences of these variables. The occurrence lists
206 // of other literal do not have to be checked.
207 //
208 if (!flags (lit).subsume)
209 continue;
210
211 for (int sign = -1; !d && sign <= 1; sign += 2) {
212
213 // First we check against all binary clauses. The other literals of
214 // all binary clauses of 'sign*lit' are stored in one consecutive
215 // array, which is way faster than storing clause pointers and
216 // dereferencing them. Since this binary clause array is also not
217 // shrunken, we also can bail out earlier if subsumption or
218 // strengthening is determined.
219
220 // In both cases the (self-)subsuming clause is stored in 'd', which
221 // makes it nonzero and forces aborting both the outer and inner loop.
222 // If the binary clause can strengthen the candidate clause 'c'
223 // (through self-subsuming resolution), then 'filled' is set to the
224 // literal which can be removed in 'c', otherwise to 'INT_MIN' which
225 // is a non-valid literal.
226
227 for (const auto &bin : bins (sign * lit)) {
228 const auto &other = bin.lit;
229 const int tmp = marked (other);
230 if (!tmp)
231 continue;
232 if (tmp < 0 && sign < 0)
233 continue;
234 if (tmp < 0) {
235 if (sign < 0)
236 continue; // tautological resolvent
237 dummy_binary->literals[0] = lit;
238 dummy_binary->literals[1] = other;
239 flipped = other;
240 } else {
241 dummy_binary->literals[0] = sign * lit;
242 dummy_binary->literals[1] = other;
243 flipped = (sign < 0) ? -lit : INT_MIN;
244 }
245
246 // This dummy binary clauses is initialized in 'Internal::Internal'
247 // and only changes it literals in the lines above. By using such
248 // a faked binary clause we can simply reuse 'subsume_clause' as
249 // well as the code around 'strengthen_clause' uniform for both real
250 // clauses and this special case for binary clauses
251
252 dummy_binary->id = bin.id;
253 d = dummy_binary;
254
255 break;
256 }
257
258 if (d)
259 break;
260
261 // In this second loop we check for larger than binary clauses to
262 // subsume or strengthen the candidate clause. This is more costly,
263 // and needs a call to 'subsume_check'. Otherwise the same contract
264 // as above for communicating 'subsumption' or 'strengthening' to the
265 // code after the loop is used.
266 //
267 const Occs &os = occs (sign * lit);
268 for (const auto &e : os) {
269 CADICAL_assert (!e->garbage); // sanity check
270 if (e->garbage)
271 continue; // defensive: not needed
272 flipped = subsume_check (e, c);
273 if (!flipped)
274 continue;
275 d = e; // leave also outer loop
276 break;
277 }
278 }
279
280 if (d)
281 break;
282 }
283
284 unmark (c);
285
286 if (flipped == INT_MIN) {
287 LOG (d, "subsuming");
288 subsume_clause (d, c);
289 return 1;
290 }
291
292 if (flipped) {
293 LOG (d, "strengthening");
294 if (lrat) {
295 CADICAL_assert (lrat_chain.empty ());
296 lrat_chain.push_back (c->id);
297 lrat_chain.push_back (d->id);
298 }
299 if (d->used > c->used)
300 c->used = d->used;
301 strengthen_clause (c, -flipped);
302 lrat_chain.clear ();
304 shrunken.push_back (c);
305 return -1;
306 }
307
308 return 0;
309}
310
311
315 bool operator() (int a, int b) {
316 const signed char u = internal->val (a), v = internal->val (b);
317 if (!u && v)
318 return true;
319 if (u && !v)
320 return false;
321 const int64_t m = internal->noccs (a), n = internal->noccs (b);
322 if (m < n)
323 return true;
324 if (m > n)
325 return false;
326 return abs (a) < abs (b);
327 }
328};
329
330/*------------------------------------------------------------------------*/
331
332// Usually called from 'subsume' below if 'subsuming' triggered it. Then
333// the idea is to subsume both redundant and irredundant clauses. It is also
334// called in the elimination loop in 'elim' in which case we focus on
335// irredundant clauses only to help bounded variable elimination. The
336// function returns true of an irredundant clause was removed or
337// strengthened, which might then in the second usage scenario trigger new
338// variable eliminations.
339
341
342 if (!opts.subsume)
343 return false;
344 if (unsat)
345 return false;
347 return false;
348 if (!stats.current.redundant && !stats.current.irredundant)
349 return false;
350
352 stats.subsumerounds++;
353
354 int64_t check_limit;
355 if (opts.subsumelimited) {
356 int64_t delta = stats.propagations.search;
357 delta *= 1e-3 * opts.subsumeeffort;
358 if (delta < opts.subsumemineff)
359 delta = opts.subsumemineff;
360 if (delta > opts.subsumemaxeff)
361 delta = opts.subsumemaxeff;
362 delta = max (delta, (int64_t) 2l * active ());
363
364 PHASE ("subsume-round", stats.subsumerounds,
365 "limit of %" PRId64 " subsumption checks", delta);
366
367 check_limit = stats.subchecks + delta;
368 } else {
369 PHASE ("subsume-round", stats.subsumerounds,
370 "unlimited subsumption checks");
371 check_limit = LONG_MAX;
372 }
373
374 int old_marked_candidate_variables_for_elimination = stats.mark.elim;
375
377
378 // Allocate schedule and occurrence lists.
379 //
380 vector<ClauseSize> schedule;
381 init_noccs ();
382
383 // Determine candidate clauses and sort them by size.
384 //
385 int64_t left_over_from_last_subsumption_round = 0;
386
387 for (auto c : clauses) {
388
389 if (c->garbage)
390 continue;
391 if (c->size > opts.subsumeclslim)
392 continue;
394 continue;
395
396 bool fixed = false;
397 int subsume = 0;
398 for (const auto &lit : *c)
399 if (val (lit))
400 fixed = true;
401 else if (flags (lit).subsume)
402 subsume++;
403
404 // If the clause contains a root level assigned (fixed) literal we will
405 // not work on it. This simplifies the code substantially since we do
406 // not have to care about assignments at all. Strengthening becomes
407 // much simpler too.
408 //
409 if (fixed) {
410 LOG (c, "skipping (fixed literal)");
411 continue;
412 }
413
414 // Further, if less than two variables in the clause were added since
415 // the last subsumption round, the clause is ignored too.
416 //
417 if (subsume < 2) {
418 LOG (c, "skipping (only %d added literals)", subsume);
419 continue;
420 }
421
422 if (c->subsume)
423 left_over_from_last_subsumption_round++;
424 schedule.push_back (ClauseSize (c->size, c));
425 for (const auto &lit : *c)
426 noccs (lit)++;
427 }
428 shrink_vector (schedule);
429
430 // Smaller clauses are checked and connected first.
431 //
432 rsort (schedule.begin (), schedule.end (), smaller_clause_size_rank ());
433
434 if (!left_over_from_last_subsumption_round)
435 for (auto cs : schedule)
436 if (cs.clause->size > 2)
437 cs.clause->subsume = true;
438
439#ifndef CADICAL_QUIET
440 int64_t scheduled = schedule.size ();
441 int64_t total = stats.current.irredundant + stats.current.redundant;
442 PHASE ("subsume-round", stats.subsumerounds,
443 "scheduled %" PRId64 " clauses %.0f%% out of %" PRId64 " clauses",
444 scheduled, percent (scheduled, total), total);
445#endif
446
447 // Now go over the scheduled clauses in the order of increasing size and
448 // try to forward subsume and strengthen them. Forward subsumption tries
449 // to find smaller or same size clauses which subsume or might strengthen
450 // the candidate. After the candidate has been processed connect one
451 // of its literals (with smallest number of occurrences at this point) in
452 // a one-watched scheme.
453
454 int64_t subsumed = 0, strengthened = 0, checked = 0;
455
456 vector<Clause *> shrunken;
457 init_occs ();
458 init_bins ();
459
460 for (const auto &s : schedule) {
461
463 break;
464 if (stats.subchecks >= check_limit)
465 break;
466
467 Clause *c = s.clause;
469
470 checked++;
471
472 // First try to subsume or strengthen this candidate clause. For binary
473 // clauses this could be done much faster by hashing and is costly due
474 // to a usually large number of binary clauses. There is further the
475 // issue, that strengthening binary clauses (through double
476 // self-subsuming resolution) would produce units, which needs much more
477 // care. In the same (lazy) spirit we also ignore clauses with fixed
478 // literals (false or true).
479 //
480 if (c->size > 2 && c->subsume) {
481 c->subsume = false;
482 const int tmp = try_to_subsume_clause (c, shrunken);
483 if (tmp > 0) {
484 subsumed++;
485 continue;
486 }
487 if (tmp < 0)
488 strengthened++;
489 }
490
491 // If not subsumed connect smallest occurring literal, where occurring
492 // means the number of times it was used to connect (as a one-watch) a
493 // previous smaller or equal sized clause. This minimizes the length of
494 // the occurrence lists traversed during 'try_to_subsume_clause'. Also
495 // note that this number is usually way smaller than the number of
496 // occurrences computed before and stored in 'noccs'.
497 //
498 int minlit = 0;
499 int64_t minoccs = 0;
500 size_t minsize = 0;
501 bool subsume = true;
502 bool binary = (c->size == 2 && !c->redundant);
503
504 for (const auto &lit : *c) {
505
506 if (!flags (lit).subsume)
507 subsume = false;
508 const size_t size = binary ? bins (lit).size () : occs (lit).size ();
509 if (minlit && minsize <= size)
510 continue;
511 const int64_t tmp = noccs (lit);
512 if (minlit && minsize == size && tmp <= minoccs)
513 continue;
514 minlit = lit, minsize = size, minoccs = tmp;
515 }
516
517 // If there is a variable in a clause different from is not 'subsume'
518 // (has been added since the last subsumption round), then this clause
519 // can not serve to strengthen or subsume another clause, since all
520 // shrunken or added clauses mark all their variables as 'subsume'.
521 //
522 if (!subsume)
523 continue;
524
525 if (!binary) {
526
527 // If smallest occurring literal occurs too often do not connect.
528 //
529 if (minsize > (size_t) opts.subsumeocclim)
530 continue;
531
532 LOG (c,
533 "watching %d with %zd current and total %" PRId64 " occurrences",
534 minlit, minsize, minoccs);
535
536 occs (minlit).push_back (c);
537
538 // This sorting should give faster failures for assumption checks
539 // since the less occurring variables are put first in a clause and
540 // thus will make it more likely to be found as witness for a clause
541 // not to be subsuming. One could in principle (see also the
542 // discussion on 'subsumption' in our 'Splatz' solver) replace marking
543 // by a kind of merge sort, as also suggested by Bayardo. It would
544 // avoid 'marked' calls and thus might be slightly faster but could
545 // not take benefit of this sorting optimization.
546 //
547 sort (c->begin (), c->end (), subsume_less_noccs (this));
548
549 } else {
550
551 // If smallest occurring literal occurs too often do not connect.
552 //
553 if (minsize > (size_t) opts.subsumebinlim)
554 continue;
555
556 LOG (c,
557 "watching %d with %zd current binary and total %" PRId64
558 " occurrences",
559 minlit, minsize, minoccs);
560
561 const int minlit_pos = (c->literals[1] == minlit);
562 const int other = c->literals[!minlit_pos];
563 bins (minlit).push_back (Bin{other, c->id});
564 }
565 }
566
567 PHASE ("subsume-round", stats.subsumerounds,
568 "subsumed %" PRId64 " and strengthened %" PRId64 " out of %" PRId64
569 " clauses %.0f%%",
570 subsumed, strengthened, scheduled,
571 percent (subsumed + strengthened, scheduled));
572
573 const int64_t remain = schedule.size () - checked;
574 const bool completed = !remain;
575
576 if (completed)
577 PHASE ("subsume-round", stats.subsumerounds,
578 "checked all %" PRId64 " scheduled clauses", checked);
579 else
580 PHASE ("subsume-round", stats.subsumerounds,
581 "checked %" PRId64 " clauses %.0f%% of scheduled (%" PRId64
582 " remain)",
583 checked, percent (checked, scheduled), remain);
584
585 // Release occurrence lists and schedule.
586 //
587 erase_vector (schedule);
588 reset_noccs ();
589 reset_occs ();
590 reset_bins ();
591
592 // Reset all old 'added' flags and mark variables in shrunken
593 // clauses as 'added' for the next subsumption round.
594 //
595 if (completed)
597
598 for (const auto &c : shrunken)
599 mark_added (c);
600 erase_vector (shrunken);
601
602 report ('s', !opts.reportall && !(subsumed + strengthened));
603
605
606 return old_marked_candidate_variables_for_elimination < stats.mark.elim;
607}
608
609/*------------------------------------------------------------------------*/
610
612
613 if (!stats.current.redundant && !stats.current.irredundant)
614 return;
615
616 if (unsat)
617 return;
618
619 backtrack ();
620 if (!propagate ()) {
622 return;
623 }
624
625 stats.subsumephases++;
626
627 if (external_prop) {
629 private_steps = true;
630 }
631
632 if (opts.subsume) {
633 reset_watches ();
634 subsume_round ();
635 init_watches ();
637 if (!unsat && !propagate ()) {
638 LOG ("propagation after subsume rounds results in inconsistency");
640 }
641 }
642
643 transred ();
644 if (external_prop) {
646 private_steps = false;
647 }
648}
649
650} // namespace CaDiCaL
651
#define ABC_NAMESPACE_IMPL_START
#define ABC_NAMESPACE_IMPL_END
#define CADICAL_assert(ignore)
Definition global.h:14
#define LOG(...)
#define PHASE(...)
Definition message.hpp:52
int sign(int lit)
Definition util.hpp:22
void shrink_vector(std::vector< T > &v)
Definition util.hpp:101
vector< Clause * > Occs
Definition occs.hpp:18
void erase_vector(std::vector< T > &v)
Definition util.hpp:90
double percent(double a, double b)
Definition util.hpp:21
void rsort(I first, I last, Rank rank)
Definition radix.hpp:45
#define START_SIMPLIFIER(S, M)
Definition profile.hpp:172
#define STOP_SIMPLIFIER(S, M)
Definition profile.hpp:197
int lit
Definition satVec.h:130
literal_iterator begin()
Definition clause.hpp:131
unsigned used
Definition clause.hpp:59
literal_iterator end()
Definition clause.hpp:132
External * external
Definition internal.hpp:312
int try_to_subsume_clause(Clause *, vector< Clause * > &shrunken)
void mark_garbage(Clause *)
vector< int64_t > lrat_chain
Definition internal.hpp:210
void report(char type, int verbose_level=0)
void unmark(int lit)
Definition internal.hpp:490
Flags & flags(int lit)
Definition internal.hpp:454
int fixed(int lit)
Bins & bins(int lit)
Definition internal.hpp:464
bool terminated_asynchronously(int factor=1)
void mark_removed(int lit)
Clause * dummy_binary
Definition internal.hpp:244
void mark_garbage_external_forgettable(int64_t id)
signed char val(int lit) const
signed char marked(int lit) const
Definition internal.hpp:478
void reset_subsume_bits()
void subsume_clause(Clause *subsuming, Clause *subsumed)
bool active(int lit)
Definition internal.hpp:360
void backtrack(int target_level=0)
int64_t & noccs(int lit)
Definition internal.hpp:466
void strengthen_clause(Clause *, int)
vector< Clause * > clauses
Definition internal.hpp:283
Occs & occs(int lit)
Definition internal.hpp:465
size_t shrink_clause(Clause *, int new_size)
int subsume_check(Clause *subsuming, Clause *subsumed)
void mark_added(int lit, int size, bool redundant)
bool likely_to_be_kept_clause(Clause *c)
void connect_watches(bool irredundant_only=false)
unsigned size
Definition vector.h:35
signed char mark
Definition value.h:8