mirror of https://github.com/YosysHQ/abc.git
cadical original
This commit is contained in:
parent
fbd19056e7
commit
f544165a88
|
|
@ -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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
2.2.0-rc1
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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 ¬_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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in New Issue