#include "internal.hpp"
namespace CaDiCaL {
inline double Internal::compute_elim_score (unsigned lit) {
assert (1 <= lit), assert (lit <= (unsigned) max_var);
const double pos = noccs (lit);
const double neg = noccs (-lit);
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;
}
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;
}
bool Internal::ineliminating () {
if (!opts.elim)
return false;
if (!preprocessing && !opts.inprocessing)
return false;
if (preprocessing)
assert (lim.preprocessing);
if (lim.elim >= stats.conflicts)
return false;
if (last.elim.fixed < stats.all.fixed)
return true;
if (last.elim.marked < stats.mark.elim)
return true;
return false;
}
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);
}
}
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);
conflict = c;
learn_empty_clause ();
conflict = 0;
break;
} else if (unit != INT_MIN) {
LOG ("new unit %d during elimination propagation of %d", unit, lit);
build_chain_for_units (unit, c, 0);
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);
}
}
}
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 ();
lrat_chain.clear ();
elim_update_removed_clause (eliminator, c, pivot);
mark_garbage (c);
}
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; int tautological = 0;
int s = 0; int t = 0;
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) {
if (!lrat)
continue;
Flags &f = flags (lit);
if (f.seen)
continue;
analyzed.push_back (lit);
f.seen = true;
int64_t id = unit_id (-lit);
lrat_chain.push_back (id);
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 ();
lrat_chain.clear ();
clear_analyzed_literals ();
unmark (c);
return false;
}
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) {
if (!lrat)
continue;
Flags &f = flags (lit);
if (f.seen)
continue;
analyzed.push_back (lit);
f.seen = true;
int64_t id = unit_id (-lit);
lrat_chain.push_back (id);
continue;
} else if ((tmp = marked (lit)) < 0) {
tautological = lit;
break;
} else if (!tmp)
clause.push_back (lit), t++;
else
assert (tmp > 0), t++;
}
clear_analyzed_literals ();
unmark (c);
const int64_t size = clause.size ();
if (lrat) {
lrat_chain.push_back (d->id);
lrat_chain.push_back (c->id);
}
if (satisfied) {
LOG (d, "satisfied by %d antecedent", satisfied);
elim_update_removed_clause (eliminator, d, satisfied);
mark_garbage (d);
clause.clear ();
lrat_chain.clear ();
return false;
}
LOG (c, "first antecedent");
LOG (d, "second antecedent");
if (tautological) {
clause.clear ();
LOG ("resolvent tautological on %d", tautological);
lrat_chain.clear ();
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");
assert (!lrat || !lrat_chain.empty ());
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;
}
if (s > size) {
assert (s == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, c, pivot);
return false;
}
if (t > size) {
assert (t == size + 1);
clause.clear ();
elim_on_the_fly_self_subsumption (eliminator, d, -pivot);
return false;
}
if (propagate_eagerly)
lrat_chain.clear ();
return true;
}
bool Internal::elim_resolvents_are_bounded (Eliminator &eliminator,
int pivot) {
const bool substitute = !eliminator.gates.empty ();
const bool resolve_gates = eliminator.definition_unit;
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);
int64_t resolvents = 0;
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 (!resolve_gates && 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;
}
inline void Internal::elim_add_resolvents (Eliminator &eliminator,
int pivot) {
const bool substitute = !eliminator.gates.empty ();
const bool resolve_gates = eliminator.definition_unit;
if (substitute) {
LOG ("substituting pivot %d by resolving with %zd gate clauses", pivot,
eliminator.gates.size ());
stats.elimsubst++;
}
switch (eliminator.gatetype) {
case EQUI:
stats.eliminated_equi++;
break;
case AND:
stats.eliminated_and++;
break;
case ITE:
stats.eliminated_ite++;
break;
case XOR:
stats.eliminated_xor++;
break;
case DEF:
stats.eliminated_def++;
break;
case NO:
break;
}
LOG ("adding all resolvents on %d", pivot);
assert (!val (pivot));
assert (!flags (pivot).eliminated ());
const Occs &ps = occs (pivot);
const Occs &ns = occs (-pivot);
#ifdef LOGGING
int64_t resolvents = 0;
#endif
for (auto &c : ps) {
if (unsat)
break;
if (c->garbage)
continue;
for (auto &d : ns) {
if (unsat)
break;
if (d->garbage)
continue;
if (!resolve_gates && substitute && c->gate == d->gate)
continue;
if (!resolve_clauses (eliminator, c, pivot, d, false))
continue;
assert (!lrat || !lrat_chain.empty ());
Clause *r = new_resolved_irredundant_clause ();
elim_update_added_clause (eliminator, r);
eliminator.enqueue (r);
lrat_chain.clear ();
clause.clear ();
#ifdef LOGGING
resolvents++;
#endif
}
}
LOG ("added %" PRId64 " resolvents to eliminate %d", resolvents, pivot);
}
void Internal::mark_eliminated_clauses_as_garbage (
Eliminator &eliminator, int pivot, bool &deleted_binary_clause) {
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);
#ifndef NDEBUG
int64_t pushed = 0;
#endif
Occs &ps = occs (pivot);
for (const auto &c : ps) {
if (c->garbage)
continue;
assert (!c->redundant);
if (!substitute || c->gate) {
if (proof)
proof->weaken_minus (c);
if (c->size == 2)
deleted_binary_clause = true;
external->push_clause_on_extension_stack (c, pivot);
#ifndef NDEBUG
pushed++;
#endif
}
mark_garbage (c);
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;
assert (!d->redundant);
if (!substitute || d->gate) {
if (proof)
proof->weaken_minus (d);
if (d->size == 2)
deleted_binary_clause = true;
external->push_clause_on_extension_stack (d, -pivot);
#ifndef NDEBUG
pushed++;
#endif
}
mark_garbage (d);
elim_update_removed_clause (eliminator, d, -pivot);
}
erase_occs (ns);
if (substitute)
assert (pushed <= substitute);
}
void Internal::try_to_eliminate_variable (Eliminator &eliminator, int pivot,
bool &deleted_binary_clause) {
if (!active (pivot))
return;
assert (!frozen (pivot));
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 ());
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,
deleted_binary_clause);
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);
}
}
int Internal::elim_round (bool &completed, bool &deleted_binary_clause) {
assert (opts.elim);
assert (!unsat);
START_SIMPLIFIER (elim, ELIM);
stats.elimrounds++;
int64_t marked_before = last.elim.marked;
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.elimeffort;
if (delta < opts.elimmineff)
delta = opts.elimmineff;
if (delta > opts.elimmaxeff)
delta = opts.elimmaxeff;
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 ();
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); else {
for (const auto &lit : *c) {
if (!active (lit))
continue;
if (falsified)
mark_elim (lit); noccs (lit)++;
}
}
}
init_occs ();
Eliminator eliminator (this);
ElimSchedule &schedule = eliminator.schedule;
assert (schedule.empty ());
for (auto idx : vars) {
if (!active (idx))
continue;
if (frozen (idx))
continue;
if (!flags (idx).elim)
continue;
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 ()));
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;
const int64_t garbage_limit = (2 * stats.irrlits / 3) + (1 << 20);
#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, deleted_binary_clause);
#ifndef QUIET
tried++;
#endif
if (stats.garbage.literals <= garbage_limit)
continue;
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
garbage_collection ();
}
completed = !schedule.size ();
if (!completed)
last.elim.marked = marked_before;
PHASE ("elim-round", stats.elimrounds,
"tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)",
tried, percent (tried, scheduled), schedule.size ());
schedule.erase ();
Instantiator instantiator;
if (!unsat && !terminated_asynchronously () && opts.instantiate)
collect_instantiation_candidates (instantiator);
reset_occs ();
reset_noccs ();
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) instantiate (instantiator);
return eliminated; }
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);
#ifdef LOGGING
int count = 0;
#endif
for (auto idx : vars) {
if (!active (idx))
continue;
if (flags (idx).elim)
continue;
mark_elim (idx);
#ifdef LOGGING
count++;
#endif
}
LOG ("marked %d variables as elimination candidates", count);
report ('^');
}
void Internal::init_citten () {
if (!opts.elimdef)
return;
assert (!citten);
citten = kitten_init ();
}
void Internal::reset_citten () {
if (citten) {
kitten_release (citten);
citten = 0;
}
}
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);
if (external_prop) {
assert (!level);
private_steps = true;
}
#ifndef QUIET
int old_active_variables = active ();
int old_eliminated = stats.all.eliminated;
#endif
if (last.elim.subsumephases == stats.subsumephases)
subsume ();
reset_watches ();
init_citten ();
bool phase_complete = false, deleted_binary_clause = false;
int round = 1;
#ifndef QUIET
int eliminated = 0;
#endif
bool round_complete = false;
while (!unsat && !phase_complete && !terminated_asynchronously ()) {
#ifndef QUIET
int eliminated =
#endif
elim_round (round_complete, deleted_binary_clause);
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;
}
if (subsume_round ())
continue;
if (block ())
continue;
if (cover ())
continue;
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);
}
reset_citten ();
if (deleted_binary_clause)
delete_garbage_clauses ();
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 (phase_complete)
increase_elimination_bound ();
#ifndef QUIET
eliminated = stats.all.eliminated - old_eliminated;
PHASE ("elim-phase", stats.elimphases, "eliminated %d variables %.2f%%",
eliminated, percent (eliminated, old_active_variables));
#endif
if (external_prop) {
assert (!level);
private_steps = false;
}
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;
}
}