abc/src/sat/cadical/cadical_external_propagate.cpp

1269 lines
40 KiB
C++

#include "global.h"
#include "internal.hpp"
#include <algorithm>
ABC_NAMESPACE_IMPL_START
namespace CaDiCaL {
/*----------------------------------------------------------------------------*/
//
// Mark a variable as an observed one. It can be a new variable. It is
// assumed to be clean (not eliminated by previous simplifications).
//
void Internal::add_observed_var (int ilit) {
int idx = vidx (ilit);
if (idx >= (int64_t) relevanttab.size ())
relevanttab.resize (1 + (size_t) idx, 0);
unsigned &ref = relevanttab[idx];
if (ref < UINT_MAX) {
ref++;
LOG ("variable %d is observed %u times", idx, ref);
} else
LOG ("variable %d remains observed forever", idx);
// TODO: instead of actually backtracking, it would be enough to notify
// backtrack and re-play again every levels' notification to the
// propagator
if (val (ilit) && level && !fixed (ilit)) {
// The variable is already assigned, but we can not send a notification
// about it because it happened on an earlier decision level.
// To not break the stack-like view of the trail, we simply backtrack to
// undo this unnotifiable assignment.
const int assignment_level = var (ilit).level;
backtrack (assignment_level - 1);
} else if (level && fixed (ilit)) {
backtrack (0);
}
}
/*----------------------------------------------------------------------------*/
//
// Removing an observed variable should happen only once it is ensured
// that there is no unexplained propagation in the implication
// graph involving this variable.
//
void Internal::remove_observed_var (int ilit) {
if (!fixed (ilit) && level) {
backtrack ();
}
CADICAL_assert (fixed (ilit) || !level);
const int idx = vidx (ilit);
CADICAL_assert ((size_t) idx < relevanttab.size ());
unsigned &ref = relevanttab[idx];
CADICAL_assert (fixed (ilit) || ref > 0);
if (fixed (ilit))
ref = 0;
else if (ref < UINT_MAX) {
if (!--ref) {
LOG ("variable %d is not observed anymore", idx);
} else
LOG ("variable %d is unobserved once but remains observed %u times",
ilit, ref);
} else
LOG ("variable %d remains observed forever", idx);
}
/*----------------------------------------------------------------------------*/
//
// Supposed to be used only by mobical.
//
bool Internal::observed (int ilit) const {
CADICAL_assert ((size_t) vidx (ilit) < relevanttab.size ());
return relevanttab[vidx (ilit)] > 0;
}
/*----------------------------------------------------------------------------*/
//
// Check for unexplained propagations upon disconnecting external propagator
//
void Internal::set_tainted_literal () {
if (!opts.ilb) {
return;
}
for (auto idx : vars) {
if (!val (idx))
continue;
if (var (idx).reason != external_reason)
continue;
if (!tainted_literal) {
tainted_literal = idx;
continue;
}
CADICAL_assert (val (tainted_literal));
if (var (idx).level < var (tainted_literal).level) {
tainted_literal = idx;
}
}
}
void Internal::renotify_trail_after_ilb () {
CADICAL_assert (opts.ilb);
if (!external_prop || external_prop_is_lazy || !trail.size () ||
!opts.ilb) {
return;
}
LOG ("notify external propagator about new assignments (after ilb)");
#ifndef CADICAL_NDEBUG
LOG ("(decision level: %d, trail size: %zd, notified %zd)", level,
trail.size (), notified);
#endif
renotify_full_trail ();
}
void Internal::renotify_trail_after_local_search () {
if (!external_prop || external_prop_is_lazy || !trail.size ()) {
return;
}
LOG ("notify external propagator about new assignments (after local "
"search)");
#ifndef CADICAL_NDEBUG
LOG ("(decision level: %d, trail size: %zd, notified %zd)", level,
trail.size (), notified);
#endif
renotify_full_trail ();
}
void Internal::renotify_full_trail_between_trail_pos (
int start_level, int end_level, int propagator_level,
std::vector<int> &assigned, bool start_new_level) {
CADICAL_assert (assigned.empty ());
int j = start_level;
#ifdef LOGGING
LOG ("starting notification of level %d from trail %d .. %d",
propagator_level, start_level, end_level);
#else
(void) propagator_level;
#endif
if (start_new_level) {
if (assigned.size ())
external->propagator->notify_assignment (assigned);
assigned.clear ();
external->propagator->notify_new_decision_level ();
}
for (; j < end_level; ++j) {
int ilit = trail[j];
// In theory, 0 ilit can happen due to pseudo-decision levels
if (!ilit)
continue;
if (!observed (ilit))
continue;
int elit = externalize (ilit); // TODO: double-check tainting
LOG ("notifying elit %d @ %d aka %s", propagator_level, elit,
LOGLIT (ilit));
CADICAL_assert (elit);
// Fixed variables might get mapped (during compact) to another
// non-observed but fixed variable.
// This happens on root level, so notification about their assignment
// is already done.
CADICAL_assert (external->observed (elit) || fixed (ilit));
if (!external->ervars[abs (elit)])
assigned.push_back (elit);
}
if (assigned.size ())
external->propagator->notify_assignment (assigned);
assigned.clear ();
}
// It repeats ALL assignments of the trail, so the already notified
// root-level assignments will be notified multiple times.
//
// As CaDiCaL is missing some '0' seperators, it is important to go
// over slices from the control stack instead of going over the trail
// directly.
void Internal::renotify_full_trail () {
const size_t end_of_trail = trail.size ();
if (level) {
notified = 0; // TODO: save the last notified root-level position
// somewhere and use it here
notify_backtrack (0);
}
std::vector<int> assigned;
int propagator_level = 0;
const int c_size = control.size ();
{ // first all root-level literals
const int start_level = 0;
const int end_level =
(control.size () > 1 ? control[1].trail : end_of_trail);
renotify_full_trail_between_trail_pos (
start_level, end_level, propagator_level, assigned, false);
}
// notify all intermediate levels
for (int i = 2; i < c_size; ++i) {
const int start_level = control[i - 1].trail;
const int end_level = control[i].trail;
propagator_level++;
LOG ("notification of %d", propagator_level);
renotify_full_trail_between_trail_pos (
start_level, end_level, propagator_level, assigned, true);
}
// and the current level if there is non-root level one
if (level) {
const int start_level = control.back ().trail;
propagator_level++;
renotify_full_trail_between_trail_pos (
start_level, end_of_trail, propagator_level, assigned, true);
}
CADICAL_assert (propagator_level == level);
notified = trail.size ();
return;
}
/*----------------------------------------------------------------------------*/
//
// Check if the variable is assigned by decision.
//
bool Internal::is_decision (int ilit) {
if (!level || fixed (ilit) || !val (ilit))
return false;
const int idx = vidx (ilit);
Var &v = var (idx);
#ifndef CADICAL_NDEBUG
LOG (v.reason,
"is_decision: i%d (current level: %d, is_fixed: %d, v.level: %d, "
"is_external_reason: %d, v.reason: )",
ilit, level, fixed (ilit), v.level, v.reason == external_reason);
#endif
if (!v.level || v.reason)
return false;
CADICAL_assert (!v.reason);
return true;
}
void Internal::force_backtrack (size_t new_level) {
if (!forced_backt_allowed || level <= 0 || new_level >= (size_t) level)
return;
#ifndef CADICAL_NDEBUG
LOG ("external propagator forces backtrack to decision level"
"%zd (from level %d)",
new_level, level);
#endif
backtrack (new_level);
}
/*----------------------------------------------------------------------------*/
//
// Call external propagator to check if there is a literal to be propagated.
// The reason of the propagation is not necessarily asked at that point.
//
// In case the externally propagated literal is already falsified, the
// reason is asked and conflict analysis starts. In case the externally
// propagated literal is already satisfied, nothing happens.
//
bool Internal::external_propagate () {
if (level)
require_mode (SEARCH);
CADICAL_assert (!unsat);
size_t before = num_assigned;
bool cb_repropagate_needed = true;
while (cb_repropagate_needed && !conflict && external_prop &&
!external_prop_is_lazy && !private_steps) {
#ifndef CADICAL_NDEBUG
LOG ("external propagation starts (decision level: %d, trail size: "
"%zd, notified %zd)",
level, trail.size (), notified);
#endif
cb_repropagate_needed = false;
// external->reset_extended (); //TODO for inprocessing
notify_assignments ();
int elit = external->propagator->cb_propagate ();
stats.ext_prop.ext_cb++;
stats.ext_prop.eprop_call++;
while (elit) {
CADICAL_assert (external->is_observed[abs (elit)]);
int ilit = external->e2i[abs (elit)];
if (elit < 0)
ilit = -ilit;
int tmp = val (ilit);
#ifndef CADICAL_NDEBUG
CADICAL_assert (fixed (ilit) || observed (ilit));
LOG ("External propagation of e%d (i%d val: %d)", elit, ilit, tmp);
#endif
if (!tmp) {
// variable is not assigned, it can be propagated
if (!level) {
Clause *res = learn_external_reason_clause (ilit, elit);
#ifndef LOGGING
LOG (res, "reason clause of external propagation of %d:", elit);
#endif
(void) res;
} else
search_assign_external (ilit);
stats.ext_prop.eprop_prop++;
if (unsat || conflict)
break;
propagate ();
if (unsat || conflict)
break;
notify_assignments ();
} else if (tmp < 0) {
LOG ("External propgation of %d is falsified under current trail",
ilit);
stats.ext_prop.eprop_conf++;
int level_before = level;
size_t assigned = num_assigned;
Clause *res = learn_external_reason_clause (ilit, elit);
#ifndef LOGGING
LOG (res, "reason clause of external propagation of %d:", elit);
#endif
(void) res;
bool trail_changed =
(num_assigned != assigned || level != level_before ||
propagated < trail.size ());
if (unsat || conflict)
break;
if (trail_changed) {
propagate ();
if (unsat || conflict)
break;
notify_assignments ();
}
} // else (tmp > 0) -> the case of a satisfied literal is ignored
elit = external->propagator->cb_propagate ();
stats.ext_prop.ext_cb++;
stats.ext_prop.eprop_call++;
}
#ifndef CADICAL_NDEBUG
LOG ("External propagation ends (decision level: %d, trail size: %zd, "
"notified %zd)",
level, trail.size (), notified);
#endif
if (!unsat && !conflict) {
int level_before = level;
size_t assigned = num_assigned;
bool has_external_clause = ask_external_clause ();
// New observed variable might have triggered a backtrack during this
// ask_external_clause call, so we need to propagate before continuing
stats.ext_prop.ext_cb++;
stats.ext_prop.elearn_call++;
bool trail_changed =
(num_assigned != assigned || level != level_before ||
propagated < trail.size ());
if (trail_changed) {
propagate (); // unsat or conflict will be caught later
if (!unsat || !conflict)
notify_assignments ();
}
#ifndef CADICAL_NDEBUG
if (has_external_clause)
LOG ("New external clauses are to be added.");
else
LOG ("No external clauses to be added.");
#endif
while (has_external_clause) {
level_before = level;
assigned = num_assigned;
add_external_clause (0);
trail_changed =
(num_assigned != assigned || level != level_before ||
propagated < trail.size ());
cb_repropagate_needed = true;
if (unsat || conflict) {
cb_repropagate_needed = false;
break;
}
if (trail_changed) {
propagate ();
if (unsat || conflict) {
cb_repropagate_needed = false;
break;
}
notify_assignments ();
}
has_external_clause = ask_external_clause ();
stats.ext_prop.ext_cb++;
stats.ext_prop.elearn_call++;
}
}
#ifndef CADICAL_NDEBUG
LOG ("External clause addition ends on decision level %d at trail "
"size "
"%zd (notified %zd)",
level, trail.size (), notified);
#endif
}
if (before < num_assigned)
did_external_prop = true;
return !conflict;
}
/*----------------------------------------------------------------------------*/
//
// Helper function, calls 'cb_has_external_clause', while maintains the
// related redundancy type of the clause.
//
bool Internal::ask_external_clause () {
ext_clause_forgettable = false;
bool res =
external->propagator->cb_has_external_clause (ext_clause_forgettable);
return res;
}
/*----------------------------------------------------------------------------*/
//
// Literals of the externally learned clause must be reordered based on the
// assignment levels of the literals.
//
void Internal::move_literals_to_watch () {
if (clause.size () < 2)
return;
if (!level)
return;
for (int i = 0; i < 2; i++) {
int highest_position = i;
int highest_literal = clause[i];
int highest_level = var (highest_literal).level;
int highest_value = val (highest_literal);
for (size_t j = i + 1; j < clause.size (); j++) {
const int other = clause[j];
const int other_level = var (other).level;
const int other_value = val (other);
if (other_value < 0) {
if (highest_value >= 0)
continue;
if (other_level <= highest_level)
continue;
} else if (other_value > 0) {
if (highest_value > 0 && other_level >= highest_level)
continue;
} else {
if (highest_value >= 0)
continue;
}
highest_position = j;
highest_literal = other;
highest_level = other_level;
highest_value = other_value;
}
#ifndef CADICAL_NDEBUG
LOG ("highest position: %d highest level: %d highest value: %d",
highest_position, highest_level, highest_value);
#endif
if (highest_position == i)
continue;
if (highest_position > i) {
std::swap (clause[i], clause[highest_position]);
}
}
}
/*----------------------------------------------------------------------------*/
//
// Reads out from the external propagator the lemma/proapgation reason
// clause literal by literal. In case propagated_elit is 0, it is about an
// external clause via 'cb_add_external_clause_lit'. Otherwise, it is about
// learning the reason of 'propagated_elit' via 'cb_add_reason_clause_lit'.
// The learned clause is simplified by the current root-level assignment
// (i.e. root-level falsified literals are removed, root satisfied clauses
// are skipped). Duplicate literals are removed, tauotologies are detected
// and skipped. It always adds the original (un-simplified) external clause
// to the proof as an input clause and
// the simplified version of it (except exceptions below) as a derived
// clause.
//
// In case the external clause, after simplifications, is satisfied, no
// clause is constructed, and the function returns 0. In case the external
// clause, after simplifications, is empty, no clause is constructed, unsat
// is set true and the function returns 0. In case the external clause,
// after simplifications, is unit, no clause is constructed,
// 'Internal::clause' has the unit literal (without 0) and the function
// returns 0.
//
// In every other cases a new clause is constructed and the pointer is in
// newest_clause
//
void Internal::add_external_clause (int propagated_elit,
bool no_backtrack) {
CADICAL_assert (original.empty ());
int elit = 0;
if (propagated_elit) {
// Propagation reason clauses are by default assumed to be forgettable
// irredundant. In case they would be unforgettably important, the
// propagator can add them as an explicit unforgettable external clause
// or set 'are_reasons_forgettable' to false.
ext_clause_forgettable = external->propagator->are_reasons_forgettable;
#ifndef CADICAL_NDEBUG
LOG ("add external reason of propagated lit: %d", propagated_elit);
#endif
elit = external->propagator->cb_add_reason_clause_lit (propagated_elit);
} else
elit = external->propagator->cb_add_external_clause_lit ();
// we need to be build a new LRAT chain if we are already in the middle of
// the analysis (like during failed assumptions)
LOG (lrat_chain, "lrat chain before");
std::vector<int64_t> lrat_chain_ext = std::move (lrat_chain);
lrat_chain.clear ();
clause.clear ();
// Read out the external lemma into original and simplify it into clause
CADICAL_assert (clause.empty ());
CADICAL_assert (original.empty ());
CADICAL_assert (!force_no_backtrack);
CADICAL_assert (!from_propagator);
force_no_backtrack = no_backtrack;
from_propagator = true;
while (elit) {
CADICAL_assert (external->is_observed[abs (elit)]);
external->add (elit);
if (propagated_elit)
elit =
external->propagator->cb_add_reason_clause_lit (propagated_elit);
else
elit = external->propagator->cb_add_external_clause_lit ();
}
external->add (elit);
CADICAL_assert (original.empty ());
CADICAL_assert (clause.empty ());
force_no_backtrack = false;
from_propagator = false;
lrat_chain = std::move (lrat_chain_ext);
LOG (lrat_chain, "lrat chain after");
}
/*----------------------------------------------------------------------------*/
//
// Recursively calls 'learn_external_reason_clause' to explain every
// backward reachable externally propagated literal starting from 'ilit'.
//
void Internal::explain_reason (int ilit, Clause *reason, int &open) {
if (!opts.exteagerreasons)
return;
#ifndef CADICAL_NDEBUG
LOG (reason, "explain_reason of %d (open: %d)", ilit, open);
#endif
CADICAL_assert (reason);
CADICAL_assert (reason != external_reason);
for (const auto &other : *reason) {
if (other == ilit)
continue;
Flags &f = flags (other);
if (f.seen)
continue;
Var &v = var (other);
if (!v.level)
continue;
CADICAL_assert (val (other) < 0);
CADICAL_assert (v.level <= level);
if (v.reason == external_reason) {
v.reason = learn_external_reason_clause (-other, 0, true);
}
if (v.level && v.reason) {
f.seen = true;
open++;
}
}
}
/*----------------------------------------------------------------------------*/
//
// In case external propagation was used, the reason clauses of the relevant
// propagations must be learned lazily before/during conflict analysis.
// While conflict analysis needs to analyze only the current level, lazy
// clause learning must check every clause on every level that is backward
// reachable from the conflicting clause to guarantee that the assignment
// levels of the variables are accurate. So this explanation round is
// separated from the conflict analysis, thereby guranteeing that the flags
// and datastructures can be properly used later.
//
// This function must be called before the conflict analysis, in order to
// guarantee that every relevant reason clause is indeed learned already and
// to be sure that the levels of assignments are set correctly.
//
// Later TODO: experiment with bounded explanation (explain only those
// literals that are directly used during conflict analysis +
// minimizing/shrinking). The assignment levels are then only
// over-approximated.
//
void Internal::explain_external_propagations () {
CADICAL_assert (conflict);
CADICAL_assert (clause.empty ());
Clause *reason = conflict;
std::vector<int> seen_lits;
int open = 0; // Seen but not explained literal
explain_reason (0, reason, open); // marks conflict clause lits as seen
int i = trail.size (); // Start at end-of-trail
while (i > 0) {
const int lit = trail[--i];
if (!flags (lit).seen)
continue;
seen_lits.push_back (lit);
Var &v = var (lit);
if (!v.level)
continue;
if (v.reason) {
open--;
explain_reason (lit, v.reason, open);
}
if (!open)
break;
}
CADICAL_assert (!open);
if (!opts.exteagerrecalc) {
for (auto lit : seen_lits) {
Flags &f = flags (lit);
f.seen = false;
}
#ifndef CADICAL_NDEBUG
for (auto idx : vars) {
CADICAL_assert (!flags (idx).seen);
}
#endif
}
// Traverse now in the opposite direction (from lower to higher levels)
// and calculate the actual assignment level for the seen assignments.
for (auto it = seen_lits.rbegin (); it != seen_lits.rend (); ++it) {
const int lit = *it;
Flags &f = flags (lit);
Var &v = var (lit);
if (v.reason) {
int real_level = 0;
for (const auto &other : *v.reason) {
if (other == lit)
continue;
int tmp = var (other).level;
if (tmp > real_level)
real_level = tmp;
}
if (v.level && !real_level) {
build_chain_for_units (lit, v.reason, 1);
learn_unit_clause (lit);
lrat_chain.clear ();
v.reason = 0;
}
CADICAL_assert (v.level >= real_level);
if (v.level > real_level) {
v.level = real_level;
}
}
f.seen = false;
}
#if 0 // has been fuzzed extensively
for (auto idx : vars) {
CADICAL_assert (!flags (idx).seen);
}
#endif
}
/*----------------------------------------------------------------------------*/
//
// Learns the reason clause of the propagation of ilit from the
// external propagator via 'add_external_clause'.
// In case of falsified propagation steps, if the propagated literal is
// already fixed to the opposite value, externalize will not necessarily
// give back the original elit (but an equivalent one). To avoid that, in
// falsified propagation cases the propagated elit is added as a second
// argument.
//
Clause *Internal::learn_external_reason_clause (int ilit,
int falsified_elit,
bool no_backtrack) {
CADICAL_assert (external->propagator);
// we cannot modify clause during analysis
auto clause_tmp = std::move (clause);
CADICAL_assert (clause.empty ());
CADICAL_assert (original.empty ());
stats.ext_prop.eprop_expl++;
int elit = 0;
if (!falsified_elit) {
CADICAL_assert (!fixed (ilit));
elit = externalize (ilit);
} else
elit = falsified_elit;
LOG ("ilit: %d, elit: %d", ilit, elit);
add_external_clause (elit, no_backtrack);
#ifndef CADICAL_NDEBUG
if (!falsified_elit && newest_clause) {
// Check if external propagation is correct wrt to the topological order
// defined by the trail. In case it is a falsified external propagation
// step, the order does not matter, the reason simply supposed to be a
// falsified clause.
const int propagated_ilit = ilit;
for (auto const reason_ilit : *newest_clause) {
CADICAL_assert (var (reason_ilit).trail <= var (propagated_ilit).trail);
}
}
#endif
clause = std::move (clause_tmp);
return newest_clause;
}
/*----------------------------------------------------------------------------*/
//
// Helper function to be able to call learn_external_reason_clause when the
// internal clause is already used in the caller side (for example during
// proof checking). These calls are assumed to be without a falsified elit.
// Dont use it in general instead of learn_external_reason_clause because it
// does not support the corner cases where a literal remains in clause.
//
Clause *Internal::wrapped_learn_external_reason_clause (int ilit) {
Clause *res;
std::vector<int64_t> chain_tmp{std::move (lrat_chain)};
lrat_chain.clear ();
if (clause.empty ()) {
res = learn_external_reason_clause (ilit, 0, true);
} else {
std::vector<int> clause_tmp{std::move (clause)};
clause.clear ();
res = learn_external_reason_clause (ilit, 0, true);
// The learn_external_reason clause can leave a literal in clause when
// there is a falsified elit arg. Here it is not allowed to
// happen.
CADICAL_assert (clause.empty ());
clause = std::move (clause_tmp);
clause_tmp.clear ();
}
CADICAL_assert (lrat_chain.empty ());
lrat_chain = std::move (chain_tmp);
chain_tmp.clear ();
return res;
}
/*----------------------------------------------------------------------------*/
//
// Checks if the new clause forces backtracking, new assignments or conflict
// analysis
//
void Internal::handle_external_clause (Clause *res) {
if (from_propagator)
stats.ext_prop.elearned++;
// at level 0 we have to do nothing...
if (!level)
return;
if (!res) {
if (from_propagator)
stats.ext_prop.elearn_prop++;
// new unit clause. For now just backtrack.
CADICAL_assert (!force_no_backtrack);
CADICAL_assert (level);
// if (!opts.chrono) {
backtrack ();
// }
return;
}
if (from_propagator)
stats.ext_prop.elearned++;
CADICAL_assert (res->size >= 2);
const int pos0 = res->literals[0];
const int pos1 = res->literals[1];
if (force_no_backtrack) {
CADICAL_assert (val (pos1) < 0);
CADICAL_assert (val (pos0) >= 0);
return;
// TODO: maybe fix levels
}
const int l1 = var (pos1).level;
if (val (pos0) < 0) { // conflicting or propagating clause
CADICAL_assert (0 < l1 && l1 <= var (pos0).level);
if (!opts.chrono) {
backtrack (l1);
}
if (val (pos0) < 0) {
conflict = res;
if (!from_propagator) {
// its better to backtrack instead of analyze
backtrack (l1 - 1);
conflict = 0;
CADICAL_assert (!val (pos0) && !val (pos1));
}
} else {
search_assign_driving (pos0, res);
}
if (from_propagator)
stats.ext_prop.elearn_conf++;
return;
}
if (val (pos1) < 0 && !val (pos0)) { // propagating clause
if (!opts.chrono) {
backtrack (l1);
}
search_assign_driving (pos0, res);
if (from_propagator)
stats.ext_prop.elearn_conf++;
return;
}
}
/*----------------------------------------------------------------------------*/
//
// Asks the external propagator if the current solution is OK
// by calling 'cb_check_found_model (model)'.
//
// The checked model is built up after everything is restored
// from the reconstruction stack and every variable is reactivated
// and so it is not just simply the trail (i.e. it can be expensive).
//
// If the external propagator approves the model, the function
// returns true.
//
// If the propagator does not approve the model, the solver asks
// the propagator to add an external clause.
// This external clause, however, does NOT have to be falsified by
// the current model. The possible cases and reactions are described
// below in the function. The possible states after that function:
// - A solution was found and accepted by the external propagator
// - A conflicting clause was learned from the external propagator
// - The empty clause was learned due to something new learned from
// the external propagator.
//
// In case only new variables were introduced, but no new clauses were
// added, the function will return without a conflict to the outer CDCL
// loop, where the new (not yet satisfied) variables are recognized and
// the search continues.
bool Internal::external_check_solution () {
if (!external_prop)
return true;
bool trail_changed = true;
bool added_new_clauses = false;
while (trail_changed || added_new_clauses) {
notify_assignments ();
if (!satisfied ())
break;
trail_changed = false; // to be on the safe side
added_new_clauses = false;
LOG ("Final check by external propagator is invoked.");
stats.ext_prop.echeck_call++;
external->reset_extended ();
external->extend ();
std::vector<int> etrail;
// Here the variables must be filtered by external->is_observed,
// because fixed variables are internally not necessarily observed
// anymore.
for (int idx = 1;
idx <= std::min ((int) external->is_observed.size () - 1,
external->max_var);
idx++) {
if (!external->is_observed[idx])
continue;
const int lit = external->ival (idx);
etrail.push_back (lit);
#ifndef CADICAL_NDEBUG
#ifdef LOGGING
bool p = external->vals[idx];
LOG ("evals[%d]: %d ival(%d): %d", idx, p, idx, lit);
#endif
#endif
}
bool is_consistent =
external->propagator->cb_check_found_model (etrail);
stats.ext_prop.ext_cb++;
if (is_consistent) {
LOG ("Found solution is approved by external propagator.");
return true;
}
bool has_external_clause = ask_external_clause ();
stats.ext_prop.ext_cb++;
stats.ext_prop.elearn_call++;
if (has_external_clause)
LOG (
"Found solution triggered new clauses from external propagator.");
while (has_external_clause) {
int level_before = level;
size_t assigned = num_assigned;
add_external_clause (0);
bool trail_changed =
(num_assigned != assigned || level != level_before ||
propagated < trail.size ());
added_new_clauses = true;
//
// There are many possible scenarios here:
// - Learned conflicting clause: return to CDCL loop (conflict true)
// - Learned conflicting unit clause that after backtrack+BCP leads to
// a new complete solution: force the outer loop to check the new
// model (trail_changed is true, but (conflict & unsat) is false)
// - Learned empty clause: return to CDCL loop (unsat true)
// - Learned a non-conflicting unit clause:
// Though it does not invalidate the current solution, the solver
// will backtrack to the root level and will repropagate it. The
// search will start again (saved phases hopefully make it quick),
// but it is needed in order to guarantee that every fixed variable
// is properly handled+notified (important for incremental use
// cases).
// - Otherwise: the solution is considered approved and the CDCL-loop
// can return with res = 10.
//
if (unsat || conflict || trail_changed)
break;
has_external_clause = ask_external_clause ();
stats.ext_prop.ext_cb++;
stats.ext_prop.elearn_call++;
}
LOG ("No more external clause to add.");
if (unsat || conflict)
break;
}
if (!unsat && conflict) {
const int conflict_level = var (conflict->literals[0]).level;
if (conflict_level != level) {
backtrack (conflict_level);
}
}
return !conflict;
}
/*----------------------------------------------------------------------------*/
//
// Notify the external propagator that an observed variable got assigned.
//
void Internal::notify_assignments () {
if (!external_prop || external_prop_is_lazy || private_steps)
return;
const size_t end_of_trail = trail.size ();
if (notified >= end_of_trail)
return;
LOG ("notify external propagator about new assignments");
std::vector<int> assigned;
while (notified < end_of_trail) {
int ilit = trail[notified++];
if (!observed (ilit))
continue;
int elit = externalize (ilit); // TODO: double-check tainting
CADICAL_assert (elit);
if (external->ervars[abs (elit)])
continue;
// Fixed variables might get mapped (during compact) to another
// non-observed but fixed variable.
// This happens on root level, so notification about their assignment is
// already done.
CADICAL_assert (external->observed (elit) ||
(fixed (ilit) && !external->ervars[abs (elit)]));
assigned.push_back (elit);
}
external->propagator->notify_assignment (assigned);
return;
}
/*----------------------------------------------------------------------------*/
void Internal::connect_propagator () {
if (level)
backtrack ();
}
/*----------------------------------------------------------------------------*/
//
// Notify the external propagator that a new decision level is started.
//
void Internal::notify_decision () {
if (!external_prop || external_prop_is_lazy || private_steps)
return;
external->propagator->notify_new_decision_level ();
}
/*----------------------------------------------------------------------------*/
//
// Notify the external propagator that backtrack to new_level.
//
void Internal::notify_backtrack (size_t new_level) {
if (!external_prop || external_prop_is_lazy || private_steps)
return;
external->propagator->notify_backtrack (new_level);
}
/*----------------------------------------------------------------------------*/
//
// Ask the external propagator if there is a suggested literal as next
// decision.
//
int Internal::ask_decision () {
if (!external_prop || external_prop_is_lazy || private_steps)
return 0;
CADICAL_assert (!unsat);
CADICAL_assert (!conflict);
notify_assignments ();
int level_before = level;
forced_backt_allowed = true;
int elit = external->propagator->cb_decide ();
forced_backt_allowed = false;
stats.ext_prop.ext_cb++;
if (level_before != level) {
propagate ();
CADICAL_assert (!unsat);
CADICAL_assert (!conflict);
notify_assignments ();
// In case the external propagator forced to backtrack below the
// pseduo decision levels, we must go back to the CDCL loop instead of
// making a decision.
if ((size_t) level < assumptions.size () ||
((size_t) level == assumptions.size () && constraint.size ())) {
return 0;
}
}
if (!elit)
return 0;
LOG ("external propagator proposes decision: %d", elit);
CADICAL_assert (external->is_observed[abs (elit)]);
if (!external->is_observed[abs (elit)])
return 0;
int ilit = external->e2i[abs (elit)];
if (elit < 0)
ilit = -ilit;
CADICAL_assert (fixed (ilit) || observed (ilit));
LOG ("Asking external propagator for decision returned: %d (internal: "
"%d, fixed: %d, val: %d)",
elit, ilit, fixed (ilit), val (ilit));
if (fixed (ilit) || val (ilit)) {
LOG ("Proposed decision variable is already assigned, falling back to "
"internal decision.");
return 0;
}
return ilit;
}
/*----------------------------------------------------------------------------*/
//
// Check if the clause is a forgettable clause coming from the external
// propagator.
//
bool Internal::is_external_forgettable (int64_t id) {
CADICAL_assert (opts.check);
return (external->forgettable_original.find (id) !=
external->forgettable_original.end ());
}
/*----------------------------------------------------------------------------*/
//
// When an external forgettable clause is deleted, it is marked in the
// 'forgettable_original' hash, so that the internal model checking can
// ignore it.
//
void Internal::mark_garbage_external_forgettable (int64_t id) {
CADICAL_assert (opts.check);
CADICAL_assert (is_external_forgettable (id));
LOG (external->forgettable_original[id],
"forgettable external lemma is deleted:");
// Mark as removed by flipping the first flag to false.
external->forgettable_original[id][0] = 0;
}
/*----------------------------------------------------------------------------*/
//
// Check that the literals in the clause are properly ordered. Used only
// internally for debug purposes.
//
void Internal::check_watched_literal_invariants () {
#ifndef CADICAL_NDEBUG
int v0 = 0;
int v1 = 0;
if (val (clause[0]) > 0)
v0 = 1;
else if (val (clause[0]) < 0)
v0 = -1;
if (val (clause[1]) > 0)
v1 = 1;
else if (val (clause[1]) < 0)
v1 = -1;
CADICAL_assert (v0 >= v1);
#endif
if (val (clause[0]) > 0) {
if (val (clause[1]) > 0) { // Case 1: Both literals are satisfied
// They are ordered by lower to higher decision level
CADICAL_assert (var (clause[0]).level <= var (clause[1]).level);
// Every other literal of the clause is either
// - satisfied at higher level
// - unassigned
// - falsified
for (size_t i = 2; i < clause.size (); i++)
CADICAL_assert (val (clause[i]) <= 0 ||
(var (clause[1]).level <= var (clause[i]).level));
} else if (val (clause[1]) ==
0) { // Case 2: First satisfied, next unassigned
// Every other literal of the clause is either
// - unassigned
// - falsified
for (size_t i = 2; i < clause.size (); i++)
CADICAL_assert (val (clause[i]) <= 0);
} else { // Case 3: First satisfied, next falsified -> could have been a
// reason of a previous propagation
// Every other literal of the clause is falsified but at a lower
// decision level
for (size_t i = 2; i < clause.size (); i++)
CADICAL_assert (val (clause[i]) < 0 &&
(var (clause[1]).level >= var (clause[i]).level));
}
} else if (val (clause[0]) == 0) {
if (val (clause[1]) == 0) { // Case 4: Both literals are unassigned
// Every other literal of the clause is either
// - unassigned
// - falsified
for (size_t i = 2; i < clause.size (); i++)
CADICAL_assert (val (clause[i]) <= 0);
} else { // Case 5: First unassigned, next falsified -> PROPAGATE
// Every other literal of the clause is falsified but at a lower
// decision level
for (size_t i = 2; i < clause.size (); i++)
CADICAL_assert (val (clause[i]) < 0 &&
(var (clause[1]).level >= var (clause[i]).level));
}
} else {
CADICAL_assert (val (clause[0]) < 0 &&
val (clause[1]) < 0); // Case 6: Both literals are falsified
// They are ordered by higher to lower decision level
CADICAL_assert (var (clause[0]).level >= var (clause[1]).level);
// Every other literal of the clause is falsified, but at a lower level
for (size_t i = 2; i < clause.size (); i++)
CADICAL_assert (val (clause[i]) < 0 &&
(var (clause[1]).level >= var (clause[i]).level));
}
}
#ifndef CADICAL_NDEBUG
/*----------------------------------------------------------------------------*/
//
// An expensive function that can be used for deep-debug trail-related
// issues in mobical. Do not use it unless it is really unavoidable.
//
// eq_class contains all the merged external literals that are currently
// compacted to the internal literal of trail[0] and return true.
//
// In case trail[0] does not exists or is not on the root level, the
// function returns false (indicating that there was no merger literal
// found).
//
bool Internal::get_merged_literals (std::vector<int> &eq_class) {
eq_class.clear ();
if (!trail.size ())
return false;
int ilit = trail[0];
size_t lit_level = var (ilit).level;
if (!lit_level) {
// Collect all the variables that are merged and mapped to that ilit
size_t e2i_size = external->e2i.size ();
int ivar = abs (ilit);
for (size_t i = 0; i < e2i_size; i++) {
int other = abs (external->e2i[i]);
if (other == ivar) {
if (external->e2i[i] == ilit)
eq_class.push_back (i);
else
eq_class.push_back (-1 * i);
}
}
return true;
}
return false;
}
/*----------------------------------------------------------------------------*/
//
// Collect all external variables that are FIXED internally. Again an
// expensive function that should be called only for debugging in mobical.
//
// Do not use it unless it is really unavoidable.
//
void Internal::get_all_fixed_literals (std::vector<int> &fixed_lits) {
fixed_lits.clear ();
if (!trail.size ())
return;
int e2i_size = external->e2i.size ();
int ilit;
for (int eidx = 1; eidx < e2i_size; eidx++) {
ilit = external->e2i[eidx];
if (ilit && !external->ervars[eidx]) {
Flags &f = flags (ilit);
if (f.status == Flags::FIXED) {
fixed_lits.push_back (vals[abs (ilit)] * eidx);
}
}
}
}
#endif
} // namespace CaDiCaL
ABC_NAMESPACE_IMPL_END