mirror of https://github.com/YosysHQ/abc.git
1905 lines
61 KiB
C++
1905 lines
61 KiB
C++
#include "global.h"
|
|
|
|
#include "vivify.hpp"
|
|
#include "internal.hpp"
|
|
#include "util.hpp"
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
ABC_NAMESPACE_IMPL_START
|
|
|
|
namespace CaDiCaL {
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Vivification is a special case of asymmetric tautology elimination (ATE)
|
|
// and asymmetric literal elimination (ALE). It strengthens and removes
|
|
// clauses proven redundant through unit propagation.
|
|
//
|
|
// The original algorithm is due to a paper by Piette, Hamadi and Sais
|
|
// published at ECAI'08. We have an inprocessing version, e.g., it does not
|
|
// necessarily run-to-completion. Our version also performs conflict
|
|
// analysis and uses a new heuristic for selecting clauses to vivify.
|
|
|
|
// Our idea is to focus on clauses with many occurrences of its literals in
|
|
// other clauses first. This both complements nicely our implementation of
|
|
// subsume, which is bounded, e.g., subsumption attempts are skipped for
|
|
// very long clauses with literals with many occurrences and also is
|
|
// stronger in the sense that it enables to remove more clauses due to unit
|
|
// propagation (AT checks).
|
|
|
|
// While first focusing on irredundant clause we then added a separate phase
|
|
// upfront which focuses on strengthening also redundant clauses in spirit
|
|
// of the ideas presented in the IJCAI'17 paper by M. Luo, C.-M. Li, F.
|
|
// Xiao, F. Manya, and Z. Lu.
|
|
|
|
// There is another very similar approach called 'distilliation' published
|
|
// by Han and Somenzi in DAC'07, which reorganizes the CNF in a trie data
|
|
// structure to reuse decisions and propagations along the trie. We used
|
|
// that as an inspiration but instead of building a trie we simple sort
|
|
// clauses and literals in such a way that we get the same effect. If a new
|
|
// clause is 'distilled' or 'vivified' we first check how many of the
|
|
// decisions (which are only lazily undone) can be reused for that clause.
|
|
// Reusing can be improved by picking a global literal order and sorting the
|
|
// literals in all clauses with respect to that order. We favor literals
|
|
// with more occurrences first. Then we sort clauses lexicographically with
|
|
// respect to that literal order.
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Candidate clause 'subsumed' is subsumed by 'subsuming'.
|
|
|
|
inline void Internal::vivify_subsume_clause (Clause *subsuming,
|
|
Clause *subsumed) {
|
|
CADICAL_assert (subsumed != subsuming);
|
|
stats.subsumed++;
|
|
stats.vivifysubs++;
|
|
#ifndef CADICAL_NDEBUG
|
|
CADICAL_assert (subsuming);
|
|
CADICAL_assert (subsumed);
|
|
CADICAL_assert (subsuming != subsumed);
|
|
CADICAL_assert (!subsumed->garbage);
|
|
// size after removeing units;
|
|
int real_size_subsuming = 0, real_size_subsumed = 0;
|
|
for (auto lit : *subsuming) {
|
|
if (!val (lit) || var (lit).level)
|
|
++real_size_subsuming;
|
|
else
|
|
CADICAL_assert (val (lit) < 0);
|
|
}
|
|
for (auto lit : *subsumed) {
|
|
if (!val (lit) || var (lit).level)
|
|
++real_size_subsumed;
|
|
else
|
|
CADICAL_assert (val (lit) < 0);
|
|
}
|
|
CADICAL_assert (real_size_subsuming <= real_size_subsumed);
|
|
#endif
|
|
LOG (subsumed, "subsumed to be deleted");
|
|
LOG (subsuming, "subsuming to be (un)deleted");
|
|
if (subsumed->redundant && subsuming->redundant &&
|
|
subsuming->glue < subsumed->glue) {
|
|
promote_clause (subsuming, subsumed->glue);
|
|
}
|
|
if (subsumed->redundant) {
|
|
stats.subred++;
|
|
++stats.vivifysubred;
|
|
} else {
|
|
stats.subirr++;
|
|
++stats.vivifysubirr;
|
|
}
|
|
if (subsuming->garbage) {
|
|
CADICAL_assert (subsuming->size == 2);
|
|
LOG (subsuming,
|
|
"binary subsuming clause was already deleted, so undeleting");
|
|
subsuming->garbage = false;
|
|
subsuming->glue = 1;
|
|
++stats.current.total;
|
|
if (subsuming->redundant)
|
|
stats.current.redundant++;
|
|
else
|
|
stats.current.irredundant++, stats.irrlits += subsuming->size;
|
|
}
|
|
if (subsumed->redundant || !subsuming->redundant) {
|
|
mark_garbage (subsumed);
|
|
return;
|
|
}
|
|
LOG ("turning redundant subsuming clause into irredundant clause");
|
|
subsuming->redundant = false;
|
|
if (proof)
|
|
proof->strengthen (subsuming->id);
|
|
mark_garbage (subsumed);
|
|
mark_added (subsuming);
|
|
stats.current.irredundant++;
|
|
stats.added.irredundant++;
|
|
stats.irrlits += subsuming->size;
|
|
CADICAL_assert (stats.current.redundant > 0);
|
|
stats.current.redundant--;
|
|
CADICAL_assert (stats.added.redundant > 0);
|
|
stats.added.redundant--;
|
|
// ... and keep 'stats.added.total'.
|
|
}
|
|
|
|
// demoting a clause (opposite is promote from subsume.cpp)
|
|
//
|
|
// This turned out to not be useful, but we kept it for the API call in the
|
|
// proof tracers.
|
|
inline void Internal::demote_clause (Clause *c) {
|
|
stats.subsumed++;
|
|
stats.vivifydemote++;
|
|
LOG (c, "demoting");
|
|
CADICAL_assert (!c->redundant);
|
|
mark_removed (c);
|
|
c->redundant = true;
|
|
CADICAL_assert (stats.current.irredundant > 0);
|
|
stats.current.irredundant--;
|
|
CADICAL_assert (stats.added.irredundant > 0);
|
|
stats.added.irredundant--;
|
|
stats.irrlits -= c->size;
|
|
stats.current.redundant++;
|
|
stats.added.redundant++;
|
|
c->glue = c->size - 1;
|
|
// ... and keep 'stats.added.total'.
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
// For vivification we have a separate dedicated propagation routine, which
|
|
// prefers to propagate binary clauses first. It also uses its own
|
|
// assignment procedure 'vivify_assign', which does not mess with phase
|
|
// saving during search nor the conflict and other statistics and further
|
|
// can be inlined separately here. The propagation routine needs to ignore
|
|
// (large) clauses which are currently vivified.
|
|
|
|
inline void Internal::vivify_assign (int lit, Clause *reason) {
|
|
require_mode (VIVIFY);
|
|
const int idx = vidx (lit);
|
|
CADICAL_assert (!vals[idx]);
|
|
CADICAL_assert (!flags (idx).eliminated () || !reason);
|
|
Var &v = var (idx);
|
|
v.level = level; // required to reuse decisions
|
|
v.trail = (int) trail.size (); // used in 'vivify_better_watch'
|
|
CADICAL_assert ((int) num_assigned < max_var);
|
|
num_assigned++;
|
|
v.reason = level ? reason : 0; // for conflict analysis
|
|
if (!level)
|
|
learn_unit_clause (lit);
|
|
const signed char tmp = sign (lit);
|
|
vals[idx] = tmp;
|
|
vals[-idx] = -tmp;
|
|
CADICAL_assert (val (lit) > 0);
|
|
CADICAL_assert (val (-lit) < 0);
|
|
trail.push_back (lit);
|
|
LOG (reason, "vivify assign %d", lit);
|
|
}
|
|
|
|
// Assume negated literals in candidate clause.
|
|
|
|
void Internal::vivify_assume (int lit) {
|
|
require_mode (VIVIFY);
|
|
level++;
|
|
control.push_back (Level (lit, trail.size ()));
|
|
LOG ("vivify decide %d", lit);
|
|
CADICAL_assert (level > 0);
|
|
CADICAL_assert (propagated == trail.size ());
|
|
vivify_assign (lit, 0);
|
|
}
|
|
|
|
// Dedicated routine similar to 'propagate' in 'propagate.cpp' and
|
|
// 'probe_propagate' with 'probe_propagate2' in 'probe.cpp'. Please refer
|
|
// to that code for more explanation on how propagation is implemented.
|
|
|
|
bool Internal::vivify_propagate (int64_t &ticks) {
|
|
require_mode (VIVIFY);
|
|
CADICAL_assert (!unsat);
|
|
START (propagate);
|
|
int64_t before = propagated2 = propagated;
|
|
for (;;) {
|
|
if (propagated2 != trail.size ()) {
|
|
const int lit = -trail[propagated2++];
|
|
LOG ("vivify propagating %d over binary clauses", -lit);
|
|
Watches &ws = watches (lit);
|
|
ticks +=
|
|
1 + cache_lines (ws.size (), sizeof (const_watch_iterator *));
|
|
for (const auto &w : ws) {
|
|
if (!w.binary ())
|
|
continue;
|
|
const signed char b = val (w.blit);
|
|
if (b > 0)
|
|
continue;
|
|
if (b < 0)
|
|
conflict = w.clause; // but continue
|
|
else {
|
|
ticks++;
|
|
build_chain_for_units (w.blit, w.clause, 0);
|
|
vivify_assign (w.blit, w.clause);
|
|
lrat_chain.clear ();
|
|
}
|
|
}
|
|
} else if (!conflict && propagated != trail.size ()) {
|
|
const int lit = -trail[propagated++];
|
|
LOG ("vivify propagating %d over large clauses", -lit);
|
|
Watches &ws = watches (lit);
|
|
const const_watch_iterator eow = ws.end ();
|
|
const_watch_iterator i = ws.begin ();
|
|
ticks += 1 + cache_lines (ws.size (), sizeof (*i));
|
|
watch_iterator j = ws.begin ();
|
|
while (i != eow) {
|
|
const Watch w = *j++ = *i++;
|
|
if (w.binary ())
|
|
continue;
|
|
if (val (w.blit) > 0)
|
|
continue;
|
|
ticks++;
|
|
if (w.clause->garbage) {
|
|
j--;
|
|
continue;
|
|
}
|
|
literal_iterator lits = w.clause->begin ();
|
|
const int other = lits[0] ^ lits[1] ^ lit;
|
|
const signed char u = val (other);
|
|
if (u > 0)
|
|
j[-1].blit = other;
|
|
else {
|
|
const int size = w.clause->size;
|
|
const const_literal_iterator end = lits + size;
|
|
const literal_iterator middle = lits + w.clause->pos;
|
|
literal_iterator k = middle;
|
|
signed char v = -1;
|
|
int r = 0;
|
|
while (k != end && (v = val (r = *k)) < 0)
|
|
k++;
|
|
if (v < 0) {
|
|
k = lits + 2;
|
|
CADICAL_assert (w.clause->pos <= size);
|
|
while (k != middle && (v = val (r = *k)) < 0)
|
|
k++;
|
|
}
|
|
w.clause->pos = k - lits;
|
|
CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ());
|
|
if (v > 0)
|
|
j[-1].blit = r;
|
|
else if (!v) {
|
|
LOG (w.clause, "unwatch %d in", r);
|
|
lits[0] = other;
|
|
lits[1] = r;
|
|
*k = lit;
|
|
ticks++;
|
|
watch_literal (r, lit, w.clause);
|
|
j--;
|
|
} else if (!u) {
|
|
if (w.clause == ignore) {
|
|
LOG ("ignoring propagation due to clause to vivify");
|
|
continue;
|
|
}
|
|
ticks++;
|
|
CADICAL_assert (v < 0);
|
|
vivify_chain_for_units (other, w.clause);
|
|
vivify_assign (other, w.clause);
|
|
lrat_chain.clear ();
|
|
} else {
|
|
if (w.clause == ignore) {
|
|
LOG ("ignoring conflict due to clause to vivify");
|
|
continue;
|
|
}
|
|
CADICAL_assert (u < 0);
|
|
CADICAL_assert (v < 0);
|
|
conflict = w.clause;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (j != i) {
|
|
while (i != eow)
|
|
*j++ = *i++;
|
|
ws.resize (j - ws.begin ());
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
int64_t delta = propagated2 - before;
|
|
stats.propagations.vivify += delta;
|
|
if (conflict)
|
|
LOG (conflict, "conflict");
|
|
STOP (propagate);
|
|
return !conflict;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Check whether a literal occurs less often. In the implementation below
|
|
// (search for 'int64_t score = ...' or '@4') we actually compute a
|
|
// weighted occurrence count similar to the Jeroslow Wang heuristic.
|
|
|
|
struct vivify_more_noccs {
|
|
|
|
Internal *internal;
|
|
|
|
vivify_more_noccs (Internal *i) : internal (i) {}
|
|
|
|
bool operator() (int a, int b) {
|
|
int64_t n = internal->noccs (a);
|
|
int64_t m = internal->noccs (b);
|
|
if (n > m)
|
|
return true; // larger occurrences / score first
|
|
if (n < m)
|
|
return false; // smaller occurrences / score last
|
|
if (a == -b)
|
|
return a > 0; // positive literal first
|
|
return abs (a) < abs (b); // smaller index first
|
|
}
|
|
};
|
|
|
|
struct vivify_more_noccs_kissat {
|
|
|
|
Internal *internal;
|
|
|
|
vivify_more_noccs_kissat (Internal *i) : internal (i) {}
|
|
|
|
bool operator() (int a, int b) {
|
|
unsigned s = internal->noccs (a);
|
|
unsigned t = internal->noccs (b);
|
|
return (((t - s) |
|
|
((internal->vlit (a) - internal->vlit (b)) & ~(s - t))) >>
|
|
31);
|
|
}
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Attempting on-the-fly subsumption during sorting when the last line
|
|
// is turned out to be trouble some for identical clauses. This is
|
|
// the single point where sorting is not asymmetric and would require
|
|
// 'stable' sorting for determinism. It can also not be made
|
|
// 'complete' on-the-fly. Instead of on-the-fly subsumption, we
|
|
// (optionnaly) thus go over the sorted scheduled in a linear scan
|
|
// again and remove certain subsumed clauses (the subsuming clause is
|
|
// syntactically a prefix of the subsumed clause), which includes
|
|
// those troublesome syntactically identical clauses.
|
|
|
|
struct vivify_flush_smaller {
|
|
|
|
bool operator() (Clause *a, Clause *b) const {
|
|
|
|
const auto eoa = a->end (), eob = b->end ();
|
|
auto i = a->begin (), j = b->begin ();
|
|
for (; i != eoa && j != eob; i++, j++)
|
|
if (*i != *j)
|
|
return *i < *j;
|
|
const bool smaller = j == eob && i != eoa;
|
|
return smaller;
|
|
}
|
|
};
|
|
|
|
void Internal::flush_vivification_schedule (std::vector<Clause *> &schedule,
|
|
int64_t &ticks) {
|
|
ticks += 1 + 3 * cache_lines (schedule.size (), sizeof (Clause *));
|
|
// we cannot use msort here, as we cannot calculate a score
|
|
stable_sort (schedule.begin (), schedule.end (), vivify_flush_smaller ());
|
|
const auto end = schedule.end ();
|
|
auto j = schedule.begin (), i = j;
|
|
|
|
Clause *prev = 0;
|
|
int64_t subsumed = 0;
|
|
for (; i != end; i++) {
|
|
ticks++;
|
|
Clause *c = *j++ = *i;
|
|
if (!prev || c->size < prev->size) {
|
|
prev = c;
|
|
continue;
|
|
}
|
|
const auto eop = prev->end ();
|
|
auto k = prev->begin ();
|
|
for (auto l = c->begin (); k != eop; k++, l++)
|
|
if (*k != *l)
|
|
break;
|
|
if (k == eop) {
|
|
LOG (c, "found subsumed");
|
|
LOG (prev, "subsuming");
|
|
CADICAL_assert (!c->garbage);
|
|
CADICAL_assert (!prev->garbage);
|
|
CADICAL_assert (c->redundant || !prev->redundant);
|
|
mark_garbage (c);
|
|
subsumed++;
|
|
j--;
|
|
} else
|
|
prev = c;
|
|
}
|
|
|
|
if (subsumed)
|
|
PHASE ("vivify", stats.vivifications,
|
|
"flushed %" PRId64 " subsumed scheduled clauses", subsumed);
|
|
|
|
stats.vivifysubs += subsumed;
|
|
stats.vivifyflushed += subsumed;
|
|
|
|
if (subsumed) {
|
|
schedule.resize (j - schedule.begin ());
|
|
shrink_vector (schedule);
|
|
} else
|
|
CADICAL_assert (j == end);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Depending on whether we try to vivify redundant or irredundant clauses,
|
|
// we schedule a clause to be vivified. For redundant clauses we initially
|
|
// only try to vivify them if they are likely to survive the next 'reduce'
|
|
// operation, but this left the last schedule empty most of the time.
|
|
bool Internal::consider_to_vivify_clause (Clause *c) {
|
|
if (c->garbage)
|
|
return false;
|
|
if (opts.vivifyonce >= 1 && c->redundant && c->vivified)
|
|
return false;
|
|
if (opts.vivifyonce >= 2 && !c->redundant && c->vivified)
|
|
return false;
|
|
if (!c->redundant)
|
|
return true;
|
|
CADICAL_assert (c->redundant);
|
|
|
|
// likely_to_be_kept_clause is too aggressive at removing tier-3 clauses
|
|
return true;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// In a strengthened clause the idea is to move non-false literals to the
|
|
// front, followed by false literals. Literals are further sorted by
|
|
// reverse assignment order. The goal is to use watches which require to
|
|
// backtrack as few as possible decision levels.
|
|
|
|
struct vivify_better_watch {
|
|
|
|
Internal *internal;
|
|
|
|
vivify_better_watch (Internal *i) : internal (i) {}
|
|
|
|
bool operator() (int a, int b) {
|
|
|
|
const signed char av = internal->val (a), bv = internal->val (b);
|
|
|
|
if (av >= 0 && bv < 0)
|
|
return true;
|
|
if (av < 0 && bv >= 0)
|
|
return false;
|
|
|
|
return internal->var (a).trail > internal->var (b).trail;
|
|
}
|
|
};
|
|
|
|
// Common code to actually strengthen a candidate clause. The resulting
|
|
// strengthened clause is communicated through the global 'clause'.
|
|
|
|
void Internal::vivify_strengthen (Clause *c, int64_t &ticks) {
|
|
|
|
CADICAL_assert (!clause.empty ());
|
|
|
|
if (clause.size () == 1) {
|
|
|
|
backtrack_without_updating_phases ();
|
|
const int unit = clause[0];
|
|
LOG (c, "vivification shrunken to unit %d", unit);
|
|
CADICAL_assert (!val (unit));
|
|
assign_unit (unit);
|
|
// lrat_chain.clear (); done in search_assign
|
|
stats.vivifyunits++;
|
|
|
|
bool ok = vivify_propagate (ticks);
|
|
if (!ok)
|
|
learn_empty_clause ();
|
|
|
|
} else {
|
|
|
|
// See explanation before 'vivify_better_watch' above.
|
|
//
|
|
sort (clause.begin (), clause.end (), vivify_better_watch (this));
|
|
|
|
int new_level = level;
|
|
|
|
const int lit0 = clause[0];
|
|
signed char val0 = val (lit0);
|
|
if (val0 < 0) {
|
|
const int level0 = var (lit0).level;
|
|
LOG ("1st watch %d negative at level %d", lit0, level0);
|
|
new_level = level0 - 1;
|
|
}
|
|
|
|
const int lit1 = clause[1];
|
|
const signed char val1 = val (lit1);
|
|
if (val1 < 0 && !(val0 > 0 && var (lit0).level <= var (lit1).level)) {
|
|
const int level1 = var (lit1).level;
|
|
LOG ("2nd watch %d negative at level %d", lit1, level1);
|
|
new_level = level1 - 1;
|
|
}
|
|
|
|
CADICAL_assert (new_level >= 0);
|
|
if (new_level < level)
|
|
backtrack (new_level);
|
|
|
|
CADICAL_assert (val (lit0) >= 0);
|
|
CADICAL_assert (val (lit1) >= 0 || (val (lit0) > 0 && val (lit1) < 0 &&
|
|
var (lit0).level <= var (lit1).level));
|
|
|
|
Clause *d = new_clause_as (c);
|
|
LOG (c, "before vivification");
|
|
LOG (d, "after vivification");
|
|
(void) d;
|
|
}
|
|
clause.clear ();
|
|
mark_garbage (c);
|
|
lrat_chain.clear ();
|
|
++stats.vivifystrs;
|
|
}
|
|
|
|
// When shortening clauses, we have to update the watched literals.
|
|
// Actually we don't do it, and instead simply backtrack back enough.
|
|
void Internal::vivify_sort_watched (Clause *c) {
|
|
|
|
sort (c->begin (), c->end (), vivify_better_watch (this));
|
|
|
|
int new_level = level;
|
|
|
|
const int lit0 = c->literals[0];
|
|
signed char val0 = val (lit0);
|
|
if (val0 < 0) {
|
|
const int level0 = var (lit0).level;
|
|
LOG ("1st watch %d negative at level %d", lit0, level0);
|
|
new_level = level0 - 1;
|
|
}
|
|
|
|
const int lit1 = c->literals[1];
|
|
const signed char val1 = val (lit1);
|
|
if (val1 < 0 && !(val0 > 0 && var (lit0).level <= var (lit1).level)) {
|
|
const int level1 = var (lit1).level;
|
|
LOG ("2nd watch %d negative at level %d", lit1, level1);
|
|
new_level = level1 - 1;
|
|
}
|
|
|
|
CADICAL_assert (new_level >= 0);
|
|
if (new_level < level)
|
|
backtrack_without_updating_phases (new_level);
|
|
|
|
CADICAL_assert (val (lit0) >= 0);
|
|
CADICAL_assert (val (lit1) >= 0 || (val (lit0) > 0 && val (lit1) < 0 &&
|
|
var (lit0).level <= var (lit1).level));
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Conflict analysis from 'start' which learns a decision only clause.
|
|
//
|
|
// We cannot use the stack-based implementation of Kissat, because we need
|
|
// to iterate over the conflict in topological ordering to produce a valid
|
|
// LRAT proof
|
|
void Internal::vivify_analyze (Clause *start, bool &subsumes,
|
|
Clause **subsuming,
|
|
const Clause *const candidate, int implied,
|
|
bool &redundant) {
|
|
const auto &t = &trail; // normal trail, so next_trail is wrong
|
|
int i = t->size (); // Start at end-of-trail.
|
|
Clause *reason = start;
|
|
CADICAL_assert (reason);
|
|
CADICAL_assert (!trail.empty ());
|
|
int uip = trail.back ();
|
|
bool mark_implied = (implied);
|
|
|
|
while (i >= 0) {
|
|
if (reason) {
|
|
redundant = (redundant || reason->redundant);
|
|
subsumes = (start != reason && reason->size <= start->size);
|
|
LOG (reason, "resolving on %d with", uip);
|
|
for (auto other : *reason) {
|
|
const Var v = var (other);
|
|
Flags &f = flags (other);
|
|
if (!marked2 (other) && v.level) {
|
|
LOG ("not subsuming due to lit %d", other);
|
|
subsumes = false;
|
|
}
|
|
if (!val (other)) {
|
|
LOG ("skipping unset lit %d", other);
|
|
continue;
|
|
}
|
|
if (other == uip) {
|
|
continue;
|
|
}
|
|
if (!v.level) {
|
|
if (f.seen || !lrat || reason == start)
|
|
continue;
|
|
LOG ("unit reason for %d", other);
|
|
int64_t id = unit_id (-other);
|
|
LOG ("adding unit reason %" PRId64 " for %s", id, LOGLIT (other));
|
|
unit_chain.push_back (id);
|
|
f.seen = true;
|
|
analyzed.push_back (other);
|
|
continue;
|
|
}
|
|
if (mark_implied && other != implied) {
|
|
LOG ("skipping non-implied literal %d on current level", other);
|
|
continue;
|
|
}
|
|
|
|
CADICAL_assert (val (other));
|
|
if (f.seen)
|
|
continue;
|
|
LOG ("pushing lit %d", other);
|
|
analyzed.push_back (other);
|
|
f.seen = true;
|
|
}
|
|
if (reason != start && reason->redundant) {
|
|
const int new_glue = recompute_glue (reason);
|
|
promote_clause (reason, new_glue);
|
|
}
|
|
if (subsumes) {
|
|
CADICAL_assert (reason);
|
|
LOG (reason, "clause found subsuming");
|
|
LOG (candidate, "clause found subsumed");
|
|
*subsuming = reason;
|
|
return;
|
|
}
|
|
} else {
|
|
LOG ("vivify analyzed decision %d", uip);
|
|
clause.push_back (-uip);
|
|
}
|
|
mark_implied = false;
|
|
|
|
uip = 0;
|
|
while (!uip && i > 0) {
|
|
CADICAL_assert (i > 0);
|
|
const int lit = (*t)[--i];
|
|
if (!var (lit).level)
|
|
continue;
|
|
if (flags (lit).seen)
|
|
uip = lit;
|
|
}
|
|
if (!uip)
|
|
break;
|
|
LOG ("uip is %d", uip);
|
|
Var &w = var (uip);
|
|
reason = w.reason;
|
|
if (lrat && reason)
|
|
lrat_chain.push_back (reason->id);
|
|
}
|
|
(void) candidate;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
// First decide which clause (candidate or conflict) to analyze and
|
|
// how to do it. We also prepare the clause by removing units.
|
|
void Internal::vivify_deduce (Clause *candidate, Clause *conflict,
|
|
int implied, Clause **subsuming,
|
|
bool &redundant) {
|
|
CADICAL_assert (lrat_chain.empty ());
|
|
bool subsumes;
|
|
Clause *reason;
|
|
|
|
CADICAL_assert (clause.empty ());
|
|
if (implied) {
|
|
reason = candidate;
|
|
mark2 (candidate);
|
|
const int not_implied = -implied;
|
|
CADICAL_assert (var (not_implied).level);
|
|
Flags &f = flags (not_implied);
|
|
f.seen = true;
|
|
LOG ("pushing implied lit %d", not_implied);
|
|
analyzed.push_back (not_implied);
|
|
clause.push_back (implied);
|
|
} else {
|
|
reason = (conflict ? conflict : candidate);
|
|
CADICAL_assert (reason);
|
|
CADICAL_assert (!reason->garbage || reason->size == 2);
|
|
mark2 (candidate);
|
|
subsumes = (candidate != reason);
|
|
redundant = reason->redundant;
|
|
LOG (reason, "resolving with");
|
|
if (lrat)
|
|
lrat_chain.push_back (reason->id);
|
|
for (auto lit : *reason) {
|
|
const Var &v = var (lit);
|
|
Flags &f = flags (lit);
|
|
CADICAL_assert (val (lit) < 0);
|
|
if (!v.level) {
|
|
if (!lrat)
|
|
continue;
|
|
LOG ("adding unit %d", lit);
|
|
if (!f.seen) {
|
|
// nevertheless we can use var (l) as if l was still assigned
|
|
// because var is updated lazily
|
|
int64_t id = unit_id (-lit);
|
|
LOG ("adding unit reason %" PRId64 " for %s", id, LOGLIT (lit));
|
|
unit_chain.push_back (id);
|
|
}
|
|
f.seen = true;
|
|
analyzed.push_back (lit);
|
|
continue;
|
|
}
|
|
CADICAL_assert (v.level);
|
|
if (!marked2 (lit)) {
|
|
LOG ("lit %d is not marked", lit);
|
|
subsumes = false;
|
|
}
|
|
LOG ("analyzing lit %d", lit);
|
|
LOG ("pushing lit %d", lit);
|
|
analyzed.push_back (lit);
|
|
f.seen = true;
|
|
}
|
|
if (reason != candidate && reason->redundant) {
|
|
const int new_glue = recompute_glue (reason);
|
|
promote_clause (reason, new_glue);
|
|
}
|
|
if (subsumes) {
|
|
CADICAL_assert (candidate != reason);
|
|
#ifndef CADICAL_NDEBUG
|
|
int nonfalse_reason = 0;
|
|
for (auto lit : *reason)
|
|
if (!fixed (lit))
|
|
++nonfalse_reason;
|
|
|
|
int nonfalse_candidate = 0;
|
|
for (auto lit : *candidate)
|
|
if (!fixed (lit))
|
|
++nonfalse_candidate;
|
|
|
|
CADICAL_assert (nonfalse_reason <= nonfalse_candidate);
|
|
#endif
|
|
LOG (candidate, "vivify subsumed 0");
|
|
LOG (reason, "vivify subsuming 0");
|
|
*subsuming = reason;
|
|
unmark (candidate);
|
|
if (lrat)
|
|
lrat_chain.clear ();
|
|
return;
|
|
}
|
|
}
|
|
|
|
vivify_analyze (reason, subsumes, subsuming, candidate, implied,
|
|
redundant);
|
|
unmark (candidate);
|
|
if (subsumes) {
|
|
CADICAL_assert (*subsuming);
|
|
LOG (candidate, "vivify subsumed");
|
|
LOG (*subsuming, "vivify subsuming");
|
|
if (lrat)
|
|
lrat_chain.clear ();
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// checks whether the clause can be strengthen or not.
|
|
bool Internal::vivify_shrinkable (const std::vector<int> &sorted,
|
|
Clause *conflict) {
|
|
|
|
unsigned count_implied = 0;
|
|
for (auto lit : sorted) {
|
|
const signed char value = val (lit);
|
|
if (!value) {
|
|
LOG ("vivification unassigned %d", lit);
|
|
return true;
|
|
}
|
|
if (value > 0) {
|
|
LOG ("vivification implied satisfied %d", lit);
|
|
if (conflict)
|
|
return true;
|
|
if (count_implied++) {
|
|
LOG ("at least one implied literal with conflict thus shrinking");
|
|
return true;
|
|
}
|
|
} else {
|
|
CADICAL_assert (value < 0);
|
|
const Var &v = var (lit);
|
|
const Flags &f = flags (lit);
|
|
if (!v.level)
|
|
continue;
|
|
if (!f.seen) {
|
|
LOG ("vivification non-analyzed %d", lit);
|
|
return true;
|
|
}
|
|
if (v.reason) {
|
|
LOG ("vivification implied falsified %d", lit);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
inline void Internal::vivify_increment_stats (const Vivifier &vivifier) {
|
|
switch (vivifier.tier) {
|
|
case Vivify_Mode::TIER1:
|
|
++stats.vivifystred1;
|
|
break;
|
|
case Vivify_Mode::TIER2:
|
|
++stats.vivifystred2;
|
|
break;
|
|
case Vivify_Mode::TIER3:
|
|
++stats.vivifystred3;
|
|
break;
|
|
default:
|
|
CADICAL_assert (vivifier.tier == Vivify_Mode::IRREDUNDANT);
|
|
++stats.vivifystrirr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
// instantiate last literal (see the description of the hack track
|
|
// 2023), fix the watches and backtrack two level back to make sure
|
|
// the watches are correct.
|
|
bool Internal::vivify_instantiate (
|
|
const std::vector<int> &sorted, Clause *c,
|
|
std::vector<std::tuple<int, Clause *, bool>> &lrat_stack,
|
|
int64_t &ticks) {
|
|
LOG ("now trying instantiation");
|
|
conflict = nullptr;
|
|
const int lit = sorted.back ();
|
|
LOG ("vivify instantiation");
|
|
CADICAL_assert (!var (lit).reason);
|
|
CADICAL_assert (var (lit).level);
|
|
CADICAL_assert (val (lit));
|
|
backtrack_without_updating_phases (level - 1);
|
|
CADICAL_assert (val (lit) == 0);
|
|
stats.vivifydecs++;
|
|
vivify_assume (lit);
|
|
bool ok = vivify_propagate (ticks);
|
|
if (!ok) {
|
|
LOG (c, "instantiate success with literal %d in", lit);
|
|
stats.vivifyinst++;
|
|
// strengthen clause
|
|
if (lrat) {
|
|
clear_analyzed_literals ();
|
|
CADICAL_assert (lrat_chain.empty ());
|
|
vivify_build_lrat (0, c, lrat_stack);
|
|
vivify_build_lrat (0, conflict, lrat_stack);
|
|
clear_analyzed_literals ();
|
|
}
|
|
int remove = lit;
|
|
conflict = nullptr;
|
|
unwatch_clause (c);
|
|
backtrack_without_updating_phases (level - 2);
|
|
strengthen_clause (c, remove);
|
|
vivify_sort_watched (c);
|
|
watch_clause (c);
|
|
CADICAL_assert (!conflict);
|
|
return true;
|
|
} else {
|
|
LOG ("vivify instantiation failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Main function: try to vivify this candidate clause in the given mode.
|
|
|
|
bool Internal::vivify_clause (Vivifier &vivifier, Clause *c) {
|
|
|
|
CADICAL_assert (c->size > 2); // see (NO-BINARY) below
|
|
CADICAL_assert (analyzed.empty ());
|
|
CADICAL_assert (!ignore);
|
|
|
|
c->vivify = false; // mark as checked / tried
|
|
c->vivified = true; // and globally remember
|
|
|
|
CADICAL_assert (!c->garbage);
|
|
|
|
auto &lrat_stack = vivifier.lrat_stack;
|
|
auto &ticks = vivifier.ticks;
|
|
ticks++;
|
|
|
|
// First check whether the candidate clause is already satisfied and at
|
|
// the same time copy its non fixed literals to 'sorted'. The literals
|
|
// in the candidate clause might not be sorted anymore due to replacing
|
|
// watches during propagation, even though we sorted them initially
|
|
// while pushing the clause onto the schedule and sorting the schedule.
|
|
//
|
|
auto &sorted = vivifier.sorted;
|
|
sorted.clear ();
|
|
|
|
for (const auto &lit : *c) {
|
|
const int tmp = fixed (lit);
|
|
if (tmp > 0) {
|
|
LOG (c, "satisfied by propagated unit %d", lit);
|
|
mark_garbage (c);
|
|
return false;
|
|
} else if (!tmp)
|
|
sorted.push_back (lit);
|
|
}
|
|
|
|
CADICAL_assert (sorted.size () > 1);
|
|
if (sorted.size () == 2) {
|
|
LOG ("skipping actual binary");
|
|
return false;
|
|
}
|
|
|
|
sort (sorted.begin (), sorted.end (), vivify_more_noccs_kissat (this));
|
|
CADICAL_assert (std::is_sorted (sorted.begin (), sorted.end (),
|
|
vivify_more_noccs (this)));
|
|
|
|
// The actual vivification checking is performed here, by assuming the
|
|
// negation of each of the remaining literals of the clause in turn and
|
|
// propagating it. If a conflict occurs or another literal in the
|
|
// clause becomes assigned during propagation, we can stop.
|
|
//
|
|
LOG (c, "vivification checking");
|
|
stats.vivifychecks++;
|
|
|
|
// If the decision 'level' is non-zero, then we can reuse decisions for
|
|
// the previous candidate, and avoid re-propagating them. In preliminary
|
|
// experiments this saved between 30%-50% decisions (and thus
|
|
// propagations), which in turn lets us also vivify more clauses within
|
|
// the same propagation bounds, or terminate earlier if vivify runs to
|
|
// completion.
|
|
//
|
|
if (level) {
|
|
#ifdef LOGGING
|
|
int orig_level = level;
|
|
#endif
|
|
// First check whether this clause is actually a reason for forcing
|
|
// one of its literals to true and then backtrack one level before
|
|
// that happened. Otherwise this clause might be incorrectly
|
|
// considered to be redundant or if this situation is checked then
|
|
// redundancy by other clauses using this forced literal becomes
|
|
// impossible.
|
|
//
|
|
int forced = 0;
|
|
|
|
// This search could be avoided if we would eagerly set the 'reason'
|
|
// boolean flag of clauses, which however we do not want to do for
|
|
// binary clauses (during propagation) and thus would still require
|
|
// a version of 'protect_reason' for binary clauses during 'reduce'
|
|
// (well binary clauses are not collected during 'reduce', but again
|
|
// this exception from the exception is pretty complex and thus a
|
|
// simply search here is probably easier to understand).
|
|
|
|
for (const auto &lit : *c) {
|
|
const signed char tmp = val (lit);
|
|
if (tmp < 0)
|
|
continue;
|
|
if (tmp > 0 && var (lit).reason == c)
|
|
forced = lit;
|
|
break;
|
|
}
|
|
if (forced) {
|
|
LOG ("clause is reason forcing %d", forced);
|
|
CADICAL_assert (var (forced).level);
|
|
backtrack_without_updating_phases (var (forced).level - 1);
|
|
}
|
|
|
|
// As long the (remaining) literals of the sorted clause match
|
|
// decisions on the trail we just reuse them.
|
|
//
|
|
if (level) {
|
|
|
|
int l = 1; // This is the decision level we want to reuse.
|
|
|
|
for (const auto &lit : sorted) {
|
|
CADICAL_assert (!fixed (lit));
|
|
const int decision = control[l].decision;
|
|
if (-lit == decision) {
|
|
LOG ("reusing decision %d at decision level %d", decision, l);
|
|
++stats.vivifyreused;
|
|
if (++l > level)
|
|
break;
|
|
} else {
|
|
LOG ("literal %d does not match decision %d at decision level %d",
|
|
lit, decision, l);
|
|
backtrack_without_updating_phases (l - 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG ("reused %d decision levels from %d", level, orig_level);
|
|
}
|
|
|
|
LOG (sorted, "sorted size %zd probing schedule", sorted.size ());
|
|
|
|
// Make sure to ignore this clause during propagation. This is not that
|
|
// easy for binary clauses (NO-BINARY), e.g., ignoring binary clauses,
|
|
// without changing 'propagate'. Actually, we do not want to remove binary
|
|
// clauses which are subsumed. Those are hyper binary resolvents and
|
|
// should be kept as learned clauses instead, unless they are transitive
|
|
// in the binary implication graph, which in turn is detected during
|
|
// transitive reduction in 'transred'.
|
|
//
|
|
ignore = c;
|
|
|
|
int subsume = 0; // determined to be redundant / subsumed
|
|
|
|
// If the candidate is redundant, i.e., we are in redundant mode, the
|
|
// clause is subsumed (in one of the two cases below where 'subsume' is
|
|
// assigned) and further all reasons involved are only binary clauses,
|
|
// then this redundant clause is what we once called a hidden tautology,
|
|
// and even for redundant clauses it makes sense to remove the candidate.
|
|
// It does not add anything to propagation power of the formula. This is
|
|
// the same argument as removing transitive clauses in the binary
|
|
// implication graph during transitive reduction.
|
|
//
|
|
|
|
// Go over the literals in the candidate clause in sorted order.
|
|
//
|
|
for (const auto &lit : sorted) {
|
|
|
|
// Exit loop as soon a literal is positively implied (case '@5' below)
|
|
// or propagation of the negation of a literal fails ('@6').
|
|
//
|
|
if (subsume)
|
|
break;
|
|
|
|
// We keep on assigning literals, even though we know already that we
|
|
// can remove one (was negatively implied), since we either might run
|
|
// into the 'subsume' case above or more false literals become implied.
|
|
// In any case this might result in stronger vivified clauses. As a
|
|
// consequence continue with this loop even if 'remove' is non-zero.
|
|
|
|
const signed char tmp = val (lit);
|
|
|
|
if (tmp) { // literal already assigned
|
|
|
|
const Var &v = var (lit);
|
|
CADICAL_assert (v.level);
|
|
if (!v.reason) {
|
|
LOG ("skipping decision %d", lit);
|
|
continue;
|
|
}
|
|
|
|
if (tmp < 0) {
|
|
CADICAL_assert (v.level);
|
|
LOG ("literal %d is already false and can be removed", lit);
|
|
continue;
|
|
}
|
|
|
|
CADICAL_assert (tmp > 0);
|
|
LOG ("subsumed since literal %d already true", lit);
|
|
subsume = lit; // will be able to subsume candidate '@5'
|
|
break;
|
|
}
|
|
|
|
CADICAL_assert (!tmp);
|
|
|
|
stats.vivifydecs++;
|
|
vivify_assume (-lit);
|
|
LOG ("negated decision %d score %" PRId64 "", lit, noccs (lit));
|
|
|
|
if (!vivify_propagate (ticks)) {
|
|
break; // hot-spot
|
|
}
|
|
}
|
|
|
|
if (subsume) {
|
|
int better_subsume_trail = var (subsume).trail;
|
|
for (auto lit : sorted) {
|
|
if (val (lit) <= 0)
|
|
continue;
|
|
const Var v = var (lit);
|
|
if (v.trail < better_subsume_trail) {
|
|
LOG ("improving subsume from %d at %d to %d at %d", subsume,
|
|
better_subsume_trail, lit, v.trail);
|
|
better_subsume_trail = v.trail;
|
|
subsume = lit;
|
|
}
|
|
}
|
|
}
|
|
|
|
Clause *subsuming = nullptr;
|
|
bool redundant = false;
|
|
const int level_after_assumptions = level;
|
|
CADICAL_assert (level_after_assumptions);
|
|
vivify_deduce (c, conflict, subsume, &subsuming, redundant);
|
|
|
|
bool res;
|
|
|
|
// reverse lrat_chain. We could probably work with reversed iterators
|
|
// (views) to be more efficient but we would have to distinguish in proof
|
|
//
|
|
if (lrat) {
|
|
for (auto id : unit_chain)
|
|
lrat_chain.push_back (id);
|
|
unit_chain.clear ();
|
|
reverse (lrat_chain.begin (), lrat_chain.end ());
|
|
}
|
|
|
|
// For an explanation of the code, see our POS'25 paper ``Revisiting
|
|
// Clause Vivification'' (although for the experiments we focused on
|
|
// Kissat instead of CaDiCaL)
|
|
if (subsuming) {
|
|
CADICAL_assert (c != subsuming);
|
|
vivify_subsume_clause (subsuming, c);
|
|
res = false;
|
|
} else if (vivify_shrinkable (sorted, conflict)) {
|
|
vivify_increment_stats (vivifier);
|
|
LOG ("vivify succeeded, learning new clause");
|
|
clear_analyzed_literals ();
|
|
LOG (lrat_chain, "lrat");
|
|
LOG (clause, "learning clause");
|
|
conflict = nullptr; // TODO dup from below
|
|
vivify_strengthen (c, ticks);
|
|
res = true;
|
|
} else if (subsume && c->redundant) {
|
|
LOG (c, "vivification implied");
|
|
mark_garbage (c);
|
|
++stats.vivifyimplied;
|
|
res = true;
|
|
} else if ((conflict || subsume) && !c->redundant && !redundant) {
|
|
if (opts.vivifydemote) {
|
|
LOG ("demote clause from irredundant to redundant");
|
|
demote_clause (c);
|
|
const int new_glue = recompute_glue (c);
|
|
promote_clause (c, new_glue);
|
|
res = false;
|
|
} else {
|
|
mark_garbage (c);
|
|
++stats.vivifyimplied;
|
|
res = true;
|
|
}
|
|
} else if (subsume) {
|
|
LOG (c, "no vivification instantiation with implied literal %d",
|
|
subsume);
|
|
CADICAL_assert (!c->redundant);
|
|
CADICAL_assert (redundant);
|
|
res = false;
|
|
++stats.vivifyimplied;
|
|
} else {
|
|
CADICAL_assert (level > 2);
|
|
CADICAL_assert ((size_t) level == sorted.size ());
|
|
LOG (c, "vivification failed on");
|
|
lrat_chain.clear ();
|
|
CADICAL_assert (!subsume);
|
|
if (!subsume && opts.vivifyinst) {
|
|
res = vivify_instantiate (sorted, c, lrat_stack, ticks);
|
|
CADICAL_assert (!conflict);
|
|
} else {
|
|
LOG ("cannot apply instantiation");
|
|
res = false;
|
|
}
|
|
}
|
|
|
|
if (conflict && level == level_after_assumptions) {
|
|
LOG ("forcing backtracking at least one level after conflict");
|
|
backtrack_without_updating_phases (level - 1);
|
|
}
|
|
|
|
clause.clear ();
|
|
clear_analyzed_literals (); // TODO why needed?
|
|
lrat_chain.clear ();
|
|
conflict = nullptr;
|
|
ignore = nullptr;
|
|
|
|
if (res) {
|
|
switch (vivifier.tier) {
|
|
case Vivify_Mode::IRREDUNDANT:
|
|
++stats.vivifiedirred;
|
|
break;
|
|
case Vivify_Mode::TIER1:
|
|
++stats.vivifiedtier1;
|
|
break;
|
|
case Vivify_Mode::TIER2:
|
|
++stats.vivifiedtier2;
|
|
break;
|
|
default:
|
|
CADICAL_assert (vivifier.tier == Vivify_Mode::TIER3);
|
|
++stats.vivifiedtier3;
|
|
break;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// when we can strengthen clause c we have to build lrat.
|
|
// uses f.seen so do not forget to clear flags afterwards.
|
|
// this can happen in three cases. (1), (2) are only sound in redundant mode
|
|
// (1) literal l in c is positively implied. in this case we call the
|
|
// function with (l, l.reason). This justifies the reduction because the new
|
|
// clause c' will include l and all decisions so l.reason is a conflict
|
|
// assuming -c' (2) conflict during vivify propagation. function is called
|
|
// with (0, conflict) similar to (1) but more direct. (3) some literals in c
|
|
// are negatively implied and can therefore be removed. in this case we call
|
|
// the function with (0, c). originally we justified each literal in c on
|
|
// its own but this is not actually necessary.
|
|
//
|
|
|
|
// Non-recursive version, as some bugs have been found by Dominik Schreiber
|
|
// during his very large experiments. DFS over the reasons with preordering
|
|
// (aka we explore the entire reason before exploring deeper)
|
|
void Internal::vivify_build_lrat (
|
|
int lit, Clause *reason,
|
|
std::vector<std::tuple<int, Clause *, bool>> &stack) {
|
|
CADICAL_assert (stack.empty ());
|
|
stack.push_back ({lit, reason, false});
|
|
while (!stack.empty ()) {
|
|
int lit;
|
|
Clause *reason;
|
|
bool finished;
|
|
std::tie (lit, reason, finished) = stack.back ();
|
|
LOG ("VIVIFY LRAT justifying %d", lit);
|
|
stack.pop_back ();
|
|
if (lit && flags (lit).seen) {
|
|
LOG ("skipping already justified");
|
|
continue;
|
|
}
|
|
if (finished) {
|
|
lrat_chain.push_back (reason->id);
|
|
if (lit && reason) {
|
|
Flags &f = flags (lit);
|
|
f.seen = true;
|
|
analyzed.push_back (lit); // CADICAL_assert (val (other) < 0);
|
|
CADICAL_assert (flags (lit).seen);
|
|
}
|
|
continue;
|
|
} else
|
|
stack.push_back ({lit, reason, true});
|
|
for (const auto &other : *reason) {
|
|
if (other == lit)
|
|
continue;
|
|
Var &v = var (other);
|
|
Flags &f = flags (other);
|
|
if (f.seen)
|
|
continue;
|
|
if (!v.level) {
|
|
const int64_t id = unit_id (-other);
|
|
lrat_chain.push_back (id);
|
|
f.seen = true;
|
|
analyzed.push_back (other);
|
|
continue;
|
|
}
|
|
if (v.reason) { // recursive justification
|
|
LOG ("VIVIFY LRAT pushing %d", other);
|
|
stack.push_back ({other, v.reason, false});
|
|
}
|
|
}
|
|
}
|
|
stack.clear ();
|
|
}
|
|
|
|
// calculate lrat_chain
|
|
//
|
|
inline void Internal::vivify_chain_for_units (int lit, Clause *reason) {
|
|
if (!lrat)
|
|
return;
|
|
if (level)
|
|
return; // not decision level 0
|
|
CADICAL_assert (lrat_chain.empty ());
|
|
for (auto &reason_lit : *reason) {
|
|
if (lit == reason_lit)
|
|
continue;
|
|
CADICAL_assert (val (reason_lit));
|
|
const int signed_reason_lit = val (reason_lit) * reason_lit;
|
|
int64_t id = unit_id (signed_reason_lit);
|
|
lrat_chain.push_back (id);
|
|
}
|
|
lrat_chain.push_back (reason->id);
|
|
}
|
|
|
|
vivify_ref create_ref (Internal *internal, Clause *c) {
|
|
LOG (c, "creating vivify_refs of clause");
|
|
vivify_ref ref;
|
|
ref.clause = c;
|
|
ref.size = c->size;
|
|
for (int i = 0; i < COUNTREF_COUNTS; ++i)
|
|
ref.count[i] = 0;
|
|
ref.vivify = c->vivify;
|
|
int lits[COUNTREF_COUNTS] = {0};
|
|
for (int i = 0; i != std::min (COUNTREF_COUNTS, c->size); ++i) {
|
|
int best = 0;
|
|
unsigned best_count = 0;
|
|
for (auto lit : *c) {
|
|
LOG ("to find best number of occurrences for literal %d, looking at "
|
|
"literal %d",
|
|
i, lit);
|
|
for (int j = 0; j != i; ++j) {
|
|
LOG ("comparing %d with literal %d", lit, lits[j]);
|
|
if (lits[j] == lit)
|
|
goto CONTINUE_WITH_NEXT_LITERAL;
|
|
}
|
|
{
|
|
const int64_t lit_count = internal->noccs (lit);
|
|
CADICAL_assert (lit_count);
|
|
LOG ("checking literal %s with %" PRId64 " occurrences",
|
|
LOGLIT (lit), lit_count);
|
|
if (lit_count <= best_count)
|
|
continue;
|
|
best_count = lit_count;
|
|
best = lit;
|
|
}
|
|
CONTINUE_WITH_NEXT_LITERAL:;
|
|
}
|
|
CADICAL_assert (best);
|
|
CADICAL_assert (best_count);
|
|
CADICAL_assert (best_count < UINT32_MAX);
|
|
ref.count[i] =
|
|
(((uint64_t) best_count) << 32) + (uint64_t) internal->vlit (best);
|
|
LOG ("final count at position %d is %s - %u: %" PRIu64, i,
|
|
LOGLIT (best), best_count, ref.count[i]);
|
|
lits[i] = best;
|
|
}
|
|
return ref;
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
inline void
|
|
Internal::vivify_prioritize_leftovers (char tag, size_t prioritized,
|
|
std::vector<Clause *> &schedule) {
|
|
if (prioritized) {
|
|
PHASE ("vivify", stats.vivifications,
|
|
"[phase %c] leftovers of %zu clause", tag, prioritized);
|
|
} else {
|
|
PHASE ("vivify", stats.vivifications,
|
|
"[phase %c] prioritizing all clause", tag);
|
|
for (auto c : schedule)
|
|
c->vivify = true;
|
|
}
|
|
#ifdef CADICAL_QUIET
|
|
(void) tag;
|
|
#endif
|
|
const size_t max = opts.vivifyschedmax;
|
|
if (schedule.size () > max) {
|
|
if (prioritized) {
|
|
// put the left-overs first to be kept
|
|
std::stable_partition (begin (schedule), end (schedule),
|
|
[] (Clause *c) { return c->vivify; });
|
|
}
|
|
schedule.resize (max);
|
|
}
|
|
// let's try to save a bit of memory
|
|
shrink_vector (schedule);
|
|
}
|
|
|
|
void Internal::vivify_initialize (Vivifier &vivifier, int64_t &ticks) {
|
|
|
|
const int tier1 = vivifier.tier1_limit;
|
|
const int tier2 = vivifier.tier2_limit;
|
|
// Count the number of occurrences of literals in all clauses,
|
|
// particularly binary clauses, which are usually responsible
|
|
// for most of the propagations.
|
|
//
|
|
init_noccs ();
|
|
|
|
// Disconnect all watches since we sort literals within clauses.
|
|
//
|
|
CADICAL_assert (watching ());
|
|
#if 0
|
|
clear_watches ();
|
|
#endif
|
|
|
|
size_t prioritized_irred = 0, prioritized_tier1 = 0,
|
|
prioritized_tier2 = 0, prioritized_tier3 = 0;
|
|
for (const auto &c : clauses) {
|
|
++ticks;
|
|
if (c->size == 2)
|
|
continue; // see also (NO-BINARY) above
|
|
if (!consider_to_vivify_clause (c))
|
|
continue;
|
|
|
|
// This computes an approximation of the Jeroslow Wang heuristic
|
|
// score
|
|
//
|
|
// nocc (L) = sum 2^(12-|C|)
|
|
// L in C in F
|
|
//
|
|
// but we cap the size at 12, that is all clauses of size 12 and
|
|
// larger contribute '1' to the score, which allows us to use 'long'
|
|
// numbers. See the example above (search for '@1').
|
|
//
|
|
const int shift = 12 - c->size;
|
|
const int64_t score = shift < 1 ? 1 : (1l << shift); // @4
|
|
for (const auto lit : *c) {
|
|
noccs (lit) += score;
|
|
}
|
|
LOG (c, "putting clause in candidates");
|
|
if (!c->redundant)
|
|
vivifier.schedule_irred.push_back (c),
|
|
prioritized_irred += (c->vivify);
|
|
else if (c->glue <= tier1)
|
|
vivifier.schedule_tier1.push_back (c),
|
|
prioritized_tier1 += (c->vivify);
|
|
else if (c->glue <= tier2)
|
|
vivifier.schedule_tier2.push_back (c),
|
|
prioritized_tier2 += (c->vivify);
|
|
else
|
|
vivifier.schedule_tier3.push_back (c),
|
|
prioritized_tier3 += (c->vivify);
|
|
++ticks;
|
|
}
|
|
|
|
vivify_prioritize_leftovers ('x', prioritized_irred,
|
|
vivifier.schedule_irred);
|
|
vivify_prioritize_leftovers ('u', prioritized_tier1,
|
|
vivifier.schedule_tier1);
|
|
vivify_prioritize_leftovers ('v', prioritized_tier2,
|
|
vivifier.schedule_tier2);
|
|
vivify_prioritize_leftovers ('w', prioritized_tier3,
|
|
vivifier.schedule_tier3);
|
|
|
|
if (opts.vivifyflush) {
|
|
clear_watches ();
|
|
for (auto &sched : vivifier.schedules) {
|
|
// approximation for schedule
|
|
ticks += 1 + cache_lines (sched.size (), sizeof (Clause *));
|
|
for (const auto &c : sched) {
|
|
// Literals in scheduled clauses are sorted with their highest score
|
|
// literals first (as explained above in the example at '@2'). This
|
|
// is also needed in the prefix subsumption checking below. We do an
|
|
// approximation below that is done only in the vivify_ref structure
|
|
// below.
|
|
//
|
|
sort (c->begin (), c->end (), vivify_more_noccs (this));
|
|
// ++ticks;
|
|
}
|
|
// Flush clauses subsumed by another clause with the same prefix,
|
|
// which also includes flushing syntactically identical clauses.
|
|
//
|
|
flush_vivification_schedule (sched, ticks);
|
|
}
|
|
// approximation for schedule
|
|
// ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *));
|
|
connect_watches (); // watch all relevant clauses
|
|
}
|
|
vivify_propagate (ticks);
|
|
|
|
PHASE ("vivify", stats.vivifications,
|
|
"[phase %c] leftovers out of %zu clauses", 'u',
|
|
vivifier.schedule_tier1.size ());
|
|
}
|
|
|
|
inline std::vector<vivify_ref> ¤t_refs_schedule (Vivifier &vivifier) {
|
|
return vivifier.refs_schedule;
|
|
}
|
|
|
|
inline std::vector<Clause *> ¤t_schedule (Vivifier &vivifier) {
|
|
switch (vivifier.tier) {
|
|
case Vivify_Mode::TIER1:
|
|
return vivifier.schedule_tier1;
|
|
break;
|
|
case Vivify_Mode::TIER2:
|
|
return vivifier.schedule_tier2;
|
|
break;
|
|
case Vivify_Mode::TIER3:
|
|
return vivifier.schedule_tier3;
|
|
break;
|
|
default:
|
|
return vivifier.schedule_irred;
|
|
break;
|
|
}
|
|
#if defined(WIN32) && !defined(__MINGW32__)
|
|
__assume(false);
|
|
#else
|
|
__builtin_unreachable ();
|
|
#endif
|
|
}
|
|
|
|
struct vivify_refcount_rank {
|
|
int offset;
|
|
vivify_refcount_rank (int j) : offset (j) {
|
|
CADICAL_assert (offset < COUNTREF_COUNTS);
|
|
}
|
|
typedef uint64_t Type;
|
|
Type operator() (const vivify_ref &a) const { return a.count[offset]; }
|
|
};
|
|
|
|
struct vivify_refcount_smaller {
|
|
int offset;
|
|
vivify_refcount_smaller (int j) : offset (j) {
|
|
CADICAL_assert (offset < COUNTREF_COUNTS);
|
|
}
|
|
bool operator() (const vivify_ref &a, const vivify_ref &b) const {
|
|
const auto s = vivify_refcount_rank (offset) (a);
|
|
const auto t = vivify_refcount_rank (offset) (b);
|
|
return s < t;
|
|
}
|
|
};
|
|
|
|
struct vivify_inversesize_rank {
|
|
vivify_inversesize_rank () {}
|
|
typedef uint64_t Type;
|
|
Type operator() (const vivify_ref &a) const { return ~a.size; }
|
|
};
|
|
|
|
struct vivify_inversesize_smaller {
|
|
vivify_inversesize_smaller () {}
|
|
bool operator() (const vivify_ref &a, const vivify_ref &b) const {
|
|
const auto s = vivify_inversesize_rank () (a);
|
|
const auto t = vivify_inversesize_rank () (b);
|
|
return s < t;
|
|
}
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
// There are two modes of vivification, one using all clauses and one
|
|
// focusing on irredundant clauses only. The latter variant working on
|
|
// irredundant clauses only can also remove irredundant asymmetric
|
|
// tautologies (clauses subsumed through unit propagation), which in
|
|
// redundant mode is incorrect (due to propagating over redundant clauses).
|
|
|
|
void Internal::vivify_round (Vivifier &vivifier, int64_t ticks_limit) {
|
|
|
|
if (unsat)
|
|
return;
|
|
if (terminated_asynchronously ())
|
|
return;
|
|
|
|
auto &refs_schedule = current_refs_schedule (vivifier);
|
|
auto &schedule = current_schedule (vivifier);
|
|
|
|
PHASE ("vivify", stats.vivifications,
|
|
"starting %c vivification round ticks limit %" PRId64
|
|
" with %zu clauses",
|
|
vivifier.tag, ticks_limit, schedule.size ());
|
|
|
|
CADICAL_assert (watching ());
|
|
|
|
int64_t ticks = 1 + schedule.size ();
|
|
|
|
// Sort candidates, with first to be tried candidate clause last, i.e.,
|
|
// many occurrences and high score literals) as in the example explained
|
|
// above (search for '@3').
|
|
//
|
|
if (vivifier.tier != Vivify_Mode::IRREDUNDANT ||
|
|
irredundant () / 10 < redundant ()) {
|
|
// Literals in scheduled clauses are sorted with their highest score
|
|
// literals first (as explained above in the example at '@2'). This is
|
|
// also needed in the prefix subsumption checking below. We do an
|
|
// approximation below that is done only in the vivify_ref structure
|
|
// below.
|
|
//
|
|
|
|
// first build the schedule with vivifier_refs
|
|
const auto end_schedule = end (schedule);
|
|
refs_schedule.resize (schedule.size ());
|
|
std::transform (begin (schedule), end_schedule, begin (refs_schedule),
|
|
[&] (Clause *c) { return create_ref (this, c); });
|
|
// now sort by size
|
|
MSORT (opts.radixsortlim, refs_schedule.begin (), refs_schedule.end (),
|
|
vivify_inversesize_rank (), vivify_inversesize_smaller ());
|
|
// now (stable) sort by number of occurrences
|
|
for (int i = 0; i < COUNTREF_COUNTS; ++i) {
|
|
const int offset = COUNTREF_COUNTS - 1 - i;
|
|
MSORT (opts.radixsortlim, refs_schedule.begin (),
|
|
refs_schedule.end (), vivify_refcount_rank (offset),
|
|
vivify_refcount_smaller (offset));
|
|
}
|
|
// force left-overs at the end
|
|
std::stable_partition (begin (refs_schedule), end (refs_schedule),
|
|
[] (vivify_ref c) { return !c.vivify; });
|
|
std::transform (begin (refs_schedule), end (refs_schedule),
|
|
begin (schedule),
|
|
[] (vivify_ref c) { return c.clause; });
|
|
refs_schedule.clear ();
|
|
} else {
|
|
// skip sorting but still put clauses with the vivify tag at the end to
|
|
// be done first Kissat does this implicitely by going twice over all
|
|
// clauses
|
|
std::stable_partition (begin (schedule), end (schedule),
|
|
[] (Clause *c) { return !c->vivify; });
|
|
}
|
|
|
|
// Remember old values of counters to summarize after each round with
|
|
// verbose messages what happened in that round.
|
|
//
|
|
int64_t checked = stats.vivifychecks;
|
|
int64_t subsumed = stats.vivifysubs;
|
|
int64_t strengthened = stats.vivifystrs;
|
|
int64_t units = stats.vivifyunits;
|
|
|
|
int64_t scheduled = schedule.size ();
|
|
stats.vivifysched += scheduled;
|
|
|
|
PHASE ("vivify", stats.vivifications,
|
|
"scheduled %" PRId64 " clauses to be vivified %.0f%%", scheduled,
|
|
percent (scheduled, stats.current.irredundant));
|
|
|
|
// Limit the number of propagations during vivification as in 'probe'.
|
|
//
|
|
const int64_t limit = ticks_limit - stats.ticks.vivify;
|
|
CADICAL_assert (limit >= 0);
|
|
|
|
// the clauses might still contain set literals, so propagation since the
|
|
// beginning
|
|
propagated2 = propagated = 0;
|
|
|
|
if (!unsat && !propagate ()) {
|
|
LOG ("propagation after connecting watches in inconsistency");
|
|
learn_empty_clause ();
|
|
}
|
|
|
|
vivifier.ticks = ticks;
|
|
int retry = 0;
|
|
while (!unsat && !terminated_asynchronously () && !schedule.empty () &&
|
|
vivifier.ticks < limit) {
|
|
Clause *c = schedule.back (); // Next candidate.
|
|
schedule.pop_back ();
|
|
if (vivify_clause (vivifier, c) && !c->garbage && c->size > 2 &&
|
|
retry < opts.vivifyretry) {
|
|
++retry;
|
|
schedule.push_back (c);
|
|
} else
|
|
retry = 0;
|
|
}
|
|
|
|
if (level)
|
|
backtrack_without_updating_phases ();
|
|
|
|
if (!unsat) {
|
|
int64_t still_need_to_be_vivified = schedule.size ();
|
|
#if 0
|
|
// in the current round we have new_clauses_to_vivify @ leftovers from previous round There are
|
|
// now two possibilities: (i) we consider all clauses as leftovers, or (ii) only the leftovers
|
|
// from previous round are considered leftovers.
|
|
//
|
|
// CaDiCaL had the first version before. If
|
|
// commented out we go to the second version.
|
|
for (auto c : schedule)
|
|
c->vivify = true;
|
|
#elif 1
|
|
// if we have gone through all the leftovers (the next candidate
|
|
// is not one), all the current clauses are leftovers for the next
|
|
// round
|
|
if (!schedule.empty () && !schedule.back ()->vivify)
|
|
for (auto c : schedule)
|
|
c->vivify = true;
|
|
#else
|
|
// do nothing like in kissat and use the candidates for next time.
|
|
#endif
|
|
// Preference clauses scheduled but not vivified yet next time.
|
|
//
|
|
if (still_need_to_be_vivified)
|
|
PHASE ("vivify", stats.vivifications,
|
|
"still need to vivify %" PRId64 " clauses %.02f%% of %" PRId64
|
|
" scheduled",
|
|
still_need_to_be_vivified,
|
|
percent (still_need_to_be_vivified, scheduled), scheduled);
|
|
else {
|
|
PHASE ("vivify", stats.vivifications,
|
|
"no previously not yet vivified clause left");
|
|
}
|
|
|
|
erase_vector (schedule); // Reclaim memory early.
|
|
}
|
|
|
|
if (!unsat) {
|
|
|
|
// Since redundant clause were disconnected during propagating vivified
|
|
// units in redundant mode, and further irredundant clauses are
|
|
// arbitrarily sorted, we have to propagate all literals again after
|
|
// connecting the first two literals in the clauses, in order to
|
|
// reestablish the watching invariant.
|
|
//
|
|
propagated2 = propagated = 0;
|
|
|
|
if (!propagate ()) {
|
|
LOG ("propagating vivified units leads to conflict");
|
|
learn_empty_clause ();
|
|
}
|
|
}
|
|
|
|
checked = stats.vivifychecks - checked;
|
|
subsumed = stats.vivifysubs - subsumed;
|
|
strengthened = stats.vivifystrs - strengthened;
|
|
units = stats.vivifyunits - units;
|
|
|
|
PHASE ("vivify", stats.vivifications,
|
|
"checked %" PRId64 " clauses %.02f%% of %" PRId64
|
|
" scheduled using %" PRIu64 " ticks",
|
|
checked, percent (checked, scheduled), scheduled, vivifier.ticks);
|
|
if (units)
|
|
PHASE ("vivify", stats.vivifications,
|
|
"found %" PRId64 " units %.02f%% of %" PRId64 " checked", units,
|
|
percent (units, checked), checked);
|
|
if (subsumed)
|
|
PHASE ("vivify", stats.vivifications,
|
|
"subsumed %" PRId64 " clauses %.02f%% of %" PRId64 " checked",
|
|
subsumed, percent (subsumed, checked), checked);
|
|
if (strengthened)
|
|
PHASE ("vivify", stats.vivifications,
|
|
"strengthened %" PRId64 " clauses %.02f%% of %" PRId64
|
|
" checked",
|
|
strengthened, percent (strengthened, checked), checked);
|
|
|
|
stats.subsumed += subsumed;
|
|
stats.strengthened += strengthened;
|
|
stats.ticks.vivify += vivifier.ticks;
|
|
|
|
bool unsuccessful = !(subsumed + strengthened + units);
|
|
report (vivifier.tag, unsuccessful);
|
|
}
|
|
|
|
void set_vivifier_mode (Vivifier &vivifier, Vivify_Mode tier) {
|
|
vivifier.tier = tier;
|
|
switch (tier) {
|
|
case Vivify_Mode::TIER1:
|
|
vivifier.tag = 'u';
|
|
break;
|
|
case Vivify_Mode::TIER2:
|
|
vivifier.tag = 'v';
|
|
break;
|
|
case Vivify_Mode::TIER3:
|
|
vivifier.tag = 'w';
|
|
break;
|
|
default:
|
|
CADICAL_assert (tier == Vivify_Mode::IRREDUNDANT);
|
|
vivifier.tag = 'x';
|
|
break;
|
|
}
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void Internal::compute_tier_limits (Vivifier &vivifier) {
|
|
if (!opts.vivifycalctier) {
|
|
vivifier.tier1_limit = 2;
|
|
vivifier.tier2_limit = 6;
|
|
return;
|
|
}
|
|
vivifier.tier1_limit = tier1[false];
|
|
vivifier.tier2_limit = tier2[false];
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
bool Internal::vivify () {
|
|
|
|
if (unsat)
|
|
return false;
|
|
if (terminated_asynchronously ())
|
|
return false;
|
|
if (!opts.vivify)
|
|
return false;
|
|
if (!stats.current.irredundant)
|
|
return false;
|
|
if (level)
|
|
backtrack ();
|
|
CADICAL_assert (opts.vivify);
|
|
CADICAL_assert (!level);
|
|
|
|
SET_EFFORT_LIMIT (totallimit, vivify, true);
|
|
|
|
private_steps = true;
|
|
|
|
START_SIMPLIFIER (vivify, VIVIFY);
|
|
stats.vivifications++;
|
|
|
|
// the effort is normalized by dividing by sumeffort below, hence no need
|
|
// to multiply by 1e-3 (also making the precision better)
|
|
double tier1effort = !opts.vivifytier1 ? 0 : (double) opts.vivifytier1eff;
|
|
double tier2effort = !opts.vivifytier2 ? 0 : (double) opts.vivifytier2eff;
|
|
double tier3effort = !opts.vivifytier3 ? 0 : (double) opts.vivifytier3eff;
|
|
double irreffort =
|
|
delaying_vivify_irredundant.bumpreasons.delay () || !opts.vivifyirred
|
|
? 0
|
|
: (double) opts.vivifyirredeff;
|
|
double sumeffort = tier1effort + tier2effort + tier3effort + irreffort;
|
|
if (!stats.current.redundant)
|
|
tier1effort = tier2effort = tier3effort = 0;
|
|
if (!sumeffort)
|
|
sumeffort = irreffort = 1;
|
|
int64_t total = totallimit - stats.ticks.vivify;
|
|
|
|
PHASE ("vivify", stats.vivifications,
|
|
"vivification limit of %" PRId64 " ticks", total);
|
|
Vivifier vivifier (Vivify_Mode::TIER1);
|
|
compute_tier_limits (vivifier);
|
|
|
|
if (vivifier.tier1_limit == vivifier.tier2_limit) {
|
|
tier1effort += tier2effort;
|
|
tier2effort = 0;
|
|
LOG ("vivification tier1 matches tier2 "
|
|
"thus using tier2 budget for tier1");
|
|
}
|
|
int64_t init_ticks = 0;
|
|
|
|
// Refill the schedule every time. Unchecked clauses are 'saved' by
|
|
// setting their 'vivify' bit, such that they can be tried next time.
|
|
// There are two things to denote: the option 'vivifyonce' does what it is
|
|
// supposed to do, and it works because the ticks are kept for the next
|
|
// schedule. Also, we limit the size of the schedule to limit the cost of
|
|
// sorting.
|
|
//
|
|
// TODO: After limiting, the cost we fixed some heuristics bug, so maybe
|
|
// we could increase the limit.
|
|
//
|
|
// TODO: count against ticks.vivify directly instead of this unholy
|
|
// shifting.
|
|
vivify_initialize (vivifier, init_ticks);
|
|
stats.ticks.vivify += init_ticks;
|
|
int64_t limit = stats.ticks.vivify;
|
|
const double shared_effort = (double) init_ticks / 4.0;
|
|
if (opts.vivifytier1) {
|
|
set_vivifier_mode (vivifier, Vivify_Mode::TIER1);
|
|
if (limit < stats.ticks.vivify)
|
|
limit = stats.ticks.vivify;
|
|
const double effort = (total * tier1effort) / sumeffort;
|
|
CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
|
|
limit);
|
|
limit += effort;
|
|
if (limit - shared_effort > stats.ticks.vivify) {
|
|
limit -= shared_effort;
|
|
CADICAL_assert (limit >= 0);
|
|
vivify_round (vivifier, limit);
|
|
} else {
|
|
LOG ("building the schedule already used our entire ticks budget for "
|
|
"tier1");
|
|
}
|
|
}
|
|
|
|
if (!unsat && tier2effort) {
|
|
// save memory (well, not really as we
|
|
// already reached the peak memory)
|
|
erase_vector (vivifier.schedule_tier1);
|
|
if (limit < stats.ticks.vivify)
|
|
limit = stats.ticks.vivify;
|
|
const double effort = (total * tier2effort) / sumeffort;
|
|
CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
|
|
limit);
|
|
limit += effort;
|
|
if (limit - shared_effort > stats.ticks.vivify) {
|
|
limit -= shared_effort;
|
|
CADICAL_assert (limit >= 0);
|
|
set_vivifier_mode (vivifier, Vivify_Mode::TIER2);
|
|
vivify_round (vivifier, limit);
|
|
} else {
|
|
LOG ("building the schedule already used our entire ticks budget for "
|
|
"tier2");
|
|
}
|
|
}
|
|
|
|
if (!unsat && tier3effort) {
|
|
erase_vector (vivifier.schedule_tier2);
|
|
if (limit < stats.ticks.vivify)
|
|
limit = stats.ticks.vivify;
|
|
const double effort = (total * tier3effort) / sumeffort;
|
|
CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
|
|
limit);
|
|
limit += effort;
|
|
if (limit - shared_effort > stats.ticks.vivify) {
|
|
limit -= shared_effort;
|
|
CADICAL_assert (limit >= 0);
|
|
set_vivifier_mode (vivifier, Vivify_Mode::TIER3);
|
|
vivify_round (vivifier, limit);
|
|
} else {
|
|
LOG ("building the schedule already used our entire ticks budget for "
|
|
"tier3");
|
|
}
|
|
}
|
|
|
|
if (!unsat && irreffort) {
|
|
erase_vector (vivifier.schedule_tier3);
|
|
if (limit < stats.ticks.vivify)
|
|
limit = stats.ticks.vivify;
|
|
const double effort = (total * irreffort) / sumeffort;
|
|
CADICAL_assert (std::numeric_limits<int64_t>::max () - (int64_t) effort >=
|
|
limit);
|
|
limit += effort;
|
|
if (limit - shared_effort > stats.ticks.vivify) {
|
|
limit -= shared_effort;
|
|
CADICAL_assert (limit >= 0);
|
|
set_vivifier_mode (vivifier, Vivify_Mode::IRREDUNDANT);
|
|
const int old = stats.vivifystrirr;
|
|
const int old_tried = stats.vivifychecks;
|
|
vivify_round (vivifier, limit);
|
|
if (stats.vivifychecks - old_tried == 0 ||
|
|
(float) (stats.vivifystrirr - old) /
|
|
(float) (stats.vivifychecks - old_tried) <
|
|
0.01) {
|
|
delaying_vivify_irredundant.bumpreasons.bump_delay ();
|
|
} else {
|
|
delaying_vivify_irredundant.bumpreasons.reduce_delay ();
|
|
}
|
|
} else {
|
|
delaying_vivify_irredundant.bumpreasons.bump_delay ();
|
|
LOG ("building the schedule already used our entire ticks budget for "
|
|
"irredundant");
|
|
}
|
|
}
|
|
|
|
reset_noccs ();
|
|
STOP_SIMPLIFIER (vivify, VIVIFY);
|
|
|
|
private_steps = false;
|
|
CADICAL_assert (!ignore);
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace CaDiCaL
|
|
|
|
ABC_NAMESPACE_IMPL_END
|