Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://github.com/TakehideSoh/SAF
18 January 2026, 11:53:51 UTC
  • Code
  • Branches (4)
  • Releases (0)
  • Visits
    • Branches
    • Releases
    • HEAD
    • refs/heads/dev
    • refs/heads/main
    • refs/tags/v1.0-cmsb2023
    • refs/tags/v1.1-CMSB2023
    No releases to show
  • 5153fb6
  • /
  • cadical
  • /
  • src
  • /
  • elim.cpp
Raw File Download Save again
Take a new snapshot of a software origin

If the archived software origin currently browsed is not synchronized with its upstream version (for instance when new commits have been issued), you can explicitly request Software Heritage to take a new snapshot of it.

Use the form below to proceed. Once a request has been submitted and accepted, it will be processed as soon as possible. You can then check its processing state by visiting this dedicated page.
swh spinner

Processing "take a new snapshot" request ...

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge
swh:1:cnt:dbb358bad7e64f784dd6559acb20b8b14aef37c1
origin badgedirectory badge
swh:1:dir:01d80ce94d8f49344a6b25660f682eedd134ff96
origin badgerevision badge
swh:1:rev:3b45819df15225431050eeb48c1a31ffdd580af7
origin badgesnapshot badge
swh:1:snp:d3fcf0e446f52630e867f11dee4c2c828bf46951

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Tip revision: 3b45819df15225431050eeb48c1a31ffdd580af7 authored by Daniel Le Berre on 22 June 2023, 20:02:19 UTC
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;
}

}

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API