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.
- checksums.yaml +7 -0
- data/LICENSE.txt +26 -0
- data/README.md +101 -0
- data/ext/cadical/cadical/LICENSE +28 -0
- data/ext/cadical/cadical/README.md +65 -0
- data/ext/cadical/cadical/VERSION +1 -0
- data/ext/cadical/cadical/src/analyze.cpp +1360 -0
- data/ext/cadical/cadical/src/arena.cpp +30 -0
- data/ext/cadical/cadical/src/arena.hpp +105 -0
- data/ext/cadical/cadical/src/assume.cpp +616 -0
- data/ext/cadical/cadical/src/averages.cpp +34 -0
- data/ext/cadical/cadical/src/averages.hpp +37 -0
- data/ext/cadical/cadical/src/backbone.cpp +631 -0
- data/ext/cadical/cadical/src/backtrack.cpp +177 -0
- data/ext/cadical/cadical/src/backward.cpp +231 -0
- data/ext/cadical/cadical/src/bins.cpp +22 -0
- data/ext/cadical/cadical/src/bins.hpp +22 -0
- data/ext/cadical/cadical/src/block.cpp +824 -0
- data/ext/cadical/cadical/src/block.hpp +37 -0
- data/ext/cadical/cadical/src/cadical.cpp +1026 -0
- data/ext/cadical/cadical/src/cadical.hpp +1419 -0
- data/ext/cadical/cadical/src/ccadical.cpp +208 -0
- data/ext/cadical/cadical/src/ccadical.h +74 -0
- data/ext/cadical/cadical/src/checker.cpp +649 -0
- data/ext/cadical/cadical/src/checker.hpp +173 -0
- data/ext/cadical/cadical/src/clause.cpp +689 -0
- data/ext/cadical/cadical/src/clause.hpp +190 -0
- data/ext/cadical/cadical/src/collect.cpp +545 -0
- data/ext/cadical/cadical/src/compact.cpp +550 -0
- data/ext/cadical/cadical/src/condition.cpp +940 -0
- data/ext/cadical/cadical/src/config.cpp +101 -0
- data/ext/cadical/cadical/src/config.hpp +20 -0
- data/ext/cadical/cadical/src/congruence.cpp +7880 -0
- data/ext/cadical/cadical/src/congruence.hpp +757 -0
- data/ext/cadical/cadical/src/constrain.cpp +64 -0
- data/ext/cadical/cadical/src/contract.cpp +27 -0
- data/ext/cadical/cadical/src/contract.hpp +139 -0
- data/ext/cadical/cadical/src/cover.cpp +704 -0
- data/ext/cadical/cadical/src/cover.hpp +34 -0
- data/ext/cadical/cadical/src/decide.cpp +347 -0
- data/ext/cadical/cadical/src/decompose.cpp +738 -0
- data/ext/cadical/cadical/src/decompose.hpp +23 -0
- data/ext/cadical/cadical/src/deduplicate.cpp +275 -0
- data/ext/cadical/cadical/src/definition.cpp +283 -0
- data/ext/cadical/cadical/src/delay.hpp +38 -0
- data/ext/cadical/cadical/src/drattracer.cpp +153 -0
- data/ext/cadical/cadical/src/drattracer.hpp +55 -0
- data/ext/cadical/cadical/src/elim.cpp +1171 -0
- data/ext/cadical/cadical/src/elim.hpp +53 -0
- data/ext/cadical/cadical/src/elimfast.cpp +570 -0
- data/ext/cadical/cadical/src/ema.cpp +95 -0
- data/ext/cadical/cadical/src/ema.hpp +68 -0
- data/ext/cadical/cadical/src/extend.cpp +289 -0
- data/ext/cadical/cadical/src/external.cpp +1034 -0
- data/ext/cadical/cadical/src/external.hpp +487 -0
- data/ext/cadical/cadical/src/external_propagate.cpp +1316 -0
- data/ext/cadical/cadical/src/factor.cpp +1011 -0
- data/ext/cadical/cadical/src/factor.hpp +54 -0
- data/ext/cadical/cadical/src/file.cpp +506 -0
- data/ext/cadical/cadical/src/file.hpp +210 -0
- data/ext/cadical/cadical/src/flags.cpp +135 -0
- data/ext/cadical/cadical/src/flags.hpp +93 -0
- data/ext/cadical/cadical/src/flip.cpp +269 -0
- data/ext/cadical/cadical/src/format.cpp +89 -0
- data/ext/cadical/cadical/src/format.hpp +36 -0
- data/ext/cadical/cadical/src/frattracer.cpp +277 -0
- data/ext/cadical/cadical/src/frattracer.hpp +67 -0
- data/ext/cadical/cadical/src/gates.cpp +766 -0
- data/ext/cadical/cadical/src/heap.hpp +212 -0
- data/ext/cadical/cadical/src/idruptracer.cpp +566 -0
- data/ext/cadical/cadical/src/idruptracer.hpp +112 -0
- data/ext/cadical/cadical/src/instantiate.cpp +365 -0
- data/ext/cadical/cadical/src/instantiate.hpp +45 -0
- data/ext/cadical/cadical/src/internal.cpp +1265 -0
- data/ext/cadical/cadical/src/internal.hpp +1941 -0
- data/ext/cadical/cadical/src/inttypes.hpp +34 -0
- data/ext/cadical/cadical/src/ipasir.cpp +47 -0
- data/ext/cadical/cadical/src/ipasir.h +37 -0
- data/ext/cadical/cadical/src/kitten.c +2599 -0
- data/ext/cadical/cadical/src/kitten.h +97 -0
- data/ext/cadical/cadical/src/level.hpp +33 -0
- data/ext/cadical/cadical/src/lidruptracer.cpp +657 -0
- data/ext/cadical/cadical/src/lidruptracer.hpp +122 -0
- data/ext/cadical/cadical/src/limit.cpp +146 -0
- data/ext/cadical/cadical/src/limit.hpp +168 -0
- data/ext/cadical/cadical/src/logging.cpp +214 -0
- data/ext/cadical/cadical/src/logging.hpp +98 -0
- data/ext/cadical/cadical/src/lookahead.cpp +520 -0
- data/ext/cadical/cadical/src/lratchecker.cpp +833 -0
- data/ext/cadical/cadical/src/lratchecker.hpp +168 -0
- data/ext/cadical/cadical/src/lrattracer.cpp +200 -0
- data/ext/cadical/cadical/src/lrattracer.hpp +61 -0
- data/ext/cadical/cadical/src/lucky.cpp +506 -0
- data/ext/cadical/cadical/src/message.cpp +218 -0
- data/ext/cadical/cadical/src/message.hpp +65 -0
- data/ext/cadical/cadical/src/minimize.cpp +224 -0
- data/ext/cadical/cadical/src/mobical.cpp +5588 -0
- data/ext/cadical/cadical/src/occs.cpp +52 -0
- data/ext/cadical/cadical/src/occs.hpp +36 -0
- data/ext/cadical/cadical/src/options.cpp +359 -0
- data/ext/cadical/cadical/src/options.hpp +442 -0
- data/ext/cadical/cadical/src/parse.cpp +471 -0
- data/ext/cadical/cadical/src/parse.hpp +75 -0
- data/ext/cadical/cadical/src/phases.cpp +56 -0
- data/ext/cadical/cadical/src/phases.hpp +19 -0
- data/ext/cadical/cadical/src/probe.cpp +990 -0
- data/ext/cadical/cadical/src/profile.cpp +107 -0
- data/ext/cadical/cadical/src/profile.hpp +283 -0
- data/ext/cadical/cadical/src/proof.cpp +691 -0
- data/ext/cadical/cadical/src/proof.hpp +125 -0
- data/ext/cadical/cadical/src/propagate.cpp +584 -0
- data/ext/cadical/cadical/src/queue.cpp +90 -0
- data/ext/cadical/cadical/src/queue.hpp +74 -0
- data/ext/cadical/cadical/src/radix.hpp +180 -0
- data/ext/cadical/cadical/src/random.cpp +207 -0
- data/ext/cadical/cadical/src/random.h +44 -0
- data/ext/cadical/cadical/src/random.hpp +98 -0
- data/ext/cadical/cadical/src/range.hpp +125 -0
- data/ext/cadical/cadical/src/reap.cpp +127 -0
- data/ext/cadical/cadical/src/reap.hpp +28 -0
- data/ext/cadical/cadical/src/reduce.cpp +278 -0
- data/ext/cadical/cadical/src/reluctant.hpp +82 -0
- data/ext/cadical/cadical/src/rephase.cpp +402 -0
- data/ext/cadical/cadical/src/report.cpp +313 -0
- data/ext/cadical/cadical/src/resources.cpp +160 -0
- data/ext/cadical/cadical/src/resources.hpp +16 -0
- data/ext/cadical/cadical/src/restart.cpp +178 -0
- data/ext/cadical/cadical/src/restore.cpp +267 -0
- data/ext/cadical/cadical/src/score.cpp +51 -0
- data/ext/cadical/cadical/src/score.hpp +16 -0
- data/ext/cadical/cadical/src/shrink.cpp +507 -0
- data/ext/cadical/cadical/src/signal.cpp +136 -0
- data/ext/cadical/cadical/src/signal.hpp +33 -0
- data/ext/cadical/cadical/src/solution.cpp +50 -0
- data/ext/cadical/cadical/src/solver.cpp +1871 -0
- data/ext/cadical/cadical/src/stable.cpp +31 -0
- data/ext/cadical/cadical/src/stack.h +110 -0
- data/ext/cadical/cadical/src/stats.cpp +920 -0
- data/ext/cadical/cadical/src/stats.hpp +418 -0
- data/ext/cadical/cadical/src/subsume.cpp +645 -0
- data/ext/cadical/cadical/src/sweep.cpp +1960 -0
- data/ext/cadical/cadical/src/sweep.hpp +61 -0
- data/ext/cadical/cadical/src/terminal.cpp +38 -0
- data/ext/cadical/cadical/src/terminal.hpp +96 -0
- data/ext/cadical/cadical/src/ternary.cpp +450 -0
- data/ext/cadical/cadical/src/testing.hpp +24 -0
- data/ext/cadical/cadical/src/tier.cpp +189 -0
- data/ext/cadical/cadical/src/tracer.hpp +186 -0
- data/ext/cadical/cadical/src/transred.cpp +253 -0
- data/ext/cadical/cadical/src/unstable.cpp +29 -0
- data/ext/cadical/cadical/src/util.cpp +129 -0
- data/ext/cadical/cadical/src/util.hpp +181 -0
- data/ext/cadical/cadical/src/var.cpp +39 -0
- data/ext/cadical/cadical/src/var.hpp +22 -0
- data/ext/cadical/cadical/src/veripbtracer.cpp +427 -0
- data/ext/cadical/cadical/src/veripbtracer.hpp +106 -0
- data/ext/cadical/cadical/src/version.cpp +107 -0
- data/ext/cadical/cadical/src/version.hpp +13 -0
- data/ext/cadical/cadical/src/vivify.cpp +1893 -0
- data/ext/cadical/cadical/src/vivify.hpp +50 -0
- data/ext/cadical/cadical/src/walk.cpp +1088 -0
- data/ext/cadical/cadical/src/walk.hpp +91 -0
- data/ext/cadical/cadical/src/walk_full_occs.cpp +966 -0
- data/ext/cadical/cadical/src/warmup.cpp +403 -0
- data/ext/cadical/cadical/src/watch.cpp +126 -0
- data/ext/cadical/cadical/src/watch.hpp +77 -0
- data/ext/cadical/cadical_ext.c +778 -0
- data/ext/cadical/extconf.rb +26 -0
- data/lib/cadical/version.rb +3 -0
- data/lib/cadical.rb +12 -0
- 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> ¤t_refs_schedule (Vivifier &vivifier) {
|
|
1442
|
+
return vivifier.refs_schedule;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
inline std::vector<Clause *> ¤t_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
|