ABC: A System for Sequential Synthesis and Verification
 
Loading...
Searching...
No Matches
cadical_vivify.cpp
Go to the documentation of this file.
1#include "global.h"
2
3#include "vivify.hpp"
4#include "internal.hpp"
5#include "util.hpp"
6#include <algorithm>
7#include <limits>
8#include <utility>
9
11
12namespace CaDiCaL {
13
14/*------------------------------------------------------------------------*/
15
16// Vivification is a special case of asymmetric tautology elimination (ATE)
17// and asymmetric literal elimination (ALE). It strengthens and removes
18// clauses proven redundant through unit propagation.
19//
20// The original algorithm is due to a paper by Piette, Hamadi and Sais
21// published at ECAI'08. We have an inprocessing version, e.g., it does not
22// necessarily run-to-completion. Our version also performs conflict
23// analysis and uses a new heuristic for selecting clauses to vivify.
24
25// Our idea is to focus on clauses with many occurrences of its literals in
26// other clauses first. This both complements nicely our implementation of
27// subsume, which is bounded, e.g., subsumption attempts are skipped for
28// very long clauses with literals with many occurrences and also is
29// stronger in the sense that it enables to remove more clauses due to unit
30// propagation (AT checks).
31
32// While first focusing on irredundant clause we then added a separate phase
33// upfront which focuses on strengthening also redundant clauses in spirit
34// of the ideas presented in the IJCAI'17 paper by M. Luo, C.-M. Li, F.
35// Xiao, F. Manya, and Z. Lu.
36
37// There is another very similar approach called 'distilliation' published
38// by Han and Somenzi in DAC'07, which reorganizes the CNF in a trie data
39// structure to reuse decisions and propagations along the trie. We used
40// that as an inspiration but instead of building a trie we simple sort
41// clauses and literals in such a way that we get the same effect. If a new
42// clause is 'distilled' or 'vivified' we first check how many of the
43// decisions (which are only lazily undone) can be reused for that clause.
44// Reusing can be improved by picking a global literal order and sorting the
45// literals in all clauses with respect to that order. We favor literals
46// with more occurrences first. Then we sort clauses lexicographically with
47// respect to that literal order.
48
49/*------------------------------------------------------------------------*/
50
51// Candidate clause 'subsumed' is subsumed by 'subsuming'.
52
54 Clause *subsumed) {
55 stats.subsumed++;
56 stats.vivifysubs++;
57#ifndef CADICAL_NDEBUG
58 CADICAL_assert (subsuming);
59 CADICAL_assert (subsumed);
60 CADICAL_assert (subsuming != subsumed);
61 CADICAL_assert (!subsumed->garbage);
62 // size after removeing units;
63 int real_size_subsuming = 0, real_size_subsumed = 0;
64 for (auto lit : *subsuming) {
65 if (!val (lit) || var (lit).level)
66 ++real_size_subsuming;
67 else
68 CADICAL_assert (val (lit) < 0);
69 }
70 for (auto lit : *subsumed) {
71 if (!val (lit) || var (lit).level)
72 ++real_size_subsumed;
73 else
74 CADICAL_assert (val (lit) < 0);
75 }
76 CADICAL_assert (real_size_subsuming <= real_size_subsumed);
77#endif
78 LOG (subsumed, "subsumed");
79 if (subsumed->redundant) {
80 stats.subred++;
81 ++stats.vivifysubred;
82 } else {
83 stats.subirr++;
84 ++stats.vivifysubirr;
85 }
86 if (subsuming->garbage) {
87 CADICAL_assert (subsuming->size == 2);
88 LOG (subsuming,
89 "binary subsuming clause was already deleted, so undeleting");
90 subsuming->garbage = false;
91 subsuming->glue = 1;
92 ++stats.current.total;
93 if (subsuming->redundant)
94 stats.current.redundant++;
95 else
96 stats.current.irredundant++, stats.irrlits += subsuming->size;
97 }
98 if (subsumed->redundant || !subsuming->redundant) {
99 mark_garbage (subsumed);
100 return;
101 }
102 LOG ("turning redundant subsuming clause into irredundant clause");
103 subsuming->redundant = false;
104 if (proof)
105 proof->strengthen (subsuming->id);
106 mark_garbage (subsumed);
107 mark_added (subsuming);
108 stats.current.irredundant++;
109 stats.added.irredundant++;
110 stats.irrlits += subsuming->size;
111 CADICAL_assert (stats.current.redundant > 0);
112 stats.current.redundant--;
113 CADICAL_assert (stats.added.redundant > 0);
114 stats.added.redundant--;
115 // ... and keep 'stats.added.total'.
116}
117
118// demoting a clause (opposite is promote from subsume.cpp)
119
121 stats.subsumed++;
122 stats.vivifydemote++;
123 LOG (c, "demoting");
125 mark_removed (c);
126 c->redundant = true;
127 CADICAL_assert (stats.current.irredundant > 0);
128 stats.current.irredundant--;
129 CADICAL_assert (stats.added.irredundant > 0);
130 stats.added.irredundant--;
131 stats.irrlits -= c->size;
132 stats.current.redundant++;
133 stats.added.redundant++;
134 c->glue = c->size - 1;
135 // ... and keep 'stats.added.total'.
136}
137
138/*------------------------------------------------------------------------*/
139// For vivification we have a separate dedicated propagation routine, which
140// prefers to propagate binary clauses first. It also uses its own
141// assignment procedure 'vivify_assign', which does not mess with phase
142// saving during search nor the conflict and other statistics and further
143// can be inlined separately here. The propagation routine needs to ignore
144// (large) clauses which are currently vivified.
145
146inline void Internal::vivify_assign (int lit, Clause *reason) {
148 const int idx = vidx (lit);
149 CADICAL_assert (!vals[idx]);
150 CADICAL_assert (!flags (idx).eliminated () || !reason);
151 Var &v = var (idx);
152 v.level = level; // required to reuse decisions
153 v.trail = (int) trail.size (); // used in 'vivify_better_watch'
155 num_assigned++;
156 v.reason = level ? reason : 0; // for conflict analysis
157 if (!level)
159 const signed char tmp = sign (lit);
160 vals[idx] = tmp;
161 vals[-idx] = -tmp;
162 CADICAL_assert (val (lit) > 0);
163 CADICAL_assert (val (-lit) < 0);
164 trail.push_back (lit);
165 LOG (reason, "vivify assign %d", lit);
166}
167
168// Assume negated literals in candidate clause.
169
172 level++;
173 control.push_back (Level (lit, trail.size ()));
174 LOG ("vivify decide %d", lit);
175 CADICAL_assert (level > 0);
176 CADICAL_assert (propagated == trail.size ());
177 vivify_assign (lit, 0);
178}
179
180// Dedicated routine similar to 'propagate' in 'propagate.cpp' and
181// 'probe_propagate' with 'probe_propagate2' in 'probe.cpp'. Please refer
182// to that code for more explanation on how propagation is implemented.
183
184bool Internal::vivify_propagate (int64_t &ticks) {
188 int64_t before = propagated2 = propagated;
189 for (;;) {
190 if (propagated2 != trail.size ()) {
191 const int lit = -trail[propagated2++];
192 LOG ("vivify propagating %d over binary clauses", -lit);
193 Watches &ws = watches (lit);
194 ticks +=
195 1 + cache_lines (ws.size (), sizeof (const_watch_iterator *));
196 for (const auto &w : ws) {
197 if (!w.binary ())
198 continue;
199 const signed char b = val (w.blit);
200 if (b > 0)
201 continue;
202 if (b < 0)
203 conflict = w.clause; // but continue
204 else {
205 ticks++;
206 build_chain_for_units (w.blit, w.clause, 0);
207 vivify_assign (w.blit, w.clause);
208 lrat_chain.clear ();
209 }
210 }
211 } else if (!conflict && propagated != trail.size ()) {
212 const int lit = -trail[propagated++];
213 LOG ("vivify propagating %d over large clauses", -lit);
214 Watches &ws = watches (lit);
215 const const_watch_iterator eow = ws.end ();
216 const_watch_iterator i = ws.begin ();
217 ticks += 1 + cache_lines (ws.size (), sizeof (*i));
218 watch_iterator j = ws.begin ();
219 while (i != eow) {
220 const Watch w = *j++ = *i++;
221 if (w.binary ())
222 continue;
223 if (val (w.blit) > 0)
224 continue;
225 ticks++;
226 if (w.clause->garbage) {
227 j--;
228 continue;
229 }
231 const int other = lits[0] ^ lits[1] ^ lit;
232 const signed char u = val (other);
233 if (u > 0)
234 j[-1].blit = other;
235 else {
236 const int size = w.clause->size;
237 const const_literal_iterator end = lits + size;
238 const literal_iterator middle = lits + w.clause->pos;
239 literal_iterator k = middle;
240 signed char v = -1;
241 int r = 0;
242 while (k != end && (v = val (r = *k)) < 0)
243 k++;
244 if (v < 0) {
245 k = lits + 2;
246 CADICAL_assert (w.clause->pos <= size);
247 while (k != middle && (v = val (r = *k)) < 0)
248 k++;
249 }
250 w.clause->pos = k - lits;
251 CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ());
252 if (v > 0)
253 j[-1].blit = r;
254 else if (!v) {
255 LOG (w.clause, "unwatch %d in", r);
256 lits[0] = other;
257 lits[1] = r;
258 *k = lit;
259 ticks++;
260 watch_literal (r, lit, w.clause);
261 j--;
262 } else if (!u) {
263 if (w.clause == ignore) {
264 LOG ("ignoring propagation due to clause to vivify");
265 continue;
266 }
267 ticks++;
268 CADICAL_assert (v < 0);
270 vivify_assign (other, w.clause);
271 lrat_chain.clear ();
272 } else {
273 if (w.clause == ignore) {
274 LOG ("ignoring conflict due to clause to vivify");
275 continue;
276 }
277 CADICAL_assert (u < 0);
278 CADICAL_assert (v < 0);
279 conflict = w.clause;
280 break;
281 }
282 }
283 }
284 if (j != i) {
285 while (i != eow)
286 *j++ = *i++;
287 ws.resize (j - ws.begin ());
288 }
289 } else
290 break;
291 }
292 int64_t delta = propagated2 - before;
293 stats.propagations.vivify += delta;
294 if (conflict)
295 LOG (conflict, "conflict");
296 STOP (propagate);
297 return !conflict;
298}
299
300/*------------------------------------------------------------------------*/
301
302// Check whether a literal occurs less often. In the implementation below
303// (search for 'int64_t score = ...' or '@4') we actually compute a
304// weighted occurrence count similar to the Jeroslow Wang heuristic.
305
307
309
311
312 bool operator() (int a, int b) {
313 int64_t n = internal->noccs (a);
314 int64_t m = internal->noccs (b);
315 if (n > m)
316 return true; // larger occurrences / score first
317 if (n < m)
318 return false; // smaller occurrences / score last
319 if (a == -b)
320 return a > 0; // positive literal first
321 return abs (a) < abs (b); // smaller index first
322 }
323};
324
326
328
330
331 bool operator() (int a, int b) {
332 unsigned t = internal->noccs (a);
333 unsigned s = internal->noccs (b);
334 return ((t - s) | ((b - a) & ~(s - t))) >> 31;
335 }
336};
337
338// Sort candidate clauses by the number of occurrences (actually by their
339// score) of their literals, with clauses to be vivified first last. We
340// assume that clauses are sorted w.r.t. more occurring (higher score)
341// literals first (with respect to 'vivify_more_noccs').
342//
343// For example if there are the following (long irredundant) clauses
344//
345// 1 -3 -4 (A)
346// -1 -2 3 4 (B)
347// 2 -3 4 (C)
348//
349// then we have the following literal scores using Jeroslow Wang scores and
350// normalizing it with 2^12 (which is the same as 1<<12):
351//
352// nocc ( 1) = 2^12 * (2^-3 ) = 512 3.
353// nocc (-1) = 2^12 * (2^-4 ) = 256 6.
354// nocc ( 2) = 2^12 * (2^-3 ) = 512 4.
355// nocc (-2) = 2^12 * (2^-4 ) = 256 7. @1
356// nocc ( 3) = 2^12 * (2^-4 ) = 256 8.
357// nocc (-3) = 2^12 * (2^-3 + 2^-3) = 1024 1.
358// nocc ( 4) = 2^12 * (2^-3 + 2^-4) = 768 2.
359// nocc (-4) = 2^12 * (2^-3 ) = 512 5.
360//
361// which gives the literal order (according to 'vivify_more_noccs')
362//
363// -3, 4, 1, 2, -4, -1, -2, 3
364//
365// Then sorting the literals in each clause gives
366//
367// -3 1 -4 (A')
368// 4 -1 -2 3 (B') @2
369// -3 4 2 (C')
370//
371// and finally sorting those clauses lexicographically w.r.t. scores is
372//
373// -3 4 2 (C')
374// -3 1 -4 (A') @3
375// 4 -1 -2 3 (B')
376//
377// This order is defined by 'vivify_clause_later' which returns 'true' if
378// the first clause should be vivified later than the second.
379
381
383
385
386 bool operator() (Clause *a, Clause *b) const {
387
388 if (a == b)
389 return false;
390
391 // First focus on clauses scheduled in the last vivify round but not
392 // checked yet since then.
393 //
394 if (!a->vivify && b->vivify)
395 return true;
396 if (a->vivify && !b->vivify)
397 return false;
398
399 // Among redundant clauses (in redundant mode) prefer small glue.
400 //
401 if (a->redundant) {
403 if (a->glue > b->glue)
404 return true;
405 if (a->glue < b->glue)
406 return false;
407 }
408
409 // Then prefer shorter size.
410 //
411 if (a->size > b->size)
412 return true;
413 if (a->size < b->size)
414 return false;
415
416 // Now compare literals in the clauses lexicographically with respect to
417 // the literal order 'vivify_more_noccs' assuming literals are sorted
418 // decreasingly with respect to that order.
419 //
420 const auto eoa = a->end (), eob = b->end ();
421 auto j = b->begin ();
422 for (auto i = a->begin (); i != eoa && j != eob; i++, j++)
423 if (*i != *j)
424 return vivify_more_noccs (internal) (*j, *i);
425
426 return j == eob; // Prefer shorter clauses to be vivified first.
427 }
428};
429
430/*------------------------------------------------------------------------*/
431
432// Attempting on-the-fly subsumption during sorting when the last line is
433// reached in 'vivify_clause_later' above turned out to be trouble some for
434// identical clauses. This is the single point where 'vivify_clause_later'
435// is not asymmetric and would require 'stable' sorting for determinism. It
436// can also not be made 'complete' on-the-fly. Instead of on-the-fly
437// subsumption we thus go over the sorted scheduled in a linear scan
438// again and remove certain subsumed clauses (the subsuming clause is
439// syntactically a prefix of the subsumed clause), which includes
440// those troublesome syntactically identical clauses.
441
443
444 bool operator() (Clause *a, Clause *b) const {
445
446 const auto eoa = a->end (), eob = b->end ();
447 auto i = a->begin (), j = b->begin ();
448 for (; i != eoa && j != eob; i++, j++)
449 if (*i != *j)
450 return *i < *j;
451
452 return j == eob && i != eoa;
453 }
454};
455
456void Internal::flush_vivification_schedule (std::vector<Clause *> &schedule,
457 int64_t &ticks) {
458 ticks += 1 + 3 * cache_lines (schedule.size (), sizeof (Clause *));
459 stable_sort (schedule.begin (), schedule.end (), vivify_flush_smaller ());
460
461 const auto end = schedule.end ();
462 auto j = schedule.begin (), i = j;
463
464 Clause *prev = 0;
465 int64_t subsumed = 0;
466 for (; i != end; i++) {
467 ticks++;
468 Clause *c = *j++ = *i;
469 if (!prev || c->size < prev->size) {
470 prev = c;
471 continue;
472 }
473 const auto eop = prev->end ();
474 auto k = prev->begin ();
475 for (auto l = c->begin (); k != eop; k++, l++)
476 if (*k != *l)
477 break;
478 if (k == eop) {
479 LOG (c, "found subsumed");
480 LOG (prev, "subsuming");
482 CADICAL_assert (!prev->garbage);
483 CADICAL_assert (c->redundant || !prev->redundant);
484 mark_garbage (c);
485 subsumed++;
486 j--;
487 } else
488 prev = c;
489 }
490
491 if (subsumed)
492 PHASE ("vivify", stats.vivifications,
493 "flushed %" PRId64 " subsumed scheduled clauses", subsumed);
494
495 stats.vivifysubs += subsumed;
496
497 if (subsumed) {
498 schedule.resize (j - schedule.begin ());
499 shrink_vector (schedule);
500 } else
501 CADICAL_assert (j == end);
502}
503
504/*------------------------------------------------------------------------*/
505
506// Depending on whether we try to vivify redundant or irredundant clauses,
507// we schedule a clause to be vivified. For redundant clauses we initially
508// only try to vivify them if they are likely to survive the next 'reduce'
509// operation, but this left the last schedule empty most of the time.
510
512 if (c->garbage)
513 return false;
514 if (opts.vivifyonce >= 1 && c->redundant && c->vivified)
515 return false;
516 if (opts.vivifyonce >= 2 && !c->redundant && c->vivified)
517 return false;
518 if (!c->redundant)
519 return true;
521
522 // likely_to_be_kept_clause is too aggressive at removing tier-3 clauses
523 return true;
524}
525
526/*------------------------------------------------------------------------*/
527
528// In a strengthened clause the idea is to move non-false literals to the
529// front, followed by false literals. Literals are further sorted by
530// reverse assignment order. The goal is to use watches which require to
531// backtrack as few as possible decision levels.
532
534
536
538
539 bool operator() (int a, int b) {
540
541 const signed char av = internal->val (a), bv = internal->val (b);
542
543 if (av >= 0 && bv < 0)
544 return true;
545 if (av < 0 && bv >= 0)
546 return false;
547
548 return internal->var (a).trail > internal->var (b).trail;
549 }
550};
551
552// Common code to actually strengthen a candidate clause. The resulting
553// strengthened clause is communicated through the global 'clause'.
554
556
557 CADICAL_assert (!clause.empty ());
558
559 if (clause.size () == 1) {
560
562 const int unit = clause[0];
563 LOG (c, "vivification shrunken to unit %d", unit);
564 CADICAL_assert (!val (unit));
565 assign_unit (unit);
566 // lrat_chain.clear (); done in search_assign
567 stats.vivifyunits++;
568
569 bool ok = propagate ();
570 if (!ok)
572
573 } else {
574
575 // See explanation before 'vivify_better_watch' above.
576 //
577 sort (clause.begin (), clause.end (), vivify_better_watch (this));
578
579 int new_level = level;
580
581 const int lit0 = clause[0];
582 signed char val0 = val (lit0);
583 if (val0 < 0) {
584 const int level0 = var (lit0).level;
585 LOG ("1st watch %d negative at level %d", lit0, level0);
586 new_level = level0 - 1;
587 }
588
589 const int lit1 = clause[1];
590 const signed char val1 = val (lit1);
591 if (val1 < 0 && !(val0 > 0 && var (lit0).level <= var (lit1).level)) {
592 const int level1 = var (lit1).level;
593 LOG ("2nd watch %d negative at level %d", lit1, level1);
594 new_level = level1 - 1;
595 }
596
597 CADICAL_assert (new_level >= 0);
598 if (new_level < level)
599 backtrack (new_level);
600
601 CADICAL_assert (val (lit0) >= 0);
602 CADICAL_assert (val (lit1) >= 0 || (val (lit0) > 0 && val (lit1) < 0 &&
603 var (lit0).level <= var (lit1).level));
604
605 Clause *d = new_clause_as (c);
606 LOG (c, "before vivification");
607 LOG (d, "after vivification");
608 (void) d;
609 }
610 clause.clear ();
611 mark_garbage (c);
612 lrat_chain.clear ();
613 ++stats.vivifystrs;
614}
615
617
618 sort (c->begin (), c->end (), vivify_better_watch (this));
619
620 int new_level = level;
621
622 const int lit0 = c->literals[0];
623 signed char val0 = val (lit0);
624 if (val0 < 0) {
625 const int level0 = var (lit0).level;
626 LOG ("1st watch %d negative at level %d", lit0, level0);
627 new_level = level0 - 1;
628 }
629
630 const int lit1 = c->literals[1];
631 const signed char val1 = val (lit1);
632 if (val1 < 0 && !(val0 > 0 && var (lit0).level <= var (lit1).level)) {
633 const int level1 = var (lit1).level;
634 LOG ("2nd watch %d negative at level %d", lit1, level1);
635 new_level = level1 - 1;
636 }
637
638 CADICAL_assert (new_level >= 0);
639 if (new_level < level)
640 backtrack (new_level);
641
642 CADICAL_assert (val (lit0) >= 0);
643 CADICAL_assert (val (lit1) >= 0 || (val (lit0) > 0 && val (lit1) < 0 &&
644 var (lit0).level <= var (lit1).level));
645}
646// Conflict analysis from 'start' which learns a decision only clause.
647//
648// We cannot use the stack-based implementation of Kissat, because we need
649// to iterate over the conflict in topological ordering to produce a valid
650// LRAT proof
651
652void Internal::vivify_analyze (Clause *start, bool &subsumes,
653 Clause **subsuming,
654 const Clause *const candidate, int implied,
655 bool &redundant) {
656 const auto &t = &trail; // normal trail, so next_trail is wrong
657 int i = t->size (); // Start at end-of-trail.
658 Clause *reason = start;
659 CADICAL_assert (reason);
660 CADICAL_assert (!trail.empty ());
661 int uip = trail.back ();
662 bool mark_implied = (implied);
663
664 while (i >= 0) {
665 if (reason) {
666 redundant = (redundant || reason->redundant);
667 subsumes = (start != reason && reason->size <= start->size);
668 LOG (reason, "resolving on %d with", uip);
669 for (auto other : *reason) {
670 const Var v = var (other);
671 Flags &f = flags (other);
672 if (!marked2 (other) && v.level) {
673 LOG ("not subsuming due to lit %d", other);
674 subsumes = false;
675 }
676 if (!val (other)) {
677 LOG ("skipping unset lit %d", other);
678 continue;
679 }
680 if (other == uip) {
681 continue;
682 }
683 if (!v.level) {
684 if (f.seen || !lrat || reason == start)
685 continue;
686 LOG ("unit reason for %d", other);
687 int64_t id = unit_id (-other);
688 LOG ("adding unit reason %zd for %d", id, other);
689 unit_chain.push_back (id);
690 f.seen = true;
691 analyzed.push_back (other);
692 continue;
693 }
694 if (mark_implied && other != implied) {
695 LOG ("skipping non-implied literal %d on current level", other);
696 continue;
697 }
698
699 CADICAL_assert (val (other));
700 if (f.seen)
701 continue;
702 LOG ("pushing lit %d", other);
703 analyzed.push_back (other);
704 f.seen = true;
705 }
706 if (start->redundant) {
707 const int new_glue = recompute_glue (start);
708 promote_clause (start, new_glue);
709 }
710 if (subsumes) {
711 CADICAL_assert (reason);
712 LOG (reason, "clause found subsuming");
713 LOG (candidate, "clause found subsumed");
714 *subsuming = reason;
715 return;
716 }
717 } else {
718 LOG ("vivify analyzed decision %d", uip);
719 clause.push_back (-uip);
720 }
721 mark_implied = false;
722
723 uip = 0;
724 while (!uip && i > 0) {
725 CADICAL_assert (i > 0);
726 const int lit = (*t)[--i];
727 if (!var (lit).level)
728 continue;
729 if (flags (lit).seen)
730 uip = lit;
731 }
732 if (!uip)
733 break;
734 LOG ("uip is %d", uip);
735 Var &w = var (uip);
736 reason = w.reason;
737 if (lrat && reason)
738 lrat_chain.push_back (reason->id);
739 }
740 (void) candidate;
741}
742
744 int implied, Clause **subsuming,
745 bool &redundant) {
746 CADICAL_assert (lrat_chain.empty ());
747 bool subsumes;
748 Clause *reason;
749
750 CADICAL_assert (clause.empty ());
751 if (implied) {
752 reason = candidate;
754 const int not_implied = -implied;
755 CADICAL_assert (var (not_implied).level);
756 Flags &f = flags (not_implied);
757 f.seen = true;
758 LOG ("pushing implied lit %d", not_implied);
759 analyzed.push_back (not_implied);
760 clause.push_back (implied);
761 } else {
762 reason = (conflict ? conflict : candidate);
763 CADICAL_assert (reason);
764 CADICAL_assert (!reason->garbage);
766 subsumes = (candidate != reason);
767 redundant = reason->redundant;
768 LOG (reason, "resolving with");
769 if (lrat)
770 lrat_chain.push_back (reason->id);
771 for (auto lit : *reason) {
772 const Var &v = var (lit);
773 Flags &f = flags (lit);
774 CADICAL_assert (val (lit) < 0);
775 if (!v.level) {
776 if (!lrat)
777 continue;
778 LOG ("adding unit %d", lit);
779 if (!f.seen) {
780 // nevertheless we can use var (l) as if l was still assigned
781 // because var is updated lazily
782 int64_t id = unit_id (-lit);
783 LOG ("adding unit reason %zd for %d", id, lit);
784 unit_chain.push_back (id);
785 }
786 f.seen = true;
787 analyzed.push_back (lit);
788 continue;
789 }
791 if (!marked2 (lit)) {
792 LOG ("lit %d is not marked", lit);
793 subsumes = false;
794 }
795 LOG ("analyzing lit %d", lit);
796 LOG ("pushing lit %d", lit);
797 analyzed.push_back (lit);
798 f.seen = true;
799 }
800 if (reason != candidate && reason->redundant) {
801 const int new_glue = recompute_glue (reason);
802 promote_clause (reason, new_glue);
803 }
804 if (subsumes) {
805 CADICAL_assert (candidate != reason);
806#ifndef CADICAL_NDEBUG
807 int nonfalse_reason = 0;
808 for (auto lit : *reason)
809 if (!fixed (lit))
810 ++nonfalse_reason;
811
812 int nonfalse_candidate = 0;
813 for (auto lit : *candidate)
814 if (!fixed (lit))
815 ++nonfalse_candidate;
816
817 CADICAL_assert (nonfalse_reason <= nonfalse_candidate);
818#endif
819 LOG (candidate, "vivify subsumed 0");
820 LOG (reason, "vivify subsuming 0");
821 *subsuming = reason;
823 if (lrat)
824 lrat_chain.clear ();
825 return;
826 }
827 }
828
829 vivify_analyze (reason, subsumes, subsuming, candidate, implied,
830 redundant);
832 if (subsumes) {
833 CADICAL_assert (*subsuming);
834 LOG (candidate, "vivify subsumed");
835 LOG (*subsuming, "vivify subsuming");
836 if (lrat)
837 lrat_chain.clear ();
838 }
839}
840/*------------------------------------------------------------------------*/
841
842bool Internal::vivify_shrinkable (const std::vector<int> &sorted,
843 Clause *conflict) {
844
845 unsigned count_implied = 0;
846 for (auto lit : sorted) {
847 const signed char value = val (lit);
848 if (!value) {
849 LOG ("vivification unassigned %d", lit);
850 return true;
851 }
852 if (value > 0) {
853 LOG ("vivification implied satisfied %d", lit);
854 if (conflict)
855 return true;
856 if (count_implied++) {
857 LOG ("at least one implied literal with conflict thus shrinking");
858 return true;
859 }
860 } else {
861 CADICAL_assert (value < 0);
862 const Var &v = var (lit);
863 const Flags &f = flags (lit);
864 if (!v.level)
865 continue;
866 if (!f.seen) {
867 LOG ("vivification non-analyzed %d", lit);
868 return true;
869 }
870 if (v.reason) {
871 LOG ("vivification implied falsified %d", lit);
872 return true;
873 }
874 }
875 }
876 return false;
877}
878/*------------------------------------------------------------------------*/
879
881 switch (vivifier.tier) {
883 ++stats.vivifystred1;
884 break;
886 ++stats.vivifystred2;
887 break;
889 ++stats.vivifystred3;
890 break;
891 default:
893 ++stats.vivifystrirr;
894 break;
895 }
896}
897/*------------------------------------------------------------------------*/
898// instantiate last literal (see the description of the hack track 2023),
899// fix the watches and
900// backtrack two level back
902 const std::vector<int> &sorted, Clause *c,
903 std::vector<std::tuple<int, Clause *, bool>> &lrat_stack,
904 int64_t &ticks) {
905 LOG ("now trying instantiation");
906 conflict = nullptr;
907 const int lit = sorted.back ();
908 LOG ("vivify instantiation");
909 CADICAL_assert (!var (lit).reason);
912 backtrack (level - 1);
913 CADICAL_assert (val (lit) == 0);
914 stats.vivifydecs++;
916 bool ok = vivify_propagate (ticks);
917 if (!ok) {
918 LOG (c, "instantiate success with literal %d in", lit);
919 stats.vivifyinst++;
920 // strengthen clause
921 if (lrat) {
923 CADICAL_assert (lrat_chain.empty ());
924 vivify_build_lrat (0, c, lrat_stack);
925 vivify_build_lrat (0, conflict, lrat_stack);
927 }
928 int remove = lit;
929 conflict = nullptr;
930 unwatch_clause (c);
932 strengthen_clause (c, remove);
934 watch_clause (c);
936 return true;
937 } else {
938 LOG ("vivify instantiation failed");
939 return false;
940 }
941}
942
943/*------------------------------------------------------------------------*/
944
945// Main function: try to vivify this candidate clause in the given mode.
946
948
949 CADICAL_assert (c->size > 2); // see (NO-BINARY) below
950 CADICAL_assert (analyzed.empty ());
951
952 c->vivify = false; // mark as checked / tried
953 c->vivified = true; // and globally remember
954
956
957 auto &lrat_stack = vivifier.lrat_stack;
958 auto &ticks = vivifier.ticks;
959 ticks++;
960
961 // First check whether the candidate clause is already satisfied and at
962 // the same time copy its non fixed literals to 'sorted'. The literals
963 // in the candidate clause might not be sorted anymore due to replacing
964 // watches during propagation, even though we sorted them initially
965 // while pushing the clause onto the schedule and sorting the schedule.
966 //
967 auto &sorted = vivifier.sorted;
968 sorted.clear ();
969
970 for (const auto &lit : *c) {
971 const int tmp = fixed (lit);
972 if (tmp > 0) {
973 LOG (c, "satisfied by propagated unit %d", lit);
974 mark_garbage (c);
975 return false;
976 } else if (!tmp)
977 sorted.push_back (lit);
978 }
979
980 CADICAL_assert (sorted.size () > 1);
981 if (sorted.size () == 2) {
982 LOG ("skipping actual binary");
983 return false;
984 }
985
986 sort (sorted.begin (), sorted.end (), vivify_more_noccs_kissat (this));
987
988 // The actual vivification checking is performed here, by assuming the
989 // negation of each of the remaining literals of the clause in turn and
990 // propagating it. If a conflict occurs or another literal in the
991 // clause becomes assigned during propagation, we can stop.
992 //
993 LOG (c, "vivification checking");
994 stats.vivifychecks++;
995
996 // If the decision 'level' is non-zero, then we can reuse decisions for
997 // the previous candidate, and avoid re-propagating them. In preliminary
998 // experiments this saved between 30%-50% decisions (and thus
999 // propagations), which in turn lets us also vivify more clauses within
1000 // the same propagation bounds, or terminate earlier if vivify runs to
1001 // completion.
1002 //
1003 if (level) {
1004#ifdef LOGGING
1005 int orig_level = level;
1006#endif
1007 // First check whether this clause is actually a reason for forcing
1008 // one of its literals to true and then backtrack one level before
1009 // that happened. Otherwise this clause might be incorrectly
1010 // considered to be redundant or if this situation is checked then
1011 // redundancy by other clauses using this forced literal becomes
1012 // impossible.
1013 //
1014 int forced = 0;
1015
1016 // This search could be avoided if we would eagerly set the 'reason'
1017 // boolean flag of clauses, which however we do not want to do for
1018 // binary clauses (during propagation) and thus would still require
1019 // a version of 'protect_reason' for binary clauses during 'reduce'
1020 // (well binary clauses are not collected during 'reduce', but again
1021 // this exception from the exception is pretty complex and thus a
1022 // simply search here is probably easier to understand).
1023
1024 for (const auto &lit : *c) {
1025 const signed char tmp = val (lit);
1026 if (tmp < 0)
1027 continue;
1028 if (tmp > 0 && var (lit).reason == c)
1029 forced = lit;
1030 break;
1031 }
1032 if (forced) {
1033 LOG ("clause is reason forcing %d", forced);
1034 CADICAL_assert (var (forced).level);
1036 }
1037
1038 // As long the (remaining) literals of the sorted clause match
1039 // decisions on the trail we just reuse them.
1040 //
1041 if (level) {
1042
1043 int l = 1; // This is the decision level we want to reuse.
1044
1045 for (const auto &lit : sorted) {
1047 const int decision = control[l].decision;
1048 if (-lit == decision) {
1049 LOG ("reusing decision %d at decision level %d", decision, l);
1050 stats.vivifyreused++;
1051 if (++l > level)
1052 break;
1053 } else {
1054 LOG ("literal %d does not match decision %d at decision level %d",
1055 lit, decision, l);
1057 break;
1058 }
1059 }
1060 }
1061
1062 LOG ("reused %d decision levels from %d", level, orig_level);
1063 }
1064
1065 LOG (sorted, "sorted size %zd probing schedule", sorted.size ());
1066
1067 // Make sure to ignore this clause during propagation. This is not that
1068 // easy for binary clauses (NO-BINARY), e.g., ignoring binary clauses,
1069 // without changing 'propagate'. Actually, we do not want to remove binary
1070 // clauses which are subsumed. Those are hyper binary resolvents and
1071 // should be kept as learned clauses instead, unless they are transitive
1072 // in the binary implication graph, which in turn is detected during
1073 // transitive reduction in 'transred'.
1074 //
1075 ignore = c;
1076
1077 int subsume = 0; // determined to be redundant / subsumed
1078
1079 // If the candidate is redundant, i.e., we are in redundant mode, the
1080 // clause is subsumed (in one of the two cases below where 'subsume' is
1081 // assigned) and further all reasons involved are only binary clauses,
1082 // then this redundant clause is what we once called a hidden tautology,
1083 // and even for redundant clauses it makes sense to remove the candidate.
1084 // It does not add anything to propagation power of the formula. This is
1085 // the same argument as removing transitive clauses in the binary
1086 // implication graph during transitive reduction.
1087 //
1088
1089 // Go over the literals in the candidate clause in sorted order.
1090 //
1091 for (const auto &lit : sorted) {
1092
1093 // Exit loop as soon a literal is positively implied (case '@5' below)
1094 // or propagation of the negation of a literal fails ('@6').
1095 //
1096 if (subsume)
1097 break;
1098
1099 // We keep on assigning literals, even though we know already that we
1100 // can remove one (was negatively implied), since we either might run
1101 // into the 'subsume' case above or more false literals become implied.
1102 // In any case this might result in stronger vivified clauses. As a
1103 // consequence continue with this loop even if 'remove' is non-zero.
1104
1105 const signed char tmp = val (lit);
1106
1107 if (tmp) { // literal already assigned
1108
1109 const Var &v = var (lit);
1111 if (!v.reason) {
1112 LOG ("skipping decision %d", lit);
1113 continue;
1114 }
1115
1116 if (tmp < 0) {
1118 LOG ("literal %d is already false and can be removed", lit);
1119 continue;
1120 }
1121
1122 CADICAL_assert (tmp > 0);
1123 LOG ("subsumed since literal %d already true", lit);
1124 subsume = lit; // will be able to subsume candidate '@5'
1125 break;
1126 }
1127
1128 CADICAL_assert (!tmp);
1129
1130 stats.vivifydecs++;
1131 vivify_assume (-lit);
1132 LOG ("negated decision %d score %" PRId64 "", lit, noccs (lit));
1133
1134 if (!vivify_propagate (ticks)) {
1135 break; // hot-spot
1136 }
1137 }
1138
1139 if (subsume) {
1140 int better_subsume_trail = var (subsume).trail;
1141 for (auto lit : sorted) {
1142 if (val (lit) <= 0)
1143 continue;
1144 const Var v = var (lit);
1145 if (v.trail < better_subsume_trail) {
1146 LOG ("improving subsume from %d at %d to %d at %d", subsume,
1147 better_subsume_trail, lit, v.trail);
1148 better_subsume_trail = v.trail;
1149 subsume = lit;
1150 }
1151 }
1152 }
1153
1154 Clause *subsuming = nullptr;
1155 bool redundant = false;
1156 const int level_after_assumptions = level;
1157 CADICAL_assert (level_after_assumptions);
1158 vivify_deduce (c, conflict, subsume, &subsuming, redundant);
1159
1160 bool res;
1161
1162 // reverse lrat_chain. We could probably work with reversed iterators
1163 // (views) to be more efficient but we would have to distinguish in proof
1164 //
1165 if (lrat) {
1166 for (auto id : unit_chain)
1167 lrat_chain.push_back (id);
1168 unit_chain.clear ();
1169 reverse (lrat_chain.begin (), lrat_chain.end ());
1170 }
1171
1172 if (subsuming) {
1173 CADICAL_assert (c != subsuming);
1174 LOG (c, "deleting subsumed clause");
1175 if (c->redundant && subsuming->redundant && c->glue < subsuming->glue) {
1176 promote_clause (c, c->glue);
1177 }
1178 vivify_subsume_clause (subsuming, c);
1179 res = false;
1180 // stats.vivifysubs++; // already done in vivify_subsume_clause
1181 } else if (vivify_shrinkable (sorted, conflict)) {
1183 LOG ("vivify succeeded, learning new clause");
1185 LOG (lrat_chain, "lrat");
1186 LOG (clause, "learning clause");
1187 conflict = nullptr; // TODO dup from below
1189 res = true;
1190 } else if (subsume && c->redundant) {
1191 LOG (c, "vivification implied");
1192 mark_garbage (c);
1193 ++stats.vivifyimplied;
1194 res = true;
1195 } else if ((conflict || subsume) && !c->redundant && !redundant) {
1196 LOG ("demote clause from irredundant to redundant");
1197 if (opts.vivifydemote) {
1198 demote_clause (c);
1199 const int new_glue = recompute_glue (c);
1200 promote_clause (c, new_glue);
1201 res = false;
1202 } else {
1203 mark_garbage (c);
1204 ++stats.vivifyimplied;
1205 res = true;
1206 }
1207 } else if (subsume) {
1208 LOG (c, "no vivification instantiation with implied literal %d",
1209 subsume);
1212 res = false;
1213 ++stats.vivifyimplied;
1214 } else {
1215 CADICAL_assert (level > 2);
1216 CADICAL_assert ((size_t) level == sorted.size ());
1217 LOG (c, "vivification failed on");
1218 lrat_chain.clear ();
1220 if (!subsume && opts.vivifyinst) {
1221 res = vivify_instantiate (sorted, c, lrat_stack, ticks);
1223 } else {
1224 LOG ("cannot apply instantiation");
1225 res = false;
1226 }
1227 }
1228
1229 if (conflict && level == level_after_assumptions) {
1230 LOG ("forcing backtracking at least one level after conflict");
1232 }
1233
1234 clause.clear ();
1235 clear_analyzed_literals (); // TODO why needed?
1236 lrat_chain.clear ();
1237 conflict = nullptr;
1238 return res;
1239}
1240
1241// when we can strengthen clause c we have to build lrat.
1242// uses f.seen so do not forget to clear flags afterwards.
1243// this can happen in three cases. (1), (2) are only sound in redundant mode
1244// (1) literal l in c is positively implied. in this case we call the
1245// function with (l, l.reason). This justifies the reduction because the new
1246// clause c' will include l and all decisions so l.reason is a conflict
1247// assuming -c' (2) conflict during vivify propagation. function is called
1248// with (0, conflict) similar to (1) but more direct. (3) some literals in c
1249// are negatively implied and can therefore be removed. in this case we call
1250// the function with (0, c). originally we justified each literal in c on
1251// its own but this is not actually necessary.
1252//
1253
1254// Non-recursive version, as some bugs have been found. DFS over the
1255// reasons with preordering (aka we explore the entire reason before
1256// exploring deeper)
1258 int lit, Clause *reason,
1259 std::vector<std::tuple<int, Clause *, bool>> &stack) {
1260 CADICAL_assert (stack.empty ());
1261 stack.push_back ({lit, reason, false});
1262 while (!stack.empty ()) {
1263 int lit;
1264 Clause *reason;
1265 bool finished;
1266 std::tie (lit, reason, finished) = stack.back ();
1267 LOG ("VIVIFY LRAT justifying %d", lit);
1268 stack.pop_back ();
1269 if (lit && flags (lit).seen) {
1270 LOG ("skipping already justified");
1271 continue;
1272 }
1273 if (finished) {
1274 lrat_chain.push_back (reason->id);
1275 if (lit && reason) {
1276 Flags &f = flags (lit);
1277 f.seen = true;
1278 analyzed.push_back (lit); // CADICAL_assert (val (other) < 0);
1279 CADICAL_assert (flags (lit).seen);
1280 }
1281 continue;
1282 } else
1283 stack.push_back ({lit, reason, true});
1284 for (const auto &other : *reason) {
1285 if (other == lit)
1286 continue;
1287 Var &v = var (other);
1288 Flags &f = flags (other);
1289 if (f.seen)
1290 continue;
1291 if (!v.level) {
1292 const int64_t id = unit_id (-other);
1293 lrat_chain.push_back (id);
1294 f.seen = true;
1295 analyzed.push_back (other);
1296 continue;
1297 }
1298 if (v.reason) { // recursive justification
1299 LOG ("VIVIFY LRAT pushing %d", other);
1300 stack.push_back ({other, v.reason, false});
1301 }
1302 }
1303 }
1304 stack.clear ();
1305}
1306
1307// calculate lrat_chain
1308//
1309inline void Internal::vivify_chain_for_units (int lit, Clause *reason) {
1310 if (!lrat)
1311 return;
1312 // LOG ("building chain for units"); bad line for debugging
1313 // equivalence if (opts.chrono && assignment_level (lit, reason)) return;
1314 if (level)
1315 return; // not decision level 0
1316 CADICAL_assert (lrat_chain.empty ());
1317 for (auto &reason_lit : *reason) {
1318 if (lit == reason_lit)
1319 continue;
1320 CADICAL_assert (val (reason_lit));
1321 const int signed_reason_lit = val (reason_lit) * reason_lit;
1322 int64_t id = unit_id (signed_reason_lit);
1323 lrat_chain.push_back (id);
1324 }
1325 lrat_chain.push_back (reason->id);
1326}
1327
1329 LOG (c, "creating vivify_refs of clause");
1330 vivify_ref ref;
1331 ref.clause = c;
1332 ref.size = c->size;
1333 for (int i = 0; i < COUNTREF_COUNTS; ++i)
1334 ref.count[i] = 0;
1335 ref.vivify = c->vivify;
1336 int lits[COUNTREF_COUNTS] = {0};
1337 for (int i = 0; i != std::min (COUNTREF_COUNTS, c->size); ++i) {
1338 int best = 0;
1339 unsigned best_count = 0;
1340 for (auto lit : *c) {
1341 LOG ("to find best number of occurrences for literal %d, looking at "
1342 "literal %d",
1343 i, lit);
1344 for (int j = 0; j != i; ++j) {
1345 LOG ("comparing %d with literal %d", lit, lits[j]);
1346 if (lits[j] == lit)
1347 goto CONTINUE_WITH_NEXT_LITERAL;
1348 }
1349 {
1350 const int64_t lit_count = internal->noccs (lit);
1351 CADICAL_assert (lit_count);
1352 LOG ("checking literal %d with %zd occurrences", lit, lit_count);
1353 if (lit_count <= best_count)
1354 continue;
1355 best_count = lit_count;
1356 best = lit;
1357 }
1358 CONTINUE_WITH_NEXT_LITERAL:;
1359 }
1360 CADICAL_assert (best);
1361 CADICAL_assert (best_count);
1362 CADICAL_assert (best_count < UINT32_MAX);
1363 ref.count[i] =
1364 ((uint64_t) best_count << 32) + (uint64_t) internal->vlit (best);
1365 LOG ("final count at position %d is %d - %d: %lu", i, best, best_count,
1366 ref.count[i]);
1367 lits[i] = best;
1368 }
1369 return ref;
1370}
1371/*------------------------------------------------------------------------*/
1372inline void
1373Internal::vivify_prioritize_leftovers ([[maybe_unused]] char tag,
1374 size_t prioritized,
1375 std::vector<Clause *> &schedule) {
1376 if (prioritized) {
1377 PHASE ("vivify", stats.vivifications,
1378 "[phase %c] leftovers of %" PRId64 " clause", tag, prioritized);
1379 } else {
1380 PHASE ("vivify", stats.vivifications,
1381 "[phase %c] prioritizing all clause", tag);
1382 for (auto c : schedule)
1383 c->vivify = true;
1384 }
1385 const size_t max = opts.vivifyschedmax;
1386 if (schedule.size () > max) {
1387 if (prioritized) {
1388 std::partition (begin (schedule), end (schedule),
1389 [] (Clause *c) { return c->vivify; });
1390 }
1391 schedule.resize (max);
1392 }
1393 // let's try to save a bit of memory
1394 shrink_vector (schedule);
1395}
1396
1398
1399 const int tier1 = vivifier.tier1_limit;
1400 const int tier2 = vivifier.tier2_limit;
1401 // Count the number of occurrences of literals in all clauses,
1402 // particularly binary clauses, which are usually responsible
1403 // for most of the propagations.
1404 //
1405 init_noccs ();
1406
1407 // Disconnect all watches since we sort literals within clauses.
1408 //
1410#if 0
1411 clear_watches ();
1412#endif
1413
1414 size_t prioritized_irred = 0, prioritized_tier1 = 0,
1415 prioritized_tier2 = 0, prioritized_tier3 = 0;
1416 for (const auto &c : clauses) {
1417 ++ticks;
1418 if (c->size == 2)
1419 continue; // see also (NO-BINARY) above
1421 continue;
1422
1423 // This computes an approximation of the Jeroslow Wang heuristic
1424 // score
1425 //
1426 // nocc (L) = sum 2^(12-|C|)
1427 // L in C in F
1428 //
1429 // but we cap the size at 12, that is all clauses of size 12 and
1430 // larger contribute '1' to the score, which allows us to use 'long'
1431 // numbers. See the example above (search for '@1').
1432 //
1433 const int shift = 12 - c->size;
1434 const int64_t score = shift < 1 ? 1 : (1l << shift); // @4
1435 for (const auto lit : *c) {
1436 noccs (lit) += score;
1437 }
1438 LOG (c, "putting clause in candidates");
1439 if (!c->redundant)
1440 vivifier.schedule_irred.push_back (c),
1441 prioritized_irred += (c->vivify);
1442 else if (c->glue <= tier1)
1443 vivifier.schedule_tier1.push_back (c),
1444 prioritized_tier1 += (c->vivify);
1445 else if (c->glue <= tier2)
1446 vivifier.schedule_tier2.push_back (c),
1447 prioritized_tier2 += (c->vivify);
1448 else
1449 vivifier.schedule_tier3.push_back (c),
1450 prioritized_tier3 += (c->vivify);
1451 ++ticks;
1452 }
1453
1454 vivify_prioritize_leftovers ('x', prioritized_irred,
1455 vivifier.schedule_irred);
1456 vivify_prioritize_leftovers ('u', prioritized_tier1,
1457 vivifier.schedule_tier1);
1458 vivify_prioritize_leftovers ('v', prioritized_tier2,
1459 vivifier.schedule_tier2);
1460 vivify_prioritize_leftovers ('w', prioritized_tier3,
1461 vivifier.schedule_tier3);
1462
1463 if (opts.vivifyflush) {
1464 clear_watches ();
1465 for (auto &sched : vivifier.schedules) {
1466 for (const auto &c : sched) {
1467 // Literals in scheduled clauses are sorted with their highest score
1468 // literals first (as explained above in the example at '@2'). This
1469 // is also needed in the prefix subsumption checking below. We do an
1470 // approximation below that is done only in the vivify_ref structure
1471 // below.
1472 //
1473 sort (c->begin (), c->end (), vivify_more_noccs (this));
1474 }
1475 // Flush clauses subsumed by another clause with the same prefix,
1476 // which also includes flushing syntactically identical clauses.
1477 //
1478 flush_vivification_schedule (sched, ticks);
1479 }
1480 connect_watches (); // watch all relevant clauses
1481 }
1482#if 0
1483 connect_watches (); // watch all relevant clauses
1484 vivify_propagate (ticks);
1485#endif
1486 vivify_propagate (ticks);
1487}
1488
1489inline std::vector<vivify_ref> &current_refs_schedule (Vivifier &vivifier) {
1490 switch (vivifier.tier) {
1491 case Vivify_Mode::TIER1:
1492 return vivifier.refs_schedule_tier1;
1493 break;
1494 case Vivify_Mode::TIER2:
1495 return vivifier.refs_schedule_tier2;
1496 break;
1497 case Vivify_Mode::TIER3:
1498 return vivifier.refs_schedule_tier3;
1499 break;
1500 default:
1501 return vivifier.refs_schedule_irred;
1502 break;
1503 }
1504#ifdef WIN32
1505 __assume(false);
1506#else
1507 __builtin_unreachable ();
1508#endif
1509}
1510
1511inline std::vector<Clause *> &current_schedule (Vivifier &vivifier) {
1512 switch (vivifier.tier) {
1513 case Vivify_Mode::TIER1:
1514 return vivifier.schedule_tier1;
1515 break;
1516 case Vivify_Mode::TIER2:
1517 return vivifier.schedule_tier2;
1518 break;
1519 case Vivify_Mode::TIER3:
1520 return vivifier.schedule_tier3;
1521 break;
1522 default:
1523 return vivifier.schedule_irred;
1524 break;
1525 }
1526#ifdef WIN32
1527 __assume(false);
1528#else
1529 __builtin_unreachable ();
1530#endif
1531}
1532
1538 typedef uint64_t Type;
1539 Type operator() (const vivify_ref &a) const { return a.count[offset]; }
1540};
1541
1547 bool operator() (const vivify_ref &a, const vivify_ref &b) const {
1548 const auto s = vivify_refcount_rank (offset) (a);
1549 const auto t = vivify_refcount_rank (offset) (b);
1550 return s < t;
1551 }
1552};
1553
1556 typedef uint64_t Type;
1557 Type operator() (const vivify_ref &a) const { return ~a.size; }
1558};
1559
1562 bool operator() (const vivify_ref &a, const vivify_ref &b) const {
1563 const auto s = vivify_inversesize_rank () (a);
1564 const auto t = vivify_inversesize_rank () (b);
1565 return s < t;
1566 }
1567};
1568
1569/*------------------------------------------------------------------------*/
1570// There are two modes of vivification, one using all clauses and one
1571// focusing on irredundant clauses only. The latter variant working on
1572// irredundant clauses only can also remove irredundant asymmetric
1573// tautologies (clauses subsumed through unit propagation), which in
1574// redundant mode is incorrect (due to propagating over redundant clauses).
1575
1576void Internal::vivify_round (Vivifier &vivifier, int64_t ticks_limit) {
1577
1578 if (unsat)
1579 return;
1581 return;
1582
1583 PHASE ("vivify", stats.vivifications,
1584 "starting %c vivification round ticks limit %" PRId64 "",
1585 vivifier.tag, ticks_limit);
1586
1587 PHASE ("vivify", stats.vivifications,
1588 "starting %c vivification round ticks limit %" PRId64 "",
1589 vivifier.tag, ticks_limit);
1590
1592
1593 auto &refs_schedule = current_refs_schedule (vivifier);
1594 auto &schedule = current_schedule (vivifier);
1595
1596 int64_t ticks = 1 + schedule.size ();
1597
1598 // Sort candidates, with first to be tried candidate clause last, i.e.,
1599 // many occurrences and high score literals) as in the example explained
1600 // above (search for '@3').
1601 //
1602 if (vivifier.tier != Vivify_Mode::IRREDUNDANT ||
1603 irredundant () / 10 < redundant ()) {
1604 // Literals in scheduled clauses are sorted with their highest score
1605 // literals first (as explained above in the example at '@2'). This is
1606 // also needed in the prefix subsumption checking below. We do an
1607 // approximation below that is done only in the vivify_ref structure
1608 // below.
1609 //
1610
1611 // first build the schedule with vivifier_refs
1612 auto end_schedule = end (schedule);
1613 refs_schedule.resize (schedule.size ());
1614 std::transform (begin (schedule), end_schedule, begin (refs_schedule),
1615 [&] (Clause *c) { return create_ref (this, c); });
1616 // now sort by size
1617 MSORT (opts.radixsortlim, refs_schedule.begin (), refs_schedule.end (),
1619 // now (stable) sort by number of occurrences
1620 for (int i = 0; i < COUNTREF_COUNTS; ++i) {
1621 const int offset = COUNTREF_COUNTS - 1 - i;
1622 MSORT (opts.radixsortlim, refs_schedule.begin (),
1623 refs_schedule.end (), vivify_refcount_rank (offset),
1624 vivify_refcount_smaller (offset));
1625 }
1626 // force left-overs at the end
1627 std::stable_partition (begin (refs_schedule), end (refs_schedule),
1628 [] (vivify_ref c) { return !c.vivify; });
1629 std::transform (begin (refs_schedule), end (refs_schedule),
1630 begin (schedule),
1631 [] (vivify_ref c) { return c.clause; });
1632 erase_vector (refs_schedule);
1633 LOG ("clause after sorting final:");
1634 } else {
1635 // skip sorting but still put clauses with the vivify tag at the end to
1636 // be done first Kissat does this implicitely by going twice over all
1637 // clauses
1638 std::stable_partition (begin (schedule), end (schedule),
1639 [] (Clause *c) { return !c->vivify; });
1640 }
1641
1642 // Remember old values of counters to summarize after each round with
1643 // verbose messages what happened in that round.
1644 //
1645 int64_t checked = stats.vivifychecks;
1646 int64_t subsumed = stats.vivifysubs;
1647 int64_t strengthened = stats.vivifystrs;
1648 int64_t units = stats.vivifyunits;
1649
1650 int64_t scheduled = schedule.size ();
1651 stats.vivifysched += scheduled;
1652
1653 PHASE ("vivify", stats.vivifications,
1654 "scheduled %" PRId64 " clauses to be vivified %.0f%%", scheduled,
1655 percent (scheduled, stats.current.irredundant));
1656
1657 // Limit the number of propagations during vivification as in 'probe'.
1658 //
1659 const int64_t limit = ticks_limit - stats.ticks.vivify;
1660 CADICAL_assert (limit >= 0);
1661
1662 // the clauses might still contain set literals, so propagation since the
1663 // beginning
1664 propagated2 = propagated = 0;
1665
1666 if (!unsat && !propagate ()) {
1667 LOG ("propagation after connecting watches in inconsistency");
1669 }
1670
1671 vivifier.ticks = ticks;
1672 int retry = 0;
1673 while (!unsat && !terminated_asynchronously () && !schedule.empty () &&
1674 vivifier.ticks < limit) {
1675 Clause *c = schedule.back (); // Next candidate.
1676 schedule.pop_back ();
1677 if (vivify_clause (vivifier, c) && !c->garbage && c->size > 2 &&
1678 retry < opts.vivifyretry) {
1679 ++retry;
1680 schedule.push_back (c);
1681 } else
1682 retry = 0;
1683 }
1684
1685 if (level)
1687
1688 if (!unsat) {
1689 int64_t still_need_to_be_vivified = schedule.size ();
1690#if 0
1691 // in the current round we have new_clauses_to_vivify @ leftovers from previous round There are
1692 // now two possibilities: (i) we consider all clauses as leftovers, or (ii) only the leftovers
1693 // from previous round are considered leftovers.
1694 //
1695 // CaDiCaL had the first version before. If
1696 // commented out we go to the second version.
1697 for (auto c : schedule)
1698 c->vivify = true;
1699#elif 1
1700 // if we have gone through all the leftovers, the current clauses are
1701 // leftovers for the next round
1702 if (!schedule.empty () && !schedule.front ()->vivify &&
1703 schedule.back ()->vivify)
1704 for (auto c : schedule)
1705 c->vivify = true;
1706#else
1707 // do nothing like in kissat and use the candidates for next time.
1708#endif
1709 // Preference clauses scheduled but not vivified yet next time.
1710 //
1711 if (still_need_to_be_vivified)
1712 PHASE ("vivify", stats.vivifications,
1713 "still need to vivify %" PRId64 " clauses %.02f%% of %" PRId64
1714 " scheduled",
1715 still_need_to_be_vivified,
1716 percent (still_need_to_be_vivified, scheduled), scheduled);
1717 else {
1718 PHASE ("vivify", stats.vivifications,
1719 "no previously not yet vivified clause left");
1720 }
1721
1722 erase_vector (schedule); // Reclaim memory early.
1723 }
1724
1725 if (!unsat) {
1726
1727 // Since redundant clause were disconnected during propagating vivified
1728 // units in redundant mode, and further irredundant clauses are
1729 // arbitrarily sorted, we have to propagate all literals again after
1730 // connecting the first two literals in the clauses, in order to
1731 // reestablish the watching invariant.
1732 //
1733 propagated2 = propagated = 0;
1734
1735 if (!propagate ()) {
1736 LOG ("propagating vivified units leads to conflict");
1738 }
1739 }
1740
1741 checked = stats.vivifychecks - checked;
1742 subsumed = stats.vivifysubs - subsumed;
1743 strengthened = stats.vivifystrs - strengthened;
1744 units = stats.vivifyunits - units;
1745
1746 PHASE ("vivify", stats.vivifications,
1747 "checked %" PRId64 " clauses %.02f%% of %" PRId64
1748 " scheduled using %" PRIu64 " ticks",
1749 checked, percent (checked, scheduled), scheduled, vivifier.ticks);
1750 if (units)
1751 PHASE ("vivify", stats.vivifications,
1752 "found %" PRId64 " units %.02f%% of %" PRId64 " checked", units,
1753 percent (units, checked), checked);
1754 if (subsumed)
1755 PHASE ("vivify", stats.vivifications,
1756 "subsumed %" PRId64 " clauses %.02f%% of %" PRId64 " checked",
1757 subsumed, percent (subsumed, checked), checked);
1758 if (strengthened)
1759 PHASE ("vivify", stats.vivifications,
1760 "strengthened %" PRId64 " clauses %.02f%% of %" PRId64
1761 " checked",
1762 strengthened, percent (strengthened, checked), checked);
1763
1764 stats.subsumed += subsumed;
1765 stats.strengthened += strengthened;
1766 stats.ticks.vivify += vivifier.ticks;
1767
1768 bool unsuccessful = !(subsumed + strengthened + units);
1769 report (vivifier.tag, unsuccessful);
1770}
1771
1773 vivifier.tier = tier;
1774 switch (tier) {
1775 case Vivify_Mode::TIER1:
1776 vivifier.tag = 'u';
1777 break;
1778 case Vivify_Mode::TIER2:
1779 vivifier.tag = 'v';
1780 break;
1781 case Vivify_Mode::TIER3:
1782 vivifier.tag = 'w';
1783 break;
1784 default:
1786 vivifier.tag = 'x';
1787 break;
1788 }
1789}
1790/*------------------------------------------------------------------------*/
1791
1793 if (!opts.vivifycalctier) {
1794 vivifier.tier1_limit = 2;
1795 vivifier.tier2_limit = 6;
1796 return;
1797 }
1798 vivifier.tier1_limit = tier1[false];
1799 vivifier.tier2_limit = tier2[false];
1800}
1801
1802/*------------------------------------------------------------------------*/
1803
1805
1806 if (unsat)
1807 return false;
1809 return false;
1810 if (!opts.vivify)
1811 return false;
1812 if (!stats.current.irredundant)
1813 return false;
1814 if (level)
1815 backtrack ();
1816 CADICAL_assert (opts.vivify);
1818
1819 SET_EFFORT_LIMIT (totallimit, vivify, true);
1820
1821 private_steps = true;
1822
1824 stats.vivifications++;
1825
1826 // the effort is normalized by dividing by sumeffort below, hence no need
1827 // to multiply by 1e-3 (also making the precision better)
1828 double tier1effort = !opts.vivifytier1 ? 0 : (double) opts.vivifytier1eff;
1829 double tier2effort = !opts.vivifytier2 ? 0 : (double) opts.vivifytier2eff;
1830 double tier3effort = !opts.vivifytier3 ? 0 : (double) opts.vivifytier3eff;
1831 double irreffort =
1832 delaying_vivify_irredundant.bumpreasons.delay () || !opts.vivifyirred
1833 ? 0
1834 : (double) opts.vivifyirredeff;
1835 double sumeffort = tier1effort + tier2effort + tier3effort + irreffort;
1836 if (!stats.current.redundant)
1837 tier1effort = tier2effort = tier3effort = 0;
1838 if (!sumeffort)
1839 sumeffort = irreffort = 1;
1840 int64_t total = totallimit - stats.ticks.vivify;
1841
1842 PHASE ("vivify", stats.vivifications,
1843 "vivification limit of %" PRId64 " ticks", total);
1846
1847 if (vivifier.tier1_limit == vivifier.tier2_limit) {
1848 tier1effort += tier2effort;
1849 tier2effort = 0;
1850 LOG ("vivification tier1 matches tier2 "
1851 "thus using tier2 budget for tier1");
1852 }
1853 int64_t init_ticks = 0;
1854
1855 // Refill the schedule every time. Unchecked clauses are 'saved' by
1856 // setting their 'vivify' bit, such that they can be tried next time.
1857 //
1858 // TODO: count against ticks.vivify directly instead of this unholy
1859 // shifting.
1860 vivify_initialize (vivifier, init_ticks);
1861 stats.ticks.vivify += init_ticks;
1862 int64_t limit = stats.ticks.vivify;
1863 const double shared_effort = (double) init_ticks / 4.0;
1864 if (opts.vivifytier1) {
1866 if (limit < stats.ticks.vivify)
1867 limit = stats.ticks.vivify;
1868 const double effort = (total * tier1effort) / sumeffort;
1869 CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
1870 limit);
1871 limit += effort;
1872 if (limit - shared_effort > stats.ticks.vivify) {
1873 limit -= shared_effort;
1874 CADICAL_assert (limit >= 0);
1876 } else {
1877 LOG ("building the schedule already used our entire ticks budget for "
1878 "tier1");
1879 }
1880 }
1881
1882 if (!unsat && tier2effort) {
1883 erase_vector (
1884 vivifier.schedule_tier1); // save memory (well, not really as we
1885 // already reached the peak memory)
1886 if (limit < stats.ticks.vivify)
1887 limit = stats.ticks.vivify;
1888 const double effort = (total * tier2effort) / sumeffort;
1889 CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
1890 limit);
1891 limit += effort;
1892 if (limit - shared_effort > stats.ticks.vivify) {
1893 limit -= shared_effort;
1894 CADICAL_assert (limit >= 0);
1897 } else {
1898 LOG ("building the schedule already used our entire ticks budget for "
1899 "tier2");
1900 }
1901 }
1902
1903 if (!unsat && tier3effort) {
1904 erase_vector (vivifier.schedule_tier2);
1905 if (limit < stats.ticks.vivify)
1906 limit = stats.ticks.vivify;
1907 const double effort = (total * tier3effort) / sumeffort;
1908 CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
1909 limit);
1910 limit += effort;
1911 if (limit - shared_effort > stats.ticks.vivify) {
1912 limit -= shared_effort;
1913 CADICAL_assert (limit >= 0);
1916 } else {
1917 LOG ("building the schedule already used our entire ticks budget for "
1918 "tier3");
1919 }
1920 }
1921
1922 if (!unsat && irreffort) {
1923 erase_vector (vivifier.schedule_tier3);
1924 if (limit < stats.ticks.vivify)
1925 limit = stats.ticks.vivify;
1926 const double effort = (total * irreffort) / sumeffort;
1927 CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
1928 limit);
1929 limit += effort;
1930 if (limit - shared_effort > stats.ticks.vivify) {
1931 limit -= shared_effort;
1932 CADICAL_assert (limit >= 0);
1934 const int old = stats.vivifystrirr;
1935 const int old_tried = stats.vivifychecks;
1937 if (stats.vivifychecks - old_tried == 0 ||
1938 (float) (stats.vivifystrirr - old) /
1939 (float) (stats.vivifychecks - old_tried) <
1940 0.01) {
1941 delaying_vivify_irredundant.bumpreasons.bump_delay ();
1942 } else {
1943 delaying_vivify_irredundant.bumpreasons.reduce_delay ();
1944 }
1945 } else {
1946 delaying_vivify_irredundant.bumpreasons.bump_delay ();
1947 LOG ("building the schedule already used our entire ticks budget for "
1948 "irredundant");
1949 }
1950 }
1951
1952 reset_noccs ();
1954
1955 private_steps = false;
1956
1957 return true;
1958}
1959
1960} // namespace CaDiCaL
1961
#define ABC_NAMESPACE_IMPL_START
#define ABC_NAMESPACE_IMPL_END
#define UINT32_MAX
Definition pstdint.h:388
#define CADICAL_assert(ignore)
Definition global.h:14
#define LOG(...)
ABC_NAMESPACE_IMPL_START typedef signed char value
#define SET_EFFORT_LIMIT(LIMIT, NAME, THRESHHOLD)
Definition limit.hpp:131
#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
Watches::const_iterator const_watch_iterator
Definition watch.hpp:48
int * literal_iterator
Definition clause.hpp:17
const char * flags()
const int * const_literal_iterator
Definition clause.hpp:18
std::vector< vivify_ref > & current_refs_schedule(Vivifier &vivifier)
vivify_ref create_ref(Internal *internal, Clause *c)
void set_vivifier_mode(Vivifier &vivifier, Vivify_Mode tier)
std::vector< Clause * > & current_schedule(Vivifier &vivifier)
vector< Watch > Watches
Definition watch.hpp:45
void erase_vector(std::vector< T > &v)
Definition util.hpp:90
double percent(double a, double b)
Definition util.hpp:21
Vivify_Mode
Definition vivify.hpp:18
Watches::iterator watch_iterator
Definition watch.hpp:47
#define STOP(P)
Definition profile.hpp:158
#define START_SIMPLIFIER(S, M)
Definition profile.hpp:172
#define STOP_SIMPLIFIER(S, M)
Definition profile.hpp:197
#define START(P)
Definition profile.hpp:150
#define MSORT(LIMIT, FIRST, LAST, RANK, LESS)
Definition radix.hpp:173
int lit
Definition satVec.h:130
literal_iterator begin()
Definition clause.hpp:131
literal_iterator end()
Definition clause.hpp:132
void vivify_round(Vivifier &, int64_t delta)
void watch_literal(int lit, int blit, Clause *c)
Definition internal.hpp:623
int64_t cache_lines(size_t bytes)
Definition internal.hpp:722
bool vivify_clause(Vivifier &, Clause *candidate)
int64_t irredundant() const
Definition internal.hpp:383
vector< int > analyzed
Definition internal.hpp:267
void vivify_sort_watched(Clause *c)
void unwatch_clause(Clause *c)
Definition internal.hpp:640
Var & var(int lit)
Definition internal.hpp:452
Delay delaying_vivify_irredundant
Definition internal.hpp:291
void mark_garbage(Clause *)
void vivify_prioritize_leftovers(char, size_t prioritized, std::vector< Clause * > &schedule)
vector< int64_t > lrat_chain
Definition internal.hpp:210
int vidx(int lit) const
Definition internal.hpp:395
void report(char type, int verbose_level=0)
void unmark(int lit)
Definition internal.hpp:490
void implied(std::vector< int > &entrailed)
Flags & flags(int lit)
Definition internal.hpp:454
vector< int > trail
Definition internal.hpp:259
int fixed(int lit)
bool terminated_asynchronously(int factor=1)
const Sange lits
Definition internal.hpp:325
void mark_removed(int lit)
void promote_clause(Clause *, int new_glue)
void vivify_assume(int lit)
void flush_vivification_schedule(std::vector< Clause * > &, int64_t &)
void vivify_increment_stats(const Vivifier &vivifier)
void watch_clause(Clause *c)
Definition internal.hpp:633
void vivify_subsume_clause(Clause *subsuming, Clause *subsumed)
void demote_clause(Clause *)
bool watching() const
Definition internal.hpp:462
signed char val(int lit) const
bool limit(const char *name, int)
int64_t unit_id(int lit) const
Definition internal.hpp:434
vector< int64_t > unit_chain
Definition internal.hpp:213
void vivify_build_lrat(int, Clause *, std::vector< std::tuple< int, Clause *, bool > > &)
void backtrack_without_updating_phases(int target_level=0)
void backtrack(int target_level=0)
int recompute_glue(Clause *)
int64_t & noccs(int lit)
Definition internal.hpp:466
unsigned vlit(int lit) const
Definition internal.hpp:408
void mark2(int lit)
Definition internal.hpp:561
void strengthen_clause(Clause *, int)
void vivify_strengthen(Clause *candidate)
void vivify_analyze(Clause *start, bool &, Clause **, const Clause *const, int implied, bool &)
vector< Clause * > clauses
Definition internal.hpp:283
signed char * vals
Definition internal.hpp:221
void vivify_deduce(Clause *candidate, Clause *conflct, int implied, Clause **, bool &)
void vivify_chain_for_units(int lit, Clause *reason)
double & score(int lit)
Definition internal.hpp:457
void require_mode(Mode m) const
Definition internal.hpp:173
bool consider_to_vivify_clause(Clause *candidate)
bool vivify_propagate(int64_t &)
void vivify_assign(int lit, Clause *)
vector< Level > control
Definition internal.hpp:282
bool marked2(int lit) const
Definition internal.hpp:555
void learn_unit_clause(int lit)
void mark_added(int lit, int size, bool redundant)
void vivify_initialize(Vivifier &vivifier, int64_t &ticks)
Clause * new_clause_as(const Clause *orig)
bool vivify_instantiate(const std::vector< int > &, Clause *, std::vector< std::tuple< int, Clause *, bool > > &lrat_stack, int64_t &ticks)
int64_t redundant() const
Definition internal.hpp:381
void compute_tier_limits(Vivifier &)
void build_chain_for_units(int lit, Clause *reason, bool forced)
bool vivify_shrinkable(const std::vector< int > &sorted, Clause *c)
vector< int > clause
Definition internal.hpp:260
void connect_watches(bool irredundant_only=false)
int level
Definition var.hpp:19
int trail
Definition var.hpp:20
Clause * reason
Definition var.hpp:21
bool binary() const
Definition watch.hpp:42
Clause * clause
Definition watch.hpp:35
bool operator()(Clause *a, Clause *b) const
bool operator()(Clause *a, Clause *b) const
Type operator()(const vivify_ref &a) const
bool operator()(const vivify_ref &a, const vivify_ref &b) const
bool operator()(int a, int b)
uint64_t count[COUNTREF_COUNTS]
Definition vivify.hpp:25
Type operator()(const vivify_ref &a) const
bool operator()(const vivify_ref &a, const vivify_ref &b) const
unsigned size
Definition vector.h:35
struct vivifier vivifier
Definition vivify.c:167
#define COUNTREF_COUNTS
Definition vivify.hpp:20
vector watches
Definition watch.h:49