cadical 0.9.0

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