#include "internal.hpp"
#include <cstdint>
namespace CaDiCaL {
struct Tagged {
bool binary : 1;
unsigned counter_pos : 31;
#ifndef NDEBUG
Clause *c;
#endif
explicit Tagged () { assert (false); }
explicit Tagged (Clause *d, unsigned pos)
: binary (d->size == 2), counter_pos (pos) {
assert ((pos & (1 << 31)) == 0);
#ifndef NDEBUG
c = d;
#endif
}
};
struct Counter {
unsigned count; unsigned pos; Clause *clause; explicit Counter (unsigned c, unsigned p, Clause *d)
: count (c), pos (p), clause (d) {}
explicit Counter (unsigned c, Clause *d)
: count (c), pos (UINT32_MAX), clause (d) {}
};
struct WalkerFO {
Internal *internal;
Random random; int64_t ticks; int64_t limit; vector<Tagged> broken; double epsilon; vector<double> table; vector<double> scores; std::vector<int>
flips; int best_trail_pos;
int64_t minimum = INT64_MAX;
std::vector<signed char> best_values; double score (unsigned);
std::vector<Counter> tclauses;
std::vector<std::vector<Tagged>> tcounters;
using TOccs = std::vector<Tagged>;
TOccs &occs (int lit) {
const int idx = internal->vlit (lit);
assert ((size_t) idx < tcounters.size ());
return tcounters[idx];
}
const TOccs &occs (int lit) const {
const int idx = internal->vlit (lit);
assert ((size_t) idx < tcounters.size ());
return tcounters[idx];
}
void connect_clause (int lit, Clause *clause, unsigned pos) {
assert (pos < tclauses.size ());
assert (tclauses[pos].clause == clause);
LOG (clause, "connecting clause on %d with already in occurrences %zu",
lit, occs (lit).size ());
occs (lit).push_back (Tagged (clause, pos));
}
void connect_clause (Clause *clause, unsigned pos) {
assert (pos < tclauses.size ());
assert (tclauses[pos].clause == clause);
for (auto lit : *clause)
connect_clause (lit, clause, pos);
}
void check_occs () const {
#ifndef NDEBUG
for (auto lit : internal->lits) {
for (auto w : occs (lit)) {
assert (w.counter_pos < tclauses.size ());
}
}
unsigned unsatisfied = 0;
for (auto c : tclauses) {
unsigned count = 0;
LOG (c.clause, "checking clause with counter %d:", c.count);
for (auto lit : *c.clause) {
if (internal->val (lit) > 0)
++count;
}
assert (count == c.count);
if (!count)
++unsatisfied;
}
assert (broken.size () == unsatisfied);
#endif
}
void check_broken () const {
#ifndef NDEBUG
for (size_t i = 0; i < broken.size (); ++i) {
const Tagged t = broken[i];
assert (t.c);
assert (t.counter_pos < tclauses.size ());
assert (tclauses[t.counter_pos].clause == t.c);
for (auto lit : *t.c) {
assert (internal->val (lit) < 0);
}
}
#endif
}
void check_all () const {
check_broken ();
check_occs ();
}
static const uint32_t invalid_position = UINT32_MAX;
void make_clause (Tagged t);
WalkerFO (Internal *, double size, int64_t limit);
void push_flipped (int flipped);
void save_walker_trail (bool);
void save_final_minimum (int64_t old_minimum);
void make_clauses_along_occurrences (int lit);
void make_clauses_along_unsatisfied (int lit);
void make_clauses (int lit);
void break_clauses (int lit);
unsigned walk_full_occs_pick_clause ();
void walk_full_occs_flip_lit (int lit);
int walk_full_occs_pick_lit (Clause *);
unsigned walk_full_occs_break_value (int lit);
};
static double cbvals[][2] = {
{0.0, 2.00}, {3.0, 2.50}, {4.0, 2.85}, {5.0, 3.70},
{6.0, 5.10}, {7.0, 7.40}, };
static const int ncbvals = sizeof cbvals / sizeof cbvals[0];
inline static double fitcbval (double size) {
int i = 0;
while (i + 2 < ncbvals &&
(cbvals[i][0] > size || cbvals[i + 1][0] < size))
i++;
const double x2 = cbvals[i + 1][0], x1 = cbvals[i][0];
const double y2 = cbvals[i + 1][1], y1 = cbvals[i][1];
const double dx = x2 - x1, dy = y2 - y1;
assert (dx);
const double res = dy * (size - x1) / dx + y1;
assert (res > 0);
return res;
}
WalkerFO::WalkerFO (Internal *i, double size, int64_t l)
: internal (i), random (internal->opts.seed), ticks (0), limit (l), best_trail_pos (-1) {
random += internal->stats.walk.count; flips.reserve (i->max_var / 4);
best_values.resize (i->max_var + 1, 0);
tcounters.resize (i->max_var * 2 + 2);
const bool use_size_based_cb = (internal->stats.walk.count & 1);
const double cb = use_size_based_cb ? fitcbval (size) : 2.0;
assert (cb);
const double base = 1 / cb;
double next = 1;
for (epsilon = next; next; next = epsilon * base)
table.push_back (epsilon = next);
PHASE ("walk", internal->stats.walk.count,
"CB %.2f with inverse %.2f as base and table size %zd", cb, base,
table.size ());
}
void WalkerFO::push_flipped (int flipped) {
LOG ("push literal %s on the flips", LOGLIT (flipped));
assert (flipped);
if (best_trail_pos < 0) {
LOG ("not pushing flipped %s to already invalid trail",
LOGLIT (flipped));
return;
}
const size_t size_trail = flips.size ();
const size_t limit = internal->max_var / 4 + 1;
if (size_trail < limit) {
flips.push_back (flipped);
LOG ("pushed flipped %s to trail which now has size %zd",
LOGLIT (flipped), size_trail + 1);
return;
}
if (best_trail_pos) {
LOG ("trail reached limit %zd but has best position %d", limit,
best_trail_pos);
save_walker_trail (true);
flips.push_back (flipped);
LOG ("pushed flipped %s to trail which now has size %zu",
LOGLIT (flipped), flips.size ());
return;
} else {
LOG ("trail reached limit %zd without best position", limit);
flips.clear ();
LOG ("not pushing %s to invalidated trail", LOGLIT (flipped));
best_trail_pos = -1;
LOG ("best trail position becomes invalid");
}
}
void WalkerFO::save_walker_trail (bool keep) {
assert (best_trail_pos != -1);
assert ((size_t) best_trail_pos <= flips.size ());
#ifdef LOGGING
const size_t size_trail = flips.size ();
#endif
const int kept = flips.size () - best_trail_pos;
LOG ("saving %d values of flipped literals on trail of size %zd",
best_trail_pos, size_trail);
const auto begin = flips.begin ();
const auto best = flips.begin () + best_trail_pos;
const auto end = flips.end ();
auto it = begin;
for (; it != best; ++it) {
const int lit = *it;
assert (lit);
const signed char value = sign (lit);
const int idx = std::abs (lit);
best_values[idx] = value;
}
if (!keep) {
LOG ("no need to shift and keep remaining %u literals", kept);
return;
}
#ifndef NDEBUG
for (auto v : internal->vars) {
if (internal->active (v))
assert (best_values[v] == internal->phases.saved[v]);
}
#endif
LOG ("flushed %u literals %.0f%% from trail", best_trail_pos,
percent (best_trail_pos, size_trail));
assert (it == best);
auto jt = begin;
for (; it != end; ++it, ++jt) {
assert (jt <= it);
assert (it < end);
*jt = *it;
}
assert ((int) (end - jt) == best_trail_pos);
assert ((int) (jt - begin) == kept);
flips.resize (kept);
LOG ("keeping %u literals %.0f%% on trail", kept,
percent (kept, size_trail));
LOG ("reset best trail position to 0");
best_trail_pos = 0;
}
void WalkerFO::save_final_minimum (int64_t old_init_minimum) {
assert (minimum <= old_init_minimum);
#ifdef NDEBUG
(void) old_init_minimum;
#endif
if (!best_trail_pos || best_trail_pos == -1)
LOG ("minimum already saved");
else
save_walker_trail (false);
++internal->stats.walk.improved;
for (auto v : internal->vars) {
if (best_values[v])
internal->phases.saved[v] = best_values[v];
else
assert (!internal->active (v));
}
internal->copy_phases (internal->phases.prev);
}
inline double WalkerFO::score (unsigned i) {
const double res = (i < table.size () ? table[i] : epsilon);
LOG ("break %u mapped to score %g", i, res);
return res;
}
unsigned WalkerFO::walk_full_occs_pick_clause () {
internal->require_mode (internal->WALK);
assert (!broken.empty ());
int64_t size = broken.size ();
if (size > INT_MAX)
size = INT_MAX;
int pos = random.pick_int (0, size - 1);
unsigned res = broken[pos].counter_pos;
LOG (tclauses[res].clause, "picking random position %d", pos);
return res;
}
unsigned WalkerFO::walk_full_occs_break_value (int lit) {
internal->require_mode (internal->WALK);
assert (internal->val (lit) > 0);
START (walkbreak);
const uint64_t old = ticks;
unsigned res = 0; ticks +=
(1 + internal->cache_lines (occs (lit).size (), sizeof (Clause *)));
for (const auto &w : occs (lit)) {
const unsigned ref = w.counter_pos;
assert (ref < tclauses.size ());
res += (tclauses[ref].count == 1);
}
internal->stats.ticks.walkbreak += ticks - old;
STOP (walkbreak);
return res;
}
int WalkerFO::walk_full_occs_pick_lit (Clause *c) {
START (walkpick);
LOG ("picking literal by break-count");
assert (scores.empty ());
const int64_t old = ++ticks;
double sum = 0;
int64_t propagations = 0;
for (const auto lit : *c) {
assert (internal->active (lit));
if (internal->var (lit).level == 1) {
LOG ("skipping assumption %d for scoring", -lit);
continue;
}
propagations++;
unsigned tmp = walk_full_occs_break_value (-lit);
double score = this->score (tmp);
LOG ("literal %d break-count %u score %g", lit, tmp, score);
scores.push_back (score);
sum += score;
}
(void) propagations; LOG ("scored %zd literals", scores.size ());
assert (!scores.empty ());
assert (this->scores.size () <= (size_t) c->size);
const double lim = sum * random.generate_double ();
LOG ("score sum %g limit %g", sum, lim);
const auto end = c->end ();
auto i = c->begin ();
auto j = scores.begin ();
int res;
for (;;) {
assert (i != end);
res = *i++;
if (internal->var (res).level > 1)
break;
LOG ("skipping assumption %d without score", -res);
}
sum = *j++;
while (sum <= lim && i != end) {
res = *i++;
if (internal->var (res).level == 1) {
LOG ("skipping assumption %d without score", -res);
continue;
}
sum += *j++;
}
scores.clear ();
LOG ("picking literal %d by break-count", res);
internal->stats.ticks.walkpick += ticks - old;
STOP (walkpick);
return res;
}
void WalkerFO::make_clause (Tagged t) {
assert (t.counter_pos < tclauses.size ());
auto count = tclauses[t.counter_pos].count++;
if (count) {
LOG (tclauses[t.counter_pos].clause,
"already made with counter %d at position %d",
tclauses[t.counter_pos].count, tclauses[t.counter_pos].pos);
assert (tclauses[t.counter_pos].clause == t.c);
assert (tclauses[t.counter_pos].pos == WalkerFO::invalid_position);
return;
}
LOG (tclauses[t.counter_pos].clause,
"make with counter %d at position %d", tclauses[t.counter_pos].count,
tclauses[t.counter_pos].pos);
assert (tclauses[t.counter_pos].pos != WalkerFO::invalid_position);
assert (tclauses[t.counter_pos].pos < broken.size ());
++ticks;
auto last = broken.back ();
#ifndef NDEBUG
assert (tclauses[t.counter_pos].clause == t.c);
assert (last.counter_pos < tclauses.size ());
assert (tclauses[last.counter_pos].clause == last.c);
#endif
unsigned pos = tclauses[t.counter_pos].pos;
assert (pos < broken.size ());
broken[pos] = last;
tclauses[last.counter_pos].pos = pos;
tclauses[t.counter_pos].pos = WalkerFO::invalid_position;
broken.pop_back ();
}
void WalkerFO::make_clauses_along_occurrences (int lit) {
const auto &occs = this->occs (lit);
LOG ("making clauses with %s along %zu occurrences", LOGLIT (lit),
occs.size ());
assert (internal->val (lit) > 0);
size_t made = 0;
ticks += (1 + internal->cache_lines (occs.size (), sizeof (Clause *)));
for (auto c : occs) {
#if 0#endif
this->make_clause (c);
made++;
}
LOG ("made %zu clauses by flipping %d, still %zu broken", made, lit,
broken.size ());
LOG ("made %zu clauses with flipped %s", made, LOGLIT (lit));
(void) made;
}
void WalkerFO::make_clauses_along_unsatisfied (int lit) {
LOG ("making clauses with %s along %zu unsatisfied", LOGLIT (lit),
this->broken.size ());
assert (internal->val (lit) > 0);
size_t made = 0;
size_t j = 0;
const size_t size = this->broken.size ();
ticks +=
(1 + internal->cache_lines (this->broken.size (), sizeof (Clause *)));
for (size_t i = 0, j = 0; i < size; ++i) {
assert (i >= j);
const auto &c = this->broken[i];
Counter &d = this->tclauses[c.counter_pos];
this->broken[j++] = this->broken[i];
assert (d.pos != WalkerFO::invalid_position);
for (auto other : *d.clause) {
if (lit == other) {
++made;
--j;
++d.count;
LOG (d.clause, "made with count %d", d.count);
d.pos = WalkerFO::invalid_position;
break;
}
}
if (d.pos != WalkerFO::invalid_position)
LOG (d.clause, "still broken");
assert (made + j == i + 1); }
assert (j <= size);
this->broken.resize (j);
LOG ("made %zu clauses with flipped %s", made, LOGLIT (lit));
(void) made;
}
void WalkerFO::make_clauses (int lit) {
START (walkflipWL);
const int64_t old = ticks;
make_clauses_along_occurrences (lit);
internal->stats.ticks.walkflipWL += ticks - old;
STOP (walkflipWL);
}
void WalkerFO::break_clauses (int lit) {
START (walkflipbroken);
const int64_t old = ticks;
LOG ("breaking clauses on %s", LOGLIT (lit));
#ifdef LOGGING
int64_t broken = 0;
#endif
const WalkerFO::TOccs &ws = occs (lit);
ticks += (1 + internal->cache_lines (ws.size (), sizeof (Clause *)));
LOG ("trying to break %zd clauses", ws.size ());
for (const auto &w : ws) {
unsigned pos = w.counter_pos;
Counter &d = tclauses[pos];
#ifndef NDEBUG
LOG (d.clause, "trying to break");
#endif
if (--d.count)
continue;
d.pos = this->broken.size ();
++ticks;
this->broken.push_back (w);
#ifdef LOGGING
broken++;
#endif
}
LOG ("broken %" PRId64 " clauses by flipping %d", broken, lit);
internal->stats.ticks.walkflipbroken += ticks - old;
STOP (walkflipbroken);
}
void WalkerFO::walk_full_occs_flip_lit (int lit) {
internal->require_mode (internal->WALK);
LOG ("flipping assign %d", lit);
assert (internal->val (lit) < 0);
const int64_t old = ticks;
const int tmp = sign (lit);
const int idx = abs (lit);
internal->set_val (idx, tmp);
assert (internal->val (lit) > 0);
make_clauses (lit);
break_clauses (-lit);
if (!broken.empty ())
check_all ();
internal->stats.ticks.walkflip += ticks - old;
}
inline void Internal::walk_full_occs_save_minimum (WalkerFO &walker) {
int64_t broken = walker.broken.size ();
if (broken >= walker.minimum)
return;
if (broken <= stats.walk.minimum) {
stats.walk.minimum = broken;
VERBOSE (3, "new global minimum %" PRId64 "", broken);
} else {
VERBOSE (3, "new walk minimum %" PRId64 "", broken);
}
walker.minimum = broken;
#ifndef NDEBUG
for (auto i : vars) {
const signed char tmp = vals[i];
if (tmp)
phases.saved[i] = tmp;
}
#endif
if (walker.best_trail_pos == -1) {
for (auto i : vars) {
const signed char tmp = vals[i];
if (tmp) {
walker.best_values[i] = tmp;
#ifndef NDEBUG
assert (tmp == phases.saved[i]);
#endif
}
}
walker.best_trail_pos = 0;
} else {
walker.best_trail_pos = walker.flips.size ();
LOG ("new best trail position %u", walker.best_trail_pos);
}
}
int Internal::walk_full_occs_round (int64_t limit, bool prev) {
stats.walk.count++;
reset_watches ();
if (last.collect.fixed < stats.all.fixed)
garbage_collection ();
#ifndef QUIET
if (localsearching) {
assert (!force_phase_messages);
force_phase_messages = true;
}
#endif
PHASE ("walk", stats.walk.count,
"random walk limit of %" PRId64 " propagations", limit);
double size = 0;
int64_t n = 0;
for (const auto c : clauses) {
if (c->garbage)
continue;
if (c->redundant) {
if (!opts.walkredundant)
continue;
if (!likely_to_be_kept_clause (c))
continue;
}
size += c->size;
n++;
}
double average_size = relative (size, n);
PHASE ("walk", stats.walk.count,
"%" PRId64 " clauses average size %.2f over %d variables", n,
average_size, active ());
WalkerFO walker (internal, average_size, limit);
bool failed = false;
level = 1;
if (assumptions.empty ()) {
LOG ("no assumptions so assigning all variables to decision phase");
} else {
LOG ("assigning assumptions to their forced phase first");
for (const auto lit : assumptions) {
signed char tmp = val (lit);
if (tmp > 0)
continue;
if (tmp < 0) {
LOG ("inconsistent assumption %d", lit);
failed = true;
break;
}
if (!active (lit))
continue;
tmp = sign (lit);
const int idx = abs (lit);
LOG ("initial assign %d to assumption phase", tmp < 0 ? -idx : idx);
set_val (idx, tmp);
assert (level == 1);
var (idx).level = 1;
}
if (!failed)
LOG ("now assigning remaining variables to their decision phase");
}
level = 2;
if (!failed) {
const bool target = opts.warmup ? false : stable || opts.target == 2;
for (auto idx : vars) {
if (!active (idx)) {
LOG ("skipping inactive variable %d", idx);
continue;
}
if (vals[idx]) {
assert (var (idx).level == 1);
LOG ("skipping assumed variable %d", idx);
continue;
}
int tmp = 0;
if (prev)
tmp = phases.prev[idx];
if (!tmp)
tmp = sign (decide_phase (idx, target));
assert (tmp == 1 || tmp == -1);
set_val (idx, tmp);
assert (level == 2);
var (idx).level = 2;
walker.best_values[idx] = tmp;
LOG ("initial assign %d to decision phase", tmp < 0 ? -idx : idx);
}
LOG ("watching satisfied and registering broken clauses");
#ifdef LOGGING
int64_t watched = 0;
#endif
for (const auto c : clauses) {
if (c->garbage)
continue;
if (c->redundant) {
if (!opts.walkredundant)
continue;
if (!likely_to_be_kept_clause (c))
continue;
}
bool satisfiable = false; int satisfied = 0;
int *lits = c->literals;
const int size = c->size;
for (int i = 0; i < size; i++) {
const int lit = lits[i];
assert (active (lit)); if (val (lit) > 0) {
swap (lits[satisfied], lits[i]);
if (!satisfied++)
LOG ("first satisfying literal %d", lit);
} else if (!satisfiable && var (lit).level > 1) {
LOG ("non-assumption potentially satisfying literal %d", lit);
satisfiable = true;
}
}
if (!satisfied && !satisfiable) {
LOG (c, "due to assumptions unsatisfiable");
LOG ("stopping local search since assumptions falsify a clause");
failed = true;
break;
}
unsigned pos = walker.tclauses.size ();
walker.tclauses.push_back (Counter (satisfied, c));
walker.connect_clause (c, pos);
if (!satisfied) {
assert (satisfiable); LOG (c, "broken");
walker.tclauses[pos].pos = walker.broken.size ();
walker.broken.push_back (Tagged (c, pos));
} else {
#ifdef LOGGING
watched++; #endif
}
}
#ifdef LOGGING
if (!failed) {
int64_t broken = walker.broken.size ();
int64_t total = watched + broken;
MSG ("watching %" PRId64 " clauses %.0f%% "
"out of %" PRId64 " (watched and broken)",
watched, percent (watched, total), total);
}
#endif
}
walker.check_all ();
int res;
if (!failed) {
int64_t broken = walker.broken.size ();
int64_t initial_minimum = broken;
PHASE ("walk", stats.walk.count,
"starting with %" PRId64 " unsatisfied clauses "
"(%.0f%% out of %" PRId64 ")",
broken, percent (broken, stats.current.irredundant),
stats.current.irredundant);
walk_full_occs_save_minimum (walker);
assert (stats.walk.minimum <= walker.minimum);
int64_t minimum = broken;
#ifndef QUIET
int64_t flips = 0;
#endif
while (!terminated_asynchronously () && !walker.broken.empty () &&
walker.ticks < walker.limit) {
#ifndef QUIET
flips++;
#endif
stats.walk.flips++;
stats.walk.broken += broken;
unsigned pos = walker.walk_full_occs_pick_clause ();
Clause *c = walker.tclauses[pos].clause;
const int lit = walker.walk_full_occs_pick_lit (c);
walker.walk_full_occs_flip_lit (lit);
walker.push_flipped (lit);
broken = walker.broken.size ();
LOG ("now have %" PRId64 " broken clauses in total", broken);
if (broken >= minimum)
continue;
minimum = broken;
VERBOSE (3, "new phase minimum %" PRId64 " after %" PRId64 " flips",
minimum, flips);
walk_full_occs_save_minimum (walker);
}
walker.save_final_minimum (initial_minimum);
#ifndef QUIET
if (minimum == initial_minimum) {
PHASE ("walk", internal->stats.walk.count,
"%sno improvement %" PRId64 "%s in %" PRId64 " flips and "
"%" PRId64 " ticks",
tout.bright_yellow_code (), minimum, tout.normal_code (),
flips, walker.ticks);
} else {
PHASE ("walk", internal->stats.walk.count,
"best phase minimum %" PRId64 " in %" PRId64 " flips and "
"%" PRId64 " ticks",
minimum, flips, walker.ticks);
}
#endif
if (opts.profile >= 2) {
PHASE ("walk", stats.walk.count, "%.2f million ticks per second",
1e-6 *
relative (walker.ticks, time () - profiles.walk.started));
PHASE ("walk", stats.walk.count, "%.2f thousand flips per second",
relative (1e-3 * flips, time () - profiles.walk.started));
} else {
PHASE ("walk", stats.walk.count, "%.2f ticks", 1e-6 * walker.ticks);
PHASE ("walk", stats.walk.count, "%.2f thousand flips", 1e-3 * flips);
}
if (minimum > 0) {
LOG ("minimum %" PRId64 " non-zero thus potentially continue",
minimum);
res = 0;
} else {
LOG ("minimum is zero thus stop local search");
res = 10;
}
} else {
res = 20;
PHASE ("walk", stats.walk.count,
"aborted due to inconsistent assumptions");
}
for (auto idx : vars)
if (active (idx))
set_val (idx, 0);
assert (level == 2);
level = 0;
init_watches ();
connect_watches ();
#ifndef QUIET
if (localsearching) {
assert (force_phase_messages);
force_phase_messages = false;
}
#endif
stats.ticks.walk += walker.ticks;
return res;
}
void Internal::walk_full_occs () {
START_INNER_WALK ();
backtrack ();
if (propagated < trail.size () && !propagate ()) {
LOG ("empty clause after root level propagation");
learn_empty_clause ();
STOP_INNER_WALK ();
return;
}
int res = 0;
if (opts.warmup)
res = warmup ();
if (res) {
LOG ("stopping walk due to warmup");
STOP_INNER_WALK ();
return;
}
const int64_t ticks = stats.ticks.search[0] + stats.ticks.search[1];
int64_t limit = ticks - last.walk.ticks;
VERBOSE (2,
"walk scheduling: last %" PRId64 " current %" PRId64
" delta %" PRId64,
last.walk.ticks, ticks, limit);
last.walk.ticks = ticks;
limit *= 1e-3 * opts.walkeffort;
if (limit < opts.walkmineff)
limit = opts.walkmineff;
if (limit > 1e3 * opts.walkmaxeff) {
MSG ("reached maximum efficiency %" PRId64, limit);
limit = 1e3 * opts.walkmaxeff;
}
(void) walk_full_occs_round (limit, false);
STOP_INNER_WALK ();
assert (!unsat);
}
}