#include "internal.hpp"
namespace CaDiCaL {
struct Walker {
Internal *internal;
Random random; int64_t propagations; int64_t limit; vector<Clause *> broken; double epsilon; vector<double> table; vector<double> scores;
double score (unsigned);
Walker (Internal *, double size, int64_t limit);
};
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;
}
Walker::Walker (Internal *i, double size, int64_t l)
: internal (i), random (internal->opts.seed), propagations (0), limit (l) {
random += internal->stats.walk.count;
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 ());
}
inline double Walker::score (unsigned i) {
const double res = (i < table.size () ? table[i] : epsilon);
LOG ("break %u mapped to score %g", i, res);
return res;
}
Clause *Internal::walk_pick_clause (Walker &walker) {
require_mode (WALK);
assert (!walker.broken.empty ());
int64_t size = walker.broken.size ();
if (size > INT_MAX)
size = INT_MAX;
int pos = walker.random.pick_int (0, size - 1);
Clause *res = walker.broken[pos];
LOG (res, "picking random position %d", pos);
return res;
}
unsigned Internal::walk_break_value (int lit) {
require_mode (WALK);
assert (val (lit) > 0);
unsigned res = 0;
for (auto &w : watches (lit)) {
assert (w.blit != lit);
if (val (w.blit) > 0)
continue;
if (w.binary ()) {
res++;
continue;
}
Clause *c = w.clause;
assert (lit == c->literals[0]);
auto begin = c->begin () + 1;
const auto end = c->end ();
auto i = begin;
int prev = 0;
while (i != end) {
const int other = *i;
*i++ = prev;
prev = other;
if (val (other) < 0)
continue;
w.blit = other; *begin = other;
break;
}
if (i != end)
continue;
while (i != begin) {
const int other = *--i;
*i = prev;
prev = other;
}
res++; }
return res;
}
int Internal::walk_pick_lit (Walker &walker, Clause *c) {
LOG ("picking literal by break-count");
assert (walker.scores.empty ());
double sum = 0;
int64_t propagations = 0;
for (const auto lit : *c) {
assert (active (lit));
if (var (lit).level == 1) {
LOG ("skipping assumption %d for scoring", -lit);
continue;
}
assert (active (lit));
propagations++;
unsigned tmp = walk_break_value (-lit);
double score = walker.score (tmp);
LOG ("literal %d break-count %u score %g", lit, tmp, score);
walker.scores.push_back (score);
sum += score;
}
LOG ("scored %zd literals", walker.scores.size ());
assert (!walker.scores.empty ());
walker.propagations += propagations;
stats.propagations.walk += propagations;
assert (walker.scores.size () <= (size_t) c->size);
const double lim = sum * walker.random.generate_double ();
LOG ("score sum %g limit %g", sum, lim);
const auto end = c->end ();
auto i = c->begin ();
auto j = walker.scores.begin ();
int res;
for (;;) {
assert (i != end);
res = *i++;
if (var (res).level > 1)
break;
LOG ("skipping assumption %d without score", -res);
}
sum = *j++;
while (sum <= lim && i != end) {
res = *i++;
if (var (res).level == 1) {
LOG ("skipping assumption %d without score", -res);
continue;
}
sum += *j++;
}
walker.scores.clear ();
LOG ("picking literal %d by break-count", res);
return res;
}
void Internal::walk_flip_lit (Walker &walker, int lit) {
require_mode (WALK);
LOG ("flipping assign %d", lit);
assert (val (lit) < 0);
const int tmp = sign (lit);
const int idx = abs (lit);
set_val (idx, tmp);
assert (val (lit) > 0);
{
LOG ("trying to make %zd broken clauses", walker.broken.size ());
const double ratio = clause_variable_ratio ();
const auto eou = walker.broken.end ();
auto j = walker.broken.begin (), i = j;
#ifdef LOGGING
int64_t made = 0;
#endif
int64_t count = 0;
while (i != eou) {
Clause *d = *j++ = *i++;
int *literals = d->literals, prev = 0;
const int size = d->size;
for (int i = 0; i < size; i++) {
const int other = literals[i];
assert (active (other));
literals[i] = prev;
prev = other;
if (other == lit)
break;
assert (val (other) < 0);
}
if (prev == lit) {
literals[0] = lit;
LOG (d, "made");
watch_literal (literals[0], literals[1], d);
#ifdef LOGGING
made++;
#endif
j--;
} else {
for (int i = size - 1; i >= 0; i--) {
int other = literals[i];
literals[i] = prev;
prev = other;
}
}
if (count--)
continue;
count = ratio; walker.propagations++;
stats.propagations.walk++;
}
LOG ("made %" PRId64 " clauses by flipping %d", made, lit);
walker.broken.resize (j - walker.broken.begin ());
}
{
walker.propagations++; stats.propagations.walk++;
#ifdef LOGGING
int64_t broken = 0;
#endif
Watches &ws = watches (-lit);
LOG ("trying to brake %zd watched clauses", ws.size ());
for (const auto &w : ws) {
Clause *d = w.clause;
LOG (d, "unwatch %d in", -lit);
int *literals = d->literals, replacement = 0, prev = -lit;
assert (literals[0] == -lit);
const int size = d->size;
for (int i = 1; i < size; i++) {
const int other = literals[i];
assert (active (other));
literals[i] = prev; prev = other;
const signed char tmp = val (other);
if (tmp < 0)
continue;
replacement = other; break;
}
if (replacement) {
literals[1] = -lit;
literals[0] = replacement;
assert (-lit != replacement);
watch_literal (replacement, -lit, d);
} else {
for (int i = size - 1; i > 0; i--) { const int other = literals[i];
literals[i] = prev;
prev = other;
}
assert (literals[0] == -lit);
LOG (d, "broken");
walker.broken.push_back (d);
#ifdef LOGGING
broken++;
#endif
}
}
LOG ("broken %" PRId64 " clauses by flipping %d", broken, lit);
ws.clear ();
}
}
inline void Internal::walk_save_minimum (Walker &walker) {
int64_t broken = walker.broken.size ();
if (broken >= stats.walk.minimum)
return;
VERBOSE (3, "new global minimum %" PRId64 "", broken);
stats.walk.minimum = broken;
for (auto i : vars) {
const signed char tmp = vals[i];
if (tmp)
phases.min[i] = phases.saved[i] = tmp;
}
}
int Internal::walk_round (int64_t limit, bool prev) {
backtrack ();
if (propagated < trail.size () && !propagate ()) {
LOG ("empty clause after root level propagation");
learn_empty_clause ();
return 20;
}
stats.walk.count++;
clear_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 ());
Walker 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) {
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, true));
assert (tmp == 1 || tmp == -1);
set_val (idx, tmp);
assert (level == 2);
var (idx).level = 2;
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; satisfied < 2 && 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;
}
if (satisfied) {
watch_literal (lits[0], lits[1], c);
#ifdef LOGGING
watched++;
#endif
} else {
assert (satisfiable); LOG (c, "broken");
walker.broken.push_back (c);
}
}
#ifdef LOGGING
if (!failed) {
int64_t broken = walker.broken.size ();
int64_t total = watched + broken;
LOG ("watching %" PRId64 " clauses %.0f%% "
"out of %" PRId64 " (watched and broken)",
watched, percent (watched, total), total);
}
#endif
}
int64_t old_global_minimum = stats.walk.minimum;
int res;
if (!failed) {
int64_t broken = walker.broken.size ();
PHASE ("walk", stats.walk.count,
"starting with %" PRId64 " unsatisfied clauses "
"(%.0f%% out of %" PRId64 ")",
broken, percent (broken, stats.current.irredundant),
stats.current.irredundant);
walk_save_minimum (walker);
int64_t minimum = broken;
#ifndef QUIET
int64_t flips = 0;
#endif
while (!terminated_asynchronously () && !walker.broken.empty () &&
walker.propagations < walker.limit) {
#ifndef QUIET
flips++;
#endif
stats.walk.flips++;
stats.walk.broken += broken;
Clause *c = walk_pick_clause (walker);
const int lit = walk_pick_lit (walker, c);
walk_flip_lit (walker, 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_save_minimum (walker);
}
if (minimum < old_global_minimum)
PHASE ("walk", stats.walk.count,
"%snew global minimum %" PRId64 "%s in %" PRId64 " flips and "
"%" PRId64 " propagations",
tout.bright_yellow_code (), minimum, tout.normal_code (),
flips, walker.propagations);
else
PHASE ("walk", stats.walk.count,
"best phase minimum %" PRId64 " in %" PRId64 " flips and "
"%" PRId64 " propagations",
minimum, flips, walker.propagations);
PHASE ("walk", stats.walk.count, "%.2f million propagations per second",
relative (1e-6 * walker.propagations,
time () - profiles.walk.started));
PHASE ("walk", stats.walk.count, "%.2f thousand flips per second",
relative (1e-3 * flips, time () - profiles.walk.started));
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");
}
copy_phases (phases.prev);
for (auto idx : vars)
if (active (idx))
set_val (idx, 0);
assert (level == 2);
level = 0;
clear_watches ();
connect_watches ();
#ifndef QUIET
if (localsearching) {
assert (force_phase_messages);
force_phase_messages = false;
}
#endif
return res;
}
void Internal::walk () {
START_INNER_WALK ();
int64_t limit = stats.propagations.search;
limit *= 1e-3 * opts.walkreleff;
if (limit < opts.walkmineff)
limit = opts.walkmineff;
if (limit > opts.walkmaxeff)
limit = opts.walkmaxeff;
(void) walk_round (limit, false);
STOP_INNER_WALK ();
}
}