cadical original

This commit is contained in:
MyskYko 2025-03-05 21:25:31 -08:00
parent fbd19056e7
commit f544165a88
156 changed files with 57627 additions and 0 deletions

28
src/sat/cadical/LICENSE Normal file
View File

@ -0,0 +1,28 @@
MIT License
Copyright (c) 2016-2021 Armin Biere, Johannes Kepler University Linz, Austria
Copyright (c) 2020-2021 Mathias Fleury, Johannes Kepler University Linz, Austria
Copyright (c) 2020-2021 Nils Froleyks, Johannes Kepler University Linz, Austria
Copyright (c) 2022-2024 Katalin Fazekas, Vienna University of Technology, Austria
Copyright (c) 2021-2024 Armin Biere, University of Freiburg, Germany
Copyright (c) 2021-2024 Mathias Fleury, University of Freiburg, Germany
Copyright (c) 2023-2024 Florian Pollitt, University of Freiburg, Germany
Copyright (c) 2024-2024 Tobias Faller, University of Freiburg, Germany
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
src/sat/cadical/VERSION Normal file
View File

@ -0,0 +1 @@
2.2.0-rc1

1279
src/sat/cadical/analyze.cpp Normal file

File diff suppressed because it is too large Load Diff

30
src/sat/cadical/arena.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "internal.hpp"
namespace CaDiCaL {
Arena::Arena (Internal *i) {
memset (this, 0, sizeof *this);
internal = i;
}
Arena::~Arena () {
delete[] from.start;
delete[] to.start;
}
void Arena::prepare (size_t bytes) {
LOG ("preparing 'to' space of arena with %zd bytes", bytes);
assert (!to.start);
to.top = to.start = new char[bytes];
to.end = to.start + bytes;
}
void Arena::swap () {
delete[] from.start;
LOG ("delete 'from' space of arena with %zd bytes",
(size_t) (from.end - from.start));
from = to;
to.start = to.top = to.end = 0;
}
} // namespace CaDiCaL

105
src/sat/cadical/arena.hpp Normal file
View File

@ -0,0 +1,105 @@
#ifndef _arena_hpp_INCLUDED
#define _arena_hpp_INCLUDED
namespace CaDiCaL {
// This memory allocation arena provides fixed size pre-allocated memory for
// the moving garbage collector 'copy_non_garbage_clauses' in 'collect.cpp'
// to hold clauses which should survive garbage collection.
// The advantage of using a pre-allocated arena is that the allocation order
// of the clauses can be adapted in such a way that clauses watched by the
// same literal are allocated consecutively. This improves locality during
// propagation and thus is more cache friendly. A similar technique is
// implemented in MiniSAT and Glucose and gives substantial speed-up in
// propagations per second even though it might even almost double peek
// memory usage. Note that in MiniSAT this arena is actually required for
// MiniSAT to be able to use 32 bit clauses references instead of 64 bit
// pointers. This would restrict the maximum number of clauses and thus is
// a restriction we do not want to use anymore.
// New learned clauses are allocated in CaDiCaL outside of this arena and
// moved to the arena during garbage collection. The additional 'to' space
// required for such a moving garbage collector is only allocated for those
// clauses surviving garbage collection, which usually needs much less
// memory than all clauses. The net effect is that in our implementation
// the moving garbage collector using this arena only needs roughly 50% more
// memory than allocating the clauses directly. Both implementations can be
// compared by varying the 'opts.arenatype' option (which also controls the
// allocation order of clauses during moving them).
// The standard sequence of using the arena is as follows:
//
// Arena arena;
// ...
// arena.prepare (bytes);
// q1 = arena.copy (p1, bytes1);
// ...
// qn = arena.copy (pn, bytesn);
// assert (bytes1 + ... + bytesn <= bytes);
// arena.swap ();
// ...
// if (!arena.contains (q)) delete q;
// ...
// arena.prepare (bytes);
// q1 = arena.copy (p1, bytes1);
// ...
// qn = arena.copy (pn, bytesn);
// assert (bytes1 + ... + bytesn <= bytes);
// arena.swap ();
// ...
//
// One has to be really careful with 'qi' references to arena memory.
struct Internal;
class Arena {
Internal *internal;
struct {
char *start, *top, *end;
} from, to;
public:
Arena (Internal *);
~Arena ();
// Prepare 'to' space to hold that amount of memory. Precondition is that
// the 'to' space is empty. The following sequence of 'copy' operations
// can use as much memory in sum as pre-allocated here.
//
void prepare (size_t bytes);
// Does the memory pointed to by 'p' belong to this arena? More precisely
// to the 'from' space, since that is the only one remaining after 'swap'.
//
bool contains (void *p) const {
char *c = (char *) p;
return (from.start <= c && c < from.top) ||
(to.start <= c && c < to.top);
}
// Allocate that amount of memory in 'to' space. This assumes the 'to'
// space has been prepared to hold enough memory with 'prepare'. Then
// copy the memory pointed to by 'p' of size 'bytes'. Note that it does
// not matter whether 'p' is in 'from' or allocated outside of the arena.
//
char *copy (const char *p, size_t bytes) {
char *res = to.top;
to.top += bytes;
assert (to.top <= to.end);
memcpy (res, p, bytes);
return res;
}
// Completely delete 'from' space and then replace 'from' by 'to' (by
// pointer swapping). Everything previously allocated (in 'from') and not
// explicitly copied to 'to' with 'copy' becomes invalid.
//
void swap ();
};
} // namespace CaDiCaL
#endif

607
src/sat/cadical/assume.cpp Normal file
View File

@ -0,0 +1,607 @@
#include "internal.hpp"
#include "options.hpp"
namespace CaDiCaL {
// Failed literal handling as pioneered by MiniSAT. This first function
// adds an assumption literal onto the assumption stack.
void Internal::assume (int lit) {
if (level && !opts.ilbassumptions)
backtrack ();
else if (val (lit) < 0)
backtrack (max (0, var (lit).level - 1));
Flags &f = flags (lit);
const unsigned char bit = bign (lit);
if (f.assumed & bit) {
LOG ("ignoring already assumed %d", lit);
return;
}
LOG ("assume %d", lit);
f.assumed |= bit;
assumptions.push_back (lit);
freeze (lit);
}
// for LRAT we actually need to implement recursive DFS
// for non-lrat use BFS. TODO: maybe derecursify to avoid stack overflow
//
void Internal::assume_analyze_literal (int lit) {
assert (lit);
Flags &f = flags (lit);
if (f.seen)
return;
f.seen = true;
analyzed.push_back (lit);
Var &v = var (lit);
assert (val (lit) < 0);
if (v.reason == external_reason) {
v.reason = wrapped_learn_external_reason_clause (-lit);
assert (v.reason || !v.level);
}
assert (v.reason != external_reason);
if (!v.level) {
int64_t id = unit_id (-lit);
lrat_chain.push_back (id);
return;
}
if (v.reason) {
assert (v.level);
LOG (v.reason, "analyze reason");
for (const auto &other : *v.reason) {
assume_analyze_literal (other);
}
lrat_chain.push_back (v.reason->id);
return;
}
assert (assumed (-lit));
LOG ("failed assumption %d", -lit);
clause.push_back (lit);
}
void Internal::assume_analyze_reason (int lit, Clause *reason) {
assert (reason);
assert (lrat_chain.empty ());
assert (reason != external_reason);
assert (lrat);
for (const auto &other : *reason)
if (other != lit)
assume_analyze_literal (other);
lrat_chain.push_back (reason->id);
}
// Find all failing assumptions starting from the one on the assumption
// stack with the lowest decision level. This goes back to MiniSAT and is
// called 'analyze_final' there.
void Internal::failing () {
START (analyze);
LOG ("analyzing failing assumptions");
assert (analyzed.empty ());
assert (clause.empty ());
assert (lrat_chain.empty ());
assert (!marked_failed);
assert (!conflict_id);
if (!unsat_constraint) {
// Search for failing assumptions in the (internal) assumption stack.
// There are in essence three cases: (1) An assumption is falsified on
// the root-level and then 'failed_unit' is set to that assumption, (2)
// two clashing assumptions are assumed and then 'failed_clashing' is
// set to the second assumed one, or otherwise (3) there is a failing
// assumption 'first_failed' with minimum (non-zero) decision level
// 'failed_level'.
int failed_unit = 0;
int failed_clashing = 0;
int first_failed = 0;
int failed_level = INT_MAX;
int efailed = 0;
for (auto &elit : external->assumptions) {
int lit = external->e2i[abs (elit)];
if (elit < 0)
lit = -lit;
if (val (lit) >= 0)
continue;
const Var &v = var (lit);
if (!v.level) {
failed_unit = lit;
efailed = elit;
break;
}
if (failed_clashing)
continue;
if (v.reason == external_reason) {
Var &ev = var (lit);
ev.reason = learn_external_reason_clause (-lit);
if (!ev.reason) {
ev.level = 0;
failed_unit = lit;
efailed = elit;
break;
}
ev.level = 0;
// Recalculate assignment level
for (const auto &other : *ev.reason) {
if (other == -lit)
continue;
assert (val (other));
int tmp = var (other).level;
if (tmp > ev.level)
ev.level = tmp;
}
if (!ev.level) {
failed_unit = lit;
efailed = elit;
break;
}
}
assert (v.reason != external_reason);
if (!v.reason) {
failed_clashing = lit;
efailed = elit;
} else if (!first_failed || v.level < failed_level) {
first_failed = lit;
efailed = elit;
failed_level = v.level;
}
}
assert (clause.empty ());
// Get the 'failed' assumption from one of the three cases.
int failed;
if (failed_unit)
failed = failed_unit;
else if (failed_clashing)
failed = failed_clashing;
else
failed = first_failed;
assert (failed);
assert (efailed);
// In any case mark literal 'failed' as failed assumption.
{
Flags &f = flags (failed);
const unsigned bit = bign (failed);
assert (!(f.failed & bit));
f.failed |= bit;
}
// First case (1).
if (failed_unit) {
assert (failed == failed_unit);
LOG ("root-level falsified assumption %d", failed);
if (proof) {
if (lrat) {
unsigned eidx = (efailed > 0) + 2u * (unsigned) abs (efailed);
assert ((size_t) eidx < external->ext_units.size ());
const int64_t id = external->ext_units[eidx];
if (id) {
lrat_chain.push_back (id);
} else {
int64_t id = unit_id (-failed_unit);
lrat_chain.push_back (id);
}
}
proof->add_assumption_clause (++clause_id, -efailed, lrat_chain);
conclusion.push_back (clause_id);
lrat_chain.clear ();
}
goto DONE;
}
// Second case (2).
if (failed_clashing) {
assert (failed == failed_clashing);
LOG ("clashing assumptions %d and %d", failed, -failed);
Flags &f = flags (-failed);
const unsigned bit = bign (-failed);
assert (!(f.failed & bit));
f.failed |= bit;
if (proof) {
vector<int> clash = {externalize (failed), externalize (-failed)};
proof->add_assumption_clause (++clause_id, clash, lrat_chain);
conclusion.push_back (clause_id);
}
goto DONE;
}
// Fall through to third case (3).
LOG ("starting with assumption %d falsified on minimum decision level "
"%d",
first_failed, failed_level);
assert (first_failed);
assert (failed_level > 0);
// The 'analyzed' stack serves as working stack for a BFS through the
// implication graph until decisions, which are all assumptions, or
// units are reached. This is simpler than corresponding code in
// 'analyze'.
{
LOG ("failed assumption %d", first_failed);
Flags &f = flags (first_failed);
assert (!f.seen);
f.seen = true;
assert (f.failed & bign (first_failed));
analyzed.push_back (-first_failed);
clause.push_back (-first_failed);
}
} else {
// unsat_constraint
// The assumptions necessary to fail each literal in the constraint are
// collected.
for (auto lit : constraint) {
lit *= -1;
assert (lit != INT_MIN);
flags (lit).seen = true;
analyzed.push_back (lit);
}
}
{
// used for unsat_constraint lrat
vector<vector<int64_t>> constraint_chains;
vector<vector<int>> constraint_clauses;
vector<int> sum_constraints;
vector<int> econstraints;
for (auto &elit : external->constraint) {
int lit = external->e2i[abs (elit)];
if (elit < 0)
lit = -lit;
if (!lit)
continue;
Flags &f = flags (lit);
if (f.seen)
continue;
if (std::find (econstraints.begin (), econstraints.end (), elit) !=
econstraints.end ())
continue;
econstraints.push_back (elit);
}
// no LRAT do bfs as it was before
if (!lrat) {
size_t next = 0;
while (next < analyzed.size ()) {
const int lit = analyzed[next++];
assert (val (lit) > 0);
Var &v = var (lit);
if (!v.level)
continue;
if (v.reason == external_reason) {
v.reason = wrapped_learn_external_reason_clause (lit);
if (!v.reason) {
v.level = 0;
continue;
}
}
assert (v.reason != external_reason);
if (v.reason) {
assert (v.level);
LOG (v.reason, "analyze reason");
for (const auto &other : *v.reason) {
Flags &f = flags (other);
if (f.seen)
continue;
f.seen = true;
assert (val (other) < 0);
analyzed.push_back (-other);
}
} else {
assert (assumed (lit));
LOG ("failed assumption %d", lit);
clause.push_back (-lit);
Flags &f = flags (lit);
const unsigned bit = bign (lit);
assert (!(f.failed & bit));
f.failed |= bit;
}
}
clear_analyzed_literals ();
} else if (!unsat_constraint) { // LRAT for case (3)
assert (clause.size () == 1);
const int lit = clause[0];
Var &v = var (lit);
assert (v.reason);
if (v.reason == external_reason) { // does this even happen?
v.reason = wrapped_learn_external_reason_clause (lit);
}
assert (v.reason != external_reason);
if (v.reason)
assume_analyze_reason (lit, v.reason);
else {
int64_t id = unit_id (lit);
lrat_chain.push_back (id);
}
for (auto &lit : clause) {
Flags &f = flags (lit);
const unsigned bit = bign (-lit);
if (!(f.failed & bit))
f.failed |= bit;
}
clear_analyzed_literals ();
} else { // LRAT for unsat_constraint
assert (clause.empty ());
clear_analyzed_literals ();
for (auto lit : constraint) {
// make sure nothing gets marked failed twice
// also might shortcut the case where
// lrat_chain is empty because clause is tautological
assert (lit != INT_MIN);
assume_analyze_literal (lit);
vector<int64_t> empty;
vector<int> empty2;
constraint_chains.push_back (empty);
constraint_clauses.push_back (empty2);
for (auto ign : clause) {
constraint_clauses.back ().push_back (ign);
Flags &f = flags (ign);
const unsigned bit = bign (-ign);
if (!(f.failed & bit)) {
sum_constraints.push_back (ign);
assert (!(f.failed & bit));
f.failed |= bit;
}
}
clause.clear ();
clear_analyzed_literals ();
for (auto p : lrat_chain) {
constraint_chains.back ().push_back (p);
}
lrat_chain.clear ();
}
for (auto &lit : sum_constraints)
clause.push_back (lit);
}
clear_analyzed_literals ();
// Doing clause minimization here does not do anything because
// the clause already contains only one literal of each level
// and minimization can never reduce the number of levels
VERBOSE (1, "found %zd failed assumptions %.0f%%", clause.size (),
percent (clause.size (), assumptions.size ()));
// We do not actually need to learn this clause, since the conflict is
// forced already by some other clauses. There is also no bumping
// of variables nor clauses necessary. But we still want to check
// correctness of the claim that the determined subset of failing
// assumptions are a high-level core or equivalently their negations
// form a unit-implied clause.
//
if (!unsat_constraint) {
external->check_learned_clause ();
if (proof) {
vector<int> eclause;
for (auto &lit : clause)
eclause.push_back (externalize (lit));
proof->add_assumption_clause (++clause_id, eclause, lrat_chain);
conclusion.push_back (clause_id);
}
} else {
assert (!lrat || (constraint.size () == constraint_clauses.size () &&
constraint.size () == constraint_chains.size ()));
for (auto p = constraint.rbegin (); p != constraint.rend (); p++) {
const auto &lit = *p;
if (lrat) {
clause.clear ();
for (auto &ign : constraint_clauses.back ())
clause.push_back (ign);
constraint_clauses.pop_back ();
}
clause.push_back (-lit);
external->check_learned_clause ();
if (proof) {
if (lrat) {
for (auto p : constraint_chains.back ()) {
lrat_chain.push_back (p);
}
constraint_chains.pop_back ();
LOG (lrat_chain, "assume proof chain with constraints");
}
vector<int> eclause;
for (auto &lit : clause)
eclause.push_back (externalize (lit));
proof->add_assumption_clause (++clause_id, eclause, lrat_chain);
conclusion.push_back (clause_id);
lrat_chain.clear ();
}
clause.pop_back ();
}
if (proof) {
for (auto &elit : econstraints) {
if (lrat) {
unsigned eidx = (elit > 0) + 2u * (unsigned) abs (elit);
assert ((size_t) eidx < external->ext_units.size ());
const int64_t id = external->ext_units[eidx];
if (id) {
lrat_chain.push_back (id);
} else {
int lit = external->e2i[abs (elit)];
if (elit < 0)
lit = -lit;
int64_t id = unit_id (-lit);
lrat_chain.push_back (id);
}
}
proof->add_assumption_clause (++clause_id, -elit, lrat_chain);
conclusion.push_back (clause_id);
lrat_chain.clear ();
}
}
}
lrat_chain.clear ();
clause.clear ();
}
DONE:
STOP (analyze);
}
bool Internal::failed (int lit) {
if (!marked_failed) {
if (!conflict_id)
failing ();
marked_failed = true;
}
conclude_unsat ();
Flags &f = flags (lit);
const unsigned bit = bign (lit);
return (f.failed & bit) != 0;
}
void Internal::conclude_unsat () {
if (!proof || concluded)
return;
concluded = true;
if (!marked_failed) {
assert (conclusion.empty ());
if (!conflict_id)
failing ();
marked_failed = true;
}
ConclusionType con;
if (conflict_id)
con = CONFLICT;
else if (unsat_constraint)
con = CONSTRAINT;
else
con = ASSUMPTIONS;
proof->conclude_unsat (con, conclusion);
}
void Internal::reset_concluded () {
if (proof)
proof->reset_assumptions ();
if (concluded) {
LOG ("reset concluded");
concluded = false;
}
if (conflict_id) {
assert (conclusion.size () == 1);
return;
}
conclusion.clear ();
}
// Add the start of each incremental phase (leaving the state
// 'UNSATISFIABLE' actually) we reset all assumptions.
void Internal::reset_assumptions () {
for (const auto &lit : assumptions) {
Flags &f = flags (lit);
const unsigned char bit = bign (lit);
f.assumed &= ~bit;
f.failed &= ~bit;
melt (lit);
}
LOG ("cleared %zd assumptions", assumptions.size ());
assumptions.clear ();
marked_failed = true;
}
struct sort_assumptions_positive_rank {
Internal *internal;
// Decision level could be 'INT_MAX' and thus 'level + 1' could overflow.
// Therefore we carefully have to use 'unsigned' for levels below.
const unsigned max_level;
sort_assumptions_positive_rank (Internal *s)
: internal (s), max_level (s->level + 1u) {}
typedef uint64_t Type;
// Set assumptions first, then sorted by position on the trail
// unset literals are sorted by literal value.
Type operator() (const int &a) const {
const int val = internal->val (a);
const bool assigned = (val != 0);
const Var &v = internal->var (a);
uint64_t res = (assigned ? (unsigned) v.level : max_level);
res <<= 32;
res |= (assigned ? v.trail : abs (a));
return res;
}
};
struct sort_assumptions_smaller {
Internal *internal;
sort_assumptions_smaller (Internal *s) : internal (s) {}
bool operator() (const int &a, const int &b) const {
return sort_assumptions_positive_rank (internal) (a) <
sort_assumptions_positive_rank (internal) (b);
}
};
// Sort the assumptions by the current position on the trail and backtrack
// to the first place where the assumptions and the current trail differ.
void Internal::sort_and_reuse_assumptions () {
assert (opts.ilbassumptions);
if (assumptions.empty ())
return;
MSORT (opts.radixsortlim, assumptions.begin (), assumptions.end (),
sort_assumptions_positive_rank (this),
sort_assumptions_smaller (this));
unsigned max_level = 0;
// assumptions are sorted by level, with unset at the end
for (auto lit : assumptions) {
if (val (lit))
max_level = var (lit).level;
else
break;
}
const unsigned size = min (level + 1u, max_level + 1);
assert ((size_t) level == control.size () - 1);
LOG (assumptions, "sorted assumptions");
int target = 0;
for (unsigned i = 1, j = 0; i < size;) {
const Level &l = control[i];
const int lit = l.decision;
const int alit = assumptions[j];
const int lev = i;
target = lev;
if (val (alit) > 0 &&
var (alit).level < lev) { // we can ignore propagated assumptions
LOG ("ILB skipping propagation %d", alit);
++j;
continue;
}
if (!lit) { // skip fake decisions
target = lev - 1;
break;
}
++i, ++j;
assert (var (lit).level == lev);
if (l.decision == alit) {
continue;
}
target = lev - 1;
LOG ("first different literal %d on the trail and %d from the "
"assumptions",
lit, alit);
break;
}
if (target < level)
backtrack (target);
LOG ("assumptions allow for reuse of trail up to level %d", level);
// COVER (target > 1);
if ((size_t) level > assumptions.size ())
stats.assumptionsreused += assumptions.size ();
else
stats.assumptionsreused += level;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,34 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::init_averages () {
LOG ("initializing averages");
INIT_EMA (averages.current.jump, opts.emajump);
INIT_EMA (averages.current.level, opts.emalevel);
INIT_EMA (averages.current.size, opts.emasize);
INIT_EMA (averages.current.glue.fast, opts.emagluefast);
INIT_EMA (averages.current.glue.slow, opts.emaglueslow);
INIT_EMA (averages.current.decisions, opts.emadecisions);
INIT_EMA (averages.current.trail.fast, opts.ematrailfast);
INIT_EMA (averages.current.trail.slow, opts.ematrailslow);
assert (!averages.swapped);
}
void Internal::swap_averages () {
LOG ("saving current averages");
swap (averages.current, averages.saved);
if (!averages.swapped)
init_averages ();
else
LOG ("swapping in previously saved averages");
averages.swapped++;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,37 @@
#ifndef _averages_hpp_INCLUDED
#define _averages_hpp_INCLUDED
#include "ema.hpp" // alphabetically after 'averages.hpp'
namespace CaDiCaL {
struct Averages {
int64_t swapped;
struct {
struct {
EMA fast; // average fast (small window) moving glucose level
EMA slow; // average slow (large window) moving glucose level
} glue;
struct {
EMA fast; // average fast (small window) moving trail level
EMA slow; // average slow (large window) moving trail level
} trail;
EMA decisions;
EMA size; // average learned clause size
EMA jump; // average (potential non-chronological) back-jump level
EMA level; // average back track level after conflict
} current, saved;
Averages () : swapped (0) {}
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,173 @@
#include "internal.hpp"
namespace CaDiCaL {
// The global assignment stack can only be (partially) reset through
// 'backtrack' which is the only function using 'unassign' (inlined and thus
// local to this file). It turns out that 'unassign' does not need a
// specialization for 'probe' nor 'vivify' and thus it is shared.
inline void Internal::unassign (int lit) {
assert (val (lit) > 0);
set_val (lit, 0);
int idx = vidx (lit);
LOG ("unassign %d @ %d", lit, var (idx).level);
num_assigned--;
// In the standard EVSIDS variable decision heuristic of MiniSAT, we need
// to push variables which become unassigned back to the heap.
//
if (!scores.contains (idx))
scores.push_back (idx);
// For VMTF we need to update the 'queue.unassigned' pointer in case this
// variable sits after the variable to which 'queue.unassigned' currently
// points. See our SAT'15 paper for more details on this aspect.
//
if (queue.bumped < btab[idx])
update_queue_unassigned (idx);
}
/*------------------------------------------------------------------------*/
// Update the current target maximum assignment and also the very best
// assignment. Whether a trail produces a conflict is determined during
// propagation. Thus that all functions in the 'search' loop after
// propagation can assume that 'no_conflict_until' is valid. If a conflict
// is found then the trail before the last decision is used (see the end of
// 'propagate'). During backtracking we can then save this largest
// propagation conflict free assignment. It is saved as both 'target'
// assignment for picking decisions in 'stable' mode and if it is the
// largest ever such assignment also as 'best' assignment. This 'best'
// assignment can then be used in future stable decisions after the next
// 'rephase_best' overwrites saved phases with it.
void Internal::update_target_and_best () {
bool reset = (rephased && stats.conflicts > last.rephase.conflicts);
if (reset) {
target_assigned = 0;
if (rephased == 'B')
best_assigned = 0; // update it again
}
if (no_conflict_until > target_assigned) {
copy_phases (phases.target);
target_assigned = no_conflict_until;
LOG ("new target trail level %zu", target_assigned);
}
if (no_conflict_until > best_assigned) {
copy_phases (phases.best);
best_assigned = no_conflict_until;
LOG ("new best trail level %zu", best_assigned);
}
if (reset) {
report (rephased);
rephased = 0;
}
}
/*------------------------------------------------------------------------*/
void Internal::backtrack (int new_level) {
assert (new_level <= level);
if (new_level == level)
return;
update_target_and_best ();
backtrack_without_updating_phases (new_level);
}
void Internal::backtrack_without_updating_phases (int new_level) {
assert (new_level <= level);
if (new_level == level)
return;
stats.backtracks++;
assert (num_assigned == trail.size ());
const size_t assigned = control[new_level + 1].trail;
LOG ("backtracking to decision level %d with decision %d and trail %zd",
new_level, control[new_level].decision, assigned);
const size_t end_of_trail = trail.size ();
size_t i = assigned, j = i;
#ifdef LOGGING
int unassigned = 0;
#endif
int reassigned = 0;
notify_backtrack (new_level);
if (external_prop && !external_prop_is_lazy && !private_steps &&
notified > assigned) {
LOG ("external propagator is notified about some unassignments (trail: "
"%zd, notified: %zd).",
trail.size (), notified);
notified = assigned;
}
while (i < end_of_trail) {
int lit = trail[i++];
Var &v = var (lit);
if (v.level > new_level) {
unassign (lit);
#ifdef LOGGING
unassigned++;
#endif
} else {
// This is the essence of the SAT'18 paper on chronological
// backtracking. It is possible to just keep out-of-order assigned
// literals on the trail without breaking the solver (after some
// modifications to 'analyze' - see 'opts.chrono' guarded code there).
assert (opts.chrono || external_prop || did_external_prop);
#ifdef LOGGING
if (!v.level)
LOG ("reassign %d @ 0 unit clause %d", lit, lit);
else
LOG (v.reason, "reassign %d @ %d", lit, v.level);
#endif
trail[j] = lit;
v.trail = j++;
reassigned++;
}
}
trail.resize (j);
LOG ("unassigned %d literals %.0f%%", unassigned,
percent (unassigned, unassigned + reassigned));
LOG ("reassigned %d literals %.0f%%", reassigned,
percent (reassigned, unassigned + reassigned));
if (propagated > assigned)
propagated = assigned;
if (propagated2 > assigned)
propagated2 = assigned;
if (no_conflict_until > assigned)
no_conflict_until = assigned;
propergated = 0; // Always go back to root-level.
assert (notified <= assigned + reassigned);
if (reassigned) {
notify_assignments ();
}
control.resize (new_level + 1);
level = new_level;
if (tainted_literal) {
assert (opts.ilb);
if (!val (tainted_literal)) {
tainted_literal = 0;
}
}
assert (num_assigned == trail.size ());
}
} // namespace CaDiCaL

View File

@ -0,0 +1,231 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Provide eager backward subsumption for resolved clauses.
// The eliminator maintains a queue of clauses that are new and have to be
// checked to subsume or strengthen other (longer or same size) clauses.
void Eliminator::enqueue (Clause *c) {
if (!internal->opts.elimbackward)
return;
if (c->enqueued)
return;
LOG (c, "backward enqueue");
backward.push (c);
c->enqueued = true;
}
Clause *Eliminator::dequeue () {
if (backward.empty ())
return 0;
Clause *res = backward.front ();
backward.pop ();
assert (res->enqueued);
res->enqueued = false;
LOG (res, "backward dequeue");
return res;
}
Eliminator::~Eliminator () {
while (dequeue ())
;
}
/*------------------------------------------------------------------------*/
void Internal::elim_backward_clause (Eliminator &eliminator, Clause *c) {
assert (opts.elimbackward);
assert (!c->redundant);
if (c->garbage)
return;
LOG (c, "attempting backward subsumption and strengthening with");
size_t len = UINT_MAX;
unsigned size = 0;
int best = 0;
bool satisfied = false;
assert (mini_chain.empty ());
for (const auto &lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) {
satisfied = true;
break;
}
if (tmp < 0)
continue;
size_t l = occs (lit).size ();
LOG ("literal %d occurs %zd times", lit, l);
if (l < len)
best = lit, len = l;
mark (lit);
size++;
}
if (satisfied) {
LOG ("clause actually already satisfied");
elim_update_removed_clause (eliminator, c);
mark_garbage (c);
} else if (len > (size_t) opts.elimocclim) {
LOG ("skipping backward subsumption due to too many occurrences");
} else {
assert (len);
LOG ("literal %d has smallest number of occurrences %zd", best, len);
LOG ("marked %d literals in clause of size %d", size, c->size);
for (auto &d : occs (best)) {
if (d == c)
continue;
if (d->garbage)
continue;
if ((unsigned) d->size < size)
continue;
int negated = 0;
unsigned found = 0;
satisfied = false;
for (const auto &lit : *d) {
signed char tmp = val (lit);
if (tmp > 0) {
satisfied = true;
break;
}
if (tmp < 0)
continue;
tmp = marked (lit);
if (!tmp)
continue;
if (tmp < 0) {
if (negated) {
size = UINT_MAX;
break;
} else
negated = lit;
}
if (++found == size)
break;
}
if (satisfied) {
LOG (d, "found satisfied clause");
elim_update_removed_clause (eliminator, d);
mark_garbage (d);
} else if (found == size) {
if (!negated) {
LOG (d, "found subsumed clause");
elim_update_removed_clause (eliminator, d);
mark_garbage (d);
stats.subsumed++;
stats.elimbwsub++;
} else {
int unit = 0;
assert (minimize_chain.empty ());
assert (analyzed.empty ());
assert (lrat_chain.empty ());
// figure out wether we strengthen c or get a new unit
for (const auto &lit : *d) {
const signed char tmp = val (lit);
if (tmp < 0) {
if (!lrat)
continue;
Flags &f = flags (lit);
assert (!f.seen);
if (f.seen)
continue;
f.seen = true;
analyzed.push_back (lit);
continue;
}
if (tmp > 0) {
satisfied = true;
break;
}
if (lit == negated)
continue;
if (unit) {
unit = INT_MIN;
continue; // needed to guarantee d is not satsified
} else
unit = lit;
}
if (lrat && !satisfied) {
// if we found a unit we need to add all unit ids from
// {c\d}U{d\c} otherwise just the unit ids from {c\d}
for (const auto &lit : *c) {
const signed char tmp = val (lit);
assert (tmp <= 0);
if (tmp >= 0)
continue;
Flags &f = flags (lit);
if (f.seen && unit && unit == INT_MIN) {
f.seen = false;
continue;
} else if (!f.seen) {
f.seen = true;
analyzed.push_back (lit);
}
}
if (unit == INT_MIN) { // we do not need units from {d\c}
for (const auto &lit : *d) {
flags (lit).seen = false;
}
}
for (const auto &lit : analyzed) {
Flags &f = flags (lit);
if (!f.seen) {
f.seen = true;
continue;
}
int64_t id = unit_id (-lit);
lrat_chain.push_back (id);
}
clear_analyzed_literals ();
lrat_chain.push_back (d->id);
lrat_chain.push_back (c->id);
} else if (lrat)
clear_analyzed_literals ();
if (satisfied) {
assert (lrat_chain.empty ());
mark_garbage (d);
elim_update_removed_clause (eliminator, d);
} else if (unit && unit != INT_MIN) {
assert (unit);
LOG (d, "unit %d through hyper unary resolution with", unit);
assign_unit (unit);
elim_propagate (eliminator, unit);
lrat_chain.clear ();
break;
} else if (occs (negated).size () <= (size_t) opts.elimocclim) {
strengthen_clause (d, negated);
remove_occs (occs (negated), d);
elim_update_removed_lit (eliminator, negated);
stats.elimbwstr++;
assert (negated != best);
eliminator.enqueue (d);
}
lrat_chain.clear ();
}
}
}
}
mini_chain.clear ();
unmark (c);
}
/*------------------------------------------------------------------------*/
void Internal::elim_backward_clauses (Eliminator &eliminator) {
if (!opts.elimbackward) {
assert (eliminator.backward.empty ());
return;
}
START (backward);
LOG ("attempting backward subsumption and strengthening with %zd clauses",
eliminator.backward.size ());
Clause *c;
while (!unsat && (c = eliminator.dequeue ()))
elim_backward_clause (eliminator, c);
STOP (backward);
}
/*------------------------------------------------------------------------*/
} // namespace CaDiCaL

22
src/sat/cadical/bins.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Binary implication graph lists.
void Internal::init_bins () {
assert (big.empty ());
if (big.size () < 2 * vsize)
big.resize (2 * vsize, Bins ());
LOG ("initialized binary implication graph");
}
void Internal::reset_bins () {
assert (!big.empty ());
erase_vector (big);
LOG ("reset binary implication graph");
}
} // namespace CaDiCaL

22
src/sat/cadical/bins.hpp Normal file
View File

@ -0,0 +1,22 @@
#ifndef _bins_hpp_INCLUDED
#define _bins_hpp_INCLUDED
#include "util.hpp" // Alphabetically after 'bins'.
namespace CaDiCaL {
using namespace std;
struct Bin {
int lit;
int64_t id;
};
typedef vector<Bin> Bins;
inline void shrink_bins (Bins &bs) { shrink_vector (bs); }
inline void erase_bins (Bins &bs) { erase_vector (bs); }
} // namespace CaDiCaL
#endif

824
src/sat/cadical/block.cpp Normal file
View File

@ -0,0 +1,824 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This implements an inprocessing version of blocked clause elimination and
// is assumed to be triggered just before bounded variable elimination. It
// has a separate 'block' flag while variable elimination uses 'elim'.
// Thus it only tries to block clauses on a literal which was removed in an
// irredundant clause in negated form before and has not been tried to use
// as blocking literal since then.
/*------------------------------------------------------------------------*/
inline bool block_more_occs_size::operator() (unsigned a, unsigned b) {
size_t s = internal->noccs (-internal->u2i (a));
size_t t = internal->noccs (-internal->u2i (b));
if (s > t)
return true;
if (s < t)
return false;
s = internal->noccs (internal->u2i (a));
t = internal->noccs (internal->u2i (b));
if (s > t)
return true;
if (s < t)
return false;
return a > b;
}
/*------------------------------------------------------------------------*/
// Determine whether 'c' is blocked on 'lit', by first marking all its
// literals and then checking all resolvents with negative clauses (with
// '-lit') are tautological. We use a move-to-front scheme for both the
// occurrence list of negative clauses (with '-lit') and then for literals
// within each such clause. The clause move-to-front scheme has the goal to
// find non-tautological clauses faster in the future, while the literal
// move-to-front scheme has the goal to faster find the matching literal,
// which makes the resolvent tautological (again in the future).
bool Internal::is_blocked_clause (Clause *c, int lit) {
LOG (c, "trying to block on %d", lit);
assert (c->size >= opts.blockminclslim);
assert (c->size <= opts.blockmaxclslim);
assert (active (lit));
assert (!val (lit));
assert (!c->garbage);
assert (!c->redundant);
assert (!level);
mark (c); // First mark all literals in 'c'.
Occs &os = occs (-lit);
LOG ("resolving against at most %zd clauses with %d", os.size (), -lit);
bool res = true; // Result is true if all resolvents tautological.
// Can not use 'auto' here since we update 'os' during traversal.
//
const auto end_of_os = os.end ();
auto i = os.begin ();
Clause *prev_d = 0; // Previous non-tautological clause.
for (; i != end_of_os; i++) {
// Move the first clause with non-tautological resolvent to the front of
// the occurrence list to improve finding it faster later.
//
Clause *d = *i;
assert (!d->garbage);
assert (!d->redundant);
assert (d->size <= opts.blockmaxclslim);
*i = prev_d; // Move previous non-tautological clause
prev_d = d; // backwards but remember clause at this position.
LOG (d, "resolving on %d against", lit);
stats.blockres++;
int prev_other = 0; // Previous non-tautological literal.
// No 'auto' since we update literals of 'd' during traversal.
//
const const_literal_iterator end_of_d = d->end ();
literal_iterator l;
for (l = d->begin (); l != end_of_d; l++) {
// Same move-to-front mechanism for literals within a clause. It
// moves the first negatively marked literal to the front to find it
// faster in the future.
//
const int other = *l;
*l = prev_other;
prev_other = other;
if (other == -lit)
continue;
assert (other != lit);
assert (active (other));
assert (!val (other));
if (marked (other) < 0) {
LOG ("found tautological literal %d", other);
d->literals[0] = other; // Move to front of 'd'.
break;
}
}
if (l == end_of_d) {
LOG ("no tautological literal found");
//
// Since we did not find a tautological literal we restore the old
// order of literals in the clause.
//
const const_literal_iterator begin_of_d = d->begin ();
while (l-- != begin_of_d) {
const int other = *l;
*l = prev_other;
prev_other = other;
}
res = false; // Now 'd' is a witness that 'c' is not blocked.
os[0] = d; // Move it to the front of the occurrence list.
break;
}
}
unmark (c); // ... all literals of the candidate clause.
// If all resolvents are tautological and thus the clause is blocked we
// restore the old order of clauses in the occurrence list of '-lit'.
//
if (res) {
assert (i == end_of_os);
const auto boc = os.begin ();
while (i != boc) {
Clause *d = *--i;
*i = prev_d;
prev_d = d;
}
}
return res;
}
/*------------------------------------------------------------------------*/
void Internal::block_schedule (Blocker &blocker) {
// Set skip flags for all literals in too large clauses.
//
for (const auto &c : clauses) {
if (c->garbage)
continue;
if (c->redundant)
continue;
if (c->size <= opts.blockmaxclslim)
continue;
for (const auto &lit : *c)
mark_skip (-lit);
}
// Connect all literal occurrences in irredundant clauses.
//
for (const auto &c : clauses) {
if (c->garbage)
continue;
if (c->redundant)
continue;
for (const auto &lit : *c) {
assert (active (lit));
assert (!val (lit));
occs (lit).push_back (c);
}
}
// We establish the invariant that 'noccs' gives the number of actual
// occurrences of 'lit' in non-garbage clauses, while 'occs' might still
// refer to garbage clauses, thus 'noccs (lit) <= occs (lit).size ()'. It
// is expensive to remove references to garbage clauses from 'occs' during
// blocked clause elimination, but decrementing 'noccs' is cheap.
for (auto lit : lits) {
if (!active (lit))
continue;
assert (!val (lit));
Occs &os = occs (lit);
noccs (lit) = os.size ();
}
// Now we fill the schedule (priority queue) of candidate literals to be
// tried as blocking literals. It is probably slightly faster to do this
// in one go after all occurrences have been determined, instead of
// filling the priority queue during pushing occurrences. Filling the
// schedule can not be fused with the previous loop (easily) since we
// first have to initialize 'noccs' for both 'lit' and '-lit'.
#ifndef QUIET
int skipped = 0;
#endif
for (auto idx : vars) {
if (!active (idx))
continue;
if (frozen (idx)) {
#ifndef QUIET
skipped += 2;
#endif
continue;
}
assert (!val (idx));
for (int sign = -1; sign <= 1; sign += 2) {
const int lit = sign * idx;
if (marked_skip (lit)) {
#ifndef QUIET
skipped++;
#endif
continue;
}
if (!marked_block (lit))
continue;
unmark_block (lit);
LOG ("scheduling %d with %" PRId64 " positive and %" PRId64
" negative occurrences",
lit, noccs (lit), noccs (-lit));
blocker.schedule.push_back (vlit (lit));
}
}
PHASE ("block", stats.blockings,
"scheduled %zd candidate literals %.2f%% (%d skipped %.2f%%)",
blocker.schedule.size (),
percent (blocker.schedule.size (), 2.0 * active ()), skipped,
percent (skipped, 2.0 * active ()));
}
/*------------------------------------------------------------------------*/
// A literal is pure if it only occurs positive. Then all clauses in which
// it occurs are blocked on it. This special case can be implemented faster
// than trying to block literals with at least one negative occurrence and
// is thus handled separately. It also allows to avoid pushing blocked
// clauses onto the extension stack.
void Internal::block_pure_literal (Blocker &blocker, int lit) {
if (frozen (lit))
return;
assert (active (lit));
Occs &pos = occs (lit);
Occs &nos = occs (-lit);
assert (!noccs (-lit));
#ifndef NDEBUG
for (const auto &c : nos)
assert (c->garbage);
#endif
stats.blockpurelits++;
LOG ("found pure literal %d", lit);
#ifdef LOGGING
int64_t pured = 0;
#endif
for (const auto &c : pos) {
if (c->garbage)
continue;
assert (!c->redundant);
LOG (c, "pure literal %d in", lit);
blocker.reschedule.push_back (c);
if (proof) {
proof->weaken_minus (c);
}
external->push_clause_on_extension_stack (c, lit);
stats.blockpured++;
mark_garbage (c);
#ifdef LOGGING
pured++;
#endif
}
erase_vector (pos);
erase_vector (nos);
mark_pure (lit);
stats.blockpured++;
LOG ("blocking %" PRId64 " clauses on pure literal %d", pured, lit);
}
/*------------------------------------------------------------------------*/
// If there is only one negative clause with '-lit' it is faster to mark it
// instead of marking all the positive clauses with 'lit' one after the
// other and then resolving against the negative clause.
void Internal::block_literal_with_one_negative_occ (Blocker &blocker,
int lit) {
assert (active (lit));
assert (!frozen (lit));
assert (noccs (lit) > 0);
assert (noccs (-lit) == 1);
Occs &nos = occs (-lit);
assert (nos.size () >= 1);
Clause *d = 0;
for (const auto &c : nos) {
if (c->garbage)
continue;
assert (!d);
d = c;
#ifndef NDEBUG
break;
#endif
}
assert (d);
nos.resize (1);
nos[0] = d;
if (d && d->size > opts.blockmaxclslim) {
LOG (d, "skipped common antecedent");
return;
}
assert (!d->garbage);
assert (!d->redundant);
assert (d->size <= opts.blockmaxclslim);
LOG (d, "common %d antecedent", lit);
mark (d);
int64_t blocked = 0;
#ifdef LOGGING
int64_t skipped = 0;
#endif
Occs &pos = occs (lit);
// Again no 'auto' since 'pos' is update during traversal.
//
const auto eop = pos.end ();
auto j = pos.begin (), i = j;
for (; i != eop; i++) {
Clause *c = *j++ = *i;
if (c->garbage) {
j--;
continue;
}
if (c->size > opts.blockmaxclslim) {
#ifdef LOGGING
skipped++;
#endif
continue;
}
if (c->size < opts.blockminclslim) {
#ifdef LOGGING
skipped++;
#endif
continue;
}
LOG (c, "trying to block on %d", lit);
// We use the same literal move-to-front strategy as in
// 'is_blocked_clause'. See there for more explanations.
int prev_other = 0; // Previous non-tautological literal.
// No 'auto' since literals of 'c' are updated during traversal.
//
const const_literal_iterator end_of_c = c->end ();
literal_iterator l;
for (l = c->begin (); l != end_of_c; l++) {
const int other = *l;
*l = prev_other;
prev_other = other;
if (other == lit)
continue;
assert (other != -lit);
assert (active (other));
assert (!val (other));
if (marked (other) < 0) {
LOG ("found tautological literal %d", other);
c->literals[0] = other; // Move to front of 'c'.
break;
}
}
if (l == end_of_c) {
LOG ("no tautological literal found");
// Restore old literal order in the clause because.
const const_literal_iterator begin_of_c = c->begin ();
while (l-- != begin_of_c) {
const int other = *l;
*l = prev_other;
prev_other = other;
}
continue; // ... with next candidate 'c' in 'pos'.
}
blocked++;
LOG (c, "blocked");
if (proof) {
proof->weaken_minus (c);
}
external->push_clause_on_extension_stack (c, lit);
blocker.reschedule.push_back (c);
mark_garbage (c);
j--;
}
if (j == pos.begin ())
erase_vector (pos);
else
pos.resize (j - pos.begin ());
stats.blocked += blocked;
LOG ("blocked %" PRId64 " clauses on %d (skipped %" PRId64 ")", blocked,
lit, skipped);
unmark (d);
}
/*------------------------------------------------------------------------*/
// Determine the set of candidate clauses with 'lit', which are checked to
// be blocked by 'lit'. Filter out too large and small clauses and which do
// not have any negated other literal in any of the clauses with '-lit'.
size_t Internal::block_candidates (Blocker &blocker, int lit) {
assert (blocker.candidates.empty ());
Occs &pos = occs (lit); // Positive occurrences of 'lit'.
Occs &nos = occs (-lit);
assert ((size_t) noccs (lit) <= pos.size ());
assert ((size_t) noccs (-lit) == nos.size ()); // Already flushed.
// Mark all literals in clauses with '-lit'. Note that 'mark2' uses
// separate bits for 'lit' and '-lit'.
//
for (const auto &c : nos)
mark2 (c);
const auto eop = pos.end ();
auto j = pos.begin (), i = j;
for (; i != eop; i++) {
Clause *c = *j++ = *i;
if (c->garbage) {
j--;
continue;
}
assert (!c->redundant);
if (c->size > opts.blockmaxclslim)
continue;
if (c->size < opts.blockminclslim)
continue;
const const_literal_iterator eoc = c->end ();
const_literal_iterator l;
for (l = c->begin (); l != eoc; l++) {
const int other = *l;
if (other == lit)
continue;
assert (other != -lit);
assert (active (other));
assert (!val (other));
if (marked2 (-other))
break;
}
if (l != eoc)
blocker.candidates.push_back (c);
}
if (j == pos.begin ())
erase_vector (pos);
else
pos.resize (j - pos.begin ());
assert (pos.size () == (size_t) noccs (lit)); // Now also flushed.
for (const auto &c : nos)
unmark (c);
return blocker.candidates.size ();
}
/*------------------------------------------------------------------------*/
// Try to find a clause with '-lit' which does not have any literal in
// clauses with 'lit'. If such a clause exists no candidate clause can be
// blocked on 'lit' since all candidates would produce a non-tautological
// resolvent with that clause.
Clause *Internal::block_impossible (Blocker &blocker, int lit) {
assert (noccs (-lit) > 1);
assert (blocker.candidates.size () > 1);
for (const auto &c : blocker.candidates)
mark2 (c);
Occs &nos = occs (-lit);
Clause *res = 0;
for (const auto &c : nos) {
assert (!c->garbage);
assert (!c->redundant);
assert (c->size <= opts.blockmaxclslim);
const const_literal_iterator eoc = c->end ();
const_literal_iterator l;
for (l = c->begin (); l != eoc; l++) {
const int other = *l;
if (other == -lit)
continue;
assert (other != lit);
assert (active (other));
assert (!val (other));
if (marked2 (-other))
break;
}
if (l == eoc)
res = c;
}
for (const auto &c : blocker.candidates)
unmark (c);
if (res) {
LOG (res, "common non-tautological resolvent producing");
blocker.candidates.clear ();
}
return res;
}
/*------------------------------------------------------------------------*/
// In the general case we have at least two negative occurrences.
void Internal::block_literal_with_at_least_two_negative_occs (
Blocker &blocker, int lit) {
assert (active (lit));
assert (!frozen (lit));
assert (noccs (lit) > 0);
assert (noccs (-lit) > 1);
Occs &nos = occs (-lit);
assert ((size_t) noccs (-lit) <= nos.size ());
int max_size = 0;
// Flush all garbage clauses in occurrence list 'nos' of '-lit' and
// determine the maximum size of negative clauses (with '-lit').
//
const auto eon = nos.end ();
auto j = nos.begin (), i = j;
for (; i != eon; i++) {
Clause *c = *j++ = *i;
if (c->garbage)
j--;
else if (c->size > max_size)
max_size = c->size;
}
if (j == nos.begin ())
erase_vector (nos);
else
nos.resize (j - nos.begin ());
assert (nos.size () == (size_t) noccs (-lit));
assert (nos.size () > 1);
// If the maximum size of a negative clause (with '-lit') exceeds the
// maximum clause size limit ignore this candidate literal.
//
if (max_size > opts.blockmaxclslim) {
LOG ("maximum size %d of clauses with %d exceeds clause size limit %d",
max_size, -lit, opts.blockmaxclslim);
return;
}
LOG ("maximum size %d of clauses with %d", max_size, -lit);
// We filter candidate clauses with positive occurrence of 'lit' in
// 'blocker.candidates' and return if no candidate clause remains.
// Candidates should be small enough and should have at least one literal
// which occurs negated in one of the clauses with '-lit'.
//
size_t candidates = block_candidates (blocker, lit);
if (!candidates) {
LOG ("no candidate clauses found");
return;
}
LOG ("found %zd candidate clauses", candidates);
// We further search for a clause with '-lit' that has no literal
// negated in any of the candidate clauses (except 'lit'). If such a
// clause exists, we know that none of the candidates is blocked.
//
if (candidates > 1 && block_impossible (blocker, lit)) {
LOG ("impossible to block any candidate clause on %d", lit);
assert (blocker.candidates.empty ());
return;
}
LOG ("trying to block %zd clauses out of %" PRId64 " with literal %d",
candidates, noccs (lit), lit);
int64_t blocked = 0;
// Go over all remaining candidates and try to block them on 'lit'.
//
for (const auto &c : blocker.candidates) {
assert (!c->garbage);
assert (!c->redundant);
if (!is_blocked_clause (c, lit))
continue;
blocked++;
LOG (c, "blocked");
if (proof) {
proof->weaken_minus (c);
}
external->push_clause_on_extension_stack (c, lit);
blocker.reschedule.push_back (c);
mark_garbage (c);
}
LOG ("blocked %" PRId64
" clauses on %d out of %zd candidates in %zd occurrences",
blocked, lit, blocker.candidates.size (), occs (lit).size ());
blocker.candidates.clear ();
stats.blocked += blocked;
if (blocked)
flush_occs (lit);
}
/*------------------------------------------------------------------------*/
// Reschedule literals in a clause (except 'lit') which was blocked.
void Internal::block_reschedule_clause (Blocker &blocker, int lit,
Clause *c) {
#ifdef NDEBUG
(void) lit;
#endif
assert (c->garbage);
for (const auto &other : *c) {
int64_t &n = noccs (other);
assert (n > 0);
n--;
LOG ("updating %d with %" PRId64 " positive and %" PRId64
" negative occurrences",
other, noccs (other), noccs (-other));
if (blocker.schedule.contains (vlit (-other)))
blocker.schedule.update (vlit (-other));
else if (active (other) && !frozen (other) && !marked_skip (-other)) {
LOG ("rescheduling to block clauses on %d", -other);
blocker.schedule.push_back (vlit (-other));
}
if (blocker.schedule.contains (vlit (other))) {
assert (other != lit);
blocker.schedule.update (vlit (other));
}
}
}
// Reschedule all literals in clauses blocked by 'lit' (except 'lit').
void Internal::block_reschedule (Blocker &blocker, int lit) {
while (!blocker.reschedule.empty ()) {
Clause *c = blocker.reschedule.back ();
blocker.reschedule.pop_back ();
block_reschedule_clause (blocker, lit, c);
}
}
/*------------------------------------------------------------------------*/
void Internal::block_literal (Blocker &blocker, int lit) {
assert (!marked_skip (lit));
if (!active (lit))
return; // Pure literal '-lit'.
if (frozen (lit))
return;
assert (!val (lit));
// If the maximum number of a negative clauses (with '-lit') exceeds the
// occurrence limit ignore this candidate literal.
//
if (noccs (-lit) > opts.blockocclim)
return;
LOG ("blocking literal candidate %d "
"with %" PRId64 " positive and %" PRId64 " negative occurrences",
lit, noccs (lit), noccs (-lit));
stats.blockcands++;
assert (blocker.reschedule.empty ());
assert (blocker.candidates.empty ());
if (!noccs (-lit))
block_pure_literal (blocker, lit);
else if (!noccs (lit)) {
// Rare situation, where the clause length limit was hit for 'lit' and
// '-lit' is skipped and then it becomes pure. Can be ignored. We also
// so it once happening for a 'elimboundmin=-1' and zero positive and
// one negative occurrence.
} else if (noccs (-lit) == 1)
block_literal_with_one_negative_occ (blocker, lit);
else
block_literal_with_at_least_two_negative_occs (blocker, lit);
// Done with blocked clause elimination on this literal and we do not
// have to try blocked clause elimination on it again until irredundant
// clauses with its negation are removed.
//
assert (!frozen (lit)); // just to be sure ...
unmark_block (lit);
}
/*------------------------------------------------------------------------*/
bool Internal::block () {
if (!opts.block)
return false;
if (unsat)
return false;
if (!stats.current.irredundant)
return false;
if (terminated_asynchronously ())
return false;
if (propagated < trail.size ()) {
LOG ("need to propagate %zd units first", trail.size () - propagated);
init_watches ();
connect_watches ();
if (!propagate ()) {
LOG ("propagating units results in empty clause");
learn_empty_clause ();
assert (unsat);
}
clear_watches ();
reset_watches ();
if (unsat)
return false;
}
START_SIMPLIFIER (block, BLOCK);
stats.blockings++;
LOG ("block-%" PRId64 "", stats.blockings);
assert (!level);
assert (!watching ());
assert (!occurring ());
mark_satisfied_clauses_as_garbage ();
init_occs (); // Occurrence lists for all literals.
init_noccs (); // Number of occurrences to avoid flushing garbage clauses.
Blocker blocker (this);
block_schedule (blocker);
int64_t blocked = stats.blocked;
int64_t resolutions = stats.blockres;
int64_t purelits = stats.blockpurelits;
int64_t pured = stats.blockpured;
while (!terminated_asynchronously () && !blocker.schedule.empty ()) {
int lit = u2i (blocker.schedule.front ());
blocker.schedule.pop_front ();
block_literal (blocker, lit);
block_reschedule (blocker, lit);
}
blocker.erase ();
reset_noccs ();
reset_occs ();
resolutions = stats.blockres - resolutions;
blocked = stats.blocked - blocked;
PHASE ("block", stats.blockings,
"blocked %" PRId64 " clauses in %" PRId64 " resolutions", blocked,
resolutions);
pured = stats.blockpured - pured;
purelits = stats.blockpurelits - purelits;
if (pured)
mark_redundant_clauses_with_eliminated_variables_as_garbage ();
if (purelits)
PHASE ("block", stats.blockings,
"found %" PRId64 " pure literals in %" PRId64 " clauses",
purelits, pured);
else
PHASE ("block", stats.blockings, "no pure literals found");
report ('b', !opts.reportall && !blocked);
STOP_SIMPLIFIER (block, BLOCK);
return blocked;
}
} // namespace CaDiCaL

37
src/sat/cadical/block.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef _block_hpp_INCLUDED
#define _block_hpp_INCLUDED
#include "heap.hpp" // Alphabetically after 'block.hpp'.
namespace CaDiCaL {
struct Internal;
struct block_more_occs_size {
Internal *internal;
block_more_occs_size (Internal *i) : internal (i) {}
bool operator() (unsigned a, unsigned b);
};
typedef heap<block_more_occs_size> BlockSchedule;
class Blocker {
friend struct Internal;
vector<struct Clause *> candidates;
vector<struct Clause *> reschedule;
BlockSchedule schedule;
Blocker (Internal *i) : schedule (block_more_occs_size (i)) {}
void erase () {
erase_vector (candidates);
erase_vector (reschedule);
schedule.erase ();
}
};
} // namespace CaDiCaL
#endif

1342
src/sat/cadical/cadical.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,196 @@
#include "cadical.hpp"
#include <cstdlib>
#include <cstring>
namespace CaDiCaL {
struct Wrapper : Learner, Terminator {
Solver *solver;
struct {
void *state;
int (*function) (void *);
} terminator;
struct {
void *state;
int max_length;
int *begin_clause, *end_clause, *capacity_clause;
void (*function) (void *, int *);
} learner;
bool terminate () {
if (!terminator.function)
return false;
return terminator.function (terminator.state);
}
bool learning (int size) {
if (!learner.function)
return false;
return size <= learner.max_length;
}
void learn (int lit) {
if (learner.end_clause == learner.capacity_clause) {
size_t count = learner.end_clause - learner.begin_clause;
size_t size = count ? 2 * count : 1;
learner.begin_clause =
(int *) realloc (learner.begin_clause, size * sizeof (int));
learner.end_clause = learner.begin_clause + count;
learner.capacity_clause = learner.begin_clause + size;
}
*learner.end_clause++ = lit;
if (lit)
return;
learner.function (learner.state, learner.begin_clause);
learner.end_clause = learner.begin_clause;
}
Wrapper () : solver (new Solver ()) {
memset (&terminator, 0, sizeof terminator);
memset (&learner, 0, sizeof learner);
}
~Wrapper () {
terminator.function = 0;
if (learner.begin_clause)
free (learner.begin_clause);
delete solver;
}
};
} // namespace CaDiCaL
using namespace CaDiCaL;
extern "C" {
#include "ccadical.h"
const char *ccadical_signature (void) { return Solver::signature (); }
CCaDiCaL *ccadical_init (void) { return (CCaDiCaL *) new Wrapper (); }
void ccadical_release (CCaDiCaL *wrapper) { delete (Wrapper *) wrapper; }
void ccadical_constrain (CCaDiCaL *wrapper, int lit) {
((Wrapper *) wrapper)->solver->constrain (lit);
}
int ccadical_constraint_failed (CCaDiCaL *wrapper) {
return ((Wrapper *) wrapper)->solver->constraint_failed ();
}
void ccadical_set_option (CCaDiCaL *wrapper, const char *name, int val) {
((Wrapper *) wrapper)->solver->set (name, val);
}
void ccadical_limit (CCaDiCaL *wrapper, const char *name, int val) {
((Wrapper *) wrapper)->solver->limit (name, val);
}
int ccadical_get_option (CCaDiCaL *wrapper, const char *name) {
return ((Wrapper *) wrapper)->solver->get (name);
}
void ccadical_add (CCaDiCaL *wrapper, int lit) {
((Wrapper *) wrapper)->solver->add (lit);
}
void ccadical_assume (CCaDiCaL *wrapper, int lit) {
((Wrapper *) wrapper)->solver->assume (lit);
}
int ccadical_solve (CCaDiCaL *wrapper) {
return ((Wrapper *) wrapper)->solver->solve ();
}
int ccadical_simplify (CCaDiCaL *wrapper) {
return ((Wrapper *) wrapper)->solver->simplify ();
}
int ccadical_val (CCaDiCaL *wrapper, int lit) {
return ((Wrapper *) wrapper)->solver->val (lit);
}
int ccadical_failed (CCaDiCaL *wrapper, int lit) {
return ((Wrapper *) wrapper)->solver->failed (lit);
}
void ccadical_print_statistics (CCaDiCaL *wrapper) {
((Wrapper *) wrapper)->solver->statistics ();
}
void ccadical_terminate (CCaDiCaL *wrapper) {
((Wrapper *) wrapper)->solver->terminate ();
}
int64_t ccadical_active (CCaDiCaL *wrapper) {
return ((Wrapper *) wrapper)->solver->active ();
}
int64_t ccadical_irredundant (CCaDiCaL *wrapper) {
return ((Wrapper *) wrapper)->solver->irredundant ();
}
int ccadical_fixed (CCaDiCaL *wrapper, int lit) {
return ((Wrapper *) wrapper)->solver->fixed (lit);
}
void ccadical_set_terminate (CCaDiCaL *ptr, void *state,
int (*terminate) (void *)) {
Wrapper *wrapper = (Wrapper *) ptr;
wrapper->terminator.state = state;
wrapper->terminator.function = terminate;
if (terminate)
wrapper->solver->connect_terminator (wrapper);
else
wrapper->solver->disconnect_terminator ();
}
void ccadical_set_learn (CCaDiCaL *ptr, void *state, int max_length,
void (*learn) (void *state, int *clause)) {
Wrapper *wrapper = (Wrapper *) ptr;
wrapper->learner.state = state;
wrapper->learner.max_length = max_length;
wrapper->learner.function = learn;
if (learn)
wrapper->solver->connect_learner (wrapper);
else
wrapper->solver->disconnect_learner ();
}
void ccadical_freeze (CCaDiCaL *ptr, int lit) {
((Wrapper *) ptr)->solver->freeze (lit);
}
void ccadical_melt (CCaDiCaL *ptr, int lit) {
((Wrapper *) ptr)->solver->melt (lit);
}
int ccadical_frozen (CCaDiCaL *ptr, int lit) {
return ((Wrapper *) ptr)->solver->frozen (lit);
}
int ccadical_trace_proof (CCaDiCaL *ptr, FILE *file, const char *path) {
return ((Wrapper *) ptr)->solver->trace_proof (file, path);
}
void ccadical_close_proof (CCaDiCaL *ptr) {
((Wrapper *) ptr)->solver->close_proof_trace ();
}
void ccadical_conclude (CCaDiCaL *ptr) {
((Wrapper *) ptr)->solver->conclude ();
}
int ccadical_vars (CCaDiCaL *ptr) {
return ((Wrapper *) ptr)->solver->vars ();
}
int ccadical_reserve_difference (CCaDiCaL *ptr, int number_of_vars) {
return ((Wrapper *) ptr)->solver->reserve_difference (number_of_vars);
}
}

View File

@ -0,0 +1,71 @@
#ifndef _ccadical_h_INCLUDED
#define _ccadical_h_INCLUDED
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif
/*------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdio.h>
// C wrapper for CaDiCaL's C++ API following IPASIR.
typedef struct CCaDiCaL CCaDiCaL;
const char *ccadical_signature (void);
CCaDiCaL *ccadical_init (void);
void ccadical_release (CCaDiCaL *);
void ccadical_add (CCaDiCaL *, int lit);
void ccadical_assume (CCaDiCaL *, int lit);
int ccadical_solve (CCaDiCaL *);
int ccadical_val (CCaDiCaL *, int lit);
int ccadical_failed (CCaDiCaL *, int lit);
void ccadical_set_terminate (CCaDiCaL *, void *state,
int (*terminate) (void *state));
void ccadical_set_learn (CCaDiCaL *, void *state, int max_length,
void (*learn) (void *state, int *clause));
/*------------------------------------------------------------------------*/
// Non-IPASIR conformant 'C' functions.
void ccadical_constrain (CCaDiCaL *, int lit);
int ccadical_constraint_failed (CCaDiCaL *);
void ccadical_set_option (CCaDiCaL *, const char *name, int val);
void ccadical_limit (CCaDiCaL *, const char *name, int limit);
int ccadical_get_option (CCaDiCaL *, const char *name);
void ccadical_print_statistics (CCaDiCaL *);
int64_t ccadical_active (CCaDiCaL *);
int64_t ccadical_irredundant (CCaDiCaL *);
int ccadical_fixed (CCaDiCaL *, int lit);
int ccadical_trace_proof (CCaDiCaL *, FILE *, const char *);
void ccadical_close_proof (CCaDiCaL *);
void ccadical_conclude (CCaDiCaL *);
void ccadical_terminate (CCaDiCaL *);
void ccadical_freeze (CCaDiCaL *, int lit);
int ccadical_frozen (CCaDiCaL *, int lit);
void ccadical_melt (CCaDiCaL *, int lit);
int ccadical_simplify (CCaDiCaL *);
int ccadical_vars (CCaDiCaL *);
int ccadical_reserve_difference (CCaDiCaL *, int number_of_vars);
/*------------------------------------------------------------------------*/
// Support legacy names used before moving to more IPASIR conforming names.
#define ccadical_reset ccadical_release
#define ccadical_sat ccadical_solve
#define ccadical_deref ccadical_val
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
}
#endif
/*------------------------------------------------------------------------*/
#endif

648
src/sat/cadical/checker.cpp Normal file
View File

@ -0,0 +1,648 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
inline unsigned Checker::l2u (int lit) {
assert (lit);
assert (lit != INT_MIN);
unsigned res = 2 * (abs (lit) - 1);
if (lit < 0)
res++;
return res;
}
inline signed char Checker::val (int lit) {
assert (lit);
assert (lit != INT_MIN);
assert (abs (lit) < size_vars);
assert (vals[lit] == -vals[-lit]);
return vals[lit];
}
signed char &Checker::mark (int lit) {
const unsigned u = l2u (lit);
assert (u < marks.size ());
return marks[u];
}
inline CheckerWatcher &Checker::watcher (int lit) {
const unsigned u = l2u (lit);
assert (u < watchers.size ());
return watchers[u];
}
/*------------------------------------------------------------------------*/
CheckerClause *Checker::new_clause () {
const size_t size = simplified.size ();
assert (size > 1), assert (size <= UINT_MAX);
const size_t bytes = sizeof (CheckerClause) + (size - 2) * sizeof (int);
CheckerClause *res = (CheckerClause *) new char[bytes];
DeferDeleteArray<char> delete_res ((char *) res);
res->next = 0;
res->hash = last_hash;
res->size = size;
int *literals = res->literals, *p = literals;
for (const auto &lit : simplified)
*p++ = lit;
num_clauses++;
// First two literals are used as watches and should not be false.
//
for (unsigned i = 0; i < 2; i++) {
int lit = literals[i];
if (!val (lit))
continue;
for (unsigned j = i + 1; j < size; j++) {
int other = literals[j];
if (val (other))
continue;
swap (literals[i], literals[j]);
break;
}
}
assert (!val (literals[0]));
assert (!val (literals[1]));
watcher (literals[0]).push_back (CheckerWatch (literals[1], res));
watcher (literals[1]).push_back (CheckerWatch (literals[0], res));
delete_res.release ();
return res;
}
void Checker::delete_clause (CheckerClause *c) {
if (c->size) {
assert (c->size > 1);
assert (num_clauses);
num_clauses--;
} else {
assert (num_garbage);
num_garbage--;
}
delete[] (char *) c;
}
void Checker::enlarge_clauses () {
assert (num_clauses == size_clauses);
const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1;
LOG ("CHECKER enlarging clauses of checker from %" PRIu64 " to %" PRIu64,
(uint64_t) size_clauses, (uint64_t) new_size_clauses);
CheckerClause **new_clauses;
new_clauses = new CheckerClause *[new_size_clauses];
clear_n (new_clauses, new_size_clauses);
for (uint64_t i = 0; i < size_clauses; i++) {
for (CheckerClause *c = clauses[i], *next; c; c = next) {
next = c->next;
const uint64_t h = reduce_hash (c->hash, new_size_clauses);
c->next = new_clauses[h];
new_clauses[h] = c;
}
}
delete[] clauses;
clauses = new_clauses;
size_clauses = new_size_clauses;
}
bool Checker::clause_satisfied (CheckerClause *c) {
for (unsigned i = 0; i < c->size; i++)
if (val (c->literals[i]) > 0)
return true;
return false;
}
// The main reason why we have an explicit garbage collection phase is that
// removing clauses from watcher lists eagerly might lead to an accumulated
// quadratic algorithm. Thus we delay removing garbage clauses from watcher
// lists until garbage collection (even though we remove garbage clauses on
// the fly during propagation too). We also remove satisfied clauses.
//
void Checker::collect_garbage_clauses () {
stats.collections++;
for (size_t i = 0; i < size_clauses; i++) {
CheckerClause **p = clauses + i, *c;
while ((c = *p)) {
if (clause_satisfied (c)) {
c->size = 0; // mark as garbage
*p = c->next;
c->next = garbage;
garbage = c;
num_garbage++;
assert (num_clauses);
num_clauses--;
} else
p = &c->next;
}
}
LOG ("CHECKER collecting %" PRIu64 " garbage clauses %.0f%%", num_garbage,
percent (num_garbage, num_clauses));
for (int lit = -size_vars + 1; lit < size_vars; lit++) {
if (!lit)
continue;
CheckerWatcher &ws = watcher (lit);
const auto end = ws.end ();
auto j = ws.begin (), i = j;
for (; i != end; i++) {
CheckerWatch &w = *i;
if (w.clause->size)
*j++ = w;
}
if (j == ws.end ())
continue;
if (j == ws.begin ())
erase_vector (ws);
else
ws.resize (j - ws.begin ());
}
for (CheckerClause *c = garbage, *next; c; c = next)
next = c->next, delete_clause (c);
assert (!num_garbage);
garbage = 0;
}
/*------------------------------------------------------------------------*/
Checker::Checker (Internal *i)
: internal (i), size_vars (0), vals (0), inconsistent (false),
num_clauses (0), num_garbage (0), size_clauses (0), clauses (0),
garbage (0), next_to_propagate (0), last_hash (0) {
// Initialize random number table for hash function.
//
Random random (42);
for (unsigned n = 0; n < num_nonces; n++) {
uint64_t nonce = random.next ();
if (!(nonce & 1))
nonce++;
assert (nonce), assert (nonce & 1);
nonces[n] = nonce;
}
memset (&stats, 0, sizeof (stats)); // Initialize statistics.
}
void Checker::connect_internal (Internal *i) {
internal = i;
LOG ("CHECKER connected to internal");
}
Checker::~Checker () {
LOG ("CHECKER delete");
vals -= size_vars;
delete[] vals;
for (size_t i = 0; i < size_clauses; i++)
for (CheckerClause *c = clauses[i], *next; c; c = next)
next = c->next, delete_clause (c);
for (CheckerClause *c = garbage, *next; c; c = next)
next = c->next, delete_clause (c);
delete[] clauses;
}
/*------------------------------------------------------------------------*/
// The simplicity for accessing 'vals' and 'watchers' directly through a
// signed integer literal, comes with the price of slightly more complex
// code in deleting and enlarging the checker data structures.
void Checker::enlarge_vars (int64_t idx) {
assert (0 < idx), assert (idx <= INT_MAX);
int64_t new_size_vars = size_vars ? 2 * size_vars : 2;
while (idx >= new_size_vars)
new_size_vars *= 2;
LOG ("CHECKER enlarging variables of checker from %" PRId64 " to %" PRId64
"",
size_vars, new_size_vars);
signed char *new_vals;
new_vals = new signed char[2 * new_size_vars];
clear_n (new_vals, 2 * new_size_vars);
new_vals += new_size_vars;
if (size_vars) // To make sanitizer happy (without '-O').
memcpy ((void *) (new_vals - size_vars), (void *) (vals - size_vars),
2 * size_vars);
vals -= size_vars;
delete[] vals;
vals = new_vals;
size_vars = new_size_vars;
watchers.resize (2 * new_size_vars);
marks.resize (2 * new_size_vars);
assert (idx < new_size_vars);
}
inline void Checker::import_literal (int lit) {
assert (lit);
assert (lit != INT_MIN);
int idx = abs (lit);
if (idx >= size_vars)
enlarge_vars (idx);
simplified.push_back (lit);
unsimplified.push_back (lit);
}
void Checker::import_clause (const vector<int> &c) {
for (const auto &lit : c)
import_literal (lit);
}
struct lit_smaller {
bool operator() (int a, int b) const {
int c = abs (a), d = abs (b);
if (c < d)
return true;
if (c > d)
return false;
return a < b;
}
};
bool Checker::tautological () {
sort (simplified.begin (), simplified.end (), lit_smaller ());
const auto end = simplified.end ();
auto j = simplified.begin ();
int prev = 0;
for (auto i = j; i != end; i++) {
int lit = *i;
if (lit == prev)
continue; // duplicated literal
if (lit == -prev)
return true; // tautological clause
const signed char tmp = val (lit);
if (tmp > 0)
return true; // satisfied literal and clause
*j++ = prev = lit;
}
simplified.resize (j - simplified.begin ());
return false;
}
/*------------------------------------------------------------------------*/
uint64_t Checker::reduce_hash (uint64_t hash, uint64_t size) {
assert (size > 0);
unsigned shift = 32;
uint64_t res = hash;
while ((((uint64_t) 1) << shift) > size) {
res ^= res >> shift;
shift >>= 1;
}
res &= size - 1;
assert (res < size);
return res;
}
uint64_t Checker::compute_hash () {
unsigned j = last_id % num_nonces;
uint64_t tmp = nonces[j] * last_id;
return last_hash = tmp;
}
CheckerClause **Checker::find () {
stats.searches++;
CheckerClause **res, *c;
const uint64_t hash = compute_hash ();
const unsigned size = simplified.size ();
const uint64_t h = reduce_hash (hash, size_clauses);
for (const auto &lit : simplified)
mark (lit) = true;
for (res = clauses + h; (c = *res); res = &c->next) {
if (c->hash == hash && c->size == size) {
bool found = true;
const int *literals = c->literals;
for (unsigned i = 0; found && i != size; i++)
found = mark (literals[i]);
if (found)
break;
}
stats.collisions++;
}
for (const auto &lit : simplified)
mark (lit) = false;
return res;
}
void Checker::insert () {
stats.insertions++;
if (num_clauses == size_clauses)
enlarge_clauses ();
const uint64_t h = reduce_hash (compute_hash (), size_clauses);
CheckerClause *c = new_clause ();
c->next = clauses[h];
clauses[h] = c;
}
/*------------------------------------------------------------------------*/
inline void Checker::assign (int lit) {
assert (!val (lit));
vals[lit] = 1;
vals[-lit] = -1;
trail.push_back (lit);
}
inline void Checker::assume (int lit) {
signed char tmp = val (lit);
if (tmp > 0)
return;
assert (!tmp);
stats.assumptions++;
assign (lit);
}
void Checker::backtrack (unsigned previously_propagated) {
assert (previously_propagated <= trail.size ());
while (trail.size () > previously_propagated) {
int lit = trail.back ();
assert (val (lit) > 0);
assert (val (-lit) < 0);
vals[lit] = vals[-lit] = 0;
trail.pop_back ();
}
trail.resize (previously_propagated);
next_to_propagate = previously_propagated;
assert (trail.size () == next_to_propagate);
}
/*------------------------------------------------------------------------*/
// This is a standard propagation routine without using blocking literals
// nor without saving the last replacement position.
bool Checker::propagate () {
bool res = true;
while (res && next_to_propagate < trail.size ()) {
int lit = trail[next_to_propagate++];
stats.propagations++;
assert (val (lit) > 0);
assert (abs (lit) < size_vars);
CheckerWatcher &ws = watcher (-lit);
const auto end = ws.end ();
auto j = ws.begin (), i = j;
for (; res && i != end; i++) {
CheckerWatch &w = *j++ = *i;
const int blit = w.blit;
assert (blit != -lit);
const signed char blit_val = val (blit);
if (blit_val > 0)
continue;
const unsigned size = w.size;
if (size == 2) { // not precise since
if (blit_val < 0)
res = false; // clause might be garbage
else
assign (w.blit); // but still sound
} else {
assert (size > 2);
CheckerClause *c = w.clause;
if (!c->size) {
j--;
continue;
} // skip garbage clauses
assert (size == c->size);
int *lits = c->literals;
int other = lits[0] ^ lits[1] ^ (-lit);
assert (other != -lit);
signed char other_val = val (other);
if (other_val > 0) {
j[-1].blit = other;
continue;
}
lits[0] = other, lits[1] = -lit;
unsigned k;
int replacement = 0;
signed char replacement_val = -1;
for (k = 2; k < size; k++)
if ((replacement_val = val (replacement = lits[k])) >= 0)
break;
if (replacement_val >= 0) {
watcher (replacement).push_back (CheckerWatch (-lit, c));
swap (lits[1], lits[k]);
j--;
} else if (!other_val)
assign (other);
else
res = false;
}
}
while (i != end)
*j++ = *i++;
ws.resize (j - ws.begin ());
}
return res;
}
bool Checker::check () {
stats.checks++;
if (inconsistent)
return true;
unsigned previously_propagated = next_to_propagate;
for (const auto &lit : simplified)
assume (-lit);
bool res = !propagate ();
backtrack (previously_propagated);
return res;
}
bool Checker::check_blocked () {
for (const auto &lit : unsimplified) {
mark (-lit) = true;
}
vector<int> not_blocked;
for (size_t i = 0; i < size_clauses; i++) {
for (CheckerClause *c = clauses[i], *next; c; c = next) {
next = c->next;
unsigned count = 0;
int first;
for (int *i = c->literals; i < c->literals + c->size; i++) {
const int lit = *i;
if (val (lit) > 0) {
LOG (c->literals, c->size, "satisfied clause");
count = 2;
break;
}
if (mark (lit)) {
count++;
LOG (c->literals, c->size, "clause");
first = lit;
}
}
if (count == 1)
not_blocked.push_back (first);
}
}
for (const auto &lit : not_blocked) {
mark (lit) = false;
}
bool blocked = false;
for (const auto &lit : unsimplified) {
if (mark (-lit))
blocked = true;
mark (-lit) = false;
}
return blocked;
}
/*------------------------------------------------------------------------*/
void Checker::add_clause (const char *type) {
#ifndef LOGGING
(void) type;
#endif
// If there are enough garbage clauses collect them first.
if (num_garbage > 0.5 * max ((size_t) size_clauses, (size_t) size_vars))
collect_garbage_clauses ();
int unit = 0;
for (const auto &lit : simplified) {
const signed char tmp = val (lit);
if (tmp < 0)
continue;
assert (!tmp);
if (unit) {
unit = INT_MIN;
break;
}
unit = lit;
}
if (simplified.empty ()) {
LOG ("CHECKER added empty %s clause", type);
inconsistent = true;
}
if (!unit) {
LOG ("CHECKER added and checked falsified %s clause", type);
inconsistent = true;
} else if (unit != INT_MIN) {
LOG ("CHECKER added and checked %s unit clause %d", type, unit);
assign (unit);
stats.units++;
if (!propagate ()) {
LOG ("CHECKER inconsistent after propagating %s unit", type);
inconsistent = true;
}
} else
insert ();
}
void Checker::add_original_clause (int64_t id, bool, const vector<int> &c,
bool) {
if (inconsistent)
return;
START (checking);
LOG (c, "CHECKER addition of original clause");
stats.added++;
stats.original++;
import_clause (c);
last_id = id;
if (tautological ())
LOG ("CHECKER ignoring satisfied original clause");
else
add_clause ("original");
simplified.clear ();
unsimplified.clear ();
STOP (checking);
}
void Checker::add_derived_clause (int64_t id, bool, const vector<int> &c,
const vector<int64_t> &) {
if (inconsistent)
return;
START (checking);
LOG (c, "CHECKER addition of derived clause");
stats.added++;
stats.derived++;
import_clause (c);
last_id = id;
if (tautological ())
LOG ("CHECKER ignoring satisfied derived clause");
else if (!check () && !check_blocked ()) { // needed for ER proof support
fatal_message_start ();
fputs ("failed to check derived clause:\n", stderr);
for (const auto &lit : unsimplified)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
} else
add_clause ("derived");
simplified.clear ();
unsimplified.clear ();
STOP (checking);
}
/*------------------------------------------------------------------------*/
void Checker::delete_clause (int64_t id, bool, const vector<int> &c) {
if (inconsistent)
return;
START (checking);
LOG (c, "CHECKER checking deletion of clause");
stats.deleted++;
simplified.clear (); // Can be non-empty if clause allocation fails.
unsimplified.clear (); // Can be non-empty if clause allocation fails.
import_clause (c);
last_id = id;
if (!tautological ()) {
CheckerClause **p = find (), *d = *p;
if (d) {
assert (d->size > 1);
// Remove from hash table, mark as garbage, connect to garbage list.
num_garbage++;
assert (num_clauses);
num_clauses--;
*p = d->next;
d->next = garbage;
garbage = d;
d->size = 0;
} else {
fatal_message_start ();
fputs ("deleted clause not in proof:\n", stderr);
for (const auto &lit : unsimplified)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
}
simplified.clear ();
unsimplified.clear ();
STOP (checking);
}
void Checker::add_assumption_clause (int64_t id, const vector<int> &c,
const vector<int64_t> &chain) {
add_derived_clause (id, true, c, chain);
delete_clause (id, true, c);
}
/*------------------------------------------------------------------------*/
void Checker::dump () {
int max_var = 0;
for (uint64_t i = 0; i < size_clauses; i++)
for (CheckerClause *c = clauses[i]; c; c = c->next)
for (unsigned i = 0; i < c->size; i++)
if (abs (c->literals[i]) > max_var)
max_var = abs (c->literals[i]);
printf ("p cnf %d %" PRIu64 "\n", max_var, num_clauses);
for (uint64_t i = 0; i < size_clauses; i++)
for (CheckerClause *c = clauses[i]; c; c = c->next) {
for (unsigned i = 0; i < c->size; i++)
printf ("%d ", c->literals[i]);
printf ("0\n");
}
}
} // namespace CaDiCaL

172
src/sat/cadical/checker.hpp Normal file
View File

@ -0,0 +1,172 @@
#ifndef _checker_hpp_INCLUDED
#define _checker_hpp_INCLUDED
#include "tracer.hpp" // Alphabetically after 'checker'.
#include <cstdint>
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This checker implements an online forward DRUP proof checker enabled by
// 'opts.checkproof' (requires 'opts.check' also to be enabled). This is
// useful for model basted testing (and delta-debugging), where we can not
// rely on an external proof checker such as 'drat-trim'. We also do not
// have yet a flow for offline incremental proof checking, while this
// checker here can also be used in an incremental setting.
//
// In essence the checker implements is a simple propagation online SAT
// solver with an additional hash table to find clauses fast for
// 'delete_clause'. It requires its own data structure for clauses
// ('CheckerClause') and watches ('CheckerWatch').
//
// In our experiments the checker slows down overall SAT solving time by a
// factor of 3, which we contribute to its slightly less efficient
// implementation.
/*------------------------------------------------------------------------*/
struct CheckerClause {
CheckerClause *next; // collision chain link for hash table
uint64_t hash; // previously computed full 64-bit hash
unsigned size; // zero if this is a garbage clause
int literals[2]; // otherwise 'literals' of length 'size'
};
struct CheckerWatch {
int blit;
unsigned size;
CheckerClause *clause;
CheckerWatch () {}
CheckerWatch (int b, CheckerClause *c)
: blit (b), size (c->size), clause (c) {}
};
typedef vector<CheckerWatch> CheckerWatcher;
/*------------------------------------------------------------------------*/
class Checker : public StatTracer {
Internal *internal;
// Capacity of variable values.
//
int64_t size_vars;
// For the assignment we want to have an as fast access as possible and
// thus we use an array which can also be indexed by negative literals and
// is actually valid in the range [-size_vars+1, ..., size_vars-1].
//
signed char *vals;
// The 'watchers' and 'marks' data structures are not that time critical
// and thus we access them by first mapping a literal to 'unsigned'.
//
static unsigned l2u (int lit);
vector<CheckerWatcher> watchers; // watchers of literals
vector<signed char> marks; // mark bits of literals
signed char &mark (int lit);
CheckerWatcher &watcher (int lit);
bool inconsistent; // found or added empty clause
uint64_t num_clauses; // number of clauses in hash table
uint64_t num_garbage; // number of garbage clauses
uint64_t size_clauses; // size of clause hash table
CheckerClause **clauses; // hash table of clauses
CheckerClause *garbage; // linked list of garbage clauses
vector<int> unsimplified; // original clause for reporting
vector<int> simplified; // clause for sorting
vector<int> trail; // for propagation
unsigned next_to_propagate; // next to propagate on trail
void enlarge_vars (int64_t idx);
void import_literal (int lit);
void import_clause (const vector<int> &);
bool tautological ();
static const unsigned num_nonces = 4;
uint64_t nonces[num_nonces]; // random numbers for hashing
uint64_t last_hash; // last computed hash value of clause
int64_t last_id;
uint64_t compute_hash (); // compute and save hash value of clause
// Reduce hash value to the actual size.
//
static uint64_t reduce_hash (uint64_t hash, uint64_t size);
void enlarge_clauses (); // enlarge hash table for clauses
void insert (); // insert clause in hash table
CheckerClause **find (); // find clause position in hash table
void add_clause (const char *type);
void collect_garbage_clauses ();
CheckerClause *new_clause ();
void delete_clause (CheckerClause *);
signed char val (int lit); // returns '-1', '0' or '1'
bool clause_satisfied (CheckerClause *);
void assign (int lit); // assign a literal to true
void assume (int lit); // assume a literal
bool propagate (); // propagate and check for conflicts
void backtrack (unsigned); // prepare for next clause
bool check (); // check simplified clause is implied
bool check_blocked (); // check if clause is blocked
struct {
int64_t added; // number of added clauses
int64_t original; // number of added original clauses
int64_t derived; // number of added derived clauses
int64_t deleted; // number of deleted clauses
int64_t assumptions; // number of assumed literals
int64_t propagations; // number of propagated literals
int64_t insertions; // number of clauses added to hash table
int64_t collisions; // number of hash collisions in 'find'
int64_t searches; // number of searched clauses in 'find'
int64_t checks; // number of implication checks
int64_t collections; // garbage collections
int64_t units;
} stats;
public:
Checker (Internal *);
virtual ~Checker ();
void connect_internal (Internal *i) override;
void add_original_clause (int64_t, bool, const vector<int> &,
bool = false) override;
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
void delete_clause (int64_t, bool, const vector<int> &) override;
void finalize_clause (int64_t, const vector<int> &) override {} // skip
void report_status (int, int64_t) override {} // skip
void begin_proof (int64_t) override {} // skip
void add_assumption_clause (int64_t, const vector<int> &,
const vector<int64_t> &) override;
void print_stats () override;
void dump (); // for debugging purposes only
};
} // namespace CaDiCaL
#endif

643
src/sat/cadical/clause.cpp Normal file
View File

@ -0,0 +1,643 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Signed marking or unmarking of a clause or the global 'clause'.
void Internal::mark (Clause *c) {
for (const auto &lit : *c)
mark (lit);
}
void Internal::mark2 (Clause *c) {
for (const auto &lit : *c)
mark2 (lit);
}
void Internal::unmark (Clause *c) {
for (const auto &lit : *c)
unmark (lit);
}
void Internal::mark_clause () {
for (const auto &lit : clause)
mark (lit);
}
void Internal::unmark_clause () {
for (const auto &lit : clause)
unmark (lit);
}
/*------------------------------------------------------------------------*/
// Mark the variables of an irredundant clause to 'have been removed', which
// will trigger these variables to be considered again in the next bounded
// variable elimination phase. This is called from 'mark_garbage' below.
// Note that 'mark_removed (int lit)' will also mark the blocking flag of
// '-lit' to trigger reconsidering blocking clauses on '-lit'.
void Internal::mark_removed (Clause *c, int except) {
LOG (c, "marking removed");
assert (!c->redundant);
for (const auto &lit : *c)
if (lit != except)
mark_removed (lit);
}
// Mark the variables of a (redundant or irredundant) clause to 'have been
// added', which triggers clauses with such a variables, to be considered
// both as a subsumed or subsuming clause in the next subsumption phase.
// This function is called from 'new_clause' below as well as in situations
// where a clause is shrunken (and thus needs to be at least considered
// again to subsume a larger clause). We also use this to tell
// 'ternary' preprocessing reconsider clauses on an added literal as well as
// trying to block clauses on it.
inline void Internal::mark_added (int lit, int size, bool redundant) {
mark_subsume (lit);
if (size == 3)
mark_ternary (lit);
if (!redundant)
mark_block (lit);
if (!redundant || size == 2)
mark_factor (lit);
}
void Internal::mark_added (Clause *c) {
LOG (c, "marking added");
assert (likely_to_be_kept_clause (c));
for (const auto &lit : *c)
mark_added (lit, c->size, c->redundant);
}
/*------------------------------------------------------------------------*/
Clause *Internal::new_clause (bool red, int glue) {
assert (clause.size () <= (size_t) INT_MAX);
const int size = (int) clause.size ();
assert (size >= 2);
if (glue > size)
glue = size;
size_t bytes = Clause::bytes (size);
Clause *c = (Clause *) new char[bytes];
DeferDeleteArray<char> clause_delete ((char *) c);
c->id = ++clause_id;
c->conditioned = false;
c->covered = false;
c->enqueued = false;
c->frozen = false;
c->garbage = false;
c->gate = false;
c->hyper = false;
c->instantiated = false;
c->moved = false;
c->reason = false;
c->redundant = red;
c->transred = false;
c->subsume = false;
c->swept = false;
c->flushed = false;
c->vivified = false;
c->vivify = false;
c->used = 0;
c->glue = glue;
c->size = size;
c->pos = 2;
for (int i = 0; i < size; i++)
c->literals[i] = clause[i];
// Just checking that we did not mess up our sophisticated memory layout.
// This might be compiler dependent though. Crucial for correctness.
//
assert (c->bytes () == bytes);
stats.current.total++;
stats.added.total++;
if (red) {
stats.current.redundant++;
stats.added.redundant++;
} else {
stats.irrlits += size;
stats.current.irredundant++;
stats.added.irredundant++;
}
clauses.push_back (c);
clause_delete.release ();
LOG (c, "new pointer %p", (void *) c);
if (likely_to_be_kept_clause (c))
mark_added (c);
return c;
}
/*------------------------------------------------------------------------*/
void Internal::promote_clause (Clause *c, int new_glue) {
assert (c->redundant);
const int tier1limit = tier1[false];
const int tier2limit = max (tier1limit, tier2[false]);
if (!c->redundant)
return;
if (c->hyper)
return;
int old_glue = c->glue;
if (new_glue >= old_glue)
return;
if (old_glue > tier1limit && new_glue <= tier1limit) {
LOG (c, "promoting with new glue %d to tier1", new_glue);
stats.promoted1++;
c->used = max_used;
} else if (old_glue > tier2limit && new_glue <= tier2limit) {
LOG (c, "promoting with new glue %d to tier2", new_glue);
stats.promoted2++;
} else if (old_glue <= tier2limit)
LOG (c, "keeping with new glue %d in tier2", new_glue);
else
LOG (c, "keeping with new glue %d in tier3", new_glue);
stats.improvedglue++;
c->glue = new_glue;
}
/*------------------------------------------------------------------------*/
void Internal::promote_clause_glue_only (Clause *c, int new_glue) {
assert (c->redundant);
if (c->hyper)
return;
int old_glue = c->glue;
const int tier1limit = tier1[false];
const int tier2limit = max (tier1limit, tier2[false]);
if (new_glue >= old_glue)
return;
if (new_glue <= tier1limit) {
LOG (c, "promoting with new glue %d to tier1", new_glue);
stats.promoted1++;
c->used = max_used;
} else if (old_glue > tier2limit && new_glue <= tier2limit) {
LOG (c, "promoting with new glue %d to tier2", new_glue);
stats.promoted2++;
} else if (old_glue <= tier2limit)
LOG (c, "keeping with new glue %d in tier2", new_glue);
else
LOG (c, "keeping with new glue %d in tier3", new_glue);
stats.improvedglue++;
c->glue = new_glue;
}
/*------------------------------------------------------------------------*/
// Shrinking a clause, e.g., removing one or more literals, requires to fix
// the 'pos' field, if it exists and points after the new last literal. We
// also have adjust the global statistics counter of irredundant literals
// for irredundant clauses, and also adjust the glue value of redundant
// clauses if the size becomes smaller than the glue. Also mark the
// literals in the resulting clause as 'added'. The result is the number of
// (aligned) removed bytes, resulting from shrinking the clause.
//
size_t Internal::shrink_clause (Clause *c, int new_size) {
if (opts.check && is_external_forgettable (c->id))
mark_garbage_external_forgettable (c->id);
assert (new_size >= 2);
int old_size = c->size;
assert (new_size < old_size);
#ifndef NDEBUG
for (int i = c->size; i < new_size; i++)
c->literals[i] = 0;
#endif
if (c->pos >= new_size)
c->pos = 2;
size_t old_bytes = c->bytes ();
c->size = new_size;
size_t new_bytes = c->bytes ();
size_t res = old_bytes - new_bytes;
if (c->redundant)
promote_clause_glue_only (c, min (c->size - 1, c->glue));
else {
int delta_size = old_size - new_size;
assert (stats.irrlits >= delta_size);
stats.irrlits -= delta_size;
}
if (likely_to_be_kept_clause (c))
mark_added (c);
return res;
}
// This is the 'raw' deallocation of a clause. If the clause is in the
// arena nothing happens. If the clause is not in the arena its memory is
// reclaimed immediately.
void Internal::deallocate_clause (Clause *c) {
char *p = (char *) c;
if (arena.contains (p))
return;
LOG (c, "deallocate pointer %p", (void *) c);
delete[] p;
}
void Internal::delete_clause (Clause *c) {
LOG (c, "delete pointer %p", (void *) c);
size_t bytes = c->bytes ();
stats.collected += bytes;
if (c->garbage) {
assert (stats.garbage.bytes >= (int64_t) bytes);
stats.garbage.bytes -= bytes;
assert (stats.garbage.clauses > 0);
stats.garbage.clauses--;
assert (stats.garbage.literals >= c->size);
stats.garbage.literals -= c->size;
// See the discussion in 'propagate' on avoiding to eagerly trace binary
// clauses as deleted (produce 'd ...' lines) as soon they are marked
// garbage. We avoid this and only trace them as deleted when they are
// actually deleted here. This allows the solver to propagate binary
// garbage clauses without producing incorrect 'd' lines. The effect
// from the proof perspective is that the deletion of these binary
// clauses occurs later in the proof file.
//
if (proof && c->size == 2 && !c->flushed) {
proof->delete_clause (c);
}
}
deallocate_clause (c);
}
// We want to eagerly update statistics as soon clauses are marked garbage.
// Otherwise 'report' for instance gives wrong numbers after 'subsume'
// before the next 'reduce'. Thus we factored out marking and accounting
// for garbage clauses.
//
// Eagerly deleting clauses instead is problematic, since references to
// these clauses need to be flushed, which is too costly to do eagerly.
//
// We also update garbage statistics at this point. This helps to
// determine whether the garbage collector should be called during for
// instance bounded variable elimination, which usually generates lots of
// garbage clauses.
//
// In order not to miss any update to these clause statistics we call
// 'check_clause_stats' after garbage collection in debugging mode.
//
void Internal::mark_garbage (Clause *c) {
assert (!c->garbage);
// Delay tracing deletion of binary clauses. See the discussion above in
// 'delete_clause' and also in 'propagate'.
//
if (proof && (c->size != 2 || !watching ())) {
c->flushed = true;
proof->delete_clause (c);
}
// Because of the internal model checking, external forgettable clauses
// must be marked as removed already upon mark_garbage, can not wait until
// actual deletion.
if (opts.check && is_external_forgettable (c->id))
mark_garbage_external_forgettable (c->id);
assert (stats.current.total > 0);
stats.current.total--;
size_t bytes = c->bytes ();
if (c->redundant) {
assert (stats.current.redundant > 0);
stats.current.redundant--;
} else {
assert (stats.current.irredundant > 0);
stats.current.irredundant--;
assert (stats.irrlits >= c->size);
stats.irrlits -= c->size;
mark_removed (c);
}
stats.garbage.bytes += bytes;
stats.garbage.clauses++;
stats.garbage.literals += c->size;
c->garbage = true;
c->used = 0;
LOG (c, "marked garbage pointer %p", (void *) c);
}
/*------------------------------------------------------------------------*/
// Almost the same function as 'search_assign' except that we do not pretend
// to learn a new unit clause (which was confusing in log files).
void Internal::assign_original_unit (int64_t id, int lit) {
assert (!level || opts.chrono);
assert (!unsat);
const int idx = vidx (lit);
assert (!vals[idx]);
assert (!flags (idx).eliminated ());
Var &v = var (idx);
v.level = 0;
v.trail = (int) trail.size ();
v.reason = 0;
const signed char tmp = sign (lit);
set_val (idx, tmp);
trail.push_back (lit);
num_assigned++;
const unsigned uidx = vlit (lit);
if (lrat || frat)
unit_clauses (uidx) = id;
LOG ("original unit assign %d", lit);
assert (num_assigned == trail.size () || level);
mark_fixed (lit);
if (level)
return;
if (propagate ())
return;
assert (conflict);
LOG ("propagation of original unit results in conflict");
learn_empty_clause ();
}
// New clause added through the API, e.g., while parsing a DIMACS file.
// Also used by external_propagate in various different modes.
// clause, original, lrat_chain and external->eclause are set.
// from_propagator and force_no_backtrack change the behaviour.
// sometimes the pointer to the new clause is needed, therefore it is
// made sure that newest_clause points to the new clause upon return.
//
// TODO: Find another name for 'tainted' in the context of ilb, tainted
// is reconstruction related already and they should not mix.
void Internal::add_new_original_clause (int64_t id) {
if (!from_propagator && level && !opts.ilb) {
backtrack ();
} else if (tainted_literal) {
assert (val (tainted_literal));
int new_level = var (tainted_literal).level - 1;
assert (new_level >= 0);
backtrack (new_level);
}
assert (!tainted_literal);
LOG (original, "original clause");
assert (clause.empty ());
bool skip = false;
unordered_set<int> learned_levels;
size_t unassigned = 0;
newest_clause = 0;
if (unsat) {
LOG ("skipping clause since formula is already inconsistent");
skip = true;
} else {
assert (clause.empty ());
for (const auto &lit : original) {
int tmp = marked (lit);
if (tmp > 0) {
LOG ("removing duplicated literal %d", lit);
} else if (tmp < 0) {
LOG ("tautological since both %d and %d occur", -lit, lit);
skip = true;
} else {
mark (lit);
tmp = fixed (lit);
if (tmp < 0) {
LOG ("removing falsified literal %d", lit);
if (lrat) {
int elit = externalize (lit);
unsigned eidx = (elit > 0) + 2u * (unsigned) abs (elit);
if (!external->ext_units[eidx]) {
int64_t uid = unit_id (-lit);
lrat_chain.push_back (uid);
}
}
} else if (tmp > 0) {
LOG ("satisfied since literal %d true", lit);
skip = true;
} else {
clause.push_back (lit);
assert (flags (lit).status != Flags::UNUSED);
tmp = val (lit);
if (tmp)
learned_levels.insert (var (lit).level);
else
unassigned++;
}
}
}
for (const auto &lit : original)
unmark (lit);
}
if (skip) {
if (from_propagator) {
stats.ext_prop.elearn_conf++;
// In case it was a skipped external forgettable, we need to mark it
// immediately as removed
if (opts.check && is_external_forgettable (id))
mark_garbage_external_forgettable (id);
}
if (proof) {
proof->delete_external_original_clause (id, false, external->eclause);
}
} else {
int64_t new_id = id;
const size_t size = clause.size ();
if (original.size () > size) {
new_id = ++clause_id;
if (proof) {
if (lrat)
lrat_chain.push_back (id);
proof->add_derived_clause (new_id, false, clause, lrat_chain);
proof->delete_external_original_clause (id, false,
external->eclause);
}
external->check_learned_clause ();
if (from_propagator) {
// The original form of the added clause is immediately forgotten
// TODO: shall we save and check the simplified form? (one with
// new_id)
if (opts.check && is_external_forgettable (id))
mark_garbage_external_forgettable (id);
}
}
external->eclause.clear ();
lrat_chain.clear ();
if (!size) {
if (from_propagator)
stats.ext_prop.elearn_conf++;
assert (!unsat);
if (!original.size ())
VERBOSE (1, "found empty original clause");
else
VERBOSE (1, "found falsified original clause");
unsat = true;
conflict_id = new_id;
marked_failed = true;
conclusion.push_back (new_id);
} else if (size == 1) {
if (force_no_backtrack) {
assert (level);
const int idx = vidx (clause[0]);
assert (val (clause[0]) >= 0);
assert (!flags (idx).eliminated ());
Var &v = var (idx);
assert (val (clause[0]));
v.level = 0;
v.reason = 0;
const unsigned uidx = vlit (clause[0]);
if (lrat || frat)
unit_clauses (uidx) = new_id;
mark_fixed (clause[0]);
} else {
const int lit = clause[0];
assert (!val (lit) || var (lit).level);
if (val (lit) < 0)
backtrack (var (lit).level - 1);
assert (val (lit) >= 0);
handle_external_clause (0);
assign_original_unit (new_id, lit);
}
} else {
move_literals_to_watch ();
#ifndef NDEBUG
check_watched_literal_invariants ();
#endif
int glue = (int) (learned_levels.size () + unassigned);
assert (glue <= (int) clause.size ());
bool clause_redundancy = from_propagator && ext_clause_forgettable;
Clause *c = new_clause (clause_redundancy, glue);
c->id = new_id;
clause_id--;
watch_clause (c);
clause.clear ();
original.clear ();
handle_external_clause (c);
newest_clause = c;
}
}
clause.clear ();
lrat_chain.clear ();
}
// Add learned new clause during conflict analysis and watch it. Requires
// that the clause is at least of size 2, and the first two literals
// are assigned at the highest decision level.
//
Clause *Internal::new_learned_redundant_clause (int glue) {
assert (clause.size () > 1);
#ifndef NDEBUG
for (size_t i = 2; i < clause.size (); i++)
assert (var (clause[0]).level >= var (clause[i]).level),
assert (var (clause[1]).level >= var (clause[i]).level);
#endif
external->check_learned_clause ();
Clause *res = new_clause (true, glue);
if (proof) {
proof->add_derived_clause (res, lrat_chain);
}
assert (watching ());
watch_clause (res);
return res;
}
// Add hyper binary resolved clause during 'probing'.
//
Clause *Internal::new_hyper_binary_resolved_clause (bool red, int glue) {
external->check_learned_clause ();
Clause *res = new_clause (red, glue);
if (proof) {
proof->add_derived_clause (res, lrat_chain);
}
assert (watching ());
watch_clause (res);
return res;
}
// Add hyper ternary resolved clause during 'ternary'.
//
Clause *Internal::new_hyper_ternary_resolved_clause (bool red) {
external->check_learned_clause ();
size_t size = clause.size ();
Clause *res = new_clause (red, size);
if (proof) {
proof->add_derived_clause (res, lrat_chain);
}
assert (!watching ());
return res;
}
Clause *Internal::new_factor_clause () {
external->check_learned_clause ();
stats.factor_added++;
stats.literals_factored += clause.size ();
Clause *res = new_clause (false, 0);
if (proof) {
proof->add_derived_clause (res, lrat_chain);
}
assert (!watching ());
assert (occurring ());
for (const auto &lit : *res) {
occs (lit).push_back (res);
}
return res;
}
// Add hyper ternary resolved clause during 'congruence' and watch it
//
Clause *
Internal::new_hyper_ternary_resolved_clause_and_watch (bool red,
bool full_watching) {
external->check_learned_clause ();
size_t size = clause.size ();
Clause *res = new_clause (red, size);
if (proof) {
proof->add_derived_clause (res, lrat_chain);
}
if (full_watching) {
assert (watching ());
watch_clause (res);
}
return res;
}
// Add a new clause with same glue and redundancy as 'orig' but literals are
// assumed to be in 'clause' in 'decompose' and 'vivify'.
//
Clause *Internal::new_clause_as (const Clause *orig) {
external->check_learned_clause ();
const int new_glue = orig->glue;
Clause *res = new_clause (orig->redundant, new_glue);
if (proof) {
proof->add_derived_clause (res, lrat_chain);
}
assert (watching ());
watch_clause (res);
return res;
}
// Add resolved clause during resolution, e.g., bounded variable
// elimination, but do not connect its occurrences here.
//
Clause *Internal::new_resolved_irredundant_clause () {
external->check_learned_clause ();
if (proof) {
proof->add_derived_clause (clause_id + 1, false, clause, lrat_chain);
}
Clause *res = new_clause (false);
assert (!watching ());
return res;
}
} // namespace CaDiCaL

188
src/sat/cadical/clause.hpp Normal file
View File

@ -0,0 +1,188 @@
#ifndef _clause_hpp_INCLUDED
#define _clause_hpp_INCLUDED
#include "util.hpp"
#include <climits>
#include <cstdint>
#include <cstdlib>
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
typedef int *literal_iterator;
typedef const int *const_literal_iterator;
/*------------------------------------------------------------------------*/
// The 'Clause' data structure is very important. There are usually many
// clauses and accessing them is a hot-spot. Thus we use common
// optimizations to reduce memory and improve cache usage, even though this
// induces some complexity in understanding the code.
//
// The most important optimization is to 'embed' the actual literals in the
// clause. This requires a variadic size structure and thus strictly is not
// 'C' conform, but supported by all compilers we used. The alternative is
// to store the actual literals somewhere else, which not only needs more
// memory but more importantly also requires another memory access and thus
// is very costly.
struct Clause {
union {
int64_t id; // Used to create LRAT-style proofs
Clause *copy; // Only valid if 'moved', then that's where to.
//
// The 'copy' field is only valid for 'moved' clauses in the moving
// garbage collector 'copy_non_garbage_clauses' for keeping clauses
// compactly in a contiguous memory arena. Otherwise, so almost all of
// the time, 'id' is valid. See 'collect.cpp' for details.
};
bool conditioned : 1; // Tried for globally blocked clause elimination.
bool covered : 1; // Already considered for covered clause elimination.
bool enqueued : 1; // Enqueued on backward queue.
bool frozen : 1; // Temporarily frozen (in covered clause elimination).
bool garbage : 1; // can be garbage collected unless it is a 'reason'
bool gate : 1; // Clause part of a gate (function definition).
bool hyper : 1; // redundant hyper binary or ternary resolved
bool instantiated : 1; // tried to instantiate
bool moved : 1; // moved during garbage collector ('copy' valid)
bool reason : 1; // reason / antecedent clause can not be collected
bool redundant : 1; // aka 'learned' so not 'irredundant' (original)
bool transred : 1; // already checked for transitive reduction
bool subsume : 1; // not checked in last subsumption round
bool swept : 1; // clause used to sweep equivalences
bool flushed : 1; // garbage in proof deleted binaries
unsigned used : 8; // resolved in conflict analysis since last 'reduce'
bool vivified : 1; // clause already vivified
bool vivify : 1; // clause scheduled to be vivified
// The glucose level ('LBD' or short 'glue') is a heuristic value for the
// expected usefulness of a learned clause, where smaller glue is consider
// more useful. During learning the 'glue' is determined as the number of
// decisions in the learned clause. Thus the glue of a clause is a strict
// upper limit on the smallest number of decisions needed to make it
// propagate. For instance a binary clause will propagate if one of its
// literals is set to false. Similarly a learned clause with glue 1 can
// propagate after one decision, one with glue 2 after 2 decisions etc.
// In some sense the glue is an abstraction of the size of the clause.
//
// See the IJCAI'09 paper by Audemard & Simon for more details. We
// switched back and forth between keeping the glue stored in a clause and
// using it only initially to determine whether it is kept, that is
// survives clause reduction. The latter strategy is not bad but also
// does not allow to use glue values for instance in 'reduce'.
//
// More recently we also update the glue and promote clauses to lower
// level tiers during conflict analysis. The idea of using three tiers is
// also due to Chanseok Oh and thus used in all recent 'Maple...' solvers.
// Tier one are the always kept clauses with low glue at most
// 'opts.reducetier1glue' (default '2'). The second tier contains all
// clauses with glue larger than 'opts.reducetier1glue' but smaller or
// equal than 'opts.reducetier2glue' (default '6'). The third tier
// consists of clauses with glue larger than 'opts.reducetier2glue'.
//
// Clauses in tier one are not deleted in 'reduce'. Clauses in tier
// two require to be unused in two consecutive 'reduce' intervals before
// being collected while for clauses in tier three not being used since
// the last 'reduce' call makes them deletion candidates. Clauses derived
// by hyper binary or ternary resolution (even though small and thus with
// low glue) are always removed if they remain unused during one interval.
// See 'mark_useless_redundant_clauses_as_garbage' in 'reduce.cpp' and
// 'bump_clause' in 'analyze.cpp'.
//
int glue;
int size; // Actual size of 'literals' (at least 2).
int pos; // Position of last watch replacement [Gent'13].
// This 'flexible array member' is of variadic 'size' (and actually
// shrunken if strengthened) and keeps the literals close to the header of
// the clause to avoid another pointer dereference, which would be costly.
// In earlier versions we used 'literals[2]' to fake it (in order to
// support older Microsoft compilers even though this feature is in C99)
// and at the same time being able to overlay the first two literals with
// the 'copy' field above, as having a flexible array member inside a
// union is not allowed. Now compilers start to figure out that those
// literals can be accessed with indices larger than 1 and produce
// warnings. After having the 'id' field mandatory we now overlay that
// one with the copy field.
// However, it turns out that even though flexible array members are in
// C99 they are not in C11++, and therefore pedantic compilation with
// '--pedantic' fails completely. Therefore we still support as
// alternative faked flexible array members, which unfortunately need
// then again more care when accessing the literals outside the faked
// virtual sizes and the compiler can somehow figure that out, because
// that would in turn produce a warning.
#ifndef NFLEXIBLE
int literals[];
#else
int literals[2];
#endif
// Supports simple range based for loops over clauses.
literal_iterator begin () { return literals; }
literal_iterator end () { return literals + size; }
const_literal_iterator begin () const { return literals; }
const_literal_iterator end () const { return literals + size; }
static size_t bytes (int size) {
// Memory sanitizer insists that clauses put into consecutive memory in
// the arena are still 8 byte aligned. We could also allocate 8 byte
// aligned memory there. However, assuming the real memory foot print
// of a clause is 8 bytes anyhow, we just allocate 8 byte aligned memory
// all the time (even if allocated outside of the arena).
//
assert (size > 1);
const size_t header_bytes = sizeof (Clause);
const size_t actual_literal_bytes = size * sizeof (int);
size_t combined_bytes = header_bytes + actual_literal_bytes;
#ifdef NFLEXIBLE
const size_t faked_literals_bytes = sizeof ((Clause *) 0)->literals;
combined_bytes -= faked_literals_bytes;
#endif
size_t aligned_bytes = align (combined_bytes, 8);
return aligned_bytes;
}
size_t bytes () const { return bytes (size); }
// Check whether this clause is ready to be collected and deleted. The
// 'reason' flag is only there to protect reason clauses in 'reduce',
// which does not backtrack to the root level. If garbage collection is
// triggered from a preprocessor, which backtracks to the root level, then
// 'reason' is false for sure. We want to use the same garbage collection
// code though for both situations and thus hide here this variance.
//
bool collect () const { return !reason && garbage; }
};
struct clause_smaller_size {
bool operator() (const Clause *a, const Clause *b) {
return a->size < b->size;
}
};
/*------------------------------------------------------------------------*/
// Place literals over the same variable close to each other. This would
// allow eager removal of identical literals and detection of tautological
// clauses but is only currently used for better logging (see also
// 'opts.logsort' in 'logging.cpp').
struct clause_lit_less_than {
bool operator() (int a, int b) const {
using namespace std;
int s = abs (a), t = abs (b);
return s < t || (s == t && a < b);
}
};
} // namespace CaDiCaL
#endif

545
src/sat/cadical/collect.cpp Normal file
View File

@ -0,0 +1,545 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Returns the positive number '1' ( > 0) if the given clause is root level
// satisfied or the negative number '-1' ( < 0) if it is not root level
// satisfied but contains a root level falsified literal. Otherwise, if it
// contains neither a satisfied nor falsified literal, then '0' is returned.
int Internal::clause_contains_fixed_literal (Clause *c) {
int satisfied = 0, falsified = 0;
for (const auto &lit : *c) {
const int tmp = fixed (lit);
if (tmp > 0) {
LOG (c, "root level satisfied literal %d in", lit);
satisfied++;
}
if (tmp < 0) {
LOG (c, "root level falsified literal %d in", lit);
falsified++;
}
}
if (satisfied)
return 1;
else if (falsified)
return -1;
else
return 0;
}
// Assume that the clause is not root level satisfied but contains a literal
// set to false (root level falsified literal), so it can be shrunken. The
// clause data is not actually reallocated at this point to avoid dealing
// with issues of special policies for watching binary clauses or whether a
// clause is extended or not. Only its size field is adjusted accordingly
// after flushing out root level falsified literals.
void Internal::remove_falsified_literals (Clause *c) {
const const_literal_iterator end = c->end ();
const_literal_iterator i;
int num_non_false = 0;
for (i = c->begin (); num_non_false < 2 && i != end; i++)
if (fixed (*i) >= 0)
num_non_false++;
if (num_non_false < 2)
return;
if (proof) {
// Flush changes the clause id, external forgettables need to be
// marked here (or the new id could be used instead of old one)
if (opts.check && is_external_forgettable (c->id))
mark_garbage_external_forgettable (c->id);
proof->flush_clause (c);
}
literal_iterator j = c->begin ();
for (i = j; i != end; i++) {
const int lit = *j++ = *i, tmp = fixed (lit);
assert (tmp <= 0);
if (tmp >= 0)
continue;
LOG ("flushing %d", lit);
j--;
}
stats.collected += shrink_clause (c, j - c->begin ());
}
// If there are new units (fixed variables) since the last garbage
// collection we go over all clauses, mark satisfied ones as garbage and
// flush falsified literals. Otherwise if no new units have been generated
// since the last garbage collection just skip this step.
void Internal::mark_satisfied_clauses_as_garbage () {
if (last.collect.fixed >= stats.all.fixed)
return;
last.collect.fixed = stats.all.fixed;
LOG ("marking satisfied clauses and removing falsified literals");
for (const auto &c : clauses) {
if (c->garbage)
continue;
const int tmp = clause_contains_fixed_literal (c);
if (tmp > 0)
mark_garbage (c);
else if (tmp < 0)
remove_falsified_literals (c);
}
}
/*------------------------------------------------------------------------*/
// Reason clauses can not be collected.
//
// We protect reasons before and release protection after garbage collection
// (actually within garbage collection).
//
// For 'reduce' we still need to make sure that all clauses which should not
// be removed are marked as such and thus we need to call it before marking
// clauses to be flushed.
void Internal::protect_reasons () {
LOG ("protecting reason clauses of all assigned variables on trail");
assert (!protected_reasons);
#ifdef LOGGING
size_t count = 0;
#endif
for (const auto &lit : trail) {
if (!active (lit))
continue;
assert (val (lit));
Var &v = var (lit);
assert (v.level > 0);
Clause *reason = v.reason;
if (!reason)
continue;
if (reason == external_reason)
continue;
LOG (reason, "protecting assigned %d reason %p", lit, (void *) reason);
assert (!reason->reason);
reason->reason = true;
#ifdef LOGGING
count++;
#endif
}
LOG ("protected %zd reason clauses referenced on trail", count);
protected_reasons = true;
}
/*------------------------------------------------------------------------*/
// After garbage collection we reset the 'reason' flag of the reasons
// of assigned literals on the trail.
void Internal::unprotect_reasons () {
LOG ("unprotecting reasons clauses of all assigned variables on trail");
assert (protected_reasons);
#ifdef LOGGING
size_t count = 0;
#endif
for (const auto &lit : trail) {
if (!active (lit))
continue;
assert (val (lit));
Var &v = var (lit);
assert (v.level > 0);
Clause *reason = v.reason;
if (!reason)
continue;
if (reason == external_reason)
continue;
LOG (reason, "unprotecting assigned %d reason %p", lit,
(void *) reason);
assert (reason->reason);
reason->reason = false;
#ifdef LOGGING
count++;
#endif
}
LOG ("unprotected %zd reason clauses referenced on trail", count);
protected_reasons = false;
}
/*------------------------------------------------------------------------*/
// Update occurrence lists before deleting garbage clauses in the context of
// preprocessing, e.g., during bounded variable elimination 'elim'. The
// result is the number of remaining clauses, which in this context means
// the number of non-garbage clauses.
size_t Internal::flush_occs (int lit) {
Occs &os = occs (lit);
const const_occs_iterator end = os.end ();
occs_iterator j = os.begin ();
const_occs_iterator i;
size_t res = 0;
Clause *c;
for (i = j; i != end; i++) {
c = *i;
if (c->collect ())
continue;
*j++ = c->moved ? c->copy : c;
// assert (!c->redundant); // -> not true in sweeping
res++;
}
os.resize (j - os.begin ());
shrink_occs (os);
return res;
}
// Update watch lists before deleting garbage clauses in the context of
// 'reduce' where we watch and no occurrence lists. We have to protect
// reason clauses not be collected and thus we have this additional check
// hidden in 'Clause.collect', which for the root level context of
// preprocessing is actually redundant.
inline void Internal::flush_watches (int lit, Watches &saved) {
assert (saved.empty ());
Watches &ws = watches (lit);
const const_watch_iterator end = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i;
for (i = j; i != end; i++) {
Watch w = *i;
Clause *c = w.clause;
if (c->collect ())
continue;
if (c->moved)
c = w.clause = c->copy;
w.size = c->size;
const int new_blit_pos = (c->literals[0] == lit);
LOG (c, "clause in flush_watch starting from %d", lit);
assert (c->literals[!new_blit_pos] == lit); /*FW1*/
w.blit = c->literals[new_blit_pos];
if (w.binary ())
*j++ = w;
else
saved.push_back (w);
}
ws.resize (j - ws.begin ());
for (const auto &w : saved)
ws.push_back (w);
saved.clear ();
shrink_vector (ws);
}
void Internal::flush_all_occs_and_watches () {
if (occurring ())
for (auto idx : vars)
flush_occs (idx), flush_occs (-idx);
if (watching ()) {
Watches tmp;
for (auto idx : vars)
flush_watches (idx, tmp), flush_watches (-idx, tmp);
}
}
/*------------------------------------------------------------------------*/
void Internal::update_reason_references () {
LOG ("update assigned reason references");
#ifdef LOGGING
size_t count = 0;
#endif
for (auto &lit : trail) {
if (!active (lit))
continue;
Var &v = var (lit);
Clause *c = v.reason;
if (!c)
continue;
if (c == external_reason)
continue;
LOG (c, "updating assigned %d reason", lit);
assert (c->reason);
assert (c->moved);
Clause *d = c->copy;
v.reason = d;
#ifdef LOGGING
count++;
#endif
}
LOG ("updated %zd assigned reason references", count);
}
/*------------------------------------------------------------------------*/
// This is a simple garbage collector which does not move clauses. It needs
// less space than the arena based clause allocator, but is not as cache
// efficient, since the copying garbage collector can put clauses together
// which are likely accessed after each other.
void Internal::delete_garbage_clauses () {
flush_all_occs_and_watches ();
LOG ("deleting garbage clauses");
#ifndef QUIET
int64_t collected_bytes = 0, collected_clauses = 0;
#endif
const auto end = clauses.end ();
auto j = clauses.begin (), i = j;
while (i != end) {
Clause *c = *j++ = *i++;
if (!c->collect ())
continue;
#ifndef QUIET
collected_bytes += c->bytes ();
collected_clauses++;
#endif
delete_clause (c);
j--;
}
clauses.resize (j - clauses.begin ());
shrink_vector (clauses);
PHASE ("collect", stats.collections,
"collected %" PRId64 " bytes of %" PRId64 " garbage clauses",
collected_bytes, collected_clauses);
}
/*------------------------------------------------------------------------*/
// This is the start of the copying garbage collector using the arena. At
// the core is the following function, which copies a clause to the 'to'
// space of the arena. Be careful if this clause is a reason of an
// assignment. In that case update the reason reference.
//
void Internal::copy_clause (Clause *c) {
LOG (c, "moving");
assert (!c->moved);
char *p = (char *) c;
char *q = arena.copy (p, c->bytes ());
c->copy = (Clause *) q;
c->moved = true;
LOG ("copied clause[%" PRId64 "] from %p to %p", c->id, (void *) c,
(void *) c->copy);
}
// This is the moving garbage collector.
void Internal::copy_non_garbage_clauses () {
size_t collected_clauses = 0, collected_bytes = 0;
size_t moved_clauses = 0, moved_bytes = 0;
// First determine 'moved_bytes' and 'collected_bytes'.
//
for (const auto &c : clauses)
if (!c->collect ())
moved_bytes += c->bytes (), moved_clauses++;
else
collected_bytes += c->bytes (), collected_clauses++;
PHASE ("collect", stats.collections,
"moving %zd bytes %.0f%% of %zd non garbage clauses", moved_bytes,
percent (moved_bytes, collected_bytes + moved_bytes),
moved_clauses);
(void) moved_clauses, (void) collected_clauses, (void) collected_bytes;
// Prepare 'to' space of size 'moved_bytes'.
//
arena.prepare (moved_bytes);
// Keep clauses in arena in the same order.
//
if (opts.arenacompact)
for (const auto &c : clauses)
if (!c->collect () && arena.contains (c))
copy_clause (c);
if (opts.arenatype == 1 || !watching ()) {
// Localize according to current clause order.
// If the option 'opts.arenatype == 1' is set, then this means the
// solver uses the original order of clauses. If there are no watches,
// we can not use the watched based copying policies below. This
// happens if garbage collection is triggered during bounded variable
// elimination.
// Copy clauses according to the order of calling 'copy_clause', which
// in essence just gives a compacting garbage collector, since their
// relative order is kept, and actually already gives the largest
// benefit due to better cache locality.
for (const auto &c : clauses)
if (!c->moved && !c->collect ())
copy_clause (c);
} else if (opts.arenatype == 2) {
// Localize according to (original) variable order.
// This is almost the version used by MiniSAT and descendants.
// Our version uses saved phases too.
for (int sign = -1; sign <= 1; sign += 2)
for (auto idx : vars)
for (const auto &w : watches (sign * likely_phase (idx)))
if (!w.clause->moved && !w.clause->collect ())
copy_clause (w.clause);
} else {
// Localize according to decision queue order.
// This is the default for search. It allocates clauses in the order of
// the decision queue and also uses saved phases. It seems faster than
// the MiniSAT version and thus we keep 'opts.arenatype == 3'.
assert (opts.arenatype == 3);
for (int sign = -1; sign <= 1; sign += 2)
for (int idx = queue.last; idx; idx = link (idx).prev)
for (const auto &w : watches (sign * likely_phase (idx)))
if (!w.clause->moved && !w.clause->collect ())
copy_clause (w.clause);
}
// Do not forget to move clauses which are not watched, which happened in
// a rare situation, and now is only left as defensive code.
//
for (const auto &c : clauses)
if (!c->collect () && !c->moved)
copy_clause (c);
flush_all_occs_and_watches ();
update_reason_references ();
// Replace and flush clause references in 'clauses'.
//
const auto end = clauses.end ();
auto j = clauses.begin (), i = j;
for (; i != end; i++) {
Clause *c = *i;
if (c->collect ())
delete_clause (c);
else
assert (c->moved), *j++ = c->copy, deallocate_clause (c);
}
clauses.resize (j - clauses.begin ());
if (clauses.size () < clauses.capacity () / 2)
shrink_vector (clauses);
if (opts.arenasort)
rsort (clauses.begin (), clauses.end (), pointer_rank ());
// Release 'from' space completely and then swap 'to' with 'from'.
//
arena.swap ();
PHASE ("collect", stats.collections,
"collected %zd bytes %.0f%% of %zd garbage clauses",
collected_bytes,
percent (collected_bytes, collected_bytes + moved_bytes),
collected_clauses);
}
/*------------------------------------------------------------------------*/
// Maintaining clause statistics is complex and error prone but necessary
// for proper scheduling of garbage collection, particularly during bounded
// variable elimination. With this function we can check whether these
// statistics are updated correctly.
void Internal::check_clause_stats () {
#ifndef NDEBUG
int64_t irredundant = 0, redundant = 0, total = 0, irrlits = 0;
for (const auto &c : clauses) {
if (c->garbage)
continue;
if (c->redundant)
redundant++;
else
irredundant++;
if (!c->redundant)
irrlits += c->size;
total++;
}
assert (stats.current.irredundant == irredundant);
assert (stats.current.redundant == redundant);
assert (stats.current.total == total);
assert (stats.irrlits == irrlits);
#endif
}
/*------------------------------------------------------------------------*/
// only delete binary clauses from watch list that are already mark as
// deleted.
void Internal::remove_garbage_binaries () {
if (unsat)
return;
START (collect);
if (!protected_reasons)
protect_reasons ();
int backtrack_level = level + 1;
Watches saved;
for (auto v : vars) {
for (auto lit : {-v, v}) {
assert (saved.empty ());
Watches &ws = watches (lit);
const const_watch_iterator end = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i;
for (i = j; i != end; i++) {
Watch w = *i;
*j++ = w;
Clause *c = w.clause;
COVER (!w.binary () && c->size == 2);
if (!w.binary ())
continue;
if (c->reason && c->garbage) {
COVER (true);
assert (c->size == 2);
backtrack_level =
min (backtrack_level, var (c->literals[0]).level);
LOG ("need to backtrack to before level %d", backtrack_level);
--j;
continue;
}
if (!c->collect ())
continue;
LOG (c, "removing from watch list");
--j;
}
ws.resize (j - ws.begin ());
shrink_vector (ws);
}
}
delete_garbage_clauses ();
unprotect_reasons ();
if (backtrack_level - 1 < level)
backtrack (backtrack_level - 1);
STOP (collect);
}
/*------------------------------------------------------------------------*/
bool Internal::arenaing () { return opts.arena && (stats.collections > 1); }
void Internal::garbage_collection () {
if (unsat)
return;
START (collect);
report ('G', 1);
stats.collections++;
mark_satisfied_clauses_as_garbage ();
if (!protected_reasons)
protect_reasons ();
if (arenaing ())
copy_non_garbage_clauses ();
else
delete_garbage_clauses ();
check_clause_stats ();
check_var_stats ();
unprotect_reasons ();
report ('C', 1);
STOP (collect);
}
} // namespace CaDiCaL

551
src/sat/cadical/compact.cpp Normal file
View File

@ -0,0 +1,551 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Compacting removes holes generated by inactive variables (fixed,
// eliminated, substituted or pure) by mapping active variables indices down
// to a contiguous interval of indices.
/*------------------------------------------------------------------------*/
bool Internal::compacting () {
if (level)
return false;
if (!opts.compact)
return false;
if (stats.conflicts < lim.compact)
return false;
int inactive = max_var - active ();
assert (inactive >= 0);
if (!inactive)
return false;
if (inactive < opts.compactmin)
return false;
return inactive >= (1e-3 * opts.compactlim) * max_var;
}
/*------------------------------------------------------------------------*/
struct Mapper {
Internal *internal;
int new_max_var; // New 'max_var' after compacting.
int *table; // Old variable index to new literal map.
int first_fixed; // First fixed variable index.
int map_first_fixed; // Mapped literal of first fixed variable.
signed char first_fixed_val; // Value of first fixed variable.
size_t new_vsize;
/*----------------------------------------------------------------------*/
// We produce a compacting garbage collector like map of old 'src' to
// new 'dst' variables. Inactive variables are just skipped except for
// fixed ones which will be mapped to the first fixed variable (in the
// appropriate phase). This avoids to handle the case 'fixed value'
// separately as it is done in Lingeling, where fixed variables are
// mapped to the internal variable '1'.
//
Mapper (Internal *i)
: internal (i), new_max_var (0), first_fixed (0), map_first_fixed (0),
first_fixed_val (0) {
table = new int[internal->max_var + 1u];
clear_n (table, internal->max_var + 1u);
assert (!internal->level);
for (auto src : internal->vars) {
const Flags &f = internal->flags (src);
if (f.active ())
table[src] = ++new_max_var;
else if (f.fixed () && !first_fixed)
table[first_fixed = src] = map_first_fixed = ++new_max_var;
}
first_fixed_val = first_fixed ? internal->val (first_fixed) : 0;
new_vsize = new_max_var + 1u;
}
~Mapper () { delete[] table; }
/*----------------------------------------------------------------------*/
// Map old variable indices. A result of zero means not mapped.
//
int map_idx (int src) {
assert (0 < src);
assert (src <= internal->max_var);
const int res = table[src];
assert (res <= new_max_var);
return res;
}
/*----------------------------------------------------------------------*/
// The 'map_idx' above is just a look-up into the 'table'. Here we have
// to care about signedness of 'src', and in addition that fixed variables
// have all to be mapped to the first fixed variable 'first_fixed'.
//
int map_lit (int src) {
int res = map_idx (abs (src));
if (!res) {
const signed char tmp = internal->val (src);
if (tmp) {
assert (first_fixed);
res = map_first_fixed;
if (tmp != first_fixed_val)
res = -res;
}
} else if ((src) < 0)
res = -res;
assert (abs (res) <= new_max_var);
return res;
}
/*----------------------------------------------------------------------*/
// Map positive variable indices in vector.
//
template <class T> void map_vector (vector<T> &v) {
for (auto src : internal->vars) {
const int dst = map_idx (src);
if (!dst)
continue;
assert (0 < dst);
assert (dst <= src);
v[dst] = v[src];
}
v.resize (new_vsize);
shrink_vector (v);
}
/*----------------------------------------------------------------------*/
// Map positive and negative variable indices in two-sided vector.
//
template <class T> void map2_vector (vector<T> &v) {
for (auto src : internal->vars) {
const int dst = map_idx (src);
if (!dst)
continue;
assert (0 < dst);
assert (dst <= src);
v[2 * dst] = v[2 * src];
v[2 * dst + 1] = v[2 * src + 1];
}
v.resize (2 * new_vsize);
shrink_vector (v);
}
/*----------------------------------------------------------------------*/
// Map a vector of literals, flush inactive literals, then resize and
// shrink it to fit the new size after flushing.
//
void map_flush_and_shrink_lits (vector<int> &v) {
const auto end = v.end ();
auto j = v.begin (), i = j;
for (; i != end; i++) {
const int src = *i;
int dst = map_idx (abs (src));
assert (abs (dst) <= abs (src));
if (!dst)
continue;
if (src < 0)
dst = -dst;
*j++ = dst;
}
v.resize (j - v.begin ());
shrink_vector (v);
}
};
/*------------------------------------------------------------------------*/
static signed char *ignore_clang_analyze_memory_leak_warning;
void Internal::compact () {
START (compact);
assert (active () < max_var);
stats.compacts++;
assert (!level);
assert (!unsat);
assert (!conflict);
assert (clause.empty ());
assert (levels.empty ());
assert (analyzed.empty ());
assert (minimized.empty ());
assert (control.size () == 1);
assert (propagated == trail.size ());
garbage_collection ();
Mapper mapper (this);
if (mapper.first_fixed)
LOG ("found first fixed %d",
sign (mapper.first_fixed_val) * mapper.first_fixed);
else
LOG ("no variable fixed");
if (!assumptions.empty ()) {
assert (!external->assumptions.empty ());
LOG ("temporarily reset internal assumptions");
reset_assumptions ();
}
const bool is_constraint = !constraint.empty ();
if (is_constraint) {
assert (!external->constraint.empty ());
LOG ("temporarily reset internal constraint");
reset_constraint ();
}
/*======================================================================*/
// In this first part we only map stuff without reallocation / shrinking.
/*======================================================================*/
// Flush the external indices. This has to occur before we map 'vals'.
// Also fixes external units.
//
for (auto eidx : external->vars) {
int src = external->e2i[eidx];
if (!src) {
continue;
}
if (lrat || frat) {
assert (eidx > 0);
assert (external->ext_units.size () >= (size_t) 2 * eidx + 1);
int64_t id1 = external->ext_units[2 * eidx];
int64_t id2 = external->ext_units[2 * eidx + 1];
assert (!id1 || !id2);
if (!id1 && !id2) {
int64_t new_id1 = unit_clauses (2 * src);
int64_t new_id2 = unit_clauses (2 * src + 1);
external->ext_units[2 * eidx] = new_id1;
external->ext_units[2 * eidx + 1] = new_id2;
}
}
int dst = mapper.map_lit (src);
LOG ("compact %" PRId64
" maps external %d to internal %d from internal %d",
stats.compacts, eidx, dst, src);
external->e2i[eidx] = dst;
}
// Delete garbage units. Needs to occur before resizing unit_clauses
//
if (lrat || frat) {
for (auto src : internal->vars) {
const int dst = mapper.map_idx (src);
assert (dst <= src);
const signed char tmp = internal->val (src);
if (!dst && !tmp) {
unit_clauses (2 * src) = 0;
unit_clauses (2 * src + 1) = 0;
continue;
}
if (!tmp || src == mapper.first_fixed) {
assert (0 < dst);
if (dst == src)
continue;
assert (!unit_clauses (2 * dst) && !unit_clauses (2 * dst + 1));
unit_clauses (2 * dst) = unit_clauses (2 * src);
unit_clauses (2 * dst + 1) = unit_clauses (2 * src + 1);
unit_clauses (2 * src) = 0;
unit_clauses (2 * src + 1) = 0;
continue;
}
int64_t id = unit_clauses (2 * src);
int lit = src;
if (!id) {
id = unit_clauses (2 * src + 1);
lit = -lit;
}
unit_clauses (2 * src) = 0;
unit_clauses (2 * src + 1) = 0;
assert (id);
}
unit_clauses_idx.resize (2 * mapper.new_vsize);
shrink_vector (unit_clauses_idx);
}
// Map the literals in all clauses.
//
for (const auto &c : clauses) {
assert (!c->garbage);
for (auto &src : *c) {
assert (!val (src));
int dst;
dst = mapper.map_lit (src);
assert (dst || c->garbage);
src = dst;
}
}
// Map the blocking literals in all watches.
//
if (!wtab.empty ())
for (auto lit : lits)
for (auto &w : watches (lit))
w.blit = mapper.map_lit (w.blit);
// We first flush inactive variables and map the links in the queue. This
// has to be done before we map the actual links data structure 'links'.
{
int prev = 0, mapped_prev = 0, next;
for (int idx = queue.first; idx; idx = next) {
next = links[idx].next;
if (idx == mapper.first_fixed)
continue;
const int dst = mapper.map_idx (idx);
if (!dst)
continue;
assert (active (idx));
if (prev)
links[prev].next = dst;
else
queue.first = dst;
links[idx].prev = mapped_prev;
mapped_prev = dst;
prev = idx;
}
if (prev)
links[prev].next = 0;
else
queue.first = 0;
queue.unassigned = queue.last = mapped_prev;
}
/*======================================================================*/
// In the second part we map, flush and shrink arrays.
/*======================================================================*/
assert (trail.size () == num_assigned);
mapper.map_flush_and_shrink_lits (trail);
propagated = trail.size ();
num_assigned = trail.size ();
if (mapper.first_fixed) {
assert (trail.size () == 1);
var (mapper.first_fixed).trail = 0; // before mapping 'vtab'
} else
assert (trail.empty ());
if (!probes.empty ())
mapper.map_flush_and_shrink_lits (probes);
if (!sweep_schedule.empty ())
mapper.map_flush_and_shrink_lits (sweep_schedule);
/*======================================================================*/
// In the third part we map stuff and also reallocate memory.
/*======================================================================*/
// Now we continue in reverse order of allocated bytes, e.g., see
// 'Internal::enlarge' which reallocates in order of allocated bytes.
mapper.map_vector (ftab);
mapper.map_vector (parents);
mapper.map_vector (marks);
mapper.map_vector (phases.saved);
mapper.map_vector (phases.forced);
mapper.map_vector (phases.target);
mapper.map_vector (phases.best);
mapper.map_vector (phases.prev);
mapper.map_vector (phases.min);
// Special code for 'frozentab'.
//
for (auto src : vars) {
const int dst = abs (mapper.map_lit (src));
if (!dst)
continue;
if (src == dst)
continue;
assert (dst < src);
if ((size_t) src >= frozentab.size ())
break;
if ((size_t) dst >= frozentab.size ())
break;
frozentab[dst] += frozentab[src];
frozentab[src] = 0;
}
frozentab.resize (min (frozentab.size (), mapper.new_vsize));
shrink_vector (frozentab);
// Special code for 'relevanttab'.
//
if (external) {
for (auto src : vars) {
const int dst = abs (mapper.map_lit (src));
if (!dst)
continue;
if (src == dst)
continue;
assert (dst < src);
relevanttab[dst] += relevanttab[src];
relevanttab[src] = 0;
}
relevanttab.resize (mapper.new_vsize);
shrink_vector (relevanttab);
}
/*----------------------------------------------------------------------*/
if (!external->assumptions.empty ()) {
for (const auto &elit : external->assumptions) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
assert (eidx <= external->max_var);
int ilit = external->e2i[eidx];
assert (ilit); // Because we froze all!!!
if (elit < 0)
ilit = -ilit;
assume (ilit);
}
PHASE ("compact", stats.compacts, "reassumed %zd external assumptions",
external->assumptions.size ());
}
// Special case for 'val' as for 'val' we trade branch less code for
// memory and always allocated an [-maxvar,...,maxvar] array.
{
signed char *new_vals = new signed char[2 * mapper.new_vsize];
ignore_clang_analyze_memory_leak_warning = new_vals;
new_vals += mapper.new_vsize;
for (auto src : vars)
new_vals[-mapper.map_idx (src)] = vals[-src];
for (auto src : vars)
new_vals[mapper.map_idx (src)] = vals[src];
new_vals[0] = 0;
vals -= vsize;
delete[] vals;
vals = new_vals;
vsize = mapper.new_vsize;
}
// 'constrain' uses 'val', so this code has to be after remapping that
if (is_constraint) {
assert (!level);
assert (!external->constraint.back ());
for (auto elit : external->constraint) {
assert (elit != INT_MIN);
int eidx = abs (elit);
assert (eidx <= external->max_var);
int ilit = external->e2i[eidx];
assert (!ilit == !elit);
if (elit < 0)
ilit = -ilit;
LOG ("re adding lit external %d internal %d to constraint", elit,
ilit);
constrain (ilit);
}
PHASE ("compact", stats.compacts,
"added %zd external literals to constraint",
external->constraint.size () - 1);
}
mapper.map_vector (i2e);
mapper.map2_vector (ptab);
mapper.map_vector (btab);
mapper.map_vector (gtab);
mapper.map_vector (links);
mapper.map_vector (vtab);
if (!ntab.empty ())
mapper.map2_vector (ntab);
if (!wtab.empty ())
mapper.map2_vector (wtab);
if (!otab.empty ())
mapper.map2_vector (otab);
if (!rtab.empty ())
mapper.map2_vector (rtab);
if (!big.empty ())
mapper.map2_vector (big);
/*======================================================================*/
// In the fourth part we map the binary heap for scores.
/*======================================================================*/
// The simplest way to map a binary heap is to get all elements from the
// heap and reinsert them. This could be slightly improved in terms of
// speed if we add a 'flush (int * map)' function to 'Heap', but that is
// pretty complicated and would require that the 'Heap' knows that mapped
// elements with 'zero' destination should be flushed.
vector<int> saved;
assert (saved.empty ());
if (!scores.empty ()) {
while (!scores.empty ()) {
const int src = scores.front ();
scores.pop_front ();
const int dst = mapper.map_idx (src);
if (!dst)
continue;
if (src == mapper.first_fixed)
continue;
saved.push_back (dst);
}
scores.erase ();
}
mapper.map_vector (stab);
if (!saved.empty ()) {
for (const auto idx : saved)
scores.push_back (idx);
scores.shrink ();
}
/*----------------------------------------------------------------------*/
PHASE ("compact", stats.compacts,
"reducing internal variables from %d to %d", max_var,
mapper.new_max_var);
/*----------------------------------------------------------------------*/
// Need to adjust the target and best assigned counters too.
size_t new_target_assigned = 0, new_best_assigned = 0;
for (auto idx : Range (mapper.new_max_var)) {
if (phases.target[idx])
new_target_assigned++;
if (phases.best[idx])
new_best_assigned++;
}
LOG ("reset target assigned from %zd to %zd", target_assigned,
new_target_assigned);
LOG ("reset best assigned from %zd to %zd", best_assigned,
new_best_assigned);
target_assigned = new_target_assigned;
best_assigned = new_best_assigned;
no_conflict_until = 0;
notified = 0;
INIT_EMA (averages.current.trail.fast, opts.ematrailfast);
INIT_EMA (averages.current.trail.slow, opts.ematrailslow);
/*----------------------------------------------------------------------*/
max_var = mapper.new_max_var;
stats.unused = 0;
stats.inactive = stats.now.fixed = mapper.first_fixed ? 1 : 0;
stats.now.substituted = stats.now.eliminated = stats.now.pure = 0;
check_var_stats ();
int64_t delta = opts.compactint * (stats.compacts + 1);
lim.compact = stats.conflicts + delta;
PHASE ("compact", stats.compacts,
"new compact limit %" PRId64 " after %" PRId64 " conflicts",
lim.compact, delta);
STOP (compact);
}
} // namespace CaDiCaL

View File

@ -0,0 +1,940 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Globally blocked clause elimination (which we call here 'conditioning')
// is described first in the PhD thesis of Benjamin Kiesl from 2019. An
// extended version, which in particular describes the algorithm implemented
// below is in our invited ATVA'19 paper [KieslHeuleBiere-ATVA'19]. This
// accordingly needs witnesses consisting potentially of more than one
// literal. It is the first technique implemented in CaDiCaL with this
// feature (PR clause elimination thus should work in principle too).
// Basically globally blocked clauses are like set blocked clauses, except
// that the witness cube (of literals to be flipped during reconstruction)
// can contain variables which are not in the blocked clause. This
// can simulate some interesting global optimizations like 'headlines' from
// the FAN algorithm for ATPG. The technique was actually motivated to
// simulate this optimization. It turns out that globally blocked clauses
// can be seen as 'conditional autarkies', where in essence the condition
// cube is the negation of the globally blocked redundant clause (it
// needs to contain one autarky literal though) and the autarky part
// represents the witness.
/*------------------------------------------------------------------------*/
// Elimination of globally blocked clauses is first tried in regular
// intervals in terms of the number of conflicts. Then the main heuristics
// is to trigger 'condition' if the decision level is above the current
// moving average of the back jump level.
// TODO We might need to consider less frequent conditioning.
bool Internal::conditioning () {
if (!opts.condition)
return false;
if (!preprocessing && !opts.inprocessing)
return false;
if (preprocessing)
assert (lim.preprocessing);
// Triggered in regular 'opts.conditionint' conflict intervals.
//
if (lim.condition > stats.conflicts)
return false;
if (!level)
return false; // One decision necessary.
if (level <= averages.current.jump)
return false; // Main heuristic.
if (!stats.current.irredundant)
return false;
double remain = active ();
if (!remain)
return false;
double ratio = stats.current.irredundant / remain;
return ratio <= opts.conditionmaxrat;
}
/*------------------------------------------------------------------------*/
// We start with the current assignment and then temporarily unassign
// literals. They are reassigned afterwards. The global state of the CDCL
// solver should not change though. Thus we copied from 'search_unassign'
// in 'backtrack.cpp' what is needed to unassign literals and then from
// 'search_assign' in 'propagate.cpp' what is needed for reassigning
// literals, but restricted the copied code to only updating the actual
// assignment (in 'vals') and not changing anything else.
// We use temporarily unassigning for two purposes. First, if a conditional
// literal does not occur negated in a candidate clause it is unassigned.
// Second, as a minor optimization, we first unassign all root-level
// assigned (fixed) literals, to avoid checking the decision level of
// literals during the procedure.
void Internal::condition_unassign (int lit) {
LOG ("condition unassign %d", lit);
assert (val (lit) > 0);
set_val (lit, 0);
}
void Internal::condition_assign (int lit) {
LOG ("condition assign %d", lit);
assert (!val (lit));
set_val (lit, 1);
}
/*------------------------------------------------------------------------*/
// The current partition into conditional part and autarky part during
// refinement is represented through a conditional bit in 'marks'.
inline bool Internal::is_conditional_literal (int lit) const {
return val (lit) > 0 && getbit (lit, 0);
}
inline bool Internal::is_autarky_literal (int lit) const {
return val (lit) > 0 && !getbit (lit, 0);
}
inline void Internal::mark_as_conditional_literal (int lit) {
LOG ("marking %d as conditional literal", lit);
assert (val (lit) > 0);
setbit (lit, 0);
assert (is_conditional_literal (lit));
assert (!is_autarky_literal (lit));
}
inline void Internal::unmark_as_conditional_literal (int lit) {
LOG ("unmarking %d as conditional literal", lit);
assert (is_conditional_literal (lit));
unsetbit (lit, 0);
}
/*------------------------------------------------------------------------*/
// We also need to know the literals which are in the current clause. These
// are just marked (also in 'marks' but with the (signed) upper two bits).
// We need a signed mark here, since we have to distinguish positive and
// negative occurrences of literals in the candidate clause.
inline bool Internal::is_in_candidate_clause (int lit) const {
return marked67 (lit) > 0;
}
inline void Internal::mark_in_candidate_clause (int lit) {
LOG ("marking %d as literal of the candidate clause", lit);
mark67 (lit);
assert (is_in_candidate_clause (lit));
assert (!is_in_candidate_clause (-lit));
}
inline void Internal::unmark_in_candidate_clause (int lit) {
LOG ("unmarking %d as literal of the candidate clause", lit);
assert (is_in_candidate_clause (lit));
unmark67 (lit);
}
/*------------------------------------------------------------------------*/
struct less_conditioned {
bool operator() (Clause *a, Clause *b) {
return !a->conditioned && b->conditioned;
}
};
// This is the function for eliminating globally blocked clauses. It is
// triggered during CDCL search according to 'conditioning' above and uses
// the current assignment as basis to find globally blocked clauses.
long Internal::condition_round (long delta) {
long limit;
#ifndef QUIET
long props = 0;
#endif
if (LONG_MAX - delta < stats.condprops)
limit = LONG_MAX;
else
limit = stats.condprops + delta;
size_t initial_trail_level = trail.size ();
int initial_level = level;
LOG ("initial trail level %zd", initial_trail_level);
protect_reasons ();
#if defined(LOGGING) || !defined(NDEBUG)
int additionally_assigned = 0;
#endif
for (auto idx : vars) {
const signed char tmp = val (idx);
Var &v = var (idx);
if (tmp) {
if (v.level) {
const int lit = tmp < 0 ? -idx : idx;
if (!active (idx)) {
LOG ("temporarily unassigning inactive literal %d", lit);
condition_unassign (lit);
}
if (frozen (idx)) {
LOG ("temporarily unassigning frozen literal %d", lit);
condition_unassign (lit);
}
}
} else if (frozen (idx)) {
LOG ("keeping frozen literal %d unassigned", idx);
} else if (!active (idx)) {
LOG ("keeping inactive literal %d unassigned", idx);
} else { // if (preprocessing) {
if (initial_level == level) {
level++;
LOG ("new condition decision level");
}
const int lit = decide_phase (idx, true);
condition_assign (lit);
v.level = level;
trail.push_back (lit);
#if defined(LOGGING) || !defined(NDEBUG)
additionally_assigned++;
#endif
}
}
LOG ("assigned %d additional literals", additionally_assigned);
// We compute statistics about the size of the assignments.
//
// The initial assignment consists of the non-root-level assigned literals
// split into a conditional and an autarky part. The conditional part
// consists of literals assigned true and occurring negated in a clause
// (touch the clause), which does not contain another literal assigned to
// true. This initial partition is the same for all refinements used in
// checking whether a candidate clause is globally blocked.
//
// For each candidate clause some of the conditional literals have to be
// unassigned, and the autarky is shrunken by turning some of the autarky
// literals into conditional literals (which might get unassigned in a
// later refinement though).
//
// The fix-point of this procedure produces a final assignment, which
// consists of the remaining assigned literals, again split into a
// conditional and an autarky part.
//
struct {
size_t assigned, conditional, autarky;
} initial, remain;
initial.assigned = 0;
for (auto idx : vars) {
const signed char tmp = val (idx);
if (!tmp)
continue;
if (!var (idx).level)
continue;
LOG ("initial assignment %ds", tmp < 0 ? -idx : idx);
initial.assigned++;
}
PHASE ("condition", stats.conditionings, "initial assignment of size %zd",
initial.assigned);
// For each candidate clause we refine the assignment (monotonically),
// by unassigning some conditional literals and turning some autarky
// literals into conditionals.
//
// As the conditional part is usually smaller than the autarky part our
// implementation only explicitly maintains the initial conditional part,
// with conditional bit set to true through 'mark_as_conditional_literal'.
// The autarky part consists of all literals assigned true which do not
// have their conditional bit set to true. Since in both cases the
// literal has to be assigned true, we only need a single bit for both the
// literal as well as its negation (it does not have to be 'signed').
//
vector<int> conditional;
vector<Clause *> candidates; // Gather candidate clauses.
#ifndef QUIET
size_t watched = 0; // Number of watched clauses.
#endif
initial.autarky = initial.assigned; // Initially all are in autarky
initial.conditional = 0; // and none in conditional part.
// Upper bound on the number of watched clauses. In principle one could
// use 'SIZE_MAX' but this is not available by default (yet).
//
const size_t size_max = clauses.size () + 1;
// Initialize additional occurrence lists.
//
init_occs ();
// Number of previously conditioned and unconditioned candidates.
//
size_t conditioned = 0, unconditioned = 0;
// Now go over all (non-garbage) irredundant clauses and check whether
// they are candidates, have to be watched, or whether they force the
// negation of some of their literals to be conditional initially.
//
for (const auto &c : clauses) {
if (c->garbage)
continue; // Can already be ignored.
if (c->redundant)
continue; // Ignore redundant clauses too.
// First determine the following numbers for the candidate clause
// (restricted to non-root-level assignments).
//
int positive = 0; // Number true literals.
int negative = 0; // Number false literals.
int watch = 0; // True Literal to watch.
//
size_t minsize = size_max; // Number of occurrences of 'watch'.
//
// But also ignore root-level satisfied but not yet garbage clauses.
//
bool satisfied = false; // Root level satisfied.
//
for (const_literal_iterator l = c->begin ();
!satisfied && l != c->end (); l++) {
const int lit = *l;
const signed char tmp = val (lit);
if (tmp && !var (lit).level)
satisfied = (tmp > 0);
else if (tmp < 0)
negative++;
else if (tmp > 0) {
const size_t size = occs (lit).size ();
if (size < minsize)
watch = lit, minsize = size;
positive++;
}
}
if (satisfied) { // Ignore root-level satisfied clauses.
mark_garbage (c); // But mark them as garbage already now.
continue; // ... with next clause 'c'.
}
// Candidates are clauses with at least a positive literal in it.
//
if (positive > 0) {
LOG (c, "found %d positive literals in candidate", positive);
candidates.push_back (c);
if (c->conditioned)
conditioned++;
else
unconditioned++;
}
// Only one positive literal in each clauses with also at least one
// negative literal has to be watched in occurrence lists. These
// watched clauses will be checked to contain only negative literals as
// soon such a positive literal is unassigned. If this is the case
// these false literals have to be unassigned and potentially new
// conditional literals have to be determined.
//
// Note that only conditional literals are unassigned. However it does
// not matter that we might also watch autarky literals, because either
// such an autarky literal remains a witness that the clause is
// satisfied as long it remains an autarky literal. Otherwise at one
// point it becomes conditional and is unassigned, but then a
// replacement watch will be searched.
//
if (negative > 0 && positive > 0) {
LOG (c, "found %d negative literals in candidate", negative);
assert (watch);
assert (val (watch) > 0);
Occs &os = occs (watch);
assert (os.size () == minsize);
os.push_back (c);
#ifndef QUIET
watched++;
#endif
LOG (c, "watching %d with %zd occurrences in", watch, minsize);
}
// The initial global conditional part for the current assignment is
// extracted from clauses with only negative literals. It is the same
// for all considered candidate clauses. These negative literals make up
// the global conditional part, are marked here.
//
if (negative > 0 && !positive) {
size_t new_conditionals = 0;
for (const_literal_iterator l = c->begin (); l != c->end (); l++) {
const int lit = *l;
signed char tmp = val (lit);
if (!tmp)
continue;
assert (tmp < 0);
if (!var (lit).level)
continue; // Not unassigned yet!
if (is_conditional_literal (-lit))
continue;
mark_as_conditional_literal (-lit);
conditional.push_back (-lit);
new_conditionals++;
}
if (new_conditionals > 0)
LOG (c, "marked %zu negations of literals as conditional in",
new_conditionals);
initial.conditional += new_conditionals;
assert (initial.autarky >= new_conditionals);
initial.autarky -= new_conditionals;
}
} // End of loop over all clauses to collect candidates etc.
PHASE ("condition", stats.conditionings, "found %zd candidate clauses",
candidates.size ());
PHASE ("condition", stats.conditionings,
"watching %zu literals and clauses", watched);
PHASE ("condition", stats.conditionings,
"initially %zd conditional literals %.0f%%", initial.conditional,
percent (initial.conditional, initial.assigned));
PHASE ("condition", stats.conditionings,
"initially %zd autarky literals %.0f%%", initial.autarky,
percent (initial.autarky, initial.assigned));
#ifdef LOGGING
for (size_t i = 0; i < conditional.size (); i++) {
LOG ("initial conditional %d", conditional[i]);
assert (is_conditional_literal (conditional[i]));
}
for (size_t i = 0; i < trail.size (); i++)
if (is_autarky_literal (trail[i]))
LOG ("initial autarky %d", trail[i]);
#endif
assert (initial.conditional == conditional.size ());
assert (initial.assigned == initial.conditional + initial.autarky);
stats.condassinit += initial.assigned;
stats.condcondinit += initial.conditional;
stats.condautinit += initial.autarky;
stats.condassvars += active ();
// To speed-up and particularly simplify the code we unassign all
// root-level variables temporarily, actually all inactive assigned
// variables. This allows us to avoid tests on whether an assigned
// literal is actually root-level assigned and thus should be ignored (not
// considered to be assigned). For this to work we have to ignore root
// level satisfied clauses as done above. These are neither candidates
// nor have to be watched. Remaining originally root-level assigned
// literals in clauses are only set to false.
//
for (const auto &lit : trail)
if (fixed (lit))
condition_unassign (lit);
// Stack to save temporarily unassigned (conditional) literals.
//
vector<int> unassigned;
// Make sure to focus on clauses not tried before by marking clauses which
// have been checked before using the 'conditioned' bit of clauses. If all
// candidates have their bit set, we have to reset it. Since the
// assignment might be completely different then last time and thus also
// the set of candidates this method does not really exactly lead to a
// round robin scheme of scheduling clauses.
//
// TODO consider computing conditioned and unconditioned over all clauses.
//
assert (conditioned + unconditioned == candidates.size ());
if (conditioned && unconditioned) {
stable_sort (candidates.begin (), candidates.end (),
less_conditioned ());
PHASE ("condition", stats.conditionings,
"focusing on %zd candidates %.0f%% not tried last time",
unconditioned, percent (unconditioned, candidates.size ()));
} else if (conditioned && !unconditioned) {
for (auto const &c : candidates) {
assert (c->conditioned);
c->conditioned = false; // Reset 'conditioned' bit.
}
PHASE ("condition", stats.conditionings,
"all %zd candidates tried before", conditioned);
} else {
assert (!conditioned);
PHASE ("condition", stats.conditionings, "all %zd candidates are fresh",
unconditioned);
}
// TODO prune assignments further!
// And thus might result in less watched clauses.
// So watching should be done here and not earlier.
// Also, see below, we might need to consider the negation of unassigned
// literals in candidate clauses as being watched.
// Now try to block all candidate clauses.
//
long blocked = 0; // Number of Successfully blocked clauses.
//
#ifndef QUIET
size_t untried = candidates.size ();
#endif
for (const auto &c : candidates) {
if (initial.autarky <= 0)
break;
if (c->reason)
continue;
bool terminated_or_limit_hit = true;
if (terminated_asynchronously ())
LOG ("asynchronous termination detected");
else if (stats.condprops >= limit)
LOG ("condition propagation limit %ld hit", limit);
else
terminated_or_limit_hit = false;
if (terminated_or_limit_hit) {
PHASE ("condition", stats.conditionings,
"%zd candidates %.0f%% not tried after %ld propagations",
untried, percent (untried, candidates.size ()), props);
break;
}
#ifndef QUIET
untried--;
#endif
assert (!c->garbage);
assert (!c->redundant);
LOG (c, "candidate");
c->conditioned = 1; // Next time later.
// We watch an autarky literal in the clause, and can stop trying to
// globally block the clause as soon it turns into a conditional
// literal and we can not find another one. If the fix-point assignment
// is reached and we still have an autarky literal left the watched one
// is reported as witness for this clause being globally blocked.
//
int watched_autarky_literal = 0;
// First mark all true literals in the candidate clause and find an
// autarky literal which witnesses that this clause has still a chance
// to be globally blocked.
//
for (const_literal_iterator l = c->begin (); l != c->end (); l++) {
const int lit = *l;
mark_in_candidate_clause (lit);
if (watched_autarky_literal)
continue;
if (!is_autarky_literal (lit))
continue;
watched_autarky_literal = lit;
// TODO assign non-assigned literals to false?
// Which might need to trigger watching additional clauses.
}
if (!watched_autarky_literal) {
LOG ("no initial autarky literal found");
for (const_literal_iterator l = c->begin (); l != c->end (); l++)
unmark_in_candidate_clause (*l);
continue;
}
stats.condcands++; // Only now ...
LOG ("watching first autarky literal %d", watched_autarky_literal);
// Save assignment sizes for statistics, logging and checking.
//
remain = initial;
// Position of next conditional and unassigned literal to process in the
// 'conditional' and the 'unassigned' stack.
//
struct {
size_t conditional, unassigned;
} next = {0, 0};
assert (unassigned.empty ());
assert (conditional.size () == initial.conditional);
while (watched_autarky_literal && stats.condprops < limit &&
next.conditional < conditional.size ()) {
assert (next.unassigned == unassigned.size ());
const int conditional_lit = conditional[next.conditional++];
LOG ("processing next conditional %d", conditional_lit);
assert (is_conditional_literal (conditional_lit));
if (is_in_candidate_clause (-conditional_lit)) {
LOG ("conditional %d negated in candidate clause", conditional_lit);
continue;
}
LOG ("conditional %d does not occur negated in candidate clause",
conditional_lit);
condition_unassign (conditional_lit);
assert (!is_conditional_literal (conditional_lit));
unassigned.push_back (conditional_lit);
assert (remain.assigned > 0);
assert (remain.conditional > 0);
remain.conditional--;
remain.assigned--;
while (watched_autarky_literal && stats.condprops < limit &&
next.unassigned < unassigned.size ()) {
const int unassigned_lit = unassigned[next.unassigned++];
LOG ("processing next unassigned %d", unassigned_lit);
assert (!val (unassigned_lit));
#ifndef QUIET
props++;
#endif
stats.condprops++;
Occs &os = occs (unassigned_lit);
if (os.empty ())
continue;
// Traverse all watched clauses of 'unassigned_lit' and find
// replacement watches or if none is found turn the negation of all
// false autarky literals in that clause into conditional literals.
// If one of those autarky literals is the watched autarky literal
// in the candidate clause, that one has to be updated too.
//
// We expect that this loop is a hot-spot for the procedure and thus
// are more careful about accessing end points for iterating.
//
auto i = os.begin (), j = i;
for (; watched_autarky_literal && j != os.end (); j++) {
Clause *d = *i++ = *j;
int replacement = 0; // New watched literal in 'd'.
int negative = 0; // Negative autarky literals in 'd'.
for (const_literal_iterator l = d->begin (); l != d->end ();
l++) {
const int lit = *l;
const signed char tmp = val (lit);
if (tmp > 0)
replacement = lit;
if (tmp < 0 && is_autarky_literal (-lit))
negative++;
}
if (replacement) {
LOG ("found replacement %d for unassigned %d", replacement,
unassigned_lit);
LOG (d, "unwatching %d in", unassigned_lit);
i--; // Drop watch!
LOG (d, "watching %d in", replacement);
assert (replacement != unassigned_lit);
occs (replacement).push_back (d);
continue; // ... with next watched clause 'd'.
}
LOG ("no replacement found for unassigned %d", unassigned_lit);
// Keep watching 'd' by 'unassigned_lit' if no replacement found.
if (!negative) {
LOG (d, "no negative autarky literals left in");
continue; // ... with next watched clause 'd'.
}
LOG (d, "found %d negative autarky literals in", negative);
for (const_literal_iterator l = d->begin ();
watched_autarky_literal && l != d->end (); l++) {
const int lit = *l;
if (!is_autarky_literal (-lit))
continue;
mark_as_conditional_literal (-lit);
conditional.push_back (-lit);
remain.conditional++;
assert (remain.autarky > 0);
remain.autarky--;
if (-lit != watched_autarky_literal)
continue;
LOG ("need to replace autarky literal %d in candidate", -lit);
replacement = 0;
// TODO save starting point because we only move it forward?
for (const_literal_iterator k = c->begin ();
!replacement && k != c->end (); k++) {
const int other = *k;
if (is_autarky_literal (other))
replacement = other;
}
watched_autarky_literal = replacement;
if (replacement) {
LOG (c, "watching autarky %d instead %d in candidate",
replacement, watched_autarky_literal);
watched_autarky_literal = replacement;
} else {
LOG ("failed to find an autarky replacement");
watched_autarky_literal = 0; // Breaks out of 4 loops!!!!!
}
} // End of loop of turning autarky literals into conditionals.
} // End of loop of all watched clauses of an unassigned literal.
//
// We might abort the occurrence traversal early but already
// removed some watches, thus have to just copy the rest.
//
if (i < j) {
while (j != os.end ())
*i++ = *j++;
LOG ("flushed %zd occurrences of %d", os.end () - i,
unassigned_lit);
os.resize (i - os.begin ());
}
} // End of loop which goes over all unprocessed unassigned literals.
} // End of loop which goes over all unprocessed conditional literals.
// We are still processing the candidate 'c' and now have reached a
// final fix-point assignment partitioned into a conditional and an
// autarky part, or during unassigned literals figured that there is no
// positive autarky literal left in 'c'.
LOG ("remaining assignment of size %zd", remain.assigned);
LOG ("remaining conditional part of size %zd", remain.conditional);
LOG ("remaining autarky part of size %zd", remain.autarky);
//
assert (remain.assigned - remain.conditional == remain.autarky);
//
#if defined(LOGGING) || !defined(NDEBUG)
//
// This is a sanity check, that the size of our implicit representation
// of the autarky part matches our 'remain' counts. We need the same
// code for determining autarky literals as in the loop below which adds
// autarky literals to the extension stack.
//
struct {
size_t assigned, conditional, autarky;
} check;
check.assigned = check.conditional = check.autarky = 0;
for (size_t i = 0; i < trail.size (); i++) {
const int lit = trail[i];
if (val (lit)) {
check.assigned++;
if (is_conditional_literal (lit)) {
LOG ("remaining conditional %d", lit);
assert (!is_autarky_literal (lit));
check.conditional++;
} else {
assert (is_autarky_literal (lit));
LOG ("remaining autarky %d", lit);
check.autarky++;
}
} else {
assert (!is_autarky_literal (lit));
assert (!is_conditional_literal (lit));
}
}
assert (remain.assigned == check.assigned);
assert (remain.conditional == check.conditional);
assert (remain.autarky == check.autarky);
#endif
// Success if an autarky literal is left in the clause and
// we did not abort the loop too early because the propagation
// limit was hit.
//
if (watched_autarky_literal && stats.condprops < limit) {
assert (is_autarky_literal (watched_autarky_literal));
assert (is_in_candidate_clause (watched_autarky_literal));
blocked++;
stats.conditioned++;
LOG (c, "positive autarky literal %d globally blocks",
watched_autarky_literal);
LOG ("remaining %zd assigned literals %.0f%%", remain.assigned,
percent (remain.assigned, initial.assigned));
LOG ("remaining %zd conditional literals %.0f%%", remain.conditional,
percent (remain.conditional, remain.assigned));
LOG ("remaining %zd autarky literals %.0f%%", remain.autarky,
percent (remain.autarky, remain.assigned));
// A satisfying assignment of a formula after removing a globally
// blocked clause might not satisfy that clause. As for variable
// elimination and classical blocked clauses, we thus maintain an
// extension stack for reconstructing an assignment which both
// satisfies the remaining formula as well as the clause.
//
// For globally blocked clauses we simply have to flip all literals in
// the autarky part and thus save the autarky on the extension stack
// in addition to the removed clause. In the classical situation (in
// bounded variable elimination etc.) we simply save one literal on
// the extension stack.
//
// TODO find a way to shrink the autarky part or some other way to
// avoid pushing too many literals on the extension stack.
//
external->push_zero_on_extension_stack ();
for (const auto &lit : trail)
if (is_autarky_literal (lit))
external->push_witness_literal_on_extension_stack (lit);
if (proof)
proof->weaken_minus (c);
external->push_clause_on_extension_stack (c);
mark_garbage (c);
stats.condassrem += remain.assigned;
stats.condcondrem += remain.conditional;
stats.condautrem += remain.autarky;
stats.condassirem += initial.assigned;
}
// In this last part specific to one candidate clause, we have to get
// back to the initial assignment and reset conditionals. First we
// assign all the unassigned literals (if necessary).
//
if (!unassigned.empty ()) {
LOG ("reassigning %zd literals", unassigned.size ());
while (!unassigned.empty ()) {
const int lit = unassigned.back ();
unassigned.pop_back ();
condition_assign (lit);
}
}
// Then we remove from the conditional stack autarky literals which
// became conditional and also reset their 'conditional' bit.
//
if (initial.conditional < conditional.size ()) {
LOG ("flushing %zd autarky literals from conditional stack",
conditional.size () - initial.conditional);
while (initial.conditional < conditional.size ()) {
const int lit = conditional.back ();
conditional.pop_back ();
unmark_as_conditional_literal (lit);
}
}
// Finally unmark all literals in the candidate clause.
//
for (const_literal_iterator l = c->begin (); l != c->end (); l++)
unmark_in_candidate_clause (*l);
} // End of loop over all candidate clauses.
PHASE ("condition", stats.conditionings,
"globally blocked %ld clauses %.0f%%", blocked,
percent (blocked, candidates.size ()));
// Unmark initial conditional variables.
//
for (const auto &lit : conditional)
unmark_as_conditional_literal (lit);
erase_vector (unassigned);
erase_vector (conditional);
erase_vector (candidates);
// Unassign additionally assigned literals.
//
#if defined(LOGGING) || !defined(NDEBUG)
int additionally_unassigned = 0;
#endif
while (trail.size () > initial_trail_level) {
int lit = trail.back ();
trail.pop_back ();
condition_unassign (lit);
#if defined(LOGGING) || !defined(NDEBUG)
additionally_unassigned++;
#endif
}
LOG ("unassigned %d additionally assigned literals",
additionally_unassigned);
assert (additionally_unassigned == additionally_assigned);
if (level > initial_level) {
LOG ("reset condition decision level");
level = initial_level;
}
reset_occs ();
delete_garbage_clauses ();
// Reassign previously assigned variables again.
//
LOG ("reassigning previously assigned variables");
for (size_t i = 0; i < initial_trail_level; i++) {
const int lit = trail[i];
const signed char tmp = val (lit);
assert (tmp >= 0);
if (!tmp)
condition_assign (lit);
}
#ifndef NDEBUG
for (const auto &lit : trail)
assert (!marked (lit));
#endif
unprotect_reasons ();
return blocked;
}
void Internal::condition (bool update_limits) {
if (unsat)
return;
if (!stats.current.irredundant)
return;
START_SIMPLIFIER (condition, CONDITION);
stats.conditionings++;
// Propagation limit to avoid too much work in 'condition'. We mark
// tried candidate clauses after giving up, such that next time we run
// 'condition' we can try them.
//
long limit = stats.propagations.search;
limit *= opts.conditioneffort;
limit /= 1000;
if (limit < opts.conditionmineff)
limit = opts.conditionmineff;
if (limit > opts.conditionmaxeff)
limit = opts.conditionmaxeff;
assert (stats.current.irredundant);
limit *= 2.0 * active () / (double) stats.current.irredundant;
limit = max (limit, 2l * active ());
PHASE ("condition", stats.conditionings,
"started after %" PRIu64 " conflicts limited by %ld propagations",
stats.conflicts, limit);
long blocked = condition_round (limit);
STOP_SIMPLIFIER (condition, CONDITION);
report ('g', !blocked);
if (!update_limits)
return;
long delta = opts.conditionint * (stats.conditionings + 1);
lim.condition = stats.conflicts + delta;
PHASE ("condition", stats.conditionings,
"next limit at %" PRIu64 " after %ld conflicts", lim.condition,
delta);
}
} // namespace CaDiCaL

101
src/sat/cadical/config.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
struct NameVal {
const char *name;
int val;
};
/*------------------------------------------------------------------------*/
// These are dummy configurations, which require additional code.
static NameVal default_config[1]; // With '-pedantic' just '[]' or
static NameVal plain_config[1]; // '[0]' gave a warning.
/*------------------------------------------------------------------------*/
// Here we have the pre-defined default configurations.
static NameVal sat_config[] = {
{"elimeffort", 10},
{"stabilizeonly", 1},
{"subsumeeffort", 60},
};
static NameVal unsat_config[] = {
{"stabilize", 0},
{"walk", 0},
};
/*------------------------------------------------------------------------*/
#define CONFIGS \
\
CONFIG (default, "set default advanced internal options") \
CONFIG (plain, "disable all internal preprocessing options") \
CONFIG (sat, "set internal options to target satisfiable instances") \
CONFIG (unsat, "set internal options to target unsatisfiable instances")
static const char *configs[] = {
#define CONFIG(N, D) #N,
CONFIGS
#undef CONFIG
};
static size_t num_configs = sizeof configs / sizeof *configs;
/*------------------------------------------------------------------------*/
bool Config::has (const char *name) {
#define CONFIG(N, D) \
if (!strcmp (name, #N)) \
return true;
CONFIGS
#undef CONFIG
return false;
}
bool Config::set (Options &opts, const char *name) {
if (!strcmp (name, "default")) {
opts.reset_default_values ();
return true;
}
if (!strcmp (name, "plain")) {
opts.disable_preprocessing ();
return true;
}
#define CONFIG(N, D) \
do { \
if (strcmp (name, #N)) \
break; \
const NameVal *BEGIN = N##_config; \
const NameVal *END = BEGIN + sizeof N##_config / sizeof (NameVal); \
for (const NameVal *P = BEGIN; P != END; P++) { \
assert (Options::has (P->name)); \
opts.set (P->name, P->val); \
} \
return true; \
} while (0);
CONFIGS
#undef CONFIG
return false;
}
/*------------------------------------------------------------------------*/
void Config::usage () {
#define CONFIG(N, D) printf (" %-14s " D "\n", "--" #N);
CONFIGS
#undef CONFIG
}
/*------------------------------------------------------------------------*/
const char **Config::begin () { return configs; }
const char **Config::end () { return &configs[num_configs]; }
} // namespace CaDiCaL

View File

@ -0,0 +1,20 @@
#ifndef _config_hpp_INCLUDED
#define _config_hpp_INCLUDED
namespace CaDiCaL {
class Options;
struct Config {
static bool has (const char *);
static bool set (Options &, const char *);
static void usage ();
static const char **begin ();
static const char **end ();
};
} // namespace CaDiCaL
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,714 @@
#ifndef _congruenc_hpp_INCLUDED
#define _congruenc_hpp_INCLUDED
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <queue>
#include <string>
#include <sys/types.h>
#include <unordered_set>
#include <vector>
#include "clause.hpp"
#include "inttypes.hpp"
#include "util.hpp"
#include "watch.hpp"
namespace CaDiCaL {
typedef int64_t LRAT_ID;
// This implements the algorithm algorithm from SAT 2024.
//
// The idea is to:
// 0. handle binary clauses
// 1. detect gates and merge gates with same inputs ('lazy')
// 2. eagerly replace the equivalent literals and merge gates with same
// inputs
// 3. forward subsume
//
// In step 0 the normalization is fully lazy but we do not care about a
// normal form. Therefore we actually eagerly merge literals.
//
// In step 2 there is a subtility: we only replace with the equivalence
// chain as far as we propagated so far. This is the eager part. For LRAT we
// produce the equivalence up to the point we have propagated, no the full
// chain. This is important for merging literals. To merge literals we use
// union-find but we only compress paths when rewriting the literal, not
// before. The compression was not considered important in Kissat, but we do
// it aggressively as a mirror of the equivalences we have generated.
//
// We have two structures for merging:
// - the lazy ones contains alls merges, with functions like
// find_representative
//
// - the eager version that gets the merges one by one, with functions
// like find_eager_representatives
//
// The two structures are nicely separated and we only working on one of
// them except for:
//
// 1. When propagating one equivalence, we first important the
// equivalence from the lazy to the eager version, producing the full
// chain.
//
// 2. When merging the literals, we merge the literals given by the lazy
// structure, then we merge their representative in the eager version,
// updating only the lazy structure. We do not update the eager version.
//
// An important point: We cannot use internal->lrat_chain and
// internal->clause because in most places we can interrupt the
// transformation to learn a new clause representing an equivalence.
// However, we can only have 2 layers so we use this->lrat_chain and
// internal->lrat_chain when we really produce the proof.
struct Internal;
#define LD_MAX_ARITY 26
#define MAX_ARITY ((1 << LD_MAX_ARITY) - 1)
enum class Gate_Type { And_Gate, XOr_Gate, ITE_Gate };
// Wrapper when we are looking for implication in if-then-else gates
struct lit_implication {
int first;
int second;
Clause *clause;
lit_implication (int f, int s, Clause *_id)
: first (f), second (s), clause (_id) {}
lit_implication (int f, int s) : first (f), second (s), clause (0) {}
lit_implication () : first (0), second (0), clause (nullptr) {}
void swap () { std::swap (first, second); }
};
// Wrapper when we are looking for equivalence for if-then-else-gate. They
// are produced by merging implication
struct lit_equivalence {
int first;
int second;
Clause *first_clause;
Clause *second_clause;
void check_invariant () {
assert (second_clause);
assert (first_clause);
assert (std::find (begin (*first_clause), end (*first_clause), first) !=
end (*first_clause));
assert (std::find (begin (*second_clause), end (*second_clause),
second) != end (*second_clause));
assert (std::find (begin (*first_clause), end (*first_clause),
-second) != end (*first_clause));
assert (std::find (begin (*second_clause), end (*second_clause),
-first) != end (*second_clause));
}
lit_equivalence (int f, Clause *f_id, int s, Clause *s_id)
: first (f), second (s), first_clause (f_id), second_clause (s_id) {}
lit_equivalence (int f, int s)
: first (f), second (s), first_clause (nullptr),
second_clause (nullptr) {}
lit_equivalence ()
: first (0), second (0), first_clause (nullptr),
second_clause (nullptr) {}
lit_equivalence swap () {
std::swap (first, second);
std::swap (first_clause, second_clause);
return *this;
}
lit_equivalence negate_both () {
first = -first;
second = -second;
std::swap (first_clause, second_clause);
return *this;
}
};
typedef std::vector<lit_implication> lit_implications;
typedef std::vector<lit_equivalence> lit_equivalences;
std::string string_of_gate (Gate_Type t);
struct LitClausePair {
int current_lit; // current literal from the gate
Clause *clause;
LitClausePair (int lit, Clause *cl) : current_lit (lit), clause (cl) {}
LitClausePair () : current_lit (0), clause (nullptr) {}
};
struct LitIdPair {
int lit; // current literal from the gate
LRAT_ID id;
LitIdPair (int l, LRAT_ID i) : lit (l), id (i) {}
LitIdPair () : lit (0), id (0) {}
};
/*------------------------------------------------------------------------*/
// Sorting the scheduled clauses is way faster if we compute and save the
// clause size in the schedule to avoid pointer access to clauses during
// sorting. This slightly increases the schedule size though.
struct ClauseSize {
size_t size;
Clause *clause;
ClauseSize (int s, Clause *c) : size (s), clause (c) {}
ClauseSize (Clause *c): size (c->size), clause (c) {}
ClauseSize () {}
};
struct smaller_clause_size_rank {
typedef size_t Type;
Type operator() (const ClauseSize &a) { return a.size; }
};
/*------------------------------------------------------------------------*/
// There are many special cases for ITE gates and we have to keep track of
// them as it is a gate property (rewriting might not make it obvious
// anymore).
// a = (a ? t : e) results in no -t and no +e gate (a --> a = t == (-a v -a v t) & (-a v a v -t))
// a = (-a ? t : e) results in no +t and no -e gate
// a = (c ? a : e) results in no t gate (none of them)
// a = (c ? t : a) results in no e gate (none of them)
enum Special_ITE_GATE {
NORMAL = 0,
NO_PLUS_THEN = (1 << 0),
NO_NEG_THEN = (1 << 1),
NO_THEN = NO_PLUS_THEN + NO_NEG_THEN,
NO_PLUS_ELSE = (1 << 2),
NO_NEG_ELSE = (1 << 3),
NO_ELSE = NO_PLUS_ELSE + NO_NEG_ELSE,
COND_LHS = NO_NEG_THEN + NO_PLUS_ELSE,
UCOND_LHS = NO_PLUS_THEN + NO_NEG_ELSE,
};
inline bool ite_flags_no_then_clauses (int8_t flag) {
return (flag & NO_THEN) == NO_THEN;
}
inline bool ite_flags_no_else_clauses (int8_t flag) {
return (flag & NO_ELSE) == NO_ELSE;
}
inline bool ite_flags_neg_cond_lhs (int8_t flag) {
return (flag & UCOND_LHS) == UCOND_LHS;
}
inline bool ite_flags_cond_lhs (int8_t flag) {
return (flag & COND_LHS) == COND_LHS;
}
/*------------------------------------------------------------------------*/
// The core structure of this algorithm: the gate. It is composed of a
// left-hand side and an array of right-hand side.
//
// There are a few tags to help remembering the status of the gate (like
// deleted)
//
// To keep track of the proof we use two extra arrays:
// - `neg_lhs_ids' contains the long clause for AND gates. Otherwise, it is
// empty. TODO: change to std::option as it contains at most one element
// - `pos_lhs_ids' contains all the remaining gates.
//
// We keep the reasons with an index. This index depends on the gates:
// - AND-Gates and ITE-Gates: the index is the literal from the RHS
//
// - XOR-Gates: if you order the clauses by the order of the literals,
// each literal is either positive (bit '1') or negative (bit '0'). This
// gives a number that we can use.
//
// TODO Florian: I do not think that you have to changed anything, look at
// the 'Look at this first' in the CPP file.
//
// Important for the proofs: the LHS is not updated.
//
// TODO: we currently use a vector for the rhs, but we could also use FMA
// and inline the structure to avoid any indirection.
//
// One warning for degenerated gate: it is a monotone property on the
// defining clauses, but not on the LHS/RHS as the LHS is not rewritten:
// take 4 = AND 3 4 (degenerated with only the clause -4 3) with a rewriting
// 4 -> 1 (unchanged clause) and later 1 -> 3 (unchanged clause) but you do
// not know anymore from the gate that it is degenerated
struct Gate {
#ifdef LOGGING
uint64_t id;
#endif
int lhs;
Gate_Type tag;
bool garbage : 1;
bool indexed : 1;
bool marked : 1;
bool shrunken : 1;
size_t hash; // TODO remove this field (the C++ implementation is caching
// it anyway)
vector<LitClausePair> pos_lhs_ids;
vector<LitClausePair> neg_lhs_ids;
bool degenerated_and_neg = false; // LRAT only relevant for AND Gates, neg lhs in RHS
bool degenerated_and_pos = false; // LRAT only relevant for AND Gates, pos lhs in RHS
int8_t degenerated_ite = Special_ITE_GATE::NORMAL;
vector<int> rhs;
size_t arity () const { return rhs.size (); }
bool operator== (Gate const &lhs) {
return tag == lhs.tag && hash == lhs.hash && rhs == lhs.rhs;
}
};
typedef vector<Gate *> GOccs;
struct GateEqualTo {
bool operator() (const Gate *const lhs, const Gate *const rhs) const {
return lhs->rhs == rhs->rhs && lhs->tag == rhs->tag;
}
};
struct CompactBinary {
Clause *clause;
LRAT_ID id;
int lit1, lit2;
CompactBinary (Clause *c, LRAT_ID i, int l1, int l2)
: clause (c), id (i), lit1 (l1), lit2 (l2) {}
CompactBinary () : clause (nullptr), id (0), lit1 (0), lit2 (0) {}
};
struct Hash {
Hash (std::array<int, 16> &ncs) : nonces (ncs) {}
std::array<int, 16> &nonces;
size_t operator() (const Gate *const g) const;
};
struct Rewrite {
int src, dst;
LRAT_ID id1;
LRAT_ID id2;
Rewrite (int _src, int _dst, LRAT_ID _id1, LRAT_ID _id2)
: src (_src), dst (_dst), id1 (_id1), id2 (_id2) {}
Rewrite () : src (0), dst (0), id1 (0), id2 (0) {}
};
struct Closure {
Closure (Internal *i);
Internal *const internal;
vector<Clause*> extra_clauses;
vector<CompactBinary> binaries;
std::vector<std::pair<size_t, size_t>> offsetsize;
bool full_watching = false;
std::array<int, 16> nonces;
typedef unordered_set<Gate *, Hash, GateEqualTo> GatesTable;
vector<bool> scheduled;
vector<signed char> marks;
vector<LitClausePair> mu1_ids, mu2_ids,
mu4_ids; // remember the ids and the literal. 2 and 4 are
// only used for lrat proofs, but we need 1 to
// promote binary clauses to irredundant
vector<int> lits; // result of definitions
vector<int> rhs; // stack for storing RHS
vector<int> unsimplified; // stack for storing unsimplified version (XOR,
// ITEs) for DRAT proof
vector<int> chain; // store clauses to be able to delete them properly
vector<int> clause; // storing partial clauses
vector<uint64_t>
glargecounts; // count for large clauses to complement internal->noccs
vector<uint64_t> gnew_largecounts; // count for large clauses to
// complement internal->noccs
GatesTable table;
std::array<lit_implications, 2> condbin;
std::array<lit_equivalences, 2> condeq;
std::vector<Clause *> new_unwatched_binary_clauses;
// LRAT proofs
vector<int> resolvent_analyzed;
mutable vector<LRAT_ID> lrat_chain; // storing LRAT chain
#ifdef LOGGING
uint64_t fresh_id;
#endif
uint64_t &new_largecounts (int lit);
uint64_t &largecounts (int lit);
void unmark_all ();
vector<int> representant; // union-find
vector<int> eager_representant; // union-find
vector<LRAT_ID> representant_id; // lrat version of union-find
vector<LRAT_ID> eager_representant_id; // lrat version of union-find
int &representative (int lit);
int representative (int lit) const;
LRAT_ID &representative_id (int lit);
LRAT_ID representative_id (int lit) const;
int &eager_representative (int lit);
int eager_representative (int lit) const;
LRAT_ID &eager_representative_id (int lit);
LRAT_ID eager_representative_id (int lit) const;
std::vector<char> lazy_propagated_idx;
char &lazy_propagated (int lit);
int find_lrat_representative_with_marks (int lit);
// representative in the union-find structure in the lazy equivalences
int find_representative (int lit);
// find the representative and produce the binary clause representing the
// normalization from the literal to the result.
int find_representative_and_compress (int, bool update_eager = true);
// find the lazy representative for the `lit' and `-lit'
void find_representative_and_compress_both (int);
// find the eager representative
int find_eager_representative (int);
// compreses the path from lit to the representative with a new clause if
// needed. Save internal->lrat_chain to avoid any issue.
int find_eager_representative_and_compress (int);
// Import the path from the literal and its negation to the representative
// in the lazy graph to the eager part, producing the binary clauses.
void import_lazy_and_find_eager_representative_and_compress_both (
int); // generates clauses for -lit and lit
// returns the ID of the LRAT clause for the normalization from the
// literal lit to its argument, assuming that the representative was
// already compressed.
LRAT_ID find_representative_lrat (int lit);
// returns the ID of the LRAT clause for the eager normalization from the
// literal lit to its argument assuming that the representative was
// already compressed.
LRAT_ID find_eager_representative_lrat (int lit);
// Writes the LRAT chain required for the eager normalization to
// `lrat_chain`.
void produce_eager_representative_lrat (int lit);
// Writes the LRAT chain required for the lazy normalization to
// `lrat_chain`.
void produce_representative_lrat (int lit);
// learns a binary clause if not unit
Clause *maybe_add_binary_clause (int a, int b);
// add binary clause
Clause *add_binary_clause (int a, int b);
// add tmp clause
Clause *add_tmp_binary_clause (int a, int b);
// add clause taking core of tmp or full
Clause *learn_binary_tmp_or_full_clause (int a, int b);
// promotes a clause from redundant to irredundant. We do this for all
// clauses involved in gates to make sure that we produce correct result.
void promote_clause (Clause *);
// Merge functions. We actually need different several versions for LRAT
// in order to simplify the proof production.
//
// When merging binary clauses, we can simply produce the LRAT chain by
// (1) using the two binary clauses and (2) the reason clause from the
// literals to the representatives.
//
// The same approach does not work for merging gates because the
// representative might be also a representative of another literal
// (because of eager rewriting), requiring to resolve more than once on
// the same literal. An example of this are the two gates 4=-2&7 and
// 6=-2&1, the rewriting 7=1 and the equivalence 4=1. The simple road of
// merging 6 and 4 (requires resolving away 1) + adding the rewrite 4 to 1
// (requires adding 1) does not work.
//
// Therefore, we actually go for the more regular road and produce two
// equivalence: the merge from the LHS, followed by the actual equivalence
// (by combining it with the rewrite). In DRAT this is less important
// because the checker finds a chain and is less restricted than our LRAT
// chain.
bool merge_literals_equivalence (int lit, int other, Clause *c1,
Clause *c2);
bool merge_literals_lrat (Gate *g, Gate *h, int lit, int other,
const std::vector<LRAT_ID> & = {},
const std::vector<LRAT_ID> & = {});
bool merge_literals_lrat (int lit, int other,
const std::vector<LRAT_ID> & = {},
const std::vector<LRAT_ID> & = {});
// proof production
vector<LitClausePair> lrat_chain_and_gate;
void push_lrat_id (const Clause *const c, int lit);
void push_lrat_unit (int lit);
// pushes the clause with the reasons to rewrite clause
// unless:
// - the rewriting is not necessary (resolvent_marked == 1)
// - it is overwritten by one of the arguments
void push_id_and_rewriting_lrat_unit (Clause *c, Rewrite rewrite1,
std::vector<LRAT_ID> &chain,
bool = true,
Rewrite rewrite2 = Rewrite (),
int execept_lhs = 0,
int except_lhs2 = 0);
void push_id_and_rewriting_lrat_full (Clause *c, Rewrite rewrite1,
std::vector<LRAT_ID> &chain,
bool = true,
Rewrite rewrite2 = Rewrite (),
int execept_lhs = 0,
int except_lhs2 = 0);
// TODO: does nothing except pushing on the stack, remove!
void push_id_on_chain (std::vector<LRAT_ID> &chain, Clause *c);
// TODO: does nothing except pushing on the stack, remove!
void push_id_on_chain (std::vector<LRAT_ID> &chain,
const std::vector<LitClausePair> &c);
// TODO: does nothing except pushing on the stack, remove!
void push_id_on_chain (std::vector<LRAT_ID> &chain, Rewrite rewrite, int);
void update_and_gate_build_lrat_chain (
Gate *g, Gate *h, std::vector<LRAT_ID> &extra_reasons_lit,
std::vector<LRAT_ID> &extra_reasons_ulit, bool remove_units = true);
void update_and_gate_unit_build_lrat_chain (
Gate *g, int src, LRAT_ID id1, LRAT_ID id2, int dst,
std::vector<LRAT_ID> &extra_reasons_lit,
std::vector<LRAT_ID> &extra_reasons_ulit);
// occs
vector<GOccs> gtab;
GOccs &goccs (int lit);
void connect_goccs (Gate *g, int lit);
vector<Gate *> garbage;
void mark_garbage (Gate *);
// remove the gate from the table
bool remove_gate (Gate *);
bool remove_gate (GatesTable::iterator git);
void index_gate (Gate *);
// second counter for size, complements noccs
uint64_t &largecount (int lit);
// simplification
bool skip_and_gate (Gate *g);
bool skip_xor_gate (Gate *g);
void update_and_gate (Gate *g, GatesTable::iterator, int src, int dst,
LRAT_ID id1, LRAT_ID id2, int falsified = 0,
int clashing = 0);
void update_xor_gate (Gate *g, GatesTable::iterator);
void shrink_and_gate (Gate *g, int falsified = 0, int clashing = 0);
bool simplify_gate (Gate *g);
void simplify_and_gate (Gate *g);
void simplify_ite_gate (Gate *g);
Clause *simplify_xor_clause (int lhs, Clause *);
void simplify_xor_gate (Gate *g);
bool simplify_gates (int lit);
void simplify_and_sort_xor_lrat_clauses (const vector<LitClausePair> &,
vector<LitClausePair> &, int,
int except2 = 0, bool flip = 0);
void simplify_unit_xor_lrat_clauses (const vector<LitClausePair> &, int);
// rewriting
bool rewriting_lhs (Gate *g, int dst);
bool rewrite_gates (int dst, int src, LRAT_ID id1, LRAT_ID id2);
bool rewrite_gate (Gate *g, int dst, int src, LRAT_ID id1, LRAT_ID id2);
void rewrite_xor_gate (Gate *g, int dst, int src);
void rewrite_and_gate (Gate *g, int dst, int src, LRAT_ID id1,
LRAT_ID id2);
void rewrite_ite_gate (Gate *g, int dst, int src);
size_t units; // next trail position to propagate
bool propagate_unit (int lit);
bool propagate_units ();
size_t propagate_units_and_equivalences ();
bool propagate_equivalence (int lit);
// gates
void init_closure ();
void reset_closure ();
void reset_extraction ();
void reset_and_gate_extraction ();
void extract_and_gates (Closure &);
void extract_gates ();
void extract_and_gates_with_base_clause (Clause *c);
void init_and_gate_extraction ();
Gate *find_first_and_gate (Clause *base_clause, int lhs);
Gate *find_remaining_and_gate (Clause *base_clause, int lhs);
void extract_and_gates ();
Gate *find_and_lits (const vector<int> &rhs, Gate *except = nullptr);
// rhs is sorted, so passing by copy
Gate *find_gate_lits (const vector<int> &rhs, Gate_Type typ,
Gate *except = nullptr);
Gate *find_xor_lits (const vector<int> &rhs);
// not const to normalize negations, also fixes the order of the LRAT
Gate *find_ite_gate (Gate *, bool &);
Gate *find_xor_gate (Gate *);
void reset_xor_gate_extraction ();
void init_xor_gate_extraction (std::vector<Clause*> &candidates);
LRAT_ID check_and_add_to_proof_chain (vector<int> &clause);
void add_xor_matching_proof_chain (Gate *g, int lhs1,
const vector<LitClausePair> &,
int lhs2, vector<LRAT_ID> &,
vector<LRAT_ID> &);
void add_xor_shrinking_proof_chain (Gate *g, int src);
void extract_xor_gates ();
void extract_xor_gates_with_base_clause (Clause *c);
Clause *find_large_xor_side_clause (std::vector<int> &lits);
void merge_condeq (int cond, lit_equivalences &condeq,
lit_equivalences &not_condeq);
void find_conditional_equivalences (int lit, lit_implications &condbin,
lit_equivalences &condeq);
void copy_conditional_equivalences (int lit, lit_implications &condbin);
void check_ite_implied (int lhs, int cond, int then_lit, int else_lit);
void check_ite_gate_implied (Gate *g);
void check_and_gate_implied (Gate *g);
void check_ite_lrat_reasons (Gate *g, bool = false);
void check_contained_module_rewriting (Clause *c, int lit, bool,
int except);
void delete_proof_chain ();
// ite gate extraction
void extract_ite_gates_of_literal (int);
void extract_ite_gates_of_variable (int idx);
void extract_condeq_pairs (int lit, lit_implications &condbin,
lit_equivalences &condeq);
void init_ite_gate_extraction (std::vector<ClauseSize> &candidates);
lit_implications::const_iterator find_lit_implication_second_literal (
int lit, lit_implications::const_iterator begin,
lit_implications::const_iterator end);
void search_condeq (int lit, int pos_lit,
lit_implications::const_iterator pos_begin,
lit_implications::const_iterator pos_end, int neg_lit,
lit_implications::const_iterator neg_begin,
lit_implications::const_iterator neg_end,
lit_equivalences &condeq);
void reset_ite_gate_extraction ();
void extract_ite_gates ();
void forward_subsume_matching_clauses ();
void extract_congruence ();
void add_ite_matching_proof_chain (Gate *g, Gate *h, int lhs1, int lhs2,
std::vector<LRAT_ID> &reasons1,
std::vector<LRAT_ID> &reasons2);
void add_ite_turned_and_binary_clauses (Gate *g);
Gate *new_and_gate (Clause *, int);
Gate *new_ite_gate (int lhs, int cond, int then_lit, int else_lit,
std::vector<LitClausePair> &&clauses);
Gate *new_xor_gate (const vector<LitClausePair> &, int);
// check
void check_xor_gate_implied (Gate const *const);
void check_ternary (int a, int b, int c);
void check_binary_implied (int a, int b);
void check_implied ();
// learn units. You can delay units if you want to learn several at once before
// propagation. Otherwise, propagate! If you need propagation even if nothing is set, use the
// second parameter.
//
// The function can also learn the empty clause if the unit is already set. Do not add the unit in
// the chain!
bool learn_congruence_unit (int unit, bool = false, bool = false);
bool fully_propagate ();
void learn_congruence_unit_falsifies_lrat_chain (Gate *g, int src,
int dst,
int clashing,
int falsified, int unit);
void learn_congruence_unit_when_lhs_set (Gate *g, int src, LRAT_ID id1,
LRAT_ID id2, int dst);
void find_units ();
void find_equivalences ();
void subsume_clause (Clause *subsuming, Clause *subsumed);
bool find_subsuming_clause (Clause *c);
void produce_rewritten_clause_lrat_and_clean (vector<LitClausePair> &,
int execept_lhs = 0,
bool = true);
// rewrite the clause using eager rewriting and rew1 and rew2, except for
// 2 literals Usage:
// - the except are used to ignore LHS of gates that have not and should
// not be rewritten.
// - TODO: except_lhs2 should never be used actually
// - the Rewrite are for additional rewrite to allow for lazy rewrites
// to be taken into account without being added to the eager rewriting
// (yet)
Clause *produce_rewritten_clause_lrat (Clause *c, int execept_lhs = 0,
bool remove_units = true, bool = true);
void produce_rewritten_clause_lrat (vector<LitClausePair> &,
int execept_lhs = 0,
bool = true);
void compute_rewritten_clause_lrat_simple (Clause *c, int except);
// variant where we update the indices after removing the tautologies and
// remove the tautological clauses
void produce_rewritten_clause_lrat_and_clean (
std::vector<LitClausePair> &litIds, int except_lhs,
size_t &old_position1, size_t &old_position2,
bool remove_units = true);
// binary extraction and ternary strengthening
void extract_binaries ();
bool find_binary (int, int) const;
Clause *new_tmp_clause (std::vector<int> &clause);
Clause *maybe_promote_tmp_binary_clause (Clause *);
void check_not_tmp_binary_clause (Clause *c);
Clause *new_clause ();
//
void sort_literals_by_var (vector<int> &rhs);
void sort_literals_by_var_except (vector<int> &rhs, int, int except2 = 0);
// schedule
queue<int> schedule;
void schedule_literal (int lit);
void add_clause_to_chain (std::vector<int>, LRAT_ID);
// proof. If delete_id is non-zero, then delete the clause instead of
// learning it
LRAT_ID simplify_and_add_to_proof_chain (vector<int> &unsimplified,
LRAT_ID delete_id = 0);
// we define our own wrapper as cadical has otherwise a non-compatible
// marking system
signed char &marked (int lit);
void set_mu1_reason (int lit, Clause *c);
void set_mu2_reason (int lit, Clause *c);
void set_mu4_reason (int lit, Clause *c);
LitClausePair marked_mu1 (int lit);
LitClausePair marked_mu2 (int lit);
LitClausePair marked_mu4 (int lit);
// XOR
uint32_t number_from_xor_reason_reversed (const std::vector<int> &rhs);
uint32_t number_from_xor_reason (const std::vector<int> &rhs, int,
int except2 = 0, bool flip = 0);
void gate_sort_lrat_reasons (std::vector<LitClausePair> &, int,
int except2 = 0, bool flip = 0);
void gate_sort_lrat_reasons (LitClausePair &, int, int except2 = 0,
bool flip = 0);
bool rewrite_ite_gate_to_and (Gate *g, int dst, int src, size_t c,
size_t d, int cond_lit_to_learn_if_degenerated);
void produce_ite_merge_then_else_reasons (
Gate *g, int dst, int src, std::vector<LRAT_ID> &reasons_implication,
std::vector<LRAT_ID> &reasons_back);
void produce_ite_merge_lhs_then_else_reasons (
Gate *g, std::vector<LRAT_ID> &reasons_implication,
std::vector<LRAT_ID> &reasons_back,
std::vector<LRAT_ID> &reasons_unit, bool, bool &);
void rewrite_ite_gate_update_lrat_reasons (Gate *g, int src, int dst);
void simplify_ite_gate_produce_unit_lrat (Gate *g, int lit, size_t idx1,
size_t idx2);
void merge_and_gate_lrat_produce_lrat (
Gate *g, Gate *h, std::vector<LRAT_ID> &reasons_lrat,
std::vector<LRAT_ID> &reasons_lrat_back, bool remove_units = true);
// first index is a binary clause after unit propagation and the second
// has length 3
bool simplify_ite_gate_to_and (Gate *g, size_t idx1, size_t idx2,
int removed);
void
merge_ite_gate_same_then_else_lrat (std::vector<LitClausePair> &clauses,
std::vector<LRAT_ID> &reasons_implication,
std::vector<LRAT_ID> &reasons_back);
void simplify_ite_gate_then_else_set (
Gate *g, std::vector<LRAT_ID> &reasons_implication,
std::vector<LRAT_ID> &reasons_back, size_t idx1, size_t idx2);
void simplify_ite_gate_condition_set (
Gate *g, std::vector<LRAT_ID> &reasons_lrat,
std::vector<LRAT_ID> &reasons_back_lrat, size_t idx1, size_t idx2);
bool normalize_ite_lits_gate (Gate *rhs);
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,64 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::constrain (int lit) {
if (lit)
constraint.push_back (lit);
else {
if (level)
backtrack ();
LOG (constraint, "shrinking constraint");
bool satisfied_constraint = false;
const vector<int>::const_iterator end = constraint.end ();
vector<int>::iterator i = constraint.begin ();
for (vector<int>::const_iterator j = i; j != end; j++) {
int tmp = marked (*j);
if (tmp > 0) {
LOG ("removing duplicated literal %d from constraint", *j);
} else if (tmp < 0) {
LOG ("tautological since both %d and %d occur in constraint", -*j,
*j);
satisfied_constraint = true;
break;
} else {
tmp = val (*j);
if (tmp < 0) {
LOG ("removing falsified literal %d from constraint clause", *j);
} else if (tmp > 0) {
LOG ("satisfied constraint with literal %d", *j);
satisfied_constraint = true;
break;
} else {
*i++ = *j;
mark (*j);
}
}
}
constraint.resize (i - constraint.begin ());
for (const auto &lit : constraint)
unmark (lit);
if (satisfied_constraint)
constraint.clear ();
else if (constraint.empty ()) {
unsat_constraint = true;
if (!conflict_id)
marked_failed = false; // allow to trigger failing ()
} else
for (const auto lit : constraint)
freeze (lit);
}
}
bool Internal::failed_constraint () { return unsat_constraint; }
void Internal::reset_constraint () {
for (auto lit : constraint)
melt (lit);
LOG ("cleared %zd constraint literals", constraint.size ());
constraint.clear ();
unsat_constraint = false;
marked_failed = true;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,27 @@
#ifndef NCONTRACTS
#include "internal.hpp"
namespace CaDiCaL {
void fatal_message_start ();
// See comments in 'contract.hpp'. Ugly hack we keep for now.
void require_solver_pointer_to_be_non_zero (const void *ptr,
const char *function_name,
const char *file_name) {
if (ptr)
return;
fatal_message_start ();
fprintf (stderr,
"invalid API usage of '%s' in '%s': "
"solver 'this' pointer zero (not initialized)\n",
function_name, file_name);
fflush (stderr);
abort ();
}
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,136 @@
#ifndef _contract_hpp_INCLUDED
#define _contract_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#ifndef NCONTRACTS
/*------------------------------------------------------------------------*/
// If the user violates API contracts while calling functions declared in
// 'cadical.hpp' and implemented in 'solver.cpp' then an error is reported.
// Currently we also force aborting the program. In the future it might be
// better to allow the user to provide a call back function, which then can
// for instance throw a C++ exception or execute a 'longjmp' in 'C' etc.
#define CONTRACT_VIOLATED(...) \
do { \
fatal_message_start (); \
fprintf (stderr, \
"invalid API usage of '%s' in '%s': ", __PRETTY_FUNCTION__, \
__FILE__); \
fprintf (stderr, __VA_ARGS__); \
fputc ('\n', stderr); \
fflush (stderr); \
abort (); \
} while (0)
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
// It would be much easier to just write 'REQUIRE (this, "not initialized")'
// which however produces warnings due to the '-Wnonnull' check. Note, that
// 'this' is always assumed to be non zero in modern C++. Much worse, if we
// use instead 'this != 0' or something similar like 'this != nullptr' then
// optimization silently removes this check ('gcc-7.4.0' at least) even
// though of course a zero pointer might be used as 'this' if the user did
// not initialize it. The only solution I found is to disable optimization
// for this check. It does not seem to be necessary for 'clang++' though
// ('clang++-6.0.0' at least). The alternative is to not check that the
// user forgot to initialize the solver pointer, but as long this works we
// keep this ugly hack. It also forces the function not to be inlined.
// The actual code I is in 'contract.cpp'.
//
void require_solver_pointer_to_be_non_zero (const void *ptr,
const char *function_name,
const char *file_name);
#define REQUIRE_NON_ZERO_THIS() \
do { \
require_solver_pointer_to_be_non_zero (this, __PRETTY_FUNCTION__, \
__FILE__); \
} while (0)
} // namespace CaDiCaL
/*------------------------------------------------------------------------*/
// These are common shortcuts for 'Solver' API contracts (requirements).
#define REQUIRE(COND, ...) \
do { \
if ((COND)) \
break; \
CONTRACT_VIOLATED (__VA_ARGS__); \
} while (0)
#define REQUIRE_INITIALIZED() \
do { \
REQUIRE_NON_ZERO_THIS (); \
REQUIRE (external, "external solver not initialized"); \
REQUIRE (internal, "internal solver not initialized"); \
} while (0)
#define REQUIRE_VALID_STATE() \
do { \
REQUIRE_INITIALIZED (); \
REQUIRE (this->state () & VALID, "solver in invalid state"); \
} while (0)
#define REQUIRE_READY_STATE() \
do { \
REQUIRE_VALID_STATE (); \
REQUIRE (state () != ADDING, \
"clause incomplete (terminating zero not added)"); \
} while (0)
#define REQUIRE_VALID_OR_SOLVING_STATE() \
do { \
REQUIRE_INITIALIZED (); \
REQUIRE (this->state () & (VALID | SOLVING), \
"solver neither in valid nor solving state"); \
} while (0)
#define REQUIRE_VALID_LIT(LIT) \
do { \
REQUIRE ((int) (LIT) && ((int) (LIT)) != INT_MIN, \
"invalid literal '%d'", (int) (LIT)); \
REQUIRE (external->is_valid_input ((int) (LIT)), \
"extension variable %d defined by the solver", (int) (LIT)); \
} while (0)
#define REQUIRE_STEADY_STATE() \
do { \
REQUIRE_INITIALIZED (); \
REQUIRE (this->state () & STEADY, "solver is not in steady state"); \
} while (0)
/*------------------------------------------------------------------------*/
#else // NCONTRACTS
/*------------------------------------------------------------------------*/
#define REQUIRE(...) \
do { \
} while (0)
#define REQUIRE_INITIALIZED() \
do { \
} while (0)
#define REQUIRE_VALID_STATE() \
do { \
} while (0)
#define REQUIRE_READY_STATE() \
do { \
} while (0)
#define REQUIRE_VALID_OR_SOLVING_STATE() \
do { \
} while (0)
#define REQUIRE_VALID_LIT(...) \
do { \
} while (0)
#define REQUIRE_STEADY_STATE() \
do { \
} while (0)
/*------------------------------------------------------------------------*/
#endif
/*------------------------------------------------------------------------*/
#endif

704
src/sat/cadical/cover.cpp Normal file
View File

@ -0,0 +1,704 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Covered clause elimination (CCE) is described in our short LPAR-10 paper
// and later in more detail in our JAIR'15 article. Actually implement
// the asymmetric version which adds asymmetric literals too but still call
// it 'CCE' in the following (and not 'ACCE'). This implementation provides
// a simplified and cleaner version of the one implemented before in
// Lingeling. We still follow quite closely the original description in the
// literature, which is based on asymmetric literal addition (ALA) and
// covered literal addition (CLA). Both can be seen as kind of propagation,
// where the literals in the original and then extended clause are assigned
// to false, and the literals on the trail (actually we use our own 'added'
// stack for that) make up the extended clause. The ALA steps can be
// implemented by simple propagation (copied from 'propagate.cpp') using
// watches, while the CLA steps need full occurrence lists to determine the
// resolution candidate clauses. The CCE is successful if a conflict is
// found during ALA steps or if during a CLA step all resolution candidates
// of a literal on the trail are satisfied (the extended clause is blocked).
struct Coveror {
std::vector<int> added; // acts as trail
std::vector<int> extend; // extension stack for witness
std::vector<int> covered; // clause literals or added through CLA
std::vector<int> intersection; // of literals in resolution candidates
size_t alas, clas; // actual number of ALAs and CLAs
struct {
size_t added, covered;
} next; // propagate next ...
Coveror () : alas (0), clas (0) {}
};
/*------------------------------------------------------------------------*/
// Push on the extension stack a clause made up of the given literal, the
// original clause (initially copied to 'covered') and all the added covered
// literals so far. The given literal will act as blocking literal for that
// clause, if CCE is successful. Only in this case, this private extension
// stack is copied to the actual extension stack of the solver. Note, that
// even though all 'added' clauses correspond to the extended clause, we
// only need to save the original and added covered literals.
inline void Internal::cover_push_extension (int lit, Coveror &coveror) {
coveror.extend.push_back (0);
coveror.extend.push_back (lit); // blocking literal comes first
bool found = false;
for (const auto &other : coveror.covered)
if (lit == other)
assert (!found), found = true;
else
coveror.extend.push_back (other);
assert (found);
(void) found;
}
// Successful covered literal addition (CLA) step.
inline void Internal::covered_literal_addition (int lit, Coveror &coveror) {
require_mode (COVER);
assert (level == 1);
cover_push_extension (lit, coveror);
for (const auto &other : coveror.intersection) {
LOG ("covered literal addition %d", other);
assert (!vals[other]), assert (!vals[-other]);
set_val (other, -1);
coveror.covered.push_back (other);
coveror.added.push_back (other);
coveror.clas++;
}
coveror.next.covered = 0;
}
// Successful asymmetric literal addition (ALA) step.
inline void Internal::asymmetric_literal_addition (int lit,
Coveror &coveror) {
require_mode (COVER);
assert (level == 1);
LOG ("initial asymmetric literal addition %d", lit);
assert (!vals[lit]), assert (!vals[-lit]);
set_val (lit, -1);
coveror.added.push_back (lit);
coveror.alas++;
coveror.next.covered = 0;
}
/*------------------------------------------------------------------------*/
// In essence copied and adapted from 'propagate' in 'propagate.cpp'. Since
// this function is also a hot-spot here in 'cover' we specialize it in the
// same spirit as 'probe_propagate' and 'vivify_propagate'. Please refer to
// the detailed comments for 'propagate' in 'propagate.cpp' for details.
bool Internal::cover_propagate_asymmetric (int lit, Clause *ignore,
Coveror &coveror) {
require_mode (COVER);
stats.propagations.cover++;
assert (val (lit) < 0);
bool subsumed = false;
LOG ("asymmetric literal propagation of %d", lit);
Watches &ws = watches (lit);
const const_watch_iterator eow = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i = j;
while (!subsumed && i != eow) {
const Watch w = *j++ = *i++;
if (w.clause == ignore)
continue; // costly but necessary here ...
const signed char b = val (w.blit);
if (b > 0)
continue;
if (w.clause->garbage)
j--;
else if (w.binary ()) {
if (b < 0) {
LOG (w.clause, "found subsuming");
subsumed = true;
} else
asymmetric_literal_addition (-w.blit, coveror);
} else {
literal_iterator lits = w.clause->begin ();
const int other = lits[0] ^ lits[1] ^ lit;
lits[0] = other, lits[1] = lit;
const signed char u = val (other);
if (u > 0)
j[-1].blit = other;
else {
const int size = w.clause->size;
const const_literal_iterator end = lits + size;
const literal_iterator middle = lits + w.clause->pos;
literal_iterator k = middle;
signed char v = -1;
int r = 0;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
w.clause->pos = k - lits;
assert (lits + 2 <= k), assert (k <= w.clause->end ());
if (v > 0)
j[-1].blit = r;
else if (!v) {
LOG (w.clause, "unwatch %d in", lit);
lits[1] = r;
*k = lit;
watch_literal (r, lit, w.clause);
j--;
} else if (!u) {
assert (v < 0);
asymmetric_literal_addition (-other, coveror);
} else {
assert (u < 0), assert (v < 0);
LOG (w.clause, "found subsuming");
subsumed = true;
break;
}
}
}
}
if (j != i) {
while (i != eow)
*j++ = *i++;
ws.resize (j - ws.begin ());
}
return subsumed;
}
// Covered literal addition (which needs full occurrence lists). The
// function returns 'true' if the extended clause is blocked on 'lit.'
bool Internal::cover_propagate_covered (int lit, Coveror &coveror) {
require_mode (COVER);
assert (val (lit) < 0);
if (frozen (lit)) {
LOG ("no covered propagation on frozen literal %d", lit);
return false;
}
stats.propagations.cover++;
LOG ("covered propagation of %d", lit);
assert (coveror.intersection.empty ());
Occs &os = occs (-lit);
const auto end = os.end ();
bool first = true;
// Compute the intersection of the literals in all the clauses with
// '-lit'. If all these clauses are double satisfied then we know that
// the extended clauses (in 'added') is blocked. All literals in the
// intersection can be added as covered literal. As soon the intersection
// becomes empty (during traversal of clauses with '-lit') we abort.
for (auto i = os.begin (); i != end; i++) {
Clause *c = *i;
if (c->garbage)
continue;
// First check whether clause is 'blocked', i.e., is double satisfied.
bool blocked = false;
for (const auto &other : *c) {
if (other == -lit)
continue;
const signed char tmp = val (other);
if (tmp < 0)
continue;
if (tmp > 0) {
blocked = true;
break;
}
}
if (blocked) { // ... if 'c' is double satisfied.
LOG (c, "blocked");
continue; // with next clause with '-lit'.
}
if (first) {
// Copy and mark literals of first clause.
for (const auto &other : *c) {
if (other == -lit)
continue;
const signed char tmp = val (other);
if (tmp < 0)
continue;
assert (!tmp);
coveror.intersection.push_back (other);
mark (other);
}
first = false;
} else {
// Unmark all literals in current clause.
for (const auto &other : *c) {
if (other == -lit)
continue;
signed char tmp = val (other);
if (tmp < 0)
continue;
assert (!tmp);
tmp = marked (other);
if (tmp > 0)
unmark (other);
}
// Then remove from intersection all marked literals.
const auto end = coveror.intersection.end ();
auto j = coveror.intersection.begin ();
for (auto k = j; k != end; k++) {
const int other = *j++ = *k;
const int tmp = marked (other);
assert (tmp >= 0);
if (tmp)
j--, unmark (other); // remove marked and unmark it
else
mark (other); // keep unmarked and mark it
}
const size_t new_size = j - coveror.intersection.begin ();
coveror.intersection.resize (new_size);
if (!coveror.intersection.empty ())
continue;
// No covered literal addition candidates in the intersection left!
// Move this clause triggering early abort to the beginning.
// This is a common move to front strategy to minimize effort.
auto begin = os.begin ();
while (i != begin) {
auto prev = i - 1;
*i = *prev;
i = prev;
}
*begin = c;
break; // early abort ...
}
}
bool res = false;
if (first) {
LOG ("all resolution candidates with %d blocked", -lit);
assert (coveror.intersection.empty ());
cover_push_extension (lit, coveror);
res = true;
} else if (coveror.intersection.empty ()) {
LOG ("empty intersection of resolution candidate literals");
} else {
LOG (coveror.intersection,
"non-empty intersection of resolution candidate literals");
covered_literal_addition (lit, coveror);
unmark (coveror.intersection);
coveror.intersection.clear ();
coveror.next.covered = 0; // Restart covering.
}
unmark (coveror.intersection);
coveror.intersection.clear ();
return res;
}
/*------------------------------------------------------------------------*/
bool Internal::cover_clause (Clause *c, Coveror &coveror) {
require_mode (COVER);
assert (!c->garbage);
LOG (c, "trying covered clauses elimination on");
bool satisfied = false;
for (const auto &lit : *c)
if (val (lit) > 0)
satisfied = true;
if (satisfied) {
LOG (c, "clause already satisfied");
mark_garbage (c);
return false;
}
assert (coveror.added.empty ());
assert (coveror.extend.empty ());
assert (coveror.covered.empty ());
assert (!level);
level = 1;
LOG ("assuming literals of candidate clause");
for (const auto &lit : *c) {
if (val (lit))
continue;
asymmetric_literal_addition (lit, coveror);
coveror.covered.push_back (lit);
}
bool tautological = false;
coveror.next.added = coveror.next.covered = 0;
while (!tautological) {
if (coveror.next.added < coveror.added.size ()) {
const int lit = coveror.added[coveror.next.added++];
tautological = cover_propagate_asymmetric (lit, c, coveror);
} else if (coveror.next.covered < coveror.covered.size ()) {
const int lit = coveror.covered[coveror.next.covered++];
tautological = cover_propagate_covered (lit, coveror);
} else
break;
}
if (tautological) {
if (coveror.extend.empty ()) {
stats.cover.asymmetric++;
stats.cover.total++;
LOG (c, "asymmetric tautological");
} else {
stats.cover.blocked++;
stats.cover.total++;
// Only copy extension stack if successful.
int prev = INT_MIN;
bool already_pushed = false;
int64_t last_id = 0;
LOG (c, "covered tautological");
assert (clause.empty ());
LOG (coveror.extend, "extension = ");
for (const auto &other : coveror.extend) {
if (!prev) {
// are we finishing a clause?
if (already_pushed) {
// add missing literals that are not needed for covering
// but avoid RAT proofs
for (auto i = 0, j = 0; i < c->size; ++i, ++j) {
const int lit = c->literals[i];
if (j >= (int) coveror.covered.size () ||
c->literals[i] != coveror.covered[j]) {
--j;
LOG ("adding lit %d not needed for ATA", lit);
clause.push_back (lit);
external->push_clause_literal_on_extension_stack (lit);
}
}
}
if (proof && already_pushed) {
if (lrat)
lrat_chain.push_back (c->id);
LOG ("LEARNING clause with id %" PRId64, last_id);
proof->add_derived_clause (last_id, false, clause, lrat_chain);
proof->weaken_plus (last_id, clause);
lrat_chain.clear ();
}
last_id = ++clause_id;
external->push_zero_on_extension_stack ();
external->push_witness_literal_on_extension_stack (other);
external->push_zero_on_extension_stack ();
external->push_id_on_extension_stack (last_id);
external->push_zero_on_extension_stack ();
clause.clear ();
already_pushed = true;
}
if (other) {
external->push_clause_literal_on_extension_stack (other);
clause.push_back (other);
LOG (clause, "current clause is");
}
prev = other;
}
if (proof) {
// add missing literals that are not needed for covering
// but avoid RAT proofs
for (auto i = 0, j = 0; i < c->size; ++i, ++j) {
const int lit = c->literals[i];
if (j >= (int) coveror.covered.size () ||
c->literals[i] != coveror.covered[j]) {
--j;
LOG ("adding lit %d not needed for ATA", lit);
clause.push_back (lit);
external->push_clause_literal_on_extension_stack (lit);
}
}
if (lrat)
lrat_chain.push_back (c->id);
proof->add_derived_clause (last_id, false, clause, lrat_chain);
proof->weaken_plus (last_id, clause);
lrat_chain.clear ();
}
clause.clear ();
mark_garbage (c);
}
}
// Backtrack and 'unassign' all literals.
assert (level == 1);
for (const auto &lit : coveror.added)
set_val (lit, 0);
level = 0;
coveror.covered.clear ();
coveror.extend.clear ();
coveror.added.clear ();
return tautological;
}
/*------------------------------------------------------------------------*/
// Not yet tried and larger clauses are tried first.
struct clause_covered_or_smaller {
bool operator() (const Clause *a, const Clause *b) {
if (a->covered && !b->covered)
return true;
if (!a->covered && b->covered)
return false;
return a->size < b->size;
}
};
int64_t Internal::cover_round () {
if (unsat)
return 0;
init_watches ();
connect_watches (true); // irredundant watches only is enough
int64_t delta = stats.propagations.search;
delta *= 1e-3 * opts.covereffort;
if (delta < opts.covermineff)
delta = opts.covermineff;
if (delta > opts.covermaxeff)
delta = opts.covermaxeff;
delta = max (delta, ((int64_t) 2) * active ());
PHASE ("cover", stats.cover.count,
"covered clause elimination limit of %" PRId64 " propagations",
delta);
int64_t limit = stats.propagations.cover + delta;
init_occs ();
vector<Clause *> schedule;
Coveror coveror;
// First connect all clauses and find all not yet tried clauses.
//
#ifndef QUIET
int64_t untried = 0;
#endif
//
for (auto c : clauses) {
assert (!c->frozen);
if (c->garbage)
continue;
if (c->redundant)
continue;
bool satisfied = false, allfrozen = true;
for (const auto &lit : *c)
if (val (lit) > 0) {
satisfied = true;
break;
} else if (allfrozen && !frozen (lit))
allfrozen = false;
if (satisfied) {
mark_garbage (c);
continue;
}
if (allfrozen) {
c->frozen = true;
continue;
}
for (const auto &lit : *c)
occs (lit).push_back (c);
if (c->size < opts.coverminclslim)
continue;
if (c->size > opts.covermaxclslim)
continue;
if (c->covered)
continue;
schedule.push_back (c);
#ifndef QUIET
untried++;
#endif
}
if (schedule.empty ()) {
PHASE ("cover", stats.cover.count, "no previously untried clause left");
for (auto c : clauses) {
if (c->garbage)
continue;
if (c->redundant)
continue;
if (c->frozen) {
c->frozen = false;
continue;
}
if (c->size < opts.coverminclslim)
continue;
if (c->size > opts.covermaxclslim)
continue;
assert (c->covered);
c->covered = false;
schedule.push_back (c);
}
} else { // Mix of tried and not tried clauses ....
for (auto c : clauses) {
if (c->garbage)
continue;
if (c->redundant)
continue;
if (c->frozen) {
c->frozen = false;
continue;
}
if (c->size < opts.coverminclslim)
continue;
if (c->size > opts.covermaxclslim)
continue;
if (!c->covered)
continue;
schedule.push_back (c);
}
}
stable_sort (schedule.begin (), schedule.end (),
clause_covered_or_smaller ());
#ifndef QUIET
const size_t scheduled = schedule.size ();
PHASE ("cover", stats.cover.count,
"scheduled %zd clauses %.0f%% with %" PRId64 " untried %.0f%%",
scheduled, percent (scheduled, stats.current.irredundant), untried,
percent (untried, scheduled));
#endif
// Heuristically it should be beneficial to intersect with smaller clauses
// first, since then the chances are higher that the intersection of
// resolution candidates becomes emptier earlier.
for (auto lit : lits) {
if (!active (lit))
continue;
Occs &os = occs (lit);
stable_sort (os.begin (), os.end (), clause_smaller_size ());
}
// This is the main loop of trying to do CCE of candidate clauses.
//
int64_t covered = 0;
//
while (!terminated_asynchronously () && !schedule.empty () &&
stats.propagations.cover < limit) {
Clause *c = schedule.back ();
schedule.pop_back ();
c->covered = true;
if (cover_clause (c, coveror))
covered++;
}
#ifndef QUIET
const size_t remain = schedule.size ();
const size_t tried = scheduled - remain;
PHASE ("cover", stats.cover.count,
"eliminated %" PRId64 " covered clauses out of %zd tried %.0f%%",
covered, tried, percent (covered, tried));
if (remain)
PHASE ("cover", stats.cover.count,
"remaining %zu clauses %.0f%% untried", remain,
percent (remain, scheduled));
else
PHASE ("cover", stats.cover.count, "all scheduled clauses tried");
#endif
reset_occs ();
reset_watches ();
return covered;
}
/*------------------------------------------------------------------------*/
bool Internal::cover () {
if (!opts.cover)
return false;
if (unsat)
return false;
if (terminated_asynchronously ())
return false;
if (!stats.current.irredundant)
return false;
// TODO: Our current algorithm for producing the necessary clauses on the
// reconstruction stack for extending the witness requires a covered
// literal addition step which (empirically) conflicts with flushing
// during restoring clauses (see 'regr00{48,51}.trace') even though
// flushing during restore is disabled by default (as is covered clause
// elimination). The consequence of combining these two options
// ('opts.cover' and 'opts.restoreflush') can thus produce incorrect
// witness reconstruction and thus invalid witnesses. This is quite
// infrequent (one out of half billion mobical test cases) but as the two
// regression traces show, does happen. Thus we disable the combination.
//
if (opts.restoreflush)
return false;
START_SIMPLIFIER (cover, COVER);
stats.cover.count++;
// During variable elimination unit clauses can be generated which need to
// be propagated properly over redundant clauses too. Since variable
// elimination avoids to have occurrence lists and watches at the same
// time this propagation is delayed until the end of variable elimination.
// Since we want to interleave CCE with it, we have to propagate here.
// Otherwise this triggers inconsistencies.
//
if (propagated < trail.size ()) {
init_watches ();
connect_watches (); // need to propagated over all clauses!
LOG ("elimination produced %zd units",
(size_t) (trail.size () - propagated));
if (!propagate ()) {
LOG ("propagating units before covered clause elimination "
"results in empty clause");
learn_empty_clause ();
assert (unsat);
}
reset_watches ();
}
assert (unsat || propagated == trail.size ());
int64_t covered = cover_round ();
STOP_SIMPLIFIER (cover, COVER);
report ('c', !opts.reportall && !covered);
return covered;
}
} // namespace CaDiCaL

34
src/sat/cadical/cover.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef _cover_hpp_INCLUDED
#define _cover_hpp_INCLUDED
/*------------------------------------------------------------------------*/
// This header only provides the 'COVER' macro for testing. It is unrelated
// to 'cover.cpp' which implements covered clause elimination (CCE), but we
// wanted to use the name base name in both cases. More explanation on CCE
// is provided in 'cover.cpp'.
/*------------------------------------------------------------------------*/
// Coverage goal, used similar to 'assert' (but with flipped condition) and
// also included even if 'NDEBUG' is defined (in optimizing compilation).
//
// This should in essence not be used in production code.
//
// There seems to be no problem overloading the name 'COVER' of this macro
// with the constant 'COVER' of 'Internal::Mode' (surprisingly).
#define COVER(COND) \
do { \
if (!(COND)) \
break; \
fprintf (stderr, \
"%scadical%s: %s:%d: %s: Coverage goal %s`%s'%s reached.\n", \
terr.bold_code (), terr.normal_code (), __FUNCTION__, \
__LINE__, __FILE__, terr.green_code (), #COND, \
terr.normal_code ()); \
fflush (stderr); \
abort (); \
} while (0)
#endif

252
src/sat/cadical/decide.cpp Normal file
View File

@ -0,0 +1,252 @@
#include "internal.hpp"
namespace CaDiCaL {
// This function determines the next decision variable on the queue, without
// actually removing it from the decision queue, e.g., calling it multiple
// times without any assignment will return the same result. This is of
// course used below in 'decide' but also in 'reuse_trail' to determine the
// largest decision level to backtrack to during 'restart' without changing
// the assigned variables (if 'opts.restartreusetrail' is non-zero).
int Internal::next_decision_variable_on_queue () {
int64_t searched = 0;
int res = queue.unassigned;
while (val (res))
res = link (res).prev, searched++;
if (searched) {
stats.searched += searched;
update_queue_unassigned (res);
}
LOG ("next queue decision variable %d bumped %" PRId64 "", res,
bumped (res));
return res;
}
// This function determines the best decision with respect to score.
//
int Internal::next_decision_variable_with_best_score () {
int res = 0;
for (;;) {
res = scores.front ();
if (!val (res))
break;
(void) scores.pop_front ();
}
LOG ("next decision variable %d with score %g", res, score (res));
return res;
}
int Internal::next_decision_variable () {
if (use_scores ())
return next_decision_variable_with_best_score ();
else
return next_decision_variable_on_queue ();
}
/*------------------------------------------------------------------------*/
// Implements phase saving as well using a target phase during
// stabilization unless decision phase is forced to the initial value
// of a phase is forced through the 'phase' option.
int Internal::decide_phase (int idx, bool target) {
const int initial_phase = opts.phase ? 1 : -1;
int phase = 0;
if (force_saved_phase)
phase = phases.saved[idx];
if (!phase)
phase = phases.forced[idx]; // swapped with opts.forcephase case!
if (!phase && opts.forcephase)
phase = initial_phase;
if (!phase && target)
phase = phases.target[idx];
if (!phase)
phase = phases.saved[idx];
// The following should not be necessary and in some version we had even
// a hard 'COVER' assertion here to check for this. Unfortunately it
// triggered for some users and we could not get to the root cause of
// 'phase' still not being set here. The logic for phase and target
// saving is pretty complex, particularly in combination with local
// search, and to avoid running in such an issue in the future again, we
// now use this 'defensive' code here, even though such defensive code is
// considered bad programming practice.
//
if (!phase)
phase = initial_phase;
return phase * idx;
}
// The likely phase of an variable used in 'collect' for optimizing
// co-location of clauses likely accessed together during search.
int Internal::likely_phase (int idx) { return decide_phase (idx, false); }
/*------------------------------------------------------------------------*/
// adds new level to control and trail
//
void Internal::new_trail_level (int lit) {
level++;
control.push_back (Level (lit, trail.size ()));
}
/*------------------------------------------------------------------------*/
bool Internal::satisfied () {
if ((size_t) level < assumptions.size () + (!!constraint.size ()))
return false;
if (num_assigned < (size_t) max_var)
return false;
assert (num_assigned == (size_t) max_var);
if (propagated < trail.size ())
return false;
size_t assigned = num_assigned;
return (assigned == (size_t) max_var);
}
bool Internal::better_decision (int lit, int other) {
int lit_idx = abs (lit);
int other_idx = abs (other);
if (stable)
return stab[lit_idx] > stab[other_idx];
else
return btab[lit_idx] > btab[other_idx];
}
// Search for the next decision and assign it to the saved phase. Requires
// that not all variables are assigned.
int Internal::decide () {
assert (!satisfied ());
START (decide);
int res = 0;
if ((size_t) level < assumptions.size ()) {
const int lit = assumptions[level];
assert (assumed (lit));
const signed char tmp = val (lit);
if (tmp < 0) {
LOG ("assumption %d falsified", lit);
res = 20;
} else if (tmp > 0) {
LOG ("assumption %d already satisfied", lit);
new_trail_level (0);
LOG ("added pseudo decision level");
notify_decision ();
} else {
LOG ("deciding assumption %d", lit);
search_assume_decision (lit);
}
} else if ((size_t) level == assumptions.size () && constraint.size ()) {
int satisfied_lit = 0; // The literal satisfying the constrain.
int unassigned_lit = 0; // Highest score unassigned literal.
int previous_lit = 0; // Move satisfied literals to the front.
const size_t size_constraint = constraint.size ();
#ifndef NDEBUG
unsigned sum = 0;
for (auto lit : constraint)
sum += lit;
#endif
for (size_t i = 0; i != size_constraint; i++) {
// Get literal and move 'constraint[i] = constraint[i-1]'.
int lit = constraint[i];
constraint[i] = previous_lit;
previous_lit = lit;
const signed char tmp = val (lit);
if (tmp < 0) {
LOG ("constraint literal %d falsified", lit);
continue;
}
if (tmp > 0) {
LOG ("constraint literal %d satisfied", lit);
satisfied_lit = lit;
break;
}
assert (!tmp);
LOG ("constraint literal %d unassigned", lit);
if (!unassigned_lit || better_decision (lit, unassigned_lit))
unassigned_lit = lit;
}
if (satisfied_lit) {
constraint[0] = satisfied_lit; // Move satisfied to the front.
LOG ("literal %d satisfies constraint and "
"is implied by assumptions",
satisfied_lit);
new_trail_level (0);
LOG ("added pseudo decision level for constraint");
notify_decision ();
} else {
// Just move all the literals back. If we found an unsatisfied
// literal then it will be satisfied (most likely) at the next
// decision and moved then to the first position.
if (size_constraint) {
for (size_t i = 0; i + 1 != size_constraint; i++)
constraint[i] = constraint[i + 1];
constraint[size_constraint - 1] = previous_lit;
}
if (unassigned_lit) {
LOG ("deciding %d to satisfy constraint", unassigned_lit);
search_assume_decision (unassigned_lit);
} else {
LOG ("failing constraint");
unsat_constraint = true;
res = 20;
}
}
#ifndef NDEBUG
for (auto lit : constraint)
sum -= lit;
assert (!sum); // Checksum of literal should not change!
#endif
} else {
int decision = ask_decision ();
if ((size_t) level < assumptions.size () ||
((size_t) level == assumptions.size () && constraint.size ())) {
// Forced backtrack below pseudo decision levels.
// So one of the two branches above will handle it.
STOP (decide);
res = decide (); // STARTS and STOPS profiling
START (decide);
} else {
stats.decisions++;
if (!decision) {
int idx = next_decision_variable ();
const bool target = (opts.target > 1 || (stable && opts.target));
decision = decide_phase (idx, target);
}
search_assume_decision (decision);
}
}
if (res)
marked_failed = false;
STOP (decide);
return res;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,733 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::decompose_analyze_binary_chain (DFS *dfs, int from) {
if (!lrat)
return;
LOG ("binary chain starting at %d", from);
DFS &from_dfs = dfs[vlit (from)];
Clause *reason = from_dfs.parent;
if (!reason)
return;
assert (reason->size == 2);
mini_chain.push_back (reason->id);
int other = reason->literals[0];
other = other == from ? -reason->literals[1] : -other;
Flags &f = flags (other);
if (f.seen)
return;
f.seen = true;
analyzed.push_back (other);
decompose_analyze_binary_chain (dfs, other);
}
vector<Clause *> Internal::decompose_analyze_binary_clauses (DFS *dfs,
int from) {
vector<Clause *> result;
LOG ("binary chain starting at %d", from);
DFS &from_dfs = dfs[vlit (from)];
Clause *reason = from_dfs.parent;
while (reason) {
result.push_back (reason);
assert (reason->size == 2);
int other = reason->literals[0];
other = other == from ? -reason->literals[1] : -other;
Flags &f = flags (other);
if (f.seen)
break;
f.seen = true;
analyzed.push_back (other);
from = other;
DFS &from_dfs = dfs[vlit (from)];
reason = from_dfs.parent;
}
return result;
}
void Internal::decompose_conflicting_scc_lrat (DFS *dfs, vector<int> &scc) {
if (!lrat)
return;
assert (lrat_chain.empty ());
assert (mini_chain.empty ());
for (auto &lit : scc) {
Flags &f = flags (lit);
if (f.seen)
return;
f.seen = true;
analyzed.push_back (lit);
decompose_analyze_binary_chain (dfs, lit);
for (auto p = mini_chain.rbegin (); p != mini_chain.rend (); p++) {
lrat_chain.push_back (*p);
}
mini_chain.clear ();
}
clear_analyzed_literals ();
}
void Internal::build_lrat_for_clause (
const vector<vector<Clause *>> &dfs_chains, bool invert) {
assert (lrat);
LOG ("building chain for not subsumed clause");
assert (lrat_chain.empty ());
assert (sign_marked.empty ());
// build chain for each replaced literal
for (const auto lit : clause) {
auto other = lit;
if (val (other) > 0) {
if (marked_decomposed (other))
continue;
mark_decomposed (other);
int64_t id = unit_id (other);
lrat_chain.push_back (id);
continue;
}
assert (mini_chain.empty ());
for (auto p : dfs_chains[vlit (other)]) {
if (marked_decomposed (other))
continue;
mark_decomposed (other);
int implied = p->literals[0];
implied = implied == other ? -p->literals[1] : -implied;
LOG ("ADDED %d -> %d (%" PRId64 ")", implied, other, p->id);
other = implied;
mini_chain.push_back (p->id);
if (val (implied) <= 0)
continue;
if (marked_decomposed (implied))
break;
mark_decomposed (implied);
int64_t id = unit_id (implied);
mini_chain.push_back (id);
break;
}
if (invert)
for (auto p = mini_chain.rbegin (); p != mini_chain.rend (); p++)
lrat_chain.push_back (*p);
else
for (auto p = mini_chain.begin (); p != mini_chain.end (); p++)
lrat_chain.push_back (*p);
mini_chain.clear ();
}
clear_sign_marked_literals ();
LOG (lrat_chain, "lrat_chain:");
}
void Internal::clear_sign_marked_literals () {
LOG ("clearing %zd marked literals", sign_marked.size ());
for (const auto &lit : sign_marked) {
// assert (marked_signed (lit)); violated on purpose in factor
unmark_decomposed (lit);
}
sign_marked.clear ();
}
// This performs one round of Tarjan's algorithm, e.g., equivalent literal
// detection and substitution, on the whole formula. We might want to
// repeat it since its application might produce new binary clauses or
// units. Such units might even result in an empty clause.
bool Internal::decompose_round () {
if (!opts.decompose)
return false;
if (unsat)
return false;
if (terminated_asynchronously ())
return false;
assert (!level);
START_SIMPLIFIER (decompose, DECOMP);
stats.decompositions++;
const size_t size_dfs = 2 * (1 + (size_t) max_var);
DFS *dfs = new DFS[size_dfs];
DeferDeleteArray<DFS> dfs_delete (dfs);
int *reprs = new int[size_dfs];
DeferDeleteArray<int> reprs_delete (reprs);
clear_n (reprs, size_dfs);
vector<vector<Clause *>> dfs_chains;
dfs_chains.resize (size_dfs);
if (lrat) {
for (size_t i = 0; i > size_dfs; i++) {
vector<Clause *> empty;
dfs_chains[i] = empty;
}
}
int substituted = 0;
#ifndef QUIET
int non_trivial_sccs = 0;
int before = active ();
#endif
unsigned dfs_idx = 0;
vector<int> work; // depth first search working stack
vector<int> scc; // collects members of one SCC
// The binary implication graph might have disconnected components and
// thus we have in general to start several depth first searches.
for (auto root_idx : vars) {
if (unsat)
break;
if (!active (root_idx))
continue;
for (int root_sign = -1; !unsat && root_sign <= 1; root_sign += 2) {
int root = root_sign * root_idx;
if (dfs[vlit (root)].min == TRAVERSED)
continue; // skip traversed
LOG ("new dfs search starting at root %d", root);
assert (work.empty ());
assert (scc.empty ());
work.push_back (root);
while (!unsat && !work.empty ()) {
int parent = work.back ();
DFS &parent_dfs = dfs[vlit (parent)];
if (parent_dfs.min == TRAVERSED) { // skip traversed
assert (reprs[vlit (parent)]);
work.pop_back ();
} else {
assert (!reprs[vlit (parent)]);
// Go over all implied literals, thus need to iterate over all
// binary watched clauses with the negation of 'parent'.
Watches &ws = watches (-parent);
// Two cases: Either the node has never been visited before, i.e.,
// it's depth first search index is zero, then perform the
// 'pre-fix' work before visiting it's children. Otherwise all
// it's children and nodes reachable from those children have been
// visited and their minimum reachable depth first search index
// has been computed. This second case is the 'post-fix' work.
if (parent_dfs.idx) { // post-fix
work.pop_back (); // 'parent' done
// Get the minimum reachable depth first search index reachable
// from the children of 'parent'.
unsigned new_min = parent_dfs.min;
for (const auto &w : ws) {
if (!w.binary ())
continue;
const int child = w.blit;
if (!active (child))
continue;
DFS &child_dfs = dfs[vlit (child)];
if (new_min > child_dfs.min)
new_min = child_dfs.min;
}
LOG ("post-fix work dfs search %d index %u reaches minimum %u",
parent, parent_dfs.idx, new_min);
if (parent_dfs.idx == new_min) { // entry to SCC
// All nodes on the 'scc' stack after and including 'parent'
// are in the same SCC. Their representative is computed as
// the smallest literal (index-wise) in the SCC. If the SCC
// contains both a literal and its negation, then the formula
// becomes unsatisfiable.
if (lrat) {
assert (analyzed.empty ());
int other, first = 0;
bool conflicting = false;
size_t j = scc.size ();
do {
assert (j > 0);
other = scc[--j];
if (!first || vlit (other) < vlit (first))
first = other;
Flags &f = flags (other);
if (other == -parent) {
conflicting = true; // conflicting scc
}
if (f.seen) {
continue; // also conflicting scc
}
f.seen = true;
analyzed.push_back (other);
} while (other != parent);
assert (!conflicting || first > 0);
vector<int> to_justify;
if (conflicting) {
LOG ("conflicting scc simulating up at %d", parent);
to_justify.push_back (-parent);
} else
to_justify.push_back (first);
while (!to_justify.empty ()) {
const int next = to_justify.back ();
to_justify.pop_back ();
Watches &next_ws = watches (-next);
for (const auto &w : next_ws) {
if (!w.binary ())
continue;
const int child = w.blit;
if (!active (child))
continue;
if (!flags (child).seen)
continue;
DFS &child_dfs = dfs[vlit (child)];
if (child_dfs.parent)
continue;
child_dfs.parent = w.clause;
to_justify.push_back (child);
}
}
clear_analyzed_literals ();
}
int other, repr = parent;
#ifndef QUIET
int size = 0;
#endif
assert (!scc.empty ());
size_t j = scc.size ();
do {
assert (j > 0);
other = scc[--j];
if (other == -parent) {
LOG ("both %d and %d in one SCC", parent, -parent);
if (lrat) {
Flags &f = flags (-parent);
f.seen = true;
analyzed.push_back (-parent);
decompose_analyze_binary_chain (dfs, parent);
for (auto p : mini_chain)
lrat_chain.push_back (p);
mini_chain.clear ();
}
assign_unit (parent);
#ifndef NDEBUG
bool ok =
#endif
propagate ();
assert (!ok);
learn_empty_clause ();
lrat_chain.clear ();
} else {
if (abs (other) < abs (repr))
repr = other;
#ifndef QUIET
size++;
#endif
}
} while (!unsat && other != parent);
if (unsat)
break;
#ifndef QUIET
LOG ("SCC of representative %d of size %d", repr, size);
#endif
do {
assert (!scc.empty ());
other = scc.back ();
scc.pop_back ();
dfs[vlit (other)].min = TRAVERSED;
if (frozen (other)) {
reprs[vlit (other)] = other;
continue;
}
reprs[vlit (other)] = repr;
if (other == repr)
continue;
substituted++;
LOG ("literal %d in SCC of %d", other, repr);
if (!lrat)
continue;
assert (mini_chain.empty ());
Flags &f = flags (repr);
f.seen = true;
analyzed.push_back (repr);
// no need to reverse dfs_chain because this is handled by
// build_lrat_for_clause.
dfs_chains[vlit (other)] =
decompose_analyze_binary_clauses (dfs, other);
clear_analyzed_literals ();
} while (other != parent);
#ifndef QUIET
if (size > 1)
non_trivial_sccs++;
#endif
} else {
// Current node 'parent' is in a non-trivial SCC but is not
// the entry point of the SCC in this depth first search, so
// keep it on the SCC stack until the entry point is reached.
parent_dfs.min = new_min;
}
} else { // pre-fix
dfs_idx++;
assert (dfs_idx < TRAVERSED);
parent_dfs.idx = parent_dfs.min = dfs_idx;
scc.push_back (parent);
LOG ("pre-fix work dfs search %d index %u", parent, dfs_idx);
// Now traverse all the children in the binary implication
// graph but keep 'parent' on the stack for 'post-fix' work.
for (const auto &w : ws) {
if (!w.binary ())
continue;
const int child = w.blit;
if (!active (child))
continue;
DFS &child_dfs = dfs[vlit (child)];
if (child_dfs.idx)
continue;
work.push_back (child);
}
}
}
}
}
}
erase_vector (work);
erase_vector (scc);
// delete [] dfs; need to postpone until after changing clauses...
// Only keep the representatives 'repr' mapping.
PHASE ("decompose", stats.decompositions,
"%d non-trivial sccs, %d substituted %.2f%%", non_trivial_sccs,
substituted, percent (substituted, before));
bool new_unit = false, new_binary_clause = false;
// Finally, mark substituted literals as such and push the equivalences of
// the substituted literals to their representative on the extension
// stack to fix an assignment during 'extend'.
// It is also necessary to do so for proper IDRUP/LIDRUP/Resolution proofs
vector<int64_t> decompose_ids;
const size_t size = 2 * (1 + (size_t) max_var);
decompose_ids.resize (size);
for (auto idx : vars) {
if (!substituted)
break;
if (unsat)
break;
if (!active (idx))
continue;
int other = reprs[vlit (idx)];
if (other == idx)
continue;
assert (!flags (other).eliminated ());
assert (!flags (other).substituted ());
LOG ("marking equivalence of %d and %d", idx, other);
assert (clause.empty ());
assert (lrat_chain.empty ());
clause.push_back (other);
clause.push_back (-idx);
if (lrat) {
build_lrat_for_clause (dfs_chains);
assert (!lrat_chain.empty ());
}
const int64_t id1 = ++clause_id;
if (proof) {
proof->add_derived_clause (id1, false, clause, lrat_chain);
proof->weaken_minus (id1, clause);
}
external->push_binary_clause_on_extension_stack (id1, -idx, other);
decompose_ids[vlit (-idx)] = id1;
lrat_chain.clear ();
clause.clear ();
assert (clause.empty ());
assert (lrat_chain.empty ());
clause.push_back (idx);
clause.push_back (-other);
if (lrat) {
build_lrat_for_clause (dfs_chains);
assert (!lrat_chain.empty ());
}
const int64_t id2 = ++clause_id;
if (proof) {
proof->add_derived_clause (id2, false, clause, lrat_chain);
proof->weaken_minus (id2, clause);
}
external->push_binary_clause_on_extension_stack (id2, idx, -other);
decompose_ids[vlit (idx)] = id2;
clause.clear ();
lrat_chain.clear ();
}
vector<Clause *> postponed_garbage;
// Now go over all clauses and find clause which contain literals that
// should be substituted by their representative.
size_t clauses_size = clauses.size ();
#ifndef QUIET
size_t garbage = 0, replaced = 0;
#endif
for (size_t i = 0; substituted && !unsat && i < clauses_size; i++) {
Clause *c = clauses[i];
if (c->garbage)
continue;
int j, size = c->size;
for (j = 0; j < size; j++) {
const int lit = c->literals[j];
if (reprs[vlit (lit)] != lit)
break;
}
if (j == size)
continue;
#ifndef QUIET
replaced++;
#endif
LOG (c, "first substituted literal %d in", substituted);
// Now copy the result to 'clause'. Substitute literals if they have a
// different representative. Skip duplicates and false literals. If a
// literal occurs in both phases or is assigned to true the clause is
// satisfied and can be marked as garbage.
assert (clause.empty ());
assert (lrat_chain.empty ());
assert (analyzed.empty ());
bool satisfied = false;
for (int k = 0; !satisfied && k < size; k++) {
const int lit = c->literals[k];
signed char tmp = val (lit);
if (tmp > 0)
satisfied = true;
else if (tmp < 0) {
if (!lrat)
continue;
Flags &f = flags (lit);
if (f.seen)
continue;
f.seen = true;
analyzed.push_back (lit);
int64_t id = unit_id (-lit);
lrat_chain.push_back (id);
continue;
} else {
const int other = reprs[vlit (lit)];
tmp = val (other);
if (tmp < 0) {
if (!lrat)
continue;
Flags &f = flags (other);
if (!f.seen) {
f.seen = true;
analyzed.push_back (other);
int64_t id = unit_id (-other);
lrat_chain.push_back (id);
}
if (other == lit)
continue;
int64_t id = decompose_ids[vlit (-lit)];
assert (id);
lrat_chain.push_back (id);
continue;
} else if (tmp > 0)
satisfied = true;
else {
tmp = marked (other);
if (tmp < 0)
satisfied = true;
else if (!tmp) {
mark (other);
clause.push_back (other);
}
if (other == lit)
continue;
if (!lrat)
continue;
int64_t id = decompose_ids[vlit (-lit)];
assert (id);
lrat_chain.push_back (id);
}
}
}
if (lrat)
lrat_chain.push_back (c->id);
clear_analyzed_literals ();
LOG (lrat_chain, "lrat_chain:");
if (satisfied) {
LOG (c, "satisfied after substitution (postponed)");
postponed_garbage.push_back (c);
#ifndef QUIET
garbage++;
#endif
} else if (!clause.size ()) {
LOG ("learned empty clause during decompose");
learn_empty_clause ();
} else if (clause.size () == 1) {
LOG (c, "unit %d after substitution", clause[0]);
assign_unit (clause[0]);
mark_garbage (c);
new_unit = true;
#ifndef QUIET
garbage++;
#endif
} else if (c->literals[0] != clause[0] || c->literals[1] != clause[1]) {
LOG ("need new clause since at least one watched literal changed");
if (clause.size () == 2)
new_binary_clause = true;
size_t d_clause_idx = clauses.size ();
Clause *d = new_clause_as (c);
assert (clauses[d_clause_idx] == d);
clauses[d_clause_idx] = c;
clauses[i] = d;
mark_garbage (c);
#ifndef QUIET
garbage++;
#endif
} else {
LOG ("simply shrinking clause since watches did not change");
assert (c->size > 2);
if (!c->redundant)
mark_removed (c);
if (proof) {
proof->add_derived_clause (++clause_id, c->redundant, clause,
lrat_chain);
proof->delete_clause (c);
c->id = clause_id;
}
size_t l;
int *literals = c->literals;
for (l = 2; l < clause.size (); l++)
literals[l] = clause[l];
int flushed = c->size - (int) l;
if (flushed) {
if (l == 2)
new_binary_clause = true;
LOG ("flushed %d literals", flushed);
(void) shrink_clause (c, l);
} else if (likely_to_be_kept_clause (c))
mark_added (c);
// we have shrunken c->size to l so even though there is an assertion
// for c->size > 2 at the beginning of this else block, the new size
// can be 2 now.
if (c->size == 2) { // cheaper to update only new binary clauses
assert (new_binary_clause);
update_watch_size (watches (c->literals[0]), c->literals[1], c);
update_watch_size (watches (c->literals[1]), c->literals[0], c);
}
LOG (c, "substituted");
}
while (!clause.empty ()) {
int lit = clause.back ();
clause.pop_back ();
assert (marked (lit) > 0);
unmark (lit);
}
lrat_chain.clear ();
}
if (proof) {
for (auto idx : vars) {
if (!substituted)
break;
if (!active (idx))
continue;
const int64_t id1 = decompose_ids[vlit (-idx)];
if (!id1)
continue;
int other = reprs[vlit (idx)];
assert (other != idx);
assert (!flags (other).eliminated ());
assert (!flags (other).substituted ());
clause.push_back (other);
clause.push_back (-idx);
proof->delete_clause (id1, false, clause);
clause.clear ();
clause.push_back (idx);
clause.push_back (-other);
const int64_t id2 = decompose_ids[vlit (idx)];
proof->delete_clause (id2, false, clause);
clause.clear ();
}
}
if (!unsat && !postponed_garbage.empty ()) {
LOG ("now marking %zd postponed garbage clauses",
postponed_garbage.size ());
for (const auto &c : postponed_garbage)
mark_garbage (c);
}
erase_vector (postponed_garbage);
PHASE ("decompose", stats.decompositions,
"%zd clauses replaced %.2f%% producing %zd garbage clauses %.2f%%",
replaced, percent (replaced, clauses_size), garbage,
percent (garbage, replaced));
erase_vector (scc);
// Propagate found units.
if (!unsat && propagated < trail.size () && !propagate ()) {
LOG ("empty clause after propagating units from substitution");
learn_empty_clause ();
}
for (auto idx : vars) {
if (!substituted)
break;
if (unsat)
break;
if (!active (idx))
continue;
int other = reprs[vlit (idx)];
if (other == idx)
continue;
assert (!flags (other).eliminated ());
assert (!flags (other).substituted ());
if (!flags (other).fixed ())
mark_substituted (idx);
}
reprs_delete.free ();
dfs_delete.free ();
erase_vector (dfs_chains);
if (substituted)
flush_all_occs_and_watches (); // particularly the 'blit's
bool success =
unsat || (substituted > 0 && (new_unit || new_binary_clause));
report ('d', !opts.reportall && !success);
STOP_SIMPLIFIER (decompose, DECOMP);
return success;
}
void Internal::decompose () {
for (int round = 1; round <= opts.decomposerounds; round++)
if (!decompose_round ())
break;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,23 @@
#ifndef _decompose_hpp_INCLUDED
#define _decompose_hpp_INCLUDED
namespace CaDiCaL {
// This implements Tarjan's algorithm for decomposing the binary implication
// graph intro strongly connected components (SCCs). Literals in one SCC
// are equivalent and we replace them all by the literal with the smallest
// index in the SCC. These variables are marked 'substituted' and will be
// removed from all clauses. Their value will be fixed during 'extend'.
#define TRAVERSED UINT_MAX // mark completely traversed
struct DFS {
unsigned idx; // depth first search index
unsigned min; // minimum reachable index
Clause *parent; // for lrat
DFS () : idx (0), min (0), parent (0) {}
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,170 @@
#include "internal.hpp"
namespace CaDiCaL {
// Equivalent literal substitution in 'decompose' and shrinking in 'subsume'
// or 'vivify' might produce duplicated binary clauses. They can not be
// found in 'subsume' nor 'vivify' since we explicitly do not consider
// binary clauses as candidates to be shrunken or subsumed. They are
// detected here by a simple scan of watch lists and then marked as garbage.
// This is actually also quite fast.
// Further it might also be possible that two binary clauses can be resolved
// to produce a unit (we call it 'hyper unary resolution'). For example
// resolving the binary clauses '1 -2' and '1 2' produces the unit '1'.
// This could be found by probing in 'probe' unless '-1' also occurs in a
// binary clause (add the clause '-1 2' to those two clauses) in which case
// '1' as well as '2' both occur positively as well as negatively and none
// of them nor their negation is considered as probe
void Internal::mark_duplicated_binary_clauses_as_garbage () {
if (!opts.deduplicate)
return;
if (unsat)
return;
if (terminated_asynchronously ())
return;
START_SIMPLIFIER (deduplicate, DEDUP);
stats.deduplications++;
assert (!level);
assert (watching ());
vector<int> stack; // To save marked literals and unmark them later.
int64_t subsumed = 0;
int64_t units = 0;
for (auto idx : vars) {
if (unsat)
break;
if (!active (idx))
continue;
int unit = 0;
for (int sign = -1; !unit && sign <= 1; sign += 2) {
const int lit = sign * idx; // Consider all literals.
assert (stack.empty ());
Watches &ws = watches (lit);
// We are removing references to garbage clause. Thus no 'auto'.
const const_watch_iterator end = ws.end ();
watch_iterator j = ws.begin ();
const_watch_iterator i;
for (i = j; !unit && i != end; i++) {
Watch w = *j++ = *i;
if (!w.binary ())
continue;
int other = w.blit;
const int tmp = marked (other);
Clause *c = w.clause;
if (tmp > 0) { // Found duplicated binary clause.
if (c->garbage) {
j--;
continue;
}
LOG (c, "found duplicated");
// The previous identical clause 'd' might be redundant and if the
// second clause 'c' is not (so irredundant), then we have to keep
// 'c' instead of 'd', thus we search for it and replace it.
if (!c->redundant) {
watch_iterator k;
for (k = ws.begin ();; k++) {
assert (k != i);
if (!k->binary ())
continue;
if (k->blit != other)
continue;
Clause *d = k->clause;
if (d->garbage)
continue;
c = d;
break;
}
*k = w;
}
LOG (c, "mark garbage duplicated");
stats.subsumed++;
stats.deduplicated++;
subsumed++;
mark_garbage (c);
j--;
} else if (tmp < 0) { // Hyper unary resolution.
LOG ("found %d %d and %d %d which produces unit %d", lit, -other,
lit, other, lit);
unit = lit;
if (lrat) {
// taken from fradical
assert (lrat_chain.empty ());
lrat_chain.push_back (c->id);
// We've forgotten where the other binary clause is, so go find
// it again
for (watch_iterator k = ws.begin ();; k++) {
assert (k != i);
if (!k->binary ())
continue;
if (k->blit != -other)
continue;
lrat_chain.push_back (k->clause->id);
break;
}
}
j = ws.begin (); // Flush 'ws'.
units++;
} else {
if (c->garbage)
continue;
mark (other);
stack.push_back (other);
}
}
if (j == ws.begin ())
erase_vector (ws);
else if (j != end)
ws.resize (j - ws.begin ()); // Shrink watchers.
for (const auto &other : stack)
unmark (other);
stack.clear ();
}
// Propagation potentially messes up the watches and thus we can not
// propagate the unit immediately after finding it. Instead we break
// out of both loops and assign and propagate the unit here.
if (unit) {
stats.failed++;
stats.hyperunary++;
assign_unit (unit);
// lrat_chain.clear (); done in search_assign
if (!propagate ()) {
LOG ("empty clause after propagating unit");
learn_empty_clause ();
}
}
}
STOP_SIMPLIFIER (deduplicate, DEDUP);
report ('2', !opts.reportall && !(subsumed + units));
}
} // namespace CaDiCaL

View File

@ -0,0 +1,283 @@
#include "internal.hpp"
namespace CaDiCaL {
#define INVALID_LIT UINT_MAX
// functions below are passed to kitten
//
struct definition_extractor {
Eliminator *eliminator;
Internal *internal;
vector<Clause *> clauses[2];
int lit;
vector<vector<int>> implicants;
int unit;
};
extern "C" {
// used to extract definitions from kitten
//
static void traverse_definition_core (void *state, unsigned id) {
definition_extractor *extractor = (definition_extractor *) state;
Clause *clause;
const vector<Clause *> &clauses0 = extractor->clauses[0];
const vector<Clause *> &clauses1 = extractor->clauses[1];
Eliminator *eliminator = extractor->eliminator;
const size_t size_clauses0 = clauses0.size ();
const size_t size_clauses1 = clauses1.size ();
assert (size_clauses0 <= UINT_MAX);
unsigned sign;
assert (id < size_clauses0 + size_clauses1);
if (id < size_clauses0) {
clause = clauses0[id];
sign = 1;
} else {
unsigned tmp = id - size_clauses0;
#ifndef NDEBUG
assert (size_clauses1 <= UINT_MAX);
assert (tmp < size_clauses1);
#endif
clause = clauses1[tmp];
sign = 2;
}
(void) size_clauses1;
clause->gate = true;
eliminator->gates.push_back (clause);
#ifdef LOGGING
Internal *internal = extractor->internal;
LOG (clause, "extracted gate");
#endif
eliminator->definition_unit |= sign;
}
// extracts relevant learned clauses from kissat for drat proofs
//
static void traverse_one_sided_core_lemma (void *state, bool learned,
size_t size,
const unsigned *lits) {
if (!learned)
return;
definition_extractor *extractor = (definition_extractor *) state;
Eliminator *eliminator = extractor->eliminator;
Internal *internal = extractor->internal;
Proof *proof = internal->proof;
const int unit = extractor->unit;
vector<proof_clause> &proof_clauses = eliminator->proof_clauses;
if (size) {
proof_clause pc;
pc.id = ++(internal->clause_id);
pc.literals.push_back (unit);
const unsigned *end = lits + size;
for (const unsigned *p = lits; p != end; p++)
pc.literals.push_back (internal->citten2lit (*p)); // conversion
proof_clauses.push_back (pc);
assert (proof);
proof->add_derived_clause (pc.id, true, pc.literals, pc.chain);
} else {
internal->assign_unit (unit);
for (const auto &pc : proof_clauses) {
proof->delete_clause (pc.id, true, pc.literals);
}
proof_clauses.clear ();
}
}
// extract lrat proofs for relevant clauses
//
static void traverse_one_sided_core_lemma_with_lrat (
void *state, unsigned cid, unsigned id, bool learned, size_t size,
const unsigned *lits, size_t chain_size, const unsigned *chain) {
definition_extractor *extractor = (definition_extractor *) state;
Eliminator *eliminator = extractor->eliminator;
Internal *internal = extractor->internal;
Proof *proof = internal->proof;
const int unit = extractor->unit;
const vector<Clause *> &clauses0 = extractor->clauses[0];
const vector<Clause *> &clauses1 = extractor->clauses[1];
vector<proof_clause> &proof_clauses = eliminator->proof_clauses;
if (!learned) { // remember clauses for mapping to kitten internal
assert (size);
assert (!chain_size);
proof_clause pc;
pc.cid = cid;
pc.learned = false;
const size_t size_clauses0 = clauses0.size ();
assert (size_clauses0 <= UINT_MAX);
if (id < size_clauses0) {
pc.id = clauses0[id]->id;
} else {
unsigned tmp = id - size_clauses0;
#ifndef NDEBUG
const size_t size_clauses1 = clauses1.size ();
assert (size_clauses1 <= UINT_MAX);
assert (tmp < size_clauses1);
#endif
pc.id = clauses1[tmp]->id;
}
proof_clauses.push_back (pc);
} else { // actually add to proof
assert (chain_size);
if (size) {
proof_clause pc;
pc.id = ++(internal->clause_id);
pc.cid = cid;
pc.learned = true;
pc.literals.push_back (unit);
const unsigned *end = lits + size;
for (const unsigned *p = lits; p != end; p++)
pc.literals.push_back (internal->citten2lit (*p)); // conversion
for (const unsigned *p = chain + chain_size; p != chain; p--) {
int64_t id = 0;
for (const auto &cpc : proof_clauses) {
if (cpc.cid == *(p - 1)) {
id = cpc.id;
break;
}
}
assert (id);
pc.chain.push_back (id);
}
proof_clauses.push_back (pc);
assert (proof);
proof->add_derived_clause (pc.id, true, pc.literals, pc.chain);
} else { // learn unit finish proof
assert (internal->lrat_chain.empty ());
for (const unsigned *p = chain + chain_size; p != chain; p--) {
int64_t id = 0;
for (const auto &cpc : proof_clauses) {
if (cpc.cid == *(p - 1)) {
id = cpc.id;
break;
}
}
assert (id);
internal->lrat_chain.push_back (id);
}
internal->assign_unit (unit);
assert (internal->lrat_chain.empty ());
for (const auto &pc : proof_clauses) {
if (pc.learned)
proof->delete_clause (pc.id, true, pc.literals);
}
proof_clauses.clear ();
}
}
}
} // end extern C
// Code ported from kissat. Kitten (and kissat) use unsigned representation
// for literals whereas CaDiCaL uses signed representation. Conversion is
// necessary for communication using lit2citten and citten2lit.
// This code is called in elim and kitten is initialized beforehand.
// To avoid confusion all cadical interal definitions with kitten are called
// citten.
//
void Internal::find_definition (Eliminator &eliminator, int lit) {
if (!opts.elimdef)
return;
if (unsat)
return;
if (val (lit))
return;
if (!eliminator.gates.empty ())
return;
assert (!val (lit));
assert (!level);
assert (citten);
const int not_lit = -lit;
definition_extractor extractor;
extractor.lit = lit;
extractor.clauses[0] = occs (lit);
extractor.clauses[1] = occs (not_lit);
extractor.eliminator = &eliminator;
extractor.internal = internal;
citten_clear_track_log_terminate ();
unsigned exported = 0;
for (unsigned sign = 0; sign < 2; sign++) {
const unsigned except = sign ? lit2citten (not_lit) : lit2citten (lit);
for (auto c : extractor.clauses[sign]) {
// to avoid copying the literals of c in their unsigned
// representation we instead implement the translation in kitten
if (!c->garbage) {
LOG (c, "adding to kitten");
citten_clause_with_id_and_exception (citten, exported, c->size,
c->literals, except);
}
exported++;
}
}
stats.definitions_checked++;
const size_t limit = opts.elimdefticks;
kitten_set_ticks_limit (citten, limit);
int status = kitten_solve (citten);
if (!exported)
goto ABORT;
if (status == 20) {
LOG ("sub-solver result UNSAT shows definition exists");
uint64_t learned;
unsigned reduced = kitten_compute_clausal_core (citten, &learned);
LOG ("1st sub-solver core of size %u original clauses out of %u",
reduced, exported);
for (int i = 2; i <= opts.elimdefcores; i++) {
kitten_shrink_to_clausal_core (citten);
kitten_shuffle_clauses (citten);
kitten_set_ticks_limit (citten, 10 * limit);
int tmp = kitten_solve (citten);
assert (!tmp || tmp == 20);
if (!tmp) {
LOG ("aborting core extraction");
goto ABORT;
}
#ifndef NDEBUG
unsigned previous = reduced;
#endif
reduced = kitten_compute_clausal_core (citten, &learned);
LOG ("%d sub-solver core of size %u original clauses out of %u", i,
reduced, exported);
assert (reduced <= previous);
#if not defined(LOGGING) && defined(NDEBUG)
(void) reduced;
#endif
}
stats.definitions_extracted++;
eliminator.gatetype = DEF;
eliminator.definition_unit = 0;
kitten_traverse_core_ids (citten, &extractor, traverse_definition_core);
assert (eliminator.definition_unit);
int unit = 0;
if (eliminator.definition_unit == 2) {
unit = not_lit;
} else if (eliminator.definition_unit == 1)
unit = lit;
if (unit) {
stats.definition_units++;
VERBOSE (2, "one sided core "
"definition extraction yields "
"failed literal");
if (proof) {
if (lrat) {
extractor.unit = unit;
kitten_trace_core (citten, &extractor,
traverse_one_sided_core_lemma_with_lrat);
} else {
extractor.unit = unit;
kitten_traverse_core_clauses (citten, &extractor,
traverse_one_sided_core_lemma);
}
} else
assign_unit (unit);
elim_propagate (eliminator, unit);
}
} else {
ABORT:
LOG ("sub-solver failed to show that definition exists");
}
stats.definition_ticks += kitten_current_ticks (citten);
return;
}
} // namespace CaDiCaL

38
src/sat/cadical/delay.hpp Normal file
View File

@ -0,0 +1,38 @@
#ifndef _delay_hpp_INCLUDED
#define _delay_hpp_INCLUDED
#include <cstdint>
#include <limits>
namespace CaDiCaL {
struct Delay {
unsigned count;
unsigned current;
Delay () : count (0), current (0) {}
bool delay () {
if (count) {
--count;
return true;
} else {
return false;
}
}
void bump_delay () {
current += current < std::numeric_limits<unsigned>::max ();
count = current;
}
void reduce_delay () {
if (!current)
return;
current /= 2;
count = current;
}
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,153 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
DratTracer::DratTracer (Internal *i, File *f, bool b)
: internal (i), file (f), binary (b)
#ifndef QUIET
,
added (0), deleted (0)
#endif
{
(void) internal;
}
void DratTracer::connect_internal (Internal *i) {
internal = i;
file->connect_internal (internal);
LOG ("DRAT TRACER connected to internal");
}
DratTracer::~DratTracer () {
LOG ("DRAT TRACER delete");
delete file;
}
/*------------------------------------------------------------------------*/
inline void DratTracer::put_binary_zero () {
assert (binary);
assert (file);
file->put ((unsigned char) 0);
}
inline void DratTracer::put_binary_lit (int lit) {
assert (binary);
assert (file);
assert (lit != INT_MIN);
unsigned idx = abs (lit);
assert (idx < (1u << 31));
unsigned x = 2u * idx + (lit < 0);
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
/*------------------------------------------------------------------------*/
void DratTracer::drat_add_clause (const vector<int> &clause) {
if (binary)
file->put ('a');
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
void DratTracer::drat_delete_clause (const vector<int> &clause) {
if (binary)
file->put ('d');
else
file->put ("d ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
/*------------------------------------------------------------------------*/
void DratTracer::add_derived_clause (int64_t, bool,
const vector<int> &clause,
const vector<int64_t> &) {
if (file->closed ())
return;
LOG ("DRAT TRACER tracing addition of derived clause");
drat_add_clause (clause);
#ifndef QUIET
added++;
#endif
}
void DratTracer::delete_clause (int64_t, bool, const vector<int> &clause) {
if (file->closed ())
return;
LOG ("DRAT TRACER tracing deletion of clause");
drat_delete_clause (clause);
#ifndef QUIET
deleted++;
#endif
}
/*------------------------------------------------------------------------*/
bool DratTracer::closed () { return file->closed (); }
#ifndef QUIET
void DratTracer::print_statistics () {
uint64_t bytes = file->bytes ();
uint64_t total = added + deleted;
MSG ("DRAT %" PRId64 " added clauses %.2f%%", added,
percent (added, total));
MSG ("DRAT %" PRId64 " deleted clauses %.2f%%", deleted,
percent (deleted, total));
MSG ("DRAT %" PRId64 " bytes (%.2f MB)", bytes,
bytes / (double) (1 << 20));
}
#endif
void DratTracer::close (bool print) {
assert (!closed ());
file->close ();
#ifndef QUIET
if (print) {
MSG ("DRAT proof file '%s' closed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
void DratTracer::flush (bool print) {
assert (!closed ());
file->flush ();
#ifndef QUIET
if (print) {
MSG ("DRAT proof file '%s' flushed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
} // namespace CaDiCaL

View File

@ -0,0 +1,53 @@
#ifndef _drattracer_h_INCLUDED
#define _drattracer_h_INCLUDED
#include "tracer.hpp"
namespace CaDiCaL {
class DratTracer : public FileTracer {
Internal *internal;
File *file;
bool binary;
#ifndef QUIET
int64_t added, deleted;
#endif
void put_binary_zero ();
void put_binary_lit (int external_lit);
// support DRAT
void drat_add_clause (const vector<int> &);
void drat_delete_clause (const vector<int> &);
public:
// own and delete 'file'
DratTracer (Internal *, File *file, bool binary);
~DratTracer ();
void connect_internal (Internal *i) override;
void begin_proof (int64_t) override {} // skip
void add_original_clause (int64_t, bool, const vector<int> &,
bool = false) override {} // skip
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
void delete_clause (int64_t, bool, const vector<int> &) override;
void finalize_clause (int64_t, const vector<int> &) override {} // skip
void report_status (int, int64_t) override {} // skip
#ifndef QUIET
void print_statistics ();
#endif
bool closed () override;
void close (bool) override;
void flush (bool) override;
};
} // namespace CaDiCaL
#endif

1172
src/sat/cadical/elim.cpp Normal file

File diff suppressed because it is too large Load Diff

53
src/sat/cadical/elim.hpp Normal file
View File

@ -0,0 +1,53 @@
#ifndef _elim_hpp_INCLUDED
#define _elim_hpp_INCLUDED
#include "heap.hpp" // Alphabetically after 'elim.hpp'.
namespace CaDiCaL {
struct Internal;
struct elim_more {
Internal *internal;
elim_more (Internal *i) : internal (i) {}
bool operator() (unsigned a, unsigned b);
};
typedef heap<elim_more> ElimSchedule;
struct proof_clause {
int64_t id;
vector<int> literals;
// for lrat
unsigned cid; // kitten id
bool learned;
vector<int64_t> chain;
};
enum GateType { NO = 0, EQUI = 1, AND = 2, ITE = 3, XOR = 4, DEF = 5 };
struct Eliminator {
Internal *internal;
ElimSchedule schedule;
Eliminator (Internal *i)
: internal (i), schedule (elim_more (i)), definition_unit (0),
gatetype (NO) {}
~Eliminator ();
queue<Clause *> backward;
Clause *dequeue ();
void enqueue (Clause *);
vector<Clause *> gates;
unsigned definition_unit;
vector<proof_clause> proof_clauses;
vector<int> marked;
GateType gatetype;
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,570 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Implements a variant of elimination with a much lower limit to be run as
// preprocessing. See elim for comments
/*------------------------------------------------------------------------*/
// Flush garbage clause, check fast elimination limits and return number of
// remaining occurrences (or 'fastelimbound + 1' if some limit was hit).
int64_t Internal::flush_elimfast_occs (int lit) {
const int64_t occslim = opts.fastelimbound;
const int64_t clslim = opts.fastelimocclim;
const int64_t failed = occslim + 1;
Occs &os = occs (lit);
const const_occs_iterator end = os.end ();
occs_iterator j = os.begin (), i = j;
int64_t res = 0;
while (i != end) {
Clause *c = *i++;
if (c->collect ())
continue;
*j++ = c;
if (c->size > clslim) {
res = failed;
break;
}
if (++res > occslim) {
assert (opts.fastelimbound < 0 || res == failed);
break;
}
}
if (i != j) {
while (i != end)
*j++ = *i++;
os.resize (j - os.begin ());
shrink_occs (os);
}
return res;
}
/*------------------------------------------------------------------------*/
// 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::elimfast_resolvents_are_bounded (Eliminator &eliminator,
int pivot) {
assert (eliminator.gates.empty ());
assert (!eliminator.definition_unit);
stats.elimtried++;
assert (!unsat);
assert (active (pivot));
const Occs &ps = occs (pivot);
const Occs &ns = occs (-pivot);
int64_t pos = ps.size ();
int64_t neg = ns.size ();
int64_t bound = opts.fastelimbound;
if (!pos || !neg)
return bound >= 0;
const int64_t sum = pos + neg;
const int64_t product = pos * neg;
if (bound > sum)
bound = sum;
LOG ("checking number resolvents on %d bounded by "
"%" PRId64 " = %" PRId64 " + %" PRId64 " + %d",
pivot, bound, pos, neg, opts.fastelimbound);
if (product <= bound) {
LOG ("fast elimination occurrence limits sufficiently small enough");
return true;
}
// 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 (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.fastelimclslim) {
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::elimfast_add_resolvents (Eliminator &eliminator,
int pivot) {
assert (eliminator.gates.empty ());
assert (!eliminator.definition_unit);
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_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);
}
/*------------------------------------------------------------------------*/
// Try to eliminate 'pivot' by bounded variable elimination.
void Internal::try_to_fasteliminate_variable (Eliminator &eliminator,
int pivot,
bool &deleted_binary_clause) {
if (!active (pivot))
return;
assert (!frozen (pivot));
// First flush garbage clauses and check limits.
int64_t bound = opts.fastelimbound;
int64_t pos = flush_elimfast_occs (pivot);
if (pos > bound) {
LOG ("too many occurrences thus not eliminated %d", pivot);
assert (!eliminator.schedule.contains (abs (pivot)));
return;
}
int64_t neg = flush_elimfast_occs (-pivot);
if (neg > bound) {
LOG ("too many occurrences thus not eliminated %d", -pivot);
assert (!eliminator.schedule.contains (abs (pivot)));
return;
}
const int64_t product = pos * neg;
const int64_t sum = pos + neg;
if (bound > sum)
bound = sum;
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);
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 (!unsat && !val (pivot)) {
if (product <= bound ||
elimfast_resolvents_are_bounded (eliminator, pivot)) {
LOG ("number of resolvents on %d are bounded", pivot);
elimfast_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);
}
/*------------------------------------------------------------------------*/
// 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::elimfast_round (bool &completed,
bool &deleted_binary_clause) {
assert (opts.fastelim);
assert (!unsat);
START_SIMPLIFIER (fastelim, ELIM);
stats.elimfastrounds++;
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 ("fastelim-round", stats.elimfastrounds,
"limit of %" PRId64 " resolutions", delta);
resolution_limit = stats.elimres + delta;
} else {
PHASE ("fastelim-round", stats.elimfastrounds, "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;
assert (schedule.empty ());
// 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;
LOG ("scheduling %d for elimination initially", idx);
schedule.push_back (idx);
}
schedule.shrink ();
#ifndef QUIET
int64_t scheduled = schedule.size ();
#endif
PHASE ("fastelim-round", stats.elimfastrounds,
"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 literals during variable elimination. If the limit is
// hit a garbage collection is performed.
//
const int64_t garbage_limit = (2 * stats.irrlits / 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_fasteliminate_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 ();
}
// 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 ("fastelim-round", stats.elimfastrounds,
"tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)",
tried, percent (tried, scheduled), schedule.size ());
schedule.erase ();
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;
stats.all.fasteliminated += eliminated;
#ifndef QUIET
int64_t resolutions = stats.elimres - old_resolutions;
PHASE ("fastelim-round", stats.elimfastrounds,
"eliminated %d variables %.0f%% in %" PRId64 " resolutions",
eliminated, percent (eliminated, scheduled), resolutions);
#endif
const int units = stats.all.fixed - old_fixed;
report ('e', !opts.reportall && !(eliminated + units));
STOP_SIMPLIFIER (fastelim, ELIM);
return eliminated; // non-zero if successful
}
/*------------------------------------------------------------------------*/
void Internal::elimfast () {
if (unsat)
return;
if (level)
backtrack ();
if (!propagate ()) {
learn_empty_clause ();
return;
}
stats.elimfastphases++;
PHASE ("fastelim-phase", stats.elimfastphases,
"starting at most %d elimination rounds", opts.fastelimrounds);
if (external_prop) {
assert (!level);
private_steps = true;
}
#ifndef QUIET
int old_active_variables = active ();
int old_eliminated = stats.all.eliminated;
#endif
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, 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
elimfast_round (round_complete, deleted_binary_clause);
if (!round_complete) {
PHASE ("fastelim-phase", stats.elimphases,
"last round %d incomplete %s", round,
eliminated ? "but successful" : "and unsuccessful");
assert (!phase_complete);
break;
}
if (round++ >= opts.fastelimrounds) {
PHASE ("fastelim-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;
// Was not able to generate new variable elimination candidates after
// variable elimination round, neither through subsumption, nor blocked,
// nor covered clause elimination.
//
PHASE ("fastelim-phase", stats.elimphases,
"no new variable elimination candidates");
assert (round_complete);
phase_complete = true;
}
for (auto idx : vars) {
if (active (idx))
flags (idx).elim = true;
}
if (phase_complete) {
stats.elimcompleted++;
PHASE ("fastelim-phase", stats.elimphases,
"fully completed elimination %" PRId64
" at elimination bound %" PRId64 "",
stats.elimcompleted, lim.elimbound);
} else {
PHASE ("fastelim-phase", stats.elimphases,
"incomplete elimination %" PRId64
" at elimination bound %" PRId64 "",
stats.elimcompleted + 1, lim.elimbound);
}
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 ();
}
}
#ifndef QUIET
eliminated = stats.all.eliminated - old_eliminated;
PHASE ("fastelim-phase", stats.elimphases,
"eliminated %d variables %.2f%%", eliminated,
percent (eliminated, old_active_variables));
#endif
if (external_prop) {
assert (!level);
private_steps = false;
}
}
} // namespace CaDiCaL

95
src/sat/cadical/ema.cpp Normal file
View File

@ -0,0 +1,95 @@
#include "internal.hpp"
namespace CaDiCaL {
// Updating an exponential moving average is placed here since we want to
// log both updates and phases of initialization, thus need 'LOG'.
//
// We now use initialization bias correction as in the ADAM method
// [KingmaBa-ICLR'15] instead of our ad-hoc initialization method used
// before. Our old variant used exponentially decreasing alphas:
//
// 1,
// 1/2, 1/2,
// 1/4, 1/4, 1/4, 1/4
// 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, 1/8,
// ...
// 2^-n, ..., 2^-n 'n' times
// alpha, alpha, ... now 'alpha' forever.
//
// where 2^-n is the smallest negative power of two above 'alpha'
//
// This old method is better than the initializations described in our
// [BiereFroehlich-POS'15] paper and actually faster than the ADAM method,
// but less precise. We consider this old method obsolete now but it
// could still be useful for implementations relying on integers instead
// of floating points because it only needs shifts and integer arithmetic.
//
// Our new method for unbiased initialization of the exponential averages
// works as follows. First the biased moving average is computed as usual.
// Note that (as already before) we use the simpler equation
//
// new_biased = old_biased + alpha * (y - old_biased);
//
// which in principle (and thus easy to remember) can be implemented as
//
// biased += alpha * (y - biased);
//
// The original formulation in the ADAM paper (with 'alpha = 1 - beta') is
//
// new_biased = beta * old_biased + (1 - beta) * y
//
// To show that these are equivalent (modulo floating point issues)
// consider the following equivalent expressions:
//
// old_biased + alpha * (y - old_biased)
// old_biased + alpha * y - alpha * old_biased
// (1 - alpha) * old_biased + alpha * y
// beta * old_biased + (1 - beta) * y
//
// The real new idea taken from the ADAM paper is however to fix the biased
// moving average with a correction term '1.0 / (1.0 - pow (beta, updated))'
// by multiplication to obtain an unbiased moving average (called simply
// 'value' in our 'code'). In order to avoid computing 'pow' every time, we
// use 'exp' which is multiplied in every update with 'beta'.
void EMA::update (Internal *internal, double y, const char *name) {
#ifdef LOGGING
updated++;
const double old_value = value;
#endif
const double old_biased = biased;
const double delta = y - old_biased;
const double scaled_delta = alpha * delta;
const double new_biased = old_biased + scaled_delta;
LOG ("update %" PRIu64 " of biased %s EMA %g with %g (delta %g) "
"yields %g (scaled delta %g)",
updated, name, old_biased, y, delta, new_biased, scaled_delta);
biased = new_biased;
const double old_exp = exp;
double new_exp, div, new_value;
if (old_exp) {
new_exp = old_exp * beta;
assert (new_exp < 1);
exp = new_exp;
div = 1 - new_exp;
assert (div > 0);
new_value = new_biased / div;
} else {
new_value = new_biased;
#ifdef LOGGING
new_exp = 0;
div = 1;
#endif
}
value = new_value;
LOG ("update %" PRIu64 " of corrected %s EMA %g with %g (delta %g) "
"yields %g (exponent %g, divisor %g)",
updated, name, old_value, y, delta, new_value, new_exp, div);
#ifndef LOGGING
(void) internal;
(void) name;
#endif
}
} // namespace CaDiCaL

68
src/sat/cadical/ema.hpp Normal file
View File

@ -0,0 +1,68 @@
#ifndef _ema_hpp_INCLUDED
#define _ema_hpp_INCLUDED
#include <cstdint>
namespace CaDiCaL {
struct Internal;
// This is a more complex generic exponential moving average class to
// support more robust initialization (see comments in the 'update'
// implementation).
struct EMA {
#ifdef LOGGING
uint64_t updated;
#endif
double value; // unbiased (corrected) moving average
double biased; // biased initialized moving average
double alpha; // input scaling with 'alpha = 1 - beta'
double beta; // decay of 'biased' with 'beta = 1 - alpha'
double exp; // 'exp = pow (beta, updated)'
EMA ()
:
#ifdef LOGGING
updated (0),
#endif
value (0), biased (0), alpha (0), beta (0), exp (0) {
}
EMA (double a)
:
#ifdef LOGGING
updated (0),
#endif
value (0), biased (0), alpha (a), beta (1 - a), exp (!!beta) {
assert (beta >= 0);
}
operator double () const { return value; }
void update (Internal *, double y, const char *name);
};
} // namespace CaDiCaL
/*------------------------------------------------------------------------*/
// Compact average update and initialization macros for better logging.
#define UPDATE_AVERAGE(A, Y) \
do { \
A.update (internal, (Y), #A); \
} while (0)
#define INIT_EMA(E, WINDOW) \
do { \
assert ((WINDOW) >= 1); \
double ALPHA = 1.0 / (double) (WINDOW); \
E = EMA (ALPHA); \
LOG ("init " #E " EMA target alpha %g window %d", ALPHA, \
(int) WINDOW); \
} while (0)
/*------------------------------------------------------------------------*/
#endif

281
src/sat/cadical/extend.cpp Normal file
View File

@ -0,0 +1,281 @@
#include "internal.hpp"
namespace CaDiCaL {
void External::push_zero_on_extension_stack () {
extension.push_back (0);
LOG ("pushing 0 on extension stack");
}
void External::push_id_on_extension_stack (int64_t id) {
const uint32_t higher_bits = static_cast<int> (id << 32);
const uint32_t lower_bits = (id & (((int64_t) 1 << 32) - 1));
extension.push_back (higher_bits);
extension.push_back (lower_bits);
LOG ("pushing id %" PRIu64 " = %d + %d", id, higher_bits, lower_bits);
}
void External::push_clause_literal_on_extension_stack (int ilit) {
assert (ilit);
const int elit = internal->externalize (ilit);
assert (elit);
extension.push_back (elit);
LOG ("pushing clause literal %d on extension stack (internal %d)", elit,
ilit);
}
void External::push_witness_literal_on_extension_stack (int ilit) {
assert (ilit);
const int elit = internal->externalize (ilit);
assert (elit);
extension.push_back (elit);
LOG ("pushing witness literal %d on extension stack (internal %d)", elit,
ilit);
if (marked (witness, elit))
return;
LOG ("marking witness %d", elit);
mark (witness, elit);
}
// The extension stack allows to reconstruct a satisfying assignment for the
// original formula after removing eliminated clauses. This was pioneered
// by Niklas Soerensson in MiniSAT and for instance is described in our
// inprocessing paper, published at IJCAR'12. This first function adds a
// clause to this stack. First the blocking or eliminated literal is added,
// and then the rest of the clause.
void External::push_clause_on_extension_stack (Clause *c) {
internal->stats.weakened++;
internal->stats.weakenedlen += c->size;
push_zero_on_extension_stack ();
push_id_on_extension_stack (c->id);
push_zero_on_extension_stack ();
for (const auto &lit : *c)
push_clause_literal_on_extension_stack (lit);
}
void External::push_clause_on_extension_stack (Clause *c, int pivot) {
push_zero_on_extension_stack ();
push_witness_literal_on_extension_stack (pivot);
push_clause_on_extension_stack (c);
}
void External::push_binary_clause_on_extension_stack (int64_t id, int pivot,
int other) {
internal->stats.weakened++;
internal->stats.weakenedlen += 2;
push_zero_on_extension_stack ();
push_witness_literal_on_extension_stack (pivot);
push_zero_on_extension_stack ();
push_id_on_extension_stack (id);
push_zero_on_extension_stack ();
push_clause_literal_on_extension_stack (pivot);
push_clause_literal_on_extension_stack (other);
}
/*------------------------------------------------------------------------*/
void External::push_external_clause_and_witness_on_extension_stack (
const vector<int> &c, const vector<int> &w, int64_t id) {
assert (id);
extension.push_back (0);
for (const auto &elit : w) {
assert (elit != INT_MIN);
init (abs (elit));
extension.push_back (elit);
mark (witness, elit);
}
extension.push_back (0);
const uint32_t higher_bits = static_cast<int> (id << 32);
const uint32_t lower_bits = (id & (((int64_t) 1 << 32) - 1));
extension.push_back (higher_bits);
extension.push_back (lower_bits);
extension.push_back (0);
for (const auto &elit : c) {
assert (elit != INT_MIN);
init (abs (elit));
extension.push_back (elit);
}
}
/*------------------------------------------------------------------------*/
// This is the actual extension process. It goes backward over the clauses
// on the extension stack and flips the assignment of one of the blocking
// literals in the conditional autarky stored before the clause. In the
// original algorithm for witness construction for variable elimination and
// blocked clause removal the conditional autarky consists of a single
// literal from the removed clause, while in general the autarky witness can
// contain an arbitrary set of literals. We are using the more general
// witness reconstruction here which for instance would also work for
// super-blocked or set-blocked clauses.
void External::extend () {
assert (!extended);
START (extend);
internal->stats.extensions++;
PHASE ("extend", internal->stats.extensions,
"mapping internal %d assignments to %d assignments",
internal->max_var, max_var);
#ifndef QUIET
int64_t updated = 0;
#endif
for (unsigned i = 1; i <= (unsigned) max_var; i++) {
const int ilit = e2i[i];
if (!ilit)
continue;
if (i >= vals.size ())
vals.resize (i + 1, false);
vals[i] = (internal->val (ilit) > 0);
#ifndef QUIET
updated++;
#endif
}
PHASE ("extend", internal->stats.extensions,
"updated %" PRId64 " external assignments", updated);
PHASE ("extend", internal->stats.extensions,
"extending through extension stack of size %zd",
extension.size ());
const auto begin = extension.begin ();
auto i = extension.end ();
#ifndef QUIET
int64_t flipped = 0;
#endif
while (i != begin) {
bool satisfied = false;
int lit;
assert (i != begin);
while ((lit = *--i)) {
if (satisfied)
continue;
if (ival (lit) == lit)
satisfied = true;
assert (i != begin);
}
assert (i != begin);
LOG ("id=%" PRId64, ((int64_t) *i << 32) + *(i - 1));
assert (*i || *(i - 1));
--i;
assert (i != begin);
--i;
assert (i != begin);
assert (!*i);
--i;
assert (i != begin);
if (satisfied)
while (*--i)
assert (i != begin);
else {
while ((lit = *--i)) {
const int tmp = ival (lit); // not 'signed char'!!!
if (tmp != lit) {
LOG ("flipping blocking literal %d", lit);
assert (lit);
assert (lit != INT_MIN);
size_t idx = abs (lit);
if (idx >= vals.size ())
vals.resize (idx + 1, false);
vals[idx] = !vals[idx];
internal->stats.extended++;
#ifndef QUIET
flipped++;
#endif
}
assert (i != begin);
}
}
}
PHASE ("extend", internal->stats.extensions,
"flipped %" PRId64 " literals during extension", flipped);
extended = true;
LOG ("extended");
STOP (extend);
}
/*------------------------------------------------------------------------*/
bool External::traverse_witnesses_backward (WitnessIterator &it) {
if (internal->unsat)
return true;
vector<int> clause, witness;
const auto begin = extension.begin ();
auto i = extension.end ();
while (i != begin) {
int lit;
while ((lit = *--i))
clause.push_back (lit);
assert (!lit);
--i;
const int64_t id =
((int64_t) * (i - 1) << 32) + static_cast<int64_t> (*i);
assert (id);
i -= 2;
assert (!*i);
assert (i != begin);
while ((lit = *--i))
witness.push_back (lit);
reverse (clause.begin (), clause.end ());
reverse (witness.begin (), witness.end ());
LOG (clause, "traversing clause");
if (!it.witness (clause, witness, id))
return false;
clause.clear ();
witness.clear ();
}
return true;
}
bool External::traverse_witnesses_forward (WitnessIterator &it) {
if (internal->unsat)
return true;
vector<int> clause, witness;
const auto end = extension.end ();
auto i = extension.begin ();
if (i != end) {
int lit = *i++;
do {
assert (!lit), (void) lit;
while ((lit = *i++))
witness.push_back (lit);
assert (!lit);
assert (i != end);
assert (!*i);
const int64_t id =
((int64_t) *i << 32) + static_cast<int64_t> (*(i + 1));
assert (id > 0);
i += 3;
assert (*i);
assert (i != end);
while (i != end && (lit = *i++))
clause.push_back (lit);
if (!it.witness (clause, witness, id))
return false;
clause.clear ();
witness.clear ();
} while (i != end);
}
return true;
}
/*------------------------------------------------------------------------*/
void External::conclude_sat () {
if (!internal->proof || concluded)
return;
concluded = true;
if (!extended)
extend ();
vector<int> model;
for (int idx = 1; idx <= max_var; idx++) {
if (ervars[idx])
continue;
const int lit = ival (idx);
model.push_back (lit);
}
internal->proof->conclude_sat (model);
}
} // namespace CaDiCaL

1022
src/sat/cadical/external.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,461 @@
#ifndef _external_hpp_INCLUDED
#define _external_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#include "range.hpp"
#include <unordered_map>
#include <vector>
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
using namespace std;
/*------------------------------------------------------------------------*/
// The CaDiCaL code is split into three layers:
//
// Solver: facade object providing the actual API of the solver
// External: communication layer between 'Solver' and 'Internal'
// Internal: the actual solver code
//
// Note, that 'Solver' is defined in 'cadical.hpp' and 'solver.cpp', while
// 'External' and 'Internal' in '{external,internal}.{hpp,cpp}'.
//
// Also note, that any user should access the library only through the
// 'Solver' API. For the library internal 'Parser' code we make an
// exception and allow access to both 'External' and 'Internal'. The former
// to enforce the same external to internal mapping of variables and the
// latter for profiling and messages. The same applies to 'App'.
//
// The 'External' class provided here stores the information needed to map
// external variable indices to internal variables (actually literals).
// This is helpful for shrinking the working size of the internal solver
// after many variables become inactive. It will also help to provide
// support for extended resolution in the future, since it allows to
// introduce only internally visible variables (even though we do not know
// how to support generating incremental proofs in this situation yet).
//
// External literals are usually called 'elit' and internal 'ilit'.
/*------------------------------------------------------------------------*/
struct Clause;
struct Internal;
struct CubesWithStatus;
/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
struct External {
/*==== start of state ==================================================*/
Internal *internal; // The actual internal solver.
int max_var; // External maximum variable index.
size_t vsize; // Allocated external size.
vector<bool> vals; // Current external (extended) assignment.
vector<int> e2i; // External 'idx' to internal 'lit'.
vector<int> assumptions; // External assumptions.
vector<int> constraint; // External constraint. Terminated by zero.
vector<int64_t>
ext_units; // External units. Needed to compute LRAT for eclause
vector<bool> ext_flags; // to avoid duplicate units
vector<int> eclause; // External version of original input clause.
// The extension stack for reconstructing complete satisfying assignments
// (models) of the original external formula is kept in this external
// solver object. It keeps track of blocked clauses and clauses containing
// eliminated variable. These irredundant clauses are stored in terms of
// external literals on the 'extension' stack after mapping the
// internal literals given as arguments with 'externalize'.
bool extended; // Have been extended.
bool concluded;
vector<int> extension; // Solution reconstruction extension stack.
vector<bool> witness; // Literal witness on extension stack.
vector<bool> tainted; // Literal tainted in adding literals.
vector<bool> ervars; // Variables added through Extended Resolution.
vector<unsigned> frozentab; // Reference counts for frozen variables.
// Regularly checked terminator if non-zero. The terminator is set from
// 'Solver::set (Terminator *)' and checked by 'Internal::terminating ()'.
Terminator *terminator;
// If there is a learner export learned clauses.
Learner *learner;
void export_learned_empty_clause ();
void export_learned_unit_clause (int ilit);
void export_learned_large_clause (const vector<int> &);
// If there is a listener for fixed assignments.
FixedAssignmentListener *fixed_listener;
// If there is an external propagator.
ExternalPropagator *propagator;
vector<bool> is_observed; // Quick flag for each external variable
// Saved 'forgettable' original clauses coming from the external
// propagator. The value of the map starts with a Boolean flag indicating
// if the clause is still present or got already deleted, and then
// followed by the literals of the clause.
unordered_map<uint64_t, vector<int>> forgettable_original;
void add_observed_var (int elit);
void remove_observed_var (int elit);
void reset_observed_vars ();
bool observed (int elit);
bool is_witness (int elit);
bool is_decision (int elit);
void force_backtrack (size_t new_level);
//----------------------------------------------------------------------//
signed char *solution; // Given solution checking for debugging.
int solution_size; // Given solution checking for debugging.
vector<int> original; // Saved original formula for checking.
// If 'opts.checkfrozen' is set make sure that only literals are added
// which were never completely molten before. These molten literals are
// marked at the beginning of the 'solve' call. Note that variables
// larger than 'max_var' are not molten and can thus always be used in the
// future. Only needed to check and debug old style freeze semantics.
//
vector<bool> moltentab;
//----------------------------------------------------------------------//
const Range vars; // Provides safe variable iterations.
/*==== end of state ====================================================*/
// These two just factor out common sanity (assertion) checking code.
inline int vidx (int elit) const {
assert (elit);
assert (elit != INT_MIN);
int res = abs (elit);
assert (res <= max_var);
return res;
}
inline int vlit (int elit) const {
assert (elit);
assert (elit != INT_MIN);
assert (abs (elit) <= max_var);
return elit;
}
inline bool is_valid_input (int elit) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
return eidx > max_var || !ervars[eidx];
}
/*----------------------------------------------------------------------*/
// The following five functions push individual literals or clauses on the
// extension stack. They all take internal literals as argument, and map
// them back to external literals first, before pushing them on the stack.
void push_zero_on_extension_stack ();
// Our general version of extension stacks always pushes a set of witness
// literals (for variable elimination the literal of the eliminated
// literal and for blocked clauses the blocking literal) followed by all
// the clause literals starting with and separated by zero.
//
void push_clause_literal_on_extension_stack (int ilit);
void push_witness_literal_on_extension_stack (int ilit);
void push_clause_on_extension_stack (Clause *);
void push_clause_on_extension_stack (Clause *, int witness);
void push_binary_clause_on_extension_stack (int64_t id, int witness,
int other);
// The main 'extend' function which extends an internal assignment to an
// external assignment using the extension stack (and sets 'extended').
//
void extend ();
void conclude_sat ();
/*----------------------------------------------------------------------*/
// Marking external literals.
unsigned elit2ulit (int elit) const {
assert (elit);
assert (elit != INT_MIN);
const int idx = abs (elit) - 1;
assert (idx <= max_var);
return 2u * idx + (elit < 0);
}
bool marked (const vector<bool> &map, int elit) const {
const unsigned ulit = elit2ulit (elit);
return ulit < map.size () ? map[ulit] : false;
}
void mark (vector<bool> &map, int elit) {
const unsigned ulit = elit2ulit (elit);
if (ulit >= map.size ())
map.resize (ulit + 1, false);
map[ulit] = true;
}
void unmark (vector<bool> &map, int elit) {
const unsigned ulit = elit2ulit (elit);
if (ulit < map.size ())
map[ulit] = false;
}
/*----------------------------------------------------------------------*/
void push_external_clause_and_witness_on_extension_stack (
const vector<int> &clause, const vector<int> &witness, int64_t id);
void push_id_on_extension_stack (int64_t id);
// Restore a clause, which was pushed on the extension stack.
void restore_clause (const vector<int>::const_iterator &begin,
const vector<int>::const_iterator &end,
const int64_t id);
void restore_clauses ();
/*----------------------------------------------------------------------*/
// Explicitly freeze and melt literals (instead of just freezing
// internally and implicitly assumed literals). Passes on freezing and
// melting to the internal solver, which has separate frozen counters.
void freeze (int elit);
void melt (int elit);
bool frozen (int elit) {
assert (elit);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var)
return false;
if (eidx >= (int) frozentab.size ())
return false;
return frozentab[eidx] > 0;
}
/*----------------------------------------------------------------------*/
External (Internal *);
~External ();
void enlarge (int new_max_var); // Enlarge allocated 'vsize'.
void init (int new_max_var,
bool extension = false); // Initialize up-to 'new_max_var'.
int internalize (
int,
bool extension = false); // Translate external to internal literal.
/*----------------------------------------------------------------------*/
// According to the CaDiCaL API contract (as well as IPASIR) we have to
// forget about the previous assumptions after a 'solve' call. This
// should however be delayed until we transition out of an 'UNSATISFIED'
// state, i.e., after no more 'failed' calls are expected. Note that
// 'failed' requires to know the failing assumptions, and the 'failed'
// status of those should cleared before at start of the next 'solve'.
// As a consequence 'reset_assumptions' is only called from
// 'transition_to_unknown_state' in API calls in 'solver.cpp'.
void reset_assumptions ();
// Similarly to 'failed', 'conclude' needs to know about failing
// assumptions and therefore needs to be reset when leaving the
// 'UNSATISFIED' state.
//
void reset_concluded ();
// Similarly a valid external assignment obtained through 'extend' has to
// be reset at each point it risks to become invalid. This is done
// in the external layer in 'external.cpp' functions..
void reset_extended ();
// Finally, the semantics of incremental solving also require that limits
// are only valid for the next 'solve' call. Since the limits can not
// really be queried, handling them is less complex and they are just
// reset immediately at the end of 'External::solve'.
void reset_limits ();
/*----------------------------------------------------------------------*/
// Proxies to IPASIR functions.
void add (int elit);
void assume (int elit);
int solve (bool preprocess_only);
// We call it 'ival' as abbreviation for 'val' with 'int' return type to
// avoid bugs due to using 'signed char tmp = val (lit)', which might turn
// a negative value into a positive one (happened in 'extend').
//
inline int ival (int elit) const {
assert (elit != INT_MIN);
int eidx = abs (elit);
bool val = false;
if (eidx <= max_var && (size_t) eidx < vals.size ())
val = vals[eidx];
if (elit < 0)
val = !val;
return val ? elit : -elit;
}
bool flip (int elit);
bool flippable (int elit);
bool failed (int elit);
void terminate ();
// Other important non IPASIR functions.
/*----------------------------------------------------------------------*/
// Add literal to external constraint.
//
void constrain (int elit);
// Returns true if 'solve' returned 20 because of the constraint.
//
bool failed_constraint ();
// Deletes the current constraint clause. Called on
// 'transition_to_unknown_state' and if a new constraint is added. Can be
// called directly using the API.
//
void reset_constraint ();
/*----------------------------------------------------------------------*/
int propagate_assumptions ();
void implied (std::vector<int> &entrailed);
void conclude_unknown ();
/*----------------------------------------------------------------------*/
int lookahead ();
CaDiCaL::CubesWithStatus generate_cubes (int, int);
int fixed (int elit) const; // Implemented in 'internal.hpp'.
/*----------------------------------------------------------------------*/
void phase (int elit);
void unphase (int elit);
/*----------------------------------------------------------------------*/
// Traversal functions for the witness stack and units. The explanation
// in 'external.cpp' for why we have to distinguish these cases.
bool traverse_all_frozen_units_as_clauses (ClauseIterator &);
bool traverse_all_non_frozen_units_as_witnesses (WitnessIterator &);
bool traverse_witnesses_backward (WitnessIterator &);
bool traverse_witnesses_forward (WitnessIterator &);
/*----------------------------------------------------------------------*/
// Copy flags for determining preprocessing state.
void copy_flags (External &other) const;
/*----------------------------------------------------------------------*/
// Check solver behaves as expected during testing and debugging.
void check_assumptions_satisfied ();
void check_constraint_satisfied ();
void check_failing ();
void check_solution_on_learned_clause ();
void check_solution_on_shrunken_clause (Clause *);
void check_solution_on_learned_unit_clause (int unit);
void check_no_solution_after_learning_empty_clause ();
void check_learned_empty_clause () {
if (solution)
check_no_solution_after_learning_empty_clause ();
}
void check_learned_unit_clause (int unit) {
if (solution)
check_solution_on_learned_unit_clause (unit);
}
void check_learned_clause () {
if (solution)
check_solution_on_learned_clause ();
}
void check_shrunken_clause (Clause *c) {
if (solution)
check_solution_on_shrunken_clause (c);
}
void check_assignment (int (External::*assignment) (int) const);
void check_satisfiable ();
void check_unsatisfiable ();
void check_solve_result (int res);
void update_molten_literals ();
/*----------------------------------------------------------------------*/
// For debugging and testing only. See 'solution.hpp' for more details.
// TODO: if elit > solution_size, elit is an extension variable. For now
// the clause will count as satisfied regardless. For the future one
// should check that actually there is one consistent extension for the
// solution that satisfies the clauses with this extension variable (by
// setting it to a value once a clause is learned which is not satisfied
// already).
//
inline int sol (int elit) const {
assert (solution);
assert (elit != INT_MIN);
int eidx = abs (elit);
if (eidx > max_var)
return 0;
else if (eidx > solution_size)
return elit;
signed char value = solution[eidx];
if (!value)
return 0;
if (elit < 0)
value = -value;
return value > 0 ? elit : -elit;
}
};
} // namespace CaDiCaL
#endif

File diff suppressed because it is too large Load Diff

921
src/sat/cadical/factor.cpp Normal file
View File

@ -0,0 +1,921 @@
#include "internal.hpp"
namespace CaDiCaL {
#define FACTORS 1
#define QUOTIENT 2
#define NOUNTED 4
inline bool factor_occs_size::operator() (unsigned a, unsigned b) {
size_t s = internal->occs (internal->u2i (a)).size ();
size_t t = internal->occs (internal->u2i (b)).size ();
if (s > t)
return true;
if (s < t)
return false;
return a > b;
}
// do full occurence list as in elim.cpp but filter out useless clauses
void Internal::factor_mode () {
reset_watches ();
assert (!watching ());
init_occs ();
const int size_limit = opts.factorsize;
vector<unsigned> bincount, largecount;
const unsigned max_lit = 2 * (max_var + 1);
enlarge_zero (bincount, max_lit);
if (size_limit > 2)
enlarge_zero (largecount, max_lit);
vector<Clause *> candidates;
int64_t &ticks = stats.ticks.factor;
ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *));
// push binary clauses on the occurrence stack.
for (const auto &c : clauses) {
ticks++;
if (c->garbage)
continue;
if (c->redundant && c->size > 2)
continue;
if (c->size > size_limit)
continue;
if (c->size == 2) {
const int lit = c->literals[0];
const int other = c->literals[1];
bincount[vlit (lit)]++;
bincount[vlit (other)]++;
occs (lit).push_back (c);
occs (other).push_back (c);
continue;
}
candidates.push_back (c);
for (const auto &lit : *c) {
largecount[vlit (lit)]++;
}
}
if (size_limit == 2)
return;
// iterate counts of larger clauses rounds often
const unsigned rounds = opts.factorcandrounds;
unsigned candidates_before = 0;
for (unsigned round = 1; round <= rounds; round++) {
LOG ("factor round %d", round);
if (candidates.size () == candidates_before)
break;
ticks += 1 + cache_lines (candidates.size (), sizeof (Clause *));
candidates_before = candidates.size ();
vector<unsigned> newlargecount;
enlarge_zero (newlargecount, max_lit);
const auto begin = candidates.begin ();
auto p = candidates.begin ();
auto q = p;
const auto end = candidates.end ();
while (p != end) {
Clause *c = *q++ = *p++;
ticks++;
for (const auto &lit : *c) {
const auto idx = vlit (lit);
if (bincount[idx] + largecount[idx] < 2) {
q--;
goto CONTINUE_WITH_NEXT_CLAUSE;
}
}
for (const auto &lit : *c) {
const auto idx = vlit (lit);
newlargecount[idx]++;
}
CONTINUE_WITH_NEXT_CLAUSE:
continue;
}
candidates.resize (q - begin);
largecount.swap (newlargecount);
}
// finally push remaining clause on the occurrence stack
for (const auto &c : candidates)
for (const auto &lit : *c)
occs (lit).push_back (c);
}
// go back to two watch scheme
void Internal::reset_factor_mode () {
reset_occs ();
init_watches ();
connect_watches ();
}
Factoring::Factoring (Internal *i, int64_t l)
: internal (i), limit (l), schedule (i) {
const unsigned max_var = internal->max_var;
const unsigned max_lit = 2 * (max_var + 1);
initial = max_var;
bound = internal->lim.elimbound;
enlarge_zero (count, max_lit);
quotients.first = quotients.last = 0;
}
Factoring::~Factoring () {
assert (counted.empty ());
assert (nounted.empty ());
assert (flauses.empty ());
internal->release_quotients (*this);
schedule.erase (); // actually not necessary
}
double Internal::tied_next_factor_score (int lit) {
double res = occs (lit).size ();
LOG ("watches score %g of %d", res, lit);
return res;
}
// the marks in cadical have 6 bits for marking but work on idx
// to mark everything (FACTORS, QUOTIENT, NOUNTED) we shift the bits
// depending on the sign of factor (+ bitmask)
// i.e. if factor is positive, we apply a bitmask to only get
// the first three bits (& 7u)
// otherwise we leftshift by 3 (>> 3) to get the bits 4,5,6
// use markfact, unmarkfact, getfact for this purpose.
//
Quotient *Internal::new_quotient (Factoring &factoring, int factor) {
assert (!getfact (factor, FACTORS));
markfact (factor, FACTORS);
Quotient *res = new Quotient (factor);
res->next = 0;
res->matched = 0;
Quotient *last = factoring.quotients.last;
res->bid = 0;
if (last) {
assert (factoring.quotients.first);
assert (!last->next);
last->next = res;
res->id = last->id + 1;
} else {
assert (!factoring.quotients.first);
factoring.quotients.first = res;
res->id = 0;
}
factoring.quotients.last = res;
res->prev = last;
LOG ("new quotient[%zu] with factor %d", res->id, factor);
return res;
}
void Internal::release_quotients (Factoring &factoring) {
for (Quotient *q = factoring.quotients.first, *next; q; q = next) {
next = q->next;
int factor = q->factor;
assert (getfact (factor, FACTORS));
unmarkfact (factor, FACTORS);
delete q;
}
factoring.quotients.first = factoring.quotients.last = 0;
}
size_t Internal::first_factor (Factoring &factoring, int factor) {
assert (!factoring.quotients.first);
Quotient *quotient = new_quotient (factoring, factor);
vector<Clause *> &qlauses = quotient->qlauses;
int64_t ticks = 0;
for (const auto &c : occs (factor)) {
qlauses.push_back (c);
ticks++;
}
size_t res = qlauses.size ();
LOG ("quotient[0] factor %d size %zu", factor, res);
// This invariant can of course be broken by previous factorings
// assert (res > 1);
stats.ticks.factor += ticks;
return res;
}
void Internal::clear_nounted (vector<int> &nounted) {
for (const auto &lit : nounted) {
assert (getfact (lit, NOUNTED));
unmarkfact (lit, NOUNTED);
}
nounted.clear ();
}
void Internal::clear_flauses (vector<Clause *> &flauses) {
for (auto c : flauses) {
assert (c->swept);
c->swept = false;
}
flauses.clear ();
}
Quotient *Internal::best_quotient (Factoring &factoring,
size_t *best_reduction_ptr) {
size_t factors = 1, best_reduction = 0;
Quotient *best = 0;
for (Quotient *q = factoring.quotients.first; q; q = q->next) {
size_t quotients = q->qlauses.size ();
size_t before_factorization = quotients * factors;
size_t after_factorization = quotients + factors;
if (before_factorization == after_factorization)
LOG ("quotient[%zu] factors %zu clauses into %zu thus no change",
factors - 1, before_factorization, after_factorization);
else if (before_factorization < after_factorization)
LOG ("quotient[%zu] factors %zu clauses into %zu thus %zu more",
factors - 1, before_factorization, after_factorization,
after_factorization - before_factorization);
else {
size_t delta = before_factorization - after_factorization;
LOG ("quotient[%zu] factors %zu clauses into %zu thus %zu less",
factors - 1, before_factorization, after_factorization, delta);
if (!best || best_reduction < delta) {
best_reduction = delta;
best = q;
}
}
factors++;
}
if (!best) {
LOG ("no decreasing quotient found");
return 0;
}
LOG ("best decreasing quotient[%zu] with reduction %zu", best->id,
best_reduction);
*best_reduction_ptr = best_reduction;
return best;
}
int Internal::next_factor (Factoring &factoring, unsigned *next_count_ptr) {
Quotient *last_quotient = factoring.quotients.last;
assert (last_quotient);
vector<Clause *> &last_clauses = last_quotient->qlauses;
vector<unsigned> &count = factoring.count;
vector<int> &counted = factoring.counted;
vector<Clause *> &flauses = factoring.flauses;
assert (counted.empty ());
assert (flauses.empty ());
const int initial = factoring.initial;
int64_t ticks = 1 + cache_lines (last_clauses.size (), sizeof (Clause *));
for (auto c : last_clauses) {
assert (!c->swept);
int min_lit = 0;
unsigned factors = 0;
size_t min_size = 0;
ticks++;
for (const auto &other : *c) {
if (getfact (other, FACTORS)) {
if (factors++)
break;
} else {
assert (!getfact (other, QUOTIENT));
markfact (other, QUOTIENT);
const size_t other_size = occs (other).size ();
if (!min_lit || other_size < min_size) {
min_lit = other;
min_size = other_size;
}
}
}
assert (factors);
if (factors == 1) {
assert (min_lit);
const int c_size = c->size;
vector<int> &nounted = factoring.nounted;
assert (nounted.empty ());
ticks += 1 + cache_lines (occs (min_lit).size (), sizeof (Clause *));
for (auto d : occs (min_lit)) {
if (c == d)
continue;
ticks++;
if (d->swept)
continue;
if (d->size != c_size)
continue;
int next = 0;
for (const auto &other : *d) {
if (getfact (other, QUOTIENT))
continue;
if (getfact (other, FACTORS))
goto CONTINUE_WITH_NEXT_MIN_WATCH;
if (getfact (other, NOUNTED))
goto CONTINUE_WITH_NEXT_MIN_WATCH;
if (next)
goto CONTINUE_WITH_NEXT_MIN_WATCH;
next = other;
}
assert (next);
if (abs (next) > abs (initial))
continue;
if (!active (next))
continue;
assert (!getfact (next, FACTORS));
assert (!getfact (next, NOUNTED));
markfact (next, NOUNTED);
nounted.push_back (next);
d->swept = true;
flauses.push_back (d);
if (!count[vlit (next)])
counted.push_back (next);
count[vlit (next)]++;
CONTINUE_WITH_NEXT_MIN_WATCH:;
}
clear_nounted (nounted);
}
for (const auto &other : *c)
if (getfact (other, QUOTIENT))
unmarkfact (other, QUOTIENT);
stats.ticks.factor += ticks;
ticks = 0;
if (stats.ticks.factor > factoring.limit)
break;
}
clear_flauses (flauses);
unsigned next_count = 0;
int next = 0;
if (stats.ticks.factor <= factoring.limit) {
unsigned ties = 0;
for (const auto &lit : counted) {
const unsigned lit_count = count[vlit (lit)];
if (lit_count < next_count)
continue;
if (lit_count == next_count) {
assert (lit_count);
ties++;
} else {
assert (lit_count > next_count);
next_count = lit_count;
next = lit;
ties = 1;
}
}
if (next_count < 2) {
LOG ("next factor count %u smaller than 2", next_count);
next = 0;
} else if (ties > 1) {
LOG ("found %u tied next factor candidate literals with count %u",
ties, next_count);
double next_score = -1;
for (const auto &lit : counted) {
const unsigned lit_count = count[vlit (lit)];
if (lit_count != next_count)
continue;
double lit_score = tied_next_factor_score (lit);
assert (lit_score >= 0);
LOG ("score %g of next factor candidate %d", lit_score, lit);
if (lit_score <= next_score)
continue;
next_score = lit_score;
next = lit;
}
assert (next_score >= 0);
assert (next);
LOG ("best score %g of next factor %d", next_score, next);
} else {
assert (ties == 1);
LOG ("single next factor %d with count %u", next, next_count);
}
}
for (const auto &lit : counted)
count[vlit (lit)] = 0;
counted.clear ();
assert (!next || next_count > 1);
*next_count_ptr = next_count;
return next;
}
void Internal::factorize_next (Factoring &factoring, int next,
unsigned expected_next_count) {
Quotient *last_quotient = factoring.quotients.last;
Quotient *next_quotient = new_quotient (factoring, next);
assert (last_quotient);
vector<Clause *> &last_clauses = last_quotient->qlauses;
vector<Clause *> &next_clauses = next_quotient->qlauses;
vector<size_t> &matches = next_quotient->matches;
vector<Clause *> &flauses = factoring.flauses;
assert (flauses.empty ());
int64_t ticks = 1 + cache_lines (last_clauses.size (), sizeof (Clause *));
size_t i = 0;
for (auto c : last_clauses) {
assert (!c->swept);
int min_lit = 0;
unsigned factors = 0;
size_t min_size = 0;
ticks++;
for (const auto &other : *c) {
if (getfact (other, FACTORS)) {
if (factors++)
break;
} else {
assert (!getfact (other, QUOTIENT));
markfact (other, QUOTIENT);
const size_t other_size = occs (other).size ();
if (!min_lit || other_size < min_size) {
min_lit = other;
min_size = other_size;
}
}
}
assert (factors);
if (factors == 1) {
assert (min_lit);
const int c_size = c->size;
ticks += 1 + cache_lines (occs (min_lit).size (), sizeof (Clause *));
for (auto d : occs (min_lit)) {
if (c == d)
continue;
ticks++;
if (d->swept)
continue;
if (d->size != c_size)
continue;
for (const auto &other : *d) {
if (getfact (other, QUOTIENT))
continue;
if (other != next)
goto CONTINUE_WITH_NEXT_MIN_WATCH;
}
LOG (c, "matched");
LOG (d, "keeping");
next_clauses.push_back (d);
matches.push_back (i);
flauses.push_back (d);
d->swept = true;
break;
CONTINUE_WITH_NEXT_MIN_WATCH:;
}
}
for (const auto &other : *c)
if (getfact (other, QUOTIENT))
unmarkfact (other, QUOTIENT);
i++;
}
clear_flauses (flauses);
stats.ticks.factor += ticks;
assert (expected_next_count <= next_clauses.size ());
(void) expected_next_count;
}
// We only need to enlarge factoring.count as everything else is
// initialized in internal
void Internal::resize_factoring (Factoring &factoring, int lit) {
assert (lit > 0);
size_t new_var_size = lit + 1;
size_t new_lit_size = 2 * new_var_size;
enlarge_zero (factoring.count, new_lit_size);
}
void Internal::flush_unmatched_clauses (Quotient *q) {
Quotient *prev = q->prev;
vector<size_t> &q_matches = q->matches, &prev_matches = prev->matches;
vector<Clause *> &q_clauses = q->qlauses, &prev_clauses = prev->qlauses;
const size_t n = q_clauses.size ();
assert (n == q_matches.size ());
bool prev_is_first = !prev->id;
size_t i = 0;
while (i < q_matches.size ()) {
size_t j = q_matches[i];
q_matches[i] = i;
assert (i <= j);
if (!prev_is_first) {
size_t matches = prev_matches[j];
prev_matches[i] = matches;
}
Clause *c = prev_clauses[j];
prev_clauses[i] = c;
i++;
}
LOG ("flushing %zu clauses of quotient[%zu]", prev_clauses.size () - n,
prev->id);
if (!prev_is_first)
prev_matches.resize (n);
prev_clauses.resize (n);
}
// special case when we have two quotients with negated factors.
// in this case, factoring does not make sense, and instead we
// can resolve the clauses of the two quotients.
// this subsumes all clauses in all quotients.
void Internal::add_self_subsuming_factor (Quotient *q, Quotient *p) {
const int factor = q->factor;
const int not_factor = p->factor;
assert (-factor == not_factor);
LOG (
"adding self subsuming factor because blocked clause is a tautology");
for (auto c : q->qlauses) {
for (const auto &lit : *c) {
if (lit == factor)
continue;
clause.push_back (lit);
}
if (lrat) {
for (auto d : p->qlauses) {
bool match = true;
for (const auto &lit : *d) {
if (lit == not_factor)
continue;
if (std::find (clause.begin (), clause.end (), lit) ==
clause.end ()) {
match = false;
break;
}
}
if (match) {
lrat_chain.push_back (d->id);
break;
}
}
lrat_chain.push_back (c->id);
assert (lrat_chain.size () == 2);
}
if (clause.size () > 1) {
new_factor_clause ();
} else {
const int unit = clause[0];
const signed char tmp = val (unit);
if (!tmp)
assign_unit (unit);
else if (tmp < 0) {
if (lrat) {
int64_t id = unit_id (-unit);
lrat_chain.push_back (id);
std::reverse (lrat_chain.begin (), lrat_chain.end ());
}
learn_empty_clause ();
clause.clear ();
lrat_chain.clear ();
break;
}
}
clause.clear ();
lrat_chain.clear ();
}
}
bool Internal::self_subsuming_factor (Quotient *q) {
Quotient *x = 0, *y = 0;
bool found = false;
for (Quotient *p = q; p; p = p->prev) {
const int factor = p->factor;
Flags &f = flags (factor);
if (f.seen) {
assert (std::find (analyzed.begin (), analyzed.end (), -factor) !=
analyzed.end ());
found = true;
x = p;
for (Quotient *r = q; r; r = r->prev) {
if (r->factor != -factor)
continue;
y = r;
break;
}
break;
}
analyzed.push_back (factor);
f.seen = true;
}
assert (!found || (x && y));
clear_analyzed_literals ();
if (found) {
add_self_subsuming_factor (x, y);
return true;
}
return false;
}
// this is a pure binary clauses containing fresh and one other literal
// it is added for all applicable quotients.
void Internal::add_factored_divider (Quotient *q, int fresh) {
const int factor = q->factor;
LOG ("factored %d divider %d", factor, fresh);
clause.push_back (factor);
clause.push_back (fresh);
new_factor_clause ();
clause.clear ();
if (lrat)
mini_chain.push_back (-clause_id);
}
// this clause is blocked on fresh, i.e., it contains all literals from
// the binaries above, but negated. This is only added to the proof, to
// make checking easier.
void Internal::blocked_clause (Quotient *q, int not_fresh) {
if (!proof)
return;
int64_t new_id = ++clause_id;
q->bid = new_id;
assert (clause.empty ());
for (Quotient *p = q; p; p = p->prev)
clause.push_back (-p->factor);
clause.push_back (not_fresh);
assert (!lrat || mini_chain.size ());
proof->add_derived_clause (new_id, true, clause, mini_chain);
mini_chain.clear ();
clause.clear ();
}
// this is the other side of the factored clauses. To derive these,
// one can resolved the blocked clause on all matching clauses of
// one type
void Internal::add_factored_quotient (Quotient *q, int not_fresh) {
LOG ("adding factored quotient[%zu] clauses", q->id);
const int factor = q->factor;
assert (lrat_chain.empty ());
auto qlauses = q->qlauses;
for (unsigned idx = 0; idx < qlauses.size (); idx++) {
const auto c = qlauses[idx];
assert (clause.empty ());
for (const auto &other : *c) {
if (other == factor) {
continue;
}
clause.push_back (other);
}
if (lrat) {
assert (proof);
assert (q->bid);
unsigned idxtoo = idx;
for (Quotient *p = q; p; p = p->prev) {
lrat_chain.push_back (p->qlauses[idxtoo]->id);
if (p->prev)
idxtoo = p->matches[idx];
}
lrat_chain.push_back (q->bid);
}
clause.push_back (not_fresh);
new_factor_clause ();
clause.clear ();
lrat_chain.clear ();
}
if (proof) {
for (Quotient *p = q; p; p = p->prev) {
clause.push_back (-p->factor);
}
clause.push_back (not_fresh);
proof->delete_clause (q->bid, true, clause);
clause.clear ();
}
}
// remove deleted clauses once factored.
void Internal::eagerly_remove_from_occurences (Clause *c) {
for (const auto &lit : *c) {
auto &occ = occs (lit);
auto p = occ.begin ();
auto q = occ.begin ();
auto begin = occ.begin ();
auto end = occ.end ();
while (p != end) {
*q = *p++;
if (*q != c)
q++;
}
assert (q + 1 == p);
occ.resize (q - begin);
}
}
// delete the factored clauses
void Internal::delete_unfactored (Quotient *q) {
LOG ("deleting unfactored quotient[%zu] clauses", q->id);
for (auto c : q->qlauses) {
eagerly_remove_from_occurences (c);
mark_garbage (c);
stats.literals_unfactored += c->size;
stats.clauses_unfactored++;
}
}
// update the priority queue for scheduling
void Internal::update_factored (Factoring &factoring, Quotient *q) {
const int factor = q->factor;
update_factor_candidate (factoring, factor);
update_factor_candidate (factoring, -factor);
for (auto c : q->qlauses) {
LOG (c, "deleting unfactored");
for (const auto &lit : *c)
if (lit != factor)
update_factor_candidate (factoring, lit);
}
}
bool Internal::apply_factoring (Factoring &factoring, Quotient *q) {
for (Quotient *p = q; p->prev; p = p->prev)
flush_unmatched_clauses (p);
if (self_subsuming_factor (q)) {
for (Quotient *p = q; p; p = p->prev)
delete_unfactored (p);
for (Quotient *p = q; p; p = p->prev)
update_factored (factoring, p);
return true;
}
const int fresh = get_new_extension_variable ();
if (!fresh)
return false;
stats.factored++;
factoring.fresh.push_back (fresh);
for (Quotient *p = q; p; p = p->prev)
add_factored_divider (p, fresh);
const int not_fresh = -fresh;
blocked_clause (q, not_fresh);
add_factored_quotient (q, not_fresh);
for (Quotient *p = q; p; p = p->prev)
delete_unfactored (p);
for (Quotient *p = q; p; p = p->prev)
update_factored (factoring, p);
assert (fresh > 0);
resize_factoring (factoring, fresh);
return true;
}
void Internal::update_factor_candidate (Factoring &factoring, int lit) {
FactorSchedule &schedule = factoring.schedule;
const size_t size = occs (lit).size ();
const unsigned idx = vlit (lit);
if (schedule.contains (idx))
schedule.update (idx);
else if (size > 1) {
schedule.push_back (idx);
}
}
void Internal::schedule_factorization (Factoring &factoring) {
for (const auto &idx : vars) {
if (active (idx)) {
Flags &f = flags (idx);
const int lit = idx;
const int not_lit = -lit;
if (f.factor & 1)
update_factor_candidate (factoring, lit);
if (f.factor & 2)
update_factor_candidate (factoring, not_lit);
}
}
#ifndef QUIET
size_t size_cands = factoring.schedule.size ();
VERBOSE (2, "scheduled %zu factorization candidate literals %.0f %%",
size_cands, percent (size_cands, max_var));
#endif
}
bool Internal::run_factorization (int64_t limit) {
Factoring factoring = Factoring (this, limit);
schedule_factorization (factoring);
bool done = false;
#ifndef QUIET
unsigned factored = 0;
#endif
int64_t *ticks = &stats.ticks.factor;
VERBOSE (3, "factorization limit of %" PRIu64 " ticks", limit - *ticks);
while (!unsat && !done && !factoring.schedule.empty ()) {
const unsigned ufirst = factoring.schedule.pop_front ();
LOG ("next factor candidate %d", ufirst);
const int first = u2i (ufirst);
const int first_idx = vidx (first);
if (!active (first_idx))
continue;
if (!occs (first).size ()) {
factoring.schedule.clear ();
break;
}
if (*ticks > limit) {
VERBOSE (2, "factorization ticks limit hit");
break;
}
if (terminated_asynchronously ())
break;
Flags &f = flags (first_idx);
const unsigned bit = 1u << (first < 0);
if (!(f.factor & bit))
continue;
f.factor &= ~bit;
const size_t first_count = first_factor (factoring, first);
if (first_count > 1) {
for (;;) {
unsigned next_count;
const int next = next_factor (factoring, &next_count);
if (next == 0)
break;
assert (next_count > 1);
if (next_count < 2)
break;
factorize_next (factoring, next, next_count);
}
size_t reduction;
Quotient *q = best_quotient (factoring, &reduction);
if (q && (int) reduction > factoring.bound) {
if (apply_factoring (factoring, q)) {
#ifndef QUIET
factored++;
#endif
} else
done = true;
}
}
release_quotients (factoring);
}
// since we cannot remove elements from the heap we check wether the
// first element in the heap has occurences
bool completed = factoring.schedule.empty ();
if (!completed) {
const unsigned idx = factoring.schedule.front ();
completed = occs (u2i (idx)).empty ();
}
// kissat initializes scores for new variables at this point, however
// this is actually done already during resize of internal
#ifndef QUIET
report ('f', !factored);
#endif
return completed;
}
int Internal::get_new_extension_variable () {
const int current_max_external = external->max_var;
const int new_external = current_max_external + 1;
const int new_internal = external->internalize (new_external, true);
// one sideeffect of internalize is enlarging the internal datastructures
// which can initialize the watches (wtab)
if (watching ())
reset_watches ();
// it does not enlarge otab, however, so we do this manually
init_occs ();
assert (vlit (new_internal));
return new_internal;
}
bool Internal::factor () {
if (unsat)
return false;
if (terminated_asynchronously ())
return false;
if (!opts.factor)
return false;
// The following assertion fails if there are *only* user propagator
// clauses (which are redundant).
// assert (stats.mark.factor || clauses.empty ());
if (last.factor.marked >= stats.mark.factor) {
VERBOSE (3,
"factorization skipped as no literals have been"
"marked to be added (%" PRIu64 " < %" PRIu64 ")",
last.factor.marked, stats.mark.factor);
return false;
}
assert (!level);
SET_EFFORT_LIMIT (limit, factor, stats.factor);
if (!stats.factor)
limit += opts.factoriniticks * 1e6;
START_SIMPLIFIER (factor, FACTOR);
stats.factor++;
#ifndef QUIET
struct {
int64_t variables, clauses, ticks;
} before, after, delta;
before.variables = stats.variables_extension + stats.variables_original;
before.ticks = stats.ticks.factor;
before.clauses = stats.current.irredundant;
#endif
factor_mode ();
bool completed = run_factorization (limit);
reset_factor_mode ();
propagated = 0;
if (!unsat && !propagate ()) {
learn_empty_clause ();
}
#ifndef QUIET
after.variables = stats.variables_extension + stats.variables_original;
after.clauses = stats.current.irredundant;
after.ticks = stats.ticks.factor;
delta.variables = after.variables - before.variables;
delta.clauses = before.clauses - after.clauses;
delta.ticks = after.ticks - before.ticks;
VERBOSE (2, "used %f million factorization ticks", delta.ticks * 1e-6);
phase ("factorization", stats.factor,
"introduced %" PRId64 " extension variables %.0f%%",
delta.variables, percent (delta.variables, before.variables));
phase ("factorization", stats.factor,
"removed %" PRId64 " irredundant clauses %.0f%%", delta.clauses,
percent (delta.clauses, before.clauses));
#endif
if (completed)
last.factor.marked = stats.mark.factor;
STOP_SIMPLIFIER (factor, FACTOR);
return true;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,54 @@
#ifndef _factor_hpp_INCLUDED
#define _factor_hpp_INCLUDED
#include "clause.hpp"
#include "heap.hpp"
namespace CaDiCaL {
struct Internal;
struct factor_occs_size {
Internal *internal;
factor_occs_size (Internal *i) : internal (i) {}
bool operator() (unsigned a, unsigned b);
};
struct Quotient {
Quotient (int f) : factor (f) {}
~Quotient () {}
int factor;
size_t id;
int64_t bid; // for LRAT
Quotient *prev, *next;
vector<Clause *> qlauses;
vector<size_t> matches;
size_t matched;
};
typedef heap<factor_occs_size> FactorSchedule;
struct Factoring {
Factoring (Internal *, int64_t);
~Factoring ();
// These are initialized by the constructor
Internal *internal;
int64_t limit;
FactorSchedule schedule;
int initial;
int bound;
vector<unsigned> count;
vector<int> fresh;
vector<int> counted;
vector<int> nounted;
vector<Clause *> flauses;
struct {
Quotient *first, *last;
} quotients;
};
} // namespace CaDiCaL
#endif

506
src/sat/cadical/file.cpp Normal file
View File

@ -0,0 +1,506 @@
#include "internal.hpp"
/*------------------------------------------------------------------------*/
// Some more low-level 'C' headers.
extern "C" {
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
}
#ifndef _WIN32
extern "C" {
#include <sys/wait.h>
}
#endif
#if defined(__APPLE__) || defined(__MACH__)
extern "C" {
#include <libproc.h>
#include <sys/proc_info.h>
}
#include <mutex>
#endif
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Private constructor.
File::File (Internal *i, bool w, int c, int p, FILE *f, const char *n)
: internal (i),
#if !defined(QUIET) || !defined(NDEBUG)
writing (w),
#endif
close_file (c), child_pid (p), file (f), _name (strdup (n)),
_lineno (1), _bytes (0) {
(void) w;
assert (f), assert (n);
}
/*------------------------------------------------------------------------*/
bool File::exists (const char *path) {
struct stat buf;
if (stat (path, &buf))
return false;
if (access (path, R_OK))
return false;
return true;
}
bool File::writable (const char *path) {
int res;
if (!path)
res = 1;
else if (!strcmp (path, "/dev/null"))
res = 0;
else {
if (!*path)
res = 2;
else {
struct stat buf;
const char *p = strrchr (path, '/');
if (!p) {
if (stat (path, &buf))
res = ((errno == ENOENT) ? 0 : -2);
else if (S_ISDIR (buf.st_mode))
res = 3;
else
res = (access (path, W_OK) ? 4 : 0);
} else if (!p[1])
res = 5;
else {
size_t len = p - path;
char *dirname = new char[len + 1];
strncpy (dirname, path, len);
dirname[len] = 0;
if (stat (dirname, &buf))
res = 6;
else if (!S_ISDIR (buf.st_mode))
res = 7;
else if (access (dirname, W_OK))
res = 8;
else if (stat (path, &buf))
res = (errno == ENOENT) ? 0 : -3;
else
res = access (path, W_OK) ? 9 : 0;
delete[] dirname;
}
}
}
return !res;
}
bool File::piping () {
struct stat stat;
int fd = fileno (file);
if (fstat (fd, &stat))
return true;
return S_ISFIFO (stat.st_mode);
}
// These are signatures for supported compressed file types. In 2018 the
// SAT Competition was running on StarExec and used internally 'bzip2'
// compressed files, but gave them uncompressed to the solver using exactly
// the same path (with '.bz2' suffix). Then 'CaDiCaL' tried to read that
// actually uncompressed file through 'bzip2', which of course failed. Now
// we double check and fall back to reading the file as is, if the signature
// does not match after issuing a warning.
static int xzsig[] = {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00, 0x00, EOF};
static int bz2sig[] = {0x42, 0x5A, 0x68, EOF};
static int gzsig[] = {0x1F, 0x8B, EOF};
static int sig7z[] = {0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, EOF};
static int lzmasig[] = {0x5D, EOF};
bool File::match (Internal *internal, const char *path, const int *sig) {
assert (path);
FILE *tmp = fopen (path, "r");
if (!tmp) {
WARNING ("failed to open '%s' to check signature", path);
return false;
}
bool res = true;
for (const int *p = sig; res && (*p != EOF); p++)
res = (cadical_getc_unlocked (tmp) == *p);
fclose (tmp);
if (!res)
WARNING ("file type signature check for '%s' failed", path);
return res;
}
size_t File::size (const char *path) {
struct stat buf;
if (stat (path, &buf))
return 0;
return (size_t) buf.st_size;
}
// Check that 'prg' is in the 'PATH' and thus can be found if executed
// through 'popen' or 'exec'.
char *File::find_program (const char *prg) {
size_t prglen = strlen (prg);
const char *c = getenv ("PATH");
if (!c)
return 0;
size_t len = strlen (c);
char *e = new char[len + 1];
strcpy (e, c);
char *res = 0;
for (char *p = e, *q; !res && p < e + len; p = q) {
for (q = p; *q && *q != ':'; q++)
;
*q++ = 0;
size_t pathlen = (q - p) + prglen;
char *path = new char[pathlen + 1];
snprintf (path, pathlen + 1, "%s/%s", p, prg);
assert (strlen (path) == pathlen);
if (exists (path))
res = path;
else
delete[] path;
}
delete[] e;
return res;
}
/*------------------------------------------------------------------------*/
FILE *File::open_file (Internal *internal, const char *path,
const char *mode) {
(void) internal;
return fopen (path, mode);
}
FILE *File::read_file (Internal *internal, const char *path) {
MSG ("opening file to read '%s'", path);
return open_file (internal, path, "r");
}
FILE *File::write_file (Internal *internal, const char *path) {
MSG ("opening file to write '%s'", path);
return open_file (internal, path, "wb");
}
/*------------------------------------------------------------------------*/
void File::split_str (const char *command, std::vector<char *> &argv) {
const char *c = command;
while (*c && *c == ' ')
c++;
while (*c) {
const char *p = c;
while (*p && *p != ' ')
p++;
const size_t bytes = p - c;
char *arg = new char[bytes + 1];
(void) strncpy (arg, c, bytes);
arg[bytes] = 0;
argv.push_back (arg);
while (*p && *p == ' ')
p++;
c = p;
}
}
void File::delete_str_vector (std::vector<char *> &argv) {
for (auto str : argv)
delete[] str;
}
FILE *File::open_pipe (Internal *internal, const char *fmt,
const char *path, const char *mode) {
#ifdef QUIET
(void) internal;
#endif
size_t prglen = 0;
while (fmt[prglen] && fmt[prglen] != ' ')
prglen++;
char *prg = new char[prglen + 1];
strncpy (prg, fmt, prglen);
prg[prglen] = 0;
char *found = find_program (prg);
if (found)
MSG ("found '%s' in path for '%s'", found, prg);
if (!found)
MSG ("did not find '%s' in path", prg);
delete[] prg;
if (!found)
return 0;
delete[] found;
size_t cmd_size = strlen (fmt) + strlen (path);
char *cmd = new char[cmd_size];
snprintf (cmd, cmd_size, fmt, path);
FILE *res = popen (cmd, mode);
delete[] cmd;
return res;
}
FILE *File::read_pipe (Internal *internal, const char *fmt, const int *sig,
const char *path) {
if (!File::exists (path)) {
LOG ("file '%s' does not exist", path);
return 0;
}
LOG ("file '%s' exists", path);
if (sig && !File::match (internal, path, sig))
return 0;
LOG ("file '%s' matches signature for '%s'", path, fmt);
MSG ("opening pipe to read '%s'", path);
return open_pipe (internal, fmt, path, "r");
}
#ifndef _WIN32
#if defined(__APPLE__) || defined(__MACH__)
static std::mutex compressed_file_writing_mutex;
#endif
FILE *File::write_pipe (Internal *internal, const char *command,
const char *path, int &child_pid) {
assert (command[0] && command[0] != ' ');
MSG ("writing through command '%s' to '%s'", command, path);
#ifdef QUIET
(void) internal;
#endif
std::vector<char *> args;
split_str (command, args);
assert (!args.empty ());
args.push_back (0);
char **argv = args.data ();
char *absolute_command_path = find_program (argv[0]);
int pipe_fds[2], out;
FILE *res = 0;
#if defined(__APPLE__) || defined(__MACH__)
compressed_file_writing_mutex.lock ();
#endif
if (!absolute_command_path)
MSG ("could not find '%s' in 'PATH' environment variable", argv[0]);
else if (::pipe (pipe_fds) < 0)
MSG ("could not generate pipe to '%s' command", command);
else if ((out = ::open (path, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0)
MSG ("could not open '%s' for writing", path);
else if ((child_pid = ::fork ()) < 0) {
MSG ("could not fork process to execute '%s' command", command);
::close (out);
} else if (child_pid) {
::close (pipe_fds[0]);
res = ::fdopen (pipe_fds[1], "wb");
} else {
// Connect stdin and stdout in child
::dup2 (pipe_fds[0], 0);
::dup2 (out, 1);
// Make sure to close all non-required fds to not cause hangs.
// This is handled now by closefrom and remains for documentation
// purposes:
//
// ::close (pipe_fds[0]);
// ::close (pipe_fds[1]);
// ::close (out);
// Surpress '7z' verbose output on 'stderr'.
if (command[0] == '7') {
::close (2);
}
// Before the fork another thread could have created more fds. These
// fds are cloned into the child process. As this inhibits pipes to
// be closed by the parent process we have to close all of the
// erroneously cloned fds here.
#ifndef NCLOSEFROM
::closefrom (3);
#else
// Simplistic replacement on Unix without 'closefrom'.
for (int fd = 3; fd != FD_SETSIZE; fd++)
::close (fd);
#endif
execv (absolute_command_path, argv);
_exit (1);
}
if (absolute_command_path)
delete[] absolute_command_path;
delete_str_vector (args);
#ifdef QUIET
(void) internal;
#endif
#if defined(__APPLE__) || defined(__MACH__)
if (!res)
compressed_file_writing_mutex.unlock ();
#endif
return res;
}
#endif
/*------------------------------------------------------------------------*/
File *File::read (Internal *internal, FILE *f, const char *n) {
return new File (internal, false, 0, 0, f, n);
}
File *File::write (Internal *internal, FILE *f, const char *n) {
return new File (internal, true, 0, 0, f, n);
}
File *File::read (Internal *internal, const char *path) {
FILE *file;
int close_input = 2;
if (has_suffix (path, ".xz")) {
file = read_pipe (internal, "xz -c -d %s", xzsig, path);
if (!file)
goto READ_FILE;
} else if (has_suffix (path, ".lzma")) {
file = read_pipe (internal, "lzma -c -d %s", lzmasig, path);
if (!file)
goto READ_FILE;
} else if (has_suffix (path, ".bz2")) {
file = read_pipe (internal, "bzip2 -c -d %s", bz2sig, path);
if (!file)
goto READ_FILE;
} else if (has_suffix (path, ".gz")) {
file = read_pipe (internal, "gzip -c -d %s", gzsig, path);
if (!file)
goto READ_FILE;
} else if (has_suffix (path, ".7z")) {
file = read_pipe (internal, "7z x -so %s 2>/dev/null", sig7z, path);
if (!file)
goto READ_FILE;
} else {
READ_FILE:
file = read_file (internal, path);
close_input = 1;
}
if (!file)
return 0;
return new File (internal, false, close_input, 0, file, path);
}
File *File::write (Internal *internal, const char *path) {
FILE *file;
int close_output = 3, child_pid = 0;
#ifndef _WIN32
if (has_suffix (path, ".xz"))
file = write_pipe (internal, "xz -c", path, child_pid);
else if (has_suffix (path, ".bz2"))
file = write_pipe (internal, "bzip2 -c", path, child_pid);
else if (has_suffix (path, ".gz"))
file = write_pipe (internal, "gzip -c", path, child_pid);
else if (has_suffix (path, ".7z"))
file = write_pipe (internal, "7z a -an -txz -si -so", path, child_pid);
else
#endif
file = write_file (internal, path), close_output = 1;
if (!file)
return 0;
return new File (internal, true, close_output, child_pid, file, path);
}
void File::close (bool print) {
assert (file);
#ifndef QUIET
if (internal->opts.quiet)
print = false;
else if (internal->opts.verbose > 0)
print = true;
#endif
if (close_file == 0) {
if (print)
MSG ("disconnecting from '%s'", name ());
}
if (close_file == 1) {
if (print)
MSG ("closing file '%s'", name ());
fclose (file);
}
if (close_file == 2) {
if (print)
MSG ("closing input pipe to read '%s'", name ());
pclose (file);
}
#ifndef _WIN32
if (close_file == 3) {
if (print)
MSG ("closing output pipe to write '%s'", name ());
fclose (file);
waitpid (child_pid, 0, 0);
#if defined(__APPLE__) || defined(__MACH__)
compressed_file_writing_mutex.unlock ();
#endif
}
#endif
file = 0; // mark as closed
// TODO what about error checking for 'fclose', 'pclose' or 'waitpid'?
#ifndef QUIET
if (print) {
if (writing) {
uint64_t written_bytes = bytes ();
double written_mb = written_bytes / (double) (1 << 20);
MSG ("after writing %" PRIu64 " bytes %.1f MB", written_bytes,
written_mb);
if (close_file == 3) {
size_t actual_bytes = size (name ());
if (actual_bytes) {
double actual_mb = actual_bytes / (double) (1 << 20);
MSG ("deflated to %zd bytes %.1f MB", actual_bytes, actual_mb);
MSG ("factor %.2f (%.2f%% compression)",
relative (written_bytes, actual_bytes),
percent (actual_bytes, written_bytes));
} else
MSG ("but could not determine actual size of written file");
}
} else {
uint64_t read_bytes = bytes ();
double read_mb = read_bytes / (double) (1 << 20);
MSG ("after reading %" PRIu64 " bytes %.1f MB", read_bytes, read_mb);
if (close_file == 2) {
size_t actual_bytes = size (name ());
double actual_mb = actual_bytes / (double) (1 << 20);
MSG ("inflated from %zd bytes %.1f MB", actual_bytes, actual_mb);
MSG ("factor %.2f (%.2f%% compression)",
relative (read_bytes, actual_bytes),
percent (actual_bytes, read_bytes));
}
}
}
#endif
}
void File::flush () {
assert (file);
fflush (file);
}
File::~File () {
if (file)
close ();
free (_name);
}
} // namespace CaDiCaL

210
src/sat/cadical/file.hpp Normal file
View File

@ -0,0 +1,210 @@
#ifndef _file_hpp_INCLUDED
#define _file_hpp_INCLUDED
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <vector>
#ifndef NDEBUG
#include <climits>
#endif
/*------------------------------------------------------------------------*/
#ifndef NUNLOCKED
#define cadical_putc_unlocked putc_unlocked
#define cadical_getc_unlocked getc_unlocked
#else
#define cadical_putc_unlocked putc
#define cadical_getc_unlocked getc
#endif
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
// Wraps a 'C' file 'FILE' with name and supports zipped reading and writing
// through 'popen' using external helper tools. Reading has line numbers.
// Compression and decompression relies on external utilities, e.g., 'gzip',
// 'bzip2', 'xz', and '7z', which should be in the 'PATH'.
struct Internal;
class File {
Internal *internal;
#if !defined(QUIET) || !defined(NDEBUG)
bool writing;
#endif
int close_file; // need to close file (1=fclose, 2=pclose, 3=pipe)
int child_pid;
FILE *file;
char *_name;
uint64_t _lineno;
uint64_t _bytes;
File (Internal *, bool, int, int, FILE *, const char *);
static FILE *open_file (Internal *, const char *path, const char *mode);
static FILE *read_file (Internal *, const char *path);
static FILE *write_file (Internal *, const char *path);
static void split_str (const char *, std::vector<char *> &);
static void delete_str_vector (std::vector<char *> &);
static FILE *open_pipe (Internal *, const char *fmt, const char *path,
const char *mode);
static FILE *read_pipe (Internal *, const char *fmt, const int *sig,
const char *path);
#ifndef __WIN32
static FILE *write_pipe (Internal *, const char *fmt, const char *path,
int &child_pid);
#endif
public:
static char *find_program (const char *prg); // search in 'PATH'
static bool exists (const char *path); // file exists?
static bool writable (const char *path); // can write to that file?
static size_t size (const char *path); // file size in bytes
bool piping (); // Is opened file a pipe?
// Does the file match the file type signature.
//
static bool match (Internal *, const char *path, const int *sig);
// Read from existing file. Assume given name.
//
static File *read (Internal *, FILE *f, const char *name);
// Open file from path name for reading (possibly through opening a pipe
// to a decompression utility, based on the suffix).
//
static File *read (Internal *, const char *path);
// Same for writing as for reading above.
//
static File *write (Internal *, FILE *, const char *name);
static File *write (Internal *, const char *path);
~File ();
// Using the 'unlocked' versions here is way faster but
// not thread safe if the same file is used by different
// threads, which on the other hand currently is impossible.
int get () {
assert (!writing);
int res = cadical_getc_unlocked (file);
if (res == '\n')
_lineno++;
if (res != EOF)
_bytes++;
return res;
}
bool put (char ch) {
assert (writing);
if (cadical_putc_unlocked (ch, file) == EOF)
return false;
_bytes++;
return true;
}
bool endl () { return put ('\n'); }
bool put (unsigned char ch) {
assert (writing);
if (cadical_putc_unlocked (ch, file) == EOF)
return false;
_bytes++;
return true;
}
bool put (const char *s) {
for (const char *p = s; *p; p++)
if (!put (*p))
return false;
return true;
}
bool put (int lit) {
assert (writing);
if (!lit)
return put ('0');
else if (lit == -2147483648) {
assert (lit == INT_MIN);
return put ("-2147483648");
} else {
char buffer[11];
int i = sizeof buffer;
buffer[--i] = 0;
assert (lit != INT_MIN);
unsigned idx = abs (lit);
while (idx) {
assert (i > 0);
buffer[--i] = '0' + idx % 10;
idx /= 10;
}
if (lit < 0 && !put ('-'))
return false;
return put (buffer + i);
}
}
bool put (int64_t l) {
assert (writing);
if (!l)
return put ('0');
else if (l == INT64_MIN) {
assert (sizeof l == 8);
return put ("-9223372036854775808");
} else {
char buffer[21];
int i = sizeof buffer;
buffer[--i] = 0;
assert (l != INT64_MIN);
uint64_t k = l < 0 ? -l : l;
while (k) {
assert (i > 0);
buffer[--i] = '0' + k % 10;
k /= 10;
}
if (l < 0 && !put ('-'))
return false;
return put (buffer + i);
}
}
bool put (uint64_t l) {
assert (writing);
if (!l)
return put ('0');
else {
char buffer[22];
int i = sizeof buffer;
buffer[--i] = 0;
while (l) {
assert (i > 0);
buffer[--i] = '0' + l % 10;
l /= 10;
}
return put (buffer + i);
}
}
const char *name () const { return _name; }
uint64_t lineno () const { return _lineno; }
uint64_t bytes () const { return _bytes; }
void connect_internal (Internal *i) { internal = i; }
bool closed () { return !file; }
void close (bool print = false);
void flush ();
};
} // namespace CaDiCaL
#endif

135
src/sat/cadical/flags.cpp Normal file
View File

@ -0,0 +1,135 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::mark_fixed (int lit) {
if (external->fixed_listener) {
int elit = externalize (lit);
assert (elit);
const int eidx = abs (elit);
if (!external->ervars[eidx])
external->fixed_listener->notify_fixed_assignment (elit);
}
Flags &f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::FIXED;
LOG ("fixed %d", abs (lit));
stats.all.fixed++;
stats.now.fixed++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.fixed ());
if (external_prop && private_steps) {
// If pre/inprocessing found a fixed assignment, we want the propagator
// to know about it.
// But at that point it is not guaranteed to be already on the trail, so
// notification will happen only later.
assert (!level);
}
}
void Internal::mark_eliminated (int lit) {
Flags &f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::ELIMINATED;
LOG ("eliminated %d", abs (lit));
stats.all.eliminated++;
stats.now.eliminated++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.eliminated ());
}
void Internal::mark_pure (int lit) {
Flags &f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::PURE;
LOG ("pure %d", abs (lit));
stats.all.pure++;
stats.now.pure++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.pure ());
}
void Internal::mark_substituted (int lit) {
Flags &f = flags (lit);
assert (f.status == Flags::ACTIVE);
f.status = Flags::SUBSTITUTED;
LOG ("substituted %d", abs (lit));
stats.all.substituted++;
stats.now.substituted++;
stats.inactive++;
assert (stats.active);
stats.active--;
assert (!active (lit));
assert (f.substituted ());
}
void Internal::mark_active (int lit) {
Flags &f = flags (lit);
assert (f.status == Flags::UNUSED);
f.status = Flags::ACTIVE;
LOG ("activate %d previously unused", abs (lit));
assert (stats.inactive);
stats.inactive--;
assert (stats.unused);
stats.unused--;
stats.active++;
assert (active (lit));
}
void Internal::reactivate (int lit) {
assert (!active (lit));
Flags &f = flags (lit);
assert (f.status != Flags::FIXED);
assert (f.status != Flags::UNUSED);
#ifdef LOGGING
const char *msg = 0;
#endif
switch (f.status) {
default:
case Flags::ELIMINATED:
assert (f.status == Flags::ELIMINATED);
assert (stats.now.eliminated > 0);
stats.now.eliminated--;
#ifdef LOGGING
msg = "eliminated";
#endif
break;
case Flags::SUBSTITUTED:
#ifdef LOGGING
msg = "substituted";
#endif
assert (stats.now.substituted > 0);
stats.now.substituted--;
break;
case Flags::PURE:
#ifdef LOGGING
msg = "pure literal";
#endif
assert (stats.now.pure > 0);
stats.now.pure--;
break;
}
#ifdef LOGGING
assert (msg);
LOG ("reactivate previously %s %d", msg, abs (lit));
#endif
f.status = Flags::ACTIVE;
f.sweep = false;
assert (active (lit));
stats.reactivated++;
assert (stats.inactive > 0);
stats.inactive--;
stats.active++;
}
} // namespace CaDiCaL

85
src/sat/cadical/flags.hpp Normal file
View File

@ -0,0 +1,85 @@
#ifndef _flags_hpp_INCLUDED
#define _flags_hpp_INCLUDED
namespace CaDiCaL {
struct Flags { // Variable flags.
// The first set of flags is related to 'analyze' and 'minimize'.
//
bool seen : 1; // seen in generating first UIP clause in 'analyze'
bool keep : 1; // keep in learned clause in 'minimize'
bool poison : 1; // can not be removed in 'minimize'
bool removable : 1; // can be removed in 'minimize'
bool shrinkable : 1; // can be removed in 'shrink'
bool added : 1; // has already been added to lrat_chain (in 'minimize')
// These three variable flags are used to schedule clauses in subsumption
// ('subsume'), variables in bounded variable elimination ('elim') and in
// hyper ternary resolution ('ternary').
//
bool elim : 1; // removed since last 'elim' round (*)
bool subsume : 1; // added since last 'subsume' round (*)
bool ternary : 1; // added in ternary clause since last 'ternary' (*)
bool sweep : 1;
bool blockable : 1;
unsigned char
marked_signed : 2; // generate correct LRAT chains in decompose
unsigned char factor : 2;
// These literal flags are used by blocked clause elimination ('block').
//
unsigned char block : 2; // removed since last 'block' round (*)
unsigned char skip : 2; // skip this literal as blocking literal
// Bits for handling assumptions.
//
unsigned char assumed : 2;
unsigned char failed : 2; // 0 if not part of failure
// 1 if positive lit is in failure
// 2 if negated lit is in failure
enum {
UNUSED = 0,
ACTIVE = 1,
FIXED = 2,
ELIMINATED = 3,
SUBSTITUTED = 4,
PURE = 5
};
unsigned char status : 3;
// Initialized explicitly in 'Internal::init' through this function.
//
Flags () {
seen = keep = poison = removable = shrinkable = added = sweep = false;
subsume = elim = ternary = true;
block = 3u;
skip = assumed = failed = marked_signed = factor = 0;
status = UNUSED;
}
bool unused () const { return status == UNUSED; }
bool active () const { return status == ACTIVE; }
bool fixed () const { return status == FIXED; }
bool eliminated () const { return status == ELIMINATED; }
bool substituted () const { return status == SUBSTITUTED; }
bool pure () const { return status == PURE; }
// The flags marked with '(*)' are copied during 'External::copy_flags',
// which in essence means they are reset in the copy if they were clear.
// This avoids the effort of fruitless preprocessing the copy.
void copy (Flags &dst) const {
dst.elim = elim;
dst.subsume = subsume;
dst.ternary = ternary;
dst.block = block;
}
};
} // namespace CaDiCaL
#endif

269
src/sat/cadical/flip.cpp Normal file
View File

@ -0,0 +1,269 @@
#include "internal.hpp"
namespace CaDiCaL {
bool Internal::flip (int lit) {
// Do not try to flip inactive literals except for unused variables.
if (!active (lit) && !flags (lit).unused ())
return false;
/*
if (flags (lit).unused ()) {
assert (lit <= max_var);
mark_active (lit);
set_val (lit, 1);
return true;
}
*/
// TODO: Unused case is not handled yet.
// if (flags (lit).unused ()) return false;
// Need to reestablish proper watching invariants as if there are no
// blocking literals as flipping in principle does not work with them.
if (propergated < trail.size ())
propergate ();
LOG ("trying to flip %d", lit);
const int idx = vidx (lit);
const signed char original_value = vals[idx];
assert (original_value);
lit = original_value < 0 ? -idx : idx;
assert (val (lit) > 0);
// Here we go over all the clauses in which 'lit' is watched by 'lit' and
// check whether assigning 'lit' to false would break watching invariants
// or even make the clause false. We also try to find replacement
// watches in case this fixes the watching invariant. This code is very
// similar to propagation of a literal in 'Internal::propagate'.
bool res = true;
Watches &ws = watches (lit);
const const_watch_iterator eow = ws.end ();
watch_iterator bow = ws.begin ();
// We first go over binary watches/clauses first as this is cheaper and
// has higher chance of failure and we can not use blocking literals.
for (const_watch_iterator i = bow; i != eow; i++) {
const Watch w = *i;
if (!w.binary ())
continue;
const signed char b = val (w.blit);
if (b > 0)
continue;
assert (b < 0);
res = false;
break;
}
if (res) {
const_watch_iterator i = bow;
watch_iterator j = bow;
while (i != eow) {
const Watch w = *j++ = *i++;
if (w.binary ())
continue;
if (w.clause->garbage) {
j--;
continue;
}
literal_iterator lits = w.clause->begin ();
const int other = lits[0] ^ lits[1] ^ lit;
const signed char u = val (other);
if (u > 0)
continue;
const int size = w.clause->size;
const literal_iterator middle = lits + w.clause->pos;
const const_literal_iterator end = lits + size;
literal_iterator k = middle;
int r = 0;
signed char v = -1;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
if (v < 0) {
res = false;
break;
}
assert (v > 0);
assert (lits + 2 <= k), assert (k <= w.clause->end ());
w.clause->pos = k - lits;
lits[0] = other, lits[1] = r, *k = lit;
watch_literal (r, lit, w.clause);
j--;
}
if (j != i) {
while (i != eow)
*j++ = *i++;
ws.resize (j - ws.begin ());
}
}
#ifdef LOGGING
if (res)
LOG ("literal %d can be flipped", lit);
else
LOG ("literal %d can not be flipped", lit);
#endif
if (res) {
const int idx = vidx (lit);
const signed char original_value = vals[idx];
assert (original_value);
lit = original_value < 0 ? -idx : idx;
assert (val (lit) > 0);
LOG ("flipping value of %d = 1 to %d = -1", lit, lit);
set_val (idx, -original_value);
assert (val (-lit) > 0);
assert (val (lit) < 0);
Var &v = var (idx);
assert (trail[v.trail] == lit);
trail[v.trail] = -lit;
if (opts.ilb) {
if (!tainted_literal)
tainted_literal = lit;
else {
assert (val (tainted_literal));
if (v.level < var (tainted_literal).level) {
tainted_literal = lit;
}
}
}
} else
LOG ("flipping value of %d failed", lit);
return res;
}
bool Internal::flippable (int lit) {
// Can not check inactive literals except for unused variables.
if (!active (lit) && !flags (lit).unused ())
return false;
/*
if (flags (lit).unused ()) {
assert (lit <= max_var);
mark_active (lit);
return true;
}
*/
// TODO: Unused case is not handled yet
// if (flags (lit).unused ()) return false;
// Need to reestablish proper watching invariants as if there are no
// blocking literals as flipping in principle does not work with them.
if (propergated < trail.size ())
propergate ();
LOG ("checking whether %d is flippable", lit);
const int idx = vidx (lit);
const signed char original_value = vals[idx];
assert (original_value);
lit = original_value < 0 ? -idx : idx;
assert (val (lit) > 0);
// Here we go over all the clauses in which 'lit' is watched by 'lit' and
// check whether assigning 'lit' to false would break watching invariants
// or even make the clause false. In contrast to 'flip' we do not try to
// find replacement literals but do use blocking literals'. Therefore we
// also do not split the traversal code into two parts.
bool res = true;
Watches &ws = watches (lit);
const const_watch_iterator eow = ws.end ();
for (watch_iterator i = ws.begin (); i != eow; i++) {
const Watch w = *i;
const signed char b = val (w.blit);
if (b > 0)
continue;
assert (b < 0);
if (w.binary ()) {
res = false;
break;
}
if (w.clause->garbage)
continue;
literal_iterator lits = w.clause->begin ();
const int other = lits[0] ^ lits[1] ^ lit;
const signed char u = val (other);
if (u > 0) {
i->blit = other;
continue;
}
const int size = w.clause->size;
const literal_iterator middle = lits + w.clause->pos;
const const_literal_iterator end = lits + size;
literal_iterator k = middle;
int r = 0;
signed char v = -1;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
if (v < 0) {
res = false;
break;
}
assert (v > 0);
assert (lits + 2 <= k);
assert (k <= w.clause->end ());
w.clause->pos = k - lits;
i->blit = r;
}
#ifdef LOGGING
if (res)
LOG ("literal %d can be flipped", lit);
else
LOG ("literal %d can not be flipped", lit);
#endif
return res;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,89 @@
#include "internal.hpp"
namespace CaDiCaL {
void Format::enlarge () {
char *old = buffer;
buffer = new char[size = size ? 2 * size : 1];
memcpy (buffer, old, count);
delete[] old;
}
inline void Format::push_char (char ch) {
if (size == count)
enlarge ();
buffer[count++] = ch;
}
void Format::push_string (const char *s) {
char ch;
while ((ch = *s++))
push_char (ch);
}
void Format::push_int (int d) {
char tmp[16];
snprintf (tmp, sizeof tmp, "%d", d);
push_string (tmp);
}
void Format::push_uint64 (uint64_t u) {
char tmp[16];
snprintf (tmp, sizeof tmp, "%" PRIu64, u);
push_string (tmp);
}
static bool match_format (const char *&str, const char *pattern) {
assert (pattern);
const char *p = str;
const char *q = pattern;
while (*q)
if (*q++ != *p++)
return false;
str = p;
return true;
}
const char *Format::add (const char *fmt, va_list &ap) {
const char *p = fmt;
char ch;
while ((ch = *p++)) {
if (ch != '%')
push_char (ch);
else if (*p == 'c')
push_char (va_arg (ap, int)), p++;
else if (*p == 'd')
push_int (va_arg (ap, int)), p++;
else if (*p == 's')
push_string (va_arg (ap, const char *)), p++;
else if (match_format (p, PRIu64))
push_uint64 (va_arg (ap, uint64_t));
else {
push_char ('%');
push_char (*p);
break;
} // unsupported
}
push_char (0);
count--; // thus automatic append in subsequent calls.
return buffer;
}
const char *Format::init (const char *fmt, ...) {
count = 0;
va_list ap;
va_start (ap, fmt);
const char *res = add (fmt, ap);
va_end (ap);
return res;
}
const char *Format::append (const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
const char *res = add (fmt, ap);
va_end (ap);
return res;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,36 @@
#ifndef _format_hpp_INCLUDED
#define _format_hpp_INCLUDED
#include <cstdarg>
#include <cstdint>
namespace CaDiCaL {
// This class provides a 'printf' style formatting utility.
// Only '%c', '%d', '%s' are supported at this point.
// It is used to capture and save an error message.
class Format {
char *buffer;
int64_t count, size;
void enlarge ();
void push_char (char);
void push_string (const char *);
void push_int (int);
void push_uint64 (uint64_t);
const char *add (const char *fmt, va_list &);
public:
Format () : buffer (0), count (0), size (0) {}
~Format () {
if (buffer)
delete[] buffer;
}
const char *init (const char *fmt, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3);
const char *append (const char *fmt, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3);
operator const char * () const { return count ? buffer : 0; }
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,277 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
FratTracer::FratTracer (Internal *i, File *f, bool b, bool a)
: internal (i), file (f), binary (b), with_antecedents (a)
#ifndef QUIET
,
added (0), deleted (0), finalized (0), original (0)
#endif
{
(void) internal;
}
void FratTracer::connect_internal (Internal *i) {
internal = i;
file->connect_internal (internal);
LOG ("FRAT TRACER connected to internal");
}
FratTracer::~FratTracer () {
LOG ("FRAT TRACER delete");
delete file;
}
/*------------------------------------------------------------------------*/
inline void FratTracer::put_binary_zero () {
assert (binary);
assert (file);
file->put ((unsigned char) 0);
}
inline void FratTracer::put_binary_lit (int lit) {
assert (binary);
assert (file);
assert (lit != INT_MIN);
unsigned x = 2 * abs (lit) + (lit < 0);
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
inline void FratTracer::put_binary_id (int64_t id, bool can_be_negative) {
assert (binary);
assert (file);
uint64_t x = abs (id);
if (can_be_negative) {
x = 2 * x + (id < 0);
}
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
/*------------------------------------------------------------------------*/
void FratTracer::frat_add_original_clause (int64_t id,
const vector<int> &clause) {
if (binary)
file->put ('o');
else
file->put ("o ");
if (binary)
put_binary_id (id);
else
file->put (id), file->put (" ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
void FratTracer::frat_add_derived_clause (int64_t id,
const vector<int> &clause) {
if (binary)
file->put ('a');
else
file->put ("a ");
if (binary)
put_binary_id (id);
else
file->put (id), file->put (" ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
void FratTracer::frat_add_derived_clause (int64_t id,
const vector<int> &clause,
const vector<int64_t> &chain) {
if (binary)
file->put ('a');
else
file->put ("a ");
if (binary)
put_binary_id (id);
else
file->put (id), file->put (" ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero (), file->put ('l');
else
file->put ("0 l ");
for (const auto &c : chain)
if (binary)
put_binary_id (c, true); // LRAT can have negative ids
else
file->put (c), file->put (' '); // in proof chain, so they get
if (binary)
put_binary_zero (); // since cadical has no rat-steps
else
file->put ("0\n"); // this is just 2c here
}
void FratTracer::frat_delete_clause (int64_t id,
const vector<int> &clause) {
if (binary)
file->put ('d');
else
file->put ("d ");
if (binary)
put_binary_id (id);
else
file->put (id), file->put (" ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
void FratTracer::frat_finalize_clause (int64_t id,
const vector<int> &clause) {
if (binary)
file->put ('f');
else
file->put ("f ");
if (binary)
put_binary_id (id);
else
file->put (id), file->put (" ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
/*------------------------------------------------------------------------*/
void FratTracer::add_original_clause (int64_t id, bool,
const vector<int> &clause, bool) {
if (file->closed ())
return;
LOG ("FRAT TRACER tracing addition of original clause");
frat_add_original_clause (id, clause);
}
void FratTracer::add_derived_clause (int64_t id, bool,
const vector<int> &clause,
const vector<int64_t> &chain) {
if (file->closed ())
return;
LOG ("FRAT TRACER tracing addition of derived clause");
if (with_antecedents)
frat_add_derived_clause (id, clause, chain);
else
frat_add_derived_clause (id, clause);
#ifndef QUIET
added++;
#endif
}
void FratTracer::delete_clause (int64_t id, bool,
const vector<int> &clause) {
if (file->closed ())
return;
LOG ("FRAT TRACER tracing deletion of clause");
frat_delete_clause (id, clause);
#ifndef QUIET
deleted++;
#endif
}
void FratTracer::finalize_clause (int64_t id, const vector<int> &clause) {
if (file->closed ())
return;
LOG ("FRAT TRACER tracing finalization of clause");
frat_finalize_clause (id, clause);
}
/*------------------------------------------------------------------------*/
bool FratTracer::closed () { return file->closed (); }
#ifndef QUIET
void FratTracer::print_statistics () {
uint64_t bytes = file->bytes ();
uint64_t total = original + added + deleted + finalized;
MSG ("FRAT %" PRId64 " original clauses %.2f%%", original,
percent (original, total));
MSG ("FRAT %" PRId64 " added clauses %.2f%%", added,
percent (added, total));
MSG ("FRAT %" PRId64 " deleted clauses %.2f%%", deleted,
percent (deleted, total));
MSG ("FRAT %" PRId64 " finalized clauses %.2f%%", finalized,
percent (finalized, total));
MSG ("FRAT %" PRId64 " bytes (%.2f MB)", bytes,
bytes / (double) (1 << 20));
}
#endif
void FratTracer::close (bool print) {
assert (!closed ());
file->close ();
#ifndef QUIET
if (print) {
MSG ("FRAT proof file '%s' closed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
void FratTracer::flush (bool print) {
assert (!closed ());
file->flush ();
#ifndef QUIET
if (print) {
MSG ("FRAT proof file '%s' flushed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
} // namespace CaDiCaL

View File

@ -0,0 +1,62 @@
#ifndef _frattracer_h_INCLUDED
#define _frattracer_h_INCLUDED
namespace CaDiCaL {
class FratTracer : public FileTracer {
Internal *internal;
File *file;
bool binary;
bool with_antecedents;
#ifndef QUIET
int64_t added, deleted;
int64_t finalized, original;
#endif
vector<int64_t> delete_ids;
void put_binary_zero ();
void put_binary_lit (int external_lit);
void put_binary_id (int64_t id, bool = false);
// support FRAT
void frat_add_original_clause (int64_t, const vector<int> &);
void frat_add_derived_clause (int64_t, const vector<int> &);
void frat_add_derived_clause (int64_t, const vector<int> &,
const vector<int64_t> &);
void frat_delete_clause (int64_t, const vector<int> &);
void frat_finalize_clause (int64_t, const vector<int> &);
public:
// own and delete 'file'
FratTracer (Internal *, File *file, bool binary, bool antecedents);
~FratTracer ();
void connect_internal (Internal *i) override;
void begin_proof (int64_t) override {} // skip
void add_original_clause (int64_t, bool, const vector<int> &,
bool = false) override;
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
void delete_clause (int64_t, bool, const vector<int> &) override;
void finalize_clause (int64_t, const vector<int> &) override;
void report_status (int, int64_t) override {} // skip
#ifndef QUIET
void print_statistics ();
#endif
bool closed () override;
void close (bool) override;
void flush (bool) override;
};
} // namespace CaDiCaL
#endif

766
src/sat/cadical/gates.cpp Normal file
View File

@ -0,0 +1,766 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// As in our original SATeLite published at SAT'05 we are trying to find
// gates in order to restrict the number of resolutions that need to be
// tried. If there is such a gate, we only need to consider resolvents
// among gate and one non-gate clauses. Resolvents between definitions will
// be tautological anyhow and resolvents among non-gates can actually be
// shown to be redundant too.
/*------------------------------------------------------------------------*/
// The next function returns a non-zero if the clause 'c', which is assumed
// to contain the literal 'first', after removing falsified literals is a
// binary clause. Then the actual second literal is returned.
int Internal::second_literal_in_binary_clause (Eliminator &eliminator,
Clause *c, int first) {
assert (!c->garbage);
int second = 0;
for (const auto &lit : *c) {
if (lit == first)
continue;
const signed char tmp = val (lit);
if (tmp < 0)
continue;
if (tmp > 0) {
mark_garbage (c);
elim_update_removed_clause (eliminator, c);
return 0;
}
if (second) {
second = INT_MIN;
break;
}
second = lit;
}
if (!second)
return 0;
if (second == INT_MIN)
return 0;
assert (active (second));
#ifdef LOGGING
if (c->size == 2)
LOG (c, "found binary");
else
LOG (c, "found actual binary %d %d", first, second);
#endif
return second;
}
/*------------------------------------------------------------------------*/
// need a copy from above that does not care about garbage
int Internal::second_literal_in_binary_clause_lrat (Clause *c, int first) {
if (c->garbage)
return 0;
int second = 0;
for (const auto &lit : *c) {
if (lit == first)
continue;
const signed char tmp = val (lit);
if (tmp < 0)
continue;
if (tmp > 0)
return 0;
if (!tmp) {
if (second) {
second = INT_MIN;
break;
}
second = lit;
}
}
if (!second)
return 0;
if (second == INT_MIN)
return 0;
return second;
}
// I needed to find the second clause for hyper unary resolution to build
// LRAT this is not efficient but I could not find a better way then just
// finding the corresponding clause in all possible clauses
//
Clause *Internal::find_binary_clause (int first, int second) {
int best = first;
int other = second;
if (occs (first).size () > occs (second).size ()) {
best = second;
other = first;
}
for (auto c : occs (best))
if (second_literal_in_binary_clause_lrat (c, best) == other)
return c;
return 0;
}
/*------------------------------------------------------------------------*/
// Mark all other literals in binary clauses with 'first'. During this
// marking we might also detect hyper unary resolvents producing a unit.
// If such a unit is found we propagate it and return immediately.
void Internal::mark_binary_literals (Eliminator &eliminator, int first) {
if (unsat)
return;
if (val (first))
return;
if (!eliminator.gates.empty ())
return;
assert (!marked (first));
assert (eliminator.marked.empty ());
const Occs &os = occs (first);
for (const auto &c : os) {
if (c->garbage)
continue;
const int second =
second_literal_in_binary_clause (eliminator, c, first);
if (!second)
continue;
const int tmp = marked (second);
if (tmp < 0) {
// had a bug where units could occur multiple times here
// solved with flags
LOG ("found binary resolved unit %d", first);
if (lrat) {
Clause *d = find_binary_clause (first, -second);
assert (d);
for (auto &lit : *d) {
if (lit == first || lit == -second)
continue;
assert (val (lit) < 0);
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);
// LOG ("gates added id %" PRId64, id);
}
for (auto &lit : *c) {
if (lit == first || lit == second)
continue;
assert (val (lit) < 0);
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);
// LOG ("gates added id %" PRId64, id);
}
lrat_chain.push_back (c->id);
lrat_chain.push_back (d->id);
// LOG ("gates added id %" PRId64, c->id);
// LOG ("gates added id %" PRId64, d->id);
clear_analyzed_literals ();
}
assign_unit (first);
elim_propagate (eliminator, first);
return;
}
if (tmp > 0) {
LOG (c, "duplicated actual binary clause");
elim_update_removed_clause (eliminator, c);
mark_garbage (c);
continue;
}
eliminator.marked.push_back (second);
mark (second);
LOG ("marked second literal %d in binary clause %d %d", second, first,
second);
}
}
// Unmark all literals saved on the 'marked' stack.
void Internal::unmark_binary_literals (Eliminator &eliminator) {
LOG ("unmarking %zd literals", eliminator.marked.size ());
for (const auto &lit : eliminator.marked)
unmark (lit);
eliminator.marked.clear ();
}
/*------------------------------------------------------------------------*/
// Find equivalence for 'pivot'. Requires that all other literals in binary
// clauses with 'pivot' are marked (through 'mark_binary_literals');
void Internal::find_equivalence (Eliminator &eliminator, int pivot) {
if (!opts.elimequivs)
return;
assert (opts.elimsubst);
if (unsat)
return;
if (val (pivot))
return;
if (!eliminator.gates.empty ())
return;
mark_binary_literals (eliminator, pivot);
if (unsat || val (pivot))
goto DONE;
for (const auto &c : occs (-pivot)) {
if (c->garbage)
continue;
const int second =
second_literal_in_binary_clause (eliminator, c, -pivot);
if (!second)
continue;
const int tmp = marked (second);
if (tmp > 0) {
LOG ("found binary resolved unit %d", second);
// did not find a bug where units could occur multiple times here
// still solved potential issues with flags
if (lrat) {
Clause *d = find_binary_clause (pivot, second);
assert (d);
for (auto &lit : *d) {
if (lit == pivot || lit == second)
continue;
assert (val (lit) < 0);
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);
// LOG ("gates added id %" PRId64, id);
}
for (auto &lit : *c) {
if (lit == -pivot || lit == second)
continue;
assert (val (lit) < 0);
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);
// LOG ("gates added id %" PRId64, id);
}
lrat_chain.push_back (c->id);
lrat_chain.push_back (d->id);
clear_analyzed_literals ();
// LOG ("gates added id %" PRId64, c->id);
// LOG ("gates added id %" PRId64, d->id);
}
assign_unit (second);
elim_propagate (eliminator, second);
if (val (pivot))
break;
if (unsat)
break;
}
if (tmp >= 0)
continue;
LOG ("found equivalence %d = %d", pivot, -second);
stats.elimequivs++;
stats.elimgates++;
LOG (c, "first gate clause");
assert (!c->gate);
c->gate = true;
eliminator.gates.push_back (c);
Clause *d = 0;
const Occs &ps = occs (pivot);
for (const auto &e : ps) {
if (e->garbage)
continue;
const int other =
second_literal_in_binary_clause (eliminator, e, pivot);
if (other == -second) {
d = e;
break;
}
}
assert (d);
LOG (d, "second gate clause");
assert (!d->gate);
d->gate = true;
eliminator.gates.push_back (d);
eliminator.gatetype = EQUI;
break;
}
DONE:
unmark_binary_literals (eliminator);
}
/*------------------------------------------------------------------------*/
// Find and gates for 'pivot' with a long clause, in which the pivot occurs
// positively. Requires that all other literals in binary clauses with
// 'pivot' are marked (through 'mark_binary_literals');
void Internal::find_and_gate (Eliminator &eliminator, int pivot) {
if (!opts.elimands)
return;
assert (opts.elimsubst);
if (unsat)
return;
if (val (pivot))
return;
if (!eliminator.gates.empty ())
return;
mark_binary_literals (eliminator, pivot);
if (unsat || val (pivot))
goto DONE;
for (const auto &c : occs (-pivot)) {
if (c->garbage)
continue;
if (c->size < 3)
continue;
bool all_literals_marked = true;
unsigned arity = 0;
int satisfied = 0;
for (const auto &lit : *c) {
if (lit == -pivot)
continue;
assert (lit != pivot);
signed char tmp = val (lit);
if (tmp < 0)
continue;
if (tmp > 0) {
satisfied = lit;
break;
}
tmp = marked (lit);
if (tmp < 0) {
arity++;
continue;
}
all_literals_marked = false;
break;
}
if (!all_literals_marked)
continue;
if (satisfied) {
LOG (c, "satisfied by %d candidate base clause", satisfied);
mark_garbage (c);
continue;
}
#ifdef LOGGING
if (opts.log) {
Logger::print_log_prefix (this);
tout.magenta ();
printf ("found arity %u AND gate %d = ", arity, -pivot);
bool first = true;
for (const auto &lit : *c) {
if (lit == -pivot)
continue;
assert (lit != pivot);
if (!first)
fputs (" & ", stdout);
printf ("%d", -lit);
first = false;
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
#endif
stats.elimands++;
stats.elimgates++;
eliminator.gatetype = AND;
(void) arity;
assert (!c->gate);
c->gate = true;
eliminator.gates.push_back (c);
for (const auto &lit : *c) {
if (lit == -pivot)
continue;
assert (lit != pivot);
signed char tmp = val (lit);
if (tmp < 0)
continue;
assert (!tmp);
assert (marked (lit) < 0);
marks[vidx (lit)] *= 2;
}
unsigned count = 0;
for (const auto &d : occs (pivot)) {
if (d->garbage)
continue;
const int other =
second_literal_in_binary_clause (eliminator, d, pivot);
if (!other)
continue;
const int tmp = marked (other);
if (tmp != 2)
continue;
LOG (d, "AND gate binary side clause");
assert (!d->gate);
d->gate = true;
eliminator.gates.push_back (d);
count++;
}
assert (count >= arity);
(void) count;
break;
}
DONE:
unmark_binary_literals (eliminator);
}
/*------------------------------------------------------------------------*/
// Find and extract ternary clauses.
bool Internal::get_ternary_clause (Clause *d, int &a, int &b, int &c) {
if (d->garbage)
return false;
if (d->size < 3)
return false;
int found = 0;
a = b = c = 0;
for (const auto &lit : *d) {
if (val (lit))
continue;
if (++found == 1)
a = lit;
else if (found == 2)
b = lit;
else if (found == 3)
c = lit;
else
return false;
}
return found == 3;
}
// This function checks whether 'd' exists as ternary clause.
bool Internal::match_ternary_clause (Clause *d, int a, int b, int c) {
if (d->garbage)
return false;
int found = 0;
for (const auto &lit : *d) {
if (val (lit))
continue;
if (a != lit && b != lit && c != lit)
return false;
found++;
}
return found == 3;
}
Clause *Internal::find_ternary_clause (int a, int b, int c) {
if (occs (b).size () > occs (c).size ())
swap (b, c);
if (occs (a).size () > occs (b).size ())
swap (a, b);
for (auto d : occs (a))
if (match_ternary_clause (d, a, b, c))
return d;
return 0;
}
/*------------------------------------------------------------------------*/
// Find if-then-else gate.
void Internal::find_if_then_else (Eliminator &eliminator, int pivot) {
if (!opts.elimites)
return;
assert (opts.elimsubst);
if (unsat)
return;
if (val (pivot))
return;
if (!eliminator.gates.empty ())
return;
const Occs &os = occs (pivot);
const auto end = os.end ();
for (auto i = os.begin (); i != end; i++) {
Clause *di = *i;
int ai, bi, ci;
if (!get_ternary_clause (di, ai, bi, ci))
continue;
if (bi == pivot)
swap (ai, bi);
if (ci == pivot)
swap (ai, ci);
assert (ai == pivot);
for (auto j = i + 1; j != end; j++) {
Clause *dj = *j;
int aj, bj, cj;
if (!get_ternary_clause (dj, aj, bj, cj))
continue;
if (bj == pivot)
swap (aj, bj);
if (cj == pivot)
swap (aj, cj);
assert (aj == pivot);
if (abs (bi) == abs (cj))
swap (bj, cj);
if (abs (ci) == abs (cj))
continue;
if (bi != -bj)
continue;
Clause *d1 = find_ternary_clause (-pivot, bi, -ci);
if (!d1)
continue;
Clause *d2 = find_ternary_clause (-pivot, bj, -cj);
if (!d2)
continue;
LOG (di, "1st if-then-else");
LOG (dj, "2nd if-then-else");
LOG (d1, "3rd if-then-else");
LOG (d2, "4th if-then-else");
LOG ("found ITE gate %d == (%d ? %d : %d)", pivot, -bi, -ci, -cj);
assert (!di->gate);
assert (!dj->gate);
assert (!d1->gate);
assert (!d2->gate);
di->gate = true;
dj->gate = true;
d1->gate = true;
d2->gate = true;
eliminator.gates.push_back (di);
eliminator.gates.push_back (dj);
eliminator.gates.push_back (d1);
eliminator.gates.push_back (d2);
stats.elimgates++;
stats.elimites++;
eliminator.gatetype = ITE;
return;
}
}
}
/*------------------------------------------------------------------------*/
// Find and extract clause.
bool Internal::get_clause (Clause *c, vector<int> &l) {
if (c->garbage)
return false;
l.clear ();
for (const auto &lit : *c) {
if (val (lit) < 0)
continue;
if (val (lit) > 0) {
l.clear ();
return false;
}
l.push_back (lit);
}
return true;
}
// Check whether 'c' contains only the literals in 'l'.
bool Internal::is_clause (Clause *c, const vector<int> &lits) {
if (c->garbage)
return false;
int size = lits.size ();
if (c->size < size)
return false;
int found = 0;
for (const auto &lit : *c) {
if (val (lit) < 0)
continue;
if (val (lit) > 0)
return false;
const auto it = find (lits.begin (), lits.end (), lit);
if (it == lits.end ())
return false;
if (++found > size)
return false;
}
return found == size;
}
Clause *Internal::find_clause (const vector<int> &lits) {
int best = 0;
size_t len = 0;
for (const auto &lit : lits) {
size_t l = occs (lit).size ();
if (best && l >= len)
continue;
len = l, best = lit;
}
for (auto c : occs (best))
if (is_clause (c, lits))
return c;
return 0;
}
void Internal::find_xor_gate (Eliminator &eliminator, int pivot) {
if (!opts.elimxors)
return;
assert (opts.elimsubst);
if (unsat)
return;
if (val (pivot))
return;
if (!eliminator.gates.empty ())
return;
vector<int> lits;
for (auto d : occs (pivot)) {
if (!get_clause (d, lits))
continue;
const int size = lits.size (); // clause size
const int arity = size - 1; // arity of XOR
if (size < 3)
continue;
if (arity > opts.elimxorlim)
continue;
assert (eliminator.gates.empty ());
unsigned needed = (1u << arity) - 1; // additional clauses
unsigned signs = 0; // literals to negate
do {
const unsigned prev = signs;
while (parity (++signs))
;
for (int j = 0; j < size; j++) {
const unsigned bit = 1u << j;
int lit = lits[j];
if ((prev & bit) != (signs & bit))
lits[j] = lit = -lit;
}
Clause *e = find_clause (lits);
if (!e)
break;
eliminator.gates.push_back (e);
} while (--needed);
if (needed) {
eliminator.gates.clear ();
continue;
}
eliminator.gates.push_back (d);
assert (eliminator.gates.size () == (1u << arity));
#ifdef LOGGING
if (opts.log) {
Logger::print_log_prefix (this);
tout.magenta ();
printf ("found arity %u XOR gate %d = ", arity, -pivot);
bool first = true;
for (const auto &lit : *d) {
if (lit == pivot)
continue;
assert (lit != -pivot);
if (!first)
fputs (" ^ ", stdout);
printf ("%d", lit);
first = false;
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
#endif
stats.elimgates++;
stats.elimxors++;
const auto end = eliminator.gates.end ();
auto j = eliminator.gates.begin ();
for (auto i = j; i != end; i++) {
Clause *e = *i;
if (e->gate)
continue;
e->gate = true;
LOG (e, "contributing");
*j++ = e;
}
eliminator.gates.resize (j - eliminator.gates.begin ());
eliminator.gatetype = XOR;
break;
}
}
/*------------------------------------------------------------------------*/
// Find a gate for 'pivot'. If such a gate is found, the gate clauses are
// marked and pushed on the stack of gates. Further hyper unary resolution
// might detect units, which are propagated. This might assign the pivot or
// even produce the empty clause.
void Internal::find_gate_clauses (Eliminator &eliminator, int pivot) {
if (!opts.elimsubst)
return;
if (unsat)
return;
if (val (pivot))
return;
assert (eliminator.gates.empty ());
find_equivalence (eliminator, pivot);
find_and_gate (eliminator, pivot);
find_and_gate (eliminator, -pivot);
find_if_then_else (eliminator, pivot);
find_xor_gate (eliminator, pivot);
find_definition (eliminator, pivot);
}
void Internal::unmark_gate_clauses (Eliminator &eliminator) {
LOG ("unmarking %zd gate clauses", eliminator.gates.size ());
for (const auto &c : eliminator.gates) {
assert (c->gate);
c->gate = false;
}
eliminator.gates.clear ();
eliminator.definition_unit = 0;
}
/*------------------------------------------------------------------------*/
} // namespace CaDiCaL

212
src/sat/cadical/heap.hpp Normal file
View File

@ -0,0 +1,212 @@
#ifndef _heap_hpp_INCLUDED
#define _heap_hpp_INCLUDED
#include "util.hpp" // Alphabetically after 'heap.hpp'.
#include <cassert>
namespace CaDiCaL {
using namespace std;
// This is a priority queue with updates for unsigned integers implemented
// as binary heap. We need to map integer elements added (through
// 'push_back') to positions on the binary heap in 'array'. This map is
// stored in the 'pos' array. This approach is really wasteful (at least in
// terms of memory) if only few and a sparse set of integers is added. So
// it should not be used in this situation. A generic priority queue would
// implement the mapping externally provided by another template parameter.
// Since we use 'UINT_MAX' as 'not contained' flag, we can only have
// 'UINT_MAX - 1' elements in the heap.
const unsigned invalid_heap_position = UINT_MAX;
template <class C> class heap {
vector<unsigned> array; // actual binary heap
vector<unsigned> pos; // positions of elements in array
C less; // less-than for elements
// Map an element to its position entry in the 'pos' map.
//
unsigned &index (unsigned e) {
if (e >= pos.size ())
pos.resize (1 + (size_t) e, invalid_heap_position);
unsigned &res = pos[e];
assert (res == invalid_heap_position || (size_t) res < array.size ());
return res;
}
bool has_parent (unsigned e) { return index (e) > 0; }
bool has_left (unsigned e) {
return (size_t) 2 * index (e) + 1 < size ();
}
bool has_right (unsigned e) {
return (size_t) 2 * index (e) + 2 < size ();
}
unsigned parent (unsigned e) {
assert (has_parent (e));
return array[(index (e) - 1) / 2];
}
unsigned left (unsigned e) {
assert (has_left (e));
return array[2 * index (e) + 1];
}
unsigned right (unsigned e) {
assert (has_right (e));
return array[2 * index (e) + 2];
}
// Exchange elements 'a' and 'b' in 'array' and fix their positions.
//
void exchange (unsigned a, unsigned b) {
unsigned &i = index (a), &j = index (b);
swap (array[i], array[j]);
swap (i, j);
}
// Bubble up an element as far as necessary.
//
void up (unsigned e) {
unsigned p;
while (has_parent (e) && less ((p = parent (e)), e))
exchange (p, e);
}
// Bubble down an element as far as necessary.
//
void down (unsigned e) {
while (has_left (e)) {
unsigned c = left (e);
if (has_right (e)) {
unsigned r = right (e);
if (less (c, r))
c = r;
}
if (!less (e, c))
break;
exchange (e, c);
}
}
// Very expensive checker for the main 'heap' invariant. Can be enabled
// to find violations of antisymmetry in the client implementation of
// 'less' and as well of course bugs in this heap implementation. It
// should be enabled during testing applications of the heap.
//
void check () {
#if 0 // EXPENSIVE HEAP CHECKING IF ENABLED
#warning "expensive checking in heap enabled"
assert (array.size () <= invalid_heap_position);
for (size_t i = 0; i < array.size (); i++) {
size_t l = 2*i + 1, r = 2*i + 2;
if (l < array.size ()) assert (!less (array[i], array[l]));
if (r < array.size ()) assert (!less (array[i], array[r]));
assert (array[i] >= 0);
{
assert ((size_t) array[i] < pos.size ());
assert (i == (size_t) pos[array[i]]);
}
}
for (size_t i = 0; i < pos.size (); i++) {
if (pos[i] == invalid_heap_position) continue;
assert (pos[i] < array.size ());
assert (array[pos[i]] == (unsigned) i);
}
#endif
}
public:
heap (const C &c) : less (c) {}
// Number of elements in the heap.
//
size_t size () const { return array.size (); }
// Check if no more elements are in the heap.
//
bool empty () const { return array.empty (); }
// Check whether 'e' is already in the heap.
//
bool contains (unsigned e) const {
if ((size_t) e >= pos.size ())
return false;
return pos[e] != invalid_heap_position;
}
// Add a new (not contained) element 'e' to the heap.
//
void push_back (unsigned e) {
assert (!contains (e));
size_t i = array.size ();
assert (i < (size_t) invalid_heap_position);
array.push_back (e);
index (e) = (unsigned) i;
up (e);
down (e);
check ();
}
// Returns the maximum element in the heap.
//
unsigned front () const {
assert (!empty ());
return array[0];
}
// Removes the maximum element in the heap.
//
unsigned pop_front () {
assert (!empty ());
unsigned res = array[0], last = array.back ();
if (size () > 1)
exchange (res, last);
index (res) = invalid_heap_position;
array.pop_back ();
if (size () > 1)
down (last);
check ();
return res;
}
// Notify the heap, that evaluation of 'less' has changed for 'e'.
//
void update (unsigned e) {
assert (contains (e));
up (e);
down (e);
check ();
}
void clear () {
array.clear ();
pos.clear ();
}
void erase () {
erase_vector (array);
erase_vector (pos);
}
void shrink () {
shrink_vector (array);
shrink_vector (pos);
}
// Standard iterators 'inherited' from 'vector'.
//
typedef typename vector<unsigned>::iterator iterator;
typedef typename vector<unsigned>::const_iterator const_iterator;
iterator begin () { return array.begin (); }
iterator end () { return array.end (); }
const_iterator begin () const { return array.begin (); }
const_iterator end () const { return array.end (); }
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,566 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
IdrupTracer::IdrupTracer (Internal *i, File *f, bool b)
: internal (i), file (f), binary (b), num_clauses (0), size_clauses (0),
clauses (0), last_hash (0), last_id (0), last_clause (0)
#ifndef QUIET
,
added (0), deleted (0)
#endif
{
(void) internal;
// Initialize random number table for hash function.
//
Random random (42);
for (unsigned n = 0; n < num_nonces; n++) {
uint64_t nonce = random.next ();
if (!(nonce & 1))
nonce++;
assert (nonce), assert (nonce & 1);
nonces[n] = nonce;
}
#ifndef NDEBUG
binary = b;
#else
(void) b;
#endif
piping = file->piping ();
}
void IdrupTracer::connect_internal (Internal *i) {
internal = i;
file->connect_internal (internal);
LOG ("IDRUP TRACER connected to internal");
}
IdrupTracer::~IdrupTracer () {
LOG ("IDRUP TRACER delete");
delete file;
for (size_t i = 0; i < size_clauses; i++)
for (IdrupClause *c = clauses[i], *next; c; c = next)
next = c->next, delete_clause (c);
delete[] clauses;
}
/*------------------------------------------------------------------------*/
void IdrupTracer::enlarge_clauses () {
assert (num_clauses == size_clauses);
const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1;
LOG ("IDRUP Tracer enlarging clauses of tracer from %" PRIu64
" to %" PRIu64,
(uint64_t) size_clauses, (uint64_t) new_size_clauses);
IdrupClause **new_clauses;
new_clauses = new IdrupClause *[new_size_clauses];
clear_n (new_clauses, new_size_clauses);
for (uint64_t i = 0; i < size_clauses; i++) {
for (IdrupClause *c = clauses[i], *next; c; c = next) {
next = c->next;
const uint64_t h = reduce_hash (c->hash, new_size_clauses);
c->next = new_clauses[h];
new_clauses[h] = c;
}
}
delete[] clauses;
clauses = new_clauses;
size_clauses = new_size_clauses;
}
IdrupClause *IdrupTracer::new_clause () {
const size_t size = imported_clause.size ();
assert (size <= UINT_MAX);
const int off = size ? -1 : 0;
const size_t bytes = sizeof (IdrupClause) + (size - off) * sizeof (int);
IdrupClause *res = (IdrupClause *) new char[bytes];
res->next = 0;
res->hash = last_hash;
res->id = last_id;
res->size = size;
int *literals = res->literals, *p = literals;
for (const auto &lit : imported_clause) {
*p++ = lit;
}
last_clause = res;
num_clauses++;
return res;
}
void IdrupTracer::delete_clause (IdrupClause *c) {
assert (c);
num_clauses--;
delete[] (char *) c;
}
uint64_t IdrupTracer::reduce_hash (uint64_t hash, uint64_t size) {
assert (size > 0);
unsigned shift = 32;
uint64_t res = hash;
while ((((uint64_t) 1) << shift) > size) {
res ^= res >> shift;
shift >>= 1;
}
res &= size - 1;
assert (res < size);
return res;
}
uint64_t IdrupTracer::compute_hash (const int64_t id) {
assert (id > 0);
unsigned j = id % num_nonces;
uint64_t tmp = nonces[j] * (uint64_t) id;
return last_hash = tmp;
}
bool IdrupTracer::find_and_delete (const int64_t id) {
if (!num_clauses)
return false;
IdrupClause **res = 0, *c;
const uint64_t hash = compute_hash (id);
const uint64_t h = reduce_hash (hash, size_clauses);
for (res = clauses + h; (c = *res); res = &c->next) {
if (c->hash == hash && c->id == id) {
break;
}
if (!c->next)
return false;
}
if (!c)
return false;
assert (c && res);
*res = c->next;
int *begin = c->literals;
for (size_t i = 0; i < c->size; i++) {
imported_clause.push_back (begin[i]);
}
delete_clause (c);
return true;
}
void IdrupTracer::insert () {
if (num_clauses == size_clauses)
enlarge_clauses ();
const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses);
IdrupClause *c = new_clause ();
c->next = clauses[h];
clauses[h] = c;
}
/*------------------------------------------------------------------------*/
inline void IdrupTracer::flush_if_piping () {
if (piping)
file->flush ();
}
inline void IdrupTracer::put_binary_zero () {
assert (binary);
assert (file);
file->put ((unsigned char) 0);
}
inline void IdrupTracer::put_binary_lit (int lit) {
assert (binary);
assert (file);
assert (lit != INT_MIN);
unsigned x = 2 * abs (lit) + (lit < 0);
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
inline void IdrupTracer::put_binary_id (int64_t id, bool can_be_negative) {
assert (binary);
assert (file);
uint64_t x = abs (id);
if (can_be_negative) {
x = 2 * x + (id < 0);
}
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
/*------------------------------------------------------------------------*/
void IdrupTracer::idrup_add_restored_clause (const vector<int> &clause) {
if (binary)
file->put ('r');
else
file->put ("r ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
// flush_if_piping ();
}
void IdrupTracer::idrup_add_derived_clause (const vector<int> &clause) {
if (binary)
file->put ('l');
else
file->put ("l ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
// flush_if_piping ();
}
void IdrupTracer::idrup_add_original_clause (const vector<int> &clause) {
if (binary)
file->put ('i');
else
file->put ("i ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
// flush_if_piping ();
}
void IdrupTracer::idrup_delete_clause (int64_t id,
const vector<int> &clause) {
if (find_and_delete (id)) {
assert (imported_clause.empty ());
if (binary)
file->put ('w');
else
file->put ("w ");
#ifndef QUIET
weakened++;
#endif
} else {
if (binary)
file->put ('d');
else
file->put ("d ");
#ifndef QUIET
deleted++;
#endif
}
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
// flush_if_piping ();
}
void IdrupTracer::idrup_conclude_and_delete (
const vector<int64_t> &conclusion) {
uint64_t size = conclusion.size ();
if (size > 1) {
if (binary) {
file->put ('U');
put_binary_id (size);
} else {
file->put ("U ");
file->put (size), file->put ("\n");
}
}
for (auto &id : conclusion) {
if (binary)
file->put ('u');
else
file->put ("u ");
(void) find_and_delete (id);
for (const auto &external_lit : imported_clause) {
// flip sign...
const auto not_elit = -external_lit;
if (binary)
put_binary_lit (not_elit);
else
file->put (not_elit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
imported_clause.clear ();
}
flush_if_piping ();
}
void IdrupTracer::idrup_report_status (int status) {
if (binary)
file->put ('s');
else
file->put ("s ");
if (status == SATISFIABLE)
file->put ("SATISFIABLE");
else if (status == UNSATISFIABLE)
file->put ("UNSATISFIABLE");
else
file->put ("UNKNOWN");
if (!binary)
file->put ("\n");
flush_if_piping ();
}
void IdrupTracer::idrup_conclude_sat (const vector<int> &model) {
if (binary)
file->put ('m');
else
file->put ("m ");
for (auto &lit : model) {
if (binary)
put_binary_lit (lit);
else
file->put (lit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
flush_if_piping ();
}
void IdrupTracer::idrup_conclude_unknown (const vector<int> &trail) {
if (binary)
file->put ('e');
else
file->put ("e ");
for (auto &lit : trail) {
if (binary)
put_binary_lit (lit);
else
file->put (lit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
flush_if_piping ();
}
void IdrupTracer::idrup_solve_query () {
if (binary)
file->put ('q');
else
file->put ("q ");
for (auto &lit : assumptions) {
if (binary)
put_binary_lit (lit);
else
file->put (lit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
flush_if_piping ();
}
/*------------------------------------------------------------------------*/
void IdrupTracer::add_derived_clause (int64_t, bool,
const vector<int> &clause,
const vector<int64_t> &) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG (clause, "IDRUP TRACER tracing addition of derived clause");
idrup_add_derived_clause (clause);
#ifndef QUIET
added++;
#endif
}
void IdrupTracer::add_assumption_clause (int64_t id,
const vector<int> &clause,
const vector<int64_t> &) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG (clause, "IDRUP TRACER tracing addition of assumption clause");
for (auto &lit : clause)
imported_clause.push_back (lit);
last_id = id;
insert ();
imported_clause.clear ();
}
void IdrupTracer::delete_clause (int64_t id, bool,
const vector<int> &clause) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG ("IDRUP TRACER tracing deletion of clause[%" PRId64 "]", id);
idrup_delete_clause (id, clause);
}
void IdrupTracer::weaken_minus (int64_t id, const vector<int> &) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG ("IDRUP TRACER tracing weaken minus of clause[%" PRId64 "]", id);
last_id = id;
insert ();
#ifndef QUIET
weakened++;
#endif
}
void IdrupTracer::conclude_unsat (ConclusionType,
const vector<int64_t> &conclusion) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG (conclusion, "IDRUP TRACER tracing conclusion of clause(s)");
idrup_conclude_and_delete (conclusion);
}
void IdrupTracer::add_original_clause (int64_t id, bool,
const vector<int> &clause,
bool restored) {
if (file->closed ())
return;
if (!restored) {
LOG (clause, "IDRUP TRACER tracing addition of original clause");
#ifndef QUIET
original++;
#endif
return idrup_add_original_clause (clause);
}
assert (restored);
if (find_and_delete (id)) {
LOG (clause,
"IDRUP TRACER the clause was not yet weakened, so no restore");
return;
}
LOG (clause, "IDRUP TRACER tracing addition of restored clause");
idrup_add_restored_clause (clause);
#ifndef QUIET
restore++;
#endif
}
void IdrupTracer::report_status (int status, int64_t) {
if (file->closed ())
return;
LOG ("IDRUP TRACER tracing report of status %d", status);
idrup_report_status (status);
}
void IdrupTracer::conclude_sat (const vector<int> &model) {
if (file->closed ())
return;
LOG (model, "IDRUP TRACER tracing conclusion of model");
idrup_conclude_sat (model);
}
void IdrupTracer::conclude_unknown (const vector<int> &trail) {
if (file->closed ())
return;
LOG (trail, "IDRUP TRACER tracing conclusion of unknown state");
idrup_conclude_unknown (trail);
}
void IdrupTracer::solve_query () {
if (file->closed ())
return;
LOG (assumptions, "IDRUP TRACER tracing solve query with assumptions");
idrup_solve_query ();
#ifndef QUIET
solved++;
#endif
}
void IdrupTracer::add_assumption (int lit) {
LOG ("IDRUP TRACER tracing addition of assumption %d", lit);
assumptions.push_back (lit);
}
void IdrupTracer::reset_assumptions () {
LOG (assumptions, "IDRUP TRACER tracing reset of assumptions");
assumptions.clear ();
}
/*------------------------------------------------------------------------*/
bool IdrupTracer::closed () { return file->closed (); }
#ifndef QUIET
void IdrupTracer::print_statistics () {
// TODO complete this.
uint64_t bytes = file->bytes ();
uint64_t total = added + deleted + weakened + restore + original;
MSG ("LIDRUP %" PRId64 " original clauses %.2f%%", original,
percent (original, total));
MSG ("LIDRUP %" PRId64 " learned clauses %.2f%%", added,
percent (added, total));
MSG ("LIDRUP %" PRId64 " deleted clauses %.2f%%", deleted,
percent (deleted, total));
MSG ("LIDRUP %" PRId64 " weakened clauses %.2f%%", weakened,
percent (weakened, total));
MSG ("LIDRUP %" PRId64 " restored clauses %.2f%%", restore,
percent (restore, total));
MSG ("LIDRUP %" PRId64 " queries %.2f", solved, relative (solved, total));
MSG ("IDRUP %" PRId64 " bytes (%.2f MB)", bytes,
bytes / (double) (1 << 20));
}
#endif
void IdrupTracer::close (bool print) {
assert (!closed ());
file->close ();
#ifndef QUIET
if (print) {
MSG ("IDRUP proof file '%s' closed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
void IdrupTracer::flush (bool print) {
assert (!closed ());
file->flush ();
#ifndef QUIET
if (print) {
MSG ("IDRUP proof file '%s' flushed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
} // namespace CaDiCaL

View File

@ -0,0 +1,110 @@
#ifndef _idruptracer_h_INCLUDED
#define _idruptracer_h_INCLUDED
class FileTracer;
namespace CaDiCaL {
struct IdrupClause {
IdrupClause *next; // collision chain link for hash table
uint64_t hash; // previously computed full 64-bit hash
int64_t id; // id of clause
unsigned size;
int literals[1];
};
class IdrupTracer : public FileTracer {
Internal *internal;
File *file;
bool binary;
bool piping; // The 'file' is a pipe and needs eagerly flushing.
// hash table for conclusion
//
uint64_t num_clauses; // number of clauses in hash table
uint64_t size_clauses; // size of clause hash table
IdrupClause **clauses; // hash table of clauses
vector<int> imported_clause;
vector<int> assumptions;
static const unsigned num_nonces = 4;
uint64_t nonces[num_nonces]; // random numbers for hashing
uint64_t last_hash; // last computed hash value of clause
int64_t last_id; // id of the last added clause
IdrupClause *last_clause;
uint64_t compute_hash (int64_t); // compute and save hash value of clause
IdrupClause *new_clause ();
void delete_clause (IdrupClause *);
static uint64_t reduce_hash (uint64_t hash, uint64_t size);
void enlarge_clauses (); // enlarge hash table for clauses
void insert (); // insert clause in hash table
bool
find_and_delete (const int64_t); // find clause position in hash table
#ifndef QUIET
int64_t added, deleted, weakened, restore, original, solved;
#endif
void flush_if_piping ();
void put_binary_zero ();
void put_binary_lit (int external_lit);
void put_binary_id (int64_t id, bool = false);
void idrup_add_derived_clause (const vector<int> &clause);
void idrup_delete_clause (int64_t id, const vector<int> &clause);
void idrup_add_restored_clause (const vector<int> &clause);
void idrup_add_original_clause (const vector<int> &clause);
void idrup_conclude_and_delete (const vector<int64_t> &conclusion);
void idrup_report_status (int status);
void idrup_conclude_sat (const vector<int> &model);
void idrup_conclude_unknown (const vector<int> &trail);
void idrup_solve_query ();
public:
IdrupTracer (Internal *, File *file, bool);
~IdrupTracer ();
// proof section:
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
void add_assumption_clause (int64_t, const vector<int> &,
const vector<int64_t> &) override;
void weaken_minus (int64_t, const vector<int> &) override;
void delete_clause (int64_t, bool, const vector<int> &) override;
void add_original_clause (int64_t, bool, const vector<int> &,
bool = false) override;
void report_status (int, int64_t) override;
void conclude_sat (const vector<int> &) override;
void conclude_unsat (ConclusionType, const vector<int64_t> &) override;
void conclude_unknown (const vector<int> &) override;
void solve_query () override;
void add_assumption (int) override;
void reset_assumptions () override;
// skip
void begin_proof (int64_t) override {}
void finalize_clause (int64_t, const vector<int> &) override {}
void strengthen (int64_t) override {}
void add_constraint (const vector<int> &) override {}
// logging and file io
void connect_internal (Internal *i) override;
#ifndef QUIET
void print_statistics ();
#endif
bool closed () override;
void close (bool) override;
void flush (bool) override;
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,365 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This provides an implementation of variable instantiation, a technique
// for removing literals with few occurrence (see also 'instantiate.hpp').
/*------------------------------------------------------------------------*/
// Triggered at the end of a variable elimination round ('elim_round').
void Internal::collect_instantiation_candidates (
Instantiator &instantiator) {
assert (occurring ());
for (auto idx : vars) {
if (frozen (idx))
continue;
if (!active (idx))
continue;
if (flags (idx).elim)
continue; // BVE attempt pending
for (int sign = -1; sign <= 1; sign += 2) {
const int lit = sign * idx;
if (noccs (lit) > opts.instantiateocclim)
continue;
Occs &os = occs (lit);
for (const auto &c : os) {
if (c->garbage)
continue;
if (opts.instantiateonce && c->instantiated)
continue;
if (c->size < opts.instantiateclslim)
continue;
bool satisfied = false;
int unassigned = 0;
for (const auto &other : *c) {
const signed char tmp = val (other);
if (tmp > 0)
satisfied = true;
if (!tmp)
unassigned++;
}
if (satisfied)
continue;
if (unassigned < 3)
continue; // avoid learning units
size_t negoccs = occs (-lit).size ();
LOG (c,
"instantiation candidate literal %d "
"with %zu negative occurrences in",
lit, negoccs);
instantiator.candidate (lit, c, c->size, negoccs);
}
}
}
}
/*------------------------------------------------------------------------*/
// Specialized propagation and assignment routines for instantiation.
inline void Internal::inst_assign (int lit) {
LOG ("instantiate assign %d", lit);
assert (!val (lit));
assert ((int) num_assigned < max_var);
num_assigned++;
set_val (lit, 1);
trail.push_back (lit);
}
// Conflict analysis is only needed to do valid resolution proofs.
// We remember propagated clauses in order of assignment (in inst_chain)
// which allows us to do a variant of conflict analysis if the instantiation
// attempt succeeds.
//
bool Internal::inst_propagate () { // Adapted from 'propagate'.
START (propagate);
int64_t before = propagated;
bool ok = true;
while (ok && propagated != trail.size ()) {
const int lit = -trail[propagated++];
LOG ("instantiate propagating %d", -lit);
Watches &ws = watches (lit);
const const_watch_iterator eow = ws.end ();
const_watch_iterator i = ws.begin ();
watch_iterator j = ws.begin ();
while (i != eow) {
const Watch w = *j++ = *i++;
const signed char b = val (w.blit);
if (b > 0)
continue;
if (w.binary ()) {
if (b < 0) {
ok = false;
LOG (w.clause, "conflict");
if (lrat) {
inst_chain.push_back (w.clause);
}
break;
} else {
if (lrat) {
inst_chain.push_back (w.clause);
}
inst_assign (w.blit);
}
} else {
literal_iterator lits = w.clause->begin ();
const int other = lits[0] ^ lits[1] ^ lit;
lits[0] = other, lits[1] = lit;
const signed char u = val (other);
if (u > 0)
j[-1].blit = other;
else {
const int size = w.clause->size;
const const_literal_iterator end = lits + size;
const literal_iterator middle = lits + w.clause->pos;
literal_iterator k = middle;
signed char v = -1;
int r = 0;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
w.clause->pos = k - lits;
assert (lits + 2 <= k), assert (k <= w.clause->end ());
if (v > 0) {
j[-1].blit = r;
} else if (!v) {
LOG (w.clause, "unwatch %d in", r);
lits[1] = r;
*k = lit;
watch_literal (r, lit, w.clause);
j--;
} else if (!u) {
assert (v < 0);
if (lrat) {
inst_chain.push_back (w.clause);
}
inst_assign (other);
} else {
assert (u < 0);
assert (v < 0);
if (lrat) {
inst_chain.push_back (w.clause);
}
LOG (w.clause, "conflict");
ok = false;
break;
}
}
}
}
if (j != i) {
while (i != eow)
*j++ = *i++;
ws.resize (j - ws.begin ());
}
}
int64_t delta = propagated - before;
stats.propagations.instantiate += delta;
STOP (propagate);
return ok;
}
/*------------------------------------------------------------------------*/
// This is the instantiation attempt.
bool Internal::instantiate_candidate (int lit, Clause *c) {
stats.instried++;
if (c->garbage)
return false;
assert (!level);
bool found = false, satisfied = false, inactive = false;
int unassigned = 0;
for (const auto &other : *c) {
if (other == lit)
found = true;
const signed char tmp = val (other);
if (tmp > 0) {
satisfied = true;
break;
}
if (!tmp && !active (other)) {
inactive = true;
break;
}
if (!tmp)
unassigned++;
}
if (!found)
return false;
if (inactive)
return false;
if (satisfied)
return false;
if (unassigned < 3)
return false;
size_t before = trail.size ();
assert (propagated == before);
assert (active (lit));
assert (inst_chain.empty ());
LOG (c, "trying to instantiate %d in", lit);
assert (!c->garbage);
c->instantiated = true;
assert (lrat_chain.empty ());
level++;
inst_assign (lit); // Assume 'lit' to true.
for (const auto &other : *c) {
if (other == lit)
continue;
const signed char tmp = val (other);
if (tmp) {
assert (tmp < 0);
continue;
}
inst_assign (-other); // Assume other to false.
}
bool ok = inst_propagate (); // Propagate.
assert (lrat_chain.empty ()); // chain will be built here
if (ok) {
inst_chain.clear ();
} else if (lrat) { // analyze conflict for lrat
assert (inst_chain.size ());
Clause *reason = inst_chain.back ();
inst_chain.pop_back ();
lrat_chain.push_back (reason->id);
for (const auto &other : *reason) {
Flags &f = flags (other);
assert (!f.seen);
f.seen = true;
analyzed.push_back (other);
}
}
while (trail.size () > before) { // Backtrack.
const int other = trail.back ();
LOG ("instantiate unassign %d", other);
trail.pop_back ();
assert (val (other) > 0);
num_assigned--;
set_val (other, 0);
// this is a variant of conflict analysis which is only needed for lrat
if (!ok && inst_chain.size () && lrat) {
Flags &f = flags (other);
if (f.seen) {
Clause *reason = inst_chain.back ();
lrat_chain.push_back (reason->id);
for (const auto &other : *reason) {
Flags &f = flags (other);
if (f.seen)
continue;
f.seen = true;
analyzed.push_back (other);
}
f.seen = false;
}
inst_chain.pop_back ();
}
}
assert (inst_chain.empty ());
// post processing step for lrat
if (!ok && lrat) {
if (flags (lit).seen)
lrat_chain.push_back (c->id);
for (const auto &other : *c) {
Flags &f = flags (other);
f.seen = false;
}
for (int other : analyzed) {
Flags &f = flags (other);
if (!f.seen) {
f.seen = true;
continue;
}
int64_t id = unit_id (-other);
lrat_chain.push_back (id);
}
clear_analyzed_literals ();
reverse (lrat_chain.begin (), lrat_chain.end ());
}
assert (analyzed.empty ());
propagated = before;
assert (level == 1);
level = 0;
if (ok) {
assert (lrat_chain.empty ());
LOG ("instantiation failed");
return false;
}
unwatch_clause (c);
LOG (lrat_chain, "instantiate proof chain");
strengthen_clause (c, lit);
watch_clause (c);
lrat_chain.clear ();
assert (c->size > 1);
LOG ("instantiation succeeded");
stats.instantiated++;
return true;
}
/*------------------------------------------------------------------------*/
// Try to instantiate all candidates collected before through the
// 'collect_instantiation_candidates' routine.
void Internal::instantiate (Instantiator &instantiator) {
assert (opts.instantiate);
START (instantiate);
stats.instrounds++;
#ifndef QUIET
const int64_t candidates = instantiator.candidates.size ();
int64_t tried = 0;
#endif
int64_t instantiated = 0;
init_watches ();
connect_watches ();
if (propagated < trail.size ()) {
if (!propagate ()) {
LOG ("propagation after connecting watches failed");
learn_empty_clause ();
assert (unsat);
}
}
PHASE ("instantiate", stats.instrounds,
"attempting to instantiate %" PRId64
" candidate literal clause pairs",
candidates);
while (!unsat && !terminated_asynchronously () &&
!instantiator.candidates.empty ()) {
Instantiator::Candidate cand = instantiator.candidates.back ();
instantiator.candidates.pop_back ();
#ifndef QUIET
tried++;
#endif
if (!active (cand.lit))
continue;
LOG (cand.clause,
"trying to instantiate %d with "
"%zd negative occurrences in",
cand.lit, cand.negoccs);
if (!instantiate_candidate (cand.lit, cand.clause))
continue;
instantiated++;
VERBOSE (2,
"instantiation %" PRId64 " (%.1f%%) succeeded "
"(%.1f%%) with %zd negative occurrences in size %d clause",
tried, percent (tried, candidates),
percent (instantiated, tried), cand.negoccs, cand.size);
}
PHASE ("instantiate", stats.instrounds,
"instantiated %" PRId64 " candidate successfully "
"out of %" PRId64 " tried %.1f%%",
instantiated, tried, percent (instantiated, tried));
report ('I', !instantiated);
reset_watches ();
STOP (instantiate);
}
} // namespace CaDiCaL

View File

@ -0,0 +1,45 @@
#ifndef _instantiate_hpp_INCLUDED
#define _instantiate_hpp_INCLUDED
namespace CaDiCaL {
// We are trying to remove literals in clauses, which occur in few clauses
// and further restrict this removal to variables for which variable
// elimination failed. Thus if for instance we succeed in removing the
// single occurrence of a literal, pure literal elimination can
// eliminate the corresponding variable in the next variable elimination
// round. The set of such literal clause candidate pairs is collected at
// the end of a variable elimination round and tried before returning. The
// name of this technique is inspired by 'variable instantiation' as
// described in [AnderssonBjesseCookHanna-DAC'02] and apparently
// successfully used in the 'Oepir' SAT solver.
struct Clause;
struct Internal;
class Instantiator {
friend struct Internal;
struct Candidate {
int lit;
int size;
size_t negoccs;
Clause *clause;
Candidate (int l, Clause *c, int s, size_t n)
: lit (l), size (s), negoccs (n), clause (c) {}
};
vector<Candidate> candidates;
public:
void candidate (int l, Clause *c, int s, size_t n) {
candidates.push_back (Candidate (l, c, s, n));
}
operator bool () const { return !candidates.empty (); }
};
} // namespace CaDiCaL
#endif

1190
src/sat/cadical/internal.cpp Normal file

File diff suppressed because it is too large Load Diff

1858
src/sat/cadical/internal.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
#ifndef _inttypes_h_INCLUDED
#define _inttypes_h_INCLUDED
// This is an essence a wrapper around '<cinttypes>' respectively
// 'inttypes.h' in order to please the 'MinGW' cross-compiler (we are using
// 'i686-w64-mingw32-gcc') to produce correct 'printf' style formatting for
// 64-bit numbers as this does not work out-of-the-box (which is also very
// annoying). This also produces lots of warnings (through '-Wformat' and
// the corresponding 'attribute' declaration for 'printf' style functions).
// Again 'MinGW' is not fully standard compliant here and we have to cover
// up for that manually.
// We repeat the code on making this work which is also contained in
// 'cadical.hpp' as we do not want to require users of the library to
// include another header file (like this one) beside 'cadical.hpp'.
#ifndef PRINTF_FORMAT
#ifdef __MINGW32__
#define __USE_MINGW_ANSI_STDIO 1
#define PRINTF_FORMAT __MINGW_PRINTF_FORMAT
#else
#define PRINTF_FORMAT printf
#endif
#endif
#include <cinttypes>
#endif

View File

@ -0,0 +1,43 @@
#include "ipasir.h"
#include "ccadical.h"
extern "C" {
const char *ipasir_signature () { return ccadical_signature (); }
void *ipasir_init () { return ccadical_init (); }
void ipasir_release (void *solver) {
ccadical_release ((CCaDiCaL *) solver);
}
void ipasir_add (void *solver, int lit) {
ccadical_add ((CCaDiCaL *) solver, lit);
}
void ipasir_assume (void *solver, int lit) {
ccadical_assume ((CCaDiCaL *) solver, lit);
}
int ipasir_solve (void *solver) {
return ccadical_solve ((CCaDiCaL *) solver);
}
int ipasir_val (void *solver, int lit) {
return ccadical_val ((CCaDiCaL *) solver, lit);
}
int ipasir_failed (void *solver, int lit) {
return ccadical_failed ((CCaDiCaL *) solver, lit);
}
void ipasir_set_terminate (void *solver, void *state,
int (*terminate) (void *state)) {
ccadical_set_terminate ((CCaDiCaL *) solver, state, terminate);
}
void ipasir_set_learn (void *solver, void *state, int max_length,
void (*learn) (void *state, int *clause)) {
ccadical_set_learn ((CCaDiCaL *) solver, state, max_length, learn);
}
}

37
src/sat/cadical/ipasir.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef _ipasir_h_INCLUDED
#define _ipasir_h_INCLUDED
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif
/*------------------------------------------------------------------------*/
// Here are the declarations for the actual IPASIR functions, which is the
// generic incremental reentrant SAT solver API used for instance in the SAT
// competition. The other 'C' API in 'ccadical.h' is (more) type safe and
// has additional functions only supported by the CaDiCaL library. Please
// also refer to our SAT Race 2015 article in the Journal of AI from 2016.
const char *ipasir_signature (void);
void *ipasir_init (void);
void ipasir_release (void *solver);
void ipasir_add (void *solver, int lit);
void ipasir_assume (void *solver, int lit);
int ipasir_solve (void *solver);
int ipasir_val (void *solver, int lit);
int ipasir_failed (void *solver, int lit);
void ipasir_set_terminate (void *solver, void *state,
int (*terminate) (void *state));
void ipasir_set_learn (void *solver, void *state, int max_length,
void (*learn) (void *state, int *clause));
/*------------------------------------------------------------------------*/
#ifdef __cplusplus
}
#endif
/*------------------------------------------------------------------------*/
#endif

2593
src/sat/cadical/kitten.c Normal file

File diff suppressed because it is too large Load Diff

97
src/sat/cadical/kitten.h Normal file
View File

@ -0,0 +1,97 @@
#ifndef _kitten_h_INCLUDED
#define _kitten_h_INCLUDED
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct kitten kitten;
kitten *kitten_init (void);
void kitten_clear (kitten *);
void kitten_release (kitten *);
#ifdef LOGGING
void kitten_set_logging (kitten *kitten);
#endif
void kitten_track_antecedents (kitten *);
void kitten_shuffle_clauses (kitten *);
void kitten_flip_phases (kitten *);
void kitten_randomize_phases (kitten *);
void kitten_assume (kitten *, unsigned lit);
void kitten_assume_signed (kitten *, int lit);
void kitten_clause (kitten *, size_t size, unsigned *);
void citten_clause_with_id (kitten *, unsigned id, size_t size, int *);
void kitten_unit (kitten *, unsigned);
void kitten_binary (kitten *, unsigned, unsigned);
void kitten_clause_with_id_and_exception (kitten *, unsigned id,
size_t size, const unsigned *,
unsigned except);
void citten_clause_with_id_and_exception (kitten *, unsigned id,
size_t size, const int *,
unsigned except);
void citten_clause_with_id_and_equivalence (kitten *, unsigned id,
size_t size, const int *,
unsigned, unsigned);
void kitten_no_ticks_limit (kitten *);
void kitten_set_ticks_limit (kitten *, uint64_t);
uint64_t kitten_current_ticks (kitten *);
void kitten_no_terminator (kitten *);
void kitten_set_terminator (kitten *, void *, int (*) (void *));
int kitten_solve (kitten *);
int kitten_status (kitten *);
signed char kitten_value (kitten *, unsigned);
signed char kitten_signed_value (kitten *, int); // converts second argument
signed char kitten_fixed (kitten *, unsigned);
signed char kitten_fixed_signed (kitten *, int); // converts
bool kitten_failed (kitten *, unsigned);
bool kitten_flip_literal (kitten *, unsigned);
bool kitten_flip_signed_literal (kitten *, int);
unsigned kitten_compute_clausal_core (kitten *, uint64_t *learned);
void kitten_shrink_to_clausal_core (kitten *);
void kitten_traverse_core_ids (kitten *, void *state,
void (*traverse) (void *state, unsigned id));
void kitten_traverse_core_clauses (kitten *, void *state,
void (*traverse) (void *state,
bool learned, size_t,
const unsigned *));
void kitten_traverse_core_clauses_with_id (
kitten *, void *state,
void (*traverse) (void *state, unsigned, bool learned, size_t,
const unsigned *));
void kitten_trace_core (kitten *, void *state,
void (*trace) (void *, unsigned, unsigned, bool,
size_t, const unsigned *, size_t,
const unsigned *));
int kitten_compute_prime_implicant (kitten *kitten, void *state,
bool (*ignore) (void *, unsigned));
void kitten_add_prime_implicant (kitten *kitten, void *state, int side,
void (*add_implicant) (void *, int, size_t,
const unsigned *));
int kitten_flip_and_implicant_for_signed_literal (kitten *kitten, int elit);
#ifdef __cplusplus
}
#endif
#endif

33
src/sat/cadical/level.hpp Normal file
View File

@ -0,0 +1,33 @@
#ifndef _level_hpp_INCLUDED
#define _level_hpp_INCLUDED
#include <climits>
namespace CaDiCaL {
// For each new decision we increase the decision level and push a 'Level'
// on the 'control' stack. The information gathered here is used in
// 'reuse_trail' and for early aborts in clause minimization.
struct Level {
int decision; // decision literal of this level
int trail; // trail start of this level
struct {
int count; // how many variables seen during 'analyze'
int trail; // smallest trail position seen on this level
} seen;
void reset () {
seen.count = 0;
seen.trail = INT_MAX;
}
Level (int d, int t) : decision (d), trail (t) { reset (); }
Level () {}
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,656 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
LidrupTracer::LidrupTracer (Internal *i, File *f, bool b)
: internal (i), file (f), binary (b), num_clauses (0), size_clauses (0),
clauses (0), last_hash (0), last_id (0), last_clause (0)
#ifndef QUIET
,
added (0), deleted (0)
#endif
{
(void) internal;
// Initialize random number table for hash function.
//
Random random (42);
for (unsigned n = 0; n < num_nonces; n++) {
uint64_t nonce = random.next ();
if (!(nonce & 1))
nonce++;
assert (nonce), assert (nonce & 1);
nonces[n] = nonce;
}
#ifndef NDEBUG
binary = b;
#else
(void) b;
#endif
piping = file->piping ();
}
void LidrupTracer::connect_internal (Internal *i) {
internal = i;
file->connect_internal (internal);
LOG ("LIDRUP TRACER connected to internal");
}
LidrupTracer::~LidrupTracer () {
LOG ("LIDRUP TRACER delete");
delete file;
for (size_t i = 0; i < size_clauses; i++)
for (LidrupClause *c = clauses[i], *next; c; c = next)
next = c->next, delete_clause (c);
delete[] clauses;
}
/*------------------------------------------------------------------------*/
void LidrupTracer::enlarge_clauses () {
assert (num_clauses == size_clauses);
const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1;
LOG ("LIDRUP Tracer enlarging clauses of tracer from %" PRIu64
" to %" PRIu64,
(uint64_t) size_clauses, (uint64_t) new_size_clauses);
LidrupClause **new_clauses;
new_clauses = new LidrupClause *[new_size_clauses];
clear_n (new_clauses, new_size_clauses);
for (uint64_t i = 0; i < size_clauses; i++) {
for (LidrupClause *c = clauses[i], *next; c; c = next) {
next = c->next;
const uint64_t h = reduce_hash (c->hash, new_size_clauses);
c->next = new_clauses[h];
new_clauses[h] = c;
}
}
delete[] clauses;
clauses = new_clauses;
size_clauses = new_size_clauses;
}
LidrupClause *LidrupTracer::new_clause () {
LidrupClause *res = new LidrupClause;
res->next = 0;
res->hash = last_hash;
res->id = last_id;
for (const auto &id : imported_chain) {
res->chain.push_back (id);
}
for (const auto &lit : imported_clause) {
res->literals.push_back (lit);
}
last_clause = res;
num_clauses++;
return res;
}
void LidrupTracer::delete_clause (LidrupClause *c) {
assert (c);
num_clauses--;
delete c;
}
uint64_t LidrupTracer::reduce_hash (uint64_t hash, uint64_t size) {
assert (size > 0);
unsigned shift = 32;
uint64_t res = hash;
while ((((uint64_t) 1) << shift) > size) {
res ^= res >> shift;
shift >>= 1;
}
res &= size - 1;
assert (res < size);
return res;
}
uint64_t LidrupTracer::compute_hash (const int64_t id) {
assert (id > 0);
unsigned j = id % num_nonces;
uint64_t tmp = nonces[j] * (uint64_t) id;
return last_hash = tmp;
}
bool LidrupTracer::find_and_delete (const int64_t id) {
if (!num_clauses)
return false;
LidrupClause **res = 0, *c;
const uint64_t hash = compute_hash (id);
const uint64_t h = reduce_hash (hash, size_clauses);
for (res = clauses + h; (c = *res); res = &c->next) {
if (c->hash == hash && c->id == id) {
break;
}
if (!c->next)
return false;
}
if (!c)
return false;
assert (c && res);
*res = c->next;
for (auto &lit : c->literals) {
imported_clause.push_back (lit);
}
for (auto &cid : c->chain) {
imported_chain.push_back (cid);
}
delete_clause (c);
return true;
}
void LidrupTracer::insert () {
if (num_clauses == size_clauses)
enlarge_clauses ();
const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses);
LidrupClause *c = new_clause ();
c->next = clauses[h];
clauses[h] = c;
}
/*------------------------------------------------------------------------*/
inline void LidrupTracer::flush_if_piping () {
if (piping)
file->flush ();
}
inline void LidrupTracer::put_binary_zero () {
assert (binary);
assert (file);
file->put ((unsigned char) 0);
}
inline void LidrupTracer::put_binary_lit (int lit) {
assert (binary);
assert (file);
assert (lit != INT_MIN);
unsigned x = 2 * abs (lit) + (lit < 0);
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
inline void LidrupTracer::put_binary_id (int64_t id, bool can_be_negative) {
assert (binary);
assert (file);
uint64_t x = abs (id);
if (can_be_negative) {
x = 2 * x + (id < 0);
}
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
/*------------------------------------------------------------------------*/
void LidrupTracer::lidrup_add_restored_clause (int64_t id) {
if (!batch_weaken.empty () || !batch_delete.empty ())
lidrup_batch_weaken_restore_and_delete ();
batch_restore.push_back (id);
}
void LidrupTracer::lidrup_add_derived_clause (
int64_t id, const vector<int> &clause, const vector<int64_t> &chain) {
lidrup_batch_weaken_restore_and_delete ();
if (binary) {
file->put ('l');
put_binary_id (id);
} else {
file->put ("l ");
file->put (id);
file->put (' ');
}
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0 ");
for (const auto &cid : chain)
if (binary)
put_binary_id (cid);
else
file->put (cid), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
void LidrupTracer::lidrup_add_original_clause (int64_t id,
const vector<int> &clause) {
lidrup_batch_weaken_restore_and_delete ();
if (binary) {
file->put ('i');
put_binary_id (id);
} else {
file->put ("i ");
file->put (id);
file->put (' ');
}
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0\n");
}
void LidrupTracer::lidrup_batch_weaken_restore_and_delete () {
assert (batch_weaken.empty () || batch_delete.empty ());
if (!batch_weaken.empty ()) {
if (binary) {
file->put ('w');
} else {
file->put ("w ");
}
for (const auto &id : batch_weaken) {
if (binary)
put_binary_id (id);
else
file->put (id), file->put (' ');
}
batch_weaken.clear ();
if (binary)
put_binary_zero ();
else
file->put ("0\n");
#ifndef QUIET
batched++;
#endif
}
if (!batch_delete.empty ()) {
if (binary) {
file->put ('d');
} else {
file->put ("d ");
}
for (const auto &id : batch_delete) {
if (binary)
put_binary_id (id);
else
file->put (id), file->put (' ');
}
batch_delete.clear ();
if (binary)
put_binary_zero ();
else
file->put ("0\n");
#ifndef QUIET
batched++;
#endif
}
if (!batch_restore.empty ()) {
if (binary) {
file->put ('r');
} else {
file->put ("r ");
}
for (const auto &id : batch_restore) {
if (binary)
put_binary_id (id);
else
file->put (id), file->put (' ');
}
batch_restore.clear ();
if (binary)
put_binary_zero ();
else
file->put ("0\n");
#ifndef QUIET
batched++;
#endif
}
}
void LidrupTracer::lidrup_conclude_and_delete (
const vector<int64_t> &conclusion) {
lidrup_batch_weaken_restore_and_delete ();
int64_t size = conclusion.size ();
if (size > 1) {
if (binary) {
file->put ('U');
put_binary_id (size);
} else {
file->put ("U ");
file->put (size), file->put ("\n");
}
}
for (auto &id : conclusion) {
if (binary)
file->put ('u');
else
file->put ("u ");
if (!find_and_delete (id)) {
assert (imported_clause.empty ());
assert (conclusion.size () == 1);
if (binary) {
put_binary_zero ();
put_binary_id (id);
put_binary_zero ();
} else {
file->put ("0 ");
file->put (id);
file->put (" 0\n");
}
} else {
for (const auto &external_lit : imported_clause) {
// flip sign...
const auto not_elit = -external_lit;
if (binary)
put_binary_lit (not_elit);
else
file->put (not_elit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0 ");
for (const auto &cid : imported_chain) {
if (binary)
put_binary_id (cid);
else
file->put (cid), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
imported_clause.clear ();
imported_chain.clear ();
}
}
flush_if_piping ();
}
void LidrupTracer::lidrup_report_status (int status) {
lidrup_batch_weaken_restore_and_delete ();
if (binary)
file->put ('s');
else
file->put ("s ");
if (status == SATISFIABLE)
file->put ("SATISFIABLE");
else if (status == UNSATISFIABLE)
file->put ("UNSATISFIABLE");
else
file->put ("UNKNOWN");
if (!binary)
file->put ("\n");
flush_if_piping ();
}
void LidrupTracer::lidrup_conclude_sat (const vector<int> &model) {
lidrup_batch_weaken_restore_and_delete ();
if (binary)
file->put ('m');
else
file->put ("m ");
for (auto &lit : model) {
if (binary)
put_binary_lit (lit);
else
file->put (lit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
flush_if_piping ();
}
void LidrupTracer::lidrup_conclude_unknown (const vector<int> &trail) {
lidrup_batch_weaken_restore_and_delete ();
if (binary)
file->put ('e');
else
file->put ("e ");
for (auto &lit : trail) {
if (binary)
put_binary_lit (lit);
else
file->put (lit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
flush_if_piping ();
}
void LidrupTracer::lidrup_solve_query () {
lidrup_batch_weaken_restore_and_delete ();
if (binary)
file->put ('q');
else
file->put ("q ");
for (auto &lit : assumptions) {
if (binary)
put_binary_lit (lit);
else
file->put (lit), file->put (' ');
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
flush_if_piping ();
}
/*------------------------------------------------------------------------*/
void LidrupTracer::add_derived_clause (int64_t id, bool,
const vector<int> &clause,
const vector<int64_t> &chain) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG (clause, "LIDRUP TRACER tracing addition of derived clause");
lidrup_add_derived_clause (id, clause, chain);
#ifndef QUIET
added++;
#endif
}
void LidrupTracer::add_assumption_clause (int64_t id,
const vector<int> &clause,
const vector<int64_t> &chain) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG (clause,
"LIDRUP TRACER tracing addition of assumption clause[%" PRId64 "]",
id);
for (auto &lit : clause)
imported_clause.push_back (lit);
for (auto &cid : chain)
imported_chain.push_back (cid);
last_id = id;
insert ();
imported_clause.clear ();
imported_chain.clear ();
}
void LidrupTracer::delete_clause (int64_t id, bool, const vector<int> &) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG ("LIDRUP TRACER tracing deletion of clause[%" PRId64 "]", id);
if (find_and_delete (id)) {
assert (imported_clause.empty ());
if (!batch_delete.empty () || !batch_restore.empty ())
lidrup_batch_weaken_restore_and_delete ();
batch_weaken.push_back (id);
#ifndef QUIET
weakened++;
#endif
} else {
if (!batch_weaken.empty () || !batch_restore.empty ())
lidrup_batch_weaken_restore_and_delete ();
batch_delete.push_back (id);
#ifndef QUIET
deleted++;
#endif
}
}
void LidrupTracer::weaken_minus (int64_t id, const vector<int> &) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG ("LIDRUP TRACER tracing weaken minus of clause[%" PRId64 "]", id);
last_id = id;
insert ();
}
void LidrupTracer::conclude_unsat (ConclusionType,
const vector<int64_t> &conclusion) {
if (file->closed ())
return;
assert (imported_clause.empty ());
LOG (conclusion, "LIDRUP TRACER tracing conclusion of clause(s)");
lidrup_conclude_and_delete (conclusion);
}
void LidrupTracer::add_original_clause (int64_t id, bool,
const vector<int> &clause,
bool restored) {
if (file->closed ())
return;
if (!restored) {
LOG (clause, "LIDRUP TRACER tracing addition of original clause");
#ifndef QUIET
original++;
#endif
return lidrup_add_original_clause (id, clause);
}
assert (restored);
if (find_and_delete (id)) {
LOG (clause,
"LIDRUP TRACER the clause was not yet weakened, so no restore");
return;
}
LOG (clause, "LIDRUP TRACER tracing addition of restored clause");
lidrup_add_restored_clause (id);
#ifndef QUIET
restore++;
#endif
}
void LidrupTracer::report_status (int status, int64_t) {
if (file->closed ())
return;
LOG ("LIDRUP TRACER tracing report of status %d", status);
lidrup_report_status (status);
}
void LidrupTracer::conclude_sat (const vector<int> &model) {
if (file->closed ())
return;
LOG (model, "LIDRUP TRACER tracing conclusion of model");
lidrup_conclude_sat (model);
}
void LidrupTracer::conclude_unknown (const vector<int> &entrailed) {
if (file->closed ())
return;
LOG (entrailed, "LIDRUP TRACER tracing conclusion of UNK");
lidrup_conclude_unknown (entrailed);
}
void LidrupTracer::solve_query () {
if (file->closed ())
return;
LOG (assumptions, "LIDRUP TRACER tracing solve query with assumptions");
lidrup_solve_query ();
#ifndef QUIET
solved++;
#endif
}
void LidrupTracer::add_assumption (int lit) {
LOG ("LIDRUP TRACER tracing addition of assumption %d", lit);
assumptions.push_back (lit);
}
void LidrupTracer::reset_assumptions () {
LOG (assumptions, "LIDRUP TRACER tracing reset of assumptions");
assumptions.clear ();
}
/*------------------------------------------------------------------------*/
bool LidrupTracer::closed () { return file->closed (); }
#ifndef QUIET
void LidrupTracer::print_statistics () {
// TODO complete this.
uint64_t bytes = file->bytes ();
uint64_t total = added + deleted + weakened + restore + original;
MSG ("LIDRUP %" PRId64 " original clauses %.2f%%", original,
percent (original, total));
MSG ("LIDRUP %" PRId64 " learned clauses %.2f%%", added,
percent (added, total));
MSG ("LIDRUP %" PRId64 " deleted clauses %.2f%%", deleted,
percent (deleted, total));
MSG ("LIDRUP %" PRId64 " weakened clauses %.2f%%", weakened,
percent (weakened, total));
MSG ("LIDRUP %" PRId64 " restored clauses %.2f%%", restore,
percent (restore, total));
MSG ("LIDRUP %" PRId64 " batches of deletions, weaken and restores %.2f",
batched, relative (batched, deleted + restore + weakened));
MSG ("LIDRUP %" PRId64 " queries %.2f", solved, relative (solved, total));
MSG ("LIDRUP %" PRId64 " bytes (%.2f MB)", bytes,
bytes / (double) (1 << 20));
}
#endif
void LidrupTracer::close (bool print) {
assert (!closed ());
file->close ();
#ifndef QUIET
if (print) {
MSG ("LIDRUP proof file '%s' closed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
void LidrupTracer::flush (bool print) {
assert (!closed ());
lidrup_batch_weaken_restore_and_delete ();
file->flush ();
#ifndef QUIET
if (print) {
MSG ("LIDRUP proof file '%s' flushed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
} // namespace CaDiCaL

View File

@ -0,0 +1,117 @@
#ifndef _lidruptracer_h_INCLUDED
#define _lidruptracer_h_INCLUDED
class FileTracer;
namespace CaDiCaL {
struct LidrupClause {
LidrupClause *next; // collision chain link for hash table
uint64_t hash; // previously computed full 64-bit hash
int64_t id; // id of clause
std::vector<int64_t> chain;
std::vector<int> literals;
};
class LidrupTracer : public FileTracer {
Internal *internal;
File *file;
bool binary;
bool piping; // The 'file' is a pipe and needs eagerly flushing.
// hash table for conclusion
//
uint64_t num_clauses; // number of clauses in hash table
uint64_t size_clauses; // size of clause hash table
LidrupClause **clauses; // hash table of clauses
vector<int> imported_clause;
vector<int> assumptions;
vector<int64_t> imported_chain;
vector<int64_t> batch_weaken;
vector<int64_t> batch_delete;
vector<int64_t> batch_restore;
static const unsigned num_nonces = 4;
uint64_t nonces[num_nonces]; // random numbers for hashing
uint64_t last_hash; // last computed hash value of clause
int64_t last_id; // id of the last added clause
LidrupClause *last_clause;
uint64_t compute_hash (int64_t); // compute and save hash value of clause
LidrupClause *new_clause ();
void delete_clause (LidrupClause *);
static uint64_t reduce_hash (uint64_t hash, uint64_t size);
void enlarge_clauses (); // enlarge hash table for clauses
void insert (); // insert clause in hash table
bool
find_and_delete (const int64_t); // find clause position in hash table
#ifndef QUIET
int64_t added, deleted, weakened, restore, original, solved, batched;
#endif
void flush_if_piping ();
void put_binary_zero ();
void put_binary_lit (int external_lit);
void put_binary_id (int64_t id, bool = true);
void lidrup_add_derived_clause (int64_t id, const vector<int> &clause,
const vector<int64_t> &chain);
void lidrup_delete_clause (int64_t id); //, const vector<int> &clause);
void
lidrup_add_restored_clause (int64_t id); //, const vector<int> &clause);
void lidrup_add_original_clause (int64_t id, const vector<int> &clause);
void lidrup_conclude_and_delete (const vector<int64_t> &conclusion);
void lidrup_report_status (int status);
void lidrup_conclude_sat (const vector<int> &model);
void lidrup_conclude_unknown (const vector<int> &trail);
void lidrup_solve_query ();
void lidrup_batch_weaken_restore_and_delete ();
public:
LidrupTracer (Internal *, File *file, bool);
~LidrupTracer ();
// proof section:
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
void add_assumption_clause (int64_t, const vector<int> &,
const vector<int64_t> &) override;
void weaken_minus (int64_t, const vector<int> &) override;
void delete_clause (int64_t, bool, const vector<int> &) override;
void add_original_clause (int64_t, bool, const vector<int> &,
bool = false) override;
void report_status (int, int64_t) override;
void conclude_sat (const vector<int> &) override;
void conclude_unsat (ConclusionType, const vector<int64_t> &) override;
void conclude_unknown (const vector<int> &) override;
void solve_query () override;
void add_assumption (int) override;
void reset_assumptions () override;
// skip
void begin_proof (int64_t) override {}
void finalize_clause (int64_t, const vector<int> &) override {}
void strengthen (int64_t) override {}
void add_constraint (const vector<int> &) override {}
// logging and file io
void connect_internal (Internal *i) override;
#ifndef QUIET
void print_statistics ();
#endif
bool closed () override;
void close (bool) override;
void flush (bool) override;
};
} // namespace CaDiCaL
#endif

129
src/sat/cadical/limit.cpp Normal file
View File

@ -0,0 +1,129 @@
#include "internal.hpp"
namespace CaDiCaL {
Limit::Limit () { memset (this, 0, sizeof *this); }
/*------------------------------------------------------------------------*/
double Internal::scale (double v) const {
const double ratio = clause_variable_ratio ();
const double factor = (ratio <= 2) ? 1.0 : log (ratio) / log (2);
double res = factor * v;
if (res < 1)
res = 1;
return res;
}
/*------------------------------------------------------------------------*/
Last::Last () { memset (this, 0, sizeof *this); }
/*------------------------------------------------------------------------*/
Inc::Inc () {
memset (this, 0, sizeof *this);
decisions = conflicts = -1; // unlimited
}
void Internal::limit_terminate (int l) {
if (l <= 0 && !lim.terminate.forced) {
LOG ("keeping unbounded terminate limit");
} else if (l <= 0) {
LOG ("reset terminate limit to be unbounded");
lim.terminate.forced = 0;
} else {
lim.terminate.forced = l;
LOG ("new terminate limit of %d calls", l);
}
}
void Internal::limit_conflicts (int l) {
if (l < 0 && inc.conflicts < 0) {
LOG ("keeping unbounded conflict limit");
} else if (l < 0) {
LOG ("reset conflict limit to be unbounded");
inc.conflicts = -1;
} else {
inc.conflicts = l;
LOG ("new conflict limit of %d conflicts", l);
}
}
void Internal::limit_decisions (int l) {
if (l < 0 && inc.decisions < 0) {
LOG ("keeping unbounded decision limit");
} else if (l < 0) {
LOG ("reset decision limit to be unbounded");
inc.decisions = -1;
} else {
inc.decisions = l;
LOG ("new decision limit of %d decisions", l);
}
}
void Internal::limit_preprocessing (int l) {
if (l < 0) {
LOG ("ignoring invalid preprocessing limit %d", l);
} else if (!l) {
LOG ("reset preprocessing limit to no preprocessing");
inc.preprocessing = 0;
} else {
inc.preprocessing = l;
LOG ("new preprocessing limit of %d preprocessing rounds", l);
}
}
void Internal::limit_local_search (int l) {
if (l < 0) {
LOG ("ignoring invalid local search limit %d", l);
} else if (!l) {
LOG ("reset local search limit to no local search");
inc.localsearch = 0;
} else {
inc.localsearch = l;
LOG ("new local search limit of %d local search rounds", l);
}
}
bool Internal::is_valid_limit (const char *name) {
if (!strcmp (name, "terminate"))
return true;
if (!strcmp (name, "conflicts"))
return true;
if (!strcmp (name, "decisions"))
return true;
if (!strcmp (name, "preprocessing"))
return true;
if (!strcmp (name, "localsearch"))
return true;
return false;
}
bool Internal::limit (const char *name, int l) {
bool res = true;
if (!strcmp (name, "terminate"))
limit_terminate (l);
else if (!strcmp (name, "conflicts"))
limit_conflicts (l);
else if (!strcmp (name, "decisions"))
limit_decisions (l);
else if (!strcmp (name, "preprocessing"))
limit_preprocessing (l);
else if (!strcmp (name, "localsearch"))
limit_local_search (l);
else
res = false;
return res;
}
void Internal::reset_limits () {
LOG ("reset limits");
limit_terminate (0);
limit_conflicts (-1);
limit_decisions (-1);
limit_preprocessing (0);
limit_local_search (0);
}
} // namespace CaDiCaL

156
src/sat/cadical/limit.hpp Normal file
View File

@ -0,0 +1,156 @@
#ifndef _limit_hpp_INCLUDED
#define _limit_hpp_INCLUDED
#include <cstdint>
#include <limits>
namespace CaDiCaL {
struct Internal;
struct Limit {
bool initialized;
int64_t conflicts; // conflict limit if non-negative
int64_t decisions; // decision limit if non-negative
int64_t preprocessing; // limit on preprocessing rounds
int64_t localsearch; // limit on local search rounds
int64_t compact; // conflict limit for next 'compact'
int64_t condition; // conflict limit for next 'condition'
int64_t elim; // conflict limit for next 'elim'
int64_t flush; // conflict limit for next 'flush'
int64_t inprobe; // conflict limit for next 'inprobe'
int64_t reduce; // conflict limit for next 'reduce'
int64_t rephase; // conflict limit for next 'rephase'
int64_t report; // report limit for header
int64_t restart; // conflict limit for next 'restart'
int64_t stabilize; // conflict/ticks limit for next 'stabilize'
int keptsize; // maximum kept size in 'reduce'
int keptglue; // maximum kept glue in 'reduce'
int64_t recompute_tier; // conflict limit for next tier recomputation
// How often rephased during (1) or out (0) of stabilization.
//
int64_t rephased[2];
// Current elimination bound per eliminated variable.
//
int64_t elimbound;
struct {
int check; // countdown to next terminator call
int forced; // forced termination for testing
} terminate;
Limit ();
};
struct Delay {
struct {
int64_t interval = 0, limit = 0;
bool bypass = 0;
bool delay () {
if (bypass)
return true;
if (limit) {
--limit;
return true;
} else {
return false;
}
}
void bump_delay () {
interval += interval < INT64_MAX;
limit = interval;
}
void reduce_delay () {
if (!interval)
return;
interval /= 2;
limit = interval;
}
void bypass_delay () { bypass = 1; }
void unbypass_delay () { bypass = 0; }
} bumpreasons;
};
struct Last {
struct {
int64_t propagations;
} transred;
struct {
int64_t ticks;
} sweep, vivify, probe;
struct {
int64_t fixed, subsumephases, marked;
} elim;
struct {
int64_t reductions;
} inprobe;
struct {
int64_t conflicts;
} reduce, rephase;
struct {
int64_t ticks;
int64_t marked;
} ternary;
struct {
int64_t fixed;
} collect;
struct {
int64_t marked, ticks;
} factor;
struct {
int64_t conflicts;
int64_t ticks;
} stabilize;
Last ();
};
struct Inc {
int64_t flush; // flushing interval in terms of conflicts
int64_t stabilize; // base ticks limit after first mode switch
int64_t conflicts; // next conflict limit if non-negative
int64_t decisions; // next decision limit if non-negative
int64_t preprocessing; // next preprocessing limit if non-negative
int64_t localsearch; // next local search limit if non-negative
Inc ();
};
#define SET_EFFORT_LIMIT(LIMIT, NAME, THRESHHOLD) \
int64_t LIMIT; \
do { \
const int64_t OLD_LIMIT = stats.ticks.NAME; \
const int64_t TICKS = stats.ticks.search[0] + stats.ticks.search[1]; \
const int64_t LAST = last.NAME.ticks; \
int64_t REFERENCE = TICKS - LAST; \
if (!REFERENCE || !stats.conflicts) { \
VERBOSE (2, "last %" PRId64 " current %" PRId64 " delta %" PRId64, \
LAST, TICKS, REFERENCE); \
REFERENCE = opts.preprocessinit; \
} \
const double EFFORT = (double) opts.NAME##effort * 1e-3; \
const int64_t DELTA = EFFORT * REFERENCE; \
const int64_t THRESH = opts.NAME##thresh * clauses.size (); \
if (THRESHHOLD && DELTA < THRESH) { \
VERBOSE (2, \
"delaying %s with ticklimit %" PRId64 \
" and threshhold %" PRId64, \
#NAME, DELTA, THRESH); \
return false; \
} \
last.NAME.ticks = TICKS; \
const int64_t NEW_LIMIT = OLD_LIMIT + DELTA; \
LIMIT = NEW_LIMIT; \
} while (0)
} // namespace CaDiCaL
#endif

215
src/sat/cadical/logging.cpp Normal file
View File

@ -0,0 +1,215 @@
#ifdef LOGGING
#include "internal.hpp"
namespace CaDiCaL {
void Logger::print_log_prefix (Internal *internal) {
internal->print_prefix ();
tout.magenta ();
fputs ("LOG ", stdout);
tout.magenta (true);
printf ("%d ", internal->level);
tout.normal ();
}
void Logger::log_empty_line (Internal *internal) {
internal->print_prefix ();
tout.magenta ();
const int len = internal->prefix.size (), max = 78 - len;
for (int i = 0; i < max; i++)
fputc ('-', stdout);
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
void Logger::log (Internal *internal, const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// It is hard to factor out the common part between the two clause loggers,
// since they are also used in slightly different contexts. Our attempt to
// do so were not more readable than the current version. See the header
// for an explanation of the difference between the following two functions.
void Logger::log (Internal *internal, const Clause *c, const char *fmt,
...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (c) {
if (c->redundant)
printf (" glue %d redundant", c->glue);
else
printf (" irredundant");
printf (" size %d clause[%" PRId64 "]", c->size, c->id);
if (c->moved)
printf (" ... (moved)");
else {
if (internal->opts.logsort) {
vector<int> s;
for (const auto &lit : *c)
s.push_back (lit);
sort (s.begin (), s.end (), clause_lit_less_than ());
for (const auto &lit : s)
printf (" %d", lit);
} else {
for (const auto &lit : *c) {
printf (" %s", loglit (internal, lit).c_str ());
}
}
}
} else if (internal->level)
printf (" decision");
else
printf (" unit");
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
void Logger::log (Internal *internal, const Gate *g, const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (g) {
printf ("%s%s%s gate[%" PRIu64 "] (arity: %ld) %s := %s",
g->degenerated_and_pos ? " deg+" : "",
g->degenerated_and_neg ? " deg-" : "",
g->garbage ? " garbage" : "", g->id, g->arity (),
loglit (internal, g->lhs).c_str (),
string_of_gate (g->tag).c_str ());
for (const auto &lit : g->rhs) {
printf (" %s", loglit (internal, lit).c_str ());
}
} else
printf (" null gate");
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// Same as above, but for the global clause 'c' (which is not a reason).
void Logger::log (Internal *internal, const vector<int> &c, const char *fmt,
...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (internal->opts.logsort) {
vector<int> s;
for (const auto &lit : c)
s.push_back (lit);
sort (s.begin (), s.end (), clause_lit_less_than ());
for (const auto &lit : s)
printf (" %d", lit);
} else {
for (const auto &lit : c)
printf (" %d", lit);
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// Now for 'restore_clause' to avoid copying (without logging).
void Logger::log (Internal *internal,
const vector<int>::const_iterator &begin,
const vector<int>::const_iterator &end, const char *fmt,
...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
if (internal->opts.logsort) {
vector<int> s;
for (auto p = begin; p != end; p++)
s.push_back (*p);
sort (s.begin (), s.end (), clause_lit_less_than ());
for (const auto &lit : s)
printf (" %d", lit);
} else {
for (auto p = begin; p != end; p++)
printf (" %d", *p);
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// for LRAT proof chains
void Logger::log (Internal *internal, const vector<int64_t> &c,
const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
for (const auto &id : c)
printf (" %" PRId64, id);
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
// for LRAT proof clauses
void Logger::log (Internal *internal, const int *literals,
const unsigned size, const char *fmt, ...) {
print_log_prefix (internal);
tout.magenta ();
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
for (unsigned i = 0; i < size; i++) {
const int lit = literals[i];
printf (" %d", lit);
}
fputc ('\n', stdout);
tout.normal ();
fflush (stdout);
}
string Logger::loglit (Internal *internal, int lit) {
std::string v = std::to_string (lit);
if (lit && -internal->max_var <= lit && internal->max_var >= lit) {
const int va = internal->val (lit);
if (va) {
v = v + "@" + std::to_string (internal->var (lit).level);
if (!internal->var (lit).reason)
v = v + "+";
}
if (va > 0)
v += "=1";
else if (va < 0)
v += "=-1";
}
return v;
}
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,98 @@
#ifndef _logging_hpp_INCLUDED
#define _logging_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#ifdef LOGGING
/*------------------------------------------------------------------------*/
#include <cstdint>
#include <vector>
namespace CaDiCaL {
// For debugging purposes and to help understanding what the solver is doing
// there is a logging facility which is compiled in by './configure -l'. It
// still has to be enabled at run-time though (again using the '-l' option
// in the stand-alone solver). It produces quite a bit of information.
using namespace std;
struct Clause;
struct Gate;
struct Internal;
struct Logger {
static void print_log_prefix (Internal *);
// Simple logging of a C-style format string.
//
static void log (Internal *, const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (2, 3);
// Prints the format string (with its argument) and then the clause. The
// clause can also be a zero pointer and then is interpreted as a decision
// (current decision level > 0) or unit clause (zero decision level) and
// printed accordingly.
//
static void log (Internal *, const Clause *, const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (3, 4);
// Same as before, except that this is meant for the global 'clause' stack
// used for new clauses (and not for reasons).
//
static void log (Internal *, const vector<int> &, const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (3, 4);
// Another variant, to avoid copying (without logging).
//
static void log (Internal *, const vector<int>::const_iterator &begin,
const vector<int>::const_iterator &end, const char *fmt,
...) CADICAL_ATTRIBUTE_FORMAT (4, 5);
// used for logging LRAT proof chains
//
static void log (Internal *, const vector<int64_t> &, const char *fmt,
...) CADICAL_ATTRIBUTE_FORMAT (3, 4);
static void log (Internal *, const int *, const unsigned, const char *fmt,
...) CADICAL_ATTRIBUTE_FORMAT (4, 5);
static void log_empty_line (Internal *);
static void log (Internal *, const Gate *, const char *fmt, ...)
CADICAL_ATTRIBUTE_FORMAT (3, 4);
static string loglit (Internal *, int lit);
};
} // namespace CaDiCaL
/*------------------------------------------------------------------------*/
// Make sure that 'logging' code is really not included (second case of the
// '#ifdef') if logging code is not included.
#define LOG(...) \
do { \
if (!internal->opts.log) \
break; \
Logger::log (internal, __VA_ARGS__); \
} while (0)
#define LOGLIT(lit) Logger::loglit (internal, lit).c_str ()
/*------------------------------------------------------------------------*/
#else // end of 'then' part of 'ifdef LOGGING'
/*------------------------------------------------------------------------*/
#define LOG(...) \
do { \
} while (0)
#define LOGLIT(...)
/*------------------------------------------------------------------------*/
#endif // end of 'else' part of 'ifdef LOGGING'
/*------------------------------------------------------------------------*/
#endif

View File

@ -0,0 +1,520 @@
#include "internal.hpp"
namespace CaDiCaL {
struct literal_occ {
int lit;
int count;
bool operator< (const literal_occ &locc) const {
return (count > locc.count) || (count == locc.count && lit < locc.lit);
}
literal_occ operator++ () {
++count;
return *this;
}
};
std::vector<int> Internal::lookahead_populate_locc () {
std::vector<literal_occ> loccs ((std::size_t) max_var + 1);
for (std::size_t lit = 0; lit < loccs.size (); ++lit) {
loccs[lit].lit = lit;
}
for (const auto &c : clauses)
if (!c->redundant)
for (const auto &lit : *c)
if (active (lit))
++loccs[std::abs (lit)];
std::sort (begin (loccs), end (loccs));
std::vector<int> locc_map;
locc_map.reserve (max_var);
for (const auto &locc : loccs)
locc_map.push_back (locc.lit);
return locc_map;
}
int Internal::lookahead_locc (const std::vector<int> &loccs) {
for (auto lit : loccs)
if (active (abs (lit)) && !assumed (lit) && !assumed (-lit) &&
!val (lit))
return lit;
return 0;
}
// This calculates the literal that appears the most often reusing the
// available datastructures and iterating over the clause set. This is too
// slow to be called iteratively. A faster (but inexact) version is
// lookahead_populate_loc and lookahead_loc.
int Internal::most_occurring_literal () {
init_noccs ();
for (const auto &c : clauses)
if (!c->redundant)
for (const auto &lit : *c)
if (active (lit))
noccs (lit)++;
int64_t max_noccs = 0;
int res = 0;
if (unsat)
return INT_MIN;
propagate ();
for (int idx = 1; idx <= max_var; idx++) {
if (!active (idx) || assumed (idx) || assumed (-idx) || val (idx))
continue;
for (int sign = -1; sign <= 1; sign += 2) {
const int lit = sign * idx;
if (!active (lit))
continue;
int64_t tmp = noccs (lit);
if (tmp <= max_noccs)
continue;
max_noccs = tmp;
res = lit;
}
}
MSG ("maximum occurrence %" PRId64 " of literal %d", max_noccs, res);
reset_noccs ();
return res;
}
// We probe on literals first, which occur more often negated and thus we
// sort the 'probes' stack in such a way that literals which occur negated
// less frequently come first. Probes are taken from the back of the stack.
struct probe_negated_noccs_rank {
Internal *internal;
probe_negated_noccs_rank (Internal *i) : internal (i) {}
typedef size_t Type;
Type operator() (int a) const { return internal->noccs (-a); }
};
// Follow the ideas in 'generate_probes' but flush non root probes and
// reorder remaining probes.
void Internal::lookahead_flush_probes () {
assert (!probes.empty ());
init_noccs ();
for (const auto &c : clauses) {
int a, b;
if (!is_binary_clause (c, a, b))
continue;
noccs (a)++;
noccs (b)++;
}
const auto eop = probes.end ();
auto j = probes.begin ();
for (auto i = j; i != eop; i++) {
int lit = *i;
if (!active (lit))
continue;
const bool have_pos_bin_occs = noccs (lit) > 0;
const bool have_neg_bin_occs = noccs (-lit) > 0;
if (have_pos_bin_occs == have_neg_bin_occs)
continue;
if (have_pos_bin_occs)
lit = -lit;
assert (!noccs (lit)), assert (noccs (-lit) > 0);
if (propfixed (lit) >= stats.all.fixed)
continue;
MSG ("keeping probe %d negated occs %" PRId64 "", lit, noccs (-lit));
*j++ = lit;
}
size_t remain = j - probes.begin ();
#ifndef QUIET
size_t flushed = probes.size () - remain;
#endif
probes.resize (remain);
rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this));
reset_noccs ();
shrink_vector (probes);
PHASE ("probe-round", stats.probingrounds,
"flushed %zd literals %.0f%% remaining %zd", flushed,
percent (flushed, remain + flushed), remain);
}
void Internal::lookahead_generate_probes () {
assert (probes.empty ());
// First determine all the literals which occur in binary clauses. It is
// way faster to go over the clauses once, instead of walking the watch
// lists for each literal.
//
init_noccs ();
for (const auto &c : clauses) {
int a, b;
if (!is_binary_clause (c, a, b))
continue;
noccs (a)++;
noccs (b)++;
}
for (int idx = 1; idx <= max_var; idx++) {
// Then focus on roots of the binary implication graph, which are
// literals occurring negatively in a binary clause, but not positively.
// If neither 'idx' nor '-idx' is a root it makes less sense to probe
// this variable.
// This argument requires that equivalent literal substitution through
// 'decompose' is performed, because otherwise there might be 'cyclic
// roots' which are not tried, i.e., -1 2 0, 1 -2 0, 1 2 3 0, 1 2 -3 0.
const bool have_pos_bin_occs = noccs (idx) > 0;
const bool have_neg_bin_occs = noccs (-idx) > 0;
// if (have_pos_bin_occs == have_neg_bin_occs) continue;
if (have_pos_bin_occs) {
int probe = -idx;
// See the discussion where 'propfixed' is used below.
//
if (propfixed (probe) >= stats.all.fixed)
continue;
MSG ("scheduling probe %d negated occs %" PRId64 "", probe,
noccs (-probe));
probes.push_back (probe);
}
if (have_neg_bin_occs) {
int probe = idx;
// See the discussion where 'propfixed' is used below.
//
if (propfixed (probe) >= stats.all.fixed)
continue;
MSG ("scheduling probe %d negated occs %" PRId64 "", probe,
noccs (-probe));
probes.push_back (probe);
}
}
rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this));
reset_noccs ();
shrink_vector (probes);
PHASE ("probe-round", stats.probingrounds,
"scheduled %zd literals %.0f%%", probes.size (),
percent (probes.size (), 2 * max_var));
}
int Internal::lookahead_next_probe () {
int generated = 0;
for (;;) {
if (probes.empty ()) {
if (generated++)
return 0;
lookahead_generate_probes ();
}
while (!probes.empty ()) {
int probe = probes.back ();
probes.pop_back ();
// Eliminated or assigned.
//
if (!active (probe) || assumed (probe) || assumed (-probe))
continue;
// There is now new unit since the last time we propagated this probe,
// thus we propagated it before without obtaining a conflict and
// nothing changed since then. Thus there is no need to propagate it
// again. This observation was independently made by Partik Simons
// et.al. in the context of implementing 'smodels' (see for instance
// Alg. 4 in his JAIR article from 2002) and it has also been
// contributed to the thesis work of Yacine Boufkhad.
//
if (propfixed (probe) >= stats.all.fixed)
continue;
return probe;
}
}
}
bool non_tautological_cube (std::vector<int> cube) {
std::sort (begin (cube), end (cube), clause_lit_less_than ());
for (size_t i = 0, j = 1; j < cube.size (); ++i, ++j)
if (cube[i] == cube[j])
return false;
else if (cube[i] == -cube[j])
return false;
else if (cube[i] == 0)
return false;
return true;
}
bool Internal::terminating_asked () {
if (external->terminator && external->terminator->terminate ()) {
MSG ("connected terminator forces termination");
return true;
}
if (termination_forced) {
MSG ("termination forced");
return true;
}
return false;
}
// We run probing on all literals with some differences:
//
// * no limit on the number of propagations. We rely on terminating to
// stop()
// * we run only one round
//
// The run can be expensive, so we actually first run the cheaper
// occurrence version and only then run lookahead.
//
int Internal::lookahead_probing () {
if (!active ())
return 0;
MSG ("lookahead-probe-round %" PRId64
" without propagations limit and %zu assumptions",
stats.probingrounds, assumptions.size ());
termination_forced = false;
#ifndef QUIET
int old_failed = stats.failed;
int64_t old_probed = stats.probed;
#endif
int64_t old_hbrs = stats.hbrs;
if (unsat)
return INT_MIN;
if (level)
backtrack ();
if (!propagate ()) {
MSG ("empty clause before probing");
learn_empty_clause ();
return INT_MIN;
}
if (terminating_asked ())
return most_occurring_literal ();
decompose ();
if (ternary ()) // If we derived a binary clause
decompose (); // then start another round of ELS.
// Remove duplicated binary clauses and perform in essence hyper unary
// resolution, i.e., derive the unit '2' from '1 2' and '-1 2'.
//
mark_duplicated_binary_clauses_as_garbage ();
lim.conflicts = -1;
if (!probes.empty ())
lookahead_flush_probes ();
// We reset 'propfixed' since there was at least another conflict thus
// a new learned clause, which might produce new propagations (and hyper
// binary resolvents). During 'generate_probes' we keep the old value.
//
for (int idx = 1; idx <= max_var; idx++)
propfixed (idx) = propfixed (-idx) = -1;
assert (unsat || propagated == trail.size ());
propagated = propagated2 = trail.size ();
int probe;
int res = most_occurring_literal ();
int max_hbrs = -1;
set_mode (PROBE);
MSG ("unsat = %d, terminating_asked () = %d ", unsat,
terminating_asked ());
init_probehbr_lrat ();
while (!unsat && !terminating_asked () &&
(probe = lookahead_next_probe ())) {
stats.probed++;
int hbrs;
probe_assign_decision (probe);
if (probe_propagate ())
hbrs = trail.size (), backtrack ();
else
hbrs = 0, failed_literal (probe);
clean_probehbr_lrat ();
if (max_hbrs < hbrs ||
(max_hbrs == hbrs &&
internal->bumped (probe) > internal->bumped (res))) {
res = probe;
max_hbrs = hbrs;
}
}
reset_mode (PROBE);
if (unsat) {
MSG ("probing derived empty clause");
res = INT_MIN;
} else if (propagated < trail.size ()) {
MSG ("probing produced %zd units",
(size_t) (trail.size () - propagated));
if (!propagate ()) {
MSG ("propagating units after probing results in empty clause");
learn_empty_clause ();
res = INT_MIN;
} else
sort_watches ();
}
#ifndef QUIET
int failed = stats.failed - old_failed;
int64_t probed = stats.probed - old_probed;
#endif
int64_t hbrs = stats.hbrs - old_hbrs;
MSG ("lookahead-probe-round %" PRId64 " probed %" PRId64
" and found %d failed literals",
stats.probingrounds, probed, failed);
if (hbrs)
PHASE ("lookahead-probe-round", stats.probingrounds,
"found %" PRId64 " hyper binary resolvents", hbrs);
MSG ("lookahead literal %d with %d\n", res, max_hbrs);
return res;
}
CubesWithStatus Internal::generate_cubes (int depth, int min_depth) {
if (!active () || depth == 0) {
CubesWithStatus cubes;
cubes.status = 0;
cubes.cubes.push_back (std::vector<int> ());
return cubes;
}
lookingahead = true;
START (lookahead);
MSG ("Generating cubes of depth %i", depth);
// presimplify required due to assumptions
termination_forced = false;
int res = already_solved ();
if (res == 0)
res = restore_clauses ();
if (unsat)
res = 10;
if (res != 0)
res = solve (true);
if (res != 0) {
MSG ("Solved during preprocessing");
CubesWithStatus cubes;
cubes.status = res;
lookingahead = false;
STOP (lookahead);
return cubes;
}
reset_limits ();
MSG ("generate cubes with %zu assumptions\n", assumptions.size ());
assert (ntab.empty ());
std::vector<int> current_assumptions{assumptions};
std::vector<std::vector<int>> cubes{{assumptions}};
auto loccs{lookahead_populate_locc ()};
LOG ("loccs populated\n");
assert (ntab.empty ());
for (int i = 0; i < depth; ++i) {
LOG ("Probing at depth %i, currently %zu have been generated", i,
cubes.size ());
std::vector<std::vector<int>> cubes2{std::move (cubes)};
cubes.clear ();
for (size_t j = 0; j < cubes2.size (); ++j) {
assert (ntab.empty ());
assert (!unsat);
reset_assumptions ();
for (auto lit : cubes2[j])
assume (lit);
restore_clauses ();
propagate ();
// preprocess_round(0); //uncomment maybe
if (unsat) {
LOG ("current cube is unsat; skipping");
unsat = false;
continue;
}
int res = terminating_asked () ? lookahead_locc (loccs)
: lookahead_probing ();
if (unsat) {
LOG ("current cube is unsat; skipping");
unsat = false;
continue;
}
if (res == 0) {
LOG ("no lit to split %i", res);
cubes.push_back (cubes2[j]);
continue;
}
assert (res != 0);
LOG ("splitting on lit %i", res);
std::vector<int> cube1{cubes2[j]};
cube1.push_back (res);
std::vector<int> cube2{std::move (cubes2[j])};
cube2.push_back (-res);
cubes.push_back (cube1);
cubes.push_back (cube2);
}
if (terminating_asked () && i >= min_depth)
break;
}
assert (std::for_each (
std::begin (cubes), std::end (cubes),
[] (std::vector<int> cube) { return non_tautological_cube (cube); }));
reset_assumptions ();
for (auto lit : current_assumptions)
assume (lit);
STOP (lookahead);
lookingahead = false;
if (unsat) {
LOG ("Solved during preprocessing");
CubesWithStatus cubes;
cubes.status = 20;
return cubes;
}
CubesWithStatus rcubes;
rcubes.status = 0;
rcubes.cubes = cubes;
return rcubes;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,829 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
inline unsigned LratChecker::l2u (int lit) {
assert (lit);
assert (lit != INT_MIN);
unsigned res = 2 * (abs (lit) - 1);
if (lit < 0)
res++;
return res;
}
signed char &LratChecker::mark (int lit) {
const unsigned u = l2u (lit);
assert (u < marks.size ());
return marks[u];
}
signed char &LratChecker::checked_lit (int lit) {
const unsigned u = l2u (lit);
assert (u < checked_lits.size ());
return checked_lits[u];
}
/*------------------------------------------------------------------------*/
LratCheckerClause *LratChecker::new_clause () {
const size_t size = imported_clause.size ();
assert (size <= UINT_MAX);
const int off = size ? 1 : 0;
const size_t bytes =
sizeof (LratCheckerClause) + (size - off) * sizeof (int);
LratCheckerClause *res = (LratCheckerClause *) new char[bytes];
res->garbage = false;
res->next = 0;
res->hash = last_hash;
res->id = last_id;
res->size = size;
res->used = false;
res->tautological = false;
int *literals = res->literals, *p = literals;
#ifndef NDEBUG
for (auto &b : checked_lits)
assert (!b); // = false;
#endif
for (const auto &lit : imported_clause) {
*p++ = lit;
checked_lit (-lit) = true;
if (checked_lit (lit)) {
LOG (imported_clause, "LRAT CHECKER clause tautological");
res->tautological = true;
}
}
for (const auto &lit : imported_clause)
checked_lit (-lit) = false;
num_clauses++;
return res;
}
void LratChecker::delete_clause (LratCheckerClause *c) {
assert (c);
if (!c->garbage) {
assert (num_clauses);
num_clauses--;
} else {
assert (num_garbage);
num_garbage--;
}
delete[] (char *) c;
}
void LratChecker::enlarge_clauses () {
assert (num_clauses == size_clauses);
const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1;
LOG ("LRAT CHECKER enlarging clauses of checker from %" PRIu64
" to %" PRIu64,
(uint64_t) size_clauses, (uint64_t) new_size_clauses);
LratCheckerClause **new_clauses;
new_clauses = new LratCheckerClause *[new_size_clauses];
clear_n (new_clauses, new_size_clauses);
for (uint64_t i = 0; i < size_clauses; i++) {
for (LratCheckerClause *c = clauses[i], *next; c; c = next) {
next = c->next;
const uint64_t h = reduce_hash (c->hash, new_size_clauses);
c->next = new_clauses[h];
new_clauses[h] = c;
}
}
delete[] clauses;
clauses = new_clauses;
size_clauses = new_size_clauses;
}
// Probably not necessary since we have no watches.
//
void LratChecker::collect_garbage_clauses () {
stats.collections++;
LOG ("LRAT CHECKER collecting %" PRIu64 " garbage clauses %.0f%%",
num_garbage, percent (num_garbage, num_clauses));
for (LratCheckerClause *c = garbage, *next; c; c = next)
next = c->next, delete_clause (c);
assert (!num_garbage);
garbage = 0;
}
/*------------------------------------------------------------------------*/
LratChecker::LratChecker (Internal *i)
: internal (i), size_vars (0), concluded (false), num_clauses (0),
num_finalized (0), num_garbage (0), size_clauses (0), clauses (0),
garbage (0), last_hash (0), last_id (0), current_id (0) {
// Initialize random number table for hash function.
//
Random random (42);
for (unsigned n = 0; n < num_nonces; n++) {
uint64_t nonce = random.next ();
if (!(nonce & 1))
nonce++;
assert (nonce), assert (nonce & 1);
nonces[n] = nonce;
}
memset (&stats, 0, sizeof (stats)); // Initialize statistics.
}
void LratChecker::connect_internal (Internal *i) {
internal = i;
LOG ("connected to internal");
}
LratChecker::~LratChecker () {
LOG ("LRAT CHECKER delete");
for (size_t i = 0; i < size_clauses; i++)
for (LratCheckerClause *c = clauses[i], *next; c; c = next)
next = c->next, delete_clause (c);
for (LratCheckerClause *c = garbage, *next; c; c = next)
next = c->next, delete_clause (c);
delete[] clauses;
}
/*------------------------------------------------------------------------*/
void LratChecker::enlarge_vars (int64_t idx) {
assert (0 < idx), assert (idx <= INT_MAX);
int64_t new_size_vars = size_vars ? 2 * size_vars : 2;
while (idx >= new_size_vars)
new_size_vars *= 2;
LOG ("LRAT CHECKER enlarging variables of checker from %" PRId64
" to %" PRId64 "",
size_vars, new_size_vars);
marks.resize (2 * new_size_vars);
checked_lits.resize (2 * new_size_vars);
assert (idx < new_size_vars);
size_vars = new_size_vars;
}
inline void LratChecker::import_literal (int lit) {
assert (lit);
assert (lit != INT_MIN);
int idx = abs (lit);
if (idx >= size_vars)
enlarge_vars (idx);
imported_clause.push_back (lit);
}
void LratChecker::import_clause (const vector<int> &c) {
for (const auto &lit : c)
import_literal (lit);
}
/*------------------------------------------------------------------------*/
uint64_t LratChecker::reduce_hash (uint64_t hash, uint64_t size) {
assert (size > 0);
unsigned shift = 32;
uint64_t res = hash;
while ((((uint64_t) 1) << shift) > size) {
res ^= res >> shift;
shift >>= 1;
}
res &= size - 1;
assert (res < size);
return res;
}
uint64_t LratChecker::compute_hash (const int64_t id) {
assert (id > 0);
unsigned j = id % num_nonces;
uint64_t tmp = nonces[j] * (uint64_t) id;
return last_hash = tmp;
}
LratCheckerClause **LratChecker::find (const int64_t id) {
stats.searches++;
LratCheckerClause **res, *c;
const uint64_t hash = compute_hash (id);
const uint64_t h = reduce_hash (hash, size_clauses);
for (res = clauses + h; (c = *res); res = &c->next) {
if (c->hash == hash && c->id == id) {
break;
}
stats.collisions++;
}
return res;
}
void LratChecker::insert () {
stats.insertions++;
if (num_clauses == size_clauses)
enlarge_clauses ();
const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses);
LratCheckerClause *c = new_clause ();
c->next = clauses[h];
clauses[h] = c;
}
/*------------------------------------------------------------------------*/
// "strict" resolution check instead of rup check
bool LratChecker::check_resolution (vector<int64_t> proof_chain) {
if (proof_chain.empty ()) {
LOG ("LRAT CHECKER resolution check skipped clause is tautological");
return true;
}
// LOG (imported_clause, "LRAT CHECKER checking clause with resolution");
#ifndef NDEBUG
for (auto &b : checked_lits)
assert (!b); // = false;
#endif
if (!proof_chain.size () || proof_chain.back () < 0)
return false;
LratCheckerClause *c = *find (proof_chain.back ());
assert (c);
for (int *i = c->literals; i < c->literals + c->size; i++) {
int lit = *i;
checked_lit (lit) = true;
assert (!checked_lit (-lit));
}
for (auto p = proof_chain.end () - 2; p >= proof_chain.begin (); p--) {
auto &id = *p;
c = *find (id);
assert (c); // since this is checked in check already
for (int *i = c->literals; i < c->literals + c->size; i++) {
int lit = *i;
if (!checked_lit (-lit))
checked_lit (lit) = true;
else
checked_lit (-lit) = false;
}
}
for (const auto &lit : imported_clause) {
if (checked_lit (-lit)) {
LOG ("LRAT CHECKER resolution failed, resolved literal %d in learned "
"clause",
lit);
for (auto &b : checked_lits)
b = false; // clearing checking bits
return false;
}
if (!checked_lit (lit)) {
// learned clause is subsumed by resolvents
checked_lit (lit) = true;
}
checked_lit (-lit) = true;
}
bool failed = false;
for (int64_t lit = 1; lit < size_vars; lit++) {
bool ok = checked_lit (lit) && checked_lit (-lit);
ok = ok || (!checked_lit (lit) && !checked_lit (-lit));
checked_lit (lit) = checked_lit (-lit) = false;
if (!ok && !failed) {
LOG ("LRAT CHECKER resolution failed, learned clause does not match "
"on "
"variable %" PRId64,
lit);
failed = true;
}
}
return !failed;
}
/*------------------------------------------------------------------------*/
bool LratChecker::check (vector<int64_t> proof_chain) {
LOG (imported_clause, "LRAT CHECKER checking clause");
stats.checks++;
#ifndef NDEBUG
for (auto &b : checked_lits)
assert (!b); // = false;
#endif
bool taut = false;
for (const auto &lit : imported_clause) { // tautological clauses
checked_lit (-lit) = true;
if (checked_lit (lit)) {
LOG (imported_clause, "LRAT CHECKER clause tautological");
assert (!proof_chain.size ()); // would be unnecessary hence a bug
taut = true;
}
}
// we assume that we can have RUP and ER clauses. One side of the ER
// clauses are pure, i.e. without any chain, the long clause is blocked,
// so the chain consists only of negative ids. Therefore these checks are
// enough to distiguish between RUP and ER
if (taut || !proof_chain.size () || proof_chain.back () < 0) {
for (const auto &lit : imported_clause) { // tautological clauses
checked_lit (-lit) = false;
}
return taut;
}
vector<LratCheckerClause *> used_clauses;
bool checking = false;
for (auto &id : proof_chain) {
LratCheckerClause *c = *find (id);
if (!c) {
LOG ("LRAT CHECKER LRAT failed. Did not find clause with id %" PRIu64,
id);
break;
}
if (c->tautological) {
LOG ("LRAT CHECKER LRAT failed. Clause with id %" PRId64
" is tautological",
id);
break;
}
used_clauses.push_back (c);
if (c->used) {
LOG ("LRAT CHECKER LRAT failed. Id %" PRId64
" was used multiple times",
id);
break;
} else
c->used = true;
int unit = 0;
for (int *i = c->literals; i < c->literals + c->size; i++) {
int lit = *i;
if (checked_lit (-lit))
continue;
if (unit && unit != lit) {
unit = INT_MIN; // multiple unfalsified literals
break;
}
unit = lit; // potential unit
}
if (unit == INT_MIN) {
LOG ("LRAT CHECKER check failed, found non unit clause %" PRId64, id);
break;
}
if (!unit) {
LOG ("LRAT CHECKER check succeded, clause falsified %" PRId64, id);
checking = true;
break;
}
// LOG ("LRAT CHECKER found unit clause %" PRIu64 ", assign %d", id,
// unit);
checked_lit (unit) = true;
}
for (auto &lc : used_clauses) {
lc->used = false;
}
for (auto &b : checked_lits)
b = false;
if (!checking) {
LOG ("LRAT CHECKER failed, no conflict found");
return false; // check failed because no empty clause was found
}
return true;
}
bool LratChecker::check_blocked (vector<int64_t> proof_chain) {
for (const auto &lit : imported_clause) {
checked_lit (-lit) = true;
mark (-lit) = true;
}
for (size_t i = 0; i < size_clauses; i++) {
for (LratCheckerClause *c = clauses[i], *next; c; c = next) {
next = c->next;
if (c->garbage)
continue;
// if c is part of the proof chain its id occurs negatively there.
if (std::find (proof_chain.begin (), proof_chain.end (), -c->id) !=
proof_chain.end ()) {
// clause needs to be blocked
unsigned count = 0;
vector<int> candidates;
for (unsigned i = 0; i < c->size; i++) {
const int lit = c->literals[i];
if (checked_lit (lit)) {
count++;
}
if (mark (lit)) {
candidates.push_back (lit);
}
}
if (count < 2) {
// check failed
for (const auto &lit : imported_clause) {
checked_lit (-lit) = false;
mark (-lit) = false;
}
return false;
} else {
// all literals outside of candidates are not valid RAT candidates
for (auto &lit : imported_clause) {
if (mark (-lit) &&
std::find (candidates.begin (), candidates.end (), -lit) ==
candidates.end ()) {
mark (-lit) = false;
}
}
}
} else {
// any literal contained in the clause is not a valid RAT candidate
for (unsigned i = 0; i < c->size; i++) {
const int lit = c->literals[i];
if (checked_lit (lit)) {
mark (lit) = false;
}
}
}
}
}
bool success = false;
for (const auto &lit : imported_clause) {
if (mark (-lit))
success = true;
checked_lit (-lit) = mark (-lit) = false;
}
return success;
}
/*------------------------------------------------------------------------*/
void LratChecker::add_original_clause (int64_t id, bool,
const vector<int> &c, bool restore) {
START (checking);
LOG (c, "LRAT CHECKER addition of original clause[%" PRId64 "]", id);
if (restore)
restore_clause (id, c);
stats.added++;
stats.original++;
import_clause (c);
last_id = id;
if (!restore && id == 1 + current_id)
current_id = id;
if (size_clauses && !restore) {
LratCheckerClause **p = find (id), *d = *p;
if (d) {
fatal_message_start ();
fputs ("different clause with id ", stderr);
fprintf (stderr, "%" PRId64, id);
fputs (" already present\n", stderr);
fatal_message_end ();
}
}
assert (id);
insert ();
imported_clause.clear ();
STOP (checking);
}
void LratChecker::add_derived_clause (int64_t id, bool,
const vector<int> &c,
const vector<int64_t> &proof_chain) {
START (checking);
LOG (c, "LRAT CHECKER addition of derived clause[%" PRId64 "]", id);
stats.added++;
stats.derived++;
import_clause (c);
last_id = id;
assert (id == current_id + 1);
current_id = id;
if (size_clauses) {
LratCheckerClause **p = find (id), *d = *p;
if (d) {
fatal_message_start ();
fputs ("different clause with id ", stderr);
fprintf (stderr, "%" PRId64, id);
fputs (" already present\n", stderr);
fatal_message_end ();
}
}
assert (id);
bool failed = true;
if (check (proof_chain) && check_resolution (proof_chain)) {
failed = false;
} else if (check_blocked (proof_chain)) {
failed = false;
}
if (failed) {
LOG (proof_chain, "LRAT CHECKER check failed with chain");
#ifdef LOGGING
for (const auto &pid : proof_chain) {
const int64_t aid = abs (pid);
LratCheckerClause **p = find (aid), *d = *p;
LOG (d->literals, d->size, "clause[%" PRId64 "]", pid);
}
#endif
fatal_message_start ();
fputs ("failed to check derived clause:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
} else
insert ();
imported_clause.clear ();
STOP (checking);
}
void LratChecker::add_assumption_clause (int64_t id, const vector<int> &c,
const vector<int64_t> &chain) {
for (auto &lit : c) {
if (std::find (assumptions.begin (), assumptions.end (), -lit) !=
assumptions.end ())
continue;
if (std::find (constraint.begin (), constraint.end (), -lit) !=
constraint.end ())
continue;
fatal_message_start ();
fputs ("clause contains non assumptions or constraint literals\n",
stderr);
fatal_message_end ();
}
add_derived_clause (id, true, c, chain);
delete_clause (id, true, c);
assumption_clauses.push_back (id);
}
void LratChecker::add_assumption (int a) { assumptions.push_back (a); }
void LratChecker::add_constraint (const vector<int> &c) {
constraint.clear ();
for (auto &lit : c) {
assert (lit);
if (std::find (constraint.begin (), constraint.end (), lit) !=
constraint.end ())
continue;
constraint.push_back (lit);
}
}
void LratChecker::reset_assumptions () {
assumption_clauses.clear ();
assumptions.clear ();
concluded = false;
// constraint.clear ();
}
void LratChecker::conclude_unsat (ConclusionType conclusion,
const vector<int64_t> &ids) {
if (concluded) {
fatal_message_start ();
fputs ("already concluded\n", stderr);
fatal_message_end ();
}
concluded = true;
if (conclusion == CONFLICT) {
LratCheckerClause **p = find (ids.back ()), *d = *p;
if (!d || d->size) {
fatal_message_start ();
fputs ("empty clause not in proof\n", stderr);
fatal_message_end ();
}
return;
} else if (conclusion == ASSUMPTIONS) {
if (ids.size () != 1 || assumption_clauses.size () != 1) {
fatal_message_start ();
fputs ("expected exactly one assumption clause\n", stderr);
fatal_message_end ();
}
if (ids.back () != assumption_clauses.back ()) {
fatal_message_start ();
fputs ("conclusion is not an assumption clause\n", stderr);
fatal_message_end ();
}
return;
} else {
assert (conclusion == CONSTRAINT);
if (constraint.size () != ids.size ()) {
fatal_message_start ();
fputs ("not complete conclusion given for constraint\n", stderr);
fputs ("The constraint contains the literals: ", stderr);
for (auto c : constraint) {
fprintf (stderr, "%d ", c);
}
fputs ("\nThe ids are: ", stderr);
for (auto c : ids) {
fprintf (stderr, "%" PRId64 " ", c);
}
fatal_message_end ();
}
for (auto &id : ids) {
if (std::find (assumption_clauses.begin (), assumption_clauses.end (),
id) != assumption_clauses.end ())
continue;
fatal_message_start ();
fputs ("assumption clause for constraint missing\n", stderr);
fatal_message_end ();
}
}
}
/*------------------------------------------------------------------------*/
void LratChecker::delete_clause (int64_t id, bool, const vector<int> &c) {
START (checking);
LOG (c, "LRAT CHECKER checking deletion of clause[%" PRId64 "]", id);
stats.deleted++;
import_clause (c);
last_id = id;
LratCheckerClause **p = find (id), *d = *p;
if (d) {
for (const auto &lit : imported_clause)
mark (lit) = true;
const int *dp = d->literals;
for (unsigned i = 0; i < d->size; i++) {
int lit = *(dp + i);
if (!mark (lit)) { // should never happen since ids
fatal_message_start (); // are unique.
fputs ("deleted clause not in proof:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
}
for (const auto &lit : imported_clause)
mark (lit) = false;
// Remove from hash table, mark as garbage, connect to garbage list.
num_garbage++;
assert (num_clauses);
num_clauses--;
*p = d->next;
d->next = garbage;
garbage = d;
d->garbage = true;
// If there are enough garbage clauses collect them.
// TODO: probably can just delete clause directly without
// specific garbage collection phase.
if (num_garbage > 0.5 * max ((size_t) size_clauses, (size_t) size_vars))
collect_garbage_clauses ();
} else {
fatal_message_start ();
fputs ("deleted clause not in proof:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
imported_clause.clear ();
STOP (checking);
}
/*------------------------------------------------------------------------*/
void LratChecker::weaken_minus (int64_t id, const vector<int> &c) {
LOG (c, "LRAT CHECKER saving clause[%" PRId64 "] to restore later", id);
import_clause (c);
assert (id <= current_id);
last_id = id;
LratCheckerClause **p = find (id), *d = *p;
if (d) {
for (const auto &lit : imported_clause)
mark (lit) = true;
const int *dp = d->literals;
for (unsigned i = 0; i < d->size; i++) {
int lit = *(dp + i);
if (!mark (lit)) { // should never happen since ids
fatal_message_start (); // are unique.
fputs ("deleted clause not in proof:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
}
for (const auto &lit : imported_clause)
mark (lit) = false;
} else {
fatal_message_start ();
fputs ("weakened clause not in proof:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
imported_clause.clear ();
vector<int> e = c;
sort (begin (e), end (e));
clauses_to_reconstruct[id] = e;
}
void LratChecker::restore_clause (int64_t id, const vector<int> &c) {
LOG (c, "LRAT CHECKER check of restoration of clause[%" PRId64 "]", id);
if (clauses_to_reconstruct.find (id) == end (clauses_to_reconstruct)) {
fatal_message_start ();
fputs ("restoring clauses not deleted previously:\n", stderr);
for (const auto &lit : c)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
vector<int> e = c;
sort (begin (e), end (e));
const vector<int> &d = clauses_to_reconstruct.find (id)->second;
bool eq = true;
if (c.size () != d.size ()) {
eq = false;
}
for (std::vector<int>::size_type i = 0; i < e.size () && eq; ++i) {
eq = (e[i] == d[i]);
}
if (!eq) {
fatal_message_start ();
fputs ("restoring clause that is different than the one imported:\n",
stderr);
for (const auto &lit : c)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fputs ("vs:\n", stderr);
for (const auto &lit : d)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
clauses_to_reconstruct.erase (id);
}
void LratChecker::finalize_clause (int64_t id, const vector<int> &c) {
START (checking);
LOG (c, "LRAT CHECKER checking finalize of clause[%" PRId64 "]", id);
stats.finalized++;
num_finalized++;
import_clause (c);
assert (id <= current_id);
last_id = id;
LratCheckerClause **p = find (id), *d = *p;
if (d) {
for (const auto &lit : imported_clause)
mark (lit) = true;
const int *dp = d->literals;
for (unsigned i = 0; i < d->size; i++) {
int lit = *(dp + i);
if (!mark (lit)) { // should never happen since ids
fatal_message_start (); // are unique.
fputs ("deleted clause not in proof:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
}
for (const auto &lit : imported_clause)
mark (lit) = false;
} else {
fatal_message_start ();
fputs ("deleted clause not in proof:\n", stderr);
for (const auto &lit : imported_clause)
fprintf (stderr, "%d ", lit);
fputc ('0', stderr);
fatal_message_end ();
}
imported_clause.clear ();
STOP (checking);
}
// check if all clauses have been deleted
void LratChecker::report_status (int, int64_t) {
START (checking);
if (num_finalized == num_clauses) {
num_finalized = 0;
LOG ("LRAT CHECKER successful finalize check, all clauses have been "
"deleted");
} else {
fatal_message_start ();
fputs ("finalize check failed ", stderr);
fprintf (stderr, "%" PRIu64, num_clauses);
fputs (" are not finalized", stderr);
fatal_message_end ();
}
STOP (checking);
}
/*------------------------------------------------------------------------*/
void LratChecker::dump () {
int max_var = 0;
for (uint64_t i = 0; i < size_clauses; i++)
for (LratCheckerClause *c = clauses[i]; c; c = c->next)
for (unsigned i = 0; i < c->size; i++)
if (abs (c->literals[i]) > max_var)
max_var = abs (c->literals[i]);
printf ("p cnf %d %" PRIu64 "\n", max_var, num_clauses);
for (uint64_t i = 0; i < size_clauses; i++)
for (LratCheckerClause *c = clauses[i]; c; c = c->next) {
for (unsigned i = 0; i < c->size; i++)
printf ("%d ", c->literals[i]);
printf ("0\n");
}
}
void LratChecker::begin_proof (int64_t id) { current_id = id; }
} // namespace CaDiCaL

View File

@ -0,0 +1,164 @@
#ifndef _lratchecker_hpp_INCLUDED
#define _lratchecker_hpp_INCLUDED
/*------------------------------------------------------------------------*/
#include <unordered_map>
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// This checker implements an LRUP checker.
// It requires LRAT-style proof chains for each learned clause
//
// Most of the infrastructure is taken from checker, but without the
// propagation
/*------------------------------------------------------------------------*/
struct LratCheckerClause {
LratCheckerClause *next; // collision chain link for hash table
uint64_t hash; // previously computed full 64-bit hash
int64_t id; // id of clause
bool garbage; // for garbage clauses
unsigned size;
bool used;
bool tautological;
int literals[1]; // 'literals' of length 'size'
};
/*------------------------------------------------------------------------*/
class LratChecker : public StatTracer {
Internal *internal;
// Capacity of variable values.
//
int64_t size_vars;
// The 'watchers' and 'marks' data structures are not that time critical
// and thus we access them by first mapping a literal to 'unsigned'.
//
static unsigned l2u (int lit);
signed char &checked_lit (int lit);
signed char &mark (int lit);
vector<signed char> checked_lits;
vector<signed char> marks; // mark bits of literals
unordered_map<int64_t, vector<int>> clauses_to_reconstruct;
vector<int> assumptions;
vector<int> constraint;
bool concluded;
uint64_t num_clauses; // number of clauses in hash table
uint64_t num_finalized;
uint64_t num_garbage; // number of garbage clauses
uint64_t size_clauses; // size of clause hash table
LratCheckerClause **clauses; // hash table of clauses
LratCheckerClause *garbage; // linked list of garbage clauses
vector<int> imported_clause; // original clause for reporting
vector<int64_t> assumption_clauses;
void enlarge_vars (int64_t idx);
void import_literal (int lit);
void import_clause (const vector<int> &);
static const unsigned num_nonces = 4;
uint64_t nonces[num_nonces]; // random numbers for hashing
uint64_t last_hash; // last computed hash value of clause
int64_t last_id; // id of the last added/deleted clause
int64_t current_id; // id of the last added clause
uint64_t compute_hash (int64_t); // compute and save hash value of clause
// Reduce hash value to the actual size.
//
static uint64_t reduce_hash (uint64_t hash, uint64_t size);
void enlarge_clauses (); // enlarge hash table for clauses
void insert (); // insert clause in hash table
LratCheckerClause **
find (const int64_t); // find clause position in hash table
void add_clause (const char *type);
void collect_garbage_clauses ();
LratCheckerClause *new_clause ();
void delete_clause (LratCheckerClause *);
bool check (vector<int64_t>); // check RUP
bool check_resolution (vector<int64_t>); // check resolution
bool check_blocked (vector<int64_t>); // check ER
struct {
int64_t added; // number of added clauses
int64_t original; // number of added original clauses
int64_t derived; // number of added derived clauses
int64_t deleted; // number of deleted clauses
int64_t finalized; // number of finalized clauses
int64_t insertions; // number of clauses added to hash table
int64_t collisions; // number of hash collisions in 'find'
int64_t searches; // number of searched clauses in 'find'
int64_t checks; // number of implication checks
int64_t collections; // garbage collections
} stats;
public:
LratChecker (Internal *);
virtual ~LratChecker ();
void connect_internal (Internal *i) override;
void begin_proof (int64_t) override;
void add_original_clause (int64_t, bool, const vector<int> &,
bool restore) override;
void restore_clause (int64_t, const vector<int> &);
// check the proof chain for the new clause and add it to the checker
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
// check if the clause is present and delete it from the checker
void delete_clause (int64_t, bool, const vector<int> &) override;
// check if the clause is present and delete it from the checker
void weaken_minus (int64_t, const vector<int> &) override;
// check if the clause is present and delete it from the checker
void finalize_clause (int64_t, const vector<int> &) override;
// check the proof chain of the assumption clause and delete it
// immediately also check that they contain only assumptions and
// constraints
void add_assumption_clause (int64_t, const vector<int> &,
const vector<int64_t> &) override;
// mark lit as assumption
void add_assumption (int) override;
// mark lits as constraint
void add_constraint (const vector<int> &) override;
void reset_assumptions () override;
// check if all clauses have been deleted
void report_status (int, int64_t) override;
void conclude_unsat (ConclusionType, const vector<int64_t> &) override;
void print_stats () override;
void dump (); // for debugging purposes only
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,200 @@
#include "internal.hpp"
#include <limits.h>
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
LratTracer::LratTracer (Internal *i, File *f, bool b)
: internal (i), file (f), binary (b)
#ifndef QUIET
,
added (0), deleted (0)
#endif
,
latest_id (0) {
(void) internal;
}
void LratTracer::connect_internal (Internal *i) {
internal = i;
file->connect_internal (internal);
LOG ("LRAT TRACER connected to internal");
}
LratTracer::~LratTracer () {
LOG ("LRAT TRACER delete");
delete file;
}
/*------------------------------------------------------------------------*/
inline void LratTracer::put_binary_zero () {
assert (binary);
assert (file);
file->put ((unsigned char) 0);
}
inline void LratTracer::put_binary_lit (int lit) {
assert (binary);
assert (file);
assert (lit != INT_MIN);
unsigned idx = abs (lit);
assert (idx < (1u << 31));
unsigned x = 2 * idx + (lit < 0);
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
inline void LratTracer::put_binary_id (int64_t id) {
assert (binary);
assert (file);
uint64_t x = abs (id);
x = 2 * x + (id < 0);
unsigned char ch;
while (x & ~0x7f) {
ch = (x & 0x7f) | 0x80;
file->put (ch);
x >>= 7;
}
ch = x;
file->put (ch);
}
/*------------------------------------------------------------------------*/
void LratTracer::lrat_add_clause (int64_t id, const vector<int> &clause,
const vector<int64_t> &chain) {
if (delete_ids.size ()) {
if (!binary)
file->put (latest_id), file->put (" ");
if (binary)
file->put ('d');
else
file->put ("d ");
for (auto &did : delete_ids) {
if (binary)
put_binary_id (did);
else
file->put (did), file->put (" ");
}
if (binary)
put_binary_zero ();
else
file->put ("0\n");
delete_ids.clear ();
}
latest_id = id;
if (binary)
file->put ('a'), put_binary_id (id);
else
file->put (id), file->put (" ");
for (const auto &external_lit : clause)
if (binary)
put_binary_lit (external_lit);
else
file->put (external_lit), file->put (' ');
if (binary)
put_binary_zero ();
else
file->put ("0 ");
for (const auto &c : chain)
if (binary)
put_binary_id (c);
else
file->put (c), file->put (' '); // in proof chain, so they get
if (binary)
put_binary_zero (); // since cadical has no rat-steps
else
file->put ("0\n"); // this is just 2c here
}
void LratTracer::lrat_delete_clause (int64_t id) {
delete_ids.push_back (id); // pushing off deletion for later
}
/*------------------------------------------------------------------------*/
void LratTracer::add_derived_clause (int64_t id, bool,
const vector<int> &clause,
const vector<int64_t> &chain) {
if (file->closed ())
return;
LOG ("LRAT TRACER tracing addition of derived clause");
lrat_add_clause (id, clause, chain);
#ifndef QUIET
added++;
#endif
}
void LratTracer::delete_clause (int64_t id, bool, const vector<int> &) {
if (file->closed ())
return;
LOG ("LRAT TRACER tracing deletion of clause");
lrat_delete_clause (id);
#ifndef QUIET
deleted++;
#endif
}
void LratTracer::begin_proof (int64_t id) {
if (file->closed ())
return;
LOG ("LRAT TRACER tracing begin of proof");
latest_id = id;
}
/*------------------------------------------------------------------------*/
bool LratTracer::closed () { return file->closed (); }
#ifndef QUIET
void LratTracer::print_statistics () {
uint64_t bytes = file->bytes ();
uint64_t total = added + deleted;
MSG ("LRAT %" PRId64 " added clauses %.2f%%", added,
percent (added, total));
MSG ("LRAT %" PRId64 " deleted clauses %.2f%%", deleted,
percent (deleted, total));
MSG ("LRAT %" PRId64 " bytes (%.2f MB)", bytes,
bytes / (double) (1 << 20));
}
#endif
void LratTracer::close (bool print) {
assert (!closed ());
file->close ();
#ifndef QUIET
if (print) {
MSG ("LRAT proof file '%s' closed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
void LratTracer::flush (bool print) {
assert (!closed ());
file->flush ();
#ifndef QUIET
if (print) {
MSG ("LRAT proof file '%s' flushed", file->name ());
print_statistics ();
}
#else
(void) print;
#endif
}
} // namespace CaDiCaL

View File

@ -0,0 +1,57 @@
#ifndef _lrattracer_h_INCLUDED
#define _lrattracer_h_INCLUDED
namespace CaDiCaL {
class LratTracer : public FileTracer {
Internal *internal;
File *file;
bool binary;
#ifndef QUIET
int64_t added, deleted;
#endif
int64_t latest_id;
vector<int64_t> delete_ids;
void put_binary_zero ();
void put_binary_lit (int external_lit);
void put_binary_id (int64_t id);
// support LRAT
void lrat_add_clause (int64_t, const vector<int> &,
const vector<int64_t> &);
void lrat_delete_clause (int64_t);
public:
// own and delete 'file'
LratTracer (Internal *, File *file, bool binary);
~LratTracer ();
void connect_internal (Internal *i) override;
void begin_proof (int64_t) override;
void add_original_clause (int64_t, bool, const vector<int> &,
bool = false) override {} // skip
void add_derived_clause (int64_t, bool, const vector<int> &,
const vector<int64_t> &) override;
void delete_clause (int64_t, bool, const vector<int> &) override;
void finalize_clause (int64_t, const vector<int> &) override {} // skip
void report_status (int, int64_t) override {} // skip
#ifndef QUIET
void print_statistics ();
#endif
bool closed () override;
void close (bool) override;
void flush (bool) override;
};
} // namespace CaDiCaL
#endif

434
src/sat/cadical/lucky.cpp Normal file
View File

@ -0,0 +1,434 @@
#include "internal.hpp"
namespace CaDiCaL {
// It turns out that even in the competition there are formulas which are
// easy to satisfy by either setting all variables to the same truth value
// or by assigning variables to the same value and propagating it. In the
// latter situation this can be done either in the order of all variables
// (forward or backward) or in the order of all clauses. These lucky
// assignments can be tested initially in a kind of pre-solving step.
// This function factors out clean up code common among the 'lucky'
// functions for backtracking and resetting a potential conflict. One could
// also use exceptions here, but there are two different reasons for
// aborting early. The first kind of aborting is due to asynchronous
// termination and the second kind due to a situation in which it is clear
// that a particular function will not be successful (for instance a
// completely negative clause is found). The latter situation returns zero
// and will just abort the particular lucky function, while the former will
// abort all (by returning '-1').
int Internal::unlucky (int res) {
if (level > 0)
backtrack ();
if (conflict)
conflict = 0;
return res;
}
int Internal::trivially_false_satisfiable () {
LOG ("checking that all clauses contain a negative literal");
assert (!level);
assert (assumptions.empty ());
for (const auto &c : clauses) {
if (terminated_asynchronously (100))
return unlucky (-1);
if (c->garbage)
continue;
if (c->redundant)
continue;
bool satisfied = false, found_negative_literal = false;
for (const auto &lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) {
satisfied = true;
break;
}
if (tmp < 0)
continue;
if (lit > 0)
continue;
found_negative_literal = true;
break;
}
if (satisfied || found_negative_literal)
continue;
LOG (c, "found purely positively");
return unlucky (0);
}
VERBOSE (1, "all clauses contain a negative literal");
for (auto idx : vars) {
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
search_assume_decision (-idx);
if (propagate ())
continue;
assert (level > 0);
LOG ("propagation failed including redundant clauses");
return unlucky (0);
}
stats.lucky.constant.zero++;
return 10;
}
int Internal::trivially_true_satisfiable () {
LOG ("checking that all clauses contain a positive literal");
assert (!level);
assert (assumptions.empty ());
for (const auto &c : clauses) {
if (terminated_asynchronously (100))
return unlucky (-1);
if (c->garbage)
continue;
if (c->redundant)
continue;
bool satisfied = false, found_positive_literal = false;
for (const auto &lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) {
satisfied = true;
break;
}
if (tmp < 0)
continue;
if (lit < 0)
continue;
found_positive_literal = true;
break;
}
if (satisfied || found_positive_literal)
continue;
LOG (c, "found purely negatively");
return unlucky (0);
}
VERBOSE (1, "all clauses contain a positive literal");
for (auto idx : vars) {
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
search_assume_decision (idx);
if (propagate ())
continue;
assert (level > 0);
LOG ("propagation failed including redundant clauses");
return unlucky (0);
}
stats.lucky.constant.one++;
return 10;
}
/*------------------------------------------------------------------------*/
inline bool Internal::lucky_propagate_discrepency (int dec) {
search_assume_decision (dec);
bool no_conflict = propagate ();
if (no_conflict)
return false;
if (level > 1) {
backtrack (level - 1);
search_assume_decision (-dec);
no_conflict = propagate ();
if (no_conflict)
return false;
return true;
} else {
analyze ();
assert (!level);
no_conflict = propagate ();
if (!no_conflict) {
analyze ();
LOG ("lucky inconsistency backward assigning to true");
return true;
}
}
return false;
}
int Internal::forward_false_satisfiable () {
LOG ("checking increasing variable index false assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (auto idx : vars) {
START:
if (terminated_asynchronously (100))
return unlucky (-1);
if (val (idx))
continue;
if (lucky_propagate_discrepency (-idx)) {
if (unsat)
return 20;
else
return unlucky (0);
} else
goto START;
}
VERBOSE (1, "forward assuming variables false satisfies formula");
assert (satisfied ());
stats.lucky.forward.zero++;
return 10;
}
int Internal::forward_true_satisfiable () {
LOG ("checking increasing variable index true assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (auto idx : vars) {
START:
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
if (lucky_propagate_discrepency (idx)) {
if (unsat)
return 20;
else
return unlucky (0);
} else
goto START;
}
VERBOSE (1, "forward assuming variables true satisfies formula");
assert (satisfied ());
stats.lucky.forward.one++;
return 10;
}
/*------------------------------------------------------------------------*/
int Internal::backward_false_satisfiable () {
LOG ("checking decreasing variable index false assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (int idx = max_var; idx > 0; idx--) {
START:
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
if (lucky_propagate_discrepency (-idx)) {
if (unsat)
return 20;
else
return unlucky (0);
} else
goto START;
}
VERBOSE (1, "backward assuming variables false satisfies formula");
assert (satisfied ());
stats.lucky.backward.zero++;
return 10;
}
int Internal::backward_true_satisfiable () {
LOG ("checking decreasing variable index true assignment");
assert (!unsat);
assert (!level);
assert (assumptions.empty ());
for (int idx = max_var; idx > 0; idx--) {
START:
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
if (lucky_propagate_discrepency (idx)) {
if (unsat)
return 20;
else
return unlucky (0);
} else
goto START;
}
VERBOSE (1, "backward assuming variables true satisfies formula");
assert (satisfied ());
stats.lucky.backward.one++;
return 10;
}
/*------------------------------------------------------------------------*/
// The following two functions test if the formula is a satisfiable horn
// formula. Actually the test is slightly more general. It goes over all
// clauses and assigns the first positive literal to true and propagates.
// Already satisfied clauses are of course skipped. A reverse function
// is not implemented yet.
int Internal::positive_horn_satisfiable () {
LOG ("checking that all clauses are positive horn satisfiable");
assert (!level);
assert (assumptions.empty ());
for (const auto &c : clauses) {
if (terminated_asynchronously (10))
return unlucky (-1);
if (c->garbage)
continue;
if (c->redundant)
continue;
int positive_literal = 0;
bool satisfied = false;
for (const auto &lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) {
satisfied = true;
break;
}
if (tmp < 0)
continue;
if (lit < 0)
continue;
positive_literal = lit;
break;
}
if (satisfied)
continue;
if (!positive_literal) {
LOG (c, "no positive unassigned literal in");
return unlucky (0);
}
assert (positive_literal > 0);
LOG (c, "found positive literal %d in", positive_literal);
search_assume_decision (positive_literal);
if (propagate ())
continue;
LOG ("propagation of positive literal %d leads to conflict",
positive_literal);
return unlucky (0);
}
for (auto idx : vars) {
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
search_assume_decision (-idx);
if (propagate ())
continue;
LOG ("propagation of remaining literal %d leads to conflict", -idx);
return unlucky (0);
}
VERBOSE (1, "clauses are positive horn satisfied");
assert (!conflict);
assert (satisfied ());
stats.lucky.horn.positive++;
return 10;
}
int Internal::negative_horn_satisfiable () {
LOG ("checking that all clauses are negative horn satisfiable");
assert (!level);
assert (assumptions.empty ());
for (const auto &c : clauses) {
if (terminated_asynchronously (10))
return unlucky (-1);
if (c->garbage)
continue;
if (c->redundant)
continue;
int negative_literal = 0;
bool satisfied = false;
for (const auto &lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0) {
satisfied = true;
break;
}
if (tmp < 0)
continue;
if (lit > 0)
continue;
negative_literal = lit;
break;
}
if (satisfied)
continue;
if (!negative_literal) {
if (level > 0)
backtrack ();
LOG (c, "no negative unassigned literal in");
return unlucky (0);
}
assert (negative_literal < 0);
LOG (c, "found negative literal %d in", negative_literal);
search_assume_decision (negative_literal);
if (propagate ())
continue;
LOG ("propagation of negative literal %d leads to conflict",
negative_literal);
return unlucky (0);
}
for (auto idx : vars) {
if (terminated_asynchronously (10))
return unlucky (-1);
if (val (idx))
continue;
search_assume_decision (idx);
if (propagate ())
continue;
LOG ("propagation of remaining literal %d leads to conflict", idx);
return unlucky (0);
}
VERBOSE (1, "clauses are negative horn satisfied");
assert (!conflict);
assert (satisfied ());
stats.lucky.horn.negative++;
return 10;
}
/*------------------------------------------------------------------------*/
int Internal::lucky_phases () {
assert (!level);
require_mode (SEARCH);
if (!opts.lucky)
return 0;
// TODO: Some of the lucky assignments can also be found if there are
// assumptions, but this is not completely implemented nor tested yet.
// Nothing done for constraint either.
// External propagator assumes a CDCL loop, so lucky is not tried here.
if (!assumptions.empty () || !constraint.empty () || external_prop)
return 0;
START (search);
START (lucky);
assert (!searching_lucky_phases);
searching_lucky_phases = true;
stats.lucky.tried++;
const int64_t active_before = stats.active;
int res = trivially_false_satisfiable ();
if (!res)
res = trivially_true_satisfiable ();
if (!res)
res = forward_true_satisfiable ();
if (!res)
res = forward_false_satisfiable ();
if (!res)
res = backward_false_satisfiable ();
if (!res)
res = backward_true_satisfiable ();
if (!res)
res = positive_horn_satisfiable ();
if (!res)
res = negative_horn_satisfiable ();
if (res < 0)
assert (termination_forced), res = 0;
if (res == 10)
stats.lucky.succeeded++;
report ('l', !res);
assert (searching_lucky_phases);
const int64_t units = active_before - stats.active;
if (!res && units)
LOG ("lucky %zd units", units);
searching_lucky_phases = false;
STOP (lucky);
STOP (search);
return res;
}
} // namespace CaDiCaL

212
src/sat/cadical/message.cpp Normal file
View File

@ -0,0 +1,212 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
#ifndef QUIET
/*------------------------------------------------------------------------*/
void Internal::print_prefix () { fputs (prefix.c_str (), stdout); }
void Internal::vmessage (const char *fmt, va_list &ap) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet)
return;
print_prefix ();
vprintf (fmt, ap);
fputc ('\n', stdout);
fflush (stdout);
}
void Internal::message (const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
vmessage (fmt, ap);
va_end (ap);
}
void Internal::message () {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet)
return;
print_prefix ();
fputc ('\n', stdout);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
void Internal::vverbose (int level, const char *fmt, va_list &ap) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet || level > opts.verbose)
return;
print_prefix ();
vprintf (fmt, ap);
fputc ('\n', stdout);
fflush (stdout);
}
void Internal::verbose (int level, const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
vverbose (level, fmt, ap);
va_end (ap);
}
void Internal::verbose (int level) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet || level > opts.verbose)
return;
print_prefix ();
fputc ('\n', stdout);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
void Internal::section (const char *title) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet)
return;
if (stats.sections++)
MSG ();
print_prefix ();
tout.blue ();
fputs ("--- [ ", stdout);
tout.blue (true);
fputs (title, stdout);
tout.blue ();
fputs (" ] ", stdout);
for (int i = strlen (title) + strlen (prefix.c_str ()) + 9; i < 78; i++)
fputc ('-', stdout);
tout.normal ();
fputc ('\n', stdout);
MSG ();
}
/*------------------------------------------------------------------------*/
void Internal::phase (const char *phase, const char *fmt, ...) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet || (!force_phase_messages && opts.verbose < 2))
return;
print_prefix ();
printf ("[%s] ", phase);
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
fputc ('\n', stdout);
fflush (stdout);
}
void Internal::phase (const char *phase, int64_t count, const char *fmt,
...) {
#ifdef LOGGING
if (!opts.log)
#endif
if (opts.quiet || (!force_phase_messages && opts.verbose < 2))
return;
print_prefix ();
printf ("[%s-%" PRId64 "] ", phase, count);
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
fputc ('\n', stdout);
fflush (stdout);
}
/*------------------------------------------------------------------------*/
#endif // ifndef QUIET
/*------------------------------------------------------------------------*/
void Internal::warning (const char *fmt, ...) {
fflush (stdout);
terr.bold ();
fputs ("cadical: ", stderr);
terr.red (1);
fputs ("warning:", stderr);
terr.normal ();
fputc (' ', stderr);
va_list ap;
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
fputc ('\n', stderr);
fflush (stderr);
}
/*------------------------------------------------------------------------*/
void Internal::error_message_start () {
fflush (stdout);
terr.bold ();
fputs ("cadical: ", stderr);
terr.red (1);
fputs ("error:", stderr);
terr.normal ();
fputc (' ', stderr);
}
void Internal::error_message_end () {
fputc ('\n', stderr);
fflush (stderr);
// TODO add possibility to use call back instead.
exit (1);
}
void Internal::verror (const char *fmt, va_list &ap) {
error_message_start ();
vfprintf (stderr, fmt, ap);
error_message_end ();
}
void Internal::error (const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
verror (fmt, ap);
va_end (ap); // unreachable
}
/*------------------------------------------------------------------------*/
void fatal_message_start () {
fflush (stdout);
terr.bold ();
fputs ("cadical: ", stderr);
terr.red (1);
fputs ("fatal error:", stderr);
terr.normal ();
fputc (' ', stderr);
}
void fatal_message_end () {
fputc ('\n', stderr);
fflush (stderr);
abort ();
}
void fatal (const char *fmt, ...) {
fatal_message_start ();
va_list ap;
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
fatal_message_end ();
abort ();
}
} // namespace CaDiCaL

View File

@ -0,0 +1,65 @@
#ifndef _message_h_INCLUDED
#define _message_h_INCLUDED
/*------------------------------------------------------------------------*/
// Macros for compact message code.
#ifndef QUIET
#define LINE() \
do { \
if (internal) \
internal->message (); \
} while (0)
#define MSG(...) \
do { \
if (internal) \
internal->message (__VA_ARGS__); \
} while (0)
#define PHASE(...) \
do { \
if (internal) \
internal->phase (__VA_ARGS__); \
} while (0)
#define SECTION(...) \
do { \
if (internal) \
internal->section (__VA_ARGS__); \
} while (0)
#define VERBOSE(...) \
do { \
if (internal) \
internal->verbose (__VA_ARGS__); \
} while (0)
#else
#define LINE() \
do { \
} while (0)
#define MSG(...) \
do { \
} while (0)
#define PHASE(...) \
do { \
} while (0)
#define SECTION(...) \
do { \
} while (0)
#define VERBOSE(...) \
do { \
} while (0)
#endif
#define FATAL fatal
#define WARNING(...) internal->warning (__VA_ARGS__)
/*------------------------------------------------------------------------*/
#endif // ifndef _message_h_INCLUDED

View File

@ -0,0 +1,224 @@
#include "internal.hpp"
namespace CaDiCaL {
// Functions for learned clause minimization. We only have the recursive
// version, which actually really is implemented recursively. We also
// played with a derecursified version, which however was more complex and
// slower. The trick to keep potential stack exhausting recursion under
// guards is to explicitly limit the recursion depth.
// Instead of signatures as in the original implementation in MiniSAT and
// our corresponding paper, we use the 'poison' idea of Allen Van Gelder to
// mark unsuccessful removal attempts, then Donald Knuth's idea to abort
// minimization if only one literal was seen on the level and a new idea of
// also aborting if the earliest seen literal was assigned afterwards.
bool Internal::minimize_literal (int lit, int depth) {
LOG ("attempt to minimize lit %d at depth %d", lit, depth);
assert (val (lit) > 0);
Flags &f = flags (lit);
Var &v = var (lit);
if (!v.level || f.removable || f.keep)
return true;
if (!v.reason || f.poison || v.level == level)
return false;
const Level &l = control[v.level];
if (!depth && l.seen.count < 2)
return false; // Don Knuth's idea
if (v.trail <= l.seen.trail)
return false; // new early abort
if (depth > opts.minimizedepth)
return false;
bool res = true;
assert (v.reason);
if (opts.minimizeticks)
stats.ticks.search[stable]++;
if (v.reason == external_reason) {
assert (!opts.exteagerreasons);
v.reason = learn_external_reason_clause (lit, 0, true);
if (!v.reason) {
assert (!v.level);
return true;
}
}
assert (v.reason != external_reason);
const const_literal_iterator end = v.reason->end ();
const_literal_iterator i;
for (i = v.reason->begin (); res && i != end; i++) {
const int other = *i;
if (other == lit)
continue;
res = minimize_literal (-other, depth + 1);
}
if (res)
f.removable = true;
else
f.poison = true;
minimized.push_back (lit);
if (!depth) {
LOG ("minimizing %d %s", lit, res ? "succeeded" : "failed");
}
return res;
}
// Sorting the clause before minimization with respect to the trail order
// (literals with smaller trail height first) is necessary but natural and
// might help to minimize the required recursion depth too.
struct minimize_trail_positive_rank {
Internal *internal;
minimize_trail_positive_rank (Internal *s) : internal (s) {}
typedef unsigned Type;
Type operator() (const int &a) const {
assert (internal->val (a));
return (unsigned) internal->var (a).trail;
}
};
struct minimize_trail_smaller {
Internal *internal;
minimize_trail_smaller (Internal *s) : internal (s) {}
bool operator() (const int &a, const int &b) const {
return internal->var (a).trail < internal->var (b).trail;
}
};
struct minimize_trail_level_positive_rank {
Internal *internal;
minimize_trail_level_positive_rank (Internal *s) : internal (s) {}
typedef uint64_t Type;
Type operator() (const int &a) const {
assert (internal->val (a));
Var &v = internal->var (a);
uint64_t res = v.level;
res <<= 32;
res |= v.trail;
return res;
}
};
struct minimize_trail_level_smaller {
Internal *internal;
minimize_trail_level_smaller (Internal *s) : internal (s) {}
bool operator() (const int &a, const int &b) const {
return minimize_trail_level_positive_rank (internal) (a) <
minimize_trail_level_positive_rank (internal) (b);
}
};
void Internal::minimize_clause () {
START (minimize);
LOG (clause, "minimizing first UIP clause");
external->check_learned_clause (); // check 1st UIP learned clause first
minimize_sort_clause ();
assert (minimized.empty ());
assert (minimize_chain.empty ());
const auto end = clause.end ();
auto j = clause.begin (), i = j;
std::vector<int> stack;
for (; i != end; i++) {
if (minimize_literal (-*i)) {
if (lrat) {
assert (mini_chain.empty ());
calculate_minimize_chain (-*i, stack);
for (auto p : mini_chain) {
minimize_chain.push_back (p);
}
mini_chain.clear ();
}
stats.minimized++;
} else
flags (*j++ = *i).keep = true;
}
LOG ("minimized %zd literals", (size_t) (clause.end () - j));
if (j != end)
clause.resize (j - clause.begin ());
clear_minimized_literals ();
for (auto p = minimize_chain.rbegin (); p != minimize_chain.rend ();
p++) {
lrat_chain.push_back (*p);
}
minimize_chain.clear ();
STOP (minimize);
}
// go backwards in reason graph and add ids
// mini_chain is in correct order so we have to add it to minimize_chain
// and then reverse when we put it on lrat_chain
//
// We have to use the non-recursive as we cannot limit the depth like the
// minimize version. Unlike the minimize version, we have to keep literals
// on the stack in order to push its reason later.
void Internal::calculate_minimize_chain (int lit, std::vector<int> &stack) {
assert (stack.empty ());
stack.push_back (vidx (lit));
while (!stack.empty ()) {
const int idx = stack.back ();
assert (idx);
stack.pop_back ();
if (idx < 0) {
Var &v = var (idx);
mini_chain.push_back (v.reason->id);
continue;
}
assert (idx);
Flags &f = flags (idx);
Var &v = var (idx);
if (f.keep || f.added || f.poison) {
continue;
}
if (!v.level) {
if (f.seen)
continue;
f.seen = true;
unit_analyzed.push_back (idx);
const int lit = val (idx) > 0 ? idx : -idx;
int64_t id = unit_id (lit);
unit_chain.push_back (id);
continue;
}
f.added = true;
assert (v.reason && f.removable);
const const_literal_iterator end = v.reason->end ();
const_literal_iterator i;
LOG (v.reason, "LRAT chain for lit %d at depth %zd by going over", lit,
stack.size ());
stack.push_back (-idx);
for (i = v.reason->begin (); i != end; i++) {
const int other = *i;
if (other == idx)
continue;
stack.push_back (vidx (other));
}
}
assert (stack.empty ());
}
// Sort the literals in reverse assignment order (thus trail order) to
// establish the base case of the recursive minimization algorithm in the
// positive case (where a literal with 'keep' true is hit).
//
void Internal::minimize_sort_clause () {
MSORT (opts.radixsortlim, clause.begin (), clause.end (),
minimize_trail_positive_rank (this),
minimize_trail_smaller (this));
}
void Internal::clear_minimized_literals () {
LOG ("clearing %zd minimized literals", minimized.size ());
for (const auto &lit : minimized) {
Flags &f = flags (lit);
f.poison = f.removable = f.shrinkable = f.added = false;
}
for (const auto &lit : clause)
assert (!flags (lit).shrinkable), flags (lit).keep =
flags (lit).shrinkable =
flags (lit).added = false;
minimized.clear ();
}
} // namespace CaDiCaL

52
src/sat/cadical/occs.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Occurrence lists.
void Internal::init_occs () {
if (otab.size () < 2 * vsize)
otab.resize (2 * vsize, Occs ());
LOG ("initialized occurrence lists");
}
void Internal::reset_occs () {
assert (occurring ());
erase_vector (otab);
LOG ("reset occurrence lists");
}
void Internal::clear_occs () {
assert (occurring ());
for (auto &occ : otab)
occ.clear ();
LOG ("clear occurrence lists");
}
/*------------------------------------------------------------------------*/
// One-sided occurrence counter (each literal has its own counter).
void Internal::init_noccs () {
assert (ntab.empty ());
if (ntab.size () < 2 * vsize)
ntab.resize (2 * vsize, 0);
LOG ("initialized two-sided occurrence counters");
}
void Internal::clear_noccs () {
assert (!ntab.empty ());
for (auto &nt : ntab)
nt = 0;
LOG ("clear two-sided occurrence counters");
}
void Internal::reset_noccs () {
assert (!max_var || !ntab.empty ());
erase_vector (ntab);
LOG ("reset two-sided occurrence counters");
}
} // namespace CaDiCaL

36
src/sat/cadical/occs.hpp Normal file
View File

@ -0,0 +1,36 @@
#ifndef _occs_h_INCLUDED
#define _occs_h_INCLUDED
#include <vector>
namespace CaDiCaL {
// Full occurrence lists used in a one-watch scheme for all clauses in
// subsumption checking and for irredundant clauses in variable elimination.
struct Clause;
using namespace std;
typedef vector<Clause *> Occs;
inline void shrink_occs (Occs &os) { shrink_vector (os); }
inline void erase_occs (Occs &os) { erase_vector (os); }
inline void remove_occs (Occs &os, Clause *c) {
const auto end = os.end ();
auto i = os.begin ();
for (auto j = i; j != end; j++) {
const Clause *d = *i++ = *j;
if (c == d)
i--;
}
assert (i + 1 == end);
os.resize (i - os.begin ());
}
typedef Occs::iterator occs_iterator;
typedef Occs::const_iterator const_occs_iterator;
} // namespace CaDiCaL
#endif

359
src/sat/cadical/options.cpp Normal file
View File

@ -0,0 +1,359 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// By default, e.g., for library usage, the 'opts.report' value is zero
// ('false') but can be set to '1' by the stand alone solver. Using here
// a static default value avoids that the stand alone solver reports that
// '--report=1' is different from the default in 'print ()' below.
//
int Options::reportdefault;
/*------------------------------------------------------------------------*/
// The order of initializations of static objects is undefined and thus we
// can not assume that this table is already initialized if a solver and
// thus the constructor of 'Options' is called. Therefore we just have to
// reinitialize this table in every call to 'Options::Options'. This does
// not produce a data race even for parallel initialization since the
// same values are written by all threads under the assumption that the
// 'reportdefault' is set before any solver is initialized. We do have to
// perform this static initialization though, since 'has' is static and does
// not require that the 'Options' constructor was called.
Option Options::table[] = {
#define OPTION(N, V, L, H, O, P, R, D) \
{#N, (int) V, (int) L, (int) H, (int) O, (bool) P, D},
OPTIONS
#undef OPTION
};
/*------------------------------------------------------------------------*/
// Binary search in 'table', which requires option names to be sorted, which
// in turned is checked at start-up in 'Options::Options'.
Option *Options::has (const char *name) {
size_t l = 0, r = number_of_options;
while (l < r) {
size_t m = l + (r - l) / 2;
Option *res = &table[m];
int tmp = strcmp (name, res->name);
if (!tmp)
return res;
if (tmp < 0)
r = m;
if (tmp > 0)
l = m + 1;
}
return 0;
}
/*------------------------------------------------------------------------*/
bool Options::parse_long_option (const char *arg, string &name, int &val) {
if (arg[0] != '-' || arg[1] != '-')
return false;
const bool has_no_prefix =
(arg[2] == 'n' && arg[3] == 'o' && arg[4] == '-');
const size_t offset = has_no_prefix ? 5 : 2;
name = arg + offset;
const size_t pos = name.find_first_of ('=');
if (pos != string::npos)
name[pos] = 0;
if (!Options::has (name.c_str ()))
return false;
if (pos == string::npos)
val = !has_no_prefix;
else {
const char *val_str = name.c_str () + pos + 1;
if (!parse_int_str (val_str, val))
return false;
}
return true;
}
/*------------------------------------------------------------------------*/
void Options::initialize_from_environment (int &val, const char *name,
const int L, const int H) {
char key[80], *q;
const char *p;
assert (strlen (name) + strlen ("CADICAL_") + 1 < sizeof (key));
for (p = "CADICAL_", q = key; *p; p++)
*q++ = *p;
for (p = name; *p; p++)
*q++ = toupper (*p);
assert (q < key + sizeof (key));
*q = 0;
const char *val_str = getenv (key);
if (!val_str)
return;
if (!parse_int_str (val_str, val))
return;
if (val < L)
val = L;
if (val > H)
val = H;
}
// Initialize all the options to their default value 'V'.
Options::Options (Internal *s) : internal (s) {
assert (number_of_options == sizeof Options::table / sizeof (Option));
// First initialize them according to defaults in 'options.hpp'.
//
const char *prev = "";
size_t i = 0;
#define OPTION(N, V, L, H, O, P, R, D) \
do { \
if ((L) > (V)) \
FATAL ("'" #N "' default '" #V "' " \
"lower minimum '" #L "' in 'options.hpp'"); \
if ((H) < (V)) \
FATAL ("'" #N "' default '" #V "' " \
"larger maximum '" #H "' in 'options.hpp'"); \
if (strcmp (prev, #N) > 0) \
FATAL ("'%s' ordered before '" #N "' in 'options.hpp'", prev); \
N = (int) (V); \
assert (&val (i) == &N); \
/* The order of initializing static data is undefined and thus */ \
/* it might be the case that the 'table' is not initialized yet. */ \
/* Thus this construction just reinitializes the table too even */ \
/* though it might not be necessary. */ \
assert (!table[i].name || !strcmp (table[i].name, #N)); \
table[i] = {#N, (int) (V), (int) (L), (int) (H), \
(int) (O), (bool) (P), D}; \
prev = #N; \
i++; \
} while (0);
OPTIONS
#undef OPTION
// Check consistency in debugging mode.
//
#ifndef NDEBUG
assert (i == number_of_options);
assert (!has ("aaaaa"));
assert (!has ("non-existing-option"));
assert (!has ("zzzzz"));
#endif
// Now overwrite default options with environment values.
//
#define OPTION(N, V, L, H, O, P, R, D) \
initialize_from_environment (N, #N, L, H);
OPTIONS
#undef OPTION
}
/*------------------------------------------------------------------------*/
void Options::set (Option *o, int new_val) {
assert (o);
int &val = o->val (this), old_val = val;
if (old_val == new_val) {
LOG ("keeping value '%d' of option '%s'", old_val, o->name);
return;
}
if (new_val < o->lo) {
LOG ("bounding '%d' to lower limit '%d' for option '%s'", new_val,
o->lo, o->name);
new_val = o->lo;
}
if (new_val > o->hi) {
LOG ("bounding '%d' to upper limit '%d' for option '%s'", new_val,
o->hi, o->name);
new_val = o->hi;
}
val = new_val;
LOG ("set option 'set (\"%s\", %d)' from '%d'", o->name, new_val,
old_val);
}
// Explicit option value setting.
bool Options::set (const char *name, int val) {
Option *o = has (name);
if (!o)
return false;
set (o, val);
return true;
}
int Options::get (const char *name) {
Option *o = has (name);
return o ? o->val (this) : 0;
}
/*------------------------------------------------------------------------*/
void Options::print () {
unsigned different = 0;
#ifdef QUIET
const bool verbose = false;
#endif
char buffer[256];
// We prefer the macro iteration here since '[VLH]' might be '1e9' etc.
#define OPTION(N, V, L, H, O, P, R, D) \
if (N != (V)) \
different++; \
if (verbose || N != (V)) { \
if ((L) == 0 && (H) == 1) { \
snprintf (buffer, sizeof buffer, "--" #N "=%s", \
(N ? "true" : "false")); \
MSG (" %s%-30s%s (%s default %s'%s'%s)", \
((N == (V)) ? "" : tout.bright_yellow_code ()), buffer, \
((N == (V)) ? "" : tout.normal_code ()), \
((N == (V)) ? "same as" : "different from"), \
((N == (V)) ? tout.green_code () : tout.yellow_code ()), \
(bool) (V) ? "true" : "false", tout.normal_code ()); \
} else { \
snprintf (buffer, sizeof buffer, "--" #N "=%d", N); \
MSG (" %s%-30s%s (%s default %s'" #V "'%s)", \
((N == (V)) ? "" : tout.bright_yellow_code ()), buffer, \
((N == (V)) ? "" : tout.normal_code ()), \
((N == (V)) ? "same as" : "different from"), \
((N == (V)) ? tout.green_code () : tout.yellow_code ()), \
tout.normal_code ()); \
} \
}
OPTIONS
#undef OPTION
if (!different)
MSG ("all options are set to their default value");
}
/*------------------------------------------------------------------------*/
void Options::usage () {
// We prefer the macro iteration here since '[VLH]' might be '1e9' etc.
#define OPTION(N, V, L, H, O, P, R, D) \
if ((L) == 0 && (H) == 1) \
printf (" %-26s " D " [%s]\n", "--" #N "=bool", \
(bool) (V) ? "true" : "false"); \
else \
printf (" %-26s " D " [" #V "]\n", "--" #N "=" #L ".." #H);
OPTIONS
#undef OPTION
}
/*------------------------------------------------------------------------*/
void Options::optimize (int val) {
if (val < 0) {
LOG ("ignoring negative optimization mode '%d'", val);
return;
}
const int max_val = 31;
if (val > max_val) {
LOG ("optimization argument '%d' reduced to '%d'", val, max_val);
val = max_val;
}
int64_t factor2 = 1;
for (int i = 0; i < val && factor2 <= INT_MAX; i++)
factor2 *= 2;
int64_t factor10 = 1;
for (int i = 0; i < val && factor10 <= INT_MAX; i++)
factor10 *= 10;
unsigned increased = 0;
#define OPTION(N, V, L, H, O, P, R, D) \
do { \
if (!(O)) \
break; \
const int64_t factor1 = ((O) == 1 ? factor2 : factor10); \
int64_t new_val = factor1 * (int64_t) (V); \
if (new_val > (H)) \
new_val = (H); \
if (new_val == (int) (V)) \
break; \
LOG ("optimization mode '%d' for '%s' " \
"gives '%" PRId64 "' instead of '%d", \
val, #N, new_val, (int) (V)); \
assert (new_val <= INT_MAX); \
N = (int) new_val; \
increased++; \
} while (0);
OPTIONS
#undef OPTION
if (increased)
MSG ("optimization mode '-O%d' increased %u limits", val, increased);
}
/*------------------------------------------------------------------------*/
void Options::disable_preprocessing () {
size_t count = 0;
#define OPTION(N, V, L, H, O, P, R, D) \
do { \
if (!(P)) \
break; \
if (!(N)) \
break; \
LOG ("plain mode disables '%s'", #N); \
assert ((L) == 0); \
assert ((H) == 1); \
count++; \
N = 0; \
} while (0);
OPTIONS
#undef OPTION
LOG ("forced plain mode disabled %zd preprocessing options", count);
#ifndef LOGGING
(void) count;
#endif
}
bool Options::is_preprocessing_option (const char *name) {
Option *o = has (name);
return o ? o->preprocessing : false;
}
/*------------------------------------------------------------------------*/
void Options::reset_default_values () {
size_t count = 0;
#define OPTION(N, V, L, H, O, P, R, D) \
do { \
if (!(R)) \
break; \
if (N == (V)) \
break; \
LOG ("resetting option '%s' to default %s", #N, #V); \
count++; \
N = (int) (V); \
} while (0);
OPTIONS
#undef OPTION
LOG ("reset %zd options to their default values", count);
#ifndef LOGGING
(void) count;
#endif
}
/*------------------------------------------------------------------------*/
void Options::copy (Options &other) const {
#ifdef LOGGING
Internal *internal = other.internal;
#endif
#define OPTION(N, V, L, H, O, P, R, D) \
if ((N) == (int) (V)) \
LOG ("keeping non default option '--%s=%s'", #N, #V); \
else if ((N) != (int) (V)) { \
LOG ("overwriting default option by '--%s=%d'", #N, N); \
other.N = N; \
}
OPTIONS
#undef OPTION
}
} // namespace CaDiCaL

416
src/sat/cadical/options.hpp Normal file
View File

@ -0,0 +1,416 @@
#ifndef _options_hpp_INCLUDED
#define _options_hpp_INCLUDED
/*------------------------------------------------------------------------*/
// In order to add a new option, simply add a new line below. Make sure that
// options are sorted correctly (with '!}sort -k 2' in 'vi'). Otherwise
// initializing the options will trigger an internal error. For the model
// based tester 'mobical' the policy is that options which become redundant
// because another one is disabled (set to zero) should have the name of the
// latter as prefix. The 'O' column determines the options which are
// target to 'optimize' them ('-O[1-3]'). A zero value in the 'O' column
// means that this option is not optimized. A value of '1' results in
// optimizing its value exponentially with exponent base '2', and a value
// of '2' uses base '10'. The 'P' column determines simplification
// options (disabled with '--plain') and 'R' which values can be reset.
// clang-format off
#define OPTIONS \
\
/* NAME DEFAULT, LO, HI,O,P,R, USAGE */ \
\
OPTION( arena, 1, 0, 1,0,0,1, "allocate clauses in arena") \
OPTION( arenacompact, 1, 0, 1,0,0,1, "keep clauses compact") \
OPTION( arenasort, 1, 0, 1,0,0,1, "sort clauses in arena") \
OPTION( arenatype, 3, 1, 3,0,0,1, "1=clause, 2=var, 3=queue") \
OPTION( binary, 1, 0, 1,0,0,1, "use binary proof format") \
OPTION( block, 0, 0, 1,0,1,1, "blocked clause elimination") \
OPTION( blockmaxclslim, 1e5, 1,2e9,2,0,1, "maximum clause size") \
OPTION( blockminclslim, 2, 2,2e9,0,0,1, "minimum clause size") \
OPTION( blockocclim, 1e2, 1,2e9,2,0,1, "occurrence limit") \
OPTION( bump, 1, 0, 1,0,0,1, "bump variables") \
OPTION( bumpreason, 1, 0, 1,0,0,1, "bump reason literals too") \
OPTION( bumpreasondepth, 1, 1, 3,0,0,1, "bump reason depth") \
OPTION( bumpreasonlimit, 10, 1,2e9,0,0,1, "bump reason limit") \
OPTION( bumpreasonrate, 100, 1,2e9,0,0,1, "bump reason decision rate") \
OPTION( check, 0, 0, 1,0,0,0, "enable internal checking") \
OPTION( checkassumptions, 1, 0, 1,0,0,0, "check assumptions satisfied") \
OPTION( checkconstraint, 1, 0, 1,0,0,0, "check constraint satisfied") \
OPTION( checkfailed, 1, 0, 1,0,0,0, "check failed literals form core") \
OPTION( checkfrozen, 0, 0, 1,0,0,0, "check all frozen semantics") \
OPTION( checkproof, 3, 0, 3,0,0,0, "1=drat, 2=lrat, 3=both") \
OPTION( checkwitness, 1, 0, 1,0,0,0, "check witness internally") \
OPTION( chrono, 1, 0, 2,0,0,1, "chronological backtracking") \
OPTION( chronoalways, 0, 0, 1,0,0,1, "force always chronological") \
OPTION( chronolevelim, 1e2, 0,2e9,0,0,1, "chronological level limit") \
OPTION( chronoreusetrail, 1, 0, 1,0,0,1, "reuse trail chronologically") \
OPTION( compact, 1, 0, 1,0,1,1, "compact internal variables") \
OPTION( compactint, 2e3, 1,2e9,0,0,1, "compacting interval") \
OPTION( compactlim, 1e2, 0,1e3,0,0,1, "inactive limit per mille") \
OPTION( compactmin, 1e2, 1,2e9,0,0,1, "minimum inactive limit") \
OPTION( condition, 0, 0, 1,0,1,1, "globally blocked clause elim") \
OPTION( conditioneffort, 100, 1,1e5,0,0,1, "relative efficiency per mille") \
OPTION( conditionint, 1e4, 1,2e9,0,0,1, "initial conflict interval") \
OPTION( conditionmaxeff, 1e7, 0,2e9,1,0,1, "maximum condition efficiency") \
OPTION( conditionmaxrat, 100, 1,2e9,1,0,1, "maximum clause variable ratio") \
OPTION( conditionmineff, 0, 0,2e9,1,0,1, "minimum condition efficiency") \
OPTION( congruence, 1, 0, 1,0,0,1, "congruence closure") \
OPTION( congruenceand, 1, 0, 1,0,0,1, "extract AND gates") \
OPTION( congruenceandarity,1e6,2,5e7,0,0,1, "AND gate arity limit") \
OPTION( congruencebinaries,1, 0, 1,0,0,1, "extract binary and strengthen ternary clauses") \
OPTION( congruenceite, 1, 0, 1,0,0,1, "extract ITE gates") \
OPTION( congruencexor, 1, 0, 1,0,0,1, "extract XOR gates") \
OPTION( congruencexorarity,4, 2, 31,0,0,1, "XOR gate arity limit") \
OPTION( congruencexorcounts,1, 1,5e6,0,0,1, "XOR gate round") \
OPTION( cover, 0, 0, 1,0,1,1, "covered clause elimination") \
OPTION( covereffort, 4, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( covermaxclslim, 1e5, 1,2e9,2,0,1, "maximum clause size") \
OPTION( covermaxeff, 1e8, 0,2e9,1,0,1, "maximum cover efficiency") \
OPTION( coverminclslim, 2, 2,2e9,0,0,1, "minimum clause size") \
OPTION( covermineff, 0, 0,2e9,1,0,1, "minimum cover efficiency") \
OPTION( decompose, 1, 0, 1,0,1,1, "decompose BIG in SCCs and ELS") \
OPTION( decomposerounds, 2, 1, 16,1,0,1, "number of decompose rounds") \
OPTION( deduplicate, 1, 0, 1,0,1,1, "remove duplicated binaries") \
OPTION( eagersubsume, 1, 0, 1,0,1,1, "subsume recently learned") \
OPTION( eagersubsumelim, 20, 1,1e3,0,0,1, "limit on subsumed candidates") \
OPTION( elim, 1, 0, 1,0,1,1, "bounded variable elimination") \
OPTION( elimands, 1, 0, 1,0,0,1, "find AND gates") \
OPTION( elimbackward, 1, 0, 1,0,0,1, "eager backward subsumption") \
OPTION( elimboundmax, 16, -1,2e6,1,0,1, "maximum elimination bound") \
OPTION( elimboundmin, 0, -1,2e6,0,0,1, "minimum elimination bound") \
OPTION( elimclslim, 1e2, 2,2e9,2,0,1, "resolvent size limit") \
OPTION( elimdef, 0, 0, 1,0,0,1, "mine definitions with kitten") \
OPTION( elimdefcores, 1, 1,100,0,0,1, "number of unsat cores") \
OPTION( elimdefticks, 2e5, 0,2e9,1,0,1, "kitten ticks limit") \
OPTION( elimeffort, 1e3, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( elimequivs, 1, 0, 1,0,0,1, "find equivalence gates") \
OPTION( elimint, 2e3, 1,2e9,0,0,1, "elimination interval") \
OPTION( elimites, 1, 0, 1,0,0,1, "find if-then-else gates") \
OPTION( elimlimited, 1, 0, 1,0,0,1, "limit resolutions") \
OPTION( elimmaxeff, 2e9, 0,2e9,1,0,1, "maximum elimination efficiency") \
OPTION( elimmineff, 1e7, 0,2e9,1,0,1, "minimum elimination efficiency") \
OPTION( elimocclim, 1e2, 0,2e9,2,0,1, "occurrence limit") \
OPTION( elimprod, 1, 0,1e4,0,0,1, "elim score product weight") \
OPTION( elimrounds, 2, 1,512,1,0,1, "usual number of rounds") \
OPTION( elimsubst, 1, 0, 1,0,0,1, "elimination by substitution") \
OPTION( elimsum, 1, 0,1e4,0,0,1, "elimination score sum weight") \
OPTION( elimxorlim, 5, 2, 27,1,0,1, "maximum XOR size") \
OPTION( elimxors, 1, 0, 1,0,0,1, "find XOR gates") \
OPTION( emadecisions, 1e5, 1,2e9,0,0,1, "window decision rate") \
OPTION( emagluefast, 33, 1,2e9,0,0,1, "window fast glue") \
OPTION( emaglueslow, 1e5, 1,2e9,0,0,1, "window slow glue") \
OPTION( emajump, 1e5, 1,2e9,0,0,1, "window back-jump level") \
OPTION( emalevel, 1e5, 1,2e9,0,0,1, "window back-track level") \
OPTION( emasize, 1e5, 1,2e9,0,0,1, "window learned clause size") \
OPTION( ematrailfast, 1e2, 1,2e9,0,0,1, "window fast trail") \
OPTION( ematrailslow, 1e5, 1,2e9,0,0,1, "window slow trail") \
OPTION( exteagerreasons, 1, 0, 1,0,0,1, "eagerly ask for all reasons (0: only when needed)") \
OPTION( exteagerrecalc, 1, 0, 1,0,0,1, "after eagerly asking for reasons recalculate all levels (0: trust the external tool)") \
OPTION( externallrat, 0, 0, 1,0,0,1, "external lrat") \
OPTION( factor, 1, 0, 1,0,1,1, "bounded variable addition") \
OPTION( factorcandrounds, 2, 0,2e9,0,0,1, "candidates reduction rounds") \
OPTION( factoreffort, 50, 0,1e6,0,0,1, "relative effort per mille") \
OPTION( factoriniticks, 300, 1,1e6,0,0,1, "initial effort in millions") \
OPTION( factorsize, 5, 2,2e9,0,0,1, "clause size limit") \
OPTION( factorthresh, 7, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \
OPTION( fastelim, 1, 0, 1,0,1,1, "fast BVE during preprocessing") \
OPTION( fastelimbound, 8, 1,1e3,1,0,1, "fast BVE bound during preprocessing") \
OPTION( fastelimclslim, 1e2, 2,2e9,2,0,1, "fast BVE resolvent size limit") \
OPTION( fastelimocclim, 100, 1,2e9,2,0,1, "fast BVE occurence limit during preprocessing") \
OPTION( fastelimrounds, 4, 1,512,1,0,1, "number of fastelim rounds") \
OPTION( flush, 0, 0, 1,0,1,1, "flush redundant clauses") \
OPTION( flushfactor, 3, 1,1e3,0,0,1, "interval increase") \
OPTION( flushint, 1e5, 1,2e9,0,0,1, "initial limit") \
OPTION( forcephase, 0, 0, 1,0,0,1, "always use initial phase") \
OPTION( frat, 0, 0, 2,0,0,1, "1=frat(lrat), 2=frat(drat)") \
OPTION( idrup, 0, 0, 1,0,0,1, "incremental proof format") \
OPTION( ilb, 0, 0, 1,0,0,1, "ILB (incremental lazy backtrack)") \
OPTION( ilbassumptions, 0, 0, 1,0,0,1, "trail reuse for assumptions (ILB-like)") \
OPTION( inprobeint, 100, 1,2e9,0,0,1, "inprobing interval" ) \
OPTION( inprobing, 1, 0, 1,0,1,1, "enable probe inprocessing") \
OPTION( inprocessing, 1, 0, 1,0,1,1, "enable general inprocessing") \
OPTION( instantiate, 0, 0, 1,0,1,1, "variable instantiation") \
OPTION( instantiateclslim, 3, 2,2e9,0,0,1, "minimum clause size") \
OPTION( instantiateocclim, 1, 1,2e9,2,0,1, "maximum occurrence limit") \
OPTION( instantiateonce, 1, 0, 1,0,0,1, "instantiate each clause once") \
OPTION( lidrup, 0, 0, 1,0,0,1, "linear incremental proof format") \
LOGOPT( log, 0, 0, 1,0,0,0, "enable logging") \
LOGOPT( logsort, 0, 0, 1,0,0,0, "sort logged clauses") \
OPTION( lrat, 0, 0, 1,0,0,1, "use LRAT proof format") \
OPTION( lucky, 1, 0, 1,0,0,1, "search for lucky phases") \
OPTION( minimize, 1, 0, 1,0,0,1, "minimize learned clauses") \
OPTION( minimizedepth, 1e3, 0,1e3,0,0,1, "minimization depth") \
OPTION( minimizeticks, 1, 0, 1,0,0,1, "increment ticks in minimization") \
OPTION( otfs, 1, 0, 1,0,0,1, "on-the-fly self subsumption") \
OPTION( phase, 1, 0, 1,0,0,1, "initial phase") \
OPTION( preprocessinit, 2e6, 0,2e9,2,0,1, "initial preprocessing base limit" ) \
OPTION( preprocesslight, 1, 0, 1,0,1,1, "lightweight preprocessing" ) \
OPTION( probe, 1, 0, 1,0,1,1, "failed literal probing" ) \
OPTION( probeeffort, 8, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( probehbr, 1, 0, 1,0,0,1, "learn hyper binary clauses") \
OPTION( probethresh, 0, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \
OPTION( profile, 2, 0, 4,0,0,0, "profiling level") \
QUTOPT( quiet, 0, 0, 1,0,0,0, "disable all messages") \
OPTION( radixsortlim, 32, 0,2e9,0,0,1, "radix sort limit") \
OPTION( realtime, 0, 0, 1,0,0,0, "real instead of process time") \
OPTION( recomputetier, 1, 0, 1,0,0,1, "recompute tiers") \
OPTION( reduce, 1, 0, 1,0,0,1, "reduce useless clauses") \
OPTION( reduceinit, 300, 1,1e6,0,0,1, "initial interval") \
OPTION( reduceint, 25, 2,1e6,0,0,1, "reduce interval") \
OPTION( reduceopt, 1, 0, 2,0,0,1, "0=prct,1=sqrt,2=max") \
OPTION( reducetarget, 75, 10,1e2,0,0,1, "reduce fraction in percent") \
OPTION( reducetier1glue, 2, 1,2e9,0,0,1, "glue of kept learned clauses") \
OPTION( reducetier2glue, 6, 1,2e9,0,0,1, "glue of tier two clauses") \
OPTION( reluctant, 1024, 0,2e9,0,0,1, "reluctant doubling period") \
OPTION( reluctantmax,1048576, 0,2e9,0,0,1, "reluctant doubling period") \
OPTION( rephase, 1, 0, 1,0,0,1, "enable resetting phase") \
OPTION( rephaseint, 1e3, 1,2e9,0,0,1, "rephase interval") \
OPTION( report,reportdefault, 0, 1,0,0,1, "enable reporting") \
OPTION( reportall, 0, 0, 1,0,0,1, "report even if not successful") \
OPTION( reportsolve, 0, 0, 1,0,0,1, "use solving not process time") \
OPTION( restart, 1, 0, 1,0,0,1, "enable restarts") \
OPTION( restartint, 2, 1,2e9,0,0,1, "restart interval") \
OPTION( restartmargin, 10, 0,1e2,0,0,1, "slow fast margin in percent") \
OPTION( restartreusetrail, 1, 0, 1,0,0,1, "enable trail reuse") \
OPTION( restoreall, 0, 0, 2,0,0,1, "restore all clauses (2=really)") \
OPTION( restoreflush, 0, 0, 1,0,0,1, "remove satisfied clauses") \
OPTION( reverse, 0, 0, 1,0,0,1, "reverse variable ordering") \
OPTION( score, 1, 0, 1,0,0,1, "use EVSIDS scores") \
OPTION( scorefactor, 950,500,1e3,0,0,1, "score factor per mille") \
OPTION( seed, 0, 0,2e9,0,0,1, "random seed") \
OPTION( shrink, 3, 0, 3,0,0,1, "shrink conflict clause (1=only with binary, 2=minimize when pulling, 3=full)") \
OPTION( shrinkreap, 1, 0, 1,0,0,1, "use a reap for shrinking") \
OPTION( shuffle, 0, 0, 1,0,0,1, "shuffle variables") \
OPTION( shufflequeue, 1, 0, 1,0,0,1, "shuffle variable queue") \
OPTION( shufflerandom, 0, 0, 1,0,0,1, "not reverse but random") \
OPTION( shufflescores, 1, 0, 1,0,0,1, "shuffle variable scores") \
OPTION( stabilize, 1, 0, 1,0,0,1, "enable stabilizing phases") \
OPTION( stabilizeinit, 1e3, 1,2e9,0,0,1, "stabilizing interval") \
OPTION( stabilizeonly, 0, 0, 1,0,0,1, "only stabilizing phases") \
OPTION( stats, 0, 0, 1,0,0,1, "print all statistics at the end of the run") \
OPTION( subsume, 1, 0, 1,0,1,1, "enable clause subsumption") \
OPTION( subsumebinlim, 1e4, 0,2e9,1,0,1, "watch list length limit") \
OPTION( subsumeclslim, 1e2, 0,2e9,2,0,1, "clause length limit") \
OPTION( subsumeeffort, 1e3, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( subsumelimited, 1, 0, 1,0,0,1, "limit subsumption checks") \
OPTION( subsumemaxeff, 1e8, 0,2e9,1,0,1, "maximum subsuming efficiency") \
OPTION( subsumemineff, 0, 0,2e9,1,0,1, "minimum subsuming efficiency") \
OPTION( subsumeocclim, 1e2, 0,2e9,1,0,1, "watch list length limit") \
OPTION( subsumestr, 1, 0, 1,0,0,1, "subsume strenghten") \
OPTION( sweep, 1, 0, 1,0,1,1, "enable SAT sweeping") \
OPTION( sweepclauses, 1024, 0,2e9,1,0,1, "environment clauses") \
OPTION( sweepcomplete, 0, 0, 1,0,0,1, "run SAT sweeping to completion") \
OPTION( sweepcountbinary, 1, 0, 1,0,0,1, "count binaries to environment") \
OPTION( sweepdepth, 2, 0,2e9,1,0,1, "environment depth") \
OPTION( sweepeffort, 1e2, 0,1e4,0,0,1, "relative effort in ticks per mille") \
OPTION( sweepfliprounds, 1, 0,2e9,1,0,1, "flipping rounds") \
OPTION( sweepmaxclauses, 3e5, 2,2e9,1,0,1, "maximum environment clauses") \
OPTION( sweepmaxdepth, 3, 1,2e9,1,0,1, "maximum environment depth") \
OPTION( sweepmaxvars, 8192, 2,2e9,1,0,1, "maximum environment variables") \
OPTION( sweeprand, 0, 0, 1,0,0,1, "randomize sweeping environment") \
OPTION( sweepthresh, 5, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \
OPTION( sweepvars, 256, 0,2e9,1,0,1, "environment variables") \
OPTION( target, 1, 0, 2,0,0,1, "target phases (1=stable only)") \
OPTION( terminateint, 10, 0,1e4,0,0,1, "termination check interval") \
OPTION( ternary, 1, 0, 1,0,1,1, "hyper ternary resolution") \
OPTION( ternaryeffort, 8, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( ternarymaxadd, 1e3, 0,1e4,1,0,1, "max clauses added in percent") \
OPTION( ternaryocclim, 1e2, 1,2e9,2,0,1, "ternary occurrence limit") \
OPTION( ternaryrounds, 2, 1, 16,1,0,1, "maximum ternary rounds") \
OPTION( ternarythresh, 6, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \
OPTION( tier1limit, 50, 0,100,0,0,1, "limit of tier1 usage in percentage") \
OPTION( tier2limit, 90, 0,100,0,0,1, "limit of tier2 usage in percentage") \
OPTION( transred, 1, 0, 1,0,1,1, "transitive reduction of BIG") \
OPTION( transredeffort, 1e2, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( transredmaxeff, 1e8, 0,2e9,1,0,1, "maximum efficiency") \
OPTION( transredmineff, 0, 0,2e9,1,0,1, "minimum efficiency") \
QUTOPT( verbose, 0, 0, 3,0,0,0, "more verbose messages") \
OPTION( veripb, 0, 0, 4,0,0,1, "odd=checkdeletions, > 2=drat") \
OPTION( vivify, 1, 0, 1,0,1,1, "vivification") \
OPTION( vivifycalctier, 0, 0, 1,0,0,1, "recalculate tier limits") \
OPTION( vivifydemote, 0, 0, 1,0,1,1, "demote irredundant or delete directly") \
OPTION( vivifyeffort, 50, 0,1e5,1,0,1, "overall efficiency per mille") \
OPTION( vivifyflush, 1, 0, 1,1,0,1, "flush subsumed before vivification rounds") \
OPTION( vivifyinst, 1, 0, 1,0,0,1, "instantiate last literal when vivify") \
OPTION( vivifyirred, 1, 0, 1,0,1,1, "vivification irred") \
OPTION( vivifyirredeff, 3, 1,100,1,0,1, "irredundant efficiency per mille") \
OPTION( vivifyonce, 0, 0, 2,0,0,1, "vivify once: 1=red, 2=red+irr") \
OPTION( vivifyretry, 0, 0, 5,0,0,1, "re-vivify clause if vivify was successful") \
OPTION( vivifyschedmax, 5e3, 10,2e9,0,0,1, "maximum schedule size") \
OPTION( vivifythresh, 20, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \
OPTION( vivifytier1, 1, 0, 1,0,1,1, "vivification tier1") \
OPTION( vivifytier1eff, 4, 0,100,1,0,1, "relative tier1 effort") \
OPTION( vivifytier2, 1, 0, 1,0,1,1, "vivification tier2") \
OPTION( vivifytier2eff, 2, 1,100,1,0,1, "relative tier2 effort") \
OPTION( vivifytier3, 1, 0, 1,0,1,1, "vivification tier3") \
OPTION( vivifytier3eff, 1, 1,100,1,0,1, "relative tier3 effort") \
OPTION( walk, 1, 0, 1,0,0,1, "enable random walks") \
OPTION( walkeffort, 20, 1,1e5,1,0,1, "relative efficiency per mille") \
OPTION( walkmaxeff, 1e7, 0,2e9,1,0,1, "maximum efficiency") \
OPTION( walkmineff, 0, 0,1e7,1,0,1, "minimum efficiency") \
OPTION( walknonstable, 1, 0, 1,0,0,1, "walk in non-stabilizing phase") \
OPTION( walkredundant, 0, 0, 1,0,0,1, "walk redundant clauses too") \
// Note, keep an empty line right before this line because of the last '\'!
// Also keep those single spaces after 'OPTION(' for proper sorting.
// clang-format on
/*------------------------------------------------------------------------*/
// Some of the 'OPTION' macros above should only be included if certain
// compile time options are enabled. This has the effect, that for instance
// if 'LOGGING' is defined, and thus logging code is included, then also the
// 'log' option is defined. Otherwise the 'log' option is not included.
#ifdef LOGGING
#define LOGOPT OPTION
#else
#define LOGOPT(...) /**/
#endif
#ifdef QUIET
#define QUTOPT(...) /**/
#else
#define QUTOPT OPTION
#endif
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
struct Internal;
/*------------------------------------------------------------------------*/
class Options;
struct Option {
const char *name;
int def, lo, hi;
int optimizable;
bool preprocessing;
const char *description;
int &val (Options *);
};
/*------------------------------------------------------------------------*/
// Produce a compile time constant for the number of options.
static const size_t number_of_options =
#define OPTION(N, V, L, H, O, P, R, D) 1 +
OPTIONS
#undef OPTION
+ 0;
/*------------------------------------------------------------------------*/
class Options {
Internal *internal;
void set (Option *, int val); // Force to [lo,hi] interval.
friend struct Option;
static Option table[];
static void initialize_from_environment (int &val, const char *name,
const int L, const int H);
friend Config;
void reset_default_values ();
void disable_preprocessing ();
public:
// For library usage we disable reporting by default while for the stand
// alone SAT solver we enable it by default. This default value has to
// be set before the constructor of 'Options' is called (which in turn is
// called from the constructor of 'Solver'). If we would simply overwrite
// its initial value while initializing the stand alone solver, we will
// get that change of the default value (from 'false' to 'true') shown
// during calls to 'print ()', which is confusing to the user.
//
static int reportdefault;
Options (Internal *);
// Makes options directly accessible, e.g., for instance declares the
// member 'int restart' here. This will give fast access to option values
// internally in the solver and thus can also be used in tight loops.
//
private:
int __start_of_options__; // Used by 'val' below.
public:
#define OPTION(N, V, L, H, O, P, R, D) \
int N; // Access option values by name.
OPTIONS
#undef OPTION
// It would be more elegant to use an anonymous 'struct' of the actual
// option values overlayed with an 'int values[number_of_options]' array
// but that is not proper ISO C++ and produces a warning. Instead we use
// the following construction which relies on '__start_of_options__' and
// that the following options are really allocated directly after it.
//
inline int &val (size_t idx) {
assert (idx < number_of_options);
return (&__start_of_options__ + 1)[idx];
}
// With the following function we can get rather fast access to the option
// limits, the default value and the description. The code uses binary
// search over the sorted option 'table'. This static data is shared
// among different instances of the solver. The actual current option
// values are here in the 'Options' class. They can be accessed by the
// offset of the static options using 'Option::val' if you have an
// 'Option' or to have even faster access directly by the member function
// (the 'N' above, e.g., 'restart').
//
static Option *has (const char *name);
bool set (const char *name, int); // Explicit version.
int get (const char *name); // Get current value.
void print (); // Print current values in command line form
static void usage (); // Print usage message for all options.
void optimize (int val); // increase some limits (val=0..31)
static bool is_preprocessing_option (const char *name);
// Parse long option argument
//
// --<name>
// --<name>=<val>
// --no-<name>
//
// where '<val>' is as in 'parse_option_value'. If parsing succeeds,
// 'true' is returned and the string will be set to the name of the
// option. Additionally the parsed value is set (last argument).
//
static bool parse_long_option (const char *, string &, int &);
// Iterating options.
typedef Option *iterator;
typedef const Option *const_iterator;
static iterator begin () { return table; }
static iterator end () { return table + number_of_options; }
void copy (Options &other) const; // Copy 'this' into 'other'.
};
inline int &Option::val (Options *opts) {
assert (Options::table <= this &&
this < Options::table + number_of_options);
return opts->val (this - Options::table);
}
} // namespace CaDiCaL
#endif

436
src/sat/cadical/parse.cpp Normal file
View File

@ -0,0 +1,436 @@
#include "internal.hpp"
/*------------------------------------------------------------------------*/
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Parse error.
#define PER(...) \
do { \
internal->error_message.init ( \
"%s:%" PRIu64 ": parse error: ", file->name (), \
(uint64_t) file->lineno ()); \
return internal->error_message.append (__VA_ARGS__); \
} while (0)
/*------------------------------------------------------------------------*/
// Parsing utilities.
inline int Parser::parse_char () { return file->get (); }
// Return an non zero error string if a parse error occurred.
inline const char *Parser::parse_string (const char *str, char prev) {
for (const char *p = str; *p; p++)
if (parse_char () == *p)
prev = *p;
else if (*p == ' ')
PER ("expected space after '%c'", prev);
else
PER ("expected '%c' after '%c'", *p, prev);
return 0;
}
inline const char *Parser::parse_positive_int (int &ch, int &res,
const char *name) {
assert (isdigit (ch));
res = ch - '0';
while (isdigit (ch = parse_char ())) {
int digit = ch - '0';
if (INT_MAX / 10 < res || INT_MAX - digit < 10 * res)
PER ("too large '%s' in header", name);
res = 10 * res + digit;
}
return 0;
}
static const char *cube_token = "unexpected 'a' in CNF";
inline const char *Parser::parse_lit (int &ch, int &lit, int &vars,
int strict) {
if (ch == 'a')
return cube_token;
int sign = 0;
if (ch == '-') {
if (!isdigit (ch = parse_char ()))
PER ("expected digit after '-'");
sign = -1;
} else if (!isdigit (ch))
PER ("expected digit or '-'");
else
sign = 1;
lit = ch - '0';
while (isdigit (ch = parse_char ())) {
int digit = ch - '0';
if (INT_MAX / 10 < lit || INT_MAX - digit < 10 * lit)
PER ("literal too large");
lit = 10 * lit + digit;
}
if (ch == '\r')
ch = parse_char ();
if (ch != 'c' && ch != ' ' && ch != '\t' && ch != '\n' && ch != EOF)
PER ("expected white space after '%d'", sign * lit);
if (lit > vars) {
if (strict != FORCED)
PER ("literal %d exceeds maximum variable %d", sign * lit, vars);
else
vars = lit;
}
lit *= sign;
return 0;
}
/*------------------------------------------------------------------------*/
// Parsing CNF in DIMACS format.
const char *Parser::parse_dimacs_non_profiled (int &vars, int strict) {
#ifndef QUIET
double start = internal->time ();
#endif
bool found_inccnf_header = false;
int ch, clauses = 0;
vars = 0;
// First read comments before header with possibly embedded options.
//
for (;;) {
ch = parse_char ();
if (strict != STRICT)
if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r')
continue;
if (ch != 'c')
break;
string buf;
while ((ch = parse_char ()) != '\n')
if (ch == EOF)
PER ("unexpected end-of-file in header comment");
else if (ch != '\r')
buf.push_back (ch);
const char *o;
for (o = buf.c_str (); *o && *o != '-'; o++)
;
if (!*o)
continue;
PHASE ("parse-dimacs", "found option '%s'", o);
if (*o)
solver->set_long_option (o);
}
if (ch != 'p')
PER ("expected 'c' or 'p'");
ch = parse_char ();
if (strict == STRICT) {
if (ch != ' ')
PER ("expected space after 'p'");
ch = parse_char ();
} else if (ch != ' ' && ch != '\t')
PER ("expected white space after 'p'");
else {
do
ch = parse_char ();
while (ch == ' ' || ch == '\t');
}
// Now read 'p cnf <var> <clauses>' header of DIMACS file
// or 'p inccnf' of incremental 'INCCNF' file.
//
if (ch == 'c') {
assert (!found_inccnf_header);
if (strict == STRICT) {
const char *err = parse_string ("nf ", 'c');
if (err)
return err;
ch = parse_char ();
if (!isdigit (ch))
PER ("expected digit after 'p cnf '");
err = parse_positive_int (ch, vars, "<max-var>");
if (err)
return err;
if (ch != ' ')
PER ("expected ' ' after 'p cnf %d'", vars);
if (!isdigit (ch = parse_char ()))
PER ("expected digit after 'p cnf %d '", vars);
err = parse_positive_int (ch, clauses, "<num-clauses>");
if (err)
return err;
if (ch != '\n')
PER ("expected new-line after 'p cnf %d %d'", vars, clauses);
} else {
if (parse_char () != 'n')
PER ("expected 'n' after 'p c'");
if (parse_char () != 'f')
PER ("expected 'f' after 'p cn'");
ch = parse_char ();
if (!isspace (ch))
PER ("expected space after 'p cnf'");
do
ch = parse_char ();
while (isspace (ch));
if (!isdigit (ch))
PER ("expected digit after 'p cnf '");
const char *err = parse_positive_int (ch, vars, "<max-var>");
if (err)
return err;
if (!isspace (ch))
PER ("expected space after 'p cnf %d'", vars);
do
ch = parse_char ();
while (isspace (ch));
if (!isdigit (ch))
PER ("expected digit after 'p cnf %d '", vars);
err = parse_positive_int (ch, clauses, "<num-clauses>");
if (err)
return err;
while (ch != '\n') {
if (ch != '\r' && !isspace (ch))
PER ("expected new-line after 'p cnf %d %d'", vars, clauses);
ch = parse_char ();
}
}
MSG ("found %s'p cnf %d %d'%s header", tout.green_code (), vars,
clauses, tout.normal_code ());
if (strict != FORCED)
solver->reserve (vars);
internal->reserve_ids (clauses);
} else if (!parse_inccnf_too)
PER ("expected 'c' after 'p '");
else if (ch == 'i') {
found_inccnf_header = true;
const char *err = parse_string ("nccnf", 'i');
if (err)
return err;
ch = parse_char ();
if (strict == STRICT) {
if (ch != '\n')
PER ("expected new-line after 'p inccnf'");
} else {
while (ch != '\n') {
if (ch != '\r' && !isspace (ch))
PER ("expected new-line after 'p inccnf'");
ch = parse_char ();
}
}
MSG ("found %s'p inccnf'%s header", tout.green_code (),
tout.normal_code ());
strict = FORCED;
} else
PER ("expected 'c' or 'i' after 'p '");
if (parse_inccnf_too)
*parse_inccnf_too = false;
// Now read body of DIMACS part.
//
int lit = 0, parsed = 0;
while ((ch = parse_char ()) != EOF) {
if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r')
continue;
if (ch == 'c') {
while ((ch = parse_char ()) != '\n' && ch != EOF)
;
if (ch == EOF)
break;
continue;
}
if (ch == 'a' && found_inccnf_header)
break;
const char *err = parse_lit (ch, lit, vars, strict);
if (err)
return err;
if (ch == 'c') {
while ((ch = parse_char ()) != '\n')
if (ch == EOF)
PER ("unexpected end-of-file in comment");
}
solver->add (lit);
if (!found_inccnf_header && !lit && parsed++ >= clauses &&
strict != FORCED)
PER ("too many clauses");
}
if (lit)
PER ("last clause without terminating '0'");
if (!found_inccnf_header && parsed < clauses && strict != FORCED)
PER ("clause missing");
#ifndef QUIET
double end = internal->time ();
MSG ("parsed %d clauses in %.2f seconds %s time", parsed, end - start,
internal->opts.realtime ? "real" : "process");
#endif
#ifndef QUIET
start = end;
size_t num_cubes = 0;
#endif
if (ch == 'a') {
assert (parse_inccnf_too);
assert (found_inccnf_header);
if (!*parse_inccnf_too)
*parse_inccnf_too = true;
for (;;) {
ch = parse_char ();
if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r')
continue;
if (ch == 'c') {
while ((ch = parse_char ()) != '\n' && ch != EOF)
;
if (ch == EOF)
break;
continue;
}
const char *err = parse_lit (ch, lit, vars, strict);
if (err == cube_token)
PER ("two 'a' in a row");
else if (err)
return err;
if (ch == 'c') {
while ((ch = parse_char ()) != '\n')
if (ch == EOF)
PER ("unexpected end-of-file in comment");
}
if (cubes)
cubes->push_back (lit);
if (!lit) {
#ifndef QUIET
num_cubes++;
#endif
for (;;) {
ch = parse_char ();
if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r')
continue;
if (ch == 'c') {
while ((ch = parse_char ()) != '\n' && ch != EOF)
;
if (ch == EOF)
break;
}
if (ch == EOF)
break;
if (ch != 'a')
PER ("expected 'a' or end-of-file after zero");
lit = INT_MIN;
break;
}
if (ch == EOF)
break;
}
}
if (lit)
PER ("last cube without terminating '0'");
}
#ifndef QUIET
if (found_inccnf_header) {
double end = internal->time ();
MSG ("parsed %zd cubes in %.2f seconds %s time", num_cubes, end - start,
internal->opts.realtime ? "real" : "process");
}
#endif
return 0;
}
/*------------------------------------------------------------------------*/
// Parsing solution in competition output format.
const char *Parser::parse_solution_non_profiled () {
external->solution = new signed char[external->max_var + 1u];
external->solution_size = external->max_var;
clear_n (external->solution, external->max_var + 1u);
int ch;
for (;;) {
ch = parse_char ();
if (ch == EOF)
PER ("missing 's' line");
else if (ch == 'c') {
while ((ch = parse_char ()) != '\n')
if (ch == EOF)
PER ("unexpected end-of-file in comment");
} else if (ch == 's')
break;
else
PER ("expected 'c' or 's'");
}
const char *err = parse_string (" SATISFIABLE", 's');
if (err)
return err;
if ((ch = parse_char ()) == '\r')
ch = parse_char ();
if (ch != '\n')
PER ("expected new-line after 's SATISFIABLE'");
#ifndef QUIET
int count = 0;
#endif
for (;;) {
ch = parse_char ();
if (ch != 'v')
PER ("expected 'v' at start-of-line");
if ((ch = parse_char ()) != ' ')
PER ("expected ' ' after 'v'");
int lit = 0;
ch = parse_char ();
do {
if (ch == ' ' || ch == '\t') {
ch = parse_char ();
continue;
}
err = parse_lit (ch, lit, external->max_var, false);
if (err)
return err;
if (ch == 'c')
PER ("unexpected comment");
if (!lit)
break;
if (external->solution[abs (lit)])
PER ("variable %d occurs twice", abs (lit));
LOG ("solution %d", lit);
external->solution[abs (lit)] = sign (lit);
#ifndef QUIET
count++;
#endif
if (ch == '\r')
ch = parse_char ();
} while (ch != '\n');
if (!lit)
break;
}
MSG ("parsed %d values %.2f%%", count,
percent (count, external->max_var));
return 0;
}
/*------------------------------------------------------------------------*/
// Wrappers to profile parsing and at the same time use the convenient
// implicit 'return' in PER in the non-profiled versions.
const char *Parser::parse_dimacs (int &vars, int strict) {
assert (strict == FORCED || strict == RELAXED || strict == STRICT);
START (parse);
const char *err = parse_dimacs_non_profiled (vars, strict);
STOP (parse);
return err;
}
const char *Parser::parse_solution () {
START (parse);
const char *err = parse_solution_non_profiled ();
STOP (parse);
return err;
}
} // namespace CaDiCaL

72
src/sat/cadical/parse.hpp Normal file
View File

@ -0,0 +1,72 @@
#ifndef _parse_hpp_INCLUDED
#define _parse_hpp_INCLUDED
#include <cassert>
#include <vector>
namespace CaDiCaL {
// Factors out common functions for parsing of DIMACS and solution files.
class File;
struct External;
struct Internal;
class Parser {
Solver *solver;
Internal *internal;
External *external;
File *file;
void perr (const char *fmt, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3);
int parse_char ();
enum {
FORCED = 0, // Force reading even if header is broken.
RELAXED = 1, // Relaxed white space treatment in header.
STRICT = 2, // Strict white space and header compliance.
};
const char *parse_string (const char *str, char prev);
const char *parse_positive_int (int &ch, int &res, const char *name);
const char *parse_lit (int &ch, int &lit, int &vars, int strict);
const char *parse_dimacs_non_profiled (int &vars, int strict);
const char *parse_solution_non_profiled ();
bool *parse_inccnf_too;
vector<int> *cubes;
public:
// Parse a DIMACS CNF or ICNF file.
//
// Return zero if successful. Otherwise parse error.
Parser (Solver *s, File *f, bool *i, vector<int> *c)
: solver (s), internal (s->internal), external (s->external),
file (f), parse_inccnf_too (i), cubes (c) {}
// Parse a DIMACS file. Return zero if successful. Otherwise a parse
// error is return. The parsed clauses are added to the solver and the
// maximum variable index found is returned in the 'vars' argument. The
// 'strict' argument can be '0' in which case the numbers in the header
// can be arbitrary, e.g., 'p cnf 0 0' all the time, without producing a
// parse error. Only for this setting the parsed literals are not checked
// to overflow the maximum variable index of the header. The strictest
// form of parsing is enforced for the value '2' of 'strict', in which
// case the header can not have additional white space, while a value of
// '1' exactly relaxes this, e.g., 'p cnf \t 1 3 \r\n' becomes legal.
//
const char *parse_dimacs (int &vars, int strict);
// Parse a solution file as used in the SAT competition, e.g., with
// comment lines 'c ...', a status line 's ...' and value lines 'v ...'.
// Returns zero if successful. Otherwise a string is returned describing
// the parse error. The parsed solution is saved in 'solution' and can be
// accessed with 'sol (int lit)'. We use it for checking learned clauses.
//
const char *parse_solution ();
};
} // namespace CaDiCaL
#endif

View File

@ -0,0 +1,44 @@
#include "internal.hpp"
namespace CaDiCaL {
void Internal::copy_phases (vector<signed char> &dst) {
START (copy);
for (auto i : vars)
dst[i] = phases.saved[i];
STOP (copy);
}
void Internal::clear_phases (vector<signed char> &dst) {
START (copy);
for (auto i : vars)
dst[i] = 0;
STOP (copy);
}
void Internal::phase (int lit) {
const int idx = vidx (lit);
signed char old_forced_phase = phases.forced[idx];
signed char new_forced_phase = sign (lit);
if (old_forced_phase == new_forced_phase) {
LOG ("forced phase remains at %d", old_forced_phase * idx);
return;
}
if (old_forced_phase)
LOG ("overwriting old forced phase %d", old_forced_phase * idx);
LOG ("new forced phase %d", new_forced_phase * idx);
phases.forced[idx] = new_forced_phase;
}
void Internal::unphase (int lit) {
const int idx = vidx (lit);
signed char old_forced_phase = phases.forced[idx];
if (!old_forced_phase) {
LOG ("forced phase of %d already reset", lit);
return;
}
LOG ("clearing old forced phase %d", old_forced_phase * idx);
phases.forced[idx] = 0;
}
} // namespace CaDiCaL

View File

@ -0,0 +1,18 @@
#ifndef _phases_hpp_INCLUDED
#define _phases_hpp_INCLUDED
namespace CaDiCaL {
struct Phases {
vector<signed char> best; // The current largest trail phase.
vector<signed char> forced; // Forced through 'phase'.
vector<signed char> min; // The current minimum unsatisfied phase.
vector<signed char> prev; // Previous during local search.
vector<signed char> saved; // The actual saved phase.
vector<signed char> target; // The current target phase.
};
} // namespace CaDiCaL
#endif

987
src/sat/cadical/probe.cpp Normal file
View File

@ -0,0 +1,987 @@
#include "internal.hpp"
namespace CaDiCaL {
/*------------------------------------------------------------------------*/
// Failed literal probing uses its own propagation and assignment
// functions. It further provides on-the-fly generation of hyper binary
// resolvents but only probes on roots of the binary implication graph. The
// search for failed literals is limited, but untried roots are kept until
// the next time 'probe' is called. Left over probes from the last attempt
// and new probes are tried until the limit is hit or all are tried.
/*------------------------------------------------------------------------*/
bool Internal::inprobing () {
if (!opts.inprobing)
return false;
if (!preprocessing && !opts.inprocessing)
return false;
if (preprocessing)
assert (lim.preprocessing);
if (stats.inprobingphases && last.inprobe.reductions == stats.reductions)
return false;
return lim.inprobe <= stats.conflicts;
}
/*------------------------------------------------------------------------*/
inline int Internal::get_parent_reason_literal (int lit) {
const int idx = vidx (lit);
int res = parents[idx];
if (lit < 0)
res = -res;
return res;
}
inline void Internal::set_parent_reason_literal (int lit, int reason) {
const int idx = vidx (lit);
if (lit < 0)
reason = -reason;
parents[idx] = reason;
}
/*-----------------------------------------------------------------------*/
// for opts.probehbr=false we need to do a lot of extra work to remember the
// correct lrat_chains... This solution is also memory intensive I think
// all corresponding functions are guarded to only work with the right
// options so they can be called without checking for options
//
// call locally after failed_literal or backtracking
//
void Internal::clean_probehbr_lrat () {
if (!lrat || opts.probehbr)
return;
for (auto &field : probehbr_chains) {
for (auto &chain : field) {
chain.clear ();
}
}
}
// call globally before a probe round (or a lookahead round)
//
void Internal::init_probehbr_lrat () {
if (!lrat || opts.probehbr)
return;
const size_t size = 2 * (1 + (size_t) max_var);
probehbr_chains.resize (size);
for (size_t i = 0; i < size; i++) {
probehbr_chains[i].resize (size);
// commented because not needed... should be empty already
/*
for (size_t j = 0; j < size; j++) {
vector<int64_t> empty;
probehbr_chains[i][j] = empty;
}
*/
}
}
// sets lrat_chain to the stored chain in probehbr_chains.
// this leads to conflict with unit reason uip
//
void Internal::get_probehbr_lrat (int lit, int uip) {
if (!lrat || opts.probehbr)
return;
assert (lit);
assert (lrat_chain.empty ());
assert (val (uip) < 0);
lrat_chain = probehbr_chains[vlit (lit)][vlit (uip)];
int64_t id = unit_id (-uip);
lrat_chain.push_back (id);
}
// sets the corresponding probehbr_chain to what is currently stored in
// lrat_chain. also clears lrat_chain.
//
void Internal::set_probehbr_lrat (int lit, int uip) {
if (!lrat || opts.probehbr)
return;
assert (lit);
assert (lrat_chain.size ());
assert (probehbr_chains[vlit (lit)][vlit (uip)].empty ());
probehbr_chains[vlit (lit)][vlit (uip)] = lrat_chain;
lrat_chain.clear ();
}
// compute lrat_chain for the part of the tree from lit to dom
// use mini_chain because it needs to be reversed
//
void Internal::probe_dominator_lrat (int dom, Clause *reason) {
if (!lrat || !dom)
return;
LOG (reason, "probe dominator LRAT for %d from", dom);
for (const auto lit : *reason) {
if (val (lit) >= 0)
continue;
const auto other = -lit;
if (other == dom)
continue;
Flags &f = flags (other);
if (f.seen)
continue;
f.seen = true;
analyzed.push_back (other);
Var u = var (other);
if (u.level) {
if (!u.reason) {
LOG ("this may be a problem %d", other);
continue;
}
probe_dominator_lrat (dom, u.reason);
continue;
}
int64_t id = unit_id (other);
lrat_chain.push_back (id);
}
lrat_chain.push_back (reason->id);
}
/*------------------------------------------------------------------------*/
// On-the-fly (dynamic) hyper binary resolution on decision level one can
// make use of the fact that the implication graph is actually a tree.
// Compute a dominator of two literals in the binary implication tree.
int Internal::probe_dominator (int a, int b) {
require_mode (PROBE);
int l = a, k = b;
Var *u = &var (l), *v = &var (k);
assert (val (l) > 0), assert (val (k) > 0);
assert (u->level == 1), assert (v->level == 1);
while (l != k) {
if (u->trail > v->trail)
swap (l, k), swap (u, v);
if (!get_parent_reason_literal (l))
return l;
int parent = get_parent_reason_literal (k);
assert (parent), assert (val (parent) > 0);
v = &var (k = parent);
assert (v->level == 1);
}
LOG ("dominator %d of %d and %d", l, a, b);
assert (val (l) > 0);
return l;
}
// The idea of dynamic on-the-fly hyper-binary resolution came up in the
// PrecoSAT solver, where it originally was used on all decision levels.
// It turned out, that most of the hyper-binary resolvents were generated
// during probing on decision level one anyhow. Thus this version is
// specialized to decision level one, where actually all long (non-binary)
// forcing clauses can be resolved to become binary. So if we find a clause
// which would force a new assignment at decision level one during probing
// we resolve it (the 'reason' argument) to obtain a hyper binary resolvent.
// It consists of the still unassigned literal (the new unit) and the
// negation of the unique closest dominator of the negation of all (false)
// literals in the clause (which has to exist on decision level one).
// There are two special cases which should be mentioned:
//
// (A) The reason is already a binary clause in a certain sense, since all
// its unwatched literals are root level fixed to false. In this
// situation it would be better to shrink the clause immediately instead
// of adding a new clause consisting only of the watched literals.
// However, this would happen during the next garbage collection anyhow.
//
// (B) The resolvent subsumes the original reason clause. This is
// equivalent to the property that the negated dominator is contained in
// the original reason. Again one could in principle shrink the clause.
//
// Note that (A) is actually subsumed by (B). The possible optimization to
// shrink the clause on-the-fly is difficult (need to update 'blit' and
// 'binary' of the other watch at least) and also not really that important.
// For (B) we simply add the new binary resolvent and mark the old subsumed
// clause as garbage instead. And since in the situation of (A) the
// shrinking will be performed at the next garbage collection anyhow, we
// do not change clauses in (A).
// The hyper binary resolvent clause is redundant unless it subsumes the
// original reason and that one is irredundant.
// If the option 'opts.probehbr' is 'false', we actually do not add the new
// hyper binary resolvent, but simply pretend we would have added it and
// still return the dominator as new reason / parent for the new unit.
// Finally note that adding clauses changes the watches of the propagated
// literal and thus we can not use standard iterators during probing but
// need to fall back to indices. One watch for the hyper binary resolvent
// clause is added at the end of the currently propagated watches, but its
// watch is a binary watch and will be skipped during propagating long
// clauses anyhow.
inline int Internal::hyper_binary_resolve (Clause *reason) {
require_mode (PROBE);
assert (level == 1);
assert (reason->size > 2);
const const_literal_iterator end = reason->end ();
const int *lits = reason->literals;
const_literal_iterator k;
#ifndef NDEBUG
// First literal unassigned, all others false.
assert (!val (lits[0]));
for (k = lits + 1; k != end; k++)
assert (val (*k) < 0);
assert (var (lits[1]).level == 1);
#endif
LOG (reason, "hyper binary resolving");
stats.hbrs++;
stats.hbrsizes += reason->size;
const int lit = lits[1];
int dom = -lit, non_root_level_literals = 0;
for (k = lits + 2; k != end; k++) {
const int other = -*k;
assert (val (other) > 0);
if (!var (other).level)
continue;
dom = probe_dominator (dom, other);
non_root_level_literals++;
}
probe_reason = reason;
if (non_root_level_literals && opts.probehbr) { // !(A)
bool contained = false;
for (k = lits + 1; !contained && k != end; k++)
contained = (*k == -dom);
const bool red = !contained || reason->redundant;
if (red)
stats.hbreds++;
LOG ("new %s hyper binary resolvent %d %d",
(red ? "redundant" : "irredundant"), -dom, lits[0]);
assert (clause.empty ());
clause.push_back (-dom);
clause.push_back (lits[0]);
probe_dominator_lrat (dom, reason);
if (lrat)
clear_analyzed_literals ();
Clause *c = new_hyper_binary_resolved_clause (red, 2);
probe_reason = c;
if (red)
c->hyper = true;
clause.clear ();
lrat_chain.clear ();
if (contained) {
stats.hbrsubs++;
LOG (reason, "subsumed original");
mark_garbage (reason);
}
} else if (non_root_level_literals && lrat) {
// still calculate LRAT and remember for later
assert (!opts.probehbr);
probe_dominator_lrat (dom, reason);
clear_analyzed_literals ();
set_probehbr_lrat (dom, lits[0]);
}
return dom;
}
/*------------------------------------------------------------------------*/
// The following functions 'probe_assign' and 'probe_propagate' are used for
// propagating during failed literal probing in simplification mode, as
// replacement of the generic propagation routine 'propagate' and
// 'search_assign'.
// The code is mostly copied from 'propagate.cpp' and specialized. We only
// comment on the differences. More explanations are in 'propagate.cpp'.
inline void Internal::probe_assign (int lit, int parent) {
require_mode (PROBE);
int idx = vidx (lit);
assert (!val (idx));
assert (!flags (idx).eliminated () || !parent);
assert (!parent || val (parent) > 0);
Var &v = var (idx);
v.level = level;
v.trail = (int) trail.size ();
assert ((int) num_assigned < max_var);
num_assigned++;
v.reason = level ? probe_reason : 0;
probe_reason = 0;
set_parent_reason_literal (lit, parent);
if (!level)
learn_unit_clause (lit);
else
assert (level == 1);
const signed char tmp = sign (lit);
set_val (idx, tmp);
assert (val (lit) > 0);
assert (val (-lit) < 0);
trail.push_back (lit);
// Do not save the current phase during inprocessing but remember the
// number of units on the trail of the last time this literal was
// assigned. This allows us to avoid some redundant failed literal
// probing attempts. Search for 'propfixed' in 'probe.cpp' for details.
//
if (level)
propfixed (lit) = stats.all.fixed;
if (parent)
LOG ("probe assign %d parent %d", lit, parent);
else if (level)
LOG ("probe assign %d probe", lit);
else
LOG ("probe assign %d negated failed literal UIP", lit);
}
void Internal::probe_assign_decision (int lit) {
require_mode (PROBE);
assert (!level);
assert (propagated == trail.size ());
level++;
control.push_back (Level (lit, trail.size ()));
probe_assign (lit, 0);
}
void Internal::probe_assign_unit (int lit) {
require_mode (PROBE);
assert (!level);
assert (active (lit));
probe_assign (lit, 0);
}
/*------------------------------------------------------------------------*/
// same as in propagate but inlined here
//
inline void Internal::probe_lrat_for_units (int lit) {
if (!lrat)
return;
if (level)
return; // not decision level 0
LOG ("building chain for units");
assert (lrat_chain.empty ());
assert (probe_reason);
for (auto &reason_lit : *probe_reason) {
if (lit == reason_lit)
continue;
assert (val (reason_lit));
if (!val (reason_lit))
continue;
const int signed_reason_lit = val (reason_lit) * reason_lit;
int64_t id = unit_id (signed_reason_lit);
lrat_chain.push_back (id);
}
lrat_chain.push_back (probe_reason->id);
}
/*------------------------------------------------------------------------*/
// This is essentially the same as 'propagate' except that we prioritize and
// always propagate binary clauses first (see our CPAIOR'13 paper on tree
// based look ahead), then immediately stop at a conflict and of course use
// 'probe_assign' instead of 'search_assign'. The binary propagation part
// is factored out too. If a new unit on decision level one is found we
// perform hyper binary resolution and thus actually build an implication
// tree instead of a DAG. Statistics counters are also different.
inline void Internal::probe_propagate2 () {
require_mode (PROBE);
int64_t &ticks = stats.ticks.probe;
while (propagated2 != trail.size ()) {
const int lit = -trail[propagated2++];
LOG ("probe propagating %d over binary clauses", -lit);
Watches &ws = watches (lit);
ticks += 1 + cache_lines (ws.size (), sizeof (const_watch_iterator *));
for (const auto &w : ws) {
if (!w.binary ())
continue;
const signed char b = val (w.blit);
if (b > 0)
continue;
ticks++;
if (b < 0)
conflict = w.clause; // but continue
else {
assert (lrat_chain.empty ());
assert (!probe_reason);
probe_reason = w.clause;
probe_lrat_for_units (w.blit);
probe_assign (w.blit, -lit);
lrat_chain.clear ();
}
}
}
}
bool Internal::probe_propagate () {
require_mode (PROBE);
assert (!unsat);
START (propagate);
int64_t before = propagated2 = propagated;
int64_t &ticks = stats.ticks.probe;
while (!conflict) {
if (propagated2 != trail.size ())
probe_propagate2 ();
else if (propagated != trail.size ()) {
const int lit = -trail[propagated++];
LOG ("probe propagating %d over large clauses", -lit);
Watches &ws = watches (lit);
ticks += 1 + cache_lines (ws.size (),
sizeof (sizeof (const_watch_iterator *)));
size_t i = 0, j = 0;
while (i != ws.size ()) {
const Watch w = ws[j++] = ws[i++];
if (w.binary ())
continue;
const signed char b = val (w.blit);
if (b > 0)
continue;
ticks++;
if (w.clause->garbage)
continue;
const literal_iterator lits = w.clause->begin ();
const int other = lits[0] ^ lits[1] ^ lit;
// lits[0] = other, lits[1] = lit;
const signed char u = val (other);
if (u > 0)
ws[j - 1].blit = other;
else {
const int size = w.clause->size;
const const_literal_iterator end = lits + size;
const literal_iterator middle = lits + w.clause->pos;
literal_iterator k = middle;
int r = 0;
signed char v = -1;
while (k != end && (v = val (r = *k)) < 0)
k++;
if (v < 0) {
k = lits + 2;
assert (w.clause->pos <= size);
while (k != middle && (v = val (r = *k)) < 0)
k++;
}
w.clause->pos = k - lits;
assert (lits + 2 <= k), assert (k <= w.clause->end ());
if (v > 0)
ws[j - 1].blit = r;
else if (!v) {
ticks++;
LOG (w.clause, "unwatch %d in", r);
*k = lit;
lits[0] = other;
lits[1] = r;
watch_literal (r, lit, w.clause);
j--;
} else if (!u) {
ticks++;
if (level == 1) {
lits[0] = other, lits[1] = lit;
assert (lrat_chain.empty ());
assert (!probe_reason);
int dom = hyper_binary_resolve (w.clause);
probe_assign (other, dom);
} else {
ticks++;
assert (lrat_chain.empty ());
assert (!probe_reason);
probe_reason = w.clause;
probe_lrat_for_units (other);
probe_assign_unit (other);
lrat_chain.clear ();
}
probe_propagate2 ();
} else
conflict = w.clause;
}
}
if (j != i) {
while (i != ws.size ())
ws[j++] = ws[i++];
ws.resize (j);
}
} else
break;
}
int64_t delta = propagated2 - before;
stats.propagations.probe += delta;
if (conflict)
LOG (conflict, "conflict");
STOP (propagate);
return !conflict;
}
/*------------------------------------------------------------------------*/
// This a specialized instance of 'analyze'.
void Internal::failed_literal (int failed) {
LOG ("analyzing failed literal probe %d", failed);
stats.failed++;
stats.probefailed++;
assert (!unsat);
assert (conflict);
assert (level == 1);
assert (analyzed.empty ());
assert (lrat_chain.empty ());
START (analyze);
LOG (conflict, "analyzing failed literal conflict");
int uip = 0;
for (const auto &lit : *conflict) {
const int other = -lit;
if (!var (other).level) {
assert (val (other) > 0);
continue;
}
uip = uip ? probe_dominator (uip, other) : other;
}
probe_dominator_lrat (uip, conflict);
if (lrat)
clear_analyzed_literals ();
LOG ("found probing UIP %d", uip);
assert (uip);
vector<int> work;
int parent = uip;
while (parent != failed) {
const int next = get_parent_reason_literal (parent);
parent = next;
assert (parent);
work.push_back (parent);
}
backtrack ();
conflict = 0;
assert (!val (uip));
probe_assign_unit (-uip);
lrat_chain.clear ();
if (!probe_propagate ())
learn_empty_clause ();
size_t j = 0;
while (!unsat && j < work.size ()) {
// assert (!opts.probehbr); assertion fails ...
const int parent = work[j++];
const signed char tmp = val (parent);
if (tmp > 0) {
assert (!opts.probehbr); // ... assertion should hold here
get_probehbr_lrat (parent, uip);
LOG ("clashing failed parent %d", parent);
learn_empty_clause ();
} else if (tmp == 0) {
assert (!opts.probehbr); // ... and here
LOG ("found unassigned failed parent %d", parent);
get_probehbr_lrat (parent, uip); // this is computed during
probe_assign_unit (-parent); // propagation and can include
lrat_chain.clear (); // multiple chains where only one
if (!probe_propagate ())
learn_empty_clause (); // is needed!
}
uip = parent;
}
work.clear ();
erase_vector (work);
STOP (analyze);
assert (unsat || val (failed) < 0);
}
/*------------------------------------------------------------------------*/
bool Internal::is_binary_clause (Clause *c, int &a, int &b) {
assert (!level);
if (c->garbage)
return false;
int first = 0, second = 0;
for (const auto &lit : *c) {
const signed char tmp = val (lit);
if (tmp > 0)
return false;
if (tmp < 0)
continue;
if (second)
return false;
if (first)
second = lit;
else
first = lit;
}
if (!second)
return false;
a = first, b = second;
return true;
}
// We probe on literals first, which occur more often negated and thus we
// sort the 'probes' stack in such a way that literals which occur negated
// less frequently come first. Probes are taken from the back of the stack.
struct probe_negated_noccs_rank {
Internal *internal;
probe_negated_noccs_rank (Internal *i) : internal (i) {}
typedef size_t Type;
Type operator() (int a) const { return internal->noccs (-a); }
};
// Fill the 'probes' schedule.
void Internal::generate_probes () {
assert (probes.empty ());
int64_t &ticks = stats.ticks.probe;
// First determine all the literals which occur in binary clauses. It is
// way faster to go over the clauses once, instead of walking the watch
// lists for each literal.
//
init_noccs ();
ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *));
for (const auto &c : clauses) {
int a, b;
ticks++;
if (!is_binary_clause (c, a, b))
continue;
noccs (a)++;
noccs (b)++;
}
for (auto idx : vars) {
// Then focus on roots of the binary implication graph, which are
// literals occurring negatively in a binary clause, but not positively.
// If neither 'idx' nor '-idx' is a root it makes less sense to probe
// this variable.
// This argument requires that equivalent literal substitution through
// 'decompose' is performed, because otherwise there might be 'cyclic
// roots' which are not tried, i.e., -1 2 0, 1 -2 0, 1 2 3 0, 1 2 -3 0.
ticks += 2;
const bool have_pos_bin_occs = noccs (idx) > 0;
const bool have_neg_bin_occs = noccs (-idx) > 0;
if (have_pos_bin_occs == have_neg_bin_occs)
continue;
int probe = have_neg_bin_occs ? idx : -idx;
// See the discussion where 'propfixed' is used below.
//
if (propfixed (probe) >= stats.all.fixed)
continue;
LOG ("scheduling probe %d negated occs %" PRId64 "", probe,
noccs (-probe));
probes.push_back (probe);
}
rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this));
reset_noccs ();
shrink_vector (probes);
PHASE ("probe-round", stats.probingrounds,
"scheduled %zd literals %.0f%%", probes.size (),
percent (probes.size (), 2u * max_var));
}
// Follow the ideas in 'generate_probes' but flush non root probes and
// reorder remaining probes.
void Internal::flush_probes () {
assert (!probes.empty ());
int64_t &ticks = stats.ticks.probe;
init_noccs ();
ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *));
for (const auto &c : clauses) {
int a, b;
ticks++;
if (!is_binary_clause (c, a, b))
continue;
noccs (a)++;
noccs (b)++;
}
const auto eop = probes.end ();
auto j = probes.begin ();
for (auto i = j; i != eop; i++) {
int lit = *i;
if (!active (lit))
continue;
ticks += 2;
const bool have_pos_bin_occs = noccs (lit) > 0;
const bool have_neg_bin_occs = noccs (-lit) > 0;
if (have_pos_bin_occs == have_neg_bin_occs)
continue;
if (have_pos_bin_occs)
lit = -lit;
assert (!noccs (lit)), assert (noccs (-lit) > 0);
if (propfixed (lit) >= stats.all.fixed)
continue;
LOG ("keeping probe %d negated occs %" PRId64 "", lit, noccs (-lit));
*j++ = lit;
}
size_t remain = j - probes.begin ();
#ifndef QUIET
size_t flushed = probes.size () - remain;
#endif
probes.resize (remain);
rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this));
reset_noccs ();
shrink_vector (probes);
PHASE ("probe-round", stats.probingrounds,
"flushed %zd literals %.0f%% remaining %zd", flushed,
percent (flushed, remain + flushed), remain);
}
int Internal::next_probe () {
int generated = 0;
for (;;) {
if (probes.empty ()) {
if (generated++)
return 0;
generate_probes ();
}
while (!probes.empty ()) {
int probe = probes.back ();
probes.pop_back ();
// Eliminated or assigned.
//
if (!active (probe))
continue;
// There is now new unit since the last time we propagated this probe,
// thus we propagated it before without obtaining a conflict and
// nothing changed since then. Thus there is no need to propagate it
// again. This observation was independently made by Partik Simons
// et.al. in the context of implementing 'smodels' (see for instance
// Alg. 4 in his JAIR article from 2002) and it has also been
// contributed to the thesis work of Yacine Boufkhad.
//
if (propfixed (probe) >= stats.all.fixed)
continue;
return probe;
}
}
}
bool Internal::probe () {
if (!opts.probe)
return false;
if (unsat)
return false;
if (terminated_asynchronously ())
return false;
SET_EFFORT_LIMIT (limit, probe, true);
START_SIMPLIFIER (probe, PROBE);
stats.probingrounds++;
// Probing is limited in terms of non-probing propagations
// 'stats.propagations'. We allow a certain percentage 'opts.probeeffort'
// (say %5) of probing propagations in each probing with a lower bound of
// 'opts.probmineff'.
//
PHASE ("probe-round", stats.probingrounds,
"probing limit of %" PRId64 " propagations ", limit);
int old_failed = stats.failed;
#ifndef QUIET
int64_t old_probed = stats.probed;
#endif
int64_t old_hbrs = stats.hbrs;
if (!probes.empty ())
flush_probes ();
// We reset 'propfixed' since there was at least another conflict thus
// a new learned clause, which might produce new propagations (and hyper
// binary resolvents). During 'generate_probes' we keep the old value.
//
for (auto idx : vars)
propfixed (idx) = propfixed (-idx) = -1;
assert (unsat || propagated == trail.size ());
propagated = propagated2 = trail.size ();
int probe;
init_probehbr_lrat ();
while (!unsat && !terminated_asynchronously () &&
stats.ticks.probe < limit && (probe = next_probe ())) {
stats.probed++;
LOG ("probing %d", probe);
probe_assign_decision (probe);
if (probe_propagate ())
backtrack ();
else
failed_literal (probe);
clean_probehbr_lrat ();
}
if (unsat)
LOG ("probing derived empty clause");
else if (propagated < trail.size ()) {
LOG ("probing produced %zd units",
(size_t) (trail.size () - propagated));
if (!propagate ()) {
LOG ("propagating units after probing results in empty clause");
learn_empty_clause ();
} else
sort_watches ();
}
int failed = stats.failed - old_failed;
#ifndef QUIET
int64_t probed = stats.probed - old_probed;
#endif
int64_t hbrs = stats.hbrs - old_hbrs;
PHASE ("probe-round", stats.probingrounds,
"probed %" PRId64 " and found %d failed literals", probed, failed);
if (hbrs)
PHASE ("probe-round", stats.probingrounds,
"found %" PRId64 " hyper binary resolvents", hbrs);
STOP_SIMPLIFIER (probe, PROBE);
report ('p', !opts.reportall && !(unsat + failed + hbrs));
return !unsat && failed;
}
/*------------------------------------------------------------------------*/
// This schedules a number of inprocessing techniques.
// These range from very cheap and beneficial (decompose) to
// more expensive and sometimes less beneficial. We want to limit
// expensive techniques to some fraction of total time or search time.
// this is done using 'ticks'.
// Generally, there are options for each of the techniques to set the
// efficiency, i.e., the fraction of ticks they are allowed as budget.
// Whenever e.g. vivify is called, the budget is calculated from the
// search ticks that have passed since the last vivify round and this
// efficiency.
// We want to be able to run inprocessing frequently, without it dominating
// runtimes. This entire inprocessing scheme is scheduled after a certain
// amount of conflicts were found, the gap between two inprocessing rounds
// increasing by a constant number each time. In effect, the number of
// inprocessing rounds is allways the square root of the number of conflicts
// with some constant factor.
// This factor can also be with the option 'inprobeint'
// Some of the techniques are not run always, for different reasons.
// 'factor' or BVA depends on certain structures of the irredundant clauses
// and as such will only be run when new irredundant clauses are derived or
// it was not able to finish with the entire search space.
// 'sweeping' is especially usefull on certain classes of formulas, and uses
// a increasing or decreasing delay that depends on how usefull it was.
// In cases where it is less usefull, we obviously want to reset the budged,
// even if the routine was delayed.
// Additionally 'vivify', 'sweep' and 'factor' can also have a big initial
// overhead in setting up the datastructures. This has to be accounted for
// with the 'ticks', however, since inprocessing is done frequently, this
// overhead is too expensive to pay. So instead, we accumulate the budget
// of 'ticks' and delay the technique until it passes a certain threshhold,
// which depends on the the cost of initialization. Note that in the case of
// sweeping, we have two different delays, one which resets the budged, and
// one which passes it to the next round. In this case the former takes
// precendent, until we would run sweeping once, at which point the focus
// switches to the latter delay until the budget is big enough, such that
// sweeping can be run. Then we switch back to the other delay.
void CaDiCaL::Internal::inprobe (bool update_limits) {
if (unsat)
return;
if (level)
backtrack ();
if (!propagate ()) {
learn_empty_clause ();
return;
}
stats.inprobingphases++;
if (external_prop) {
assert (!level);
private_steps = true;
}
const int before = active ();
const int before_extended = stats.variables_extension;
// schedule of inprobing techniques.
//
{
mark_duplicated_binary_clauses_as_garbage ();
decompose ();
if (ternary ())
decompose (); // If we derived a binary clause
if (probe ())
decompose ();
if (extract_gates ())
decompose ();
if (sweep ()) // full occurrence list
decompose (); // ... and (ELS) afterwards.
(void) vivify (); // resets watches
transred (); // builds big.
factor (); // resets watches, partial occurrence list
}
if (external_prop) {
assert (!level);
private_steps = false;
}
if (!update_limits)
return;
const int after = active ();
const int after_extended = stats.variables_extension;
const int diff_extended = after_extended - before_extended;
assert (diff_extended >= 0);
const int removed = before - after + diff_extended;
assert (removed >= 0);
if (removed) {
stats.inprobesuccess++;
PHASE ("probe-phase", stats.inprobingphases,
"successfully removed %d active variables %.0f%%", removed,
percent (removed, before));
} else
PHASE ("probe-phase", stats.inprobingphases,
"could not remove any active variable");
const int64_t delta =
25 * opts.inprobeint * log10 (stats.inprobingphases + 9);
lim.inprobe = stats.conflicts + delta;
PHASE ("probe-phase", stats.inprobingphases,
"new limit at %" PRId64 " conflicts after %" PRId64 " conflicts",
lim.inprobe, delta);
last.inprobe.reductions = stats.reductions;
}
} // namespace CaDiCaL

107
src/sat/cadical/profile.cpp Normal file
View File

@ -0,0 +1,107 @@
#ifndef QUIET
#include "internal.hpp"
namespace CaDiCaL {
// Initialize all profile counters with constant name and profiling level.
Profiles::Profiles (Internal *s)
: internal (s)
#define PROFILE(NAME, LEVEL) , NAME (#NAME, LEVEL)
PROFILES
#undef PROFILE
{
}
void Internal::start_profiling (Profile &profile, double s) {
assert (profile.level <= opts.profile);
assert (!profile.active);
profile.started = s;
profile.active = true;
}
void Internal::stop_profiling (Profile &profile, double s) {
assert (profile.level <= opts.profile);
assert (profile.active);
profile.value += s - profile.started;
profile.active = false;
}
double Internal::update_profiles () {
double now = time ();
#define PROFILE(NAME, LEVEL) \
do { \
Profile &profile = profiles.NAME; \
if (profile.active) { \
assert (profile.level <= opts.profile); \
profile.value += now - profile.started; \
profile.started = now; \
} \
} while (0);
PROFILES
#undef PROFILE
return now;
}
double Internal::solve_time () {
(void) update_profiles ();
return profiles.solve.value;
}
#define PRT(S, T) \
MSG ("%s" S "%s", tout.magenta_code (), T, tout.normal_code ())
void Internal::print_profile () {
double now = update_profiles ();
const char *time_type = opts.realtime ? "real" : "process";
SECTION ("run-time profiling");
PRT ("%s time taken by individual solving procedures", time_type);
PRT ("(percentage relative to %s time for solving)", time_type);
LINE ();
const size_t size = sizeof profiles / sizeof (Profile);
struct Profile *profs[size];
size_t n = 0;
#define PROFILE(NAME, LEVEL) \
do { \
if (LEVEL > opts.profile) \
break; \
Profile *p = &profiles.NAME; \
if (p == &profiles.solve) \
break; \
if (!profiles.NAME.value && p != &profiles.parse && \
p != &profiles.search && p != &profiles.simplify) \
break; \
profs[n++] = p; \
} while (0);
PROFILES
#undef PROFILE
assert (n <= size);
// Explicit bubble sort to avoid heap allocation since 'print_profile'
// is also called during catching a signal after out of heap memory.
// This only makes sense if 'profs' is allocated on the stack, and
// not the heap, which should be the case.
double solve = profiles.solve.value;
for (size_t i = 0; i < n; i++) {
for (size_t j = i + 1; j < n; j++)
if (profs[j]->value > profs[i]->value)
swap (profs[i], profs[j]);
MSG ("%12.2f %7.2f%% %s", profs[i]->value,
percent (profs[i]->value, solve), profs[i]->name);
}
MSG (" =================================");
MSG ("%12.2f %7.2f%% solve", solve, percent (solve, now));
LINE ();
PRT ("last line shows %s time for solving", time_type);
PRT ("(percentage relative to total %s time)", time_type);
}
} // namespace CaDiCaL
#endif // ifndef QUIET

Some files were not shown because too many files have changed in this diff Show More