https://github.com/TakehideSoh/SAF
Tip revision: 3b45819df15225431050eeb48c1a31ffdd580af7 authored by Daniel Le Berre on 22 June 2023, 20:02:19 UTC
Typo in README
Typo in README
Tip revision: 3b45819
elim.cpp
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Implements a variant of bounded variable elimination as originally
// described in our SAT'05 paper introducing 'SATeLite'. This is an
// inprocessing version, i.e., it is interleaved with search and triggers
// subsumption and strengthening, blocked and covered clause elimination
// during elimination rounds. It focuses only those variables which
// occurred in removed irredundant clauses since the last time an
// elimination round was run. By bounding the maximum resolvent size we can
// run each elimination round until completion. See the code of 'elim' for
// how elimination rounds are interleaved with blocked clause elimination
// and subsumption (which in turn also calls vivification and transitive
// reduction of the binary implication graph).
/*------------------------------------------------------------------------*/
inline double Internal::compute_elim_score (unsigned lit) {
assert (1 <= lit), assert (lit <= (unsigned) max_var);
const unsigned uidx = 2*lit;
const double pos = internal->ntab [uidx];
const double neg = internal->ntab [uidx + 1];
if (!pos) return -neg;
if (!neg) return -pos;
double sum = 0, prod = 0;
if (opts.elimsum) sum = opts.elimsum * (pos + neg);
if (opts.elimprod) prod = opts.elimprod * (pos * neg);
return prod + sum;
}
/*------------------------------------------------------------------------*/
inline bool elim_more::operator () (unsigned a, unsigned b) {
const auto s = internal->compute_elim_score (a);
const auto t = internal->compute_elim_score (b);
if (s > t) return true;
if (s < t) return false;
return a > b;
}
/*------------------------------------------------------------------------*/
// Note that the new fast subsumption algorithm implemented in 'subsume'
// does not distinguish between irredundant and redundant clauses and is
// also run during search to strengthen and remove 'sticky' redundant
// clauses but also irredundant ones. So beside learned units during search
// or as consequence of other preprocessors, these subsumption rounds during
// search can remove (irredundant) clauses (and literals), which in turn
// might make new bounded variable elimination possible. This is tested
// in the 'bool eliminating ()' guard.
bool Internal::eliminating () {
if (!opts.elim) return false;
if (!preprocessing && !opts.inprocessing) return false;
if (preprocessing) assert (lim.preprocessing);
// Respect (increasing) conflict limit.
//
if (lim.elim >= stats.conflicts) return false;
// Wait until there are new units or new removed variables
// (in removed or shrunken irredundant clauses and thus marked).
//
if (last.elim.fixed < stats.all.fixed) return true;
if (last.elim.marked < stats.mark.elim) return true;
return false;
}
/*------------------------------------------------------------------------*/
// Update the global elimination schedule after adding or removing a clause.
void
Internal::elim_update_added_clause (Eliminator & eliminator, Clause * c) {
assert (!c->redundant);
ElimSchedule & schedule = eliminator.schedule;
for (const auto & lit : *c) {
if (!active (lit)) continue;
occs (lit).push_back (c);
if (frozen (lit)) continue;
noccs (lit)++;
const int idx = abs (lit);
if (schedule.contains (idx)) schedule.update (idx);
}
}
void Internal::elim_update_removed_lit (Eliminator & eliminator, int lit) {
if (!active (lit)) return;
if (frozen (lit)) return;
int64_t & score = noccs (lit);
assert (score > 0);
score--;
const int idx = abs (lit);
ElimSchedule & schedule = eliminator.schedule;
if (schedule.contains (idx)) schedule.update (idx);
else {
LOG ("rescheduling %d for elimination after removing clause", idx);
schedule.push_back (idx);
}
}
void
Internal::elim_update_removed_clause (Eliminator & eliminator,
Clause * c, int except)
{
assert (!c->redundant);
for (const auto & lit : *c) {
if (lit == except) continue;
assert (lit != -except);
elim_update_removed_lit (eliminator, lit);
}
}
/*------------------------------------------------------------------------*/
// Since we do not have watches we have to do our own unit propagation
// during elimination as soon we find a unit clause. This finds new units
// and also marks clauses satisfied by those units as garbage immediately.
void Internal::elim_propagate (Eliminator & eliminator, int root) {
assert (val (root) > 0);
vector<int> work;
size_t i = 0;
work.push_back (root);
while (i < work.size ()) {
int lit = work[i++];
LOG ("elimination propagation of %d", lit);
assert (val (lit) > 0);
const Occs & ns = occs (-lit);
for (const auto & c : ns) {
if (c->garbage) continue;
int unit = 0, satisfied = 0;
for (const auto & other : *c) {
const signed char tmp = val (other);
if (tmp < 0) continue;
if (tmp > 0) { satisfied = other; break; }
if (unit) unit = INT_MIN;
else unit = other;
}
if (satisfied) {
LOG (c, "elimination propagation of %d finds %d satisfied",
lit, satisfied);
elim_update_removed_clause (eliminator, c, satisfied);
mark_garbage (c);
} else if (!unit) {
LOG ("empty clause during elimination propagation of %d", lit);
learn_empty_clause ();
break;
} else if (unit != INT_MIN) {
LOG ("new unit %d during elimination propagation of %d", unit, lit);
assign_unit (unit);
work.push_back (unit);
}
}
if (unsat) break;
const Occs & ps = occs (lit);
for (const auto & c : ps) {
if (c->garbage) continue;
LOG (c, "elimination propagation of %d produces satisfied", lit);
elim_update_removed_clause (eliminator, c, lit);
mark_garbage (c);
}
}
}
/*------------------------------------------------------------------------*/
// On-the-fly self-subsuming resolution during variable elimination is due
// to HyoJung Han, Fabio Somenzi, SAT'09. Basically while resolving two
// clauses we test the resolvent to be smaller than one of the antecedents.
// If this is the case the pivot can be removed from the antecedent
// on-the-fly and the resolution can be skipped during elimination.
void Internal::elim_on_the_fly_self_subsumption (Eliminator & eliminator,
Clause * c, int pivot)
{
LOG (c, "pivot %d on-the-fly self-subsuming resolution", pivot);
stats.elimotfstr++;
stats.strengthened++;
assert (clause.empty ());
for (const auto & lit : *c) {
if (lit == pivot) continue;
const signed char tmp = val (lit);
assert (tmp <= 0);
if (tmp < 0) continue;
clause.push_back (lit);
}
Clause * r = new_resolved_irredundant_clause ();
elim_update_added_clause (eliminator, r);
clause.clear ();
elim_update_removed_clause (eliminator, c, pivot);
mark_garbage (c);
}
/*------------------------------------------------------------------------*/
// Resolve two clauses on the pivot literal 'pivot', which is assumed to
// occur in opposite phases in 'c' and 'd'. The actual resolvent is stored
// in the temporary global 'clause' if it is not redundant. It is
// considered redundant if one of the clauses is already marked as garbage
// it is root level satisfied, the resolvent is empty, a unit, or produces a
// self-subsuming resolution, which results in the pivot to be removed from
// at least one of the antecedents.
// Note that current root level assignments are taken into account, i.e., by
// removing root level falsified literals. The function returns true if the
// resolvent is not redundant and for instance has to be taken into account
// during bounded variable elimination.
// Detected units are immediately assigned and in case the last argument is
// true also propagated eagerly in a elimination specific propagation
// routine, which not only finds units but also updates the schedule.
// When this function is called during computation of the number of
// non-trivial (or non-satisfied) resolvents we can eagerly propagate units.
// But during actually adding the resolvents this results in problems as we
// found one rare test case '../test/trace/reg0056.trace' (out of billions),
// where the pivot itself was assigned during such a propagation while
// adding resolvents and lead to pushing a clause to the reconstruction
// stack that later flipped the value of the pivot (while all other literals
// in that clause were unit implied too). Not pushing the pivot clauses to
// the reconstruction stack produced a wrong model too. Our fix is to only
// eagerly propagate during computation of the number of resolvents and
// otherwise delay propagation until the end of elimination (which is less
// precise regarding scheduling but very rarely happens).
bool Internal::resolve_clauses (Eliminator & eliminator,
Clause * c, int pivot, Clause * d,
const bool propagate_eagerly) {
assert (!c->redundant);
assert (!d->redundant);
stats.elimres++;
if (c->garbage || d->garbage) return false;
if (c->size > d->size) { pivot = -pivot; swap (c, d); }
assert (!level);
assert (clause.empty ());
int satisfied = 0; // Contains this satisfying literal.
int tautological = 0; // Clashing literal if tautological.
int s = 0; // Actual literals from 'c'.
int t = 0; // Actual literals from 'd'.
// First determine whether the first antecedent is satisfied, add its
// literals to 'clause' and mark them (except for 'pivot').
//
for (const auto & lit : *c) {
if (lit == pivot) { s++; continue; }
assert (lit != -pivot);
const signed char tmp = val (lit);
if (tmp > 0) { satisfied = lit; break; }
else if (tmp < 0) continue;
else mark (lit), clause.push_back (lit), s++;
}
if (satisfied) {
LOG (c, "satisfied by %d antecedent", satisfied);
elim_update_removed_clause (eliminator, c, satisfied);
mark_garbage (c);
clause.clear ();
unmark (c);
return false;
}
// Then determine whether the second antecedent is satisfied, add its
// literal to 'clause' and check whether a clashing literal is found, such
// that the resolvent would be tautological.
//
for (const auto & lit : *d) {
if (lit == -pivot) { t++; continue; }
assert (lit != pivot);
signed char tmp = val (lit);
if (tmp > 0) { satisfied = lit; break; }
else if (tmp < 0) continue;
else if ((tmp = marked (lit)) < 0) { tautological = lit; break; }
else if (!tmp) clause.push_back (lit), t++;
else assert (tmp > 0), t++;
}
unmark (c);
const int64_t size = clause.size ();
if (satisfied) {
LOG (d, "satisfied by %d antecedent", satisfied);
elim_update_removed_clause (eliminator, d, satisfied);
mark_garbage (d);
clause.clear ();
return false;
}
LOG (c, "first antecedent");
LOG (d, "second antecedent");
if (tautological) {
clause.clear ();
LOG ("resolvent tautological on %d", tautological);
return false;
}
if (!size) {
clause.clear ();
LOG ("empty resolvent");
learn_empty_clause ();
return false;
}
if (size == 1) {
int unit = clause[0];
LOG ("unit resolvent %d", unit);
clause.clear ();
assign_unit (unit);
if (propagate_eagerly)
elim_propagate (eliminator, unit);
return false;
}
LOG (clause, "resolvent");
// Double self-subsuming resolution. The clauses 'c' and 'd' are
// identical except for the pivot which occurs in different phase. The
// resolvent subsumes both antecedents.
if (s > size && t > size) {
assert (s == size + 1);
assert (t == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, c, pivot);
LOG (d, "double pivot %d on-the-fly self-subsuming resolution", -pivot);
stats.elimotfsub++;
stats.subsumed++;
elim_update_removed_clause (eliminator, d, -pivot);
mark_garbage (d);
return false;
}
// Single self-subsuming resolution: The pivot can be removed from 'c',
// which is implemented by adding a clause which is the same as 'c' but
// with 'pivot' removed and then marking 'c' as garbage.
if (s > size) {
assert (s == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, c, pivot);
return false;
}
// Same single self-subsuming resolution situation, but only for 'd'.
if (t > size) {
assert (t == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, d, -pivot);
return false;
}
return true;
}
/*------------------------------------------------------------------------*/
// Check whether the number of non-tautological resolvents on 'pivot' is
// smaller or equal to the number of clauses with 'pivot' or '-pivot'. This
// is the main criteria of bounded variable elimination. As a side effect
// it flushes garbage clauses with that variable, sorts its occurrence lists
// (smallest clauses first) and also negates pivot if it has more positive
// than negative occurrences.
bool
Internal::elim_resolvents_are_bounded (Eliminator & eliminator, int pivot)
{
const bool substitute = !eliminator.gates.empty ();
if (substitute) LOG ("trying to substitute %d", pivot);
stats.elimtried++;
assert (!unsat);
assert (active (pivot));
const Occs & ps = occs (pivot);
const Occs & ns = occs (-pivot);
const int64_t pos = ps.size ();
const int64_t neg = ns.size ();
if (!pos || !neg) return lim.elimbound >= 0;
const int64_t bound = pos + neg + lim.elimbound;
LOG ("checking number resolvents on %d bounded by "
"%" PRId64 " = %" PRId64 " + %" PRId64 " + %" PRId64,
pivot, bound, pos, neg, lim.elimbound);
// Try all resolutions between a positive occurrence (outer loop) of
// 'pivot' and a negative occurrence of 'pivot' (inner loop) as long the
// bound on non-tautological resolvents is not hit and the size of the
// generated resolvents does not exceed the resolvent clause size limit.
int64_t resolvents = 0; // Non-tautological resolvents.
for (const auto & c : ps) {
assert (!c->redundant);
if (c->garbage) continue;
for (const auto & d : ns) {
assert (!d->redundant);
if (d->garbage) continue;
if (substitute && c->gate == d->gate) continue;
stats.elimrestried++;
if (resolve_clauses (eliminator, c, pivot, d, true)) {
resolvents++;
int size = clause.size ();
clause.clear ();
LOG ("now at least %" PRId64
" non-tautological resolvents on pivot %d",
resolvents, pivot);
if (size > opts.elimclslim) {
LOG ("resolvent size %d too big after %" PRId64
" resolvents on %d",
size, resolvents, pivot);
return false;
}
if (resolvents > bound) {
LOG ("too many non-tautological resolvents on %d", pivot);
return false;
}
} else if (unsat) return false;
else if (val (pivot)) return false;
}
}
LOG ("need %" PRId64 " <= %" PRId64 " non-tautological resolvents",
resolvents, bound);
return true;
}
/*------------------------------------------------------------------------*/
// Add all resolvents on 'pivot' and connect them.
inline void
Internal::elim_add_resolvents (Eliminator & eliminator, int pivot) {
const bool substitute = !eliminator.gates.empty ();
if (substitute) {
LOG ("substituting pivot %d by resolving with %zd gate clauses",
pivot, eliminator.gates.size ());
stats.elimsubst++;
}
LOG ("adding all resolvents on %d", pivot);
assert (!val (pivot));
assert (!flags (pivot).eliminated ());
const Occs & ps = occs (pivot);
const Occs & ns = occs (-pivot);
int64_t resolvents = 0;
for (auto & c : ps) {
if (unsat) break;
if (c->garbage) continue;
for (auto & d : ns) {
if (unsat) break;
if (d->garbage) continue;
if (substitute && c->gate == d->gate) continue;
if (!resolve_clauses (eliminator, c, pivot, d, false)) continue;
Clause * r = new_resolved_irredundant_clause ();
elim_update_added_clause (eliminator, r);
eliminator.enqueue (r);
clause.clear ();
resolvents++;
}
}
LOG ("added %" PRId64 " resolvents to eliminate %d", resolvents, pivot);
}
/*------------------------------------------------------------------------*/
// Remove clauses with 'pivot' and '-pivot' by marking them as garbage and
// push them on the extension stack.
void
Internal::mark_eliminated_clauses_as_garbage (Eliminator & eliminator,
int pivot)
{
assert (!unsat);
LOG ("marking irredundant clauses with %d as garbage", pivot);
const int64_t substitute = eliminator.gates.size ();
if (substitute)
LOG ("pushing %" PRId64 " gate clauses on extension stack", substitute);
int64_t pushed = 0;
Occs & ps = occs (pivot);
for (const auto & c : ps) {
if (c->garbage) continue;
mark_garbage (c);
assert (!c->redundant);
if (!substitute || c->gate) {
external->push_clause_on_extension_stack (c, pivot);
pushed++;
}
elim_update_removed_clause (eliminator, c, pivot);
}
erase_occs (ps);
LOG ("marking irredundant clauses with %d as garbage", -pivot);
Occs & ns = occs (-pivot);
for (const auto & d : ns) {
if (d->garbage) continue;
mark_garbage (d);
assert (!d->redundant);
if (!substitute || d->gate) {
external->push_clause_on_extension_stack (d, -pivot);
pushed++;
}
elim_update_removed_clause (eliminator, d, -pivot);
}
erase_occs (ns);
if (substitute) assert (pushed <= substitute);
// Unfortunately, we can not use the trick by Niklas Soerensson anymore,
// which avoids saving all clauses on the extension stack. This would
// break our new incremental 'restore' logic.
}
/*------------------------------------------------------------------------*/
// Try to eliminate 'pivot' by bounded variable elimination.
void
Internal::try_to_eliminate_variable (Eliminator & eliminator, int pivot) {
if (!active (pivot)) return;
assert (!frozen (pivot));
// First flush garbage clauses.
//
int64_t pos = flush_occs (pivot);
int64_t neg = flush_occs (-pivot);
if (pos > neg) { pivot = -pivot; swap (pos, neg); }
LOG ("pivot %d occurs positively %" PRId64 " times and negatively %" PRId64 " times",
pivot, pos, neg);
assert (!eliminator.schedule.contains (abs (pivot)));
assert (pos <= neg);
if (pos && neg > opts.elimocclim) {
LOG ("too many occurrences thus not eliminated %d", pivot);
assert (!eliminator.schedule.contains (abs (pivot)));
return;
}
LOG ("trying to eliminate %d", pivot);
assert (!flags (pivot).eliminated ());
// Sort occurrence lists, such that shorter clauses come first.
Occs & ps = occs (pivot);
stable_sort (ps.begin (), ps.end (), clause_smaller_size ());
Occs & ns = occs (-pivot);
stable_sort (ns.begin (), ns.end (), clause_smaller_size ());
if (pos) find_gate_clauses (eliminator, pivot);
if (!unsat && !val (pivot)) {
if (elim_resolvents_are_bounded (eliminator, pivot)) {
LOG ("number of resolvents on %d are bounded", pivot);
elim_add_resolvents (eliminator, pivot);
if (!unsat) mark_eliminated_clauses_as_garbage (eliminator, pivot);
if (active (pivot)) mark_eliminated (pivot);
} else LOG ("too many resolvents on %d so not eliminated", pivot);
}
unmark_gate_clauses (eliminator);
elim_backward_clauses (eliminator);
}
/*------------------------------------------------------------------------*/
void
Internal::mark_redundant_clauses_with_eliminated_variables_as_garbage () {
for (const auto & c : clauses) {
if (c->garbage || !c->redundant) continue;
bool clean = true;
for (const auto & lit : *c) {
Flags & f = flags (lit);
if (f.eliminated ()) { clean = false; break; }
if (f.pure ()) { clean = false; break; }
}
if (!clean) mark_garbage (c);
}
}
/*------------------------------------------------------------------------*/
// This function performs one round of bounded variable elimination and
// returns the number of eliminated variables. The additional result
// 'completed' is true if this elimination round ran to completion (all
// variables have been tried). Otherwise it was asynchronously terminated
// or the resolution limit was hit.
int Internal::elim_round (bool & completed) {
assert (opts.elim);
assert (!unsat);
START_SIMPLIFIER (elim, ELIM);
stats.elimrounds++;
last.elim.marked = stats.mark.elim;
assert (!level);
int64_t resolution_limit;
if (opts.elimlimited) {
int64_t delta = stats.propagations.search;
delta *= 1e-3 * opts.elimreleff;
if (delta < opts.elimineff) delta = opts.elimineff;
if (delta > opts.elimaxeff) delta = opts.elimaxeff;
delta = max (delta, (int64_t) 2l * active ());
PHASE ("elim-round", stats.elimrounds,
"limit of %" PRId64 " resolutions", delta);
resolution_limit = stats.elimres + delta;
} else {
PHASE ("elim-round", stats.elimrounds, "resolutions unlimited");
resolution_limit = LONG_MAX;
}
init_noccs ();
// First compute the number of occurrences of each literal and at the same
// time mark satisfied clauses and update 'elim' flags of variables in
// clauses with root level assigned literals (both false and true).
//
for (const auto & c : clauses) {
if (c->garbage || c->redundant) continue;
bool satisfied = false, falsified = false;
for (const auto & lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) satisfied = true;
else if (tmp < 0) falsified = true;
else assert (active (lit));
}
if (satisfied) mark_garbage (c); // forces more precise counts
else {
for (const auto & lit : *c) {
if (!active (lit)) continue;
if (falsified) mark_elim (lit); // simulate unit propagation
noccs (lit)++;
}
}
}
init_occs ();
Eliminator eliminator (this);
ElimSchedule & schedule = eliminator.schedule;
// Now find elimination candidates which occurred in clauses removed since
// the last time we ran bounded variable elimination, which in turned
// triggered their 'elim' bit to be set.
//
for (auto idx : vars) {
if (!active (idx)) continue;
if (frozen (idx)) continue;
if (!flags (idx).elim) continue;
flags (idx).elim = false;
LOG ("scheduling %d for elimination initially", idx);
schedule.push_back (idx);
}
schedule.shrink ();
#ifndef QUIET
int64_t scheduled = schedule.size ();
#endif
PHASE ("elim-round", stats.elimrounds,
"scheduled %" PRId64 " variables %.0f%% for elimination",
scheduled, percent (scheduled, active ()));
// Connect irredundant clauses.
//
for (const auto & c : clauses)
if (!c->garbage && !c->redundant)
for (const auto & lit : *c)
if (active (lit))
occs (lit).push_back (c);
#ifndef QUIET
const int64_t old_resolutions = stats.elimres;
#endif
const int old_eliminated = stats.all.eliminated;
const int old_fixed = stats.all.fixed;
// Limit on garbage bytes during variable elimination. If the limit is hit
// a garbage collection is performed.
//
const int64_t garbage_limit = (2*stats.irrbytes/3) + (1<<20);
// Main loops tries to eliminate variables according to the schedule. The
// schedule is updated dynamically and variables are potentially
// rescheduled to be tried again if they occur in a removed clause.
//
#ifndef QUIET
int64_t tried = 0;
#endif
while (!unsat &&
!terminated_asynchronously () &&
stats.elimres <= resolution_limit &&
!schedule.empty ()) {
int idx = schedule.front ();
schedule.pop_front ();
flags (idx).elim = false;
try_to_eliminate_variable (eliminator, idx);
#ifndef QUIET
tried++;
#endif
if (stats.garbage <= garbage_limit) continue;
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
garbage_collection ();
}
// If the schedule is empty all variables have been tried (even
// rescheduled ones). Otherwise asynchronous termination happened or we
// ran into the resolution limit (or derived unsatisfiability).
//
completed = !schedule.size ();
PHASE ("elim-round", stats.elimrounds,
"tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)",
tried, percent (tried, scheduled), schedule.size ());
schedule.erase ();
// Collect potential literal clause instantiation pairs, which needs full
// occurrence lists and thus we have it here before resetting them.
//
Instantiator instantiator;
if (!unsat &&
!terminated_asynchronously () &&
opts.instantiate)
collect_instantiation_candidates (instantiator);
reset_occs ();
reset_noccs ();
// Mark all redundant clauses with eliminated variables as garbage.
//
if (!unsat)
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
int eliminated = stats.all.eliminated - old_eliminated;
#ifndef QUIET
int64_t resolutions = stats.elimres - old_resolutions;
PHASE ("elim-round", stats.elimrounds,
"eliminated %d variables %.0f%% in %" PRId64 " resolutions",
eliminated, percent (eliminated, scheduled), resolutions);
#endif
last.elim.subsumephases = stats.subsumephases;
const int units = stats.all.fixed - old_fixed;
report ('e', !opts.reportall && !(eliminated + units));
STOP_SIMPLIFIER (elim, ELIM);
if (!unsat &&
!terminated_asynchronously () &&
instantiator) // Do we have candidate pairs?
instantiate (instantiator);
return eliminated; // non-zero if successful
}
/*------------------------------------------------------------------------*/
// Increase elimination bound (additional clauses allowed during variable
// elimination), which is triggered if elimination with last bound completed
// (including no new subsumptions). This was pioneered by GlueMiniSAT in
// the SAT Race 2015 and then picked up Chanseok Oh in his COMinisatPS
// solver, which in turn is used in the Maple series of SAT solvers.
// The bound is no increased if the maximum bound is reached.
void Internal::increase_elimination_bound () {
if (lim.elimbound >= opts.elimboundmax) return;
if (lim.elimbound < 0) lim.elimbound = 0;
else if (!lim.elimbound) lim.elimbound = 1;
else lim.elimbound *= 2;
if (lim.elimbound > opts.elimboundmax)
lim.elimbound = opts.elimboundmax;
PHASE ("elim-phase", stats.elimphases,
"new elimination bound %" PRId64 "", lim.elimbound);
// Now reschedule all active variables for elimination again.
//
int count = 0;
for (auto idx : vars) {
if (!active (idx)) continue;
if (flags (idx).elim) continue;
mark_elim (idx);
count++;
}
LOG ("marked %d variables as elimination candidates", count);
report ('^');
}
/*------------------------------------------------------------------------*/
void Internal::elim (bool update_limits) {
if (unsat) return;
if (level) backtrack ();
if (!propagate ()) { learn_empty_clause (); return; }
stats.elimphases++;
PHASE ("elim-phase", stats.elimphases,
"starting at most %d elimination rounds",
opts.elimrounds);
#ifndef QUIET
int old_active_variables = active ();
int old_eliminated = stats.all.eliminated;
#endif
// Make sure there was a complete subsumption phase since last
// elimination including vivification etc.
//
if (last.elim.subsumephases == stats.subsumephases)
subsume (update_limits);
reset_watches (); // saves lots of memory
// Alternate one round of bounded variable elimination ('elim_round') and
// subsumption ('subsume_round'), blocked ('block') and covered clause
// elimination ('cover') until nothing changes, or the round limit is hit.
// The loop also aborts early if no variable could be eliminated, the
// empty clause is resolved, it is asynchronously terminated or a
// resolution limit is hit.
// This variable determines whether the whole loop of this bounded
// variable elimination phase ('elim') ran until completion. This
// potentially triggers an incremental increase of the elimination bound.
//
bool phase_complete = false;
int round = 1;
while (!unsat &&
!phase_complete &&
!terminated_asynchronously ()) {
bool round_complete;
#ifndef QUIET
int eliminated =
#endif
elim_round (round_complete);
if (!round_complete) {
PHASE ("elim-phase", stats.elimphases,
"last round %d incomplete %s",
round, eliminated ? "but successful" : "and unsuccessful");
assert (!phase_complete);
break;
}
if (round++ >= opts.elimrounds) {
PHASE ("elim-phase", stats.elimphases,
"round limit %d hit (%s)", round-1,
eliminated ? "though last round successful" :
"last round unsuccessful anyhow");
assert (!phase_complete);
break;
}
// Prioritize 'subsumption' over blocked and covered clause elimination.
if (subsume_round ()) continue;
if (block ()) continue;
if (cover ()) continue;
// Was not able to generate new variable elimination candidates after
// variable elimination round, neither through subsumption, nor blocked,
// nor covered clause elimination.
//
PHASE ("elim-phase", stats.elimphases,
"no new variable elimination candidates");
assert (round_complete);
phase_complete = true;
}
if (phase_complete) {
stats.elimcompleted++;
PHASE ("elim-phase", stats.elimphases,
"fully completed elimination %" PRId64
" at elimination bound %" PRId64 "",
stats.elimcompleted, lim.elimbound);
} else {
PHASE ("elim-phase", stats.elimphases,
"incomplete elimination %" PRId64
" at elimination bound %" PRId64 "",
stats.elimcompleted + 1, lim.elimbound);
}
init_watches ();
connect_watches ();
if (unsat) LOG ("elimination derived empty clause");
else if (propagated < trail.size ()) {
LOG ("elimination produced %zd units",
(size_t)(trail.size () - propagated));
if (!propagate ()) {
LOG ("propagating units after elimination results in empty clause");
learn_empty_clause ();
}
}
// If we ran variable elimination until completion we increase the
// variable elimination bound and reschedule elimination of all variables.
//
if (phase_complete) increase_elimination_bound ();
#ifndef QUIET
int eliminated = stats.all.eliminated - old_eliminated;
PHASE ("elim-phase", stats.elimphases,
"eliminated %d variables %.2f%%",
eliminated, percent (eliminated, old_active_variables));
#endif
if (!update_limits) return;
int64_t delta = scale (opts.elimint * (stats.elimphases + 1));
lim.elim = stats.conflicts + delta;
PHASE ("elim-phase", stats.elimphases,
"new limit at %" PRId64 " conflicts after %" PRId64 " conflicts",
lim.elim, delta);
last.elim.fixed = stats.all.fixed;
}
}
