diff --git a/src/sat/kissat/allocate.c b/src/sat/kissat/allocate.c new file mode 100644 index 000000000..68bb68a88 --- /dev/null +++ b/src/sat/kissat/allocate.c @@ -0,0 +1,161 @@ +#include "allocate.h" +#include "error.h" +#include "internal.h" +#include "logging.h" + +#undef LOGPREFIX +#define LOGPREFIX "ALLOCATE" + +#include + +#ifdef LOGGING +#include +#endif + +static void inc_bytes (kissat *solver, size_t bytes) { +#ifdef METRICS + if (!solver) + return; + ADD (allocated_current, bytes); + LOG5 ("allocated_current = %s", + FORMAT_BYTES (solver->statistics.allocated_current)); + if (solver->statistics.allocated_current >= + solver->statistics.allocated_max) { + solver->statistics.allocated_max = solver->statistics.allocated_current; + LOG5 ("allocated_max = %s", + FORMAT_BYTES (solver->statistics.allocated_max)); + } +#else + (void) solver; + (void) bytes; +#endif +} + +static void dec_bytes (kissat *solver, size_t bytes) { +#ifdef METRICS + if (!solver) + return; + SUB (allocated_current, bytes); + LOG5 ("allocated_current = %s", + FORMAT_BYTES (solver->statistics.allocated_current)); +#else + (void) solver; + (void) bytes; +#endif +} + +void *kissat_malloc (kissat *solver, size_t bytes) { + void *res; + if (!bytes) + return 0; + res = malloc (bytes); + LOG4 ("malloc (%zu) = %p", bytes, res); + if (!res) + kissat_fatal ("out-of-memory allocating %zu bytes", bytes); + inc_bytes (solver, bytes); + return res; +} + +void kissat_free (kissat *solver, void *ptr, size_t bytes) { + if (ptr) { + LOG4 ("free (%p[%zu])", ptr, bytes); + dec_bytes (solver, bytes); + free (ptr); + } else + assert (!bytes); +} + +char *kissat_strdup (kissat *solver, const char *str) { + char *res = kissat_malloc (solver, strlen (str) + 1); + return strcpy (res, str); +} + +void kissat_freestr (struct kissat *solver, char *str) { + assert (str); + kissat_free (solver, str, strlen (str) + 1); +} + +void *kissat_nalloc (kissat *solver, size_t n, size_t size) { + void *res; + if (!n || !size) + return 0; + if (MAX_SIZE_T / size < n) + kissat_fatal ("invalid 'kissat_nalloc (..., %zu, %zu)' call", n, size); + const size_t bytes = n * size; + res = malloc (bytes); + LOG4 ("nalloc (%zu, %zu) = %p", n, size, res); + if (!res) + kissat_fatal ("out-of-memory allocating " + "%zu = %zu x %zu bytes", + bytes, n, size); + inc_bytes (solver, bytes); + return res; +} + +void *kissat_calloc (kissat *solver, size_t n, size_t size) { + void *res; + if (!n || !size) + return 0; + if (MAX_SIZE_T / size < n) + kissat_fatal ("invalid 'kissat_calloc (..., %zu, %zu)' call", n, size); + res = calloc (n, size); + LOG4 ("calloc (%zu, %zu) = %p", n, size, res); + const size_t bytes = n * size; + if (!res) + kissat_fatal ("out-of-memory allocating " + "%zu = %zu x %zu bytes", + bytes, n, size); + inc_bytes (solver, bytes); + return res; +} + +void kissat_dealloc (kissat *solver, void *ptr, size_t n, size_t size) { + if (!n || !size) + return; + if (MAX_SIZE_T / size < n) + kissat_fatal ("invalid 'kissat_dealloc (..., %zu, %zu)' call", n, size); + const size_t bytes = n * size; + kissat_free (solver, ptr, bytes); +} + +void *kissat_realloc (kissat *solver, void *p, size_t old_bytes, + size_t new_bytes) { + if (old_bytes == new_bytes) + return p; + if (!new_bytes) { + kissat_free (solver, p, old_bytes); + return 0; + } + dec_bytes (solver, old_bytes); +#ifdef LOGGING + if (GET_OPTION (log) > 3) + kissat_begin_logging (solver, LOGPREFIX, "realloc (%p[%zu, %zu) = ", p, + old_bytes, new_bytes); +#endif + void *res = realloc (p, new_bytes); +#ifdef LOGGING + if (GET_OPTION (log) > 3) { + printf ("%p", res); + kissat_end_logging (); + } +#endif + if (new_bytes && !res) + kissat_fatal ("out-of-memory reallocating from %zu to %zu bytes", + old_bytes, new_bytes); + inc_bytes (solver, new_bytes); + return res; +} + +void *kissat_nrealloc (kissat *solver, void *p, size_t o, size_t n, + size_t size) { + if (!size) { + assert (!p); + assert (!o); + return 0; + } + const size_t max = MAX_SIZE_T / size; + if (max < o || max < n) + kissat_fatal ("invalid 'kissat_nrealloc (..., %zu, %zu, %zu)' call", o, + n, size); + return kissat_realloc (solver, p, o * size, n * size); +} diff --git a/src/sat/kissat/allocate.h b/src/sat/kissat/allocate.h new file mode 100644 index 000000000..db24fd5f8 --- /dev/null +++ b/src/sat/kissat/allocate.h @@ -0,0 +1,36 @@ +#ifndef _allocate_h_INCLUDED +#define _allocate_h_INCLUDED + +#include + +struct kissat; + +void *kissat_malloc (struct kissat *, size_t bytes); +void kissat_free (struct kissat *, void *, size_t bytes); + +char *kissat_strdup (struct kissat *, const char *); +void kissat_freestr (struct kissat *, char *); + +void *kissat_calloc (struct kissat *, size_t n, size_t size); +void *kissat_nalloc (struct kissat *, size_t n, size_t size); +void kissat_dealloc (struct kissat *, void *ptr, size_t n, size_t size); + +void *kissat_realloc (struct kissat *, void *, size_t old, size_t bytes); +void *kissat_nrealloc (struct kissat *, void *, size_t o, size_t n, size_t); + +#define NALLOC(P, N) \ + do { \ + (P) = kissat_nalloc (solver, (N), sizeof *(P)); \ + } while (0) + +#define CALLOC(P, N) \ + do { \ + (P) = kissat_calloc (solver, (N), sizeof *(P)); \ + } while (0) + +#define DEALLOC(P, N) \ + do { \ + kissat_dealloc (solver, (P), (N), sizeof *(P)); \ + } while (0) + +#endif diff --git a/src/sat/kissat/analyze.c b/src/sat/kissat/analyze.c new file mode 100644 index 000000000..d21b0a74b --- /dev/null +++ b/src/sat/kissat/analyze.c @@ -0,0 +1,579 @@ +#include "analyze.h" +#include "backtrack.h" +#include "bump.h" +#include "deduce.h" +#include "inline.h" +#include "learn.h" +#include "minimize.h" +#include "print.h" +#include "rank.h" +#include "shrink.h" +#include "sort.h" +#include "tiers.h" + +#include + +static bool one_literal_on_conflict_level (kissat *solver, clause *conflict, + unsigned *conflict_level_ptr) { + assert (conflict); + assert (conflict->size > 1); + + unsigned jump_level = INVALID_LEVEL; + unsigned conflict_level = INVALID_LEVEL; + unsigned literals_on_conflict_level = 0; + unsigned forced_lit = INVALID_LIT; + + assigned *all_assigned = solver->assigned; + + unsigned *lits = conflict->lits; + const unsigned conflict_size = conflict->size; + const unsigned *const end_of_lits = lits + conflict_size; + + for (const unsigned *p = lits; p != end_of_lits; p++) { + const unsigned lit = *p; + assert (VALUE (lit) < 0); + const unsigned idx = IDX (lit); + const unsigned lit_level = all_assigned[idx].level; + if (conflict_level == INVALID_LEVEL || conflict_level < lit_level) { + literals_on_conflict_level = 1; + jump_level = conflict_level; + conflict_level = lit_level; + forced_lit = lit; + } else { + if (jump_level == INVALID_LEVEL || jump_level < lit_level) + jump_level = lit_level; + if (conflict_level == lit_level) + literals_on_conflict_level++; + } + if (literals_on_conflict_level > 1 && conflict_level == solver->level) + break; + } + assert (conflict_level != INVALID_LEVEL); + assert (literals_on_conflict_level); + + LOG ("found %u literals on conflict level %u", literals_on_conflict_level, + conflict_level); + *conflict_level_ptr = conflict_level; + + if (!conflict_level) { + solver->inconsistent = true; + LOG ("learned empty clause from conflict at conflict level zero"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return false; + } + + if (conflict_level < solver->level) { + LOG ("forced backtracking due to conflict level %u < level %u", + conflict_level, solver->level); + kissat_backtrack_after_conflict (solver, conflict_level); + } + + if (conflict_size > 2) { + for (unsigned i = 0; i < 2; i++) { + const unsigned lit = lits[i]; + const unsigned lit_idx = IDX (lit); + unsigned highest_position = i; + unsigned highest_literal = lit; + unsigned highest_level = all_assigned[lit_idx].level; + for (unsigned j = i + 1; j < conflict_size; j++) { + const unsigned other = lits[j]; + const unsigned other_idx = IDX (other); + const unsigned level = all_assigned[other_idx].level; + if (highest_level >= level) + continue; + highest_literal = other; + highest_position = j; + highest_level = level; + if (highest_level == conflict_level) + break; + } + if (highest_position == i) + continue; + reference ref = INVALID_REF; + if (highest_position > 1) { + ref = kissat_reference_clause (solver, conflict); + kissat_unwatch_blocking (solver, lit, ref); + } + lits[highest_position] = lit; + lits[i] = highest_literal; + if (highest_position > 1) + kissat_watch_blocking (solver, lits[i], lits[!i], ref); + } + } + + if (literals_on_conflict_level > 1) + return false; + + assert (literals_on_conflict_level == 1); + assert (forced_lit != INVALID_LIT); + assert (jump_level != INVALID_LEVEL); + assert (jump_level < conflict_level); + + LOG ("reusing conflict as driving clause of %s", LOGLIT (forced_lit)); + + unsigned new_level = kissat_determine_new_level (solver, jump_level); + kissat_backtrack_after_conflict (solver, new_level); + + if (conflict_size == 2) { + assert (conflict == &solver->conflict); + const unsigned other = lits[0] ^ lits[1] ^ forced_lit; + kissat_assign_binary (solver, forced_lit, other); + } else { + const reference ref = kissat_reference_clause (solver, conflict); + kissat_assign_reference (solver, forced_lit, ref, conflict); + } + + return true; +} + +static inline void mark_reason_side_literal (kissat *solver, + assigned *all_assigned, + unsigned lit) { + const unsigned idx = IDX (lit); + const assigned *a = all_assigned + idx; + if (a->level && !a->analyzed) + kissat_push_analyzed (solver, all_assigned, idx); +} + +static inline void analyze_reason_side_literal (kissat *solver, + size_t limit, ward *arena, + assigned *all_assigned, + unsigned lit) { + const unsigned idx = IDX (lit); + const assigned *a = all_assigned + idx; + assert (a->level); + assert (a->analyzed); + assert (a->reason != UNIT_REASON); + if (a->reason == DECISION_REASON) + return; + if (a->binary) { + const unsigned other = a->reason; + mark_reason_side_literal (solver, all_assigned, other); + } else { + const reference ref = a->reason; + assert (ref < SIZE_STACK (solver->arena)); + clause *c = (clause *) (arena + ref); + const unsigned not_lit = NOT (lit); + INC (search_ticks); + for (all_literals_in_clause (other, c)) + if (other != not_lit) { + assert (other != lit); + mark_reason_side_literal (solver, all_assigned, other); + if (SIZE_STACK (solver->analyzed) > limit) + break; + } + } +} + +static void analyze_reason_side_literals (kissat *solver) { + if (!GET_OPTION (bump)) + return; + if (!GET_OPTION (bumpreasons)) + return; + if (solver->probing) + return; + if (DELAYING (bumpreasons)) + return; + const double decision_rate = AVERAGE (decision_rate); + const int decision_rate_limit = GET_OPTION (bumpreasonsrate); + if (decision_rate >= decision_rate_limit) { + LOG ("decision rate %g >= limit %d", decision_rate, + decision_rate_limit); + return; + } + assigned *all_assigned = solver->assigned; +#ifndef NDEBUG + for (all_stack (unsigned, lit, solver->clause)) + assert (all_assigned[IDX (lit)].analyzed); +#endif + LOG ("trying to bump reason side literals too"); + const size_t saved = SIZE_STACK (solver->analyzed); + const size_t limit = GET_OPTION (bumpreasonslimit) * saved; + LOG ("analyzed already %zu literals thus limit %zu", saved, limit); + ward *arena = BEGIN_STACK (solver->arena); + for (all_stack (unsigned, lit, solver->clause)) { + analyze_reason_side_literal (solver, limit, arena, all_assigned, lit); + if (SIZE_STACK (solver->analyzed) > limit) + break; + } + if (SIZE_STACK (solver->analyzed) > limit) { + LOG ("too many additional reason side literals"); + while (SIZE_STACK (solver->analyzed) > saved) { + const unsigned idx = POP_STACK (solver->analyzed); + struct assigned *a = all_assigned + idx; + LOG ("marking %s as not analyzed", LOGVAR (idx)); + assert (a->analyzed); + a->analyzed = false; + } + BUMP_DELAY (bumpreasons); + } else + REDUCE_DELAY (bumpreasons); +} + +#define RADIX_SORT_LEVELS_LIMIT 32 + +#define RANK_LEVEL(A) (A) +#define SMALLER_LEVEL(A, B) (RANK_LEVEL (A) < RANK_LEVEL (B)) + +static void sort_levels (kissat *solver) { + unsigneds *levels = &solver->levels; + size_t glue = SIZE_STACK (*levels); + if (glue < RADIX_SORT_LEVELS_LIMIT) + SORT_STACK (unsigned, *levels, SMALLER_LEVEL); + else + RADIX_STACK (unsigned, unsigned, *levels, RANK_LEVEL); + LOG ("sorted %zu levels", glue); +} + +static void sort_deduced_clause (kissat *solver) { + sort_levels (solver); +#ifndef NDEBUG + const size_t size_frames = SIZE_STACK (solver->frames); +#endif + frame *frames = BEGIN_STACK (solver->frames); + unsigned pos = 1; + const unsigned *const begin_levels = BEGIN_STACK (solver->levels); + const unsigned *const end_levels = END_STACK (solver->levels); + unsigned const *p = end_levels; + while (p != begin_levels) { + const unsigned level = *--p; + assert (level < size_frames); + frame *f = frames + level; + const unsigned used = f->used; +#ifndef NDEBUG + f->saved = used; +#endif + assert (used > 0); + assert (UINT_MAX - used >= pos); + f->used = pos; + pos += used; + } + unsigneds *clause = &solver->clause; + const size_t size_clause = SIZE_STACK (*clause); +#ifndef NDEBUG + assert (pos == size_clause); +#endif + unsigned const *begin_clause = BEGIN_STACK (*clause); + const unsigned *const end_clause = END_STACK (*clause); + assert (begin_clause < end_clause); + + unsigneds *shadow = &solver->shadow; + while (SIZE_STACK (*shadow) < size_clause) + PUSH_STACK (*shadow, INVALID_LIT); + + const unsigned not_uip = *begin_clause++; + POKE_STACK (*shadow, 0, not_uip); + + const assigned *const assigned = solver->assigned; + + for (const unsigned *p = begin_clause; p != end_clause; p++) { + const unsigned lit = *p; + const unsigned idx = IDX (lit); + const struct assigned *a = assigned + idx; + const unsigned level = a->level; + assert (level < size_frames); + frame *f = frames + level; + const unsigned pos = f->used++; + POKE_STACK (*shadow, pos, lit); + } + + assert (size_clause == SIZE_STACK (*shadow)); + SWAP (unsigneds, *clause, *shadow); + + pos = 1; + p = end_levels; + while (p != begin_levels) { + const unsigned level = *--p; + assert (level < size_frames); + frame *f = frames + level; + const unsigned end = f->used; + assert (pos < end); + f->used = end - pos; + assert (f->used == f->saved); + pos = end; + } + + CLEAR_STACK (*shadow); + LOGTMP ("level sorted deduced"); + +#ifndef NDEBUG + unsigned prev_level = solver->level; + for (all_stack (unsigned, lit, solver->clause)) { + const unsigned idx = IDX (lit); + const unsigned lit_level = assigned[idx].level; + assert (prev_level >= lit_level); + prev_level = lit_level; + } +#endif +} + +static void reset_levels (kissat *solver) { + LOG ("reset %zu marked levels", SIZE_STACK (solver->levels)); + frame *frames = BEGIN_STACK (solver->frames); +#ifndef NDEBUG + const size_t size_frames = SIZE_STACK (solver->frames); +#endif + for (all_stack (unsigned, level, solver->levels)) { + assert (level < size_frames); + frame *f = frames + level; + assert (f->used > 0); + f->used = 0; + } + CLEAR_STACK (solver->levels); +} + +void kissat_reset_only_analyzed_literals (kissat *solver) { + LOG ("reset %zu analyzed variables", SIZE_STACK (solver->analyzed)); + assigned *assigned = solver->assigned; + for (all_stack (unsigned, idx, solver->analyzed)) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (!a->poisoned); + assert (!a->removable); + assert (!a->shrinkable); + a->analyzed = false; + } + CLEAR_STACK (solver->analyzed); +} + +static void reset_removable (kissat *solver) { + LOG ("reset %zu removable variables", SIZE_STACK (solver->removable)); + assigned *assigned = solver->assigned; +#ifndef NDEBUG + unsigned not_removable = 0; +#endif + for (all_stack (unsigned, idx, solver->removable)) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (a->removable || !not_removable++); + a->removable = false; + } + CLEAR_STACK (solver->removable); +} + +static void reset_analysis_but_not_analyzed_literals (kissat *solver) { + reset_removable (solver); + reset_levels (solver); + LOG ("reset %zu learned literals", SIZE_STACK (solver->clause)); + CLEAR_STACK (solver->clause); +} + +static void update_trail_average (kissat *solver) { + assert (!solver->probing); +#if defined(LOGGING) || !defined(QUIET) + const unsigned size = SIZE_ARRAY (solver->trail); + const unsigned assigned = size - solver->unflushed; + const unsigned active = solver->active; + const double filled = kissat_percent (assigned, active); +#else + (void) solver; +#endif + LOG ("trail filled %.0f%% (size %u, unflushed %u, active %u)", filled, + size, solver->unflushed, active); +#ifndef QUIET + UPDATE_AVERAGE (trail, filled); +#endif +} + +static void update_decision_rate_average (kissat *solver) { + assert (!solver->probing); + const uint64_t current = DECISIONS; + const uint64_t previous = + solver->averages[solver->stable].saved_decisions; + assert (previous <= current); + const uint64_t decisions = current - previous; + solver->averages[solver->stable].saved_decisions = current; + UPDATE_AVERAGE (decision_rate, decisions); +} + +static void analyze_failed_literal (kissat *solver, clause *conflict) { + assert (solver->level == 1); + const unsigned failed = FRAME (1).decision; + + LOGCLS (conflict, "analyzing failed literal %s conflict", + LOGLIT (failed)); + + unsigneds *units = &solver->clause; + assert (EMPTY_STACK (*units)); + assert (EMPTY_STACK (solver->analyzed)); + + const unsigned not_failed = NOT (failed); + assigned *all_assigned = solver->assigned; +#ifndef NDEBUG + const value *const values = solver->values; +#endif + unsigned const *t = END_ARRAY (solver->trail); + unsigned unresolved = 0; + unsigned unit = INVALID_LIT; + + for (all_literals_in_clause (lit, conflict)) { + assert (lit != failed); + if (lit == not_failed) { + LOG ("negation %s of failed literal %s occurs in conflict", + LOGLIT (not_failed), LOGLIT (failed)); + goto DONE; + } + assert (values[lit] < 0); + const unsigned idx = IDX (lit); + assigned *a = all_assigned + idx; + if (!a->level) + continue; + assert (a->level == 1); + LOG ("analyzing conflict literal %s", LOGLIT (lit)); + kissat_push_analyzed (solver, all_assigned, idx); + unresolved++; + } + + for (;;) { + unsigned lit; + assigned *a; + do { + assert (t > BEGIN_ARRAY (solver->trail)); + lit = *--t; + assert (values[lit] > 0); + const unsigned idx = IDX (lit); + a = all_assigned + idx; + } while (!a->analyzed); + if (unresolved == 1) { + unit = NOT (lit); + LOG ("learning additional unit %s", LOGLIT (unit)); + PUSH_STACK (*units, unit); + } + if (a->binary) { + const unsigned other = a->reason; + LOGBINARY (lit, other, "resolving %s reason", LOGLIT (lit)); + assert (other != failed); + assert (other != unit); + assert (values[other] < 0); + if (other == not_failed) { + LOG ("negation %s of failed literal %s in reason", + LOGLIT (not_failed), LOGLIT (failed)); + goto DONE; + } + const unsigned idx = IDX (other); + assigned *b = all_assigned + idx; + assert (b->level == 1); + if (!b->analyzed) { + LOG ("analyzing reason literal %s", LOGLIT (other)); + kissat_push_analyzed (solver, all_assigned, idx); + unresolved++; + } + } else { + assert (a->reason != UNIT_REASON); + assert (a->reason != DECISION_REASON); + const reference ref = a->reason; + LOGREF (ref, "resolving %s reason", LOGLIT (lit)); + clause *reason = kissat_dereference_clause (solver, ref); + for (all_literals_in_clause (other, reason)) { + assert (other != NOT (lit)); + assert (other != failed); + if (other == lit) + continue; + if (other == unit) + continue; + if (other == not_failed) { + LOG ("negation %s of failed literal %s occurs in reason", + LOGLIT (not_failed), LOGLIT (failed)); + goto DONE; + } + assert (values[other] < 0); + const unsigned idx = IDX (other); + assigned *b = all_assigned + idx; + if (!b->level) + continue; + assert (b->level == 1); + if (b->analyzed) + continue; + LOG ("analyzing reason literal %s", LOGLIT (other)); + kissat_push_analyzed (solver, all_assigned, idx); + unresolved++; + } + } + assert (unresolved > 0); + unresolved--; + LOG ("after resolving %s there are %u unresolved literals", + LOGLIT (lit), unresolved); + } +DONE: + LOG ("learning negated failed literal %s", LOGLIT (not_failed)); + PUSH_STACK (*units, not_failed); + + if (!solver->probing) + kissat_update_learned (solver, 0, 1); + + LOG ("failed literal %s produced %zu units", LOGLIT (failed), + SIZE_STACK (*units)); + + kissat_backtrack_without_updating_phases (solver, 0); + + for (all_stack (unsigned, lit, *units)) + kissat_learned_unit (solver, lit); + CLEAR_STACK (*units); + if (!solver->probing) { + solver->iterating = true; + INC (iterations); + } +} + +static void update_tier_limits (kissat *solver) { + INC (retiered); + kissat_compute_and_set_tier_limits (solver); + if (solver->limits.glue.interval < (1u << 16)) + solver->limits.glue.interval *= 2; + solver->limits.glue.conflicts = CONFLICTS + solver->limits.glue.interval; +} + +int kissat_analyze (kissat *solver, clause *conflict) { + if (solver->inconsistent) { + assert (!solver->level); + return 20; + } + + START (analyze); + if (!solver->probing) { + update_trail_average (solver); + update_decision_rate_average (solver); +#ifndef QUIET + UPDATE_AVERAGE (level, solver->level); +#endif + } + int res; + do { + LOGCLS (conflict, "analyzing conflict %" PRIu64, CONFLICTS); + unsigned conflict_level; + if (one_literal_on_conflict_level (solver, conflict, &conflict_level)) + res = 1; + else if (!conflict_level) + res = -1; + else if (conflict_level == 1) { + analyze_failed_literal (solver, conflict); + res = 1; + } else if ((conflict = + kissat_deduce_first_uip_clause (solver, conflict))) { + reset_analysis_but_not_analyzed_literals (solver); + INC (conflicts); + if (CONFLICTS > solver->limits.glue.conflicts) + update_tier_limits (solver); + res = 0; // And continue with new conflict analysis. + } else { + if (GET_OPTION (minimize)) { + sort_deduced_clause (solver); + kissat_minimize_clause (solver); + if (GET_OPTION (shrink)) + kissat_shrink_clause (solver); + } + analyze_reason_side_literals (solver); + kissat_learn_clause (solver); + reset_analysis_but_not_analyzed_literals (solver); + res = 1; + } + if (!EMPTY_STACK (solver->analyzed)) { + if (!solver->probing && GET_OPTION (bump)) + kissat_bump_analyzed (solver); + kissat_reset_only_analyzed_literals (solver); + } + } while (!res); + STOP (analyze); + return res > 0 ? 0 : 20; +} diff --git a/src/sat/kissat/analyze.h b/src/sat/kissat/analyze.h new file mode 100644 index 000000000..be4393c76 --- /dev/null +++ b/src/sat/kissat/analyze.h @@ -0,0 +1,12 @@ +#ifndef _analyze_h_INCLUDED +#define _analyze_h_INCLUDED + +#include + +struct clause; +struct kissat; + +int kissat_analyze (struct kissat *, struct clause *); +void kissat_reset_only_analyzed_literals (struct kissat *); + +#endif diff --git a/src/sat/kissat/ands.c b/src/sat/kissat/ands.c new file mode 100644 index 000000000..51bff792a --- /dev/null +++ b/src/sat/kissat/ands.c @@ -0,0 +1,88 @@ +#include "ands.h" +#include "eliminate.h" +#include "gates.h" +#include "inline.h" + +bool kissat_find_and_gate (kissat *solver, unsigned lit, + unsigned negative) { + if (!GET_OPTION (ands)) + return false; + size_t marked = kissat_mark_binaries (solver, lit); + if (!marked) + return false; + if (marked < 2) { + kissat_unmark_binaries (solver, lit); + return false; + } + + unsigned not_lit = NOT (lit); + watches *not_watches = &WATCHES (not_lit); + + ward *const arena = BEGIN_STACK (solver->arena); + value *marks = solver->marks; + const value *const values = solver->values; + + clause *base = 0; + for (all_binary_large_watches (watch, *not_watches)) { + if (watch.type.binary) + continue; + const reference ref = watch.large.ref; + assert (ref < SIZE_STACK (solver->arena)); + clause *c = (clause *) (arena + ref); + assert (!c->garbage); + base = c; + for (all_literals_in_clause (other, c)) { + if (other == not_lit) + continue; + const value value = values[other]; + if (value > 0) { + kissat_eliminate_clause (solver, c, INVALID_LIT); + base = 0; + break; + } + if (value < 0) + continue; + const unsigned not_other = NOT (other); + signed char mark = marks[not_other]; + if (mark) + continue; + base = 0; + break; + } + if (base) + break; + } + if (!base) { + kissat_unmark_binaries (solver, lit); + return false; + } + LOGCLS (base, "found and gate %s base clause", LOGLIT (not_lit)); + for (all_literals_in_clause (other, base)) { + if (other == not_lit) + continue; + if (values[other]) + continue; + const unsigned not_other = NOT (other); + assert (marks[not_other]); + marks[not_other] = 0; + } + watch tmp = kissat_binary_watch (0); + watches *watches = &WATCHES (lit); + for (all_binary_large_watches (watch, *watches)) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + assert (!solver->values[other]); + if (marks[other]) { + marks[other] = 0; + continue; + } + tmp.binary.lit = other; + PUSH_STACK (solver->gates[negative], tmp); + } + tmp = kissat_large_watch (kissat_reference_clause (solver, base)); + PUSH_STACK (solver->gates[!negative], tmp); + solver->gate_eliminated = GATE_ELIMINATED (ands); + INC (ands_extracted); + return true; +} diff --git a/src/sat/kissat/ands.h b/src/sat/kissat/ands.h new file mode 100644 index 000000000..606c41d6e --- /dev/null +++ b/src/sat/kissat/ands.h @@ -0,0 +1,11 @@ +#ifndef _ands_h_INCLUDED +#define _ands_h_INCLUDED + +#include + +struct kissat; + +bool kissat_find_and_gate (struct kissat *, unsigned lit, + unsigned negative); + +#endif diff --git a/src/sat/kissat/application.h b/src/sat/kissat/application.h new file mode 100644 index 000000000..f4a5b12f7 --- /dev/null +++ b/src/sat/kissat/application.h @@ -0,0 +1,8 @@ +#ifndef _application_h_INCLUDED +#define _application_h_INCLUDED + +struct kissat; + +int kissat_application (struct kissat *, int argc, char **argv); + +#endif diff --git a/src/sat/kissat/arena.c b/src/sat/kissat/arena.c new file mode 100644 index 000000000..7296d2605 --- /dev/null +++ b/src/sat/kissat/arena.c @@ -0,0 +1,108 @@ +#include "error.h" +#include "internal.h" +#include "logging.h" +#include "print.h" + +static void report_resized (kissat *solver, const char *mode, + arena before) { +#ifndef QUIET + ward *const old_begin = BEGIN_STACK (before); + ward *const new_begin = BEGIN_STACK (solver->arena); + const bool moved = (new_begin != old_begin); + const uint64_t capacity = CAPACITY_STACK (solver->arena); + const uint64_t bytes = capacity * sizeof (ward); + kissat_phase (solver, "arena", GET (arena_resized), + "%s to %s %d-byte-words %s (%s)", mode, + FORMAT_COUNT (capacity), (int) sizeof (ward), + FORMAT_BYTES (bytes), (moved ? "moved" : "in place")); +#else + (void) solver; + (void) mode; + (void) before; +#endif +} + +reference kissat_allocate_clause (kissat *solver, size_t size) { + assert (size <= UINT_MAX); + const size_t res = SIZE_STACK (solver->arena); + assert (res <= MAX_REF); + const size_t bytes = kissat_bytes_of_clause (size); + assert (kissat_aligned_word (bytes)); + const size_t needed = bytes / sizeof (ward); + assert (needed <= UINT_MAX); + size_t capacity = CAPACITY_STACK (solver->arena); + assert (kissat_is_power_of_two (MAX_ARENA)); + assert (capacity <= MAX_ARENA); + size_t available = capacity - res; + if (needed > available) { + const arena before = solver->arena; + do { + assert (kissat_is_zero_or_power_of_two (capacity)); + if (capacity == MAX_ARENA) + kissat_fatal ("maximum arena capacity " + "of 2^%u %zu-byte-words %s exhausted" +#ifdef COMPACT + " (consider a configuration without '--compact')" +#endif + , + LD_MAX_ARENA, sizeof (ward), + FORMAT_BYTES (MAX_ARENA * sizeof (ward))); + kissat_stack_enlarge (solver, (chars *) &solver->arena, + sizeof (ward)); + capacity = CAPACITY_STACK (solver->arena); + available = capacity - res; + } while (needed > available); + INC (arena_resized); + INC (arena_enlarged); + report_resized (solver, "enlarged", before); + assert (capacity <= MAX_ARENA); + } + solver->arena.end += needed; + LOG ("allocated clause[%zu] of size %zu bytes %s", res, size, + FORMAT_BYTES (bytes)); + return (reference) res; +} + +void kissat_shrink_arena (kissat *solver) { + const arena before = solver->arena; + const size_t capacity = CAPACITY_STACK (before); + const size_t size = SIZE_STACK (before); +#ifndef QUIET + const size_t capacity_bytes = capacity * sizeof (ward); + kissat_phase (solver, "arena", GET (arena_resized), + "capacity of %s %d-byte-words %s", FORMAT_COUNT (capacity), + (int) sizeof (ward), FORMAT_BYTES (capacity_bytes)); + const size_t size_bytes = size * sizeof (ward); + kissat_phase (solver, "arena", GET (arena_resized), + "filled %.0f%% with %s %d-byte-words %s", + kissat_percent (size, capacity), FORMAT_COUNT (size), + (int) sizeof (ward), FORMAT_BYTES (size_bytes)); +#endif + if (size > capacity / 4) { + kissat_phase (solver, "arena", GET (arena_resized), + "not shrinking since more than 25%% filled"); + return; + } + INC (arena_resized); + INC (arena_shrunken); + SHRINK_STACK (solver->arena); + report_resized (solver, "shrunken", before); +} + +#if !defined(NDEBUG) || defined(LOGGING) + +bool kissat_clause_in_arena (const kissat *solver, const clause *c) { + if (!kissat_aligned_pointer (c)) + return false; + const char *p = (char *) c; + const char *begin = (char *) BEGIN_STACK (solver->arena); + const char *end = (char *) END_STACK (solver->arena); + if (p < begin) + return false; + const size_t bytes = kissat_bytes_of_clause (c->size); + if (end < p + bytes) + return false; + return true; +} + +#endif diff --git a/src/sat/kissat/arena.h b/src/sat/kissat/arena.h new file mode 100644 index 000000000..da83c7e39 --- /dev/null +++ b/src/sat/kissat/arena.h @@ -0,0 +1,46 @@ +#ifndef _arena_h_INCLUDED +#define _arena_h_INCLUDED + +#include "reference.h" +#include "stack.h" +#include "utilities.h" + +#ifdef COMPACT +typedef word ward; +#else +typedef w2rd ward; +#endif + +#define LD_MAX_ARENA_32 (29 - (unsigned) sizeof (ward) / 4) + +#define LD_MAX_ARENA ((sizeof (word) == 4) ? LD_MAX_ARENA_32 : LD_MAX_REF) + +#define MAX_ARENA ((size_t) 1 << LD_MAX_ARENA) + +// clang-format off + +typedef STACK (ward) arena; + +// clang-format on + +struct clause; +struct kissat; + +reference kissat_allocate_clause (struct kissat *, size_t size); +void kissat_shrink_arena (struct kissat *); + +#if !defined(NDEBUG) || defined(LOGGING) + +bool kissat_clause_in_arena (const struct kissat *, const struct clause *); + +#endif + +static inline word kissat_align_ward (word w) { +#ifdef COMPACT + return kissat_align_word (w); +#else + return kissat_align_w2rd (w); +#endif +} + +#endif diff --git a/src/sat/kissat/array.h b/src/sat/kissat/array.h new file mode 100644 index 000000000..3ae23f90b --- /dev/null +++ b/src/sat/kissat/array.h @@ -0,0 +1,58 @@ +#ifndef _array_h_INCLUDED +#define _array_h_INCLUDED + +#include "allocate.h" +#include "stack.h" + +#define ARRAY(TYPE) \ + struct { \ + TYPE *begin; \ + TYPE *end; \ + } + +#define ALLOCATE_ARRAY(A, N) \ + do { \ + const size_t TMP_N = (N); \ + (A).begin = (A).end = \ + kissat_nalloc (solver, TMP_N, sizeof *(A).begin); \ + } while (0) + +#define EMPTY_ARRAY EMPTY_STACK +#define SIZE_ARRAY SIZE_STACK + +#define PUSH_ARRAY(A, E) \ + do { \ + *(A).end++ = (E); \ + } while (0) + +#define REALLOCATE_ARRAY(A, O, N) \ + do { \ + const size_t SIZE = SIZE_ARRAY (A); \ + (A).begin = \ + kissat_nrealloc (solver, (A).begin, (O), (N), sizeof *(A).begin); \ + (A).end = (A).begin + SIZE; \ + } while (0) + +#define RELEASE_ARRAY(A, N) \ + do { \ + const size_t TMP_NIZE = (N); \ + DEALLOC ((A).begin, TMP_NIZE); \ + } while (0) + +#define CLEAR_ARRAY CLEAR_STACK +#define TOP_ARRAY TOP_STACK +#define PEEK_ARRAY PEEK_STACK +#define POKE_ARRAY POKE_STACK +#define POP_ARRAY POP_STACK +#define BEGIN_ARRAY BEGIN_STACK +#define END_ARRAY END_STACK +#define RESIZE_ARRAY RESIZE_STACK +#define SET_END_OF_ARRAY SET_END_OF_STACK + +// clang-format off + +typedef ARRAY (unsigned) unsigned_array; + +// clang-format on + +#endif diff --git a/src/sat/kissat/assign.c b/src/sat/kissat/assign.c new file mode 100644 index 000000000..6a34f3529 --- /dev/null +++ b/src/sat/kissat/assign.c @@ -0,0 +1,59 @@ +#include "assign.h" +#include "inline.h" +#include "inlineassign.h" +#include "logging.h" + +#include + +void kissat_assign_unit (kissat *solver, unsigned lit, const char *reason) { + kissat_assign (solver, solver->probing, 0, false, lit, UNIT_REASON); + LOGUNARY (lit, "assign %s %s", LOGLIT (lit), reason); +#ifndef LOGGING + (void) reason; +#endif +} + +void kissat_learned_unit (kissat *solver, unsigned lit) { + kissat_assign_unit (solver, lit, "learned reason"); + CHECK_AND_ADD_UNIT (lit); + ADD_UNIT_TO_PROOF (lit); +} + +void kissat_original_unit (kissat *solver, unsigned lit) { + kissat_assign_unit (solver, lit, "original reason"); +} + +void kissat_assign_decision (kissat *solver, unsigned lit) { + kissat_assign (solver, solver->probing, solver->level, false, lit, + DECISION_REASON); + LOG ("assign %s decision", LOGLIT (lit)); +} + +void kissat_assign_binary (kissat *solver, unsigned lit, unsigned other) { + assert (VALUE (other) < 0); + assigned *assigned = solver->assigned; + const unsigned other_idx = IDX (other); + struct assigned *a = assigned + other_idx; + unsigned level = a->level; + if (GET_OPTION (jumpreasons) && level && a->binary) { + LOGBINARY (lit, other, "jumping %s reason", LOGLIT (lit)); + INC (jumped_reasons); + other = a->reason; + } + kissat_assign (solver, solver->probing, a->level, true, lit, other); + LOGBINARY (lit, other, "assign %s reason", LOGLIT (lit)); +} + +void kissat_assign_reference (kissat *solver, unsigned lit, reference ref, + clause *reason) { + assert (reason == kissat_dereference_clause (solver, ref)); + assigned *assigned = solver->assigned; + value *values = solver->values; + const unsigned level = + kissat_assignment_level (solver, values, assigned, lit, reason); + assert (level <= solver->level); + assert (ref != DECISION_REASON); + assert (ref != UNIT_REASON); + kissat_assign (solver, solver->probing, level, false, lit, ref); + LOGREF (ref, "assign %s reason", LOGLIT (lit)); +} diff --git a/src/sat/kissat/assign.h b/src/sat/kissat/assign.h new file mode 100644 index 000000000..64ac2027d --- /dev/null +++ b/src/sat/kissat/assign.h @@ -0,0 +1,55 @@ +#ifndef _assign_h_INCLUDED +#define _assign_h_INCLUDED + +#include + +#define DECISION_REASON UINT_MAX +#define UNIT_REASON (DECISION_REASON - 1) + +#define INVALID_LEVEL UINT_MAX +#define INVALID_TRAIL UINT_MAX + +typedef struct assigned assigned; +struct clause; + +struct assigned { + unsigned level; + unsigned trail; + + bool analyzed : 1; + bool binary : 1; + bool poisoned : 1; + bool removable : 1; + bool shrinkable : 1; + + unsigned reason; +}; + +#define ASSIGNED(LIT) \ + (assert (VALID_INTERNAL_LITERAL (LIT)), solver->assigned + IDX (LIT)) + +#define LEVEL(LIT) (ASSIGNED (LIT)->level) +#define TRAIL(LIT) (ASSIGNED (LIT)->trail) +#define REASON(LIT) (ASSIGNED (LIT)->reason) + +#ifndef FAST_ASSIGN + +#include "reference.h" + +struct kissat; +struct clause; + +void kissat_assign_unit (struct kissat *, unsigned lit, const char *); +void kissat_learned_unit (struct kissat *, unsigned lit); +void kissat_original_unit (struct kissat *, unsigned lit); + +void kissat_assign_decision (struct kissat *, unsigned lit); + +void kissat_assign_binary (struct kissat *, unsigned, unsigned); + +void kissat_assign_reference (struct kissat *, unsigned lit, reference, + struct clause *); + +#endif + +#endif diff --git a/src/sat/kissat/attribute.h b/src/sat/kissat/attribute.h new file mode 100644 index 000000000..9ec5f40ab --- /dev/null +++ b/src/sat/kissat/attribute.h @@ -0,0 +1,10 @@ +#ifndef _attribute_h_INCLUDED +#define _attribute_h_INCLUDED + +#define ATTRIBUTE_FORMAT(FORMAT_POSITION, VARIADIC_ARGUMENT_POSITION) \ + __attribute__ (( \ + format (printf, FORMAT_POSITION, VARIADIC_ARGUMENT_POSITION))) + +#define ATTRIBUTE_ALWAYS_INLINE __attribute__ ((always_inline)) + +#endif diff --git a/src/sat/kissat/averages.c b/src/sat/kissat/averages.c new file mode 100644 index 000000000..03c96a73f --- /dev/null +++ b/src/sat/kissat/averages.c @@ -0,0 +1,17 @@ +#include "internal.h" + +void kissat_init_averages (kissat *solver, averages *averages) { + if (averages->initialized) + return; +#define INIT_EMA(EMA, WINDOW) \ + kissat_init_smooth (solver, &averages->EMA, WINDOW, #EMA) +#ifndef QUIET + INIT_EMA (level, GET_OPTION (emaslow)); + INIT_EMA (size, GET_OPTION (emaslow)); + INIT_EMA (trail, GET_OPTION (emaslow)); +#endif + INIT_EMA (fast_glue, GET_OPTION (emafast)); + INIT_EMA (slow_glue, GET_OPTION (emaslow)); + INIT_EMA (decision_rate, GET_OPTION (emaslow)); + averages->initialized = true; +} diff --git a/src/sat/kissat/averages.h b/src/sat/kissat/averages.h new file mode 100644 index 000000000..d8cd03045 --- /dev/null +++ b/src/sat/kissat/averages.h @@ -0,0 +1,33 @@ +#ifndef _averages_h_INCLUDED +#define _averages_h_INCLUDED + +#include "smooth.h" + +#include + +typedef struct averages averages; + +struct averages { + bool initialized; + smooth fast_glue, slow_glue; +#ifndef QUIET + smooth level, size, trail; +#endif + smooth decision_rate; + uint64_t saved_decisions; +}; + +struct kissat; + +void kissat_init_averages (struct kissat *, averages *); + +#define AVERAGES (solver->averages[solver->stable]) + +#define EMA(NAME) (AVERAGES.NAME) + +#define AVERAGE(NAME) (EMA (NAME).value) + +#define UPDATE_AVERAGE(NAME, VALUE) \ + kissat_update_smooth (solver, &EMA (NAME), VALUE) + +#endif diff --git a/src/sat/kissat/backbone.c b/src/sat/kissat/backbone.c new file mode 100644 index 000000000..6a774efce --- /dev/null +++ b/src/sat/kissat/backbone.c @@ -0,0 +1,598 @@ +#include "backbone.h" +#include "allocate.h" +#include "analyze.h" +#include "backtrack.h" +#include "decide.h" +#include "inline.h" +#include "internal.h" +#include "logging.h" +#include "print.h" +#include "proprobe.h" +#include "report.h" +#include "terminate.h" +#include "trail.h" +#include "utilities.h" + +static void schedule_backbone_candidates (kissat *solver, + unsigneds *candidates) { + flags *flags = solver->flags; + unsigned not_rescheduled = 0; + for (all_variables (idx)) { + const struct flags *f = flags + idx; + if (!f->active) + continue; + const unsigned lit = LIT (idx); + if (f->backbone0) { + PUSH_STACK (*candidates, lit); + LOG ("rescheduling backbone literal candidate %s", LOGLIT (lit)); + } else + not_rescheduled++; + if (f->backbone1) { + const unsigned not_lit = NOT (lit); + PUSH_STACK (*candidates, not_lit); + LOG ("rescheduling backbone literal candidate %s", LOGLIT (not_lit)); + } else + not_rescheduled++; + } +#ifndef QUIET + const size_t rescheduled = SIZE_STACK (*candidates); + const unsigned active_literals = 2u * solver->active; + kissat_very_verbose ( + solver, "rescheduled %zu backbone candidate literals %.0f%%", + rescheduled, kissat_percent (rescheduled, active_literals)); +#endif + if (not_rescheduled) { + for (all_variables (idx)) { + struct flags *f = flags + idx; + if (!f->active) + continue; + const unsigned lit = LIT (idx); + if (!f->backbone0) { + LOG ("scheduling backbone literal candidate %s", LOGLIT (lit)); + PUSH_STACK (*candidates, lit); + } + if (!f->backbone1) { + const unsigned not_lit = NOT (lit); + LOG ("scheduling backbone literal candidate %s", LOGLIT (not_lit)); + PUSH_STACK (*candidates, not_lit); + } + } + } +#ifndef QUIET + const size_t total = SIZE_STACK (*candidates); + kissat_very_verbose (solver, + "scheduled %zu backbone candidate literals %.0f%%" + " in total", + total, kissat_percent (total, active_literals)); +#endif +} + +static void keep_backbone_candidates (kissat *solver, + unsigneds *candidates) { + flags *flags = solver->flags; + size_t prioritized = 0; + size_t remain = 0; + for (all_stack (unsigned, lit, *candidates)) { + const unsigned idx = IDX (lit); + const struct flags *f = flags + idx; + if (!f->active) + continue; + remain++; + if (NEGATED (lit)) + prioritized += f->backbone1; + else + prioritized += f->backbone0; + } + assert (prioritized <= remain); + if (!remain) { + kissat_very_verbose (solver, "no backbone candidates remain"); +#ifndef NDEBUG + for (all_variables (idx)) { + const struct flags *f = flags + idx; + if (!f->active) + continue; + assert (!f->backbone0); + assert (!f->backbone1); + } +#endif + return; + } +#ifndef QUIET + const size_t active_literals = 2u * solver->active; +#endif + if (prioritized == remain) + kissat_very_verbose (solver, + "keeping all remaining %zu backbone " + "candidates %.0f%% prioritized (all were)", + remain, kissat_percent (remain, active_literals)); + else if (!prioritized) { + for (all_stack (unsigned, lit, *candidates)) { + const unsigned idx = IDX (lit); + struct flags *f = flags + idx; + if (!f->active) + continue; + if (NEGATED (lit)) { + assert (!f->backbone1); + f->backbone1 = true; + } else { + assert (!f->backbone0); + f->backbone0 = true; + } + } + kissat_very_verbose (solver, + "keeping all remaining %zu backbone " + "candidates %.0f%% prioritized (none was)", + remain, kissat_percent (remain, active_literals)); + } else { + kissat_very_verbose (solver, + "keeping %zu backbone candidates %.0f%% " + "prioritized (%.0f%% of remaining %zu)", + prioritized, + kissat_percent (prioritized, active_literals), + kissat_percent (prioritized, remain), remain); + } +} + +static inline void backbone_assign (kissat *solver, unsigned_array *trail, + value *values, assigned *assigned, + unsigned lit, unsigned reason) { + const unsigned not_lit = NOT (lit); + assert (!values[lit]); + assert (!values[not_lit]); + values[lit] = 1; + values[not_lit] = -1; + PUSH_ARRAY (*trail, lit); + const unsigned idx = IDX (lit); + struct assigned *a = assigned + idx; + a->reason = reason; + a->level = solver->level; +} + +static inline clause * +backbone_propagate_literal (kissat *solver, const bool stop_early, + const watches *const all_watches, + unsigned_array *trail, value *values, + assigned *assigned, unsigned lit) { + LOG ("backbone propagating %s", LOGLIT (lit)); + assert (VALID_INTERNAL_LITERAL (lit)); + assert (values[lit] > 0); + + const unsigned not_lit = NOT (lit); + assert (values[not_lit] < 0); + + assert (not_lit < LITS); + const watches *const watches = all_watches + not_lit; + + const watch *const begin_watches = BEGIN_CONST_WATCHES (*watches); + const watch *const end_watches = END_CONST_WATCHES (*watches); + const watch *p = begin_watches; + + while (p != end_watches) { + const watch watch = *p++; + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + assert (VALID_INTERNAL_LITERAL (other)); + const value value = values[other]; + if (value > 0) + continue; + if (value < 0) + return kissat_binary_conflict (solver, not_lit, other); + assert (!value); + backbone_assign (solver, trail, values, assigned, other, lit); + LOG ("backbone assign %s reason binary clause %s %s", LOGLIT (other), + LOGLIT (other), LOGLIT (not_lit)); + } else { + if (stop_early) { +#ifndef NDEBUG + for (const union watch *q = p + 1; q != end_watches; q++) { + const union watch watch = *q++; + assert (!watch.type.binary); + } +#endif + break; + } + + p++; + } + } + + const size_t touched = p - begin_watches; + solver->ticks += 1 + kissat_cache_lines (touched, sizeof (watch)); + + return 0; +} + +static inline clause *backbone_propagate (kissat *solver, + unsigned_array *trail, + value *values, + assigned *assigned) { + const bool stop_early = + solver->large_clauses_watched_after_binary_clauses; + + clause *conflict = 0; + solver->ticks = 0; + + const watches *const watches = solver->watches; + unsigned *propagate = solver->propagate; + + while (!conflict && propagate != END_ARRAY (*trail)) + conflict = backbone_propagate_literal ( + solver, stop_early, watches, trail, values, assigned, *propagate++); + + assert (solver->propagate <= propagate); + const unsigned propagated = propagate - solver->propagate; + solver->propagate = propagate; + + ADD (backbone_propagations, propagated); + ADD (probing_propagations, propagated); + ADD (propagations, propagated); + + const uint64_t ticks = solver->ticks; + + ADD (backbone_ticks, ticks); + ADD (probing_ticks, ticks); + ADD (ticks, ticks); + + return conflict; +} + +static inline void backbone_backtrack (kissat *solver, + unsigned_array *trail, value *values, + unsigned *saved, + unsigned decision_level) { + assert (decision_level <= solver->level); + unsigned *end_trail = END_ARRAY (*trail); + assert (saved != end_trail); + LOG ("backbone backtracking to trail level %zu and decision level %u", + (size_t) (saved - BEGIN_ARRAY (*trail)), decision_level); + while (saved != end_trail) { + const unsigned lit = *--end_trail; + const unsigned not_lit = NOT (lit); + LOG ("backbone unassign %s", LOGLIT (lit)); + assert (values[lit] > 0); + assert (values[not_lit] < 0); + values[lit] = values[not_lit] = 0; + } + SET_END_OF_ARRAY (solver->trail, saved); + solver->level = decision_level; + solver->propagate = saved; +} + +static unsigned backbone_analyze (kissat *solver, clause *conflict) { + assert (conflict); + LOGCLS (conflict, "backbone analyzing"); + assert (conflict->size == 2); + + assigned *const assigned = solver->assigned; + + kissat_push_analyzed (solver, assigned, IDX (conflict->lits[0])); + kissat_push_analyzed (solver, assigned, IDX (conflict->lits[1])); + + const unsigned *t = END_ARRAY (solver->trail); + + for (;;) { + assert (t > BEGIN_ARRAY (solver->trail)); + + unsigned lit = *--t; + + const unsigned lit_idx = IDX (lit); + const struct assigned *a = assigned + lit_idx; + if (!a->analyzed) + continue; + + LOG ("backbone analyzing %s", LOGLIT (lit)); + const unsigned reason = a->reason; + assert (reason != UNIT_REASON); + assert (reason != DECISION_REASON); + const unsigned reason_idx = IDX (reason); + const struct assigned *b = assigned + reason_idx; + if (!b->analyzed) { + LOG ("reason %s of %s not yet analyzed", LOGLIT (reason), + LOGLIT (lit)); + kissat_push_analyzed (solver, assigned, reason_idx); + } else { + LOG ("backbone UIP %s", LOGLIT (reason)); + kissat_reset_only_analyzed_literals (solver); + return reason; + } + } +} + +#ifndef NDEBUG + +static void +check_large_clauses_watched_after_binary_clauses (kissat *solver) { + for (all_literals (lit)) { + bool large = false; + for (all_binary_blocking_watches (watch, WATCHES (lit))) + if (watch.type.binary) + assert (!large); + else + large = true; + } +} + +#endif + +static unsigned compute_backbone (kissat *solver) { +#ifndef NDEBUG + if (solver->large_clauses_watched_after_binary_clauses) + check_large_clauses_watched_after_binary_clauses (solver); +#endif + size_t failed = 0; + unsigneds units; + unsigneds candidates; + INIT_STACK (candidates); + INIT_STACK (units); + schedule_backbone_candidates (solver, &candidates); +#ifndef QUIET + const size_t scheduled = SIZE_STACK (candidates); +#endif +#if defined(METRICS) && (!defined(QUIET) || !defined(NDEBUG)) + const uint64_t implied_before = solver->statistics.backbone_implied; +#endif + unsigned_array *trail = &solver->trail; + value *values = solver->values; + flags *flags = solver->flags; + assigned *assigned = solver->assigned; + + assert (kissat_propagated (solver)); + assert (kissat_trail_flushed (solver)); + + unsigned inconsistent = INVALID_LIT; + + SET_EFFORT_LIMIT (ticks_limit, backbone, backbone_ticks); + size_t round_limit = GET_OPTION (backbonerounds); + assert (solver->statistics.backbone_computations); + round_limit *= solver->statistics.backbone_computations; + const size_t max_rounds = GET_OPTION (backbonemaxrounds); + if (round_limit > max_rounds) + round_limit = max_rounds; + + size_t round = 0; + + for (;;) { + if (round >= round_limit) { + kissat_very_verbose (solver, "backbone round limit %zu hit", round); + break; + } + const uint64_t ticks = solver->statistics.backbone_ticks; + if (ticks > ticks_limit) { + kissat_very_verbose (solver, + "backbone ticks limit %" PRIu64 " hit " + "after %" PRIu64 " ticks", + ticks_limit, ticks); + break; + } + size_t previous = failed; + assert (!solver->inconsistent); + if (TERMINATED (backbone_terminated_1)) + break; + round++; + INC (backbone_rounds); + LOG ("starting backbone round %zu", round); + unsigned *const begin_candidates = BEGIN_STACK (candidates); + assert (!solver->level); +#if !defined(QUIET) && defined(METRICS) + size_t decisions = 0; + uint64_t propagated = solver->statistics.backbone_propagations; +#endif + unsigned active_before = solver->active; + { + unsigned *q = begin_candidates; + const unsigned *p = begin_candidates; + const unsigned *const end_candidates = END_STACK (candidates); + while (p != end_candidates) { + assert (!solver->inconsistent); + const unsigned probe = *q++ = *p++; + const value value = values[probe]; + if (value > 0) { + q--; + LOG ("removing satisfied backbone probe %s", LOGLIT (probe)); + const unsigned idx = IDX (probe); + struct flags *f = flags + idx; + if (NEGATED (probe)) + f->backbone1 = false; + else + f->backbone0 = false; + continue; + } + if (value < 0) { + const unsigned idx = IDX (probe); + struct assigned *a = assigned + idx; + if (a->level) + LOG ("skipping falsified backbone probe %s", LOGLIT (probe)); + else { + LOG ("removing root-level falsified backbone probe %s", + LOGLIT (probe)); + q--; + } + continue; + } + if (solver->statistics.backbone_ticks > ticks_limit) + break; + if (TERMINATED (backbone_terminated_2)) + break; + const unsigned level = solver->level; + unsigned *const saved = END_ARRAY (*trail); + assert (level != UINT_MAX); +#if !defined(QUIET) && defined(METRICS) + decisions++; +#endif + solver->level = level + 1; + INC (backbone_probes); + backbone_assign (solver, trail, values, assigned, probe, + DECISION_REASON); + LOG ("backbone assume %s", LOGLIT (probe)); + clause *conflict = + backbone_propagate (solver, trail, values, assigned); + if (!conflict) { + LOG ("propagating backbone probe %s successful", LOGLIT (probe)); + continue; + } + + failed++; + INC (backbone_units); + q--; + + LOG ("propagating backbone probe %s failed", LOGLIT (probe)); + unsigned uip = backbone_analyze (solver, conflict); + unsigned not_uip = NOT (uip); + backbone_backtrack (solver, trail, values, saved, level); + + PUSH_STACK (units, not_uip); + backbone_assign (solver, trail, values, assigned, not_uip, + UNIT_REASON); + LOG ("backbone forced assign %s", LOGLIT (not_uip)); + assert (failed == SIZE_STACK (units)); + + conflict = backbone_propagate (solver, trail, values, assigned); + if (conflict) { + LOG ("propagating backbone forced %s failed", LOGLIT (not_uip)); + inconsistent = not_uip; + break; + } + + LOG ("propagating backbone forced %s successful", LOGLIT (not_uip)); + } +#ifndef QUIET + size_t remain = end_candidates - p; + if (remain) + kissat_extremely_verbose (solver, + "backbone round %zu aborted with " + "%zu candidates %.0f%% remaining", + round, remain, + kissat_percent (remain, scheduled)); + else + kissat_extremely_verbose (solver, + "backbone round %zu completed with " + "all %zu scheduled candidates tried", + round, scheduled); +#endif + while (p != end_candidates) + *q++ = *p++; + + SET_END_OF_STACK (candidates, q); + } + if (inconsistent == INVALID_LIT) { + LOG ("flushing satisfied probe candidates"); + unsigned *q = begin_candidates; + const unsigned *p = begin_candidates; + const unsigned *const end_candidates = END_STACK (candidates); + while (p != end_candidates) { + const unsigned probe = *q++ = *p++; + const value value = values[probe]; + if (value > 0) { + q--; + LOG ("removing satisfied backbone probe %s", LOGLIT (probe)); + const unsigned idx = IDX (probe); + struct flags *f = flags + idx; + if (NEGATED (probe)) + f->backbone1 = false; + else + f->backbone0 = false; + continue; + } + if (value < 0) { + LOG ("keeping falsified probe %s", LOGLIT (probe)); + continue; + } + assert (!value); + LOG ("keeping unassigned probe %s", LOGLIT (probe)); + } + LOG ("flushed %zu probe candidates", + (size_t) (q - BEGIN_STACK (candidates))); + SET_END_OF_STACK (candidates, q); + } + if (!EMPTY_ARRAY (*trail)) + backbone_backtrack (solver, trail, values, BEGIN_ARRAY (*trail), 0); + if (inconsistent == INVALID_LIT && previous < failed) { + for (size_t i = previous; i < failed; i++) { + const unsigned unit = PEEK_STACK (units, i); + LOG ("assigning backbone unit %s", LOGLIT (unit)); + kissat_learned_unit (solver, unit); + } + if (kissat_probing_propagate (solver, 0, true)) + break; + } + assert (solver->active <= active_before); + unsigned implied = active_before - solver->active; + assert (failed <= failed); + ADD (backbone_implied, implied); +#ifndef QUIET +#ifdef METRICS + propagated = solver->statistics.backbone_propagations - propagated; + kissat_very_verbose (solver, + "backbone round %zu with %zu decisions " + "(%.2f propagations per decision)", + round, decisions, + kissat_average (propagated, decisions)); +#endif + size_t left = SIZE_STACK (candidates); + kissat_very_verbose (solver, + "backbone round %zu produced %zu failed literals" + " %u implied (%zu candidates left %.0f%%)", + round, failed - previous, implied, left, + kissat_percent (left, scheduled)); +#endif + if (inconsistent != INVALID_LIT) + break; + if (EMPTY_STACK (candidates)) + break; + } + + if (inconsistent != INVALID_LIT && !solver->inconsistent) { + LOG ("assuming forced unit %s", LOGLIT (inconsistent)); + kissat_learned_unit (solver, inconsistent); + (void) kissat_probing_propagate (solver, 0, true); + assert (solver->inconsistent); + } + RELEASE_STACK (units); + if (solver->inconsistent) + kissat_phase (solver, "backbone", GET (backbone_computations), + "inconsistent binary clauses"); + else { + keep_backbone_candidates (solver, &candidates); +#if defined(METRICS) && (!defined(QUIET) || !defined(NDEBUG)) + assert (implied_before <= solver->statistics.backbone_implied); +#endif +#if defined(METRICS) && !defined(QUIET) + const uint64_t total_implied = + solver->statistics.backbone_implied - implied_before; + kissat_phase (solver, "backbone", GET (backbone_computations), + "found %zu backbone literals %" PRIu64 + " implied in %zu rounds", + failed, total_implied, round); +#endif + } + RELEASE_STACK (candidates); + return failed; +} + +void kissat_binary_clauses_backbone (kissat *solver) { + if (solver->inconsistent) + return; + if (!GET_OPTION (backbone)) + return; + if (TERMINATED (backbone_terminated_3)) + return; + assert (solver->watching); + assert (solver->probing); + assert (!solver->level); + START (backbone); + INC (backbone_computations); +#if !defined(NDEBUG) || defined(METRICS) + assert (!solver->backbone_computing); + solver->backbone_computing = true; +#endif +#ifndef QUIET + const unsigned failed = +#endif + compute_backbone (solver); + REPORT (!failed, 'b'); +#if !defined(NDEBUG) || defined(METRICS) + assert (solver->backbone_computing); + solver->backbone_computing = false; +#endif + STOP (backbone); +} diff --git a/src/sat/kissat/backbone.h b/src/sat/kissat/backbone.h new file mode 100644 index 000000000..0ddf87c99 --- /dev/null +++ b/src/sat/kissat/backbone.h @@ -0,0 +1,9 @@ +#ifndef _backbone_h_INCLUDED +#define _backbone_h_INCLUDED + +#include + +struct kissat; +void kissat_binary_clauses_backbone (struct kissat *); + +#endif diff --git a/src/sat/kissat/backtrack.c b/src/sat/kissat/backtrack.c new file mode 100644 index 000000000..09ba8df70 --- /dev/null +++ b/src/sat/kissat/backtrack.c @@ -0,0 +1,177 @@ +#include "backtrack.h" +#include "analyze.h" +#include "inline.h" +#include "inlineheap.h" +#include "inlinequeue.h" +#include "print.h" +#include "proprobe.h" +#include "propsearch.h" +#include "trail.h" + +static inline void unassign (kissat *solver, value *values, unsigned lit) { + LOG ("unassign %s", LOGLIT (lit)); + assert (values[lit] > 0); + const unsigned not_lit = NOT (lit); + values[lit] = values[not_lit] = 0; + assert (solver->unassigned < VARS); + solver->unassigned++; +} + +static inline void add_unassigned_variable_back_to_queue (kissat *solver, + links *links, + unsigned lit) { + assert (!solver->stable); + const unsigned idx = IDX (lit); + if (links[idx].stamp > solver->queue.search.stamp) + kissat_update_queue (solver, links, idx); +} + +static inline void add_unassigned_variable_back_to_heap (kissat *solver, + heap *scores, + unsigned lit) { + assert (solver->stable); + const unsigned idx = IDX (lit); + if (!kissat_heap_contains (scores, idx)) + kissat_push_heap (solver, scores, idx); +} + +static void kissat_update_target_and_best_phases (kissat *solver) { + if (solver->probing) + return; + + if (!solver->stable) + return; + + const unsigned assigned = kissat_assigned (solver); +#ifdef LOGGING + LOG ("updating target and best phases"); + LOG ("currently %u variables assigned", assigned); +#endif + + if (solver->target_assigned < assigned) { + kissat_extremely_verbose (solver, + "updating target assigned " + "trail height from %u to %u", + solver->target_assigned, assigned); + solver->target_assigned = assigned; + kissat_save_target_phases (solver); + INC (target_saved); + } + + if (solver->best_assigned < assigned) { + kissat_extremely_verbose (solver, + "updating best assigned " + "trail height from %u to %u", + solver->best_assigned, assigned); + solver->best_assigned = assigned; + kissat_save_best_phases (solver); + INC (best_saved); + } +} + +void kissat_backtrack_without_updating_phases (kissat *solver, + unsigned new_level) { + assert (solver->level >= new_level); + if (solver->level == new_level) + return; + + LOG ("backtracking to decision level %u", new_level); + + frame *new_frame = &FRAME (new_level + 1); + SET_END_OF_STACK (solver->frames, new_frame); + + value *values = solver->values; + unsigned *trail = BEGIN_ARRAY (solver->trail); + unsigned *new_end = trail + new_frame->trail; + assigned *assigned = solver->assigned; + + unsigned *old_end = END_ARRAY (solver->trail); + unsigned unassigned = 0, reassigned = 0; + + unsigned *q = new_end; + if (solver->stable) { + heap *scores = SCORES; + for (const unsigned *p = q; p != old_end; p++) { + const unsigned lit = *p; + const unsigned idx = IDX (lit); + assert (idx < VARS); + struct assigned *a = assigned + idx; + const unsigned level = a->level; + if (level <= new_level) { + const unsigned new_trail = q - trail; + assert (new_trail <= a->trail); + a->trail = new_trail; + *q++ = lit; + LOG ("reassign %s", LOGLIT (lit)); + reassigned++; + } else { + unassign (solver, values, lit); + add_unassigned_variable_back_to_heap (solver, scores, lit); + unassigned++; + } + } + } else { + links *links = solver->links; + for (const unsigned *p = q; p != old_end; p++) { + const unsigned lit = *p; + const unsigned idx = IDX (lit); + assert (idx < VARS); + struct assigned *a = assigned + idx; + const unsigned level = a->level; + if (level <= new_level) { + const unsigned new_trail = q - trail; + assert (new_trail <= a->trail); + a->trail = new_trail; + *q++ = lit; + LOG ("reassign %s", LOGLIT (lit)); + reassigned++; + } else { + unassign (solver, values, lit); + add_unassigned_variable_back_to_queue (solver, links, lit); + unassigned++; + } + } + } + SET_END_OF_ARRAY (solver->trail, q); + + solver->level = new_level; + LOG ("unassigned %u literals", unassigned); + LOG ("reassigned %u literals", reassigned); + (void) unassigned, (void) reassigned; + + assert (new_end <= END_ARRAY (solver->trail)); + LOG ("propagation will resume at trail position %zu", + (size_t) (new_end - trail)); + solver->propagate = new_end; + + assert (!solver->extended); +} + +void kissat_backtrack_in_consistent_state (kissat *solver, + unsigned new_level) { + kissat_update_target_and_best_phases (solver); + kissat_backtrack_without_updating_phases (solver, new_level); +} + +void kissat_backtrack_after_conflict (kissat *solver, unsigned new_level) { + if (solver->level) + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + kissat_update_target_and_best_phases (solver); + kissat_backtrack_without_updating_phases (solver, new_level); +} + +void kissat_backtrack_propagate_and_flush_trail (kissat *solver) { + if (solver->level) { + assert (solver->watching); + kissat_backtrack_in_consistent_state (solver, 0); +#ifndef NDEBUG + clause *conflict = +#endif + solver->probing ? kissat_probing_propagate (solver, 0, true) + : kissat_search_propagate (solver); + assert (!conflict); + } + + assert (kissat_propagated (solver)); + assert (kissat_trail_flushed (solver)); +} diff --git a/src/sat/kissat/backtrack.h b/src/sat/kissat/backtrack.h new file mode 100644 index 000000000..7a9b93bf7 --- /dev/null +++ b/src/sat/kissat/backtrack.h @@ -0,0 +1,11 @@ +#ifndef _backtrack_h_INCLUDED +#define _backtrack_h_INCLUDED + +struct kissat; + +void kissat_backtrack_without_updating_phases (struct kissat *, unsigned); +void kissat_backtrack_in_consistent_state (struct kissat *, unsigned); +void kissat_backtrack_after_conflict (struct kissat *, unsigned); +void kissat_backtrack_propagate_and_flush_trail (struct kissat *); + +#endif diff --git a/src/sat/kissat/build.c b/src/sat/kissat/build.c new file mode 100644 index 000000000..0426854a2 --- /dev/null +++ b/src/sat/kissat/build.c @@ -0,0 +1,81 @@ +#include "build.h" +#include "colors.h" +#include "kissat.h" +#include "print.h" + +#include + +const char *kissat_signature (void) { return "kissat-" VERSION; } + +const char *kissat_id (void) { return ID; } + +const char *kissat_compiler (void) { return COMPILER; } + +static const char *copyright_lines[] = { + "Copyright (c) 2021-2024 Armin Biere University of Freiburg", + "Copyright (c) 2019-2021 Armin Biere Johannes Kepler University Linz", + 0}; + +const char **kissat_copyright (void) { return copyright_lines; } + +const char *kissat_version (void) { return VERSION; } + +#define PREFIX(COLORS) \ + do { \ + if (prefix) \ + fputs (prefix, stdout); \ + COLOR (COLORS); \ + } while (0) + +#define NL() \ + do { \ + fputs ("\n", stdout); \ + COLOR (NORMAL); \ + } while (0) + +void kissat_build (const char *prefix) { + TERMINAL (stdout, 1); + if (!prefix) + connected_to_terminal = false; + + PREFIX (MAGENTA); + if (ID) + printf ("Version %s %s", VERSION, ID); + else + printf ("Version %s", VERSION); + NL (); + + PREFIX (MAGENTA); + printf ("%s", COMPILER); + NL (); + + PREFIX (MAGENTA); + printf ("%s", BUILD); + NL (); +} + +void kissat_banner (const char *prefix, const char *name) { + TERMINAL (stdout, 1); + if (!prefix) + connected_to_terminal = false; + + PREFIX (BOLD MAGENTA); + printf ("%s", name); + NL (); + + PREFIX (BOLD MAGENTA); + NL (); + + for (const char **p = kissat_copyright (), *line; (line = *p); p++) { + PREFIX (BOLD MAGENTA); + fputs (line, stdout); + NL (); + } + + if (prefix) { + PREFIX (""); + NL (); + } + + kissat_build (prefix); +} diff --git a/src/sat/kissat/bump.c b/src/sat/kissat/bump.c new file mode 100644 index 000000000..5771db4ac --- /dev/null +++ b/src/sat/kissat/bump.c @@ -0,0 +1,120 @@ +#include "bump.h" +#include "analyze.h" +#include "inlineheap.h" +#include "inlinequeue.h" +#include "inlinevector.h" +#include "internal.h" +#include "logging.h" +#include "print.h" +#include "rank.h" +#include "sort.h" + +#define RANK(A) ((A).rank) +#define SMALLER(A, B) (RANK (A) < RANK (B)) + +#define RADIX_SORT_BUMP_LIMIT 32 + +static void sort_bump (kissat *solver) { + const size_t size = SIZE_STACK (solver->analyzed); + if (size < RADIX_SORT_BUMP_LIMIT) { + LOG ("quick sorting %zu analyzed variables", size); + SORT_STACK (datarank, solver->ranks, SMALLER); + } else { + LOG ("radix sorting %zu analyzed variables", size); + RADIX_STACK (datarank, unsigned, solver->ranks, RANK); + } +} + +void kissat_rescale_scores (kissat *solver) { + INC (rescaled); + heap *scores = &solver->scores; + const double max_score = kissat_max_score_on_heap (scores); + kissat_phase (solver, "rescale", GET (rescaled), + "maximum score %g increment %g", max_score, solver->scinc); + const double rescale = MAX (max_score, solver->scinc); + assert (rescale > 0); + const double factor = 1.0 / rescale; + kissat_rescale_heap (solver, scores, factor); + solver->scinc *= factor; + kissat_phase (solver, "rescale", GET (rescaled), "rescaled by factor %g", + factor); +} + +void kissat_bump_score_increment (kissat *solver) { + const double old_scinc = solver->scinc; + const double decay = GET_OPTION (decay) * 1e-3; + assert (0 <= decay), assert (decay <= 0.5); + const double factor = 1.0 / (1.0 - decay); + const double new_scinc = old_scinc * factor; + LOG ("new score increment %g = %g * %g", new_scinc, factor, old_scinc); + solver->scinc = new_scinc; + if (new_scinc > MAX_SCORE) + kissat_rescale_scores (solver); +} + +static inline void bump_analyzed_variable_score (kissat *solver, + unsigned idx) { + heap *scores = &solver->scores; + const double old_score = kissat_get_heap_score (scores, idx); + const double inc = solver->scinc; + const double new_score = old_score + inc; + LOG ("new score[%u] = %g = %g + %g", idx, new_score, old_score, inc); + kissat_update_heap (solver, scores, idx, new_score); + if (new_score > MAX_SCORE) + kissat_rescale_scores (solver); +} + +void kissat_bump_variable (kissat *solver, unsigned idx) { + bump_analyzed_variable_score (solver, idx); +} + +static void bump_analyzed_variable_scores (kissat *solver) { + flags *flags = solver->flags; + + for (all_stack (unsigned, idx, solver->analyzed)) + if (flags[idx].active) + bump_analyzed_variable_score (solver, idx); + + kissat_bump_score_increment (solver); +} + +static void move_analyzed_variables_to_front_of_queue (kissat *solver) { + assert (EMPTY_STACK (solver->ranks)); + const links *const links = solver->links; + for (all_stack (unsigned, idx, solver->analyzed)) { + // clang-format off + const datarank rank = { .data = idx, .rank = links[idx].stamp }; + // clang-format on + PUSH_STACK (solver->ranks, rank); + } + + sort_bump (solver); + + flags *flags = solver->flags; + unsigned idx; + + for (all_stack (datarank, rank, solver->ranks)) + if (flags[idx = rank.data].active) + kissat_move_to_front (solver, idx); + + CLEAR_STACK (solver->ranks); +} + +void kissat_bump_analyzed (kissat *solver) { + START (bump); + const size_t bumped = SIZE_STACK (solver->analyzed); + if (!solver->stable) + move_analyzed_variables_to_front_of_queue (solver); + else + bump_analyzed_variable_scores (solver); + ADD (literals_bumped, bumped); + STOP (bump); +} + +void kissat_update_scores (kissat *solver) { + assert (solver->stable); + heap *scores = SCORES; + for (all_variables (idx)) + if (ACTIVE (idx) && !kissat_heap_contains (scores, idx)) + kissat_push_heap (solver, scores, idx); +} diff --git a/src/sat/kissat/bump.h b/src/sat/kissat/bump.h new file mode 100644 index 000000000..55d541562 --- /dev/null +++ b/src/sat/kissat/bump.h @@ -0,0 +1,16 @@ +#ifndef _bump_h_INCLUDED +#define _bump_h_INCLUDED + +#include + +struct kissat; + +void kissat_bump_analyzed (struct kissat *); +void kissat_update_scores (struct kissat *); +void kissat_rescale_scores (struct kissat *); +void kissat_bump_variable (struct kissat *, unsigned idx); +void kissat_bump_score_increment (struct kissat *); + +#define MAX_SCORE 1e150 + +#endif diff --git a/src/sat/kissat/check.c b/src/sat/kissat/check.c new file mode 100644 index 000000000..0245a0866 --- /dev/null +++ b/src/sat/kissat/check.c @@ -0,0 +1,1033 @@ +#ifndef NDEBUG + +#include "check.h" +#include "error.h" +#include "internal.h" +#include "literal.h" +#include "logging.h" +#include "print.h" + +#include +#include +#include + +#undef LOGPREFIX +#define LOGPREFIX "CHECK" + +void kissat_check_satisfying_assignment (kissat *solver) { + LOG ("checking satisfying assignment"); + const int *const begin = BEGIN_STACK (solver->original); + const int *const end = END_STACK (solver->original); +#ifdef LOGGING + size_t count = 0; +#endif + for (const int *p = begin, *q; p != end; p = q + 1) { + bool satisfied = false; + int lit, other; + for (q = p; (lit = *q); q++) + if (!satisfied && kissat_value (solver, lit) == lit) + satisfied = true; +#ifdef LOGGING + count++; +#endif + if (satisfied) + continue; + for (q = p; (lit = *q); q++) + for (const int *r = q + 1; (other = *r); r++) + if (lit == -other) + satisfied = true; + if (satisfied) + continue; + kissat_fatal_message_start (); + fputs ("unsatisfied clause:\n", stderr); + for (q = p; (lit = *q); q++) + fprintf (stderr, "%d ", lit); + fputs ("0\n", stderr); + fflush (stderr); + kissat_abort (); + } + LOG ("assignment satisfies all %zu original clauses", count); +} + +#include "allocate.h" +#include "inline.h" +#include "sort.h" + +typedef struct hash hash; +typedef struct bucket bucket; + +// clang-format off + +typedef STACK (bucket*) buckets; + +// clang-format on + +struct bucket { + bucket *next; + unsigned size; + unsigned hash; + unsigned lits[]; +}; + +struct checker { + bool inconsistent; + + unsigned vars; + unsigned size; + + unsigned buckets; + unsigned hashed; + + bucket **table; + + buckets *watches; + bool *marks; + bool *large; + bool *used; + signed char *values; + + bool marked; + unsigneds imported; + + unsigneds trail; + unsigned propagated; + + unsigned nonces[32]; + + uint64_t added; + uint64_t blocked; + uint64_t checked; + uint64_t collisions; + uint64_t decisions; + uint64_t propagations; + uint64_t pure; + uint64_t removed; + uint64_t satisfied; + uint64_t searches; + uint64_t unchecked; +}; + +#define LOGIMPORTED3(...) \ + LOGUNSIGNEDS3 (SIZE_STACK (checker->imported), \ + BEGIN_STACK (checker->imported), __VA_ARGS__) + +#define LOGLINE3(...) \ + LOGUNSIGNEDS3 (bucket->size, bucket->lits, __VA_ARGS__) + +#define MAX_NONCES (sizeof checker->nonces / sizeof *checker->nonces) + +static inline bool less_unsigned (unsigned a, unsigned b) { return a < b; } + +static void sort_line (kissat *solver, checker *checker) { + SORT_STACK (unsigned, checker->imported, less_unsigned); + LOGIMPORTED3 ("sorted checker"); +} + +static unsigned hash_line (checker *checker) { + unsigned res = 0, pos = 0; + for (all_stack (unsigned, lit, checker->imported)) { + res += checker->nonces[pos++] * lit; + if (pos == MAX_NONCES) + pos = 0; + } + return res; +} + +static size_t bytes_line (unsigned size) { + return sizeof (bucket) + size * sizeof (unsigned); +} + +static void init_nonces (kissat *solver, checker *checker) { + generator random = 42; + for (unsigned i = 0; i < MAX_NONCES; i++) + checker->nonces[i] = 1 | kissat_next_random32 (&random); + LOG3 ("initialized %zu checker nonces", MAX_NONCES); +#ifndef LOGGING + (void) solver; +#endif +} + +void kissat_init_checker (kissat *solver) { + LOG ("initializing internal proof checker"); + checker *checker = kissat_calloc (solver, 1, sizeof (struct checker)); + solver->checker = checker; + init_nonces (solver, checker); +} + +static void release_hash (kissat *solver, checker *checker) { + for (unsigned h = 0; h < checker->hashed; h++) { + for (bucket *bucket = checker->table[h], *next; bucket; bucket = next) { + next = bucket->next; + kissat_free (solver, bucket, bytes_line (bucket->size)); + } + } + kissat_dealloc (solver, checker->table, checker->hashed, + sizeof (bucket *)); +} + +static void release_watches (kissat *solver, checker *checker) { + const unsigned lits = 2 * checker->vars; + for (unsigned i = 0; i < lits; i++) + RELEASE_STACK (checker->watches[i]); + kissat_dealloc (solver, checker->watches, 2 * checker->size, + sizeof (buckets)); +} + +void kissat_release_checker (kissat *solver) { + LOG ("releasing internal proof checker"); + checker *checker = solver->checker; + release_hash (solver, checker); + RELEASE_STACK (checker->imported); + RELEASE_STACK (checker->trail); + kissat_free (solver, checker->marks, 2 * checker->size * sizeof (bool)); + kissat_free (solver, checker->used, 2 * checker->size * sizeof (bool)); + kissat_free (solver, checker->large, 2 * checker->size * sizeof (bool)); + kissat_free (solver, checker->values, 2 * checker->size); + release_watches (solver, checker); + kissat_free (solver, checker, sizeof (struct checker)); +} + +#ifndef QUIET + +#include + +#define PERCENT_ADDED(NAME) kissat_percent (checker->NAME, checker->added) +#define PERCENT_CHECKED(NAME) \ + kissat_percent (checker->NAME, checker->checked) + +void kissat_print_checker_statistics (kissat *solver, bool verbose) { + checker *checker = solver->checker; + PRINT_STAT ("checker_added", checker->added, 100, "%", ""); + if (verbose) + PRINT_STAT ("checker_blocked", checker->blocked, + PERCENT_CHECKED (blocked), "%", "checked"); + PRINT_STAT ("checker_checked", checker->checked, PERCENT_ADDED (checked), + "%", "added"); + if (verbose) { + PRINT_STAT ("checker_collisions", checker->collisions, + kissat_percent (checker->collisions, checker->searches), + "%", "per search"); + PRINT_STAT ("checker_decisions", checker->decisions, + kissat_average (checker->decisions, checker->checked), "", + "per check"); + PRINT_STAT ("checker_propagations", checker->propagations, + kissat_average (checker->propagations, checker->checked), + "", "per check"); + PRINT_STAT ("checker_pure", checker->pure, PERCENT_CHECKED (pure), "%", + "checked"); + } + PRINT_STAT ("checker_removed", checker->removed, PERCENT_ADDED (removed), + "%", "added"); + if (verbose) { + PRINT_STAT ("checker_satisfied", checker->satisfied, + PERCENT_CHECKED (satisfied), "%", "checked"); + PRINT_STAT ("checker_unchecked", checker->unchecked, + PERCENT_ADDED (unchecked), "%", "added"); + } +} + +#endif + +#define MAX_VARS (1u << 29) +#define MAX_SIZE (1u << 30) + +static unsigned reduce_hash (unsigned hash, unsigned mod) { + if (mod < 2) + return 0; + assert (mod); + unsigned res = hash; + for (unsigned shift = 16, mask = 0xffff; res >= mod; + mask >>= (shift >>= 1)) + res = (res >> shift) & mask; + assert (res < mod); + return res; +} + +static void resize_hash (kissat *solver, checker *checker) { + const unsigned old_hashed = checker->hashed; + assert (old_hashed < MAX_SIZE); + const unsigned new_hashed = old_hashed ? 2 * old_hashed : 1; + bucket **table = kissat_calloc (solver, new_hashed, sizeof (bucket *)); + bucket **old_table = checker->table; + for (unsigned i = 0; i < old_hashed; i++) { + for (bucket *bucket = old_table[i], *next; bucket; bucket = next) { + next = bucket->next; + const unsigned reduced = reduce_hash (bucket->hash, new_hashed); + bucket->next = table[reduced]; + table[reduced] = bucket; + } + } + kissat_dealloc (solver, checker->table, old_hashed, sizeof (bucket *)); + checker->hashed = new_hashed; + checker->table = table; +} + +static bucket *new_line (kissat *solver, checker *checker, unsigned size, + unsigned hash) { + bucket *res = kissat_malloc (solver, bytes_line (size)); + res->next = 0; + res->size = size; + res->hash = hash; + memcpy (res->lits, BEGIN_STACK (checker->imported), + size * sizeof *res->lits); + return res; +} + +#define CHECKER_LITS (2 * (checker)->vars) +#define VALID_CHECKER_LIT(LIT) ((LIT) < CHECKER_LITS) + +static bucket decision_line; +static bucket unit_line; + +static void checker_assign (kissat *solver, checker *checker, unsigned lit, + bucket *bucket) { +#ifdef LOGGING + if (bucket == &decision_line) + LOG3 ("checker assign %u (decision)", lit); + else if (bucket == &unit_line) + LOG3 ("checker assign %u (unit)", lit); + else + LOGLINE3 ("checker assign %u reason", lit); +#else + (void) bucket; +#endif + assert (VALID_CHECKER_LIT (lit)); + const unsigned not_lit = lit ^ 1; + signed char *values = checker->values; + assert (!values[lit]); + assert (!values[not_lit]); + values[lit] = 1; + values[not_lit] = -1; + PUSH_STACK (checker->trail, lit); +} + +static buckets *checker_watches (checker *checker, unsigned lit) { + assert (VALID_CHECKER_LIT (lit)); + return checker->watches + lit; +} + +static void watch_checker_literal (kissat *solver, checker *checker, + bucket *bucket, unsigned lit) { + LOGLINE3 ("checker watches %u in", lit); + buckets *buckets = checker_watches (checker, lit); + PUSH_STACK (*buckets, bucket); +} + +static void unwatch_checker_literal (kissat *solver, checker *checker, + bucket *bucket, unsigned lit) { + LOGLINE3 ("checker unwatches %u in", lit); + buckets *buckets = checker_watches (checker, lit); + REMOVE_STACK (struct bucket *, *buckets, bucket); +#ifndef LOGGING + (void) solver; +#endif +} + +static void unwatch_line (kissat *solver, checker *checker, + bucket *bucket) { + assert (bucket->size > 1); + const unsigned *const lits = bucket->lits; + unwatch_checker_literal (solver, checker, bucket, lits[0]); + unwatch_checker_literal (solver, checker, bucket, lits[1]); +} + +static bool satisfied_or_trivial_imported (kissat *solver, + checker *checker) { + const unsigned *const lits = BEGIN_STACK (checker->imported); + const unsigned *const end_of_lits = END_STACK (checker->imported); + const signed char *values = checker->values; + bool *marks = checker->marks; + unsigned const *p; + bool res = false; + for (p = lits; !res && p != end_of_lits; p++) { + const unsigned lit = *p; + if (marks[lit]) + continue; + marks[lit] = true; + const unsigned not_lit = lit ^ 1; + if (marks[not_lit]) { + LOGIMPORTED3 ("trivial by %u and %u imported checker", not_lit, lit); + res = true; + } else if (values[lit] > 0) { + LOGIMPORTED3 ("satisfied by %u imported checker", lit); + res = true; + } + } + for (const unsigned *q = lits; q != p; q++) + marks[*q] = 0; +#ifndef LOGGING + (void) solver; +#endif + return res; +} + +static void mark_line (checker *checker) { + bool *marks = checker->marks; + for (all_stack (unsigned, lit, checker->imported)) + marks[lit] = 1; + checker->marked = true; +} + +static void unmark_line (checker *checker) { + bool *marks = checker->marks; + for (all_stack (unsigned, lit, checker->imported)) + marks[lit] = 0; + checker->marked = false; +} + +static bool simplify_imported (kissat *solver, checker *checker) { + if (checker->inconsistent) { + LOG3 ("skipping addition since checker already inconsistent"); + return true; + } + unsigned non_false = 0; +#ifdef LOGGING + unsigned num_false = 0; +#endif + const unsigned *const end_of_lits = END_STACK (checker->imported); + unsigned *lits = BEGIN_STACK (checker->imported); + const signed char *values = checker->values; + bool *marks = checker->marks; + bool res = false; + unsigned *p; + for (p = lits; !res && p != end_of_lits; p++) { + const unsigned lit = *p; + if (marks[lit]) + continue; + marks[lit] = true; + const unsigned not_lit = lit ^ 1; + if (marks[not_lit]) { + LOG3 ("simplified checker clause trivial (contains %u and %u)", + not_lit, lit); + res = true; + } else { + signed char lit_value = values[lit]; + if (lit_value < 0) { +#ifdef LOGGING + num_false++; +#endif + } else if (lit_value > 0) { + LOG3 ("simplified checker clause satisfied by %u", lit); + res = true; + } else { + if (!non_false) + SWAP (unsigned, *p, lits[0]); + else if (non_false == 1) + SWAP (unsigned, *p, lits[1]); + non_false++; + } + } + } + for (const unsigned *q = lits; q != p; q++) + marks[*q] = 0; + if (!res) { + if (!non_false) { + LOG3 ("simplified checker clause inconsistent"); + checker->inconsistent = true; + res = true; + } else if (non_false == 1) { + LOG3 ("simplified checker clause unit"); + checker_assign (solver, checker, lits[0], &unit_line); + res = true; + } + } + if (!res) { + LOG3 ("non-trivial and non-satisfied imported checker clause " + "has %u false and %u non-false literals", + num_false, non_false); + LOGIMPORTED3 ("simplified checker"); + } + return res; +} + +static void use_literal (kissat *solver, checker *checker, unsigned lit) { + if (checker->used[lit]) + return; + checker->used[lit] = true; +#ifdef LOGGING + LOG3 ("used checker literal %u", lit); +#else + (void) solver; +#endif +} + +static void large_literal (kissat *solver, checker *checker, unsigned lit) { + if (checker->large[lit]) + return; + checker->large[lit] = true; +#ifdef LOGGING + LOG3 ("large checker literal %u", lit); +#else + (void) solver; +#endif +} + +static void use_line (kissat *solver, checker *checker) { + bool large = (SIZE_STACK (checker->imported) > 2); + for (all_stack (unsigned, lit, checker->imported)) { + use_literal (solver, checker, lit); + if (large) + large_literal (solver, checker, lit); + } +} + +static void insert_imported (kissat *solver, checker *checker, + unsigned hash) { + size_t size = SIZE_STACK (checker->imported); + assert (size <= UINT_MAX); + if (checker->buckets == checker->hashed) + resize_hash (solver, checker); + bucket *bucket = new_line (solver, checker, size, hash); + const unsigned reduced = reduce_hash (hash, checker->hashed); + struct bucket **p = checker->table + reduced; + bucket->next = *p; + *p = bucket; + LOGLINE3 ("inserted checker"); + const unsigned *const lits = BEGIN_STACK (checker->imported); + const signed char *values = checker->values; + assert (!values[lits[0]]); + assert (!values[lits[1]]); + watch_checker_literal (solver, checker, bucket, lits[0]); + watch_checker_literal (solver, checker, bucket, lits[1]); + checker->buckets++; + checker->added++; +} + +static void insert_imported_if_not_simplified (kissat *solver, + checker *checker) { + sort_line (solver, checker); + const unsigned hash = hash_line (checker); + if (!simplify_imported (solver, checker)) { + insert_imported (solver, checker, hash); + use_line (solver, checker); + } +} + +static bool match_line (checker *checker, unsigned size, unsigned hash, + bucket *bucket) { + if (bucket->size != size) + return false; + if (bucket->hash != hash) + return false; + if (!checker->marked) + mark_line (checker); + const unsigned *const lits = bucket->lits; + const unsigned *const end_of_lits = lits + bucket->size; + const bool *const marks = checker->marks; + for (const unsigned *p = lits; p != end_of_lits; p++) + if (!marks[*p]) + return false; + return true; +} + +static void resize_checker (kissat *solver, checker *checker, + unsigned new_vars) { + const unsigned vars = checker->vars; + const unsigned size = checker->size; + if (new_vars > size) { + assert (new_vars <= MAX_SIZE); + unsigned new_size = size ? 2 * size : 1; + while (new_size < new_vars) + new_size *= 2; + assert (new_size <= MAX_SIZE); + LOG3 ("resizing checker form %u to %u", size, new_size); + const unsigned size2 = 2 * size; + const unsigned new_size2 = 2 * new_size; + checker->marks = kissat_realloc (solver, checker->marks, size2, + new_size2 * sizeof (bool)); + checker->used = kissat_realloc (solver, checker->used, size2, + new_size2 * sizeof (bool)); + checker->large = kissat_realloc (solver, checker->large, size2, + new_size2 * sizeof (bool)); + checker->values = + kissat_realloc (solver, checker->values, size2, new_size2); + checker->watches = kissat_realloc ( + solver, checker->watches, size2 * sizeof *checker->watches, + new_size2 * sizeof *checker->watches); + checker->size = new_size; + } + const unsigned delta = new_vars - vars; + if (delta == 1) + LOG3 ("initializing one checker variable %u", vars); + else + LOG3 ("initializing %u checker variables from %u to %u", delta, vars, + new_vars - 1); + const unsigned vars2 = 2 * vars; + const unsigned new_vars2 = 2 * new_vars; + const unsigned delta2 = 2 * delta; + assert (delta2 == new_vars2 - vars2); + memset (checker->watches + vars2, 0, delta2 * sizeof *checker->watches); + memset (checker->marks + vars2, 0, delta2); + memset (checker->used + vars2, 0, delta2); + memset (checker->large + vars2, 0, delta2); + memset (checker->values + vars2, 0, delta2); + checker->vars = new_vars; +} + +static inline unsigned +import_external_checker (kissat *solver, checker *checker, int elit) { + assert (elit); + const unsigned var = ABS (elit) - 1; + if (var >= checker->vars) + resize_checker (solver, checker, var + 1); + assert (var < checker->vars); + return 2 * var + (elit < 0); +} + +static inline unsigned +import_internal_checker (kissat *solver, checker *checker, unsigned ilit) { + const int elit = kissat_export_literal (solver, ilit); + return import_external_checker (solver, checker, elit); +} + +static inline int export_checker (checker *checker, unsigned ilit) { + assert (ilit <= 2 * checker->vars); + return (1 + (ilit >> 1)) * ((ilit & 1) ? -1 : 1); +} + +static bucket *find_line (kissat *solver, checker *checker, size_t size, + bool remove) { + if (!checker->hashed) + return 0; + sort_line (solver, checker); + checker->searches++; + const unsigned hash = hash_line (checker); + const unsigned reduced = reduce_hash (hash, checker->hashed); + struct bucket **p, *bucket; + for (p = checker->table + reduced; + (bucket = *p) && !match_line (checker, size, hash, bucket); + p = &bucket->next) + checker->collisions++; + if (checker->marked) + unmark_line (checker); + if (bucket && remove) + *p = bucket->next; + return bucket; +} + +static void remove_line (kissat *solver, checker *checker, size_t size) { + bucket *bucket = find_line (solver, checker, size, true); + if (!bucket) { + kissat_fatal_message_start (); + fputs ("trying to remove non-existing clause:\n", stderr); + for (all_stack (unsigned, lit, checker->imported)) + fprintf (stderr, "%d ", export_checker (checker, lit)); + fputs ("0\n", stderr); + fflush (stderr); + kissat_abort (); + } + unwatch_line (solver, checker, bucket); + LOGLINE3 ("removed checker"); + kissat_free (solver, bucket, bytes_line (size)); + assert (checker->buckets > 0); + checker->buckets--; + checker->removed++; +} + +static void import_external_literals (kissat *solver, checker *checker, + size_t size, const int *elits) { + if (size > UINT_MAX) + kissat_fatal ("can not check handle original clause of size %zu", size); + CLEAR_STACK (checker->imported); + for (size_t i = 0; i < size; i++) { + const unsigned lit = + import_external_checker (solver, checker, elits[i]); + PUSH_STACK (checker->imported, lit); + } + LOGIMPORTED3 ("checker imported external"); +} + +static void import_internal_literals (kissat *solver, checker *checker, + size_t size, const unsigned *ilits) { + assert (size <= UINT_MAX); + CLEAR_STACK (checker->imported); + for (size_t i = 0; i < size; i++) { + const unsigned ilit = ilits[i]; + const unsigned lit = import_internal_checker (solver, checker, ilit); + PUSH_STACK (checker->imported, lit); + } + LOGIMPORTED3 ("checker imported internal"); +} + +static void import_clause (kissat *solver, checker *checker, clause *c) { + import_internal_literals (solver, checker, c->size, c->lits); + LOGIMPORTED3 ("checker imported clause"); +} + +static void import_binary (kissat *solver, checker *checker, unsigned a, + unsigned b) { + CLEAR_STACK (checker->imported); + const unsigned c = import_internal_checker (solver, checker, a); + const unsigned d = import_internal_checker (solver, checker, b); + PUSH_STACK (checker->imported, c); + PUSH_STACK (checker->imported, d); + LOGIMPORTED3 ("checker imported binary"); +} + +static void import_internal_unit (kissat *solver, checker *checker, + unsigned a) { + CLEAR_STACK (checker->imported); + const unsigned b = import_internal_checker (solver, checker, a); + PUSH_STACK (checker->imported, b); + LOGIMPORTED3 ("checker imported unit"); +} + +static bool checker_propagate (kissat *solver, checker *checker) { + unsigned propagated = checker->propagated; + signed char *values = checker->values; + bool res = true; + while (res && propagated < SIZE_STACK (checker->trail)) { + const unsigned lit = PEEK_STACK (checker->trail, propagated); + const unsigned not_lit = lit ^ 1; + LOG3 ("checker propagate %u", lit); + assert (values[lit] > 0); + assert (values[not_lit] < 0); + propagated++; + buckets *buckets = checker_watches (checker, not_lit); + bucket **begin_of_lines = BEGIN_STACK (*buckets), **q = begin_of_lines; + bucket *const *end_of_lines = END_STACK (*buckets), *const *p = q; + while (p != end_of_lines) { + bucket *bucket = *q++ = *p++; + if (!res) + continue; + unsigned *lits = bucket->lits; + const unsigned other = not_lit ^ lits[0] ^ lits[1]; + const signed char other_value = values[other]; + if (other_value > 0) + continue; + const unsigned *const end_of_lits = lits + bucket->size; + unsigned replacement; + signed char replacement_value = -1; + unsigned *r; + for (r = lits + 2; r != end_of_lits; r++) { + replacement = *r; + if (replacement == other) + continue; + if (replacement == not_lit) + continue; + replacement_value = values[replacement]; + if (replacement_value >= 0) + break; + } + if (replacement_value >= 0) { + lits[0] = other; + lits[1] = replacement; + *r = not_lit; + LOGLINE3 ("checker unwatching %u in", not_lit); + watch_checker_literal (solver, checker, bucket, replacement); + q--; + } else if (other_value < 0) { + LOGLINE3 ("checker conflict"); + res = false; + } else + checker_assign (solver, checker, other, bucket); + } + SET_END_OF_STACK (*buckets, q); + } + checker->propagations += propagated - checker->propagated; + checker->propagated = propagated; + return res; +} + +static bool bucket_redundant (kissat *solver, checker *checker, + size_t size) { + if (!checker_propagate (solver, checker)) { + LOG3 ("root level checker unit propagations leads to conflict"); + LOG2 ("checker becomes inconsistent"); + checker->inconsistent = true; + return true; + } + if (checker->inconsistent) { + LOG3 ("skipping removal since checker already inconsistent"); + return true; + } + if (!size) + kissat_fatal ("checker can not remove empty checker clause"); + if (size == 1) { + const unsigned unit = PEEK_STACK (checker->imported, 0); + const signed char value = checker->values[unit]; + if (value < 0 && !checker->inconsistent) + kissat_fatal ("consistent checker can not remove falsified unit %d", + export_checker (checker, unit)); + if (!value) + kissat_fatal ("checker can not remove unassigned unit %d", + export_checker (checker, unit)); + LOG3 ("checker skips removal of satisfied unit %u", unit); + return true; + } else if (satisfied_or_trivial_imported (solver, checker)) { + LOGIMPORTED3 ("satisfied imported checker"); + return true; + } else + return false; +} + +static void remove_line_if_not_redundant (kissat *solver, + checker *checker) { + size_t size = SIZE_STACK (checker->imported); + if (!bucket_redundant (solver, checker, size)) + remove_line (solver, checker, size); +} + +static void checker_backtrack (checker *checker, unsigned saved) { + unsigned *begin = BEGIN_STACK (checker->trail) + saved; + unsigned *p = END_STACK (checker->trail); + signed char *values = checker->values; + while (p != begin) { + const unsigned lit = *--p; + assert (VALID_CHECKER_LIT (lit)); + const unsigned not_lit = lit ^ 1; + assert (values[lit] > 0); + assert (values[not_lit] < 0); + values[lit] = values[not_lit] = 0; + } + checker->propagated = saved; + SET_END_OF_STACK (checker->trail, begin); +} + +static bool checker_blocked_literal (kissat *solver, checker *checker, + unsigned lit) { + signed char *values = checker->values; + assert (values[lit] < 0); + const unsigned not_lit = lit ^ 1; + if (checker->large[not_lit]) + return false; + buckets *buckets = checker_watches (checker, not_lit); + bucket *const *const begin_of_lines = BEGIN_STACK (*buckets); + bucket *const *const end_of_lines = END_STACK (*buckets); + bucket *const *p = begin_of_lines; + while (p != end_of_lines) { + bucket *bucket = *p++; + const unsigned *const lits = bucket->lits; + const unsigned *const end_of_lits = lits + bucket->size; + const unsigned *l = lits; + while (l != end_of_lits) { + const unsigned other = *l++; + if (other == not_lit) + continue; + if (values[other] > 0) + goto CONTINUE_WITH_NEXT_BUCKET; + } + return false; + CONTINUE_WITH_NEXT_BUCKET:; + } +#ifdef LOGGING + LOG3 ("blocked literal %u", lit); +#else + (void) solver; +#endif + return true; +} + +static bool checker_blocked_imported (kissat *solver, checker *checker) { + for (all_stack (unsigned, lit, checker->imported)) + if (checker_blocked_literal (solver, checker, lit)) + return true; + return false; +} + +static void check_line (kissat *solver, checker *checker) { + checker->checked++; + if (checker->inconsistent) + return; + if (!checker_propagate (solver, checker)) { + LOG3 ("root level checker unit propagations leads to conflict"); + LOG2 ("checker becomes inconsistent"); + checker->inconsistent = true; + return; + } + const unsigned saved = SIZE_STACK (checker->trail); + signed char *values = checker->values; + bool satisfied = false, pure = false; + unsigned decisions = 0, prev = INVALID_LIT; + for (all_stack (unsigned, lit, checker->imported)) { + assert (prev != lit); + prev = lit; + signed char lit_value = values[lit]; + if (lit_value < 0) + continue; + if (lit_value > 0) { + LOG3 ("found satisfied literal %u", lit); + checker->satisfied++; + satisfied = true; + break; + } + const unsigned not_lit = lit ^ 1; + bool used = checker->used[not_lit]; + if (!used) { + LOG3 ("found pure literal %u", lit); + checker->pure++; + pure = true; + break; + } + checker_assign (solver, checker, not_lit, &decision_line); + decisions++; + } + checker->decisions += decisions; + if (!satisfied && !pure) { + if (!checker_propagate (solver, checker)) + LOG3 ("checker imported clause unit implied"); + else if (checker_blocked_imported (solver, checker)) { + LOG3 ("checker imported clause binary blocked"); + checker->blocked++; + } else { + kissat_fatal_message_start (); + fputs ("failed to check clause:\n", stderr); + for (all_stack (unsigned, lit, checker->imported)) + fprintf (stderr, "%d ", export_checker (checker, lit)); + fputs ("0\n", stderr); + fflush (stderr); + kissat_abort (); + } + } + checker_backtrack (checker, saved); +} + +void kissat_add_unchecked_external (kissat *solver, size_t size, + const int *elits) { + LOGINTS3 (size, elits, "adding unchecked external checker"); + checker *checker = solver->checker; + checker->unchecked++; + import_external_literals (solver, checker, size, elits); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_add_unchecked_internal (kissat *solver, size_t size, + unsigned *lits) { + LOGUNSIGNEDS3 (size, lits, "adding unchecked internal checker"); + checker *checker = solver->checker; + checker->unchecked++; + assert (size <= UINT_MAX); + import_internal_literals (solver, checker, size, lits); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_check_and_add_binary (kissat *solver, unsigned a, unsigned b) { + LOGBINARY3 (a, b, "checking and adding internal checker"); + checker *checker = solver->checker; + assert (VALID_INTERNAL_LITERAL (a)); + assert (VALID_INTERNAL_LITERAL (b)); + import_binary (solver, checker, a, b); + check_line (solver, checker); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_check_and_add_clause (kissat *solver, clause *clause) { + LOGCLS3 (clause, "checking and adding internal checker"); + checker *checker = solver->checker; + import_clause (solver, checker, clause); + check_line (solver, checker); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_check_and_add_empty (kissat *solver) { + LOG3 ("checking and adding empty checker clause"); + checker *checker = solver->checker; + CLEAR_STACK (checker->imported); + check_line (solver, checker); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_check_and_add_internal (kissat *solver, size_t size, + const unsigned *lits) { + LOGUNSIGNEDS3 (size, lits, "checking and adding internal checker"); + checker *checker = solver->checker; + import_internal_literals (solver, checker, size, lits); + check_line (solver, checker); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_check_and_add_unit (kissat *solver, unsigned a) { + LOG3 ("checking and adding internal checker internal unit %u", a); + checker *checker = solver->checker; + assert (VALID_INTERNAL_LITERAL (a)); + import_internal_unit (solver, checker, a); + check_line (solver, checker); + insert_imported_if_not_simplified (solver, checker); +} + +void kissat_check_shrink_clause (kissat *solver, clause *c, unsigned remove, + unsigned keep) { + LOGCLS3 (c, "checking and shrinking by %u internal checker", remove); + checker *checker = solver->checker; + CLEAR_STACK (checker->imported); + const value *const values = solver->values; + for (all_literals_in_clause (ilit, c)) { + if (ilit == remove) + continue; + if (ilit != keep && values[ilit] < 0 && !LEVEL (ilit)) + continue; + const unsigned lit = import_internal_checker (solver, checker, ilit); + PUSH_STACK (checker->imported, lit); + } + LOGIMPORTED3 ("checker imported internal"); + check_line (solver, checker); + insert_imported_if_not_simplified (solver, checker); + import_clause (solver, checker, c); + remove_line_if_not_redundant (solver, checker); +} + +void kissat_remove_checker_binary (kissat *solver, unsigned a, unsigned b) { + LOGBINARY3 (a, b, "removing internal checker"); + checker *checker = solver->checker; + assert (VALID_INTERNAL_LITERAL (a)); + assert (VALID_INTERNAL_LITERAL (b)); + import_binary (solver, checker, a, b); + remove_line_if_not_redundant (solver, checker); +} + +void kissat_remove_checker_clause (kissat *solver, clause *clause) { + LOGCLS3 (clause, "removing internal checker"); + checker *checker = solver->checker; + import_clause (solver, checker, clause); + remove_line_if_not_redundant (solver, checker); +} + +bool kissat_checker_contains_clause (kissat *solver, clause *clause) { + checker *checker = solver->checker; + import_clause (solver, checker, clause); + size_t size = SIZE_STACK (checker->imported); + if (bucket_redundant (solver, checker, size)) + return true; + return find_line (solver, checker, size, false); +} + +void kissat_remove_checker_external (kissat *solver, size_t size, + const int *elits) { + LOGINTS3 (size, elits, "removing external checker"); + checker *checker = solver->checker; + import_external_literals (solver, checker, size, elits); + remove_line_if_not_redundant (solver, checker); +} + +void kissat_remove_checker_internal (kissat *solver, size_t size, + const unsigned *ilits) { + LOGUNSIGNEDS3 (size, ilits, "removing internal checker"); + checker *checker = solver->checker; + import_internal_literals (solver, checker, size, ilits); + remove_line_if_not_redundant (solver, checker); +} + +void dump_line (bucket *bucket) { + printf ("bucket[%p]", (void *) bucket); + for (unsigned i = 0; i < bucket->size; i++) + printf (" %u", bucket->lits[i]); + fputc ('\n', stdout); +} + +void dump_checker (kissat *solver) { + checker *checker = solver->checker; + printf ("%s\n", checker->inconsistent ? "inconsistent" : "consistent"); + printf ("vars %u\n", checker->vars); + printf ("size %u\n", checker->size); + printf ("buckets %u\n", checker->buckets); + printf ("hashed %u\n", checker->hashed); + for (unsigned i = 0; i < SIZE_STACK (checker->trail); i++) + printf ("trail[%u] %u\n", i, PEEK_STACK (checker->trail, i)); + for (unsigned h = 0; h < checker->hashed; h++) + for (bucket *bucket = checker->table[h]; bucket; bucket = bucket->next) + dump_line (bucket); +} + +#else +int kissat_check_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/check.h b/src/sat/kissat/check.h new file mode 100644 index 000000000..f7eaf27a1 --- /dev/null +++ b/src/sat/kissat/check.h @@ -0,0 +1,175 @@ +#ifndef _check_h_INCLUDED +#define _check_h_INCLUDED + +#ifndef NDEBUG + +#include +#include + +struct kissat; + +void kissat_check_satisfying_assignment (struct kissat *); + +typedef struct checker checker; + +struct clause; + +void kissat_init_checker (struct kissat *); +void kissat_release_checker (struct kissat *); + +#ifndef QUIET +void kissat_print_checker_statistics (struct kissat *, bool verbose); +#endif + +void kissat_add_unchecked_external (struct kissat *, size_t, const int *); + +void kissat_check_and_add_binary (struct kissat *, unsigned, unsigned); +void kissat_check_and_add_clause (struct kissat *, struct clause *c); +void kissat_check_and_add_empty (struct kissat *); +void kissat_check_and_add_internal (struct kissat *, size_t, + const unsigned *); +void kissat_check_and_add_unit (struct kissat *, unsigned); + +void kissat_check_shrink_clause (struct kissat *, struct clause *, + unsigned remove, unsigned keep); + +void kissat_remove_checker_binary (struct kissat *, unsigned, unsigned); +void kissat_remove_checker_clause (struct kissat *, struct clause *c); +void kissat_remove_checker_external (struct kissat *, size_t, const int *); + +void kissat_remove_checker_internal (struct kissat *, size_t, + const unsigned *); + +#define ADD_UNCHECKED_EXTERNAL(SIZE, LITS) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_add_unchecked_external (solver, (SIZE), (LITS)); \ + } while (0) + +#define CHECK_AND_ADD_BINARY(A, B) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_check_and_add_binary (solver, (A), (B)); \ + } while (0) + +#define CHECK_AND_ADD_TERNARY(A, B, C) \ + do { \ + if (GET_OPTION (check) > 1) { \ + unsigned CLAUSE[3] = {(A), (B), (C)}; \ + kissat_check_and_add_internal (solver, 3, CLAUSE); \ + } \ + } while (0) + +#define CHECK_AND_ADD_CLAUSE(CLAUSE) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_check_and_add_clause (solver, (CLAUSE)); \ + } while (0) + +#define CHECK_AND_ADD_EMPTY() \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_check_and_add_empty (solver); \ + } while (0) + +#define CHECK_AND_ADD_LITS(SIZE, LITS) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_check_and_add_internal (solver, (SIZE), (LITS)); \ + } while (0) + +#define CHECK_AND_ADD_STACK(S) \ + CHECK_AND_ADD_LITS (SIZE_STACK (S), BEGIN_STACK (S)) + +#define CHECK_AND_ADD_UNIT(A) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_check_and_add_unit (solver, (A)); \ + } while (0) + +#define CHECK_SHRINK_CLAUSE(C, REMOVE, KEEP) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_check_shrink_clause (solver, (C), (REMOVE), (KEEP)); \ + } while (0) + +#define REMOVE_CHECKER_BINARY(A, B) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_remove_checker_binary (solver, (A), (B)); \ + } while (0) + +#define REMOVE_CHECKER_TERNARY(A, B, C) \ + do { \ + if (GET_OPTION (check) > 1) { \ + unsigned CLAUSE[3] = {(A), (B), (C)}; \ + kissat_remove_checker_internal (solver, 3, CLAUSE); \ + } \ + } while (0) + +#define REMOVE_CHECKER_CLAUSE(CLAUSE) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_remove_checker_clause (solver, (CLAUSE)); \ + } while (0) + +#define REMOVE_CHECKER_LITS(SIZE, LITS) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_remove_checker_internal (solver, (SIZE), (LITS)); \ + } while (0) + +#define REMOVE_CHECKER_STACK(S) \ + do { \ + if (GET_OPTION (check) > 1) \ + kissat_remove_checker_internal (solver, SIZE_STACK (S), \ + BEGIN_STACK (S)); \ + } while (0) + +#else + +#define ADD_UNCHECKED_EXTERNAL(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_BINARY(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_TERNARY(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_CLAUSE(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_EMPTY(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_LITS(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_STACK(...) \ + do { \ + } while (0) +#define CHECK_AND_ADD_UNIT(...) \ + do { \ + } while (0) +#define CHECK_SHRINK_CLAUSE(...) \ + do { \ + } while (0) +#define REMOVE_CHECKER_BINARY(...) \ + do { \ + } while (0) +#define REMOVE_CHECKER_TERNARY(...) \ + do { \ + } while (0) +#define REMOVE_CHECKER_CLAUSE(...) \ + do { \ + } while (0) +#define REMOVE_CHECKER_LITS(...) \ + do { \ + } while (0) +#define REMOVE_CHECKER_STACK(...) \ + do { \ + } while (0) + +#endif +#endif diff --git a/src/sat/kissat/classify.c b/src/sat/kissat/classify.c new file mode 100644 index 000000000..4514fb7bb --- /dev/null +++ b/src/sat/kissat/classify.c @@ -0,0 +1,28 @@ +#include "classify.h" +#include "internal.h" +#include "print.h" + +void kissat_classify (struct kissat *solver) { + statistics *s = &solver->statistics; + uint64_t clauses = s->clauses_binary + s->clauses_irredundant; + unsigned small_clauses_limit = GET_OPTION (smallclauses); + if (clauses <= small_clauses_limit) { + solver->classification.small = true; + solver->classification.bigbig = false; + } else { + solver->classification.small = false; + unsigned bigbigfraction = GET_OPTION (bigbigfraction); + double percent = bigbigfraction / 1000.0; + double actual = kissat_percent (s->clauses_binary, clauses); + if (actual >= percent) + solver->classification.bigbig = true; + else + solver->classification.bigbig = false; + } + kissat_very_verbose ( + solver, "formula classified as having a %s total number of clauses", + solver->classification.small ? "small" : "large"); + kissat_very_verbose ( + solver, "formula classified to have a %s binary clauses fraction", + solver->classification.bigbig ? "large" : "small"); +} diff --git a/src/sat/kissat/classify.h b/src/sat/kissat/classify.h new file mode 100644 index 000000000..f1f9b84d7 --- /dev/null +++ b/src/sat/kissat/classify.h @@ -0,0 +1,17 @@ +#ifndef _classify_h_INCLUDED +#define _classify_h_INCLUDED + +#include + +struct kissat; + +struct classification { + bool small; + bool bigbig; +}; + +typedef struct classification classification; + +void kissat_classify (struct kissat *); + +#endif diff --git a/src/sat/kissat/clause.c b/src/sat/kissat/clause.c new file mode 100644 index 000000000..8d4859193 --- /dev/null +++ b/src/sat/kissat/clause.c @@ -0,0 +1,187 @@ +#include "allocate.h" +#include "collect.h" +#include "inline.h" + +#include + +static void inc_clause (kissat *solver, bool original, bool redundant, + bool binary) { + if (binary) + INC (clauses_binary); + else if (redundant) + INC (clauses_redundant); + else + INC (clauses_irredundant); + INC (clauses_added); + if (original) + INC (clauses_original); +} + +static void dec_clause (kissat *solver, bool redundant, bool binary) { + if (binary) + DEC (clauses_binary); + else if (redundant) + DEC (clauses_redundant); + else + DEC (clauses_irredundant); +} + +static void init_clause (clause *res, bool redundant, unsigned glue, + unsigned size) { + assert (size <= UINT_MAX); + assert (redundant || !glue); + + glue = MIN (MAX_GLUE, glue); + + res->glue = glue; + res->garbage = false; + res->quotient = false; + res->reason = false; + res->redundant = redundant; + res->shrunken = false; + res->subsume = false; + res->swept = false; + res->vivify = false; + + res->used = 0; + + res->searched = 2; + res->size = size; +} + +void kissat_connect_referenced (kissat *solver, reference ref) { + watches *all_watches = solver->watches; + clause *c = kissat_dereference_clause (solver, ref); + kissat_inlined_connect_clause (solver, all_watches, c, ref); +} + +void kissat_connect_clause (kissat *solver, clause *c) { + watches *all_watches = solver->watches; + const reference ref = kissat_reference_clause (solver, c); + kissat_inlined_connect_clause (solver, all_watches, c, ref); +} + +static reference new_binary_clause (kissat *solver, bool original, + bool watch, unsigned first, + unsigned second) { + assert (first != second); + assert (first != NOT (second)); + if (watch) + kissat_watch_binary (solver, first, second); + kissat_mark_added_literal (solver, first); + kissat_mark_added_literal (solver, second); + inc_clause (solver, original, false, true); + if (!original) { + CHECK_AND_ADD_BINARY (first, second); + ADD_BINARY_TO_PROOF (first, second); + } + return INVALID_REF; +} + +static reference new_large_clause (kissat *solver, bool original, + bool redundant, unsigned glue, + unsigned size, unsigned *lits) { + assert (size > 2); + reference res = kissat_allocate_clause (solver, size); + clause *c = kissat_unchecked_dereference_clause (solver, res); + init_clause (c, redundant, glue, size); + memcpy (c->lits, lits, size * sizeof (unsigned)); + LOGREF (res, "new"); + if (solver->watching) + kissat_watch_reference (solver, lits[0], lits[1], res); + else + kissat_connect_clause (solver, c); + if (redundant) { + if (solver->first_reducible == INVALID_REF) + solver->first_reducible = res; + } else { + kissat_mark_added_literals (solver, size, lits); + solver->last_irredundant = res; + } + inc_clause (solver, original, redundant, false); + if (!original) { + CHECK_AND_ADD_CLAUSE (c); + ADD_CLAUSE_TO_PROOF (c); + } + return res; +} + +static reference new_clause (kissat *solver, bool original, bool redundant, + unsigned glue, unsigned size, unsigned *lits) { + reference res; + if (size == 2) + res = new_binary_clause (solver, original, true, lits[0], lits[1]); + else + res = new_large_clause (solver, original, redundant, glue, size, lits); + kissat_defrag_watches_if_needed (solver); + return res; +} + +void kissat_new_binary_clause (kissat *solver, unsigned first, + unsigned second) { + (void) new_binary_clause (solver, false, true, first, second); +} + +void kissat_new_unwatched_binary_clause (kissat *solver, unsigned first, + unsigned second) { + (void) new_binary_clause (solver, false, false, first, second); +} + +reference kissat_new_original_clause (kissat *solver) { + const unsigned size = SIZE_STACK (solver->clause); + unsigned *lits = BEGIN_STACK (solver->clause); + kissat_sort_literals (solver, size, lits); + reference res = new_clause (solver, true, false, 0, size, lits); + return res; +} + +reference kissat_new_irredundant_clause (kissat *solver) { + const unsigned size = SIZE_STACK (solver->clause); + unsigned *lits = BEGIN_STACK (solver->clause); + return new_clause (solver, false, false, 0, size, lits); +} + +reference kissat_new_redundant_clause (kissat *solver, unsigned glue) { + const unsigned size = SIZE_STACK (solver->clause); + unsigned *lits = BEGIN_STACK (solver->clause); + return new_clause (solver, false, true, glue, size, lits); +} + +static void mark_clause_as_garbage (kissat *solver, clause *c) { + assert (!c->garbage); + LOGCLS (c, "garbage"); + if (!c->redundant) + kissat_mark_removed_literals (solver, c->size, c->lits); + REMOVE_CHECKER_CLAUSE (c); + DELETE_CLAUSE_FROM_PROOF (c); + assert (c->size > 2); + dec_clause (solver, c->redundant, false); + c->garbage = true; +} + +void kissat_mark_clause_as_garbage (kissat *solver, clause *c) { + assert (!c->garbage); + mark_clause_as_garbage (solver, c); + size_t bytes = kissat_actual_bytes_of_clause (c); + ADD (arena_garbage, bytes); +} + +clause *kissat_delete_clause (kissat *solver, clause *c) { + LOGCLS (c, "delete"); + assert (c->size > 2); + assert (c->garbage); + size_t bytes = kissat_actual_bytes_of_clause (c); + SUB (arena_garbage, bytes); + INC (clauses_deleted); + return (clause *) ((char *) c + bytes); +} + +void kissat_delete_binary (kissat *solver, unsigned a, unsigned b) { + LOGBINARY (a, b, "delete"); + kissat_mark_removed_literal (solver, a); + kissat_mark_removed_literal (solver, b); + REMOVE_CHECKER_BINARY (a, b); + DELETE_BINARY_FROM_PROOF (a, b); + dec_clause (solver, false, true); + INC (clauses_deleted); +} diff --git a/src/sat/kissat/clause.h b/src/sat/kissat/clause.h new file mode 100644 index 000000000..d926a6cc2 --- /dev/null +++ b/src/sat/kissat/clause.h @@ -0,0 +1,90 @@ +#ifndef _clause_h_INCLUDED +#define _clause_h_INCLUDED + +#include "arena.h" +#include "literal.h" +#include "reference.h" +#include "utilities.h" + +#include + +typedef struct clause clause; + +#define LD_MAX_GLUE 19 +#define LD_MAX_USED 5 + +#define MAX_GLUE ((1u << LD_MAX_GLUE) - 1) +#define MAX_USED ((1u << LD_MAX_USED) - 1) + +struct clause { + unsigned glue : LD_MAX_GLUE; + + bool garbage : 1; + bool quotient : 1; + bool reason : 1; + bool redundant : 1; + bool shrunken : 1; + bool subsume : 1; + bool swept : 1; + bool vivify : 1; + + unsigned used : LD_MAX_USED; + + unsigned searched; + unsigned size; + + unsigned lits[3]; +}; + +#define SIZE_OF_CLAUSE_HEADER ((size_t) & ((clause *) 0)->searched) + +#define BEGIN_LITS(C) ((C)->lits) +#define END_LITS(C) (BEGIN_LITS (C) + (C)->size) + +#define all_literals_in_clause(LIT, C) \ + unsigned LIT, \ + *LIT##_PTR = BEGIN_LITS (C), *const LIT##_END = END_LITS (C); \ + LIT##_PTR != LIT##_END && ((LIT = *LIT##_PTR), true); \ + ++LIT##_PTR + +static inline size_t kissat_bytes_of_clause (unsigned size) { + const size_t res = sizeof (clause) + (size - 3) * sizeof (unsigned); + return kissat_align_ward (res); +} + +static inline size_t kissat_actual_bytes_of_clause (clause *c) { + unsigned const *p = END_LITS (c); + if (c->shrunken) + while (*p++ != INVALID_LIT) + ; + return kissat_align_ward ((char *) p - (char *) c); +} + +static inline clause *kissat_next_clause (clause *c) { + word bytes = kissat_actual_bytes_of_clause (c); + return (clause *) ((char *) c + bytes); +} + +struct kissat; + +void kissat_new_binary_clause (struct kissat *, unsigned, unsigned); +void kissat_new_unwatched_binary_clause (struct kissat *, unsigned, + unsigned); + +reference kissat_new_original_clause (struct kissat *); +reference kissat_new_irredundant_clause (struct kissat *); +reference kissat_new_redundant_clause (struct kissat *, unsigned glue); + +#ifndef INLINE_SORT +void kissat_sort_literals (struct kissat *, unsigned size, unsigned *lits); +#endif + +void kissat_connect_clause (struct kissat *, clause *); +void kissat_connect_referenced (struct kissat *solver, reference); + +clause *kissat_delete_clause (struct kissat *, clause *); +void kissat_delete_binary (struct kissat *, unsigned, unsigned); + +void kissat_mark_clause_as_garbage (struct kissat *, clause *); + +#endif diff --git a/src/sat/kissat/collect.c b/src/sat/kissat/collect.c new file mode 100644 index 000000000..77d45319e --- /dev/null +++ b/src/sat/kissat/collect.c @@ -0,0 +1,738 @@ +#define INLINE_SORT + +#include "collect.h" +#include "allocate.h" +#include "colors.h" +#include "compact.h" +#include "inline.h" +#include "print.h" +#include "report.h" +#include "sort.c" +#include "trail.h" + +#include +#include + +static void flush_watched_clauses_by_literal (kissat *solver, unsigned lit, + bool compact, + reference start) { + assert (start != INVALID_REF); + + const value *const values = solver->values; + const assigned *const all_assigned = solver->assigned; + + const value lit_value = values[lit]; + const assigned *const lit_assigned = all_assigned + IDX (lit); + const value lit_fixed = + (lit_value && !lit_assigned->level) ? lit_value : 0; + const unsigned mlit = kissat_map_literal (solver, lit, true); + + watches *lit_watches = &WATCHES (lit); + watch *begin = BEGIN_WATCHES (*lit_watches), *q = begin; + const watch *const end_of_watches = END_WATCHES (*lit_watches), *p = q; + + while (p != end_of_watches) { + watch head = *p++; + if (head.type.binary) { + const unsigned other = head.binary.lit; + const unsigned other_idx = IDX (other); + const value other_value = values[other]; + const value other_fixed = + (other_value && !all_assigned[other_idx].level) ? other_value : 0; + const unsigned mother = kissat_map_literal (solver, other, compact); + if (lit_fixed > 0 || other_fixed > 0 || mother == INVALID_LIT) { + if (lit < other) + kissat_delete_binary (solver, lit, other); + } else { + assert (!lit_fixed); + assert (!other_fixed); + + { + head.binary.lit = mother; + *q++ = head; +#ifdef LOGGING + if (lit < other) { + LOGBINARY (lit, other, "SRC"); + LOGBINARY (mlit, mother, "DST"); + } +#endif + } + } + } else { + assert (solver->watching); + const watch tail = *p++; + if (!lit_fixed) { + const reference ref = tail.large.ref; + if (ref < start) { + *q++ = head; + *q++ = tail; + } + } + } + } + + assert (!lit_fixed || q == begin); + SET_END_OF_WATCHES (*lit_watches, q); +#ifdef LOGGING + const size_t size_lit_watches = SIZE_WATCHES (*lit_watches); + LOG ("keeping %zu watches[%u]", size_lit_watches, lit); +#endif + if (!compact) + return; + + if (mlit == INVALID_LIT) + return; + + watches *mlit_watches = &WATCHES (mlit); +#if defined(LOGGING) || !defined(NDEBUG) + const size_t size_mlit_watches = SIZE_WATCHES (*mlit_watches); +#endif + if (lit_fixed) + assert (!size_mlit_watches); + else if (mlit < lit) { + assert (mlit != INVALID_LIT); + assert (mlit < lit); + *mlit_watches = *lit_watches; + LOG ("copied watches[%u] = watches[%u] (size %zu)", mlit, lit, + size_mlit_watches); + memset (lit_watches, 0, sizeof *lit_watches); + } else + assert (mlit == lit); +} + +static void flush_all_watched_clauses (kissat *solver, bool compact, + reference start) { + assert (solver->watching); + LOG ("starting to flush watches at clause[%" REFERENCE_FORMAT "]", start); + for (all_variables (idx)) { + const unsigned lit = LIT (idx); + flush_watched_clauses_by_literal (solver, lit, compact, start); + const unsigned not_lit = NOT (lit); + flush_watched_clauses_by_literal (solver, not_lit, compact, start); + } +} + +static void update_large_reason (kissat *solver, assigned *assigned, + unsigned forced, clause *dst) { + assert (dst->reason); + assert (forced != INVALID_LIT); + reference dst_ref = kissat_reference_clause (solver, dst); + const unsigned forced_idx = IDX (forced); + struct assigned *a = assigned + forced_idx; + assert (!a->binary); + if (a->reason != dst_ref) { + LOG ("reason reference %u of %s updated to %u", a->reason, + LOGLIT (forced), dst_ref); + a->reason = dst_ref; + } + dst->reason = false; +} + +static unsigned get_forced (const value *values, clause *dst) { + assert (dst->reason); + unsigned forced = INVALID_LIT; + for (all_literals_in_clause (lit, dst)) { + const value value = values[lit]; + if (value <= 0) + continue; + forced = lit; + break; + } + assert (forced != INVALID_LIT); + return forced; +} + +static void get_forced_and_update_large_reason (kissat *solver, + assigned *assigned, + const value *const values, + clause *dst) { + const unsigned forced = get_forced (values, dst); + update_large_reason (solver, assigned, forced, dst); +} + +static void update_first_reducible (kissat *solver, const clause *end, + clause *first_reducible) { + if (first_reducible >= end) { + LOG ("first reducible after end of arena"); + solver->first_reducible = INVALID_REF; + } else if (first_reducible) { + LOGCLS (first_reducible, "updating first reducible clause to"); + solver->first_reducible = + kissat_reference_clause (solver, first_reducible); + } else { + LOG ("first reducible clause becomes invalid"); + solver->first_reducible = INVALID_REF; + } +} + +static void update_last_irredundant (kissat *solver, const clause *end, + clause *last_irredundant) { + if (!last_irredundant) { + LOG ("no more large irredundant clauses left"); + solver->last_irredundant = INVALID_REF; + } else if (end <= last_irredundant) { + LOG ("last irredundant clause after end of arena"); + solver->last_irredundant = INVALID_REF; + } else { + LOGCLS (last_irredundant, "updating last irredundant clause to"); + reference ref = kissat_reference_clause (solver, last_irredundant); + solver->last_irredundant = ref; + } +} + +void kissat_update_first_reducible (kissat *solver, clause *reducible) { + assert (reducible); + assert (!reducible->garbage); + assert (reducible->redundant); + if (solver->first_reducible != INVALID_REF) { + reference ref = kissat_reference_clause (solver, reducible); + if (ref >= solver->first_reducible) { + LOG ("no need to update larger first reducible"); + return; + } + } + clause *end = (clause *) END_STACK (solver->arena); + update_first_reducible (solver, end, reducible); +} + +void kissat_update_last_irredundant (kissat *solver, clause *irredundant) { + assert (irredundant); + assert (!irredundant->garbage); + assert (!irredundant->redundant); + if (solver->last_irredundant != INVALID_REF) { + reference ref = kissat_reference_clause (solver, irredundant); + if (ref <= solver->last_irredundant) { + LOG ("no need to update smaller last irredundant"); + return; + } + } + clause *end = (clause *) END_STACK (solver->arena); + update_last_irredundant (solver, end, irredundant); +} + +static void move_redundant_clauses_to_the_end (kissat *solver, + reference ref) { + INC (moved); + assert (ref != INVALID_REF); +#ifndef NDEBUG + const size_t size = SIZE_STACK (solver->arena); + assert ((size_t) ref <= size); +#endif + clause *begin = (clause *) (BEGIN_STACK (solver->arena) + ref); + clause *end = (clause *) END_STACK (solver->arena); + size_t bytes_redundant = (char *) end - (char *) begin; + kissat_phase (solver, "move", GET (moved), + "moving redundant clauses of %s to the end", + FORMAT_BYTES (bytes_redundant)); + kissat_mark_reason_clauses (solver, ref); + clause *redundant = (clause *) kissat_malloc (solver, bytes_redundant); + clause *p = begin, *q = begin, *r = redundant; + + const value *const values = solver->values; + assigned *assigned = solver->assigned; + + clause *last_irredundant = kissat_last_irredundant_clause (solver); + + while (p != end) { + assert (!p->shrunken); + size_t bytes = kissat_bytes_of_clause (p->size); + if (p->redundant) { + memcpy (r, p, bytes); + r = (clause *) (bytes + (char *) r); + } else { + LOGCLS (p, "old DST"); + memmove (q, p, bytes); + LOGCLS (q, "new DST"); + last_irredundant = q; + if (q->reason) + get_forced_and_update_large_reason (solver, assigned, values, q); + q = (clause *) (bytes + (char *) q); + } + p = (clause *) (bytes + (char *) p); + } + r = redundant; + clause *first_reducible = 0; + while (q != end) { + size_t bytes = kissat_bytes_of_clause (r->size); + memcpy (q, r, bytes); + LOGCLS (q, "new DST"); + if (q->reason) + get_forced_and_update_large_reason (solver, assigned, values, q); + assert (q->redundant); + if (!first_reducible) + first_reducible = q; + r = (clause *) (bytes + (char *) r); + q = (clause *) (bytes + (char *) q); + } + assert ((char *) r <= (char *) redundant + bytes_redundant); + kissat_free (solver, redundant, bytes_redundant); + + assert (!first_reducible || first_reducible < q); + + update_first_reducible (solver, q, first_reducible); + update_last_irredundant (solver, q, last_irredundant); + kissat_reset_last_learned (solver); +} + +static reference sparse_sweep_garbage_clauses (kissat *solver, bool compact, + reference start) { + assert (solver->watching); + LOG ("sparse garbage collection starting at clause[%" REFERENCE_FORMAT + "]", + start); +#ifdef CHECKING_OR_PROVING + const bool checking_or_proving = kissat_checking_or_proving (solver); +#endif + assert (EMPTY_STACK (solver->added)); + assert (EMPTY_STACK (solver->removed)); + + const value *const values = solver->values; + assigned *assigned = solver->assigned; + +#ifndef QUIET + size_t flushed_garbage_clauses = 0; + size_t flushed_satisfied_clauses = 0; +#endif + size_t flushed = 0; + + clause *begin = (clause *) BEGIN_STACK (solver->arena); + const clause *const end = (clause *) END_STACK (solver->arena); + + clause *first, *src, *dst; + if (start) + first = kissat_dereference_clause (solver, start); + else + first = begin; + src = dst = first; + + clause *first_redundant = 0; + clause *first_reducible = 0; + clause *last_irredundant; + + if (start) + last_irredundant = kissat_last_irredundant_clause (solver); + else + last_irredundant = 0; +#ifdef LOGGING + size_t redundant_bytes = 0; +#endif + for (clause *next; src != end; src = next) { + if (src->garbage) { + next = kissat_delete_clause (solver, src); +#ifndef QUIET + flushed_garbage_clauses++; +#endif + if (last_irredundant == src) { + if (first == begin) + last_irredundant = 0; + else + last_irredundant = first; + } + continue; + } + + assert (src->size > 1); + LOGCLS (src, "SRC"); + next = kissat_next_clause (src); +#if !defined(NDEBUG) || defined(CHECKING_OR_PROVING) + const unsigned old_size = src->size; +#endif + assert (SIZE_OF_CLAUSE_HEADER == sizeof (unsigned)); + *(unsigned *) dst = *(unsigned *) src; + + unsigned *q = dst->lits; + + unsigned mfirst = INVALID_LIT; + unsigned msecond = INVALID_LIT; + unsigned forced = INVALID_LIT; + unsigned other = INVALID_LIT; + unsigned non_false = 0; + + bool satisfied = false; + + for (all_literals_in_clause (lit, src)) { +#ifdef CHECKING_OR_PROVING + if (checking_or_proving) + PUSH_STACK (solver->removed, lit); +#endif + if (satisfied) + continue; + + const value tmp = values[lit]; + const unsigned idx = IDX (lit); + const unsigned level = tmp ? assigned[idx].level : INVALID_LEVEL; + + if (tmp < 0 && !level) + flushed++; + else if (tmp > 0 && !level) { + assert (!satisfied); + assert (!dst->reason); + LOG ("SRC satisfied by %s", LOGLIT (lit)); + satisfied = true; + } else { + const unsigned mlit = kissat_map_literal (solver, lit, compact); + + if (tmp > 0) { + assert (level); + forced = non_false++ ? INVALID_LIT : lit; + } else if (tmp < 0) + other = lit; + + if (mfirst == INVALID_LIT) + mfirst = mlit; + else if (msecond == INVALID_LIT) + msecond = mlit; + + *q++ = mlit; + +#ifdef CHECKING_OR_PROVING + if (checking_or_proving) + PUSH_STACK (solver->added, lit); +#endif + } + } + + if (satisfied) { + if (dst->redundant) + DEC (clauses_redundant); + else + DEC (clauses_irredundant); +#ifndef QUIET + flushed_satisfied_clauses++; +#endif +#ifdef CHECKING_OR_PROVING + if (checking_or_proving) { + REMOVE_CHECKER_STACK (solver->removed); + DELETE_STACK_FROM_PROOF (solver->removed); + CLEAR_STACK (solver->added); + CLEAR_STACK (solver->removed); + } +#endif + if (last_irredundant == src) { + if (first == begin) + last_irredundant = 0; + else + last_irredundant = first; + } + continue; + } + + const unsigned new_size = q - dst->lits; + assert (new_size <= old_size); + assert (1 < new_size); + + if (new_size == 2) { + assert (mfirst != INVALID_LIT); + assert (msecond != INVALID_LIT); + + statistics *statistics = &solver->statistics; + assert (statistics->clauses_binary < UINT64_MAX); + statistics->clauses_binary++; + bool redundant = dst->redundant; + if (redundant) { + assert (statistics->clauses_redundant > 0); + statistics->clauses_redundant--; + redundant = false; + } else { + assert (statistics->clauses_irredundant > 0); + statistics->clauses_irredundant--; + } + LOGBINARY (mfirst, msecond, "DST"); + kissat_watch_binary (solver, mfirst, msecond); + + if (dst->reason) { + assert (non_false == 1); + assert (other != INVALID_LIT); + assert (forced != INVALID_LIT); + + const unsigned forced_idx = IDX (forced); + struct assigned *a = assigned + forced_idx; + assert (!a->binary); + + LOGBINARY (mfirst, msecond, + "reason clause[%u] of %s updated to binary reason", + a->reason, LOGLIT (forced)); + + a->binary = true; + a->reason = other; + } + + if (!redundant && last_irredundant == src) { + if (first == begin) + last_irredundant = 0; + else + last_irredundant = first; + } + } else { + assert (2 < new_size); + + dst->size = new_size; + dst->shrunken = false; + dst->searched = 2; + + LOGCLS (dst, "DST"); + if (dst->reason) + update_large_reason (solver, assigned, forced, dst); + + clause *next_dst = kissat_next_clause (dst); + + if (dst->redundant) { + if (!first_reducible) + first_reducible = dst; +#ifdef LOGGING + redundant_bytes += (char *) next_dst - (char *) dst; +#endif + if (!first_redundant) + first_redundant = dst; + } else + last_irredundant = dst; + + dst = next_dst; + } + +#ifdef CHECKING_OR_PROVING + if (!checking_or_proving) + continue; + + if (new_size != old_size) { + assert (1 < new_size); + assert (new_size < old_size); + + CHECK_AND_ADD_STACK (solver->added); + ADD_STACK_TO_PROOF (solver->added); + + REMOVE_CHECKER_STACK (solver->removed); + DELETE_STACK_FROM_PROOF (solver->removed); + } + CLEAR_STACK (solver->added); + CLEAR_STACK (solver->removed); +#endif + } + + update_first_reducible (solver, dst, first_reducible); + update_last_irredundant (solver, dst, last_irredundant); + kissat_reset_last_learned (solver); + + if (first_redundant) + LOGCLS (first_redundant, "determined first redundant clause as"); + +#if !defined(QUIET) || defined(METRICS) + size_t bytes = (char *) END_STACK (solver->arena) - (char *) dst; +#endif +#ifndef QUIET + if (flushed) + kissat_phase (solver, "collect", GET (garbage_collections), + "flushed %zu falsified literals in large clauses", + flushed); + size_t flushed_clauses = + flushed_satisfied_clauses + flushed_garbage_clauses; + if (flushed_satisfied_clauses) + kissat_phase ( + solver, "collect", GET (garbage_collections), + "flushed %zu satisfied large clauses %.0f%%", + flushed_satisfied_clauses, + kissat_percent (flushed_satisfied_clauses, flushed_clauses)); + if (flushed_garbage_clauses) + kissat_phase ( + solver, "collect", GET (garbage_collections), + "flushed %zu large garbage clauses %.0f%%", flushed_garbage_clauses, + kissat_percent (flushed_garbage_clauses, flushed_clauses)); + kissat_phase (solver, "collect", GET (garbage_collections), + "collected %s in total", FORMAT_BYTES (bytes)); +#endif + ADD (flushed, flushed); +#ifdef METRICS + ADD (allocated_collected, bytes); +#endif + + reference res = INVALID_REF; + + if (first_redundant && last_irredundant && + first_redundant < last_irredundant) { +#ifdef LOGGING + size_t move_bytes = (char *) dst - (char *) first_redundant; + LOG ("redundant bytes %s (%.0f%%) out of %s moving bytes", + FORMAT_BYTES (redundant_bytes), + kissat_percent (redundant_bytes, move_bytes), + FORMAT_BYTES (move_bytes)); +#endif + assert (first_redundant < dst); + res = kissat_reference_clause (solver, first_redundant); + assert (res != INVALID_REF); + } + + SET_END_OF_STACK (solver->arena, (ward *) dst); + kissat_shrink_arena (solver); + +#ifdef METRICS + if (solver->statistics.arena_garbage) + kissat_very_verbose (solver, "still %s garbage left in arena", + FORMAT_BYTES (solver->statistics.arena_garbage)); + else + kissat_very_verbose (solver, "all garbage clauses in arena collected"); +#endif + + return res; +} + +static void rewatch_clauses (kissat *solver, reference start) { + LOG ("rewatching clause[%" REFERENCE_FORMAT "] and following clauses", + start); + assert (solver->watching); + + const value *const values = solver->values; + const assigned *const assigned = solver->assigned; + watches *watches = solver->watches; + ward *const arena = BEGIN_STACK (solver->arena); + + clause *end = (clause *) END_STACK (solver->arena); + clause *c = (clause *) (BEGIN_STACK (solver->arena) + start); + assert (c <= end); + + for (clause *next; c != end; c = next) { + next = kissat_next_clause (c); + + unsigned *lits = c->lits; + kissat_sort_literals (solver, values, assigned, c->size, lits); + c->searched = 2; + + const reference ref = (ward *) c - arena; + const unsigned l0 = lits[0]; + const unsigned l1 = lits[1]; + + kissat_push_blocking_watch (solver, watches + l0, l1, ref); + kissat_push_blocking_watch (solver, watches + l1, l0, ref); + } +} + +void kissat_sparse_collect (kissat *solver, bool compact, reference start) { + assert (solver->watching); + START (collect); + INC (garbage_collections); + INC (sparse_gcs); + REPORT (1, 'G'); + unsigned vars, mfixed; + if (compact) + vars = kissat_compact_literals (solver, &mfixed); + else { + vars = solver->vars; + mfixed = INVALID_LIT; + } + flush_all_watched_clauses (solver, compact, start); + reference move = sparse_sweep_garbage_clauses (solver, compact, start); + if (compact) + kissat_finalize_compacting (solver, vars, mfixed); + if (move != INVALID_REF) + move_redundant_clauses_to_the_end (solver, move); + rewatch_clauses (solver, start); + REPORT (1, 'C'); + kissat_check_statistics (solver); + STOP (collect); +} + +bool kissat_compacting (kissat *solver) { + if (!GET_OPTION (compact)) + return false; + unsigned inactive = solver->vars - solver->active; + unsigned limit = GET_OPTION (compactlim) / 1e2 * solver->vars; + bool compact = (inactive > limit); + LOG ("%u inactive variables %.0f%% <= limit %u %.0f%%", inactive, + kissat_percent (inactive, solver->vars), limit, + kissat_percent (limit, solver->vars)); + return compact; +} + +void kissat_initial_sparse_collect (kissat *solver) { + assert (!solver->level); + assert (!solver->inconsistent); + assert (solver->watching); + assert (kissat_trail_flushed (solver)); + if (solver->statistics.units) { + bool compact = GET_OPTION (compact); + kissat_sparse_collect (solver, compact, 0); + } + REPORT (0, '.'); +} + +static void dense_sweep_garbage_clauses (kissat *solver) { + assert (!solver->level); + assert (!solver->watching); + + LOG ("dense garbage collection"); + +#ifndef QUIET + size_t flushed_garbage_clauses = 0; +#endif + clause *first_reducible = 0; + clause *last_irredundant = 0; + + clause *begin = (clause *) BEGIN_STACK (solver->arena); + const clause *const end = (clause *) END_STACK (solver->arena); + + clause *src = begin; + clause *dst = src; + + for (clause *next; src != end; src = next) { + if (src->garbage) { + next = kissat_delete_clause (solver, src); +#ifndef QUIET + flushed_garbage_clauses++; +#endif + continue; + } + assert (src->size > 1); + LOGCLS (src, "SRC"); + next = kissat_next_clause (src); + assert (SIZE_OF_CLAUSE_HEADER == sizeof (unsigned)); + *(unsigned *) dst = *(unsigned *) src; + dst->searched = src->searched; + dst->size = src->size; + dst->shrunken = false; + memmove (dst->lits, src->lits, src->size * sizeof (unsigned)); + LOGCLS (dst, "DST"); + if (!dst->redundant) + last_irredundant = dst; + else if (!first_reducible) + first_reducible = dst; + dst = kissat_next_clause (dst); + } + + update_first_reducible (solver, dst, first_reducible); + update_last_irredundant (solver, dst, last_irredundant); + kissat_reset_last_learned (solver); + +#if !defined(QUIET) || defined(METRICS) + size_t bytes = (char *) END_STACK (solver->arena) - (char *) dst; +#endif + kissat_phase (solver, "collect", GET (garbage_collections), + "flushed %zu large garbage clauses", + flushed_garbage_clauses); + kissat_phase (solver, "collect", GET (garbage_collections), + "collected %s in total", FORMAT_BYTES (bytes)); +#ifdef METRICS + ADD (allocated_collected, bytes); +#endif + + SET_END_OF_STACK (solver->arena, (ward *) dst); + kissat_shrink_arena (solver); + +#ifdef METRICS + if (solver->statistics.arena_garbage) + kissat_very_verbose (solver, "still %s garbage left in arena", + FORMAT_BYTES (solver->statistics.arena_garbage)); + else + kissat_very_verbose (solver, "all garbage clauses in arena collected"); +#endif +} + +void kissat_dense_collect (kissat *solver) { + assert (!solver->watching); + assert (!solver->level); + START (collect); + INC (garbage_collections); + INC (dense_garbage_collections); + REPORT (1, 'G'); + dense_sweep_garbage_clauses (solver); + REPORT (1, 'C'); + STOP (collect); +} diff --git a/src/sat/kissat/collect.h b/src/sat/kissat/collect.h new file mode 100644 index 000000000..6a9c8c4ef --- /dev/null +++ b/src/sat/kissat/collect.h @@ -0,0 +1,33 @@ +#ifndef _collect_h_INCLUDED +#define _collect_h_INCLUDED + +#include "internal.h" + +bool kissat_compacting (kissat *); +void kissat_dense_collect (kissat *); +void kissat_sparse_collect (kissat *, bool compact, reference start); +void kissat_initial_sparse_collect (kissat *); + +static inline void kissat_defrag_watches (kissat *solver) { + kissat_defrag_vectors (solver, LITS, solver->watches); +} + +static inline void kissat_defrag_watches_if_needed (kissat *solver) { + const size_t size = SIZE_STACK (solver->vectors.stack); + const size_t size_limit = GET_OPTION (defragsize); + if (size <= size_limit) + return; + + const size_t usable = solver->vectors.usable; + const size_t usable_limit = (size * GET_OPTION (defraglim)) / 100; + if (usable <= usable_limit) + return; + + INC (vectors_defrags_needed); + kissat_defrag_watches (solver); +} + +void kissat_update_last_irredundant (kissat *, clause *last_irredundant); +void kissat_update_first_reducible (kissat *, clause *first_reducible); + +#endif diff --git a/src/sat/kissat/colors.c b/src/sat/kissat/colors.c new file mode 100644 index 000000000..a3f2e6db9 --- /dev/null +++ b/src/sat/kissat/colors.c @@ -0,0 +1,19 @@ +#include "colors.h" + +#include + +int kissat_is_terminal[3] = {0, -1, -1}; + +int kissat_initialize_terminal (int fd) { + assert (fd == 1 || fd == 2); + assert (kissat_is_terminal[fd] < 0); + return kissat_is_terminal[fd] = isatty (fd); +} + +void kissat_force_colors (void) { + kissat_is_terminal[1] = kissat_is_terminal[2] = 1; +} + +void kissat_force_no_colors (void) { + kissat_is_terminal[1] = kissat_is_terminal[2] = 0; +} diff --git a/src/sat/kissat/colors.h b/src/sat/kissat/colors.h new file mode 100644 index 000000000..fe981e505 --- /dev/null +++ b/src/sat/kissat/colors.h @@ -0,0 +1,68 @@ +#ifndef _colors_h_INCLUDED +#define _colors_h_INCLUDED + +#include +#include +#include + +#include "keatures.h" + +#define BLUE "\033[34m" +#define BOLD "\033[1m" +#define CYAN "\033[36m" +#define GREEN "\033[32m" +#define MAGENTA "\033[35m" +#define NORMAL "\033[0m" +#define RED "\033[31m" +#define WHITE "\037[34m" +#define YELLOW "\033[33m" + +#define LIGHT_GRAY "\033[1;37m" +#define DARK_GRAY "\033[0;37m" + +#ifdef KISSAT_HAS_FILENO +#define assert_if_has_fileno assert +#else +#define assert_if_has_fileno(...) \ + do { \ + } while (0) +#endif + +#define TERMINAL(F, I) \ + assert_if_has_fileno (fileno (F) == \ + I); /* 'fileno' only in POSIX not C99 */ \ + assert ((I == 1 && F == stdout) || (I == 2 && F == stderr)); \ + bool connected_to_terminal = kissat_connected_to_terminal (I); \ + FILE *terminal_file = F + +#define COLOR(CODE) \ + do { \ + if (!connected_to_terminal) \ + break; \ + fputs (CODE, terminal_file); \ + } while (0) + +extern int kissat_is_terminal[3]; + +int kissat_initialize_terminal (int fd); +void kissat_force_colors (void); +void kissat_force_no_colors (void); + +static inline bool kissat_connected_to_terminal (int fd) { + assert (fd == 1 || fd == 2); + int res = kissat_is_terminal[fd]; + if (res < 0) + res = kissat_initialize_terminal (fd); + assert (res == 0 || res == 1); + return res; +} + +static inline const char *kissat_bold_green_color_code (int fd) { + return kissat_connected_to_terminal (fd) ? BOLD GREEN : ""; +} + +static inline const char *kissat_normal_color_code (int fd) { + return kissat_connected_to_terminal (fd) ? NORMAL : ""; +} + +#endif diff --git a/src/sat/kissat/compact.c b/src/sat/kissat/compact.c new file mode 100644 index 000000000..2d27fa9ca --- /dev/null +++ b/src/sat/kissat/compact.c @@ -0,0 +1,376 @@ +#include "compact.h" +#include "inline.h" +#include "inlineheap.h" +#include "print.h" +#include "resize.h" + +#include + +static void reimport_literal (kissat *solver, unsigned eidx, + unsigned mlit) { + import *import = &PEEK_STACK (solver->import, eidx); + assert (import->imported); + assert (!import->eliminated); + LOG ("reimporting external variable %u as internal literal %u (was %u)", + eidx, mlit, import->lit); + import->lit = mlit; +} + +unsigned kissat_compact_literals (kissat *solver, unsigned *mfixed_ptr) { + INC (compacted); +#if !defined(QUIET) || !defined(NDEBUG) + const unsigned active = solver->active; +#ifndef QUIET + const unsigned inactive = solver->vars - active; + kissat_phase (solver, "compact", GET (compacted), + "compacting garbage collection " + "(%u inactive variables %.2f%%)", + inactive, kissat_percent (inactive, solver->vars)); +#endif +#endif +#ifdef LOGGING + assert (!solver->compacting); + solver->compacting = true; +#endif + unsigned mfixed = INVALID_LIT; + unsigned vars = 0; + for (all_variables (iidx)) { + const flags *const flags = FLAGS (iidx); + if (flags->eliminated) + continue; + const unsigned ilit = LIT (iidx); + unsigned mlit; + if (flags->fixed) { + const value value = kissat_fixed (solver, ilit); + assert (value); + if (mfixed == INVALID_LIT) { + mlit = mfixed = LIT (vars); + LOG2 ("first fixed %u mapped to %u assigned to %d", ilit, mfixed, + value); + if (value < 0) + mfixed = NOT (mfixed); + LOG2 ("all other fixed mapped to %u", mfixed); + vars++; + } else if (value < 0) { + mlit = NOT (mfixed); + LOG2 ("negatively fixed %u mapped to %u", ilit, mlit); + } else { + mlit = mfixed; + LOG2 ("positively fixed %u mapped to %u", ilit, mlit); + } + } else if (flags->active) { + assert (flags->active); + mlit = LIT (vars); + LOG2 ("remapping %u to %u", ilit, mlit); + vars++; + } else { + const int elit = PEEK_STACK (solver->export, iidx); + if (elit) { + const unsigned eidx = ABS (elit); + import *import = &PEEK_STACK (solver->import, eidx); + assert (import->imported); + assert (!import->eliminated); + import->imported = false; + LOG2 ("external variable %d not imported anymore", eidx); + POKE_STACK (solver->export, iidx, 0); + } else + LOG2 ("skipping inactive %u", ilit); + continue; + } + assert (mlit <= ilit); + assert (mlit != NOT (ilit)); + if (mlit == ilit) + continue; + const int elit = PEEK_STACK (solver->export, iidx); + const unsigned eidx = ABS (elit); + if (elit < 0) + mlit = NOT (mlit); + reimport_literal (solver, eidx, mlit); + } + *mfixed_ptr = mfixed; + LOG ("compacting to %u variables %.2f%% from %u", vars, + kissat_percent (vars, solver->vars), solver->vars); + assert (vars == active || vars == active + 1); + return vars; +} + +static void compact_literal (kissat *solver, unsigned dst_lit, + unsigned src_lit) { + assert (dst_lit < src_lit); + assert (dst_lit != NOT (src_lit)); + const unsigned dst_idx = IDX (dst_lit); + const unsigned src_idx = IDX (src_lit); + assert (dst_idx != src_idx); + LOG ("mapping old internal literal %u to %u", src_lit, dst_lit); + solver->assigned[dst_idx] = solver->assigned[src_idx]; + solver->flags[dst_idx] = solver->flags[src_idx]; + + solver->phases.best[dst_idx] = solver->phases.best[src_idx]; + solver->phases.saved[dst_idx] = solver->phases.saved[src_idx]; + solver->phases.target[dst_idx] = solver->phases.target[src_idx]; + + const unsigned not_src_lit = NOT (src_lit); + const unsigned not_dst_lit = NOT (dst_lit); + solver->values[dst_lit] = solver->values[src_lit]; + solver->values[not_dst_lit] = solver->values[not_src_lit]; +} + +static unsigned map_idx (kissat *solver, unsigned iidx) { + int elit = PEEK_STACK (solver->export, iidx); + if (!elit) + return INVALID_IDX; + assert (elit); + const unsigned eidx = ABS (elit); + assert (eidx); + import *import = &PEEK_STACK (solver->import, eidx); + assert (import->imported); + if (import->eliminated) + return INVALID_IDX; + const unsigned mlit = import->lit; + const unsigned midx = IDX (mlit); + assert (midx <= iidx); + return midx; +} + +static void compact_queue (kissat *solver) { + LOG ("compacting queue"); + links *links = solver->links, *l; + unsigned *p = &solver->queue.first, prev = DISCONNECT; + solver->queue.stamp = 0; + for (unsigned idx; !DISCONNECTED (idx = *p); p = &l->next) { + const unsigned midx = map_idx (solver, idx); + assert (midx != INVALID_IDX); + l = links + idx; + l->prev = prev; + l->stamp = ++solver->queue.stamp; + if (idx == solver->queue.search.idx) { + solver->queue.search.idx = midx; + solver->queue.search.stamp = l->stamp; + } + *p = prev = midx; + } + solver->queue.last = prev; + *p = DISCONNECT; + for (all_variables (idx)) { + const unsigned midx = map_idx (solver, idx); + if (midx == INVALID_IDX) + continue; + links[midx] = links[idx]; + } +} + +static void compact_stack (kissat *solver, unsigneds *stack) { + unsigned *q = BEGIN_STACK (*stack); + const unsigned *const end = END_STACK (*stack); + for (const unsigned *p = q; p != end; p++) { + const unsigned idx = *p; + const unsigned midx = map_idx (solver, idx); + if (midx == INVALID_IDX) + continue; + *q++ = midx; + } + SET_END_OF_STACK (*stack, q); + SHRINK_STACK (*stack); +} + +static void compact_scores (kissat *solver, heap *old_scores, + unsigned vars) { + LOG ("compacting scores"); + + heap new_scores; + memset (&new_scores, 0, sizeof new_scores); + kissat_resize_heap (solver, &new_scores, vars); + + if (old_scores->tainted) { + LOG ("copying scores of tainted old scores heap"); + for (all_variables (idx)) { + const unsigned midx = map_idx (solver, idx); + if (midx == INVALID_IDX) + continue; + const double score = kissat_get_heap_score (old_scores, idx); + kissat_update_heap (solver, &new_scores, midx, score); + } + } else + LOG ("no need to copy scores of old untainted scores heap"); + + LOG ("now pushing mapped literals onto new heap"); + for (all_stack (unsigned, idx, old_scores->stack)) { + const unsigned midx = map_idx (solver, idx); + if (midx == INVALID_IDX) + continue; + kissat_push_heap (solver, &new_scores, midx); + } + + kissat_release_heap (solver, old_scores); + *old_scores = new_scores; +} + +static void compact_trail (kissat *solver) { + LOG ("compacting trail"); + const size_t size = SIZE_ARRAY (solver->trail); + for (size_t i = 0; i < size; i++) { + const unsigned ilit = PEEK_ARRAY (solver->trail, i); + const unsigned mlit = kissat_map_literal (solver, ilit, true); + assert (mlit != INVALID_LIT); + POKE_ARRAY (solver->trail, i, mlit); + const unsigned idx = IDX (ilit); + assigned *a = solver->assigned + idx; + if (!a->binary) + continue; + const unsigned other = a->reason; + assert (VALID_INTERNAL_LITERAL (other)); + const unsigned mother = kissat_map_literal (solver, other, true); + assert (mother != INVALID_LIT); + a->reason = mother; + } +} + +static void compact_frames (kissat *solver) { + LOG ("compacting frames"); + const size_t size = SIZE_STACK (solver->frames); + for (size_t level = 1; level < size; level++) { + frame *frame = &FRAME (level); + const unsigned ilit = frame->decision; + const unsigned mlit = kissat_map_literal (solver, ilit, true); + assert (mlit != INVALID_LIT); + frame->decision = mlit; + } +} + +static void compact_export (kissat *solver, unsigned vars) { + LOG ("compacting export"); + const size_t size = SIZE_STACK (solver->export); + assert (size <= UINT_MAX); + assert (size == solver->vars); + for (unsigned iidx = 0; iidx < size; iidx++) { + const unsigned elit = PEEK_STACK (solver->export, iidx); + if (!elit) + continue; + const unsigned midx = map_idx (solver, iidx); + if (midx == INVALID_IDX) + continue; + POKE_STACK (solver->export, midx, elit); + } + RESIZE_STACK (solver->export, vars); + SHRINK_STACK (solver->export); +#ifndef NDEBUG + assert (SIZE_STACK (solver->export) == vars); + for (unsigned iidx = 0; iidx < vars; iidx++) { + const int elit = PEEK_STACK (solver->export, iidx); + assert (VALID_EXTERNAL_LITERAL (elit)); + const unsigned eidx = ABS (elit); + const import *const import = &PEEK_STACK (solver->import, eidx); + assert (import->imported); + if (import->eliminated) + continue; + unsigned mlit = import->lit; + if (elit < 0) + mlit = NOT (mlit); + const unsigned ilit = LIT (iidx); + assert (mlit == ilit); + } +#endif +} + +static void compact_units (kissat *solver, unsigned mfixed) { + LOG ("compacting units (first fixed %u)", mfixed); + assert (kissat_fixed (solver, mfixed) > 0); + for (all_stack (int, elit, solver->units)) { + const unsigned eidx = ABS (elit); + const unsigned mlit = elit < 0 ? NOT (mfixed) : mfixed; + const import *const import = &PEEK_STACK (solver->import, eidx); + assert (import->imported); + assert (!import->eliminated); + const unsigned ilit = import->lit; + if (mlit != ilit) + reimport_literal (solver, eidx, mlit); + } +} + +static void compact_best_and_target_values (kissat *solver, unsigned vars) { + const value *const best = solver->phases.best; + const value *const target = solver->phases.target; + const flags *const flags = solver->flags; + + unsigned best_assigned = 0; + unsigned target_assigned = 0; + + for (unsigned idx = 0; idx < vars; idx++) { + if (!flags[idx].active) + continue; + if (target[idx]) + target_assigned++; + if (best[idx]) + best_assigned++; + } + + if (solver->target_assigned != target_assigned) { + LOG ("compacting target assigned from %u to %u", + solver->target_assigned, target_assigned); + solver->target_assigned = target_assigned; + } + + if (solver->best_assigned != best_assigned) { + LOG ("compacting best assigned from %u to %u", solver->best_assigned, + best_assigned); + solver->best_assigned = best_assigned; + } +} + +void kissat_finalize_compacting (kissat *solver, unsigned vars, + unsigned mfixed) { + LOG ("finalizing compacting"); + assert (vars <= solver->vars); +#ifdef LOGGING + assert (solver->compacting); +#endif + if (vars == solver->vars) { +#ifdef LOGGING + solver->compacting = false; + LOG ("number of variables does not change"); +#endif + return; + } + + unsigned reduced = solver->vars - vars; + LOG ("compacted number of variables from %u to %u", solver->vars, vars); + + bool first = true; + for (all_variables (iidx)) { + flags *flags = FLAGS (iidx); + if (flags->fixed && first) + first = false; + else if (!flags->active) + POKE_STACK (solver->export, iidx, 0); + } + + compact_trail (solver); + + for (all_variables (iidx)) { + const unsigned ilit = LIT (iidx); + const unsigned mlit = kissat_map_literal (solver, ilit, true); + if (mlit != INVALID_LIT && ilit != mlit) + compact_literal (solver, mlit, ilit); + } + + if (mfixed != INVALID_LIT) + compact_units (solver, mfixed); + + memset (solver->assigned + vars, 0, reduced * sizeof (assigned)); + memset (solver->flags + vars, 0, reduced * sizeof (flags)); + memset (solver->values + 2 * vars, 0, 2 * reduced * sizeof (value)); + memset (solver->watches + 2 * vars, 0, 2 * reduced * sizeof (watches)); + + compact_queue (solver); + compact_stack (solver, &solver->sweep_schedule); + compact_scores (solver, SCORES, vars); + compact_frames (solver); + compact_export (solver, vars); + compact_best_and_target_values (solver, vars); + + solver->vars = vars; +#ifdef LOGGING + solver->compacting = false; +#endif + kissat_decrease_size (solver); +} diff --git a/src/sat/kissat/compact.h b/src/sat/kissat/compact.h new file mode 100644 index 000000000..89d5ecd31 --- /dev/null +++ b/src/sat/kissat/compact.h @@ -0,0 +1,9 @@ +#ifndef _compact_h_INCLUDED +#define _compact_h_INCLUDED + +struct kissat; + +unsigned kissat_compact_literals (struct kissat *, unsigned *mfixed_ptr); +void kissat_finalize_compacting (struct kissat *, unsigned vars, + unsigned mfixed); +#endif diff --git a/src/sat/kissat/config.c b/src/sat/kissat/config.c new file mode 100644 index 000000000..3c79f7a1e --- /dev/null +++ b/src/sat/kissat/config.c @@ -0,0 +1,83 @@ +#ifndef NOPTIONS + +#include "config.h" +#include "kissat.h" +#include "options.h" + +#include +#include + +int kissat_has_configuration (const char *name) { + if (!strcmp (name, "basic")) + return 1; + if (!strcmp (name, "default")) + return 1; + if (!strcmp (name, "plain")) + return 1; + if (!strcmp (name, "sat")) + return 1; + if (!strcmp (name, "unsat")) + return 1; + return 0; +} + +void kissat_configuration_usage (void) { +#define FMT " --%-8s %s" + printf (FMT "\n", "basic", + "basic CDCL solving " + "('--plain' but no restarts, minimize, reduce)"); + printf (FMT "\n", "default", "default configuration"); + printf (FMT "\n", "plain", + "plain CDCL solving without advanced techniques"); + printf (FMT " ('--target=%d --restartint=%d')\n", "sat", + "target satisfiable instances", (int) TARGET_SAT, + (int) RESTARTINT_SAT); + printf (FMT " ('--stable=%d')\n", "unsat", + "target unsatisfiable instances", (int) STABLE_UNSAT); +} + +static void set_plain_options (kissat *solver) { + kissat_set_option (solver, "bumpreasons", 0); + kissat_set_option (solver, "chrono", 0); + kissat_set_option (solver, "compact", 0); + kissat_set_option (solver, "eagersubsume", 0); + kissat_set_option (solver, "jumpreasons", 0); + kissat_set_option (solver, "otfs", 0); + kissat_set_option (solver, "preprocess", 0); + kissat_set_option (solver, "reorder", 0); + kissat_set_option (solver, "rephase", 0); + kissat_set_option (solver, "restartreusetrail", 0); + kissat_set_option (solver, "simplify", 0); + kissat_set_option (solver, "stable", 2); + kissat_set_option (solver, "tumble", 0); +} + +int kissat_set_configuration (kissat *solver, const char *name) { + if (!strcmp (name, "basic")) { + set_plain_options (solver); + kissat_set_option (solver, "restart", 0); + kissat_set_option (solver, "reduce", 0); + kissat_set_option (solver, "minimize", 0); + return 1; + } + if (!strcmp (name, "default")) + return 1; + if (!strcmp (name, "plain")) { + set_plain_options (solver); + return 1; + } + if (!strcmp (name, "sat")) { + kissat_set_option (solver, "target", TARGET_SAT); + kissat_set_option (solver, "restartint", RESTARTINT_SAT); + return 1; + } + if (!strcmp (name, "unsat")) { + kissat_set_option (solver, "stable", STABLE_UNSAT); + return 1; + } + return 0; +} + +#else +int kissat_config_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/config.h b/src/sat/kissat/config.h new file mode 100644 index 000000000..16eac8c05 --- /dev/null +++ b/src/sat/kissat/config.h @@ -0,0 +1,8 @@ +#ifndef NOPTIONS +#ifndef _config_h_INCLUDED +#define _config_h_INCLUDED + +void kissat_configuration_usage (void); + +#endif +#endif diff --git a/src/sat/kissat/congruence.c b/src/sat/kissat/congruence.c new file mode 100644 index 000000000..01b7dde00 --- /dev/null +++ b/src/sat/kissat/congruence.c @@ -0,0 +1,4635 @@ +#include "congruence.h" +#include "dense.h" +#include "fifo.h" +#include "inline.h" +#include "inlinevector.h" +#include "internal.h" +#include "logging.h" +#include "print.h" +#include "proprobe.h" +#include "rank.h" +#include "reference.h" +#include "report.h" +#include "sort.h" +#include "terminate.h" +#include "trail.h" +#include "utilities.h" + +#include +#include +#include + +// #define INDEX_LARGE_CLAUSES +// #define INDEX_BINARY_CLAUSES +#define MERGE_CONDITIONAL_EQUIVALENCES + +#define AND_GATE 0 +#define XOR_GATE 1 +#define ITE_GATE 2 + +#define LD_MAX_ARITY 26 +#define MAX_ARITY ((1 << LD_MAX_ARITY) - 1) + +struct gate { +#if defined(LOGGING) || !defined(NDEBUG) + size_t id; +#endif + unsigned lhs; + unsigned hash; + unsigned tag : 2; + bool garbage : 1; + bool indexed : 1; + bool marked : 1; + bool shrunken : 1; + unsigned arity : LD_MAX_ARITY; + unsigned rhs[]; +}; + +typedef struct gate gate; +typedef STACK (gate *) gates; + +struct gate_hash_table { + gate **table; + size_t size; + size_t entries; +}; + +typedef struct gate_hash_table gate_hash_table; + +#define REMOVED ((gate *) (~(uintptr_t) 0)) + +#define BEGIN_RHS(G) ((G)->rhs) +#define END_RHS(G) (BEGIN_RHS (G) + (G)->arity) + +#define all_rhs_literals_in_gate(LIT, G) \ + unsigned LIT, \ + *LIT##_PTR = BEGIN_RHS (G), *const LIT##_END = END_RHS (G); \ + LIT##_PTR != LIT##_END && ((LIT = *LIT##_PTR), true); \ + ++LIT##_PTR + +#ifdef INDEX_BINARY_CLAUSES + +struct binary_clause { + unsigned lits[2]; +}; + +typedef struct binary_clause binary_clause; + +struct binary_hash_table { + binary_clause *table; + size_t size, size2, count; +}; + +typedef struct binary_hash_table binary_hash_table; + +#endif + +struct hash_ref { + unsigned hash; + reference ref; +}; + +#ifdef INDEX_LARGE_CLAUSES + +typedef struct hash_ref hash_ref; + +struct large_clause_hash_table { + hash_ref *table; + size_t size, size2, count; +}; + +typedef struct large_clause_hash_table large_clause_hash_table; + +#endif + +#define SIZE_NONCES 16 + +struct closure { + kissat *solver; + bool *scheduled; + gates *occurrences; + gates garbage; + unsigneds lits; + unsigneds rhs; + unsigneds unsimplified; + litpairs binaries; + unsigned_fifo schedule; + unsigned *repr; + gate_hash_table hash; + uint64_t nonces[SIZE_NONCES]; + unsigned *units; + unsigned *equivalences; + unsigned *negbincount; + unsigned *largecount; + litpairs condbin[2]; + litpairs condeq[2]; +#ifdef INDEX_BINARY_CLAUSES + binary_hash_table bintab; +#endif +#ifdef INDEX_LARGE_CLAUSES + large_clause_hash_table clauses; +#endif +#ifdef CHECKING_OR_PROVING + unsigneds chain; +#endif +#if defined(LOGGING) || !defined(NDEBUG) + size_t gates_added; +#endif +#ifndef NDEBUG + unsigneds implied; +#endif +}; + +typedef struct closure closure; + +static void init_closure (kissat *solver, closure *closure) { + closure->solver = solver; + CALLOC (closure->scheduled, VARS); + CALLOC (closure->occurrences, LITS); + INIT_STACK (closure->garbage); + INIT_STACK (closure->lits); + INIT_STACK (closure->rhs); + INIT_STACK (closure->unsimplified); + INIT_STACK (closure->binaries); + INIT_FIFO (closure->schedule); + + NALLOC (closure->repr, LITS); + for (all_literals (lit)) + closure->repr[lit] = lit; + + closure->hash.table = 0; + closure->hash.size = closure->hash.entries = 0; + + generator random = solver->random; + for (size_t i = 0; i != SIZE_NONCES; i++) + closure->nonces[i] = 1 | kissat_next_random64 (&random); + +#ifdef CHECKING_OR_PROVING + INIT_STACK (closure->chain); +#endif +#if defined(LOGGING) || !defined(NDEBUG) + closure->gates_added = 0; +#endif +#ifndef NDEBUG + INIT_STACK (closure->implied); +#endif +} + +static size_t bytes_gate (size_t arity) { + return sizeof (gate) + arity * sizeof (unsigned); +} + +static unsigned actual_gate_arity (gate *g) { + unsigned res = g->arity; + if (!g->shrunken) + return res; + while (g->rhs[res++] != INVALID_LIT) + ; + return res; +} + +#define CLOGANDGATE(G, ...) \ + do { \ + assert ((G)->tag == AND_GATE); \ + LOGANDGATE ((G)->id, closure->repr, (G)->lhs, (G)->arity, (G)->rhs, \ + __VA_ARGS__); \ + } while (0) + +#define CLOGXORGATE(G, ...) \ + do { \ + assert ((G)->tag == XOR_GATE); \ + LOGXORGATE ((G)->id, closure->repr, (G)->lhs, (G)->arity, (G)->rhs, \ + __VA_ARGS__); \ + } while (0) + +#define CLOGITEGATE(G, ...) \ + do { \ + assert ((G)->tag == ITE_GATE); \ + LOGITEGATE ((G)->id, closure->repr, (G)->lhs, (G)->rhs[0], \ + (G)->rhs[1], (G)->rhs[2], __VA_ARGS__); \ + } while (0) + +#define CLOGREPR(L) LOGREPR ((L), closure->repr) + +#define LOGATE(G, ...) \ + do { \ + if ((G)->tag == AND_GATE) \ + CLOGANDGATE (G, __VA_ARGS__); \ + else if ((G)->tag == XOR_GATE) \ + CLOGXORGATE (G, __VA_ARGS__); \ + else { \ + assert ((G)->tag == ITE_GATE); \ + CLOGITEGATE (G, __VA_ARGS__); \ + } \ + } while (0) + +static void delete_gate (closure *closure, gate *g) { + kissat *const solver = closure->solver; + LOGATE (g, "delete"); + unsigned actual_arity = actual_gate_arity (g); + size_t actual_bytes = bytes_gate (actual_arity); + kissat_free (solver, g, actual_bytes); +} + +void reset_gate_hash_table (closure *closure) { + kissat *const solver = closure->solver; + gate **table = closure->hash.table; + for (size_t pos = 0; pos != closure->hash.size; pos++) { + gate *g = table[pos]; + if (g && g != REMOVED && !g->garbage) + delete_gate (closure, g); + } + DEALLOC (table, closure->hash.size); +} + +static void reset_closure (closure *closure) { + kissat *const solver = closure->solver; + + gates *occurrences = closure->occurrences; + for (all_literals (lit)) + RELEASE_STACK (occurrences[lit]); + DEALLOC (occurrences, LITS); + + reset_gate_hash_table (closure); + for (all_pointers (gate, g, closure->garbage)) + delete_gate (closure, g); + + RELEASE_STACK (closure->garbage); + RELEASE_STACK (closure->binaries); + DEALLOC (closure->scheduled, VARS); + RELEASE_STACK (closure->lits); + RELEASE_STACK (closure->rhs); + RELEASE_STACK (closure->unsimplified); + RELEASE_FIFO (closure->schedule); +#ifdef CHECKING_OR_PROVING + RELEASE_STACK (closure->chain); +#endif +#ifndef NDEBUG + RELEASE_STACK (closure->implied); +#endif + + if (!solver->inconsistent && solver->unflushed) + kissat_flush_trail (solver); +} + +static unsigned reset_repr (closure *closure) { + kissat *const solver = closure->solver; + unsigned res = 0, *repr = closure->repr; + for (all_variables (idx)) { + unsigned lit = LIT (idx); + if (!VALUE (lit) && repr[lit] != lit) + res++; + } + DEALLOC (repr, LITS); + return res; +} + +#ifndef NDEBUG + +static void check_lits_sorted (size_t size, const unsigned *lits) { + unsigned prev = INVALID_LIT; + const unsigned *const end_lits = lits + size; + for (const unsigned *p = lits; p != end_lits; p++) { + const unsigned lit = *p; + if (prev != INVALID_LIT) { + assert (prev != lit); + const unsigned not_lit = lit ^ 1; + assert (prev != not_lit); + assert (prev < lit); + } + prev = lit; + } +} + +static void check_and_lits_normalized (size_t arity, const unsigned *lits) { + assert (arity > 1); + check_lits_sorted (arity, lits); +} + +static void check_xor_lits_normalized (const unsigned arity, + const unsigned *lits) { + assert (arity > 1); + check_lits_sorted (arity, lits); + for (size_t i = 1; i != arity; i++) + assert (lits[i - 1] < lits[i]); +} + +static void check_ite_lits_normalized (kissat *solver, + const unsigned *lits) { + assert (!NEGATED (lits[0])); + assert (!NEGATED (lits[1])); + assert (lits[0] != lits[1]); + assert (lits[0] != lits[2]); + assert (lits[1] != lits[2]); + assert (lits[0] != NOT (lits[1])); + assert (lits[0] != NOT (lits[2])); + assert (lits[1] != NOT (lits[2])); +} + +#else + +#define check_lits_sorted(...) \ + do { \ + } while (0) + +#define check_and_lits_normalized check_lits_sorted +#define check_xor_lits_normalized check_lits_sorted +#define check_ite_lits_normalized check_lits_sorted + +#endif + +#define LESS_LIT(A, B) ((A) < (B)) + +static void sort_lits (kissat *solver, size_t arity, unsigned *lits) { + SORT (unsigned, arity, lits, LESS_LIT); + check_lits_sorted (arity, lits); +} + +static unsigned hash_lits (closure *closure, unsigned tag, size_t arity, + const unsigned *lits) { +#ifndef NDEBUG + if (tag == AND_GATE) + check_and_lits_normalized (arity, lits); + else if (tag == XOR_GATE) + check_xor_lits_normalized (arity, lits); + else { + assert (tag == ITE_GATE); + check_ite_lits_normalized (closure->solver, lits); + } +#endif + const unsigned *end_lits = lits + arity; + const uint64_t *const nonces = closure->nonces; + const uint64_t *const end_nonces = nonces + SIZE_NONCES; + const uint64_t *n = nonces + tag; + uint64_t hash = 0; + assert (n < end_nonces); + for (const unsigned *l = lits; l != end_lits; l++) { + hash += *l; + hash *= *n++; + hash = (hash << 4) | (hash >> 60); + if (n == end_nonces) + n = nonces; + } + hash ^= hash >> 32; + return hash; +} + +#ifndef NDEBUG +static bool is_power_of_two (size_t n) { return n && ~(n & (n - 1)); } +#endif + +static size_t reduce_hash (unsigned hash, size_t size, size_t size2) { + assert (size <= size2); + assert (size2 <= 2 * size); + assert (is_power_of_two (size2)); + unsigned res = hash; + res &= size2 - 1; + if (res >= size) + res -= size; + assert (res < size); + return res; +} + +#define MAX_HASH_TABLE_SIZE ((size_t) 1 << 32) + +static bool closure_hash_table_is_full (closure *closure) { + if (closure->hash.size == MAX_HASH_TABLE_SIZE) + return false; + if (2 * closure->hash.entries < closure->hash.size) + return false; + return true; +} + +static bool match_lits (gate *g, unsigned tag, unsigned hash, size_t size, + const unsigned *lits) { + assert (!g->garbage); + if (g->tag != tag) + return false; + if (g->hash != hash) + return false; + if (g->arity != size) + return false; + const unsigned *p = lits; + for (all_rhs_literals_in_gate (lit, g)) + if (lit != *p++) + return false; + return true; +} + +static void resize_gate_hash_table (closure *closure) { + kissat *solver = closure->solver; + gate_hash_table *hash = &closure->hash; + const size_t old_size = hash->size; + const size_t new_size = old_size ? 2 * old_size : 1; + const size_t old_entries = hash->entries; + kissat_extremely_verbose ( + solver, + "resizing gate table of size %zu filled with %zu entries %.0f%%", + old_size, old_entries, kissat_percent (old_entries, old_size)); + gate **old_table = hash->table, **new_table; + CALLOC (new_table, new_size); + size_t flushed = 0; + for (size_t old_pos = 0; old_pos != old_size; old_pos++) { + gate *g = old_table[old_pos]; + if (!g) + continue; + if (g == REMOVED) { + flushed++; + continue; + } + size_t new_pos = reduce_hash (g->hash, new_size, new_size); + while (new_table[new_pos]) { + assert (new_table[new_pos] != REMOVED); + if (++new_pos == new_size) + new_pos = 0; + } + new_table[new_pos] = g; + } + kissat_extremely_verbose ( + solver, "flushed %zu entries %.0f%% resizing table of size %zu", + flushed, kissat_percent (flushed, old_size), old_size); + DEALLOC (old_table, old_size); + assert (flushed <= old_entries); + const size_t new_entries = old_entries - flushed; + hash->table = new_table; + hash->size = new_size; + hash->entries = new_entries; + kissat_very_verbose ( + solver, "resized gate table to %zu with %zu entries %.0f%%", new_size, + new_entries, kissat_percent (new_entries, new_size)); +} + +static bool remove_gate (closure *closure, gate *g) { + if (!g->indexed) + return false; + kissat *solver = closure->solver; + assert (!solver->inconsistent); + const size_t hash_size = closure->hash.size; + size_t pos = reduce_hash (g->hash, hash_size, hash_size); + gate **table = closure->hash.table; + INC (congruent_lookups); + INC (congruent_lookups_removed); + unsigned collisions = 0; + while (table[pos] != g) { + collisions++; + if (++pos == hash_size) + pos = 0; + } + ADD (congruent_collisions_removed, collisions); + ADD (congruent_collisions, collisions); + table[pos] = REMOVED; + LOGATE (g, "removing from hash table"); + g->indexed = false; + return true; +} + +static gate *find_gate (closure *closure, unsigned tag, unsigned hash, + size_t size, const unsigned *lits, gate *except) { + assert (!except || !except->garbage); + if (!closure->hash.entries) + return 0; + kissat *solver = closure->solver; + assert (!solver->inconsistent); + assert (hash == hash_lits (closure, tag, size, lits)); + const size_t hash_size = closure->hash.size; + size_t start_pos = reduce_hash (hash, hash_size, hash_size); + gate **table = closure->hash.table, *g; + INC (congruent_lookups); + INC (congruent_lookups_find); + size_t pos = start_pos; + unsigned collisions = 0; + gate *res = 0; + while ((g = table[pos])) { + if (g == REMOVED) + ; + else if (g->garbage) { + assert (g->indexed); + g->indexed = false; + table[pos] = REMOVED; + } else if (g != except && match_lits (g, tag, hash, size, lits)) { + INC (congruent_matched); + res = g; + break; + } + collisions++; + if (++pos == hash_size) + pos = 0; + if (pos == start_pos) + break; + } + ADD (congruent_collisions_find, collisions); + ADD (congruent_collisions, collisions); + return res; +} + +static void index_gate (closure *closure, gate *g) { + assert (!g->indexed); + kissat *solver = closure->solver; + assert (!solver->inconsistent); + assert (g->arity > 1); + if (closure_hash_table_is_full (closure)) + resize_gate_hash_table (closure); + LOGATE (g, "adding to hash table"); + INC (congruent_indexed); + assert (g->hash == hash_lits (closure, g->tag, g->arity, g->rhs)); + const size_t hash_size = closure->hash.size; + size_t pos = reduce_hash (g->hash, hash_size, hash_size); + gate **table = closure->hash.table, *h; + unsigned collisions = 0; + while ((h = table[pos]) && h != REMOVED) { + collisions++; + if (++pos == hash_size) + pos = 0; + } + ADD (congruent_collisions_index, collisions); + ADD (congruent_collisions, collisions); + table[pos] = g; + closure->hash.entries++; + g->indexed = true; +} + +static unsigned parity_lits (kissat *solver, unsigneds *lits) { + unsigned res = 0; + for (all_stack (unsigned, lit, *lits)) + res ^= NEGATED (lit); +#ifdef NDEBUG + (void) solver; +#endif + return res; +} + +static void inc_lits (kissat *solver, unsigneds *lits) { + unsigned *p = BEGIN_STACK (*lits); + unsigned *end = END_STACK (*lits); + unsigned carry = 1; + while (carry && p != end) { + unsigned lit = *p; + unsigned not_lit = NOT (lit); + carry = !NEGATED (not_lit); + *p++ = not_lit; + } +#ifdef NDEBUG + (void) solver; +#endif +} + +#ifndef NDEBUG + +#define LESS_LITERAL(A, B) ((A) < (B)) + +static void check_implied (closure *closure) { + kissat *const solver = closure->solver; + unsigneds *implied = &closure->implied; + SORT_STACK (unsigned, *implied, LESS_LITERAL); + unsigned *q = BEGIN_STACK (*implied); + const unsigned *const end = END_STACK (*implied); + unsigned prev = INVALID_LIT; + bool tautological = false; + for (const unsigned *p = q; p != end; p++) { + const unsigned lit = *p; + if (prev == lit) + continue; + const unsigned not_lit = NOT (lit); + if (prev == not_lit) { + tautological = true; + break; + } + *q++ = prev = lit; + } + if (!tautological) { + SET_END_OF_STACK (*implied, q); + CHECK_AND_ADD_STACK (*implied); + REMOVE_CHECKER_STACK (*implied); + } + CLEAR_STACK (*implied); +} + +static void check_clause (closure *closure) { + kissat *const solver = closure->solver; + unsigneds *implied = &closure->implied; + unsigneds *clause = &solver->clause; + for (all_stack (unsigned, lit, *clause)) + PUSH_STACK (*implied, lit); + check_implied (closure); +} + +static void check_binary_implied (closure *closure, unsigned a, + unsigned b) { + kissat *const solver = closure->solver; + unsigneds *implied = &closure->implied; + assert (EMPTY_STACK (*implied)); + PUSH_STACK (*implied, a); + PUSH_STACK (*implied, b); + check_implied (closure); +} + +static void check_and_gate_implied (closure *closure, gate *g) { + assert (g->tag == AND_GATE); + kissat *const solver = closure->solver; + if (GET_OPTION (check) < 2) + return; + CLOGANDGATE (g, "checking implied"); + const unsigned lhs = g->lhs; + const unsigned not_lhs = NOT (lhs); + for (all_rhs_literals_in_gate (other, g)) + check_binary_implied (closure, not_lhs, other); + unsigneds *implied = &closure->implied; + assert (EMPTY_STACK (*implied)); + PUSH_STACK (*implied, lhs); + for (all_rhs_literals_in_gate (other, g)) { + const unsigned not_other = NOT (other); + PUSH_STACK (*implied, not_other); + } + check_implied (closure); +} + +static void check_xor_gate_implied (closure *closure, gate *g) { + assert (g->tag == XOR_GATE); + kissat *const solver = closure->solver; + if (GET_OPTION (check) < 2) + return; + CLOGXORGATE (g, "checking implied"); + const unsigned lhs = g->lhs; + const unsigned not_lhs = NOT (lhs); + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + PUSH_STACK (*clause, not_lhs); + for (all_rhs_literals_in_gate (other, g)) { + assert (!NEGATED (other)); + PUSH_STACK (*clause, other); + } + unsigned arity = g->arity; + unsigned end = 1u << arity; + unsigned parity = NEGATED (not_lhs); + assert (parity == parity_lits (solver, clause)); + for (unsigned i = 0; i != end; i++) { + while (i && parity_lits (solver, clause) != parity) + inc_lits (solver, clause); + check_clause (closure); + inc_lits (solver, clause); + } + CLEAR_STACK (*clause); +} + +static void check_ternary (closure *closure, unsigned a, unsigned b, + unsigned c) { + kissat *const solver = closure->solver; + if (GET_OPTION (check) < 2) + return; + unsigneds *implied = &closure->implied; + PUSH_STACK (*implied, a); + PUSH_STACK (*implied, b); + PUSH_STACK (*implied, c); + check_implied (closure); +} + +static void check_ite_implied (closure *closure, unsigned lhs, + unsigned cond, unsigned then_lit, + unsigned else_lit) { + kissat *const solver = closure->solver; + if (GET_OPTION (check) < 2) + return; + LOG ("checking implied ITE gate %s := %s ? %s : %s", LOGLIT (lhs), + LOGLIT (cond), LOGLIT (then_lit), LOGLIT (else_lit)); + const unsigned not_lhs = NOT (lhs); + const unsigned not_cond = NOT (cond); + const unsigned not_then_lit = NOT (then_lit); + const unsigned not_else_lit = NOT (else_lit); + check_ternary (closure, cond, not_else_lit, lhs); + check_ternary (closure, cond, else_lit, not_lhs); + check_ternary (closure, not_cond, not_then_lit, lhs); + check_ternary (closure, not_cond, then_lit, not_lhs); +} + +static void check_ite_gate_implied (closure *closure, gate *g) { + assert (g->tag == ITE_GATE); + assert (g->arity == 3); +#ifndef NOPTIONS + kissat *const solver = closure->solver; +#endif + if (GET_OPTION (check) < 2) + return; + const unsigned lhs = g->lhs; + const unsigned cond = g->rhs[0]; + const unsigned then_lit = g->rhs[1]; + const unsigned else_lit = g->rhs[2]; + check_ite_implied (closure, lhs, cond, then_lit, else_lit); +} + +#else + +#define check_and_gate_implied(...) \ + do { \ + } while (0) + +#define check_xor_gate_implied check_and_gate_implied +#define check_ternary check_and_gate_implied +#define check_ite_implied check_and_gate_implied +#define check_ite_gate_implied check_and_gate_implied + +#endif + +static inline unsigned find_repr (closure *closure, unsigned lit) { + const unsigned *const repr = closure->repr; + unsigned res = lit, next = repr[res]; + while (res != next) + res = next, next = repr[res]; + return res; +} + +#ifndef MERGE_CONDITIONAL_EQUIVALENCES + +static clause *find_other_two (kissat *solver, watches *watches, unsigned a, + unsigned b, unsigned ignore) { + assert (!solver->watching); + const value *const values = solver->values; + const watch *const begin_watches = BEGIN_WATCHES (*watches); + const watch *const end_watches = END_WATCHES (*watches); + const watch *p = begin_watches; + while (p != end_watches) { + const watch watch = *p++; + assert (!watch.type.binary); + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + assert (!c->garbage); + unsigned found = 0; + for (all_literals_in_clause (lit, c)) { + if (values[lit]) + continue; + if (lit == ignore) + continue; + if (lit == a || lit == b) { + found++; + continue; + } + goto CONTINUE_WITH_NEXT_WATCH; + } + assert (found <= 2); + if (found == 2) + return c; + CONTINUE_WITH_NEXT_WATCH:; + } + return 0; +} + +static clause *find_ternary_clause (kissat *solver, unsigned a, unsigned b, + unsigned c) { + assert (!solver->watching); + watches *const a_watches = &WATCHES (a); + watches *const b_watches = &WATCHES (b); + watches *const c_watches = &WATCHES (c); + const size_t size_a = SIZE_WATCHES (*a_watches); + const size_t size_b = SIZE_WATCHES (*b_watches); + const size_t size_c = SIZE_WATCHES (*c_watches); + if (size_a <= size_b && size_a <= size_b) + return find_other_two (solver, a_watches, b, c, a); + if (size_b <= size_a && size_b <= size_c) + return find_other_two (solver, b_watches, a, c, b); + assert (size_c <= size_a && size_c <= size_b); + return find_other_two (solver, c_watches, a, b, c); +} + +#endif + +static bool learn_congruence_unit (closure *closure, unsigned unit) { + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + const value value = solver->values[unit]; + if (value > 0) + return true; + INC (congruent_units); + if (value < 0) { + solver->inconsistent = 1; + LOG ("inconsistent congruence unit %s", LOGLIT (unit)); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return false; + } + LOG ("learning congruence unit %s", LOGLIT (unit)); + kissat_learned_unit (solver, unit); + clause *conflict = kissat_probing_propagate (solver, 0, false); + if (!conflict) + return true; + assert (solver->inconsistent); + LOG ("propagating congruence unit %s yields conflict", LOGLIT (unit)); + return false; +} + +static void add_binary_clause (closure *closure, unsigned a, unsigned b) { + kissat *const solver = closure->solver; + if (solver->inconsistent) + return; + if (a == NOT (b)) + return; + value a_value = VALUE (a); + if (a_value > 0) + return; + value b_value = VALUE (b); + if (b_value > 0) + return; + unsigned unit = INVALID_LIT; + if (a == b) + unit = a; + else if (a_value < 0 && !b_value) + unit = b; + else if (!a_value && b_value < 0) + unit = a; + if (unit != INVALID_LIT) { + (void) !learn_congruence_unit (closure, unit); + return; + } + assert (!a_value), assert (!b_value); + LOGBINARY (a, b, "adding representative"); + if (solver->watching) + kissat_new_binary_clause (solver, a, b); + else { + kissat_new_unwatched_binary_clause (solver, a, b); + litpair litpair = {.lits = {a < b ? a : b, a < b ? b : a}}; + PUSH_STACK (closure->binaries, litpair); + } +} + +static void schedule_literal (closure *closure, unsigned lit) { + kissat *const solver = closure->solver; + unsigned idx = IDX (lit); + bool *scheduled = closure->scheduled + idx; + if (*scheduled) + return; + *scheduled = true; + ENQUEUE_FIFO (closure->schedule, lit); + LOG ("scheduled propagation of merged %s", CLOGREPR (lit)); +} + +static unsigned dequeue_next_scheduled_literal (closure *closure) { + unsigned res; + DEQUEUE_FIFO (closure->schedule, res); +#if defined(LOGGING) || !defined(NDEBUG) + kissat *const solver = closure->solver; +#endif + unsigned idx = IDX (res); + bool *scheduled = closure->scheduled + idx; + assert (*scheduled); + *scheduled = false; + LOG ("dequeued from schedule %s", CLOGREPR (res)); + return res; +} + +static bool merge_literals (closure *closure, unsigned lit, + unsigned other) { + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + unsigned repr_lit = find_repr (closure, lit); + unsigned repr_other = find_repr (closure, other); + unsigned *const repr = closure->repr; + if (repr_lit == repr_other) { + LOG ("already merged %s and %s", LOGREPR (lit, repr), + LOGREPR (other, repr)); + return false; + } + const value *const values = solver->values; + const value lit_value = values[lit]; + const value other_value = values[other]; + assert (lit_value == values[repr_lit]); + assert (other_value == values[repr_other]); + if (lit_value) { + if (lit_value == other_value) { + LOG ("not merging %s and %s assigned to the same value", + LOGREPR (lit, repr), LOGREPR (other, repr)); + return false; + } + if (lit_value == -other_value) { + LOG ("merging inconsistently assigned %s and %s", LOGREPR (lit, repr), + LOGREPR (other, repr)); + solver->inconsistent = true; + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return false; + } + assert (!other_value); + LOG ("merging assigned %s and unassigned %s", LOGREPR (lit, repr), + LOGREPR (other, repr)); + const unsigned unit = (lit_value < 0) ? NOT (other) : other; + (void) learn_congruence_unit (closure, unit); + return false; + } + if (!lit_value && other_value) { + LOG ("merging unassigned %s and assigned %s", LOGREPR (lit, repr), + LOGREPR (other, repr)); + const unsigned unit = (other_value < 0) ? NOT (lit) : lit; + (void) learn_congruence_unit (closure, unit); + return false; + } + unsigned smaller = repr_lit; + unsigned larger = repr_other; + if (smaller > larger) + SWAP (unsigned, smaller, larger); + assert (repr[smaller] == smaller); + assert (repr[larger] > smaller); + if (repr_lit == NOT (repr_other)) { + LOG ("merging clashing %s and %s", LOGREPR (lit, repr), + LOGREPR (other, repr)); + kissat_learned_unit (solver, smaller); + solver->inconsistent = true; + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return false; + } + LOG ("merging %s and %s", LOGREPR (lit, repr), LOGREPR (other, repr)); + const unsigned not_smaller = NOT (smaller); + const unsigned not_larger = NOT (larger); + repr[larger] = smaller; + repr[not_larger] = not_smaller; + LOG ("congruence repr[%s] = %s", LOGLIT (larger), LOGLIT (smaller)); + LOG ("congruence repr[%s] = %s", LOGLIT (not_larger), + LOGLIT (not_smaller)); + add_binary_clause (closure, not_larger, smaller); + add_binary_clause (closure, larger, not_smaller); + schedule_literal (closure, larger); + INC (congruent); + return true; +} + +static void connect_occurrence (closure *closure, unsigned lit, gate *g) { + gates *const occurrences = closure->occurrences; + kissat *const solver = closure->solver; + PUSH_STACK (occurrences[lit], g); + LOG ("connected %s to gate[%zu]", LOGLIT (lit), g->id); +} + +static gate *new_gate (closure *closure, unsigned tag, unsigned hash, + unsigned lhs, unsigned arity, const unsigned *lits) { + kissat *const solver = closure->solver; + const size_t bytes = bytes_gate (arity); + gate *g = kissat_malloc (solver, bytes); +#if defined(LOGGING) || !defined(NDEBUG) + g->id = closure->gates_added++; +#endif + g->tag = tag; + g->hash = hash; + g->lhs = lhs; + g->arity = arity; + g->garbage = false; + g->indexed = false; + g->marked = false; + g->shrunken = false; + memcpy (g->rhs, lits, arity * sizeof *lits); + for (all_rhs_literals_in_gate (lit, g)) + connect_occurrence (closure, lit, g); + LOGATE (g, "new"); + index_gate (closure, g); + ADD (congruent_arity, arity); + INC (congruent_gates); + return g; +} + +static gate *find_and_lits (closure *closure, unsigned *hash_ptr, + unsigned arity, unsigned *lits, gate *except) { + kissat *const solver = closure->solver; + sort_lits (solver, arity, lits); + const unsigned hash = hash_lits (closure, AND_GATE, arity, lits); + gate *g = find_gate (closure, AND_GATE, hash, arity, lits, except); + *hash_ptr = hash; + if (g) { + CLOGANDGATE (g, "found matching"); + INC (congruent_matched_ands); + } else + LOGANDGATE (INVALID_GATE_ID, closure->repr, INVALID_LIT, arity, lits, + "could not find matching"); + return g; +} + +static gate *find_and_gate (closure *closure, unsigned *h, gate *g) { + return find_and_lits (closure, h, g->arity, g->rhs, g); +} + +static gate *new_and_gate (closure *closure, unsigned lhs) { + kissat *const solver = closure->solver; + unsigneds *all_lits = &closure->lits; + unsigneds *rhs_stack = &closure->rhs; + CLEAR_STACK (*rhs_stack); + for (all_stack (unsigned, lit, *all_lits)) + if (lhs != lit) { + unsigned not_lit = NOT (lit); + assert (lhs != not_lit); + PUSH_STACK (*rhs_stack, not_lit); + } + const unsigned arity = SIZE_STACK (*rhs_stack); + unsigned *rhs_lits = BEGIN_STACK (*rhs_stack); + assert (arity + 1 == SIZE_STACK (*all_lits)); + unsigned hash; + gate *g = find_and_lits (closure, &hash, arity, rhs_lits, 0); + if (g) { + if (merge_literals (closure, g->lhs, lhs)) + INC (congruent_ands); + return 0; + } + g = new_gate (closure, AND_GATE, hash, lhs, arity, rhs_lits); + check_and_gate_implied (closure, g); + ADD (congruent_arity_ands, arity); + INC (congruent_gates_ands); + return g; +} + +#ifdef CHECKING_OR_PROVING + +static void copy_literals (kissat *solver, unsigneds *dst, + const unsigneds *src) { + for (all_stack (unsigned, lit, *src)) + PUSH_STACK (*dst, lit); + PUSH_STACK (*dst, INVALID_LIT); +} + +static void simplify_and_add_to_proof_chain (kissat *solver, mark *marks, + unsigneds *unsimplified, + unsigneds *clause, + unsigneds *chain) { + assert (EMPTY_STACK (*clause)); +#ifndef NDEBUG + for (all_stack (unsigned, lit, *unsimplified)) + assert (!(marks[lit] & 4)); +#endif + bool trivial = false; + for (all_stack (unsigned, lit, *unsimplified)) { + mark lit_mark = marks[lit]; + if (lit_mark & 4) + continue; + const unsigned not_lit = NOT (lit); + const mark not_lit_mark = marks[not_lit]; + if (not_lit_mark & 4) { + trivial = true; + break; + } + lit_mark |= 4; + marks[lit] = lit_mark; + PUSH_STACK (*clause, lit); + } + for (all_stack (unsigned, lit, *clause)) { + mark mark = marks[lit]; + assert (mark & 4); + mark &= ~4u; + marks[lit] = mark; + } + if (!trivial) { + CHECK_AND_ADD_STACK (*clause); + ADD_STACK_TO_PROOF (*clause); + copy_literals (solver, chain, clause); + } + CLEAR_STACK (*clause); +} + +#define SIMPLIFY_AND_ADD_TO_PROOF_CHAIN() \ + simplify_and_add_to_proof_chain (solver, marks, unsimplified, clause, \ + chain) + +static void add_xor_matching_proof_chain (closure *closure, gate *g, + unsigned lhs1, unsigned lhs2) { + if (lhs1 == lhs2) + return; + kissat *const solver = closure->solver; + if (!kissat_checking_or_proving (solver)) + return; + LOG ("starting XOR matching proof chain"); + unsigneds *const unsimplified = &closure->unsimplified; + unsigneds *const clause = &solver->clause; + unsigneds *const chain = &closure->chain; + mark *const marks = solver->marks; + assert (EMPTY_STACK (*unsimplified)); + assert (EMPTY_STACK (*chain)); + assert (g->arity > 1); + const unsigned reduced_arity = g->arity - 1; + for (unsigned i = 0; i != reduced_arity; i++) + PUSH_STACK (*unsimplified, g->rhs[i]); + const unsigned not_lhs1 = NOT (lhs1); + const unsigned not_lhs2 = NOT (lhs2); + do { + const size_t size = SIZE_STACK (*unsimplified); + assert (size < 32); + for (unsigned i = 0; i != 1u << size; i++) { + PUSH_STACK (*unsimplified, not_lhs1); + PUSH_STACK (*unsimplified, lhs2); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end -= 2; + PUSH_STACK (*unsimplified, lhs1); + PUSH_STACK (*unsimplified, not_lhs2); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end -= 2; + inc_lits (solver, unsimplified); + } + assert (!EMPTY_STACK (*unsimplified)); + unsimplified->end--; + } while (!EMPTY_STACK (*unsimplified)); + LOG ("finished XOR matching proof chain"); +} + +static void delete_proof_chain (closure *closure) { + kissat *const solver = closure->solver; + unsigneds *chain = &closure->chain; + if (!kissat_checking_or_proving (solver)) { + assert (EMPTY_STACK (*chain)); + return; + } + if (EMPTY_STACK (*chain)) + return; + LOG ("starting deletion of proof chain"); + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + const unsigned *start = BEGIN_STACK (*chain); + const unsigned *end = END_STACK (*chain); + const unsigned *p = start; + while (p != end) { + const unsigned lit = *p; + if (lit == INVALID_LIT) { + while (start != p) { + const unsigned other = *start++; + PUSH_STACK (*clause, other); + } + REMOVE_CHECKER_STACK (*clause); + DELETE_STACK_FROM_PROOF (*clause); + CLEAR_STACK (*clause); + start++; + } + p++; + } + assert (EMPTY_STACK (*clause)); + assert (start == end); + CLEAR_STACK (*chain); + LOG ("finished deletion of proof chain"); +} + +#else + +#define add_xor_matching_proof_chain(...) \ + do { \ + } while (0) + +#define delete_proof_chain(...) \ + do { \ + } while (0) + +#endif + +static gate *find_xor_lits (closure *closure, unsigned *hash_ptr, + unsigned arity, unsigned *lits, gate *except) { + kissat *const solver = closure->solver; + sort_lits (solver, arity, lits); + const unsigned hash = hash_lits (closure, XOR_GATE, arity, lits); + gate *g = find_gate (closure, XOR_GATE, hash, arity, lits, except); + *hash_ptr = hash; + if (g) { + CLOGXORGATE (g, "found matching"); + INC (congruent_matched_xors); + } else + LOGXORGATE (INVALID_GATE_ID, closure->repr, INVALID_LIT, arity, lits, + "tried but did not find matching"); + return g; +} + +static gate *find_xor_gate (closure *closure, unsigned *h, gate *g) { + return find_xor_lits (closure, h, g->arity, g->rhs, g); +} + +static gate *new_xor_gate (closure *closure, unsigned lhs) { + kissat *const solver = closure->solver; + unsigneds *all_lits = &closure->lits; + unsigneds *rhs_stack = &closure->rhs; + CLEAR_STACK (*rhs_stack); + const unsigned not_lhs = NOT (lhs); + for (all_stack (unsigned, lit, *all_lits)) + if (lit != lhs && lit != not_lhs) { + assert (!NEGATED (lit)); + PUSH_STACK (*rhs_stack, lit); + } + const unsigned arity = SIZE_STACK (*rhs_stack); + unsigned *rhs_lits = BEGIN_STACK (*rhs_stack); + assert (arity + 1 == SIZE_STACK (*all_lits)); + unsigned hash; + gate *g = find_xor_lits (closure, &hash, arity, rhs_lits, 0); + if (g) { + add_xor_matching_proof_chain (closure, g, g->lhs, lhs); + if (merge_literals (closure, g->lhs, lhs)) + INC (congruent_xors); + if (!solver->inconsistent) + delete_proof_chain (closure); + return 0; + } + g = new_gate (closure, XOR_GATE, hash, lhs, arity, rhs_lits); + check_xor_gate_implied (closure, g); + ADD (congruent_arity_xors, arity); + INC (congruent_gates_xors); + return g; +} + +#ifdef CHECKING_OR_PROVING + +static void add_ite_matching_proof_chain (closure *closure, gate *g, + unsigned lhs1, unsigned lhs2) { + if (lhs1 == lhs2) + return; + kissat *const solver = closure->solver; + if (!kissat_checking_or_proving (solver)) + return; + LOG ("starting ITE matching proof chain"); + unsigneds *const unsimplified = &closure->unsimplified; + unsigneds *clause = &solver->clause; + mark *const marks = solver->marks; + unsigneds *chain = &closure->chain; + assert (EMPTY_STACK (*clause)); + assert (EMPTY_STACK (*chain)); + const unsigned *rhs = g->rhs; + const unsigned cond = rhs[0]; + const unsigned not_cond = NOT (cond); + const unsigned not_lhs1 = NOT (lhs1); + const unsigned not_lhs2 = NOT (lhs2); + PUSH_STACK (*unsimplified, lhs1); + PUSH_STACK (*unsimplified, not_lhs2); + PUSH_STACK (*unsimplified, cond); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end--; + PUSH_STACK (*unsimplified, not_cond); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end--; + CHECK_AND_ADD_STACK (*unsimplified); + ADD_STACK_TO_PROOF (*unsimplified); + copy_literals (solver, chain, unsimplified); + CLEAR_STACK (*unsimplified); + PUSH_STACK (*unsimplified, not_lhs1); + PUSH_STACK (*unsimplified, lhs2); + PUSH_STACK (*unsimplified, cond); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end--; + PUSH_STACK (*unsimplified, not_cond); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end--; + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + CLEAR_STACK (*unsimplified); + LOG ("finished ITE matching proof chain"); +} + +static void add_ite_turned_and_binary_clauses (closure *closure, gate *g) { + kissat *const solver = closure->solver; + if (!kissat_checking_or_proving (solver)) + return; + LOG ("starting ITE turned AND supporting binary clauses"); + unsigneds *const unsimplified = &closure->unsimplified; + unsigneds *clause = &solver->clause; + unsigneds *chain = &closure->chain; + mark *const marks = solver->marks; + assert (EMPTY_STACK (*unsimplified)); + assert (EMPTY_STACK (*chain)); + const unsigned not_lhs = NOT (g->lhs); + const unsigned *rhs = g->rhs; + PUSH_STACK (*unsimplified, not_lhs); + PUSH_STACK (*unsimplified, rhs[0]); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + unsimplified->end--; + PUSH_STACK (*unsimplified, rhs[1]); + SIMPLIFY_AND_ADD_TO_PROOF_CHAIN (); + CLEAR_STACK (*unsimplified); +} + +#else + +#define add_ite_matching_proof_chain(...) \ + do { \ + } while (0) + +#define add_ite_turned_and_binary_clauses add_ite_matching_proof_chain + +#endif + +static bool normalize_ite_lits (kissat *solver, unsigned *lits) { +#ifdef NDEBUG + (void) solver; +#endif + if (NEGATED (lits[0])) { + lits[0] = NOT (lits[0]); + SWAP (unsigned, lits[1], lits[2]); + } + if (!NEGATED (lits[1])) + return false; + lits[1] = NOT (lits[1]); + lits[2] = NOT (lits[2]); + return true; +} + +static gate *find_ite_lits (closure *closure, unsigned *hash_ptr, + bool *negate_lhs_ptr, unsigned arity, + unsigned *lits, gate *except) { + kissat *const solver = closure->solver; + assert (arity == 3); + LOGITEGATE (INVALID_GATE_ID, closure->repr, INVALID_LIT, lits[0], lits[1], + lits[2], "finding not yet normalized"); + bool negate_lhs = normalize_ite_lits (solver, lits); +#ifdef LOGGING + if (negate_lhs) + LOG ("normalization forces negation of LHS"); + LOGITEGATE (INVALID_GATE_ID, closure->repr, INVALID_LIT, lits[0], lits[1], + lits[2], "normalized"); +#endif + *negate_lhs_ptr = negate_lhs; + const unsigned hash = hash_lits (closure, ITE_GATE, arity, lits); + gate *g = find_gate (closure, ITE_GATE, hash, arity, lits, except); + *hash_ptr = hash; + if (g) { + CLOGITEGATE (g, "found matching"); + INC (congruent_matched_ites); + } else + LOGITEGATE (INVALID_GATE_ID, closure->repr, INVALID_LIT, lits[0], + lits[1], lits[2], "tried but did not find matching"); + return g; +} + +static gate *find_ite_gate (closure *closure, unsigned *h, + bool *negate_lhs_ptr, gate *g) { + return find_ite_lits (closure, h, negate_lhs_ptr, g->arity, g->rhs, g); +} + +static gate *new_ite_gate (closure *closure, unsigned lhs, unsigned cond, + unsigned then_lit, unsigned else_lit) { + kissat *const solver = closure->solver; + const unsigned not_then_lit = NOT (then_lit); + if (else_lit == not_then_lit) { +#ifdef LOGGING + if (NEGATED (then_lit)) + LOG ("skipping ternary XOR gate %s := %s ^ %s", LOGLIT (lhs), + LOGLIT (cond), LOGLIT (not_then_lit)); + else + LOG ("skipping ternary XOR gate %s := %s ^ %s", LOGLIT (NOT (lhs)), + LOGLIT (cond), LOGLIT (then_lit)); +#endif + return 0; + } + if (else_lit == then_lit) { + LOG ("found trivial ITE gate %s := %s ? %s : %s", LOGLIT (lhs), + LOGLIT (cond), LOGLIT (then_lit), LOGLIT (else_lit)); + if (merge_literals (closure, lhs, then_lit)) + INC (congruent_trivial_ite); + return 0; + } + unsigneds *rhs_stack = &closure->rhs; + CLEAR_STACK (*rhs_stack); + PUSH_STACK (*rhs_stack, cond); + PUSH_STACK (*rhs_stack, then_lit); + PUSH_STACK (*rhs_stack, else_lit); + assert (SIZE_STACK (*rhs_stack) == 3); + const unsigned arity = 3; + unsigned *rhs_lits = BEGIN_STACK (*rhs_stack); + bool negate_lhs; + unsigned hash; + gate *g = find_ite_lits (closure, &hash, &negate_lhs, arity, rhs_lits, 0); + if (g) { + if (negate_lhs) + lhs = NOT (lhs); + add_ite_matching_proof_chain (closure, g, g->lhs, lhs); + if (merge_literals (closure, g->lhs, lhs)) + INC (congruent_ites); + if (!solver->inconsistent) + delete_proof_chain (closure); + return 0; + } + if (negate_lhs) + lhs = NOT (lhs); + g = new_gate (closure, ITE_GATE, hash, lhs, arity, rhs_lits); + check_ite_gate_implied (closure, g); + INC (congruent_gates_ites); + return g; +} + +static void mark_gate_as_garbage (closure *closure, gate *g) { + kissat *const solver = closure->solver; + assert (!g->garbage); + g->garbage = true; + LOGATE (g, "marked as garbage"); + PUSH_STACK (closure->garbage, g); +} + +static void shrink_gate (closure *closure, gate *g, + const unsigned *new_end_rhs) { + unsigned *const rhs = g->rhs; + const unsigned old_arity = g->arity; + unsigned *const old_end_rhs = rhs + old_arity; + assert (rhs <= new_end_rhs); + assert (new_end_rhs <= old_end_rhs); + if (new_end_rhs == old_end_rhs) + return; + const unsigned new_arity = new_end_rhs - rhs; + if (!g->shrunken) { + assert (old_end_rhs[-1] != INVALID_LIT); + old_end_rhs[-1] = INVALID_LIT; + g->shrunken = true; + } + g->arity = new_arity; +#ifdef LOGGING + kissat *const solver = closure->solver; + LOGATE (g, "shrunken"); +#else + (void) closure; +#endif +} + +static bool skip_and_gate (closure *closure, gate *g) { + assert (g->tag == AND_GATE); + if (g->garbage) + return true; + kissat *const solver = closure->solver; + const value *const values = solver->values; + const unsigned lhs = g->lhs; + const value value_lhs = values[lhs]; + if (value_lhs > 0) { + mark_gate_as_garbage (closure, g); + return true; + } + assert (g->arity > 1); + return false; +} + +static bool gate_contains (gate *g, unsigned lit) { + for (all_rhs_literals_in_gate (other, g)) + if (lit == other) + return true; + return false; +} + +static bool rewriting_lhs (closure *closure, gate *g, unsigned dst) { +#ifndef NDEBUG + kissat *const solver = closure->solver; +#endif + if (dst != g->lhs && dst != NOT (g->lhs)) + return false; + mark_gate_as_garbage (closure, g); + return true; +} + +static void shrink_and_gate (closure *closure, gate *g, + unsigned *new_end_rhs, unsigned falsifies, + unsigned clashing) { + assert (g->tag == AND_GATE); +#ifndef NDEBUG + kissat *const solver = closure->solver; +#endif + if (falsifies != INVALID_LIT) { + assert (g->arity); + g->rhs[0] = falsifies; + new_end_rhs = g->rhs + 1; + } else if (clashing != INVALID_LIT) { + assert (1 < g->arity); + g->rhs[0] = clashing; + g->rhs[1] = NOT (clashing); + new_end_rhs = g->rhs + 2; + } + shrink_gate (closure, g, new_end_rhs); +} + +static void update_and_gate (closure *closure, gate *g, unsigned falsifies, + unsigned clashing) { + assert (g->tag == AND_GATE); + bool garbage = true; + kissat *const solver = closure->solver; + if (falsifies != INVALID_LIT || clashing != INVALID_LIT) + (void) learn_congruence_unit (closure, NOT (g->lhs)); + else if (g->arity == 1) { + const value value_lhs = VALUE (g->lhs); + if (value_lhs > 0) + (void) learn_congruence_unit (closure, g->rhs[0]); + else if (value_lhs < 0) + (void) learn_congruence_unit (closure, NOT (g->rhs[0])); + else if (merge_literals (closure, g->lhs, g->rhs[0])) { + INC (congruent_unary_ands); + INC (congruent_unary); + } + } else { + unsigned hash; + gate *h = find_and_gate (closure, &hash, g); + if (h) { + assert (garbage); + if (merge_literals (closure, g->lhs, h->lhs)) + INC (congruent_ands); + } else { + remove_gate (closure, g); + g->hash = hash; + index_gate (closure, g); + garbage = false; + } + } + if (garbage && !solver->inconsistent) + mark_gate_as_garbage (closure, g); +} + +static void simplify_and_gate (closure *closure, gate *g) { + if (skip_and_gate (closure, g)) + return; + kissat *const solver = closure->solver; + const value *const values = solver->values; + CLOGANDGATE (g, "simplifying"); + const unsigned old_arity = g->arity; + unsigned *const rhs = g->rhs; + unsigned *const end_of_rhs = rhs + old_arity; + const unsigned *p = rhs; + unsigned *q = rhs; + unsigned falsifies = INVALID_LIT; + while (p != end_of_rhs) { + const unsigned lit = *p++; + const value value = values[lit]; + if (value > 0) + continue; + if (value < 0) { + LOG ("found falsifying literal %s", LOGLIT (lit)); + falsifies = lit; + continue; + } + *q++ = lit; + } + shrink_and_gate (closure, g, q, falsifies, INVALID_LIT); + CLOGANDGATE (g, "simplified"); + check_and_gate_implied (closure, g); + update_and_gate (closure, g, falsifies, INVALID_LIT); + INC (congruent_simplified); + INC (congruent_simplified_ands); +} + +static void rewrite_and_gate (closure *closure, gate *g, unsigned dst, + unsigned src) { + if (skip_and_gate (closure, g)) + return; + if (!gate_contains (g, src)) + return; + assert (src != INVALID_LIT); + assert (dst != INVALID_LIT); + kissat *const solver = closure->solver; + const value *const values = solver->values; + assert (values[src] == values[dst]); + CLOGANDGATE (g, "rewriting %s by %s in", CLOGREPR (src), CLOGREPR (dst)); + const unsigned old_arity = g->arity; + const unsigned not_lhs = NOT (g->lhs); + unsigned *const rhs = g->rhs; + unsigned *const end_of_rhs = rhs + old_arity; + const unsigned *p = rhs; + unsigned *q = rhs; + unsigned falsifies = INVALID_LIT; + unsigned clashing = INVALID_LIT; + const unsigned not_dst = NOT (dst); + unsigned dst_count = 0, not_dst_count = 0; + while (p != end_of_rhs) { + unsigned lit = *p++; + if (lit == src) + lit = dst; + if (lit == not_lhs) { + LOG ("found negated LHS literal %s", LOGLIT (lit)); + clashing = lit; + break; + } + const value value = values[lit]; + if (value > 0) + continue; + if (value < 0) { + LOG ("found falsifying literal %s", LOGLIT (lit)); + falsifies = lit; + break; + } + if (lit == dst) { + if (not_dst_count) { + LOG ("clashing literals %s and %s", LOGLIT (not_dst), LOGLIT (dst)); + clashing = not_dst; + break; + } + if (dst_count++) + continue; + } + if (lit == not_dst) { + if (dst_count) { + assert (!not_dst_count); + LOG ("clashing literals %s and %s", LOGLIT (dst), LOGLIT (not_dst)); + clashing = dst; + break; + } + assert (!not_dst_count); + not_dst_count++; + } + *q++ = lit; + } + assert (dst_count <= 2); + assert (not_dst_count <= 1); + shrink_and_gate (closure, g, q, falsifies, clashing); + CLOGANDGATE (g, "rewritten"); + check_and_gate_implied (closure, g); + update_and_gate (closure, g, falsifies, clashing); + INC (congruent_rewritten); + INC (congruent_rewritten_ands); +} + +static bool skip_xor_gate (gate *g) { + assert (g->tag == XOR_GATE); + if (g->garbage) + return true; + assert (g->arity > 1); + return false; +} + +#ifdef CHECKING_OR_PROVING + +static void add_xor_shrinking_proof_chain (closure *closure, gate *g, + unsigned pivot) { + kissat *const solver = closure->solver; + if (!kissat_checking_or_proving (solver)) + return; + LOG ("starting XOR shrinking proof chain"); + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + for (unsigned i = 0; i != g->arity; i++) { + unsigned lit = g->rhs[i]; + PUSH_STACK (*clause, lit); + } + const unsigned lhs = g->lhs; + const unsigned not_lhs = NOT (lhs); + PUSH_STACK (*clause, not_lhs); + const unsigned parity = NEGATED (not_lhs); + assert (parity == parity_lits (solver, clause)); + const unsigned not_pivot = NOT (pivot); + const size_t size = SIZE_STACK (*clause); + assert (size < 32); + const unsigned end = 1u << size; + for (unsigned i = 0; i != end; i++) { + while (i && parity != parity_lits (solver, clause)) + inc_lits (solver, clause); + PUSH_STACK (*clause, pivot); + CHECK_AND_ADD_STACK (*clause); + ADD_STACK_TO_PROOF (*clause); + clause->end--; + PUSH_STACK (*clause, not_pivot); + CHECK_AND_ADD_STACK (*clause); + ADD_STACK_TO_PROOF (*clause); + clause->end--; + CHECK_AND_ADD_STACK (*clause); + ADD_STACK_TO_PROOF (*clause); + PUSH_STACK (*clause, pivot); + REMOVE_CHECKER_STACK (*clause); + DELETE_STACK_FROM_PROOF (*clause); + clause->end--; + PUSH_STACK (*clause, not_pivot); + REMOVE_CHECKER_STACK (*clause); + DELETE_STACK_FROM_PROOF (*clause); + clause->end--; + inc_lits (solver, clause); + } + CLEAR_STACK (*clause); + LOG ("finished XOR shrinking proof chain"); +} + +#else + +#define add_xor_shrinking_proof_chain(...) \ + do { \ + } while (0) +#endif + +static void shrink_xor_gate (closure *closure, gate *g, + unsigned *new_end_rhs) { + assert (g->tag == XOR_GATE); + shrink_gate (closure, g, new_end_rhs); +} + +static void update_xor_gate (closure *closure, gate *g) { + assert (g->tag == XOR_GATE); + kissat *const solver = closure->solver; + bool garbage = true; + if (g->arity == 0) + (void) learn_congruence_unit (closure, NOT (g->lhs)); + else if (g->arity == 1) { + const value value_lhs = VALUE (g->lhs); + if (value_lhs > 0) + (void) learn_congruence_unit (closure, g->rhs[0]); + else if (value_lhs < 0) + (void) learn_congruence_unit (closure, NOT (g->rhs[0])); + else if (merge_literals (closure, g->lhs, g->rhs[0])) { + INC (congruent_unary_xors); + INC (congruent_unary); + } + } else { + assert (g->arity > 1); + unsigned hash; + gate *h = find_xor_gate (closure, &hash, g); + if (h) { + assert (garbage); + add_xor_matching_proof_chain (closure, g, g->lhs, h->lhs); + if (merge_literals (closure, g->lhs, h->lhs)) + INC (congruent_xors); + if (!solver->inconsistent) + delete_proof_chain (closure); + } else { + remove_gate (closure, g); + g->hash = hash; + index_gate (closure, g); + garbage = false; + } + } + if (garbage && !solver->inconsistent) + mark_gate_as_garbage (closure, g); +} + +static void simplify_xor_gate (closure *closure, gate *g) { + if (skip_xor_gate (g)) + return; + kissat *const solver = closure->solver; + const value *const values = solver->values; + CLOGXORGATE (g, "simplifying"); + unsigned *q = g->rhs, *const end_of_rhs = q + g->arity; + unsigned negate = 0; + for (const unsigned *p = q; p != end_of_rhs; p++) { + const unsigned lit = *p; + assert (!NEGATED (lit)); + const value value = values[lit]; + if (value > 0) + negate ^= 1; + if (!value) + *q++ = lit; + } + if (negate) { + LOG ("flipping LHS literal %s", LOGLIT (g->lhs)); + g->lhs = NOT (g->lhs); + } + shrink_xor_gate (closure, g, q); + update_xor_gate (closure, g); + CLOGXORGATE (g, "simplified"); + check_xor_gate_implied (closure, g); + INC (congruent_simplified); + INC (congruent_simplified_xors); +} + +static void rewrite_xor_gate (closure *closure, gate *g, unsigned dst, + unsigned src) { + if (skip_xor_gate (g)) + return; + if (rewriting_lhs (closure, g, dst)) + return; + if (!gate_contains (g, src)) + return; + kissat *const solver = closure->solver; + CLOGXORGATE (g, "rewriting %s by %s in", CLOGREPR (src), CLOGREPR (dst)); + const value *const values = solver->values; + unsigned *q = g->rhs, *end_of_rhs = q + g->arity; + unsigned original_dst_negated = NEGATED (dst); + unsigned negate = original_dst_negated; + unsigned dst_count = 0; + dst = STRIP (dst); + for (const unsigned *p = q; p != end_of_rhs; p++) { + unsigned lit = *p; + assert (!NEGATED (lit)); + if (lit == src) + lit = dst; + const value value = values[lit]; + if (value > 0) + negate ^= 1; + if (value) + continue; + if (lit == dst) + dst_count++; + *q++ = lit; + } + if (negate) { + LOG ("flipping LHS literal %s", LOGLIT (g->lhs)); + g->lhs = NOT (g->lhs); + } + assert (dst_count <= 2); + if (dst_count == 2) { + CLOGXORGATE (g, "literals %s and %s were both in", LOGLIT (src), + LOGLIT (dst)); + end_of_rhs = q; + q = g->rhs; + for (const unsigned *p = q; p != end_of_rhs; p++) { + const unsigned lit = *p; + if (lit != dst) + *q++ = lit; + } + assert (q + 2 == end_of_rhs); + } + shrink_xor_gate (closure, g, q); + CLOGXORGATE (g, "rewritten"); + if (dst_count > 1) + add_xor_shrinking_proof_chain (closure, g, src); + update_xor_gate (closure, g); + if (!g->garbage && !solver->inconsistent && original_dst_negated && + dst_count == 1) { + assert (!NEGATED (dst)); + connect_occurrence (closure, dst, g); + } + check_xor_gate_implied (closure, g); + INC (congruent_rewritten); + INC (congruent_rewritten_xors); +} + +static bool skip_ite_gate (gate *g) { + assert (g->tag == ITE_GATE); + if (g->garbage) + return true; + return false; +} + +static void simplify_ite_gate (closure *closure, gate *g) { + if (skip_ite_gate (g)) + return; + kissat *const solver = closure->solver; + const value *const values = solver->values; + CLOGITEGATE (g, "simplifying"); + assert (g->arity == 3); + bool garbage = true; + const unsigned lhs = g->lhs; + unsigned *const rhs = g->rhs; + const unsigned cond = rhs[0]; + const unsigned then_lit = rhs[1]; + const unsigned else_lit = rhs[2]; + const value cond_value = values[cond]; + if (cond_value > 0) { + if (merge_literals (closure, lhs, then_lit)) { + INC (congruent_unary_ites); + INC (congruent_unary); + } + } else if (cond_value < 0) { + if (merge_literals (closure, lhs, else_lit)) { + INC (congruent_unary_ites); + INC (congruent_unary); + } + } else { + const value then_value = values[then_lit]; + const value else_value = values[else_lit]; + const unsigned not_lhs = NOT (lhs); + assert (then_value || else_value); + if (then_value > 0 && else_value > 0) + learn_congruence_unit (closure, lhs); + else if (then_value < 0 && else_value < 0) + learn_congruence_unit (closure, not_lhs); + else if (then_value > 0 && else_value < 0) { + if (merge_literals (closure, lhs, cond)) { + INC (congruent_unary_ites); + INC (congruent_unary); + } + } else if (then_value < 0 && else_value > 0) { + const unsigned not_cond = NOT (cond); + if (merge_literals (closure, lhs, not_cond)) { + INC (congruent_unary_ites); + INC (congruent_unary); + } + } else { + assert (!!else_value + !!then_value == 1); + if (then_value > 0) { + assert (!else_value); + g->lhs = not_lhs; + rhs[0] = NOT (cond); + rhs[1] = NOT (else_lit); + } else if (then_value < 0) { + assert (!else_value); + rhs[0] = NOT (cond); + rhs[1] = else_lit; + } else if (else_value > 0) { + assert (!then_value); + g->lhs = not_lhs; + rhs[0] = NOT (then_lit); + rhs[1] = cond; + } else { + assert (else_value < 0); + assert (!then_value); + rhs[0] = cond; + rhs[1] = then_lit; + } + if (rhs[0] > rhs[1]) + SWAP (unsigned, rhs[0], rhs[1]); + assert (!g->shrunken); + g->shrunken = true; + rhs[2] = INVALID_LIT; + g->arity = 2; + g->tag = AND_GATE; + assert (rhs[0] < rhs[1]); + assert (rhs[0] != NOT (rhs[1])); + CLOGANDGATE (g, "simplified"); + check_and_gate_implied (closure, g); + unsigned hash; + gate *h = find_and_gate (closure, &hash, g); + if (h) { + assert (garbage); + if (merge_literals (closure, g->lhs, h->lhs)) + INC (congruent_ands); + } else { + remove_gate (closure, g); + g->hash = hash; + index_gate (closure, g); + garbage = false; + for (all_rhs_literals_in_gate (lit, g)) + if (lit != cond && lit != then_lit && lit != else_lit) + connect_occurrence (closure, lit, g); + } + } + } + if (garbage && !solver->inconsistent) + mark_gate_as_garbage (closure, g); + INC (congruent_simplified); + INC (congruent_simplified_ites); +} + +static void rewrite_ite_gate (closure *closure, gate *g, unsigned dst, + unsigned src) { + if (skip_ite_gate (g)) + return; + if (!gate_contains (g, src)) + return; + kissat *const solver = closure->solver; + CLOGITEGATE (g, "rewriting %s by %s in", CLOGREPR (src), CLOGREPR (dst)); + unsigned *const rhs = g->rhs; + assert (g->arity == 3); + const unsigned lhs = g->lhs; + const unsigned cond = rhs[0]; + const unsigned then_lit = rhs[1]; + const unsigned else_lit = rhs[2]; + const unsigned not_lhs = NOT (lhs); + const unsigned not_dst = NOT (dst); + const unsigned not_cond = NOT (cond); + const unsigned not_then_lit = NOT (then_lit); + const unsigned not_else_lit = NOT (else_lit); + unsigned new_tag = AND_GATE; + bool garbage = false; + bool shrink = true; + if (src == cond) { + if (dst == then_lit) { + // then_lit ? then_lit : else_lit + // then_lit & then_lit | !then_lit & else_lit + // then_lit | !then_lit & else_lit + // then_lit | else_lit + // !(!then_lit & !else_lit) + g->lhs = not_lhs; + rhs[0] = not_then_lit; + rhs[1] = not_else_lit; + } else if (not_dst == then_lit) { + // !then_lit ? then_lit : else_lit + // !then_lit & then_lit | then_lit & else_lit + // then_lit & else_lit + rhs[0] = else_lit; + assert (rhs[1] == then_lit); + } else if (dst == else_lit) { + // else_list ? then_lit : else_lit + // else_list & then_lit | !else_list & else_lit + // else_list & then_lit + rhs[0] = else_lit; + assert (rhs[1] == then_lit); + } else if (not_dst == else_lit) { + // !else_list ? then_lit : else_lit + // !else_list & then_lit | else_lit & else_lit + // !else_list & then_lit | else_lit + // then_lit | else_lit + // !(!then_lit & !else_lit) + g->lhs = not_lhs; + rhs[0] = not_then_lit; + rhs[1] = not_else_lit; + } else { + shrink = false; + rhs[0] = dst; + } + } else if (src == then_lit) { + if (dst == cond) { + // cond ? cond : else_lit + // cond & cond | !cond & else_lit + // cond | !cond & else_lit + // cond | else_lit + // !(!cond & !else_lit) + g->lhs = not_lhs; + rhs[0] = not_cond; + rhs[1] = not_else_lit; + } else if (not_dst == cond) { + // cond ? !cond : else_lit + // cond & !cond | !cond & else_lit + // !cond & else_lit + rhs[0] = not_cond; + rhs[1] = else_lit; + } else if (dst == else_lit) { + // cond ? else_lit : else_lit + // else_lit + if (merge_literals (closure, lhs, else_lit)) { + INC (congruent_unary_ites); + INC (congruent_unary); + } + garbage = true; + } else if (not_dst == else_lit) { + // cond ? !else_lit : else_lit + // cond & !else_lit | !cond & else_lit + // cond ^ else_lit + new_tag = XOR_GATE; + assert (rhs[0] == cond); + rhs[1] = else_lit; + } else { + shrink = false; + rhs[1] = dst; + } + } else { + assert (src == else_lit); + if (dst == cond) { + // cond ? then_lit : cond + // cond & then_lit | !cond & cond + // cond & then_lit + assert (rhs[0] == cond); + assert (rhs[1] == then_lit); + } else if (not_dst == cond) { + // cond ? then_lit : !cond + // cond & then_lit | !cond & !cond + // cond & then_lit | !cond + // then_lit | !cond + // !(!then_lit & cond) + g->lhs = not_lhs; + assert (rhs[0] == cond); + rhs[1] = not_then_lit; + } else if (dst == then_lit) { + // cond ? then_lit : then_lit + // then_lit + if (merge_literals (closure, lhs, then_lit)) { + INC (congruent_unary_ites); + INC (congruent_unary); + } + garbage = true; + } else if (not_dst == then_lit) { + // cond ? then_lit : !then_lit + // cond & then_lit | !cond & !then_lit + // !(cond ^ then_lit) + new_tag = XOR_GATE; + g->lhs = not_lhs; + assert (rhs[0] == cond); + assert (rhs[1] == then_lit); + } else { + shrink = false; + rhs[2] = dst; + } + } + if (!garbage) { + if (shrink) { + if (rhs[0] > rhs[1]) + SWAP (unsigned, rhs[0], rhs[1]); + if (new_tag == XOR_GATE) { + bool negate_lhs = false; + if (NEGATED (rhs[0])) { + rhs[0] = NOT (rhs[0]); + negate_lhs = !negate_lhs; + } + if (NEGATED (rhs[1])) { + rhs[1] = NOT (rhs[1]); + negate_lhs = !negate_lhs; + } + if (negate_lhs) + g->lhs = NOT (g->lhs); + } + assert (!g->shrunken); + g->shrunken = true; + rhs[2] = INVALID_LIT; + g->arity = 2; + g->tag = new_tag; + assert (rhs[0] < rhs[1]); + assert (rhs[0] != NOT (rhs[1])); + LOGATE (g, "rewritten"); + gate *h; + unsigned hash; + if (new_tag == AND_GATE) { + check_and_gate_implied (closure, g); + h = find_and_gate (closure, &hash, g); + } else { + assert (new_tag == XOR_GATE); + check_xor_gate_implied (closure, g); + h = find_xor_gate (closure, &hash, g); + } + if (h) { + garbage = true; + if (new_tag == XOR_GATE) + add_xor_matching_proof_chain (closure, g, g->lhs, h->lhs); + else + add_ite_turned_and_binary_clauses (closure, g); + if (merge_literals (closure, g->lhs, h->lhs)) + INC (congruent_ands); + if (!solver->inconsistent) + delete_proof_chain (closure); + } else { + garbage = false; + remove_gate (closure, g); + g->hash = hash; + index_gate (closure, g); + assert (g->arity == 2); + for (all_rhs_literals_in_gate (lit, g)) + if (lit != dst) + if (lit != cond && lit != then_lit && lit != else_lit) + connect_occurrence (closure, lit, g); + if (g->tag == AND_GATE) + for (all_rhs_literals_in_gate (lit, g)) + add_binary_clause (closure, NOT (g->lhs), lit); + } + } else { + CLOGITEGATE (g, "rewritten"); + assert (rhs[0] != rhs[1]); + assert (rhs[0] != rhs[2]); + assert (rhs[1] != rhs[2]); + assert (rhs[0] != NOT (rhs[1])); + assert (rhs[0] != NOT (rhs[2])); + assert (rhs[1] != NOT (rhs[2])); + check_ite_gate_implied (closure, g); + unsigned hash; + bool negate_lhs; + gate *h = find_ite_gate (closure, &hash, &negate_lhs, g); + assert (lhs == g->lhs); + assert (not_lhs == NOT (g->lhs)); + if (h) { + garbage = true; + unsigned normalized_lhs = negate_lhs ? not_lhs : lhs; + add_ite_matching_proof_chain (closure, h, h->lhs, normalized_lhs); + if (merge_literals (closure, h->lhs, normalized_lhs)) + INC (congruent_ites); + if (!solver->inconsistent) + delete_proof_chain (closure); + } else { + garbage = false; + remove_gate (closure, g); + if (negate_lhs) + g->lhs = not_lhs; + CLOGITEGATE (g, "normalized"); + g->hash = hash; + index_gate (closure, g); + assert (g->arity == 3); + for (all_rhs_literals_in_gate (lit, g)) + if (lit != dst) + if (lit != cond && lit != then_lit && lit != else_lit) + connect_occurrence (closure, lit, g); + } + } + } + if (garbage && !solver->inconsistent) + mark_gate_as_garbage (closure, g); + INC (congruent_rewritten); + INC (congruent_rewritten_ites); +} + +static bool simplify_gate (closure *closure, gate *g) { + if (g->tag == AND_GATE) + simplify_and_gate (closure, g); + else if (g->tag == XOR_GATE) + simplify_xor_gate (closure, g); + else + simplify_ite_gate (closure, g); + return !closure->solver->inconsistent; +} + +static bool rewrite_gate (closure *closure, gate *g, unsigned dst, + unsigned src) { + if (g->tag == AND_GATE) + rewrite_and_gate (closure, g, dst, src); + else if (g->tag == XOR_GATE) + rewrite_xor_gate (closure, g, dst, src); + else + rewrite_ite_gate (closure, g, dst, src); + return !closure->solver->inconsistent; +} + +struct offsetsize { + unsigned offset, size; +}; + +typedef struct offsetsize offsetsize; + +#define RANK_OTHER(A) ((A).lits[1]) +#define LESS_OTHER(A, B) (RANK_OTHER (A) < RANK_OTHER (B)) + +static bool find_binary (kissat *solver, litpair *binaries, + offsetsize *offsetsize, unsigned lit, + unsigned other) { + assert (lit != other); + if (lit > other) + SWAP (unsigned, lit, other); + size_t l = offsetsize[lit].offset; + size_t r = l + offsetsize[lit].size; + while (l < r) { + const size_t m = (l + r) / 2; + const unsigned tmp = binaries[m].lits[1]; + if (tmp < other) + l = m + 1; + else if (tmp > other) + r = m; + else { + assert (binaries[m].lits[0] == lit); + assert (binaries[m].lits[1] == other); +#ifdef LOGGING + LOGBINARY (lit, other, "found"); +#else + (void) solver; +#endif + return true; + } + } + return false; +} + +static uint64_t rank_litpair (litpair p) { + uint64_t res = p.lits[0]; + res <<= 32; + res += p.lits[1]; + return res; +} + +static void extract_binaries (closure *closure) { + kissat *const solver = closure->solver; + if (!GET_OPTION (congruencebinaries)) + return; + START (extractbinaries); + litpair *binaries = BEGIN_STACK (closure->binaries); + offsetsize *offsetsize; + CALLOC (offsetsize, LITS); + { + litpair *end = END_STACK (closure->binaries); + litpair *p = binaries; + while (p != end) { + litpair *q = p + 1; + const unsigned lit = p->lits[0]; + while (q != end && q->lits[0] == lit) + q++; + const size_t size = q - p; + assert (size), assert (size <= UINT_MAX); + const size_t offset = p - binaries; + if (size < 32) + SORT (litpair, size, p, LESS_OTHER); + else + RADIX_SORT (litpair, unsigned, size, p, RANK_OTHER); + offsetsize[lit].offset = offset; + offsetsize[lit].size = size; + p = q; + } + } + clause *last_irredundant = kissat_last_irredundant_clause (solver); + const size_t before = SIZE_STACK (closure->binaries); + size_t extracted = 0, duplicated = 0; + const value *const values = solver->values; + for (all_clauses (d)) { + if (d->garbage) + continue; + if (last_irredundant && last_irredundant < d) + break; + if (d->redundant) + continue; + if (d->size != 3) + continue; + const unsigned *lits = d->lits; + const unsigned a = lits[0]; + if (values[a]) + continue; + const unsigned b = lits[1]; + if (values[b]) + continue; + const unsigned c = lits[2]; + if (values[c]) + continue; + const unsigned not_a = NOT (a); + const unsigned not_b = NOT (b); + const unsigned not_c = NOT (c); + unsigned l = INVALID_LIT, k = INVALID_LIT; + if (find_binary (solver, binaries, offsetsize, not_a, b) || + find_binary (solver, binaries, offsetsize, not_a, c)) + l = b, k = c; + else if (find_binary (solver, binaries, offsetsize, not_b, a) || + find_binary (solver, binaries, offsetsize, not_b, c)) + l = a, k = c; + else if (find_binary (solver, binaries, offsetsize, not_c, a) || + find_binary (solver, binaries, offsetsize, not_c, b)) + l = a, k = b; + else + continue; + LOGCLS (d, "strengthening"); + if (!find_binary (solver, binaries, offsetsize, l, k)) { + LOGBINARY (l, k, "strengthened"); + add_binary_clause (closure, l, k); + binaries = BEGIN_STACK (closure->binaries); + extracted++; + } + } + DEALLOC (offsetsize, LITS); + { + litpair *end = END_STACK (closure->binaries); + litpair *added = binaries + before; +#ifndef NDEBUG + const size_t after = end - binaries; + assert (after - before == extracted); +#endif + RADIX_SORT (litpair, uint64_t, extracted, added, rank_litpair); + litpair *q = added; + unsigned prev_lit = INVALID_LIT; + unsigned prev_other = INVALID_LIT; + for (const litpair *p = q; p != end; p++) { + const litpair pair = *p; + const unsigned lit = pair.lits[0]; + const unsigned other = pair.lits[1]; + if (p == added || lit != prev_lit || other != prev_other) { + q->lits[0] = lit; + q->lits[1] = other; + prev_lit = lit; + prev_other = other; + q++; + } else { + duplicated++; + LOGBINARY (lit, other, "removing duplicated"); + kissat_delete_binary (solver, lit, other); + } + } + SET_END_OF_STACK (closure->binaries, q); + } + ADD (congruent_binaries, extracted - duplicated); + kissat_verbose (solver, "extracted %zu binaries (plus %zu duplicated)", + extracted, duplicated); + STOP (extractbinaries); +} + +#ifndef INDEX_BINARY_CLAUSES + +static gate *find_first_and_gate (closure *closure, unsigned lhs, + unsigneds *lits) { + kissat *const solver = closure->solver; + assert (!solver->watching); + mark *const marks = solver->marks; + + const unsigned not_lhs = NOT (lhs); + LOG ("trying to find AND gate with first LHS %s", LOGLIT (lhs)); + LOG ("negated LHS %s occurs in %u binary clauses", LOGLIT (not_lhs), + closure->negbincount[lhs]); + + unsigneds *const marked = &solver->analyzed; + assert (EMPTY_STACK (*marked)); + + const unsigned arity = SIZE_STACK (*lits) - 1; + unsigned matched = 0; + assert (1 < arity); + + watches *watches = &WATCHES (not_lhs); + const watch *const end = END_WATCHES (*watches); + const watch *p = BEGIN_WATCHES (*watches); + + while (p != end) { + const watch watch = *p++; + assert (watch.type.binary); + const unsigned other = watch.binary.lit; + const mark tmp = marks[other]; + if (tmp) { + matched++; + assert (~(tmp & 2)); + marks[other] |= 2; + PUSH_STACK (*marked, other); + } + } + + LOG ("found %zu initial LHS candidates", SIZE_STACK (*marked)); + if (matched < arity) + return 0; + + return new_and_gate (closure, lhs); +} + +static gate *find_remaining_and_gate (closure *closure, unsigned lhs, + unsigneds *lits) { + kissat *const solver = closure->solver; + assert (!solver->watching); + mark *const marks = solver->marks; + const unsigned not_lhs = NOT (lhs); + + if (marks[not_lhs] < 2) { + LOG ("skipping no-candidate LHS %s", LOGLIT (lhs)); + return false; + } + + LOG ("trying to find AND gate with remaining LHS %s", LOGLIT (lhs)); + LOG ("negated LHS %s occurs times in %u binary clauses", LOGLIT (not_lhs), + closure->negbincount[lhs]); + + const unsigned arity = SIZE_STACK (*lits) - 1; + unsigned matched = 0; + assert (1 < arity); + + { + watches *watches = &WATCHES (not_lhs); + const watch *const end_watches = END_WATCHES (*watches); + const watch *p = BEGIN_WATCHES (*watches); + while (p != end_watches) { + const watch watch = *p++; + assert (watch.type.binary); + const unsigned other = watch.binary.lit; + mark mark = marks[other]; + if (!mark) + continue; + matched++; + if (!(mark & 2)) + continue; + assert (!(mark & 4)); + marks[other] = mark | 4; + } + } + + { + unsigneds *const marked = &solver->analyzed; + assert (!EMPTY_STACK (*marked)); + unsigned *const begin_marked = BEGIN_STACK (*marked); + const unsigned *const end_marked = END_STACK (*marked); + unsigned *q = begin_marked; + const unsigned *p = q; + assert (marks[not_lhs] == 3); + while (p != end_marked) { + const unsigned lit = *p++; + if (lit == not_lhs) { + marks[not_lhs] = 1; + continue; + } + mark mark = marks[lit]; + assert ((mark & 3) == 3); + if (mark & 4) { + mark = 3; + *q++ = lit; + LOG2 ("keeping LHS candidate %s", LOGLIT (NOT (lit))); + } else { + LOG2 ("dropping LHS candidate %s", LOGLIT (NOT (lit))); + mark = 1; + } + marks[lit] = mark; + } + assert (q != end_marked); + assert (marks[not_lhs] == 1); + SET_END_OF_STACK (*marked, q); + LOG ("after filtering %zu LHS candidates remain", SIZE_STACK (*marked)); + } + + if (matched < arity) + return 0; + + return new_and_gate (closure, lhs); +} + +#endif + +static inline bool smaller_negated_bin_count (const unsigned *negbincount, + unsigned a, unsigned b) { + unsigned c = negbincount[a]; + unsigned d = negbincount[b]; + if (c < d) + return true; + if (c > d) + return false; + return a < b; +} + +#define SMALLER_NEGATED_BIN_COUNT(A, B) \ + smaller_negated_bin_count (negbincount, A, B) + +static void sort_lits_by_negbincount (closure *closure, size_t size, + unsigned *lits) { + const unsigned *const negbincount = closure->negbincount; + kissat *const solver = closure->solver; + SORT (unsigned, size, lits, SMALLER_NEGATED_BIN_COUNT); +} + +#ifdef INDEX_BINARY_CLAUSES + +static unsigned hash_binary (closure *closure, binary_clause *binary) { + return hash_lits (closure, 0, 2, binary->lits); +} + +static bool indexed_binary (closure *closure, unsigned lit, + unsigned other) { + assert (lit != other); +#ifdef LOGGING + kissat *const solver = closure->solver; +#endif + binary_hash_table *bintab = &closure->bintab; + if (!bintab->count) { + LOG ("did not find binary %s %s", LOGLIT (lit), LOGLIT (other)); + return false; + } + assert (bintab->size); + SWAP (unsigned, lit, other); + if (lit > other) + SWAP (unsigned, lit, other); + binary_clause binary = {.lits = {lit, other}}; + const unsigned hash = hash_binary (closure, &binary); + const size_t size = bintab->size; + const size_t size2 = bintab->size2; + size_t pos = reduce_hash (hash, size, size2); + binary_clause *table = bintab->table; + unsigned lit0, lit1; + while ((lit1 = table[pos].lits[1])) { + if (lit1 == other) { + lit0 = table[pos].lits[0]; + assert (lit0 < other); + if (lit0 == lit) { + LOG ("found binary %s %s", LOGLIT (lit), LOGLIT (other)); + return true; + } + } + if (++pos == size) + pos = 0; + } + LOG ("did not find binary %s %s", LOGLIT (lit), LOGLIT (other)); + return false; +} + +#endif + +static void extract_and_gates_with_base_clause (closure *closure, + clause *c) { + assert (!c->garbage); + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + value *values = solver->values; + unsigned arity_limit = MIN (GET_OPTION (congruenceandarity), MAX_ARITY); + const unsigned size_limit = arity_limit + 1; + const unsigned *const negbincount = closure->negbincount; + unsigneds *lits = &closure->lits; + unsigned size = 0, max_negbincount = 0; + CLEAR_STACK (*lits); + for (all_literals_in_clause (lit, c)) { + value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + assert (!solver->level); + LOGCLS (c, "found satisfied %s in", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + return; + } + if (++size > size_limit) { + LOGCLS (c, "too large actual size %u thus skipping", size); + return; + } + const unsigned count = negbincount[lit]; + if (!count) { + LOGCLS (c, + "%s negated does not occur in any binary clause " + "thus skipping", + LOGLIT (lit)); + return; + } + if (count > max_negbincount) + max_negbincount = count; + PUSH_STACK (*lits, lit); + } + if (size < 3) { + LOGCLS (c, "actual size %u too small thus skipping", size); + return; + } + const unsigned arity = size - 1; + if (max_negbincount < arity) { + LOGCLS (c, + "all literals have less than %u negated occurrences " + "thus skipping", + arity); + return; + } + unsigned *begin_lits = BEGIN_STACK (*lits), *reduced_lits = begin_lits; + LOGCOUNTEDLITS (size, begin_lits, negbincount, + "counted candidate arity %u AND gate base clause", arity); + const unsigned *const end_lits = END_STACK (*lits); +#ifndef INDEX_BINARY_CLAUSES + mark *const marks = solver->marks; + unsigneds *marked = &solver->analyzed; + assert (EMPTY_STACK (*marked)); +#endif + for (unsigned *p = begin_lits; p != end_lits; p++) { + const unsigned lit = *p, count = negbincount[lit]; +#ifndef INDEX_BINARY_CLAUSES + const unsigned not_lit = NOT (lit); + marks[not_lit] = 1; +#endif + if (count < arity) { + if (reduced_lits < p) + *p = *reduced_lits, *reduced_lits++ = lit; + else if (reduced_lits == p) + reduced_lits++; + } + } + assert (reduced_lits < end_lits); + const size_t reduced_size = end_lits - reduced_lits; + assert (reduced_size); + LOGCLS (c, "trying as base arity %u AND gate", arity); + sort_lits_by_negbincount (closure, reduced_size, reduced_lits); +#ifdef LOGGING + if (begin_lits < reduced_lits) { + LOGCOUNTEDLITS (reduced_lits - begin_lits, begin_lits, negbincount, + "skipping low occurrence"); + LOGCOUNTEDLITS (reduced_size, reduced_lits, negbincount, + "remaining LHS candidate"); + } else + LOGCOUNTEDLITS (reduced_size, reduced_lits, negbincount, + "all remain LHS candidate"); +#endif +#ifdef LOGGING + unsigned extracted = 0; +#endif +#ifndef INDEX_BINARY_CLAUSES + bool first = true; +#endif + for (unsigned *p = reduced_lits; p != end_lits; p++) { + if (solver->inconsistent) + break; + if (c->garbage) + break; + const unsigned lhs = *p; + LOG ("trying LHS candidate literal %s with %u negated occurrences", + LOGLIT (lhs), negbincount[lhs]); + assert (arity <= negbincount[lhs]); +#ifdef INDEX_BINARY_CLAUSES + const unsigned not_lhs = NOT (lhs); + for (const unsigned *q = begin_lits; q != end_lits; q++) + if (p != q) { + const unsigned rhs = *q, not_rhs = NOT (rhs); + if (!indexed_binary (closure, not_lhs, not_rhs)) + goto CONTINUE_WITH_NEXT_LHS; + } + (void) new_and_gate (closure, lhs); +#ifdef LOGGING + extracted++; +#endif + CONTINUE_WITH_NEXT_LHS:; +#else + if (first) { + first = false; + assert (EMPTY_STACK (*marked)); + if (find_first_and_gate (closure, lhs, lits)) { +#ifdef LOGGING + extracted++; +#endif + } + } else if (EMPTY_STACK (*marked)) { + LOG ("early abort AND gate search"); + break; + } else if (find_remaining_and_gate (closure, lhs, lits)) { +#ifdef LOGGING + extracted++; +#endif + } +#endif + } +#ifndef INDEX_BINARY_CLAUSES + for (const unsigned *p = begin_lits; p != end_lits; p++) { + const unsigned lit = *p, not_lit = NOT (lit); + marks[not_lit] = 0; + } + CLEAR_STACK (*marked); +#endif +#ifdef LOGGING + if (extracted) + LOGCLS (c, "extracted %u with arity %u AND base", extracted, arity); +#endif +} + +#ifdef INDEX_LARGE_CLAUSES + +static bool valid_large_clause (hash_ref *clause) { + return clause->hash || clause->ref; +} + +static clause *find_indexed_large_clause (closure *closure, + unsigneds *lits) { + kissat *const solver = closure->solver; + size_t size_lits = SIZE_STACK (*lits); + assert (size_lits > 2); +#ifdef LOGGING + { + unsigned *begin = BEGIN_STACK (*lits); + unsigned arity = size_lits - 1; + LOGCOUNTEDLITS (size_lits, begin, closure->largecount, + "trying to find arity %u XOR side clause", arity); + } +#endif + large_clause_hash_table *clauses = &closure->clauses; + if (!clauses->count) + return 0; + const value *const values = solver->values; + mark *const marks = solver->marks; + unsigneds *sorted = &solver->clause; + assert (EMPTY_STACK (*sorted)); + for (all_stack (unsigned, lit, *lits)) { + assert (!values[lit]); + PUSH_STACK (*sorted, lit); + marks[lit] = 1; + } + assert (size_lits == SIZE_STACK (*sorted)); + unsigned *begin_sorted = BEGIN_STACK (*sorted); + sort_lits (solver, size_lits, begin_sorted); + const unsigned hash = hash_lits (closure, 0, size_lits, begin_sorted); + const size_t hash_size = clauses->size; + const size_t hash_size2 = clauses->size2; + size_t pos = reduce_hash (hash, hash_size, hash_size2); + hash_ref *table = clauses->table, *hash_ref; + clause *res = 0; + while (valid_large_clause (hash_ref = table + pos)) { + if (hash_ref->hash == hash) { + reference ref = hash_ref->ref; + if (ref == INVALID_REF) { + assert (!hash); + ref = 0; + } + clause *c = kissat_dereference_clause (solver, ref); + for (all_literals_in_clause (other, c)) + if (!values[other] && !marks[other]) + goto CONTINUE_WITH_NEXT_HASH_BUCKET; + res = c; + break; + } + CONTINUE_WITH_NEXT_HASH_BUCKET: + if (++pos == hash_size) + pos = 0; + } + while (!EMPTY_STACK (*sorted)) { + const unsigned lit = POP_STACK (*sorted); + marks[lit] = 0; + } + if (res) + LOGCLS (res, "found indexed matching XOR side clause"); + else + LOG ("no matching XOR side clause found"); + return res; +} + +#else + +static clause *find_large_xor_side_clause (closure *closure, + unsigneds *lits) { + kissat *const solver = closure->solver; + assert (!solver->watching); + const unsigned *const largecount = closure->largecount; + unsigned least_occurring_literal = INVALID_LIT; + unsigned count_least_occurring = UINT_MAX; + mark *marks = solver->marks; + const size_t size_lits = SIZE_STACK (*lits); +#if defined(LOGGING) || !defined(NDEBUG) + const unsigned arity = size_lits - 1; +#endif +#ifndef NDEBUG + const unsigned count_limit = 1u << (arity - 1); +#endif + const value *const values = solver->values; + LOGCOUNTEDLITS (size_lits, BEGIN_STACK (*lits), largecount, + "trying to find arity %u XOR side clause", arity); + for (all_stack (unsigned, lit, *lits)) { + assert (!values[lit]); + marks[lit] = 1; + unsigned count = largecount[lit]; + assert (count_limit <= count); + if (count >= count_least_occurring) + continue; + count_least_occurring = count; + least_occurring_literal = lit; + } + clause *res = 0; + assert (least_occurring_literal != INVALID_LIT); + LOG ("searching XOR side clause watched by %s#%u", + LOGLIT (least_occurring_literal), count_least_occurring); + watches *const watches = &WATCHES (least_occurring_literal); + watch *p = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + while (p != end) { + const watch watch = *p++; + if (watch.type.binary) + break; + const reference ref = watch.large.ref; + clause *const c = kissat_dereference_clause (solver, ref); + if (c->garbage) + continue; + if (c->size < size_lits) + continue; + size_t found = 0; + for (all_literals_in_clause (other, c)) { + const value value = values[other]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "found satisfied %s in", LOGLIT (other)); + kissat_mark_clause_as_garbage (solver, c); + assert (c->garbage); + break; + } + if (marks[other]) + found++; + else { + found = UINT_MAX; + break; + } + } + if (found < UINT_MAX && !c->garbage) { + res = c; + break; + } + } + for (all_stack (unsigned, lit, *lits)) + marks[lit] = 0; + if (res) + LOGCLS (res, "found matching XOR side"); + else + LOG ("no matching XOR side clause found"); + return res; +} + +#endif + +static void extract_xor_gates_with_base_clause (closure *closure, + clause *c) { + assert (!c->garbage); + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + const value *const values = solver->values; + unsigned smallest = INVALID_LIT, largest = INVALID_LIT; + const unsigned arity_limit = + MIN (GET_OPTION (congruencexorarity), MAX_ARITY); + const unsigned size_limit = arity_limit + 1; + unsigned negated = 0, size = 0; + unsigneds *lits = &closure->lits; + CLEAR_STACK (*lits); + bool first = true; + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "found satisfied %s in", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + return; + } + if (size == size_limit) { + LOGCLS (c, "size limit %u for XOR base clause exceeded in", + size_limit); + return; + } + if (first) { + largest = smallest = lit; + first = false; + } else { + assert (smallest != INVALID_LIT); + assert (largest != INVALID_LIT); + if (lit < smallest) + smallest = lit; + if (lit > largest) { + if (NEGATED (largest)) { + LOGCLS (c, "not largest literal %s occurs negated in XOR base", + LOGLIT (largest)); + return; + } + largest = lit; + } + } + if (NEGATED (lit) && lit < largest) { + LOGCLS (c, "negated literal %s not largest in XOR base", + LOGLIT (lit)); + return; + } + if (NEGATED (lit) && negated++) { + LOGCLS (c, "more than one negated literal in XOR base"); + return; + } + PUSH_STACK (*lits, lit); + size++; + } + assert (size == SIZE_STACK (*lits)); + if (size < 3) { + LOGCLS (c, "short XOR base clause"); + return; + } + const unsigned arity = size - 1; + const unsigned needed_clauses = 1u << (arity - 1); + const unsigned *const largecount = closure->largecount; + for (all_stack (unsigned, lit, *lits)) + for (unsigned sign = 0; sign != 2; sign++, lit = NOT (lit)) { + unsigned count = largecount[lit]; + if (count >= needed_clauses) + continue; + LOGCLS (c, + "literal %s in XOR base clause only occurs " + "%u times in large clauses thus skipping", + LOGLIT (lit), count); + return; + } + LOGCLS (c, "trying arity %u XOR base", arity); + assert (smallest != INVALID_LIT); + assert (largest != INVALID_LIT); + const unsigned end = 1u << arity; + assert (negated == parity_lits (solver, lits)); +#if !defined(NDEBUG) || defined(LOGGING) + unsigned found = 0; +#endif + for (unsigned i = 0; i != end; i++) { + while (i && parity_lits (solver, lits) != negated) + inc_lits (solver, lits); + if (i) { +#ifdef INDEX_LARGE_CLAUSES + clause *d = find_indexed_large_clause (closure, lits); +#else + clause *d = find_large_xor_side_clause (closure, lits); +#endif + if (!d) + return; + assert (!d->redundant); + } else + assert (!c->redundant); + inc_lits (solver, lits); +#if !defined(NDEBUG) || defined(LOGGING) + found++; +#endif + } + while (parity_lits (solver, lits) != negated) + inc_lits (solver, lits); + LOGUNSIGNEDS2 (size, BEGIN_STACK (*lits), "back to original"); + LOG ("found all needed %u matching clauses:", found); + assert (found == 1u << arity); + if (negated) { + unsigned *p = BEGIN_STACK (*lits), lit; + while (!NEGATED (lit = *p)) + p++; + LOG ("flipping RHS literal %s", LOGLIT (lit)); + *p = NOT (lit); + } + unsigned extracted = 0; + for (all_stack (unsigned, lhs, *lits)) { + if (!negated) + lhs = NOT (lhs); + gate *g = new_xor_gate (closure, lhs); + if (g) + extracted++; + if (solver->inconsistent) + break; + } + if (!extracted) + LOG ("no arity %u XOR gate extracted", arity); +} + +#ifdef INDEX_BINARY_CLAUSES + +static void init_bintab (closure *closure) { + kissat *const solver = closure->solver; + size_t limit = BINARY_CLAUSES; + size_t size = 2 * limit; + size_t size2 = 1; + while (size > size2) + size2 *= 2; + assert (!limit || size2 <= 2 * size); + binary_hash_table *bintab = &closure->bintab; + CALLOC (bintab->table, size); + bintab->count = 0; + bintab->size = size; + bintab->size2 = size2; + kissat_very_verbose ( + solver, "allocated binary clause hash table of size %zu", size); +} + +#ifndef NDEBUG + +static bool binaries_hash_table_is_full (binary_hash_table *bintab) { + if (bintab->size == MAX_HASH_TABLE_SIZE) + return false; + if (2 * bintab->count < bintab->size) + return false; + return true; +} + +#endif + +static void index_binary (closure *closure, unsigned lit, unsigned other) { + assert (lit < other); + binary_hash_table *bintab = &closure->bintab; + assert (!binaries_hash_table_is_full (bintab)); + binary_clause binary = {.lits = {lit, other}}; + const unsigned hash = hash_binary (closure, &binary); + const size_t size = bintab->size; + const size_t size2 = bintab->size2; + size_t pos = reduce_hash (hash, size, size2); + binary_clause *table = bintab->table; + while (table[pos].lits[1]) + if (++pos == size) + pos = 0; + table[pos] = binary; + bintab->count++; +#ifdef LOGGING + kissat *const solver = closure->solver; + LOG ("indexed binary %s %s", LOGLIT (lit), LOGLIT (other)); +#endif +} + +static void reset_bintab (closure *closure) { + kissat *const solver = closure->solver; + binary_hash_table *bintab = &closure->bintab; + DEALLOC (bintab->table, bintab->size); +} + +#endif + +static void init_and_gate_extraction (closure *closure) { + kissat *const solver = closure->solver; + assert (!solver->watching); + unsigned *negbincount; + CALLOC (negbincount, LITS); + litpairs *binaries = &closure->binaries; +#ifdef INDEX_BINARY_CLAUSES + init_bintab (closure); +#endif + for (all_stack (litpair, pair, *binaries)) { + const unsigned lit = pair.lits[0], other = pair.lits[1]; + const unsigned not_lit = NOT (lit), not_other = NOT (other); + negbincount[not_lit]++, negbincount[not_other]++; + kissat_watch_binary (solver, lit, other); +#ifdef INDEX_BINARY_CLAUSES + index_binary (closure, lit, other); +#endif + } +#ifndef QUIET + size_t connected = SIZE_STACK (*binaries); + kissat_very_verbose (solver, "connected %zu binary clauses", connected); +#endif + closure->negbincount = negbincount; +} + +static void reset_and_gate_extraction (closure *closure) { + kissat *const solver = closure->solver; + DEALLOC (closure->negbincount, LITS); + kissat_flush_all_connected (solver); +#ifdef INDEX_BINARY_CLAUSES + reset_bintab (closure); +#endif +} + +#ifdef INDEX_LARGE_CLAUSES + +static void init_large_clauses (closure *closure, size_t expected) { + kissat *const solver = closure->solver; + size_t size = 2 * expected; + size_t size2 = 1; + while (size > size2) + size2 *= 2; + assert (!expected || size2 <= 2 * size); + large_clause_hash_table *clauses = &closure->clauses; + CALLOC (clauses->table, size); + clauses->count = 0; + clauses->size = size; + clauses->size2 = size2; + kissat_very_verbose ( + solver, "allocated large clause hash table of size %zu", size); +} + +#ifndef NDEBUG + +static bool large_clause_hash_table_is_full (closure *closure) { + if (closure->clauses.size == MAX_HASH_TABLE_SIZE) + return false; + if (2 * closure->clauses.count < closure->clauses.size) + return false; + return true; +} + +#endif + +static void index_large_clause (closure *closure, reference ref) { + assert (!large_clause_hash_table_is_full (closure)); + kissat *const solver = closure->solver; + clause *c = kissat_dereference_clause (solver, ref); + const value *const values = solver->values; + unsigneds *lits = &closure->lits; + CLEAR_STACK (*lits); + for (all_literals_in_clause (lit, c)) + if (!values[lit]) + PUSH_STACK (*lits, lit); + const size_t size_lits = SIZE_STACK (*lits); + unsigned *begin_lits = BEGIN_STACK (*lits); + assert (3 <= size_lits); + sort_lits (solver, size_lits, begin_lits); + const unsigned hash = hash_lits (closure, 0, size_lits, begin_lits); + large_clause_hash_table *clauses = &closure->clauses; + if (!hash && !ref) { + ref = INVALID_REF; + assert (ref); + } + const size_t hash_size = clauses->size; + const size_t hash_size2 = clauses->size2; + size_t pos = reduce_hash (hash, hash_size, hash_size2); + hash_ref *table = clauses->table, *clause; + while (valid_large_clause (clause = table + pos)) + if (++pos == hash_size) + pos = 0; + clause->hash = hash; + clause->ref = ref; + assert (valid_large_clause (clause)); + clauses->count++; + LOGCLS (c, "indexed"); +} + +static void reset_large_clauses (closure *closure) { + large_clause_hash_table *clauses = &closure->clauses; + kissat *const solver = closure->solver; + DEALLOC (clauses->table, clauses->size); +} + +#endif + +static void init_xor_gate_extraction (closure *closure, + references *candidates) { + assert (EMPTY_STACK (*candidates)); + kissat *const solver = closure->solver; + assert (!solver->watching); + const unsigned arity_limit = GET_OPTION (congruencexorarity); + const unsigned size_limit = arity_limit + 1; + clause *last_irredundant = kissat_last_irredundant_clause (solver); + const value *const values = solver->values; + unsigned *largecount; + CALLOC (largecount, LITS); + for (all_clauses (c)) { + if (c->garbage) + continue; + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + unsigned size = 0; + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "satisfied %s in", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + goto CONTINUE_COUNTING_NEXT_CLAUSE; + } + if (size == size_limit) + goto CONTINUE_COUNTING_NEXT_CLAUSE; + size++; + } + if (size < 3) + continue; + for (all_literals_in_clause (lit, c)) + if (!values[lit]) + largecount[lit]++; + reference ref = kissat_reference_clause (solver, c); + PUSH_STACK (*candidates, ref); + CONTINUE_COUNTING_NEXT_CLAUSE:; + } +#ifndef QUIET + size_t considered_clauses = IRREDUNDANT_CLAUSES; + size_t original_candidates = SIZE_STACK (*candidates); + kissat_very_verbose ( + solver, + "%zu original candidate XOR base clauses " + "(%.0f%% of %zu irredundant clauses)", + original_candidates, + kissat_percent (original_candidates, considered_clauses), + considered_clauses); +#endif + const unsigned counting_rounds = GET_OPTION (congruencexorcounts); + for (unsigned round = 1; round <= counting_rounds; round++) { + size_t removed = 0; + unsigned *new_largecount; + CALLOC (new_largecount, LITS); + const reference *const end_candidates = END_STACK (*candidates); + reference *q = BEGIN_STACK (*candidates), *p = q; + while (p != end_candidates) { + const reference ref = *p++; + clause *c = kissat_dereference_clause (solver, ref); + unsigned size = 0; + for (all_literals_in_clause (lit, c)) + if (!values[lit]) + size++; + assert (3 <= size); + assert (size <= size_limit); + const unsigned arity = size - 1; + const unsigned needed_clauses = 1u << (arity - 1); + for (all_literals_in_clause (lit, c)) + if (largecount[lit] < needed_clauses) { + removed++; + goto CONTINUE_WITH_NEXT_CANDIDATE_CLAUSE; + } + for (all_literals_in_clause (lit, c)) + if (!values[lit]) + new_largecount[lit]++; + *q++ = ref; + CONTINUE_WITH_NEXT_CANDIDATE_CLAUSE:; + } + DEALLOC (largecount, LITS); + largecount = new_largecount; + SET_END_OF_STACK (*candidates, q); + if (!removed) + break; +#ifndef QUIET + size_t remaining_candidates = SIZE_STACK (*candidates); + const char *how_often; + char buffer[64]; + if (round == 1) + how_often = "once"; + else if (round == 2) + how_often = "twice"; + else { + sprintf (buffer, "%u times", round); + how_often = buffer; + } + kissat_very_verbose ( + solver, + "%zu XOR base clause candidates remain (%.0f%% " + "original candidates)" + " after counting %s", + remaining_candidates, + kissat_percent (remaining_candidates, original_candidates), + how_often); +#endif + } + closure->largecount = largecount; +#ifdef INDEX_LARGE_CLAUSES + init_large_clauses (closure, SIZE_STACK (*candidates)); +#endif + for (all_stack (reference, ref, *candidates)) { + kissat_connect_referenced (solver, ref); +#ifdef INDEX_LARGE_CLAUSES + index_large_clause (closure, ref); +#endif + } +#ifndef QUIET + size_t connected = SIZE_STACK (*candidates); + kissat_very_verbose (solver, "connected %zu large clauses %.0f%%", + connected, + kissat_percent (connected, IRREDUNDANT_CLAUSES)); +#endif +} + +static void reset_xor_gate_extraction (closure *closure) { + kissat *const solver = closure->solver; + DEALLOC (closure->largecount, LITS); + kissat_flush_all_connected (solver); +#ifdef INDEX_LARGE_CLAUSES + reset_large_clauses (closure); +#endif +} + +static void init_ite_gate_extraction (closure *closure, + references *candidates) { + assert (EMPTY_STACK (*candidates)); + kissat *const solver = closure->solver; + clause *last_irredundant = kissat_last_irredundant_clause (solver); + const value *const values = solver->values; + unsigned *largecount; + CALLOC (largecount, LITS); + references ternary; + INIT_STACK (ternary); + for (all_clauses (c)) { + if (c->garbage) + continue; + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + unsigned size = 0; + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "satisfied %s in", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + goto CONTINUE_COUNTING_NEXT_CLAUSE; + } + if (size == 3) + goto CONTINUE_COUNTING_NEXT_CLAUSE; + size++; + } + if (size < 3) + continue; + assert (size == 3); + const reference ref = kissat_reference_clause (solver, c); + PUSH_STACK (ternary, ref); + LOGCLS (c, "counting original ITE gate base"); + for (all_literals_in_clause (lit, c)) + if (!values[lit]) + largecount[lit]++; + CONTINUE_COUNTING_NEXT_CLAUSE:; + } +#ifndef QUIET + size_t counted = SIZE_STACK (ternary); + kissat_very_verbose (solver, + "counted %zu ternary ITE clauses " + "(%.0f%% of %" PRIu64 " irredundant clauses)", + counted, + kissat_percent (counted, IRREDUNDANT_CLAUSES), + IRREDUNDANT_CLAUSES); + size_t connected = 0; +#endif + for (all_stack (reference, ref, ternary)) { + clause *c = kissat_dereference_clause (solver, ref); + assert (!c->garbage); + unsigned positive = 0, negative = 0, twice = 0; + for (all_literals_in_clause (lit, c)) { + if (values[lit]) + continue; + const unsigned not_lit = NOT (lit); + const unsigned count_not_lit = largecount[not_lit]; + if (!count_not_lit) + goto CONTINUE_WITH_NEXT_TERNARY_CLAUSE; + const unsigned count_lit = largecount[lit]; + assert (count_lit); + if (count_lit > 1 && count_not_lit > 1) + twice++; + if (NEGATED (lit)) + negative++; + else + positive++; + } + if (twice < 2) + goto CONTINUE_WITH_NEXT_TERNARY_CLAUSE; +#ifndef QUIET + connected++; +#endif + kissat_connect_clause (solver, c); + if (positive && negative) + PUSH_STACK (*candidates, ref); + CONTINUE_WITH_NEXT_TERNARY_CLAUSE:; + } + RELEASE_STACK (ternary); +#ifndef QUIET + kissat_very_verbose (solver, + "connected %zu ITE clauses " + "(%.0f%% of %" PRIu64 " counted clauses)", + connected, kissat_percent (connected, counted), + IRREDUNDANT_CLAUSES); + size_t size_candidates = SIZE_STACK (*candidates); + kissat_very_verbose (solver, + "%zu candidates ITE base clauses " + "(%.0f%% of %zu connected)", + size_candidates, + kissat_percent (size_candidates, connected), + connected); +#endif + closure->largecount = largecount; + INIT_STACK (closure->condbin[0]); + INIT_STACK (closure->condbin[1]); + INIT_STACK (closure->condeq[0]); + INIT_STACK (closure->condeq[1]); +} + +static void reset_ite_gate_extraction (closure *closure) { + kissat *const solver = closure->solver; + RELEASE_STACK (closure->condbin[0]); + RELEASE_STACK (closure->condbin[1]); + RELEASE_STACK (closure->condeq[0]); + RELEASE_STACK (closure->condeq[1]); + DEALLOC (closure->largecount, LITS); + kissat_flush_all_connected (solver); +} + +static void unmark_all (unsigneds *marked, signed char *marks) { + for (all_stack (unsigned, lit, *marked)) + marks[lit] = 0; + CLEAR_STACK (*marked); +} + +#ifdef MERGE_CONDITIONAL_EQUIVALENCES + +static void copy_conditional_equivalences (kissat *solver, unsigned lit, + watches *watches, + litpairs *condbin) { + assert (EMPTY_STACK (*condbin)); + const value *const values = solver->values; + const watch *const begin_watches = BEGIN_WATCHES (*watches); + const watch *const end_watches = END_WATCHES (*watches); + for (const watch *p = begin_watches; p != end_watches; p++) { + const watch watch = *p; + if (watch.type.binary) + break; + const unsigned ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + unsigned first = INVALID_LIT, second = INVALID_LIT; + for (all_literals_in_clause (other, c)) { + if (values[other]) + continue; + if (other == lit) + continue; + if (first == INVALID_LIT) + first = other; + else { + assert (second == INVALID_LIT); + second = other; + } + } + assert (first != INVALID_LIT); + assert (second != INVALID_LIT); + litpair pair; + if (first < second) + pair = (litpair){.lits = {first, second}}; + else { + assert (second < first); + pair = (litpair){.lits = {second, first}}; + } + LOG ("literal %s conditional binary clause %s %s", LOGLIT (lit), + LOGLIT (first), LOGLIT (second)); + PUSH_STACK (*condbin, pair); + } +} + +static bool less_litpair (litpair p, litpair q) { + const unsigned a = p.lits[0], b = q.lits[0]; + if (a < b) + return true; + if (a > b) + return false; + const unsigned c = p.lits[1], d = q.lits[1]; + return c < d; +} + +#define RADIDX_SORT_PAIR_LIMIT 32 + +static void sort_pairs (kissat *solver, litpairs *pairs) { + const size_t size = SIZE_STACK (*pairs); + if (size < 32) + SORT_STACK (litpair, *pairs, less_litpair); + else + for (int i = 1; i >= 0; i--) + RADIX_STACK (litpair, uint64_t, *pairs, rank_litpair); +} + +static bool find_litpair_second_literal (unsigned lit, const litpair *begin, + const litpair *end) { + const litpair *l = begin, *r = end; + while (l != r) { + const litpair *m = l + (r - l) / 2; + assert (begin <= m), assert (m < end); + unsigned other = m->lits[1]; + if (other < lit) + l = m + 1; + else if (other > lit) + r = m; + else + return true; + } + return false; +} + +static void search_condeq (closure *closure, unsigned lit, unsigned pos_lit, + const litpair *pos_begin, const litpair *pos_end, + unsigned neg_lit, const litpair *neg_begin, + const litpair *neg_end, litpairs *condeq) { + kissat *const solver = closure->solver; + assert (neg_lit == NOT (pos_lit)); + assert (pos_begin < pos_end); + assert (neg_begin < neg_end); + assert (pos_begin->lits[0] == pos_lit); + assert (neg_begin->lits[0] == neg_lit); + assert (pos_end <= neg_begin || neg_end <= pos_begin); + for (const litpair *p = pos_begin; p != pos_end; p++) { + const unsigned other = p->lits[1]; + const unsigned not_other = NOT (other); + if (find_litpair_second_literal (not_other, neg_begin, neg_end)) { + unsigned first, second; + if (NEGATED (pos_lit)) + first = neg_lit, second = other; + else + first = pos_lit, second = not_other; + LOG ("found conditional %s equivalence %s = %s", LOGLIT (lit), + LOGLIT (first), LOGLIT (second)); + assert (!NEGATED (first)); + assert (first < second); + check_ternary (closure, lit, first, NOT (second)); + check_ternary (closure, lit, NOT (first), second); + litpair equivalence = {.lits = {first, second}}; + PUSH_STACK (*condeq, equivalence); + if (NEGATED (second)) { + litpair inverse_equivalence = {.lits = {NOT (second), NOT (first)}}; + PUSH_STACK (*condeq, inverse_equivalence); + } else { + litpair inverse_equivalence = {.lits = {second, first}}; + PUSH_STACK (*condeq, inverse_equivalence); + } + } + } +#ifndef LOGGING + (void) lit; +#endif +} + +static void extract_condeq_pairs (closure *closure, unsigned lit, + litpairs *condbin, litpairs *condeq) { +#if defined(LOGGING) || !defined(NDEBUG) + kissat *const solver = closure->solver; +#endif + const litpair *const begin = BEGIN_STACK (*condbin); + const litpair *const end = END_STACK (*condbin); + const litpair *pos_begin = begin; + unsigned next_lit; + for (;;) { + if (pos_begin == end) + return; + next_lit = pos_begin->lits[0]; + if (!NEGATED (next_lit)) + break; + pos_begin++; + } + for (;;) { + assert (pos_begin != end); + assert (next_lit == pos_begin->lits[0]); + assert (!NEGATED (next_lit)); + const unsigned pos_lit = next_lit; + const litpair *pos_end = pos_begin + 1; + for (;;) { + if (pos_end == end) + return; + next_lit = pos_end->lits[0]; + if (next_lit != pos_lit) + break; + pos_end++; + } + assert (pos_end != end); + assert (next_lit == pos_end->lits[0]); + const unsigned neg_lit = NOT (pos_lit); + if (next_lit != neg_lit) { + if (NEGATED (next_lit)) { + pos_begin = pos_end + 1; + for (;;) { + if (pos_begin == end) + return; + next_lit = pos_begin->lits[0]; + if (!NEGATED (next_lit)) + break; + pos_begin++; + } + } else + pos_begin = pos_end; + continue; + } + const litpair *const neg_begin = pos_end; + const litpair *neg_end = neg_begin + 1; + while (neg_end != end) { + next_lit = neg_end->lits[0]; + if (next_lit != neg_lit) + break; + neg_end++; + } +#ifdef LOGGING + if (kissat_logging (solver)) { + for (const litpair *p = pos_begin; p != pos_end; p++) + LOG ("conditional %s binary clause %s %s with positive %s", + LOGLIT (lit), LOGLIT (p->lits[0]), LOGLIT (p->lits[1]), + LOGLIT (pos_lit)); + for (const litpair *p = neg_begin; p != neg_end; p++) + LOG ("conditional %s binary clause %s %s with negative %s", + LOGLIT (lit), LOGLIT (p->lits[0]), LOGLIT (p->lits[1]), + LOGLIT (neg_lit)); + } +#endif + const size_t pos_size = pos_end - pos_begin; + const size_t neg_size = neg_end - neg_begin; + if (pos_size <= neg_size) { + LOG ("searching negation of %zu conditional binary clauses " + "with positive %s in %zu conditional binary clauses with %s", + pos_size, LOGLIT (pos_lit), neg_size, LOGLIT (neg_lit)); + search_condeq (closure, lit, pos_lit, pos_begin, pos_end, neg_lit, + neg_begin, neg_end, condeq); + } else { + LOG ("searching negation of %zu conditional binary clauses " + "with negative %s in %zu conditional binary clauses with %s", + neg_size, LOGLIT (neg_lit), pos_size, LOGLIT (pos_lit)); + search_condeq (closure, lit, neg_lit, neg_begin, neg_end, pos_lit, + pos_begin, pos_end, condeq); + } + if (neg_end == end) + return; + assert (next_lit == neg_end->lits[0]); + if (NEGATED (next_lit)) { + pos_begin = neg_end + 1; + for (;;) { + if (pos_begin == end) + return; + next_lit = pos_begin->lits[0]; + if (!NEGATED (next_lit)) + break; + pos_begin++; + } + } else + pos_begin = neg_end; + } +} + +static void find_conditional_equivalences (closure *closure, unsigned lit, + watches *watches, + litpairs *condbin, + litpairs *condeq) { + assert (EMPTY_STACK (*condbin)); + assert (EMPTY_STACK (*condeq)); + assert (SIZE_WATCHES (*watches) > 1); + kissat *const solver = closure->solver; + copy_conditional_equivalences (solver, lit, watches, condbin); + sort_pairs (solver, condbin); +#ifdef LOGGING + if (kissat_logging (solver)) { + for (all_stack (litpair, pair, *condbin)) + LOG ("sorted conditional %s binary clause %s %s", LOGLIT (lit), + LOGLIT (pair.lits[0]), LOGLIT (pair.lits[1])); + LOG ("found %zu conditional %s binary clauses", SIZE_STACK (*condbin), + LOGLIT (lit)); + } +#endif + extract_condeq_pairs (closure, lit, condbin, condeq); + sort_pairs (solver, condeq); +#ifdef LOGGING + if (kissat_logging (solver)) { + for (all_stack (litpair, pair, *condeq)) + LOG ("sorted conditional %s equivalence %s = %s", LOGLIT (lit), + LOGLIT (pair.lits[0]), LOGLIT (pair.lits[1])); + LOG ("found %zu conditional %s equivalences", SIZE_STACK (*condeq), + LOGLIT (lit)); + } +#endif +} + +static void merge_condeq (closure *closure, unsigned cond, litpairs *condeq, + litpairs *not_condeq) { + kissat *solver = closure->solver; + assert (!NEGATED (cond)); + const litpair *const begin_condeq = BEGIN_STACK (*condeq); + const litpair *const end_condeq = END_STACK (*condeq); + const litpair *const begin_not_condeq = BEGIN_STACK (*not_condeq); + const litpair *const end_not_condeq = END_STACK (*not_condeq); + const litpair *p = begin_condeq; + const litpair *q = begin_not_condeq; + while (p != end_condeq) { + litpair cond_pair = *p++; + const unsigned lhs = cond_pair.lits[0]; + const unsigned then_lit = cond_pair.lits[1]; + assert (!NEGATED (lhs)); + while (q != end_not_condeq && q->lits[0] < lhs) + q++; + while (q != end_not_condeq && q->lits[0] == lhs) { + litpair not_cond_pair = *q++; + const unsigned else_lit = not_cond_pair.lits[1]; + new_ite_gate (closure, lhs, cond, then_lit, else_lit); + if (solver->inconsistent) + return; + } + } +} + +static void extract_ite_gates_of_literal (closure *closure, unsigned lit, + unsigned not_lit, + watches *lit_watches, + watches *not_lit_watches) { +#ifndef NDEBUG + kissat *solver = closure->solver; +#endif + litpairs *condbin = closure->condbin; + litpairs *condeq = closure->condeq; + find_conditional_equivalences (closure, lit, lit_watches, condbin + 0, + condeq + 0); + if (EMPTY_STACK (condeq[0])) + goto CLEAN_UP; + find_conditional_equivalences (closure, not_lit, not_lit_watches, + condbin + 1, condeq + 1); + if (EMPTY_STACK (condeq[1])) + goto CLEAN_UP; + if (NEGATED (lit)) + merge_condeq (closure, not_lit, condeq + 0, condeq + 1); + else + merge_condeq (closure, lit, condeq + 1, condeq + 0); +CLEAN_UP: + CLEAR_STACK (condbin[0]); + CLEAR_STACK (condbin[1]); + CLEAR_STACK (condeq[0]); + CLEAR_STACK (condeq[1]); +} + +static void extract_ite_gates_of_variable (closure *closure, unsigned idx) { + kissat *const solver = closure->solver; + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + watches *lit_watches = &WATCHES (lit); + watches *not_lit_watches = &WATCHES (not_lit); + const size_t size_lit_watches = SIZE_WATCHES (*lit_watches); + const size_t size_not_lit_watches = SIZE_WATCHES (*not_lit_watches); + if (size_lit_watches <= size_not_lit_watches) { + if (size_lit_watches > 1) + extract_ite_gates_of_literal (closure, lit, not_lit, lit_watches, + not_lit_watches); + } else { + if (size_not_lit_watches > 1) + extract_ite_gates_of_literal (closure, not_lit, lit, not_lit_watches, + lit_watches); + } +} + +#else + +static void mark_third_literal_in_ternary_clauses ( + kissat *solver, const value *const values, unsigneds *marked, + mark *marks, unsigned a, unsigned b) { + assert (!solver->watching); + assert (EMPTY_STACK (*marked)); + watches *a_watches = &WATCHES (a); + watches *b_watches = &WATCHES (b); + const size_t size_a = SIZE_WATCHES (*a_watches); + const size_t size_b = SIZE_WATCHES (*b_watches); + watches *watches = size_a <= size_b ? a_watches : b_watches; + const watch *const begin = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + for (const watch *p = begin; p != end; p++) { + const watch watch = *p; + assert (!watch.type.binary); + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + assert (!c->garbage); + unsigned third = INVALID_LIT, found = 0; + for (all_literals_in_clause (lit, c)) { + if (values[lit]) + continue; + if (lit == a || lit == b) { + found++; + continue; + } + if (third != INVALID_LIT) + goto NEXT_WATCH; + third = lit; + } + assert (found <= 2); + if (found < 2) + goto NEXT_WATCH; + assert (third != INVALID_LIT); + if (third == INVALID_LIT) + goto NEXT_WATCH; + if (marks[third]) + goto NEXT_WATCH; + LOGCLS (c, "marking %s as third literal in", LOGLIT (third)); + PUSH_STACK (*marked, third); + marks[third] = 1; + NEXT_WATCH:; + } +} + +static void extract_ite_gate (closure *closure, const value *const values, + mark *const marks, unsigned lhs, + unsigned cond, unsigned then_lit) { + kissat *const solver = closure->solver; + assert (!solver->watching); + unsigned a = NOT (lhs), b = cond; + watches *a_watches = &WATCHES (a); + watches *b_watches = &WATCHES (b); + const size_t size_a = SIZE_WATCHES (*a_watches); + const size_t size_b = SIZE_WATCHES (*b_watches); + watches *watches = size_a <= size_b ? a_watches : b_watches; + const watch *const begin = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + for (const watch *p = begin; p != end; p++) { + const watch watch = *p; + assert (!watch.type.binary); + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + assert (!c->garbage); + unsigned else_lit = INVALID_LIT, found = 0; + for (all_literals_in_clause (lit, c)) { + if (values[lit]) + continue; + if (lit == a || lit == b) { + found++; + continue; + } + if (else_lit != INVALID_LIT) + goto NEXT_WATCH; + else_lit = lit; + } + assert (found <= 2); + if (found < 2) + goto NEXT_WATCH; + assert (else_lit != INVALID_LIT); + unsigned not_else_lit = NOT (else_lit); + if (!marks[not_else_lit]) + goto NEXT_WATCH; + LOGCLS (c, "found fourth matching"); + check_ite_implied (closure, lhs, cond, then_lit, else_lit); + marks[not_else_lit] = 0; + new_ite_gate (closure, lhs, cond, then_lit, else_lit); + NEXT_WATCH:; + } + SWAP (unsigned, a, b); +} + +static void extract_ite_gates_with_base_clause (closure *closure, + clause *c) { + assert (!c->garbage); + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + const value *const values = solver->values; + const unsigned *const largecount = closure->largecount; + unsigneds *lits = &closure->lits; + CLEAR_STACK (*lits); + unsigned sum = 0; + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "found satisfied %s in", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + return; + } + PUSH_STACK (*lits, lit); + sum ^= lit; + } + const size_t size = SIZE_STACK (*lits); + assert (size <= 3); + if (size < 3) + return; + mark *const marks = solver->marks; + unsigneds *marked = &solver->analyzed; + for (all_stack (unsigned, lhs, *lits)) { + if (NEGATED (lhs)) + continue; + if (largecount[lhs] < 2) + continue; + const unsigned not_lhs = NOT (lhs); + if (largecount[not_lhs] < 2) + continue; + for (all_stack (unsigned, not_cond, *lits)) { + if (not_cond == lhs) + continue; + if (!NEGATED (not_cond)) + continue; + if (largecount[not_cond] < 2) + continue; + const unsigned cond = NOT (not_cond); + if (largecount[cond] < 2) + continue; + const unsigned not_then_lit = sum ^ lhs ^ not_cond; + const unsigned then_lit = NOT (not_then_lit); + if (!largecount[then_lit]) + continue; + LOGCLS (c, "found first ITE gate '%s := %s ? %s : ...' gate base", + LOGLIT (lhs), LOGLIT (cond), LOGLIT (then_lit)); + clause *d = find_ternary_clause (solver, not_lhs, not_cond, then_lit); + if (!d) + continue; + LOGCLS (d, "found matching second ITE gate"); + mark_third_literal_in_ternary_clauses (solver, values, marked, marks, + lhs, cond); + extract_ite_gate (closure, values, marks, lhs, cond, then_lit); + unmark_all (marked, marks); + } + } +} + +#endif + +static void extract_and_gates (closure *closure) { + kissat *const solver = closure->solver; + if (!GET_OPTION (congruenceands)) + return; + START (extractands); +#ifndef QUIET + const statistics *s = &solver->statistics; + const uint64_t matched_before = s->congruent_matched_ands; + const uint64_t gates_before = s->congruent_gates_ands; +#endif + init_and_gate_extraction (closure); + clause *last_irredundant = kissat_last_irredundant_clause (solver); + for (all_clauses (c)) { + if (TERMINATED (congruence_terminated_1)) + break; + if (solver->inconsistent) + break; + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + if (c->garbage) + continue; + extract_and_gates_with_base_clause (closure, c); + } + reset_and_gate_extraction (closure); +#ifndef QUIET + const uint64_t matched = s->congruent_matched_ands - matched_before; + const uint64_t extracted = s->congruent_gates_ands - gates_before; + const uint64_t found = matched + extracted; + kissat_phase (solver, "congruence", GET (closures), + "found %" PRIu64 " AND gates (%" PRIu64 + " extracted %.0f%% + %" PRIu64 " matched %.0f%%)", + found, extracted, kissat_percent (extracted, found), + matched, kissat_percent (matched, found)); +#endif + STOP (extractands); +} + +static void extract_xor_gates (closure *closure) { + kissat *const solver = closure->solver; + if (!GET_OPTION (congruencexors)) + return; + START (extractxors); + references candidates; + INIT_STACK (candidates); + init_xor_gate_extraction (closure, &candidates); + SHRINK_STACK (candidates); +#ifndef QUIET + const statistics *s = &solver->statistics; + const uint64_t matched_before = s->congruent_matched_xors; + const uint64_t gates_before = s->congruent_gates_xors; +#endif + for (all_stack (reference, ref, candidates)) { + if (TERMINATED (congruence_terminated_2)) + break; + if (solver->inconsistent) + break; + clause *c = kissat_dereference_clause (solver, ref); + if (c->garbage) + continue; + extract_xor_gates_with_base_clause (closure, c); + } + reset_xor_gate_extraction (closure); + RELEASE_STACK (candidates); +#ifndef QUIET + const uint64_t matched = s->congruent_matched_xors - matched_before; + const uint64_t extracted = s->congruent_gates_xors - gates_before; + const uint64_t found = matched + extracted; + kissat_phase (solver, "congruence", GET (closures), + "found %" PRIu64 " XOR gates (%" PRIu64 + " extracted %.0f%% + %" PRIu64 " matched %.0f%%)", + found, extracted, kissat_percent (extracted, found), + matched, kissat_percent (matched, found)); +#endif + STOP (extractxors); +} + +static void extract_ite_gates (closure *closure) { + kissat *const solver = closure->solver; + if (!GET_OPTION (congruenceites)) + return; + START (extractites); + references candidates; + INIT_STACK (candidates); + init_ite_gate_extraction (closure, &candidates); +#ifndef QUIET + const statistics *s = &solver->statistics; + const uint64_t matched_before = s->congruent_matched_ites; + const uint64_t gates_before = s->congruent_gates_ites; +#endif +#ifdef MERGE_CONDITIONAL_EQUIVALENCES + for (all_variables (idx)) + if (ACTIVE (idx)) { + extract_ite_gates_of_variable (closure, idx); + if (solver->inconsistent) + break; + } +#else + for (all_stack (reference, ref, candidates)) { + if (TERMINATED (congruence_terminated_3)) + break; + if (solver->inconsistent) + break; + clause *c = kissat_dereference_clause (solver, ref); + if (c->garbage) + continue; + extract_ite_gates_with_base_clause (closure, c); + } +#endif + reset_ite_gate_extraction (closure); + RELEASE_STACK (candidates); +#ifndef QUIET + const uint64_t matched = s->congruent_matched_ites - matched_before; + const uint64_t extracted = s->congruent_gates_ites - gates_before; + const uint64_t found = matched + extracted; + kissat_phase (solver, "congruence", GET (closures), + "found %" PRIu64 " ITE gates (%" PRIu64 + " extracted %.0f%% + %" PRIu64 " matched %.0f%%)", + found, extracted, kissat_percent (extracted, found), + matched, kissat_percent (matched, found)); +#endif + STOP (extractites); +} + +static void init_extraction (closure *closure) { + kissat *const solver = closure->solver; + kissat_enter_dense_mode (solver, &closure->binaries); +} + +static void reset_extraction (closure *closure) { + kissat *const solver = closure->solver; + kissat_resume_sparse_mode (solver, false, &closure->binaries); + RELEASE_STACK (closure->binaries); +} + +static void extract_gates (closure *closure) { + kissat *const solver = closure->solver; + START (extract); + assert (!solver->level); +#ifndef QUIET + const statistics *s = &solver->statistics; + const uint64_t before = s->congruent_gates + s->congruent_matched; +#endif + init_extraction (closure); + extract_binaries (closure); + assert (!solver->inconsistent); + extract_and_gates (closure); + if (!solver->inconsistent && !TERMINATED (congruence_terminated_4)) { + extract_xor_gates (closure); + if (!solver->inconsistent && !TERMINATED (congruence_terminated_5)) + extract_ite_gates (closure); + } + reset_extraction (closure); +#ifndef QUIET + const uint64_t after = s->congruent_gates + s->congruent_matched; + const uint64_t found = after - before; + kissat_phase (solver, "congruence", GET (closures), + "found %" PRIu64 " gates (%.2f%% variables)", found, + kissat_percent (found, solver->active)); +#endif + STOP (extract); +} + +static void find_units (closure *closure) { + kissat *const solver = closure->solver; + assert (solver->watching); + assert (!solver->inconsistent); + assert (kissat_propagated (solver)); + closure->units = solver->propagate; + unsigneds *marked = &solver->analyzed; + mark *const marks = solver->marks; + size_t units = 0; + for (all_variables (idx)) { + RESTART: + if (!ACTIVE (idx)) + continue; + unsigned lit = LIT (idx); + for (unsigned sign = 0; sign != 2; sign++, lit++) { + watches *const watches = &WATCHES (lit); + const watch *p = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + assert (EMPTY_STACK (*marked)); + while (p != end) { + const watch watch = *p++; + if (!watch.type.binary) + break; + const unsigned other = watch.binary.lit; + const unsigned not_other = NOT (other); + if (marks[not_other]) { + LOG ("binary clauses %s %s and %s %s yield unit %s", LOGLIT (lit), + LOGLIT (other), LOGLIT (lit), LOGLIT (not_other), + LOGLIT (lit)); + units++; + bool failed = !learn_congruence_unit (closure, lit); + unmark_all (marked, marks); + if (failed) + return; + else + goto RESTART; + } + if (marks[other]) + continue; + marks[other] = 1; + PUSH_STACK (*marked, other); + } + unmark_all (marked, marks); + } + } + assert (EMPTY_STACK (*marked)); +#ifndef QUIET + kissat_very_verbose (solver, "found %zu units", units); +#else + (void) units; +#endif +} + +static void find_equivalences (closure *closure) { + kissat *const solver = closure->solver; + assert (solver->watching); + assert (!solver->inconsistent); + unsigneds *const marked = &solver->analyzed; + mark *const marks = solver->marks; + assert (EMPTY_STACK (*marked)); + for (all_variables (idx)) { + RESTART: + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + watches *lit_watches = &WATCHES (lit); + const watch *p = BEGIN_WATCHES (*lit_watches); + const watch *const end_lit_watches = END_WATCHES (*lit_watches); + assert (EMPTY_STACK (*marked)); + while (p != end_lit_watches) { + const watch watch = *p++; + if (!watch.type.binary) + break; + const unsigned other = watch.binary.lit; + if (lit > other) + continue; + if (marks[other]) + continue; + marks[other] = 1; + PUSH_STACK (*marked, other); + } + if (EMPTY_STACK (*marked)) + continue; + const unsigned not_lit = NOT (lit); + watches *not_lit_watches = &WATCHES (not_lit); + p = BEGIN_WATCHES (*not_lit_watches); + const watch *const end_not_lit_watches = END_WATCHES (*not_lit_watches); + while (p != end_not_lit_watches) { + const watch watch = *p++; + if (!watch.type.binary) + break; + const unsigned other = watch.binary.lit; + if (not_lit > other) + continue; + if (lit == other) + continue; + const unsigned not_other = NOT (other); + if (marks[not_other]) { + unsigned lit_repr = find_repr (closure, lit); + unsigned other_repr = find_repr (closure, other); + if (lit_repr != other_repr) { + if (merge_literals (closure, lit, other)) + INC (congruent_equivalences); + unmark_all (marked, marks); + if (solver->inconsistent) + return; + else + goto RESTART; + } + } + } + unmark_all (marked, marks); + } + assert (EMPTY_STACK (*marked)); +#ifndef QUIET + size_t found = SIZE_FIFO (closure->schedule); + kissat_very_verbose (solver, "found %zu equivalences", found); +#endif +} + +static bool simplify_gates (closure *closure, unsigned lit) { + kissat *const solver = closure->solver; + LOG ("simplifying gates with RHS literal %s", LOGLIT (lit)); + assert (solver->values[lit]); + gates *lit_occurrences = closure->occurrences + lit; + for (all_pointers (gate, g, *lit_occurrences)) + if (!simplify_gate (closure, g)) + return false; + RELEASE_STACK (*lit_occurrences); + return true; +} + +static bool rewrite_gates (closure *closure, unsigned dst, unsigned src) { + kissat *const solver = closure->solver; + LOG ("rewriting gates with RHS literal %s", LOGLIT (src)); + gates *occurrences = closure->occurrences; + gates *src_occurrences = occurrences + src; + gates *dst_occurrences = occurrences + dst; + for (all_pointers (gate, g, *src_occurrences)) + if (!rewrite_gate (closure, g, dst, src)) + return false; + else if (!g->garbage && gate_contains (g, dst)) + PUSH_STACK (*dst_occurrences, g); + RELEASE_STACK (*src_occurrences); + return true; +} + +static bool propagate_unit (closure *closure, unsigned lit) { + kissat *const solver = closure->solver; + LOG ("propagation of congruence unit %s", LOGLIT (lit)); + (void) solver; + assert (!solver->inconsistent); + const unsigned not_lit = NOT (lit); + return simplify_gates (closure, lit) && simplify_gates (closure, not_lit); +} + +static bool propagate_equivalence (closure *closure, unsigned lit) { + kissat *const solver = closure->solver; + LOG ("propagation of congruence equivalence %s", CLOGREPR (lit)); + assert (!solver->inconsistent); + if (VALUE (lit)) + return true; + const unsigned lit_repr = find_repr (closure, lit); + if (solver->inconsistent) + return false; + const unsigned not_lit = NOT (lit); + const unsigned not_lit_repr = NOT (lit_repr); + return rewrite_gates (closure, lit_repr, lit) && + rewrite_gates (closure, not_lit_repr, not_lit); +} + +static bool propagate_units (closure *closure) { + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + const unsigned_array *const trail = &solver->trail; + while (closure->units != trail->end) + if (!propagate_unit (closure, *closure->units++)) + return false; + return true; +} + +static size_t propagate_units_and_equivalences (closure *closure) { + kissat *const solver = closure->solver; + assert (!solver->inconsistent); + START (merge); + unsigned_fifo *schedule = &closure->schedule; + size_t propagated = 0; + while (!TERMINATED (congruence_terminated_6) && + propagate_units (closure) && !EMPTY_FIFO (*schedule)) { + propagated++; + unsigned lit = dequeue_next_scheduled_literal (closure); + if (!propagate_equivalence (closure, lit)) + break; + } +#ifndef QUIET + const size_t units = closure->units - solver->trail.begin; + kissat_very_verbose (solver, "propagated %zu congruence units", units); + kissat_very_verbose (solver, "propagated %zu congruence equivalences", + propagated); +#endif + STOP (merge); + return propagated; +} + +#ifndef NDEBUG + +static void dump_closure_literal (closure *closure, unsigned ilit) { + kissat *const solver = closure->solver; + const int elit = kissat_export_literal (solver, ilit); + printf ("%u(%d)", ilit, elit); + unsigned repr_ilit = find_repr (closure, ilit); + if (repr_ilit != ilit) { + const int repr_elit = kissat_export_literal (solver, repr_ilit); + printf ("[%u(%d)]", repr_ilit, repr_elit); + } + const int value = VALUE (ilit); + assert (!solver->level); + if (value) + printf ("@0=%d", value); +} + +static void dump_units (closure *closure) { + unsigned_array *trail = &closure->solver->trail; + size_t i = 0, propagate = closure->units - trail->begin; + for (all_stack (unsigned, lit, *trail)) { + printf ("trail[%zu] ", i); + dump_closure_literal (closure, lit); + if (i == propagate) + fputs (" <-- next unit to propagate", stdout); + fputc ('\n', stdout); + i++; + } +} + +static void dump_equivalences (closure *closure) { + kissat *const solver = closure->solver; + for (all_variables (idx)) { + unsigned lit = LIT (idx); + unsigned repr = closure->repr[lit]; + if (repr != lit) + printf ("repr[%u(%d)] = %u(%d)\n", lit, + kissat_export_literal (solver, lit), repr, + kissat_export_literal (solver, repr)); + } +} + +static void dump_gate (closure *closure, gate *g) { + const unsigned tag = g->tag; + const char *str; + switch (tag) { + case AND_GATE: + str = "AND"; + break; + case ITE_GATE: + str = "ITE"; + break; + case XOR_GATE: + str = "XOR"; + break; + default: + str = "UNKNOWN"; + break; + } + printf ("%p %s gate[%zu] ", (void *) g, str, g->id); + dump_closure_literal (closure, g->lhs); + fputs (" := ", stdout); + if (g->tag == ITE_GATE) { + dump_closure_literal (closure, g->rhs[0]); + fputs (" ? ", stdout); + dump_closure_literal (closure, g->rhs[1]); + fputs (" : ", stdout); + dump_closure_literal (closure, g->rhs[2]); + } else { + bool first = true; + for (all_rhs_literals_in_gate (rhs, g)) { + if (first) + first = false; + else if (g->tag == AND_GATE) + fputs (" & ", stdout); + else + fputs (" ^ ", stdout); + dump_closure_literal (closure, rhs); + } + } + fputs (g->indexed ? " removed" : " indexed", stdout); + if (g->garbage) + fputs (" garbage", stdout); + fputc ('\n', stdout); +} + +#define LESS_GATE(G, H) ((G)->id < (H)->id) + +static void dump_gates (closure *closure) { + gates gates; + INIT_STACK (gates); + kissat *const solver = closure->solver; + for (unsigned pos = 0; pos != closure->hash.size; pos++) { + gate *g = closure->hash.table[pos]; + if (!g) + continue; + if (g == REMOVED) + continue; + PUSH_STACK (gates, g); + } + SORT_STACK (gate *, gates, LESS_GATE); + for (all_pointers (gate, g, gates)) + dump_gate (closure, g); + RELEASE_STACK (gates); +} + +void kissat_dump_closure (closure *closure) { + dump_units (closure); + dump_equivalences (closure); + dump_gates (closure); +} + +#endif + +static bool find_subsuming_clause (closure *closure, clause *c) { + assert (!c->garbage); + kissat *const solver = closure->solver; + const reference c_ref = kissat_reference_clause (solver, c); + const value *const values = solver->values; + mark *marks = solver->marks; + { + const unsigned *const end_lits = c->lits + c->size; + for (const unsigned *p = c->lits; p != end_lits; p++) { + const unsigned lit = *p; + assert (values[lit] <= 0); + const unsigned repr_lit = find_repr (closure, lit); + const value value_repr_lit = values[repr_lit]; + assert (value_repr_lit <= 0); + if (value_repr_lit < 0) + continue; + if (marks[repr_lit]) + continue; + assert (!marks[NOT (repr_lit)]); + marks[repr_lit] = 1; + } + } + unsigned least_occurring_literal = INVALID_LIT; + unsigned count_least_occurring = UINT_MAX; + LOGREPRCLS (c, closure->repr, "trying to forward subsume"); + clause *subsuming = 0; + for (all_literals_in_clause (lit, c)) { + const unsigned repr_lit = find_repr (closure, lit); + watches *const watches = &WATCHES (repr_lit); + const watch *p = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + const size_t count = end - p; + assert (count <= UINT_MAX); + if (count < count_least_occurring) { + count_least_occurring = count; + least_occurring_literal = repr_lit; + } + while (p != end) { + const watch watch = *p++; + assert (!watch.type.binary); + const reference d_ref = watch.large.ref; + clause *const d = kissat_dereference_clause (solver, d_ref); + assert (c != d); + assert (!d->garbage); + if (!c->redundant && d->redundant) + continue; + for (all_literals_in_clause (other, d)) { + const value value = values[other]; + if (value < 0) + continue; + assert (!value); + const unsigned repr_other = find_repr (closure, other); + if (!marks[repr_other]) + goto CONTINUE_WITH_NEXT_CLAUSE; + } + subsuming = d; + goto FOUND_SUBSUMING; + CONTINUE_WITH_NEXT_CLAUSE:; + } + } +FOUND_SUBSUMING: + for (all_literals_in_clause (lit, c)) { + const unsigned repr_lit = find_repr (closure, lit); + const value value = values[repr_lit]; + if (!value) + marks[repr_lit] = 0; + } + if (subsuming) { + LOGREPRCLS (c, closure->repr, "subsumed"); + LOGREPRCLS (subsuming, closure->repr, "subsuming"); + kissat_mark_clause_as_garbage (solver, c); + INC (congruent_subsumed); + return true; + } else { + assert (least_occurring_literal != INVALID_LIT); + assert (count_least_occurring < UINT_MAX); + LOGCLS (c, "forward subsumption failed of"); + LOG ("connecting %u occurring %s", count_least_occurring, + LOGLIT (least_occurring_literal)); + kissat_connect_literal (solver, least_occurring_literal, c_ref); + return false; + } +} + +struct refsize { + reference ref; + unsigned size; +}; + +typedef struct refsize refsize; +typedef STACK (refsize) refsizes; + +#define RANKREFSIZE(REFSIZE) ((REFSIZE).size) + +static void sort_references_by_clause_size (kissat *solver, + refsizes *candidates) { + RADIX_STACK (refsize, unsigned, *candidates, RANKREFSIZE); +} + +static void forward_subsume_matching_clauses (closure *closure) { + kissat *const solver = closure->solver; + START (matching); + reset_closure (closure); + litpairs binaries; + INIT_STACK (binaries); + kissat_enter_dense_mode (solver, &binaries); + bool *matchable; +#ifndef QUIET + unsigned count_matchable = 0; +#endif + CALLOC (matchable, VARS); + for (all_variables (idx)) + if (ACTIVE (idx)) { + const unsigned lit = LIT (idx); + const unsigned repr = find_repr (closure, lit); + if (lit == repr) + continue; + const unsigned repr_idx = IDX (repr); + if (!matchable[idx]) { + LOG ("matchable %s", LOGVAR (idx)); + matchable[idx] = true; +#ifndef QUIET + count_matchable++; +#endif + } + if (!matchable[repr_idx]) { + LOG ("matchable %s", LOGVAR (repr_idx)); + matchable[repr_idx] = true; +#ifndef QUIET + count_matchable++; +#endif + } + } + kissat_phase (solver, "congruence", GET (closures), + "found %u matchable variables %.0f%%", count_matchable, + kissat_percent (count_matchable, solver->active)); + size_t potential = 0; + refsizes candidates; + INIT_STACK (candidates); + clause *last_irredundant = kissat_last_irredundant_clause (solver); + const value *const values = solver->values; + mark *const marks = solver->marks; + unsigneds *marked = &solver->analyzed; + for (all_clauses (c)) { + if (c->garbage) + continue; + if (last_irredundant && last_irredundant < c) + break; + potential++; + bool contains_matchable = false; + assert (EMPTY_STACK (*marked)); + LOGREPRCLS (c, closure->repr, "considering"); + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "satisfied %s in", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + break; + } + if (!contains_matchable) { + const unsigned lit_idx = IDX (lit); + if (matchable[lit_idx]) + contains_matchable = true; + } + const unsigned repr = find_repr (closure, lit); + assert (!values[repr]); + if (marks[repr]) + continue; + const unsigned not_repr = NOT (repr); + if (marks[not_repr]) { + LOGCLS (c, "matches both %s and %s", CLOGREPR (lit), + LOGLIT (not_repr)); + kissat_mark_clause_as_garbage (solver, c); + break; + } + marks[repr] = 1; + PUSH_STACK (*marked, repr); + } + const size_t size = SIZE_STACK (*marked); + for (all_stack (unsigned, repr, *marked)) + marks[repr] = 0; + CLEAR_STACK (*marked); + if (c->garbage) + continue; + if (!contains_matchable) { + LOGREPRCLS (c, closure->repr, "no matchable variable in"); + continue; + } + const reference ref = kissat_reference_clause (solver, c); + assert (size <= UINT_MAX); + refsize refsize = {.ref = ref, .size = size}; + PUSH_STACK (candidates, refsize); + } + DEALLOC (matchable, VARS); +#ifndef QUIET + const size_t size_candidates = SIZE_STACK (candidates); + kissat_very_verbose ( + solver, "considering %zu matchable subsumption candidates %.0f%%", + size_candidates, kissat_percent (size_candidates, potential)); +#else + (void) potential; +#endif + sort_references_by_clause_size (solver, &candidates); +#ifndef QUIET + size_t tried = 0, subsumed = 0; +#endif + for (all_stack (refsize, refsize, candidates)) { + if (TERMINATED (congruence_terminated_7)) + break; +#ifndef QUIET + tried++; +#endif + const unsigned ref = refsize.ref; + clause *c = kissat_dereference_clause (solver, ref); + if (find_subsuming_clause (closure, c)) { +#ifndef QUIET + subsumed++; +#endif + } + } + kissat_phase (solver, "congruence", GET (closures), + "subsumed %zu clauses out of %zu tried %.0f%%", subsumed, + tried, kissat_percent (subsumed, tried)); + kissat_resume_sparse_mode (solver, false, &binaries); + RELEASE_STACK (candidates); + RELEASE_STACK (binaries); + STOP (matching); +} + +bool kissat_congruence (kissat *solver) { + if (solver->inconsistent) + return false; + kissat_check_statistics (solver); + assert (!solver->level); + assert (solver->probing); + assert (solver->watching); + if (!GET_OPTION (congruence)) + return false; + if (!GET_OPTION (congruenceands) && !GET_OPTION (congruenceites) && + !GET_OPTION (congruencexors)) + return false; + if (GET_OPTION (congruenceonce) && solver->statistics.closures) + return false; + if (TERMINATED (congruence_terminated_8)) + return false; + if (DELAYING (congruence)) + return false; + START (congruence); + INC (closures); + closure closure; + init_closure (solver, &closure); + extract_gates (&closure); + bool reset = false; + if (!solver->inconsistent && !TERMINATED (congruence_terminated_9)) { + find_units (&closure); + if (!solver->inconsistent && !TERMINATED (congruence_terminated_10)) { + find_equivalences (&closure); + if (!solver->inconsistent && !TERMINATED (congruence_terminated_11)) { + size_t propagated = propagate_units_and_equivalences (&closure); + if (!solver->inconsistent && propagated && + !TERMINATED (congruence_terminated_12)) { + forward_subsume_matching_clauses (&closure); + reset = true; + } + } + } + } + if (!reset) + reset_closure (&closure); + unsigned equivalent = reset_repr (&closure); + kissat_phase (solver, "congruence", GET (closures), + "merged %u equivalent variables %.2f%%", equivalent, + kissat_percent (equivalent, solver->active)); + assert (solver->active >= equivalent); +#ifndef QUIET + solver->active -= equivalent; + REPORT (!equivalent, 'c'); + if (!solver->inconsistent) + solver->active += equivalent; +#endif + if (kissat_average (equivalent, solver->active) < 0.001) + BUMP_DELAY (congruence); + else + REDUCE_DELAY (congruence); + STOP (congruence); + kissat_check_statistics (solver); + return equivalent; +} diff --git a/src/sat/kissat/congruence.h b/src/sat/kissat/congruence.h new file mode 100644 index 000000000..84e1eb643 --- /dev/null +++ b/src/sat/kissat/congruence.h @@ -0,0 +1,9 @@ +#ifndef _congruence_h_INCLUDED +#define _congruence_h_INCLUDED + +#include + +struct kissat; +bool kissat_congruence (struct kissat *); + +#endif diff --git a/src/sat/kissat/cover.h b/src/sat/kissat/cover.h new file mode 100644 index 000000000..f5a82b71e --- /dev/null +++ b/src/sat/kissat/cover.h @@ -0,0 +1,28 @@ +#ifndef _cover_h_INCLUDED +#define _cover_h_INCLUDED + +#include +#include + +#define COVER(COND) \ + ((COND) ? \ +\ + (fflush (stdout), \ + fprintf (stderr, "%s:%ld: %s: Coverage goal `%s' reached.\n", \ + __FILE__, (long) __LINE__, __func__, #COND), \ + abort (), (void) 0) \ + : (void) 0) + +#ifdef COVERAGE +#define FLUSH_COVERAGE() \ + do { \ + void __gcov_dump (void); \ + __gcov_dump (); \ + } while (0) +#else +#define FLUSH_COVERAGE() \ + do { \ + } while (0) +#endif + +#endif diff --git a/src/sat/kissat/decide.c b/src/sat/kissat/decide.c new file mode 100644 index 000000000..522930e26 --- /dev/null +++ b/src/sat/kissat/decide.c @@ -0,0 +1,244 @@ +#include "decide.h" +#include "inlineframes.h" +#include "inlineheap.h" +#include "inlinequeue.h" +#include "print.h" + +#include + +static unsigned last_enqueued_unassigned_variable (kissat *solver) { + assert (solver->unassigned); + const links *const links = solver->links; + const value *const values = solver->values; + unsigned res = solver->queue.search.idx; + if (values[LIT (res)]) { + do { + res = links[res].prev; + assert (!DISCONNECTED (res)); + } while (values[LIT (res)]); + kissat_update_queue (solver, links, res); + } +#ifdef LOGGING + const unsigned stamp = links[res].stamp; + LOG ("last enqueued unassigned %s stamp %u", LOGVAR (res), stamp); +#endif +#ifdef CHECK_QUEUE + for (unsigned i = links[res].next; !DISCONNECTED (i); i = links[i].next) + assert (VALUE (LIT (i))); +#endif + return res; +} + +static unsigned largest_score_unassigned_variable (kissat *solver) { + heap *scores = SCORES; + unsigned res = kissat_max_heap (scores); + const value *const values = solver->values; + while (values[LIT (res)]) { + kissat_pop_max_heap (solver, scores); + res = kissat_max_heap (scores); + } +#if defined(LOGGING) || defined(CHECK_HEAP) + const double score = kissat_get_heap_score (scores, res); +#endif + LOG ("largest score unassigned %s score %g", LOGVAR (res), score); +#ifdef CHECK_HEAP + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + if (VALUE (LIT (idx))) + continue; + const double idx_score = kissat_get_heap_score (scores, idx); + assert (score >= idx_score); + } +#endif + return res; +} + +void kissat_start_random_sequence (kissat *solver) { + if (!GET_OPTION (randec)) + return; + + if (solver->stable && !GET_OPTION (randecstable)) + return; + + if (!solver->stable && !GET_OPTION (randecfocused)) + return; + + if (solver->randec) + kissat_very_verbose (solver, + "continuing random decision sequence " + "at %s conflicts", + FORMAT_COUNT (CONFLICTS)); + else { + INC (random_sequences); + const uint64_t count = solver->statistics.random_sequences; + const unsigned length = GET_OPTION (randeclength) * LOGN (count); + kissat_very_verbose (solver, + "starting random decision sequence " + "at %s conflicts for %s conflicts", + FORMAT_COUNT (CONFLICTS), FORMAT_COUNT (length)); + solver->randec = length; + + UPDATE_CONFLICT_LIMIT (randec, random_sequences, LOGN, false); + } +} + +static unsigned next_random_decision (kissat *solver) { + if (!VARS) + return INVALID_IDX; + + if (solver->warming) + return INVALID_IDX; + + if (!GET_OPTION (randec)) + return INVALID_IDX; + + if (solver->stable && !GET_OPTION (randecstable)) + return INVALID_IDX; + + if (!solver->stable && !GET_OPTION (randecfocused)) + return INVALID_IDX; + + if (!solver->randec) { + assert (solver->level); + if (solver->level > 1) + return INVALID_IDX; + + uint64_t conflicts = CONFLICTS; + limits *limits = &solver->limits; + if (conflicts < limits->randec.conflicts) + return INVALID_IDX; + + kissat_start_random_sequence (solver); + } + + for (;;) { + unsigned idx = kissat_next_random32 (&solver->random) % VARS; + if (!ACTIVE (idx)) + continue; + unsigned lit = LIT (idx); + if (solver->values[lit]) + continue; + return idx; + } +} + +unsigned kissat_next_decision_variable (kissat *solver) { +#ifdef LOGGING + const char *type = 0; +#endif + unsigned res = next_random_decision (solver); + if (res == INVALID_IDX) { + if (solver->stable) { +#ifdef LOGGING + type = "maximum score"; +#endif + res = largest_score_unassigned_variable (solver); + INC (score_decisions); + } else { +#ifdef LOGGING + type = "dequeued"; +#endif + res = last_enqueued_unassigned_variable (solver); + INC (queue_decisions); + } + } else { +#ifdef LOGGING + type = "random"; +#endif + INC (random_decisions); + } + LOG ("next %s decision %s", type, LOGVAR (res)); + return res; +} + +int kissat_decide_phase (kissat *solver, unsigned idx) { + bool force = GET_OPTION (forcephase); + + value *target; + if (force) + target = 0; + else if (!GET_OPTION (target)) + target = 0; + else if (solver->stable || GET_OPTION (target) > 1) + target = solver->phases.target + idx; + else + target = 0; + + value *saved; + if (force) + saved = 0; + else if (GET_OPTION (phasesaving)) + saved = solver->phases.saved + idx; + else + saved = 0; + + value res = 0; + + if (!solver->stable) { + switch ((solver->statistics.switched >> 1) & 7) { + case 1: + res = INITIAL_PHASE; + break; + case 3: + res = -INITIAL_PHASE; + break; + } + } + + if (!res && target && (res = *target)) { + LOG ("%s uses target decision phase %d", LOGVAR (idx), (int) res); + INC (target_decisions); + } + + if (!res && saved && (res = *saved)) { + LOG ("%s uses saved decision phase %d", LOGVAR (idx), (int) res); + INC (saved_decisions); + } + + if (!res) { + res = INITIAL_PHASE; + LOG ("%s uses initial decision phase %d", LOGVAR (idx), (int) res); + INC (initial_decisions); + } + assert (res); + + return res < 0 ? -1 : 1; +} + +void kissat_decide (kissat *solver) { + START (decide); + assert (solver->unassigned); + if (solver->warming) + INC (warming_decisions); + else { + INC (decisions); + if (solver->stable) + INC (stable_decisions); + else + INC (focused_decisions); + } + solver->level++; + assert (solver->level != INVALID_LEVEL); + const unsigned idx = kissat_next_decision_variable (solver); + const value value = kissat_decide_phase (solver, idx); + unsigned lit = LIT (idx); + if (value < 0) + lit = NOT (lit); + kissat_push_frame (solver, lit); + assert (solver->level < SIZE_STACK (solver->frames)); + LOG ("decide literal %s", LOGLIT (lit)); + kissat_assign_decision (solver, lit); + STOP (decide); +} + +void kissat_internal_assume (kissat *solver, unsigned lit) { + assert (solver->unassigned); + assert (!VALUE (lit)); + solver->level++; + assert (solver->level != INVALID_LEVEL); + kissat_push_frame (solver, lit); + assert (solver->level < SIZE_STACK (solver->frames)); + LOG ("assuming literal %s", LOGLIT (lit)); + kissat_assign_decision (solver, lit); +} diff --git a/src/sat/kissat/decide.h b/src/sat/kissat/decide.h new file mode 100644 index 000000000..9864ae2e1 --- /dev/null +++ b/src/sat/kissat/decide.h @@ -0,0 +1,14 @@ +#ifndef _decide_h_INCLUDED +#define _decide_h_INCLUDED + +struct kissat; + +void kissat_decide (struct kissat *); +void kissat_start_random_sequence (struct kissat *); +void kissat_internal_assume (struct kissat *, unsigned lit); +unsigned kissat_next_decision_variable (struct kissat *); +int kissat_decide_phase (struct kissat *, unsigned idx); + +#define INITIAL_PHASE (GET_OPTION (phase) ? 1 : -1) + +#endif diff --git a/src/sat/kissat/deduce.c b/src/sat/kissat/deduce.c new file mode 100644 index 000000000..be1235f69 --- /dev/null +++ b/src/sat/kissat/deduce.c @@ -0,0 +1,168 @@ +#include "deduce.h" +#include "inline.h" +#include "promote.h" +#include "strengthen.h" + +static inline void recompute_and_promote (kissat *solver, clause *c) { + assert (c->redundant); + const unsigned old_glue = c->glue; + const unsigned new_glue = kissat_recompute_glue (solver, c, old_glue); + if (new_glue < old_glue) + kissat_promote_clause (solver, c, new_glue); +} + +static inline void mark_clause_as_used (kissat *solver, clause *c) { + if (!c->redundant) + return; + INC (clauses_used); + c->used = MAX_USED; + LOGCLS (c, "using"); + recompute_and_promote (solver, c); + unsigned glue = MIN (c->glue, MAX_GLUE_USED); + solver->statistics.used[solver->stable].glue[glue]++; + if (solver->stable) + INC (clauses_used_stable); + else + INC (clauses_used_focused); +} + +bool kissat_recompute_and_promote (kissat *solver, clause *c) { + assert (c->redundant); + const unsigned old_glue = c->glue; + const unsigned new_glue = kissat_recompute_glue (solver, c, old_glue); + if (new_glue >= old_glue) + return false; + kissat_promote_clause (solver, c, new_glue); + return true; +} + +static inline bool analyze_literal (kissat *solver, assigned *all_assigned, + frame *frames, unsigned lit) { + assert (VALUE (lit) < 0); + const unsigned idx = IDX (lit); + assigned *a = all_assigned + idx; + const unsigned level = a->level; + if (!level) + return false; + solver->antecedent_size++; + if (a->analyzed) + return false; + LOG ("analyzing literal %s", LOGLIT (lit)); + kissat_push_analyzed (solver, all_assigned, idx); + assert (level <= solver->level); +#if defined(LOGGING) || !defined(NDEBUG) + PUSH_STACK (solver->resolvent, lit); +#endif + solver->resolvent_size++; + if (level == solver->level) + return true; + assert (a->analyzed); + PUSH_STACK (solver->clause, lit); + LOG ("learned literal %s", LOGLIT (lit)); + frame *f = frames + level; + if (f->used++) + return false; + LOG ("pulling in decision level %u", level); + PUSH_STACK (solver->levels, level); + return false; +} + +clause *kissat_deduce_first_uip_clause (kissat *solver, clause *conflict) { + START (deduce); + assert (EMPTY_STACK (solver->analyzed)); + assert (EMPTY_STACK (solver->levels)); + assert (EMPTY_STACK (solver->clause)); +#if defined(LOGGING) || !defined(NDEBUG) + CLEAR_STACK (solver->resolvent); +#endif + if (conflict->size > 2) + mark_clause_as_used (solver, conflict); + PUSH_STACK (solver->clause, INVALID_LIT); + solver->antecedent_size = 0; + solver->resolvent_size = 0; + unsigned unresolved_on_current_level = 0, conflict_size = 0; + assigned *all_assigned = solver->assigned; + frame *frames = BEGIN_STACK (solver->frames); + for (all_literals_in_clause (lit, conflict)) { + assert (VALUE (lit) < 0); + if (LEVEL (lit)) + conflict_size++; + if (analyze_literal (solver, all_assigned, frames, lit)) + unresolved_on_current_level++; + } + assert (unresolved_on_current_level > 1); + LOG ("starting with %u unresolved literals on current decision level", + unresolved_on_current_level); + assert (solver->antecedent_size == solver->resolvent_size); + LOGRES2 ("initial"); + const bool otfs = GET_OPTION (otfs); + unsigned const *t = END_ARRAY (solver->trail); + unsigned uip = INVALID_LIT; + unsigned resolved = 0; + assigned *a = 0; + for (;;) { + do { + assert (t > BEGIN_ARRAY (solver->trail)); + uip = *--t; + a = ASSIGNED (uip); + } while (!a->analyzed || a->level != solver->level); + if (unresolved_on_current_level == 1) + break; + assert (a->reason != DECISION_REASON); + assert (a->level == solver->level); + solver->antecedent_size = 1; + resolved++; + if (a->binary) { + const unsigned other = a->reason; + LOGBINARY (uip, other, "resolving %s reason", LOGLIT (uip)); + if (analyze_literal (solver, all_assigned, frames, other)) + unresolved_on_current_level++; + } else { + const reference ref = a->reason; + LOGREF (ref, "resolving %s reason", LOGLIT (uip)); + clause *reason = kissat_dereference_clause (solver, ref); + for (all_literals_in_clause (lit, reason)) + if (lit != uip && + analyze_literal (solver, all_assigned, frames, lit)) + unresolved_on_current_level++; + mark_clause_as_used (solver, reason); + } + assert (unresolved_on_current_level > 0); + unresolved_on_current_level--; + LOG ("after resolving %s there are %u literals left " + "on current decision level", + LOGLIT (uip), unresolved_on_current_level); + assert (solver->resolvent_size > 0); + solver->resolvent_size--; +#if defined(LOGGING) || !defined(NDEBUG) + LOG2 ("actual antecedent size %u", solver->antecedent_size); + REMOVE_STACK (unsigned, solver->resolvent, NOT (uip)); + assert (SIZE_STACK (solver->resolvent) == solver->resolvent_size); + LOGRES2 ("new"); +#endif + if (otfs && solver->antecedent_size > 2 && + solver->resolvent_size < solver->antecedent_size) { + assert (!a->binary); + assert (solver->antecedent_size && solver->resolvent_size + 1); + clause *reason = kissat_dereference_clause (solver, a->reason); + assert (!reason->garbage); + clause *res = kissat_on_the_fly_strengthen (solver, reason, uip); + if (resolved == 1 && solver->resolvent_size < conflict_size) { + assert (!conflict->garbage); + assert (conflict_size > 2); + kissat_on_the_fly_subsume (solver, res, conflict); + } + STOP (deduce); + return res; + } + } + assert (uip != INVALID_LIT); + LOG ("first unique implication point %s (1st UIP)", LOGLIT (uip)); + assert (PEEK_STACK (solver->clause, 0) == INVALID_LIT); + POKE_STACK (solver->clause, 0, NOT (uip)); + LOGTMP ("deduced not yet minimized 1st UIP"); + if (!solver->probing) + ADD (literals_deduced, SIZE_STACK (solver->clause)); + STOP (deduce); + return 0; +} diff --git a/src/sat/kissat/deduce.h b/src/sat/kissat/deduce.h new file mode 100644 index 000000000..38a5ec4c0 --- /dev/null +++ b/src/sat/kissat/deduce.h @@ -0,0 +1,14 @@ +#ifndef _deduce_h_INCLUDED +#define _deduce_h_INCLUDED + +#include + +struct clause; +struct kissat; + +struct clause *kissat_deduce_first_uip_clause (struct kissat *, + struct clause *); + +bool kissat_recompute_and_promote (struct kissat *, struct clause *); + +#endif diff --git a/src/sat/kissat/definition.c b/src/sat/kissat/definition.c new file mode 100644 index 000000000..759a4f906 --- /dev/null +++ b/src/sat/kissat/definition.c @@ -0,0 +1,230 @@ +#include "definition.h" +#include "allocate.h" +#include "gates.h" +#include "inline.h" +#include "kitten.h" +#include "print.h" + +typedef struct definition_extractor definition_extractor; + +struct definition_extractor { + unsigned lit; + kissat *solver; + watches *watches[2]; +}; + +static void traverse_definition_core (void *state, unsigned id) { + definition_extractor *extractor = state; + kissat *solver = extractor->solver; + watch watch; + watches *watches0 = extractor->watches[0]; + watches *watches1 = extractor->watches[1]; + const size_t size_watches0 = SIZE_WATCHES (*watches0); + assert (size_watches0 <= UINT_MAX); + unsigned sign; + if (id < size_watches0) { + watch = BEGIN_WATCHES (*watches0)[id]; + LOGWATCH (extractor->lit, watch, "gate[0]"); + sign = 0; + } else { + unsigned tmp = id - size_watches0; +#ifndef NDEBUG + const size_t size_watches1 = SIZE_WATCHES (*watches1); + assert (size_watches1 <= UINT_MAX); + assert (tmp < size_watches1); +#endif + watch = BEGIN_WATCHES (*watches1)[tmp]; + LOGWATCH (NOT (extractor->lit), watch, "gate[1]"); + sign = 1; + } + PUSH_STACK (solver->gates[sign], watch); +} + +#if !defined(NDEBUG) || !defined(NPROOFS) + +typedef struct lemma_extractor lemma_extractor; + +struct lemma_extractor { + kissat *solver; + unsigned lemmas; + unsigned unit; +}; + +static void traverse_one_sided_core_lemma (void *state, bool learned, + size_t size, + const unsigned *lits) { + if (!learned) + return; + lemma_extractor *extractor = state; + kissat *solver = extractor->solver; + const unsigned unit = extractor->unit; + unsigneds *added = &solver->added; + assert (extractor->lemmas || EMPTY_STACK (*added)); + if (size) { + PUSH_STACK (*added, size + 1); + const size_t offset = SIZE_STACK (*added); + PUSH_STACK (*added, unit); + const unsigned *end = lits + size; + for (const unsigned *p = lits; p != end; p++) + PUSH_STACK (*added, *p); + unsigned *extended = &PEEK_STACK (*added, offset); + assert (offset + size + 1 == SIZE_STACK (*added)); + CHECK_AND_ADD_LITS (size + 1, extended); + ADD_LITS_TO_PROOF (size + 1, extended); + } else { + kissat_learned_unit (solver, unit); + const unsigned *end = END_STACK (*added); + unsigned *begin = BEGIN_STACK (*added); + for (unsigned *p = begin, size; p != end; p += size) { + size = *p++; + assert (p + size <= end); + REMOVE_CHECKER_LITS (size, p); + DELETE_LITS_FROM_PROOF (size, p); + } + CLEAR_STACK (*added); + } + extractor->lemmas++; +} + +#endif + +bool kissat_find_definition (kissat *solver, unsigned lit) { + if (!GET_OPTION (definitions)) + return false; + START (definition); + struct kitten *kitten = solver->kitten; + assert (kitten); + kitten_clear (kitten); + const unsigned not_lit = NOT (lit); + definition_extractor extractor; + extractor.lit = lit; + extractor.solver = solver; + extractor.watches[0] = &WATCHES (lit); + extractor.watches[1] = &WATCHES (not_lit); + kitten_track_antecedents (kitten); + unsigned exported = 0; +#if !defined(QUIET) || !defined(NDEBUG) + size_t occs[2] = {0, 0}; +#endif + for (unsigned sign = 0; sign < 2; sign++) { + const unsigned except = sign ? not_lit : lit; + for (all_binary_large_watches (watch, *extractor.watches[sign])) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + kitten_clause_with_id_and_exception (kitten, exported, 1, &other, + INVALID_LIT); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + kitten_clause_with_id_and_exception (kitten, exported, c->size, + c->lits, except); + } +#if !defined(QUIET) || !defined(NDEBUG) + occs[sign]++; +#endif + exported++; + } + } + bool res = false; + LOG ("exported %u = %zu + %zu environment clauses to sub-solver", + exported, occs[0], occs[1]); + INC (definitions_checked); + const size_t limit = GET_OPTION (definitionticks); + kitten_set_ticks_limit (kitten, limit); + int status = kitten_solve (kitten); + if (status == 20) { + LOG ("sub-solver result UNSAT shows definition exists"); + uint64_t learned; + unsigned reduced = kitten_compute_clausal_core (kitten, &learned); + LOG ("1st sub-solver core of size %u original clauses out of %u", + reduced, exported); + for (int i = 2; i <= GET_OPTION (definitioncores); i++) { + kitten_shrink_to_clausal_core (kitten); + kitten_shuffle_clauses (kitten); + kitten_set_ticks_limit (kitten, 10 * limit); + int tmp = kitten_solve (kitten); + assert (!tmp || tmp == 20); + if (!tmp) { + LOG ("aborting core extraction"); + goto ABORT; + } +#ifndef NDEBUG + unsigned previous = reduced; +#endif + reduced = kitten_compute_clausal_core (kitten, &learned); + LOG ("%s sub-solver core of size %u original clauses out of %u", + FORMAT_ORDINAL (i), reduced, exported); + assert (reduced <= previous); +#if defined(QUIET) && defined(NDEBUG) + (void) reduced; +#endif + } + INC (definitions_extracted); + kitten_traverse_core_ids (kitten, &extractor, traverse_definition_core); + size_t size[2]; + size[0] = SIZE_STACK (solver->gates[0]); + size[1] = SIZE_STACK (solver->gates[1]); +#if !defined(QUIET) || !defined(NDEBUG) + assert (reduced == size[0] + size[1]); +#ifdef METRICS + kissat_extremely_verbose ( + solver, + "definition extracted[%" PRIu64 "] " + "size %u = %zu + %zu clauses %.0f%% " + "of %u = %zu + %zu (checked %" PRIu64 ")", + solver->statistics.definitions_extracted, reduced, size[0], size[1], + kissat_percent (reduced, exported), exported, occs[0], occs[1], + solver->statistics.definitions_checked); +#else + kissat_extremely_verbose (solver, + "definition extracted with core " + "size %u = %zu + %zu clauses %.0f%% " + "of %u = %zu + %zu", + reduced, size[0], size[1], + kissat_percent (reduced, exported), exported, + occs[0], occs[1]); +#endif +#endif + unsigned unit = INVALID_LIT; + if (!size[0]) { + unit = not_lit; + assert (size[1]); + } else if (!size[1]) + unit = lit; + + if (unit != INVALID_LIT) { + INC (definition_units); + + kissat_extremely_verbose (solver, "one sided core " + "definition extraction yields " + "failed literal"); +#if !defined(NDEBUG) || !defined(NPROOFS) + if (false +#ifndef NDEBUG + || GET_OPTION (check) > 1 +#endif +#ifndef NPROOFS + || solver->proof +#endif + ) { + lemma_extractor extractor; + extractor.solver = solver; + extractor.unit = unit; + extractor.lemmas = 0; + kitten_traverse_core_clauses (kitten, &extractor, + traverse_one_sided_core_lemma); + } else +#endif + kissat_learned_unit (solver, unit); + } + solver->gate_eliminated = GATE_ELIMINATED (definitions); + solver->resolve_gate = true; + res = true; + } else { + ABORT: + LOG ("sub-solver failed to show that definition exists"); + } + CLEAR_STACK (solver->analyzed); + STOP (definition); + return res; +} diff --git a/src/sat/kissat/definition.h b/src/sat/kissat/definition.h new file mode 100644 index 000000000..2fea782c0 --- /dev/null +++ b/src/sat/kissat/definition.h @@ -0,0 +1,10 @@ +#ifndef _definition_h_INCLUDED +#define _definition_h_INCLUDED + +#include + +struct kissat; + +bool kissat_find_definition (struct kissat *, unsigned lit); + +#endif diff --git a/src/sat/kissat/dense.c b/src/sat/kissat/dense.c new file mode 100644 index 000000000..d7592419a --- /dev/null +++ b/src/sat/kissat/dense.c @@ -0,0 +1,235 @@ +#define INLINE_SORT + +#include "dense.h" +#include "inline.h" +#include "proprobe.h" +#include "propsearch.h" +#include "trail.h" + +#include "sort.c" + +#include + +static void flush_large_watches (kissat *solver, litpairs *irredundant) { + assert (!solver->level); + assert (solver->watching); +#ifndef LOGGING + LOG ("flushing large watches"); + if (irredundant) + LOG ("flushing and saving irredundant binary clauses too"); + else + LOG ("keep watching irredundant binary clauses"); +#endif + const value *const values = solver->values; + mark *const marks = solver->marks; +#ifndef NDEBUG + for (all_literals (lit)) + assert (!marks[lit]); +#endif + size_t flushed = 0, collected = 0; +#ifdef LOGGING + size_t deduplicated = 0; +#endif + watches *all_watches = solver->watches; + unsigneds *marked = &solver->analyzed; + for (all_literals (lit)) { + const value lit_value = values[lit]; + watches *watches = all_watches + lit; + watch *begin = BEGIN_WATCHES (*watches), *q = begin; + const watch *const end_watches = END_WATCHES (*watches), *p = q; + assert (EMPTY_STACK (*marked)); + while (p != end_watches) { + const watch watch = *p++; + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + const value other_value = values[other]; + if (!lit_value && !other_value) { + const mark mark = marks[other]; + if (mark) { + if (lit < other) { + kissat_delete_binary (solver, lit, other); +#ifdef LOGGING + deduplicated++; +#endif + } + } else { + marks[other] = 1; + PUSH_STACK (*marked, other); + if (irredundant) { + const unsigned other = watch.binary.lit; + if (lit < other) { + const litpair litpair = {.lits = {lit, other}}; + PUSH_STACK (*irredundant, litpair); + } + } else + *q++ = watch; + } + } else { + assert (lit_value > 0 || other_value > 0); + if (lit < other) { + kissat_delete_binary (solver, lit, other); + collected++; + } + } + } else { + flushed++; + p++; + } + } + if (irredundant) + memset (watches, 0, sizeof *watches); + else + SET_END_OF_WATCHES (*watches, q); + for (all_stack (unsigned, other, *marked)) + marks[other] = 0; + CLEAR_ARRAY (*marked); + } + assert (EMPTY_STACK (*marked)); + LOG ("flushed %zu large watches", flushed); + LOG ("removed %zu duplicated binary clauses", deduplicated); + LOG ("collected %zu satisfied binary clauses", collected); + if (irredundant) { + LOG ("saved %zu irredundant binary clauses", SIZE_STACK (*irredundant)); + kissat_release_vectors (solver); + } + (void) collected; + (void) flushed; +} + +void kissat_enter_dense_mode (kissat *solver, litpairs *irredundant) { + assert (!solver->level); + assert (solver->watching); + assert (kissat_propagated (solver)); + LOG ("entering dense mode with full occurrence lists"); + if (irredundant) + flush_large_watches (solver, irredundant); + else + kissat_flush_large_watches (solver); + LOG ("switched to full occurrence lists"); + solver->watching = false; +} + +static void resume_watching_irredundant_binaries (kissat *solver, + litpairs *binaries) { + assert (binaries); +#ifdef LOGGING + size_t resumed_watching = 0; +#endif + watches *all_watches = solver->watches; + for (all_stack (litpair, litpair, *binaries)) { + const unsigned first = litpair.lits[0]; + const unsigned second = litpair.lits[1]; + + assert (!ELIMINATED (IDX (first))); + assert (!ELIMINATED (IDX (second))); + + watches *first_watches = all_watches + first; + watch first_watch = kissat_binary_watch (second); + PUSH_WATCHES (*first_watches, first_watch); + + watches *second_watches = all_watches + second; + watch second_watch = kissat_binary_watch (first); + PUSH_WATCHES (*second_watches, second_watch); + +#ifdef LOGGING + resumed_watching++; +#endif + } + LOG ("resumed watching %zu binary clauses", resumed_watching); +} + +static void +resume_watching_large_clauses_after_elimination (kissat *solver) { +#ifdef LOGGING + size_t resumed_watching_redundant = 0; + size_t resumed_watching_irredundant = 0; +#endif + const flags *const flags = solver->flags; + watches *watches = solver->watches; + const value *const values = solver->values; + const assigned *const assigned = solver->assigned; + ward *const arena = BEGIN_STACK (solver->arena); + + for (all_clauses (c)) { + if (c->garbage) + continue; + bool collect = false; + for (all_literals_in_clause (lit, c)) { + if (values[lit] > 0) { + LOGCLS (c, "%s satisfied", LOGLIT (lit)); + collect = true; + break; + } + const unsigned idx = IDX (lit); + if (flags[idx].eliminated) { + LOGCLS (c, "containing eliminated %s", LOGLIT (lit)); + collect = true; + break; + } + } + if (collect) { + kissat_mark_clause_as_garbage (solver, c); + continue; + } + + assert (c->size > 2); + + unsigned *lits = c->lits; + kissat_sort_literals (solver, values, assigned, c->size, lits); + c->searched = 2; + + const reference ref = (ward *) c - arena; + const unsigned l0 = lits[0]; + const unsigned l1 = lits[1]; + + kissat_push_blocking_watch (solver, watches + l0, l1, ref); + kissat_push_blocking_watch (solver, watches + l1, l0, ref); + +#ifdef LOGGING + if (c->redundant) + resumed_watching_redundant++; + else + resumed_watching_irredundant++; +#endif + } + LOG ("resumed watching %zu irredundant and %zu redundant large clauses", + resumed_watching_irredundant, resumed_watching_redundant); +} + +void kissat_resume_sparse_mode (kissat *solver, bool flush_eliminated, + litpairs *irredundant) { + assert (!solver->level); + assert (!solver->watching); + if (solver->inconsistent) + return; + LOG ("resuming sparse mode watching clauses"); + kissat_flush_large_connected (solver); + LOG ("switched to watching clauses"); + solver->watching = true; + if (irredundant) { + LOG ("resuming watching %zu irredundant binaries", + SIZE_STACK (*irredundant)); + resume_watching_irredundant_binaries (solver, irredundant); + } + if (flush_eliminated) + resume_watching_large_clauses_after_elimination (solver); + else + kissat_watch_large_clauses (solver); + LOG ("forcing to propagate units on all clauses"); + kissat_reset_propagate (solver); + + clause *conflict; + if (solver->probing) + conflict = kissat_probing_propagate (solver, 0, true); + else + conflict = kissat_search_propagate (solver); + +#ifndef NDEBUG + if (conflict) + assert (solver->inconsistent); + else + assert (kissat_trail_flushed (solver)); +#else + (void) conflict; +#endif +} diff --git a/src/sat/kissat/dense.h b/src/sat/kissat/dense.h new file mode 100644 index 000000000..171e4e5ac --- /dev/null +++ b/src/sat/kissat/dense.h @@ -0,0 +1,12 @@ +#ifndef _dense_h_INCLUDED +#define _dense_h_INCLUDED + +#include "watch.h" + +void kissat_enter_dense_mode (struct kissat *, + litpairs *saved_irredundant_binary_clauses); + +void kissat_resume_sparse_mode (struct kissat *, bool flush_eliminated, + litpairs *); + +#endif diff --git a/src/sat/kissat/dump.c b/src/sat/kissat/dump.c new file mode 100644 index 000000000..e8598b130 --- /dev/null +++ b/src/sat/kissat/dump.c @@ -0,0 +1,293 @@ +#ifndef NDEBUG + +#include "inline.h" + +#include + +static void dump_literal (kissat *solver, unsigned ilit) { + const int elit = kissat_export_literal (solver, ilit); + printf ("%u(%d)", ilit, elit); + const int value = VALUE (ilit); + if (value) { + const unsigned ilit_level = LEVEL (ilit); + printf ("@%u=%d", ilit_level, value); + } +} + +static void dump_binary (kissat *solver, unsigned a, unsigned b) { + printf ("binary clause "); + dump_literal (solver, a); + fputc (' ', stdout); + dump_literal (solver, b); + fputc ('\n', stdout); +} + +static void dump_clause (kissat *solver, clause *c) { + if (c->redundant) + printf ("redundant glue %u", c->glue); + else + printf ("irredundant"); + const reference ref = kissat_reference_clause (solver, c); + if (c->garbage) + printf (" garbage"); + printf (" clause[%u]", ref); + for (all_literals_in_clause (lit, c)) { + fputc (' ', stdout); + dump_literal (solver, lit); + } + fputc ('\n', stdout); +} + +static void dump_ref (kissat *solver, reference ref) { + clause *c = kissat_dereference_clause (solver, ref); + dump_clause (solver, c); +} + +static void dump_trail (kissat *solver) { + unsigned prev = 0; + for (unsigned level = 0; level <= solver->level; level++) { + frame *frame = &FRAME (level); + unsigned next; + if (level < solver->level) + next = frame[1].trail; + else + next = SIZE_ARRAY (solver->trail); + if (next == prev) + printf ("frame[%u] has no assignments\n", level); + else { + printf ("frame[%u] has %u assignments\n", level, next - prev); + if (prev < next) + printf ("block[%u] = trail[%u..%u]\n", level, prev, next - 1); + } + for (unsigned i = prev; i < next; i++) { + printf ("trail[%u] ", i); + const unsigned lit = PEEK_ARRAY (solver->trail, i); + dump_literal (solver, lit); + const unsigned lit_level = LEVEL (lit); + assert (lit_level <= level); + if (lit_level < level) + printf (" out-of-order"); + assigned *a = ASSIGNED (lit); + if (!lit_level) { + printf (" UNIT\n"); + assert (!a->binary); + assert (a->reason == UNIT_REASON); + } else { + fputc (' ', stdout); + if (a->binary) { + const unsigned other = a->reason; + dump_binary (solver, lit, other); + } else if (a->reason == DECISION_REASON) + printf ("DECISION\n"); + else { + assert (a->reason != UNIT_REASON); + const reference ref = a->reason; + dump_ref (solver, ref); + } + } + } + prev = next; + } +} + +static void dump_values (kissat *solver) { + for (unsigned idx = 0; idx < VARS; idx++) { + unsigned lit = LIT (idx); + int value = solver->values[lit]; + printf ("val[%u] = ", lit); + if (!value) + printf ("unassigned\n"); + else + printf ("%d\n", value); + } +} + +static void dump_queue (kissat *solver) { + const queue *const queue = &solver->queue; + printf ("queue: first %u, last %u, stamp %u, search %u (stamp %u)\n", + queue->first, queue->last, queue->stamp, queue->search.idx, + queue->search.stamp); + const links *const links = solver->links; + for (unsigned idx = queue->first; !DISCONNECTED (idx); + idx = links[idx].next) { + const struct links *l = links + idx; + printf ("%u ( prev %u, next %u, stamp %u )\n", idx, l->prev, l->next, + l->stamp); + } +} + +static void dump_scores (kissat *solver) { + heap *heap = SCORES; + printf ("scores.vars = %u\n", heap->vars); + printf ("scores.size = %u\n", heap->size); + for (unsigned i = 0; i < SIZE_STACK (heap->stack); i++) + printf ("scores.stack[%u] = %u\n", i, PEEK_STACK (heap->stack, i)); + for (unsigned i = 0; i < heap->vars; i++) + printf ("scores.score[%u] = %g\n", i, heap->score[i]); + for (unsigned i = 0; i < heap->vars; i++) + printf ("scores.pos[%u] = %u\n", i, heap->pos[i]); +} + +static void dump_export (kissat *solver) { + const unsigned size = SIZE_STACK (solver->export); + for (unsigned idx = 0; idx < size; idx++) + printf ("export[%u] = %u\n", LIT (idx), + PEEK_STACK (solver->export, idx)); +} + +void dump_map (kissat *solver) { + const unsigned size = SIZE_STACK (solver->export); + unsigned first = INVALID_LIT; + for (unsigned idx = 0; idx < size; idx++) { + const unsigned ilit = LIT (idx); + const int elit = PEEK_STACK (solver->export, idx); + printf ("map[%u] -> %d", ilit, elit); + if (elit) { + const unsigned eidx = ABS (elit); + const import *const import = &PEEK_STACK (solver->import, eidx); + if (import->eliminated) + printf (" -> eliminated[%u]", import->lit); + else { + unsigned mlit = import->lit; + if (elit < 0) + mlit = NOT (mlit); + printf (" -> %u", mlit); + } + } + if (!LEVEL (ilit) && VALUE (ilit)) { + if (first == INVALID_LIT) { + first = ilit; + printf (" #"); + } else + printf (" *"); + } + fputc ('\n', stdout); + } +} + +static void dump_import (kissat *solver) { + const unsigned size = SIZE_STACK (solver->import); + for (unsigned idx = 1; idx < size; idx++) { + import *import = &PEEK_STACK (solver->import, idx); + printf ("import[%u] = ", idx); + if (!import->imported) + printf ("undefined\n"); + else if (import->eliminated) { + unsigned pos = import->lit; + printf ("eliminated[%u]", pos); + if (pos < SIZE_STACK (solver->eliminated)) { + int value = PEEK_STACK (solver->eliminated, pos); + if (value) + printf (" (assigned to %d)", value); + } + fputc ('\n', stdout); + } else + printf ("%u\n", import->lit); + } +} + +static void dump_etrail (kissat *solver) { + for (unsigned i = 0; i < SIZE_STACK (solver->etrail); i++) + printf ("etrail[%u] = %d\n", i, (int) PEEK_STACK (solver->etrail, i)); +} + +static void dump_extend (kissat *solver) { + const extension *const begin = BEGIN_STACK (solver->extend); + const extension *const end = END_STACK (solver->extend); + for (const extension *p = begin, *q; p != end; p = q) { + assert (p->blocking); + printf ("extend[%zu] %d", (size_t) (p - begin), p->lit); + if (!p[1].blocking) + fputs (" :", stdout); + for (q = p + 1; q != end && !q->blocking; q++) + printf (" %d", q->lit); + fputc ('\n', stdout); + } +} + +static void dump_binaries (kissat *solver) { + for (all_literals (lit)) { + if (solver->watching) { + for (all_binary_blocking_watches (watch, WATCHES (lit))) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + if (lit > other) + continue; + dump_binary (solver, lit, other); + } + } else { + for (all_binary_large_watches (watch, WATCHES (lit))) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + if (lit > other) + continue; + dump_binary (solver, lit, other); + } + } + } +} + +static void dump_clauses (kissat *solver) { + for (all_clauses (c)) + dump_clause (solver, c); +} + +void kissat_dump_vectors (kissat *solver) { + vectors *vectors = &solver->vectors; + unsigneds *stack = &vectors->stack; + printf ("vectors.size = %zu\n", SIZE_STACK (*stack)); + printf ("vectors.capacity = %zu\n", CAPACITY_STACK (*stack)); + printf ("vectors.usable = %zu\n", vectors->usable); + const unsigned *const begin = BEGIN_STACK (*stack); + const unsigned *const end = END_STACK (*stack); + if (begin == end) + return; + fputc ('-', stdout); + for (const unsigned *p = begin + 1; p != end; p++) + if (*p == INVALID_LIT) + fputs (" -", stdout); + else + printf (" %u", *p); + fputc ('\n', stdout); +} + +int kissat_dump (kissat *solver) { + if (!solver) + return 0; + printf ("vars = %u\n", solver->vars); + printf ("size = %u\n", solver->size); + printf ("level = %u\n", solver->level); + printf ("active = %u\n", solver->active); + printf ("assigned = %u\n", kissat_assigned (solver)); + printf ("unassigned = %u\n", solver->unassigned); + dump_import (solver); + dump_export (solver); +#ifdef LOGGING + if (solver->compacting) + dump_map (solver); +#endif + dump_etrail (solver); + dump_extend (solver); + dump_trail (solver); + printf ("stable = %u\n", (unsigned) solver->stable); + if (solver->stable) + dump_scores (solver); + else + dump_queue (solver); + dump_values (solver); + printf ("binary = %" PRIu64 "\n", solver->statistics.clauses_binary); + printf ("irredundant = %" PRIu64 "\n", + solver->statistics.clauses_irredundant); + printf ("redundant = %" PRIu64 "\n", + solver->statistics.clauses_redundant); + dump_binaries (solver); + dump_clauses (solver); + dump_extend (solver); + return 0; +} + +#else +int kissat_dump_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/eliminate.c b/src/sat/kissat/eliminate.c new file mode 100644 index 000000000..9b7164c83 --- /dev/null +++ b/src/sat/kissat/eliminate.c @@ -0,0 +1,603 @@ +#include "eliminate.h" +#include "allocate.h" +#include "backtrack.h" +#include "collect.h" +#include "dense.h" +#include "forward.h" +#include "inline.h" +#include "inlineheap.h" +#include "kitten.h" +#include "print.h" +#include "propdense.h" +#include "report.h" +#include "resolve.h" +#include "terminate.h" +#include "trail.h" +#include "weaken.h" + +#include +#include + +bool kissat_eliminating (kissat *solver) { + if (!solver->enabled.eliminate) + return false; + statistics *statistics = &solver->statistics; + if (!statistics->clauses_irredundant) + return false; + const uint64_t conflicts = statistics->conflicts; + if (solver->last.conflicts.reduce == conflicts) + return false; + limits *limits = &solver->limits; + if (limits->eliminate.conflicts > conflicts) + return false; + if (limits->eliminate.variables.eliminate < + statistics->variables_eliminate) + return true; + if (limits->eliminate.variables.subsume < statistics->variables_subsume) + return true; + return false; +} + +static inline double variable_score (kissat *solver, unsigned idx) { + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + size_t occlim = GET_OPTION (eliminateocclim); + size_t pos = SIZE_WATCHES (WATCHES (lit)); + size_t neg = SIZE_WATCHES (WATCHES (not_lit)); + if (pos > occlim) + pos = occlim; + if (neg > occlim) + neg = occlim; + double prod = pos * neg; + double sum = pos + neg; + double occlim2 = occlim * (double) occlim; + assert (prod <= occlim2); + double score = prod - sum; + assert (score <= occlim2); + double relevancy; + if (solver->stable) + relevancy = kissat_get_heap_score (&solver->scores, idx); + else + relevancy = LINK (idx).stamp; + double res = relevancy + score - occlim2; + LOG ("variable score of %s computed as " + "%g = %g + (%zu*%zu - %zu - %zu) - %g" + " = %g + %g - %g", + LOGVAR (idx), res, relevancy, pos, neg, pos, neg, occlim2, relevancy, + score, occlim2); + return res; +} + +static inline void update_variable_score (kissat *solver, heap *schedule, + unsigned idx) { + assert (schedule->size); + assert (schedule == &solver->schedule); + double new_score = variable_score (solver, idx); + LOG ("new score %g for variable %s", new_score, LOGVAR (idx)); + kissat_update_heap (solver, schedule, idx, -new_score); +} + +void kissat_update_variable_score (kissat *solver, unsigned idx) { + update_variable_score (solver, &solver->schedule, idx); +} + +static inline void update_after_adding_stack (kissat *solver, + unsigneds *stack) { + assert (!solver->probing); + heap *schedule = &solver->schedule; + if (!schedule->size) + return; + for (all_stack (unsigned, lit, *stack)) + update_variable_score (solver, schedule, IDX (lit)); +} + +static inline void update_after_removing_variable (kissat *solver, + unsigned idx) { + heap *schedule = &solver->schedule; + if (!schedule->size) + return; + assert (!solver->probing); + flags *f = solver->flags + idx; + if (f->fixed) + return; + assert (!f->eliminated); + update_variable_score (solver, schedule, idx); + if (!kissat_heap_contains (schedule, idx)) + kissat_push_heap (solver, schedule, idx); +} + +static inline void update_after_removing_clause (kissat *solver, clause *c, + unsigned except) { + if (!solver->schedule.size) + return; + assert (c->garbage); + for (all_literals_in_clause (lit, c)) + if (lit != except) + update_after_removing_variable (solver, IDX (lit)); +} + +void kissat_eliminate_binary (kissat *solver, unsigned lit, + unsigned other) { + kissat_disconnect_binary (solver, other, lit); + kissat_delete_binary (solver, lit, other); + update_after_removing_variable (solver, IDX (other)); +} + +void kissat_eliminate_clause (kissat *solver, clause *c, unsigned lit) { + kissat_mark_clause_as_garbage (solver, c); + update_after_removing_clause (solver, c, lit); +} + +static unsigned schedule_variables (kissat *solver) { + LOG ("initializing variable schedule"); + assert (!solver->schedule.size); + + kissat_resize_heap (solver, &solver->schedule, solver->vars); + + flags *all_flags = solver->flags; + + size_t scheduled = 0; + for (all_variables (idx)) { + flags *flags = all_flags + idx; + if (!flags->active) + continue; + if (!flags->eliminate) + continue; + LOG ("scheduling %s", LOGVAR (idx)); + scheduled++; + update_after_removing_variable (solver, idx); + } + assert (scheduled == kissat_size_heap (&solver->schedule)); +#ifndef QUIET + size_t active = solver->active; + kissat_phase (solver, "eliminate", GET (eliminations), + "scheduled %zu variables %.0f%%", scheduled, + kissat_percent (scheduled, active)); +#endif + return scheduled; +} + +void kissat_flush_units_while_connected (kissat *solver) { + const unsigned *propagate = solver->propagate; + const unsigned *end_trail = END_ARRAY (solver->trail); + assert (propagate <= end_trail); + const size_t units = end_trail - propagate; + if (!units) + return; +#ifdef LOGGING + LOG ("propagating and flushing %zu units", units); +#endif + if (!kissat_dense_propagate (solver)) + return; + LOG ("marking and flushing unit satisfied clauses"); + + end_trail = END_ARRAY (solver->trail); + while (propagate != end_trail) { + const unsigned unit = *propagate++; + watches *unit_watches = &WATCHES (unit); + watch *begin = BEGIN_WATCHES (*unit_watches), *q = begin; + const watch *const end = END_WATCHES (*unit_watches), *p = q; + if (begin == end) + continue; + LOG ("marking %s satisfied clauses as garbage", LOGLIT (unit)); + while (p != end) { + const watch watch = *q++ = *p++; + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + if (!solver->values[other]) + update_after_removing_variable (solver, IDX (other)); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + if (!c->garbage) + kissat_eliminate_clause (solver, c, unit); + assert (c->garbage); + q--; + } + } + assert (q <= end); + size_t flushed = end - q; + if (!flushed) + continue; + LOG ("flushing %zu references satisfied by %s", flushed, LOGLIT (unit)); + SET_END_OF_WATCHES (*unit_watches, q); + } +} + +static void connect_resolvents (kissat *solver) { + const value *const values = solver->values; + assert (EMPTY_STACK (solver->clause)); + bool satisfied = false; +#ifdef LOGGING + uint64_t added = 0; +#endif + for (all_stack (unsigned, other, solver->resolvents)) { + if (other == INVALID_LIT) { + if (satisfied) + satisfied = false; + else { + LOGTMP ("temporary resolvent"); + const size_t size = SIZE_STACK (solver->clause); + if (!size) { + assert (!solver->inconsistent); + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + solver->inconsistent = true; + break; + } else if (size == 1) { + const unsigned unit = PEEK_STACK (solver->clause, 0); + LOG ("resolved unit clause %s", LOGLIT (unit)); + kissat_learned_unit (solver, unit); + } else { + assert (size > 1); + (void) kissat_new_irredundant_clause (solver); + update_after_adding_stack (solver, &solver->clause); +#ifdef LOGGING + added++; +#endif + } + } + CLEAR_STACK (solver->clause); + } else if (!satisfied) { + const value value = values[other]; + if (value > 0) { + LOGTMP ("now %s satisfied resolvent", LOGLIT (other)); + satisfied = true; + } else if (value < 0) + LOG2 ("dropping now falsified literal %s", LOGLIT (other)); + else + PUSH_STACK (solver->clause, other); + } + } + LOG ("added %" PRIu64 " new clauses", added); + CLEAR_STACK (solver->resolvents); +} + +static void weaken_clauses (kissat *solver, unsigned lit) { + const unsigned not_lit = NOT (lit); + + const value *const values = solver->values; + assert (!values[lit]); + + watches *pos_watches = &WATCHES (lit); + + for (all_binary_large_watches (watch, *pos_watches)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + const value value = values[other]; + if (value <= 0) + kissat_weaken_binary (solver, lit, other); + kissat_eliminate_binary (solver, lit, other); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + if (c->garbage) + continue; + bool satisfied = false; + for (all_literals_in_clause (other, c)) { + const value value = values[other]; + if (value <= 0) + continue; + satisfied = true; + break; + } + if (!satisfied) + kissat_weaken_clause (solver, lit, c); + LOGCLS (c, "removing %s", LOGLIT (lit)); + kissat_eliminate_clause (solver, c, lit); + } + } + RELEASE_WATCHES (*pos_watches); + + watches *neg_watches = &WATCHES (not_lit); + + bool optimize = !GET_OPTION (incremental); + for (all_binary_large_watches (watch, *neg_watches)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + const value value = values[other]; + if (!optimize && value <= 0) + kissat_weaken_binary (solver, not_lit, other); + kissat_eliminate_binary (solver, not_lit, other); + } else { + const reference ref = watch.large.ref; + clause *d = kissat_dereference_clause (solver, ref); + if (d->garbage) + continue; + bool satisfied = false; + for (all_literals_in_clause (other, d)) { + const value value = values[other]; + if (value <= 0) + continue; + satisfied = true; + break; + } + if (!optimize && !satisfied) + kissat_weaken_clause (solver, not_lit, d); + LOGCLS (d, "removing %s", LOGLIT (not_lit)); + kissat_eliminate_clause (solver, d, not_lit); + } + } + if (optimize && !EMPTY_WATCHES (*neg_watches)) + kissat_weaken_unit (solver, not_lit); + RELEASE_WATCHES (*neg_watches); + + kissat_flush_units_while_connected (solver); +} + +static void try_to_eliminate_all_variables_again (kissat *solver) { + LOG ("trying to elimination all variables again"); + flags *all_flags = solver->flags; + for (all_variables (idx)) { + flags *flags = all_flags + idx; + flags->eliminate = true; + } + solver->limits.eliminate.variables.eliminate = 0; +} + +static void set_next_elimination_bound (kissat *solver, bool complete) { + const unsigned max_bound = GET_OPTION (eliminatebound); + const unsigned current_bound = + solver->bounds.eliminate.additional_clauses; + assert (current_bound <= max_bound); + + if (complete) { + if (current_bound == max_bound) { + kissat_phase (solver, "eliminate", GET (eliminations), + "completed maximum elimination bound %u", + current_bound); + limits *limits = &solver->limits; + statistics *statistics = &solver->statistics; + limits->eliminate.variables.eliminate = + statistics->variables_eliminate; + limits->eliminate.variables.subsume = statistics->variables_subsume; +#ifndef QUIET + bool first = !solver->bounds.eliminate.max_bound_completed++; + REPORT (!first, first ? '!' : ':'); +#endif + } else { + const unsigned next_bound = + !current_bound ? 1 : MIN (2 * current_bound, max_bound); + kissat_phase (solver, "eliminate", GET (eliminations), + "completed elimination bound %u next %u", current_bound, + next_bound); + solver->bounds.eliminate.additional_clauses = next_bound; + try_to_eliminate_all_variables_again (solver); + REPORT (0, '^'); + } + } else + kissat_phase (solver, "eliminate", GET (eliminations), + "incomplete elimination bound %u", current_bound); +} + +static bool can_eliminate_variable (kissat *solver, unsigned idx) { + flags *flags = FLAGS (idx); + + if (!flags->active) + return false; + if (!flags->eliminate) + return false; + + return true; +} + +static bool eliminate_variable (kissat *solver, unsigned idx) { + LOG ("next elimination candidate %s", LOGVAR (idx)); +#ifdef LOGGING + if (GET_OPTION (log)) + (void) variable_score (solver, idx); +#endif + + assert (!solver->inconsistent); + assert (can_eliminate_variable (solver, idx)); + + LOG ("marking %s as not removed", LOGVAR (idx)); + FLAGS (idx)->eliminate = false; + + unsigned lit; + if (!kissat_generate_resolvents (solver, idx, &lit)) + return false; + connect_resolvents (solver); + if (!solver->inconsistent) + weaken_clauses (solver, lit); + INC (eliminated); + kissat_mark_eliminated_variable (solver, idx); + if (solver->gate_eliminated) { + INC (gates_eliminated); +#ifdef METRICS + assert (*solver->gate_eliminated < UINT64_MAX); + *solver->gate_eliminated += 1; +#endif + } + return true; +} + +static void eliminate_variables (kissat *solver) { + kissat_very_verbose (solver, + "trying to eliminate variables with bound %u", + solver->bounds.eliminate.additional_clauses); + assert (!solver->inconsistent); +#ifndef QUIET + unsigned before = solver->active; + unsigned eliminated = 0; + uint64_t tried = 0; +#endif + unsigned last_round_eliminated = 0; + + SET_EFFORT_LIMIT (resolution_limit, eliminate, eliminate_resolutions); + + bool complete; + int round = 0; + + const bool forward = GET_OPTION (forward); + + for (;;) { + round++; + LOG ("starting new elimination round %d", round); + + if (forward) { + unsigned *propagate = solver->propagate; + complete = kissat_forward_subsume_during_elimination (solver); + if (solver->inconsistent) + break; + kissat_flush_large_connected (solver); + kissat_connect_irredundant_large_clauses (solver); + solver->propagate = propagate; + kissat_flush_units_while_connected (solver); + if (solver->inconsistent) + break; + } else { + kissat_connect_irredundant_large_clauses (solver); + complete = true; + } + +#ifndef QUIET + const unsigned last_round_scheduled = +#endif + schedule_variables (solver); + kissat_very_verbose ( + solver, + "scheduled %u variables %.0f%% to eliminate " + "in round %d", + last_round_scheduled, + kissat_percent (last_round_scheduled, solver->active), round); + + unsigned last_round_eliminated = 0; + + while (!solver->inconsistent && + !kissat_empty_heap (&solver->schedule)) { + if (TERMINATED (eliminate_terminated_1)) { + complete = false; + break; + } + unsigned idx = kissat_pop_max_heap (solver, &solver->schedule); + if (!can_eliminate_variable (solver, idx)) + continue; + statistics *s = &solver->statistics; + if (s->eliminate_resolutions > resolution_limit) { + kissat_extremely_verbose ( + solver, + "eliminate round %u hits " + "resolution limit %" PRIu64 " at %" PRIu64 " resolutions", + round, resolution_limit, s->eliminate_resolutions); + complete = false; + break; + } +#ifndef QUIET + tried++; +#endif + if (eliminate_variable (solver, idx)) + last_round_eliminated++; + if (solver->inconsistent) + break; + kissat_flush_units_while_connected (solver); + } + + if (last_round_eliminated) { + complete = false; +#ifndef QUIET + eliminated += last_round_eliminated; +#endif + } + + if (!solver->inconsistent) { + kissat_flush_large_connected (solver); + kissat_dense_collect (solver); + } + + kissat_phase ( + solver, "eliminate", GET (eliminations), + "eliminated %u variables %.0f%% in round %u", last_round_eliminated, + kissat_percent (last_round_eliminated, last_round_scheduled), + round); + REPORT (!last_round_eliminated, 'e'); + + if (solver->inconsistent) + break; + kissat_release_heap (solver, &solver->schedule); + if (complete) + break; + if (round == GET_OPTION (eliminaterounds)) + break; + if (solver->statistics.eliminate_resolutions > resolution_limit) + break; + if (TERMINATED (eliminate_terminated_2)) + break; + } + + const unsigned remain = kissat_size_heap (&solver->schedule); + kissat_release_heap (solver, &solver->schedule); +#ifndef QUIET + kissat_very_verbose (solver, + "eliminated %u variables %.0f%% of %" PRIu64 " tried" + " (%u remain %.0f%%)", + eliminated, kissat_percent (eliminated, tried), + tried, remain, + kissat_percent (remain, solver->active)); + kissat_phase (solver, "eliminate", GET (eliminations), + "eliminated %u variables %.0f%% out of %u in %d rounds", + eliminated, kissat_percent (eliminated, before), before, + round); +#endif + if (!solver->inconsistent) { + const bool complete = !remain && !last_round_eliminated; + set_next_elimination_bound (solver, complete); + if (!complete) { + const flags *end = solver->flags + VARS; +#ifndef QUIET + unsigned dropped = 0; +#endif + for (struct flags *f = solver->flags; f != end; f++) + if (f->eliminate) { + f->eliminate = false; +#ifndef QUIET + dropped++; +#endif + } + + kissat_very_verbose (solver, "dropping %u eliminate candidates", + dropped); + } + } +} + +static void init_map_and_kitten (kissat *solver) { + if (!GET_OPTION (definitions)) + return; + assert (!solver->kitten); + solver->kitten = kitten_embedded (solver); +} + +static void reset_map_and_kitten (kissat *solver) { + if (solver->kitten) { + kitten_release (solver->kitten); + solver->kitten = 0; + } +} + +static void eliminate (kissat *solver) { + kissat_backtrack_propagate_and_flush_trail (solver); + assert (!solver->inconsistent); + STOP_SEARCH_AND_START_SIMPLIFIER (eliminate); + kissat_phase (solver, "eliminate", GET (eliminations), + "elimination limit of %" PRIu64 " conflicts hit", + solver->limits.eliminate.conflicts); + init_map_and_kitten (solver); + kissat_enter_dense_mode (solver, 0); + eliminate_variables (solver); + kissat_resume_sparse_mode (solver, true, 0); + reset_map_and_kitten (solver); + kissat_check_statistics (solver); + STOP_SIMPLIFIER_AND_RESUME_SEARCH (eliminate); +} + +int kissat_eliminate (kissat *solver) { + assert (!solver->inconsistent); + INC (eliminations); + eliminate (solver); + kissat_classify (solver); + UPDATE_CONFLICT_LIMIT (eliminate, eliminations, NLOG2N, true); + solver->last.ticks.eliminate = solver->statistics.search_ticks; + return solver->inconsistent ? 20 : 0; +} diff --git a/src/sat/kissat/eliminate.h b/src/sat/kissat/eliminate.h new file mode 100644 index 000000000..0d6cfe9a5 --- /dev/null +++ b/src/sat/kissat/eliminate.h @@ -0,0 +1,19 @@ +#ifndef _eliminate_hpp_INCLUDED +#define _eliminate_hpp_INCLUDED + +#include + +struct kissat; +struct clause; +struct heap; + +void kissat_flush_units_while_connected (struct kissat *); + +bool kissat_eliminating (struct kissat *); +int kissat_eliminate (struct kissat *); + +void kissat_eliminate_binary (struct kissat *, unsigned, unsigned); +void kissat_eliminate_clause (struct kissat *, struct clause *, unsigned); +void kissat_update_variable_score (struct kissat *, unsigned idx); + +#endif diff --git a/src/sat/kissat/equivalences.c b/src/sat/kissat/equivalences.c new file mode 100644 index 000000000..c06215d24 --- /dev/null +++ b/src/sat/kissat/equivalences.c @@ -0,0 +1,37 @@ +#include "gates.h" +#include "inlinevector.h" +#include "logging.h" + +bool kissat_find_equivalence_gate (kissat *solver, unsigned lit) { + if (!GET_OPTION (equivalences)) + return false; + if (!kissat_mark_binaries (solver, lit)) + return false; + value *marks = solver->marks; + unsigned not_lit = NOT (lit); + watches *watches = &WATCHES (not_lit); + unsigned replace = INVALID_LIT; + for (all_binary_large_watches (watch, *watches)) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + const unsigned not_other = NOT (other); + if (!marks[not_other]) + continue; + replace = other; + break; + } + kissat_unmark_binaries (solver, lit); + if (replace == INVALID_LIT) + return false; + LOG ("found equivalence gate %s = %s", LOGLIT (lit), LOGLIT (replace)); + + const watch watch1 = kissat_binary_watch (replace); + PUSH_STACK (solver->gates[1], watch1); + + const watch watch0 = kissat_binary_watch (NOT (replace)); + PUSH_STACK (solver->gates[0], watch0); + solver->gate_eliminated = GATE_ELIMINATED (equivalences); + INC (equivalences_extracted); + return true; +} diff --git a/src/sat/kissat/equivalences.h b/src/sat/kissat/equivalences.h new file mode 100644 index 000000000..ad9c14d57 --- /dev/null +++ b/src/sat/kissat/equivalences.h @@ -0,0 +1,10 @@ +#ifndef _equivs_h_INCLUDED +#define _equivs_h_INCLUDED + +#include + +struct kissat; + +bool kissat_find_equivalence_gate (struct kissat *, unsigned lit); + +#endif diff --git a/src/sat/kissat/error.c b/src/sat/kissat/error.c new file mode 100644 index 000000000..c4c55d18d --- /dev/null +++ b/src/sat/kissat/error.c @@ -0,0 +1,62 @@ +#include "error.h" +#include "colors.h" +#include "cover.h" + +#include +#include + +static void (*kissat_abort_function) (void); + +void kissat_call_function_instead_of_abort (void (*f) (void)) { + kissat_abort_function = f; +} + +// clang-format off + +void +kissat_abort (void) +{ + if (kissat_abort_function) + { FLUSH_COVERAGE (); kissat_abort_function (); } // Keep all in this line. + else + { FLUSH_COVERAGE (); abort (); } // Keep all in this line. +} + +// clang-format on + +static void typed_error_message_start (const char *type) { + fflush (stdout); + TERMINAL (stderr, 2); + COLOR (BOLD); + fputs ("kissat: ", stderr); + COLOR (RED); + fputs (type, stderr); + fputs (": ", stderr); + COLOR (NORMAL); +} + +void kissat_fatal_message_start (void) { + typed_error_message_start ("fatal error"); +} + +static void vprint_error (const char *type, const char *fmt, va_list *ap) { + typed_error_message_start (type); + vfprintf (stderr, fmt, *ap); + fputc ('\n', stderr); + fflush (stderr); +} + +void kissat_error (const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + vprint_error ("error", fmt, &ap); + va_end (ap); +} + +void kissat_fatal (const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + vprint_error ("fatal error", fmt, &ap); + va_end (ap); + kissat_abort (); +} diff --git a/src/sat/kissat/error.h b/src/sat/kissat/error.h new file mode 100644 index 000000000..69ee50706 --- /dev/null +++ b/src/sat/kissat/error.h @@ -0,0 +1,18 @@ +#ifndef _error_h_INCLUDED +#define _error_h_INCLUDED + +#include "attribute.h" + +// clang-format off + +void kissat_error (const char *fmt, ...) ATTRIBUTE_FORMAT (1, 2); +void kissat_fatal (const char *fmt, ...) ATTRIBUTE_FORMAT (1, 2); + +void kissat_fatal_message_start (void); + +void kissat_call_function_instead_of_abort (void (*)(void)); +void kissat_abort (void); + +// clang-format on + +#endif diff --git a/src/sat/kissat/extend.c b/src/sat/kissat/extend.c new file mode 100644 index 000000000..cd0dfa006 --- /dev/null +++ b/src/sat/kissat/extend.c @@ -0,0 +1,181 @@ +#include "colors.h" +#include "inline.h" + +static void undo_eliminated_assignment (kissat *solver) { + size_t size_etrail = SIZE_STACK (solver->etrail); +#ifdef LOGGING + size_t size_eliminated = SIZE_STACK (solver->eliminated); +#endif + if (!size_etrail) { + LOG ("all %zu eliminated variables are unassigned", size_eliminated); + return; + } + + LOG ("unassigning %zu eliminated variables %.0f%%", size_etrail, + kissat_percent (size_etrail, size_eliminated)); + + value *values = BEGIN_STACK (solver->eliminated); + + while (!EMPTY_STACK (solver->etrail)) { + const unsigned pos = POP_STACK (solver->etrail); + assert (pos < SIZE_STACK (solver->eliminated)); + assert (values[pos]); + LOG2 ("unassigned eliminated[%u] external variable", pos); + values[pos] = 0; + } +} + +static void extend_assign (kissat *solver, value *values, int lit) { + assert (lit); + assert (lit != INT_MIN); + const unsigned idx = ABS (lit); + import *import = &PEEK_STACK (solver->import, idx); + assert (import->eliminated); + assert (import->imported); + const unsigned pos = import->lit; + assert (pos < SIZE_STACK (solver->eliminated)); + const value value = lit < 0 ? -1 : 1; + values[pos] = value; + assert (kissat_value (solver, lit) == lit); + LOG ("assigned eliminated[%u] external literal %d", pos, value * idx); + PUSH_STACK (solver->etrail, pos); +} + +void kissat_extend (kissat *solver) { + assert (!EMPTY_STACK (solver->extend)); + assert (!solver->extended); + + START (extend); + solver->extended = true; + + undo_eliminated_assignment (solver); + + LOG ("extending solution with reconstruction stack of size %zu", + SIZE_STACK (solver->extend)); + + value *evalues = BEGIN_STACK (solver->eliminated); + value *ivalues = solver->values; + + const import *const imports = BEGIN_STACK (solver->import); + + const extension *const begin = BEGIN_STACK (solver->extend); + extension const *p = END_STACK (solver->extend); + +#ifdef LOGGING + size_t assigned = 0; + size_t flipped = 0; +#endif + + while (p != begin) { + unsigned pos = UINT_MAX; + bool satisfied = false; + + int eliminated = 0; + int blocking = 0; + + size_t size = 0; +#ifndef LOGGING + (void) size; +#endif + do { + size++; + assert (begin < p); + const extension ext = *--p; + const int elit = ext.lit; + if (ext.blocking) + blocking = elit; + + if (satisfied) + continue; + + assert (elit != INT_MIN); + const unsigned eidx = ABS (elit); + assert (eidx < SIZE_STACK (solver->import)); + const import *const import = imports + eidx; + assert (import->imported); + + if (import->eliminated) { + const unsigned tmp = import->lit; + assert (tmp < SIZE_STACK (solver->eliminated)); + value value = evalues[tmp]; + + if (elit < 0) + value = -value; + + if (value > 0) { + LOG2 ("previously assigned eliminated literal %d " + "satisfies clause", + elit); + satisfied = true; + } else if (!value && (!eliminated || pos < tmp)) { +#ifdef LOGGING + if (eliminated) + LOG2 ("earlier unassigned eliminated literal %d", elit); + else + LOG2 ("found unassigned eliminated literal %d", elit); +#endif + eliminated = elit; + pos = tmp; + } + } else { + const unsigned ilit = import->lit; + value value = ivalues[ilit]; + assert (value); + + if (elit < 0) + value = -value; + + if (value > 0) { + LOG2 ("internal literal %s satisfies clause", LOGLIT (ilit)); + satisfied = true; + } + } + } while (!blocking); + + if (satisfied) { + LOGEXT2 (size, p, "satisfied"); + continue; + } + + if (eliminated && eliminated != blocking) { + LOGEXT2 (size, p, + "assigning eliminated unassigned external literal %d " + "to satisfy size %zu witness labelled clause at", + eliminated, size); + extend_assign (solver, evalues, eliminated); +#ifdef LOGGING + assigned++; +#endif + continue; + } + +#ifdef LOGGING + const unsigned blocking_idx = ABS (blocking); + assert (blocking_idx < SIZE_STACK (solver->import)); + assert (imports[blocking_idx].eliminated); + const unsigned blocking_pos = imports[blocking_idx].lit; + assert (blocking_pos < SIZE_STACK (solver->eliminated)); + const value blocking_value = evalues[blocking_pos]; + LOGEXT2 (size, p, + "%s blocking external literal %d " + "to satisfy size %zu witness labelled clause at", + blocking_value ? "flipping" : "assigning", blocking, size); + if (blocking_value) + flipped++; + else + assigned++; +#endif + extend_assign (solver, evalues, blocking); + } + +#ifdef LOGGING + size_t total = SIZE_STACK (solver->eliminated); + LOG ("assigned %zu external variables %.0f%% out of %zu eliminated", + assigned, kissat_percent (assigned, total), total); + LOG ("flipped %zu external variables %.0f%% out of %zu assigned", flipped, + kissat_percent (flipped, assigned), assigned); + LOG ("extended assignment complete"); +#endif + + STOP (extend); +} diff --git a/src/sat/kissat/extend.h b/src/sat/kissat/extend.h new file mode 100644 index 000000000..049028b25 --- /dev/null +++ b/src/sat/kissat/extend.h @@ -0,0 +1,30 @@ +#ifndef _extend_h_INCLUDED +#define _extend_h_INCLUDED + +#include "stack.h" +#include "utilities.h" + +typedef struct extension extension; + +struct extension { + signed int lit : 31; + bool blocking : 1; +}; + +// clang-format off +typedef STACK (extension) extensions; +// clang-format on + +static inline extension kissat_extension (bool blocking, int lit) { + assert (ABS (lit) < (1 << 30)); + extension res; + res.blocking = blocking; + res.lit = lit; + return res; +} + +struct kissat; + +void kissat_extend (struct kissat *solver); + +#endif diff --git a/src/sat/kissat/factor.c b/src/sat/kissat/factor.c new file mode 100644 index 000000000..e234d0957 --- /dev/null +++ b/src/sat/kissat/factor.c @@ -0,0 +1,1136 @@ +#include "factor.h" +#include "bump.h" +#include "clause.h" +#include "dense.h" +#include "heap.h" +#include "import.h" +#include "inline.h" +#include "inlineheap.h" +#include "inlinequeue.h" +#include "inlinevector.h" +#include "internal.h" +#include "logging.h" +#include "print.h" +#include "report.h" +#include "sort.h" +#include "terminate.h" +#include "vector.h" +#include "watch.h" + +#include + +#define FACTOR 1 +#define QUOTIENT 2 +#define NOUNTED 4 + +struct quotient { + size_t id; + struct quotient *prev, *next; + unsigned factor; + statches clauses; + sizes matches; + size_t matched; +}; + +typedef struct quotient quotient; + +struct scores { + double *score; + unsigneds scored; +}; + +typedef struct scores scores; + +struct factoring { + kissat *solver; + size_t size, allocated; + unsigned initial; + unsigned *count; + scores *scores; + unsigned hops; + unsigned bound; + unsigneds fresh; + unsigneds counted; + unsigneds nounted; + references qlauses; + uint64_t limit; + struct { + quotient *first, *last; + } quotients; + heap schedule; +}; + +typedef struct factoring factoring; + +static void init_factoring (kissat *solver, factoring *factoring, + uint64_t limit) { + memset (factoring, 0, sizeof *factoring); + factoring->solver = solver; + factoring->initial = factoring->allocated = factoring->size = LITS; + factoring->limit = limit; + factoring->bound = solver->bounds.eliminate.additional_clauses; + if (GET_OPTION (factorstructural)) + factoring->hops = GET_OPTION (factorhops); + const unsigned hops = factoring->hops; + if (hops) { + CALLOC (factoring->scores, hops); + for (unsigned i = 0; i != hops; i++) { + scores *scores = factoring->scores + i; + NALLOC (scores->score, VARS); + double *score = scores->score; + for (all_variables (idx)) + score[idx] = -1; + } + } + CALLOC (factoring->count, factoring->allocated); +#ifndef NDEBUG + for (all_literals (lit)) + assert (!solver->marks[lit]); +#endif +} + +static void release_quotients (factoring *factoring) { + kissat *const solver = factoring->solver; + mark *marks = solver->marks; + for (quotient *q = factoring->quotients.first, *next; q; q = next) { + next = q->next; + unsigned factor = q->factor; + assert (marks[factor] == FACTOR); + marks[factor] = 0; + RELEASE_STACK (q->clauses); + RELEASE_STACK (q->matches); + kissat_free (solver, q, sizeof *q); + } + const unsigned hops = factoring->hops; + if (hops) { + for (unsigned i = 0; i != hops; i++) { + scores *scores = factoring->scores + i; + unsigneds *scored = &scores->scored; + double *score = scores->score; + while (!EMPTY_STACK (*scored)) { + unsigned idx = POP_STACK (*scored); + score[idx] = -1; + } + } + } + factoring->quotients.first = factoring->quotients.last = 0; +} + +static void release_factoring (factoring *factoring) { + kissat *const solver = factoring->solver; + assert (EMPTY_STACK (solver->analyzed)); + assert (EMPTY_STACK (factoring->counted)); + assert (EMPTY_STACK (factoring->nounted)); + assert (EMPTY_STACK (factoring->qlauses)); + DEALLOC (factoring->count, factoring->allocated); + RELEASE_STACK (factoring->counted); + RELEASE_STACK (factoring->nounted); + RELEASE_STACK (factoring->fresh); + RELEASE_STACK (factoring->qlauses); + release_quotients (factoring); + kissat_release_heap (solver, &factoring->schedule); + assert (!(factoring->allocated & 1)); + const size_t allocated_score = factoring->allocated / 2; + const unsigned hops = factoring->hops; + if (hops) { + for (unsigned i = 0; i != hops; i++) { + scores *scores = factoring->scores + i; + double *score = scores->score; + DEALLOC (score, allocated_score); + RELEASE_STACK (scores->scored); + } + DEALLOC (factoring->scores, hops); + } +#ifndef NDEBUG + for (all_literals (lit)) + assert (!solver->marks[lit]); +#endif +} + +static void update_candidate (factoring *factoring, unsigned lit) { + heap *cands = &factoring->schedule; + kissat *const solver = factoring->solver; + const size_t size = SIZE_WATCHES (solver->watches[lit]); + if (size > 1) { + kissat_adjust_heap (solver, cands, lit); + kissat_update_heap (solver, cands, lit, size); + if (!kissat_heap_contains (cands, lit)) + kissat_push_heap (solver, cands, lit); + } else if (kissat_heap_contains (cands, lit)) + kissat_pop_heap (solver, cands, lit); +} + +static void schedule_factorization (factoring *factoring) { + kissat *const solver = factoring->solver; + flags *flags = solver->flags; + for (all_variables (idx)) { + if (ACTIVE (idx)) { + struct flags *f = flags + idx; + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + if (f->factor & 1) + update_candidate (factoring, lit); + if (f->factor & 2) + update_candidate (factoring, not_lit); + } + } +#ifndef QUIET + heap *cands = &factoring->schedule; + size_t size_cands = kissat_size_heap (cands); + kissat_very_verbose ( + solver, "scheduled %zu factorization candidate literals %.0f %%", + size_cands, kissat_percent (size_cands, LITS)); +#endif +} + +static quotient *new_quotient (factoring *factoring, unsigned factor) { + kissat *const solver = factoring->solver; + mark *marks = solver->marks; + assert (!marks[factor]); + marks[factor] = FACTOR; + quotient *res = kissat_malloc (solver, sizeof *res); + memset (res, 0, sizeof *res); + res->factor = factor; + quotient *last = factoring->quotients.last; + 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; + } + factoring->quotients.last = res; + res->prev = last; + LOG ("new quotient[%zu] with factor %s", res->id, LOGLIT (factor)); + return res; +} + +static size_t first_factor (factoring *factoring, unsigned factor) { + kissat *const solver = factoring->solver; + watches *all_watches = solver->watches; + watches *factor_watches = all_watches + factor; + assert (!factoring->quotients.first); + quotient *quotient = new_quotient (factoring, factor); + statches *clauses = "ient->clauses; + uint64_t ticks = 0; + for (all_binary_large_watches (watch, *factor_watches)) { + PUSH_STACK (*clauses, watch); +#ifndef NDEBUG + if (watch.type.binary) + continue; + const reference ref = watch.large.ref; + clause *const c = kissat_dereference_clause (solver, ref); + assert (!c->quotient); +#endif + ticks++; + } + size_t res = SIZE_STACK (*clauses); + LOG ("quotient[0] factor %s size %zu", LOGLIT (factor), res); + assert (res > 1); + ADD (factor_ticks, ticks); + return res; +} + +static void clear_nounted (kissat *solver, unsigneds *nounted) { + mark *marks = solver->marks; + for (all_stack (unsigned, lit, *nounted)) { + assert (marks[lit] & NOUNTED); + marks[lit] &= ~NOUNTED; + } + CLEAR_STACK (*nounted); +} + +static void clear_qlauses (kissat *solver, references *qlauses) { + ward *const arena = BEGIN_STACK (solver->arena); + for (all_stack (reference, ref, *qlauses)) { + clause *const c = (clause *) (arena + ref); + assert (c->quotient); + c->quotient = false; + } + CLEAR_STACK (*qlauses); +} + +static double distinct_paths (factoring *factoring, unsigned src_lit, + unsigned dst_idx, unsigned hops) { + kissat *const solver = factoring->solver; + const unsigned src_idx = IDX (src_lit); + bool matched = (src_idx == dst_idx); + if (!hops) + return matched; + const unsigned next_hops = hops - 1; + scores *scores = &factoring->scores[next_hops]; + double *score = scores->score; + double res = score[src_idx]; + if (res >= 0) + return res; + res = matched; + for (unsigned sign = 0; sign != 2; sign++) { + const unsigned signed_src_lit = src_lit ^ sign; + watches *const watches = &WATCHES (signed_src_lit); + uint64_t ticks = + 1 + kissat_cache_lines (SIZE_WATCHES (*watches), sizeof (watch)); + for (all_binary_large_watches (watch, *watches)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + res += distinct_paths (factoring, other, dst_idx, next_hops); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + ticks++; + for (all_literals_in_clause (other, c)) + if (other != signed_src_lit) + res += distinct_paths (factoring, other, dst_idx, next_hops); + } + } + ADD (factor_ticks, ticks); + } + assert (res >= 0); + score[src_idx] = res; + unsigneds *scored = &scores->scored; + PUSH_STACK (*scored, src_idx); + LOG ("caching %g distinct paths from %s to %s in %u hops", res, + LOGVAR (src_idx), LOGVAR (dst_idx), hops); + return res; +} + +static double structural_score (factoring *factoring, unsigned lit) { + const quotient *first_quotient = factoring->quotients.first; + assert (first_quotient); + const unsigned first_factor = first_quotient->factor; +#ifndef NDEBUG + kissat *const solver = factoring->solver; +#endif + const unsigned first_factor_idx = IDX (first_factor); + return distinct_paths (factoring, lit, first_factor_idx, factoring->hops); +} + +static double watches_score (factoring *factoring, unsigned lit) { + kissat *const solver = factoring->solver; + watches *watches = solver->watches + lit; + double res = SIZE_WATCHES (*watches); + LOG ("watches score %g of %s", res, LOGLIT (lit)); + return res; +} + +static double tied_next_factor_score (factoring *factoring, unsigned lit) { + if (factoring->hops) + return structural_score (factoring, lit); + else + return watches_score (factoring, lit); +} + +static unsigned next_factor (factoring *factoring, + unsigned *next_count_ptr) { + quotient *last_quotient = factoring->quotients.last; + assert (last_quotient); + statches *last_clauses = &last_quotient->clauses; + kissat *const solver = factoring->solver; + watches *all_watches = solver->watches; + unsigned *count = factoring->count; + unsigneds *counted = &factoring->counted; + references *qlauses = &factoring->qlauses; + assert (EMPTY_STACK (*counted)); + assert (EMPTY_STACK (*qlauses)); + ward *const arena = BEGIN_STACK (solver->arena); + mark *marks = solver->marks; + const unsigned initial = factoring->initial; + uint64_t ticks = + 1 + kissat_cache_lines (SIZE_STACK (*last_clauses), sizeof (watch)); + for (all_stack (watch, quotient_watch, *last_clauses)) { + if (quotient_watch.type.binary) { + const unsigned q = quotient_watch.binary.lit; + watches *q_watches = all_watches + q; + ticks += 1 + kissat_cache_lines (SIZE_WATCHES (*q_watches), + sizeof (watch)); + for (all_binary_large_watches (next_watch, *q_watches)) { + if (!next_watch.type.binary) + continue; + const unsigned next = next_watch.binary.lit; + if (next > initial) + continue; + if (marks[next] & FACTOR) + continue; + const unsigned next_idx = IDX (next); + if (!ACTIVE (next_idx)) + continue; + if (!count[next]) + PUSH_STACK (*counted, next); + count[next]++; + } + } else { + const reference c_ref = quotient_watch.large.ref; + clause *const c = (clause *) (arena + c_ref); + assert (!c->quotient); + unsigned min_lit = INVALID_LIT, factors = 0; + size_t min_size = 0; + ticks++; + for (all_literals_in_clause (other, c)) { + if (marks[other] & FACTOR) { + if (factors++) + break; + } else { + assert (!(marks[other] & QUOTIENT)); + marks[other] |= QUOTIENT; + watches *other_watches = all_watches + other; + const size_t other_size = SIZE_WATCHES (*other_watches); + if (min_lit != INVALID_LIT && min_size <= other_size) + continue; + min_lit = other; + min_size = other_size; + } + } + assert (factors); + if (factors == 1) { + assert (min_lit != INVALID_LIT); + watches *min_watches = all_watches + min_lit; + unsigned c_size = c->size; + unsigneds *nounted = &factoring->nounted; + assert (EMPTY_STACK (*nounted)); + ticks += 1 + kissat_cache_lines (SIZE_WATCHES (*min_watches), + sizeof (watch)); + for (all_binary_large_watches (min_watch, *min_watches)) { + if (min_watch.type.binary) + continue; + const reference d_ref = min_watch.large.ref; + if (c_ref == d_ref) + continue; + clause *const d = (clause *) (arena + d_ref); + ticks++; + if (d->quotient) + continue; + if (d->size != c_size) + continue; + unsigned next = INVALID_LIT; + for (all_literals_in_clause (other, d)) { + const mark mark = marks[other]; + if (mark & QUOTIENT) + continue; + if (mark & FACTOR) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + if (mark & NOUNTED) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + if (next != INVALID_LIT) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + next = other; + } + assert (next != INVALID_LIT); + if (next > initial) + continue; + const unsigned next_idx = IDX (next); + if (!ACTIVE (next_idx)) + continue; + assert (!(marks[next] & (FACTOR | NOUNTED))); + marks[next] |= NOUNTED; + PUSH_STACK (*nounted, next); + d->quotient = true; + PUSH_STACK (*qlauses, d_ref); + if (!count[next]) + PUSH_STACK (*counted, next); + count[next]++; + CONTINUE_WITH_NEXT_MIN_WATCH:; + } + clear_nounted (solver, nounted); + } + for (all_literals_in_clause (other, c)) + marks[other] &= ~QUOTIENT; + } + ADD (factor_ticks, ticks); + ticks = 0; + if (solver->statistics.factor_ticks > factoring->limit) + break; + } + clear_qlauses (solver, qlauses); + unsigned next_count = 0, next = INVALID_LIT; + if (solver->statistics.factor_ticks <= factoring->limit) { + unsigned ties = 0; + for (all_stack (unsigned, lit, *counted)) { + const unsigned lit_count = count[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 = INVALID_LIT; + } else if (ties > 1) { + LOG ("found %u tied next factor candidate literals with count %u", + ties, next_count); + double next_score = -1; + for (all_stack (unsigned, lit, *counted)) { + const unsigned lit_count = count[lit]; + if (lit_count != next_count) + continue; + double lit_score = tied_next_factor_score (factoring, lit); + assert (lit_score >= 0); + LOG ("score %g of next factor candidate %s", lit_score, + LOGLIT (lit)); + if (lit_score <= next_score) + continue; + next_score = lit_score; + next = lit; + } + assert (next_score >= 0); + assert (next != INVALID_LIT); + LOG ("best score %g of next factor %s", next_score, LOGLIT (next)); + } else { + assert (ties == 1); + LOG ("single next factor %s with count %u", LOGLIT (next), + next_count); + } + } + for (all_stack (unsigned, lit, *counted)) + count[lit] = 0; + CLEAR_STACK (*counted); + assert (next == INVALID_LIT || next_count > 1); + *next_count_ptr = next_count; + return next; +} + +static void factorize_next (factoring *factoring, unsigned next, + unsigned expected_next_count) { + quotient *last_quotient = factoring->quotients.last; + quotient *next_quotient = new_quotient (factoring, next); + + kissat *const solver = factoring->solver; + watches *all_watches = solver->watches; + ward *const arena = BEGIN_STACK (solver->arena); + mark *marks = solver->marks; + + assert (last_quotient); + statches *last_clauses = &last_quotient->clauses; + statches *next_clauses = &next_quotient->clauses; + sizes *matches = &next_quotient->matches; + references *qlauses = &factoring->qlauses; + assert (EMPTY_STACK (*qlauses)); + + uint64_t ticks = + 1 + kissat_cache_lines (SIZE_STACK (*last_clauses), sizeof (watch)); + + size_t i = 0; + + for (all_stack (watch, last_watch, *last_clauses)) { + if (last_watch.type.binary) { + const unsigned q = last_watch.binary.lit; + watches *q_watches = all_watches + q; + ticks += 1 + kissat_cache_lines (SIZE_WATCHES (*q_watches), + sizeof (watch)); + for (all_binary_large_watches (q_watch, *q_watches)) + if (q_watch.type.binary && q_watch.binary.lit == next) { + LOGBINARY (last_quotient->factor, q, "matched"); + LOGBINARY (next, q, "keeping"); + PUSH_STACK (*next_clauses, last_watch); + PUSH_STACK (*matches, i); + break; + } + } else { + const reference c_ref = last_watch.large.ref; + clause *const c = (clause *) (arena + c_ref); + assert (!c->quotient); + unsigned min_lit = INVALID_LIT, factors = 0; + size_t min_size = 0; + ticks++; + for (all_literals_in_clause (other, c)) { + if (marks[other] & FACTOR) { + if (factors++) + break; + } else { + assert (!(marks[other] & QUOTIENT)); + marks[other] |= QUOTIENT; + watches *other_watches = all_watches + other; + const size_t other_size = SIZE_WATCHES (*other_watches); + if (min_lit != INVALID_LIT && min_size <= other_size) + continue; + min_lit = other; + min_size = other_size; + } + } + assert (factors); + if (factors == 1) { + assert (min_lit != INVALID_LIT); + watches *min_watches = all_watches + min_lit; + unsigned c_size = c->size; + ticks += 1 + kissat_cache_lines (SIZE_WATCHES (*min_watches), + sizeof (watch)); + for (all_binary_large_watches (min_watch, *min_watches)) { + if (min_watch.type.binary) + continue; + const reference d_ref = min_watch.large.ref; + if (c_ref == d_ref) + continue; + clause *const d = (clause *) (arena + d_ref); + ticks++; + if (d->quotient) + continue; + if (d->size != c_size) + continue; + for (all_literals_in_clause (other, d)) { + const mark mark = marks[other]; + if (mark & QUOTIENT) + continue; + if (other != next) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + } + LOGCLS (c, "matched"); + LOGCLS (d, "keeping"); + PUSH_STACK (*next_clauses, min_watch); + PUSH_STACK (*matches, i); + PUSH_STACK (*qlauses, d_ref); + d->quotient = true; + break; + CONTINUE_WITH_NEXT_MIN_WATCH:; + } + } + for (all_literals_in_clause (other, c)) + marks[other] &= ~QUOTIENT; + } + i++; + } + + clear_qlauses (solver, qlauses); + ADD (factor_ticks, ticks); + + assert (expected_next_count <= SIZE_STACK (*next_clauses)); + (void) expected_next_count; +} + +static quotient *best_quotient (factoring *factoring, + size_t *best_reduction_ptr) { + size_t factors = 1, best_reduction = 0; + quotient *best = 0; + kissat *const solver = factoring->solver; + for (quotient *q = factoring->quotients.first; q; q = q->next) { + size_t quotients = SIZE_STACK (q->clauses); + 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; + (void) solver; + return best; +} + +static void resize_factoring (factoring *factoring, unsigned lit) { + kissat *const solver = factoring->solver; + assert (lit > NOT (lit)); + const size_t old_size = factoring->size; + assert (lit > old_size); + const size_t old_allocated = factoring->allocated; + size_t new_size = lit + 1; + if (new_size > old_allocated) { + size_t new_allocated = 2 * old_allocated; + while (new_size > new_allocated) + new_allocated *= 2; + unsigned *count = factoring->count; + count = kissat_nrealloc (solver, count, old_allocated, new_allocated, + sizeof *count); + const size_t delta_allocated = new_allocated - old_allocated; + const size_t delta_bytes = delta_allocated * sizeof *count; + memset (count + old_size, 0, delta_bytes); + factoring->count = count; + assert (!(old_allocated & 1)); + assert (!(new_allocated & 1)); + const size_t old_allocated_score = old_allocated / 2; + const size_t new_allocated_score = new_allocated / 2; + for (unsigned i = 0; i != factoring->hops; i++) { + scores *scores = factoring->scores + i; + double *score = scores->score; + score = kissat_nrealloc (solver, score, old_allocated_score, + new_allocated_score, sizeof *score); + for (size_t i = old_allocated_score; i != new_allocated_score; i++) + score[i] = -1; + scores->score = score; + } + factoring->allocated = new_allocated; + } + factoring->size = new_size; +} + +static void flush_unmatched_clauses (kissat *solver, quotient *q) { + quotient *prev = q->prev; + sizes *q_matches = &q->matches, *prev_matches = &prev->matches; + statches *q_clauses = &q->clauses, *prev_clauses = &prev->clauses; + const size_t n = SIZE_STACK (*q_clauses); + assert (n == SIZE_STACK (*q_matches)); + bool prev_is_first = !prev->id; + size_t i = 0; + while (i != n) { + size_t j = PEEK_STACK (*q_matches, i); + assert (i <= j); + if (!prev_is_first) { + size_t matches = PEEK_STACK (*prev_matches, j); + POKE_STACK (*prev_matches, i, matches); + } + watch watch = PEEK_STACK (*prev_clauses, j); + POKE_STACK (*prev_clauses, i, watch); + i++; + } + LOG ("flushing %zu clauses of quotient[%zu]", + SIZE_STACK (*prev_clauses) - n, prev->id); + if (!prev_is_first) + RESIZE_STACK (*prev_matches, n); + RESIZE_STACK (*prev_clauses, n); + (void) solver; +} + +static void add_factored_divider (factoring *factoring, quotient *q, + unsigned fresh) { + const unsigned factor = q->factor; + kissat *const solver = factoring->solver; + LOGBINARY (fresh, factor, "factored %s divider", LOGLIT (factor)); + kissat_new_binary_clause (solver, fresh, factor); + INC (clauses_factored); + ADD (literals_factored, 2); +} + +static void add_factored_quotient (factoring *factoring, quotient *q, + unsigned not_fresh) { + kissat *const solver = factoring->solver; + LOG ("adding factored quotient[%zu] clauses", q->id); + for (all_stack (watch, watch, q->clauses)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + LOGBINARY (not_fresh, other, "factored quotient"); + kissat_new_binary_clause (solver, not_fresh, other); + ADD (literals_factored, 2); + } else { + const reference c_ref = watch.large.ref; + clause *const c = kissat_dereference_clause (solver, c_ref); + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + const unsigned factor = q->factor; +#ifndef NDEBUG + bool found = false; +#endif + PUSH_STACK (*clause, not_fresh); + for (all_literals_in_clause (other, c)) { + if (other == factor) { +#ifndef NDEBUG + found = true; +#endif + continue; + } + PUSH_STACK (*clause, other); + } + assert (found); + ADD (literals_factored, c->size); + kissat_new_irredundant_clause (solver); + CLEAR_STACK (*clause); + } + INC (clauses_factored); + } +} + +static void eagerly_remove_watch (kissat *solver, watches *watches, + watch needle) { + watch *p = BEGIN_WATCHES (*watches); + watch *end = END_WATCHES (*watches); + assert (p != end); + watch *last = end - 1; + while (p->raw != needle.raw) + p++, assert (p != end); + if (p != last) + memmove (p, p + 1, (last - p) * sizeof *p); + SET_END_OF_WATCHES (*watches, last); +} + +static void eagerly_remove_binary (kissat *solver, watches *watches, + unsigned lit) { + const watch needle = kissat_binary_watch (lit); + eagerly_remove_watch (solver, watches, needle); +} + +static void delete_unfactored (factoring *factoring, quotient *q) { + kissat *const solver = factoring->solver; + LOG ("deleting unfactored quotient[%zu] clauses", q->id); + const unsigned factor = q->factor; + for (all_stack (watch, watch, q->clauses)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + LOGBINARY (factor, other, "deleting unfactored"); + eagerly_remove_binary (solver, &WATCHES (other), factor); + eagerly_remove_binary (solver, &WATCHES (factor), other); + kissat_delete_binary (solver, factor, other); + ADD (literals_unfactored, 2); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + LOGCLS (c, "deleting unfactored"); + for (all_literals_in_clause (lit, c)) + eagerly_remove_watch (solver, &WATCHES (lit), watch); + kissat_mark_clause_as_garbage (solver, c); + ADD (literals_unfactored, c->size); + } + INC (clauses_unfactored); + } +} + +static void update_factored (factoring *factoring, quotient *q) { + kissat *const solver = factoring->solver; + const unsigned factor = q->factor; + update_candidate (factoring, factor); + update_candidate (factoring, NOT (factor)); + for (all_stack (watch, watch, q->clauses)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + update_candidate (factoring, other); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + LOGCLS (c, "deleting unfactored"); + for (all_literals_in_clause (lit, c)) + if (lit != factor) + update_candidate (factoring, lit); + } + } +} + +static bool apply_factoring (factoring *factoring, quotient *q) { + kissat *const solver = factoring->solver; + const unsigned fresh = kissat_fresh_literal (solver); + if (fresh == INVALID_LIT) + return false; + INC (factored); + PUSH_STACK (factoring->fresh, fresh); + for (quotient *p = q; p->prev; p = p->prev) + flush_unmatched_clauses (solver, p); + for (quotient *p = q; p; p = p->prev) + add_factored_divider (factoring, p, fresh); + const unsigned not_fresh = NOT (fresh); + add_factored_quotient (factoring, q, not_fresh); + for (quotient *p = q; p; p = p->prev) + delete_unfactored (factoring, p); + for (quotient *p = q; p; p = p->prev) + update_factored (factoring, p); + assert (fresh < not_fresh); + resize_factoring (factoring, not_fresh); + return true; +} + +static void +adjust_scores_and_phases_of_fresh_varaibles (factoring *factoring) { + const unsigned *begin = BEGIN_STACK (factoring->fresh); + const unsigned *end = END_STACK (factoring->fresh); + kissat *const solver = factoring->solver; + { + const unsigned *p = begin; + while (p != end) { + const unsigned lit = *p++; + const unsigned idx = IDX (lit); + LOG ("unbumping fresh[%zu] %s", (size_t) (p - begin - 1), + LOGVAR (idx)); + const double score = 0; + kissat_update_heap (solver, &solver->scores, idx, score); + } + } + { + const unsigned *p = end; + links *links = solver->links; + queue *queue = &solver->queue; + while (p != begin) { + const unsigned lit = *--p; + const unsigned idx = IDX (lit); + kissat_dequeue_links (idx, links, queue); + } + queue->stamp = 0; + unsigned rest = queue->first; + p = end; + while (p != begin) { + const unsigned lit = *--p; + const unsigned idx = IDX (lit); + struct links *l = links + idx; + if (DISCONNECTED (queue->first)) { + assert (DISCONNECTED (queue->last)); + queue->last = idx; + } else { + struct links *first = links + queue->first; + assert (DISCONNECTED (first->prev)); + first->prev = idx; + } + l->next = queue->first; + queue->first = idx; + assert (DISCONNECTED (l->prev)); + l->stamp = ++queue->stamp; + } + while (!DISCONNECTED (rest)) { + struct links *l = links + rest; + l->stamp = ++queue->stamp; + rest = l->next; + } + solver->queue.search.idx = queue->last; + solver->queue.search.stamp = queue->stamp; + } +} + +static bool run_factorization (kissat *solver, uint64_t limit) { + factoring factoring; + init_factoring (solver, &factoring, limit); + schedule_factorization (&factoring); + bool done = false; +#ifndef QUIET + unsigned factored = 0; +#endif + uint64_t *ticks = &solver->statistics.factor_ticks; + kissat_extremely_verbose ( + solver, "factorization limit of %" PRIu64 " ticks", limit - *ticks); + while (!done && !kissat_empty_heap (&factoring.schedule)) { + const unsigned first = + kissat_pop_max_heap (solver, &factoring.schedule); + const unsigned first_idx = IDX (first); + if (!ACTIVE (first_idx)) + continue; + if (*ticks > limit) { + kissat_very_verbose (solver, "factorization ticks limit hit"); + break; + } + if (TERMINATED (factor_terminated_1)) + break; + struct flags *f = solver->flags + first_idx; + const unsigned bit = 1u << NEGATED (first); + 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 unsigned next = next_factor (&factoring, &next_count); + if (next == INVALID_LIT) + 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 && reduction > factoring.bound) { + if (apply_factoring (&factoring, q)) { +#ifndef QUIET + factored++; +#endif + } else + done = true; + } + } + release_quotients (&factoring); + } + bool completed = kissat_empty_heap (&factoring.schedule); + adjust_scores_and_phases_of_fresh_varaibles (&factoring); + release_factoring (&factoring); + REPORT (!factored, 'f'); + return completed; +} + +static void connect_clauses_to_factor (kissat *solver) { + const unsigned size_limit = GET_OPTION (factorsize); + if (size_limit < 3) { + kissat_extremely_verbose (solver, "only factorizing binary clauses"); + return; + } + kissat_very_verbose (solver, "factorizing clauses of maximum size %u", + size_limit); + clause *last_irredundant = kissat_last_irredundant_clause (solver); + ward *const arena = BEGIN_STACK (solver->arena); + watches *all_watches = solver->watches; + unsigned *bincount, *largecount; + CALLOC (bincount, LITS); + for (all_literals (lit)) { + if (!ACTIVE (IDX (lit))) + continue; + for (all_binary_large_watches (watch, WATCHES (lit))) { + assert (watch.type.binary); + const unsigned other = watch.type.lit; + if (lit > other) + continue; + bincount[lit]++; + bincount[other]++; + } + } + CALLOC (largecount, LITS); + size_t initial_candidates = 0; + for (all_clauses (c)) { + if (c->garbage) + continue; + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + if (c->size > size_limit) + continue; + for (all_literals_in_clause (lit, c)) + largecount[lit]++; + initial_candidates++; + } + kissat_very_verbose (solver, + "initially found %zu large clause candidates", + initial_candidates); + size_t candidates = initial_candidates; + const unsigned rounds = GET_OPTION (factorcandrounds); + for (unsigned round = 1; round <= rounds; round++) { + size_t new_candidates = 0; + unsigned *newlargecount; + CALLOC (newlargecount, LITS); + for (all_clauses (c)) { + if (c->garbage) + continue; + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + if (c->size > size_limit) + continue; + for (all_literals_in_clause (lit, c)) + if (bincount[lit] + largecount[lit] < 2) + goto CONTINUE_WITH_NEXT_CLAUSE1; + for (all_literals_in_clause (lit, c)) + newlargecount[lit]++; + new_candidates++; + CONTINUE_WITH_NEXT_CLAUSE1:; + } + DEALLOC (largecount, LITS); + largecount = newlargecount; + if (candidates == new_candidates) { + kissat_very_verbose (solver, + "no large factorization candidate clauses " + "reduction in round %u", + round); + break; + } + candidates = new_candidates; + kissat_very_verbose ( + solver, + "reduced to %zu large factorization candidate clauses %.0f%% in " + "round %u", + candidates, kissat_percent (candidates, initial_candidates), round); + } +#ifndef QUIET + size_t connected = 0; +#endif + for (all_clauses (c)) { + if (c->garbage) + continue; + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + if (c->size > size_limit) + continue; + for (all_literals_in_clause (lit, c)) + if (bincount[lit] + largecount[lit] < 2) + goto CONTINUE_WITH_NEXT_CLAUSE2; + const reference ref = (ward *) c - arena; + kissat_inlined_connect_clause (solver, all_watches, c, ref); +#ifndef QUIET + connected++; +#endif + CONTINUE_WITH_NEXT_CLAUSE2:; + } + DEALLOC (largecount, LITS); + DEALLOC (bincount, LITS); + kissat_very_verbose ( + solver, "connected %zu large factorization candidate clauses %.0f%%", + connected, kissat_percent (candidates, initial_candidates)); +} + +void kissat_factor (kissat *solver) { + assert (!solver->level); + if (solver->inconsistent) + return; + if (!GET_OPTION (factor)) + return; + statistics *s = &solver->statistics; + if (solver->limits.factor.marked >= s->literals_factor) { + kissat_extremely_verbose ( + solver, + "factorization skipped as no literals have been marked to be added " + "(%" PRIu64 " < %" PRIu64, + solver->limits.factor.marked, s->literals_factor); + return; + } + START (factor); + INC (factorizations); + kissat_phase (solver, "factorization", GET (factorizations), + "binary clause bounded variable addition"); + uint64_t limit = GET_OPTION (factoriniticks); + if (s->factorizations > 1) { + SET_EFFORT_LIMIT (tmp, factor, factor_ticks); + limit = tmp; + } else { + kissat_very_verbose (solver, + "initially limiting to %" PRIu64 + " million factorization ticks", + limit); + limit *= 1e6; + limit += s->factor_ticks; + } +#ifndef QUIET + struct { + int64_t variables, binary, clauses, ticks; + } before, after, delta; + before.variables = s->variables_extension + s->variables_original; + before.binary = BINARY_CLAUSES; + before.clauses = IRREDUNDANT_CLAUSES; + before.ticks = s->factor_ticks; +#endif + kissat_enter_dense_mode (solver, 0); + connect_clauses_to_factor (solver); + bool completed = run_factorization (solver, limit); + kissat_resume_sparse_mode (solver, false, 0); +#ifndef QUIET + after.variables = s->variables_extension + s->variables_original; + after.binary = BINARY_CLAUSES; + after.clauses = IRREDUNDANT_CLAUSES; + after.ticks = s->factor_ticks; + delta.variables = after.variables - before.variables; + delta.binary = before.binary - after.binary; + delta.clauses = before.clauses - after.clauses; + delta.ticks = after.ticks - before.ticks; + kissat_very_verbose (solver, "used %f million factorization ticks", + delta.ticks * 1e-6); + kissat_phase (solver, "factorization", GET (factorizations), + "introduced %" PRId64 " extension variables %.0f%%", + delta.variables, + kissat_percent (delta.variables, before.variables)); + kissat_phase (solver, "factorization", GET (factorizations), + "removed %" PRId64 " binary clauses %.0f%%", delta.binary, + kissat_percent (delta.binary, before.binary)); + kissat_phase (solver, "factorization", GET (factorizations), + "removed %" PRId64 " large clauses %.0f%%", delta.clauses, + kissat_percent (delta.clauses, before.clauses)); +#endif + if (completed) + solver->limits.factor.marked = s->literals_factor; + STOP (factor); +} diff --git a/src/sat/kissat/factor.h b/src/sat/kissat/factor.h new file mode 100644 index 000000000..0223186cf --- /dev/null +++ b/src/sat/kissat/factor.h @@ -0,0 +1,9 @@ +#ifndef _factor_h_INCLUDED +#define _factor_h_INCLUDED + +#include + +struct kissat; +void kissat_factor (struct kissat *); + +#endif diff --git a/src/sat/kissat/fastassign.h b/src/sat/kissat/fastassign.h new file mode 100644 index 000000000..6951ed893 --- /dev/null +++ b/src/sat/kissat/fastassign.h @@ -0,0 +1,41 @@ +#ifndef _fastassign_h_INCLUDED +#define _fastassign_h_INCLUDED + +#define FAST_ASSIGN + +#include "inline.h" +#include "inlineassign.h" + +static inline void kissat_fast_binary_assign ( + kissat *solver, const bool probing, const unsigned level, value *values, + assigned *assigned, unsigned lit, unsigned other) { + if (GET_OPTION (jumpreasons) && level && solver->classification.bigbig) { + unsigned other_idx = IDX (other); + struct assigned *a = assigned + other_idx; + if (a->binary) { + LOGBINARY (lit, other, "jumping %s reason", LOGLIT (lit)); + INC (jumped_reasons); + other = a->reason; + } + } + kissat_fast_assign (solver, probing, level, values, assigned, true, lit, + other); + LOGBINARY (lit, other, "assign %s reason", LOGLIT (lit)); +} + +static inline void +kissat_fast_assign_reference (kissat *solver, value *values, + assigned *assigned, unsigned lit, + reference ref, clause *reason) { + assert (reason == kissat_dereference_clause (solver, ref)); + const unsigned level = + kissat_assignment_level (solver, values, assigned, lit, reason); + assert (level <= solver->level); + assert (ref != DECISION_REASON); + assert (ref != UNIT_REASON); + kissat_fast_assign (solver, solver->probing, level, values, assigned, + false, lit, ref); + LOGREF (ref, "assign %s reason", LOGLIT (lit)); +} + +#endif diff --git a/src/sat/kissat/fastel.c b/src/sat/kissat/fastel.c new file mode 100644 index 000000000..64ddccf3a --- /dev/null +++ b/src/sat/kissat/fastel.c @@ -0,0 +1,934 @@ +#include "fastel.h" +#include "dense.h" +#include "eliminate.h" +#include "inline.h" +#include "internal.h" +#include "print.h" +#include "rank.h" +#include "report.h" +#include "terminate.h" +#include "weaken.h" + +static bool fast_forward_subsumed (kissat *solver, clause *c) { + assert (!c->garbage); + assert (!c->redundant); + unsigned max_occurring = INVALID_LIT; + size_t max_occurrence = 0; + watches *all_watches = solver->watches; + mark *marks = solver->marks; + value *values = solver->values; + for (all_literals_in_clause (other, c)) { + const unsigned other_idx = IDX (other); + if (!ACTIVE (other_idx)) + continue; + watches *other_watches = all_watches + other; + size_t other_occurrence = SIZE_WATCHES (*other_watches); + if (other_occurrence <= max_occurrence) + continue; + max_occurrence = other_occurrence; + max_occurring = other; + marks[other] = 1; + } + bool subsumed = false; + const size_t fasteloccs = GET_OPTION (fasteloccs); + for (all_literals_in_clause (other, c)) { + if (other == max_occurring) + continue; + const unsigned other_idx = IDX (other); + if (!ACTIVE (other_idx)) + continue; + watches *other_watches = all_watches + other; + const size_t size_other_watches = SIZE_WATCHES (*other_watches); + if (size_other_watches > fasteloccs) + continue; + for (all_binary_large_watches (watch, *other_watches)) { + if (watch.type.binary) { + const unsigned other2 = watch.type.lit; + if (marks[other2]) { + LOGBINARY (other, other2, "subsuming"); + subsumed = true; + break; + } + } else { + const reference d_ref = watch.large.ref; + clause *d = kissat_dereference_clause (solver, d_ref); + if (d == c) + continue; + if (d->garbage) + continue; + if (d->size > c->size) + continue; + assert (!d->redundant); + subsumed = true; + for (all_literals_in_clause (other2, d)) { + if (values[other2] < 0) + continue; + if (!marks[other2]) { + subsumed = false; + break; + } + } + if (subsumed) + LOGCLS (d, "subsuming"); + } + } + if (subsumed) + break; + } + for (all_literals_in_clause (other, c)) + marks[other] = 0; + if (subsumed) { + LOGCLS (c, "subsumed"); + kissat_mark_clause_as_garbage (solver, c); + INC (subsumed); + INC (fast_subsumed); + } + return subsumed; +} + +static size_t flush_occurrences (kissat *solver, unsigned lit) { + const size_t fasteloccs = GET_OPTION (fasteloccs); + const size_t fastelclslim = GET_OPTION (fastelclslim); + const size_t fastelsub = GET_OPTION (fastelsub); + const value *const values = solver->values; + const flags *const all_flags = solver->flags; + watches *watches = &WATCHES (lit); + watch *begin = BEGIN_WATCHES (*watches); + watch *end = END_WATCHES (*watches); + watch *q = begin, *p = q; + size_t res = 0; + while (p != end) { + const watch watch = *q++ = *p++; + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + if (values[other] > 0) + continue; + const unsigned other_idx = IDX (other); + const flags *other_flags = all_flags + other_idx; + if (other_flags->eliminated) { + q--; + continue; + } + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + if (c->garbage) { + q--; + continue; + } + if (c->size > fastelclslim) { + res = fasteloccs + 1; + break; + } + for (all_literals_in_clause (other, c)) { + value other_value = values[other]; + if (other_value > 0) { + LOGCLS (c, "%s satisfied", LOGLIT (other)); + kissat_mark_clause_as_garbage (solver, c); + q--; + continue; + } + } + if (fastelsub && fast_forward_subsumed (solver, c)) { + q--; + continue; + } + } + if (++res > fasteloccs) + break; + } + if (q < p) { + while (p != end) + *q++ = *p++; + SET_END_OF_WATCHES (*watches, q); + } + return res; +} + +static void do_fast_resolve_binary_binary (kissat *solver, unsigned pivot, + unsigned clit, unsigned dlit) { + assert (!FLAGS (IDX (clit))->eliminated); + assert (!FLAGS (IDX (dlit))->eliminated); + if (clit == NOT (dlit)) { + LOG ("resolvent tautological"); + return; + } + value *values = solver->values; + value cval = values[clit]; + if (cval > 0) { + LOG ("1st antecedent satisfied"); + return; + } + value dval = values[dlit]; + if (dval > 0) { + LOG ("2nd antecedent satisfied"); + return; + } + if (cval < 0 && dval < 0) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return; + } + if (cval < 0) { + LOG ("resolved unit clause %s", LOGLIT (dlit)); + INC (eliminate_units); + kissat_learned_unit (solver, dlit); + return; + } + if (dval < 0) { + LOG ("resolved unit clause %s", LOGLIT (clit)); + INC (eliminate_units); + kissat_learned_unit (solver, clit); + return; + } + if (clit == dlit) { + LOG ("resolved unit clause %s", LOGLIT (clit)); + INC (eliminate_units); + kissat_learned_unit (solver, clit); + return; + } + assert (!cval); + assert (!dval); + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + PUSH_STACK (*clause, clit); + PUSH_STACK (*clause, dlit); + LOGTMP ("%s resolvent", LOGVAR (pivot)); +#ifndef LOGGING + (void) pivot; +#endif + kissat_new_irredundant_clause (solver); + CLEAR_STACK (*clause); +} + +static void do_fast_resolve_binary_large (kissat *solver, unsigned pivot, + unsigned lit, clause *c) { + assert (!FLAGS (IDX (lit))->eliminated); + if (c->garbage) + return; + assert (!c->redundant); + value *values = solver->values; + value lit_val = values[lit]; + if (lit_val > 0) { + LOG ("binary clause antecedent satisfied"); + return; + } + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + if (!lit_val) + PUSH_STACK (*clause, lit); + bool satisfied = false, tautological = false; + const unsigned not_lit = NOT (lit); + for (all_literals_in_clause (other, c)) { + const unsigned idx_other = IDX (other); + if (idx_other == pivot) + continue; + if (other == lit) + continue; + if (other == not_lit) { + LOG ("resolvent tautological"); + tautological = true; + break; + } + value other_val = values[other]; + if (other_val < 0) + continue; + if (other_val > 0) { + LOG ("large clause antecedent satisfied"); + kissat_mark_clause_as_garbage (solver, c); + satisfied = true; + break; + } + PUSH_STACK (*clause, other); + } + if (satisfied || tautological) { + CLEAR_STACK (*clause); + return; + } + size_t size = SIZE_STACK (*clause); + if (!size) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return; + } + if (size == 1) { + const unsigned unit = PEEK_STACK (*clause, 0); + CLEAR_STACK (*clause); + LOG ("resolved unit clause %s", LOGLIT (unit)); + INC (eliminate_units); + kissat_learned_unit (solver, unit); + return; + } + LOGTMP ("%s resolvent", LOGVAR (pivot)); + kissat_new_irredundant_clause (solver); + CLEAR_STACK (*clause); +} + +static void do_fast_resolve_large_large (kissat *solver, unsigned pivot, + clause *c, clause *d) { + if (c->garbage) + return; + if (d->garbage) + return; + assert (!c->redundant); + assert (!d->redundant); + value *values = solver->values; + mark *marks = solver->marks; + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + bool satisfied = false, tautological = false; + for (all_literals_in_clause (other, c)) { + const unsigned idx_other = IDX (other); + if (idx_other == pivot) + continue; + value other_val = values[other]; + if (other_val < 0) + continue; + if (other_val > 0) { + LOG ("1st antecedent satisfied"); + satisfied = true; + break; + } + PUSH_STACK (*clause, other); + marks[other] = 1; + } + if (satisfied || tautological) { + for (all_stack (unsigned, other, *clause)) + marks[other] = 0; + CLEAR_STACK (*clause); + return; + } + size_t marked = SIZE_STACK (*clause); + for (all_literals_in_clause (other, d)) { + const unsigned idx_other = IDX (other); + if (idx_other == pivot) + continue; + value other_val = values[other]; + if (other_val < 0) + continue; + if (other_val > 0) { + LOG ("2nd antecedent satisfied"); + satisfied = true; + break; + } + mark mark_other = marks[other]; + if (mark_other) + continue; + const unsigned not_other = NOT (other); + mark mark_not_other = marks[not_other]; + if (mark_not_other) { + LOG ("tautological resolvent"); + tautological = true; + break; + } + PUSH_STACK (*clause, other); + } + if (satisfied || tautological) { + for (all_stack (unsigned, other, *clause)) + marks[other] = 0; + CLEAR_STACK (*clause); + return; + } + size_t size = SIZE_STACK (*clause); + if (!size) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return; + } + if (size == 1) { + const unsigned unit = PEEK_STACK (*clause, 0); + CLEAR_STACK (*clause); + marks[unit] = 0; + LOG ("resolved unit clause %s", LOGLIT (unit)); + INC (eliminate_units); + kissat_learned_unit (solver, unit); + return; + } + LOGTMP ("%s resolvent", LOGVAR (pivot)); + kissat_new_irredundant_clause (solver); + RESIZE_STACK (*clause, marked); + for (all_stack (unsigned, other, *clause)) + marks[other] = 0; + CLEAR_STACK (*clause); +} + +static void do_fast_resolve (kissat *solver, unsigned pivot, watch cwatch, + watch dwatch) { + assert (!solver->inconsistent); + LOGWATCH (LIT (pivot), cwatch, "1st fast %s elimination antecedent", + LOGVAR (pivot)); + LOGWATCH (NOT (LIT (pivot)), dwatch, "1st fast %s elimination antecedent", + LOGVAR (pivot)); + const unsigned clit = cwatch.binary.lit; + const unsigned dlit = dwatch.binary.lit; + const reference cref = cwatch.large.ref; + const reference dref = dwatch.large.ref; + const bool cbin = cwatch.type.binary; + const bool dbin = dwatch.type.binary; + clause *c = cbin ? 0 : kissat_dereference_clause (solver, cref); + clause *d = dbin ? 0 : kissat_dereference_clause (solver, dref); + if (cbin && dbin) + do_fast_resolve_binary_binary (solver, pivot, clit, dlit); + else if (cbin && !dbin) + do_fast_resolve_binary_large (solver, pivot, clit, d); + else if (!cbin && dbin) + do_fast_resolve_binary_large (solver, pivot, dlit, c); + else { + assert (!cbin), assert (!dbin); + do_fast_resolve_large_large (solver, pivot, c, d); + } +} + +static void fast_delete_and_weaken_clauses (kissat *solver, unsigned lit) { + watches *all_watches = solver->watches; + watches *lit_watches = all_watches + lit; + value *values = solver->values; + for (all_binary_large_watches (watch, *lit_watches)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + const value value = values[other]; + if (value <= 0) + kissat_weaken_binary (solver, lit, other); + kissat_delete_binary (solver, lit, other); + } else { + const reference ref = watch.large.ref; + clause *c = kissat_dereference_clause (solver, ref); + if (!c->garbage) { + bool satisfied = false; + for (all_literals_in_clause (other, c)) + if (values[other] > 0) { + satisfied = true; + break; + } + if (!satisfied) + kissat_weaken_clause (solver, lit, c); + kissat_mark_clause_as_garbage (solver, c); + } + } + } + RELEASE_WATCHES (*lit_watches); +} + +static void do_fast_eliminate (kissat *solver, unsigned pivot) { + LOG ("fast variable elimination of %s", LOGVAR (pivot)); + const unsigned lit = LIT (pivot); + const unsigned not_lit = NOT (lit); + watches *all_watches = solver->watches; + watches *lit_watches = all_watches + lit; + watches *not_lit_watches = all_watches + not_lit; + LOG ("occurs %zu positively", SIZE_WATCHES (*lit_watches)); + LOG ("occurs %zu negatively", SIZE_WATCHES (*not_lit_watches)); + watch *begin_lit_watches = BEGIN_WATCHES (*lit_watches); + watch *begin_not_lit_watches = BEGIN_WATCHES (*not_lit_watches); + watch *end_lit_watches = END_WATCHES (*lit_watches); + watch *end_not_lit_watches = END_WATCHES (*not_lit_watches); + for (watch *p = begin_lit_watches; p < end_lit_watches; p++) + for (watch *q = begin_not_lit_watches; q < end_not_lit_watches; q++) { + do_fast_resolve (solver, pivot, *p, *q); + if (solver->inconsistent) + return; + watches *new_all_watches = solver->watches; + const size_t i = p - begin_lit_watches; + const size_t j = q - begin_not_lit_watches; + all_watches = new_all_watches; + lit_watches = all_watches + lit; + not_lit_watches = all_watches + not_lit; + begin_lit_watches = BEGIN_WATCHES (*lit_watches); + begin_not_lit_watches = BEGIN_WATCHES (*not_lit_watches); + end_lit_watches = END_WATCHES (*lit_watches); + end_not_lit_watches = END_WATCHES (*not_lit_watches); + p = begin_lit_watches + i; + q = begin_not_lit_watches + j; + } + assert (!solver->inconsistent); + INC (eliminated); + INC (fast_eliminated); + kissat_mark_eliminated_variable (solver, pivot); + fast_delete_and_weaken_clauses (solver, lit); + fast_delete_and_weaken_clauses (solver, not_lit); +} + +static bool can_fast_resolve_binary_binary (kissat *solver, unsigned clit, + unsigned dlit) { + assert (!FLAGS (IDX (clit))->eliminated); + assert (!FLAGS (IDX (dlit))->eliminated); + if (clit == NOT (dlit)) + return false; + value *values = solver->values; + value cval = values[clit]; + if (cval > 0) + return false; + value dval = values[dlit]; + if (dval > 0) + return false; + if (cval < 0 && dval < 0) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + return false; + } + if (cval < 0) { + LOG ("resolved unit clause %s", LOGLIT (dlit)); + INC (eliminate_units); + kissat_learned_unit (solver, dlit); + return false; + } + if (dval < 0) { + LOG ("resolved unit clause %s", LOGLIT (clit)); + INC (eliminate_units); + kissat_learned_unit (solver, clit); + return false; + } + if (clit == dlit) { + LOG ("resolved unit clause %s", LOGLIT (clit)); + INC (eliminate_units); + kissat_learned_unit (solver, clit); + return false; + } + assert (!cval); + assert (!dval); + return true; +} + +static bool can_fast_resolve_binary_large (kissat *solver, unsigned pivot, + unsigned lit, clause *c) { + assert (!FLAGS (IDX (lit))->eliminated); + if (c->garbage) + return false; + assert (!c->redundant); + value *values = solver->values; + value lit_val = values[lit]; + if (lit_val > 0) + return false; + const unsigned not_lit = NOT (lit); + bool found_lit = false; + for (all_literals_in_clause (other, c)) { + if (other == lit) + found_lit = true; + if (other == not_lit) + return false; + value other_val = values[other]; + if (other_val > 0) { + LOG ("large clause antecedent satisfied"); + kissat_mark_clause_as_garbage (solver, c); + return false; + } + } + if (found_lit) { + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + for (all_literals_in_clause (other, c)) { + const unsigned idx = IDX (other); + if (idx == pivot) + continue; + value value = values[other]; + if (value < 0) + continue; + assert (!value); + PUSH_STACK (*clause, other); + } + LOGTMP ("self-subsuming resolvent"); + INC (strengthened); + INC (fast_strengthened); + const size_t size = SIZE_STACK (*clause); + const reference ref = kissat_reference_clause (solver, c); + if (!size) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + } else if (size == 1) { + const unsigned unit = PEEK_STACK (*clause, 0); + LOG ("resolved %s unit clause", LOGLIT (unit)); + INC (eliminate_units); + kissat_learned_unit (solver, unit); + } else + kissat_new_irredundant_clause (solver); + CLEAR_STACK (*clause); + c = kissat_dereference_clause (solver, ref); + LOGCLS (c, "self-subsuming antecedent"); + kissat_mark_clause_as_garbage (solver, c); + return false; + } + return true; +} + +static bool can_fast_resolve_large_large (kissat *solver, unsigned pivot, + clause *c, clause *d) { + if (c->garbage) + return false; + if (d->garbage) + return false; + assert (!c->redundant); + assert (!d->redundant); + value *values = solver->values; + mark *marks = solver->marks; + bool satisfied = false; + unsigneds *clause = &solver->clause; + assert (EMPTY_STACK (*clause)); + for (all_literals_in_clause (other, c)) { + const unsigned idx_other = IDX (other); + if (idx_other == pivot) + continue; + value other_val = values[other]; + if (other_val < 0) + continue; + if (other_val > 0) { + satisfied = true; + LOGCLS (c, "%s satisfied", LOGLIT (other)); + kissat_mark_clause_as_garbage (solver, c); + break; + } + assert (!marks[other]); + marks[other] = 1; + PUSH_STACK (*clause, other); + } + bool tautological = false; + if (!satisfied) { + for (all_literals_in_clause (other, d)) { + const unsigned idx_other = IDX (other); + if (idx_other == pivot) + continue; + value other_val = values[other]; + if (other_val < 0) + continue; + if (other_val > 0) { + satisfied = true; + LOGCLS (d, "%s satisfied", LOGLIT (other)); + kissat_mark_clause_as_garbage (solver, d); + break; + } + const unsigned not_other = NOT (other); + const mark mark_not_other = marks[not_other]; + if (mark_not_other) { + tautological = true; + break; + } + const mark other_mark = marks[other]; + if (other_mark) + continue; + PUSH_STACK (*clause, other); + } + } + for (all_literals_in_clause (other, c)) + marks[other] = 0; + bool strengthened = false; + if (!satisfied && !tautological) { + const size_t size = SIZE_STACK (*clause); + if (!size) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + strengthened = true; + } else if (size == 1) { + const unsigned unit = PEEK_STACK (*clause, 0); + LOG ("resolved %s unit clause", LOGLIT (unit)); + INC (eliminate_units); + kissat_learned_unit (solver, unit); + strengthened = true; + } else { + bool c_subsumed = false, d_subsumed = false; + bool marked = false; + if (size < c->size) { + marked = true; + for (all_stack (unsigned, other, *clause)) + marks[other] = 1; + size_t count = 0; + for (all_literals_in_clause (other, c)) + if (marks[other]) + count++; + c_subsumed = (count >= size); + } + if (size < d->size) { + if (!marked) { + marked = true; + for (all_stack (unsigned, other, *clause)) + marks[other] = 1; + } + size_t count = 0; + for (all_literals_in_clause (other, d)) + if (marks[other]) + count++; + d_subsumed = (count >= size); + } + if (marked) { + for (all_stack (unsigned, other, *clause)) + marks[other] = 0; + } + if (c_subsumed || d_subsumed) { + LOGTMP ("self-subsuming resolvent"); + INC (strengthened); + INC (fast_strengthened); + const reference c_ref = kissat_reference_clause (solver, c); + const reference d_ref = kissat_reference_clause (solver, d); + kissat_new_irredundant_clause (solver); + strengthened = true; + if (c_subsumed) { + c = kissat_dereference_clause (solver, c_ref); + LOGCLS (c, "self-subsuming antecedent"); + kissat_mark_clause_as_garbage (solver, c); + } + if (d_subsumed) { + d = kissat_dereference_clause (solver, d_ref); + LOGCLS (d, "self-subsuming antecedent"); + kissat_mark_clause_as_garbage (solver, d); + } + if (c_subsumed && d_subsumed) + INC (fast_subsumed); + } + } + } + CLEAR_STACK (*clause); + return !satisfied && !tautological && !strengthened; +} + +static bool can_fast_resolve (kissat *solver, unsigned pivot, watch cwatch, + watch dwatch) { + assert (!solver->inconsistent); + const unsigned clit = cwatch.binary.lit; + const unsigned dlit = dwatch.binary.lit; + const reference cref = cwatch.large.ref; + const reference dref = dwatch.large.ref; + const bool cbin = cwatch.type.binary; + const bool dbin = dwatch.type.binary; + clause *c = cbin ? 0 : kissat_dereference_clause (solver, cref); + clause *d = dbin ? 0 : kissat_dereference_clause (solver, dref); + if (cbin && dbin) + return can_fast_resolve_binary_binary (solver, clit, dlit); + if (cbin && !dbin) + return can_fast_resolve_binary_large (solver, pivot, clit, d); + if (!cbin && dbin) + return can_fast_resolve_binary_large (solver, pivot, dlit, c); + assert (!cbin), assert (!dbin); + return can_fast_resolve_large_large (solver, pivot, c, d); +} + +static bool resolvents_limited (kissat *solver, unsigned pivot, + size_t limit) { + const unsigned lit = LIT (pivot); + const unsigned not_lit = NOT (lit); + watches *all_watches = solver->watches; + watches *lit_watches = all_watches + lit; + watches *not_lit_watches = all_watches + not_lit; + watch *begin_lit_watches = BEGIN_WATCHES (*lit_watches); + watch *begin_not_lit_watches = BEGIN_WATCHES (*not_lit_watches); + watch *end_lit_watches = END_WATCHES (*lit_watches); + watch *end_not_lit_watches = END_WATCHES (*not_lit_watches); + size_t resolved = 0; + for (watch *p = begin_lit_watches; p < end_lit_watches; p++) + for (watch *q = begin_not_lit_watches; q < end_not_lit_watches; q++) { + if (can_fast_resolve (solver, pivot, *p, *q) && ++resolved > limit) + return false; + if (solver->inconsistent) + return false; + watches *new_all_watches = solver->watches; + const size_t i = p - begin_lit_watches; + const size_t j = q - begin_not_lit_watches; + all_watches = new_all_watches; + lit_watches = all_watches + lit; + not_lit_watches = all_watches + not_lit; + begin_lit_watches = BEGIN_WATCHES (*lit_watches); + begin_not_lit_watches = BEGIN_WATCHES (*not_lit_watches); + end_lit_watches = END_WATCHES (*lit_watches); + end_not_lit_watches = END_WATCHES (*not_lit_watches); + p = begin_lit_watches + i; + q = begin_not_lit_watches + j; + } + return true; +} + +static bool try_to_fast_eliminate (kissat *solver, unsigned pivot) { + assert (!solver->inconsistent); + if (!ACTIVE (pivot)) + return false; + const unsigned lit = LIT (pivot); + const unsigned not_lit = NOT (lit); + const size_t fasteloccs = GET_OPTION (fasteloccs); + const size_t pos = flush_occurrences (solver, lit); + if (pos > fasteloccs) + return false; + const size_t neg = flush_occurrences (solver, not_lit); + if (neg > fasteloccs) + return false; + const size_t sum = pos + neg; + const size_t product = pos * neg; + if (sum > fasteloccs) + return false; + const size_t fastelim = GET_OPTION (fastelim); + if (product <= fastelim) { + do_fast_eliminate (solver, pivot); + return true; + } + if (resolvents_limited (solver, pivot, fastelim)) { + do_fast_eliminate (solver, pivot); + return true; + } + return false; +} + +static void flush_eliminated_binary_clauses_of_literal (kissat *solver, + unsigned lit) { + flags *all_flags = solver->flags; + watches *watches = &WATCHES (lit); + watch *begin = BEGIN_WATCHES (*watches); + watch *end = END_WATCHES (*watches); + watch *q = begin, *p = q; + while (p != end) { + watch watch = *q++ = *p++; + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + const unsigned other_idx = IDX (other); + flags *other_flags = all_flags + other_idx; + if (other_flags->eliminated) + q--; + } + SET_END_OF_WATCHES (*watches, q); +} + +static void flush_eliminated_binary_clauses (kissat *solver) { + for (all_variables (idx)) { + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + flush_eliminated_binary_clauses_of_literal (solver, lit); + flush_eliminated_binary_clauses_of_literal (solver, not_lit); + } +} + +struct candidate { + unsigned pivot; + unsigned score; +}; + +typedef struct candidate candidate; +typedef STACK (candidate) candidates; + +#define RANK_CANDIDATE(CANDIDATE) ((CANDIDATE).score) + +void kissat_fast_variable_elimination (kissat *solver) { + if (solver->inconsistent) + return; + if (!GET_OPTION (fastel)) + return; +#ifndef QUIET + const unsigned variables_before = solver->active; +#endif + assert (!solver->level); + START (fastel); + kissat_enter_dense_mode (solver, 0); + kissat_connect_irredundant_large_clauses (solver); + const unsigned fastelrounds = GET_OPTION (fastelrounds); + const size_t fasteloccs = GET_OPTION (fasteloccs); +#ifndef QUIET + unsigned eliminated = 0; +#endif + unsigned round = 0; + candidates candidates; + INIT_STACK (candidates); + bool done = false; + do { + if (round++ >= fastelrounds) + break; + kissat_extremely_verbose ( + solver, "gathering candidates for fast elimination round %u", + round); + assert (EMPTY_STACK (candidates)); + flags *all_flags = solver->flags; + for (all_variables (pivot)) { + flags *pivot_flags = all_flags + pivot; + if (!pivot_flags->active) + continue; + if (!pivot_flags->eliminate) + continue; + const unsigned lit = LIT (pivot); + const size_t pos = flush_occurrences (solver, lit); + if (pos > fasteloccs) + continue; + const unsigned not_lit = LIT (pivot); + const size_t neg = flush_occurrences (solver, not_lit); + if (neg > fasteloccs) + continue; + const unsigned score = pos + neg; + if (score > fasteloccs) + continue; + candidate candidate = {pivot, score}; + PUSH_STACK (candidates, candidate); + } +#ifndef QUIET + const size_t size_candidates = SIZE_STACK (candidates); + const size_t active_variables = solver->active; + kissat_extremely_verbose ( + solver, "gathered %zu candidates %.0f%% in elimination round %u", + size_candidates, kissat_percent (size_candidates, active_variables), + round); +#endif + RADIX_STACK (candidate, unsigned, candidates, RANK_CANDIDATE); + unsigned eliminated_this_round = 0; + for (all_stack (candidate, candidate, candidates)) { + const unsigned pivot = candidate.pivot; + flags *pivot_flags = all_flags + pivot; + if (!pivot_flags->active) + continue; + if (!pivot_flags->eliminate) + continue; + if (TERMINATED (fastel_terminated_1)) { + done = true; + break; + } + if (try_to_fast_eliminate (solver, pivot)) + eliminated_this_round++; + if (solver->inconsistent) { + done = true; + break; + } + pivot_flags->eliminate = false; + kissat_flush_units_while_connected (solver); + if (solver->inconsistent) { + done = true; + break; + } + } + CLEAR_STACK (candidates); +#ifndef QUIET + eliminated += eliminated_this_round; + kissat_very_verbose ( + solver, "fast eliminated %u of %zu candidates %.0f%% in round %u", + eliminated_this_round, size_candidates, + kissat_percent (eliminated_this_round, size_candidates), round); +#endif + if (!eliminated_this_round) + done = true; + } while (!done); + RELEASE_STACK (candidates); + for (all_variables (idx)) + FLAGS (idx)->eliminate = true; + flush_eliminated_binary_clauses (solver); + kissat_resume_sparse_mode (solver, true, 0); +#ifndef QUIET + const unsigned original_variables = solver->statistics.variables_original; + const unsigned variables_after = solver->active; + kissat_verbose ( + solver, + "[fastel] " + "fast elimination of %u variables %.0f%% (%u remain %.0f%%)", + eliminated, kissat_percent (eliminated, variables_before), + variables_after, + kissat_percent (variables_after, original_variables)); +#endif + STOP (fastel); + REPORT (!eliminated, 'e'); +} diff --git a/src/sat/kissat/fastel.h b/src/sat/kissat/fastel.h new file mode 100644 index 000000000..a2a5cfcd5 --- /dev/null +++ b/src/sat/kissat/fastel.h @@ -0,0 +1,6 @@ +#ifndef _fastel_h_INCLUDED + +struct kissat; +void kissat_fast_variable_elimination (struct kissat *); + +#endif diff --git a/src/sat/kissat/fifo.h b/src/sat/kissat/fifo.h new file mode 100644 index 000000000..508989aa5 --- /dev/null +++ b/src/sat/kissat/fifo.h @@ -0,0 +1,97 @@ +#ifndef _fifo_h_INCLUDED +#define _fifo_h_INCLUDED + +#include "stack.h" + +#include + +#define FIFO(TYPE) \ + struct { \ + TYPE *begin; \ + TYPE *end; \ + TYPE *start; \ + TYPE *limit; \ + TYPE *allocated; \ + } + +#define BEGIN_FIFO(F) ((F).begin) +#define END_FIFO(F) ((F).end) +#define START_FIFO(F) ((F).start) +#define LIMIT_FIFO(F) ((F).limit) +#define ALLOCATED_FIFO(F) ((F).allocated) + +#define INIT_FIFO(F) memset (&(F), 0, sizeof (F)) + +#define EMPTY_FIFO(F) (END_FIFO (F) == BEGIN_FIFO (F)) +#define FULL_FIFO(F) (END_FIFO (F) == ALLOCATED_FIFO (F)) +#define SIZE_FIFO(F) (END_FIFO (F) - BEGIN_FIFO (F)) + +#define MOVABLE_FIFO(F) (BEGIN_FIFO (F) == LIMIT_FIFO (F)) +#define CAPACITY_FIFO(F) (ALLOCATED_FIFO (F) - START_FIFO (F)) + +#define ENLARGE_FIFO(F) \ + do { \ + size_t OLD_BEGIN_OFFSET = BEGIN_FIFO (F) - START_FIFO (F); \ + size_t OLD_END_OFFSET = END_FIFO (F) - START_FIFO (F); \ + size_t OLD_CAPACITY = CAPACITY_FIFO (F); \ + size_t NEW_CAPACITY = OLD_CAPACITY ? 2 * OLD_CAPACITY : 2; \ + size_t OLD_BYTES = OLD_CAPACITY * sizeof *BEGIN_FIFO (F); \ + size_t NEW_BYTES = NEW_CAPACITY * sizeof *BEGIN_FIFO (F); \ + START_FIFO (F) = \ + kissat_realloc (solver, START_FIFO (F), OLD_BYTES, NEW_BYTES); \ + ALLOCATED_FIFO (F) = START_FIFO (F) + NEW_CAPACITY; \ + LIMIT_FIFO (F) = START_FIFO (F) + NEW_CAPACITY / 2; \ + BEGIN_FIFO (F) = START_FIFO (F) + OLD_BEGIN_OFFSET; \ + END_FIFO (F) = START_FIFO (F) + OLD_END_OFFSET; \ + assert (BEGIN_FIFO (F) < LIMIT_FIFO (F)); \ + } while (0) + +#define MOVE_FIFO(F) \ + do { \ + size_t SIZE = SIZE_FIFO (F); \ + size_t BYTES = SIZE * sizeof *BEGIN_FIFO (F); \ + memmove (START_FIFO (F), BEGIN_FIFO (F), BYTES); \ + BEGIN_FIFO (F) = START_FIFO (F); \ + END_FIFO (F) = BEGIN_FIFO (F) + SIZE; \ + } while (0) + +#define ENQUEUE_FIFO(F, E) \ + do { \ + if (FULL_FIFO (F)) \ + ENLARGE_FIFO (F); \ + *END_FIFO (F)++ = (E); \ + } while (0) + +#define DEQUEUE_FIFO(F, E) \ + do { \ + assert (!EMPTY_FIFO (F)); \ + (E) = *BEGIN_FIFO (F)++; \ + if (MOVABLE_FIFO (F)) \ + MOVE_FIFO (F); \ + } while (0) + +#define POP_FIFO(F) (assert (!EMPTY_FIFO (F)), *--END_FIFO (F)) + +#define RELEASE_FIFO(F) \ + do { \ + size_t CAPACITY = CAPACITY_FIFO (F); \ + size_t BYTES = CAPACITY * sizeof *BEGIN_FIFO (F); \ + kissat_free (solver, START_FIFO (F), BYTES); \ + INIT_FIFO (F); \ + } while (0) + +#define CLEAR_FIFO(F) \ + do { \ + BEGIN_FIFO (F) = END_FIFO (F) = START_FIFO (F); \ + } while (0) + +struct unsigned_fifo { + unsigned *begin, *end; + unsigned *start, *limit, *allocated; +}; + +typedef struct unsigned_fifo unsigned_fifo; + +#define all_fifo all_stack + +#endif diff --git a/src/sat/kissat/file.c b/src/sat/kissat/file.c new file mode 100644 index 000000000..b6bda097b --- /dev/null +++ b/src/sat/kissat/file.c @@ -0,0 +1,310 @@ +#include "file.h" +#include "keatures.h" +#include "utilities.h" + +#include +#include +#include +#include +#include +#include + +bool kissat_file_exists (const char *path) { + if (!path) + return false; + struct stat buf; + if (stat (path, &buf)) + return false; + return true; +} + +bool kissat_file_readable (const char *path) { + if (!path) + return false; + struct stat buf; + if (stat (path, &buf)) + return false; + if (access (path, R_OK)) + return false; + return true; +} + +bool kissat_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)) { + if (errno == ENOENT) + res = 0; + else + res = -2; + } else if (S_ISDIR (buf.st_mode)) + res = 3; + else if (access (path, W_OK)) + res = 4; + else + res = 0; + } else if (!p[1]) + res = 5; + else { + const size_t len = p - path; + char *dirname = malloc (len + 1); + if (dirname) { + 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)) { + if (errno == ENOENT) + res = 0; + else + res = -3; + } else if (access (path, W_OK)) + res = 9; + else + res = 0; + free (dirname); + } else + res = 10; + } + } + } + return !res; +} + +size_t kissat_file_size (const char *path) { + struct stat buf; + if (stat (path, &buf)) + return 0; + return (size_t) buf.st_size; +} + +bool kissat_find_executable (const char *name) { + const size_t name_len = strlen (name); + const char *environment = getenv ("PATH"); + if (!environment) + return false; + const size_t dirs_len = strlen (environment); + char *dirs = malloc (dirs_len + 1); + if (!dirs) + return false; + strcpy (dirs, environment); + bool res = false; + const char *end = dirs + dirs_len + 1; + for (char *dir = dirs, *q; !res && dir != end; dir = q) { + for (q = dir; *q && *q != ':'; q++) + assert (q + 1 < end); + *q++ = 0; + const size_t path_len = (q - dir) + name_len; + char *path = malloc (path_len + 1); + if (!path) { + free (dirs); + return false; + } + sprintf (path, "%s/%s", dir, name); + assert (strlen (path) == path_len); + res = kissat_file_readable (path); + free (path); + } + free (dirs); + return res; +} + +static int bz2sig[] = {0x42, 0x5A, 0x68, EOF}; +static int gzsig[] = {0x1F, 0x8B, EOF}; +static int lzmasig[] = {0x5D, 0x00, 0x00, 0x80, 0x00, EOF}; +static int sig7z[] = {0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, EOF}; +static int xzsig[] = {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00, 0x00, EOF}; +static int Zsig[] = {0x1F, 0x9D, 0x90, EOF}; + +static bool match_signature (const char *path, const int *sig) { + assert (path); + FILE *tmp = fopen (path, "r"); + if (!tmp) + return false; + bool res = true; + for (const int *p = sig; res && (*p != EOF); p++) + res = (getc (tmp) == *p); + fclose (tmp); + return res; +} + +#ifdef KISSAT_HAS_COMPRESSION + +static FILE *open_pipe (const char *fmt, const char *path, + const char *mode) { + size_t name_len = 0; + while (fmt[name_len] && fmt[name_len] != ' ') + name_len++; + char *name = malloc (name_len + 1); + if (!name) + return 0; + strncpy (name, fmt, name_len); + name[name_len] = 0; + bool found = kissat_find_executable (name); + free (name); + if (!found) + return 0; + char *cmd = malloc (strlen (fmt) + strlen (path)); + if (!cmd) + return 0; + sprintf (cmd, fmt, path); + FILE *res = popen (cmd, mode); + free (cmd); + return res; +} + +static FILE *read_pipe (const char *fmt, const int *sig, const char *path) { + if (!kissat_file_readable (path)) + return 0; + if (sig && !match_signature (path, sig)) + return 0; + return open_pipe (fmt, path, "r"); +} + +#ifndef SAFE + +static FILE *write_pipe (const char *fmt, const char *path) { + return open_pipe (fmt, path, "w"); +} + +#endif + +#endif + +void kissat_read_already_open_file (file *file, FILE *f, const char *path) { + file->file = f; + file->close = false; + file->reading = true; + file->compressed = false; + file->path = path; + file->bytes = 0; +} + +void kissat_write_already_open_file (file *file, FILE *f, + const char *path) { + file->file = f; + file->close = false; + file->reading = false; + file->compressed = false; + file->path = path; + file->bytes = 0; +} + +#ifndef KISSAT_HAS_COMPRESSION + +bool kissat_looks_like_a_compressed_file (const char *path) { +#define RETURN_TRUE_IF_COMPRESSED(SUFFIX, SIGNATURE) \ + if (kissat_has_suffix (path, SUFFIX) && \ + match_signature (path, SIGNATURE)) \ + return true + + RETURN_TRUE_IF_COMPRESSED (".bz2", bz2sig); + RETURN_TRUE_IF_COMPRESSED (".gz", gzsig); + RETURN_TRUE_IF_COMPRESSED (".lzma", lzmasig); + RETURN_TRUE_IF_COMPRESSED (".7z", sig7z); + RETURN_TRUE_IF_COMPRESSED (".xz", xzsig); + RETURN_TRUE_IF_COMPRESSED (".Z", Zsig); + + return false; +} + +#endif + +bool kissat_open_to_read_file (file *file, const char *path) { +#ifdef KISSAT_HAS_COMPRESSION +#define READ_PIPE(SUFFIX, CMD, SIG) \ + do { \ + if (kissat_has_suffix (path, SUFFIX)) { \ + file->file = read_pipe (CMD, SIG, path); \ + if (!file->file) \ + break; \ + file->close = true; \ + file->reading = true; \ + file->compressed = true; \ + file->path = path; \ + file->bytes = 0; \ + return true; \ + } \ + } while (0) + READ_PIPE (".bz2", "bzip2 -c -d %s", bz2sig); + READ_PIPE (".gz", "gzip -c -d %s", gzsig); + READ_PIPE (".lzma", "lzma -c -d %s", lzmasig); + READ_PIPE (".7z", "7z x -so %s 2>/dev/null", sig7z); + READ_PIPE (".xz", "xz -c -d %s", xzsig); + READ_PIPE (".Z", "gzip -c -d %s", Zsig); +#endif + file->file = fopen (path, "r"); + if (!file->file) + return false; + file->close = true; + file->reading = true; + file->compressed = false; + file->path = path; + file->bytes = 0; + + return true; +} + +bool kissat_open_to_write_file (file *file, const char *path) { +#if defined(KISSAT_HAS_COMPRESSION) && !defined(SAFE) +#define WRITE_PIPE(SUFFIX, CMD) \ + do { \ + if (kissat_has_suffix (path, SUFFIX)) { \ + if (SUFFIX[1] == '7' && kissat_file_readable (path) && \ + unlink (path)) \ + return false; \ + file->file = write_pipe (CMD, path); \ + if (!file->file) \ + return false; \ + file->close = true; \ + file->reading = false; \ + file->compressed = true; \ + file->path = path; \ + file->bytes = 0; \ + return true; \ + } \ + } while (0) + WRITE_PIPE (".bz2", "bzip2 -c > %s"); + WRITE_PIPE (".gz", "gzip -c > %s"); + WRITE_PIPE (".lzma", "lzma -c > %s"); + WRITE_PIPE (".7z", "7z a -si %s 2>/dev/null"); + WRITE_PIPE (".xz", "xz -c > %s"); +#endif + file->file = fopen (path, "w"); + if (!file->file) + return false; + file->close = true; + file->reading = false; + file->compressed = false; + file->path = path; + file->bytes = 0; + return true; +} + +void kissat_close_file (file *file) { + assert (file); + assert (file->file); +#ifdef KISSAT_HAS_COMPRESSION + if (file->close && file->compressed) + pclose (file->file); +#else + assert (!file->compressed); +#endif + if (file->close && !file->compressed) + fclose (file->file); + file->file = 0; +} diff --git a/src/sat/kissat/file.h b/src/sat/kissat/file.h new file mode 100644 index 000000000..300cdb138 --- /dev/null +++ b/src/sat/kissat/file.h @@ -0,0 +1,124 @@ +#ifndef _file_h_INCLUDED +#define _file_h_INCLUDED + +#include +#include +#include +#include + +#include "attribute.h" +#include "keatures.h" + +bool kissat_file_exists (const char *path); +bool kissat_file_readable (const char *path); +bool kissat_file_writable (const char *path); +size_t kissat_file_size (const char *path); +bool kissat_find_executable (const char *name); + +typedef struct file file; + +struct file { + FILE *file; + bool close; + bool reading; + bool compressed; + const char *path; + uint64_t bytes; +}; + +void kissat_read_already_open_file (file *, FILE *, const char *path); +void kissat_write_already_open_file (file *, FILE *, const char *path); + +bool kissat_open_to_read_file (file *, const char *path); +bool kissat_open_to_write_file (file *, const char *path); + +void kissat_close_file (file *); + +#ifndef KISSAT_HAS_COMPRESSION + +bool kissat_looks_like_a_compressed_file (const char *path); + +#endif + +// clang-format off + +static inline size_t +kissat_read (file *, void *, size_t) ATTRIBUTE_ALWAYS_INLINE; + +static inline size_t +kissat_write (file *, void *, size_t) ATTRIBUTE_ALWAYS_INLINE; + +static inline int kissat_getc (file *) ATTRIBUTE_ALWAYS_INLINE; + +static inline int kissat_putc (file *, int) ATTRIBUTE_ALWAYS_INLINE; + +static inline void kissat_flush (file *) ATTRIBUTE_ALWAYS_INLINE; + +// clang-format on + +static inline size_t kissat_read (file *file, void *ptr, size_t bytes) { + assert (file); + assert (file->file); + assert (file->reading); +#ifdef KISSAT_HAS_UNLOCKEDIO + size_t res = fread_unlocked (ptr, 1, bytes, file->file); +#else + size_t res = fread (ptr, 1, bytes, file->file); +#endif + file->bytes += res; + return res; +} + +static inline size_t kissat_write (file *file, void *ptr, size_t bytes) { + assert (file); + assert (file->file); + assert (!file->reading); +#ifdef KISSAT_HAS_UNLOCKEDIO + size_t res = fwrite_unlocked (ptr, 1, bytes, file->file); +#else + size_t res = fwrite (ptr, 1, bytes, file->file); +#endif + file->bytes += res; + return res; +} + +static inline int kissat_getc (file *file) { + assert (file); + assert (file->file); + assert (file->reading); +#ifdef KISSAT_HAS_UNLOCKEDIO + int res = getc_unlocked (file->file); +#else + int res = getc (file->file); +#endif + if (res != EOF) + file->bytes++; + return res; +} + +static inline int kissat_putc (file *file, int ch) { + assert (file); + assert (file->file); + assert (!file->reading); +#ifdef KISSAT_HAS_UNLOCKEDIO + int res = putc_unlocked (ch, file->file); +#else + int res = putc (ch, file->file); +#endif + if (res != EOF) + file->bytes++; + return ch; +} + +static inline void kissat_flush (file *file) { + assert (file); + assert (file->file); + assert (!file->reading); +#ifdef KISSAT_HAS_UNLOCKEDIO + fflush_unlocked (file->file); +#else + fflush (file->file); +#endif +} + +#endif diff --git a/src/sat/kissat/flags.c b/src/sat/kissat/flags.c new file mode 100644 index 000000000..b7189350e --- /dev/null +++ b/src/sat/kissat/flags.c @@ -0,0 +1,115 @@ +#include "inline.h" +#include "inlineheap.h" +#include "inlinequeue.h" + +static inline void activate_literal (kissat *solver, unsigned lit) { + const unsigned idx = IDX (lit); + flags *f = FLAGS (idx); + if (f->active) + return; + lit = STRIP (lit); + LOG ("activating %s", LOGVAR (idx)); + f->active = true; + assert (!f->fixed); + assert (!f->eliminated); + solver->active++; + INC (variables_activated); + kissat_enqueue (solver, idx); + const double score = 1.0 - 1.0 / solver->statistics.variables_activated; + kissat_update_heap (solver, &solver->scores, idx, score); + if (solver->stable) { + const unsigned lit = LIT (idx); + if (!VALUE (lit)) + kissat_push_heap (solver, &solver->scores, idx); + } + assert (solver->unassigned < UINT_MAX); + solver->unassigned++; + kissat_mark_removed_literal (solver, lit); + kissat_mark_added_literal (solver, lit); + assert (!VALUE (lit)); + assert (!VALUE (NOT (lit))); + assert (!SAVED (idx)); + assert (!TARGET (idx)); + assert (!BEST (idx)); +} + +static inline void deactivate_variable (kissat *solver, flags *f, + unsigned idx) { + assert (solver->flags + idx == f); + LOG ("deactivating %s", LOGVAR (idx)); + assert (f->active); + assert (f->eliminated || f->fixed); + f->active = false; + assert (solver->active > 0); + solver->active--; + kissat_dequeue (solver, idx); + if (kissat_heap_contains (SCORES, idx)) + kissat_pop_heap (solver, SCORES, idx); +} + +void kissat_activate_literal (kissat *solver, unsigned lit) { + activate_literal (solver, lit); +} + +void kissat_activate_literals (kissat *solver, unsigned size, + unsigned *lits) { + for (unsigned i = 0; i < size; i++) + activate_literal (solver, lits[i]); +} + +void kissat_mark_fixed_literal (kissat *solver, unsigned lit) { + assert (VALUE (lit) > 0); + const unsigned idx = IDX (lit); + LOG ("marking internal %s as fixed", LOGVAR (idx)); + flags *f = FLAGS (idx); + assert (f->active); + assert (!f->eliminated); + assert (!f->fixed); + f->fixed = true; + deactivate_variable (solver, f, idx); + INC (units); + int elit = kissat_export_literal (solver, lit); + assert (elit); + PUSH_STACK (solver->units, elit); + LOG ("pushed external unit literal %d (internal %u)", elit, lit); +} + +void kissat_mark_eliminated_variable (kissat *solver, unsigned idx) { + const unsigned lit = LIT (idx); + assert (!VALUE (lit)); + LOG ("marking internal %s as eliminated", LOGVAR (idx)); + flags *f = FLAGS (idx); + assert (f->active); + assert (!f->eliminated); + assert (!f->fixed); + f->eliminated = true; + deactivate_variable (solver, f, idx); + int elit = kissat_export_literal (solver, lit); + assert (elit); + assert (elit != INT_MIN); + unsigned eidx = ABS (elit); + import *import = &PEEK_STACK (solver->import, eidx); + assert (!import->eliminated); + assert (import->imported); + assert (STRIP (import->lit) == STRIP (lit)); + size_t pos = SIZE_STACK (solver->eliminated); + assert (pos < (1u << 30)); + import->lit = pos; + import->eliminated = true; + PUSH_STACK (solver->eliminated, (value) 0); + LOG ("marked external variable %u as eliminated", eidx); + assert (solver->unassigned > 0); + solver->unassigned--; +} + +void kissat_mark_removed_literals (kissat *solver, unsigned size, + unsigned *lits) { + for (unsigned i = 0; i < size; i++) + kissat_mark_removed_literal (solver, lits[i]); +} + +void kissat_mark_added_literals (kissat *solver, unsigned size, + unsigned *lits) { + for (unsigned i = 0; i < size; i++) + kissat_mark_added_literal (solver, lits[i]); +} diff --git a/src/sat/kissat/flags.h b/src/sat/kissat/flags.h new file mode 100644 index 000000000..fd73b2f5b --- /dev/null +++ b/src/sat/kissat/flags.h @@ -0,0 +1,37 @@ +#ifndef _flags_h_INCLUDED +#define _flags_h_INCLUDED + +#include + +typedef struct flags flags; + +struct flags { + bool active : 1; + bool backbone0 : 1; + bool backbone1 : 1; + bool eliminate : 1; + bool eliminated : 1; + unsigned factor : 2; + bool fixed : 1; + bool subsume : 1; + bool sweep : 1; + bool transitive : 1; +}; + +#define FLAGS(IDX) (assert ((IDX) < VARS), (solver->flags + (IDX))) + +#define ACTIVE(IDX) (FLAGS (IDX)->active) +#define ELIMINATED(IDX) (FLAGS (IDX)->eliminated) + +struct kissat; + +void kissat_activate_literal (struct kissat *, unsigned); +void kissat_activate_literals (struct kissat *, unsigned, unsigned *); + +void kissat_mark_eliminated_variable (struct kissat *, unsigned idx); +void kissat_mark_fixed_literal (struct kissat *, unsigned lit); + +void kissat_mark_added_literals (struct kissat *, unsigned, unsigned *); +void kissat_mark_removed_literals (struct kissat *, unsigned, unsigned *); + +#endif diff --git a/src/sat/kissat/format.c b/src/sat/kissat/format.c new file mode 100644 index 000000000..d8e247d1a --- /dev/null +++ b/src/sat/kissat/format.c @@ -0,0 +1,145 @@ +#include "format.h" + +#include +#include +#include +#include +#include +#include + +char *kissat_next_format_string (format *format) { + assert (format->pos < NUM_FORMAT_STRINGS); + char *res = format->str[format->pos++]; + if (format->pos == NUM_FORMAT_STRINGS) + format->pos = 0; + return res; +} + +static void format_count (char *res, uint64_t w) { + if (w >= 128 && kissat_is_power_of_two (w)) { + unsigned l; + for (l = 0; ((uint64_t) 1 << l) != w; l++) + assert (l + 1 < 8 * sizeof (word)); + sprintf (res, "2^%u", l); + } else if (w >= 1000 && !(w % 1000)) { + unsigned l; + for (l = 0; !(w % 10); l++) + w /= 10; + sprintf (res, "%" PRIu64 "e%u", w, l); + } else + sprintf (res, "%" PRIu64, w); +} + +const char *kissat_format_count (format *format, uint64_t w) { + char *res = kissat_next_format_string (format); + format_count (res, w); + return res; +} + +const char *kissat_format_value (format *format, bool boolean, int value) { + if (boolean && value) + return "true"; + if (boolean && !value) + return "false"; + if (value == INT_MAX) + return "INT_MAX"; + if (value == INT_MIN) + return "INT_MIN"; + char *res = kissat_next_format_string (format); + if (value < 0) { + *res = '-'; + format_count (res + 1, ABS (value)); + } else + format_count (res, value); + return res; +} + +const char *kissat_format_bytes (format *format, uint64_t bytes) { + char *res = kissat_next_format_string (format); + if (bytes < (1u << 10)) + sprintf (res, "%" PRIu64 " bytes", bytes); + else if (bytes < (1u << 20)) + sprintf (res, "%" PRIu64 " bytes (%" PRIu64 " KB)", bytes, + (bytes + (1 << 9)) >> 10); + else if (bytes < (1u << 30)) + sprintf (res, "%" PRIu64 " bytes (%" PRIu64 " MB)", bytes, + (bytes + (1u << 19)) >> 20); + else + sprintf (res, "%" PRIu64 " bytes (%" PRIu64 " GB)", bytes, + (bytes + (1u << 29)) >> 30); + return res; +} + +const char *kissat_format_time (format *format, double seconds) { + if (!seconds) + return "0s"; + char *res = kissat_next_format_string (format); + uint64_t rounded = round (seconds); + uint64_t minutes = rounded / 60; + rounded %= 60; + uint64_t hours = minutes / 60; + minutes %= 60; + uint64_t days = hours / 24; + hours %= 24; + char *tmp = res; + if (days) { + sprintf (res, "%" PRIu64 "d", days); + tmp += strlen (res); + } + if (hours) { + if (tmp != res) + *tmp++ = ' '; + sprintf (tmp, "%" PRIu64 "h", hours); + tmp += strlen (tmp); + } + if (minutes) { + if (tmp != res) + *tmp++ = ' '; + sprintf (tmp, "%" PRIu64 "m", minutes); + tmp += strlen (tmp); + } + if (rounded) { + if (tmp != res) + *tmp++ = ' '; + sprintf (tmp, "%" PRIu64 "s", rounded); + } + return res; +} + +const char *kissat_format_signs (format *format, unsigned size, + word signs) { + char *res = kissat_next_format_string (format); + assert (size + 1 < FORMAT_STRING_SIZE); + char *p = res; + word bit = 1; + for (unsigned i = 0; i < size; i++, bit <<= 1) + *p++ = (bit & signs) ? '1' : '0'; + *p = 0; + return res; +} + +const char *kissat_format_ordinal (format *format, uint64_t ordinal) { + char const *suffix; + unsigned mod100 = ordinal % 100; + if (10 <= mod100 && mod100 <= 19) + suffix = "th"; + else { + switch (mod100 % 10) { + case 1: + suffix = "st"; + break; + case 2: + suffix = "nd"; + break; + case 3: + suffix = "rd"; + break; + default: + suffix = "th"; + break; + } + } + char *res = kissat_next_format_string (format); + sprintf (res, "%" PRIu64 "%s", ordinal, suffix); + return res; +} diff --git a/src/sat/kissat/format.h b/src/sat/kissat/format.h new file mode 100644 index 000000000..d52e93d71 --- /dev/null +++ b/src/sat/kissat/format.h @@ -0,0 +1,42 @@ +#ifndef _format_h_INCLUDED +#define _format_h_INCLUDED + +#include "utilities.h" + +#include +#include + +#define NUM_FORMAT_STRINGS 16 +#define FORMAT_STRING_SIZE 128 + +typedef struct format format; + +struct format { + unsigned pos; + char str[NUM_FORMAT_STRINGS][FORMAT_STRING_SIZE]; +}; + +char *kissat_next_format_string (format *); + +char const *kissat_format_bytes (format *, uint64_t bytes); +char const *kissat_format_count (format *, uint64_t); +char const *kissat_format_ordinal (format *, uint64_t); +char const *kissat_format_signs (format *, unsigned size, word); +char const *kissat_format_time (format *, double seconds); +char const *kissat_format_value (format *, bool boolean, int value); + +#define FORMAT_BYTES(BYTES) kissat_format_bytes (&solver->format, BYTES) + +#define FORMAT_COUNT(WORD) kissat_format_count (&solver->format, WORD) + +#define FORMAT_ORDINAL(WORD) kissat_format_ordinal (&solver->format, WORD) + +#define FORMAT_SIGNS(SIZE, SIGNS) \ + kissat_format_signs (&solver->format, SIZE, SIGNS) + +#define FORMAT_TIME(SECONDS) kissat_format_time (&solver->format, SECONDS) + +#define FORMAT_VALUE(BOOLEAN, VALUE) \ + kissat_format_value (&solver->format, BOOLEAN, VALUE) + +#endif diff --git a/src/sat/kissat/forward.c b/src/sat/kissat/forward.c new file mode 100644 index 000000000..d2bbbab47 --- /dev/null +++ b/src/sat/kissat/forward.c @@ -0,0 +1,691 @@ +#include "forward.h" +#include "allocate.h" +#include "eliminate.h" +#include "inline.h" +#include "print.h" +#include "rank.h" +#include "report.h" +#include "sort.h" +#include "terminate.h" + +#include + +static size_t remove_duplicated_binaries_with_literal (kissat *solver, + unsigned lit) { + watches *watches = &WATCHES (lit); + value *marks = solver->marks; + flags *flags = solver->flags; + + watch *begin = BEGIN_WATCHES (*watches), *q = begin; + const watch *const end = END_WATCHES (*watches), *p = q; + + while (p != end) { + const watch watch = *q++ = *p++; + assert (watch.type.binary); + const unsigned other = watch.binary.lit; + struct flags *f = flags + IDX (other); + if (!f->active) + continue; + if (!f->subsume) + continue; + const value marked = marks[other]; + if (marked) { + q--; + if (lit < other) { + kissat_delete_binary (solver, lit, other); + INC (duplicated); + } + } else { + const unsigned not_other = NOT (other); + if (marks[not_other]) { + LOGBINARY (lit, other, + "duplicate hyper unary resolution on %s " + "first antecedent", + LOGLIT (other)); + LOGBINARY (lit, not_other, + "duplicate hyper unary resolution on %s " + "second antecedent", + LOGLIT (not_other)); + PUSH_STACK (solver->delayed, lit); + } + marks[other] = 1; + } + } + + for (const watch *r = begin; r != q; r++) + marks[r->binary.lit] = 0; + + if (q == end) + return 0; + + size_t removed = end - q; + SET_END_OF_WATCHES (*watches, q); + LOG ("removed %zu watches with literal %s", removed, LOGLIT (lit)); + + return removed; +} + +static void remove_all_duplicated_binary_clauses (kissat *solver) { + LOG ("removing all duplicated irredundant binary clauses"); +#if !defined(QUIET) || !defined(NDEBUG) + size_t removed = 0; +#endif + assert (EMPTY_STACK (solver->delayed)); + + const flags *const all_flags = solver->flags; + + for (all_variables (idx)) { + const flags *const flags = all_flags + idx; + if (!flags->active) + continue; + if (!flags->subsume) + continue; + const unsigned int lit = LIT (idx); + const unsigned int not_lit = NOT (lit); +#if !defined(QUIET) || !defined(NDEBUG) + removed += +#endif + remove_duplicated_binaries_with_literal (solver, lit); +#if !defined(QUIET) || !defined(NDEBUG) + removed += +#endif + remove_duplicated_binaries_with_literal (solver, not_lit); + } + assert (!(removed & 1)); + + size_t units = SIZE_STACK (solver->delayed); + if (units) { + LOG ("found %zu hyper unary resolved units", units); + const value *const values = solver->values; + for (all_stack (unsigned, unit, solver->delayed)) { + + const value value = values[unit]; + if (value > 0) { + LOG ("skipping satisfied resolved unit %s", LOGLIT (unit)); + continue; + } + if (value < 0) { + LOG ("found falsified resolved unit %s", LOGLIT (unit)); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + solver->inconsistent = true; + break; + } + LOG ("new resolved unit clause %s", LOGLIT (unit)); + kissat_learned_unit (solver, unit); + } + CLEAR_STACK (solver->delayed); + if (!solver->inconsistent) + kissat_flush_units_while_connected (solver); + } + + REPORT (!removed && !units, '2'); +} + +static void find_forward_subsumption_candidates (kissat *solver, + references *candidates) { + const unsigned clslim = GET_OPTION (subsumeclslim); + + const value *const values = solver->values; + const flags *const flags = solver->flags; + + clause *last_irredundant = kissat_last_irredundant_clause (solver); + + for (all_clauses (c)) { + if (last_irredundant && c > last_irredundant) + break; + if (c->garbage) + continue; + c->subsume = false; + if (c->redundant) + continue; + if (c->size > clslim) + continue; + assert (c->size > 2); + unsigned subsume = 0; + for (all_literals_in_clause (lit, c)) { + const unsigned idx = IDX (lit); + const struct flags *f = flags + idx; + if (f->subsume) + subsume++; + if (values[lit] > 0) { + LOGCLS (c, "satisfied by %s", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + assert (c->garbage); + break; + } + } + if (c->garbage) + continue; + if (subsume < 2) + continue; + const unsigned ref = kissat_reference_clause (solver, c); + PUSH_STACK (*candidates, ref); + } +} + +static inline unsigned +get_size_of_reference (kissat *solver, ward *const arena, reference ref) { + assert (ref < SIZE_STACK (solver->arena)); + const clause *const c = (clause *) (arena + ref); + (void) solver; + return c->size; +} + +#define GET_SIZE_OF_REFERENCE(REF) \ + get_size_of_reference (solver, arena, (REF)) + +static void sort_forward_subsumption_candidates (kissat *solver, + references *candidates) { + reference *references = BEGIN_STACK (*candidates); + size_t size = SIZE_STACK (*candidates); + ward *const arena = BEGIN_STACK (solver->arena); + RADIX_SORT (reference, unsigned, size, references, GET_SIZE_OF_REFERENCE); +} + +static inline bool forward_literal (kissat *solver, unsigned lit, + bool binaries, unsigned *remove, + unsigned limit) { + watches *watches = &WATCHES (lit); + const size_t size_watches = SIZE_WATCHES (*watches); + + if (!size_watches) + return false; + + if (size_watches > limit) + return false; + + watch *begin = BEGIN_WATCHES (*watches), *q = begin; + const watch *const end = END_WATCHES (*watches), *p = q; + + uint64_t steps = 1 + kissat_cache_lines (size_watches, sizeof (watch)); + uint64_t checks = 0; + + const value *const values = solver->values; + const value *const marks = solver->marks; + ward *const arena = BEGIN_STACK (solver->arena); + + bool subsume = false; + + while (p != end) { + const watch watch = *q++ = *p++; + + if (watch.type.binary) { + if (!binaries) + continue; + + const unsigned other = watch.binary.lit; + if (marks[other]) { + LOGBINARY (lit, other, "forward subsuming"); + subsume = true; + break; + } else { + const unsigned not_other = NOT (other); + if (marks[not_other]) { + LOGBINARY (lit, other, "forward %s strengthener", LOGLIT (other)); + assert (!subsume); + *remove = not_other; + break; + } + } + } else { + const reference ref = watch.large.ref; + assert (ref < SIZE_STACK (solver->arena)); + clause *d = (clause *) (arena + ref); + steps++; + + if (d->garbage) { + q--; + continue; + } + + checks++; + subsume = true; + + unsigned candidate = INVALID_LIT; + + for (all_literals_in_clause (other, d)) { + if (marks[other]) + continue; + const value value = values[other]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (d, "satisfied by %s", LOGLIT (other)); + kissat_mark_clause_as_garbage (solver, d); + assert (d->garbage); + candidate = INVALID_LIT; + subsume = false; + break; + } + if (!subsume) { + assert (candidate != INVALID_LIT); + candidate = INVALID_LIT; + break; + } + subsume = false; + const unsigned not_other = NOT (other); + if (!marks[not_other]) { + assert (candidate == INVALID_LIT); + break; + } + candidate = not_other; + } + + if (d->garbage) { + assert (!subsume); + q--; + break; + } + + if (subsume) { + LOGCLS (d, "forward subsuming"); + assert (subsume); + break; + } + + if (candidate != INVALID_LIT) { + LOGCLS (d, "forward %s strengthener", LOGLIT (candidate)); + *remove = candidate; + } + } + } + + if (p != q) { + while (p != end) + *q++ = *p++; + + SET_END_OF_WATCHES (*watches, q); + } + + ADD (subsumption_checks, checks); + ADD (forward_checks, checks); + ADD (forward_steps, steps); + + return subsume; +} + +static inline bool forward_marked_clause (kissat *solver, clause *c, + unsigned *remove) { + const unsigned limit = GET_OPTION (subsumeocclim); + const flags *const flags = solver->flags; + INC (forward_steps); + + for (all_literals_in_clause (lit, c)) { + const unsigned idx = IDX (lit); + if (!flags[idx].active) + continue; + + assert (!VALUE (lit)); + + if (forward_literal (solver, lit, true, remove, limit)) + return true; + + if (forward_literal (solver, NOT (lit), false, remove, limit)) + return true; + } + return false; +} + +static bool forward_subsumed_clause (kissat *solver, clause *c, + bool *strengthened, + unsigneds *new_binaries) { + assert (!c->garbage); + LOGCLS2 (c, "trying to forward subsume"); + + value *marks = solver->marks; + const value *const values = solver->values; + unsigned non_false = 0, unit = INVALID_LIT; + + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value < 0) + continue; + if (value > 0) { + LOGCLS (c, "satisfied by %s", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + assert (c->garbage); + break; + } + marks[lit] = 1; + if (non_false++) + unit ^= lit; + else + unit = lit; + } + + if (c->garbage || non_false <= 1) + for (all_literals_in_clause (lit, c)) + marks[lit] = 0; + + if (c->garbage) + return false; + + if (!non_false) { + LOGCLS (c, "found falsified clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + solver->inconsistent = true; + return false; + } + + if (non_false == 1) { + assert (VALID_INTERNAL_LITERAL (unit)); + LOG ("new remaining non-false literal unit clause %s", LOGLIT (unit)); + kissat_learned_unit (solver, unit); + kissat_mark_clause_as_garbage (solver, c); + kissat_flush_units_while_connected (solver); + return false; + } + + unsigned remove = INVALID_LIT; + const bool subsume = forward_marked_clause (solver, c, &remove); + + for (all_literals_in_clause (lit, c)) + marks[lit] = 0; + + if (subsume) { + LOGCLS (c, "forward subsumed"); + kissat_mark_clause_as_garbage (solver, c); + INC (subsumed); + INC (forward_subsumed); + } else if (remove != INVALID_LIT) { + *strengthened = true; + INC (strengthened); + INC (forward_strengthened); + LOGCLS (c, "forward strengthening by removing %s in", LOGLIT (remove)); + if (non_false == 2) { + unit ^= remove; + assert (VALID_INTERNAL_LITERAL (unit)); + LOG ("forward strengthened unit clause %s", LOGLIT (unit)); + kissat_learned_unit (solver, unit); + kissat_mark_clause_as_garbage (solver, c); + kissat_flush_units_while_connected (solver); + LOGCLS (c, "%s satisfied", LOGLIT (unit)); + } else { + SHRINK_CLAUSE_IN_PROOF (c, remove, INVALID_LIT); + CHECK_SHRINK_CLAUSE (c, remove, INVALID_LIT); + kissat_mark_removed_literal (solver, remove); + if (non_false > 3) { + unsigned *lits = c->lits; + unsigned new_size = 0; + for (unsigned i = 0; i < c->size; i++) { + const unsigned lit = lits[i]; + if (remove == lit) + continue; + const value value = values[lit]; + if (value < 0) + continue; + assert (!value); + lits[new_size++] = lit; + kissat_mark_added_literal (solver, lit); + } + assert (new_size == non_false - 1); + assert (new_size > 2); + if (!c->shrunken) { + c->shrunken = true; + lits[c->size - 1] = INVALID_LIT; + } + c->size = new_size; + c->searched = 2; + c->subsume = true; + LOGCLS (c, "forward strengthened"); + } else { + assert (non_false == 3); + LOGCLS (c, "garbage"); + assert (!c->garbage); + const size_t bytes = kissat_actual_bytes_of_clause (c); + ADD (arena_garbage, bytes); + c->garbage = true; + unsigned first = INVALID_LIT, second = INVALID_LIT; + for (all_literals_in_clause (lit, c)) { + if (lit == remove) + continue; + const value value = values[lit]; + if (value < 0) + continue; + assert (!value); + if (first == INVALID_LIT) + first = lit; + else { + assert (second == INVALID_LIT); + second = lit; + } + kissat_mark_added_literal (solver, lit); + } + assert (first != INVALID_LIT); + assert (second != INVALID_LIT); + LOGBINARY (first, second, "forward strengthened"); + kissat_watch_other (solver, first, second); + kissat_watch_other (solver, second, first); + assert (new_binaries); + assert (solver->statistics.clauses_irredundant); + solver->statistics.clauses_irredundant--; + assert (solver->statistics.clauses_binary < UINT64_MAX); + solver->statistics.clauses_binary++; + PUSH_STACK (*new_binaries, first); + PUSH_STACK (*new_binaries, second); + } + } + } + + return subsume; +} + +static void connect_subsuming (kissat *solver, unsigned occlim, clause *c) { + assert (!c->garbage); + + unsigned min_lit = INVALID_LIT; + size_t min_occs = MAX_SIZE_T; + + const flags *const all_flags = solver->flags; + + bool subsume = true; + + for (all_literals_in_clause (lit, c)) { + const unsigned idx = IDX (lit); + const flags *const flags = all_flags + idx; + if (!flags->active) + continue; + if (!flags->subsume) { + subsume = false; + break; + } + watches *watches = &WATCHES (lit); + const size_t occs = SIZE_WATCHES (*watches); + if (min_lit != INVALID_LIT && occs > min_occs) + continue; + min_lit = lit; + min_occs = occs; + } + if (!subsume) + return; + + if (min_occs > occlim) + return; + LOG ("connecting %s with %zu occurrences", LOGLIT (min_lit), min_occs); + const reference ref = kissat_reference_clause (solver, c); + kissat_connect_literal (solver, min_lit, ref); +} + +static bool forward_subsume_all_clauses (kissat *solver) { + references candidates; + INIT_STACK (candidates); + + find_forward_subsumption_candidates (solver, &candidates); +#ifndef QUIET + size_t scheduled = SIZE_STACK (candidates); + kissat_phase ( + solver, "forward", GET (forward_subsumptions), + "scheduled %zu irredundant clauses %.0f%%", scheduled, + kissat_percent (scheduled, solver->statistics.clauses_irredundant)); +#endif + sort_forward_subsumption_candidates (solver, &candidates); + + const reference *const end_of_candidates = END_STACK (candidates); + reference *p = BEGIN_STACK (candidates); + +#ifndef QUIET + size_t subsumed = 0; + size_t strengthened = 0; + size_t checked = 0; +#endif + const unsigned occlim = GET_OPTION (subsumeocclim); + + unsigneds new_binaries; + INIT_STACK (new_binaries); + + { + SET_EFFORT_LIMIT (steps_limit, forward, forward_steps); + + ward *arena = BEGIN_STACK (solver->arena); + + while (p != end_of_candidates) { + if (solver->statistics.forward_steps > steps_limit) + break; + if (TERMINATED (forward_terminated_1)) + break; + reference ref = *p++; + clause *c = (clause *) (arena + ref); + assert (kissat_clause_in_arena (solver, c)); + assert (!c->garbage); +#ifndef QUIET + checked++; +#endif + bool not_subsumed_but_strengthened = false; + if (forward_subsumed_clause ( + solver, c, ¬_subsumed_but_strengthened, &new_binaries)) { +#ifndef QUIET + subsumed++; +#endif + } else if (not_subsumed_but_strengthened) { +#ifndef QUIET + strengthened++; +#endif + } + if (solver->inconsistent) + break; + if (!c->garbage) + connect_subsuming (solver, occlim, c); + } + } +#ifndef QUIET + if (subsumed) + kissat_phase (solver, "forward", GET (forward_subsumptions), + "subsumed %zu clauses %.2f%% of %zu checked %.0f%%", + subsumed, kissat_percent (subsumed, checked), checked, + kissat_percent (checked, scheduled)); + if (strengthened) + kissat_phase (solver, "forward", GET (forward_subsumptions), + "strengthened %zu clauses %.2f%% of %zu checked %.0f%%", + strengthened, kissat_percent (strengthened, checked), + checked, kissat_percent (checked, scheduled)); + if (!subsumed && !strengthened) + kissat_phase (solver, "forward", GET (forward_subsumptions), + "no clause subsumed nor strengthened " + "out of %zu checked %.0f%%", + checked, kissat_percent (checked, scheduled)); +#endif + struct flags *flags = solver->flags; + + for (all_variables (idx)) + flags[idx].subsume = false; + + ward *arena = BEGIN_STACK (solver->arena); + unsigned reactivated = 0; +#ifndef QUIET + size_t remain = 0; +#endif + for (reference *q = BEGIN_STACK (candidates); q != end_of_candidates; + q++) { + const reference ref = *q; + clause *c = (clause *) (arena + ref); + assert (kissat_clause_in_arena (solver, c)); + if (c->garbage) + continue; + if (q < p && !c->subsume) + continue; +#ifndef QUIET + remain++; +#endif + for (all_literals_in_clause (lit, c)) { + const unsigned idx = IDX (lit); + struct flags *f = flags + idx; + if (f->subsume) + continue; + LOGCLS (c, + "reactivating subsume flag of %s " + "in remaining or strengthened", + LOGVAR (idx)); + f->subsume = true; + assert (reactivated < UINT_MAX); + reactivated++; + } + } + + while (!EMPTY_STACK (new_binaries)) { + unsigned lits[2]; + lits[1] = POP_STACK (new_binaries); + lits[0] = POP_STACK (new_binaries); + for (unsigned i = 0; i < 2; i++) { + const unsigned lit = lits[i]; + const unsigned idx = IDX (lit); + struct flags *f = flags + idx; + if (f->subsume) + continue; + LOGBINARY (lits[0], lits[1], + "reactivating subsume flag of %s " + "in strengthened binary clause", + LOGVAR (idx)); + f->subsume = true; + assert (reactivated < UINT_MAX); + reactivated++; + } + } + RELEASE_STACK (new_binaries); + + kissat_very_verbose (solver, + "marked %u variables %.0f%% to be reconsidered " + "in next forward subsumption", + reactivated, + kissat_percent (reactivated, solver->active)); +#ifndef QUIET + if (remain) + kissat_phase (solver, "forward", GET (forward_subsumptions), + "%zu unchecked clauses remain %.0f%%", remain, + kissat_percent (remain, scheduled)); + else + kissat_phase (solver, "forward", GET (forward_subsumptions), + "all %zu scheduled clauses checked", scheduled); +#endif + RELEASE_STACK (candidates); + REPORT (!subsumed, 's'); + + bool completed; + if (solver->inconsistent) + completed = true; + else if (reactivated) + completed = false; + else + completed = true; +#ifndef QUIET + kissat_very_verbose (solver, "forward subsumption considered %scomplete", + completed ? "" : "in"); +#endif + return completed; +} + +bool kissat_forward_subsume_during_elimination (kissat *solver) { + START (subsume); + START (forward); + assert (GET_OPTION (forward)); + INC (forward_subsumptions); + assert (!solver->watching); + remove_all_duplicated_binary_clauses (solver); + bool complete = true; + if (!solver->inconsistent) + complete = forward_subsume_all_clauses (solver); + STOP (forward); + STOP (subsume); + return complete; +} diff --git a/src/sat/kissat/forward.h b/src/sat/kissat/forward.h new file mode 100644 index 000000000..95a8dc5d3 --- /dev/null +++ b/src/sat/kissat/forward.h @@ -0,0 +1,10 @@ +#ifndef _forward_h_INCLUDED +#define _forward_h_INCLUDED + +#include + +struct kissat; + +bool kissat_forward_subsume_during_elimination (struct kissat *); + +#endif diff --git a/src/sat/kissat/frames.h b/src/sat/kissat/frames.h new file mode 100644 index 000000000..088877b2a --- /dev/null +++ b/src/sat/kissat/frames.h @@ -0,0 +1,32 @@ +#ifndef _frames_h_INCLUDED +#define _frames_h_INCLUDED + +#include "literal.h" +#include "stack.h" + +#include + +typedef struct frame frame; +typedef struct slice slice; + +struct frame { + bool promote; + unsigned decision; + unsigned trail; + unsigned used; +#ifndef NDEBUG + unsigned saved; +#endif +}; + +// clang-format off + +typedef STACK (frame) frames; + +// clang-format on + +struct kissat; + +#define FRAME(LEVEL) (PEEK_STACK (solver->frames, (LEVEL))) + +#endif diff --git a/src/sat/kissat/gates.c b/src/sat/kissat/gates.c new file mode 100644 index 000000000..7da560181 --- /dev/null +++ b/src/sat/kissat/gates.c @@ -0,0 +1,104 @@ +#include "gates.h" +#include "ands.h" +#include "definition.h" +#include "eliminate.h" +#include "equivalences.h" +#include "ifthenelse.h" +#include "inline.h" + +size_t kissat_mark_binaries (kissat *solver, unsigned lit) { + value *marks = solver->marks; + size_t res = 0; + watches *watches = &WATCHES (lit); + for (all_binary_large_watches (watch, *watches)) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + assert (!solver->values[other]); + if (marks[other]) + continue; + marks[other] = 1; + res++; + } + return res; +} + +void kissat_unmark_binaries (kissat *solver, unsigned lit) { + value *marks = solver->marks; + watches *watches = &WATCHES (lit); + for (all_binary_large_watches (watch, *watches)) + if (watch.type.binary) + marks[watch.binary.lit] = 0; +} + +bool kissat_find_gates (kissat *solver, unsigned lit) { + solver->gate_eliminated = 0; + solver->resolve_gate = false; + if (!GET_OPTION (extract)) + return false; + INC (gates_checked); + const unsigned not_lit = NOT (lit); + if (EMPTY_WATCHES (WATCHES (not_lit))) + return false; + bool res = false; + if (kissat_find_equivalence_gate (solver, lit)) + res = true; + else if (kissat_find_and_gate (solver, lit, 0)) + res = true; + else if (kissat_find_and_gate (solver, not_lit, 1)) + res = true; + else if (kissat_find_if_then_else_gate (solver, lit, 0)) + res = true; + else if (kissat_find_if_then_else_gate (solver, not_lit, 1)) + res = true; + else if (kissat_find_definition (solver, lit)) + res = true; + if (res) + INC (gates_extracted); + return res; +} + +static void get_antecedents (kissat *solver, unsigned lit, + unsigned negative) { + assert (!solver->watching); + assert (!negative || negative == 1); + + statches *gates = solver->gates + negative; + watches *watches = &WATCHES (lit); + + statches *antecedents = solver->antecedents + negative; + assert (EMPTY_STACK (*antecedents)); + + const watch *const begin_gates = BEGIN_STACK (*gates); + const watch *const end_gates = END_STACK (*gates); + watch const *g = begin_gates; + + const watch *const begin_watches = BEGIN_WATCHES (*watches); + const watch *const end_watches = END_WATCHES (*watches); + watch const *w = begin_watches; + + while (w != end_watches) { + const watch watch = *w++; + if (g != end_gates && g->raw == watch.raw) + g++; + else + PUSH_STACK (*antecedents, watch); + } + + assert (g == end_gates); +#ifdef LOGGING + size_t size_gates = SIZE_STACK (*gates); + size_t size_antecedents = SIZE_STACK (*antecedents); + size_t size_watches = SIZE_WATCHES (*watches); + LOG ("got %zu antecedent %.0f%% and %zu gate clauses %.0f%% " + "out of %zu watches of literal %s", + size_antecedents, kissat_percent (size_antecedents, size_watches), + size_gates, kissat_percent (size_gates, size_watches), size_watches, + LOGLIT (lit)); +#endif +} + +void kissat_get_antecedents (kissat *solver, unsigned lit) { + get_antecedents (solver, lit, 0); + get_antecedents (solver, NOT (lit), 1); +} diff --git a/src/sat/kissat/gates.h b/src/sat/kissat/gates.h new file mode 100644 index 000000000..f5ce18a90 --- /dev/null +++ b/src/sat/kissat/gates.h @@ -0,0 +1,22 @@ +#ifndef _gates_h_INCLUDED +#define _gates_h_INCLUDED + +#include +#include + +struct kissat; +struct clause; + +bool kissat_find_gates (struct kissat *, unsigned lit); +void kissat_get_antecedents (struct kissat *, unsigned lit); + +size_t kissat_mark_binaries (struct kissat *, unsigned lit); +void kissat_unmark_binaries (struct kissat *, unsigned lit); + +#ifndef METRICS +#define GATE_ELIMINATED(...) true +#else +#define GATE_ELIMINATED(NAME) (&solver->statistics.NAME##_eliminated) +#endif + +#endif diff --git a/src/sat/kissat/handle.h b/src/sat/kissat/handle.h new file mode 100644 index 000000000..132fc3b68 --- /dev/null +++ b/src/sat/kissat/handle.h @@ -0,0 +1,43 @@ +#ifndef _handle_h_INCLUDED +#define _handle_h_INCLUDED + +#include + +void kissat_init_signal_handler (void (*handler) (int)); +void kissat_reset_signal_handler (void); + +void kissat_init_alarm (void (*handler) (void)); +void kissat_reset_alarm (void); + +#ifdef __MINGW32__ +#define SIGNAL_SIGBUS +#else +#define SIGNAL_SIGBUS SIGNAL (SIGBUS) +#endif + +#define SIGNALS \ + SIGNAL (SIGABRT) \ + SIGNAL_SIGBUS \ + SIGNAL (SIGINT) \ + SIGNAL (SIGSEGV) \ + SIGNAL (SIGTERM) + +// clang-format off + +static inline const char * +kissat_signal_name (int sig) +{ +#define SIGNAL(SIG) \ + if (sig == SIG) return #SIG; + SIGNALS +#undef SIGNAL +#ifndef __MINGW32__ + if (sig == SIGALRM) + return "SIGALRM"; +#endif + return "SIGUNKNOWN"; +} + +// clang-format on + +#endif diff --git a/src/sat/kissat/heap.c b/src/sat/kissat/heap.c new file mode 100644 index 000000000..12deaad75 --- /dev/null +++ b/src/sat/kissat/heap.c @@ -0,0 +1,108 @@ +#include "allocate.h" +#include "inlineheap.h" +#include "internal.h" +#include "logging.h" + +#include + +void kissat_release_heap (kissat *solver, heap *heap) { + RELEASE_STACK (heap->stack); + DEALLOC (heap->pos, heap->size); + DEALLOC (heap->score, heap->size); + memset (heap, 0, sizeof *heap); +} + +#ifndef NDEBUG + +void kissat_check_heap (heap *heap) { + const unsigned *const stack = BEGIN_STACK (heap->stack); + const unsigned end = SIZE_STACK (heap->stack); + const unsigned *const pos = heap->pos; + const double *const score = heap->score; + for (unsigned i = 0; i < end; i++) { + const unsigned idx = stack[i]; + const unsigned idx_pos = pos[idx]; + assert (idx_pos == i); + unsigned child_pos = HEAP_CHILD (idx_pos); + unsigned parent_pos = HEAP_PARENT (child_pos); + assert (parent_pos == idx_pos); + if (child_pos < end) { + unsigned child = stack[child_pos]; + assert (score[idx] >= score[child]); + if (++child_pos < end) { + parent_pos = HEAP_PARENT (child_pos); + assert (parent_pos == idx_pos); + child = stack[child_pos]; + assert (score[idx] >= score[child]); + } + } + } +} + +#endif + +void kissat_resize_heap (kissat *solver, heap *heap, unsigned new_size) { + const unsigned old_size = heap->size; + if (old_size >= new_size) + return; + LOG ("resizing %s heap from %u to %u", + (heap->tainted ? "tainted" : "untainted"), old_size, new_size); + + heap->pos = kissat_nrealloc (solver, heap->pos, old_size, new_size, + sizeof (unsigned)); + if (heap->tainted) { + heap->score = kissat_nrealloc (solver, heap->score, old_size, new_size, + sizeof (double)); + } else { + if (old_size) + DEALLOC (heap->score, old_size); + heap->score = kissat_calloc (solver, new_size, sizeof (double)); + } + heap->size = new_size; +#ifdef CHECK_HEAP + kissat_check_heap (heap); +#endif +} + +void kissat_rescale_heap (kissat *solver, heap *heap, double factor) { + LOG ("rescaling scores on heap with factor %g", factor); + double *score = heap->score; + for (unsigned i = 0; i < heap->vars; i++) + score[i] *= factor; +#ifndef NDEBUG + kissat_check_heap (heap); +#endif +#ifndef LOGGING + (void) solver; +#endif +} + +void kissat_enlarge_heap (kissat *solver, heap *heap, unsigned new_vars) { + const unsigned old_vars = heap->vars; + assert (old_vars < new_vars); + assert (new_vars <= heap->size); + const size_t delta = new_vars - heap->vars; + memset (heap->pos + old_vars, 0xff, delta * sizeof (unsigned)); + heap->vars = new_vars; + if (heap->tainted) + memset (heap->score + old_vars, 0, delta * sizeof (double)); + LOG ("enlarged heap from %u to %u", old_vars, new_vars); +#ifndef LOGGING + (void) solver; +#endif +} + +#ifndef NDEBUG + +static void dump_heap (heap *heap) { + for (unsigned i = 0; i < SIZE_STACK (heap->stack); i++) + printf ("heap.stack[%u] = %u\n", i, PEEK_STACK (heap->stack, i)); + for (unsigned i = 0; i < heap->vars; i++) + printf ("heap.pos[%u] = %u\n", i, heap->pos[i]); + for (unsigned i = 0; i < heap->vars; i++) + printf ("heap.score[%u] = %g\n", i, heap->score[i]); +} + +void kissat_dump_heap (heap *heap) { dump_heap (heap); } + +#endif diff --git a/src/sat/kissat/heap.h b/src/sat/kissat/heap.h new file mode 100644 index 000000000..8a9736ee2 --- /dev/null +++ b/src/sat/kissat/heap.h @@ -0,0 +1,85 @@ +#ifndef _heap_h_INCLUDED +#define _heap_h_INCLUDED + +#include "stack.h" +#include "utilities.h" + +#include +#include +#include + +#define DISCONTAIN UINT_MAX +#define DISCONTAINED(IDX) ((int) (IDX) < 0) + +typedef struct heap heap; + +struct heap { + bool tainted; + unsigned vars; + unsigned size; + unsigneds stack; + double *score; + unsigned *pos; +}; + +struct kissat; + +void kissat_resize_heap (struct kissat *, heap *, unsigned size); +void kissat_release_heap (struct kissat *, heap *); + +static inline bool kissat_heap_contains (heap *heap, unsigned idx) { + return idx < heap->vars && !DISCONTAINED (heap->pos[idx]); +} + +static inline unsigned kissat_get_heap_pos (const heap *heap, + unsigned idx) { + return idx < heap->vars ? heap->pos[idx] : DISCONTAIN; +} + +static inline double kissat_get_heap_score (const heap *heap, + unsigned idx) { + return idx < heap->vars ? heap->score[idx] : 0.0; +} + +static inline bool kissat_empty_heap (heap *heap) { + return EMPTY_STACK (heap->stack); +} + +static inline size_t kissat_size_heap (heap *heap) { + return SIZE_STACK (heap->stack); +} + +static inline unsigned kissat_max_heap (heap *heap) { + assert (!kissat_empty_heap (heap)); + return PEEK_STACK (heap->stack, 0); +} + +void kissat_rescale_heap (struct kissat *, heap *heap, double factor); + +void kissat_enlarge_heap (struct kissat *, heap *, unsigned new_vars); + +static inline double kissat_max_score_on_heap (heap *heap) { + if (!heap->tainted) + return 0; + assert (heap->vars); + const double *const score = heap->score; + const double *const end = score + heap->vars; + double res = score[0]; + for (const double *p = score + 1; p != end; p++) + res = MAX (res, *p); + return res; +} + +#ifndef NDEBUG +void kissat_dump_heap (heap *); +#endif + +#ifndef NDEBUG +void kissat_check_heap (heap *); +#else +#define kissat_check_heap(...) \ + do { \ + } while (0) +#endif + +#endif diff --git a/src/sat/kissat/ifthenelse.c b/src/sat/kissat/ifthenelse.c new file mode 100644 index 000000000..db4031f68 --- /dev/null +++ b/src/sat/kissat/ifthenelse.c @@ -0,0 +1,174 @@ +#include "ifthenelse.h" +#include "eliminate.h" +#include "gates.h" +#include "inline.h" + +static bool get_ternary_clause (kissat *solver, reference ref, unsigned *p, + unsigned *q, unsigned *r) { + clause *clause = kissat_dereference_clause (solver, ref); + if (clause->garbage) + return false; + const value *const values = solver->values; + unsigned a = INVALID_LIT, b = INVALID_LIT, c = INVALID_LIT; + unsigned found = 0; + for (all_literals_in_clause (other, clause)) { + const value value = values[other]; + if (value > 0) { + kissat_eliminate_clause (solver, clause, INVALID_LIT); + return false; + } + if (value < 0) + continue; + if (++found == 1) + a = other; + else if (found == 2) + b = other; + else if (found == 3) + c = other; + else + return false; + } + if (found != 3) + return false; + assert (a != INVALID_LIT); + assert (b != INVALID_LIT); + assert (c != INVALID_LIT); + *p = a; + *q = b; + *r = c; + return true; +} + +static bool match_ternary_ref (kissat *solver, reference ref, unsigned a, + unsigned b, unsigned c) { + clause *clause = kissat_dereference_clause (solver, ref); + if (clause->garbage) + return false; + const value *const values = solver->values; + unsigned found = 0; + for (all_literals_in_clause (other, clause)) { + const value value = values[other]; + if (value > 0) { + kissat_eliminate_clause (solver, clause, INVALID_LIT); + return false; + } + if (value < 0) + continue; + if (a != other && b != other && c != other) + return false; + found++; + } + if (found == 3) + return true; + solver->resolve_gate = true; + return true; +} + +static bool match_ternary_watch (kissat *solver, watch watch, unsigned a, + unsigned b, unsigned c) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + if (other != b && other != c) + return false; + solver->resolve_gate = true; + return true; + } else { + const reference ref = watch.large.ref; + return match_ternary_ref (solver, ref, a, b, c); + } +} + +static inline const watch *find_ternary_clause (kissat *solver, + uint64_t *steps, unsigned a, + unsigned b, unsigned c) { + watches *watches = &WATCHES (a); + const watch *const begin = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + for (const watch *p = begin; p != end; p++) { + *steps += 1; + if (match_ternary_watch (solver, *p, a, b, c)) + return p; + } + return 0; +} + +bool kissat_find_if_then_else_gate (kissat *solver, unsigned lit, + unsigned negative) { + if (!GET_OPTION (ifthenelse)) + return false; + watches *watches = &WATCHES (lit); + const watch *const begin = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); + if (begin == end) + return false; + uint64_t large_clauses = 0; + for (const watch *p = begin; p != end; p++) + if (!p->type.binary) + large_clauses++; + const uint64_t limit = GET_OPTION (eliminateocclim); + if (large_clauses * large_clauses > limit) + return false; + const watch *const last = end - 1; + uint64_t steps = 0; + for (const watch *p1 = begin; steps < limit && p1 != last; p1++) { + watch w1 = *p1; + if (w1.type.binary) + continue; + unsigned a1, b1, c1; + if (!get_ternary_clause (solver, p1->large.ref, &a1, &b1, &c1)) + continue; + if (b1 == lit) + SWAP (unsigned, a1, b1); + if (c1 == lit) + SWAP (unsigned, a1, c1); + assert (a1 == lit); + for (const watch *p2 = p1 + 1; steps < limit && p2 != end; p2++) { + watch w2 = *p2; + if (w2.type.binary) + continue; + unsigned a2, b2, c2; + if (!get_ternary_clause (solver, p2->large.ref, &a2, &b2, &c2)) + continue; + if (b2 == lit) + SWAP (unsigned, a2, b2); + if (c2 == lit) + SWAP (unsigned, a2, c2); + assert (a2 == lit); + if (STRIP (b1) == STRIP (c2)) + SWAP (unsigned, b2, c2); + if (STRIP (c1) == STRIP (c2)) + continue; + const unsigned not_b2 = NOT (b2); + if (b1 != not_b2) + continue; + solver->resolve_gate = false; + const unsigned not_lit = NOT (lit); + const unsigned not_c1 = NOT (c1); + const watch *const p3 = + find_ternary_clause (solver, &steps, not_lit, b1, not_c1); + if (!p3) + continue; + const unsigned not_c2 = NOT (c2); + const watch *const p4 = + find_ternary_clause (solver, &steps, not_lit, b2, not_c2); + if (!p4) + continue; + watch w3 = p3 < p4 ? *p3 : *p4; + watch w4 = p3 < p4 ? *p4 : *p3; + LOGWATCH (lit, w1, "1st if-then-else"); + LOGWATCH (lit, w2, "2nd if-then-else"); + LOGWATCH (not_lit, w3, "3rd if-then-else"); + LOGWATCH (not_lit, w4, "4th if-then-else"); + LOG ("found if-then-else gate %s = (%s ? %s : %s)", LOGLIT (lit), + LOGLIT (NOT (b1)), LOGLIT (not_c1), LOGLIT (not_c2)); + solver->gate_eliminated = GATE_ELIMINATED (if_then_else); + PUSH_STACK (solver->gates[negative], w1); + PUSH_STACK (solver->gates[negative], w2); + PUSH_STACK (solver->gates[!negative], w3); + PUSH_STACK (solver->gates[!negative], w4); + INC (if_then_else_extracted); + return true; + } + } + return false; +} diff --git a/src/sat/kissat/ifthenelse.h b/src/sat/kissat/ifthenelse.h new file mode 100644 index 000000000..82097883e --- /dev/null +++ b/src/sat/kissat/ifthenelse.h @@ -0,0 +1,11 @@ +#ifndef _ifthenelse_h_INCLUDED +#define _ifthenelse_h_INCLUDED + +#include + +struct kissat; + +bool kissat_find_if_then_else_gate (struct kissat *, unsigned lit, + unsigned negative); + +#endif diff --git a/src/sat/kissat/import.c b/src/sat/kissat/import.c new file mode 100644 index 000000000..38ca379c0 --- /dev/null +++ b/src/sat/kissat/import.c @@ -0,0 +1,100 @@ +#include "internal.h" +#include "logging.h" +#include "resize.h" + +static void adjust_imports_for_external_literal (kissat *solver, + unsigned eidx) { + while (eidx >= SIZE_STACK (solver->import)) { + struct import import; + import.lit = 0; + import.extension = false; + import.imported = false; + import.eliminated = false; + PUSH_STACK (solver->import, import); + } +} + +static void adjust_exports_for_external_literal (kissat *solver, + unsigned eidx, + bool extension) { + struct import *import = &PEEK_STACK (solver->import, eidx); + unsigned iidx = solver->vars; + kissat_enlarge_variables (solver, iidx + 1); + unsigned ilit = 2 * iidx; + import->extension = extension; + import->imported = true; + if (extension) + INC (variables_extension); + else + INC (variables_original); + assert (!import->eliminated); + import->lit = ilit; + LOG ("importing %s external variable %u as internal literal %u", + extension ? "extension" : "original", eidx, ilit); + while (iidx >= SIZE_STACK (solver->export)) + PUSH_STACK (solver->export, 0); + POKE_STACK (solver->export, iidx, (int) eidx); + LOG ("exporting internal variable %u as external literal %u", iidx, eidx); +} + +static inline unsigned import_literal (kissat *solver, int elit, + bool extension) { + const unsigned eidx = ABS (elit); + adjust_imports_for_external_literal (solver, eidx); + struct import *import = &PEEK_STACK (solver->import, eidx); + if (import->eliminated) + return INVALID_LIT; + unsigned ilit; + if (!import->imported) + adjust_exports_for_external_literal (solver, eidx, extension); + assert (import->imported); + ilit = import->lit; + if (elit < 0) + ilit = NOT (ilit); + assert (VALID_INTERNAL_LITERAL (ilit)); + return ilit; +} + +unsigned kissat_import_literal (kissat *solver, int elit) { + assert (VALID_EXTERNAL_LITERAL (elit)); + if (GET_OPTION (tumble)) + return import_literal (solver, elit, false); + const unsigned eidx = ABS (elit); + assert (SIZE_STACK (solver->import) <= UINT_MAX); + unsigned other = SIZE_STACK (solver->import); + if (eidx < other) + return import_literal (solver, elit, false); + if (!other) + adjust_imports_for_external_literal (solver, other++); + + unsigned ilit = 0; + do { + assert (VALID_EXTERNAL_LITERAL ((int) other)); + ilit = import_literal (solver, other, false); + } while (other++ < eidx); + + if (elit < 0) + ilit = NOT (ilit); + + return ilit; +} + +unsigned kissat_fresh_literal (kissat *solver) { + size_t imported = SIZE_STACK (solver->import); + assert (imported <= EXTERNAL_MAX_VAR); + if (imported == EXTERNAL_MAX_VAR) { + LOG ("can not get another external variable"); + return INVALID_LIT; + } + assert (imported <= (unsigned) INT_MAX); + int eidx = (int) imported; + unsigned res = import_literal (solver, eidx, true); +#ifndef NDEBUG + struct import *import = &PEEK_STACK (solver->import, imported); + assert (import->imported); + assert (import->extension); +#endif + INC (fresh); + kissat_activate_literal (solver, res); + return res; +} diff --git a/src/sat/kissat/import.h b/src/sat/kissat/import.h new file mode 100644 index 000000000..4b82a5f3c --- /dev/null +++ b/src/sat/kissat/import.h @@ -0,0 +1,9 @@ +#ifndef _import_h_INLCUDED +#define _import_h_INLCUDED + +struct kissat; + +unsigned kissat_import_literal (struct kissat *solver, int lit); +unsigned kissat_fresh_literal (struct kissat *solver); + +#endif diff --git a/src/sat/kissat/inline.h b/src/sat/kissat/inline.h new file mode 100644 index 000000000..d1f9b789f --- /dev/null +++ b/src/sat/kissat/inline.h @@ -0,0 +1,328 @@ +#ifndef _inline_h_INCLUDED +#define _inline_h_INCLUDED + +#include "inlinevector.h" +#include "logging.h" + +#ifdef METRICS + +static inline size_t kissat_allocated (kissat *solver) { + return solver->statistics.allocated_current; +} + +#endif + +static inline bool kissat_propagated (kissat *solver) { + assert (BEGIN_ARRAY (solver->trail) <= solver->propagate); + assert (solver->propagate <= END_ARRAY (solver->trail)); + return solver->propagate == END_ARRAY (solver->trail); +} + +static inline bool kissat_trail_flushed (kissat *solver) { + return !solver->unflushed && EMPTY_ARRAY (solver->trail); +} + +static inline void kissat_reset_propagate (kissat *solver) { + solver->propagate = BEGIN_ARRAY (solver->trail); +} + +static inline value kissat_fixed (kissat *solver, unsigned lit) { + assert (lit < LITS); + const value res = solver->values[lit]; + if (!res) + return 0; + if (LEVEL (lit)) + return 0; + return res; +} + +static inline void kissat_mark_removed_literal (kissat *solver, + unsigned lit) { + const unsigned idx = IDX (lit); + flags *flags = FLAGS (idx); + if (flags->fixed) + return; + if (!flags->eliminate) { + LOG ("marking %s to be eliminated", LOGVAR (idx)); + flags->eliminate = true; + INC (variables_eliminate); + } +} + +static inline void kissat_mark_added_literal (kissat *solver, + unsigned lit) { + const unsigned idx = IDX (lit); + flags *flags = FLAGS (idx); + if (!flags->subsume) { + LOG ("marking %s to forward subsume", LOGVAR (idx)); + flags->subsume = true; + INC (variables_subsume); + } + const unsigned bit = 1u << NEGATED (lit); + if (!(flags->factor & bit)) { + flags->factor |= bit; + LOG ("marking literal %s to factor", LOGLIT (lit)); + INC (literals_factor); + } +} + +static inline void +kissat_push_large_watch (kissat *solver, watches *watches, reference ref) { + const watch watch = kissat_large_watch (ref); + PUSH_WATCHES (*watches, watch); +} + +static inline void kissat_push_binary_watch (kissat *solver, + watches *watches, + unsigned other) { + const watch watch = kissat_binary_watch (other); + PUSH_WATCHES (*watches, watch); +} + +static inline void kissat_push_blocking_watch (kissat *solver, + watches *watches, + unsigned blocking, + reference ref) { + assert (solver->watching); + const watch head = kissat_blocking_watch (blocking); + PUSH_WATCHES (*watches, head); + const watch tail = kissat_large_watch (ref); + PUSH_WATCHES (*watches, tail); +} + +static inline void kissat_watch_other (kissat *solver, unsigned lit, + unsigned other) { + LOGBINARY (lit, other, "watching %s blocking %s in", LOGLIT (lit), + LOGLIT (other)); + watches *watches = &WATCHES (lit); + kissat_push_binary_watch (solver, watches, other); +} + +static inline void kissat_watch_binary (kissat *solver, unsigned a, + unsigned b) { + kissat_watch_other (solver, a, b); + kissat_watch_other (solver, b, a); +} + +static inline void kissat_watch_blocking (kissat *solver, unsigned lit, + unsigned blocking, + reference ref) { + assert (solver->watching); + LOGREF3 (ref, "watching %s blocking %s in", LOGLIT (lit), + LOGLIT (blocking)); + watches *watches = &WATCHES (lit); + kissat_push_blocking_watch (solver, watches, blocking, ref); +} + +static inline void kissat_unwatch_blocking (kissat *solver, unsigned lit, + reference ref) { + assert (solver->watching); + LOGREF3 (ref, "unwatching %s in", LOGLIT (lit)); + watches *watches = &WATCHES (lit); + kissat_remove_blocking_watch (solver, watches, ref); +} + +static inline void kissat_disconnect_binary (kissat *solver, unsigned lit, + unsigned other) { + assert (!solver->watching); + watches *watches = &WATCHES (lit); + const watch watch = kissat_binary_watch (other); + REMOVE_WATCHES (*watches, watch); +} + +static inline void +kissat_disconnect_reference (kissat *solver, unsigned lit, reference ref) { + assert (!solver->watching); + LOGREF3 (ref, "disconnecting %s in", LOGLIT (lit)); + const watch watch = kissat_large_watch (ref); + watches *watches = &WATCHES (lit); + REMOVE_WATCHES (*watches, watch); +} + +static inline void kissat_watch_reference (kissat *solver, unsigned a, + unsigned b, reference ref) { + assert (solver->watching); + kissat_watch_blocking (solver, a, b, ref); + kissat_watch_blocking (solver, b, a, ref); +} + +static inline void kissat_connect_literal (kissat *solver, unsigned lit, + reference ref) { + assert (!solver->watching); + LOGREF3 (ref, "connecting %s in", LOGLIT (lit)); + watches *watches = &WATCHES (lit); + kissat_push_large_watch (solver, watches, ref); +} + +static inline clause *kissat_unchecked_dereference_clause (kissat *solver, + reference ref) { + return (clause *) &PEEK_STACK (solver->arena, ref); +} + +static inline clause *kissat_dereference_clause (kissat *solver, + reference ref) { + clause *res = kissat_unchecked_dereference_clause (solver, ref); + assert (kissat_clause_in_arena (solver, res)); + return res; +} + +static inline reference kissat_reference_clause (kissat *solver, + const clause *c) { + assert (kissat_clause_in_arena (solver, c)); + return (ward *) c - BEGIN_STACK (solver->arena); +} + +static inline void kissat_inlined_connect_clause (kissat *solver, + watches *all_watches, + clause *c, + reference ref) { + assert (!solver->watching); + assert (ref == kissat_reference_clause (solver, c)); + assert (c == kissat_dereference_clause (solver, ref)); + for (all_literals_in_clause (lit, c)) { + assert (!solver->watching); + LOGREF3 (ref, "connecting %s in", LOGLIT (lit)); + assert (lit < LITS); + watches *lit_watches = all_watches + lit; + kissat_push_large_watch (solver, lit_watches, ref); + } +} + +static inline void kissat_watch_clause (kissat *solver, clause *c) { + assert (c->searched < c->size); + const reference ref = kissat_reference_clause (solver, c); + kissat_watch_reference (solver, c->lits[0], c->lits[1], ref); +} + +static inline int kissat_export_literal (kissat *solver, unsigned ilit) { + const unsigned iidx = IDX (ilit); + assert (iidx < (unsigned) INT_MAX); + int elit = PEEK_STACK (solver->export, iidx); + if (!elit) + return 0; + if (NEGATED (ilit)) + elit = -elit; + assert (VALID_EXTERNAL_LITERAL (elit)); + return elit; +} + +static inline unsigned kissat_map_literal (kissat *solver, unsigned ilit, + bool map) { + if (!map) + return ilit; + int elit = kissat_export_literal (solver, ilit); + if (!elit) + return INVALID_LIT; + const unsigned eidx = ABS (elit); + const import *const import = &PEEK_STACK (solver->import, eidx); + if (import->eliminated) + return INVALID_LIT; + unsigned mlit = import->lit; + if (elit < 0) + mlit = NOT (mlit); + return mlit; +} + +static inline clause *kissat_last_irredundant_clause (kissat *solver) { + return (solver->last_irredundant == INVALID_REF) + ? 0 + : kissat_dereference_clause (solver, solver->last_irredundant); +} + +static inline clause *kissat_binary_conflict (kissat *solver, unsigned a, + unsigned b) { + LOGBINARY (a, b, "conflicting"); + clause *res = &solver->conflict; + res->size = 2; + unsigned *lits = res->lits; + lits[0] = a; + lits[1] = b; + return res; +} + +static inline void kissat_push_analyzed (kissat *solver, assigned *assigned, + unsigned idx) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (!a->analyzed); + a->analyzed = true; + PUSH_STACK (solver->analyzed, idx); + LOG2 ("%s analyzed", LOGVAR (idx)); +} + +static inline bool kissat_analyzed (kissat *solver) { + return !EMPTY_STACK (solver->analyzed); +} + +static inline void +kissat_push_removable (kissat *solver, assigned *assigned, unsigned idx) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (!a->removable); + a->removable = true; + PUSH_STACK (solver->removable, idx); + LOG2 ("%s removable", LOGVAR (idx)); +} + +static inline void kissat_push_poisoned (kissat *solver, assigned *assigned, + unsigned idx) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (!a->poisoned); + a->poisoned = true; + PUSH_STACK (solver->poisoned, idx); + LOG2 ("%s poisoned", LOGVAR (idx)); +} + +static inline void +kissat_push_shrinkable (kissat *solver, assigned *assigned, unsigned idx) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (!a->shrinkable); + a->shrinkable = true; + PUSH_STACK (solver->shrinkable, idx); + LOG2 ("%s shrinkable", LOGVAR (idx)); +} + +static inline int kissat_checking (kissat *solver) { +#ifndef NDEBUG +#ifdef NOPTIONS + (void) solver; +#endif + return GET_OPTION (check); +#else + (void) solver; + return 0; +#endif +} + +static inline bool kissat_logging (kissat *solver) { +#ifdef LOGGING +#ifdef NOPTIONS + (void) solver; +#endif + return GET_OPTION (log) > 0; +#else + (void) solver; + return false; +#endif +} + +static inline bool kissat_proving (kissat *solver) { +#ifdef NPROOFS + (void) solver; + return false; +#else + return solver->proof != 0; +#endif +} + +static inline bool kissat_checking_or_proving (kissat *solver) { + return kissat_checking (solver) || kissat_proving (solver); +} + +#if !defined(NDEBUG) || !defined(NPROOFS) +#define CHECKING_OR_PROVING +#endif + +#endif diff --git a/src/sat/kissat/inlineassign.h b/src/sat/kissat/inlineassign.h new file mode 100644 index 000000000..b8d3b88a1 --- /dev/null +++ b/src/sat/kissat/inlineassign.h @@ -0,0 +1,100 @@ +#ifndef _inlineassign_h_INLCUDED +#define _inlineassign_h_INLCUDED + +#ifdef FAST_ASSIGN +#define kissat_assign kissat_fast_assign +#endif + +static inline void kissat_assign (kissat *solver, const bool probing, + const unsigned level, +#ifdef FAST_ASSIGN + value *values, assigned *assigned, +#endif + bool binary, unsigned lit, + unsigned reason) { + const unsigned not_lit = NOT (lit); + + watches watches = WATCHES (not_lit); + if (!kissat_empty_vector (&watches)) { + watch *w = BEGIN_WATCHES (watches); + __builtin_prefetch (w, 0, 1); + } + +#ifndef FAST_ASSIGN + value *values = solver->values; +#endif + assert (!values[lit]); + assert (!values[not_lit]); + + values[lit] = 1; + values[not_lit] = -1; + + assert (solver->unassigned > 0); + solver->unassigned--; + + if (!level) { + kissat_mark_fixed_literal (solver, lit); + assert (solver->unflushed < UINT_MAX); + solver->unflushed++; + if (reason != UNIT_REASON) { + CHECK_AND_ADD_UNIT (lit); + ADD_UNIT_TO_PROOF (lit); + reason = UNIT_REASON; + binary = false; + } + } + + const size_t trail = SIZE_ARRAY (solver->trail); + PUSH_ARRAY (solver->trail, lit); + + const unsigned idx = IDX (lit); + +#if !defined(PROBING_PROPAGATION) + if (!probing) { + const bool negated = NEGATED (lit); + const value new_value = BOOL_TO_VALUE (negated); + value *saved = &SAVED (idx); + *saved = new_value; + } +#endif + + struct assigned b; + + b.level = level; + b.trail = trail; + + b.analyzed = false; + b.binary = binary; + b.poisoned = false; + b.reason = reason; + b.removable = false; + b.shrinkable = false; + +#ifndef FAST_ASSIGN + assigned *assigned = solver->assigned; +#endif + struct assigned *a = assigned + idx; + *a = b; +} + +static inline unsigned +kissat_assignment_level (kissat *solver, value *values, assigned *assigned, + unsigned lit, clause *reason) { + unsigned res = 0; + for (all_literals_in_clause (other, reason)) { + if (other == lit) + continue; + assert (values[other] < 0), (void) values; + const unsigned other_idx = IDX (other); + struct assigned *a = assigned + other_idx; + const unsigned level = a->level; + if (res < level) + res = level; + } +#ifdef NDEBUG + (void) solver; +#endif + return res; +} + +#endif diff --git a/src/sat/kissat/inlineframes.h b/src/sat/kissat/inlineframes.h new file mode 100644 index 000000000..75c5186a6 --- /dev/null +++ b/src/sat/kissat/inlineframes.h @@ -0,0 +1,18 @@ +#ifndef _inlineframes_h_INCLUDEDd +#define _inlineframes_h_INCLUDEDd + +#include "allocate.h" +#include "internal.h" + +static inline void kissat_push_frame (kissat *solver, unsigned decision) { + assert (!solver->level || decision != UINT_MAX); + const size_t trail = SIZE_ARRAY (solver->trail); + frame frame; + frame.decision = decision; + frame.promote = false; + frame.trail = trail; + frame.used = 0; + PUSH_STACK (solver->frames, frame); +} + +#endif diff --git a/src/sat/kissat/inlineheap.h b/src/sat/kissat/inlineheap.h new file mode 100644 index 000000000..0bc8c122d --- /dev/null +++ b/src/sat/kissat/inlineheap.h @@ -0,0 +1,176 @@ +#ifndef _inlineheap_h_INCLUDED +#define _inlineheap_h_INCLUDED + +#include "allocate.h" +#include "internal.h" +#include "logging.h" + +#define HEAP_CHILD(POS) (assert ((POS) < (1u << 31)), (2 * (POS) + 1)) + +#define HEAP_PARENT(POS) (assert ((POS) > 0), (((POS) - 1) / 2)) + +static inline void kissat_bubble_up (kissat *solver, heap *heap, + unsigned idx) { + unsigned *stack = BEGIN_STACK (heap->stack); + unsigned *pos = heap->pos; + unsigned idx_pos = pos[idx]; + const double *const score = heap->score; + const double idx_score = score[idx]; + while (idx_pos) { + const unsigned parent_pos = HEAP_PARENT (idx_pos); + const unsigned parent = stack[parent_pos]; + if (score[parent] >= idx_score) + break; + LOG ("heap bubble up: %u@%u = %g swapped with %u@%u = %g", parent, + parent_pos, score[parent], idx, idx_pos, idx_score); + stack[idx_pos] = parent; + pos[parent] = idx_pos; + idx_pos = parent_pos; + } + stack[idx_pos] = idx; + pos[idx] = idx_pos; +#ifndef LOGGING + (void) solver; +#endif +} + +static inline void kissat_bubble_down (kissat *solver, heap *heap, + unsigned idx) { + unsigned *stack = BEGIN_STACK (heap->stack); + const unsigned end = SIZE_STACK (heap->stack); + unsigned *pos = heap->pos; + unsigned idx_pos = pos[idx]; + const double *const score = heap->score; + const double idx_score = score[idx]; + for (;;) { + unsigned child_pos = HEAP_CHILD (idx_pos); + if (child_pos >= end) + break; + unsigned child = stack[child_pos]; + double child_score = score[child]; + const unsigned sibling_pos = child_pos + 1; + if (sibling_pos < end) { + const unsigned sibling = stack[sibling_pos]; + const double sibling_score = score[sibling]; + if (sibling_score > child_score) { + child = sibling; + child_pos = sibling_pos; + child_score = sibling_score; + } + } + if (child_score <= idx_score) + break; + LOG ("heap bubble down: %u@%u = %g swapped with %u@%u = %g", child, + child_pos, score[child], idx, idx_pos, idx_score); + stack[idx_pos] = child; + pos[child] = idx_pos; + idx_pos = child_pos; + } + stack[idx_pos] = idx; + pos[idx] = idx_pos; +#ifndef LOGGING + (void) solver; +#endif +} + +#define HEAP_IMPORT(IDX) \ + do { \ + assert ((IDX) < UINT_MAX - 1); \ + if (heap->vars <= (IDX)) \ + kissat_enlarge_heap (solver, heap, (IDX) + 1); \ + } while (0) + +#define CHECK_HEAP_IMPORTED(IDX) + +static inline void kissat_push_heap (kissat *solver, heap *heap, + unsigned idx) { + LOG ("push heap %u", idx); + assert (!kissat_heap_contains (heap, idx)); + HEAP_IMPORT (idx); + heap->pos[idx] = SIZE_STACK (heap->stack); + PUSH_STACK (heap->stack, idx); + kissat_bubble_up (solver, heap, idx); +} + +static inline void kissat_pop_heap (kissat *solver, heap *heap, + unsigned idx) { + LOG ("pop heap %u", idx); + assert (kissat_heap_contains (heap, idx)); + const unsigned last = POP_STACK (heap->stack); + heap->pos[last] = DISCONTAIN; + if (last == idx) + return; + const unsigned idx_pos = heap->pos[idx]; + heap->pos[idx] = DISCONTAIN; + POKE_STACK (heap->stack, idx_pos, last); + heap->pos[last] = idx_pos; + kissat_bubble_up (solver, heap, last); + kissat_bubble_down (solver, heap, last); +#ifdef CHECK_HEAP + kissat_check_heap (heap); +#endif +} + +static inline unsigned kissat_pop_max_heap (kissat *solver, heap *heap) { + assert (!EMPTY_STACK (heap->stack)); + unsigneds *stack = &heap->stack; + unsigned *const begin = BEGIN_STACK (*stack); + const unsigned idx = *begin; + assert (!heap->pos[idx]); + LOG ("pop max heap %u", idx); + const unsigned last = POP_STACK (*stack); + unsigned *const pos = heap->pos; + pos[last] = DISCONTAIN; + if (last == idx) + return idx; + pos[idx] = DISCONTAIN; + *begin = last; + pos[last] = 0; + kissat_bubble_down (solver, heap, last); +#ifdef CHECK_HEAP + kissat_check_heap (heap); +#endif + return idx; +} + +static inline void kissat_adjust_heap (kissat *solver, heap *heap, + unsigned idx) { + const unsigned new_vars = idx + 1; + const unsigned old_vars = heap->vars; + if (new_vars <= old_vars) + return; + const unsigned old_size = heap->size; + if (idx >= old_size) { + size_t new_size = old_size ? 2 * old_size : 1; + while (idx >= new_size) + new_size *= 2; + assert (new_size < DISCONTAIN); + kissat_resize_heap (solver, heap, new_size); + } + kissat_enlarge_heap (solver, heap, idx + 1); +} + +static inline void kissat_update_heap (kissat *solver, heap *heap, + unsigned idx, double new_score) { + const double old_score = kissat_get_heap_score (heap, idx); + if (old_score == new_score) + return; + HEAP_IMPORT (idx); + LOG ("update heap %u score from %g to %g", idx, old_score, new_score); + heap->score[idx] = new_score; + if (!heap->tainted) { + heap->tainted = true; + LOG ("tainted heap"); + } + if (!kissat_heap_contains (heap, idx)) + return; + if (new_score > old_score) + kissat_bubble_up (solver, heap, idx); + else + kissat_bubble_down (solver, heap, idx); +#ifdef CHECK_HEAP + kissat_check_heap (heap); +#endif +} + +#endif diff --git a/src/sat/kissat/inlinequeue.h b/src/sat/kissat/inlinequeue.h new file mode 100644 index 000000000..017522679 --- /dev/null +++ b/src/sat/kissat/inlinequeue.h @@ -0,0 +1,117 @@ +#ifndef _inlinequeue_h_INCLUDED +#define _inlinequeue_h_INCLUDED + +#include "internal.h" +#include "logging.h" + +static inline void kissat_update_queue (kissat *solver, const links *links, + unsigned idx) { + assert (!DISCONNECTED (idx)); + const unsigned stamp = links[idx].stamp; + LOG ("queue updated to %s stamped %u", LOGVAR (idx), stamp); + solver->queue.search.idx = idx; + solver->queue.search.stamp = stamp; +} + +static inline void kissat_enqueue_links (kissat *solver, unsigned i, + links *links, queue *queue) { + struct links *p = links + i; + assert (DISCONNECTED (p->prev)); + assert (DISCONNECTED (p->next)); + const unsigned j = p->prev = queue->last; + queue->last = i; + if (DISCONNECTED (j)) + queue->first = i; + else { + struct links *l = links + j; + assert (DISCONNECTED (l->next)); + l->next = i; + } + if (queue->stamp == UINT_MAX) { + kissat_reassign_queue_stamps (solver); + assert (p->stamp == queue->stamp); + } else + p->stamp = ++queue->stamp; +} + +static inline void kissat_dequeue_links (unsigned i, links *links, + queue *queue) { + struct links *l = links + i; + const unsigned j = l->prev, k = l->next; + l->prev = l->next = DISCONNECT; + if (DISCONNECTED (j)) { + assert (queue->first == i); + queue->first = k; + } else { + struct links *p = links + j; + assert (p->next == i); + p->next = k; + } + if (DISCONNECTED (k)) { + assert (queue->last == i); + queue->last = j; + } else { + struct links *n = links + k; + assert (n->prev == i); + n->prev = j; + } +} + +static inline void kissat_enqueue (kissat *solver, unsigned idx) { + assert (idx < solver->vars); + links *links = solver->links, *l = links + idx; + l->prev = l->next = DISCONNECT; + kissat_enqueue_links (solver, idx, links, &solver->queue); + LOG ("enqueued %s stamped %u", LOGVAR (idx), l->stamp); + if (!VALUE (LIT (idx))) + kissat_update_queue (solver, links, idx); + kissat_check_queue (solver); +} + +static inline void kissat_dequeue (kissat *solver, unsigned idx) { + assert (idx < solver->vars); + LOG ("dequeued %s", LOGVAR (idx)); + links *links = solver->links; + if (solver->queue.search.idx == idx) { + struct links *l = links + idx; + unsigned search = l->next; + if (search == DISCONNECT) + search = l->prev; + if (search == DISCONNECT) { + solver->queue.search.idx = DISCONNECT; + solver->queue.search.stamp = 0; + } else + kissat_update_queue (solver, links, search); + } + kissat_dequeue_links (idx, links, &solver->queue); + kissat_check_queue (solver); +} + +static inline void kissat_move_to_front (kissat *solver, unsigned idx) { + queue *queue = &solver->queue; + links *links = solver->links; + if (idx == queue->last) { + assert (DISCONNECTED (links[idx].next)); + return; + } + assert (idx < solver->vars); + const value tmp = VALUE (LIT (idx)); + if (tmp && queue->search.idx == idx) { + unsigned prev = links[idx].prev; + if (!DISCONNECTED (prev)) + kissat_update_queue (solver, links, prev); + else { + unsigned next = links[idx].next; + assert (!DISCONNECTED (next)); + kissat_update_queue (solver, links, next); + } + } + kissat_dequeue_links (idx, links, queue); + kissat_enqueue_links (solver, idx, links, queue); + LOG ("moved-to-front %s stamped %u", LOGVAR (idx), LINK (idx).stamp); + if (!tmp) + kissat_update_queue (solver, links, idx); + kissat_check_queue (solver); +} + +#endif diff --git a/src/sat/kissat/inlinevector.h b/src/sat/kissat/inlinevector.h new file mode 100644 index 000000000..b7adbbf7e --- /dev/null +++ b/src/sat/kissat/inlinevector.h @@ -0,0 +1,193 @@ +#ifndef _inlinevector_h_INCLUDED +#define _inlinevector_h_INCLUDED + +#include "internal.h" + +static inline unsigned *kissat_begin_vector (kissat *solver, + vector *vector) { +#ifdef COMPACT + return BEGIN_STACK (solver->vectors.stack) + vector->offset; +#else + (void) solver; + return vector->begin; +#endif +} + +static inline unsigned *kissat_end_vector (kissat *solver, vector *vector) { +#ifdef COMPACT + return kissat_begin_vector (solver, vector) + vector->size; +#else + (void) solver; + return vector->end; +#endif +} + +static inline const unsigned * +kissat_begin_const_vector (kissat *solver, const vector *vector) { +#ifdef COMPACT + return BEGIN_STACK (solver->vectors.stack) + vector->offset; +#else + (void) solver; + return vector->begin; +#endif +} + +static inline const unsigned * +kissat_end_const_vector (kissat *solver, const vector *vector) { +#ifdef COMPACT + return kissat_begin_const_vector (solver, vector) + vector->size; +#else + (void) solver; + return vector->end; +#endif +} + +#if defined(LOGGING) || defined(TEST_VECTOR) + +static inline size_t kissat_offset_vector (kissat *solver, vector *vector) { +#ifdef COMPACT + (void) solver; + return vector->offset; +#else + unsigned *begin_vector = vector->begin; + unsigned *begin_stack = BEGIN_STACK (solver->vectors.stack); + return begin_vector ? begin_vector - begin_stack : 0; +#endif +} + +#endif + +static inline size_t kissat_size_vector (const vector *vector) { +#ifdef COMPACT + return vector->size; +#else + return vector->end - vector->begin; +#endif +} + +static inline bool kissat_empty_vector (vector *vector) { +#ifdef COMPACT + return !vector->size; +#else + return vector->end == vector->begin; +#endif +} + +static inline void kissat_inc_usable (kissat *solver) { + assert (MAX_SECTOR > solver->vectors.usable); + solver->vectors.usable++; +} + +static inline void kissat_add_usable (kissat *solver, size_t inc) { + assert (MAX_SECTOR - inc >= solver->vectors.usable); + solver->vectors.usable += inc; +} + +static inline unsigned *kissat_last_vector_pointer (kissat *solver, + vector *vector) { + assert (!kissat_empty_vector (vector)); +#ifdef COMPACT + assert (vector->size); + unsigned *begin = kissat_begin_vector (solver, vector); + return begin + vector->size - 1; +#else + (void) solver; + return vector->end - 1; +#endif +} + +#ifdef TEST_VECTOR + +static inline void kissat_pop_vector (kissat *solver, vector *vector) { + assert (!kissat_empty_vector (vector)); +#ifdef COMPACT + unsigned *p = kissat_last_vector_pointer (solver, vector); + vector->size--; + *p = INVALID_VECTOR_ELEMENT; +#else + *--vector->end = INVALID_VECTOR_ELEMENT; + (void) solver; +#endif + kissat_inc_usable (solver); +} + +#endif + +static inline void kissat_release_vector (kissat *solver, vector *vector) { + kissat_resize_vector (solver, vector, 0); +} + +static inline void kissat_dec_usable (kissat *solver) { + assert (solver->vectors.usable > 0); + solver->vectors.usable--; +} + +static inline void kissat_push_vectors (kissat *solver, vector *vector, + unsigned e) { + unsigneds *stack = &solver->vectors.stack; + assert (e != INVALID_VECTOR_ELEMENT); + if ( +#ifdef COMPACT + !vector->size && !vector->offset +#else + !vector->begin +#endif + ) { + if (EMPTY_STACK (*stack)) + PUSH_STACK (*stack, 0); + if (FULL_STACK (*stack)) { + unsigned *end = kissat_enlarge_vector (solver, vector); + assert (*end == INVALID_VECTOR_ELEMENT); + *end = e; + kissat_dec_usable (solver); + } else { +#ifdef COMPACT + assert ((uint64_t) SIZE_STACK (*stack) < MAX_VECTORS); + vector->offset = SIZE_STACK (*stack); + assert (vector->offset); + *stack->end++ = e; +#else + assert (stack->end < stack->allocated); + *(vector->begin = stack->end++) = e; +#endif + } +#if !defined(COMPACT) + vector->end = vector->begin; +#endif + } else { + unsigned *end = kissat_end_vector (solver, vector); + if (end == END_STACK (*stack)) { + if (FULL_STACK (*stack)) { + end = kissat_enlarge_vector (solver, vector); + assert (*end == INVALID_VECTOR_ELEMENT); + *end = e; + kissat_dec_usable (solver); + } else + *stack->end++ = e; + } else { + if (*end != INVALID_VECTOR_ELEMENT) + end = kissat_enlarge_vector (solver, vector); + assert (*end == INVALID_VECTOR_ELEMENT); + *end = e; + kissat_dec_usable (solver); + } + } +#ifndef COMPACT + vector->end++; +#else + vector->size++; +#endif + kissat_check_vectors (solver); +} + +#ifdef TEST_VECTOR + +#define all_vector(E, V) \ + unsigned E, *E##_PTR = kissat_begin_vector (solver, &V), \ + *const E##_END = kissat_end_vector (solver, &V); \ + E##_PTR != E##_END && (E = *E##_PTR, true); \ + E##_PTR++ + +#endif + +#endif diff --git a/src/sat/kissat/internal.c b/src/sat/kissat/internal.c new file mode 100644 index 000000000..1c0002019 --- /dev/null +++ b/src/sat/kissat/internal.c @@ -0,0 +1,520 @@ +#include "allocate.h" +#include "backtrack.h" +#include "error.h" +#include "import.h" +#include "inline.h" +#include "inlineframes.h" +#include "print.h" +#include "propsearch.h" +#include "require.h" +#include "resize.h" +#include "resources.h" +#include "search.h" + +#include +#include +#include +#include +#include + +void kissat_reset_last_learned (kissat *solver) { + for (really_all_last_learned (p)) + *p = INVALID_REF; +} + +kissat *kissat_init (void) { + kissat *solver = kissat_calloc (0, 1, sizeof *solver); +#ifndef NOPTIONS + kissat_init_options (&solver->options); +#else + kissat_init_options (); +#endif +#ifndef QUIET + kissat_init_profiles (&solver->profiles); +#endif + START (total); + kissat_init_queue (solver); + assert (INTERNAL_MAX_LIT < UINT_MAX); + kissat_push_frame (solver, UINT_MAX); + solver->watching = true; + solver->conflict.size = 2; + solver->scinc = 1.0; + solver->first_reducible = INVALID_REF; + solver->last_irredundant = INVALID_REF; + kissat_reset_last_learned (solver); +#ifndef NDEBUG + kissat_init_checker (solver); +#endif + solver->prefix = kissat_strdup (solver, "c "); + return solver; +} + +void kissat_set_prefix (kissat *solver, const char *prefix) { + kissat_freestr (solver, solver->prefix); + solver->prefix = kissat_strdup (solver, prefix); +} + +#define DEALLOC_GENERIC(NAME, ELEMENTS_PER_BLOCK) \ + do { \ + const size_t block_size = ELEMENTS_PER_BLOCK * sizeof *solver->NAME; \ + kissat_dealloc (solver, solver->NAME, solver->size, block_size); \ + solver->NAME = 0; \ + } while (0) + +#define DEALLOC_VARIABLE_INDEXED(NAME) DEALLOC_GENERIC (NAME, 1) + +#define DEALLOC_LITERAL_INDEXED(NAME) DEALLOC_GENERIC (NAME, 2) + +#define RELEASE_LITERAL_INDEXED_STACKS(NAME, ACCESS) \ + do { \ + for (all_stack (unsigned, IDX_RILIS, solver->active)) { \ + const unsigned LIT_RILIS = LIT (IDX_RILIS); \ + const unsigned NOT_LIT_RILIS = NOT (LIT_RILIS); \ + RELEASE_STACK (ACCESS (LIT_RILIS)); \ + RELEASE_STACK (ACCESS (NOT_LIT_RILIS)); \ + } \ + DEALLOC_LITERAL_INDEXED (NAME); \ + } while (0) + +void kissat_release (kissat *solver) { + kissat_require_initialized (solver); + kissat_release_heap (solver, SCORES); + kissat_release_heap (solver, &solver->schedule); + kissat_release_vectors (solver); + kissat_release_phases (solver); + + RELEASE_STACK (solver->export); + RELEASE_STACK (solver->import); + + DEALLOC_VARIABLE_INDEXED (assigned); + DEALLOC_VARIABLE_INDEXED (flags); + DEALLOC_VARIABLE_INDEXED (links); + + DEALLOC_LITERAL_INDEXED (marks); + DEALLOC_LITERAL_INDEXED (values); + DEALLOC_LITERAL_INDEXED (watches); + + RELEASE_STACK (solver->import); + RELEASE_STACK (solver->eliminated); + RELEASE_STACK (solver->extend); + RELEASE_STACK (solver->witness); + RELEASE_STACK (solver->etrail); + + RELEASE_STACK (solver->delayed); + + RELEASE_STACK (solver->clause); + RELEASE_STACK (solver->shadow); +#if defined(LOGGING) || !defined(NDEBUG) + RELEASE_STACK (solver->resolvent); +#endif + + RELEASE_STACK (solver->arena); + + RELEASE_STACK (solver->units); + RELEASE_STACK (solver->frames); + RELEASE_STACK (solver->sorter); + + RELEASE_ARRAY (solver->trail, solver->size); + + RELEASE_STACK (solver->analyzed); + RELEASE_STACK (solver->levels); + RELEASE_STACK (solver->minimize); + RELEASE_STACK (solver->poisoned); + RELEASE_STACK (solver->promote); + RELEASE_STACK (solver->removable); + RELEASE_STACK (solver->shrinkable); + RELEASE_STACK (solver->xorted[0]); + RELEASE_STACK (solver->xorted[1]); + + RELEASE_STACK (solver->sweep_schedule); + + RELEASE_STACK (solver->ranks); + + RELEASE_STACK (solver->antecedents[0]); + RELEASE_STACK (solver->antecedents[1]); + RELEASE_STACK (solver->gates[0]); + RELEASE_STACK (solver->gates[1]); + RELEASE_STACK (solver->resolvents); + +#if !defined(NDEBUG) || !defined(NPROOFS) + RELEASE_STACK (solver->added); + RELEASE_STACK (solver->removed); +#endif + +#if !defined(NDEBUG) || !defined(NPROOFS) || defined(LOGGING) + RELEASE_STACK (solver->original); +#endif + +#ifndef QUIET + RELEASE_STACK (solver->profiles.stack); +#endif + + kissat_freestr (solver, solver->prefix); + +#ifndef NDEBUG + kissat_release_checker (solver); +#endif +#if !defined(NDEBUG) && defined(METRICS) + uint64_t leaked = solver->statistics.allocated_current; + if (leaked) + if (!getenv ("LEAK")) + kissat_fatal ("internally leaking %" PRIu64 " bytes", leaked); +#endif + + kissat_free (0, solver, sizeof *solver); +} + +void kissat_reserve (kissat *solver, int max_var) { + kissat_require_initialized (solver); + kissat_require (0 <= max_var, "negative maximum variable argument '%d'", + max_var); + kissat_require (max_var <= EXTERNAL_MAX_VAR, + "invalid maximum variable argument '%d'", max_var); + kissat_increase_size (solver, (unsigned) max_var); + if (!GET_OPTION (tumble)) { + for (int idx = 1; idx <= max_var; idx++) + (void) kissat_import_literal (solver, idx); + for (unsigned idx = 0; idx != (unsigned) max_var; idx++) + kissat_activate_literal (solver, LIT (idx)); + } +} + +int kissat_get_option (kissat *solver, const char *name) { + kissat_require_initialized (solver); + kissat_require (name, "name zero pointer"); +#ifndef NOPTIONS + return kissat_options_get (&solver->options, name); +#else + (void) solver; + return kissat_options_get (name); +#endif +} + +int kissat_set_option (kissat *solver, const char *name, int new_value) { +#ifndef NOPTIONS + kissat_require_initialized (solver); + kissat_require (name, "name zero pointer"); +#ifndef NOPTIONS + return kissat_options_set (&solver->options, name, new_value); +#else + return kissat_options_set (name, new_value); +#endif +#else + (void) solver, (void) new_value; + return kissat_options_get (name); +#endif +} + +void kissat_set_decision_limit (kissat *solver, unsigned limit) { + kissat_require_initialized (solver); + limits *limits = &solver->limits; + limited *limited = &solver->limited; + statistics *statistics = &solver->statistics; + limited->decisions = true; + assert (UINT64_MAX - limit >= statistics->decisions); + limits->decisions = statistics->decisions + limit; + LOG ("set decision limit to %" PRIu64 " after %u decisions", + limits->decisions, limit); +} + +void kissat_set_conflict_limit (kissat *solver, unsigned limit) { + kissat_require_initialized (solver); + limits *limits = &solver->limits; + limited *limited = &solver->limited; + statistics *statistics = &solver->statistics; + limited->conflicts = true; + assert (UINT64_MAX - limit >= statistics->conflicts); + limits->conflicts = statistics->conflicts + limit; + LOG ("set conflict limit to %" PRIu64 " after %u conflicts", + limits->conflicts, limit); +} + +void kissat_print_statistics (kissat *solver) { +#ifndef QUIET + kissat_require_initialized (solver); + const int verbosity = kissat_verbosity (solver); + if (verbosity < 0) + return; + if (GET_OPTION (profile)) { + kissat_section (solver, "profiling"); + kissat_profiles_print (solver); + } + const bool complete = GET_OPTION (statistics); + kissat_section (solver, "statistics"); + const bool verbose = (complete || verbosity > 0); + kissat_statistics_print (solver, verbose); +#ifndef NPROOFS + if (solver->proof) { + kissat_section (solver, "proof"); + kissat_print_proof_statistics (solver, verbose); + } +#endif +#ifndef NDEBUG + if (GET_OPTION (check) > 1) { + kissat_section (solver, "checker"); + kissat_print_checker_statistics (solver, verbose); + } +#endif + kissat_section (solver, "glue usage"); + kissat_print_glue_usage (solver); + kissat_section (solver, "resources"); + kissat_print_resources (solver); +#endif + (void) solver; +} + +void kissat_add (kissat *solver, int elit) { + kissat_require_initialized (solver); + kissat_require (!GET (searches), "incremental solving not supported"); +#if !defined(NDEBUG) || !defined(NPROOFS) || defined(LOGGING) + const int checking = kissat_checking (solver); + const bool logging = kissat_logging (solver); + const bool proving = kissat_proving (solver); +#endif + if (elit) { + kissat_require_valid_external_internal (elit); +#if !defined(NDEBUG) || !defined(NPROOFS) || defined(LOGGING) + if (checking || logging || proving) + PUSH_STACK (solver->original, elit); +#endif + unsigned ilit = kissat_import_literal (solver, elit); + + const mark mark = MARK (ilit); + if (!mark) { + const value value = kissat_fixed (solver, ilit); + if (value > 0) { + if (!solver->clause_satisfied) { + LOG ("adding root level satisfied literal %u(%d)@0=1", ilit, + elit); + solver->clause_satisfied = true; + } + } else if (value < 0) { + LOG ("adding root level falsified literal %u(%d)@0=-1", ilit, elit); + if (!solver->clause_shrink) { + solver->clause_shrink = true; + LOG ("thus original clause needs shrinking"); + } + } else { + MARK (ilit) = 1; + MARK (NOT (ilit)) = -1; + assert (SIZE_STACK (solver->clause) < UINT_MAX); + PUSH_STACK (solver->clause, ilit); + } + } else if (mark < 0) { + assert (mark < 0); + if (!solver->clause_trivial) { + LOG ("adding dual literal %u(%d) and %u(%d)", NOT (ilit), -elit, + ilit, elit); + solver->clause_trivial = true; + } + } else { + assert (mark > 0); + LOG ("adding duplicated literal %u(%d)", ilit, elit); + if (!solver->clause_shrink) { + solver->clause_shrink = true; + LOG ("thus original clause needs shrinking"); + } + } + } else { +#if !defined(NDEBUG) || !defined(NPROOFS) || defined(LOGGING) + const size_t offset = solver->offset_of_last_original_clause; + size_t esize = SIZE_STACK (solver->original) - offset; + int *elits = BEGIN_STACK (solver->original) + offset; + assert (esize <= UINT_MAX); +#endif + ADD_UNCHECKED_EXTERNAL (esize, elits); + const size_t isize = SIZE_STACK (solver->clause); + unsigned *ilits = BEGIN_STACK (solver->clause); + assert (isize < (unsigned) INT_MAX); + + if (solver->inconsistent) + LOG ("inconsistent thus skipping original clause"); + else if (solver->clause_satisfied) + LOG ("skipping satisfied original clause"); + else if (solver->clause_trivial) + LOG ("skipping trivial original clause"); + else { + kissat_activate_literals (solver, isize, ilits); + + if (!isize) { + if (solver->clause_shrink) + LOG ("all original clause literals root level falsified"); + else + LOG ("found empty original clause"); + + if (!solver->inconsistent) { + LOG ("thus solver becomes inconsistent"); + solver->inconsistent = true; + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + } + } else if (isize == 1) { + unsigned unit = TOP_STACK (solver->clause); + + if (solver->clause_shrink) + LOGUNARY (unit, "original clause shrinks to"); + else + LOGUNARY (unit, "found original"); + + kissat_original_unit (solver, unit); + + COVER (solver->level); + if (!solver->level) + (void) kissat_search_propagate (solver); + } else { + reference res = kissat_new_original_clause (solver); + + const unsigned a = ilits[0]; + const unsigned b = ilits[1]; + + const value u = VALUE (a); + const value v = VALUE (b); + + const unsigned k = u ? LEVEL (a) : UINT_MAX; + const unsigned l = v ? LEVEL (b) : UINT_MAX; + + bool assign = false; + + if (!u && v < 0) { + LOG ("original clause immediately forcing"); + assign = true; + } else if (u < 0 && k == l) { + LOG ("both watches falsified at level @%u", k); + assert (v < 0); + assert (k > 0); + kissat_backtrack_without_updating_phases (solver, k - 1); + } else if (u < 0) { + LOG ("watches falsified at levels @%u and @%u", k, l); + assert (v < 0); + assert (k > l); + assert (l > 0); + assign = true; + } else if (u > 0 && v < 0) { + LOG ("first watch satisfied at level @%u " + "second falsified at level @%u", + k, l); + assert (k <= l); + } else if (!u && v > 0) { + LOG ("first watch unassigned " + "second falsified at level @%u", + l); + assign = true; + } else { + assert (!u); + assert (!v); + } + + if (assign) { + assert (solver->level > 0); + + if (isize == 2) { + assert (res == INVALID_REF); + kissat_assign_binary (solver, a, b); + } else { + assert (res != INVALID_REF); + clause *c = kissat_dereference_clause (solver, res); + kissat_assign_reference (solver, a, res, c); + } + } + } + } + +#if !defined(NDEBUG) || !defined(NPROOFS) + if (solver->clause_satisfied || solver->clause_trivial) { +#ifndef NDEBUG + if (checking > 1) + kissat_remove_checker_external (solver, esize, elits); +#endif +#ifndef NPROOFS + if (proving) { + if (esize == 1) + LOG ("skipping deleting unit from proof"); + else + kissat_delete_external_from_proof (solver, esize, elits); + } +#endif + } else if (!solver->inconsistent && solver->clause_shrink) { +#ifndef NDEBUG + if (checking > 1) { + kissat_check_and_add_internal (solver, isize, ilits); + kissat_remove_checker_external (solver, esize, elits); + } +#endif +#ifndef NPROOFS + if (proving) { + kissat_add_lits_to_proof (solver, isize, ilits); + kissat_delete_external_from_proof (solver, esize, elits); + } +#endif + } +#endif + +#if !defined(NDEBUG) || !defined(NPROOFS) || defined(LOGGING) + if (checking) { + LOGINTS (esize, elits, "saved original"); + PUSH_STACK (solver->original, 0); + solver->offset_of_last_original_clause = + SIZE_STACK (solver->original); + } else if (logging || proving) { + LOGINTS (esize, elits, "reset original"); + CLEAR_STACK (solver->original); + solver->offset_of_last_original_clause = 0; + } +#endif + for (all_stack (unsigned, lit, solver->clause)) + MARK (lit) = MARK (NOT (lit)) = 0; + + CLEAR_STACK (solver->clause); + + solver->clause_satisfied = false; + solver->clause_trivial = false; + solver->clause_shrink = false; + } +} + +int kissat_solve (kissat *solver) { + kissat_require_initialized (solver); + kissat_require (EMPTY_STACK (solver->clause), + "incomplete clause (terminating zero not added)"); + kissat_require (!GET (searches), "incremental solving not supported"); + return kissat_search (solver); +} + +void kissat_terminate (kissat *solver) { + kissat_require_initialized (solver); + solver->termination.flagged = ~(unsigned) 0; + assert (solver->termination.flagged); +} + +void kissat_set_terminate (kissat *solver, void *state, + int (*terminate) (void *)) { + solver->termination.terminate = 0; + solver->termination.state = state; + solver->termination.terminate = terminate; +} + +int kissat_value (kissat *solver, int elit) { + kissat_require_initialized (solver); + kissat_require_valid_external_internal (elit); + const unsigned eidx = ABS (elit); + if (eidx >= SIZE_STACK (solver->import)) + return 0; + const import *const import = &PEEK_STACK (solver->import, eidx); + if (!import->imported) + return 0; + value tmp; + if (import->eliminated) { + if (!solver->extended && !EMPTY_STACK (solver->extend)) + kissat_extend (solver); + const unsigned eliminated = import->lit; + tmp = PEEK_STACK (solver->eliminated, eliminated); + } else { + const unsigned ilit = import->lit; + tmp = VALUE (ilit); + } + if (!tmp) + return 0; + if (elit < 0) + tmp = -tmp; + return tmp < 0 ? -elit : elit; +} diff --git a/src/sat/kissat/internal.h b/src/sat/kissat/internal.h new file mode 100644 index 000000000..8a4b46c80 --- /dev/null +++ b/src/sat/kissat/internal.h @@ -0,0 +1,295 @@ +#ifndef _internal_h_INCLUDED +#define _internal_h_INCLUDED + +#include "arena.h" +#include "array.h" +#include "assign.h" +#include "averages.h" +#include "check.h" +#include "classify.h" +#include "clause.h" +#include "cover.h" +#include "extend.h" +#include "flags.h" +#include "format.h" +#include "frames.h" +#include "heap.h" +#include "kimits.h" +#include "kissat.h" +#include "literal.h" +#include "mode.h" +#include "options.h" +#include "phases.h" +#include "profile.h" +#include "proof.h" +#include "queue.h" +#include "random.h" +#include "reluctant.h" +#include "rephase.h" +#include "smooth.h" +#include "stack.h" +#include "statistics.h" +#include "value.h" +#include "vector.h" +#include "watch.h" + +typedef struct datarank datarank; + +struct datarank { + unsigned data; + unsigned rank; +}; + +typedef struct import import; + +struct import { + unsigned lit; + bool extension; + bool imported; + bool eliminated; +}; + +typedef struct termination termination; + +struct termination { +#ifdef COVERAGE + volatile uint64_t flagged; +#else + volatile bool flagged; +#endif + volatile void *state; + int (*volatile terminate) (void *); +}; + +// clang-format off + +typedef STACK (value) eliminated; +typedef STACK (import) imports; +typedef STACK (datarank) dataranks; +typedef STACK (watch) statches; +typedef STACK (watch *) patches; + +// clang-format on + +struct kitten; + +struct kissat { +#if !defined(NDEBUG) || defined(METRICS) + bool backbone_computing; +#endif +#ifdef LOGGING + bool compacting; +#endif + bool extended; + bool inconsistent; + bool iterating; + bool preprocessing; + bool probing; +#ifndef QUIET + bool sectioned; +#endif + bool stable; +#if !defined(NDEBUG) || defined(METRICS) + bool transitive_reducing; + bool vivifying; +#endif + bool warming; + bool watching; + + bool large_clauses_watched_after_binary_clauses; + + termination termination; + + unsigned vars; + unsigned size; + unsigned active; + unsigned randec; + + ints export; + ints units; + imports import; + extensions extend; + unsigneds witness; + + assigned *assigned; + flags *flags; + + mark *marks; + + value *values; + phases phases; + + eliminated eliminated; + unsigneds etrail; + + links *links; + queue queue; + + heap scores; + double scinc; + + heap schedule; + double scoreshift; + + unsigned level; + frames frames; + + unsigned_array trail; + unsigned *propagate; + + unsigned best_assigned; + unsigned target_assigned; + unsigned unflushed; + unsigned unassigned; + + unsigneds delayed; + +#if defined(LOGGING) || !defined(NDEBUG) + unsigneds resolvent; +#endif + unsigned resolvent_size; + unsigned antecedent_size; + + dataranks ranks; + + unsigneds analyzed; + unsigneds levels; + unsigneds minimize; + unsigneds poisoned; + unsigneds promote; + unsigneds removable; + unsigneds shrinkable; + + clause conflict; + + bool clause_satisfied; + bool clause_shrink; + bool clause_trivial; + + unsigneds clause; + unsigneds shadow; + + arena arena; + vectors vectors; + reference first_reducible; + reference last_irredundant; + watches *watches; + + reference last_learned[4]; + + sizes sorter; + + generator random; + averages averages[2]; + unsigned tier1[2], tier2[2]; + reluctant reluctant; + + bounds bounds; + classification classification; + delays delays; + enabled enabled; + limited limited; + limits limits; + remember last; + unsigned walked; + + mode mode; + + uint64_t ticks; + + format format; + char *prefix; + + statches antecedents[2]; + statches gates[2]; + patches xorted[2]; + unsigneds resolvents; + bool resolve_gate; + + struct kitten *kitten; +#ifdef METRICS + uint64_t *gate_eliminated; +#else + bool gate_eliminated; +#endif + bool sweep_incomplete; + unsigneds sweep_schedule; + +#if !defined(NDEBUG) || !defined(NPROOFS) + unsigneds added; + unsigneds removed; +#endif + +#if !defined(NDEBUG) || !defined(NPROOFS) || defined(LOGGING) + ints original; + size_t offset_of_last_original_clause; +#endif + +#ifndef QUIET + profiles profiles; +#endif + +#ifndef NOPTIONS + options options; +#endif + +#ifndef NDEBUG + checker *checker; +#endif + +#ifndef NPROOFS + proof *proof; +#endif + + statistics statistics; +}; + +#define VARS (solver->vars) +#define LITS (2 * solver->vars) + +#if 0 +#define TIEDX (GET_OPTION (focusedtiers) ? 0 : solver->stable) +#define TIER1 (solver->tier1[TIEDX]) +#define TIER2 (solver->tier2[TIEDX]) +#else +#define TIER1 (solver->tier1[0]) +#define TIER2 (solver->tier2[1]) +#endif + +#define SCORES (&solver->scores) + +static inline unsigned kissat_assigned (kissat *solver) { + assert (VARS >= solver->unassigned); + return VARS - solver->unassigned; +} + +#define all_variables(IDX) \ + unsigned IDX = 0, IDX##_END = solver->vars; \ + IDX != IDX##_END; \ + ++IDX + +#define all_literals(LIT) \ + unsigned LIT = 0, LIT##_END = LITS; \ + LIT != LIT##_END; \ + ++LIT + +#define all_clauses(C) \ + clause *C = (clause *) BEGIN_STACK (solver->arena), \ + *const C##_END = (clause *) END_STACK (solver->arena), *C##_NEXT; \ + C != C##_END && (C##_NEXT = kissat_next_clause (C), true); \ + C = C##_NEXT + +#define capacity_last_learned \ + (sizeof solver->last_learned / sizeof *solver->last_learned) + +#define real_end_last_learned (solver->last_learned + capacity_last_learned) + +#define really_all_last_learned(REF_PTR) \ + reference *REF_PTR = solver->last_learned, \ + *REF_PTR##_END = real_end_last_learned; \ + REF_PTR != REF_PTR##_END; \ + REF_PTR++ + +void kissat_reset_last_learned (kissat *solver); + +#endif diff --git a/src/sat/kissat/keatures.h b/src/sat/kissat/keatures.h new file mode 100644 index 000000000..d0652be99 --- /dev/null +++ b/src/sat/kissat/keatures.h @@ -0,0 +1,18 @@ +#ifndef _keatures_h_INCLUDED +#define _keatures_h_INCLUDED + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define KISSAT_IS_BIG_ENDIAN +#endif + +#if defined(_POSIX_C_SOURCE) || defined(__APPLE__) +#define KISSAT_HAS_COMPRESSION +#define KISSAT_HAS_COLORS +#define KISSAT_HAS_FILENO +#endif + +#if defined(_POSIX_C_SOURCE) +#define KISSAT_HAS_UNLOCKEDIO +#endif + +#endif diff --git a/src/sat/kissat/kimits.c b/src/sat/kissat/kimits.c new file mode 100644 index 000000000..9fe4dcf05 --- /dev/null +++ b/src/sat/kissat/kimits.c @@ -0,0 +1,194 @@ +#include "internal.h" +#include "logging.h" +#include "mode.h" +#include "print.h" +#include "reduce.h" +#include "rephase.h" +#include "resources.h" +#include "restart.h" + +#include +#include + +double kissat_logn (uint64_t count) { + assert (count > 0); + const double res = log10 (count + 9); + assert (res >= 1); + return res; +} + +double kissat_sqrt (uint64_t count) { + assert (count > 0); + const double res = sqrt (count); + assert (res >= 1); + return res; +} + +double kissat_nlogpown (uint64_t count, unsigned exponent) { + assert (count > 0); + const double tmp = log10 (count + 9); + double factor = 1; + while (exponent--) + factor *= tmp; + assert (factor >= 1); + const double res = count * factor; + assert (res >= 1); + return res; +} + +uint64_t kissat_scale_delta (kissat *solver, const char *pretty, + uint64_t delta) { + const uint64_t C = BINIRR_CLAUSES; + double f = kissat_logn (C + 1) - 5; + const double ff = f * f; + assert (ff >= 0); + const double fff = 4.5 * ff + 25; + uint64_t scaled = fff * delta; + assert (delta <= scaled); + // clang-format off + kissat_very_verbose (solver, + "scaled %s delta %" PRIu64 + " = %g * %" PRIu64 + " = (4.5 (log10(%" PRIu64 ") - 5)^2 + 25) * %" PRIu64, + pretty, scaled, fff, delta, C, delta); + // clang-format on + (void) pretty; + return scaled; +} + +static void init_enabled (kissat *solver) { + bool probe; + if (!GET_OPTION (simplify)) + probe = false; + else if (!GET_OPTION (probe)) + probe = false; + else if (GET_OPTION (substitute)) + probe = true; + else if (GET_OPTION (sweep)) + probe = true; + else if (GET_OPTION (vivify)) + probe = true; + else + probe = false; + kissat_very_verbose (solver, "probing %sabled", probe ? "en" : "dis"); + solver->enabled.probe = probe; + + bool eliminate; + if (!GET_OPTION (simplify)) + eliminate = false; + else if (!GET_OPTION (eliminate)) + eliminate = false; + else + eliminate = true; + kissat_very_verbose (solver, "eliminate %sabled", + eliminate ? "en" : "dis"); + solver->enabled.eliminate = eliminate; +} + +#define INIT_CONFLICT_LIMIT(NAME, SCALE) \ + do { \ + const uint64_t DELTA = GET_OPTION (NAME##init); \ + const uint64_t SCALED = \ + !(SCALE) ? DELTA : kissat_scale_delta (solver, #NAME, DELTA); \ + limits->NAME.conflicts = CONFLICTS + SCALED; \ + kissat_very_verbose (solver, \ + "initial " #NAME " limit of %s conflicts", \ + FORMAT_COUNT (limits->NAME.conflicts)); \ + } while (0) + +void kissat_init_limits (kissat *solver) { + assert (solver->statistics.searches == 1); + + init_enabled (solver); + + limits *limits = &solver->limits; + + if (GET_OPTION (randec)) + INIT_CONFLICT_LIMIT (randec, false); + + if (GET_OPTION (reduce)) + INIT_CONFLICT_LIMIT (reduce, false); + + if (GET_OPTION (reorder)) + INIT_CONFLICT_LIMIT (reorder, false); + + if (GET_OPTION (rephase)) + INIT_CONFLICT_LIMIT (rephase, false); + + if (!solver->stable) + kissat_update_focused_restart_limit (solver); + + kissat_init_mode_limit (solver); + + if (solver->enabled.eliminate) { + INIT_CONFLICT_LIMIT (eliminate, true); + solver->bounds.eliminate.max_bound_completed = 0; + solver->bounds.eliminate.additional_clauses = 0; + kissat_very_verbose (solver, "reset elimination bound to zero"); + } + + if (solver->enabled.probe) + INIT_CONFLICT_LIMIT (probe, true); +} + +#ifndef QUIET + +static const char *delay_description (kissat *solver, delay *delay) { + delays *delays = &solver->delays; + if (delay == &delays->bumpreasons) + return "bumping reason side literals"; + else if (delay == &delays->congruence) + return "congruence closure"; + else if (delay == &delays->sweep) + return "sweeping"; + else { + assert (delay == &delays->vivifyirr); + return "vivifying irredundant clauses"; + } +} + +#endif + +#define VERY_VERBOSE_IF_NOT_BUMPREASONS(...) \ + VERY_VERBOSE_OR_LOG (delay == &solver->delays.bumpreasons, __VA_ARGS__) + +void kissat_reduce_delay (kissat *solver, delay *delay) { + if (!delay->current) + return; + delay->current /= 2; + VERY_VERBOSE_IF_NOT_BUMPREASONS ( + solver, "%s delay interval decreased to %u", + delay_description (solver, delay), delay->current); + delay->count = delay->current; +#ifdef QUIET + (void) solver; +#endif +} + +void kissat_bump_delay (kissat *solver, delay *delay) { + delay->current += delay->current < UINT_MAX; + VERY_VERBOSE_IF_NOT_BUMPREASONS ( + solver, "%s delay interval increased to %u", + delay_description (solver, delay), delay->current); + delay->count = delay->current; +#ifdef QUIET + (void) solver; +#endif +} + +bool kissat_delaying (kissat *solver, delay *delay) { + if (delay->count) { + delay->count--; + VERY_VERBOSE_IF_NOT_BUMPREASONS ( + solver, "%s still delayed (%u more times)", + delay_description (solver, delay), delay->current); + return true; + } else { + VERY_VERBOSE_IF_NOT_BUMPREASONS (solver, "%s not delayed", + delay_description (solver, delay)); + return false; + } +#ifdef QUIET + (void) solver; +#endif +} diff --git a/src/sat/kissat/kimits.h b/src/sat/kissat/kimits.h new file mode 100644 index 000000000..93339b3b5 --- /dev/null +++ b/src/sat/kissat/kimits.h @@ -0,0 +1,185 @@ +#ifndef _limits_h_INCLUDED +#define _limits_h_INCLUDED + +#include +#include + +typedef struct bounds bounds; +typedef struct changes changes; +typedef struct delays delays; +typedef struct delay delay; +typedef struct remember remember; +typedef struct enabled enabled; +typedef struct limited limited; +typedef struct limits limits; + +struct bounds { + struct { + uint64_t max_bound_completed; + unsigned additional_clauses; + } eliminate; +}; + +struct limits { + uint64_t conflicts; + uint64_t decisions; + uint64_t reports; + + struct { + uint64_t count; + uint64_t ticks; + uint64_t conflicts; + } mode; + + struct { + struct { + uint64_t eliminate; + uint64_t subsume; + } variables; + uint64_t conflicts; + } eliminate; + + struct { + uint64_t marked; + } factor; + + struct { + uint64_t conflicts; + } probe, randec, reduce, reorder, rephase, restart; + + struct { + uint64_t conflicts; + uint64_t interval; + } glue; +}; + +struct limited { + bool conflicts; + bool decisions; +}; + +struct enabled { + bool eliminate; + bool focus; + bool mode; + bool probe; +}; + +struct delay { + unsigned count; + unsigned current; +}; + +struct delays { + delay bumpreasons; + delay congruence; + delay sweep; + delay vivifyirr; +}; + +struct remember { + struct { + uint64_t eliminate; + uint64_t probe; + } ticks; + struct { + uint64_t reduce; + } conflicts; +}; + +struct kissat; + +changes kissat_changes (struct kissat *); + +bool kissat_changed (changes before, changes after); + +void kissat_init_limits (struct kissat *); + +uint64_t kissat_scale_delta (struct kissat *, const char *, uint64_t); + +double kissat_nlogpown (uint64_t, unsigned); +double kissat_sqrt (uint64_t); +double kissat_logn (uint64_t); + +#define LOGN(COUNT) kissat_logn (COUNT) +#define LINEAR(COUNT) (COUNT) +#define NLOGN(COUNT) kissat_nlogpown (COUNT, 1) +#define NLOG2N(COUNT) kissat_nlogpown (COUNT, 2) +#define NLOG3N(COUNT) kissat_nlogpown (COUNT, 3) + +#define SQRT(COUNT) kissat_sqrt (COUNT) + +#define UPDATE_CONFLICT_LIMIT(NAME, COUNT, SCALE_COUNT_FUNCTION, \ + SCALE_DELTA) \ + do { \ + if (solver->inconsistent) \ + break; \ + const struct statistics *statistics = &solver->statistics; \ + assert (statistics->COUNT > 0); \ + struct limits *limits = &solver->limits; \ + uint64_t DELTA = GET_OPTION (NAME##int); \ + const double SCALING = SCALE_COUNT_FUNCTION (statistics->COUNT); \ + assert (SCALING >= 1); \ + DELTA *= SCALING; \ + const uint64_t SCALED = \ + !(SCALE_DELTA) ? DELTA \ + : kissat_scale_delta (solver, #NAME, DELTA); \ + limits->NAME.conflicts = CONFLICTS + SCALED; \ + kissat_phase ( \ + solver, #NAME, GET (COUNT), "new limit of %s after %s conflicts", \ + FORMAT_COUNT (limits->NAME.conflicts), FORMAT_COUNT (SCALED)); \ + } while (0) + +#include + +#define SET_EFFORT_LIMIT(LIMIT, NAME, START) \ + uint64_t LIMIT; \ + do { \ + const uint64_t OLD_LIMIT = solver->statistics.START; \ + const uint64_t TICKS = solver->statistics.search_ticks; \ + const uint64_t LAST = solver->probing ? solver->last.ticks.probe \ + : solver->last.ticks.eliminate; \ + uint64_t REFERENCE = TICKS - LAST; \ + const uint64_t MINEFFORT = 1e6 * GET_OPTION (mineffort); \ + if (REFERENCE < MINEFFORT) { \ + REFERENCE = MINEFFORT; \ + kissat_extremely_verbose ( \ + solver, #NAME " effort reference %s set to 'mineffort'", \ + FORMAT_COUNT (REFERENCE)); \ + } else { \ + kissat_extremely_verbose ( \ + solver, #NAME " effort reference %s = %s - %s 'search_ticks'", \ + FORMAT_COUNT (REFERENCE), FORMAT_COUNT (TICKS), \ + FORMAT_COUNT (LAST)); \ + } \ + const double EFFORT = (double) GET_OPTION (NAME##effort) * 1e-3; \ + const uint64_t DELTA = EFFORT * REFERENCE; \ +\ + kissat_extremely_verbose ( \ + solver, #NAME " effort delta %s = %g * %s '" #START "'", \ + FORMAT_COUNT (DELTA), EFFORT, FORMAT_COUNT (REFERENCE)); \ +\ + const uint64_t NEW_LIMIT = OLD_LIMIT + DELTA; \ + kissat_very_verbose (solver, \ + #NAME " effort limit %s = %s + %s '" #START "'", \ + FORMAT_COUNT (NEW_LIMIT), \ + FORMAT_COUNT (OLD_LIMIT), FORMAT_COUNT (DELTA)); \ +\ + LIMIT = NEW_LIMIT; \ +\ + } while (0) + +struct kissat; + +bool kissat_delaying (struct kissat *, delay *); +void kissat_bump_delay (struct kissat *, delay *); +void kissat_reduce_delay (struct kissat *, delay *); + +#define DELAYING(NAME) kissat_delaying (solver, &solver->delays.NAME) + +#define BUMP_DELAY(NAME) kissat_bump_delay (solver, &solver->delays.NAME) + +#define REDUCE_DELAY(NAME) \ + kissat_reduce_delay (solver, &solver->delays.NAME) + +#endif diff --git a/src/sat/kissat/kissat.h b/src/sat/kissat/kissat.h new file mode 100644 index 000000000..eec380566 --- /dev/null +++ b/src/sat/kissat/kissat.h @@ -0,0 +1,44 @@ +#ifndef _kissat_h_INCLUDED +#define _kissat_h_INCLUDED + +typedef struct kissat kissat; + +// Default (partial) IPASIR interface. + +const char *kissat_signature (void); +kissat *kissat_init (void); +void kissat_add (kissat *solver, int lit); +int kissat_solve (kissat *solver); +int kissat_value (kissat *solver, int lit); +void kissat_release (kissat *solver); + +void kissat_set_terminate (kissat *solver, void *state, + int (*terminate) (void *state)); + +// Additional API functions. + +void kissat_terminate (kissat *solver); +void kissat_reserve (kissat *solver, int max_var); + +const char *kissat_id (void); +const char *kissat_version (void); +const char *kissat_compiler (void); + +const char **kissat_copyright (void); +void kissat_build (const char *line_prefix); +void kissat_banner (const char *line_prefix, const char *name_of_app); + +int kissat_get_option (kissat *solver, const char *name); +int kissat_set_option (kissat *solver, const char *name, int new_value); + +void kissat_set_prefix (kissat *solver, const char *prefix); + +int kissat_has_configuration (const char *name); +int kissat_set_configuration (kissat *solver, const char *name); + +void kissat_set_conflict_limit (kissat *solver, unsigned); +void kissat_set_decision_limit (kissat *solver, unsigned); + +void kissat_print_statistics (kissat *solver); + +#endif diff --git a/src/sat/kissat/kitten.c b/src/sat/kissat/kitten.c new file mode 100644 index 000000000..903230f81 --- /dev/null +++ b/src/sat/kissat/kitten.c @@ -0,0 +1,2794 @@ +#include "kitten.h" +#include "random.h" +#include "stack.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*------------------------------------------------------------------------*/ +#ifdef STAND_ALONE_KITTEN +/*------------------------------------------------------------------------*/ + +#include + +// clang-format off + +static const char * usage = +"usage: kitten [-h]" +#ifdef LOGGING +" [-l]" +#endif +" [-n] [-O[]] [-p] [ [ ] ]\n" +"\n" +"where\n" +"\n" +#ifdef LOGGING +" -l enable low-level logging\n" +#endif +" -n do not print witness\n" +" -O[] core shrinking effort\n" +" -p produce DRAT proof\n" +" -a assume a literal\n" +" -t set time limit\n" +"\n" +"and\n" +"\n" +" input in DIMACS format (default '')\n" +" DRAT proof or clausal core file\n" +; + +// clang-format on + +// Replacement for 'kissat' allocators in the stand alone variant. + +typedef signed char value; + +static void die (const char *fmt, ...) { + fputs ("kitten: error: ", stderr); + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); + exit (1); +} + +static inline void *kitten_calloc (size_t n, size_t size) { + void *res = calloc (n, size); + if (n && size && !res) + die ("out of memory allocating '%zu * %zu' bytes", n, size); + return res; +} + +#define CALLOC(P, N) \ + do { \ + (P) = kitten_calloc (N, sizeof *(P)); \ + } while (0) +#define DEALLOC(P, N) free (P) + +#undef ENLARGE_STACK + +#define ENLARGE_STACK(S) \ + do { \ + assert (FULL_STACK (S)); \ + const size_t SIZE = SIZE_STACK (S); \ + const size_t OLD_CAPACITY = CAPACITY_STACK (S); \ + const size_t NEW_CAPACITY = OLD_CAPACITY ? 2 * OLD_CAPACITY : 1; \ + const size_t BYTES = NEW_CAPACITY * sizeof *(S).begin; \ + (S).begin = realloc ((S).begin, BYTES); \ + if (!(S).begin) \ + die ("out of memory reallocating '%zu' bytes", BYTES); \ + (S).allocated = (S).begin + NEW_CAPACITY; \ + (S).end = (S).begin + SIZE; \ + } while (0) + +// Beside allocators above also use stand alone statistics counters. + +#define INC(NAME) \ + do { \ + statistics *statistics = &kitten->statistics; \ + assert (statistics->NAME < UINT64_MAX); \ + statistics->NAME++; \ + } while (0) + +#define ADD(NAME, DELTA) \ + do { \ + statistics *statistics = &kitten->statistics; \ + assert (statistics->NAME <= UINT64_MAX - (DELTA)); \ + statistics->NAME += (DELTA); \ + } while (0) + +#define KITTEN_TICKS (kitten->statistics.kitten_ticks) + +/*------------------------------------------------------------------------*/ +#else // '#ifdef STAND_ALONE_KITTEN' +/*------------------------------------------------------------------------*/ + +#include "allocate.h" // Use 'kissat' allocator if embedded. +#include "error.h" // Use 'kissat_fatal' if embedded. +#include "internal.h" // Also use 'kissat' statistics if embedded. +#include "terminate.h" // For macros defining termination macro. + +#define KITTEN_TICKS (solver->statistics.kitten_ticks) + +/*------------------------------------------------------------------------*/ +#endif // STAND_ALONE_KITTEN +/*------------------------------------------------------------------------*/ + +#define INVALID UINT_MAX +#define MAX_VARS ((1u << 31) - 1) + +#define CORE_FLAG (1u) +#define LEARNED_FLAG (2u) + +// clang-format off + +typedef struct kar kar; +typedef struct kink kink; +typedef struct klause klause; +typedef struct katch katch; +typedef STACK (unsigned) klauses; +typedef STACK (katch) katches; + +// clang-format on + +struct kar { + unsigned level; + unsigned reason; +}; + +struct kink { + unsigned next; + unsigned prev; + uint64_t stamp; +}; + +#define KITTEN_BLIT + +#ifdef KITTEN_BLIT + +struct katch { + unsigned blit; + unsigned ref : 31; + bool binary : 1; +}; + +#else + +struct katch { + unsigned ref; +}; + +#endif + +struct klause { + unsigned aux; + unsigned size; + unsigned flags; + unsigned lits[1]; +}; + +#ifdef STAND_ALONE_KITTEN + +typedef struct statistics statistics; + +struct statistics { + uint64_t learned; + uint64_t original; + uint64_t kitten_flip; + uint64_t kitten_flipped; + uint64_t kitten_sat; + uint64_t kitten_solve; + uint64_t kitten_solved; + uint64_t kitten_conflicts; + uint64_t kitten_decisions; + uint64_t kitten_propagations; + uint64_t kitten_ticks; + uint64_t kitten_unknown; + uint64_t kitten_unsat; +}; + +#endif + +typedef struct kimits kimits; + +struct kimits { + uint64_t ticks; +}; + +struct kitten { +#ifndef STAND_ALONE_KITTEN + struct kissat *kissat; +#define solver (kitten->kissat) +#endif + + // First zero initialized field in 'clear_kitten' is 'status'. + // + int status; + +#if defined(STAND_ALONE_KITTEN) && defined(LOGGING) + bool logging; +#endif + bool antecedents; + bool learned; + + unsigned level; + unsigned propagated; + unsigned unassigned; + unsigned inconsistent; + unsigned failing; + + uint64_t generator; + + size_t lits; + size_t evars; + + size_t end_original_ref; + + struct { + unsigned first, last; + uint64_t stamp; + unsigned search; + } queue; + + // The 'size' field below is the first not zero reinitialized field + // by 'memset' in 'clear_kitten' (after 'kissat'). + + size_t size; + size_t esize; + + kar *vars; + kink *links; + value *marks; + value *values; + bool *failed; + unsigned char *phases; + unsigned *import; + katches *watches; + + unsigneds analyzed; + unsigneds assumptions; + unsigneds core; + unsigneds eclause; + unsigneds export; + unsigneds klause; + unsigneds klauses; + unsigneds resolved; + unsigneds trail; + unsigneds units; + + kimits limits; + uint64_t initialized; + +#ifdef STAND_ALONE_KITTEN + statistics statistics; +#endif +}; + +/*------------------------------------------------------------------------*/ + +static inline bool is_core_klause (klause *c) { + return c->flags & CORE_FLAG; +} + +static inline bool is_learned_klause (klause *c) { + return c->flags & LEARNED_FLAG; +} + +static inline void set_core_klause (klause *c) { c->flags |= CORE_FLAG; } + +static inline void unset_core_klause (klause *c) { c->flags &= ~CORE_FLAG; } + +static inline klause *dereference_klause (kitten *kitten, unsigned ref) { + unsigned *res = BEGIN_STACK (kitten->klauses) + ref; + assert (res < END_STACK (kitten->klauses)); + return (klause *) res; +} + +#ifdef LOGGING + +static inline unsigned reference_klause (kitten *kitten, const klause *c) { + const unsigned *const begin = BEGIN_STACK (kitten->klauses); + const unsigned *p = (const unsigned *) c; + assert (begin <= p); + assert (p < END_STACK (kitten->klauses)); + const unsigned res = p - begin; + return res; +} + +#endif + +/*------------------------------------------------------------------------*/ + +#define KATCHES(KIT) (kitten->watches[assert ((KIT) < kitten->lits), (KIT)]) + +#define all_klauses(C) \ + klause *C = begin_klauses (kitten), *end_##C = end_klauses (kitten); \ + (C) != end_##C; \ + (C) = next_klause (kitten, C) + +#define all_original_klauses(C) \ + klause *C = begin_klauses (kitten), \ + *end_##C = end_original_klauses (kitten); \ + (C) != end_##C; \ + (C) = next_klause (kitten, C) + +#define all_learned_klauses(C) \ + klause *C = begin_learned_klauses (kitten), \ + *end_##C = end_klauses (kitten); \ + (C) != end_##C; \ + (C) = next_klause (kitten, C) + +#define all_kits(KIT) \ + size_t KIT = 0, KIT_##END = kitten->lits; \ + KIT != KIT_##END; \ + KIT++ + +#define BEGIN_KLAUSE(C) (C)->lits + +#define END_KLAUSE(C) (BEGIN_KLAUSE (C) + (C)->size) + +#define all_literals_in_klause(KIT, C) \ + unsigned KIT, *KIT##_PTR = BEGIN_KLAUSE (C), \ + *KIT##_END = END_KLAUSE (C); \ + KIT##_PTR != KIT##_END && ((KIT = *KIT##_PTR), true); \ + ++KIT##_PTR + +#define all_antecedents(REF, C) \ + unsigned REF, *REF##_PTR = antecedents (C), \ + *REF##_END = REF##_PTR + (C)->aux; \ + REF##_PTR != REF##_END && ((REF = *REF##_PTR), true); \ + ++REF##_PTR + +#ifdef LOGGING + +#ifdef STAND_ALONE_KITTEN +#define logging (kitten->logging) +#else +#define logging GET_OPTION (log) +#endif + +static void log_basic (kitten *, const char *, ...) + __attribute__ ((format (printf, 2, 3))); + +static void log_basic (kitten *kitten, const char *fmt, ...) { + assert (logging); + printf ("c KITTEN %u ", kitten->level); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); + fflush (stdout); +} + +static void log_reference (kitten *, unsigned, const char *, ...) + __attribute__ ((format (printf, 3, 4))); + +static void log_reference (kitten *kitten, unsigned ref, const char *fmt, + ...) { + klause *c = dereference_klause (kitten, ref); + assert (logging); + printf ("c KITTEN %u ", kitten->level); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + if (is_learned_klause (c)) { + fputs (" learned", stdout); + if (c->aux) + printf ("[%u]", c->aux); + } else { + fputs (" original", stdout); + if (c->aux != INVALID) + printf ("[%u]", c->aux); + } + printf (" size %u clause[%u]", c->size, ref); + value *values = kitten->values; + kar *vars = kitten->vars; + for (all_literals_in_klause (lit, c)) { + printf (" %u", lit); + const value value = values[lit]; + if (value) + printf ("@%u=%d", vars[lit / 2].level, (int) value); + } + fputc ('\n', stdout); + fflush (stdout); +} + +#define LOG(...) \ + do { \ + if (logging) \ + log_basic (kitten, __VA_ARGS__); \ + } while (0) + +#define ROG(...) \ + do { \ + if (logging) \ + log_reference (kitten, __VA_ARGS__); \ + } while (0) + +#else + +#define LOG(...) \ + do { \ + } while (0) +#define ROG(...) \ + do { \ + } while (0) + +#endif + +static void check_queue (kitten *kitten) { +#ifdef CHECK_KITTEN + const unsigned vars = kitten->lits / 2; + unsigned found = 0, prev = INVALID; + kink *links = kitten->links; + uint64_t stamp = 0; + for (unsigned idx = kitten->queue.first, next; idx != INVALID; + idx = next) { + kink *link = links + idx; + assert (link->prev == prev); + assert (!found || stamp < link->stamp); + assert (link->stamp < kitten->queue.stamp); + stamp = link->stamp; + next = link->next; + prev = idx; + found++; + } + assert (found == vars); + unsigned next = INVALID; + found = 0; + for (unsigned idx = kitten->queue.last, prev; idx != INVALID; + idx = prev) { + kink *link = links + idx; + assert (link->next == next); + prev = link->prev; + next = idx; + found++; + } + assert (found == vars); + value *values = kitten->values; + bool first = true; + for (unsigned idx = kitten->queue.search, next; idx != INVALID; + idx = next) { + kink *link = links + idx; + next = link->next; + const unsigned lit = 2 * idx; + assert (first || values[lit]); + first = false; + } +#else + (void) kitten; +#endif +} + +static void update_search (kitten *kitten, unsigned idx) { + if (kitten->queue.search == idx) + return; + kitten->queue.search = idx; + LOG ("search updated to %u stamped %" PRIu64, idx, + kitten->links[idx].stamp); +} + +static void enqueue (kitten *kitten, unsigned idx) { + LOG ("enqueue %u", idx); + kink *links = kitten->links; + kink *l = links + idx; + const unsigned last = kitten->queue.last; + if (last == INVALID) + kitten->queue.first = idx; + else + links[last].next = idx; + l->prev = last; + l->next = INVALID; + kitten->queue.last = idx; + l->stamp = kitten->queue.stamp++; + LOG ("stamp %" PRIu64, l->stamp); +} + +static void dequeue (kitten *kitten, unsigned idx) { + LOG ("dequeue %u", idx); + kink *links = kitten->links; + kink *l = links + idx; + const unsigned prev = l->prev; + const unsigned next = l->next; + if (prev == INVALID) + kitten->queue.first = next; + else + links[prev].next = next; + if (next == INVALID) + kitten->queue.last = prev; + else + links[next].prev = prev; +} + +static void init_queue (kitten *kitten, size_t old_vars, size_t new_vars) { + for (size_t idx = old_vars; idx < new_vars; idx++) { + assert (!kitten->values[2 * idx]); + assert (kitten->unassigned < UINT_MAX); + kitten->unassigned++; + enqueue (kitten, idx); + } + LOG ("initialized decision queue from %zu to %zu", old_vars, new_vars); + update_search (kitten, kitten->queue.last); + check_queue (kitten); +} + +static void initialize_kitten (kitten *kitten) { + kitten->queue.first = INVALID; + kitten->queue.last = INVALID; + kitten->inconsistent = INVALID; + kitten->failing = INVALID; + kitten->queue.search = INVALID; + kitten->limits.ticks = UINT64_MAX; + kitten->generator = kitten->initialized++; +} + +static void clear_kitten (kitten *kitten) { + size_t bytes = (char *) &kitten->size - (char *) &kitten->status; + memset (&kitten->status, 0, bytes); +#ifdef STAND_ALONE_KITTEN + memset (&kitten->statistics, 0, sizeof (statistics)); +#endif + initialize_kitten (kitten); +} + +#define RESIZE1(P) \ + do { \ + void *OLD_PTR = (P); \ + CALLOC ((P), new_size / 2); \ + const size_t BYTES = old_vars * sizeof *(P); \ + if (BYTES) \ + memcpy ((P), OLD_PTR, BYTES); \ + void *NEW_PTR = (P); \ + (P) = OLD_PTR; \ + DEALLOC ((P), old_size / 2); \ + (P) = NEW_PTR; \ + } while (0) + +#define RESIZE2(P) \ + do { \ + void *OLD_PTR = (P); \ + CALLOC ((P), new_size); \ + const size_t BYTES = old_lits * sizeof *(P); \ + if (BYTES) \ + memcpy ((P), OLD_PTR, BYTES); \ + void *NEW_PTR = (P); \ + (P) = OLD_PTR; \ + DEALLOC ((P), old_size); \ + (P) = NEW_PTR; \ + } while (0) + +static void enlarge_internal (kitten *kitten, size_t new_lits) { + const size_t old_lits = kitten->lits; + assert (old_lits < new_lits); + const size_t old_size = kitten->size; + const unsigned new_vars = new_lits / 2; + const unsigned old_vars = old_lits / 2; + if (old_size < new_lits) { + size_t new_size = old_size ? 2 * old_size : 2; + while (new_size <= new_lits) + new_size *= 2; + LOG ("internal literals resized to %zu from %zu (requested %zu)", + new_size, old_size, new_lits); + + RESIZE1 (kitten->marks); + RESIZE1 (kitten->phases); + RESIZE2 (kitten->values); + RESIZE2 (kitten->failed); + RESIZE1 (kitten->vars); + RESIZE1 (kitten->links); + RESIZE2 (kitten->watches); + + kitten->size = new_size; + } + kitten->lits = new_lits; + init_queue (kitten, old_vars, new_vars); + LOG ("internal literals activated until %zu literals", new_lits); + return; +} + +static const char *status_to_string (int status) { + switch (status) { + case 10: + return "formula satisfied"; + case 20: + return "formula inconsistent"; + case 21: + return "formula inconsistent and core computed"; + default: + assert (!status); + return "formula unsolved"; + } +} + +static void invalid_api_usage (const char *fun, const char *fmt, ...) { + fprintf (stderr, "kitten: fatal error: invalid API usage in '%s': ", fun); + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); + fflush (stderr); + abort (); +} + +#define INVALID_API_USAGE(...) invalid_api_usage (__func__, __VA_ARGS__) + +#define REQUIRE_INITIALIZED() \ + do { \ + if (!kitten) \ + INVALID_API_USAGE ("solver argument zero"); \ + } while (0) + +#define REQUIRE_STATUS(EXPECTED) \ + do { \ + REQUIRE_INITIALIZED (); \ + if (kitten->status != (EXPECTED)) \ + INVALID_API_USAGE ("invalid status '%s' (expected '%s')", \ + status_to_string (kitten->status), \ + status_to_string (EXPECTED)); \ + } while (0) + +#define UPDATE_STATUS(STATUS) \ + do { \ + if (kitten->status != (STATUS)) \ + LOG ("updating status from '%s' to '%s'", \ + status_to_string (kitten->status), status_to_string (STATUS)); \ + else \ + LOG ("keeping status at '%s'", status_to_string (STATUS)); \ + kitten->status = (STATUS); \ + } while (0) + +#ifdef STAND_ALONE_KITTEN + +kitten *kitten_init (void) { + kitten *kitten; + CALLOC (kitten, 1); + initialize_kitten (kitten); + return kitten; +} + +#else + +kitten *kitten_embedded (struct kissat *kissat) { + if (!kissat) + INVALID_API_USAGE ("'kissat' argument zero"); + + kitten *kitten; + struct kitten dummy; + dummy.kissat = kissat; + kitten = &dummy; + CALLOC (kitten, 1); + kitten->kissat = kissat; + initialize_kitten (kitten); + return kitten; +} + +#endif + +void kitten_track_antecedents (kitten *kitten) { + REQUIRE_STATUS (0); + + if (kitten->learned) + INVALID_API_USAGE ("can not start tracking antecedents after learning"); + + LOG ("enabling antecedents tracking"); + kitten->antecedents = true; +} + +void kitten_randomize_phases (kitten *kitten) { + REQUIRE_INITIALIZED (); + + LOG ("randomizing phases"); + + unsigned char *phases = kitten->phases; + const unsigned vars = kitten->size / 2; + + uint64_t random = kissat_next_random64 (&kitten->generator); + + unsigned i = 0; + const unsigned rest = vars & ~63u; + + while (i != rest) { + uint64_t *p = (uint64_t *) (phases + i); + p[0] = (random >> 0) & 0x0101010101010101; + p[1] = (random >> 1) & 0x0101010101010101; + p[2] = (random >> 2) & 0x0101010101010101; + p[3] = (random >> 3) & 0x0101010101010101; + p[4] = (random >> 4) & 0x0101010101010101; + p[5] = (random >> 5) & 0x0101010101010101; + p[6] = (random >> 6) & 0x0101010101010101; + p[7] = (random >> 7) & 0x0101010101010101; + random = kissat_next_random64 (&kitten->generator); + i += 64; + } + + unsigned shift = 0; + while (i != vars) + phases[i++] = (random >> shift++) & 1; +} + +void kitten_flip_phases (kitten *kitten) { + REQUIRE_INITIALIZED (); + + LOG ("flipping phases"); + + unsigned char *phases = kitten->phases; + const unsigned vars = kitten->size / 2; + + unsigned i = 0; + const unsigned rest = vars & ~7u; + + while (i != rest) { + uint64_t *p = (uint64_t *) (phases + i); + *p ^= 0x0101010101010101; + i += 8; + } + + while (i != vars) + phases[i++] ^= 1; +} + +void kitten_no_ticks_limit (kitten *kitten) { + REQUIRE_INITIALIZED (); + LOG ("forcing no ticks limit"); + kitten->limits.ticks = UINT64_MAX; +} + +void kitten_set_ticks_limit (kitten *kitten, uint64_t delta) { + REQUIRE_INITIALIZED (); + const uint64_t current = KITTEN_TICKS; + uint64_t limit; + if (UINT64_MAX - delta <= current) { + LOG ("forcing unlimited ticks limit"); + limit = UINT64_MAX; + } else { + limit = current + delta; + LOG ("new limit of %" PRIu64 " ticks after %" PRIu64, limit, delta); + } + + kitten->limits.ticks = limit; +} + +static void shuffle_unsigned_array (kitten *kitten, size_t size, + unsigned *a) { + for (size_t i = 0; i != size; i++) { + const size_t j = kissat_pick_random (&kitten->generator, 0, i); + if (j == i) + continue; + const unsigned first = a[i]; + const unsigned second = a[j]; + a[i] = second; + a[j] = first; + } +} + +static void shuffle_unsigned_stack (kitten *kitten, unsigneds *stack) { + const size_t size = SIZE_STACK (*stack); + unsigned *a = BEGIN_STACK (*stack); + shuffle_unsigned_array (kitten, size, a); +} + +static void shuffle_katches_array (kitten *kitten, size_t size, katch *a) { + for (size_t i = 0; i != size; i++) { + const size_t j = kissat_pick_random (&kitten->generator, 0, i); + if (j == i) + continue; + const katch first = a[i]; + const katch second = a[j]; + a[i] = second; + a[j] = first; + } +} + +static void shuffle_katches_stack (kitten *kitten, katches *stack) { + const size_t size = SIZE_STACK (*stack); + katch *a = BEGIN_STACK (*stack); + shuffle_katches_array (kitten, size, a); +} + +static void shuffle_katches (kitten *kitten) { + LOG ("shuffling watch lists"); + const size_t lits = kitten->lits; + for (size_t lit = 0; lit != lits; lit++) + shuffle_katches_stack (kitten, &KATCHES (lit)); +} + +static void shuffle_queue (kitten *kitten) { + LOG ("shuffling variable decision order"); + + const unsigned vars = kitten->lits / 2; + for (unsigned i = 0; i != vars; i++) { + const unsigned idx = kissat_pick_random (&kitten->generator, 0, vars); + dequeue (kitten, idx); + enqueue (kitten, idx); + } + update_search (kitten, kitten->queue.last); +} + +static void shuffle_units (kitten *kitten) { + LOG ("shuffling units"); + shuffle_unsigned_stack (kitten, &kitten->units); +} + +void kitten_shuffle_clauses (kitten *kitten) { + REQUIRE_STATUS (0); + shuffle_queue (kitten); + shuffle_katches (kitten); + shuffle_units (kitten); +} + +static inline unsigned *antecedents (klause *c) { + assert (is_learned_klause (c)); + return c->lits + c->size; +} + +static inline void watch_klause (kitten *kitten, unsigned lit, klause *c, + unsigned ref) { + ROG (ref, "watching %u in", lit); + katches *watches = &KATCHES (lit); + katch katch; + katch.ref = ref; +#ifdef KITTEN_BLIT + const unsigned size = c->size; + assert (lit == c->lits[0] || lit == c->lits[1]); + const unsigned blit = c->lits[0] ^ c->lits[1] ^ lit; + const bool binary = size == 2; + assert (size > 1); + assert (ref < (1u << 31)); + katch.blit = blit; + katch.binary = binary; +#else + (void) c; +#endif + PUSH_STACK (*watches, katch); +} + +static inline void connect_new_klause (kitten *kitten, unsigned ref) { + ROG (ref, "new"); + + klause *c = dereference_klause (kitten, ref); + + if (!c->size) { + if (kitten->inconsistent == INVALID) { + ROG (ref, "registering inconsistent empty"); + kitten->inconsistent = ref; + } else + ROG (ref, "ignoring inconsistent empty"); + } else if (c->size == 1) { + ROG (ref, "watching unit"); + PUSH_STACK (kitten->units, ref); + } else { + watch_klause (kitten, c->lits[0], c, ref); + watch_klause (kitten, c->lits[1], c, ref); + } +} + +static unsigned new_reference (kitten *kitten) { + size_t ref = SIZE_STACK (kitten->klauses); + if (ref >= INVALID) { +#ifdef STAND_ALONE_KITTEN + die ("maximum number of literals exhausted"); +#else + kissat_fatal ("kitten: maximum number of literals exhausted"); +#endif + } + const unsigned res = (unsigned) ref; + assert (res != INVALID); + INC (kitten_ticks); + return res; +} + +static void new_original_klause (kitten *kitten, unsigned id) { + unsigned res = new_reference (kitten); + unsigned size = SIZE_STACK (kitten->klause); + unsigneds *klauses = &kitten->klauses; + PUSH_STACK (*klauses, id); + PUSH_STACK (*klauses, size); + PUSH_STACK (*klauses, 0); + for (all_stack (unsigned, lit, kitten->klause)) + PUSH_STACK (*klauses, lit); + connect_new_klause (kitten, res); + kitten->end_original_ref = SIZE_STACK (*klauses); +#ifdef STAND_ALONE_KITTEN + kitten->statistics.original++; +#endif +} + +static void enlarge_external (kitten *kitten, size_t eidx) { + const size_t old_size = kitten->esize; + const unsigned old_evars = kitten->evars; + assert (old_evars <= eidx); + const unsigned new_evars = eidx + 1; + if (old_size <= eidx) { + size_t new_size = old_size ? 2 * old_size : 1; + while (new_size <= eidx) + new_size *= 2; + LOG ("external resizing to %zu variables from %zu (requested %u)", + new_size, old_size, new_evars); + unsigned *old_import = kitten->import; + CALLOC (kitten->import, new_size); + const size_t bytes = old_evars * sizeof *kitten->import; + if (bytes) + memcpy (kitten->import, old_import, bytes); + DEALLOC (old_import, old_size); + kitten->esize = new_size; + } + kitten->evars = new_evars; + LOG ("external variables enlarged to %u", new_evars); +} + +static unsigned import_literal (kitten *kitten, unsigned elit) { + const unsigned eidx = elit / 2; + if (eidx >= kitten->evars) + enlarge_external (kitten, eidx); + + unsigned iidx = kitten->import[eidx]; + if (!iidx) { + iidx = SIZE_STACK (kitten->export); + PUSH_STACK (kitten->export, eidx); + kitten->import[eidx] = iidx + 1; + } else + iidx--; + unsigned ilit = 2 * iidx + (elit & 1); + LOG ("imported external literal %u as internal literal %u", elit, ilit); + const size_t new_lits = (ilit | 1) + (size_t) 1; + assert (ilit < new_lits); + assert (ilit / 2 < new_lits / 2); + if (new_lits > kitten->lits) + enlarge_internal (kitten, new_lits); + return ilit; +} + +static unsigned export_literal (kitten *kitten, unsigned ilit) { + const unsigned iidx = ilit / 2; + assert (iidx < SIZE_STACK (kitten->export)); + const unsigned eidx = PEEK_STACK (kitten->export, iidx); + const unsigned elit = 2 * eidx + (ilit & 1); + return elit; +} + +unsigned new_learned_klause (kitten *kitten) { + unsigned res = new_reference (kitten); + unsigneds *klauses = &kitten->klauses; + const size_t size = SIZE_STACK (kitten->klause); + assert (size <= UINT_MAX); + const size_t aux = + kitten->antecedents ? SIZE_STACK (kitten->resolved) : 0; + assert (aux <= UINT_MAX); + PUSH_STACK (*klauses, (unsigned) aux); + PUSH_STACK (*klauses, (unsigned) size); + PUSH_STACK (*klauses, LEARNED_FLAG); + for (all_stack (unsigned, lit, kitten->klause)) + PUSH_STACK (*klauses, lit); + if (aux) + for (all_stack (unsigned, ref, kitten->resolved)) + PUSH_STACK (*klauses, ref); + connect_new_klause (kitten, res); + kitten->learned = true; +#ifdef STAND_ALONE_KITTEN + kitten->statistics.learned++; +#endif + return res; +} + +void kitten_clear (kitten *kitten) { + LOG ("clear kitten of size %zu", kitten->size); + + assert (EMPTY_STACK (kitten->analyzed)); + assert (EMPTY_STACK (kitten->klause)); + assert (EMPTY_STACK (kitten->eclause)); + assert (EMPTY_STACK (kitten->resolved)); + + CLEAR_STACK (kitten->assumptions); + CLEAR_STACK (kitten->core); + CLEAR_STACK (kitten->klause); + CLEAR_STACK (kitten->klauses); + CLEAR_STACK (kitten->trail); + CLEAR_STACK (kitten->units); + + for (all_kits (kit)) + CLEAR_STACK (KATCHES (kit)); + + while (!EMPTY_STACK (kitten->export)) + kitten->import[POP_STACK (kitten->export)] = 0; + + const size_t lits = kitten->size; + const unsigned vars = lits / 2; + +#ifndef NDEBUG + for (unsigned i = 0; i < vars; i++) + assert (!kitten->marks[i]); +#endif + + if (vars) { + memset (kitten->phases, 0, vars); + memset (kitten->vars, 0, vars); + } + + if (lits) { + memset (kitten->values, 0, lits); + memset (kitten->failed, 0, lits); + } + + clear_kitten (kitten); +} + +void kitten_release (kitten *kitten) { + RELEASE_STACK (kitten->analyzed); + RELEASE_STACK (kitten->assumptions); + RELEASE_STACK (kitten->core); + RELEASE_STACK (kitten->eclause); + RELEASE_STACK (kitten->export); + RELEASE_STACK (kitten->klause); + RELEASE_STACK (kitten->klauses); + RELEASE_STACK (kitten->resolved); + RELEASE_STACK (kitten->trail); + RELEASE_STACK (kitten->units); + + for (size_t lit = 0; lit < kitten->size; lit++) + RELEASE_STACK (kitten->watches[lit]); + +#ifndef STAND_ALONE_KITTEN + const size_t lits = kitten->size; + const unsigned vars = lits / 2; +#endif + DEALLOC (kitten->marks, vars); + DEALLOC (kitten->phases, vars); + DEALLOC (kitten->values, lits); + DEALLOC (kitten->failed, lits); + DEALLOC (kitten->vars, vars); + DEALLOC (kitten->links, vars); + DEALLOC (kitten->watches, lits); + DEALLOC (kitten->import, kitten->esize); +#ifdef STAND_ALONE_KITTEN + free (kitten); +#else + kissat_free (kitten->kissat, kitten, sizeof *kitten); +#endif +} + +static inline void move_to_front (kitten *kitten, unsigned idx) { + if (idx == kitten->queue.last) + return; + LOG ("move to front variable %u", idx); + dequeue (kitten, idx); + enqueue (kitten, idx); + assert (kitten->values[2 * idx]); +} + +static inline void assign (kitten *kitten, unsigned lit, unsigned reason) { +#ifdef LOGGING + if (reason == INVALID) + LOG ("assign %u as decision", lit); + else + ROG (reason, "assign %u reason", lit); +#endif + value *values = kitten->values; + const unsigned not_lit = lit ^ 1; + assert (!values[lit]); + assert (!values[not_lit]); + values[lit] = 1; + values[not_lit] = -1; + const unsigned idx = lit / 2; + const unsigned sign = lit & 1; + kitten->phases[idx] = sign; + PUSH_STACK (kitten->trail, lit); + kar *v = kitten->vars + idx; + v->level = kitten->level; + if (!v->level) { + assert (reason != INVALID); + klause *c = dereference_klause (kitten, reason); + if (c->size > 1) { + if (kitten->antecedents) { + PUSH_STACK (kitten->resolved, reason); + for (all_literals_in_klause (other, c)) + if (other != lit) { + const unsigned other_idx = other / 2; + const unsigned other_ref = kitten->vars[other_idx].reason; + assert (other_ref != INVALID); + PUSH_STACK (kitten->resolved, other_ref); + } + } + PUSH_STACK (kitten->klause, lit); + reason = new_learned_klause (kitten); + CLEAR_STACK (kitten->resolved); + CLEAR_STACK (kitten->klause); + } + } + v->reason = reason; + assert (kitten->unassigned); + kitten->unassigned--; +} + +static inline unsigned propagate_literal (kitten *kitten, unsigned lit) { + LOG ("propagating %u", lit); + value *values = kitten->values; + assert (values[lit] > 0); + const unsigned not_lit = lit ^ 1; + katches *watches = kitten->watches + not_lit; + unsigned conflict = INVALID; + katch *q = BEGIN_STACK (*watches); + const katch *const end_watches = END_STACK (*watches); + katch const *p = q; + uint64_t ticks = (((char *) end_watches - (char *) q) >> 7) + 1; + while (p != end_watches) { + katch katch = *q++ = *p++; + const unsigned ref = katch.ref; +#ifdef KITTEN_BLIT + const unsigned blit = katch.blit; + assert (blit != not_lit); + const value blit_value = values[blit]; + if (blit_value > 0) + continue; + if (katch.binary) { + if (blit_value < 0) { + ROG (ref, "conflict"); + INC (kitten_conflicts); + conflict = ref; + break; + } else { + assert (!blit_value); + assign (kitten, blit, ref); + continue; + } + } +#endif + klause *c = dereference_klause (kitten, ref); + assert (c->size > 1); + unsigned *lits = c->lits; + const unsigned other = lits[0] ^ lits[1] ^ not_lit; + const value other_value = values[other]; + ticks++; + if (other_value > 0) { +#ifdef KITTEN_BLIT + q[-1].blit = other; +#endif + continue; + } + value replacement_value = -1; + unsigned replacement = INVALID; + const unsigned *const end_lits = lits + c->size; + unsigned *r; + for (r = lits + 2; r != end_lits; r++) { + replacement = *r; + replacement_value = values[replacement]; + if (replacement_value >= 0) + break; + } + if (replacement_value >= 0) { + assert (replacement != INVALID); + ROG (ref, "unwatching %u in", not_lit); + lits[0] = other; + lits[1] = replacement; + *r = not_lit; + watch_klause (kitten, replacement, c, ref); + q--; + } else if (other_value < 0) { + ROG (ref, "conflict"); + INC (kitten_conflicts); + conflict = ref; + break; + } else { + assert (!other_value); + assign (kitten, other, ref); + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_STACK (*watches, q); + ADD (kitten_ticks, ticks); + return conflict; +} + +static inline unsigned propagate (kitten *kitten) { + assert (kitten->inconsistent == INVALID); + unsigned propagated = 0; + unsigned conflict = INVALID; + while (conflict == INVALID && + kitten->propagated < SIZE_STACK (kitten->trail)) { + const unsigned lit = PEEK_STACK (kitten->trail, kitten->propagated); + conflict = propagate_literal (kitten, lit); + kitten->propagated++; + propagated++; + } + ADD (kitten_propagations, propagated); + return conflict; +} + +static void bump (kitten *kitten) { + value *marks = kitten->marks; + for (all_stack (unsigned, idx, kitten->analyzed)) { + marks[idx] = 0; + move_to_front (kitten, idx); + } + check_queue (kitten); +} + +static inline void unassign (kitten *kitten, value *values, unsigned lit) { + const unsigned not_lit = lit ^ 1; + assert (values[lit]); + assert (values[not_lit]); + const unsigned idx = lit / 2; +#ifdef LOGGING + kar *var = kitten->vars + idx; + kitten->level = var->level; + LOG ("unassign %u", lit); +#endif + values[lit] = values[not_lit] = 0; + assert (kitten->unassigned < kitten->lits / 2); + kitten->unassigned++; + kink *links = kitten->links; + kink *link = links + idx; + if (link->stamp > links[kitten->queue.search].stamp) + update_search (kitten, idx); +} + +static void backtrack (kitten *kitten, unsigned jump) { + check_queue (kitten); + assert (jump < kitten->level); + LOG ("back%s to level %u", + (kitten->level == jump + 1 ? "tracking" : "jumping"), jump); + kar *vars = kitten->vars; + value *values = kitten->values; + unsigneds *trail = &kitten->trail; + while (!EMPTY_STACK (*trail)) { + const unsigned lit = TOP_STACK (*trail); + const unsigned idx = lit / 2; + const unsigned level = vars[idx].level; + if (level == jump) + break; + (void) POP_STACK (*trail); + unassign (kitten, values, lit); + } + kitten->propagated = SIZE_STACK (*trail); + kitten->level = jump; + check_queue (kitten); +} + +void completely_backtrack_to_root_level (kitten *kitten) { + check_queue (kitten); + LOG ("completely backtracking to level 0"); + value *values = kitten->values; + unsigneds *trail = &kitten->trail; +#ifndef NDEBUG + kar *vars = kitten->vars; +#endif + for (all_stack (unsigned, lit, *trail)) { + assert (vars[lit / 2].level); + unassign (kitten, values, lit); + } + CLEAR_STACK (*trail); + kitten->propagated = 0; + kitten->level = 0; + check_queue (kitten); +} + +static void analyze (kitten *kitten, unsigned conflict) { + assert (kitten->level); + assert (kitten->inconsistent == INVALID); + assert (EMPTY_STACK (kitten->analyzed)); + assert (EMPTY_STACK (kitten->resolved)); + assert (EMPTY_STACK (kitten->klause)); + PUSH_STACK (kitten->klause, INVALID); + unsigned reason = conflict; + value *marks = kitten->marks; + const kar *const vars = kitten->vars; + const unsigned level = kitten->level; + unsigned const *p = END_STACK (kitten->trail); + unsigned open = 0, jump = 0, size = 1, uip; + for (;;) { + assert (reason != INVALID); + klause *c = dereference_klause (kitten, reason); + assert (c); + ROG (reason, "analyzing"); + PUSH_STACK (kitten->resolved, reason); + for (all_literals_in_klause (lit, c)) { + const unsigned idx = lit / 2; + if (marks[idx]) + continue; + assert (kitten->values[lit] < 0); + LOG ("analyzed %u", lit); + marks[idx] = true; + PUSH_STACK (kitten->analyzed, idx); + const kar *const v = vars + idx; + const unsigned tmp = v->level; + if (tmp < level) { + if (tmp > jump) { + jump = tmp; + if (size > 1) { + const unsigned other = PEEK_STACK (kitten->klause, 1); + POKE_STACK (kitten->klause, 1, lit); + lit = other; + } + } + PUSH_STACK (kitten->klause, lit); + size++; + } else + open++; + } + unsigned idx; + do { + assert (BEGIN_STACK (kitten->trail) < p); + uip = *--p; + } while (!marks[idx = uip / 2]); + assert (open); + if (!--open) + break; + reason = vars[idx].reason; + } + const unsigned not_uip = uip ^ 1; + LOG ("first UIP %u jump level %u size %u", not_uip, jump, size); + POKE_STACK (kitten->klause, 0, not_uip); + bump (kitten); + CLEAR_STACK (kitten->analyzed); + const unsigned learned_ref = new_learned_klause (kitten); + CLEAR_STACK (kitten->resolved); + CLEAR_STACK (kitten->klause); + backtrack (kitten, jump); + assign (kitten, not_uip, learned_ref); +} + +static void failing (kitten *kitten) { + assert (kitten->inconsistent == INVALID); + assert (!EMPTY_STACK (kitten->assumptions)); + assert (EMPTY_STACK (kitten->analyzed)); + assert (EMPTY_STACK (kitten->resolved)); + assert (EMPTY_STACK (kitten->klause)); + LOG ("analyzing failing assumptions"); + const value *const values = kitten->values; + const kar *const vars = kitten->vars; + unsigned failed_clashing = INVALID; + unsigned first_failed = INVALID; + unsigned failed_unit = INVALID; + for (all_stack (unsigned, lit, kitten->assumptions)) { + if (values[lit] >= 0) + continue; + if (first_failed == INVALID) + first_failed = lit; + const unsigned failed_idx = lit / 2; + const kar *const failed_var = vars + failed_idx; + if (!failed_var->level) { + failed_unit = lit; + break; + } + if (failed_clashing == INVALID && failed_var->reason == INVALID) + failed_clashing = lit; + } + unsigned failed; + if (failed_unit != INVALID) + failed = failed_unit; + else if (failed_clashing != INVALID) + failed = failed_clashing; + else + failed = first_failed; + assert (failed != INVALID); + const unsigned failed_idx = failed / 2; + const kar *const failed_var = vars + failed_idx; + const unsigned failed_reason = failed_var->reason; + LOG ("first failed assumption %u", failed); + kitten->failed[failed] = true; + + if (failed_unit != INVALID) { + assert (dereference_klause (kitten, failed_reason)->size == 1); + LOG ("root-level falsified assumption %u", failed); + kitten->failing = failed_reason; + ROG (kitten->failing, "failing reason"); + return; + } + + const unsigned not_failed = failed ^ 1; + if (failed_clashing != INVALID) { + LOG ("clashing with negated assumption %u", not_failed); + kitten->failed[not_failed] = true; + assert (kitten->failing == INVALID); + return; + } + + value *marks = kitten->marks; + assert (!marks[failed_idx]); + marks[failed_idx] = true; + PUSH_STACK (kitten->analyzed, failed_idx); + PUSH_STACK (kitten->klause, not_failed); + + for (size_t next = 0; next < SIZE_STACK (kitten->analyzed); next++) { + const unsigned idx = PEEK_STACK (kitten->analyzed, next); + assert (marks[idx]); + const kar *var = vars + idx; + const unsigned reason = var->reason; + if (reason == INVALID) { + unsigned lit = 2 * idx; + if (values[lit] < 0) + lit ^= 1; + LOG ("failed assumption %u", lit); + assert (!kitten->failed[lit]); + kitten->failed[lit] = true; + const unsigned not_lit = lit ^ 1; + PUSH_STACK (kitten->klause, not_lit); + } else { + ROG (reason, "analyzing"); + PUSH_STACK (kitten->resolved, reason); + klause *c = dereference_klause (kitten, reason); + for (all_literals_in_klause (other, c)) { + const unsigned other_idx = other / 2; + if (other_idx == idx) + continue; + if (marks[other_idx]) + continue; + marks[other_idx] = true; + PUSH_STACK (kitten->analyzed, other_idx); + LOG ("analyzing final literal %u", other ^ 1); + } + } + } + + for (all_stack (unsigned, idx, kitten->analyzed)) + assert (marks[idx]), marks[idx] = 0; + CLEAR_STACK (kitten->analyzed); + + const size_t resolved = SIZE_STACK (kitten->resolved); + assert (resolved); + + if (resolved == 1) { + kitten->failing = PEEK_STACK (kitten->resolved, 0); + ROG (kitten->failing, "reusing as core"); + } else { + kitten->failing = new_learned_klause (kitten); + ROG (kitten->failing, "new core"); + } + + CLEAR_STACK (kitten->resolved); + CLEAR_STACK (kitten->klause); +} + +static void flush_trail (kitten *kitten) { + unsigneds *trail = &kitten->trail; + LOG ("flushing %zu root-level literals from trail", SIZE_STACK (*trail)); + assert (!kitten->level); + kitten->propagated = 0; + CLEAR_STACK (*trail); +} + +static int decide (kitten *kitten) { + if (!kitten->level && !EMPTY_STACK (kitten->trail)) + flush_trail (kitten); + + const value *const values = kitten->values; + unsigned decision = INVALID; + const size_t assumptions = SIZE_STACK (kitten->assumptions); + while (kitten->level < assumptions) { + unsigned assumption = PEEK_STACK (kitten->assumptions, kitten->level); + value value = values[assumption]; + if (value < 0) { + LOG ("found failing assumption %u", assumption); + failing (kitten); + return 20; + } else if (value > 0) { + + kitten->level++; + LOG ("pseudo decision level %u for already satisfied assumption %u", + kitten->level, assumption); + } else { + decision = assumption; + LOG ("using assumption %u as decision", decision); + break; + } + } + + if (!kitten->unassigned) + return 10; + + if (KITTEN_TICKS >= kitten->limits.ticks) { + LOG ("ticks limit %" PRIu64 " hit after %" PRIu64 " ticks", + kitten->limits.ticks, KITTEN_TICKS); + return -1; + } + +#ifndef STAND_ALONE_KITTEN + if (TERMINATED (kitten_terminated_1)) + return -1; +#endif + + if (decision == INVALID) { + unsigned idx = kitten->queue.search; + const kink *const links = kitten->links; + for (;;) { + assert (idx != INVALID); + if (!values[2 * idx]) + break; + idx = links[idx].prev; + } + update_search (kitten, idx); + const unsigned phase = kitten->phases[idx]; + decision = 2 * idx + phase; + LOG ("decision %u variable %u phase %u", decision, idx, phase); + } + INC (kitten_decisions); + kitten->level++; + assign (kitten, decision, INVALID); + return 0; +} + +static void inconsistent (kitten *kitten, unsigned ref) { + assert (ref != INVALID); + assert (kitten->inconsistent == INVALID); + + if (!kitten->antecedents) { + kitten->inconsistent = ref; + ROG (ref, "registering inconsistent virtually empty"); + return; + } + + unsigneds *analyzed = &kitten->analyzed; + unsigneds *resolved = &kitten->resolved; + + assert (EMPTY_STACK (*analyzed)); + assert (EMPTY_STACK (*resolved)); + + value *marks = kitten->marks; + const kar *const vars = kitten->vars; + unsigned next = 0; + + for (;;) { + assert (ref != INVALID); + klause *c = dereference_klause (kitten, ref); + assert (c); + ROG (ref, "analyzing inconsistent"); + PUSH_STACK (*resolved, ref); + for (all_literals_in_klause (lit, c)) { + const unsigned idx = lit / 2; + assert (!vars[idx].level); + if (marks[idx]) + continue; + assert (kitten->values[lit] < 0); + LOG ("analyzed %u", lit); + marks[idx] = true; + PUSH_STACK (kitten->analyzed, idx); + } + if (next == SIZE_STACK (kitten->analyzed)) + break; + const unsigned idx = PEEK_STACK (kitten->analyzed, next); + next++; + const kar *const v = vars + idx; + assert (!v->level); + ref = v->reason; + } + assert (EMPTY_STACK (kitten->klause)); + ref = new_learned_klause (kitten); + ROG (ref, "registering final inconsistent empty"); + kitten->inconsistent = ref; + + for (all_stack (unsigned, idx, *analyzed)) + marks[idx] = 0; + + CLEAR_STACK (*analyzed); + CLEAR_STACK (*resolved); +} + +static int propagate_units (kitten *kitten) { + if (kitten->inconsistent != INVALID) + return 20; + + if (EMPTY_STACK (kitten->units)) { + LOG ("no root level unit clauses"); + return 0; + } + + LOG ("propagating %zu root level unit clauses", + SIZE_STACK (kitten->units)); + + const value *const values = kitten->values; + + for (size_t next = 0; next < SIZE_STACK (kitten->units); next++) { + const unsigned ref = PEEK_STACK (kitten->units, next); + assert (ref != INVALID); + klause *c = dereference_klause (kitten, ref); + assert (c->size == 1); + ROG (ref, "propagating unit"); + const unsigned unit = c->lits[0]; + const value value = values[unit]; + if (value > 0) + continue; + if (value < 0) { + inconsistent (kitten, ref); + return 20; + } + assign (kitten, unit, ref); + const unsigned conflict = propagate (kitten); + if (conflict == INVALID) + continue; + inconsistent (kitten, conflict); + return 20; + } + return 0; +} + +/*------------------------------------------------------------------------*/ + +static klause *begin_klauses (kitten *kitten) { + return (klause *) BEGIN_STACK (kitten->klauses); +} + +static klause *end_original_klauses (kitten *kitten) { + return (klause *) (BEGIN_STACK (kitten->klauses) + + kitten->end_original_ref); +} + +static klause *end_klauses (kitten *kitten) { + return (klause *) END_STACK (kitten->klauses); +} + +static klause *next_klause (kitten *kitten, klause *c) { + assert (begin_klauses (kitten) <= c); + assert (c < end_klauses (kitten)); + unsigned *res = c->lits + c->size; + if (kitten->antecedents && is_learned_klause (c)) + res += c->aux; + return (klause *) res; +} + +/*------------------------------------------------------------------------*/ + +static void reset_core (kitten *kitten) { + LOG ("resetting core clauses"); + size_t reset = 0; + for (all_klauses (c)) + if (is_core_klause (c)) + unset_core_klause (c), reset++; + LOG ("reset %zu core clauses", reset); + CLEAR_STACK (kitten->core); +} + +static void reset_assumptions (kitten *kitten) { + LOG ("reset %zu assumptions", SIZE_STACK (kitten->assumptions)); + while (!EMPTY_STACK (kitten->assumptions)) { + const unsigned assumption = POP_STACK (kitten->assumptions); + kitten->failed[assumption] = false; + } +#ifndef NDEBUG + for (size_t i = 0; i < kitten->size; i++) + assert (!kitten->failed[i]); +#endif + CLEAR_STACK (kitten->assumptions); + if (kitten->failing != INVALID) { + ROG (kitten->failing, "reset failed assumption reason"); + kitten->failing = INVALID; + } +} + +static void reset_incremental (kitten *kitten) { + if (kitten->level) + completely_backtrack_to_root_level (kitten); + if (!EMPTY_STACK (kitten->assumptions)) + reset_assumptions (kitten); + else + assert (kitten->failing == INVALID); + if (kitten->status == 21) + reset_core (kitten); + UPDATE_STATUS (0); +} + +/*------------------------------------------------------------------------*/ + +static bool flip_literal (kitten *kitten, unsigned lit) { + INC (kitten_flip); + signed char *values = kitten->values; + assert (values[lit]); + if (!kitten->vars[lit / 2].level) { + LOG ("can not flip root-level assigned literal %u", lit); + return false; + } + if (values[lit] < 0) + lit ^= 1; + LOG ("trying to flip value of satisfied literal %u", lit); + assert (values[lit] > 0); + katches *watches = kitten->watches + lit; + katch *q = BEGIN_STACK (*watches); + const katch *const end_watches = END_STACK (*watches); + katch const *p = q; + uint64_t ticks = (((char *) end_watches - (char *) q) >> 7) + 1; + bool res = true; + while (p != end_watches) { + const katch katch = *q++ = *p++; +#ifdef KITTEN_BLIT + const unsigned blit = katch.blit; + assert (blit != lit); + const value blit_value = values[blit]; + if (blit_value > 0) + continue; +#endif + const unsigned ref = katch.ref; + klause *c = dereference_klause (kitten, ref); + unsigned *lits = c->lits; + const unsigned other = lits[0] ^ lits[1] ^ lit; + const value other_value = values[other]; + ticks++; + if (other_value > 0) + continue; + value replacement_value = -1; + unsigned replacement = INVALID; + const unsigned *const end_lits = lits + c->size; + unsigned *r; + for (r = lits + 2; r != end_lits; r++) { + replacement = *r; + assert (replacement != lit); + replacement_value = values[replacement]; + assert (replacement_value); + if (replacement_value > 0) + break; + } + if (replacement_value > 0) { + assert (replacement != INVALID); + ROG (ref, "unwatching %u in", lit); + lits[0] = other; + lits[1] = replacement; + *r = lit; + watch_klause (kitten, replacement, c, ref); + q--; + } else { + assert (replacement_value < 0); + ROG (ref, "single satisfied"); + res = false; + break; + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_STACK (*watches, q); + ADD (kitten_ticks, ticks); + if (res) { + LOG ("flipping value of %u", lit); + values[lit] = -1; + const unsigned not_lit = lit ^ 1; + values[not_lit] = 1; + INC (kitten_flipped); + } else + LOG ("failed to flip value of %u", lit); + return res; +} + +/*------------------------------------------------------------------------*/ + +void kitten_assume (kitten *kitten, unsigned elit) { + REQUIRE_INITIALIZED (); + if (kitten->status) + reset_incremental (kitten); + const unsigned ilit = import_literal (kitten, elit); + LOG ("registering assumption %u", ilit); + PUSH_STACK (kitten->assumptions, ilit); +} + +void kitten_clause_with_id_and_exception (kitten *kitten, unsigned id, + size_t size, + const unsigned *elits, + unsigned except) { + REQUIRE_INITIALIZED (); + if (kitten->status) + reset_incremental (kitten); + assert (EMPTY_STACK (kitten->klause)); + const unsigned *const end = elits + size; + for (const unsigned *p = elits; p != end; p++) { + const unsigned elit = *p; + if (elit == except) + continue; + const unsigned ilit = import_literal (kitten, elit); + assert (ilit < kitten->lits); + const unsigned iidx = ilit / 2; + if (kitten->marks[iidx]) + INVALID_API_USAGE ("variable '%u' of literal '%u' occurs twice", + elit / 2, elit); + kitten->marks[iidx] = true; + PUSH_STACK (kitten->klause, ilit); + } + for (unsigned *p = kitten->klause.begin; p != kitten->klause.end; p++) + kitten->marks[*p / 2] = false; + new_original_klause (kitten, id); + CLEAR_STACK (kitten->klause); +} + +void kitten_clause (kitten *kitten, size_t size, unsigned *elits) { + kitten_clause_with_id_and_exception (kitten, INVALID, size, elits, + INVALID); +} + +void kitten_unit (kitten *kitten, unsigned lit) { + kitten_clause (kitten, 1, &lit); +} + +void kitten_binary (kitten *kitten, unsigned a, unsigned b) { + unsigned clause[2] = {a, b}; + kitten_clause (kitten, 2, clause); +} + +#ifdef STAND_ALONE_KITTEN +static volatile bool time_limit_hit; +#endif + +int kitten_solve (kitten *kitten) { + REQUIRE_INITIALIZED (); + if (kitten->status) + reset_incremental (kitten); + else if (kitten->level) + completely_backtrack_to_root_level (kitten); + + LOG ("starting solving under %zu assumptions", + SIZE_STACK (kitten->assumptions)); + + INC (kitten_solved); + + int res = propagate_units (kitten); + while (!res) { + const unsigned conflict = propagate (kitten); + if (conflict != INVALID) { + if (kitten->level) + analyze (kitten, conflict); + else { + inconsistent (kitten, conflict); + res = 20; + } + } else +#ifdef STAND_ALONE_KITTEN + if (time_limit_hit) { + time_limit_hit = false; + break; + } else +#endif + res = decide (kitten); + } + + if (res < 0) + res = 0; + + if (!res && !EMPTY_STACK (kitten->assumptions)) + reset_assumptions (kitten); + + UPDATE_STATUS (res); + + if (res == 10) + INC (kitten_sat); + else if (res == 20) + INC (kitten_unsat); + else + INC (kitten_unknown); + + LOG ("finished solving with result %d", res); + + return res; +} + +int kitten_status (kitten *kitten) { return kitten->status; } + +unsigned kitten_compute_clausal_core (kitten *kitten, + uint64_t *learned_ptr) { + REQUIRE_STATUS (20); + + if (!kitten->antecedents) + INVALID_API_USAGE ("antecedents not tracked"); + + LOG ("computing clausal core"); + + unsigneds *resolved = &kitten->resolved; + assert (EMPTY_STACK (*resolved)); + + unsigned original = 0; + uint64_t learned = 0; + + unsigned reason_ref = kitten->inconsistent; + + if (reason_ref == INVALID) { + assert (!EMPTY_STACK (kitten->assumptions)); + reason_ref = kitten->failing; + if (reason_ref == INVALID) { + LOG ("assumptions mutually inconsistent"); + goto DONE; + } + } + + PUSH_STACK (*resolved, reason_ref); + unsigneds *core = &kitten->core; + assert (EMPTY_STACK (*core)); + + while (!EMPTY_STACK (*resolved)) { + const unsigned c_ref = POP_STACK (*resolved); + if (c_ref == INVALID) { + const unsigned d_ref = POP_STACK (*resolved); + ROG (d_ref, "core[%zu]", SIZE_STACK (*core)); + PUSH_STACK (*core, d_ref); + klause *d = dereference_klause (kitten, d_ref); + assert (!is_core_klause (d)); + set_core_klause (d); + if (is_learned_klause (d)) + learned++; + else + original++; + } else { + klause *c = dereference_klause (kitten, c_ref); + if (is_core_klause (c)) + continue; + PUSH_STACK (*resolved, c_ref); + PUSH_STACK (*resolved, INVALID); + ROG (c_ref, "analyzing antecedent core"); + if (!is_learned_klause (c)) + continue; + for (all_antecedents (d_ref, c)) { + klause *d = dereference_klause (kitten, d_ref); + if (!is_core_klause (d)) + PUSH_STACK (*resolved, d_ref); + } + } + } + +DONE: + + if (learned_ptr) + *learned_ptr = learned; + + LOG ("clausal core of %u original clauses", original); + LOG ("clausal core of %" PRIu64 " learned clauses", learned); +#ifdef STAND_ALONE_KITTEN + kitten->statistics.original = original; + kitten->statistics.learned = 0; +#endif + UPDATE_STATUS (21); + + return original; +} + +void kitten_traverse_core_ids (kitten *kitten, void *state, + void (*traverse) (void *, unsigned)) { + REQUIRE_STATUS (21); + + LOG ("traversing core of original clauses"); + + unsigned traversed = 0; + + for (all_original_klauses (c)) { + assert (!is_learned_klause (c)); + if (is_learned_klause (c)) + continue; + if (!is_core_klause (c)) + continue; + ROG (reference_klause (kitten, c), "traversing"); + traverse (state, c->aux); + traversed++; + } + + LOG ("traversed %u original core clauses", traversed); + (void) traversed; + + assert (kitten->status == 21); +} + +void kitten_traverse_core_clauses (kitten *kitten, void *state, + void (*traverse) (void *, bool, size_t, + const unsigned *)) { + REQUIRE_STATUS (21); + + LOG ("traversing clausal core"); + + unsigned traversed = 0; + + for (all_stack (unsigned, c_ref, kitten->core)) { + klause *c = dereference_klause (kitten, c_ref); + assert (is_core_klause (c)); + const bool learned = is_learned_klause (c); + unsigneds *eclause = &kitten->eclause; + assert (EMPTY_STACK (*eclause)); + for (all_literals_in_klause (ilit, c)) { + const unsigned elit = export_literal (kitten, ilit); + PUSH_STACK (*eclause, elit); + } + const size_t size = SIZE_STACK (*eclause); + const unsigned *elits = eclause->begin; + ROG (reference_klause (kitten, c), "traversing"); + traverse (state, learned, size, elits); + CLEAR_STACK (*eclause); + traversed++; + } + + LOG ("traversed %u core clauses", traversed); + (void) traversed; + + assert (kitten->status == 21); +} + +void kitten_shrink_to_clausal_core (kitten *kitten) { + REQUIRE_STATUS (21); + + LOG ("shrinking formula to core of original clauses"); + + CLEAR_STACK (kitten->trail); + + kitten->unassigned = kitten->lits / 2; + kitten->propagated = 0; + kitten->level = 0; + + update_search (kitten, kitten->queue.last); + + memset (kitten->values, 0, kitten->lits); + + for (all_kits (lit)) + CLEAR_STACK (KATCHES (lit)); + + assert (kitten->inconsistent != INVALID); + klause *inconsistent = dereference_klause (kitten, kitten->inconsistent); + if (is_learned_klause (inconsistent) || inconsistent->size) { + ROG (kitten->inconsistent, "resetting inconsistent"); + kitten->inconsistent = INVALID; + } else + ROG (kitten->inconsistent, "keeping inconsistent"); + + CLEAR_STACK (kitten->units); + + klause *begin = begin_klauses (kitten), *q = begin; + klause const *const end = end_original_klauses (kitten); +#ifdef LOGGING + unsigned original = 0; +#endif + for (klause *c = begin, *next; c != end; c = next) { + next = next_klause (kitten, c); + assert (!is_learned_klause (c)); + if (is_learned_klause (c)) + continue; + if (!is_core_klause (c)) + continue; + unset_core_klause (c); + const unsigned dst = (unsigned *) q - (unsigned *) begin; + const unsigned size = c->size; + if (!size) { + if (!kitten->inconsistent) + kitten->inconsistent = dst; + } else if (size == 1) + PUSH_STACK (kitten->units, dst); + else { + watch_klause (kitten, c->lits[0], c, dst); + watch_klause (kitten, c->lits[1], c, dst); + } + if (c == q) + q = next; + else { + const size_t bytes = (char *) next - (char *) c; + memmove (q, c, bytes); + q = (klause *) ((char *) q + bytes); + } +#ifdef LOGGING + original++; +#endif + } + SET_END_OF_STACK (kitten->klauses, (unsigned *) q); + kitten->end_original_ref = SIZE_STACK (kitten->klauses); + LOG ("end of original clauses at %zu", kitten->end_original_ref); + LOG ("%u original clauses left", original); + + CLEAR_STACK (kitten->core); + + UPDATE_STATUS (0); +} + +signed char kitten_value (kitten *kitten, unsigned elit) { + REQUIRE_STATUS (10); + const unsigned eidx = elit / 2; + if (eidx >= kitten->evars) + return 0; + unsigned iidx = kitten->import[eidx]; + if (!iidx) + return 0; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + return kitten->values[ilit]; +} + +signed char kitten_fixed (kitten *kitten, unsigned elit) { + const unsigned eidx = elit / 2; + if (eidx >= kitten->evars) + return 0; + unsigned iidx = kitten->import[eidx]; + if (!iidx) + return 0; + iidx--; + const unsigned ilit = 2 * iidx + (elit & 1); + signed char res = kitten->values[ilit]; + if (!res) + return 0; + kar *v = kitten->vars + iidx; + if (v->level) + return 0; + return res; +} + +bool kitten_flip_literal (kitten *kitten, unsigned elit) { + REQUIRE_STATUS (10); + const unsigned eidx = elit / 2; + if (eidx >= kitten->evars) + return false; + unsigned iidx = kitten->import[eidx]; + if (!iidx) + return false; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + return flip_literal (kitten, ilit); +} + +bool kitten_failed (kitten *kitten, unsigned elit) { + REQUIRE_STATUS (20); + const unsigned eidx = elit / 2; + if (eidx >= kitten->evars) + return false; + unsigned iidx = kitten->import[eidx]; + if (!iidx) + return false; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + return kitten->failed[ilit]; +} + +/*------------------------------------------------------------------------*/ + +#ifdef STAND_ALONE_KITTEN + +#include +#include +#include +#include + +static double process_time (void) { + struct rusage u; + double res; + if (getrusage (RUSAGE_SELF, &u)) + return 0; + res = u.ru_utime.tv_sec + 1e-6 * u.ru_utime.tv_usec; + res += u.ru_stime.tv_sec + 1e-6 * u.ru_stime.tv_usec; + return res; +} + +static double maximum_resident_set_size (void) { + struct rusage u; + if (getrusage (RUSAGE_SELF, &u)) + return 0; + const uint64_t bytes = ((uint64_t) u.ru_maxrss) << 10; + return bytes / (double) (1 << 20); +} + +#include "attribute.h" + +static void msg (const char *, ...) __attribute__ ((format (printf, 1, 2))); + +static void msg (const char *fmt, ...) { + fputs ("c ", stdout); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); + fflush (stdout); +} + +#undef logging + +typedef struct parser parser; + +struct parser { + const char *path; + uint64_t lineno; + FILE *file; +#ifdef LOGGING + bool logging; +#endif +}; + +static void parse_error (parser *parser, const char *msg) { + fprintf (stderr, "kitten: error: %s:%" PRIu64 ": parse error: %s\n", + parser->path, parser->lineno, msg); + exit (1); +} + +#define ERROR(...) parse_error (parser, __VA_ARGS__) + +static inline int next_char (parser *parser) { + int res = getc (parser->file); + if (res == '\r') { + res = getc (parser->file); + if (res != '\n') + ERROR ("expected new line after carriage return"); + } + if (res == '\n') + parser->lineno++; + return res; +} + +#define NEXT() next_char (parser) + +#define FAILED INT_MIN + +static int parse_number (parser *parser, int *res_ptr) { + int ch = NEXT (); + if (!isdigit (ch)) + return FAILED; + int res = ch - '0'; + while (isdigit (ch = NEXT ())) { + if (!res) + return FAILED; + if (INT_MAX / 10 < res) + return FAILED; + res *= 10; + const int digit = ch - '0'; + if (INT_MAX - digit < res) + return FAILED; + res += digit; + } + *res_ptr = res; + return ch; +} + +static kitten *parse (parser *parser, ints *originals, int *max_var) { + signed char *marks = 0; + size_t size_marks = 0; + int last = '\n', ch; + for (;;) { + ch = NEXT (); + if (ch == 'c') { + while ((ch = NEXT ()) != '\n') + if (ch == EOF) + ERROR ("unexpected end-of-file in comment"); + } else if (ch != ' ' && ch != '\t' && ch != '\n') + break; + last = ch; + } + if (ch != 'p' || last != '\n') + ERROR ("expected 'c' or 'p' at start of line"); + bool valid = (NEXT () == ' ' && NEXT () == 'c' && NEXT () == 'n' && + NEXT () == 'f' && NEXT () == ' '); + int vars = 0; + int clauses = 0; + if (valid && (parse_number (parser, &vars) != ' ' || + parse_number (parser, &clauses) != '\n')) + valid = false; + if (!valid) + ERROR ("invalid header"); + msg ("found header 'p cnf %d %d'", vars, clauses); + *max_var = vars; + kitten *kitten = kitten_init (); +#ifdef LOGGING + kitten->logging = parser->logging; +#define logging (kitten->logging) +#endif + unsigneds clause; + INIT_STACK (clause); + int iidx = 0; + int parsed = 0; + bool tautological = false; + uint64_t offset = 0; + for (;;) { + ch = NEXT (); + if (ch == EOF) { + if (parsed < clauses) + ERROR ("clause missing"); + if (iidx) + ERROR ("zero missing"); + break; + } + if (ch == ' ' || ch == '\t' || ch == '\n') + continue; + if (ch == 'c') { + while ((ch = NEXT ()) != '\n') + if (ch == EOF) + ERROR ("unexpected end-of-file in comment"); + continue; + } + int sign = 1; + if (ch == '-') + sign = -1; + else + ungetc (ch, parser->file); + ch = parse_number (parser, &iidx); + if (ch == FAILED) + ERROR ("invalid literal"); + if (ch == EOF) + ERROR ("unexpected end-of-file after literal"); + if (ch == 'c') { + while ((ch = NEXT ()) != '\n') + if (ch == EOF) + ERROR ("unexpected end-of-file in comment"); + } else if (ch != ' ' && ch != '\t' && ch != '\n') + ERROR ("expected comment or white space after literal"); + if (iidx > vars) + ERROR ("maximum variable exceeded"); + if (parsed == clauses) + ERROR ("too many clauses"); + const int ilit = sign * iidx; + if (originals) + PUSH_STACK (*originals, ilit); + if (!iidx) { + for (all_stack (unsigned, ulit, clause)) + marks[ulit / 2] = 0; + if (tautological) { + LOG ("skipping tautological clause"); + tautological = false; + } else if (offset > UINT_MAX) + ERROR ("too many original literals"); + else { + assert (SIZE_STACK (clause) <= UINT_MAX); + const unsigned size = SIZE_STACK (clause); + const unsigned *const lits = BEGIN_STACK (clause); + kitten_clause_with_id_and_exception (kitten, offset, size, lits, + INVALID); + } + CLEAR_STACK (clause); + parsed++; + + if (originals) + offset = SIZE_STACK (*originals); + else + offset = parsed; + } else if (!tautological) { + const unsigned uidx = iidx - 1; + const unsigned ulit = 2 * uidx + (sign < 0); + if (uidx >= size_marks) { + size_t new_size_marks = size_marks ? 2 * size_marks : 1; + while (uidx >= new_size_marks) + new_size_marks *= 2; + signed char *new_marks; + CALLOC (new_marks, new_size_marks); + if (size_marks) + memcpy (new_marks, marks, size_marks); + DEALLOC (marks, size_marks); + size_marks = new_size_marks; + marks = new_marks; + } + value mark = marks[uidx]; + if (sign < 0) + mark = -mark; + if (mark > 0) + LOG ("dropping repeated %d in parsed clause", ilit); + else if (mark < 0) { + LOG ("parsed clause contains both %d and %d", -ilit, ilit); + tautological = true; + } else { + LOG ("adding parsed integer literal %d as external literal %u", + ilit, ulit); + marks[uidx] = sign; + PUSH_STACK (clause, ulit); + } + } + } + RELEASE_STACK (clause); + free (marks); + return kitten; +} + +typedef struct line line; + +struct line { + char buffer[80]; + size_t size; +}; + +static void flush_line (line *line) { + if (!line->size) + return; + for (size_t i = 0; i < line->size; i++) + fputc (line->buffer[i], stdout); + fputc ('\n', stdout); + line->size = 0; +} + +static inline void print_lit (line *line, int lit) { + char tmp[16]; + sprintf (tmp, " %d", lit); + const size_t len = strlen (tmp); + if (line->size + len > 78) + flush_line (line); + if (!line->size) { + line->buffer[0] = 'v'; + line->size = 1; + } + strcpy (line->buffer + line->size, tmp); + line->size += len; +} + +static void print_witness (kitten *kitten, int max_var) { + assert (max_var >= 0); + line line = {.size = 0}; + const size_t parsed_lits = 2 * (size_t) max_var; + for (size_t ulit = 0; ulit < parsed_lits; ulit += 2) { + const value sign = kitten_value (kitten, ulit); + const int iidx = ulit / 2 + 1; + const int ilit = (sign > 0 ? iidx : -iidx); + print_lit (&line, ilit); + } + print_lit (&line, 0); + flush_line (&line); +} + +static double percent (double a, double b) { return b ? 100 * a / b : 0; } + +typedef struct core_writer core_writer; + +struct core_writer { + FILE *file; + ints *originals; +#ifndef NDEBUG + unsigned written; +#endif +}; + +static void write_offset (void *ptr, unsigned offset) { + core_writer *writer = ptr; +#ifndef NDEBUG + writer->written++; +#endif + int const *p = &PEEK_STACK (*writer->originals, offset); + FILE *file = writer->file; + while (*p) + fprintf (file, "%d ", *p++); + fputs ("0\n", file); +} + +static void write_core (kitten *kitten, unsigned reduced, ints *originals, + FILE *file) { + assert (originals); + fprintf (file, "p cnf %zu %u\n", kitten->evars, reduced); + core_writer writer; + writer.file = file; + writer.originals = originals; +#ifndef NDEBUG + writer.written = 0; +#endif + kitten_traverse_core_ids (kitten, &writer, write_offset); + assert (writer.written == reduced); +} + +#ifndef NDEBUG + +typedef struct lemma_writer lemma_writer; + +struct lemma_writer { + FILE *file; + uint64_t written; +}; + +#endif + +static void write_lemma (void *ptr, bool learned, size_t size, + const unsigned *lits) { + if (!learned) + return; + const unsigned *const end = lits + size; +#ifdef NDEBUG + FILE *file = ptr; +#else + lemma_writer *writer = ptr; + FILE *file = writer->file; + writer->written++; +#endif + for (const unsigned *p = lits; p != end; p++) { + const unsigned ulit = *p; + const unsigned idx = ulit / 2; + const unsigned sign = ulit & 1; + assert (idx + 1 <= (unsigned) INT_MAX); + int ilit = idx + 1; + if (sign) + ilit = -ilit; + fprintf (file, "%d ", ilit); + } + fputs ("0\n", file); +} + +static void write_lemmas (kitten *kitten, uint64_t reduced, FILE *file) { + void *state; +#ifdef NDEBUG + state = file; + (void) reduced; +#else + lemma_writer writer; + writer.file = file; + writer.written = 0; + state = &writer; +#endif + kitten_traverse_core_clauses (kitten, state, write_lemma); + assert (writer.written == reduced); +} + +static void print_statistics (statistics statistics) { + msg ("conflicts: %20" PRIu64, + statistics.kitten_conflicts); + msg ("decisions: %20" PRIu64, + statistics.kitten_decisions); + msg ("propagations: %20" PRIu64, + statistics.kitten_propagations); + msg ("maximum-resident-set-size: %23.2f MB", + maximum_resident_set_size ()); + msg ("process-time: %23.2f seconds", process_time ()); +} + +static volatile int time_limit; +static volatile kitten *static_kitten; + +#define SIGNALS \ + SIGNAL (SIGABRT) \ + SIGNAL (SIGBUS) \ + SIGNAL (SIGINT) \ + SIGNAL (SIGSEGV) \ + SIGNAL (SIGTERM) + +// clang-format off + +#define SIGNAL(SIG) \ +static void (*SIG ## _handler)(int); +SIGNALS +#undef SIGNAL + +static void (*SIGALRM_handler)(int); + +// clang-format on + +static void reset_alarm (void) { + if (time_limit > 0) { + time_limit = 0; + time_limit_hit = false; + } +} + +static void reset_signals (void) { +#define SIGNAL(SIG) signal (SIG, SIG##_handler); + SIGNALS +#undef SIGNAL + static_kitten = 0; +} + +static const char *signal_name (int sig) { +#define SIGNAL(SIG) \ + if (sig == SIG) \ + return #SIG; + SIGNALS +#undef SIGNAL + return "SIGUNKNOWN"; +} + +static void catch_signal (int sig) { + if (!static_kitten) + return; + statistics statistics = static_kitten->statistics; + reset_signals (); + msg ("caught signal %d (%s)", sig, signal_name (sig)); + print_statistics (statistics); + raise (sig); +} + +static void catch_alarm (int sig) { + assert (sig == SIGALRM); + if (time_limit > 0) + time_limit_hit = true; + (void) sig; +} + +static void init_signals (kitten *kitten) { + static_kitten = kitten; +#define SIGNAL(SIG) SIG##_handler = signal (SIG, catch_signal); + SIGNALS +#undef SIGNAL + if (time_limit > 0) { + SIGALRM_handler = signal (SIGALRM, catch_alarm); + alarm (time_limit); + } +} + +static bool parse_arg (const char *arg, unsigned *res_ptr) { + unsigned res = 0; + int ch; + while ((ch = *arg++)) { + if (!isdigit (ch)) + return false; + if (UINT_MAX / 10 < res) + return false; + res *= 10; + const unsigned digit = ch - '0'; + if (UINT_MAX - digit < res) + return false; + res += digit; + } + *res_ptr = res; + return true; +} + +static bool parse_lit (const char *arg, int *res_ptr) { + int res = 0, sign, ch; + if (*arg == '-') { + sign = -1; + if (*++arg == '0') + return false; + } else + sign = 1; + while ((ch = *arg++)) { + if (!isdigit (ch)) + return false; + if (INT_MAX / 10 < res) + return false; + res *= 10; + const int digit = ch - '0'; + if (INT_MAX - digit < res) + return false; + res += digit; + } + res *= sign; + *res_ptr = res; + return true; +} + +#undef logging + +int main (int argc, char **argv) { + ints assumptions; + INIT_STACK (assumptions); + const char *dimacs_path = 0; + const char *output_path = 0; + unsigned shrink = 0; + bool witness = true; + bool proof = false; +#ifdef LOGGING + bool logging = false; +#endif + for (int i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp (arg, "-h")) + fputs (usage, stdout), exit (0); +#ifdef LOGGING + else if (!strcmp (arg, "-l")) + logging = true; +#endif + else if (!strcmp (arg, "-n")) + witness = false; + else if (!strcmp (arg, "-p")) + proof = true; + else if (!strcmp (arg, "-a")) { + if (++i == argc) + die ("argument to '-a' missing"); + int lit; + if (!parse_lit (argv[i], &lit) || !lit) + die ("invalid '-a %s'", argv[i]); + PUSH_STACK (assumptions, lit); + } else if (!strcmp (arg, "-t")) { + if (++i == argc) + die ("argument to '-t' missing"); + if ((time_limit = atoi (argv[i])) <= 0) + die ("invalid argument in '-t %s'", argv[i]); + } else if (arg[0] == '-' && arg[1] == 'O' && !arg[2]) + shrink = 1; + else if (arg[0] == '-' && arg[1] == 'O' && parse_arg (arg + 2, &shrink)) + ; + else if (*arg == '-') + die ("invalid option '%s' (try '-h')", arg); + else if (output_path) + die ("too many arguments (try '-h')"); + else if (dimacs_path) + output_path = arg; + else + dimacs_path = arg; + } + if (proof && !output_path) + die ("option '-p' without output file"); + if (shrink && !EMPTY_STACK (assumptions)) + die ("can not using shrinking with assumptions"); + FILE *dimacs_file; + bool close_dimacs_file = true; + if (!dimacs_path) + close_dimacs_file = false, dimacs_file = stdin, dimacs_path = ""; + else if (!(dimacs_file = fopen (dimacs_path, "r"))) + die ("can not open '%s' for reading", dimacs_path); + msg ("Kitten SAT Solver"); + msg ("Copyright (c) 2021-2024 Armin Biere University of Freiburg"); + msg ("Copyright (c) 2020-2021 Armin Biere Johannes Kepler University " + "Linz"); + msg ("reading '%s'", dimacs_path); + ints originals; + INIT_STACK (originals); + kitten *kitten; + int max_var; + { + parser parser; + parser.path = dimacs_path; + parser.lineno = 1; + parser.file = dimacs_file; +#ifdef LOGGING + parser.logging = logging; +#endif + kitten = parse (&parser, ((output_path && !proof) ? &originals : 0), + &max_var); + } + if (close_dimacs_file) + fclose (dimacs_file); + msg ("parsed DIMACS file in %.2f seconds", process_time ()); + init_signals (kitten); + if (output_path) { + msg ("tracking antecedents"); + kitten_track_antecedents (kitten); + } + if (!EMPTY_STACK (assumptions)) { + msg ("assuming %zu assumptions", SIZE_STACK (assumptions)); + for (all_stack (int, ilit, assumptions)) { + unsigned ulit = 2u * (abs (ilit) - 1) + (ilit < 0); + kitten_assume (kitten, ulit); + } + } + int res = kitten_solve (kitten); + if (res == 10) { + printf ("s SATISFIABLE\n"); + fflush (stdout); + if (witness) + print_witness (kitten, max_var); + } else if (res == 20) { + printf ("s UNSATISFIABLE\n"); + fflush (stdout); + if (output_path) { + const unsigned original = kitten->statistics.original; + const uint64_t learned = kitten->statistics.learned; + unsigned reduced_original, round = 0; + uint64_t reduced_learned; + + for (;;) { + msg ("computing clausal core of %" PRIu64 " clauses", + kitten->statistics.original + kitten->statistics.learned); + + reduced_original = + kitten_compute_clausal_core (kitten, &reduced_learned); + + msg ("found %" PRIu64 " core lemmas %.0f%% " + "out of %" PRIu64 " learned clauses", + reduced_learned, percent (reduced_learned, learned), learned); + + if (!shrink--) + break; + + msg ("shrinking round %u", ++round); + + msg ("reduced to %u core clauses %.0f%% " + "out of %u original clauses", + reduced_original, percent (reduced_original, original), + original); + + kitten_shrink_to_clausal_core (kitten); + kitten_shuffle_clauses (kitten); + + reset_alarm (); + res = kitten_solve (kitten); + assert (res == 20); + } + FILE *output_file = fopen (output_path, "w"); + if (!output_file) + die ("can not open '%s' for writing", output_path); + if (proof) { + msg ("writing proof to '%s'", output_path); + write_lemmas (kitten, reduced_learned, output_file); + msg ("written %" PRIu64 " core lemmas %.0f%% of %" PRIu64 + " learned clauses", + reduced_learned, percent (reduced_learned, learned), learned); + } else { + msg ("writing original clausal core to '%s'", output_path); + write_core (kitten, reduced_original, &originals, output_file); + msg ("written %u core clauses %.0f%% of %u original clauses", + reduced_original, percent (reduced_original, original), + original); + } + fclose (output_file); + } + } else { + fputs ("s UNKNOWN\n", stdout); + fflush (stdout); + } + RELEASE_STACK (originals); + RELEASE_STACK (assumptions); + statistics statistics = kitten->statistics; + reset_signals (); + kitten_release (kitten); + print_statistics (statistics); + msg ("exit %d", res); + return res; +} + +#endif diff --git a/src/sat/kissat/kitten.h b/src/sat/kissat/kitten.h new file mode 100644 index 000000000..e7ead94dc --- /dev/null +++ b/src/sat/kissat/kitten.h @@ -0,0 +1,54 @@ +#ifndef _kitten_h_INCLUDED +#define _kitten_h_INCLUDED + +#include +#include +#include + +typedef struct kitten kitten; + +kitten *kitten_init (void); +void kitten_clear (kitten *); +void kitten_release (kitten *); + +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_clause (kitten *, size_t size, unsigned *); +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 kitten_no_ticks_limit (kitten *); +void kitten_set_ticks_limit (kitten *, uint64_t); + +int kitten_solve (kitten *); +int kitten_status (kitten *); + +signed char kitten_value (kitten *, unsigned); +signed char kitten_fixed (kitten *, unsigned); +bool kitten_failed (kitten *, unsigned); +bool kitten_flip_literal (kitten *, unsigned); + +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 *)); +struct kissat; +kitten *kitten_embedded (struct kissat *); + +#endif diff --git a/src/sat/kissat/krite.c b/src/sat/kissat/krite.c new file mode 100644 index 000000000..85d91d9d9 --- /dev/null +++ b/src/sat/kissat/krite.c @@ -0,0 +1,45 @@ +#include "krite.h" +#include "inline.h" +#include "internal.h" +#include "watch.h" + +#include + +void kissat_write_dimacs (kissat *solver, FILE *file) { + size_t imported = SIZE_STACK (solver->import); + if (imported) + imported--; + fprintf (file, "p cnf %zu %" PRIu64 "\n", imported, BINIRR_CLAUSES); + assert (solver->watching); + if (solver->watching) { + for (all_literals (ilit)) + for (all_binary_blocking_watches (watch, WATCHES (ilit))) + if (watch.type.binary) { + const unsigned iother = watch.binary.lit; + if (iother < ilit) + continue; + const int elit = kissat_export_literal (solver, ilit); + const int eother = kissat_export_literal (solver, iother); + fprintf (file, "%d %d 0\n", elit, eother); + } + } else { + for (all_literals (ilit)) + for (all_binary_large_watches (watch, WATCHES (ilit))) + if (watch.type.binary) { + const unsigned iother = watch.binary.lit; + if (iother < ilit) + continue; + const int elit = kissat_export_literal (solver, ilit); + const int eother = kissat_export_literal (solver, iother); + fprintf (file, "%d %d 0\n", elit, eother); + } + } + for (all_clauses (c)) + if (!c->garbage && !c->redundant) { + for (all_literals_in_clause (ilit, c)) { + const int elit = kissat_export_literal (solver, ilit); + fprintf (file, "%d ", elit); + } + fputs ("0\n", file); + } +} diff --git a/src/sat/kissat/krite.h b/src/sat/kissat/krite.h new file mode 100644 index 000000000..a8af42c9c --- /dev/null +++ b/src/sat/kissat/krite.h @@ -0,0 +1,9 @@ +#ifndef _krite_h_INCLUDED +#define _krite_h_INCLUDED + +#include + +struct kissat; +void kissat_write_dimacs (struct kissat *, FILE *); + +#endif diff --git a/src/sat/kissat/learn.c b/src/sat/kissat/learn.c new file mode 100644 index 000000000..36a25be89 --- /dev/null +++ b/src/sat/kissat/learn.c @@ -0,0 +1,210 @@ +#include "learn.h" +#include "backtrack.h" +#include "inline.h" +#include "reluctant.h" + +#include + +static unsigned backjump_limit (struct kissat *solver) { +#ifdef NOPTIONS + (void) solver; +#endif + return GET_OPTION (chrono) ? (unsigned) GET_OPTION (chronolevels) + : UINT_MAX; +} + +unsigned kissat_determine_new_level (kissat *solver, unsigned jump) { + assert (solver->level); + const unsigned back = solver->level - 1; + assert (jump <= back); + + const unsigned delta = back - jump; + const unsigned limit = backjump_limit (solver); + + unsigned res; + + if (!delta) { + res = jump; + LOG ("using identical backtrack and jump level %u", res); + } else if (delta > limit) { + res = back; + LOG ("backjumping over %u levels (%u - %u) considered inefficient", + delta, back, jump); + LOG ("backtracking chronologically to backtrack level %u", res); + INC (chronological); + } else { + res = jump; + LOG ("backjumping over %u levels (%u - %u) considered efficient", delta, + back, jump); + LOG ("backjumping non-chronologically to jump level %u", res); + } + return res; +} + +static void learn_unit (kissat *solver, unsigned not_uip) { + assert (not_uip == PEEK_STACK (solver->clause, 0)); + LOG ("learned unit clause %s triggers iteration", LOGLIT (not_uip)); + const unsigned new_level = kissat_determine_new_level (solver, 0); + kissat_backtrack_after_conflict (solver, new_level); + kissat_learned_unit (solver, not_uip); + if (!solver->probing) { + solver->iterating = true; + INC (iterations); + } +} + +static void learn_binary (kissat *solver, unsigned not_uip) { + const unsigned other = PEEK_STACK (solver->clause, 1); + const unsigned jump_level = LEVEL (other); + const unsigned new_level = + kissat_determine_new_level (solver, jump_level); + kissat_backtrack_after_conflict (solver, new_level); +#ifndef NDEBUG + const reference ref = +#endif + kissat_new_redundant_clause (solver, 1); + assert (ref == INVALID_REF); + kissat_assign_binary (solver, not_uip, other); +} + +static void insert_last_learned (kissat *solver, reference ref) { + const reference *const end = + solver->last_learned + GET_OPTION (eagersubsume); + reference prev = ref; + for (reference *p = solver->last_learned; p != end; p++) { + reference tmp = *p; + *p = prev; + prev = tmp; + } +} + +static reference learn_reference (kissat *solver, unsigned not_uip, + unsigned glue) { + assert (solver->level > 1); + assert (SIZE_STACK (solver->clause) > 2); + unsigned *lits = BEGIN_STACK (solver->clause); + assert (lits[0] == not_uip); + unsigned *q = lits + 1; + unsigned jump_lit = *q; + unsigned jump_level = LEVEL (jump_lit); + const unsigned *const end = END_STACK (solver->clause); + const unsigned backtrack_level = solver->level - 1; + assigned *all_assigned = solver->assigned; + for (unsigned *p = lits + 2; p != end; p++) { + const unsigned lit = *p; + const unsigned idx = IDX (lit); + const unsigned level = all_assigned[idx].level; + if (jump_level >= level) + continue; + jump_level = level; + jump_lit = lit; + q = p; + if (level == backtrack_level) + break; + } + *q = lits[1]; + lits[1] = jump_lit; + const reference ref = kissat_new_redundant_clause (solver, glue); + assert (ref != INVALID_REF); + clause *c = kissat_dereference_clause (solver, ref); + c->used = MAX_USED; + const unsigned new_level = + kissat_determine_new_level (solver, jump_level); + kissat_backtrack_after_conflict (solver, new_level); + kissat_assign_reference (solver, not_uip, ref, c); + return ref; +} + +void kissat_update_learned (kissat *solver, unsigned glue, unsigned size) { + assert (!solver->probing); + INC (clauses_learned); + LOG ("learned[%" PRIu64 "] clause glue %u size %u", GET (clauses_learned), + glue, size); + if (solver->stable) + kissat_tick_reluctant (&solver->reluctant); + ADD (literals_learned, size); +#ifndef QUIET + UPDATE_AVERAGE (size, size); +#endif + UPDATE_AVERAGE (fast_glue, glue); + UPDATE_AVERAGE (slow_glue, glue); +} + +static void flush_last_learned (kissat *solver) { + reference *q = solver->last_learned; + const reference *const end = q + GET_OPTION (eagersubsume), *p = q; + while (p != end) { + reference ref = *p++; + if (ref != INVALID_REF) + *q++ = ref; + } + while (q != end) + *q++ = INVALID_REF; +} + +static void eagerly_subsume_last_learned (kissat *solver) { + value *marks = solver->marks; + for (all_stack (unsigned, lit, solver->clause)) { + assert (!marks[lit]); + marks[lit] = 1; + } + unsigned clause_size = SIZE_STACK (solver->clause); + unsigned subsumed = 0; + reference *p = solver->last_learned; + const reference *const end = p + GET_OPTION (eagersubsume); + while (p != end) { + reference ref = *p++; + if (ref == INVALID_REF) + continue; + clause *c = kissat_dereference_clause (solver, ref); + if (c->garbage) + continue; + if (!c->redundant) + continue; + unsigned c_size = c->size; + if (c_size <= clause_size) + continue; + LOGCLS2 (c, "trying to eagerly subsume"); + unsigned needed = clause_size; + unsigned remain = c_size; + for (all_literals_in_clause (lit, c)) { + if (marks[lit] && !--needed) + break; + else if (--remain < needed) + break; + } + if (needed) + continue; + LOGCLS (c, "eagerly subsumed"); + kissat_mark_clause_as_garbage (solver, c); + p[-1] = INVALID_REF; + subsumed++; + INC (eagerly_subsumed); + } + for (all_stack (unsigned, lit, solver->clause)) + marks[lit] = 0; + if (subsumed) + flush_last_learned (solver); +} + +void kissat_learn_clause (kissat *solver) { + const unsigned not_uip = PEEK_STACK (solver->clause, 0); + const unsigned size = SIZE_STACK (solver->clause); + const size_t glue = SIZE_STACK (solver->levels); + assert (glue <= UINT_MAX); + if (!solver->probing) + kissat_update_learned (solver, glue, size); + assert (size > 0); + reference ref = INVALID_REF; + if (size == 1) + learn_unit (solver, not_uip); + else if (size == 2) + learn_binary (solver, not_uip); + else + ref = learn_reference (solver, not_uip, glue); + if (GET_OPTION (eagersubsume)) { + eagerly_subsume_last_learned (solver); + if (ref != INVALID_REF) + insert_last_learned (solver, ref); + } +} diff --git a/src/sat/kissat/learn.h b/src/sat/kissat/learn.h new file mode 100644 index 000000000..b11923966 --- /dev/null +++ b/src/sat/kissat/learn.h @@ -0,0 +1,10 @@ +#ifndef _learn_h_INCLUDED +#define _learn_h_INCLUDED + +struct kissat; + +void kissat_learn_clause (struct kissat *); +void kissat_update_learned (struct kissat *, unsigned glue, unsigned size); +unsigned kissat_determine_new_level (struct kissat *, unsigned jump); + +#endif diff --git a/src/sat/kissat/literal.h b/src/sat/kissat/literal.h new file mode 100644 index 000000000..75d7814c6 --- /dev/null +++ b/src/sat/kissat/literal.h @@ -0,0 +1,36 @@ +#ifndef _literal_h_INCLUDED +#define _literal_h_INCLUDED + +#include + +#define LD_MAX_VAR 30u +#define LD_MAX_LIT (1 + LD_MAX_VAR) + +#define EXTERNAL_MAX_VAR ((1 << LD_MAX_VAR) - 1) +#define INTERNAL_MAX_VAR ((1u << LD_MAX_VAR) - 2) +#define INTERNAL_MAX_LIT (2 * INTERNAL_MAX_VAR + 1) + +#define ILLEGAL_LIT ((1u << LD_MAX_LIT) - 1) + +#define INVALID_IDX UINT_MAX +#define INVALID_LIT UINT_MAX + +#define VALID_INTERNAL_INDEX(IDX) ((IDX) < VARS) + +#define VALID_INTERNAL_LITERAL(LIT) ((LIT) < LITS) + +#define VALID_EXTERNAL_LITERAL(LIT) \ + ((LIT) && ((LIT) != INT_MIN) && ABS (LIT) <= EXTERNAL_MAX_VAR) + +#define IDX(LIT) \ + (assert (VALID_INTERNAL_LITERAL (LIT)), (((unsigned) (LIT)) >> 1)) + +#define LIT(IDX) (assert (VALID_INTERNAL_INDEX (IDX)), ((IDX) << 1)) + +#define NOT(LIT) (assert (VALID_INTERNAL_LITERAL (LIT)), ((LIT) ^ 1u)) + +#define NEGATED(LIT) (assert (VALID_INTERNAL_LITERAL (LIT)), ((LIT) & 1u)) + +#define STRIP(LIT) (assert (VALID_INTERNAL_LITERAL (LIT)), ((LIT) & ~1u)) + +#endif diff --git a/src/sat/kissat/logging.c b/src/sat/kissat/logging.c new file mode 100644 index 000000000..36abd33ce --- /dev/null +++ b/src/sat/kissat/logging.c @@ -0,0 +1,456 @@ +#if defined(LOGGING) && !defined(QUIET) + +#include "colors.h" +#include "inline.h" + +#include +#include + +static void begin_logging (kissat *solver, const char *prefix, + const char *fmt, va_list *ap) { + TERMINAL (stdout, 1); + assert (GET_OPTION (log)); + fputs (solver->prefix, stdout); + COLOR (MAGENTA); + printf ("%s %u ", prefix, solver->level); + vprintf (fmt, *ap); +} + +static void end_logging (void) { + TERMINAL (stdout, 1); + fputc ('\n', stdout); + COLOR (NORMAL); + fflush (stdout); +} + +void kissat_begin_logging (kissat *solver, const char *prefix, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); +} + +void kissat_end_logging (void) { end_logging (); } + +void kissat_log_msg (kissat *solver, const char *prefix, const char *fmt, + ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + end_logging (); +} + +static void append_sprintf (char *str, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + const size_t len = strlen (str); + vsprintf (str + len, fmt, ap); + va_end (ap); +} + +const char *kissat_log_repr (kissat *solver, unsigned lit, + const unsigned *repr) { + assert (solver); + char *res = kissat_next_format_string (&solver->format); + sprintf (res, "%u", lit); + if (!solver->compacting && GET_OPTION (log) > 1) + append_sprintf (res, "(%d)", kissat_export_literal (solver, lit)); + if (repr && repr[lit] != lit) { + strcat (res, "["); + unsigned repr_lit = repr[lit]; + append_sprintf (res, "%u", repr_lit); + if (!solver->compacting && GET_OPTION (log) > 1) + append_sprintf (res, "(%d)", + kissat_export_literal (solver, repr_lit)); + strcat (res, "]"); + } + if (!solver->compacting && GET_OPTION (log) > 1 && solver->values) { + const value value = VALUE (lit); + if (value) { + append_sprintf (res, "=%d", value); + if (solver->assigned) + append_sprintf (res, "@%u", LEVEL (lit)); + } + } + assert (strlen (res) < FORMAT_STRING_SIZE); + return res; +} + +const char *kissat_log_lit (kissat *solver, unsigned lit) { + return kissat_log_repr (solver, lit, 0); +} + +const char *kissat_log_var (kissat *solver, unsigned idx) { + assert (solver); + char *res = kissat_next_format_string (&solver->format); + const unsigned lit = LIT (idx); + sprintf (res, "variable %u (literal %s)", idx, LOGLIT (lit)); + assert (strlen (res) < FORMAT_STRING_SIZE); + return res; +} + +static void log_lits (kissat *solver, size_t size, const unsigned *lits, + const unsigned *counts) { + for (size_t i = 0; i < size; i++) { + const unsigned lit = lits[i]; + fputc (' ', stdout); + fputs (LOGLIT (lit), stdout); + if (counts) + printf ("#%u", counts[lit]); + } +} + +static void log_reprs (kissat *solver, size_t size, const unsigned *lits, + const unsigned *repr) { + for (size_t i = 0; i < size; i++) { + const unsigned lit = lits[i]; + fputc (' ', stdout); + fputs (LOGREPR (lit, repr), stdout); + } +} + +void kissat_log_lits (kissat *solver, const char *prefix, size_t size, + const unsigned *const lits, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %zu clause", size); + log_lits (solver, size, lits, 0); + end_logging (); +} + +void kissat_log_litset (kissat *solver, const char *prefix, size_t size, + const unsigned *const lits, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %zu literal set {", size); + log_lits (solver, size, lits, 0); + fputs (" }", stdout); + end_logging (); +} + +void kissat_log_litpart (kissat *solver, const char *prefix, size_t size, + const unsigned *const lits, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + size_t classes = 0; + for (size_t i = 0; i < size; i++) + if (lits[i] == INVALID_LIT) + classes++; + printf (" %zu literals %zu classes literal partition [", size - classes, + classes); + for (size_t i = 0; i < size; i++) { + const unsigned lit = lits[i]; + if (lit == INVALID_LIT) { + if (i + 1 != size) + fputs (" |", stdout); + } else { + fputc (' ', stdout); + fputs (LOGLIT (lit), stdout); + } + } + fputs (" ]", stdout); + end_logging (); +} + +void kissat_log_counted_ref_lits (kissat *solver, const char *prefix, + reference ref, size_t size, + const unsigned *const lits, + const unsigned *const counts, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %zu clause", size); + printf ("[%u]", ref); + log_lits (solver, size, lits, counts); + end_logging (); +} + +void kissat_log_counted_lits (kissat *solver, const char *prefix, + size_t size, const unsigned *const lits, + const unsigned *const counts, const char *fmt, + ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %zu literals", size); + log_lits (solver, size, lits, counts); + end_logging (); +} + +void kissat_log_resolvent (kissat *solver, const char *prefix, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + const size_t size = SIZE_STACK (solver->resolvent); + printf (" size %zu resolvent", size); + const unsigned *const lits = BEGIN_STACK (solver->resolvent); + log_lits (solver, size, lits, 0); + end_logging (); +} + +void kissat_log_ints (kissat *solver, const char *prefix, size_t size, + const int *const lits, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %zu external literals clause", size); + for (size_t i = 0; i < size; i++) + printf (" %d", lits[i]); + end_logging (); +} + +void kissat_log_unsigneds (kissat *solver, const char *prefix, size_t size, + const unsigned *const lits, const char *fmt, + ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %zu clause", size); + for (size_t i = 0; i < size; i++) + printf (" %u", lits[i]); + end_logging (); +} + +static void log_gate (kissat *solver, size_t id, const char *type, + const char *op, const char *empty_rhs_constant, + const unsigned *repr, unsigned lhs, size_t size, + const unsigned *rhs) { + printf (" arity %zu %s gate", size, type); + if (id != INVALID_GATE_ID) + printf ("[%zu]", id); + fputc (' ', stdout); + if (lhs == INVALID_LIT) + fputs ("", stdout); + else + fputs (LOGREPR (lhs, repr), stdout); + if (size) + for (size_t i = 0; i < size; i++) { + fputc (' ', stdout); + fputs (i ? op : ":=", stdout); + fputc (' ', stdout); + fputs (LOGREPR (rhs[i], repr), stdout); + } + else { + fputs (" := ", stdout); + fputs (empty_rhs_constant, stdout); + } +} + +void kissat_log_and_gate (kissat *solver, const char *prefix, size_t id, + unsigned *repr, unsigned lhs, size_t size, + const unsigned *rhs, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_gate (solver, id, "AND", "&", "", repr, lhs, size, rhs); + end_logging (); +} + +void kissat_log_xor_gate (kissat *solver, const char *prefix, size_t id, + unsigned *repr, unsigned lhs, size_t size, + const unsigned *rhs, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_gate (solver, id, "XOR", "^", "", repr, lhs, size, rhs); + end_logging (); +} + +void kissat_log_ite_gate (kissat *solver, const char *prefix, size_t id, + unsigned *repr, unsigned lhs, unsigned cond, + unsigned then_lit, unsigned else_lit, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" ITE gate"); + if (id != INVALID_GATE_ID) + printf ("[%zu]", id); + fputc (' ', stdout); + if (lhs == INVALID_LIT) + fputs ("", stdout); + else + fputs (LOGREPR (lhs, repr), stdout); + fputs (" := ", stdout); + fputs (LOGREPR (cond, repr), stdout); + fputs (" ? ", stdout); + fputs (LOGREPR (then_lit, repr), stdout); + fputs (" : ", stdout); + fputs (LOGREPR (else_lit, repr), stdout); + end_logging (); +} + +void kissat_log_extensions (kissat *solver, const char *prefix, size_t size, + const extension *const exts, const char *fmt, + ...) { + assert (size > 0); + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + const extension *const begin = BEGIN_STACK (solver->extend); + const size_t pos = exts - begin; + printf (" extend[%zu]", pos); + printf (" %d", exts[0].lit); + if (size > 1) + fputs (" :", stdout); + for (size_t i = 1; i < size; i++) + printf (" %d", exts[i].lit); + end_logging (); +} + +static void log_clause (kissat *solver, const clause *c) { + fputc (' ', stdout); + if (c == &solver->conflict) { + fputs ("static ", stdout); + fputs (c->redundant ? "redundant" : "irredundant", stdout); + fputs (" binary conflict clause", stdout); + } else { + if (c->redundant) + printf ("redundant glue %u", c->glue); + else + fputs ("irredundant", stdout); + printf (" size %u", c->size); + if (c->reason) + fputs (" reason", stdout); + if (c->garbage) + fputs (" garbage", stdout); + fputs (" clause", stdout); + if (kissat_clause_in_arena (solver, c)) { + reference ref = kissat_reference_clause (solver, c); + printf ("[%u]", ref); + } + } +} + +void kissat_log_clause (kissat *solver, const char *prefix, const clause *c, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_clause (solver, c); + log_lits (solver, c->size, c->lits, 0); + end_logging (); +} + +void kissat_log_counted_clause (kissat *solver, const char *prefix, + const clause *c, const unsigned *counts, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_clause (solver, c); + log_lits (solver, c->size, c->lits, counts); + end_logging (); +} + +void kissat_log_repr_clause (kissat *solver, const char *prefix, + const clause *c, const unsigned *repr, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_clause (solver, c); + log_reprs (solver, c->size, c->lits, repr); + end_logging (); +} + +static void log_binary (kissat *solver, unsigned a, unsigned b) { + printf (" binary clause %s %s", LOGLIT (a), LOGLIT (b)); +} + +void kissat_log_binary (kissat *solver, const char *prefix, unsigned a, + unsigned b, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_binary (solver, a, b); + end_logging (); +} + +void kissat_log_unary (kissat *solver, const char *prefix, unsigned a, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" unary clause %s", LOGLIT (a)); + end_logging (); +} + +static void log_ref (kissat *solver, reference ref) { + clause *c = kissat_dereference_clause (solver, ref); + log_clause (solver, c); + log_lits (solver, c->size, c->lits, 0); +} + +void kissat_log_ref (kissat *solver, const char *prefix, reference ref, + const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + log_ref (solver, ref); + end_logging (); +} + +void kissat_log_watch (kissat *solver, const char *prefix, unsigned lit, + watch watch, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + if (watch.type.binary) + log_binary (solver, lit, watch.binary.lit); + else + log_ref (solver, watch.large.ref); + end_logging (); +} + +void kissat_log_xor (kissat *solver, const char *prefix, unsigned lit, + unsigned size, const unsigned *lits, const char *fmt, + ...) { + va_list ap; + va_start (ap, fmt); + begin_logging (solver, prefix, fmt, &ap); + va_end (ap); + printf (" size %u XOR gate ", size); + fputs (kissat_log_lit (solver, lit), stdout); + printf (" ="); + for (unsigned i = 0; i < size; i++) { + if (i) + fputs (" ^ ", stdout); + else + fputc (' ', stdout); + fputs (kissat_log_lit (solver, lits[i]), stdout); + } + end_logging (); +} + +#else + +int kissat_log_dummy_to_avoid_pedantic_warning; + +#endif diff --git a/src/sat/kissat/logging.h b/src/sat/kissat/logging.h new file mode 100644 index 000000000..bb2b53218 --- /dev/null +++ b/src/sat/kissat/logging.h @@ -0,0 +1,464 @@ +#ifndef _logging_h_INCLUDED +#define _logging_h_INCLUDED + +#if defined(LOGGING) && !defined(QUIET) + +#include "attribute.h" +#include "extend.h" +#include "reference.h" +#include "watch.h" + +#include + +// clang-format off + +const char * kissat_log_lit (kissat *, unsigned lit); +const char * kissat_log_var (kissat *, unsigned idx); +const char * kissat_log_repr (kissat *, unsigned lit, const unsigned *); + +void kissat_begin_logging (kissat *, const char *prefix, + const char *fmt, ...) +ATTRIBUTE_FORMAT (3, 4); + +void kissat_end_logging (void); + +void kissat_log_msg (kissat *, const char*, const char *fmt, ...) +ATTRIBUTE_FORMAT (3, 4); + +void kissat_log_clause (kissat *, const char*, const clause *, + const char *, ...) +ATTRIBUTE_FORMAT (4, 5); + +void kissat_log_counted_clause (kissat *, const char*, const clause *, + const unsigned *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_repr_clause (kissat *, const char*, const clause *, + const unsigned *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_binary (kissat *, const char*, + unsigned, unsigned, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_unary (kissat *, const char*, unsigned, const char *, ...) +ATTRIBUTE_FORMAT (4, 5); + +void kissat_log_lits (kissat *, const char*, + size_t, const unsigned *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_litset (kissat *, const char*, + size_t, const unsigned *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_litpart (kissat *, const char*, + size_t, const unsigned *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_counted_ref_lits (kissat *, const char*, reference, + size_t, const unsigned *, + const unsigned * counts, const char *, ...) +ATTRIBUTE_FORMAT (7, 8); + +void kissat_log_counted_lits (kissat *, const char*, + size_t, const unsigned *, + const unsigned * counts, const char *, ...) +ATTRIBUTE_FORMAT (6, 7); + +void kissat_log_unsigneds (kissat *, const char*, + size_t, const unsigned *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +#define INVALID_GATE_ID (~(size_t)0) + +void kissat_log_and_gate (kissat *, const char*, size_t id, unsigned * repr, + unsigned lhs, size_t, const unsigned * rhs, + const char *, ...) +ATTRIBUTE_FORMAT (8, 9); + +void kissat_log_xor_gate (kissat *, const char*, size_t id, unsigned * repr, + unsigned lhs, size_t, const unsigned * rhs, + const char *, ...) +ATTRIBUTE_FORMAT (8, 9); + +void kissat_log_ite_gate (kissat *, const char*, size_t id, unsigned * repr, + unsigned lhs, unsigned cond, unsigned then_lit, + unsigned else_lit, const char *, ...) +ATTRIBUTE_FORMAT (9, 10); + +void kissat_log_ints (kissat *, const char*, + size_t, const int *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_extensions (kissat *, const char *, + size_t, const extension *, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +void kissat_log_xor (struct kissat *, const char*, unsigned lit, + unsigned size, const unsigned *lits, const char *fmt, ...) +ATTRIBUTE_FORMAT (6, 7); + +void kissat_log_ref (kissat *, const char*, reference, const char *, ...) +ATTRIBUTE_FORMAT (4, 5); + +void kissat_log_resolvent (kissat *, const char*, const char *, ...) +ATTRIBUTE_FORMAT (3, 4); + +void kissat_log_watch (kissat *, const char*, + unsigned, watch, const char *, ...) +ATTRIBUTE_FORMAT (5, 6); + +// clang-format on + +#ifndef LOGPREFIX +#define LOGPREFIX "LOG" +#endif + +#define LOG(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_msg (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOG2(...) \ + do { \ + if (solver && GET_OPTION (log) > 1) \ + kissat_log_msg (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOG3(...) \ + do { \ + if (solver && GET_OPTION (log) > 2) \ + kissat_log_msg (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOG4(...) \ + do { \ + if (solver && GET_OPTION (log) > 3) \ + kissat_log_msg (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOG5(...) \ + do { \ + if (solver && GET_OPTION (log) > 4) \ + kissat_log_msg (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGLITS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_lits (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGLITSET(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_litset (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGLITPART(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_litpart (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGCOUNTEDREFLITS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_counted_ref_lits (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGCOUNTEDLITS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_counted_lits (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGLITS3(...) \ + do { \ + if (GET_OPTION (log) > 2) \ + kissat_log_lits (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGRES(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_resolvent (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGRES2(...) \ + do { \ + if (solver && GET_OPTION (log) > 1) \ + kissat_log_resolvent (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGEXT(...) \ + do { \ + if (GET_OPTION (log)) \ + kissat_log_extensions (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGEXT2(...) \ + do { \ + if (GET_OPTION (log) > 1) \ + kissat_log_extensions (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGINTS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_ints (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGINTS3(...) \ + do { \ + if (GET_OPTION (log) > 2) \ + kissat_log_ints (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGUNSIGNEDS2(...) \ + do { \ + if (GET_OPTION (log) > 1) \ + kissat_log_unsigneds (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGUNSIGNEDS3(...) \ + do { \ + if (GET_OPTION (log) > 2) \ + kissat_log_unsigneds (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGANDGATE(...) \ + do { \ + if (GET_OPTION (log) > 0) \ + kissat_log_and_gate (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGXORGATE(...) \ + do { \ + if (GET_OPTION (log) > 0) \ + kissat_log_xor_gate (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGITEGATE(...) \ + do { \ + if (GET_OPTION (log) > 0) \ + kissat_log_ite_gate (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGCLS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_clause (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGCOUNTEDCLS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_counted_clause (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGREPRCLS(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_repr_clause (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGLINE(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_line (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGCLS2(...) \ + do { \ + if (GET_OPTION (log) > 1) \ + kissat_log_clause (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGCLS3(...) \ + do { \ + if (GET_OPTION (log) > 2) \ + kissat_log_clause (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGREF(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_ref (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGREF2(...) \ + do { \ + if (solver && GET_OPTION (log) > 1) \ + kissat_log_ref (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGREF3(...) \ + do { \ + if (solver && GET_OPTION (log) > 2) \ + kissat_log_ref (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGBINARY(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_binary (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGBINARY2(...) \ + do { \ + if (GET_OPTION (log) > 1) \ + kissat_log_binary (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGBINARY3(...) \ + do { \ + if (GET_OPTION (log) > 2) \ + kissat_log_binary (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGUNARY(...) \ + do { \ + if (solver && GET_OPTION (log)) \ + kissat_log_unary (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGLIT(LIT) kissat_log_lit (solver, (LIT)) +#define LOGVAR(IDX) kissat_log_var (solver, (IDX)) +#define LOGREPR(LIT, REPR) kissat_log_repr (solver, (LIT), (REPR)) + +#define LOGWATCH(...) \ + do { \ + if (GET_OPTION (log)) \ + kissat_log_watch (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#define LOGXOR(...) \ + do { \ + if (GET_OPTION (log)) \ + kissat_log_xor (solver, LOGPREFIX, __VA_ARGS__); \ + } while (0) + +#else + +#define LOG(...) \ + do { \ + } while (0) +#define LOG2(...) \ + do { \ + } while (0) +#define LOG3(...) \ + do { \ + } while (0) +#define LOG4(...) \ + do { \ + } while (0) +#define LOG5(...) \ + do { \ + } while (0) +#define LOGRES(...) \ + do { \ + } while (0) +#define LOGRES2(...) \ + do { \ + } while (0) +#define LOGLITS(...) \ + do { \ + } while (0) +#define LOGLITSET(...) \ + do { \ + } while (0) +#define LOGLITPART(...) \ + do { \ + } while (0) +#define LOGLITS3(...) \ + do { \ + } while (0) +#define LOGCOUNTEDREFLITS(...) \ + do { \ + } while (0) +#define LOGCOUNTEDLITS(...) \ + do { \ + } while (0) +#define LOGEXT(...) \ + do { \ + } while (0) +#define LOGEXT2(...) \ + do { \ + } while (0) +#define LOGINTS(...) \ + do { \ + } while (0) +#define LOGINTS3(...) \ + do { \ + } while (0) +#define LOGUNSIGNEDS2(...) \ + do { \ + } while (0) +#define LOGUNSIGNEDS3(...) \ + do { \ + } while (0) +#define LOGANDGATE(...) \ + do { \ + } while (0) +#define LOGXORGATE(...) \ + do { \ + } while (0) +#define LOGITEGATE(...) \ + do { \ + } while (0) +#define LOGCLS(...) \ + do { \ + } while (0) +#define LOGCLS2(...) \ + do { \ + } while (0) +#define LOGCLS3(...) \ + do { \ + } while (0) +#define LOGCOUNTEDCLS(...) \ + do { \ + } while (0) +#define LOGREPRCLS(...) \ + do { \ + } while (0) +#define LOGLINE(...) \ + do { \ + } while (0) +#define LOGREF(...) \ + do { \ + } while (0) +#define LOGREF2(...) \ + do { \ + } while (0) +#define LOGREF3(...) \ + do { \ + } while (0) +#define LOGBINARY(...) \ + do { \ + } while (0) +#define LOGBINARY2(...) \ + do { \ + } while (0) +#define LOGBINARY3(...) \ + do { \ + } while (0) +#define LOGUNARY(...) \ + do { \ + } while (0) +#define LOGWATCH(...) \ + do { \ + } while (0) +#define LOGXOR(...) \ + do { \ + } while (0) + +#endif + +#define LOGTMP(...) \ + LOGLITS (SIZE_STACK (solver->clause), BEGIN_STACK (solver->clause), \ + __VA_ARGS__) + +#endif diff --git a/src/sat/kissat/lucky.c b/src/sat/kissat/lucky.c new file mode 100644 index 000000000..74219b095 --- /dev/null +++ b/src/sat/kissat/lucky.c @@ -0,0 +1,393 @@ +#include "lucky.h" +#include "analyze.h" +#include "backtrack.h" +#include "decide.h" +#include "inline.h" +#include "internal.h" +#include "print.h" +#include "proprobe.h" +#include "report.h" + +static bool no_all_negative_clauses (struct kissat *solver) { + clause *last_irredundant = kissat_last_irredundant_clause (solver); + for (all_clauses (c)) { + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + if (c->garbage) + continue; + for (all_literals_in_clause (lit, c)) + if (!NEGATED (lit) && VALUE (lit) >= 0) + goto CONTINUE_WITH_NEXT_CLAUSE; + kissat_verbose (solver, "found all negative large clause"); + return false; + CONTINUE_WITH_NEXT_CLAUSE:; + } + assert (solver->watching); + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + for (all_binary_blocking_watches (watch, WATCHES (not_lit))) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + if (NEGATED (other) && ACTIVE (IDX (other))) { + kissat_verbose (solver, "found all negative binary clause"); + return false; + } + } + } + kissat_message (solver, "lucky no all-negative clause"); + return true; +} + +static bool no_all_positive_clauses (struct kissat *solver) { + clause *last_irredundant = kissat_last_irredundant_clause (solver); + for (all_clauses (c)) { + if (last_irredundant && last_irredundant < c) + break; + if (c->redundant) + continue; + if (c->garbage) + continue; + for (all_literals_in_clause (lit, c)) + if (NEGATED (lit) && VALUE (lit) >= 0) + goto CONTINUE_WITH_NEXT_CLAUSE; + kissat_verbose (solver, "found all positive large clause"); + return false; + CONTINUE_WITH_NEXT_CLAUSE:; + } + assert (solver->watching); + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + for (all_binary_blocking_watches (watch, WATCHES (lit))) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + if (!NEGATED (other) && ACTIVE (IDX (other))) { + kissat_verbose (solver, "found all positive binary clause"); + return false; + } + } + } + kissat_message (solver, "lucky no all-positive clause"); + return true; +} + +static int forward_false_satisfiable (struct kissat *solver) { + assert (!solver->level); +#ifndef QUIET + unsigned conflicts = 0; +#endif + for (all_stack (import, import, solver->import)) { + if (!import.imported) + continue; + if (import.eliminated) + continue; + const unsigned lit = import.lit; + const unsigned idx = IDX (lit); + if (!ACTIVE (idx)) + continue; + if (VALUE (lit)) + continue; + const unsigned not_lit = NOT (lit); + kissat_internal_assume (solver, not_lit); + clause *c = kissat_probing_propagate (solver, 0, true); + if (!c) + continue; +#ifndef QUIET + conflicts++; +#endif + if (solver->level > 1) { + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + kissat_internal_assume (solver, lit); + clause *d = kissat_probing_propagate (solver, 0, true); + if (!d) + continue; + kissat_verbose (solver, + "inconsistency after %u conflicts " + "forward assigning %u variables to false", + conflicts, solver->level); + kissat_backtrack_without_updating_phases (solver, 0); + return 0; + } else { + LOG ("failed literal %s", LOGLIT (not_lit)); + kissat_analyze (solver, c); + assert (!solver->level); + clause *d = kissat_probing_propagate (solver, 0, true); + if (d) { + kissat_analyze (solver, d); + assert (solver->inconsistent); + kissat_verbose (solver, + "lucky inconsistency forward assigning to false"); + return 20; + } + } + } + + kissat_message (solver, "lucky in forward setting literals to false"); + return 10; +} + +static int forward_true_satisfiable (struct kissat *solver) { + assert (!solver->level); +#ifndef QUIET + unsigned conflicts = 0; +#endif + for (all_stack (import, import, solver->import)) { + if (!import.imported) + continue; + if (import.eliminated) + continue; + const unsigned lit = import.lit; + const unsigned idx = IDX (lit); + if (!ACTIVE (idx)) + continue; + if (VALUE (lit)) + continue; + kissat_internal_assume (solver, lit); + clause *c = kissat_probing_propagate (solver, 0, true); + if (!c) + continue; +#ifndef QUIET + conflicts++; +#endif + if (solver->level > 1) { + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + const unsigned not_lit = NOT (lit); + kissat_internal_assume (solver, not_lit); + clause *d = kissat_probing_propagate (solver, 0, true); + if (!d) + continue; + kissat_verbose (solver, + "inconsistency after %u conflicts " + "forward assigning %u variables to true", + conflicts, solver->level); + kissat_backtrack_without_updating_phases (solver, 0); + return 0; + } else { + LOG ("failed literal %s", LOGLIT (lit)); + kissat_analyze (solver, c); + assert (!solver->level); + clause *d = kissat_probing_propagate (solver, 0, true); + if (d) { + kissat_analyze (solver, d); + assert (solver->inconsistent); + kissat_verbose (solver, + "lucky inconsistency forward assigning to true"); + return 20; + } + } + } + kissat_message (solver, "lucky in forward setting literals to true"); + return 10; +} + +static int backward_false_satisfiable (struct kissat *solver) { + assert (!solver->level); +#ifndef QUIET + unsigned conflicts = 0; +#endif + import *begin = BEGIN_STACK (solver->import); + import *end = END_STACK (solver->import); + import *p = end; + while (p != begin) { + const import import = *--p; + if (!import.imported) + continue; + if (import.eliminated) + continue; + const unsigned lit = import.lit; + const unsigned idx = IDX (lit); + if (!ACTIVE (idx)) + continue; + if (VALUE (lit)) + continue; + const unsigned not_lit = NOT (lit); + kissat_internal_assume (solver, not_lit); + clause *c = kissat_probing_propagate (solver, 0, true); + if (!c) + continue; +#ifndef QUIET + conflicts++; +#endif + if (solver->level > 1) { + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + kissat_internal_assume (solver, lit); + clause *d = kissat_probing_propagate (solver, 0, true); + if (!d) + continue; + kissat_verbose (solver, + "inconsistency after %u conflicts " + "backward assigning %u variables to false", + conflicts, solver->level); + kissat_backtrack_without_updating_phases (solver, 0); + return 0; + } else { + LOG ("failed literal %s", LOGLIT (not_lit)); + kissat_analyze (solver, c); + assert (!solver->level); + clause *d = kissat_probing_propagate (solver, 0, true); + if (d) { + kissat_analyze (solver, d); + assert (solver->inconsistent); + kissat_verbose (solver, + "lucky inconsistency backward assigning to false"); + return 20; + } + } + } + kissat_message (solver, "lucky in backward setting literals to false"); + return 10; +} + +static int backward_true_satisfiable (struct kissat *solver) { + assert (!solver->level); +#ifndef QUIET + unsigned conflicts = 0; +#endif + import *begin = BEGIN_STACK (solver->import); + import *end = END_STACK (solver->import); + import *p = end; + while (p != begin) { + const import import = *--p; + if (!import.imported) + continue; + if (import.eliminated) + continue; + const unsigned lit = import.lit; + const unsigned idx = IDX (lit); + if (!ACTIVE (idx)) + continue; + if (VALUE (lit)) + continue; + kissat_internal_assume (solver, lit); + clause *c = kissat_probing_propagate (solver, 0, true); + if (!c) + continue; +#ifndef QUIET + conflicts++; +#endif + if (solver->level > 1) { + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + const unsigned not_lit = NOT (lit); + kissat_internal_assume (solver, not_lit); + clause *d = kissat_probing_propagate (solver, 0, true); + if (!d) + continue; + kissat_verbose (solver, + "inconsistency after %u conflicts " + "backward assigning %u variables to true", + conflicts, solver->level); + kissat_backtrack_without_updating_phases (solver, 0); + return 0; + } else { + LOG ("failed literal %s", LOGLIT (lit)); + kissat_analyze (solver, c); + assert (!solver->level); + clause *d = kissat_probing_propagate (solver, 0, true); + if (d) { + kissat_analyze (solver, d); + assert (solver->inconsistent); + kissat_verbose (solver, + "lucky inconsistency backward assigning to true"); + return 20; + } + } + } + kissat_message (solver, "lucky in backward setting literals to true"); + return 10; +} + +int kissat_lucky (struct kissat *solver) { + + if (solver->inconsistent) + return 0; + + if (!GET_OPTION (lucky)) + return 0; + + START (lucky); + assert (!solver->level); + assert (!solver->probing); + solver->probing = true; + assert (kissat_propagated (solver)); + + int res = 0; + + if (no_all_negative_clauses (solver)) { + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + if (VALUE (lit)) + continue; + kissat_internal_assume (solver, lit); +#ifndef NDEBUG + clause *c = +#endif + kissat_probing_propagate (solver, 0, true); + assert (!c); + } + kissat_verbose (solver, "set all variables to true"); + assert (kissat_propagated (solver)); + assert (!solver->unassigned); + res = 10; + } + + if (!res && no_all_positive_clauses (solver)) { + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + if (VALUE (lit)) + continue; + const unsigned not_lit = NOT (lit); + kissat_internal_assume (solver, not_lit); +#ifndef NDEBUG + clause *c = +#endif + kissat_probing_propagate (solver, 0, true); + assert (!c); + } + kissat_verbose (solver, "set all variables to false"); + assert (kissat_propagated (solver)); + assert (!solver->unassigned); + res = 10; + } + + const unsigned active_before = solver->active; + + if (!res) + res = forward_false_satisfiable (solver); + + if (!res) + res = forward_true_satisfiable (solver); + + if (!res) + res = backward_false_satisfiable (solver); + + if (!res) + res = backward_true_satisfiable (solver); + + const unsigned active_after = solver->active; + const unsigned units = active_before - active_after; + + if (!res && units) + kissat_message (solver, "lucky %u units", units); + +#ifndef QUIET + bool success = res || units; +#endif + assert (solver->probing); + solver->probing = false; + REPORT (!success, 'l'); + STOP (lucky); + + return res; +} diff --git a/src/sat/kissat/lucky.h b/src/sat/kissat/lucky.h new file mode 100644 index 000000000..ef97c67dc --- /dev/null +++ b/src/sat/kissat/lucky.h @@ -0,0 +1,7 @@ +#ifndef _lucky_h_INCLUDED +#define _lucky_h_INCLUDED + +struct kissat; +int kissat_lucky (struct kissat *); + +#endif diff --git a/src/sat/kissat/minimize.c b/src/sat/kissat/minimize.c new file mode 100644 index 000000000..94dcb63d5 --- /dev/null +++ b/src/sat/kissat/minimize.c @@ -0,0 +1,211 @@ +#include "minimize.h" +#include "inline.h" + +static inline int minimized_index (kissat *solver, bool minimizing, + assigned *a, unsigned lit, unsigned idx, + unsigned depth) { +#if !defined(LOGGING) && defined(NDEBUG) + (void) lit; +#endif +#ifdef NDEBUG + (void) idx; +#endif + assert (IDX (lit) == idx); + assert (solver->assigned + idx == a); + if (!a->level) { + LOG2 ("skipping root level literal %s", LOGLIT (lit)); + return 1; + } + if (a->removable && depth) { + LOG2 ("skipping removable literal %s", LOGLIT (lit)); + return 1; + } + assert (a->reason != UNIT_REASON); + if (a->reason == DECISION_REASON) { + LOG2 ("can not remove decision literal %s", LOGLIT (lit)); + return -1; + } + if (a->poisoned) { + LOG2 ("can not remove poisoned literal %s", LOGLIT (lit)); + return -1; + } + if (minimizing || !depth) { + frame *frame = &FRAME (a->level); + if (frame->used <= 1) { + LOG2 ("can not remove singleton frame literal %s", LOGLIT (lit)); + return -1; + } + } + return 0; +} + +static bool minimize_literal (kissat *, bool, assigned *, unsigned lit, + unsigned depth); + +static inline bool minimize_reference (kissat *solver, bool minimizing, + assigned *assigned, reference ref, + unsigned lit, unsigned depth) { + const unsigned next_depth = (depth == UINT_MAX) ? depth : depth + 1; + const unsigned not_lit = NOT (lit); + clause *c = kissat_dereference_clause (solver, ref); + if (GET_OPTION (minimizeticks)) + INC (search_ticks); + for (all_literals_in_clause (other, c)) + if (other != not_lit && + !minimize_literal (solver, minimizing, assigned, other, next_depth)) + return false; + return true; +} + +static inline bool minimize_binary (kissat *solver, bool minimizing, + assigned *assigned, unsigned lit, + unsigned depth) { + const size_t saved = SIZE_STACK (solver->minimize); + bool res; + for (unsigned next = lit;;) { + const unsigned next_idx = IDX (next); + struct assigned *a = assigned + next_idx; + int tmp = minimized_index (solver, minimizing, a, next, next_idx, 1); + if (tmp) { + res = (tmp > 0); + break; + } + PUSH_STACK (solver->minimize, next_idx); + if (!a->binary) { + const unsigned next_depth = (depth == UINT_MAX) ? depth : depth + 1; + res = minimize_reference (solver, minimizing, assigned, a->reason, + next, next_depth); + break; + } + next = a->reason; + } + unsigned *begin = BEGIN_STACK (solver->minimize) + saved; + const unsigned *const end = END_STACK (solver->minimize); + assert (begin <= end); + if (res) + for (const unsigned *p = begin; p != end; p++) + kissat_push_removable (solver, assigned, *p); + else + for (const unsigned *p = begin; p != end; p++) + kissat_push_poisoned (solver, assigned, *p); + SET_END_OF_STACK (solver->minimize, begin); + return res; +} + +static bool minimize_literal (kissat *solver, bool minimizing, + assigned *assigned, unsigned lit, + unsigned depth) { + LOG ("trying to minimize literal %s at recursion depth %d", LOGLIT (lit), + depth); + assert (VALUE (lit) < 0); + assert (depth || EMPTY_STACK (solver->minimize)); + assert (GET_OPTION (minimizedepth) > 0); + if (depth >= (unsigned) GET_OPTION (minimizedepth)) + return false; + const unsigned idx = IDX (lit); + struct assigned *a = assigned + idx; + int tmp = minimized_index (solver, minimizing, a, lit, idx, depth); + if (tmp > 0) + return true; + if (tmp < 0) + return false; +#ifdef LOGGING + const unsigned not_lit = NOT (lit); +#endif + bool res; + if (a->binary) { + const unsigned other = a->reason; + LOGBINARY2 (not_lit, other, "minimizing along %s reason", + LOGLIT (not_lit)); + res = minimize_binary (solver, minimizing, assigned, other, depth); + } else { + const reference ref = a->reason; + LOGREF2 (ref, "minimizing along %s reason", LOGLIT (not_lit)); + res = + minimize_reference (solver, minimizing, assigned, ref, lit, depth); + } + if (!depth) + return res; + if (!res) + kissat_push_poisoned (solver, assigned, idx); + else if (!a->removable) + kissat_push_removable (solver, assigned, idx); + return res; +} + +bool kissat_minimize_literal (kissat *solver, unsigned lit, + bool lit_in_clause) { + assert (EMPTY_STACK (solver->minimize)); + return minimize_literal (solver, false, solver->assigned, lit, + !lit_in_clause); +} + +void kissat_reset_poisoned (kissat *solver) { + LOG ("reset %zu poisoned variables", SIZE_STACK (solver->poisoned)); + assigned *assigned = solver->assigned; + for (all_stack (unsigned, idx, solver->poisoned)) { + assert (idx < VARS); + struct assigned *a = assigned + idx; + assert (a->poisoned); + a->poisoned = false; + } + CLEAR_STACK (solver->poisoned); +} + +void kissat_minimize_clause (kissat *solver) { + START (minimize); + + assert (EMPTY_STACK (solver->minimize)); + assert (EMPTY_STACK (solver->removable)); + assert (EMPTY_STACK (solver->poisoned)); + assert (!EMPTY_STACK (solver->clause)); + + unsigned *lits = BEGIN_STACK (solver->clause); + unsigned *end = END_STACK (solver->clause); + + assigned *assigned = solver->assigned; +#ifndef NDEBUG + assert (lits < end); + const unsigned not_uip = lits[0]; + assert (assigned[IDX (not_uip)].level == solver->level); +#endif + for (const unsigned *p = lits; p != end; p++) + kissat_push_removable (solver, assigned, IDX (*p)); + + if (GET_OPTION (shrink) > 2) { + STOP (minimize); + return; + } + + unsigned minimized = 0; + + for (unsigned *p = end; --p > lits;) { + const unsigned lit = *p; + assert (lit != not_uip); + if (minimize_literal (solver, true, assigned, lit, 0)) { + LOG ("minimized literal %s", LOGLIT (lit)); + *p = INVALID_LIT; + minimized++; + } else + LOG ("keeping literal %s", LOGLIT (lit)); + } + + unsigned *q = lits; + for (const unsigned *p = lits; p != end; p++) { + const unsigned lit = *p; + if (lit != INVALID_LIT) + *q++ = lit; + } + assert (q + minimized == end); + SET_END_OF_STACK (solver->clause, q); + LOG ("clause minimization removed %u literals", minimized); + + assert (!solver->probing); + ADD (literals_minimized, minimized); + + LOGTMP ("minimized learned"); + + kissat_reset_poisoned (solver); + + STOP (minimize); +} diff --git a/src/sat/kissat/minimize.h b/src/sat/kissat/minimize.h new file mode 100644 index 000000000..ae06fb9ad --- /dev/null +++ b/src/sat/kissat/minimize.h @@ -0,0 +1,14 @@ +#ifndef _minimize_h_INCLUDED +#define _minimize_h_INCLUDED + +#include + +struct kissat; + +void kissat_reset_poisoned (struct kissat *); + +void kissat_minimize_clause (struct kissat *); +bool kissat_minimize_literal (struct kissat *, unsigned, + bool lit_in_clause); + +#endif diff --git a/src/sat/kissat/mode.c b/src/sat/kissat/mode.c new file mode 100644 index 000000000..b1390eca1 --- /dev/null +++ b/src/sat/kissat/mode.c @@ -0,0 +1,215 @@ +#include "bump.h" +#include "decide.h" +#include "inline.h" +#include "print.h" +#include "report.h" +#include "resources.h" +#include "restart.h" + +#include + +#ifndef QUIET + +static const char *mode_string (kissat *solver) { + return solver->stable ? "stable" : "focused"; +} + +#endif + +void kissat_init_mode_limit (kissat *solver) { + limits *limits = &solver->limits; + + if (GET_OPTION (stable) == 1) { + assert (!solver->stable); + + const uint64_t conflicts_delta = GET_OPTION (modeinit); + const uint64_t conflicts_limit = CONFLICTS + conflicts_delta; + + assert (conflicts_limit); + + limits->mode.conflicts = conflicts_limit; + limits->mode.ticks = 0; + limits->mode.count = 0; + + kissat_very_verbose (solver, + "initial %s mode switching limit " + "at %s after %s conflicts", + mode_string (solver), + FORMAT_COUNT (conflicts_limit), + FORMAT_COUNT (conflicts_delta)); + + solver->mode.ticks = solver->statistics.search_ticks; +#ifndef QUIET + solver->mode.conflicts = CONFLICTS; +#ifdef METRICS + solver->mode.propagations = solver->statistics.search_propagations; +#endif + // clang-format off + solver->mode.entered = kissat_process_time (); + kissat_very_verbose (solver, + "starting %s mode at %.2f seconds " + "(%" PRIu64 " conflicts, %" PRIu64 " ticks" +#ifdef METRICS + ", %" PRIu64 " propagations, %" PRIu64 " visits" +#endif + ")", mode_string (solver), + solver->mode.entered, solver->mode.conflicts, solver->mode.ticks +#ifdef METRICS + , solver->mode.propagations, solver->mode.visits +#endif + ); +// clang-format on +#endif + } else + kissat_very_verbose (solver, + "no need to set mode limit (only %s mode enabled)", + mode_string (solver)); +} + +static void update_mode_limit (kissat *solver, uint64_t delta_ticks) { + kissat_init_averages (solver, &AVERAGES); + + limits *limits = &solver->limits; + statistics *statistics = &solver->statistics; + + assert (GET_OPTION (stable) == 1); + + if (limits->mode.count & 1) { + limits->mode.ticks = statistics->search_ticks + delta_ticks; +#ifndef QUIET + assert (solver->stable); + kissat_phase (solver, "stable", GET (stable_modes), + "new stable mode switching limit of %s " + "after %s ticks", + FORMAT_COUNT (limits->mode.ticks), + FORMAT_COUNT (delta_ticks)); +#endif + } else { + assert (limits->mode.ticks); + const uint64_t interval = GET_OPTION (modeint); + const uint64_t count = (statistics->switched + 1) / 2; + const uint64_t scaled = interval * kissat_nlogpown (count, 4); + limits->mode.conflicts = statistics->conflicts + scaled; +#ifndef QUIET + assert (!solver->stable); + kissat_phase (solver, "focused", GET (focused_modes), + "new focused mode switching limit of %s " + "after %s conflicts", + FORMAT_COUNT (limits->mode.conflicts), + FORMAT_COUNT (scaled)); +#endif + } + + solver->mode.ticks = statistics->search_ticks; +#ifndef QUIET + solver->mode.conflicts = statistics->conflicts; +#ifdef METRICS + solver->mode.propagations = statistics->search_propagations; +#endif +#endif +} + +static void report_switching_from_mode (kissat *solver, + uint64_t *delta_ticks) { + statistics *statistics = &solver->statistics; + *delta_ticks = statistics->search_ticks - solver->mode.ticks; + +#ifndef QUIET + if (kissat_verbosity (solver) < 2) + return; + + const double current_time = kissat_process_time (); + const double delta_time = current_time - solver->mode.entered; + + const uint64_t delta_conflicts = + statistics->conflicts - solver->mode.conflicts; +#ifdef METRICS + const uint64_t delta_propagations = + statistics->search_propagations - solver->mode.propagations; +#endif + solver->mode.entered = current_time; + + // clang-format off + kissat_very_verbose (solver, "%s mode took %.2f seconds " + "(%s conflicts, %s ticks" +#ifdef METRICS + ", %s propagations" +#endif + ")", solver->stable ? "stable" : "focused", + delta_time, FORMAT_COUNT (delta_conflicts), FORMAT_COUNT (*delta_ticks) +#ifdef METRICS + , FORMAT_COUNT (delta_propagations) +#endif + ); + // clang-format on +#else + (void) solver; +#endif +} + +static void switch_to_focused_mode (kissat *solver) { + assert (solver->stable); + uint64_t delta; + report_switching_from_mode (solver, &delta); + REPORT (0, ']'); + STOP (stable); + INC (focused_modes); + kissat_phase (solver, "focus", GET (focused_modes), + "switching to focused mode after %s conflicts", + FORMAT_COUNT (CONFLICTS)); + solver->stable = false; + update_mode_limit (solver, delta); + START (focused); + REPORT (0, '{'); + kissat_reset_search_of_queue (solver); + kissat_update_focused_restart_limit (solver); +} + +static void switch_to_stable_mode (kissat *solver) { + assert (!solver->stable); + uint64_t delta; + report_switching_from_mode (solver, &delta); + REPORT (0, '}'); + STOP (focused); + INC (stable_modes); + solver->stable = true; + kissat_phase (solver, "stable", GET (stable_modes), + "switched to stable mode after %" PRIu64 " conflicts", + CONFLICTS); + update_mode_limit (solver, delta); + START (stable); + REPORT (0, '['); + kissat_init_reluctant (solver); + kissat_update_scores (solver); +} + +bool kissat_switching_search_mode (kissat *solver) { + assert (!solver->inconsistent); + + if (GET_OPTION (stable) != 1) + return false; + + limits *limits = &solver->limits; + statistics *statistics = &solver->statistics; + + if (limits->mode.count & 1) + return statistics->search_ticks >= limits->mode.ticks; + else + return statistics->conflicts >= limits->mode.conflicts; +} + +void kissat_switch_search_mode (kissat *solver) { + assert (kissat_switching_search_mode (solver)); + + INC (switched); + solver->limits.mode.count++; + + if (solver->stable) + switch_to_focused_mode (solver); + else + switch_to_stable_mode (solver); + + solver->averages[solver->stable].saved_decisions = DECISIONS; + + kissat_start_random_sequence (solver); +} diff --git a/src/sat/kissat/mode.h b/src/sat/kissat/mode.h new file mode 100644 index 000000000..915e746e4 --- /dev/null +++ b/src/sat/kissat/mode.h @@ -0,0 +1,24 @@ +#ifndef _mode_h_INCLUDED +#define _mode_h_INCLUDED + +struct kissat; + +typedef struct mode mode; + +struct mode { + uint64_t ticks; +#ifndef QUIET + double entered; + uint64_t conflicts; +#ifdef METRICS + uint64_t propagations; + uint64_t visits; +#endif +#endif +}; + +void kissat_init_mode_limit (struct kissat *); +bool kissat_switching_search_mode (struct kissat *); +void kissat_switch_search_mode (struct kissat *); + +#endif diff --git a/src/sat/kissat/options.c b/src/sat/kissat/options.c new file mode 100644 index 000000000..cf618905e --- /dev/null +++ b/src/sat/kissat/options.c @@ -0,0 +1,396 @@ +#include "options.h" +#include "error.h" +#include "print.h" + +#include +#include +#include +#include +#include + +#ifdef NOPTIONS + +static const opt table[] = { +#define OPTION(N, V, L, H, D) {#N, (int) (V), D}, + OPTIONS +#undef OPTION +}; + +#else + +static const opt table[] = { +#define OPTION(N, V, L, H, D) {#N, (int) (V), (int) (L), (int) (H), D}, + OPTIONS +#undef OPTION +}; + +#endif + +#define size_table (sizeof table / sizeof *table) + +const opt *kissat_options_begin = table; +const opt *kissat_options_end = table + size_table; + +static void check_table_sorted (void) { +#ifndef NDEBUG + opt const *p = 0; + for (all_options (o)) + if (p && strcmp (p->name, o->name) >= 0) + kissat_fatal ("option '%s' before option '%s'", p->name, o->name); + else + p = o; +#endif +} + +const opt *kissat_options_has (const char *name) { + size_t l = 0, m, r = size_table; + int tmp; + opt const *o; + assert (l < r); + while (l + 1 < r) { + m = l + (r - l) / 2; + tmp = strcmp (name, (o = table + m)->name); + if (tmp < 0) + r = m; + else if (tmp > 0) + l = m; + else + return o; + } + o = table + l; + tmp = strcmp (o->name, name); + return tmp ? 0 : o; +} + +bool kissat_parse_option_value (const char *val_str, int *res_ptr) { + if (!strcmp (val_str, "true")) { + *res_ptr = 1; + return true; + } + if (!strcmp (val_str, "false")) { + *res_ptr = 0; + return true; + } + int sign = 1; + char const *p = val_str; + int ch = *p++; + if (ch == '-') { + sign = -1; + ch = *p++; + } + if (!isdigit (ch)) // at least one digit + return false; + const unsigned max = -(unsigned) INT_MIN; + unsigned res = ch - '0'; + while (isdigit ((ch = *p++))) { + if (max / 10 < res) + return false; + res *= 10; + const unsigned digit = ch - '0'; + if (max - digit < res) + return false; + res += digit; + if (!res) + return false; // invalid '00' + } + if (ch == 'e') // parse '13e5' etc. + { + if (!isdigit ((ch = *p++))) // at least one digit + return false; + if (res) { + if (*p) // exactly one digit + return false; + const unsigned digit = ch - '0'; + for (unsigned i = 0; i < digit; i++) { + if (max / 10 < res) + return false; + res *= 10; + } + } else // parse '0^123123123' etc. + { + while (isdigit (ch = *p++)) // arbitrary many digits + ; + if (ch) + return false; + } + } else if (ch == '^') // parse '2^11' etc. + { + const unsigned base = res; + if (!isdigit ((ch = *p++))) // at least one digit + return false; + unsigned exp = ch - '0'; + if (base < 2) // parse '0^123123123' etc. + { + while (isdigit (ch = *p++)) // arbitrary many digits + ; + if (ch) + return false; + } else if (isdigit (ch = *p++)) // parse '2^30' etc. + { + if (*p) // at most two digits + return false; + exp *= 10; + const unsigned digit = ch - '0'; + exp += digit; + if (!exp) // '2^00' invalid + return false; + } else if (ch) + return false; + if (exp) + for (unsigned i = 1; i < exp; i++) { + if (max / base < res) + return false; + res *= base; + } + else if (base) + res = 1; // parse '3^0' + else + return false; // '0^0' invalid + } else if (ch) + return false; + assert (res <= max); + if (sign > 0 && res == max) + return false; + res *= sign; + *res_ptr = res; + return true; +} + +const char *kissat_parse_option_name (const char *arg, const char *name) { + if (arg[0] != '-' || arg[1] != '-') + return 0; + char const *p = arg + 2, *q = name; + if (p[0] == 'n' && p[1] == 'o' && p[2] == '-') + return strcmp (p + 3, name) ? 0 : "0"; + while (*p && *p == *q) + p++, q++; + if (*q) + return 0; + if (*p != '=') + return 0; + return p + 1; +} + +#ifdef NOPTIONS + +void kissat_init_options (void) { check_table_sorted (); } + +int kissat_options_get (const char *name) { + const opt *const o = kissat_options_has (name); + return o ? o->value : 0; +} + +#else + +#include "format.h" + +#include +#include +#include + +static void kissat_printf_usage (const char *option, const char *fmt, ...) { + va_list ap; + printf (" %-26s ", option); + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); +} + +static void check_ranges (void) { +#define OPTION(N, V, L, H, D) \ + do { \ + if ((int) (L) > (int) (H)) \ + kissat_fatal ("minimum '%d' of option '%s' above maximum '%d'", \ + (int) (L), #N, (int) (H)); \ + if ((int) (V) < (int) (L)) \ + kissat_fatal ( \ + "default value '%d' of option '%s' below minimum '%d'", \ + (int) (V), #N, (int) (L)); \ + if ((int) (V) > (int) (H)) \ + kissat_fatal ( \ + "default value '%d' of option '%s' above maximum '%d'", \ + (int) (V), #N, (int) (H)); \ + } while (0); + OPTIONS +#undef OPTION +} + +static void check_name_length (void) { +#ifndef NDEBUG +#define OPTION(N, V, L, H, D) \ + if (strlen (#N) + 1 > kissat_options_max_name_buffer_size) \ + kissat_fatal ("option '%s' name length %zu " \ + "exceeds maximum name buffer size %zu", \ + #N, strlen (#N), kissat_options_max_name_buffer_size); + OPTIONS +#undef OPTION +#endif +} + +int kissat_options_get (const options *options, const char *name) { + const int *const p = + kissat_options_ref (options, kissat_options_has (name)); + return p ? *p : 0; +} + +int kissat_options_set_opt (options *options, const opt *o, int value) { + assert (kissat_options_begin <= o); + assert (o < kissat_options_end); + int *p = (int *) options + (o - table); + int res = *p; + if (value == res) + return res; + if (value < o->low) + value = o->low; + if (value > o->high) + value = o->high; + *p = value; + return res; +} + +int kissat_options_set (options *options, const char *name, int value) { + const opt *const o = kissat_options_has (name); + if (!o) + return 0; + return kissat_options_set_opt (options, o, value); +} + +void kissat_init_options (options *options) { + check_ranges (); + check_name_length (); + check_table_sorted (); +#define OPTION(N, V, L, H, D) \ + assert ((L) <= (V)); \ + assert ((V) <= (H)); \ + options->N = (V); + OPTIONS +#undef OPTION +} + +#define FORMAT_OPTION_LIMIT(V) \ + (((V) == INT_MIN || (V) == INT_MAX) \ + ? "." \ + : kissat_format_value (&format, false, (V))) + +void kissat_options_usage (void) { + check_ranges (); + check_name_length (); + check_table_sorted (); + format format; + memset (&format, 0, sizeof format); +#define OPTION(N, V, L, H, D) \ + do { \ + const bool b = ((L) == 0 && (H) == 1); \ + char buffer[96]; \ + if (b) \ + sprintf (buffer, "--%s=", #N); \ + else { \ + const char *low_str = FORMAT_OPTION_LIMIT ((L)); \ + const char *high_str = FORMAT_OPTION_LIMIT ((H)); \ + sprintf (buffer, "--%s=%s..%s", #N, low_str, high_str); \ + } \ + const char *val_str = kissat_format_value (&format, b, (V)); \ + kissat_printf_usage (buffer, "%s [%s]", D, val_str); \ + } while (0); + OPTIONS +#undef OPTION +} + +bool kissat_options_parse_arg (const char *arg, char *buffer, + int *val_ptr) { + if (arg[0] != '-' || arg[1] != '-') + return false; + char const *name = arg + 2, *p = name; + int ch; + while ((ch = *p) && ch != '=') + p++; + if (ch) { + assert (ch == '='); + const size_t len = p - name; + if (len >= kissat_options_max_name_buffer_size) + return false; + memcpy (buffer, name, len); + buffer[len] = 0; + const opt *const o = kissat_options_has (buffer); + if (!o) + return false; + int value; + if (!kissat_parse_option_value (p + 1, &value)) + return false; + if (value < o->low || value > o->high) + return false; + *val_ptr = value; + } else { + int value = 0; + if (arg[2] == 'n' && arg[3] == 'o' && arg[4] == '-') { + name += 3; + const opt *const o = kissat_options_has (name); + if (!o || o->low > (value = 0)) + return false; + } else { + const opt *const o = kissat_options_has (name); + if (!o || o->high < (value = 1)) + return false; + } + assert (strlen (name) < kissat_options_max_name_buffer_size); + strcpy (buffer, name); + *val_ptr = value; + } + return true; +} + +static bool ignore_embedded_option_for_fuzzing (const char *name) { +#ifdef EMBEDDED + if (!strcmp (name, "embedded")) + return true; +#endif +#ifndef QUIET + if (!strcmp (name, "quiet")) + return true; +#endif + (void) name; + return false; +} + +void kissat_print_embedded_option_list () { +#define OPTION(N, V, L, H, D) \ + if (!ignore_embedded_option_for_fuzzing (#N)) \ + printf ("c --%s=%d\n", #N, (int) (V)); + OPTIONS +#undef OPTION +} + +static bool ignore_range_option_for_fuzzing (const char *name) { +#ifdef LOGGING + if (!strcmp (name, "log")) + return true; +#endif +#ifdef EMBEDDED + if (!strcmp (name, "embedded")) + return true; +#endif +#ifndef QUIET + if (!strcmp (name, "quiet")) + return true; +#endif + if (!strcmp (name, "reduce")) + return true; + if (!strcmp (name, "reluctant")) + return true; + if (!strcmp (name, "rephase")) + return true; + if (!strcmp (name, "restart")) + return true; + return false; +} + +void kissat_print_option_range_list (void) { +#define OPTION(N, V, L, H, D) \ + if (!ignore_range_option_for_fuzzing (#N)) \ + printf ("%s %d %d %d\n", #N, (int) (L), (int) (V), (int) (H)); + OPTIONS +#undef OPTION +} + +#endif diff --git a/src/sat/kissat/options.h b/src/sat/kissat/options.h new file mode 100644 index 000000000..26cd6f066 --- /dev/null +++ b/src/sat/kissat/options.h @@ -0,0 +1,299 @@ +#ifndef _options_h_INLCUDED +#define _options_h_INLCUDED + +#include +#include + +// clang-format off + +#define OPTIONS \ + OPTION (ands, 1, 0, 1, "extract and eliminate and gates") \ + OPTION (backbone, 1, 0, 2, "binary clause backbone (2=eager)") \ + OPTION (backboneeffort, 20, 0, 1e5, "effort in per mille") \ + OPTION (backbonemaxrounds, 1e3, 1, INT_MAX, "maximum backbone rounds") \ + OPTION (backbonerounds, 100, 1, INT_MAX, "backbone rounds limit") \ + OPTION (bigbigfraction, 990, 0, 1000, "big binary clause fraction per mille") \ + OPTION (bump, 1, 0, 1, "enable variable bumping") \ + OPTION (bumpreasons, 1, 0, 1, "bump reason side literals too") \ + OPTION (bumpreasonslimit, 10, 1, INT_MAX, "relative reason literals limit") \ + OPTION (bumpreasonsrate, 10, 1, INT_MAX, "decision rate limit") \ + DBGOPT (check, 2, 0, 2, "check model (1) and derived clauses (2)") \ + OPTION (chrono, 1, 0, 1, "allow chronological backtracking") \ + OPTION (chronolevels, 100, 0, INT_MAX, "maximum jumped over levels") \ + OPTION (compact, 1, 0, 1, "enable compacting garbage collection") \ + OPTION (compactlim, 10, 0, 100, "compact inactive limit (in percent)") \ + OPTION (congruence, 1, 0, 1, "congruence closure on extracted gates") \ + OPTION (congruenceandarity, 1000000, 2, 50000000, "AND gate arity limit") \ + OPTION (congruenceands, 1, 0, 1, "extract AND gates for congruence closure") \ + OPTION (congruencebinaries, 1, 0, 1, "extract certain binary clauses") \ + OPTION (congruenceites, 1, 0, 1, "extract ITE gates for congruence closure") \ + OPTION (congruenceonce, 0, 0, 1, "congruence closure only initially") \ + OPTION (congruencexorarity, 4, 2, 20, "congruence XOR gate arity limit") \ + OPTION (congruencexorcounts, 2, 1, INT_MAX, "XOR counting rounds") \ + OPTION (congruencexors, 1, 0, 1, "extract XOR gates for congruence closure") \ + OPTION (decay, 50, 1, 200, "per mille scores decay") \ + OPTION (definitioncores, 2, 1, 100, "how many cores") \ + OPTION (definitions, 1, 0, 1, "extract general definitions") \ + OPTION (definitionticks, 1e6, 0, INT_MAX, "kitten ticks limits") \ + OPTION (defraglim, 75, 50, 100, "usable defragmentation limit in percent") \ + OPTION (defragsize, 1 << 18, 10, INT_MAX, "size defragmentation limit") \ + OPTION (eagersubsume, 4, 0, 4, "eagerly subsume previous learned clauses") \ + OPTION (eliminate, 1, 0, 1, "bounded variable elimination BVE") \ + OPTION (eliminatebound, 16, 0, 1 << 13, "maximum elimination bound") \ + OPTION (eliminateclslim, 100, 1, INT_MAX, "elimination clause size limit") \ + OPTION (eliminateeffort, 100, 0, 2e3, "effort in per mille") \ + OPTION (eliminateinit, 500, 0, INT_MAX, "initial elimination interval") \ + OPTION (eliminateint, 500, 10, INT_MAX, "base elimination interval") \ + OPTION (eliminateocclim, 2e3, 0, INT_MAX, "elimination occurrence limit") \ + OPTION (eliminaterounds, 2, 1, 1e4, "elimination rounds limit") \ + OPTION (emafast, 33, 10, 1e6, "fast exponential moving average window") \ + OPTION (emaslow, 1e5, 100, 1e6, "slow exponential moving average window") \ + EMBOPT (embedded, 1, 0, 1, "parse and apply embedded options") \ + OPTION (equivalences, 1, 0, 1, "extract and eliminate equivalence gates") \ + OPTION (extract, 1, 0, 1, "extract gates in variable elimination") \ + OPTION (factor, 1, 0, 1, "bounded variable addition") \ + OPTION (factorcandrounds, 2, 0, INT_MAX, "candidates reduction rounds") \ + OPTION (factoreffort, 50, 0, 1e6, "bounded variable effort in per mille") \ + OPTION (factorhops, 3, 1, 10, "structural factoring heuristic hops") \ + OPTION (factoriniticks, 700, 1, 1000000, "initial ticks ticks in millions") \ + OPTION (factorsize, 5, 2, INT_MAX, "bounded variable addition clause size") \ + OPTION (factorstructural, 0, 0, 1, "structural bounded variable addition") \ + OPTION (fastel, 1, 0, 1, "initial fast variable elimination") \ + OPTION (fastelclslim, 100, 1, INT_MAX, "fast elimination clause length limit") \ + OPTION (fastelim, 8, 1, 1000, "fast elimination resolvents limit") \ + OPTION (fasteloccs, 100, 1, 1000, "fast elimination occurrence limit") \ + OPTION (fastelrounds, 4, 1, 1000, "fast elimination rounds") \ + OPTION (fastelsub, 1, 0, 1, "forward subsuming fast variable elimination") \ + OPTION (flushproof, 0, 0, 1, "flush proof lines immediately") \ + OPTION (focusedtiers, 1, 0, 1, "always used focused mode tiers") \ + OPTION (forcephase, 0, 0, 1, "force initial phase") \ + OPTION (forward, 1, 0, 1, "forward subsumption in BVE") \ + OPTION (forwardeffort, 100, 0, 1e6, "effort in per mille") \ + OPTION (ifthenelse, 1, 0, 1, "extract and eliminate if-then-else gates") \ + OPTION (incremental, 0, 0, 1, "enable incremental solving") \ + OPTION (jumpreasons, 1, 0, 1, "jump binary reasons") \ + LOGOPT (log, 0, 0, 5, "logging level (1=on,2=more,3=check,4/5=mem)") \ + OPTION (lucky, 1, 0, 1, "try some lucky assignments") \ + OPTION (luckyearly, 1, 0, 1, "lucky assignments before preprocessing") \ + OPTION (luckylate, 1, 0, 1, "lucky assignments after preprocessing") \ + OPTION (mineffort, 10, 0, INT_MAX, "minimum absolute effort in millions") \ + OPTION (minimize, 1, 0, 1, "learned clause minimization") \ + OPTION (minimizedepth, 1e3, 1, 1e6, "minimization depth") \ + OPTION (minimizeticks, 1, 0, 1, "count ticks in minimize and shrink") \ + OPTION (modeinit, 1e3, 10, 1e8, "initial focused conflicts limit") \ + OPTION (modeint, 1e3, 10, 1e8, "focused conflicts interval") \ + OPTION (otfs, 1, 0, 1, "on-the-fly strengthening") \ + OPTION (phase, 1, 0, 1, "initial decision phase") \ + OPTION (phasesaving, 1, 0, 1, "enable phase saving") \ + OPTION (preprocess, 1, 0, 1, "initial preprocessing") \ + OPTION (preprocessbackbone, 1, 0, 1, "backbone preprocessing") \ + OPTION (preprocesscongruence, 1, 0, 1, "congruence preprocessing") \ + OPTION (preprocessfactor, 1, 0, 1, "variable addition preprocessing") \ + OPTION (preprocessprobe, 1, 0, 1, "probing preprocessing") \ + OPTION (preprocessrounds, 1, 1, INT_MAX, "initial preprocessing rounds") \ + OPTION (preprocessweep, 1, 0, 1, "sweep preprocessing") \ + OPTION (probe, 1, 0, 1, "enable probing") \ + OPTION (probeinit, 100, 0, INT_MAX, "initial probing interval") \ + OPTION (probeint, 100, 2, INT_MAX, "probing interval") \ + OPTION (proberounds, 2, 1, INT_MAX, "probing rounds") \ + NQTOPT (profile, 2, 0, 4, "profile level") \ + OPTION (promote, 1, 0, 1, "promote clauses") \ + NQTOPT (quiet, 0, 0, 1, "disable all messages") \ + OPTION (randec, 1, 0, 1, "random decisions") \ + OPTION (randecfocused, 1, 0, 1, "random decisions in focused mode") \ + OPTION (randecinit, 500, 0, INT_MAX, "random decisions interval") \ + OPTION (randecint, 500, 0, INT_MAX, "initial random decisions interval") \ + OPTION (randeclength, 10, 1, INT_MAX, "random conflicts length") \ + OPTION (randecstable, 0, 0, 1, "random decisions in stable mode") \ + OPTION (reduce, 1, 0, 1, "learned clause reduction") \ + OPTION (reducehigh, 900, 0, 1000, "high reduce fraction per mille") \ + OPTION (reduceinit, 1e3, 2, 1e5, "initial reduce interval") \ + OPTION (reduceint, 1e3, 2, 1e5, "base reduce interval") \ + OPTION (reducelow, 500, 0, 1000, "low reduce fraction per mille") \ + OPTION (reluctant, 1, 0, 1, "stable reluctant doubling restarting") \ + OPTION (reluctantint, 1 << 10, 2, 1 << 15, "reluctant interval") \ + OPTION (reluctantlim, 1 << 20, 0, 1 << 30, "reluctant limit (0=unlimited)") \ + OPTION (reorder, 2, 0, 2, "reorder decisions (1=stable-mode-only)") \ + OPTION (reorderinit, 1e4, 0, 1e5, "initial reorder interval") \ + OPTION (reorderint, 1e4, 1, 1e5, "base reorder interval") \ + OPTION (reordermaxsize, 100, 2, 256, "reorder maximum clause size") \ + OPTION (rephase, 1, 0, 1, "reinitialization of decision phases") \ + OPTION (rephaseinit, 1e3, 10, 1e5, "initial rephase interval") \ + OPTION (rephaseint, 1e3, 10, 1e5, "base rephase interval") \ + OPTION (restart, 1, 0, 1, "enable restarts") \ + OPTION (restartint, RESTARTINT_DEFAULT, 1, 1e4, "base restart interval") \ + OPTION (restartmargin, 10, 0, 25, "fast/slow margin in percent") \ + OPTION (restartreusetrail, 1, 0, 1, "restarts tries to reuse trail") \ + OPTION (seed, 0, 0, INT_MAX, "random seed") \ + OPTION (shrink, 3, 0, 3, "learned clauses (1=bin,2=lrg,3=rec)") \ + OPTION (simplify, 1, 0, 1, "enable probing and elimination") \ + OPTION (smallclauses, 1e5, 0, INT_MAX, "small clauses limit") \ + OPTION (stable, STABLE_DEFAULT, 0, 2, "enable stable search mode") \ + NQTOPT (statistics, 0, 0, 1, "print complete statistics") \ + OPTION (substitute, 1, 0, 1, "equivalent literal substitution") \ + OPTION (substituteeffort, 10, 1, 1e3, "effort in per mille") \ + OPTION (substituterounds, 2, 1, 100, "maximum substitution rounds") \ + OPTION (subsumeclslim, 1e3, 1, INT_MAX, "subsumption clause size limit") \ + OPTION (subsumeocclim, 1e3, 0, INT_MAX, "subsumption occurrence limit") \ + OPTION (sweep, 1, 0, 1, "enable SAT sweeping") \ + OPTION (sweepclauses, 1024, 0, INT_MAX, "environment clauses") \ + OPTION (sweepcomplete, 0, 0, 1, "run SAT sweeping until completion") \ + OPTION (sweepdepth, 2, 0, INT_MAX, "environment depth") \ + OPTION (sweepeffort, 100, 0, 1e4, "effort in per mille") \ + OPTION (sweepfliprounds, 1, 0, INT_MAX, "flipping rounds") \ + OPTION (sweepmaxclauses, 32768, 2, INT_MAX, "maximum environment clauses") \ + OPTION (sweepmaxdepth, 3, 1, INT_MAX, "maximum environment depth") \ + OPTION (sweepmaxvars, 8192, 2, INT_MAX, "maximum environment variables") \ + OPTION (sweeprand, 0, 0, 1, "randomize sweeping environment") \ + OPTION (sweepvars, 256, 0, INT_MAX, "environment variables") \ + OPTION (target, TARGET_DEFAULT, 0, 2, "target phases (1=stable,2=focused)") \ + OPTION (tier1, 2, 1, 100, "learned clause tier one glue limit") \ + OPTION (tier1relative, 500, 0, 1000, "relative tier one glue limit") \ + OPTION (tier2, 6, 1, 1e3, "learned clause tier two glue limit") \ + OPTION (tier2relative, 900, 0, 1000, "relative tier two glue limit") \ + OPTION (transitive, 1, 0, 1, "transitive reduction of binary clauses") \ + OPTION (transitiveeffort, 20, 0, 2e3, "effort in per mille") \ + OPTION (transitivekeep, 1, 0, 1, "keep transitivity candidates") \ + OPTION (tumble, 1, 0, 1, "tumbled external indices order") \ + NQTOPT (verbose, 0, 0, 3, "verbosity level") \ + OPTION (vivify, 1, 0, 1, "vivify clauses") \ + OPTION (vivifyeffort, 100, 0, 1e3, "effort in per mille") \ + OPTION (vivifyfocusedtiers, 1, 0, 1, "use focused tier limits") \ + OPTION (vivifyirr, 3, 0, 100, "relative irredundant effort") \ + OPTION (vivifysort, 1, 0, 1, "sort vivification candidates") \ + OPTION (vivifytier1, 3, 0, 100, "relative tier1 effort") \ + OPTION (vivifytier2, 3, 0, 100, "relative tier2 effort") \ + OPTION (vivifytier3, 1, 0, 100, "relative tier3 effort") \ + OPTION (walkeffort, 50, 0, 1e6, "effort in per mille") \ + OPTION (walkinitially, 0, 0, 1, "initial local search") \ + OPTION (warmup, 1, 0, 1, "initialize phases by unit propagation") + +// clang-format on + +#define TARGET_SAT 2 +#define TARGET_DEFAULT 1 + +#define STABLE_DEFAULT 1 +#define STABLE_UNSAT 0 + +#define RESTARTINT_DEFAULT 1 +#define RESTARTINT_SAT 50 + +#ifdef SAT +#undef TARGET_DEFAULT +#define TARGET_DEFAULT TARGET_SAT +#undef RESTARTINT_DEFAULT +#define RESTARTINT_DEFAULT RESTARTINT_SAT +#endif + +#ifdef UNSAT +#undef STABLE_DEFAULT +#define STABLE_DEFAULT STABLE_UNSAT +#endif + +#if defined(LOGGING) && !defined(QUIET) +#define LOGOPT OPTION +#else +#define LOGOPT(...) /**/ +#endif + +#ifndef QUIET +#define NQTOPT OPTION +#else +#define NQTOPT(...) /**/ +#endif + +#ifndef NDEBUG +#define DBGOPT OPTION +#else +#define DBGOPT(...) /**/ +#endif + +#ifdef EMBEDDED +#define EMBOPT OPTION +#else +#define EMBOPT(...) /**/ +#endif + +// clang-format on + +#define TIER1RELATIVE (GET_OPTION (tier1relative) / 1000.0) +#define TIER2RELATIVE (GET_OPTION (tier2relative) / 1000.0) + +typedef struct opt opt; + +struct opt { + const char *name; +#ifndef NOPTIONS + int value; + const int low; + const int high; +#else + const int value; +#endif + const char *description; +}; + +extern const opt *kissat_options_begin; +extern const opt *kissat_options_end; + +#define all_options(O) \ + opt const *O = kissat_options_begin; \ + O != kissat_options_end; \ + ++O + +const char *kissat_parse_option_name (const char *arg, const char *name); +bool kissat_parse_option_value (const char *val_str, int *res_ptr); + +#ifndef NOPTIONS + +void kissat_options_usage (void); + +const opt *kissat_options_has (const char *name); + +#define kissat_options_max_name_buffer_size ((size_t) 32) + +bool kissat_options_parse_arg (const char *arg, char *name, int *val_str); +void kissat_options_print_value (int value, char *buffer); + +typedef struct options options; + +struct options { +#define OPTION(N, V, L, H, D) int N; + OPTIONS +#undef OPTION +}; + +void kissat_init_options (options *); + +int kissat_options_get (const options *, const char *name); +int kissat_options_set_opt (options *, const opt *, int new_value); +int kissat_options_set (options *, const char *name, int new_value); + +void kissat_print_embedded_option_list (void); +void kissat_print_option_range_list (void); + +static inline int *kissat_options_ref (const options *options, + const opt *o) { + if (!o) + return 0; + assert (kissat_options_begin <= o); + assert (o < kissat_options_end); + return (int *) options + (o - kissat_options_begin); +} + +#define GET_OPTION(NAME) ((int) solver->options.NAME) + +#else + +void kissat_init_options (void); +int kissat_options_get (const char *name); + +#define GET_OPTION(N) kissat_options_##N + +#define OPTION(N, V, L, H, D) static const int GET_OPTION (N) = (int) (V); +OPTIONS +#undef OPTION +#endif +#define GET1K_OPTION(NAME) (((int64_t) 1000) * GET_OPTION (NAME)) +#endif diff --git a/src/sat/kissat/parse.h b/src/sat/kissat/parse.h new file mode 100644 index 000000000..665371259 --- /dev/null +++ b/src/sat/kissat/parse.h @@ -0,0 +1,19 @@ +#ifndef _parse_h_INCLUDED +#define _parse_h_INCLUDED + +#include "file.h" + +enum strictness { + RELAXED_PARSING = 0, + NORMAL_PARSING = 1, + PEDANTIC_PARSING = 2, +}; + +typedef enum strictness strictness; + +struct kissat; + +const char *kissat_parse_dimacs (struct kissat *, strictness, file *, + uint64_t *linenoptr, int *max_var_ptr); + +#endif diff --git a/src/sat/kissat/phases.c b/src/sat/kissat/phases.c new file mode 100644 index 000000000..5fb84c20d --- /dev/null +++ b/src/sat/kissat/phases.c @@ -0,0 +1,68 @@ +#include "allocate.h" +#include "internal.h" +#include "logging.h" + +#include + +#define realloc_phases(NAME) \ + do { \ + solver->phases.NAME = \ + kissat_realloc (solver, solver->phases.NAME, old_size, new_size); \ + } while (0) + +#define increase_phases(NAME) \ + do { \ + assert (old_size < new_size); \ + realloc_phases (NAME); \ + memset (solver->phases.NAME + old_size, 0, new_size - old_size); \ + } while (0) + +void kissat_increase_phases (kissat *solver, unsigned new_size) { + const unsigned old_size = solver->size; + assert (old_size < new_size); + LOG ("increasing phases from %u to %u", old_size, new_size); + increase_phases (best); + increase_phases (saved); + increase_phases (target); +} + +void kissat_decrease_phases (kissat *solver, unsigned new_size) { + const unsigned old_size = solver->size; + assert (old_size > new_size); + LOG ("decreasing phases from %u to %u", old_size, new_size); + realloc_phases (best); + realloc_phases (saved); + realloc_phases (target); +} + +#define release_phases(NAME, SIZE) \ + kissat_free (solver, solver->phases.NAME, SIZE) + +void kissat_release_phases (kissat *solver) { + const unsigned size = solver->size; + release_phases (best, size); + release_phases (saved, size); + release_phases (target, size); +} + +static void save_phases (kissat *solver, value *phases) { + const value *const values = solver->values; + const value *const end = phases + VARS; + value const *v = values; + for (value *p = phases, tmp; p != end; p++, v += 2) + if ((tmp = *v)) + *p = tmp; + assert (v == values + LITS); +} + +void kissat_save_best_phases (kissat *solver) { + assert (sizeof (value) == 1); + LOG ("saving %u best values", VARS); + save_phases (solver, solver->phases.best); +} + +void kissat_save_target_phases (kissat *solver) { + assert (sizeof (value) == 1); + LOG ("saving %u target values", VARS); + save_phases (solver, solver->phases.target); +} diff --git a/src/sat/kissat/phases.h b/src/sat/kissat/phases.h new file mode 100644 index 000000000..4a2456fec --- /dev/null +++ b/src/sat/kissat/phases.h @@ -0,0 +1,32 @@ +#ifndef _phases_h_INCLUDED +#define _phases_h_INCLUDED + +#include "value.h" + +typedef struct phases phases; + +struct phases { + value *best; + value *saved; + value *target; +}; + +#define BEST(IDX) \ + (solver->phases.best[assert (VALID_INTERNAL_INDEX (IDX)), (IDX)]) + +#define SAVED(IDX) \ + (solver->phases.saved[assert (VALID_INTERNAL_INDEX (IDX)), (IDX)]) + +#define TARGET(IDX) \ + (solver->phases.target[assert (VALID_INTERNAL_INDEX (IDX)), (IDX)]) + +struct kissat; + +void kissat_increase_phases (struct kissat *, unsigned); +void kissat_decrease_phases (struct kissat *, unsigned); +void kissat_release_phases (struct kissat *); + +void kissat_save_best_phases (struct kissat *); +void kissat_save_target_phases (struct kissat *); + +#endif diff --git a/src/sat/kissat/preprocess.c b/src/sat/kissat/preprocess.c new file mode 100644 index 000000000..96a0beee0 --- /dev/null +++ b/src/sat/kissat/preprocess.c @@ -0,0 +1,178 @@ +#include "preprocess.h" +#include "collect.h" +#include "fastel.h" +#include "internal.h" +#include "print.h" +#include "probe.h" +#include "propinitially.h" +#include "report.h" +#include "sweep.h" +#include "terminate.h" + +#include + +bool kissat_preprocessing (struct kissat *solver) { + assert (!solver->level); + assert (!solver->inconsistent); + if (!GET_OPTION (preprocess)) + return false; + if (!GET_OPTION (probe)) + return false; + if (!GET_OPTION (preprocessprobe)) + return false; +#if defined(NDEBUG) && defined(NOPTIONS) + (void) solver; +#endif + return true; +} + +int kissat_preprocess (struct kissat *solver) { + assert (kissat_preprocessing (solver)); + if (!kissat_initially_propagate (solver)) { + assert (solver->inconsistent); + return 20; + } + START (preprocess); + assert (!solver->preprocessing); + solver->preprocessing = true; + REPORT (0, '('); + const unsigned max_rounds = GET_OPTION (preprocessrounds); +#ifndef QUIET + const unsigned variables_initially = solver->active; + const uint64_t clauses_initially = BINIRR_CLAUSES; + const unsigned variables_originally = SIZE_STACK (solver->import); + const uint64_t clauses_originally = solver->statistics.clauses_original; + kissat_verbose (solver, + "[preprocess] running at most %u preprocesing rounds", + max_rounds); + kissat_verbose ( + solver, + "[preprocess] initially %u variables %.0f%% and %" PRIu64 + " clauses %.0f%%", + variables_initially, + kissat_percent (variables_initially, variables_originally), + clauses_initially, + kissat_percent (clauses_initially, clauses_originally)); +#endif + kissat_initial_sparse_collect (solver); + unsigned round = 1; + for (;;) { + if (solver->inconsistent) + break; + if (TERMINATED (preprocess_terminated_1)) + break; + const unsigned variables_before = solver->active; + const uint64_t clauses_before = BINIRR_CLAUSES; +#ifndef QUIET + kissat_verbose (solver, + "[preprocess-%u] started preprocessing round %u", round, + round); +#endif + if (GET_OPTION (preprocessprobe)) + kissat_probe_initially (solver); + if (GET_OPTION (fastel)) + kissat_fast_variable_elimination (solver); + const unsigned variables_after = solver->active; + const uint64_t clauses_after = BINIRR_CLAUSES; +#ifndef QUIET + if (variables_after < variables_before) { + unsigned removed = variables_before - variables_after; + kissat_verbose ( + solver, "[preprocess-%u] removed %u variables %.0f%% in round %u", + round, removed, kissat_percent (removed, variables_before), + round); + } else if (variables_after > variables_before) { + unsigned added = variables_after - variables_before; + kissat_verbose ( + solver, "[preprocess-%u] added %u variables %.0f%% in round %u", + round, added, kissat_percent (added, variables_before), round); + } else + kissat_verbose ( + solver, + "[preprocess-%u] number variables %u unchanged in round %u", + round, variables_before, round); + if (clauses_after < clauses_before) { + uint64_t removed = clauses_before - clauses_after; + kissat_verbose (solver, + "[preprocess-%u] removed %" PRIu64 + " irredundant and binary clauses %.0f%% in round %u", + round, removed, + kissat_percent (removed, clauses_before), round); + } else if (clauses_after > clauses_before) { + uint64_t added = clauses_after - clauses_before; + kissat_verbose (solver, + "[preprocess-%u] added %" PRIu64 + " irredundant and binary clauses %.0f%% in round %u", + round, added, kissat_percent (added, clauses_before), + round); + } else + kissat_verbose ( + solver, + "[preprocess-%u] number irredundant and binary clauses %" PRIu64 + " unchanged in round %u", + round, clauses_before, round); +#endif + if (round == max_rounds) + break; + if (variables_before == variables_after && + clauses_before == clauses_after) + break; + round++; + } +#ifndef QUIET + const unsigned variables_finally = solver->active; + const uint64_t clauses_finally = BINIRR_CLAUSES; + kissat_verbose (solver, "[preprocess] finished after %u rounds", round); + if (variables_finally < variables_initially) { + unsigned removed = variables_initially - variables_finally; + kissat_verbose ( + solver, + "[preprocess] removed %u variables %.0f%% (%u remain %.0f%%)", + removed, kissat_percent (removed, variables_initially), + variables_finally, + kissat_percent (variables_finally, variables_originally)); + } else if (variables_finally > variables_initially) { + unsigned added = variables_finally - variables_initially; + kissat_verbose ( + solver, "[preprocess] added %u variables %.0f%% (%u remain %.0f%%)", + added, kissat_percent (added, variables_initially), + variables_finally, + kissat_percent (variables_finally, variables_originally)); + } else + kissat_verbose ( + solver, + "[preprocess] number variables %u unchanged (%u remain %.0f%%)", + variables_initially, variables_finally, + kissat_percent (variables_finally, variables_originally)); + if (clauses_finally < clauses_initially) { + uint64_t removed = clauses_initially - clauses_finally; + kissat_verbose (solver, + "[preprocess] removed %" PRIu64 + " irredundant and binary clauses %.0f%% (%" PRIu64 + " remain %.0f%%)", + removed, kissat_percent (removed, clauses_initially), + clauses_finally, + kissat_percent (clauses_finally, clauses_originally)); + } else if (clauses_finally > clauses_initially) { + uint64_t added = clauses_finally - clauses_initially; + kissat_verbose (solver, + "[preprocess] added %" PRIu64 + " irredundant and binary clauses %.0f%% (%" PRIu64 + " remain %.0f%%)", + added, kissat_percent (added, clauses_initially), + clauses_finally, + kissat_percent (clauses_finally, clauses_originally)); + } else + kissat_verbose ( + solver, + "[preprocess] number irredundant and binary clauses %" PRIu64 + " unchanged (%" PRIu64 " remain %.0f%%)", + clauses_initially, clauses_finally, + kissat_percent (clauses_finally, clauses_originally)); +#endif + REPORT (0, ')'); + assert (solver->preprocessing); + solver->preprocessing = false; + STOP (preprocess); + return solver->inconsistent ? 20 : 0; +} diff --git a/src/sat/kissat/preprocess.h b/src/sat/kissat/preprocess.h new file mode 100644 index 000000000..056b5ecde --- /dev/null +++ b/src/sat/kissat/preprocess.h @@ -0,0 +1,10 @@ +#ifndef _kissat_preprocess_h_INCLUDED +#define _kissat_preprocess_h_INCLUDED + +#include + +struct kissat; +bool kissat_preprocessing (struct kissat *); +int kissat_preprocess (struct kissat *); + +#endif diff --git a/src/sat/kissat/print.c b/src/sat/kissat/print.c new file mode 100644 index 000000000..454b8f343 --- /dev/null +++ b/src/sat/kissat/print.c @@ -0,0 +1,172 @@ +#ifndef QUIET + +#include "print.h" +#include "colors.h" +#include "handle.h" +#include "internal.h" + +#include +#include +#include + +static inline int verbosity (kissat *solver) { + if (!solver) + return -1; +#ifdef LOGGING + if (GET_OPTION (log)) + return 3; +#endif +#ifndef QUIET + if (GET_OPTION (quiet)) + return -1; + return GET_OPTION (verbose); +#else + return 0; +#endif +} + +void kissat_warning (kissat *solver, const char *fmt, ...) { + if (verbosity (solver) < 0) + return; + TERMINAL (stdout, 1); + fputs (solver->prefix, stdout); + COLOR (BOLD YELLOW); + fputs ("warning:", stdout); + COLOR (NORMAL); + fputc (' ', stdout); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); +#ifdef NOPTIONS + (void) solver; +#endif +} + +void kissat_signal (kissat *solver, const char *type, int sig) { + if (verbosity (solver) < 0) + return; + TERMINAL (stdout, 1); + fputs (solver->prefix, stdout); + COLOR (BOLD RED); + printf ("%s signal %d (%s)", type, sig, kissat_signal_name (sig)); + COLOR (NORMAL); + fputc ('\n', stdout); + fflush (stdout); +} + +static void print_message (kissat *solver, const char *color, + const char *fmt, va_list *ap) { + TERMINAL (stdout, 1); + fputs (solver->prefix, stdout); + COLOR (color); + vprintf (fmt, *ap); + fputc ('\n', stdout); + COLOR (NORMAL); + fflush (stdout); +} + +static void print_line (kissat *solver) { + char ch; + for (const char *p = solver->prefix; (ch = *p); p++) + if (ch && (ch != ' ' || p[1])) + fputc (ch, stdout); + fputc ('\n', stdout); + fflush (stdout); +} + +int kissat_verbosity (kissat *solver) { return verbosity (solver); } + +void kissat_message (kissat *solver, const char *fmt, ...) { + if (verbosity (solver) < 0) + return; + va_list ap; + va_start (ap, fmt); + print_message (solver, "", fmt, &ap); + va_end (ap); +} + +void kissat_line (kissat *solver) { + if (verbosity (solver) >= 0) + print_line (solver); +} + +void kissat_prefix (kissat *solver) { fputs (solver->prefix, stdout); } + +void kissat_verbose (kissat *solver, const char *fmt, ...) { + if (verbosity (solver) < 1) + return; + va_list ap; + va_start (ap, fmt); + print_message (solver, LIGHT_GRAY, fmt, &ap); + va_end (ap); +} + +void kissat_very_verbose (kissat *solver, const char *fmt, ...) { + if (verbosity (solver) < 2) + return; + va_list ap; + va_start (ap, fmt); + print_message (solver, DARK_GRAY, fmt, &ap); + va_end (ap); +} + +void kissat_extremely_verbose (kissat *solver, const char *fmt, ...) { + if (verbosity (solver) < 3) + return; + va_list ap; + va_start (ap, fmt); + print_message (solver, DARK_GRAY, fmt, &ap); + va_end (ap); +} + +void kissat_section (kissat *solver, const char *name) { + if (verbosity (solver) < 0) + return; + TERMINAL (stdout, 1); + if (solver->sectioned) + kissat_line (solver); + else + solver->sectioned = true; + fputs (solver->prefix, stdout); + COLOR (BLUE); + fputs ("---- [ ", stdout); + COLOR (BOLD BLUE); + fputs (name, stdout); + COLOR (NORMAL BLUE); + fputs (" ] ", stdout); + for (size_t i = strlen (name); i < 66; i++) + fputc ('-', stdout); + COLOR (NORMAL); + fputc ('\n', stdout); + print_line (solver); + fflush (stdout); +} + +void kissat_phase (kissat *solver, const char *name, uint64_t count, + const char *fmt, ...) { + if (verbosity (solver) < 1) + return; + TERMINAL (stdout, 1); + fputs (solver->prefix, stdout); + if (solver->stable) + COLOR (CYAN); + else + COLOR (BOLD CYAN); + printf ("[%s", name); + if (count != UINT64_MAX) + printf ("-%" PRIu64, count); + fputs ("] ", stdout); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + COLOR (NORMAL); + fputc ('\n', stdout); + fflush (stdout); +} + +#else +int kissat_print_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/print.h b/src/sat/kissat/print.h new file mode 100644 index 000000000..d6f8a329f --- /dev/null +++ b/src/sat/kissat/print.h @@ -0,0 +1,65 @@ +// clang-format off + +#ifndef _print_h_INCLUDED +#define _print_h_INCLUDED + +#ifndef QUIET + +#include + +#include "attribute.h" + +struct kissat; + +int kissat_verbosity (struct kissat *); + +void kissat_line (struct kissat *); +void kissat_prefix (struct kissat*); +void kissat_signal (struct kissat *, const char *type, int sig); +void kissat_section (struct kissat *, const char *name); + +void +kissat_message (struct kissat *, const char *fmt, ...) +ATTRIBUTE_FORMAT (2, 3); + +void kissat_verbose (struct kissat *, const char *fmt, ...) +ATTRIBUTE_FORMAT (2, 3); + +void kissat_very_verbose (struct kissat *, const char *fmt, ...) +ATTRIBUTE_FORMAT (2, 3); + +void kissat_extremely_verbose (struct kissat *, const char *fmt, ...) +ATTRIBUTE_FORMAT (2, 3); + +void kissat_warning (struct kissat *, const char *fmt, ...) +ATTRIBUTE_FORMAT (2, 3); + +void kissat_phase (struct kissat *, const char *name, uint64_t, + const char * fmt, ...) +ATTRIBUTE_FORMAT (4, 5); + +#else + +#define kissat_line(...) do { } while (0) +#define kissat_message(...) do { } while (0) +#define kissat_phase(...) do { } while (0) +#define kissat_section(...) do { } while (0) +#define kissat_signal(...) do { } while (0) +#define kissat_verbose(...) do { } while (0) +#define kissat_very_verbose(...) do { } while (0) +#define kissat_extremely_verbose(...) do { } while (0) +#define kissat_warning(...) do { } while (0) + +#endif + +#define VERY_VERBOSE_OR_LOG(ONLY_LOG, SOLVER, ...) \ +do { \ + if (ONLY_LOG) \ + LOG (__VA_ARGS__); \ + else \ + kissat_very_verbose (SOLVER, __VA_ARGS__); \ +} while (0) + +#endif + +// clang-format on diff --git a/src/sat/kissat/probe.c b/src/sat/kissat/probe.c new file mode 100644 index 000000000..606710577 --- /dev/null +++ b/src/sat/kissat/probe.c @@ -0,0 +1,104 @@ +#include "probe.h" +#include "backbone.h" +#include "backtrack.h" +#include "congruence.h" +#include "factor.h" +#include "internal.h" +#include "print.h" +#include "substitute.h" +#include "sweep.h" +#include "terminate.h" +#include "transitive.h" +#include "vivify.h" + +#include + +bool kissat_probing (kissat *solver) { + if (!solver->enabled.probe) + return false; + statistics *statistics = &solver->statistics; + const uint64_t conflicts = statistics->conflicts; + if (solver->last.conflicts.reduce == conflicts) + return false; + return solver->limits.probe.conflicts < conflicts; +} + +static void probe (kissat *solver) { + kissat_backtrack_propagate_and_flush_trail (solver); + assert (!solver->inconsistent); + STOP_SEARCH_AND_START_SIMPLIFIER (probe); + kissat_phase (solver, "probe", GET (probings), + "probing limit hit after %" PRIu64 " conflicts", + solver->limits.probe.conflicts); + kissat_congruence (solver); + kissat_substitute (solver, false); + kissat_binary_clauses_backbone (solver); + kissat_vivify (solver); + kissat_sweep (solver); + kissat_substitute (solver, false); + kissat_transitive_reduction (solver); + kissat_binary_clauses_backbone (solver); + kissat_factor (solver); + STOP_SIMPLIFIER_AND_RESUME_SEARCH (probe); +} + +static void probe_initially (kissat *solver) { + assert (!solver->level); + assert (!solver->inconsistent); + kissat_phase (solver, "probe", GET (probings), "initial probing"); + bool substitute_at_the_end = true; + if (GET_OPTION (preprocesscongruence)) { + if (kissat_congruence (solver)) { + kissat_substitute (solver, true); + substitute_at_the_end = false; + } + } + if (GET_OPTION (preprocessbackbone)) + kissat_binary_clauses_backbone (solver); + if (GET_OPTION (preprocessweep)) { + if (kissat_sweep (solver)) { + kissat_substitute (solver, true); + substitute_at_the_end = false; + } + } + if (substitute_at_the_end) + kissat_substitute (solver, false); + if (GET_OPTION (preprocessfactor)) + kissat_factor (solver); +} + +int kissat_probe (kissat *solver) { + assert (!solver->inconsistent); + INC (probings); + assert (!solver->probing); + solver->probing = true; + const unsigned max_rounds = GET_OPTION (proberounds); + for (unsigned round = 0; round != max_rounds; round++) { + unsigned before = solver->active; + probe (solver); + if (solver->inconsistent) + break; + if (before == solver->active) + break; + } + kissat_classify (solver); + UPDATE_CONFLICT_LIMIT (probe, probings, NLOGN, true); + solver->last.ticks.probe = solver->statistics.search_ticks; + assert (solver->probing); + solver->probing = false; + return solver->inconsistent ? 20 : 0; +} + +int kissat_probe_initially (kissat *solver) { + assert (!solver->level); + assert (!solver->inconsistent); + INC (probings); + START (probe); + assert (!solver->probing); + solver->probing = true; + probe_initially (solver); + assert (solver->probing); + solver->probing = false; + STOP (probe); + return solver->inconsistent ? 20 : 0; +} diff --git a/src/sat/kissat/probe.h b/src/sat/kissat/probe.h new file mode 100644 index 000000000..a788918f3 --- /dev/null +++ b/src/sat/kissat/probe.h @@ -0,0 +1,12 @@ +#ifndef _probe_h_INCLUDED +#define _probe_h_INCLUDED + +#include + +struct kissat; + +bool kissat_probing (struct kissat *); +int kissat_probe (struct kissat *); +int kissat_probe_initially (struct kissat *); + +#endif diff --git a/src/sat/kissat/profile.c b/src/sat/kissat/profile.c new file mode 100644 index 000000000..01cd65573 --- /dev/null +++ b/src/sat/kissat/profile.c @@ -0,0 +1,148 @@ +#ifndef QUIET + +#include "internal.h" +#include "resources.h" +#include "sort.h" + +#include +#include + +void kissat_init_profiles (profiles *profiles) { +#define PROF(NAME, LEVEL) profiles->NAME = (profile){LEVEL, #NAME, 0, 0}; + PROFS +#undef PROF +} + +#define SIZE_PROFS (sizeof (profiles) / sizeof (profile)) + +static inline bool less_profile (profile *p, profile *q) { + if (p->time > q->time) + return true; + if (p->time < q->time) + return false; + return strcmp (p->name, q->name) < 0; +} + +static void print_profile (kissat *solver, profile *p, double total) { + printf ("%s%14.2f %7.2f %% %s\n", solver->prefix, p->time, + kissat_percent (p->time, total), p->name); +} + +static double flush_profile (profile *profile, double now) { + const double delta = now - profile->entered; + profile->time += delta; + profile->entered = now; + return delta; +} + +static void flush_profiles (profiles *profiles, const double now) { + for (all_pointers (profile, p, profiles->stack)) + flush_profile (p, now); +} + +static void push_profile (kissat *solver, profile *profile, double now) { + profile->entered = now; + PUSH_STACK (solver->profiles.stack, profile); +} + +void kissat_profiles_print (kissat *solver) { + profiles *named = &solver->profiles; + double now = kissat_process_time (); + flush_profiles (named, now); + profile *unsorted = (profile *) named; + profile *sorted[SIZE_PROFS]; + const profile *const end = unsorted + SIZE_PROFS; + size_t size = 0; + for (profile *p = unsorted; p != end; p++) + if (p->level <= GET_OPTION (profile) && + (p == &named->search || p == &named->simplify || + (p != &named->total && p->time))) + sorted[size++] = p; + INSERTION_SORT (profile *, size, sorted, less_profile); + const double total = named->total.time; + for (size_t i = 0; i < size; i++) + print_profile (solver, sorted[i], total); + printf ("%s=============================================\n", + solver->prefix); + print_profile (solver, &named->total, total); +} + +void kissat_start (kissat *solver, profile *profile) { + const double now = kissat_process_time (); + push_profile (solver, profile, now); +} + +void kissat_stop (kissat *solver, profile *profile) { + assert (TOP_STACK (solver->profiles.stack) == profile); + (void) POP_STACK (solver->profiles.stack); + const double now = kissat_process_time (); + flush_profile (profile, now); +} + +void kissat_stop_search_and_start_simplifier (kissat *solver, + profile *profile) { + struct profile *search = &PROFILE (search); + assert (search->level <= GET_OPTION (profile)); + const double now = kissat_process_time (); + while (TOP_STACK (solver->profiles.stack) != search) { + struct profile *mode = POP_STACK (solver->profiles.stack); + assert (search->level <= mode->level); +#ifndef NDEBUG + if (solver->stable) + assert (mode == &PROFILE (stable)); + else + assert (mode == &PROFILE (focused)); +#endif + flush_profile (mode, now); + } + (void) POP_STACK (solver->profiles.stack); + struct profile *simplify = &PROFILE (simplify); + assert (search->level == simplify->level); + assert (simplify->level <= profile->level); + flush_profile (search, now); + push_profile (solver, simplify, now); + if (profile->level <= GET_OPTION (profile)) + push_profile (solver, profile, now); +} + +void kissat_stop_simplifier_and_resume_search (kissat *solver, + profile *profile) { + struct profile *simplify = &PROFILE (simplify); + struct profile *top = POP_STACK (solver->profiles.stack); + const double now = kissat_process_time (); + const double delta = flush_profile (simplify, now); +#ifndef NDEBUG + const double entered = now - delta; + assert (solver->mode.entered <= entered); +#endif + solver->mode.entered += delta; + if (top == profile) { + flush_profile (profile, now); + assert (TOP_STACK (solver->profiles.stack) == simplify); + (void) POP_STACK (solver->profiles.stack); + } else { + assert (simplify == top); + assert (profile->level > GET_OPTION (profile)); + } +#ifndef NDEBUG + struct profile *search = &PROFILE (search); + assert (search->level == simplify->level); +#endif + assert (simplify->level <= profile->level); + push_profile (solver, &PROFILE (search), now); + struct profile *mode = + solver->stable ? &PROFILE (stable) : &PROFILE (focused); + assert (search->level <= mode->level); + if (mode->level <= GET_OPTION (profile)) + push_profile (solver, mode, now); +} + +double kissat_time (kissat *solver) { + const double now = kissat_process_time (); + flush_profiles (&solver->profiles, now); + return PROFILE (total).time; +} + +#else +int kissat_profile_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/profile.h b/src/sat/kissat/profile.h new file mode 100644 index 000000000..62d03cf71 --- /dev/null +++ b/src/sat/kissat/profile.h @@ -0,0 +1,143 @@ +#ifndef _profile_h_INCLUDED +#define _profile_h_INCLUDED + +#ifndef QUIET + +#include "stack.h" + +typedef struct profile profile; +typedef struct profiles profiles; + +#define PROFS \ + PROF (analyze, 3) \ + PROF (backbone, 2) \ + PROF (bump, 3) \ + PROF (collect, 3) \ + PROF (congruence, 2) \ + PROF (decide, 4) \ + PROF (deduce, 3) \ + PROF (definition, 3) \ + PROF (defrag, 3) \ + PROF (dominate, 4) \ + PROF (eliminate, 2) \ + PROF (extend, 2) \ + PROF (extract, 3) \ + PROF (extractands, 3) \ + PROF (extractbinaries, 3) \ + PROF (extractites, 3) \ + PROF (extractxors, 3) \ + PROF (factor, 2) \ + PROF (fastel, 2) \ + PROF (focused, 2) \ + PROF (forward, 4) \ + PROF (lucky, 2) \ + PROF (matching, 3) \ + PROF (merge, 3) \ + PROF (minimize, 3) \ + PROF (parse, 1) \ + PROF (preprocess, 2) \ + PROF (probe, 2) \ + PROF (propagate, 4) \ + PROF (radix, 4) \ + PROF (reduce, 2) \ + PROF (reorder, 3) \ + PROF (rephase, 3) \ + PROF (restart, 3) \ + PROF (search, 1) \ + PROF (shrink, 3) \ + PROF (simplify, 1) \ + PROF (sort, 4) \ + PROF (stable, 2) \ + PROF (substitute, 2) \ + PROF (subsume, 2) \ + PROF (sweep, 2) \ + PROF (sweepbackbone, 3) \ + PROF (sweepequivalences, 3) \ + PROF (total, 0) \ + PROF (transitive, 2) \ + PROF (vivify, 2) \ + PROF (vivify0, 3) \ + PROF (vivify1, 3) \ + PROF (vivify2, 3) \ + PROF (vivify3, 3) \ + PROF (vivifysort, 4) \ + PROF (walking, 2) \ + PROF (warmup, 3) + +struct profile { + int level; + const char *name; + double entered; + double time; +}; + +struct profiles { +#define PROF(NAME, LEVEL) profile NAME; + PROFS +#undef PROF + STACK (profile *) stack; +}; + +struct kissat; + +void kissat_init_profiles (profiles *); +void kissat_profiles_print (struct kissat *); +void kissat_start (struct kissat *, profile *); +void kissat_stop (struct kissat *, profile *); + +void kissat_stop_search_and_start_simplifier (struct kissat *, profile *); +void kissat_stop_simplifier_and_resume_search (struct kissat *, profile *); + +double kissat_time (struct kissat *); + +#define PROFILE(NAME) (solver->profiles.NAME) + +#define START(NAME) \ + do { \ + profile *profile = &PROFILE (NAME); \ + if (GET_OPTION (profile) >= profile->level) \ + kissat_start (solver, profile); \ + } while (0) + +#define STOP(NAME) \ + do { \ + profile *profile = &PROFILE (NAME); \ + if (GET_OPTION (profile) >= profile->level) \ + kissat_stop (solver, profile); \ + } while (0) + +#define STOP_SEARCH_AND_START_SIMPLIFIER(NAME) \ + do { \ + if (GET_OPTION (profile) >= PROFILE (search).level) \ + kissat_stop_search_and_start_simplifier (solver, &PROFILE (NAME)); \ + } while (0) + +#define STOP_SIMPLIFIER_AND_RESUME_SEARCH(NAME) \ + do { \ + if (GET_OPTION (profile) >= PROFILE (search).level) \ + kissat_stop_simplifier_and_resume_search (solver, &PROFILE (NAME)); \ + } while (0) + +#else + +#define START(...) \ + do { \ + } while (0) +#define STOP(...) \ + do { \ + } while (0) + +#define STOP_SEARCH_AND_START_SIMPLIFIER(...) \ + do { \ + } while (0) +#define STOP_SIMPLIFIER_AND_RESUME_SEARCH(...) \ + do { \ + } while (0) + +#define STOP_AND_START(...) \ + do { \ + } while (0) + +#endif + +#endif diff --git a/src/sat/kissat/promote.c b/src/sat/kissat/promote.c new file mode 100644 index 000000000..152cfed4c --- /dev/null +++ b/src/sat/kissat/promote.c @@ -0,0 +1,42 @@ +#include "promote.h" +#include "internal.h" +#include "logging.h" + +void kissat_promote_clause (kissat *solver, clause *c, unsigned new_glue) { + if (!GET_OPTION (promote)) + return; + assert (c->redundant); + const unsigned old_glue = c->glue; + assert (new_glue < old_glue); + const unsigned tier1 = TIER1; + const unsigned tier2 = MAX (TIER2, TIER1); + if (old_glue <= tier1) { + LOGCLS (c, "keeping with new glue %u in tier1", new_glue); + INC (clauses_kept1); + } else if (new_glue <= tier1) { + assert (tier1 < old_glue); + assert (new_glue <= tier1); + LOGCLS (c, "promoting with new glue %u to tier1", new_glue); + INC (clauses_promoted1); + } else if (tier2 < old_glue && new_glue <= tier2) { + assert (tier2 < old_glue); + assert (tier1 < new_glue), assert (new_glue <= tier2); + LOGCLS (c, "promoting with new glue %u to tier2", new_glue); + INC (clauses_promoted2); + } else if (old_glue <= tier2) { + assert (tier1 < old_glue), assert (old_glue <= tier2); + assert (tier1 < new_glue), assert (new_glue <= tier2); + LOGCLS (c, "keeping with new glue %u in tier2", new_glue); + INC (clauses_kept2); + } else { + assert (tier2 < old_glue); + assert (tier2 < new_glue); + LOGCLS (c, "keeping with new glue %u in tier3", new_glue); + INC (clauses_kept3); + } + INC (clauses_improved); + c->glue = new_glue; +#ifndef LOGGING + (void) solver; +#endif +} diff --git a/src/sat/kissat/promote.h b/src/sat/kissat/promote.h new file mode 100644 index 000000000..3a6073cf6 --- /dev/null +++ b/src/sat/kissat/promote.h @@ -0,0 +1,33 @@ +#ifndef _promote_h_INCLUDED +#define _promote_h_INCLUDED + +#include "internal.h" + +void kissat_promote_clause (struct kissat *, clause *, unsigned new_glue); + +static inline unsigned kissat_recompute_glue (kissat *solver, clause *c, + unsigned limit) { + assert (limit); + assert (EMPTY_STACK (solver->promote)); + unsigned res = 0; + for (all_literals_in_clause (lit, c)) { + assert (VALUE (lit)); + const unsigned level = LEVEL (lit); + frame *frame = &FRAME (level); + if (frame->promote) + continue; + if (++res == limit) + break; + frame->promote = true; + PUSH_STACK (solver->promote, level); + } + for (all_stack (unsigned, level, solver->promote)) { + frame *frame = &FRAME (level); + assert (frame->promote); + frame->promote = false; + } + CLEAR_STACK (solver->promote); + return res; +} + +#endif diff --git a/src/sat/kissat/proof.c b/src/sat/kissat/proof.c new file mode 100644 index 000000000..5c819906b --- /dev/null +++ b/src/sat/kissat/proof.c @@ -0,0 +1,408 @@ +#ifndef NPROOFS + +#include "allocate.h" +#include "error.h" +#include "file.h" +#include "inline.h" + +#undef NDEBUG + +#ifndef NDEBUG +#include +#endif + +#define size_buffer (1u << 20) + +struct write_buffer { + unsigned char chars[size_buffer]; + size_t pos; +}; + +typedef struct write_buffer write_buffer; + +struct proof { + write_buffer buffer; + kissat *solver; + bool binary; + file *file; + ints line; + uint64_t added; + uint64_t deleted; + uint64_t lines; + uint64_t literals; +#ifndef NDEBUG + bool empty; + char *units; + size_t size_units; +#endif +#if !defined(NDEBUG) || defined(LOGGING) + unsigneds imported; +#endif +}; + +#undef LOGPREFIX +#define LOGPREFIX "PROOF" + +#define LOGIMPORTED3(...) \ + LOGLITS3 (SIZE_STACK (proof->imported), BEGIN_STACK (proof->imported), \ + __VA_ARGS__) + +#define LOGLINE3(...) \ + LOGINTS3 (SIZE_STACK (proof->line), BEGIN_STACK (proof->line), \ + __VA_ARGS__) + +void kissat_init_proof (kissat *solver, file *file, bool binary) { + assert (file); + assert (!solver->proof); + proof *proof = kissat_calloc (solver, 1, sizeof (struct proof)); + proof->binary = binary; + proof->file = file; + proof->solver = solver; + solver->proof = proof; + LOG ("starting to trace %s proof", binary ? "binary" : "non-binary"); +} + +static void flush_buffer (proof *proof) { + size_t bytes = proof->buffer.pos; + if (!bytes) + return; + size_t written = kissat_write (proof->file, proof->buffer.chars, bytes); + if (bytes != written) + kissat_fatal ("flushing %zu bytes in proof write-buffer failed", bytes); + proof->buffer.pos = 0; +} + +void kissat_release_proof (kissat *solver) { + proof *proof = solver->proof; + assert (proof); + LOG ("stopping to trace proof"); + flush_buffer (proof); + kissat_flush (proof->file); + RELEASE_STACK (proof->line); +#ifndef NDEBUG + kissat_free (solver, proof->units, proof->size_units); +#endif +#if !defined(NDEBUG) || defined(LOGGING) + RELEASE_STACK (proof->imported); +#endif + kissat_free (solver, proof, sizeof (struct proof)); + solver->proof = 0; +} + +#ifndef QUIET + +#include + +#define PERCENT_LINES(NAME) kissat_percent (proof->NAME, proof->lines) + +void kissat_print_proof_statistics (kissat *solver, bool verbose) { + proof *proof = solver->proof; + PRINT_STAT ("proof_added", proof->added, PERCENT_LINES (added), "%", + "per line"); + PRINT_STAT ("proof_bytes", proof->file->bytes, + proof->file->bytes / (double) (1 << 20), "MB", ""); + PRINT_STAT ("proof_deleted", proof->deleted, PERCENT_LINES (deleted), "%", + "per line"); + if (verbose) + PRINT_STAT ("proof_lines", proof->lines, 100, "%", ""); + if (verbose) + PRINT_STAT ("proof_literals", proof->literals, + kissat_average (proof->literals, proof->lines), "", + "per line"); +} + +#endif + +// clang-format off + +static inline void write_char (proof *, unsigned char) + ATTRIBUTE_ALWAYS_INLINE; + +static inline void import_external_proof_literal (kissat *, proof *, int) + ATTRIBUTE_ALWAYS_INLINE; + +static inline void +import_internal_proof_literal (kissat *, proof *, unsigned) + ATTRIBUTE_ALWAYS_INLINE; + +// clang-format on + +static inline void write_char (proof *proof, unsigned char ch) { + write_buffer *buffer = &proof->buffer; + if (buffer->pos == size_buffer) + flush_buffer (proof); + buffer->chars[buffer->pos++] = ch; +} + +static inline void import_internal_proof_literal (kissat *solver, + proof *proof, + unsigned ilit) { + int elit = kissat_export_literal (solver, ilit); + assert (elit); + PUSH_STACK (proof->line, elit); + proof->literals++; +#if !defined(NDEBUG) || defined(LOGGING) + PUSH_STACK (proof->imported, ilit); +#endif +} + +static inline void import_external_proof_literal (kissat *solver, + proof *proof, int elit) { + assert (elit); + PUSH_STACK (proof->line, elit); + proof->literals++; +#ifndef NDEBUG + assert (EMPTY_STACK (proof->imported)); +#endif +} + +static void import_internal_proof_binary (kissat *solver, proof *proof, + unsigned a, unsigned b) { + assert (EMPTY_STACK (proof->line)); + import_internal_proof_literal (solver, proof, a); + import_internal_proof_literal (solver, proof, b); +} + +static void import_internal_proof_literals (kissat *solver, proof *proof, + size_t size, + const unsigned *ilits) { + assert (EMPTY_STACK (proof->line)); + assert (size <= UINT_MAX); + for (size_t i = 0; i < size; i++) + import_internal_proof_literal (solver, proof, ilits[i]); +} + +static void import_external_proof_literals (kissat *solver, proof *proof, + size_t size, const int *elits) { + assert (EMPTY_STACK (proof->line)); + assert (size <= UINT_MAX); + for (size_t i = 0; i < size; i++) + import_external_proof_literal (solver, proof, elits[i]); +} + +static void import_proof_clause (kissat *solver, proof *proof, + const clause *c) { + import_internal_proof_literals (solver, proof, c->size, c->lits); +} + +static void print_binary_proof_line (proof *proof) { + assert (proof->binary); + for (all_stack (int, elit, proof->line)) { + unsigned x = 2u * ABS (elit) + (elit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + write_char (proof, ch); + x >>= 7; + } + write_char (proof, x); + } + write_char (proof, 0); +} + +static void print_non_binary_proof_line (proof *proof) { + assert (!proof->binary); + char buffer[16]; + char *end_of_buffer = buffer + sizeof buffer; + *--end_of_buffer = 0; + for (all_stack (int, elit, proof->line)) { + char *p = end_of_buffer; + assert (!*p); + assert (elit); + assert (elit != INT_MIN); + unsigned eidx; + if (elit < 0) { + write_char (proof, '-'); + eidx = -elit; + } else + eidx = elit; + for (unsigned tmp = eidx; tmp; tmp /= 10) + *--p = '0' + (tmp % 10); + while (p != end_of_buffer) + write_char (proof, *p++); + write_char (proof, ' '); + } + write_char (proof, '0'); + write_char (proof, '\n'); +} + +static void print_proof_line (proof *proof) { + proof->lines++; + if (proof->binary) + print_binary_proof_line (proof); + else + print_non_binary_proof_line (proof); + CLEAR_STACK (proof->line); +#if !defined(NDEBUG) || defined(LOGGING) + CLEAR_STACK (proof->imported); +#endif +#ifndef NOPTIONS + kissat *solver = proof->solver; +#endif + if (GET_OPTION (flushproof)) { + flush_buffer (proof); + kissat_flush (proof->file); + } +} + +#ifndef NDEBUG + +static unsigned external_to_proof_literal (int elit) { + assert (elit); + assert (elit != INT_MIN); + return 2u * (abs (elit) - 1) + (elit < 0); +} + +static void resize_proof_units (proof *proof, unsigned plit) { + kissat *solver = proof->solver; + const size_t old_size = proof->size_units; + size_t new_size = old_size ? old_size : 2; + while (new_size <= plit) + new_size *= 2; + char *new_units = kissat_calloc (solver, new_size, 1); + if (old_size) + memcpy (new_units, proof->units, old_size); + kissat_dealloc (solver, proof->units, old_size, 1); + proof->units = new_units; + proof->size_units = new_size; +} + +static void check_repeated_proof_lines (proof *proof) { + size_t size = SIZE_STACK (proof->line); + if (!size) { + assert (!proof->empty); + proof->empty = true; + } else if (size == 1) { + const int eunit = PEEK_STACK (proof->line, 0); + const unsigned punit = external_to_proof_literal (eunit); + assert (punit != INVALID_LIT); + if (!proof->size_units || proof->size_units <= punit) + resize_proof_units (proof, punit); + proof->units[punit] = 1; + } +} + +#endif + +static void print_added_proof_line (proof *proof) { + proof->added++; +#ifdef LOGGING + struct kissat *solver = proof->solver; + assert (SIZE_STACK (proof->imported) == SIZE_STACK (proof->line)); + LOGIMPORTED3 ("added proof line"); + LOGLINE3 ("added proof line"); +#endif +#ifndef NDEBUG + check_repeated_proof_lines (proof); +#endif + if (proof->binary) + write_char (proof, 'a'); + print_proof_line (proof); +} + +static void print_delete_proof_line (proof *proof) { + proof->deleted++; +#ifdef LOGGING + struct kissat *solver = proof->solver; + if (SIZE_STACK (proof->imported) == SIZE_STACK (proof->line)) + LOGIMPORTED3 ("deleted internal proof line"); + LOGLINE3 ("deleted external proof line"); +#endif + write_char (proof, 'd'); + if (!proof->binary) + write_char (proof, ' '); + print_proof_line (proof); +} + +void kissat_add_binary_to_proof (kissat *solver, unsigned a, unsigned b) { + proof *proof = solver->proof; + assert (proof); + import_internal_proof_binary (solver, proof, a, b); + print_added_proof_line (proof); +} + +void kissat_add_clause_to_proof (kissat *solver, const clause *c) { + proof *proof = solver->proof; + assert (proof); + import_proof_clause (solver, proof, c); + print_added_proof_line (proof); +} + +void kissat_add_empty_to_proof (kissat *solver) { + proof *proof = solver->proof; + assert (proof); + assert (EMPTY_STACK (proof->line)); + print_added_proof_line (proof); +} + +void kissat_add_lits_to_proof (kissat *solver, size_t size, + const unsigned *ilits) { + proof *proof = solver->proof; + assert (proof); + import_internal_proof_literals (solver, proof, size, ilits); + print_added_proof_line (proof); +} + +void kissat_add_unit_to_proof (kissat *solver, unsigned ilit) { + proof *proof = solver->proof; + assert (proof); + assert (EMPTY_STACK (proof->line)); + import_internal_proof_literal (solver, proof, ilit); + print_added_proof_line (proof); +} + +void kissat_shrink_clause_in_proof (kissat *solver, const clause *c, + unsigned remove, unsigned keep) { + proof *proof = solver->proof; + const value *const values = solver->values; + assert (EMPTY_STACK (proof->line)); + const unsigned *ilits = c->lits; + const unsigned size = c->size; + for (unsigned i = 0; i != size; i++) { + const unsigned ilit = ilits[i]; + if (ilit == remove) + continue; + if (ilit != keep && values[ilit] < 0 && !LEVEL (ilit)) + continue; + import_internal_proof_literal (solver, proof, ilit); + } + print_added_proof_line (proof); + import_proof_clause (solver, proof, c); + print_delete_proof_line (proof); +} + +void kissat_delete_binary_from_proof (kissat *solver, unsigned a, + unsigned b) { + proof *proof = solver->proof; + assert (proof); + import_internal_proof_binary (solver, proof, a, b); + print_delete_proof_line (proof); +} + +void kissat_delete_clause_from_proof (kissat *solver, const clause *c) { + proof *proof = solver->proof; + assert (proof); + import_proof_clause (solver, proof, c); + print_delete_proof_line (proof); +} + +void kissat_delete_external_from_proof (kissat *solver, size_t size, + const int *elits) { + proof *proof = solver->proof; + assert (proof); + LOGINTS3 (size, elits, "explicitly deleted"); + import_external_proof_literals (solver, proof, size, elits); + print_delete_proof_line (proof); +} + +void kissat_delete_internal_from_proof (kissat *solver, size_t size, + const unsigned *ilits) { + proof *proof = solver->proof; + assert (proof); + import_internal_proof_literals (solver, proof, size, ilits); + print_delete_proof_line (proof); +} + +#else +int kissat_proof_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/proof.h b/src/sat/kissat/proof.h new file mode 100644 index 000000000..a87e60de5 --- /dev/null +++ b/src/sat/kissat/proof.h @@ -0,0 +1,164 @@ +#ifndef _proof_h_INCLUDED +#define _proof_h_INCLUDED + +#ifndef NPROOFS + +#include +#include + +typedef struct proof proof; + +struct clause; +struct file; + +void kissat_init_proof (struct kissat *, struct file *, bool binary); +void kissat_release_proof (struct kissat *); + +#ifndef QUIET +void kissat_print_proof_statistics (struct kissat *, bool verbose); +#endif + +void kissat_add_binary_to_proof (struct kissat *, unsigned, unsigned); +void kissat_add_clause_to_proof (struct kissat *, const struct clause *c); +void kissat_add_empty_to_proof (struct kissat *); +void kissat_add_lits_to_proof (struct kissat *, size_t, const unsigned *); +void kissat_add_unit_to_proof (struct kissat *, unsigned); + +void kissat_shrink_clause_in_proof (struct kissat *, const struct clause *, + unsigned remove, unsigned keep); + +void kissat_delete_binary_from_proof (struct kissat *, unsigned, unsigned); +void kissat_delete_clause_from_proof (struct kissat *, + const struct clause *c); +void kissat_delete_external_from_proof (struct kissat *, size_t, + const int *); +void kissat_delete_internal_from_proof (struct kissat *, size_t, + const unsigned *); + +#define ADD_BINARY_TO_PROOF(A, B) \ + do { \ + if (solver->proof) \ + kissat_add_binary_to_proof (solver, (A), (B)); \ + } while (0) + +#define ADD_TERNARY_TO_PROOF(A, B, C) \ + do { \ + if (solver->proof) { \ + unsigned CLAUSE[3] = {(A), (B), (C)}; \ + kissat_add_lits_to_proof (solver, 3, CLAUSE); \ + } \ + } while (0) + +#define ADD_CLAUSE_TO_PROOF(CLAUSE) \ + do { \ + if (solver->proof) \ + kissat_add_clause_to_proof (solver, (CLAUSE)); \ + } while (0) + +#define ADD_EMPTY_TO_PROOF() \ + do { \ + if (solver->proof) \ + kissat_add_empty_to_proof (solver); \ + } while (0) + +#define ADD_LITS_TO_PROOF(SIZE, LITS) \ + do { \ + if (solver->proof) \ + kissat_add_lits_to_proof (solver, (SIZE), (LITS)); \ + } while (0) + +#define ADD_STACK_TO_PROOF(S) \ + ADD_LITS_TO_PROOF (SIZE_STACK (S), BEGIN_STACK (S)) + +#define ADD_UNIT_TO_PROOF(A) \ + do { \ + if (solver->proof) \ + kissat_add_unit_to_proof (solver, (A)); \ + } while (0) + +#define SHRINK_CLAUSE_IN_PROOF(C, REMOVE, KEEP) \ + do { \ + if (solver->proof) \ + kissat_shrink_clause_in_proof (solver, (C), (REMOVE), (KEEP)); \ + } while (0) + +#define DELETE_BINARY_FROM_PROOF(A, B) \ + do { \ + if (solver->proof) \ + kissat_delete_binary_from_proof (solver, (A), (B)); \ + } while (0) + +#define DELETE_TERNARY_FROM_PROOF(A, B, C) \ + do { \ + if (solver->proof) { \ + unsigned CLAUSE[3] = {(A), (B), (C)}; \ + kissat_delete_internal_from_proof (solver, 3, CLAUSE); \ + } \ + } while (0) + +#define DELETE_CLAUSE_FROM_PROOF(CLAUSE) \ + do { \ + if (solver->proof) \ + kissat_delete_clause_from_proof (solver, (CLAUSE)); \ + } while (0) + +#define DELETE_LITS_FROM_PROOF(SIZE, LITS) \ + do { \ + if (solver->proof) \ + kissat_delete_internal_from_proof (solver, (SIZE), (LITS)); \ + } while (0) + +#define DELETE_STACK_FROM_PROOF(S) \ + do { \ + if (solver->proof) \ + kissat_delete_internal_from_proof (solver, SIZE_STACK (S), \ + BEGIN_STACK (S)); \ + } while (0) + +#else + +#define ADD_BINARY_TO_PROOF(...) \ + do { \ + } while (0) +#define ADD_TERNARY_TO_PROOF(...) \ + do { \ + } while (0) +#define ADD_CLAUSE_TO_PROOF(...) \ + do { \ + } while (0) +#define ADD_LITS_TO_PROOF(...) \ + do { \ + } while (0) +#define ADD_EMPTY_TO_PROOF(...) \ + do { \ + } while (0) +#define ADD_STACK_TO_PROOF(...) \ + do { \ + } while (0) +#define ADD_UNIT_TO_PROOF(...) \ + do { \ + } while (0) + +#define SHRINK_CLAUSE_IN_PROOF(...) \ + do { \ + } while (0) + +#define DELETE_BINARY_FROM_PROOF(...) \ + do { \ + } while (0) +#define DELETE_TERNARY_FROM_PROOF(...) \ + do { \ + } while (0) +#define DELETE_CLAUSE_FROM_PROOF(...) \ + do { \ + } while (0) +#define DELETE_LITS_FROM_PROOF(...) \ + do { \ + } while (0) +#define DELETE_STACK_FROM_PROOF(...) \ + do { \ + } while (0) + +#endif + +#endif diff --git a/src/sat/kissat/propbeyond.c b/src/sat/kissat/propbeyond.c new file mode 100644 index 000000000..b42192b05 --- /dev/null +++ b/src/sat/kissat/propbeyond.c @@ -0,0 +1,48 @@ +#include "propbeyond.h" +#include "fastassign.h" +#include "trail.h" + +#define PROPAGATE_LITERAL propagate_literal_beyond_conflicts +#define CONTINUE_PROPAGATING_AFTER_CONFLICT +#define PROPAGATION_TYPE "beyond conflict" + +#include "proplit.h" + +static inline void +update_beyond_propagation_statistics (kissat *solver, + const unsigned *saved_propagate) { + assert (saved_propagate <= solver->propagate); + const unsigned propagated = solver->propagate - saved_propagate; + + LOG ("propagated %u literals", propagated); + LOG ("propagation took %" PRIu64 " ticks", solver->ticks); + + ADD (ticks, solver->ticks); + + ADD (propagations, propagated); + ADD (warming_propagations, propagated); +} + +static void propagate_literals_beyond_conflicts (kissat *solver) { + unsigned *propagate = solver->propagate; + while (propagate != END_ARRAY (solver->trail)) + if (propagate_literal_beyond_conflicts (solver, *propagate++)) + INC (warming_conflicts); + solver->propagate = propagate; +} + +void kissat_propagate_beyond_conflicts (kissat *solver) { + assert (!solver->probing); + assert (solver->watching); + assert (solver->warming); + assert (!solver->inconsistent); + + START (propagate); + + solver->ticks = 0; + const unsigned *saved_propagate = solver->propagate; + propagate_literals_beyond_conflicts (solver); + update_beyond_propagation_statistics (solver, saved_propagate); + + STOP (propagate); +} diff --git a/src/sat/kissat/propbeyond.h b/src/sat/kissat/propbeyond.h new file mode 100644 index 000000000..09eaf1b4e --- /dev/null +++ b/src/sat/kissat/propbeyond.h @@ -0,0 +1,9 @@ +#ifndef _propall_h_INCLUDED +#define _propall_h_INCLUDED + +struct kissat; +struct clause; + +void kissat_propagate_beyond_conflicts (struct kissat *); + +#endif diff --git a/src/sat/kissat/propdense.c b/src/sat/kissat/propdense.c new file mode 100644 index 000000000..48ccecb4f --- /dev/null +++ b/src/sat/kissat/propdense.c @@ -0,0 +1,108 @@ +#include "propdense.h" +#include "fastassign.h" + +static inline bool non_watching_propagate_literal (kissat *solver, + unsigned lit) { + assert (!solver->watching); + LOG ("propagating %s", LOGLIT (lit)); + assert (VALUE (lit) > 0); + const unsigned not_lit = NOT (lit); + + watches *watches = &WATCHES (not_lit); + const size_t size_watches = SIZE_WATCHES (*watches); + unsigned ticks = 1 + kissat_cache_lines (size_watches, sizeof (watch)); + + ward *const arena = BEGIN_STACK (solver->arena); + assigned *assigned = solver->assigned; + value *values = solver->values; + flags *flags = solver->flags; + + for (all_binary_large_watches (watch, *watches)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + assert (VALID_INTERNAL_LITERAL (other)); + const value other_value = values[other]; + if (other_value > 0) + continue; + if (other_value < 0) { + LOGBINARY (not_lit, other, "conflicting"); + return false; + } + const unsigned other_idx = IDX (other); + if (flags[other_idx].eliminated) + continue; + assert (!solver->level); + kissat_fast_binary_assign (solver, solver->probing, 0, values, + assigned, other, not_lit); + } else { + const reference ref = watch.large.ref; + assert (ref < SIZE_STACK (solver->arena)); + clause *c = (clause *) (arena + ref); + assert (c->size > 2); + assert (!c->redundant); + ticks++; + if (c->garbage) + continue; + unsigned non_false = 0; + unsigned unit = INVALID_LIT; + bool satisfied = false; + for (all_literals_in_clause (other, c)) { + if (other == not_lit) + continue; + assert (VALID_INTERNAL_LITERAL (other)); + const value other_value = values[other]; + if (other_value < 0) + continue; + if (other_value > 0) { + satisfied = true; + assert (!solver->level); + LOGCLS (c, "%s satisfied", LOGLIT (other)); + kissat_mark_clause_as_garbage (solver, c); + break; + } + if (!non_false++) + unit = other; + else if (non_false > 1) + break; + } + if (satisfied) + continue; + if (!non_false) { + LOGREF (ref, "conflicting"); + return false; + } + if (non_false == 1) + kissat_fast_assign_reference (solver, values, assigned, unit, ref, + c); + } + } + + ADD (ticks, ticks); + ADD (dense_ticks, ticks); + + return true; +} + +bool kissat_dense_propagate (kissat *solver) { + assert (!solver->level); + assert (!solver->watching); + assert (!solver->inconsistent); + START (propagate); + unsigned *propagate = solver->propagate; + bool res = true; + while (res && propagate != END_ARRAY (solver->trail)) + res = non_watching_propagate_literal (solver, *propagate++); + const unsigned propagated = propagate - solver->propagate; + solver->propagate = propagate; + ADD (dense_propagations, propagated); + ADD (propagations, propagated); + if (!res) { + assert (!solver->inconsistent); + LOG ("inconsistent root propagation"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + solver->inconsistent = true; + } + STOP (propagate); + return res; +} diff --git a/src/sat/kissat/propdense.h b/src/sat/kissat/propdense.h new file mode 100644 index 000000000..adbaae1cd --- /dev/null +++ b/src/sat/kissat/propdense.h @@ -0,0 +1,11 @@ +#ifndef _propdense_h_INCLUDED +#define _propdense_h_INCLUDED + +#include +#include + +struct kissat; + +bool kissat_dense_propagate (struct kissat *); + +#endif diff --git a/src/sat/kissat/propinitially.c b/src/sat/kissat/propinitially.c new file mode 100644 index 000000000..5f66db3d1 --- /dev/null +++ b/src/sat/kissat/propinitially.c @@ -0,0 +1,56 @@ +#include "propinitially.h" +#include "analyze.h" +#include "fastassign.h" +#include "print.h" +#include "trail.h" + +#define PROPAGATE_LITERAL initially_propagate_literal +#define PROPAGATION_TYPE "initially" + +#include "proplit.h" + +static inline void +update_initial_propagation_statistics (kissat *solver, + const unsigned *saved_propagate) { + assert (saved_propagate <= solver->propagate); + const unsigned propagated = solver->propagate - saved_propagate; + + LOG ("propagated %u literals", propagated); + LOG ("propagation took %" PRIu64 " ticks", solver->ticks); + + ADD (propagations, propagated); + ADD (ticks, solver->ticks); +} + +static clause *initially_propagate (kissat *solver) { + clause *res = 0; + unsigned *propagate = solver->propagate; + while (!res && propagate != END_ARRAY (solver->trail)) + res = initially_propagate_literal (solver, *propagate++); + solver->propagate = propagate; + return res; +} + +bool kissat_initially_propagate (kissat *solver) { + assert (!solver->probing); + assert (solver->watching); + assert (!solver->inconsistent); + + START (propagate); + + solver->ticks = 0; + const unsigned *saved_propagate = solver->propagate; + clause *conflict = initially_propagate (solver); + update_initial_propagation_statistics (solver, saved_propagate); + kissat_update_conflicts_and_trail (solver, conflict, true); + if (conflict) { + int res = kissat_analyze (solver, conflict); + assert (solver->inconsistent); + assert (res == 20); + (void) res; + } + + STOP (propagate); + + return !conflict; +} diff --git a/src/sat/kissat/propinitially.h b/src/sat/kissat/propinitially.h new file mode 100644 index 000000000..d53f84c9a --- /dev/null +++ b/src/sat/kissat/propinitially.h @@ -0,0 +1,10 @@ +#ifndef _propinitially_h_INCLUDED +#define _propinitially_h_INCLUDED + +#include + +struct kissat; + +bool kissat_initially_propagate (struct kissat *); + +#endif diff --git a/src/sat/kissat/proplit.h b/src/sat/kissat/proplit.h new file mode 100644 index 000000000..c8ae9c1cb --- /dev/null +++ b/src/sat/kissat/proplit.h @@ -0,0 +1,204 @@ +static inline void kissat_watch_large_delayed (kissat *solver, + watches *all_watches, + unsigneds *delayed) { + assert (all_watches == solver->watches); + assert (delayed == &solver->delayed); + const unsigned *const end_delayed = END_STACK (*delayed); + unsigned const *d = BEGIN_STACK (*delayed); + while (d != end_delayed) { + const unsigned lit = *d++; + assert (d != end_delayed); + const watch watch = {.raw = *d++}; + assert (!watch.type.binary); + assert (lit < LITS); + watches *const lit_watches = all_watches + lit; + assert (d != end_delayed); + const reference ref = *d++; + const unsigned blocking = watch.blocking.lit; + LOGREF3 (ref, "watching %s blocking %s in", LOGLIT (lit), + LOGLIT (blocking)); + kissat_push_blocking_watch (solver, lit_watches, blocking, ref); + } + CLEAR_STACK (*delayed); +} + +static inline void +kissat_delay_watching_large (kissat *solver, unsigneds *const delayed, + unsigned lit, unsigned other, reference ref) { + const watch watch = kissat_blocking_watch (other); + PUSH_STACK (*delayed, lit); + PUSH_STACK (*delayed, watch.raw); + PUSH_STACK (*delayed, ref); +} + +static inline clause *PROPAGATE_LITERAL (kissat *solver, +#if defined(PROBING_PROPAGATION) + const clause *const ignore, +#endif + const unsigned lit) { + assert (solver->watching); + LOG (PROPAGATION_TYPE " propagating %s", LOGLIT (lit)); + assert (VALUE (lit) > 0); + assert (EMPTY_STACK (solver->delayed)); + + watches *const all_watches = solver->watches; + ward *const arena = BEGIN_STACK (solver->arena); + assigned *const assigned = solver->assigned; + value *const values = solver->values; + + const unsigned not_lit = NOT (lit); + + assert (not_lit < LITS); + watches *watches = all_watches + not_lit; + + watch *const begin_watches = BEGIN_WATCHES (*watches); + const watch *const end_watches = END_WATCHES (*watches); + + watch *q = begin_watches; + const watch *p = q; + + unsigneds *const delayed = &solver->delayed; + assert (EMPTY_STACK (*delayed)); + + const size_t size_watches = SIZE_WATCHES (*watches); + uint64_t ticks = 1 + kissat_cache_lines (size_watches, sizeof (watch)); + const unsigned idx = IDX (lit); + struct assigned *const a = assigned + idx; + const bool probing = solver->probing; + const unsigned level = a->level; + clause *res = 0; + + while (p != end_watches) { + const watch head = *q++ = *p++; + const unsigned blocking = head.blocking.lit; + assert (VALID_INTERNAL_LITERAL (blocking)); + const value blocking_value = values[blocking]; + const bool binary = head.type.binary; + watch tail; + if (!binary) + tail = *q++ = *p++; + if (blocking_value > 0) + continue; + if (binary) { + if (blocking_value < 0) { + res = kissat_binary_conflict (solver, not_lit, blocking); +#ifndef CONTINUE_PROPAGATING_AFTER_CONFLICT + break; +#endif + } else { + assert (!blocking_value); + kissat_fast_binary_assign (solver, probing, level, values, assigned, + blocking, not_lit); + ticks++; + } + } else { + const reference ref = tail.raw; + assert (ref < SIZE_STACK (solver->arena)); + clause *const c = (clause *) (arena + ref); + ticks++; + if (c->garbage) { + q -= 2; + continue; + } + unsigned *const lits = BEGIN_LITS (c); + const unsigned other = lits[0] ^ lits[1] ^ not_lit; + assert (lits[0] != lits[1]); + assert (VALID_INTERNAL_LITERAL (other)); + assert (not_lit != other); + assert (lit != other); + const value other_value = values[other]; + if (other_value > 0) { + q[-2].blocking.lit = other; + continue; + } + const unsigned *const end_lits = lits + c->size; + unsigned *const searched = lits + c->searched; + assert (c->lits + 2 <= searched); + assert (searched < end_lits); + unsigned *r, replacement = INVALID_LIT; + value replacement_value = -1; + for (r = searched; r != end_lits; r++) { + replacement = *r; + assert (VALID_INTERNAL_LITERAL (replacement)); + replacement_value = values[replacement]; + if (replacement_value >= 0) + break; + } + if (replacement_value < 0) { + for (r = lits + 2; r != searched; r++) { + replacement = *r; + assert (VALID_INTERNAL_LITERAL (replacement)); + replacement_value = values[replacement]; + if (replacement_value >= 0) + break; + } + } + + if (replacement_value >= 0) { + c->searched = r - lits; + assert (replacement != INVALID_LIT); + LOGREF3 (ref, "unwatching %s in", LOGLIT (not_lit)); + q -= 2; + lits[0] = other; + lits[1] = replacement; + assert (lits[0] != lits[1]); + *r = not_lit; + kissat_delay_watching_large (solver, delayed, replacement, other, + ref); + ticks++; + } else if (other_value) { + assert (replacement_value < 0); + assert (blocking_value < 0); + assert (other_value < 0); +#if defined(PROBING_PROPAGATION) + if (c == ignore) { + LOGREF (ref, "conflicting but ignored"); + continue; + } +#endif + LOGREF (ref, "conflicting"); + res = c; +#ifndef CONTINUE_PROPAGATING_AFTER_CONFLICT + break; +#endif + } else { + assert (replacement_value < 0); +#if defined(PROBING_PROPAGATION) + if (c == ignore) { + LOGREF (ref, "forcing %s but ignored", LOGLIT (other)); + continue; + } +#endif + kissat_fast_assign_reference (solver, values, assigned, other, ref, + c); + ticks++; + } + } + } + solver->ticks += ticks; + + while (p != end_watches) + *q++ = *p++; + SET_END_OF_WATCHES (*watches, q); + + kissat_watch_large_delayed (solver, all_watches, delayed); + + return res; +} + +static inline void kissat_update_conflicts_and_trail (kissat *solver, + clause *conflict, + bool flush) { + if (conflict) { +#ifndef PROBING_PROPAGATION + INC (conflicts); +#endif + if (!solver->level) { + LOG (PROPAGATION_TYPE " propagation on root-level failed"); + solver->inconsistent = true; + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + } + } else if (flush && !solver->level && solver->unflushed) + kissat_flush_trail (solver); +} diff --git a/src/sat/kissat/proprobe.c b/src/sat/kissat/proprobe.c new file mode 100644 index 000000000..e95873317 --- /dev/null +++ b/src/sat/kissat/proprobe.c @@ -0,0 +1,59 @@ +#include "proprobe.h" +#include "fastassign.h" +#include "trail.h" + +#define PROPAGATE_LITERAL probing_propagate_literal +#define PROPAGATION_TYPE "probing" +#define PROBING_PROPAGATION + +#include "proplit.h" + +static void update_probing_propagation_statistics (kissat *solver, + unsigned propagated) { + const uint64_t ticks = solver->ticks; + LOG (PROPAGATION_TYPE " propagation took %u propagations", propagated); + LOG (PROPAGATION_TYPE " propagation took %" PRIu64 " ticks", ticks); + + ADD (propagations, propagated); + ADD (probing_propagations, propagated); + +#if defined(METRICS) + if (solver->backbone_computing) { + ADD (backbone_propagations, propagated); + ADD (backbone_ticks, ticks); + } + if (solver->vivifying) { + ADD (vivify_propagations, propagated); + ADD (vivify_ticks, ticks); + } +#endif + + ADD (probing_ticks, ticks); + ADD (ticks, ticks); +} + +clause *kissat_probing_propagate (kissat *solver, clause *ignore, + bool flush) { + assert (solver->probing); + assert (solver->watching); + assert (!solver->inconsistent); + + START (propagate); + + clause *conflict = 0; + unsigned *propagate = solver->propagate; + solver->ticks = 0; + while (!conflict && propagate != END_ARRAY (solver->trail)) { + const unsigned lit = *propagate++; + conflict = probing_propagate_literal (solver, ignore, lit); + } + + const unsigned propagated = propagate - solver->propagate; + solver->propagate = propagate; + update_probing_propagation_statistics (solver, propagated); + kissat_update_conflicts_and_trail (solver, conflict, flush); + + STOP (propagate); + + return conflict; +} diff --git a/src/sat/kissat/proprobe.h b/src/sat/kissat/proprobe.h new file mode 100644 index 000000000..9fbebe8cc --- /dev/null +++ b/src/sat/kissat/proprobe.h @@ -0,0 +1,12 @@ +#ifndef _proprobe_h_INCLUDED +#define _proprobe_h_INCLUDED + +#include + +struct kissat; +struct clause; + +struct clause *kissat_probing_propagate (struct kissat *, struct clause *, + bool flush); + +#endif diff --git a/src/sat/kissat/propsearch.c b/src/sat/kissat/propsearch.c new file mode 100644 index 000000000..c1265301c --- /dev/null +++ b/src/sat/kissat/propsearch.c @@ -0,0 +1,71 @@ +#include "propsearch.h" +#include "fastassign.h" +#include "print.h" +#include "trail.h" + +#define PROPAGATE_LITERAL search_propagate_literal +#define PROPAGATION_TYPE "search" + +#include "proplit.h" + +static inline void +update_search_propagation_statistics (kissat *solver, + const unsigned *saved_propagate) { + assert (saved_propagate <= solver->propagate); + const unsigned propagated = solver->propagate - saved_propagate; + + LOG ("propagated %u literals", propagated); + LOG ("propagation took %" PRIu64 " ticks", solver->ticks); + + ADD (propagations, propagated); + ADD (ticks, solver->ticks); + + ADD (search_propagations, propagated); + ADD (search_ticks, solver->ticks); + + if (solver->stable) { + ADD (stable_propagations, propagated); + ADD (stable_ticks, solver->ticks); + } else { + ADD (focused_propagations, propagated); + ADD (focused_ticks, solver->ticks); + } +} + +static clause *search_propagate (kissat *solver) { + clause *res = 0; + unsigned *propagate = solver->propagate; + while (!res && propagate != END_ARRAY (solver->trail)) + res = search_propagate_literal (solver, *propagate++); + solver->propagate = propagate; + return res; +} + +clause *kissat_search_propagate (kissat *solver) { + assert (!solver->probing); + assert (solver->watching); + assert (!solver->inconsistent); + + START (propagate); + + solver->ticks = 0; + const unsigned *saved_propagate = solver->propagate; + clause *conflict = search_propagate (solver); + update_search_propagation_statistics (solver, saved_propagate); + kissat_update_conflicts_and_trail (solver, conflict, true); + if (conflict && solver->randec) { + if (!--solver->randec) + kissat_very_verbose (solver, "last random decision conflict"); + else if (solver->randec == 1) + kissat_very_verbose (solver, + "one more random decision conflict to go"); + else + kissat_very_verbose (solver, + "%s more random decision conflicts to go", + FORMAT_COUNT (solver->randec)); + } + + STOP (propagate); + + return conflict; +} diff --git a/src/sat/kissat/propsearch.h b/src/sat/kissat/propsearch.h new file mode 100644 index 000000000..a774d9c5b --- /dev/null +++ b/src/sat/kissat/propsearch.h @@ -0,0 +1,9 @@ +#ifndef _propsearch_h_INCLUDED +#define _propsearch_h_INCLUDED + +struct kissat; +struct clause; + +struct clause *kissat_search_propagate (struct kissat *); + +#endif diff --git a/src/sat/kissat/queue.c b/src/sat/kissat/queue.c new file mode 100644 index 000000000..f64dfe2a3 --- /dev/null +++ b/src/sat/kissat/queue.c @@ -0,0 +1,55 @@ +#include "inline.h" +#include "inlinequeue.h" +#include "print.h" + +void kissat_init_queue (kissat *solver) { + queue *queue = &solver->queue; + queue->first = queue->last = DISCONNECT; + assert (!queue->stamp); + queue->search.idx = DISCONNECT; + assert (!queue->search.stamp); +} + +void kissat_reset_search_of_queue (kissat *solver) { + LOG ("reset last search cache of queue"); + queue *queue = &solver->queue; + links *links = solver->links; + const unsigned last = queue->last; + assert (!DISCONNECTED (last)); + kissat_update_queue (solver, links, last); +} + +void kissat_reassign_queue_stamps (kissat *solver) { + kissat_very_verbose (solver, "need to reassign enqueue stamps on queue"); + + queue *queue = &solver->queue; + links *links = solver->links; + queue->stamp = 0; + + struct links *l; + for (unsigned idx = queue->first; !DISCONNECTED (idx); idx = l->next) + (l = links + idx)->stamp = ++queue->stamp; + + if (!DISCONNECTED (queue->search.idx)) + queue->search.stamp = links[queue->search.idx].stamp; +} + +#if defined(CHECK_QUEUE) && !defined(NDEBUG) +void kissat_check_queue (kissat *solver) { + links *links = solver->links; + queue *queue = &solver->queue; + bool passed_search_idx = false; + const bool focused = !solver->stable; + for (unsigned idx = queue->first, prev = DISCONNECT; !DISCONNECTED (idx); + idx = links[idx].next) { + if (!DISCONNECTED (prev)) + assert (links[prev].stamp < links[idx].stamp); + if (focused && passed_search_idx) + assert (VALUE (LIT (idx))); + if (idx == queue->search.idx) + passed_search_idx = true; + } + if (!DISCONNECTED (queue->search.idx)) + assert (links[queue->search.idx].stamp == queue->search.stamp); +} +#endif diff --git a/src/sat/kissat/queue.h b/src/sat/kissat/queue.h new file mode 100644 index 000000000..271e7e567 --- /dev/null +++ b/src/sat/kissat/queue.h @@ -0,0 +1,38 @@ +#ifndef _queue_h_INCLUDED +#define _queue_h_INCLUDED + +#define DISCONNECT UINT_MAX +#define DISCONNECTED(IDX) ((int) (IDX) < 0) + +struct kissat; + +typedef struct links links; +typedef struct queue queue; + +struct links { + unsigned prev, next; + unsigned stamp; +}; + +struct queue { + unsigned first, last, stamp; + struct { + unsigned idx, stamp; + } search; +}; + +void kissat_init_queue (struct kissat *); +void kissat_reset_search_of_queue (struct kissat *); +void kissat_reassign_queue_stamps (struct kissat *); + +#define LINK(IDX) (solver->links[assert ((IDX) < VARS), (IDX)]) + +#if defined(CHECK_QUEUE) && !defined(NDEBUG) +void kissat_check_queue (struct kissat *); +#else +#define kissat_check_queue(...) \ + do { \ + } while (0) +#endif + +#endif diff --git a/src/sat/kissat/random.h b/src/sat/kissat/random.h new file mode 100644 index 000000000..7f00fbf99 --- /dev/null +++ b/src/sat/kissat/random.h @@ -0,0 +1,44 @@ +#ifndef _random_h_INCLUDED +#define _random_h_INCLUDED + +#include +#include +#include + +typedef uint64_t generator; + +static inline uint64_t kissat_next_random64 (generator *rng) { + *rng *= 6364136223846793005ul; + *rng += 1442695040888963407ul; + return *rng; +} + +static inline unsigned kissat_next_random32 (generator *rng) { + return kissat_next_random64 (rng) >> 32; +} + +static inline unsigned kissat_pick_random (generator *rng, unsigned l, + unsigned r) { + assert (l <= r); + if (l == r) + return l; + const unsigned delta = r - l; + const unsigned tmp = kissat_next_random32 (rng); + const double fraction = tmp / 4294967296.0; + assert (0 <= fraction), assert (fraction < 1); + const unsigned scaled = delta * fraction; + assert (scaled < delta); + const unsigned res = l + scaled; + assert (l <= res), assert (res < r); + return res; +} + +static inline bool kissat_pick_bool (generator *rng) { + return kissat_pick_random (rng, 0, 2); +} + +static inline double kissat_pick_double (generator *rng) { + return kissat_next_random32 (rng) / 4294967296.0; +} + +#endif diff --git a/src/sat/kissat/rank.h b/src/sat/kissat/rank.h new file mode 100644 index 000000000..283536536 --- /dev/null +++ b/src/sat/kissat/rank.h @@ -0,0 +1,140 @@ +#ifndef _rank_h_INCLUDED +#define _rank_h_INCLUDED + +#include "allocate.h" + +#include + +#ifdef NDEBUG +#define CHECK_RANKED(...) \ + do { \ + } while (0) +#else +#define CHECK_RANKED(N, A, RANK) \ + do { \ + assert (0 < (N)); \ + for (size_t I_CHECK_RANKED = 0; I_CHECK_RANKED < N - 1; \ + I_CHECK_RANKED++) \ + assert (RANK (A[I_CHECK_RANKED]) <= RANK (A[I_CHECK_RANKED + 1])); \ + } while (0) +#endif + +#define RADIX_SORT(VTYPE, RTYPE, N, V, RANK) \ + do { \ + const size_t N_RADIX = (N); \ + if (N_RADIX <= 1) \ + break; \ +\ + START (radix); \ +\ + VTYPE *V_RADIX = (V); \ +\ + const size_t LENGTH_RADIX = 8; \ + const size_t WIDTH_RADIX = (1 << LENGTH_RADIX); \ + const RTYPE MASK_RADIX = WIDTH_RADIX - 1; \ +\ + size_t COUNT_RADIX[WIDTH_RADIX]; \ +\ + VTYPE *TMP_RADIX = 0; \ + const size_t BYTES_TMP_RADIX = N_RADIX * sizeof (VTYPE); \ +\ + VTYPE *A_RADIX = V_RADIX; \ + VTYPE *B_RADIX = 0; \ + VTYPE *C_RADIX = A_RADIX; \ +\ + RTYPE MLOWER_RADIX = 0; \ + RTYPE MUPPER_RADIX = MASK_RADIX; \ +\ + bool BOUNDED_RADIX = false; \ + RTYPE UPPER_RADIX = 0; \ + RTYPE LOWER_RADIX = ~UPPER_RADIX; \ + RTYPE SHIFT_RADIX = MASK_RADIX; \ +\ + for (size_t I_RADIX = 0; I_RADIX < 8 * sizeof (RTYPE); \ + I_RADIX += LENGTH_RADIX, SHIFT_RADIX <<= LENGTH_RADIX) { \ + if (BOUNDED_RADIX && \ + (LOWER_RADIX & SHIFT_RADIX) == (UPPER_RADIX & SHIFT_RADIX)) \ + continue; \ +\ + memset (COUNT_RADIX + MLOWER_RADIX, 0, \ + (MUPPER_RADIX - MLOWER_RADIX + 1) * sizeof *COUNT_RADIX); \ +\ + VTYPE *END_RADIX = C_RADIX + N_RADIX; \ +\ + bool SORTED_RADIX = true; \ + RTYPE LAST_RADIX = 0; \ +\ + for (VTYPE *P_RADIX = C_RADIX; P_RADIX != END_RADIX; P_RADIX++) { \ + RTYPE R_RADIX = RANK (*P_RADIX); \ + if (!BOUNDED_RADIX) { \ + LOWER_RADIX &= R_RADIX; \ + UPPER_RADIX |= R_RADIX; \ + } \ + RTYPE S_RADIX = R_RADIX >> I_RADIX; \ + RTYPE M_RADIX = S_RADIX & MASK_RADIX; \ + if (SORTED_RADIX && LAST_RADIX > M_RADIX) \ + SORTED_RADIX = false; \ + else \ + LAST_RADIX = M_RADIX; \ + COUNT_RADIX[M_RADIX]++; \ + } \ +\ + MLOWER_RADIX = (LOWER_RADIX >> I_RADIX) & MASK_RADIX; \ + MUPPER_RADIX = (UPPER_RADIX >> I_RADIX) & MASK_RADIX; \ +\ + if (!BOUNDED_RADIX) { \ + BOUNDED_RADIX = true; \ + if ((LOWER_RADIX & SHIFT_RADIX) == (UPPER_RADIX & SHIFT_RADIX)) \ + continue; \ + } \ +\ + if (SORTED_RADIX) \ + continue; \ +\ + size_t POS_RADIX = 0; \ + for (size_t J_RADIX = MLOWER_RADIX; J_RADIX <= MUPPER_RADIX; \ + J_RADIX++) { \ + const size_t DELTA_RADIX = COUNT_RADIX[J_RADIX]; \ + COUNT_RADIX[J_RADIX] = POS_RADIX; \ + POS_RADIX += DELTA_RADIX; \ + } \ +\ + if (!TMP_RADIX) { \ + assert (C_RADIX == A_RADIX); \ + TMP_RADIX = kissat_malloc (solver, BYTES_TMP_RADIX); \ + B_RADIX = TMP_RADIX; \ + } \ +\ + assert (B_RADIX == TMP_RADIX); \ +\ + VTYPE *D_RADIX = (C_RADIX == A_RADIX) ? B_RADIX : A_RADIX; \ +\ + for (VTYPE *P_RADIX = C_RADIX; P_RADIX != END_RADIX; P_RADIX++) { \ + RTYPE R_RADIX = RANK (*P_RADIX); \ + RTYPE S_RADIX = R_RADIX >> I_RADIX; \ + RTYPE M_RADIX = S_RADIX & MASK_RADIX; \ + const size_t POS_RADIX = COUNT_RADIX[M_RADIX]++; \ + D_RADIX[POS_RADIX] = *P_RADIX; \ + } \ +\ + C_RADIX = D_RADIX; \ + } \ +\ + if (C_RADIX == B_RADIX) \ + memcpy (A_RADIX, B_RADIX, N_RADIX * sizeof *A_RADIX); \ +\ + if (TMP_RADIX) \ + kissat_free (solver, TMP_RADIX, BYTES_TMP_RADIX); \ +\ + CHECK_RANKED (N_RADIX, V_RADIX, RANK); \ + STOP (radix); \ + } while (0) + +#define RADIX_STACK(VTYPE, RTYPE, S, RANK) \ + do { \ + const size_t N_RADIX_STACK = SIZE_STACK (S); \ + VTYPE *A_RADIX_STACK = BEGIN_STACK (S); \ + RADIX_SORT (VTYPE, RTYPE, N_RADIX_STACK, A_RADIX_STACK, RANK); \ + } while (0) + +#endif diff --git a/src/sat/kissat/reduce.c b/src/sat/kissat/reduce.c new file mode 100644 index 000000000..027798520 --- /dev/null +++ b/src/sat/kissat/reduce.c @@ -0,0 +1,198 @@ +#include "reduce.h" +#include "allocate.h" +#include "collect.h" +#include "inline.h" +#include "print.h" +#include "rank.h" +#include "report.h" +#include "tiers.h" +#include "trail.h" + +#include +#include + +bool kissat_reducing (kissat *solver) { + if (!GET_OPTION (reduce)) + return false; + if (!solver->statistics.clauses_redundant) + return false; + if (CONFLICTS < solver->limits.reduce.conflicts) + return false; + return true; +} + +typedef struct reducible reducible; + +struct reducible { + uint64_t rank; + unsigned ref; +}; + +#define RANK_REDUCIBLE(RED) (RED).rank + +// clang-format off +typedef STACK (reducible) reducibles; +// clang-format on + +static bool collect_reducibles (kissat *solver, reducibles *reds, + reference start_ref) { + assert (start_ref != INVALID_REF); + assert (start_ref <= SIZE_STACK (solver->arena)); + ward *const arena = BEGIN_STACK (solver->arena); + clause *start = (clause *) (arena + start_ref); + const clause *const end = (clause *) END_STACK (solver->arena); + assert (start < end); + while (start != end && !start->redundant) + start = kissat_next_clause (start); + if (start == end) { + solver->first_reducible = INVALID_REF; + LOG ("no reducible clause candidate left"); + return false; + } + const reference redundant = (ward *) start - arena; +#ifdef LOGGING + if (redundant < solver->first_reducible) + LOG ("updating start of redundant clauses from %zu to %zu", + (size_t) solver->first_reducible, (size_t) redundant); + else + LOG ("no update to start of redundant clauses %zu", + (size_t) solver->first_reducible); +#endif + solver->first_reducible = redundant; + const unsigned tier1 = TIER1; + const unsigned tier2 = MAX (tier1, TIER2); + assert (tier1 <= tier2); + for (clause *c = start; c != end; c = kissat_next_clause (c)) { + if (!c->redundant) + continue; + if (c->garbage) + continue; + const unsigned used = c->used; + if (used) + c->used = used - 1; + if (c->reason) + continue; + const unsigned glue = c->glue; + if (glue <= tier1 && used) + continue; + if (glue <= tier2 && used >= MAX_USED - 1) + continue; + assert (kissat_clause_in_arena (solver, c)); + reducible red; + const uint64_t negative_size = ~c->size; + const uint64_t negative_glue = ~c->glue; + red.rank = negative_size | (negative_glue << 32); + red.ref = (ward *) c - arena; + PUSH_STACK (*reds, red); + } + if (EMPTY_STACK (*reds)) { + kissat_phase (solver, "reduce", GET (reductions), + "did not find any reducible redundant clause"); + return false; + } + return true; +} + +#define USEFULNESS RANK_REDUCIBLE + +static void sort_reducibles (kissat *solver, reducibles *reds) { + RADIX_STACK (reducible, uint64_t, *reds, USEFULNESS); +} + +static void mark_less_useful_clauses_as_garbage (kissat *solver, + reducibles *reds) { + statistics *statistics = &solver->statistics; + const double high = GET_OPTION (reducehigh) * 0.1; + const double low = GET_OPTION (reducelow) * 0.1; + double percent; + if (low < high) { + const double delta = high - low; + percent = high - delta / log10 (statistics->reductions + 9); + } else + percent = low; + const double fraction = percent / 100.0; + const size_t size = SIZE_STACK (*reds); + size_t target = size * fraction; +#ifndef QUIET + const size_t clauses = + statistics->clauses_irredundant + statistics->clauses_redundant; + kissat_phase (solver, "reduce", GET (reductions), + "reducing %zu (%.0f%%) out of %zu (%.0f%%) " + "reducible clauses", + target, kissat_percent (target, size), size, + kissat_percent (size, clauses)); +#endif + unsigned reduced = 0, reduced1 = 0, reduced2 = 0, reduced3 = 0; + ward *arena = BEGIN_STACK (solver->arena); + const reducible *const begin = BEGIN_STACK (*reds); + const reducible *const end = END_STACK (*reds); + const unsigned tier1 = TIER1; + const unsigned tier2 = TIER2; + for (const reducible *p = begin; p != end && target--; p++) { + clause *c = (clause *) (arena + p->ref); + assert (kissat_clause_in_arena (solver, c)); + assert (!c->garbage); + assert (!c->reason); + assert (c->redundant); + LOGCLS (c, "reducing"); + kissat_mark_clause_as_garbage (solver, c); + reduced++; + if (c->glue <= tier1) + reduced1++; + else if (c->glue <= tier2) + reduced2++; + else + reduced3++; + } + ADD (clauses_reduced_tier1, reduced1); + ADD (clauses_reduced_tier2, reduced2); + ADD (clauses_reduced_tier3, reduced3); + ADD (clauses_reduced, reduced); +} + +int kissat_reduce (kissat *solver) { + START (reduce); + INC (reductions); + kissat_phase (solver, "reduce", GET (reductions), + "reduce limit %" PRIu64 " hit after %" PRIu64 " conflicts", + solver->limits.reduce.conflicts, CONFLICTS); + kissat_compute_and_set_tier_limits (solver); + bool compact = kissat_compacting (solver); + reference start = compact ? 0 : solver->first_reducible; + if (start != INVALID_REF) { +#ifndef QUIET + size_t arena_size = SIZE_STACK (solver->arena); + size_t words_to_sweep = arena_size - start; + size_t bytes_to_sweep = sizeof (word) * words_to_sweep; + kissat_phase (solver, "reduce", GET (reductions), + "reducing clauses after offset %" REFERENCE_FORMAT + " in arena", + start); + kissat_phase (solver, "reduce", GET (reductions), + "reducing %zu words %s %.0f%%", words_to_sweep, + FORMAT_BYTES (bytes_to_sweep), + kissat_percent (words_to_sweep, arena_size)); +#endif + if (kissat_flush_and_mark_reason_clauses (solver, start)) { + reducibles reds; + INIT_STACK (reds); + if (collect_reducibles (solver, &reds, start)) { + sort_reducibles (solver, &reds); + mark_less_useful_clauses_as_garbage (solver, &reds); + RELEASE_STACK (reds); + kissat_sparse_collect (solver, compact, start); + } else if (compact) + kissat_sparse_collect (solver, compact, start); + else + kissat_unmark_reason_clauses (solver, start); + } else + assert (solver->inconsistent); + } else + kissat_phase (solver, "reduce", GET (reductions), "nothing to reduce"); + kissat_classify (solver); + UPDATE_CONFLICT_LIMIT (reduce, reductions, SQRT, false); + solver->last.conflicts.reduce = CONFLICTS; + REPORT (0, '-'); + STOP (reduce); + return solver->inconsistent ? 20 : 0; +} diff --git a/src/sat/kissat/reduce.h b/src/sat/kissat/reduce.h new file mode 100644 index 000000000..c82c0481d --- /dev/null +++ b/src/sat/kissat/reduce.h @@ -0,0 +1,11 @@ +#ifndef _reduce_h_INCLUDED +#define _reduce_h_INCLUDED + +#include + +struct kissat; + +bool kissat_reducing (struct kissat *); +int kissat_reduce (struct kissat *); + +#endif diff --git a/src/sat/kissat/reference.h b/src/sat/kissat/reference.h new file mode 100644 index 000000000..028fc4b5f --- /dev/null +++ b/src/sat/kissat/reference.h @@ -0,0 +1,19 @@ +#ifndef _reference_h_INCLUDED +#define _reference_h_INCLUDED + +#include "stack.h" + +typedef unsigned reference; + +#define REFERENCE_FORMAT "u" + +#define LD_MAX_REF 31u +#define MAX_REF ((1u << LD_MAX_REF) - 1) + +#define INVALID_REF UINT_MAX + +// clang-format off +typedef STACK (reference) references; +// clang-format on + +#endif diff --git a/src/sat/kissat/reluctant.c b/src/sat/kissat/reluctant.c new file mode 100644 index 000000000..54b4558e5 --- /dev/null +++ b/src/sat/kissat/reluctant.c @@ -0,0 +1,67 @@ +#include "internal.h" +#include "logging.h" + +void kissat_enable_reluctant (reluctant *reluctant, uint64_t period, + uint64_t limit) { + if (limit && period > limit) + period = limit; + reluctant->limited = (limit > 0); + reluctant->trigger = false; + reluctant->period = period; + reluctant->wait = period; + reluctant->u = reluctant->v = 1; + reluctant->limit = limit; +} + +void kissat_disable_reluctant (reluctant *reluctant) { + reluctant->period = 0; +} + +void kissat_tick_reluctant (reluctant *reluctant) { + if (!reluctant->period) + return; + + if (reluctant->trigger) + return; + + assert (reluctant->wait > 0); + if (--reluctant->wait) + return; + + uint64_t u = reluctant->u; + uint64_t v = reluctant->v; + + if ((u & -u) == v) { + u++; + v = 1; + } else { + assert (UINT64_MAX / 2 >= v); + v *= 2; + } + + assert (v); + assert (UINT64_MAX / v >= reluctant->period); + uint64_t wait = v * reluctant->period; + + if (reluctant->limited && wait > reluctant->limit) { + u = v = 1; + wait = reluctant->period; + } + + reluctant->trigger = true; + reluctant->wait = wait; + reluctant->u = u; + reluctant->v = v; +} + +void kissat_init_reluctant (kissat *solver) { + if (GET_OPTION (reluctant)) { + LOG ("enable reluctant doubling with period %d limit %d", + GET_OPTION (reluctantint), GET_OPTION (reluctantlim)); + kissat_enable_reluctant (&solver->reluctant, GET_OPTION (reluctantint), + GET_OPTION (reluctantlim)); + } else { + LOG ("reluctant doubling disabled and thus no stable restarts"); + kissat_disable_reluctant (&solver->reluctant); + } +} diff --git a/src/sat/kissat/reluctant.h b/src/sat/kissat/reluctant.h new file mode 100644 index 000000000..93513aabe --- /dev/null +++ b/src/sat/kissat/reluctant.h @@ -0,0 +1,33 @@ +#ifndef _reluctant_h_INCLUDED +#define _reluctant_h_INCLUDED + +#include +#include + +typedef struct reluctant reluctant; + +struct reluctant { + bool limited; + bool trigger; + uint64_t period; + uint64_t wait; + uint64_t u, v; + uint64_t limit; +}; + +void kissat_enable_reluctant (reluctant *, uint64_t period, uint64_t limit); +void kissat_disable_reluctant (reluctant *); +void kissat_tick_reluctant (reluctant *); + +static inline bool kissat_reluctant_triggered (reluctant *reluctant) { + if (!reluctant->trigger) + return false; + reluctant->trigger = false; + return true; +} + +struct kissat; + +void kissat_init_reluctant (struct kissat *); + +#endif diff --git a/src/sat/kissat/reorder.c b/src/sat/kissat/reorder.c new file mode 100644 index 000000000..e1d4fe8c5 --- /dev/null +++ b/src/sat/kissat/reorder.c @@ -0,0 +1,217 @@ +#include "reorder.h" +#include "backtrack.h" +#include "bump.h" +#include "inline.h" +#include "inlineheap.h" +#include "inlinequeue.h" +#include "inlinevector.h" +#include "internal.h" +#include "logging.h" +#include "print.h" +#include "report.h" +#include "sort.h" + +bool kissat_reordering (kissat *solver) { + if (!GET_OPTION (reorder)) + return false; + if (!solver->stable && GET_OPTION (reorder) < 2) + return false; + if (solver->level) + return false; + return CONFLICTS >= solver->limits.reorder.conflicts; +} + +static double *compute_weights (kissat *solver) { + double *weights = kissat_calloc (solver, LITS, sizeof *weights); + const unsigned max_size = GET_OPTION (reordermaxsize); + LOG ("limiting weight computation to maximum clause size %u", max_size); + assert (2 <= max_size); + double *table = kissat_nalloc (solver, max_size + 1, sizeof *table); + { + double weight = 1; + for (unsigned size = 2; size <= max_size; size++) { + LOG ("score table[%u] = %g", size, weight); + table[size] = weight, weight /= 2.0; + } + } + { + assert (!solver->level); + const signed char *const values = solver->values; + const clause *last = kissat_last_irredundant_clause (solver); + for (all_clauses (c)) { + if (last && c > last) + break; + if (c->redundant) + continue; + if (c->garbage) + continue; + unsigned size = 0; + for (all_literals_in_clause (lit, c)) { + const signed char value = values[lit]; + if (value > 0) + goto CONTINUE_WITH_NEXT_CLAUSE; + if (!value && size < max_size && ++size == max_size) + break; + } + const double weight = table[size]; + for (all_literals_in_clause (lit, c)) + weights[lit] += weight; + CONTINUE_WITH_NEXT_CLAUSE:; + } + } + assert (solver->watching); + { + double weight = table[2]; + kissat_dealloc (solver, table, max_size + 1, sizeof *table); + for (all_literals (lit)) { + const unsigned idx = IDX (lit); + if (!ACTIVE (idx)) + continue; + watches *watches = &WATCHES (lit); + for (all_binary_blocking_watches (watch, *watches)) { + if (!watch.type.binary) + continue; + const unsigned other = watch.type.lit; + if (other < lit) + continue; + const unsigned other_idx = IDX (other); + if (!ACTIVE (other_idx)) + continue; + weights[lit] += weight; + weights[other] += weight; + } + } + } + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + unsigned lit = LIT (idx), not_lit = NOT (lit); + double pos = weights[lit], neg = weights[not_lit]; + double max_pos_neg = MAX (pos, neg); + double min_pos_neg = MIN (pos, neg); + double scaled_min_pos_neg = 2 * min_pos_neg; + double weight = max_pos_neg + scaled_min_pos_neg; + LOG ("computed weight %g " + "= %g + %g = max (%g, %g) + 2 * min (%g, %g) of %s", + weight, max_pos_neg, scaled_min_pos_neg, pos, neg, pos, neg, + LOGVAR (idx)); + weights[idx] = weight; + } + return weights; +} + +static bool less_focused_order (unsigned a, unsigned b, links *links, + double *weights) { + double u = weights[a], v = weights[b]; + if (u < v) + return true; + if (u > v) + return false; + unsigned s = links[a].stamp, t = links[b].stamp; + return s < t; +} + +static bool less_stable_order (unsigned a, unsigned b, heap *scores, + double *weights) { + double u = weights[a], v = weights[b]; + if (u < v) + return true; + if (u > v) + return false; + double s = kissat_get_heap_score (scores, a); + double t = kissat_get_heap_score (scores, b); + if (s < t) + return true; + if (s > t) + return false; + return b < a; +} + +#define LESS_FOCUSED_ORDER(A, B) less_focused_order (A, B, links, weights) + +#define LESS_STABLE_ORDER(A, B) less_stable_order (A, B, scores, weights) + +static void sort_active_variables_by_weight (kissat *solver, + unsigneds *sorted, + double *weights) { + INIT_STACK (*sorted); + for (all_variables (idx)) + if (ACTIVE (idx)) + PUSH_STACK (*sorted, idx); + if (solver->stable) { + heap *scores = SCORES; + SORT_STACK (unsigned, *sorted, LESS_STABLE_ORDER); +#ifdef LOGGING + for (all_stack (unsigned, idx, *sorted)) + if (ACTIVE (idx)) + LOG ("reordered %s with weight %g score %g", LOGVAR (idx), + weights[idx], kissat_get_heap_score (scores, idx)); +#endif + } else { + struct links *links = solver->links; + SORT_STACK (unsigned, *sorted, LESS_FOCUSED_ORDER); +#ifdef LOGGING + for (all_stack (unsigned, idx, *sorted)) + if (ACTIVE (idx)) + LOG ("reordered %s with weight %g stamp %u", LOGVAR (idx), + weights[idx], links[idx].stamp); +#endif + } +} + +static void reorder_focused (kissat *solver) { + INC (reordered_focused); + assert (!solver->stable); + double *weights = compute_weights (solver); + unsigneds sorted; + sort_active_variables_by_weight (solver, &sorted, weights); + kissat_dealloc (solver, weights, LITS, sizeof *weights); + for (all_stack (unsigned, idx, sorted)) { + assert (ACTIVE (idx)); + kissat_move_to_front (solver, idx); + } + RELEASE_STACK (sorted); +} + +static void reorder_stable (kissat *solver) { + INC (reordered_stable); + assert (solver->stable); + double *weights = compute_weights (solver); + kissat_rescale_scores (solver); + unsigneds sorted; + sort_active_variables_by_weight (solver, &sorted, weights); + heap *scores = SCORES; + while (!EMPTY_STACK (sorted)) { + unsigned idx = POP_STACK (sorted); + assert (ACTIVE (idx)); + const double old_score = kissat_get_heap_score (scores, idx); + const double weight = weights[idx]; + const double new_score = old_score + weight; + LOG ("updating score of %s to %g = %g (old score) + %g (weight)", + LOGVAR (idx), new_score, old_score, weight); + kissat_update_heap (solver, scores, idx, new_score); + } + kissat_dealloc (solver, weights, LITS, sizeof *weights); + RELEASE_STACK (sorted); +} + +void kissat_reorder (kissat *solver) { + START (reorder); + INC (reordered); + assert (!solver->level); + kissat_phase (solver, "reorder", GET (reordered), + "reorder limit %" PRIu64 " hit a after %" PRIu64 + " conflicts in %s mode ", + solver->limits.reorder.conflicts, CONFLICTS, + solver->stable ? "stable" : "focused"); + if (solver->stable) + reorder_stable (solver); + else + reorder_focused (solver); + kissat_phase (solver, "reorder", GET (reordered), + "reordered decisions in %s search mode", + solver->stable ? "stable" : "focused"); + UPDATE_CONFLICT_LIMIT (reorder, reordered, LINEAR, false); + REPORT (0, 'o'); + STOP (reorder); +} diff --git a/src/sat/kissat/reorder.h b/src/sat/kissat/reorder.h new file mode 100644 index 000000000..24351927e --- /dev/null +++ b/src/sat/kissat/reorder.h @@ -0,0 +1,11 @@ +#ifndef _reorder_h_INCLUDED +#define _reorder_h_INCLUDED + +#include + +struct kissat; + +bool kissat_reordering (struct kissat *); +void kissat_reorder (struct kissat *); + +#endif diff --git a/src/sat/kissat/rephase.c b/src/sat/kissat/rephase.c new file mode 100644 index 000000000..33783f675 --- /dev/null +++ b/src/sat/kissat/rephase.c @@ -0,0 +1,137 @@ +#include "rephase.h" +#include "backtrack.h" +#include "decide.h" +#include "internal.h" +#include "logging.h" +#include "print.h" +#include "report.h" +#include "terminate.h" +#include "walk.h" + +#include +#include + +static void kissat_reset_best_assigned (kissat *solver) { + if (!solver->best_assigned) + return; + kissat_extremely_verbose (solver, + "resetting best assigned trail height %u to 0", + solver->best_assigned); + solver->best_assigned = 0; +} + +static void kissat_reset_target_assigned (kissat *solver) { + if (!solver->target_assigned) + return; + kissat_extremely_verbose ( + solver, "resetting target assigned trail height %u to 0", + solver->target_assigned); + solver->target_assigned = 0; +} + +bool kissat_rephasing (kissat *solver) { + if (!GET_OPTION (rephase)) + return false; + if (!solver->stable) + return false; + return CONFLICTS > solver->limits.rephase.conflicts; +} + +static char rephase_best (kissat *solver) { + const value *const best = solver->phases.best; + const value *const end_of_best = best + VARS; + value const *b; + + value *const saved = solver->phases.saved; + value *s, tmp; + + for (s = saved, b = best; b != end_of_best; s++, b++) + if ((tmp = *b)) + *s = tmp; + + INC (rephased_best); + + return 'B'; +} + +static char rephase_original (kissat *solver) { + const value initial_phase = INITIAL_PHASE; + value *s = solver->phases.saved; + const value *const end = s + VARS; + while (s != end) + *s++ = initial_phase; + INC (rephased_original); + return 'O'; +} + +static char rephase_inverted (kissat *solver) { + const value inverted_initial_phase = -INITIAL_PHASE; + value *s = solver->phases.saved; + const value *const end = s + VARS; + while (s != end) + *s++ = inverted_initial_phase; + INC (rephased_inverted); + return 'I'; +} + +static char rephase_walking (kissat *solver) { + assert (kissat_walking (solver)); + STOP (rephase); + kissat_walk (solver); + START (rephase); + INC (rephased_walking); + return 'W'; +} + +static char (*rephase_schedule[]) (kissat *) = { + rephase_best, rephase_walking, rephase_inverted, + rephase_best, rephase_walking, rephase_original, +}; + +#define size_rephase_schedule \ + (sizeof rephase_schedule / sizeof *rephase_schedule) + +#ifndef QUIET + +static const char *rephase_type_as_string (char type) { + if (type == 'B') + return "best"; + if (type == 'I') + return "inverted"; + if (type == 'O') + return "original"; + assert (type == 'W'); + return "walking"; +} + +#endif + +static char reset_phases (kissat *solver) { + const uint64_t count = GET (rephased); + assert (count > 0); + const uint64_t select = (count - 1) % (uint64_t) size_rephase_schedule; + const char type = rephase_schedule[select](solver); + kissat_phase ( + solver, "rephase", GET (rephased), "%s phases in %s search mode", + rephase_type_as_string (type), solver->stable ? "stable" : "focused"); + LOG ("copying saved phases as target phases"); + memcpy (solver->phases.target, solver->phases.saved, VARS); + UPDATE_CONFLICT_LIMIT (rephase, rephased, NLOG3N, false); + kissat_reset_target_assigned (solver); + if (type == 'B') + kissat_reset_best_assigned (solver); + return type; +} + +void kissat_rephase (kissat *solver) { + kissat_backtrack_propagate_and_flush_trail (solver); + assert (!solver->inconsistent); + START (rephase); + INC (rephased); +#ifndef QUIET + const char type = +#endif + reset_phases (solver); + REPORT (0, type); + STOP (rephase); +} diff --git a/src/sat/kissat/rephase.h b/src/sat/kissat/rephase.h new file mode 100644 index 000000000..b59382e4e --- /dev/null +++ b/src/sat/kissat/rephase.h @@ -0,0 +1,11 @@ +#ifndef _rephase_h_INCLUDED +#define _rephase_h_INCLUDED + +#include + +struct kissat; + +bool kissat_rephasing (struct kissat *); +void kissat_rephase (struct kissat *); + +#endif diff --git a/src/sat/kissat/report.c b/src/sat/kissat/report.c new file mode 100644 index 000000000..a9fc092b5 --- /dev/null +++ b/src/sat/kissat/report.c @@ -0,0 +1,159 @@ +#ifndef QUIET + +#include "report.h" +#include "colors.h" +#include "internal.h" +#include "print.h" +#include "resources.h" + +#include +#include + +#define MB (kissat_current_resident_set_size () / (double) (1 << 20)) + +#define REMAINING_VARIABLES \ + kissat_percent (solver->active, statistics->variables_original) + +#define REPORTS \ + REP ("seconds", "5.2f", kissat_time (solver)) \ + REP ("MB", "2.0f", MB) \ + REP ("level", ".0f", AVERAGE (level)) \ + REP ("switched", "1" PRIu64, statistics->switched) \ + REP ("reductions", "1" PRIu64, statistics->reductions) \ + REP ("restarts", "2" PRIu64, statistics->restarts) \ + REP ("rate", ".0f", AVERAGE (decision_rate)) \ + REP ("conflicts", "3" PRIu64, CONFLICTS) \ + REP ("redundant", "3" PRIu64, REDUNDANT_CLAUSES) \ + REP ("size/glue", ".1f", \ + kissat_average (AVERAGE (size), AVERAGE (slow_glue))) \ + REP ("size", ".0f", AVERAGE (size)) \ + REP ("glue", ".0f", AVERAGE (slow_glue)) \ + REP ("tier1", "1u", solver->tier1[solver->stable]) \ + REP ("tier2", "1u", solver->tier2[solver->stable]) \ + REP ("trail", ".0f%%", AVERAGE (trail)) \ + REP ("binary", "3" PRIu64, BINARY_CLAUSES) \ + REP ("irredundant", "2" PRIu64, IRREDUNDANT_CLAUSES) \ + REP ("variables", "2u", solver->active) \ + REP ("remaining", "1.0f%%", REMAINING_VARIABLES) + +void kissat_report (kissat *solver, bool verbose, char type) { + statistics *statistics = &solver->statistics; + const int verbosity = kissat_verbosity (solver); + if (verbosity < 0) + return; + if (verbose && verbosity < 2) + return; + char line[128], *p = line; + unsigned pad[32], n = 1, pos = 0; + pad[0] = 0; + // clang-format off +#define REP(NAME,FMT,VALUE) \ + do { \ + *p++ = ' ', pos++; \ + sprintf (p, "%" FMT, VALUE); \ + while (*p) \ + p++, pos++; \ + pad[n++] = pos; \ + } while (0); + REPORTS +#undef REP + // clang-format on + assert (p < line + sizeof line); + TERMINAL (stdout, 1); + if (!(solver->limits.reports++ % 20)) { +#define ROWS 3 + unsigned last[ROWS]; + char rows[ROWS][128], *r[ROWS]; + for (unsigned j = 0; j < ROWS; j++) + last[j] = 0, rows[j][0] = 0, r[j] = rows[j]; + unsigned row = 0, i = 1; +#define REP(NAME, FMT, VALUE) \ + do { \ + if (last[row]) \ + *r[row]++ = ' ', last[row]++; \ + unsigned target = pad[i]; \ + const unsigned name_len = strlen (NAME); \ + const unsigned val_len = target - pad[i - 1] - 1; \ + if (val_len < name_len) \ + target += (name_len - val_len) / 2; \ + while (last[row] + name_len < target) \ + *r[row]++ = ' ', last[row]++; \ + for (const char *p = NAME; *p; p++) \ + *r[row]++ = *p, last[row]++; \ + if (++row == ROWS) \ + row = 0; \ + i++; \ + } while (0); + REPORTS +#undef REP + assert (i == n); + for (unsigned j = 0; j < ROWS; j++) { + assert (r[j] < rows[j] + sizeof rows[j]); + *r[j] = 0; + } + if (solver->limits.reports > 1) + kissat_line (solver); + for (unsigned j = 0; j < ROWS; j++) { + fputs (solver->prefix, stdout); + COLOR (CYAN); + fputs (rows[j], stdout); + COLOR (NORMAL); + fputc ('\n', stdout); + } + kissat_line (solver); + } + kissat_prefix (solver); + switch (type) { + case '1': + case '0': + case '?': + case 'i': + case '.': + COLOR (BOLD); + break; + case 'e': + COLOR (BOLD GREEN); + break; + case '2': + case 's': + COLOR (GREEN); + break; + case 'f': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + COLOR (BLUE); + break; + case 'b': + case 'c': + case 'd': + case '=': + COLOR (BOLD BLUE); + break; + case '[': + case ']': + COLOR (MAGENTA); + break; + case '(': + case ')': + COLOR (BOLD YELLOW); + } + fputc (type, stdout); + COLOR (NORMAL); + if (solver->preprocessing) + COLOR (YELLOW); + else if (solver->stable) + COLOR (MAGENTA); + fputs (line, stdout); + COLOR (NORMAL); + fputc ('\n', stdout); + fflush (stdout); +} + +#else + +int kissat_report_dummy_to_avoid_warning; + +#endif diff --git a/src/sat/kissat/report.h b/src/sat/kissat/report.h new file mode 100644 index 000000000..744aea32a --- /dev/null +++ b/src/sat/kissat/report.h @@ -0,0 +1,22 @@ +#ifndef _report_h_INCLUDED +#define _report_h_INCLUDED + +#ifdef QUIET + +#define REPORT(...) \ + do { \ + } while (0) + +#else + +#include + +struct kissat; + +void kissat_report (struct kissat *, bool verbose, char type); + +#define REPORT(LEVEL, TYPE) kissat_report (solver, (LEVEL), (TYPE)) + +#endif + +#endif diff --git a/src/sat/kissat/require.h b/src/sat/kissat/require.h new file mode 100644 index 000000000..9325c9b3f --- /dev/null +++ b/src/sat/kissat/require.h @@ -0,0 +1,30 @@ +#ifndef _require_h_INCLUDED +#define _require_h_INCLUDED + +#define kissat_require(COND, ...) \ + do { \ + if ((COND)) \ + break; \ + kissat_fatal_message_start (); \ + fprintf (stderr, "calling '%s': ", __func__); \ + fprintf (stderr, __VA_ARGS__); \ + fputc ('\n', stderr); \ + fflush (stderr); \ + kissat_abort (); \ + } while (0) + +#define kissat_require_initialized(SOLVER) \ + kissat_require (SOLVER, "uninitialized") + +#define kissat_require_valid_external_internal(LIT) \ + do { \ + kissat_require ((LIT) != INT_MIN, "invalid literal '%d' (INT_MIN)", \ + (LIT)); \ + const int TMP_IDX = ABS (LIT); \ + kissat_require (TMP_IDX <= EXTERNAL_MAX_VAR, \ + "invalid literal '%d' (variable larger than %d)", \ + (LIT), EXTERNAL_MAX_VAR); \ + assert (VALID_EXTERNAL_LITERAL (LIT)); \ + } while (0) + +#endif diff --git a/src/sat/kissat/resize.c b/src/sat/kissat/resize.c new file mode 100644 index 000000000..593d813c8 --- /dev/null +++ b/src/sat/kissat/resize.c @@ -0,0 +1,137 @@ +#include "resize.h" +#include "allocate.h" +#include "inline.h" +#include "require.h" + +#include +#include + +#define NREALLOC_GENERIC(TYPE, NAME, ELEMENTS_PER_BLOCK) \ + do { \ + const size_t block_size = sizeof (TYPE); \ + solver->NAME = \ + kissat_nrealloc (solver, solver->NAME, old_size, new_size, \ + ELEMENTS_PER_BLOCK * block_size); \ + } while (0) + +#define CREALLOC_GENERIC(TYPE, NAME, ELEMENTS_PER_BLOCK) \ + do { \ + const size_t block_size = sizeof (TYPE); \ + TYPE *NAME = \ + kissat_calloc (solver, ELEMENTS_PER_BLOCK * new_size, block_size); \ + if (old_size) { \ + const size_t bytes = ELEMENTS_PER_BLOCK * old_size * block_size; \ + memcpy (NAME, solver->NAME, bytes); \ + } \ + kissat_dealloc (solver, solver->NAME, ELEMENTS_PER_BLOCK *old_size, \ + block_size); \ + solver->NAME = NAME; \ + } while (0) + +#define NREALLOC_VARIABLE_INDEXED(TYPE, NAME) \ + NREALLOC_GENERIC (TYPE, NAME, 1) + +#define NREALLOC_LITERAL_INDEXED(TYPE, NAME) \ + NREALLOC_GENERIC (TYPE, NAME, 2) + +#define CREALLOC_VARIABLE_INDEXED(TYPE, NAME) \ + CREALLOC_GENERIC (TYPE, NAME, 1) + +#define CREALLOC_LITERAL_INDEXED(TYPE, NAME) \ + CREALLOC_GENERIC (TYPE, NAME, 2) + +static void reallocate_trail (kissat *solver, unsigned old_size, + unsigned new_size) { + unsigned propagated = solver->propagate - BEGIN_ARRAY (solver->trail); + REALLOCATE_ARRAY (solver->trail, old_size, new_size); + solver->propagate = BEGIN_ARRAY (solver->trail) + propagated; +} + +void kissat_increase_size (kissat *solver, unsigned new_size) { + assert (solver->vars <= new_size); + const unsigned old_size = solver->size; + if (old_size >= new_size) + return; + +#ifdef METRICS + LOG ("%s before increasing size from %u to %u", + FORMAT_BYTES (kissat_allocated (solver)), old_size, new_size); +#endif + CREALLOC_VARIABLE_INDEXED (assigned, assigned); + CREALLOC_VARIABLE_INDEXED (flags, flags); + NREALLOC_VARIABLE_INDEXED (links, links); + + CREALLOC_LITERAL_INDEXED (mark, marks); + CREALLOC_LITERAL_INDEXED (value, values); + CREALLOC_LITERAL_INDEXED (watches, watches); + + reallocate_trail (solver, old_size, new_size); + kissat_resize_heap (solver, SCORES, new_size); + kissat_increase_phases (solver, new_size); + + solver->size = new_size; + +#ifdef METRICS + LOG ("%s after increasing size from %u to %u", + FORMAT_BYTES (kissat_allocated (solver)), old_size, new_size); +#endif +} + +void kissat_decrease_size (kissat *solver) { + const unsigned old_size = solver->size; + const unsigned new_size = solver->vars; + +#ifdef METRICS + LOG ("%s before decreasing size from %u to %u", + FORMAT_BYTES (kissat_allocated (solver)), old_size, new_size); +#endif + + NREALLOC_VARIABLE_INDEXED (assigned, assigned); + NREALLOC_VARIABLE_INDEXED (flags, flags); + NREALLOC_VARIABLE_INDEXED (links, links); + + NREALLOC_LITERAL_INDEXED (mark, marks); + NREALLOC_LITERAL_INDEXED (value, values); + NREALLOC_LITERAL_INDEXED (watches, watches); + + reallocate_trail (solver, old_size, new_size); + kissat_resize_heap (solver, SCORES, new_size); + kissat_decrease_phases (solver, new_size); + + solver->size = new_size; + +#ifdef METRICS + LOG ("%s after decreasing size from %u to %u", + FORMAT_BYTES (kissat_allocated (solver)), old_size, new_size); +#endif +} + +void kissat_enlarge_variables (kissat *solver, unsigned new_vars) { + if (solver->vars >= new_vars) + return; + assert (new_vars <= INTERNAL_MAX_VAR + 1); + LOG ("enlarging variables from %u to %u", solver->vars, new_vars); + const size_t old_size = solver->size; + if (old_size < new_vars) { + LOG ("old size %zu below requested new number of variables %u", + old_size, new_vars); + size_t new_size; + if (!old_size) + new_size = new_vars; + else { + if (kissat_is_power_of_two (old_size)) { + assert (old_size <= UINT_MAX / 2); + new_size = 2 * old_size; + } else { + assert (1 < old_size); + new_size = 2; + } + while (new_size < new_vars) { + assert (new_size <= UINT_MAX / 2); + new_size *= 2; + } + } + kissat_increase_size (solver, new_size); + } + solver->vars = new_vars; +} diff --git a/src/sat/kissat/resize.h b/src/sat/kissat/resize.h new file mode 100644 index 000000000..089fe9394 --- /dev/null +++ b/src/sat/kissat/resize.h @@ -0,0 +1,10 @@ +#ifndef _resize_h_INCLUDED +#define _resize_h_INCLUDED + +struct kissat; + +void kissat_decrease_size (struct kissat *solver); +void kissat_increase_size (struct kissat *, unsigned new_size); +void kissat_enlarge_variables (struct kissat *, unsigned new_vars); + +#endif diff --git a/src/sat/kissat/resolve.c b/src/sat/kissat/resolve.c new file mode 100644 index 000000000..49567b768 --- /dev/null +++ b/src/sat/kissat/resolve.c @@ -0,0 +1,370 @@ +#include "resolve.h" +#include "eliminate.h" +#include "gates.h" +#include "inline.h" +#include "print.h" + +#include +#include + +static inline unsigned occurrences_literal (kissat *solver, unsigned lit, + bool *update) { + assert (!solver->watching); + + watches *watches = &WATCHES (lit); +#ifdef LOGGING + const size_t size_watches = SIZE_WATCHES (*watches); + LOG ("literal %s has %zu watches", LOGLIT (lit), size_watches); +#endif + const unsigned clslim = GET_OPTION (eliminateclslim); + + watch *const begin = BEGIN_WATCHES (*watches), *q = begin; + const watch *const end = END_WATCHES (*watches), *p = q; + + const value *const values = solver->values; + ward *const arena = BEGIN_STACK (solver->arena); + + bool failed = false; + unsigned res = 0; + + while (p != end) { + const watch head = *q++ = *p++; + if (head.type.binary) { + const unsigned other = head.binary.lit; + const value value = values[other]; + assert (value >= 0); + if (value > 0) { + kissat_eliminate_binary (solver, lit, other); + q--; + } else + res++; + } else { + const reference ref = head.large.ref; + assert (ref < SIZE_STACK (solver->arena)); + clause *const c = (struct clause *) (arena + ref); + if (c->garbage) + q--; + else if (c->size > clslim) { + LOG ("literal %s watches too long clause of size %u", LOGLIT (lit), + c->size); + failed = true; + break; + } else + res++; + } + } + while (p != end) + *q++ = *p++; + SET_END_OF_WATCHES (*watches, q); + if (failed) + return UINT_MAX; + if (q != end) { + *update = true; + LOG ("literal %s actually occurs only %u times", LOGLIT (lit), res); + } + return res; +} + +static inline clause *watch_to_clause (kissat *solver, ward *const arena, + clause *const tmp, unsigned lit, + watch watch) { + clause *res; + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + tmp->lits[0] = lit; + tmp->lits[1] = other; + res = tmp; + } else { + const reference ref = watch.large.ref; + assert (ref < SIZE_STACK (solver->arena)); + res = (struct clause *) (arena + ref); + } +#ifdef NDEBUG + (void) solver; +#endif + return res; +} + +static bool generate_resolvents (kissat *solver, unsigned lit, + statches *const watches0, + statches *const watches1, + uint64_t *const resolved_ptr, + uint64_t limit) { + const unsigned not_lit = NOT (lit); + unsigned resolved = *resolved_ptr; + bool failed = false; + + clause tmp0, tmp1; + memset (&tmp0, 0, sizeof tmp0); + memset (&tmp1, 0, sizeof tmp1); + tmp0.size = tmp1.size = 2; + + ward *const arena = BEGIN_STACK (solver->arena); + const value *const values = solver->values; + value *const marks = solver->marks; + + const unsigned clslim = GET_OPTION (eliminateclslim); + + for (all_stack (watch, watch0, *watches0)) { + clause *const c = watch_to_clause (solver, arena, &tmp0, lit, watch0); + + if (c->garbage) { + assert (c != &tmp0); + continue; + } + + bool first_antecedent_satisfied = false; + + for (all_literals_in_clause (other, c)) { + if (other == lit) + continue; + const value value = values[other]; + if (value < 0) + continue; + if (value > 0) { + first_antecedent_satisfied = true; + if (c != &tmp0) + kissat_eliminate_clause (solver, c, other); + break; + } + } + + if (first_antecedent_satisfied) + continue; + + for (all_literals_in_clause (other, c)) { + if (other == lit) + continue; + assert (!marks[other]); + marks[other] = 1; + } + + for (all_stack (watch, watch1, *watches1)) { + clause *const d = + watch_to_clause (solver, arena, &tmp1, not_lit, watch1); + + if (d->garbage) { + assert (d != &tmp1); + continue; + } + + LOGCLS (c, "first %s antecedent", LOGLIT (lit)); + LOGCLS (d, "second %s antecedent", LOGLIT (not_lit)); + + bool resolvent_satisfied_or_tautological = false; + const size_t saved = SIZE_STACK (solver->resolvents); + + INC (eliminate_resolutions); + + for (all_literals_in_clause (other, d)) { + if (other == not_lit) + continue; + const value value = values[other]; + if (value < 0) { + LOG2 ("dropping falsified literal %s", LOGLIT (other)); + continue; + } + if (value > 0) { + if (d != &tmp1) + kissat_eliminate_clause (solver, d, other); + resolvent_satisfied_or_tautological = true; + break; + } + if (marks[other]) { + LOG2 ("dropping repeated %s literal", LOGLIT (other)); + continue; + } + const unsigned not_other = NOT (other); + if (marks[not_other]) { + LOG ("resolvent tautological on %s and %s " + "with second %s antecedent", + LOGLIT (NOT (other)), LOGLIT (other), LOGLIT (not_lit)); + resolvent_satisfied_or_tautological = true; + break; + } + LOG2 ("including unassigned literal %s", LOGLIT (other)); + PUSH_STACK (solver->resolvents, other); + } + + if (resolvent_satisfied_or_tautological) { + RESIZE_STACK (solver->resolvents, saved); + continue; + } + + if (++resolved > limit) { + LOG ("limit of %" PRIu64 " resolvent exceeded", limit); + failed = true; + break; + } + + for (all_literals_in_clause (other, c)) { + if (other == lit) + continue; + const value value = values[other]; + assert (value <= 0); + if (value < 0) { + LOG2 ("dropping falsified literal %s", LOGLIT (other)); + continue; + } + PUSH_STACK (solver->resolvents, other); + } + + size_t size_resolvent = SIZE_STACK (solver->resolvents) - saved; + LOGLITS (size_resolvent, BEGIN_STACK (solver->resolvents) + saved, + "resolvent"); + + if (!size_resolvent) { + assert (!solver->inconsistent); + solver->inconsistent = true; + LOG ("resolved empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + failed = true; + break; + } + + if (size_resolvent == 1) { + const unsigned unit = PEEK_STACK (solver->resolvents, saved); + INC (eliminate_units); + kissat_learned_unit (solver, unit); + RESIZE_STACK (solver->resolvents, saved); + if (marks[unit] <= 0) + continue; + LOGCLS (c, "first antecedent becomes satisfied"); + first_antecedent_satisfied = true; + (void) first_antecedent_satisfied; + break; + } + + if (size_resolvent > clslim) { + LOG ("resolvent size limit exceeded"); + failed = true; + break; + } + + PUSH_STACK (solver->resolvents, INVALID_LIT); + } + + for (all_literals_in_clause (other, c)) { + if (other == lit) + continue; + assert (marks[other] == 1); + marks[other] = 0; + } + + if (failed) + break; + } + + *resolved_ptr = resolved; + + return !failed; +} + +bool kissat_generate_resolvents (kissat *solver, unsigned idx, + unsigned *lit_ptr) { + unsigned lit = LIT (idx); + unsigned not_lit = NOT (lit); + + bool update = false; + bool pure = false; + uint64_t limit; + + { + unsigned pos_count = occurrences_literal (solver, lit, &update); + unsigned neg_count = occurrences_literal (solver, not_lit, &update); + + if (pos_count > neg_count) { + SWAP (unsigned, lit, not_lit); + SWAP (size_t, pos_count, neg_count); + } + + const unsigned occlim = GET_OPTION (eliminateocclim); + limit = pos_count + (uint64_t) neg_count; + + if (pos_count && limit > occlim) { + LOG ("no elimination of variable %u " + "since it has %" PRIu64 " > %u occurrences", + idx, limit, occlim); + return false; + } + + if (pos_count) { + const uint64_t bound = solver->bounds.eliminate.additional_clauses; + limit += bound; + LOG ("trying to eliminate %s " + "limit %" PRIu64 " bound %" PRIu64, + LOGVAR (idx), limit, bound); + } else { + LOG ("eliminating pure literal %s thus its variable %u", LOGLIT (lit), + idx); + pure = true; + } + } + + *lit_ptr = lit; + + INC (eliminate_attempted); + if (pure) + return true; + + const bool gates = !pure && kissat_find_gates (solver, lit); + + statches *const gates0 = &solver->gates[0]; + statches *const gates1 = &solver->gates[1]; + + if (solver->values[lit]) { + kissat_extremely_verbose (solver, "definition produced unit"); + CLEAR_STACK (*gates0); + CLEAR_STACK (*gates1); + return false; + } + + bool failed = false; + uint64_t resolved = 0; + + kissat_get_antecedents (solver, lit); + statches *const antecedents0 = &solver->antecedents[0]; + statches *const antecedents1 = &solver->antecedents[1]; + + if (gates) { + LOG ("resolving gates[0] against antecedents[1] clauses"); + if (!generate_resolvents (solver, lit, gates0, antecedents1, &resolved, + limit)) + failed = true; + else { + LOG ("resolving gates[1] against antecedents[0] clauses"); + if (!generate_resolvents (solver, not_lit, gates1, antecedents0, + &resolved, limit)) { + failed = true; + } else if (solver->resolve_gate) { + LOG ("need to resolved gates[0] against gates[1] too"); + if (!generate_resolvents (solver, lit, gates0, gates1, &resolved, + limit)) + failed = true; + } + } + } else { + LOG ("no gate extracted thus resolving all clauses"); + if (!generate_resolvents (solver, lit, antecedents0, antecedents1, + &resolved, limit)) + failed = true; + } + + CLEAR_STACK (*antecedents0); + CLEAR_STACK (*antecedents1); + + if (failed) { + LOG ("elimination of %s failed", LOGVAR (IDX (lit))); + CLEAR_STACK (solver->resolvents); + if (update) + kissat_update_variable_score (solver, idx); + } + + LOG ("resolved %" PRIu64 " resolvents", resolved); + + CLEAR_STACK (*gates0); + CLEAR_STACK (*gates1); + + return !failed; +} diff --git a/src/sat/kissat/resolve.h b/src/sat/kissat/resolve.h new file mode 100644 index 000000000..26bf7a0e9 --- /dev/null +++ b/src/sat/kissat/resolve.h @@ -0,0 +1,11 @@ +#ifndef _resolve_h_INCLUDED +#define _resolve_h_INCLUDED + +#include + +struct kissat; + +bool kissat_generate_resolvents (struct kissat *, unsigned idx, + unsigned *lit_ptr); + +#endif diff --git a/src/sat/kissat/resources.c b/src/sat/kissat/resources.c new file mode 100644 index 000000000..31c0378af --- /dev/null +++ b/src/sat/kissat/resources.c @@ -0,0 +1,104 @@ +#include "resources.h" + +#include + +double kissat_wall_clock_time (void) { + struct timeval tv; + if (gettimeofday (&tv, 0)) + return 0; + return 1e-6 * tv.tv_usec + tv.tv_sec; +} + +#ifndef QUIET + +#include "internal.h" +#include "statistics.h" +#include "utilities.h" + +#include +#include +#include +#include +#include +#include + +double kissat_process_time (void) { + struct rusage u; + double res; + if (getrusage (RUSAGE_SELF, &u)) + return 0; + res = u.ru_utime.tv_sec + 1e-6 * u.ru_utime.tv_usec; + res += u.ru_stime.tv_sec + 1e-6 * u.ru_stime.tv_usec; + return res; +} + +uint64_t kissat_maximum_resident_set_size (void) { + struct rusage u; + if (getrusage (RUSAGE_SELF, &u)) + return 0; + return ((uint64_t) u.ru_maxrss) << 10; +} + +#ifdef __APPLE__ + +#include +mach_port_t mach_task_self (void); + +uint64_t kissat_current_resident_set_size (void) { + struct task_basic_info info; + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if (KERN_SUCCESS != task_info (mach_task_self (), TASK_BASIC_INFO, + (task_info_t) &info, &count)) + return 0; + return info.resident_size; +} + +#else + +uint64_t kissat_current_resident_set_size (void) { + char path[48]; + sprintf (path, "/proc/%" PRIu64 "/statm", (uint64_t) getpid ()); + FILE *file = fopen (path, "r"); + if (!file) + return 0; + uint64_t dummy, rss; + int scanned = fscanf (file, "%" PRIu64 " %" PRIu64 "", &dummy, &rss); + fclose (file); + return scanned == 2 ? rss * sysconf (_SC_PAGESIZE) : 0; +} + +#endif + +void kissat_print_resources (kissat *solver) { + uint64_t rss = kissat_maximum_resident_set_size (); + double t = kissat_time (solver); + printf ("%s" + "%-" SFW1 "s " + "%" SFW2 PRIu64 " " + "%-" SFW3 "s " + "%" SFW4 ".0f " + "MB\n", + solver->prefix, "maximum-resident-set-size:", rss, "bytes", + rss / (double) (1 << 20)); +#ifdef METRICS + statistics *statistics = &solver->statistics; + uint64_t max_allocated = statistics->allocated_max + sizeof (kissat); + printf ("%s" + "%-" SFW1 "s " + "%" SFW2 PRIu64 " " + "%-" SFW3 "s " + "%" SFW4 ".0f " + "%%\n", + solver->prefix, "max-allocated:", max_allocated, "bytes", + kissat_percent (max_allocated, rss)); +#endif + { + format buffer; + memset (&buffer, 0, sizeof buffer); + printf ("%sprocess-time: %30s %18.2f seconds\n", solver->prefix, + kissat_format_time (&buffer, t), t); + } + fflush (stdout); +} + +#endif diff --git a/src/sat/kissat/resources.h b/src/sat/kissat/resources.h new file mode 100644 index 000000000..d9bce7853 --- /dev/null +++ b/src/sat/kissat/resources.h @@ -0,0 +1,23 @@ +#ifndef _resources_h_INCLUDED +#define _resources_h_INCLUDED + +double kissat_wall_clock_time (void); + +#ifndef QUIET + +#ifndef _resources_h_INLCUDED +#define _resources_h_INLCUDED + +#include + +struct kissat; + +double kissat_process_time (void); +uint64_t kissat_current_resident_set_size (void); +uint64_t kissat_maximum_resident_set_size (void); +void kissat_print_resources (struct kissat *); + +#endif + +#endif +#endif diff --git a/src/sat/kissat/restart.c b/src/sat/kissat/restart.c new file mode 100644 index 000000000..dc4edf634 --- /dev/null +++ b/src/sat/kissat/restart.c @@ -0,0 +1,131 @@ +#include "restart.h" +#include "backtrack.h" +#include "bump.h" +#include "decide.h" +#include "internal.h" +#include "kimits.h" +#include "logging.h" +#include "print.h" +#include "reluctant.h" +#include "report.h" + +#include + +bool kissat_restarting (kissat *solver) { + assert (solver->unassigned); + if (!GET_OPTION (restart)) + return false; + if (!solver->level) + return false; + if (CONFLICTS < solver->limits.restart.conflicts) + return false; + if (solver->stable) + return kissat_reluctant_triggered (&solver->reluctant); + const double fast = AVERAGE (fast_glue); + const double slow = AVERAGE (slow_glue); + const double margin = (100.0 + GET_OPTION (restartmargin)) / 100.0; + const double limit = margin * slow; + kissat_extremely_verbose (solver, + "restart glue limit %g = " + "%.02f * %g (slow glue) %c %g (fast glue)", + limit, margin, slow, + (limit > fast ? '>' + : limit == fast ? '=' + : '<'), + fast); + return (limit <= fast); +} + +void kissat_update_focused_restart_limit (kissat *solver) { + assert (!solver->stable); + limits *limits = &solver->limits; + uint64_t restarts = solver->statistics.restarts; + uint64_t delta = GET_OPTION (restartint); + if (restarts) + delta += kissat_logn (restarts) - 1; + limits->restart.conflicts = CONFLICTS + delta; + kissat_extremely_verbose (solver, + "focused restart limit at %" PRIu64 + " after %" PRIu64 " conflicts ", + limits->restart.conflicts, delta); +} + +static unsigned reuse_stable_trail (kissat *solver) { + const heap *const scores = SCORES; + const unsigned next_idx = kissat_next_decision_variable (solver); + const double limit = kissat_get_heap_score (scores, next_idx); + unsigned level = solver->level, res = 0; + while (res < level) { + frame *f = &FRAME (res + 1); + const unsigned idx = IDX (f->decision); + const double score = kissat_get_heap_score (scores, idx); + if (score <= limit) + break; + res++; + } + return res; +} + +static unsigned reuse_focused_trail (kissat *solver) { + const links *const links = solver->links; + const unsigned next_idx = kissat_next_decision_variable (solver); + const unsigned limit = links[next_idx].stamp; + LOG ("next decision variable stamp %u", limit); + unsigned level = solver->level, res = 0; + while (res < level) { + frame *f = &FRAME (res + 1); + const unsigned idx = IDX (f->decision); + const unsigned score = links[idx].stamp; + if (score <= limit) + break; + res++; + } + return res; +} + +static unsigned reuse_trail (kissat *solver) { + assert (solver->level); + assert (!EMPTY_STACK (solver->trail)); + + if (!GET_OPTION (restartreusetrail)) + return 0; + + unsigned res; + + if (solver->stable) + res = reuse_stable_trail (solver); + else + res = reuse_focused_trail (solver); + + LOG ("matching trail level %u", res); + + if (res) { + INC (restarts_reused_trails); + ADD (restarts_reused_levels, res); + LOG ("restart reuses trail at decision level %u", res); + } else + LOG ("restarts does not reuse the trail"); + + return res; +} + +void kissat_restart (kissat *solver) { + START (restart); + INC (restarts); + ADD (restarts_levels, solver->level); + if (solver->stable) + INC (stable_restarts); + else + INC (focused_restarts); + unsigned level = reuse_trail (solver); + kissat_extremely_verbose (solver, + "restarting after %" PRIu64 " conflicts" + " (limit %" PRIu64 ")", + CONFLICTS, solver->limits.restart.conflicts); + LOG ("restarting to level %u", level); + kissat_backtrack_in_consistent_state (solver, level); + if (!solver->stable) + kissat_update_focused_restart_limit (solver); + REPORT (1, 'R'); + STOP (restart); +} diff --git a/src/sat/kissat/restart.h b/src/sat/kissat/restart.h new file mode 100644 index 000000000..c17ab6499 --- /dev/null +++ b/src/sat/kissat/restart.h @@ -0,0 +1,13 @@ +#ifndef _restart_h_INCLUDED +#define _restart_h_INCLUDED + +#include + +struct kissat; + +bool kissat_restarting (struct kissat *); +void kissat_restart (struct kissat *); + +void kissat_update_focused_restart_limit (struct kissat *); + +#endif diff --git a/src/sat/kissat/search.c b/src/sat/kissat/search.c new file mode 100644 index 000000000..77f44ad99 --- /dev/null +++ b/src/sat/kissat/search.c @@ -0,0 +1,229 @@ +#include "search.h" +#include "analyze.h" +#include "bump.h" +#include "classify.h" +#include "decide.h" +#include "eliminate.h" +#include "inline.h" +#include "internal.h" +#include "logging.h" +#include "lucky.h" +#include "preprocess.h" +#include "print.h" +#include "probe.h" +#include "propsearch.h" +#include "reduce.h" +#include "reluctant.h" +#include "reorder.h" +#include "rephase.h" +#include "report.h" +#include "restart.h" +#include "terminate.h" +#include "trail.h" +#include "walk.h" + +#include + +static void init_tiers (kissat *solver) { + for (unsigned stable = 0; stable != 2; stable++) { + if (!solver->tier1[stable]) { + assert (!solver->tier2[stable]); + solver->tier1[stable] = GET_OPTION (tier1); + solver->tier2[stable] = GET_OPTION (tier2); + if (solver->tier2[stable] <= solver->tier1[stable]) + solver->tier2[stable] = solver->tier1[stable]; + LOG ("initialized tier1[%u] glue to %u", stable, + solver->tier1[stable]); + LOG ("initialized tier2[%u] glue to %u", stable, + solver->tier2[stable]); + if (!solver->limits.glue.interval) + solver->limits.glue.interval = 2; + } + } +} + +static void start_search (kissat *solver) { + START (search); + INC (searches); + + bool stable = (GET_OPTION (stable) == 2); + + solver->stable = stable; + kissat_phase (solver, "search", GET (searches), + "initializing %s search after %" PRIu64 " conflicts", + (stable ? "stable" : "focus"), CONFLICTS); + + kissat_init_averages (solver, &AVERAGES); + + kissat_classify (solver); + + if (solver->stable) { + kissat_init_reluctant (solver); + kissat_update_scores (solver); + } + + init_tiers (solver); + + kissat_init_limits (solver); + + unsigned seed = GET_OPTION (seed); + solver->random = seed; + LOG ("initialized random number generator with seed %u", seed); + +#ifndef QUIET + limits *limits = &solver->limits; + limited *limited = &solver->limited; + if (!limited->conflicts && !limited->decisions) + kissat_very_verbose (solver, "starting unlimited search"); + else if (limited->conflicts && !limited->decisions) + kissat_very_verbose ( + solver, "starting search with conflicts limited to %" PRIu64, + limits->conflicts); + else if (!limited->conflicts && limited->decisions) + kissat_very_verbose ( + solver, "starting search with decisions limited to %" PRIu64, + limits->decisions); + else + kissat_very_verbose ( + solver, + "starting search with decisions limited to %" PRIu64 + " and conflicts limited to %" PRIu64, + limits->decisions, limits->conflicts); + if (stable) { + START (stable); + REPORT (0, '['); + } else { + START (focused); + REPORT (0, '{'); + } +#endif +} + +static void stop_search (kissat *solver) { + if (solver->limited.conflicts) { + LOG ("reset conflict limit"); + solver->limited.conflicts = false; + } + + if (solver->limited.decisions) { + LOG ("reset decision limit"); + solver->limited.decisions = false; + } + + if (solver->termination.flagged) { + kissat_very_verbose (solver, "termination forced externally"); + solver->termination.flagged = 0; + } + + if (solver->stable) { + REPORT (0, ']'); + STOP (stable); + solver->stable = false; + } else { + REPORT (0, '}'); + STOP (focused); + } + STOP (search); +} + +static void report_search_result (kissat *solver, int res) { +#ifndef QUIET + LOG ("search result %d", res); + char type = (res == 10 ? '1' : res == 20 ? '0' : '?'); + REPORT (0, type); +#else + (void) solver, (void) res; +#endif +} + +static void iterate (kissat *solver) { + assert (solver->iterating); + solver->iterating = false; + REPORT (0, 'i'); +} + +static bool conflict_limit_hit (kissat *solver) { + if (!solver->limited.conflicts) + return false; + if (solver->limits.conflicts > solver->statistics.conflicts) + return false; + kissat_very_verbose ( + solver, "conflict limit %" PRIu64 " hit after %" PRIu64 " conflicts", + solver->limits.conflicts, solver->statistics.conflicts); + return true; +} + +static bool decision_limit_hit (kissat *solver) { + if (!solver->limited.decisions) + return false; + if (solver->limits.decisions > solver->statistics.decisions) + return false; + kissat_very_verbose ( + solver, "decision limit %" PRIu64 " hit after %" PRIu64 " decisions", + solver->limits.decisions, solver->statistics.decisions); + return true; +} + +static bool searching (kissat *solver) { + if (!kissat_propagated (solver)) + return true; + if (!GET_OPTION (probeinit)) + return true; + if (!GET_OPTION (eliminateinit)) + return true; + if (conflict_limit_hit (solver)) + return false; + return true; +} + +int kissat_search (kissat *solver) { + REPORT (0, '*'); + int res = 0; + if (solver->inconsistent) + res = 20; + if (!res && GET_OPTION (luckyearly)) + res = kissat_lucky (solver); + if (!res && kissat_preprocessing (solver)) + res = kissat_preprocess (solver); + if (!res && GET_OPTION (luckylate)) + res = kissat_lucky (solver); + if (!res) + kissat_classify (solver); + if (!res && searching (solver)) { + start_search (solver); + while (!res) { + clause *conflict = kissat_search_propagate (solver); + if (conflict) + res = kissat_analyze (solver, conflict); + else if (solver->iterating) + iterate (solver); + else if (!solver->unassigned) + res = 10; + else if (TERMINATED (search_terminated_1)) + break; + else if (kissat_reducing (solver)) + res = kissat_reduce (solver); + else if (kissat_switching_search_mode (solver)) + kissat_switch_search_mode (solver); + else if (kissat_restarting (solver)) + kissat_restart (solver); + else if (kissat_reordering (solver)) + kissat_reorder (solver); + else if (kissat_rephasing (solver)) + kissat_rephase (solver); + else if (kissat_probing (solver)) + res = kissat_probe (solver); + else if (kissat_eliminating (solver)) + res = kissat_eliminate (solver); + else if (conflict_limit_hit (solver)) + break; + else if (decision_limit_hit (solver)) + break; + else + kissat_decide (solver); + } + stop_search (solver); + } + report_search_result (solver, res); + return res; +} diff --git a/src/sat/kissat/search.h b/src/sat/kissat/search.h new file mode 100644 index 000000000..b7f17f894 --- /dev/null +++ b/src/sat/kissat/search.h @@ -0,0 +1,8 @@ +#ifndef _search_h_INCLUDED +#define _search_h_INCLUDED + +struct kissat; + +int kissat_search (struct kissat *); + +#endif diff --git a/src/sat/kissat/shrink.c b/src/sat/kissat/shrink.c new file mode 100644 index 000000000..d0614df97 --- /dev/null +++ b/src/sat/kissat/shrink.c @@ -0,0 +1,395 @@ +#include "shrink.h" +#include "allocate.h" +#include "inline.h" +#include "minimize.h" + +static void reset_shrinkable (kissat *solver) { +#ifdef LOGGING + size_t reset = 0; +#endif + while (!EMPTY_STACK (solver->shrinkable)) { + const unsigned idx = POP_STACK (solver->shrinkable); + assigned *a = solver->assigned + idx; + assert (a->shrinkable); + a->shrinkable = false; +#ifdef LOGGING + reset++; +#endif + } + LOG ("resetting %zu shrinkable variables", reset); +} + +static void mark_shrinkable_as_removable (kissat *solver) { +#ifdef LOGGING + size_t marked = 0, reset = 0; +#endif + struct assigned *assigned = solver->assigned; + while (!EMPTY_STACK (solver->shrinkable)) { + const unsigned idx = POP_STACK (solver->shrinkable); + struct assigned *a = assigned + idx; + assert (a->shrinkable); + a->shrinkable = false; + assert (!a->poisoned); +#ifdef LOGGING + reset++; +#endif + if (a->removable) + continue; + kissat_push_removable (solver, assigned, idx); +#ifdef LOGGING + marked++; +#endif + } + LOG ("resetting %zu shrinkable variables", reset); + LOG ("marked %zu removable variables", marked); +} + +static inline int shrink_literal (kissat *solver, assigned *assigned, + unsigned level, unsigned lit) { + assert (solver->assigned == assigned); + assert (VALUE (lit) < 0); + + const unsigned idx = IDX (lit); + struct assigned *a = assigned + idx; + assert (a->level <= level); + if (!a->level) { + LOG2 ("skipping root level assigned %s", LOGLIT (lit)); + return 0; + } + if (a->shrinkable) { + LOG2 ("skipping already shrinkable literal %s", LOGLIT (lit)); + return 0; + } + if (a->level < level) { + if (a->removable) { + LOG2 ("skipping removable thus shrinkable %s", LOGLIT (lit)); + return 0; + } + const bool always_minimize_on_lower_level = (GET_OPTION (shrink) > 2); + if (always_minimize_on_lower_level && + kissat_minimize_literal (solver, lit, false)) { + LOG2 ("minimized thus shrinkable %s", LOGLIT (lit)); + return 0; + } + LOG ("literal %s on lower level %u < %u not removable/shrinkable", + LOGLIT (lit), a->level, level); + return -1; + } + LOG2 ("marking %s as shrinkable", LOGLIT (lit)); + a->shrinkable = true; + PUSH_STACK (solver->shrinkable, idx); + return 1; +} + +static inline unsigned shrunken_block (kissat *solver, unsigned level, + unsigned *begin_block, + unsigned *end_block, unsigned uip) { + assert (uip != INVALID_LIT); + const unsigned not_uip = NOT (uip); + LOG ("found unique implication point %s on level %u", LOGLIT (uip), + level); + + assert (begin_block < end_block); +#if defined(LOGGING) || !defined(NDEBUG) + const size_t tmp = end_block - begin_block; + LOG ("shrinking %zu literals on level %u to single literal %s", tmp, + level, LOGLIT (not_uip)); + assert (tmp > 1); +#endif + +#ifdef LOGGING + bool not_uip_was_in_clause = false; +#endif + unsigned block_shrunken = 0; + + for (unsigned *p = begin_block; p != end_block; p++) { + const unsigned lit = *p; + if (lit == INVALID_LIT) + continue; +#ifdef LOGGING + if (lit == not_uip) + not_uip_was_in_clause = true; + else + LOG ("shrunken literal %s", LOGLIT (lit)); +#endif + *p = INVALID_LIT; + block_shrunken++; + } + *begin_block = not_uip; + assert (block_shrunken); + block_shrunken--; +#ifdef LOGGING + if (not_uip_was_in_clause) + LOG ("keeping single literal %s on level %u", LOGLIT (not_uip), level); + else + LOG ("shrunken all literals on level %u and added %s instead", level, + LOGLIT (not_uip)); +#endif + const unsigned uip_idx = IDX (uip); + assigned *assigned = solver->assigned; + struct assigned *a = assigned + uip_idx; + if (!a->analyzed) + kissat_push_analyzed (solver, assigned, uip_idx); + + mark_shrinkable_as_removable (solver); +#ifndef LOGGING + (void) level; +#endif + return block_shrunken; +} + +static inline void push_literals_of_block (kissat *solver, + assigned *assigned, + unsigned *begin_block, + unsigned *end_block, + unsigned level) { + assert (assigned == solver->assigned); + + for (const unsigned *p = begin_block; p != end_block; p++) { + const unsigned lit = *p; + if (lit == INVALID_LIT) + continue; +#ifndef NDEBUG + int tmp = +#endif + shrink_literal (solver, assigned, level, lit); + assert (tmp > 0); + } +} + +static inline unsigned shrink_along_binary (kissat *solver, + assigned *assigned, + unsigned level, unsigned uip, + unsigned other) { + assert (VALUE (other) < 0); + LOGBINARY2 (uip, other, "shrinking along %s reason", LOGLIT (uip)); + int tmp = shrink_literal (solver, assigned, level, other); +#ifndef LOGGING + (void) uip; +#endif + return tmp > 0; +} + +static inline unsigned +shrink_along_large (kissat *solver, assigned *assigned, unsigned level, + unsigned uip, reference ref, bool *failed_ptr) { + unsigned open = 0; + LOGREF2 (ref, "shrinking along %s reason", LOGLIT (uip)); + clause *c = kissat_dereference_clause (solver, ref); + if (GET_OPTION (minimizeticks)) + INC (search_ticks); + for (all_literals_in_clause (other, c)) { + if (other == uip) + continue; + assert (VALUE (other) < 0); + int tmp = shrink_literal (solver, assigned, level, other); + if (tmp < 0) { + *failed_ptr = true; + break; + } + if (tmp > 0) + open++; + } + return open; +} + +static inline unsigned shrink_along_reason (kissat *solver, + assigned *assigned, + unsigned level, unsigned uip, + bool resolve_large_clauses, + bool *failed_ptr) { + unsigned open = 0; + const unsigned uip_idx = IDX (uip); + struct assigned *a = assigned + uip_idx; + assert (a->shrinkable); + assert (a->level == level); + assert (a->reason != DECISION_REASON); + if (a->binary) { + const unsigned other = a->reason; + open = shrink_along_binary (solver, assigned, level, uip, other); + } else { + reference ref = a->reason; + if (resolve_large_clauses) + open = shrink_along_large (solver, assigned, level, uip, ref, + failed_ptr); + else { + LOGREF (ref, "not shrinking %s reason", LOGLIT (uip)); + *failed_ptr = true; + } + } + return open; +} + +static inline unsigned shrink_block (kissat *solver, unsigned *begin_block, + unsigned *end_block, unsigned level, + unsigned max_trail) { + assert (level < solver->level); + + unsigned open = end_block - begin_block; + + LOG ("trying to shrink %u literals on level %u", open, level); + LOG ("maximum trail position %u on level %u", max_trail, level); + + assigned *assigned = solver->assigned; + + push_literals_of_block (solver, assigned, begin_block, end_block, level); + + assert (SIZE_STACK (solver->shrinkable) == open); + + const unsigned *const begin_trail = BEGIN_ARRAY (solver->trail); + + const bool resolve_large_clauses = (GET_OPTION (shrink) > 1); + unsigned uip = INVALID_LIT; + bool failed = false; + + const unsigned *t = begin_trail + max_trail; + + while (!failed) { + { + do + assert (begin_trail <= t), uip = *t--; + while (!assigned[IDX (uip)].shrinkable); + } + if (open == 1) + break; + open += shrink_along_reason (solver, assigned, level, uip, + resolve_large_clauses, &failed); + assert (open > 1); + open--; + } + + unsigned block_shrunken = 0; + if (failed) + reset_shrinkable (solver); + else + block_shrunken = + shrunken_block (solver, level, begin_block, end_block, uip); + + return block_shrunken; +} + +static unsigned *next_block (kissat *solver, unsigned *begin_lits, + unsigned *end_block, unsigned *level_ptr, + unsigned *max_trail_ptr) { + assigned *assigned = solver->assigned; + + unsigned level = INVALID_LEVEL; + unsigned max_trail = 0; + + unsigned *begin_block = end_block; + + while (begin_lits < begin_block) { + const unsigned lit = begin_block[-1]; + assert (lit != INVALID_LIT); + const unsigned idx = IDX (lit); + struct assigned *a = assigned + idx; + unsigned lit_level = a->level; + if (level == INVALID_LEVEL) { + level = lit_level; + LOG ("starting to shrink level %u", level); + } else { + assert (lit_level >= level); + if (lit_level > level) + break; + } + begin_block--; + const unsigned trail = a->trail; + if (trail > max_trail) + max_trail = trail; + } + + *level_ptr = level; + *max_trail_ptr = max_trail; + + return begin_block; +} + +static unsigned minimize_block (kissat *solver, unsigned *begin_block, + unsigned *end_block) { + unsigned minimized = 0; + + for (unsigned *p = begin_block; p != end_block; p++) { + const unsigned lit = *p; + assert (lit != INVALID_LIT); + if (!kissat_minimize_literal (solver, lit, true)) + continue; + LOG ("minimize-shrunken literal %s", LOGLIT (lit)); + *p = INVALID_LIT; + minimized++; + } + + return minimized; +} + +static inline unsigned * +minimize_and_shrink_block (kissat *solver, unsigned *begin_lits, + unsigned *end_block, unsigned *total_shrunken, + unsigned *total_minimized) { + assert (EMPTY_STACK (solver->shrinkable)); + + unsigned level, max_trail; + + unsigned *begin_block = + next_block (solver, begin_lits, end_block, &level, &max_trail); + + unsigned open = end_block - begin_block; + assert (open > 0); + + unsigned block_shrunken = 0; + unsigned block_minimized = 0; + + if (open < 2) + LOG ("only one literal on level %u", level); + else { + block_shrunken = + shrink_block (solver, begin_block, end_block, level, max_trail); + if (!block_shrunken) + block_minimized = minimize_block (solver, begin_block, end_block); + } + + block_shrunken += block_minimized; + LOG ("shrunken %u literals on level %u (including %u minimized)", + block_shrunken, level, block_minimized); + + *total_minimized += block_minimized; + *total_shrunken += block_shrunken; + + return begin_block; +} + +void kissat_shrink_clause (kissat *solver) { + assert (GET_OPTION (minimize) > 0); + assert (GET_OPTION (shrink) > 0); + assert (!EMPTY_STACK (solver->clause)); + + START (shrink); + + unsigned total_shrunken = 0; + unsigned total_minimized = 0; + + unsigned *begin_lits = BEGIN_STACK (solver->clause); + unsigned *end_lits = END_STACK (solver->clause); + + unsigned *end_block = END_STACK (solver->clause); + + while (end_block != begin_lits) + end_block = minimize_and_shrink_block ( + solver, begin_lits, end_block, &total_shrunken, &total_minimized); + unsigned *q = begin_lits; + for (const unsigned *p = q; p != end_lits; p++) { + const unsigned lit = *p; + if (lit != INVALID_LIT) + *q++ = lit; + } + LOG ("clause shrunken by %u literals (including %u minimized)", + total_shrunken, total_minimized); + assert (q + total_shrunken == end_lits); + SET_END_OF_STACK (solver->clause, q); + ADD (literals_shrunken, total_shrunken); + ADD (literals_minshrunken, total_minimized); + + LOGTMP ("shrunken learned"); + kissat_reset_poisoned (solver); + + STOP (shrink); +} diff --git a/src/sat/kissat/shrink.h b/src/sat/kissat/shrink.h new file mode 100644 index 000000000..662df06ee --- /dev/null +++ b/src/sat/kissat/shrink.h @@ -0,0 +1,8 @@ +#ifndef _shrink_h_INCLUDED +#define _shrink_h_INCLUDED + +struct kissat; + +void kissat_shrink_clause (struct kissat *); + +#endif diff --git a/src/sat/kissat/smooth.c b/src/sat/kissat/smooth.c new file mode 100644 index 000000000..487680f06 --- /dev/null +++ b/src/sat/kissat/smooth.c @@ -0,0 +1,73 @@ +#include "allocate.h" +#include "internal.h" +#include "logging.h" + +void kissat_init_smooth (kissat *solver, smooth *smooth, int window, + const char *name) { + assert (window > 0); + const double alpha = 1.0 / window; + LOG ("initialized %s EMA alpha %g window %d", name, alpha, window); + smooth->value = 0; + smooth->biased = 0; + smooth->alpha = alpha; + smooth->beta = 1.0 - alpha; + assert (smooth->beta > 0); + smooth->exp = 1.0; +#ifdef LOGGING + smooth->name = name; + smooth->updated = 0; +#else + (void) solver; + (void) name; +#endif +} + +void kissat_update_smooth (kissat *solver, smooth *smooth, double y) { +#ifdef LOGGING + smooth->updated++; + const double old_value = smooth->value; +#endif + const double old_biased = smooth->biased; + const double alpha = smooth->alpha; + const double beta = smooth->beta; + 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)", + smooth->updated, smooth->name, old_biased, y, delta, new_biased, + scaled_delta); + smooth->biased = new_biased; + double old_exp = smooth->exp; + double new_exp, div, new_value; + if (old_exp) { + new_exp = old_exp * beta; + assert (new_exp < 1); + if (new_exp == old_exp) { + new_exp = 0; + new_value = new_biased; +#ifdef LOGGING + div = 1; +#endif + } else { + div = 1 - new_exp; + assert (div > 0); + new_value = new_biased / div; + } + smooth->exp = new_exp; + } else { + new_value = new_biased; +#ifdef LOGGING + new_exp = 0; + div = 1; +#endif + } + smooth->value = new_value; + LOG ("update %" PRIu64 " of corrected %s EMA %g " + "with %g (delta %g) yields %g (exponent %g, div %g)", + smooth->updated, smooth->name, old_value, y, delta, new_value, + new_exp, div); +#ifndef LOGGING + (void) solver; +#endif +} diff --git a/src/sat/kissat/smooth.h b/src/sat/kissat/smooth.h new file mode 100644 index 000000000..11f2efe9f --- /dev/null +++ b/src/sat/kissat/smooth.h @@ -0,0 +1,22 @@ +#ifndef _smooth_h_INCLUDED +#define _smooth_h_INCLUDED + +#include + +typedef struct smooth smooth; + +struct smooth { + double value, biased, alpha, beta, exp; +#ifdef LOGGING + const char *name; + uint64_t updated; +#endif +}; + +struct kissat; + +void kissat_init_smooth (struct kissat *, smooth *, int window, + const char *); +void kissat_update_smooth (struct kissat *, smooth *, double); + +#endif diff --git a/src/sat/kissat/sort.c b/src/sat/kissat/sort.c new file mode 100644 index 000000000..ba1ea32b2 --- /dev/null +++ b/src/sat/kissat/sort.c @@ -0,0 +1,98 @@ +#include "internal.h" +#include "logging.h" + +static inline value +move_smallest_literal_to_front (kissat *solver, const value *const values, + const assigned *const assigned, + bool satisfied_is_enough, unsigned start, + unsigned size, unsigned *lits) { + assert (1 < size); + assert (start < size); + + unsigned a = lits[start]; + + value u = values[a]; + if (!u || (u > 0 && satisfied_is_enough)) + return u; + + unsigned pos = 0, best = a; + + { + const unsigned i = IDX (a); + unsigned k = (u ? assigned[i].level : UINT_MAX); + + assert (start < UINT_MAX); + for (unsigned i = start + 1; i < size; i++) { + const unsigned b = lits[i]; + const value v = values[b]; + + if (!v || (v > 0 && satisfied_is_enough)) { + best = b; + pos = i; + u = v; + break; + } + + const unsigned j = IDX (b); + const unsigned l = (v ? assigned[j].level : UINT_MAX); + + bool better; + + if (u < 0 && v > 0) + better = true; + else if (u > 0 && v < 0) + better = false; + else if (u < 0) { + assert (v < 0); + better = (k < l); + } else { + assert (u > 0); + assert (v > 0); + assert (!satisfied_is_enough); + better = (k > l); + } + + if (!better) + continue; + + best = b; + pos = i; + u = v; + k = l; + } + } + + if (!pos) + return u; + + lits[start] = best; + lits[pos] = a; + + LOG ("new smallest literal %s at %u swapped with %s at %u", LOGLIT (best), + pos, LOGLIT (a), start); +#ifndef LOGGING + (void) solver; +#endif + return u; +} + +#ifdef INLINE_SORT +static inline +#endif + void + kissat_sort_literals (kissat *solver, +#ifdef INLINE_SORT + const value *const values, + const assigned *assigned, +#endif + unsigned size, unsigned *lits) { +#ifndef INLINE_SORT + const value *const values = solver->values; + const assigned *const assigned = solver->assigned; +#endif + value u = move_smallest_literal_to_front (solver, values, assigned, false, + 0, size, lits); + if (size > 2) + move_smallest_literal_to_front (solver, values, assigned, (u >= 0), 1, + size, lits); +} diff --git a/src/sat/kissat/sort.h b/src/sat/kissat/sort.h new file mode 100644 index 000000000..9e0e6ee94 --- /dev/null +++ b/src/sat/kissat/sort.h @@ -0,0 +1,154 @@ +#ifndef _sort_h_INCLUDED +#define _sort_h_INCLUDED + +#include "utilities.h" + +#define GREATER_SWAP(TYPE, P, Q, LESS) \ + do { \ + if (LESS (Q, P)) \ + SWAP (TYPE, P, Q); \ + } while (0) + +#define SORTER (solver->sorter) + +#define PARTITION(TYPE, L, R, A, LESS) \ + do { \ + const size_t L_PARTITION = (L); \ + I_QUICK_SORT = L_PARTITION - 1; \ +\ + size_t J_PARTITION = (R); \ +\ + TYPE PIVOT_PARTITION = A[J_PARTITION]; \ +\ + for (;;) { \ + while (LESS (A[++I_QUICK_SORT], PIVOT_PARTITION)) \ + ; \ + while (LESS (PIVOT_PARTITION, A[--J_PARTITION])) \ + if (J_PARTITION == L_PARTITION) \ + break; \ +\ + if (I_QUICK_SORT >= J_PARTITION) \ + break; \ +\ + SWAP (TYPE, A[I_QUICK_SORT], A[J_PARTITION]); \ + } \ +\ + SWAP (TYPE, A[I_QUICK_SORT], A[R]); \ + } while (0) + +#define QUICK_SORT_LIMIT 10 + +#define QUICK_SORT(TYPE, N, A, LESS) \ + do { \ + assert (N); \ + assert (EMPTY_STACK (SORTER)); \ +\ + size_t L_QUICK_SORT = 0; \ + size_t R_QUICK_SORT = N - 1; \ +\ + if (R_QUICK_SORT - L_QUICK_SORT <= QUICK_SORT_LIMIT) \ + break; \ +\ + for (;;) { \ + const size_t M = L_QUICK_SORT + (R_QUICK_SORT - L_QUICK_SORT) / 2; \ +\ + SWAP (TYPE, A[M], A[R_QUICK_SORT - 1]); \ +\ + GREATER_SWAP (TYPE, A[L_QUICK_SORT], A[R_QUICK_SORT - 1], LESS); \ + GREATER_SWAP (TYPE, A[L_QUICK_SORT], A[R_QUICK_SORT], LESS); \ + GREATER_SWAP (TYPE, A[R_QUICK_SORT - 1], A[R_QUICK_SORT], LESS); \ +\ + size_t I_QUICK_SORT; \ +\ + PARTITION (TYPE, L_QUICK_SORT + 1, R_QUICK_SORT - 1, A, LESS); \ + assert (L_QUICK_SORT < I_QUICK_SORT); \ + assert (I_QUICK_SORT <= R_QUICK_SORT); \ +\ + size_t LL_QUICK_SORT; \ + size_t RR_QUICK_SORT; \ +\ + if (I_QUICK_SORT - L_QUICK_SORT < R_QUICK_SORT - I_QUICK_SORT) { \ + LL_QUICK_SORT = I_QUICK_SORT + 1; \ + RR_QUICK_SORT = R_QUICK_SORT; \ + R_QUICK_SORT = I_QUICK_SORT - 1; \ + } else { \ + LL_QUICK_SORT = L_QUICK_SORT; \ + RR_QUICK_SORT = I_QUICK_SORT - 1; \ + L_QUICK_SORT = I_QUICK_SORT + 1; \ + } \ + if (R_QUICK_SORT - L_QUICK_SORT > QUICK_SORT_LIMIT) { \ + assert (RR_QUICK_SORT - LL_QUICK_SORT > QUICK_SORT_LIMIT); \ + PUSH_STACK (SORTER, LL_QUICK_SORT); \ + PUSH_STACK (SORTER, RR_QUICK_SORT); \ + } else if (RR_QUICK_SORT - LL_QUICK_SORT > QUICK_SORT_LIMIT) { \ + L_QUICK_SORT = LL_QUICK_SORT; \ + R_QUICK_SORT = RR_QUICK_SORT; \ + } else if (!EMPTY_STACK (SORTER)) { \ + R_QUICK_SORT = POP_STACK (SORTER); \ + L_QUICK_SORT = POP_STACK (SORTER); \ + } else \ + break; \ + } \ + } while (0) + +#define INSERTION_SORT(TYPE, N, A, LESS) \ + do { \ + size_t L_INSERTION_SORT = 0; \ + size_t R_INSERTION_SORT = N - 1; \ +\ + for (size_t I_INSERTION_SORT = R_INSERTION_SORT; \ + I_INSERTION_SORT > L_INSERTION_SORT; I_INSERTION_SORT--) \ + GREATER_SWAP (TYPE, A[I_INSERTION_SORT - 1], A[I_INSERTION_SORT], \ + LESS); \ +\ + for (size_t I_INSERTION_SORT = L_INSERTION_SORT + 2; \ + I_INSERTION_SORT <= R_INSERTION_SORT; I_INSERTION_SORT++) { \ + TYPE PIVOT_INSERTION_SORT = A[I_INSERTION_SORT]; \ +\ + size_t J_INSERTION_SORT = I_INSERTION_SORT; \ +\ + while (LESS (PIVOT_INSERTION_SORT, A[J_INSERTION_SORT - 1])) { \ + A[J_INSERTION_SORT] = A[J_INSERTION_SORT - 1]; \ + J_INSERTION_SORT--; \ + } \ +\ + A[J_INSERTION_SORT] = PIVOT_INSERTION_SORT; \ + } \ + } while (0) + +#ifdef NDEBUG +#define CHECK_SORTED(...) \ + do { \ + } while (0) +#else +#define CHECK_SORTED(N, A, LESS) \ + do { \ + for (size_t I_CHECK_SORTED = 0; I_CHECK_SORTED < N - 1; \ + I_CHECK_SORTED++) \ + assert (!LESS (A[I_CHECK_SORTED + 1], A[I_CHECK_SORTED])); \ + } while (0) +#endif + +#define SORT(TYPE, N, A, LESS) \ + do { \ + const size_t N_SORT = (N); \ + if (!N_SORT) \ + break; \ + START (sort); \ + TYPE *A_SORT = (A); \ + QUICK_SORT (TYPE, N_SORT, A_SORT, LESS); \ + INSERTION_SORT (TYPE, N_SORT, A_SORT, LESS); \ + CHECK_SORTED (N_SORT, A_SORT, LESS); \ + STOP (sort); \ + } while (0) + +#define SORT_STACK(TYPE, S, LESS) \ + do { \ + const size_t N_SORT_STACK = SIZE_STACK (S); \ + if (N_SORT_STACK <= 1) \ + break; \ + TYPE *A_SORT_STACK = BEGIN_STACK (S); \ + SORT (TYPE, N_SORT_STACK, A_SORT_STACK, LESS); \ + } while (0) + +#endif diff --git a/src/sat/kissat/stack.c b/src/sat/kissat/stack.c new file mode 100644 index 000000000..4fc7f3352 --- /dev/null +++ b/src/sat/kissat/stack.c @@ -0,0 +1,51 @@ +#include "stack.h" +#include "allocate.h" +#include "utilities.h" + +#include + +void kissat_stack_enlarge (struct kissat *solver, chars *s, size_t bytes) { + const size_t size = SIZE_STACK (*s); + const size_t old_bytes = CAPACITY_STACK (*s); + assert (MAX_SIZE_T / 2 >= old_bytes); + size_t new_bytes; + if (old_bytes) + new_bytes = 2 * old_bytes; + else { + new_bytes = bytes; + while (!kissat_aligned_word (new_bytes)) + new_bytes <<= 1; + } + s->begin = kissat_realloc (solver, s->begin, old_bytes, new_bytes); + s->allocated = s->begin + new_bytes; + s->end = s->begin + size; +} + +void kissat_shrink_stack (struct kissat *solver, chars *s, size_t bytes) { + assert (bytes > 0); + const size_t old_bytes_capacity = CAPACITY_STACK (*s); + assert (kissat_aligned_word (old_bytes_capacity)); + assert (!(old_bytes_capacity % bytes)); + assert (kissat_is_zero_or_power_of_two (old_bytes_capacity / bytes)); + const size_t old_bytes_size = SIZE_STACK (*s); + assert (!(old_bytes_size % bytes)); + const size_t old_size = old_bytes_size / bytes; + size_t new_capacity; + if (old_size) { + const unsigned ld_old_size = kissat_log2_ceiling_of_word (old_size); + new_capacity = ((size_t) 1) << ld_old_size; + } else + new_capacity = 0; + assert (kissat_is_zero_or_power_of_two (new_capacity)); + size_t new_bytes_capacity = new_capacity * bytes; + while (!kissat_aligned_word (new_bytes_capacity)) + new_bytes_capacity <<= 1; + if (new_bytes_capacity == old_bytes_capacity) + return; + assert (new_bytes_capacity < old_bytes_capacity); + s->begin = kissat_realloc (solver, s->begin, old_bytes_capacity, + new_bytes_capacity); + s->allocated = s->begin + new_bytes_capacity; + s->end = s->begin + old_bytes_size; + assert (s->end <= s->allocated); +} diff --git a/src/sat/kissat/stack.h b/src/sat/kissat/stack.h new file mode 100644 index 000000000..23e07f597 --- /dev/null +++ b/src/sat/kissat/stack.h @@ -0,0 +1,140 @@ +#ifndef _stack_h_INCLUDED +#define _stack_h_INCLUDED + +#include + +#define STACK(TYPE) \ + struct { \ + TYPE *begin; \ + TYPE *end; \ + TYPE *allocated; \ + } + +#define FULL_STACK(S) ((S).end == (S).allocated) +#define EMPTY_STACK(S) ((S).begin == (S).end) +#define SIZE_STACK(S) ((size_t) ((S).end - (S).begin)) +#define CAPACITY_STACK(S) ((size_t) ((S).allocated - (S).begin)) + +#define INIT_STACK(S) \ + do { \ + (S).begin = (S).end = (S).allocated = 0; \ + } while (0) + +#define TOP_STACK(S) (END_STACK (S)[assert (!EMPTY_STACK (S)), -1]) + +#define PEEK_STACK(S, N) \ + (BEGIN_STACK (S)[assert ((N) < SIZE_STACK (S)), (N)]) + +#define POKE_STACK(S, N, E) \ + do { \ + PEEK_STACK (S, N) = (E); \ + } while (0) + +#define POP_STACK(S) (assert (!EMPTY_STACK (S)), *--(S).end) + +#define ENLARGE_STACK(S) \ + do { \ + assert (FULL_STACK (S)); \ + kissat_stack_enlarge (solver, (chars *) &(S), sizeof *(S).begin); \ + } while (0) + +#define SHRINK_STACK(S) \ + do { \ + if (!FULL_STACK (S)) \ + kissat_shrink_stack (solver, (chars *) &(S), sizeof *(S).begin); \ + } while (0) + +#define PUSH_STACK(S, E) \ + do { \ + if (FULL_STACK (S)) \ + ENLARGE_STACK (S); \ + *(S).end++ = (E); \ + } while (0) + +#define BEGIN_STACK(S) (S).begin + +#define END_STACK(S) (S).end + +#define CLEAR_STACK(S) \ + do { \ + (S).end = (S).begin; \ + } while (0) + +#define RESIZE_STACK(S, NEW_SIZE) \ + do { \ + const size_t TMP_NEW_SIZE = (NEW_SIZE); \ + assert (TMP_NEW_SIZE <= SIZE_STACK (S)); \ + (S).end = (S).begin + TMP_NEW_SIZE; \ + } while (0) + +#define SET_END_OF_STACK(S, P) \ + do { \ + assert (BEGIN_STACK (S) <= (P)); \ + assert ((P) <= END_STACK (S)); \ + if ((P) == END_STACK (S)) \ + break; \ + (S).end = (P); \ + } while (0) + +#define RELEASE_STACK(S) \ + do { \ + DEALLOC ((S).begin, CAPACITY_STACK (S)); \ + INIT_STACK (S); \ + } while (0) + +#define REMOVE_STACK(T, S, E) \ + do { \ + assert (!EMPTY_STACK (S)); \ + T *END_REMOVE_STACK = END_STACK (S); \ + T *P_REMOVE_STACK = BEGIN_STACK (S); \ + while (*P_REMOVE_STACK != (E)) { \ + P_REMOVE_STACK++; \ + assert (P_REMOVE_STACK != END_REMOVE_STACK); \ + } \ + P_REMOVE_STACK++; \ + while (P_REMOVE_STACK != END_REMOVE_STACK) { \ + P_REMOVE_STACK[-1] = *P_REMOVE_STACK; \ + P_REMOVE_STACK++; \ + } \ + (S).end--; \ + } while (0) + +#define REVERSE_STACK(T, S) \ + do { \ + if (SIZE_STACK (S) < 2) \ + break; \ + T *HEAD = (S).begin, *TAIL = (S).end - 1; \ + while (HEAD < TAIL) { \ + T TMP = *HEAD; \ + *HEAD++ = *TAIL; \ + *TAIL-- = TMP; \ + } \ + } while (0) + +#define all_stack(T, E, S) \ + T E, *E##_PTR = BEGIN_STACK (S), *const E##_END = END_STACK (S); \ + E##_PTR != E##_END && (E = *E##_PTR, true); \ + ++E##_PTR + +#define all_pointers(T, E, S) \ + T *E, *const *E##_PTR = BEGIN_STACK (S), \ + *const *const E##_END = END_STACK (S); \ + E##_PTR != E##_END && (E = *E##_PTR, true); \ + ++E##_PTR + +// clang-format off + +typedef STACK (char) chars; +typedef STACK (int) ints; +typedef STACK (size_t) sizes; +typedef STACK (unsigned) unsigneds; + +// clang-format on + +struct kissat; + +void kissat_stack_enlarge (struct kissat *, chars *, + size_t size_of_element); +void kissat_shrink_stack (struct kissat *, chars *, size_t size_of_element); + +#endif diff --git a/src/sat/kissat/statistics.c b/src/sat/kissat/statistics.c new file mode 100644 index 000000000..ad4bfa068 --- /dev/null +++ b/src/sat/kissat/statistics.c @@ -0,0 +1,414 @@ +#if !defined(QUIET) || !defined(NDEBUG) +#include "internal.h" +#endif + +#ifndef QUIET + +#include "resources.h" +#include "tiers.h" +#include "utilities.h" + +#include +#include +#include +#include + +void kissat_print_glue_usage (kissat *solver) { + const int64_t stable = solver->statistics.clauses_used_stable; + const int64_t focused = solver->statistics.clauses_used_focused; + if (!stable && !focused) + printf ("%sno clauses used at all\n", solver->prefix); + else { + if (focused) + kissat_print_tier_usage_statistics (solver, false); + if (focused && stable) + printf ("c\n"); + if (stable) + kissat_print_tier_usage_statistics (solver, true); + } + fflush (stdout); +} + +// clang-format off + +void +kissat_statistics_print (kissat * solver, bool verbose) +{ + statistics *statistics = &solver->statistics; + + const double time = kissat_process_time (); + size_t variables = solver->statistics.variables_original; +#ifdef METRICS + const double rss = kissat_maximum_resident_set_size (); +#endif + +/*------------------------------------------------------------------------*/ + +#define RELATIVE(FIRST,SECOND) \ + kissat_average (statistics->FIRST, statistics->SECOND) + +/*------------------------------------------------------------------------*/ + +#define CONF_INT(NAME) \ + RELATIVE (conflicts, NAME) + +#define SWEEPS_PER_COMPLETED(NAME) \ + RELATIVE (sweep, NAME) + +#define NO_SECONDARY(NAME) \ + 0 + +/*------------------------------------------------------------------------*/ + +#define PER_BACKBONE(NAME) \ + RELATIVE (NAME, backbone_computations) + +#define PER_BACKBONE_UNIT(NAME) \ + RELATIVE (NAME, backbone_units) + +#define PER_CLOSURE(NAME) \ + RELATIVE(NAME, closures) + +#define PER_CLS_ADDED(NAME) \ + RELATIVE (NAME, clauses_added) + +#define PER_CLS_FACTORED(NAME) \ + RELATIVE (NAME, clauses_factored) + +#define PER_CLS_LEARNED(NAME) \ + RELATIVE (NAME, clauses_learned) + +#define PER_CLS_UNFACTORED(NAME) \ + RELATIVE (NAME, clauses_unfactored) + +#define PER_CONFLICT(NAME) \ + RELATIVE (NAME, conflicts) + +#define PER_CONGRANDS(NAME) \ + RELATIVE (NAME, congruent_gates_ands) + +#define PER_CONGRGATES(NAME) \ + RELATIVE (NAME, congruent_gates) + +#define PER_CONGRITES(NAME) \ + RELATIVE (NAME, congruent_gates_ites) + +#define PER_CONGRLOOKUP(NAME) \ + RELATIVE (NAME, congruent_lookups) + +#define PER_CONGRXORS(NAME) \ + RELATIVE (NAME, congruent_gates_xors) + +#define PER_FIXED(NAME) \ + RELATIVE (NAME, units) + +#ifndef STATISTICS +#define PER_FLIPPED(NAME) \ + -1 +#else +#define PER_FLIPPED(NAME) \ + RELATIVE (NAME, flipped) +#endif + +#define PER_FORWARD_CHECK(NAME) \ + RELATIVE (NAME, forward_checks) + +#define PER_KITTEN_PROP(NAME) \ + RELATIVE (NAME, kitten_propagations) + +#define PER_KITTEN_SOLVED(NAME) \ + RELATIVE (NAME, kitten_solved) + +#define PER_PROPAGATION(NAME) \ + RELATIVE (NAME, propagations) + +#define PER_RESTART(NAME) \ + RELATIVE (NAME, restarts) + +#define PER_SECOND(NAME) \ + kissat_average (statistics->NAME, time) + +#define PER_SWEEP_VARIABLES(NAME) \ + kissat_average (statistics->NAME, statistics->sweep_variables) + +#define PER_VARIABLE(NAME) \ + kissat_average (statistics->NAME, variables) + +#define PER_VIVIFICATION(NAME) \ + RELATIVE (NAME, vivifications) + +#define PER_VIVIFY_CHECK(NAME) \ + RELATIVE (NAME, vivify_checks) + +#if 0 //ndef STATISTICS +#define PER_WALKS(NAME) \ + -1 +#else +#define PER_WALKS(NAME) \ + RELATIVE (NAME, walks) +#endif + +/*------------------------------------------------------------------------*/ + +#define PERCENT(FIRST,SECOND) \ + kissat_percent (statistics->FIRST, statistics->SECOND) + +/*------------------------------------------------------------------------*/ + +#define PCNT_ARENA_RESIZED(NAME) \ + PERCENT (NAME, arena_resized) + +#define PCNT_CLS_ADDED(NAME) \ + PERCENT (NAME, clauses_added) + +#define PCNT_CLS_IMPROVED(NAME) \ + PERCENT (NAME, clauses_improved) + +#define PCNT_CLS_IMPROVED(NAME) \ + PERCENT (NAME, clauses_improved) + +#define PCNT_CLS_FACTORED(NAME) \ + PERCENT (NAME, clauses_factored) + +#define PCNT_CLS_LEARNED(NAME) \ + PERCENT (NAME, clauses_learned) + +#define PCNT_CLS_ORIGINAL(NAME) \ + PERCENT (NAME, clauses_original) + +#define PCNT_CLS_REDUCED(NAME) \ + PERCENT (NAME, clauses_reduced) + +#define PCNT_CLS_USED(NAME) \ + PERCENT (NAME, clauses_used) + +#define PCNT_COLLECTIONS(NAME) \ + PERCENT (NAME, garbage_collections) + +#define PCNT_CONFLICTS(NAME) \ + PERCENT (NAME, conflicts) + +#define PCNT_CONGRCOLS(NAME) \ + PERCENT (NAME, congruent_collisions) + +#define PCNT_CONGRGATES(NAME) \ + PERCENT (NAME, congruent_gates) + +#define PCNT_CONGRLOOKUP(NAME) \ + PERCENT (NAME, congruent_lookups) + +#define PCNT_CONGRMATCHED(NAME) \ + PERCENT (NAME, congruent_matched) + +#define PCNT_CONGREWR(NAME) \ + PERCENT (NAME, congruent_rewritten) + +#define PCNT_CONGRSIMPS(NAME) \ + PERCENT (NAME, congruent_simplified) + +#define PCNT_CONGRUENT(NAME) \ + PERCENT (NAME, congruent) + +#define PCNT_CONGRUNARY(NAME) \ + PERCENT (NAME, congruent_unary) + +#define PCNT_DECISIONS(NAME) \ + PERCENT (NAME, decisions) + +#define PCNT_DEFRAGS(NAME) \ + PERCENT (NAME, defragmentations) + +#define PCNT_ELIM_ATTEMPTS(NAME) \ + PERCENT (NAME, eliminate_attempted) + +#define PCNT_ELIMINATED(NAME) \ + PERCENT (NAME, eliminated) + +#define PCNT_EXTRACTED(NAME) \ + PERCENT (NAME, gates_extracted) + +#define PCNT_IRREDUNDANT(NAME) \ + PERCENT (NAME, clauses_irredundant) + +#define PCNT_KITTEN_FLIP(NAME) \ + PERCENT (NAME, kitten_flip) + +#define PCNT_KITTEN_SOLVED(NAME) \ + PERCENT (NAME, kitten_solved) + +#define PCNT_LITS_DEDUCED(NAME) \ + PERCENT (NAME, literals_deduced) + +#define PCNT_LITS_SHRUNKEN(NAME) \ + PERCENT (NAME, literals_shrunken) + +#define PCNT_PROPS(NAME) \ + PERCENT (NAME, propagations) + +#define PCNT_REDUCTIONS(NAME) \ + PERCENT (NAME, reductions) + +#define PCNT_REDUNDANT_CLAUSES(NAME) \ + PERCENT (NAME, clauses_redundant) + +#define PCNT_REORDERED(NAME) \ + PERCENT (NAME, reordered) + +#define PCNT_REPHASED(NAME) \ + PERCENT (NAME, rephased) + +#define PCNT_RESIDENT_SET(NAME) \ + kissat_percent (statistics->NAME, rss) + +#define PCNT_RESTARTS(NAME) \ + PERCENT (NAME, restarts) + +#define PCNT_RESTARTS_LEVELS(NAME) \ + PERCENT (NAME, restarts_levels) + +#define PCNT_SEARCHES(NAME) \ + PERCENT (NAME, searches) + +#define PCNT_STRENGTHENED(NAME) \ + PERCENT (NAME, strengthened) + +#define PCNT_SUBSUMED(NAME) \ + PERCENT (NAME, subsumed) + +#define PCNT_SUBSUMPTION_CHECK(NAME) \ + PERCENT (NAME, subsumption_checks) + +#define PCNT_SWEEP_FLIP_BACKBONE(NAME) \ + PERCENT (NAME, sweep_flip_backbone) + +#define PCNT_SWEEP_FLIP_EQUIVALENCES(NAME) \ + PERCENT (NAME, sweep_flip_equivalences) + +#define PCNT_SWEEP_SOLVED_BACKBONE(NAME) \ + PERCENT (NAME, sweep_solved_backbone) + +#define PCNT_SWEEP_SOLVED_EQUIVALENCES(NAME) \ + PERCENT (NAME, sweep_solved_equivalences) + +#define PCNT_SWEEP_SOLVED(NAME) \ + PERCENT (NAME, sweep_solved) + +#ifndef STATISTICS +#define PCNT_TICKS(NAME) \ + -1 +#else +#define PCNT_TICKS(NAME) \ + PERCENT (NAME, ticks) +#endif + +#define PCNT_VARIABLES(NAME) \ + kissat_percent (statistics->NAME, variables) + +#define PCNT_VIVIFIED(NAME) \ + PERCENT (NAME, vivified) + +#define PCNT_VIVIFY_IMP(NAME) \ + PERCENT (NAME, vivified_implied) + +#define PCNT_VIVIFY_INST(NAME) \ + PERCENT (NAME, vivified_instantiated) + +#define PCNT_VIVIFY_CHECK(NAME) \ + PERCENT (NAME, vivify_checks) + +#define PCNT_VIVIFY_PROBES(NAME) \ + PERCENT (NAME, vivify_probes) + +#define PCNT_VIVIFY_SHRUNKEN(NAME) \ + PERCENT (NAME, vivified_shrunken) + +#define PCNT_VIVIFY_SUB(NAME) \ + PERCENT (NAME, vivified_subsumed) + +#define PCNT_WALKS(NAME) \ + PERCENT (NAME, walks) + +#define COUNTER(NAME,VERBOSE,OTHER,UNITS,TYPE) \ + if (verbose || !VERBOSE || (VERBOSE == 1 && statistics->NAME)) \ + PRINT_STAT (#NAME, statistics->NAME, OTHER(NAME), UNITS, TYPE); +#define IGNORE(...) + + METRICS_COUNTERS_AND_STATISTICS + +#undef COUNTER +#undef METRIC +#undef STATISTIC + + fflush (stdout); +} + +// clang-format on + +#elif defined(NDEBUG) +int kissat_statistics_dummy_to_avoid_warning; +#endif + +/*------------------------------------------------------------------------*/ + +#ifndef NDEBUG + +#include "inlinevector.h" + +void kissat_check_statistics (kissat *solver) { + if (solver->inconsistent) + return; + + size_t redundant = 0; + size_t irredundant = 0; + size_t arena_garbage = 0; + + for (all_clauses (c)) { + if (c->garbage) { + arena_garbage += kissat_actual_bytes_of_clause (c); + continue; + } + if (c->redundant) + redundant++; + else + irredundant++; + } + + size_t binary = 0; + + if (solver->watching) { + for (all_literals (lit)) { + watches *watches = &WATCHES (lit); + + for (all_binary_blocking_watches (watch, *watches)) { + if (watch.type.binary) { + binary++; + } + } + } + } else { + for (all_literals (lit)) { + watches *watches = &WATCHES (lit); + + for (all_binary_large_watches (watch, *watches)) { + if (watch.type.binary) { + binary++; + } + } + } + } + + assert (!(binary & 1)); + binary /= 2; + + statistics *statistics = &solver->statistics; + assert (statistics->clauses_binary == binary); + assert (statistics->clauses_redundant == redundant); + assert (statistics->clauses_irredundant == irredundant); +#ifdef METRICS + assert (statistics->arena_garbage == arena_garbage); +#else + (void) arena_garbage; +#endif +} + +#endif diff --git a/src/sat/kissat/statistics.h b/src/sat/kissat/statistics.h new file mode 100644 index 000000000..8c04f4d33 --- /dev/null +++ b/src/sat/kissat/statistics.h @@ -0,0 +1,467 @@ +#ifndef _statistics_h_INCLUDED +#define _statistics_h_INCLUDED + +#include +#include + +// clang-format off + +#define METRICS_COUNTERS_AND_STATISTICS \ +\ + METRIC (allocated_collected, 2, PCNT_RESIDENT_SET, "%", "resident set") \ + METRIC (allocated_current, 2, PCNT_RESIDENT_SET, "%", "resident set") \ + METRIC (allocated_max, 2, PCNT_RESIDENT_SET, "%", "resident set") \ + STATISTIC (ands_eliminated, 1, PCNT_ELIMINATED, "%", "eliminated") \ + METRIC (ands_extracted, 1, PCNT_EXTRACTED, "%", "extracted") \ + METRIC (arena_enlarged, 1, PCNT_ARENA_RESIZED, "%", "resize") \ + METRIC (arena_garbage, 1, PCNT_RESIDENT_SET, "%", "resident set") \ + METRIC (arena_resized, 1, CONF_INT, "", "interval") \ + METRIC (arena_shrunken, 1, PCNT_ARENA_RESIZED, "%", "resize") \ + COUNTER (backbone_computations, 2, CONF_INT, "", "interval") \ + METRIC (backbone_implied, 1, PER_BACKBONE_UNIT, 0, "per unit") \ + METRIC (backbone_probes, 2, PER_VARIABLE, "", "per variable") \ + METRIC (backbone_propagations, 2, PCNT_PROPS, "%", "propagations") \ + METRIC (backbone_rounds, 2, PER_BACKBONE, 0, "per backbone") \ + COUNTER (backbone_ticks, 2, PCNT_TICKS, "%", "ticks") \ + STATISTIC (backbone_units, 1, PCNT_VARIABLES, "%", "variables") \ + METRIC (best_saved, 1, CONF_INT, "", "interval") \ + COUNTER (chronological, 1, PCNT_CONFLICTS, "%", "conflicts") \ + COUNTER (clauses_added, 2, PCNT_CLS_ADDED, "%", "added") \ + COUNTER (clauses_binary, 2, PCNT_CLS_ADDED, "%", "added") \ + STATISTIC (clauses_deleted, 1, PCNT_CLS_ADDED, "%", "added") \ + STATISTIC (clauses_factored, 1, PCNT_CLS_ADDED, "%", "added") \ + STATISTIC (clauses_improved, 1, PCNT_CLS_LEARNED, "%", "learned") \ + COUNTER (clauses_irredundant, 2, PCNT_CLS_ADDED, "%", "added") \ + STATISTIC (clauses_kept1, 1, PCNT_CLS_IMPROVED, "%", "improved") \ + STATISTIC (clauses_kept2, 1, PCNT_CLS_IMPROVED, "%", "improved") \ + STATISTIC (clauses_kept3, 1, PCNT_CLS_IMPROVED, "%", "improved") \ + COUNTER (clauses_learned, 2, PCNT_CONFLICTS, "%", "conflicts") \ + COUNTER (clauses_original, 2, PCNT_CLS_ADDED, "%", "added") \ + STATISTIC (clauses_promoted1, 1, PCNT_CLS_IMPROVED, "%", "improved") \ + STATISTIC (clauses_promoted2, 1, PCNT_CLS_IMPROVED, "%", "improved") \ + STATISTIC (clauses_reduced, 1, PCNT_CLS_LEARNED, "%", "learned") \ + STATISTIC (clauses_reduced_tier1, 1, PCNT_CLS_REDUCED, "%", "reduced") \ + STATISTIC (clauses_reduced_tier2, 1, PCNT_CLS_REDUCED, "%", "reduced") \ + STATISTIC (clauses_reduced_tier3, 1, PCNT_CLS_REDUCED, "%", "reduced") \ + COUNTER (clauses_redundant, 2, NO_SECONDARY, 0, 0) \ + STATISTIC (clauses_unfactored, 1, PCNT_CLS_FACTORED, "%", "factored") \ + COUNTER (clauses_used, 2, PCNT_CLS_LEARNED, "%", "learned") \ + COUNTER (clauses_used_focused, 2, PCNT_CLS_USED, "%", "used") \ + COUNTER (clauses_used_stable, 2, PCNT_CLS_USED, "%", "used") \ + COUNTER (closures, 2, CONF_INT, "", "interval") \ + METRIC (compacted, 1, PCNT_REDUCTIONS, "%", "reductions") \ + COUNTER (conflicts, 0, PER_SECOND, 0, "per second") \ + COUNTER (congruent, 1, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (congruent_ands, 1, PCNT_CONGRUENT, "%", "congruent") \ + STATISTIC (congruent_arity, 1, PER_CONGRGATES, 0, "per gate") \ + STATISTIC (congruent_arity_ands, 1, PER_CONGRANDS, 0, "per AND") \ + STATISTIC (congruent_arity_xors, 1, PER_CONGRXORS, 0, "per XOR") \ + STATISTIC (congruent_binaries, 1, PCNT_CLS_ADDED, "%", "clauses added") \ + STATISTIC (congruent_ites, 1, PCNT_CONGRUENT, "%", "congruent") \ + STATISTIC (congruent_collisions, 1, PER_CONGRLOOKUP, 0, "per lookup") \ + STATISTIC (congruent_collisions_find, 1, PCNT_CONGRCOLS, "%", "collisions") \ + STATISTIC (congruent_collisions_index, 1, PCNT_CONGRCOLS, "%", "collisions") \ + STATISTIC (congruent_collisions_removed, 1, PCNT_CONGRCOLS, "%", "collisions") \ + STATISTIC (congruent_equivalences, 1, PCNT_CONGRUENT, "%", "congruent") \ + COUNTER (congruent_gates, 2, PER_CLOSURE, 0, "per closure") \ + COUNTER (congruent_gates_ands, 2, PCNT_CONGRGATES, "%", "gates") \ + COUNTER (congruent_gates_ites, 2, PCNT_CONGRGATES, "%", "gates") \ + COUNTER (congruent_gates_xors, 2, PCNT_CONGRGATES, "%", "gates") \ + STATISTIC (congruent_indexed, 1, PER_CONGRGATES, 0, "per gate") \ + STATISTIC (congruent_lookups, 1, PER_CONGRGATES, 0, "per gate") \ + STATISTIC (congruent_lookups_find, 1, PCNT_CONGRLOOKUP, "%", "lookups") \ + STATISTIC (congruent_lookups_removed, 1, PCNT_CONGRLOOKUP, "%", "lookups") \ + COUNTER (congruent_matched, 2, PCNT_CONGRUENT, "%", "congruent") \ + COUNTER (congruent_matched_ands, 2, PCNT_CONGRMATCHED, "%", "matched") \ + COUNTER (congruent_matched_ites, 2, PCNT_CONGRMATCHED, "%", "matched") \ + COUNTER (congruent_matched_xors, 2, PCNT_CONGRMATCHED, "%", "matched") \ + STATISTIC (congruent_rewritten, 1, PCNT_CONGRGATES, "%", "gates") \ + STATISTIC (congruent_rewritten_ands, 1, PCNT_CONGREWR, "%", "rewritten") \ + STATISTIC (congruent_rewritten_ites, 1, PCNT_CONGREWR, "%", "rewritten") \ + STATISTIC (congruent_rewritten_xors, 1, PCNT_CONGREWR, "%", "rewritten") \ + STATISTIC (congruent_simplified, 1, PCNT_CONGRGATES, "%", "gates") \ + STATISTIC (congruent_simplified_ands, 1, PCNT_CONGRSIMPS, "%", "simplified") \ + STATISTIC (congruent_simplified_ites, 1, PCNT_CONGRSIMPS, "%", "simplified") \ + STATISTIC (congruent_simplified_xors, 1, PCNT_CONGRSIMPS, "%", "simplified") \ + STATISTIC (congruent_subsumed, 1, PCNT_CLS_ORIGINAL, "%", "original") \ + STATISTIC (congruent_trivial_ite, 1, PCNT_CONGRUENT, "%", "congruent") \ + STATISTIC (congruent_unary, 1, PCNT_CONGRUENT, "%", "congruent") \ + STATISTIC (congruent_unary_ands, 1, PCNT_CONGRUNARY, "%", "unary") \ + STATISTIC (congruent_unary_ites, 1, PCNT_CONGRUNARY, "%", "unary") \ + STATISTIC (congruent_unary_xors, 1, PCNT_CONGRUNARY, "%", "unary") \ + STATISTIC (congruent_units, 1, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (congruent_xors, 1, PCNT_CONGRUENT, "%", "congruent") \ + COUNTER (decisions, 0, PER_CONFLICT, 0, "per conflict") \ + METRIC (definitions_checked, 1, PCNT_ELIM_ATTEMPTS, "%", "attempts") \ + STATISTIC (definitions_eliminated, 1, PCNT_ELIMINATED, "%", "eliminated") \ + METRIC (definitions_extracted, 1, PCNT_EXTRACTED, "%", "extracted") \ + STATISTIC (definition_units, 1, PCNT_VARIABLES, "%", "variables") \ + METRIC (defragmentations, 1, CONF_INT, "", "interval") \ + METRIC (dense_garbage_collections, 2, PCNT_COLLECTIONS, "%", "collections") \ + METRIC (dense_propagations, 1, PCNT_PROPS, "%", "propagations") \ + METRIC (dense_ticks, 1, PCNT_TICKS, "%", "ticks") \ + METRIC (duplicated, 1, PCNT_CLS_ADDED, "%", "added") \ + STATISTIC (eagerly_subsumed, 1, PCNT_CLS_LEARNED, "%", "learned") \ + STATISTIC (eliminate_attempted, 1, PER_VARIABLE, 0, "per variable") \ + COUNTER (eliminated, 1, PCNT_VARIABLES, "%", "variables") \ + COUNTER (eliminate_resolutions, 2, PER_SECOND, 0, "per second") \ + STATISTIC (eliminate_units, 1, PCNT_VARIABLES, "%", "variables") \ + COUNTER (eliminations, 2, CONF_INT, "", "interval") \ + STATISTIC (equivalences_eliminated, 1, PCNT_ELIMINATED, "%", "eliminated") \ + METRIC (equivalences_extracted, 1, PCNT_EXTRACTED, "%", "extracted") \ + METRIC (extensions, 1, PCNT_SEARCHES, "%", "searches") \ + COUNTER (factored, 1, PCNT_VARIABLES, "%", "variables") \ + COUNTER (factorizations, 2, CONF_INT, "", "interval") \ + COUNTER (factor_ticks, 2, PCNT_TICKS, "%", "ticks") \ + COUNTER (fast_eliminated, 1, PCNT_ELIMINATED, "%", "eliminated") \ + COUNTER (fast_strengthened, 1, PCNT_STRENGTHENED, "%", "per strengthened") \ + COUNTER (fast_subsumed, 1, PCNT_SUBSUMED, "%", "per subsumed") \ + STATISTIC (fresh, 1, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (flipped, 1, PER_WALKS, 0, "per walk") \ + METRIC (flushed, 2, PER_FIXED, 0, "per fixed") \ + METRIC (focused_decisions, 1, PCNT_DECISIONS, "%", "decisions") \ + METRIC (focused_modes, 1, CONF_INT, "", "interval") \ + METRIC (focused_propagations, 1, PCNT_PROPS, "%", "propagations") \ + METRIC (focused_restarts, 1, PCNT_RESTARTS, "%", "restarts") \ + METRIC (focused_ticks, 1, PCNT_TICKS, "%", "ticks") \ + COUNTER (forward_checks, 2, NO_SECONDARY, 0, 0) \ + COUNTER (forward_steps, 2, PER_FORWARD_CHECK, 0, "per check") \ + STATISTIC (forward_strengthened, 1, PCNT_STRENGTHENED, "%", "per strengthened") \ + STATISTIC (forward_subsumed, 1, PCNT_SUBSUMED, "%", "per subsumed") \ + METRIC (forward_subsumptions, 1, CONF_INT, "", "interval") \ + METRIC (garbage_collections, 2, CONF_INT, "", "interval") \ + METRIC (gates_checked, 1, PCNT_ELIM_ATTEMPTS, "%", "attempts") \ + STATISTIC (gates_eliminated, 1, PCNT_ELIMINATED, "%", "eliminated") \ + METRIC (gates_extracted, 1, PCNT_ELIM_ATTEMPTS, "%", "attempts") \ + STATISTIC (if_then_else_eliminated, 1, PCNT_ELIMINATED, "%", "eliminated") \ + METRIC (if_then_else_extracted, 1, PCNT_EXTRACTED, "%", "extracted") \ + METRIC (initial_decisions, 1, PCNT_DECISIONS, "%", "decisions") \ + COUNTER (iterations, 1, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (jumped_reasons, 1, PCNT_PROPS, "%", "propagations") \ + STATISTIC (kitten_conflicts, 1, PER_KITTEN_SOLVED, 0, "per solved") \ + STATISTIC (kitten_decisions, 1, PER_KITTEN_SOLVED, 0, "per solved") \ + STATISTIC (kitten_flip, 1, NO_SECONDARY, 0, 0) \ + STATISTIC (kitten_flipped, 1, PCNT_KITTEN_FLIP, "%", "flip") \ + COUNTER (kitten_propagations, 2, PER_KITTEN_SOLVED, 0, "per solved") \ + STATISTIC (kitten_sat, 1, PCNT_KITTEN_SOLVED, "%", "solved") \ + COUNTER (kitten_solved, 2, NO_SECONDARY, 0, 0) \ + COUNTER (kitten_ticks, 2, PER_KITTEN_PROP, 0, "per prop") \ + STATISTIC (kitten_unknown, 1, PCNT_KITTEN_SOLVED, "%", "solved") \ + STATISTIC (kitten_unsat, 1, PCNT_KITTEN_SOLVED, "%", "solved") \ + METRIC (literals_bumped, 1, PER_CLS_LEARNED, 0, "per clause") \ + METRIC (literals_deduced, 1, PER_CLS_LEARNED, 0, "per clause") \ + COUNTER (literals_factor, 2, PER_VARIABLE, 0, "per variable") \ + STATISTIC (literals_factored, 1, PER_CLS_FACTORED, 0, "per factored") \ + METRIC (literals_learned, 1, PER_CLS_LEARNED, 0, "per clause") \ + METRIC (literals_minimized, 1, PCNT_LITS_DEDUCED, "%", "deduced") \ + METRIC (literals_minshrunken, 1, PCNT_LITS_SHRUNKEN, "%", "shrunken") \ + METRIC (literals_shrunken, 1, PCNT_LITS_DEDUCED, "%", "deduced") \ + STATISTIC (literals_unfactored, 2, PER_CLS_UNFACTORED, 0, "per unfactored") \ + METRIC (moved, 1, PCNT_REDUCTIONS, "%", "reductions") \ + STATISTIC (on_the_fly_strengthened, 1, PCNT_CONFLICTS, "%", "of conflicts") \ + STATISTIC (on_the_fly_subsumed, 1, PCNT_CONFLICTS, "%", "of conflicts") \ + METRIC (probing_propagations, 1, PCNT_PROPS, "%", "propagations") \ + COUNTER (probings, 2, CONF_INT, "", "interval") \ + COUNTER (probing_ticks, 2, PCNT_TICKS, "%", "ticks") \ + COUNTER (propagations, 0, PER_SECOND, "", "per second") \ + STATISTIC (queue_decisions, 1, PCNT_DECISIONS, "%", "decision") \ + STATISTIC (random_decisions, 1, PCNT_DECISIONS, "%", "decision") \ + COUNTER (random_sequences, 2, CONF_INT, "", "interval") \ + COUNTER (reductions, 1, CONF_INT, "", "interval") \ + COUNTER (reordered, 1, CONF_INT, "", "interval") \ + STATISTIC (reordered_focused, 1, PCNT_REORDERED, "%", "reordered") \ + STATISTIC (reordered_stable, 1, PCNT_REORDERED, "%", "reordered") \ + COUNTER (rephased, 1, CONF_INT, "", "interval") \ + METRIC (rephased_best, 1, PCNT_REPHASED, "%", "rephased") \ + METRIC (rephased_inverted, 1, PCNT_REPHASED, "%", "rephased") \ + METRIC (rephased_original, 1, PCNT_REPHASED, "%", "rephased") \ + METRIC (rephased_walking, 1, PCNT_REPHASED, "%", "rephased") \ + METRIC (rescaled, 2, CONF_INT, "", "interval") \ + COUNTER (restarts, 1, CONF_INT, "", "interval") \ + STATISTIC (restarts_levels, 1, PER_RESTART, 0, "per restart") \ + STATISTIC (restarts_reused_levels, 1, PCNT_RESTARTS_LEVELS, "%", "levels") \ + STATISTIC (restarts_reused_trails, 1, PCNT_RESTARTS, "%", "restarts") \ + COUNTER (retiered, 2, CONF_INT, "", "interval") \ + METRIC (saved_decisions, 1, PCNT_DECISIONS, "%", "decisions") \ + METRIC (score_decisions, 0, PCNT_DECISIONS, "%", "decision") \ + COUNTER (searches, 2, CONF_INT, "", "interval") \ + METRIC (search_propagations, 2, PCNT_PROPS, "%", "propagations") \ + COUNTER (search_ticks, 2, PCNT_TICKS, "%", "ticks") \ + METRIC (sparse_gcs, 2, PCNT_COLLECTIONS, "%", "collections") \ + METRIC (stable_decisions, 1, PCNT_DECISIONS, "%", "decisions") \ + METRIC (stable_modes, 2, CONF_INT, "", "interval") \ + METRIC (stable_propagations, 1, PCNT_PROPS, "%", "propagations") \ + METRIC (stable_restarts, 1, PCNT_RESTARTS, "%", "restarts") \ + METRIC (stable_ticks, 2, PCNT_TICKS, "%", "ticks") \ + COUNTER (strengthened, 1, PCNT_SUBSUMPTION_CHECK, "%", "checks") \ + COUNTER (substituted, 1, PCNT_VARIABLES, "%", "variables") \ + COUNTER (substitute_ticks, 2, PCNT_TICKS, "%", "ticks") \ + STATISTIC (substitute_units, 1, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (substitutions, 2, CONF_INT, "", "interval") \ + COUNTER (subsumed, 1, PCNT_SUBSUMPTION_CHECK, "%", "checks") \ + COUNTER (subsumption_checks, 2, NO_SECONDARY, 0, 0) \ + COUNTER (sweep, 2, CONF_INT, "", "interval") \ + STATISTIC (sweep_clauses, 1, PER_SWEEP_VARIABLES, 0, "per sweep_variables") \ + COUNTER (sweep_completed, 2, SWEEPS_PER_COMPLETED, 0, "sweeps") \ + STATISTIC (sweep_depth, 1, PER_SWEEP_VARIABLES, 0, "per sweep_variables") \ + STATISTIC (sweep_environment, 1, PER_SWEEP_VARIABLES, 0, "per sweep_variables") \ + COUNTER (sweep_equivalences, 2, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (sweep_fixed_backbone, 1, PER_SWEEP_VARIABLES, 0, "per sweep_variables") \ + STATISTIC (sweep_flip_backbone, 1, PER_SWEEP_VARIABLES, 0, "per sweep_variables") \ + STATISTIC (sweep_flipped_backbone, 1, PCNT_SWEEP_FLIP_BACKBONE, "%", "sweep_flip_backbone") \ + STATISTIC (sweep_flip_equivalences, 1, PER_SWEEP_VARIABLES, 0, "per sweep_variables") \ + STATISTIC (sweep_flipped_equivalences, 1, PCNT_SWEEP_FLIP_EQUIVALENCES, "%", "sweep_flip_equivalences") \ + STATISTIC (sweep_sat, 1, PCNT_SWEEP_SOLVED, "%", "sweep_solved") \ + STATISTIC (sweep_sat_backbone, 1, PCNT_SWEEP_SOLVED_BACKBONE, "%", "sweep_solved_backbone") \ + STATISTIC (sweep_sat_equivalences, 1, PCNT_SWEEP_SOLVED_EQUIVALENCES, "%", "sweep_solved_equivalences") \ + COUNTER (sweep_solved, 2, PCNT_KITTEN_SOLVED, "%", "kitten_solved") \ + STATISTIC (sweep_solved_backbone, 1, PCNT_SWEEP_SOLVED, "%", "sweep_solved") \ + STATISTIC (sweep_solved_equivalences, 1, PCNT_SWEEP_SOLVED, "%", "sweep_solved") \ + STATISTIC (sweep_unknown_backbone, 1, PCNT_SWEEP_SOLVED_BACKBONE, "%", "sweep_solved_backbone") \ + STATISTIC (sweep_unknown_equivalences, 1, PCNT_SWEEP_SOLVED_EQUIVALENCES, "%", "sweep_solved_equivalences") \ + COUNTER (sweep_units, 2, PCNT_VARIABLES, "%", "variables") \ + STATISTIC (sweep_unsat, 1, PCNT_SWEEP_SOLVED, "%", "sweep_solved") \ + STATISTIC (sweep_unsat_backbone, 1, PCNT_SWEEP_SOLVED_BACKBONE, "%", "sweep_solve_backbone") \ + STATISTIC (sweep_unsat_equivalences, 1, PCNT_SWEEP_SOLVED_EQUIVALENCES, "%", "sweep_solve_equivalences") \ + STATISTIC (sweep_variables, 1, PCNT_VARIABLES, "%", "variables") \ + COUNTER (switched, 0, CONF_INT, "", "interval") \ + METRIC (target_decisions, 1, PCNT_DECISIONS, "%", "decisions") \ + METRIC (target_saved, 1, CONF_INT, "", "interval") \ + STATISTIC (ticks, 2, PER_PROPAGATION, 0, "per prop") \ + METRIC (transitive_probes, 2, PER_VARIABLE, "", "per variable") \ + METRIC (transitive_propagations, 2, PCNT_PROPS, "%", "propagations") \ + METRIC (transitive_reduced, 1, PCNT_CLS_ADDED, "%", "added") \ + METRIC (transitive_reductions, 1, CONF_INT, "", "interval") \ + COUNTER (transitive_ticks, 2, PCNT_TICKS, "%", "ticks") \ + METRIC (transitive_units, 1, PCNT_VARIABLES, "%", "variables") \ + COUNTER (units, 2, PCNT_VARIABLES, "%", "variables") \ + COUNTER (variables_activated, 2, PER_VARIABLE, 0, "per variable") \ + COUNTER (variables_eliminate, 2, PER_VARIABLE, 0, "variables") \ + COUNTER (variables_extension, 2, PER_VARIABLE, 0, "per variable") \ + COUNTER (variables_factor, 2, PER_VARIABLE, 0, "per variable") \ + COUNTER (variables_original, 2, PER_VARIABLE, 0, "per variable") \ + COUNTER (variables_subsume, 2, PER_VARIABLE, 0, "per variable") \ + METRIC (vectors_defrags_needed, 1, PCNT_DEFRAGS, "%", "defrags") \ + METRIC (vectors_enlarged, 2, CONF_INT, "", "interval") \ + COUNTER (vivifications, 2, CONF_INT, "", "interval") \ + COUNTER (vivified, 1, PCNT_VIVIFY_CHECK, "%", "checks") \ + STATISTIC (vivified_asym, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_implied, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_instantiated, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_instirr, 1, PCNT_VIVIFY_INST, "%", "instantiated") \ + STATISTIC (vivified_instred, 1, PCNT_VIVIFY_INST, "%", "instantiated") \ + STATISTIC (vivified_irredundant, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_promoted, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_shrunken, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_shrunkirr, 1, PCNT_VIVIFY_SHRUNKEN, "%", "shrunken") \ + STATISTIC (vivified_shrunkred, 1, PCNT_VIVIFY_SHRUNKEN, "%", "shrunken") \ + STATISTIC (vivified_subirr, 1, PCNT_VIVIFY_SUB, "%", "subsumed") \ + STATISTIC (vivified_subred, 1, PCNT_VIVIFY_SUB, "%", "subsumed") \ + STATISTIC (vivified_subsumed, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_tier1, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_tier2, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_tier3, 1, PCNT_VIVIFIED, "%", "vivified") \ + STATISTIC (vivified_unlearn, 1, PCNT_VIVIFIED, "%", "vivified") \ + COUNTER (vivify_checks, 2, PER_VIVIFICATION, "", "per vivify") \ + COUNTER (vivify_probes, 2, PER_VIVIFY_CHECK, 0, "per check") \ + STATISTIC (vivify_propagations, 2, PCNT_PROPS, "%", "propagations") \ + COUNTER (vivify_reused, 2, PCNT_VIVIFY_PROBES, "%", "probes") \ + STATISTIC (vivify_ticks, 2, PCNT_TICKS, "%", "ticks") \ + STATISTIC (vivify_units, 1, PCNT_VARIABLES, "%", "variables") \ + METRIC (walk_decisions, 1, PCNT_WALKS, "%", "walks") \ + STATISTIC (walk_improved, 1, PCNT_WALKS, "%", "walks") \ + METRIC (walk_previous, 1, PCNT_WALKS, "%", "walks") \ + COUNTER (walks, 1, CONF_INT, "", "interval") \ + COUNTER (walk_steps, 2, PER_FLIPPED, 0, "per flipped") \ + STATISTIC (warming_conflicts, 1, PER_WALKS, 0, "per walk") \ + COUNTER (warming_decisions, 2, PER_WALKS, 0, "per walk") \ + COUNTER (warming_propagations, 2, PCNT_PROPS, "%", "propagations") \ + COUNTER (warmups, 2, PCNT_WALKS, "%", "walks") \ + METRIC (weakened, 1, PCNT_CLS_ADDED, "%", "added") + +// clang-format on + +/*------------------------------------------------------------------------*/ +#ifdef METRICS + +#define METRIC COUNTER +#define STATISTIC COUNTER + +#ifndef STATISTICS +#define STATISTICS +#endif + +#elif STATISTICS + +#define METRIC IGNORE +#define STATISTIC COUNTER + +#else + +#define METRIC IGNORE +#define STATISTIC IGNORE + +#endif +/*------------------------------------------------------------------------*/ +// clang-format off + +typedef struct statistics statistics; + +#define MAX_GLUE_USED 127 + +struct statistics +{ +#define COUNTER(NAME,VERBOSE,OTHER,UNITS,TYPE) \ + uint64_t NAME; +#define IGNORE(...) + + METRICS_COUNTERS_AND_STATISTICS + +#undef COUNTER +#undef IGNORE + + struct { + uint64_t glue[MAX_GLUE_USED + 1]; + } used[2]; +}; + +// clang-format on +/*------------------------------------------------------------------------*/ + +#define CLAUSES (IRREDUNDANT_CLAUSES + BINARY_CLAUSES + REDUNDANT_CLAUSES) +#define CONFLICTS (solver->statistics.conflicts) +#define DECISIONS (solver->statistics.decisions) +#define IRREDUNDANT_CLAUSES (solver->statistics.clauses_irredundant) +#define LEARNED_CLAUSES (solver->statistics.learned) +#define REDUNDANT_CLAUSES (solver->statistics.clauses_redundant) +#define BINARY_CLAUSES (solver->statistics.clauses_binary) +#define BINIRR_CLAUSES (BINARY_CLAUSES + IRREDUNDANT_CLAUSES) + +/*------------------------------------------------------------------------*/ + +#define COUNTER(NAME, VERBOSE, OTHER, UNITS, TYPE) \ +\ + static inline void kissat_inc_##NAME (statistics *statistics) { \ + assert (statistics->NAME < UINT64_MAX); \ + statistics->NAME++; \ + } \ +\ + static inline void kissat_dec_##NAME (statistics *statistics) { \ + assert (statistics->NAME); \ + statistics->NAME--; \ + } \ +\ + static inline void kissat_add_##NAME (statistics *statistics, \ + uint64_t n) { \ + assert (UINT64_MAX - n >= statistics->NAME); \ + statistics->NAME += n; \ + } \ +\ + static inline void kissat_sub_##NAME (statistics *statistics, \ + uint64_t n) { \ + assert (n <= statistics->NAME); \ + statistics->NAME -= n; \ + } \ +\ + static inline uint64_t kissat_get_##NAME (statistics *statistics) { \ + return statistics->NAME; \ + } + +/*------------------------------------------------------------------------*/ + +#define IGNORE(NAME, VERBOSE, OTHER, UNITS, TYPE) \ +\ + static inline void kissat_inc_##NAME (statistics *statistics) { \ + (void) statistics; \ + } \ +\ + static inline void kissat_dec_##NAME (statistics *statistics) { \ + (void) statistics; \ + } \ +\ + static inline void kissat_add_##NAME (statistics *statistics, \ + uint64_t n) { \ + (void) statistics; \ + (void) n; \ + } \ +\ + static inline void kissat_sub_##NAME (statistics *statistics, \ + uint64_t n) { \ + (void) statistics; \ + (void) n; \ + } \ + static inline uint64_t kissat_get_##NAME (statistics *statistics) { \ + (void) statistics; \ + return UINT64_MAX; \ + } + +/*------------------------------------------------------------------------*/ +// clang-format off + +METRICS_COUNTERS_AND_STATISTICS + +#undef COUNTER +#undef IGNORE + +// clang-format on +/*------------------------------------------------------------------------*/ + +#define INC(NAME) kissat_inc_##NAME (&solver->statistics) +#define DEC(NAME) kissat_dec_##NAME (&solver->statistics) +#define ADD(NAME, N) kissat_add_##NAME (&solver->statistics, (N)) +#define SUB(NAME, N) kissat_sub_##NAME (&solver->statistics, (N)) +#define GET(NAME) kissat_get_##NAME (&solver->statistics) + +/*------------------------------------------------------------------------*/ +#ifndef QUIET + +struct kissat; + +void kissat_statistics_print (struct kissat *, bool verbose); +void kissat_print_glue_usage (struct kissat *); + +// Format widths of individual parts during printing statistics lines. +// Shared between 'statistics.c' and 'resources.c' to align the printing. + +#define SFW1 "30" +#define SFW2 "12" +#define SFW3 "5" +#define SFW4 "10" + +#define SFW34 "16" // SFE3 + space + SFE 4 +#define SFW34EXTENDED "19" // SFE3 + space + SFE 4 + ".2" + +#define PRINT_STAT(NAME, PRIMARY, SECONDARY, UNITS, TYPE) \ + do { \ + printf ("%s%-" SFW1 "s %" SFW2 PRIu64 " ", solver->prefix, NAME ":", \ + (uint64_t) PRIMARY); \ + const double SAVED_SECONDARY = (double) (SECONDARY); \ + const char *SAVED_UNITS = (const char *) (UNITS); \ + const char *SAVED_TYPE = (const char *) (TYPE); \ + if (SAVED_TYPE && SAVED_SECONDARY >= 0) { \ + if (SAVED_UNITS) \ + printf ("%" SFW34 ".0f %-2s", SAVED_SECONDARY, SAVED_UNITS); \ + else \ + printf ("%" SFW34EXTENDED ".2f", SAVED_SECONDARY); \ + fputc (' ', stdout); \ + fputs (SAVED_TYPE, stdout); \ + } \ + fputc ('\n', stdout); \ + } while (0) + +#endif + +#ifndef NDEBUG + +struct kissat; +void kissat_check_statistics (struct kissat *); + +#else + +#define kissat_check_statistics(...) \ + do { \ + } while (0) + +#endif + +#endif diff --git a/src/sat/kissat/strengthen.c b/src/sat/kissat/strengthen.c new file mode 100644 index 000000000..d5107d7f5 --- /dev/null +++ b/src/sat/kissat/strengthen.c @@ -0,0 +1,187 @@ +#include "strengthen.h" +#include "collect.h" +#include "inline.h" +#include "promote.h" + +static clause *large_on_the_fly_strengthen (kissat *solver, clause *c, + unsigned lit) { + assert (solver->antecedent_size > 3); + LOGCLS (c, + "large on-the-fly strengthening " + "by removing %s from", + LOGLIT (lit)); + unsigned *lits = c->lits; + assert (lits[0] == lit || lits[1] == lit); + INC (on_the_fly_strengthened); +#ifndef NDEBUG + clause *old_next = kissat_next_clause (c); +#endif + if (lits[0] == lit) + SWAP (unsigned, lits[0], lits[1]); + assert (lits[1] == lit); + const reference ref = kissat_reference_clause (solver, c); + kissat_unwatch_blocking (solver, lit, ref); + SHRINK_CLAUSE_IN_PROOF (c, lit, lits[0]); + CHECK_SHRINK_CLAUSE (c, lit, lits[0]); + { + const unsigned old_size = c->size; + unsigned new_size = 1; + const bool irredundant = !c->redundant; + for (unsigned i = 2; i < old_size; i++) { + const unsigned other = lits[i]; + assert (VALUE (other) < 0); + if (!LEVEL (other)) + continue; + lits[new_size++] = other; + if (irredundant) + kissat_mark_added_literal (solver, other); + } + assert (new_size > 2); + c->size = new_size; + c->searched = 2; + if (c->redundant && c->glue >= new_size) + kissat_promote_clause (solver, c, new_size - 1); + if (!c->shrunken) { + c->shrunken = true; + lits[old_size - 1] = INVALID_LIT; + } + } + LOGCLS (c, "unsorted on-the-fly strengthened"); + { + assert (VALUE (lits[1]) < 0); + unsigned highest_pos = 1; + unsigned highest_level = LEVEL (lits[1]); + const unsigned size = c->size; + for (unsigned i = 2; i < size; i++) { + const unsigned other = lits[i]; + assert (VALUE (other) < 0); + const unsigned level = LEVEL (other); + if (level <= highest_level) + continue; + highest_pos = i; + highest_level = level; + } + if (highest_pos != 1) + SWAP (unsigned, lits[1], lits[highest_pos]); + LOGCLS (c, "sorted on-the-fly strengthened"); + kissat_watch_blocking (solver, lits[1], lits[0], ref); + } + { + watches *watches = &WATCHES (lits[0]); +#ifndef NDEBUG + const watch *const end_of_watches = END_WATCHES (*watches); +#endif + watch *p = BEGIN_WATCHES (*watches); + assert (solver->watching); + for (;;) { + assert (p != end_of_watches); + const watch head = *p++; + if (head.type.binary) + continue; + assert (p != end_of_watches); + const watch tail = *p++; + if (tail.large.ref == ref) + break; + } + p[-2].blocking.lit = lits[1]; + LOGREF (ref, "updating watching %s now blocking %s in", + LOGLIT (lits[0]), LOGLIT (lits[1])); + } +#ifndef NDEBUG + clause *new_next = kissat_next_clause (c); + assert (old_next == new_next); +#endif + LOGCLS (c, "conflicting"); + return c; +} + +static clause *binary_on_the_fly_strengthen (kissat *solver, clause *c, + unsigned lit) { + assert (solver->antecedent_size == 3); + LOGCLS (c, + "binary on-the-fly strengthening " + "by removing %s from", + LOGLIT (lit)); + unsigned first = INVALID_LIT, second = INVALID_LIT; +#ifndef NDEBUG + bool found = false; +#endif + for (all_literals_in_clause (other, c)) { + if (other == lit) { +#ifndef NDEBUG + assert (!found); + found = true; +#endif + continue; + } + assert (VALUE (other) < 0); + if (!LEVEL (other)) + continue; + if (first == INVALID_LIT) + first = other; + else { + assert (second == INVALID_LIT); + second = other; +#ifndef NDEBUG + break; +#endif + } + } + assert (found); + assert (second != INVALID_LIT); + LOGBINARY (first, second, "on-the-fly strengthened"); + kissat_new_binary_clause (solver, first, second); + const reference ref = kissat_reference_clause (solver, c); + kissat_unwatch_blocking (solver, c->lits[0], ref); + kissat_unwatch_blocking (solver, c->lits[1], ref); + kissat_mark_clause_as_garbage (solver, c); + clause *conflict = kissat_binary_conflict (solver, first, second); + return conflict; +} + +clause *kissat_on_the_fly_strengthen (kissat *solver, clause *c, + unsigned lit) { + assert (!c->garbage); + assert (solver->antecedent_size > 2); + if (!c->redundant) + kissat_mark_removed_literal (solver, lit); + clause *res; + if (solver->antecedent_size == 3) + res = binary_on_the_fly_strengthen (solver, c, lit); + else + res = large_on_the_fly_strengthen (solver, c, lit); + return res; +} + +void kissat_on_the_fly_subsume (kissat *solver, clause *c, clause *d) { + LOGCLS (c, "on-the-fly subsuming"); + LOGCLS (d, "on-the-fly subsumed"); + assert (c != d); + assert (!d->garbage); + assert (c->size > 1); + assert (c->size <= d->size); + kissat_mark_clause_as_garbage (solver, d); + INC (on_the_fly_subsumed); + if (d->redundant) { + if (c->redundant && c->glue > d->glue) + kissat_promote_clause (solver, c, d->glue); + return; + } + if (!c->redundant) + return; + if (c->size > 2) { + c->redundant = false; + LOGCLS (c, "turned"); + kissat_update_last_irredundant (solver, c); + } + statistics *statistics = &solver->statistics; + if (c->size > 2) { + assert (statistics->clauses_irredundant < UINT64_MAX); + statistics->clauses_irredundant++; + } else { + assert (statistics->clauses_binary < UINT64_MAX); + statistics->clauses_binary++; + } + assert (statistics->clauses_redundant > 0); + statistics->clauses_redundant--; +} diff --git a/src/sat/kissat/strengthen.h b/src/sat/kissat/strengthen.h new file mode 100644 index 000000000..1e6a395fc --- /dev/null +++ b/src/sat/kissat/strengthen.h @@ -0,0 +1,17 @@ +#ifndef _strengthen_h_INCLUDED +#define _strengthen_h_INCLUDED + +#include + +struct clause; +struct kissat; + +struct clause *kissat_on_the_fly_strengthen (struct kissat *, + struct clause *, unsigned lit); + +void kissat_on_the_fly_subsume (struct kissat *, struct clause *, + struct clause *); + +bool issat_strengthen_clause (struct kissat *, struct clause *, unsigned); + +#endif diff --git a/src/sat/kissat/substitute.c b/src/sat/kissat/substitute.c new file mode 100644 index 000000000..f95efe746 --- /dev/null +++ b/src/sat/kissat/substitute.c @@ -0,0 +1,617 @@ +#include "substitute.h" +#include "allocate.h" +#include "backtrack.h" +#include "inline.h" +#include "print.h" +#include "proprobe.h" +#include "report.h" +#include "terminate.h" +#include "trail.h" +#include "weaken.h" + +#include + +static void assign_and_propagate_units (kissat *solver, unsigneds *units) { + if (EMPTY_STACK (*units)) + return; + LOG ("assigning and propagating %zu units", SIZE_STACK (*units)); + while (!solver->inconsistent && !EMPTY_STACK (*units)) { + const unsigned unit = POP_STACK (*units); + LOG ("trying to assign and propagate unit %s now", LOGLIT (unit)); + const value value = VALUE (unit); + if (value > 0) { + LOG ("skipping satisfied unit %s", LOGLIT (unit)); + } else if (value < 0) { + LOG ("inconsistent unit %s", LOGLIT (unit)); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + solver->inconsistent = true; + } else { + kissat_learned_unit (solver, unit); + INC (substitute_units); + assert (!solver->level); + (void) kissat_probing_propagate (solver, 0, false); + } + } +} + +static void determine_representatives (kissat *solver, unsigned *repr) { + size_t bytes = LITS * sizeof (unsigned); + unsigned *mark = kissat_calloc (solver, LITS, sizeof *mark); + unsigned *reach = kissat_malloc (solver, LITS * sizeof *reach); + watches *all_watches = solver->watches; + const flags *const flags = solver->flags; + unsigned reached = 0; + unsigneds scc; + unsigneds work; + unsigneds units; + INIT_STACK (scc); + INIT_STACK (work); + INIT_STACK (units); +#ifdef LOGGING + unsigned trivial_sccs = 0; + unsigned non_trivial_sccs = 0; +#endif + bool inconsistent = false; + uint64_t ticks = 0; + for (all_literals (root)) { + if (inconsistent) + break; + if (mark[root]) + continue; + if (!ACTIVE (IDX (root))) + continue; + assert (EMPTY_STACK (scc)); + assert (EMPTY_STACK (work)); + LOG ("substitute root %s", LOGLIT (root)); + PUSH_STACK (work, root); + bool failed = false; + const unsigned mark_root = reached + 1; + while (!inconsistent && !EMPTY_STACK (work)) { + unsigned lit = TOP_STACK (work); + if (lit == INVALID_LIT) { + (void) POP_STACK (work); + lit = POP_STACK (work); + const unsigned not_lit = NOT (lit); + unsigned reach_lit = reach[lit]; + unsigned mark_lit = mark[lit]; + assert (reach_lit == mark_lit); + assert (repr[lit] == INVALID_LIT); + watches *watches = all_watches + not_lit; + const size_t size_watches = SIZE_WATCHES (*watches); + ticks += 1 + kissat_cache_lines (size_watches, sizeof (watch)); + for (all_binary_blocking_watches (watch, *watches)) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + const unsigned idx_other = IDX (other); + if (!flags[idx_other].active) + continue; + assert (mark[other]); + unsigned reach_other = reach[other]; + if (reach_other < reach_lit) + reach_lit = reach_other; + } + if (reach_lit != mark_lit) { + LOG ("reach[%s] = %u", LOGLIT (lit), reach_lit); + reach[lit] = reach_lit; + continue; + } + unsigned *end_scc = END_STACK (scc); + unsigned *begin_scc = end_scc; + do + assert (begin_scc != BEGIN_STACK (scc)); + while (*--begin_scc != lit); + SET_END_OF_STACK (scc, begin_scc); + const size_t size_scc = end_scc - begin_scc; + unsigned min_lit = lit; + if (size_scc > 1) { + for (const unsigned *p = begin_scc; p != end_scc; p++) { + const unsigned other = *p; + if (other < min_lit) + min_lit = other; + } +#ifdef LOGGING + non_trivial_sccs++; +#endif + LOG ("size %zu SCC entered trough %s representative %s", size_scc, + LOGLIT (lit), LOGLIT (min_lit)); + } else { +#ifdef LOGGING + trivial_sccs++; +#endif + LOG ("trivial size one SCC with %s", LOGLIT (lit)); + assert (min_lit == lit); + } + for (const unsigned *p = begin_scc; p != end_scc; p++) { + const unsigned other = *p; + LOG ("substitute repr[%s] = %s", LOGLIT (other), + LOGLIT (min_lit)); + repr[other] = min_lit; + reach[other] = UINT_MAX; + + const unsigned not_other = NOT (other); + const unsigned repr_not_other = repr[not_other]; + if (repr_not_other == INVALID_LIT) + continue; + if (min_lit == repr_not_other) { + LOG ("clashing literals %s and %s in same SCC", LOGLIT (other), + LOGLIT (not_other)); + PUSH_STACK (units, min_lit); + inconsistent = true; + break; + } + assert (NOT (min_lit) == repr_not_other); + if (failed) + continue; + const unsigned mark_not_other = mark[not_other]; + assert (mark_not_other != INVALID_LIT); + assert (mark[root] == mark_root); + if (mark_root > mark_not_other) + continue; + LOG ("root %s implies both %s and %s", LOGLIT (root), + LOGLIT (other), LOGLIT (not_other)); + const unsigned unit = NOT (root); + PUSH_STACK (units, unit); + failed = true; + } + if (inconsistent) + break; + } else if (!mark[lit]) { + PUSH_STACK (work, INVALID_LIT); + PUSH_STACK (scc, lit); + mark[lit] = reach[lit] = ++reached; + LOG ("substitute mark[%s] = %u", LOGLIT (lit), reached); + const unsigned not_lit = NOT (lit); + watches *watches = all_watches + not_lit; + const size_t size_watches = SIZE_WATCHES (*watches); + ticks += 1 + kissat_cache_lines (size_watches, sizeof (watch)); + for (all_binary_blocking_watches (watch, *watches)) { + if (!watch.type.binary) + continue; + const unsigned other = watch.binary.lit; + const unsigned idx_other = IDX (other); + if (!flags[idx_other].active) + continue; + if (!mark[other]) + PUSH_STACK (work, other); + } + } else + (void) POP_STACK (work); + } + } + RELEASE_STACK (work); + RELEASE_STACK (scc); + kissat_extremely_verbose (solver, + "determining substitution " + "representatives took %" PRIu64 + " 'substitute_ticks'", + ticks); + ADD (substitute_ticks, ticks); + LOG ("reached %u literals", reached); + LOG ("found %u non-trivial SCCs", non_trivial_sccs); + LOG ("found %u trivial SCCs", trivial_sccs); + LOG ("found %zu units", SIZE_STACK (units)); + assign_and_propagate_units (solver, &units); + assert (!inconsistent || solver->inconsistent); + RELEASE_STACK (units); + kissat_free (solver, reach, bytes); + kissat_free (solver, mark, bytes); + for (all_literals (lit)) + if (repr[lit] == INVALID_LIT) + repr[lit] = lit; +} + +static bool *add_representative_equivalences (kissat *solver, + unsigned *repr) { + if (solver->inconsistent) + return 0; + bool *eliminate = kissat_calloc (solver, VARS, sizeof *eliminate); + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + const unsigned other = repr[lit]; + if (lit == other) + continue; + assert (other < lit); +#ifdef CHECKING_OR_PROVING + const unsigned not_lit = NOT (lit); + const unsigned not_other = NOT (other); + + CHECK_AND_ADD_BINARY (not_lit, other); + ADD_BINARY_TO_PROOF (not_lit, other); + + CHECK_AND_ADD_BINARY (lit, not_other); + ADD_BINARY_TO_PROOF (lit, not_other); +#endif + eliminate[idx] = true; + } + return eliminate; +} + +static void remove_representative_equivalences (kissat *solver, + unsigned *repr, + bool *eliminate) { + if (!solver->inconsistent) { + value *values = solver->values; + const bool incremental = GET_OPTION (incremental); + for (all_variables (idx)) { + if (!eliminate[idx]) + continue; + + assert (ACTIVE (idx)); + + const unsigned lit = LIT (idx); + const unsigned other = repr[lit]; + const unsigned not_lit = NOT (lit); + const unsigned not_other = NOT (other); + assert (other < lit); + assert (not_other < not_lit); + + REMOVE_CHECKER_BINARY (not_lit, other); + DELETE_BINARY_FROM_PROOF (not_lit, other); + + REMOVE_CHECKER_BINARY (lit, not_other); + DELETE_BINARY_FROM_PROOF (lit, not_other); + + INC (substituted); + kissat_mark_eliminated_variable (solver, idx); + const value other_value = values[other]; + if (incremental || other_value) { + if (other_value <= 0) + kissat_weaken_binary (solver, not_lit, other); + if (other_value >= 0) + kissat_weaken_binary (solver, lit, not_other); + } else { + kissat_weaken_binary (solver, not_lit, other); + kissat_weaken_unit (solver, lit); + } + } + } + if (eliminate) + kissat_dealloc (solver, eliminate, VARS, sizeof *eliminate); +} + +static void substitute_binaries (kissat *solver, unsigned *repr) { + if (solver->inconsistent) + return; + assert (sizeof (watch) == sizeof (unsigned)); + statches *delayed_watched = (statches *) &solver->delayed; + watches *all_watches = solver->watches; +#ifdef LOGGING + size_t removed = 0; + size_t substituted = 0; +#endif + unsigneds units; + INIT_STACK (units); + litwatches delayed_deleted; + INIT_STACK (delayed_deleted); +#ifdef CHECKING_OR_PROVING + litpairs delayed_removed; + INIT_STACK (delayed_removed); +#endif + for (all_literals (lit)) { + const unsigned repr_lit = repr[lit]; + const unsigned not_repr_lit = NOT (repr_lit); + assert (EMPTY_STACK (*delayed_watched)); + watches *watches = all_watches + lit; + watch *begin = BEGIN_WATCHES (*watches), *q = begin; + const watch *const end = END_WATCHES (*watches), *p = q; + while (p != end) { + const watch src = *p++; + if (!src.type.binary) + continue; + const unsigned other = src.binary.lit; + const unsigned repr_other = repr[other]; + LOGBINARY (lit, other, "substituting"); + const litwatch litwatch = {lit, src}; + if (repr_other == not_repr_lit) { + LOGBINARY (repr_other, repr_lit, "becomes tautological"); + if (lit < other) { +#ifdef LOGGING + removed++; +#endif + PUSH_STACK (delayed_deleted, litwatch); + } + } else if (repr_other == repr_lit) { + const unsigned unit = repr_lit; + LOG ("simplifies to unit %s", LOGLIT (unit)); + if (lit < other) { +#ifdef LOGGING + removed++; +#endif + PUSH_STACK (units, unit); + PUSH_STACK (delayed_deleted, litwatch); + } + } else { + watch dst = src; + dst.binary.lit = repr_other; + if (lit == repr_lit && other == repr_other) { + LOGBINARY (lit, other, "unchanged"); + *q++ = dst; + } else { + if (lit == repr_lit) { + LOGBINARY (repr_lit, repr_other, "substituted in place"); + *q++ = dst; + } else { + LOGBINARY (repr_lit, repr_other, "delayed substituted"); + PUSH_STACK (*delayed_watched, dst); + } + + if (lit < other) { +#ifdef LOGGING + substituted++; +#endif +#ifdef CHECKING_OR_PROVING + ADD_BINARY_TO_PROOF (repr_lit, repr_other); + CHECK_AND_ADD_BINARY (repr_lit, repr_other); + const litpair litpair = {{lit, other}}; + PUSH_STACK (delayed_removed, litpair); +#endif + } + } + } + } + SET_END_OF_WATCHES (*watches, q); + if (lit == repr_lit) + continue; + watches = all_watches + repr_lit; + for (all_stack (watch, watch, *delayed_watched)) + PUSH_WATCHES (*watches, watch); + CLEAR_STACK (*delayed_watched); + } + assign_and_propagate_units (solver, &units); + RELEASE_STACK (units); + for (all_stack (litwatch, litwatch, delayed_deleted)) { + const unsigned lit = litwatch.lit; + const watch watch = litwatch.watch; + assert (watch.type.binary); + const unsigned other = watch.binary.lit; + kissat_delete_binary (solver, lit, other); + } + RELEASE_STACK (delayed_deleted); +#ifdef CHECKING_OR_PROVING + for (all_stack (litpair, litpair, delayed_removed)) { + const unsigned lit = litpair.lits[0]; + const unsigned other = litpair.lits[1]; + DELETE_BINARY_FROM_PROOF (lit, other); + REMOVE_CHECKER_BINARY (lit, other); + } + RELEASE_STACK (delayed_removed); +#endif + LOG ("substituted %zu binary clauses", substituted); + LOG ("removed %zu binary clauses", removed); +} + +static void substitute_clauses (kissat *solver, unsigned *repr) { + if (solver->inconsistent) + return; + const value *const values = solver->values; + value *marks = solver->marks; +#ifdef LOGGING + size_t substituted = 0; + size_t removed = 0; +#endif + unsigneds units; + INIT_STACK (units); + references delayed_garbage; + INIT_STACK (delayed_garbage); + for (all_clauses (c)) { + if (c->garbage) + continue; + LOGCLS (c, "substituting"); + assert (EMPTY_STACK (solver->clause)); + bool shrink = false; + bool satisfied = false; + bool substitute = false; + bool tautological = false; + for (all_literals_in_clause (lit, c)) { + const value lit_value = values[lit]; + if (lit_value < 0) { + LOG ("dropping falsified %s", LOGLIT (lit)); + shrink = true; + continue; + } + if (lit_value > 0) { + LOGCLS (c, "satisfied by %s", LOGLIT (lit)); + satisfied = true; + break; + } + const unsigned repr_lit = repr[lit]; + const value repr_value = values[repr_lit]; + if (repr_value < 0) { + LOG ("dropping falsified substituted %s (was %s)", + LOGLIT (repr_lit), LOGLIT (lit)); + shrink = true; + continue; + } + if (repr_value > 0) { + LOGCLS (c, "satisfied by substituted %s (was %s)", + LOGLIT (repr_lit), LOGLIT (lit)); + satisfied = true; + break; + } + if (lit != repr_lit) { + assert (!values[repr_lit]); + LOG ("substituted literal %s (was %s)", LOGLIT (repr_lit), + LOGLIT (lit)); + substitute = true; + } else + LOG ("copying literal %s", LOGLIT (lit)); + if (marks[repr_lit]) { + shrink = true; + LOG ("skipping duplicated %s", LOGLIT (repr_lit)); + continue; + } + const unsigned not_repr_lit = NOT (repr_lit); + if (marks[not_repr_lit]) { + LOG ("substituted clause tautological " + "containing both %s and %s", + LOGLIT (not_repr_lit), LOGLIT (repr_lit)); + tautological = true; + break; + } + marks[repr_lit] = true; + PUSH_STACK (solver->clause, repr_lit); + } + if (satisfied || tautological) { + kissat_mark_clause_as_garbage (solver, c); +#ifdef LOGGING + removed++; +#endif + } else if (substitute || shrink) { + const unsigned size = SIZE_STACK (solver->clause); + if (!size) { + LOG ("simplifies to empty clause"); + + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + + solver->inconsistent = true; + break; + } else if (size == 1) { + assert (shrink); +#ifdef LOGGING + removed++; +#endif + const unsigned unit = PEEK_STACK (solver->clause, 0); + LOGCLS (c, "simplifies to unit %s", LOGLIT (unit)); + PUSH_STACK (units, unit); + const reference ref = kissat_reference_clause (solver, c); + PUSH_STACK (delayed_garbage, ref); + } else if (size == 2) { + assert (shrink); +#ifdef LOGGING + substituted++; +#endif + const unsigned first = PEEK_STACK (solver->clause, 0); + const unsigned second = PEEK_STACK (solver->clause, 1); + LOGCLS (c, "unsubstituted"); + LOGBINARY (first, second, "substituted"); + kissat_new_binary_clause (solver, first, second); + kissat_mark_clause_as_garbage (solver, c); + } else { +#ifdef LOGGING + substituted++; +#endif + LOGCLS (c, "unsubstituted"); + + const unsigned new_size = SIZE_STACK (solver->clause); + unsigned *new_lits = BEGIN_STACK (solver->clause); + + ADD_LITS_TO_PROOF (new_size, new_lits); + CHECK_AND_ADD_LITS (new_size, new_lits); + + DELETE_CLAUSE_FROM_PROOF (c); + REMOVE_CHECKER_CLAUSE (c); + + const unsigned old_size = c->size; + unsigned *old_lits = c->lits; + + assert (new_size <= old_size); + memcpy (old_lits, new_lits, new_size * sizeof *old_lits); + + assert (shrink == (new_size < old_size)); + if (new_size < old_size) { + c->size = new_size; + c->searched = 2; + if (!c->shrunken) { + c->shrunken = true; + assert (c->lits == old_lits); + old_lits[old_size - 1] = INVALID_LIT; + } + } + LOGCLS (c, "unsorted substituted"); + } + } else + LOGCLS (c, "unchanged"); + for (all_stack (unsigned, lit, solver->clause)) + marks[lit] = 0; + CLEAR_STACK (solver->clause); + } + assign_and_propagate_units (solver, &units); + RELEASE_STACK (units); + for (all_stack (reference, ref, delayed_garbage)) { + clause *c = kissat_dereference_clause (solver, ref); + kissat_mark_clause_as_garbage (solver, c); + } + RELEASE_STACK (delayed_garbage); + LOG ("substituted %zu large clauses", substituted); + LOG ("removed %zu substituted large clauses", removed); +} + +static bool substitute_round (kissat *solver, unsigned round) { + assert (!solver->inconsistent); + const unsigned active = solver->active; + size_t bytes = LITS * sizeof (unsigned); + unsigned *repr = kissat_malloc (solver, bytes); + memset (repr, 0xff, bytes); + determine_representatives (solver, repr); + bool *eliminate = add_representative_equivalences (solver, repr); + substitute_binaries (solver, repr); + substitute_clauses (solver, repr); + remove_representative_equivalences (solver, repr, eliminate); + kissat_dealloc (solver, repr, LITS, sizeof *repr); + unsigned removed = active - solver->active; + kissat_phase (solver, "substitute", GET (substitutions), + "round %u removed %u variables %.0f%%", round, removed, + kissat_percent (removed, active)); + kissat_check_statistics (solver); + REPORT (!removed, 'd'); +#ifdef QUIET + (void) round; +#endif + return !solver->inconsistent && removed; +} + +static void substitute_rounds (kissat *solver, bool complete) { + START (substitute); + INC (substitutions); + const unsigned maxrounds = GET_OPTION (substituterounds); + for (unsigned round = 1; round <= maxrounds; round++) { + const uint64_t before = solver->statistics.substitute_ticks; + if (!substitute_round (solver, round)) + break; + const uint64_t after = solver->statistics.substitute_ticks; + const uint64_t ticks = after - before; + if (!complete) { + const uint64_t reference = + solver->statistics.search_ticks - solver->last.ticks.probe; + const double fraction = GET_OPTION (substituteeffort) * 1e-3; + const uint64_t limit = fraction * reference; + if (ticks > limit) { + kissat_extremely_verbose ( + solver, + "last substitute round took %" PRIu64 " 'substitute_ticks' " + "> limit %" PRIu64 " = %g * %" PRIu64 " 'search_ticks'", + ticks, limit, fraction, reference); + break; + } + } + } + if (!solver->inconsistent) { + kissat_watch_large_clauses (solver); + LOG ("now all large clauses are watched after binary clauses"); + solver->large_clauses_watched_after_binary_clauses = true; + kissat_reset_propagate (solver); + assert (!solver->level); + (void) kissat_probing_propagate (solver, 0, true); + } + STOP (substitute); +} + +void kissat_substitute (kissat *solver, bool complete) { + if (solver->inconsistent) + return; + assert (solver->probing); + assert (solver->watching); + assert (!solver->level); + LOG ("assuming not all large clauses watched after binary clauses"); + solver->large_clauses_watched_after_binary_clauses = false; + if (!GET_OPTION (substitute)) + return; + if (TERMINATED (substitute_terminated_1)) + return; + substitute_rounds (solver, complete); +} diff --git a/src/sat/kissat/substitute.h b/src/sat/kissat/substitute.h new file mode 100644 index 000000000..63a464ee9 --- /dev/null +++ b/src/sat/kissat/substitute.h @@ -0,0 +1,10 @@ +#ifndef _substitute_h_INCLUDED +#define _substitute_h_INCLUDED + +#include + +struct kissat; + +void kissat_substitute (struct kissat *, bool complete); + +#endif diff --git a/src/sat/kissat/sweep.c b/src/sat/kissat/sweep.c new file mode 100644 index 000000000..866867112 --- /dev/null +++ b/src/sat/kissat/sweep.c @@ -0,0 +1,1724 @@ +#include "sweep.h" +#include "dense.h" +#include "inline.h" +#include "kitten.h" +#include "logging.h" +#include "print.h" +#include "promote.h" +#include "propdense.h" +#include "proprobe.h" +#include "random.h" +#include "rank.h" +#include "report.h" +#include "terminate.h" + +#include +#include + +struct sweeper { + kissat *solver; + unsigned *depths; + unsigned *reprs; + unsigned *next, *prev; + unsigned first, last; + unsigned encoded; + unsigned save; + unsigneds vars; + references refs; + unsigneds clause; + unsigneds backbone; + unsigneds partition; + unsigneds core[2]; + struct { + uint64_t ticks; + unsigned clauses, depth, vars; + } limit; +}; + +typedef struct sweeper sweeper; + +static int sweep_solve (sweeper *sweeper) { + kissat *solver = sweeper->solver; + kitten *kitten = solver->kitten; + kitten_randomize_phases (kitten); + INC (sweep_solved); + int res = kitten_solve (kitten); + if (res == 10) + INC (sweep_sat); + if (res == 20) + INC (sweep_unsat); + return res; +} + +static void set_kitten_ticks_limit (sweeper *sweeper) { + uint64_t remaining = 0; + kissat *solver = sweeper->solver; + if (solver->statistics.kitten_ticks < sweeper->limit.ticks) + remaining = sweeper->limit.ticks - solver->statistics.kitten_ticks; + LOG ("'kitten_ticks' remaining %" PRIu64, remaining); + kitten_set_ticks_limit (solver->kitten, remaining); +} + +static bool kitten_ticks_limit_hit (sweeper *sweeper, const char *when) { + kissat *solver = sweeper->solver; + if (solver->statistics.kitten_ticks >= sweeper->limit.ticks) { + LOG ("'kitten_ticks' limit of %" PRIu64 " ticks hit after %" PRIu64 + " ticks during %s", + sweeper->limit.ticks, solver->statistics.kitten_ticks, when); + return true; + } +#ifndef LOGGING + (void) when; +#endif + return false; +} + +static void init_sweeper (kissat *solver, sweeper *sweeper) { + sweeper->solver = solver; + sweeper->encoded = 0; + CALLOC (sweeper->depths, VARS); + NALLOC (sweeper->reprs, LITS); + for (all_literals (lit)) + sweeper->reprs[lit] = lit; + NALLOC (sweeper->prev, VARS); + memset (sweeper->prev, 0xff, VARS * sizeof *sweeper->prev); + NALLOC (sweeper->next, VARS); + memset (sweeper->next, 0xff, VARS * sizeof *sweeper->next); +#ifndef NDEBUG + for (all_variables (idx)) + assert (sweeper->prev[idx] == INVALID_IDX); + for (all_variables (idx)) + assert (sweeper->next[idx] == INVALID_IDX); +#endif + sweeper->first = sweeper->last = INVALID_IDX; + INIT_STACK (sweeper->vars); + INIT_STACK (sweeper->refs); + INIT_STACK (sweeper->clause); + INIT_STACK (sweeper->backbone); + INIT_STACK (sweeper->partition); + INIT_STACK (sweeper->core[0]); + INIT_STACK (sweeper->core[1]); + assert (!solver->kitten); + solver->kitten = kitten_embedded (solver); + kitten_track_antecedents (solver->kitten); + kissat_enter_dense_mode (solver, 0); + kissat_connect_irredundant_large_clauses (solver); + + unsigned completed = solver->statistics.sweep_completed; + const unsigned max_completed = 32; + if (completed > max_completed) + completed = max_completed; + + uint64_t vars_limit = GET_OPTION (sweepvars); + vars_limit <<= completed; + const unsigned max_vars_limit = GET_OPTION (sweepmaxvars); + if (vars_limit > max_vars_limit) + vars_limit = max_vars_limit; + sweeper->limit.vars = vars_limit; + kissat_extremely_verbose (solver, "sweeper variable limit %u", + sweeper->limit.vars); + + uint64_t depth_limit = solver->statistics.sweep_completed; + depth_limit += GET_OPTION (sweepdepth); + const unsigned max_depth = GET_OPTION (sweepmaxdepth); + if (depth_limit > max_depth) + depth_limit = max_depth; + sweeper->limit.depth = depth_limit; + kissat_extremely_verbose (solver, "sweeper depth limit %u", + sweeper->limit.depth); + + uint64_t clause_limit = GET_OPTION (sweepclauses); + clause_limit <<= completed; + const unsigned max_clause_limit = GET_OPTION (sweepmaxclauses); + if (clause_limit > max_clause_limit) + clause_limit = max_clause_limit; + sweeper->limit.clauses = clause_limit; + kissat_extremely_verbose (solver, "sweeper clause limit %u", + sweeper->limit.clauses); + + if (GET_OPTION (sweepcomplete)) { + sweeper->limit.ticks = UINT64_MAX; + kissat_extremely_verbose (solver, "unlimited sweeper ticks limit"); + } else { + SET_EFFORT_LIMIT (ticks_limit, sweep, kitten_ticks); + sweeper->limit.ticks = ticks_limit; + } + set_kitten_ticks_limit (sweeper); +} + +static unsigned release_sweeper (sweeper *sweeper) { + kissat *solver = sweeper->solver; + + unsigned merged = 0; + for (all_variables (idx)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + if (sweeper->reprs[lit] != lit) + merged++; + } + DEALLOC (sweeper->depths, VARS); + DEALLOC (sweeper->reprs, LITS); + DEALLOC (sweeper->prev, VARS); + DEALLOC (sweeper->next, VARS); + RELEASE_STACK (sweeper->vars); + RELEASE_STACK (sweeper->refs); + RELEASE_STACK (sweeper->clause); + RELEASE_STACK (sweeper->backbone); + RELEASE_STACK (sweeper->partition); + RELEASE_STACK (sweeper->core[0]); + RELEASE_STACK (sweeper->core[1]); + kitten_release (solver->kitten); + solver->kitten = 0; + kissat_resume_sparse_mode (solver, false, 0); + return merged; +} + +static void clear_sweeper (sweeper *sweeper) { + kissat *solver = sweeper->solver; + LOG ("clearing sweeping environment"); + kitten_clear (solver->kitten); + kitten_track_antecedents (solver->kitten); + for (all_stack (unsigned, idx, sweeper->vars)) { + assert (sweeper->depths[idx]); + sweeper->depths[idx] = 0; + } + CLEAR_STACK (sweeper->vars); + for (all_stack (reference, ref, sweeper->refs)) { + clause *c = kissat_dereference_clause (solver, ref); + assert (c->swept); + c->swept = false; + } + CLEAR_STACK (sweeper->refs); + CLEAR_STACK (sweeper->backbone); + CLEAR_STACK (sweeper->partition); + sweeper->encoded = 0; + set_kitten_ticks_limit (sweeper); +} + +static unsigned sweep_repr (sweeper *sweeper, unsigned lit) { + unsigned res; + { + unsigned prev = lit; + while ((res = sweeper->reprs[prev]) != prev) + prev = res; + } + if (res == lit) + return res; +#if defined(LOGGING) || !defined(NDEBUG) + kissat *solver = sweeper->solver; +#endif + LOG ("sweeping repr[%s] = %s", LOGLIT (lit), LOGLIT (res)); + { + const unsigned not_res = NOT (res); + unsigned next, prev = lit; + ; + while ((next = sweeper->reprs[prev]) != res) { + const unsigned not_prev = NOT (prev); + sweeper->reprs[not_prev] = not_res; + sweeper->reprs[prev] = res; + prev = next; + } + assert (sweeper->reprs[NOT (prev)] == not_res); + } + return res; +} + +static void add_literal_to_environment (sweeper *sweeper, unsigned depth, + unsigned lit) { + const unsigned repr = sweep_repr (sweeper, lit); + if (repr != lit) + return; + kissat *solver = sweeper->solver; + const unsigned idx = IDX (lit); + if (sweeper->depths[idx]) + return; + assert (depth < UINT_MAX); + sweeper->depths[idx] = depth + 1; + PUSH_STACK (sweeper->vars, idx); + LOG ("sweeping[%u] adding literal %s", depth, LOGLIT (lit)); +} + +static void sweep_clause (sweeper *sweeper, unsigned depth) { + kissat *solver = sweeper->solver; + assert (SIZE_STACK (sweeper->clause) > 1); + for (all_stack (unsigned, lit, sweeper->clause)) + add_literal_to_environment (sweeper, depth, lit); + kitten_clause (solver->kitten, SIZE_STACK (sweeper->clause), + BEGIN_STACK (sweeper->clause)); + CLEAR_STACK (sweeper->clause); + sweeper->encoded++; +} + +static void sweep_binary (sweeper *sweeper, unsigned depth, unsigned lit, + unsigned other) { + if (sweep_repr (sweeper, lit) != lit) + return; + if (sweep_repr (sweeper, other) != other) + return; + kissat *solver = sweeper->solver; + LOGBINARY (lit, other, "sweeping[%u]", depth); + value *values = solver->values; + assert (!values[lit]); + const value other_value = values[other]; + if (other_value > 0) { + LOGBINARY (lit, other, "skipping satisfied"); + return; + } + const unsigned *depths = sweeper->depths; + const unsigned other_idx = IDX (other); + const unsigned other_depth = depths[other_idx]; + const unsigned lit_idx = IDX (lit); + const unsigned lit_depth = depths[lit_idx]; + if (other_depth && other_depth < lit_depth) { + LOGBINARY (lit, other, "skipping depth %u copied", other_depth); + return; + } + assert (!other_value); + assert (EMPTY_STACK (sweeper->clause)); + PUSH_STACK (sweeper->clause, lit); + PUSH_STACK (sweeper->clause, other); + sweep_clause (sweeper, depth); +} + +static void sweep_reference (sweeper *sweeper, unsigned depth, + reference ref) { + assert (EMPTY_STACK (sweeper->clause)); + kissat *solver = sweeper->solver; + clause *c = kissat_dereference_clause (solver, ref); + if (c->swept) + return; + if (c->garbage) + return; + LOGCLS (c, "sweeping[%u]", depth); + value *values = solver->values; + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value > 0) { + kissat_mark_clause_as_garbage (solver, c); + CLEAR_STACK (sweeper->clause); + return; + } + if (value < 0) + continue; + PUSH_STACK (sweeper->clause, lit); + } + PUSH_STACK (sweeper->refs, ref); + c->swept = true; + sweep_clause (sweeper, depth); +} + +static void save_core_clause (void *state, bool learned, size_t size, + const unsigned *lits) { + sweeper *sweeper = state; + kissat *solver = sweeper->solver; + if (solver->inconsistent) + return; + const value *const values = solver->values; + unsigneds *core = sweeper->core + sweeper->save; + size_t saved = SIZE_STACK (*core); + const unsigned *end = lits + size; + unsigned non_false = 0; + for (const unsigned *p = lits; p != end; p++) { + const unsigned lit = *p; + const value value = values[lit]; + if (value > 0) { + LOGLITS (size, lits, "extracted %s satisfied lemma", LOGLIT (lit)); + RESIZE_STACK (*core, saved); + return; + } + PUSH_STACK (*core, lit); + if (value < 0) + continue; + if (!learned && ++non_false > 1) { + LOGLITS (size, lits, "ignoring extracted original clause"); + RESIZE_STACK (*core, saved); + return; + } + } +#ifdef LOGGING + unsigned *saved_lits = BEGIN_STACK (*core) + saved; + size_t saved_size = SIZE_STACK (*core) - saved; + LOGLITS (saved_size, saved_lits, "saved core[%u]", sweeper->save); +#endif + PUSH_STACK (*core, INVALID_LIT); +} + +static void add_core (sweeper *sweeper, unsigned core_idx) { + kissat *solver = sweeper->solver; + if (solver->inconsistent) + return; + LOG ("check and add extracted core[%u] lemmas to proof", core_idx); + assert (core_idx == 0 || core_idx == 1); + unsigneds *core = sweeper->core + core_idx; + const value *const values = solver->values; + + unsigned *q = BEGIN_STACK (*core); + const unsigned *const end_core = END_STACK (*core), *p = q; + + while (p != end_core) { + const unsigned *c = p; + while (*p != INVALID_LIT) + p++; +#ifdef LOGGING + size_t old_size = p - c; + LOGLITS (old_size, c, "simplifying extracted core[%u] lemma", core_idx); +#endif + bool satisfied = false; + unsigned unit = INVALID_LIT; + + unsigned *d = q; + + for (const unsigned *l = c; !satisfied && l != p; l++) { + const unsigned lit = *l; + const value value = values[lit]; + if (value > 0) { + satisfied = true; + break; + } + if (!value) + unit = *q++ = lit; + } + + size_t new_size = q - d; + p++; + + if (satisfied) { + q = d; + LOG ("not adding satisfied clause"); + continue; + } + + if (!new_size) { + LOG ("sweeping produced empty clause"); + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + solver->inconsistent = true; + CLEAR_STACK (*core); + return; + } + + if (new_size == 1) { + q = d; + assert (unit != INVALID_LIT); + LOG ("sweeping produced unit %s", LOGLIT (unit)); + CHECK_AND_ADD_UNIT (unit); + ADD_UNIT_TO_PROOF (unit); + kissat_assign_unit (solver, unit, "sweeping backbone reason"); + INC (sweep_units); + continue; + } + + *q++ = INVALID_LIT; + + assert (new_size > 1); + LOGLITS (new_size, d, "adding extracted core[%u] lemma", core_idx); + CHECK_AND_ADD_LITS (new_size, d); + ADD_LITS_TO_PROOF (new_size, d); + } + SET_END_OF_STACK (*core, q); +#ifndef LOGGING + (void) core_idx; +#endif +} + +static void save_core (sweeper *sweeper, unsigned core) { + kissat *solver = sweeper->solver; + LOG ("saving extracted core[%u] lemmas", core); + assert (core == 0 || core == 1); + assert (EMPTY_STACK (sweeper->core[core])); + sweeper->save = core; + kitten_compute_clausal_core (solver->kitten, 0); + kitten_traverse_core_clauses (solver->kitten, sweeper, save_core_clause); +} + +static void clear_core (sweeper *sweeper, unsigned core_idx) { + kissat *solver = sweeper->solver; + if (solver->inconsistent) + return; +#if defined(LOGGING) || !defined(NDEBUG) || !defined(NPROOFS) + assert (core_idx == 0 || core_idx == 1); + LOG ("clearing core[%u] lemmas", core_idx); +#endif + unsigneds *core = sweeper->core + core_idx; +#ifdef CHECKING_OR_PROVING + LOG ("deleting sub-solver core clauses"); + const unsigned *const end = END_STACK (*core); + const unsigned *c = BEGIN_STACK (*core); + for (const unsigned *p = c; c != end; c = ++p) { + while (*p != INVALID_LIT) + p++; + const size_t size = p - c; + assert (size > 1); + REMOVE_CHECKER_LITS (size, c); + DELETE_LITS_FROM_PROOF (size, c); + } +#endif + CLEAR_STACK (*core); +} + +static void save_add_clear_core (sweeper *sweeper) { + save_core (sweeper, 0); + add_core (sweeper, 0); + clear_core (sweeper, 0); +} + +#define LOGBACKBONE(MESSAGE) \ + LOGLITSET (SIZE_STACK (sweeper->backbone), \ + BEGIN_STACK (sweeper->backbone), MESSAGE) + +#define LOGPARTITION(MESSAGE) \ + LOGLITPART (SIZE_STACK (sweeper->partition), \ + BEGIN_STACK (sweeper->partition), MESSAGE) + +static void init_backbone_and_partition (sweeper *sweeper) { + kissat *solver = sweeper->solver; + LOG ("initializing backbone and equivalent literals candidates"); + for (all_stack (unsigned, idx, sweeper->vars)) { + if (!ACTIVE (idx)) + continue; + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + const signed char tmp = kitten_value (solver->kitten, lit); + const unsigned candidate = (tmp < 0) ? not_lit : lit; + LOG ("sweeping candidate %s", LOGLIT (candidate)); + PUSH_STACK (sweeper->backbone, candidate); + PUSH_STACK (sweeper->partition, candidate); + } + PUSH_STACK (sweeper->partition, INVALID_LIT); + + LOGBACKBONE ("initialized backbone candidates"); + LOGPARTITION ("initialized equivalence candidates"); +} + +static void sweep_empty_clause (sweeper *sweeper) { + assert (!sweeper->solver->inconsistent); + save_add_clear_core (sweeper); + assert (sweeper->solver->inconsistent); +} + +static void sweep_refine_partition (sweeper *sweeper) { + kissat *solver = sweeper->solver; + LOG ("refining partition"); + kitten *kitten = solver->kitten; + unsigneds old_partition = sweeper->partition; + unsigneds new_partition; + INIT_STACK (new_partition); + const value *const values = solver->values; + const unsigned *const old_begin = BEGIN_STACK (old_partition); + const unsigned *const old_end = END_STACK (old_partition); +#ifdef LOGGING + unsigned old_classes = 0; + unsigned new_classes = 0; +#endif + for (const unsigned *p = old_begin, *q; p != old_end; p = q + 1) { + unsigned assigned_true = 0, other; + for (q = p; (other = *q) != INVALID_LIT; q++) { + if (sweep_repr (sweeper, other) != other) + continue; + if (values[other]) + continue; + signed char value = kitten_value (kitten, other); + if (!value) + LOG ("dropping sub-solver unassigned %s", LOGLIT (other)); + else if (value > 0) { + PUSH_STACK (new_partition, other); + assigned_true++; + } + } +#ifdef LOGGING + LOG ("refining class %u of size %zu", old_classes, (size_t) (q - p)); + old_classes++; +#endif + if (assigned_true == 0) + LOG ("no positive literal in class"); + else if (assigned_true == 1) { +#ifdef LOGGING + other = +#else + (void) +#endif + POP_STACK (new_partition); + LOG ("dropping singleton class %s", LOGLIT (other)); + } else { + LOG ("%u positive literal in class", assigned_true); + PUSH_STACK (new_partition, INVALID_LIT); +#ifdef LOGGING + new_classes++; +#endif + } + + unsigned assigned_false = 0; + for (q = p; (other = *q) != INVALID_LIT; q++) { + if (sweep_repr (sweeper, other) != other) + continue; + if (values[other]) + continue; + signed char value = kitten_value (kitten, other); + if (value < 0) { + PUSH_STACK (new_partition, other); + assigned_false++; + } + } + + if (assigned_false == 0) + LOG ("no negative literal in class"); + else if (assigned_false == 1) { +#ifdef LOGGING + other = +#else + (void) +#endif + POP_STACK (new_partition); + LOG ("dropping singleton class %s", LOGLIT (other)); + } else { + LOG ("%u negative literal in class", assigned_false); + PUSH_STACK (new_partition, INVALID_LIT); +#ifdef LOGGING + new_classes++; +#endif + } + } + RELEASE_STACK (old_partition); + sweeper->partition = new_partition; + LOG ("refined %u classes into %u", old_classes, new_classes); + LOGPARTITION ("refined equivalence candidates"); +} + +static void sweep_refine_backbone (sweeper *sweeper) { + kissat *solver = sweeper->solver; + LOG ("refining backbone candidates"); + const unsigned *const end = END_STACK (sweeper->backbone); + unsigned *q = BEGIN_STACK (sweeper->backbone); + const value *const values = solver->values; + kitten *kitten = solver->kitten; + for (const unsigned *p = q; p != end; p++) { + const unsigned lit = *p; + if (values[lit]) + continue; + signed char value = kitten_value (kitten, lit); + if (!value) + LOG ("dropping sub-solver unassigned %s", LOGLIT (lit)); + else if (value >= 0) + *q++ = lit; + } + SET_END_OF_STACK (sweeper->backbone, q); + LOGBACKBONE ("refined backbone candidates"); +} + +static void sweep_refine (sweeper *sweeper) { +#ifdef LOGGING + kissat *solver = sweeper->solver; +#endif + if (EMPTY_STACK (sweeper->backbone)) + LOG ("no need to refine empty backbone candidates"); + else + sweep_refine_backbone (sweeper); + if (EMPTY_STACK (sweeper->partition)) + LOG ("no need to refine empty partition candidates"); + else + sweep_refine_partition (sweeper); +} + +static void flip_backbone_literals (struct sweeper *sweeper) { + struct kissat *solver = sweeper->solver; + const unsigned max_rounds = GET_OPTION (sweepfliprounds); + if (!max_rounds) + return; + assert (!EMPTY_STACK (sweeper->backbone)); + struct kitten *kitten = solver->kitten; + if (kitten_status (kitten) != 10) + return; +#ifdef LOGGING + unsigned total_flipped = 0; +#endif + unsigned flipped, round = 0; + do { + round++; + flipped = 0; + unsigned *begin = BEGIN_STACK (sweeper->backbone), *q = begin; + const unsigned *const end = END_STACK (sweeper->backbone), *p = q; + while (p != end) { + const unsigned lit = *p++; + INC (sweep_flip_backbone); + if (kitten_flip_literal (kitten, lit)) { + LOG ("flipping backbone candidate %s succeeded", LOGLIT (lit)); +#ifdef LOGGING + total_flipped++; +#endif + INC (sweep_flipped_backbone); + flipped++; + } else { + LOG ("flipping backbone candidate %s failed", LOGLIT (lit)); + *q++ = lit; + } + } + SET_END_OF_STACK (sweeper->backbone, q); + LOG ("flipped %u backbone candidates in round %u", flipped, round); + + if (TERMINATED (sweep_terminated_1)) + break; + if (solver->statistics.kitten_ticks > sweeper->limit.ticks) + break; + } while (flipped && round < max_rounds); + LOG ("flipped %u backbone candidates in total in %u rounds", + total_flipped, round); +} + +static bool sweep_backbone_candidate (sweeper *sweeper, unsigned lit) { + kissat *solver = sweeper->solver; + LOG ("trying backbone candidate %s", LOGLIT (lit)); + kitten *kitten = solver->kitten; + signed char value = kitten_fixed (kitten, lit); + if (value) { + INC (sweep_fixed_backbone); + LOG ("literal %s already fixed", LOGLIT (lit)); + assert (value > 0); + return false; + } + + INC (sweep_flip_backbone); + if (kitten_status (kitten) == 10 && kitten_flip_literal (kitten, lit)) { + INC (sweep_flipped_backbone); + LOG ("flipping %s succeeded", LOGLIT (lit)); + LOGBACKBONE ("refined backbone candidates"); + return false; + } + + LOG ("flipping %s failed", LOGLIT (lit)); + const unsigned not_lit = NOT (lit); + INC (sweep_solved_backbone); + kitten_assume (kitten, not_lit); + int res = sweep_solve (sweeper); + if (res == 10) { + LOG ("sweeping backbone candidate %s failed", LOGLIT (lit)); + sweep_refine (sweeper); + INC (sweep_sat_backbone); + return false; + } + + if (res == 20) { + LOG ("sweep unit %s", LOGLIT (lit)); + save_add_clear_core (sweeper); + INC (sweep_unsat_backbone); + return true; + } + + INC (sweep_unknown_backbone); + + LOG ("sweeping backbone candidate %s failed", LOGLIT (lit)); + return false; +} + +static void add_binary (kissat *solver, unsigned lit, unsigned other) { + kissat_new_binary_clause (solver, lit, other); +} + +static bool scheduled_variable (sweeper *sweeper, unsigned idx) { +#ifndef NDEBUG + kissat *const solver = sweeper->solver; + assert (VALID_INTERNAL_INDEX (idx)); +#endif + return sweeper->prev[idx] != INVALID_IDX || sweeper->first == idx; +} + +static void schedule_inner (sweeper *sweeper, unsigned idx) { + kissat *const solver = sweeper->solver; + assert (VALID_INTERNAL_INDEX (idx)); + if (!ACTIVE (idx)) + return; + const unsigned next = sweeper->next[idx]; + if (next != INVALID_IDX) { + LOG ("rescheduling inner %s as last", LOGVAR (idx)); + const unsigned prev = sweeper->prev[idx]; + assert (sweeper->prev[next] == idx); + sweeper->prev[next] = prev; + if (prev == INVALID_IDX) { + assert (sweeper->first == idx); + sweeper->first = next; + } else { + assert (sweeper->next[prev] == idx); + sweeper->next[prev] = next; + } + const unsigned last = sweeper->last; + if (last == INVALID_IDX) { + assert (sweeper->first == INVALID_IDX); + sweeper->first = idx; + } else { + assert (sweeper->next[last] == INVALID_IDX); + sweeper->next[last] = idx; + } + sweeper->prev[idx] = last; + sweeper->next[idx] = INVALID_IDX; + sweeper->last = idx; + } else if (sweeper->last != idx) { + LOG ("scheduling inner %s as last", LOGVAR (idx)); + const unsigned last = sweeper->last; + if (last == INVALID_IDX) { + assert (sweeper->first == INVALID_IDX); + sweeper->first = idx; + } else { + assert (sweeper->next[last] == INVALID_IDX); + sweeper->next[last] = idx; + } + assert (sweeper->next[idx] == INVALID_IDX); + sweeper->prev[idx] = last; + sweeper->last = idx; + } else + LOG ("keeping inner %s scheduled as last", LOGVAR (idx)); +} + +static void schedule_outer (sweeper *sweeper, unsigned idx) { +#if !defined(NDEBUG) || defined(LOGGING) + kissat *const solver = sweeper->solver; +#endif + assert (VALID_INTERNAL_INDEX (idx)); + assert (!scheduled_variable (sweeper, idx)); + assert (ACTIVE (idx)); + const unsigned first = sweeper->first; + if (first == INVALID_IDX) { + assert (sweeper->last == INVALID_IDX); + sweeper->last = idx; + } else { + assert (sweeper->prev[first] == INVALID_IDX); + sweeper->prev[first] = idx; + } + assert (sweeper->prev[idx] == INVALID_IDX); + sweeper->next[idx] = first; + sweeper->first = idx; + LOG ("scheduling outer %s as first", LOGVAR (idx)); +} + +static unsigned next_scheduled (sweeper *sweeper) { +#if !defined(NDEBUG) || defined(LOGGING) + kissat *const solver = sweeper->solver; +#endif + unsigned res = sweeper->last; + if (res == INVALID_IDX) { + LOG ("no more scheduled variables left"); + return INVALID_IDX; + } + assert (VALID_INTERNAL_INDEX (res)); + LOG ("dequeuing next scheduled %s", LOGVAR (res)); + const unsigned prev = sweeper->prev[res]; + assert (sweeper->next[res] == INVALID_IDX); + sweeper->prev[res] = INVALID_IDX; + if (prev == INVALID_IDX) { + assert (sweeper->first == res); + sweeper->first = INVALID_IDX; + } else { + assert (sweeper->next[prev] == res); + sweeper->next[prev] = INVALID_IDX; + } + sweeper->last = prev; + return res; +} + +#define all_scheduled(IDX) \ + unsigned IDX = sweeper->first, NEXT_##IDX; \ + IDX != INVALID_IDX && (NEXT_##IDX = sweeper->next[IDX], true); \ + IDX = NEXT_##IDX + +static void substitute_connected_clauses (sweeper *sweeper, unsigned lit, + unsigned repr) { + kissat *solver = sweeper->solver; + if (solver->inconsistent) + return; + value *const values = solver->values; + if (values[lit]) + return; + if (values[repr]) + return; + LOG ("substituting %s with %s in all irredundant clauses", LOGLIT (lit), + LOGLIT (repr)); + + assert (lit != repr); + assert (lit != NOT (repr)); + +#ifdef CHECKING_OR_PROVING + const bool checking_or_proving = kissat_checking_or_proving (solver); + assert (EMPTY_STACK (solver->added)); + assert (EMPTY_STACK (solver->removed)); +#endif + + unsigneds *const delayed = &solver->delayed; + assert (EMPTY_STACK (*delayed)); + + { + watches *lit_watches = &WATCHES (lit); + watch *const begin_watches = BEGIN_WATCHES (*lit_watches); + const watch *const end_watches = END_WATCHES (*lit_watches); + + watch *q = begin_watches; + const watch *p = q; + + while (p != end_watches) { + const watch head = *q++ = *p++; + if (head.type.binary) { + const unsigned other = head.binary.lit; + const value other_value = values[other]; + if (other == NOT (repr)) + continue; + if (other_value < 0) + break; + if (other_value > 0) + continue; + if (other == repr) { + CHECK_AND_ADD_UNIT (lit); + ADD_UNIT_TO_PROOF (lit); + kissat_assign_unit (solver, lit, "substituted binary clause"); + INC (sweep_units); + break; + } + CHECK_AND_ADD_BINARY (repr, other); + ADD_BINARY_TO_PROOF (repr, other); + REMOVE_CHECKER_BINARY (lit, other); + DELETE_BINARY_FROM_PROOF (lit, other); + PUSH_STACK (*delayed, head.raw); + watch src = {.raw = head.raw}; + watch dst = {.raw = head.raw}; + src.binary.lit = lit; + dst.binary.lit = repr; + watches *other_watches = &WATCHES (other); + kissat_substitute_large_watch (solver, other_watches, src, dst); + q--; + } else { + const reference ref = head.large.ref; + assert (EMPTY_STACK (sweeper->clause)); + clause *c = kissat_dereference_clause (solver, ref); + if (c->garbage) + continue; + + bool satisfied = false; + bool repr_already_watched = false; + const unsigned not_repr = NOT (repr); +#ifndef NDEBUG + bool found = false; +#endif + for (all_literals_in_clause (other, c)) { + if (other == lit) { +#ifndef NDEBUG + assert (!found); + found = true; +#endif + PUSH_STACK (solver->clause, repr); + continue; + } + assert (other != NOT (lit)); + if (other == repr) { + assert (!repr_already_watched); + repr_already_watched = true; + continue; + } + if (other == not_repr) { + satisfied = true; + break; + } + const value tmp = values[other]; + if (tmp < 0) + continue; + if (tmp > 0) { + satisfied = true; + break; + } + PUSH_STACK (solver->clause, other); + } + + if (satisfied) { + CLEAR_STACK (solver->clause); + kissat_mark_clause_as_garbage (solver, c); + continue; + } + assert (found); + + const unsigned new_size = SIZE_STACK (solver->clause); + + if (new_size == 0) { + LOGCLS (c, "substituted empty clause"); + assert (!solver->inconsistent); + solver->inconsistent = true; + CHECK_AND_ADD_EMPTY (); + ADD_EMPTY_TO_PROOF (); + break; + } + + if (new_size == 1) { + LOGCLS (c, "reduces to unit"); + const unsigned unit = POP_STACK (solver->clause); + CHECK_AND_ADD_UNIT (unit); + ADD_UNIT_TO_PROOF (unit); + kissat_assign_unit (solver, unit, "substituted large clause"); + INC (sweep_units); + break; + } + + CHECK_AND_ADD_STACK (solver->clause); + ADD_STACK_TO_PROOF (solver->clause); + REMOVE_CHECKER_CLAUSE (c); + DELETE_CLAUSE_FROM_PROOF (c); + + if (!c->redundant) + kissat_mark_added_literals (solver, new_size, + BEGIN_STACK (solver->clause)); + + if (new_size == 2) { + const unsigned second = POP_STACK (solver->clause); + const unsigned first = POP_STACK (solver->clause); + LOGCLS (c, "reduces to binary clause %s %s", LOGLIT (first), + LOGLIT (second)); + assert (first == repr || second == repr); + const unsigned other = first ^ second ^ repr; + const watch src = {.raw = head.raw}; + watch dst = kissat_binary_watch (repr); + watches *other_watches = &WATCHES (other); + kissat_substitute_large_watch (solver, other_watches, src, dst); + assert (solver->statistics.clauses_irredundant); + solver->statistics.clauses_irredundant--; + assert (solver->statistics.clauses_binary < UINT64_MAX); + solver->statistics.clauses_binary++; + dst.binary.lit = other; + PUSH_STACK (*delayed, dst.raw); + const size_t bytes = kissat_actual_bytes_of_clause (c); + ADD (arena_garbage, bytes); + c->garbage = true; + q--; + continue; + } + + assert (2 < new_size); + const unsigned old_size = c->size; + assert (new_size <= old_size); + + const unsigned *const begin = BEGIN_STACK (solver->clause); + const unsigned *const end = END_STACK (solver->clause); + + unsigned *lits = c->lits; + unsigned *q = lits; + + for (const unsigned *p = begin; p != end; p++) { + const unsigned other = *p; + *q++ = other; + } + + if (new_size < old_size) { + c->size = new_size; + c->searched = 2; + if (c->redundant && c->glue >= new_size) + kissat_promote_clause (solver, c, new_size - 1); + if (!c->shrunken) { + c->shrunken = true; + lits[old_size - 1] = INVALID_LIT; + } + } + + LOGCLS (c, "substituted"); + + if (!repr_already_watched) + PUSH_STACK (*delayed, head.raw); + CLEAR_STACK (solver->clause); + q--; + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_WATCHES (*lit_watches, q); + } + { + const unsigned *const begin_delayed = BEGIN_STACK (*delayed); + const unsigned *const end_delayed = END_STACK (*delayed); + for (const unsigned *p = begin_delayed; p != end_delayed; p++) { + const watch head = {.raw = *p}; + watches *repr_watches = &WATCHES (repr); + PUSH_WATCHES (*repr_watches, head); + } + + CLEAR_STACK (*delayed); + } + +#ifdef CHECKING_OR_PROVING + if (checking_or_proving) { + CLEAR_STACK (solver->added); + CLEAR_STACK (solver->removed); + } +#endif +} + +static void sweep_remove (sweeper *sweeper, unsigned lit) { + kissat *solver = sweeper->solver; + assert (sweeper->reprs[lit] != lit); + unsigneds *partition = &sweeper->partition; + unsigned *const begin_partition = BEGIN_STACK (*partition), *p; + const unsigned *const end_partition = END_STACK (*partition); + for (p = begin_partition; *p != lit; p++) + assert (p + 1 != end_partition); + unsigned *begin_class = p; + while (begin_class != begin_partition && begin_class[-1] != INVALID_LIT) + begin_class--; + const unsigned *end_class = p; + while (*end_class != INVALID_LIT) + end_class++; + const unsigned size = end_class - begin_class; + LOG ("removing non-representative %s from equivalence class of size %u", + LOGLIT (lit), size); + assert (size > 1); + unsigned *q = begin_class; + if (size == 2) { + LOG ("completely squashing equivalence class of %s", LOGLIT (lit)); + for (const unsigned *r = end_class + 1; r != end_partition; r++) + *q++ = *r; + } else { + for (const unsigned *r = begin_class; r != end_partition; r++) + if (r != p) + *q++ = *r; + } + SET_END_OF_STACK (*partition, q); +#ifndef LOGGING + (void) solver; +#endif +} + +static void flip_partition_literals (struct sweeper *sweeper) { + struct kissat *solver = sweeper->solver; + const unsigned max_rounds = GET_OPTION (sweepfliprounds); + if (!max_rounds) + return; + assert (!EMPTY_STACK (sweeper->partition)); + struct kitten *kitten = solver->kitten; + if (kitten_status (kitten) != 10) + return; +#ifdef LOGGING + unsigned total_flipped = 0; +#endif + unsigned flipped, round = 0; + do { + round++; + flipped = 0; + unsigned *begin = BEGIN_STACK (sweeper->partition), *dst = begin; + const unsigned *const end = END_STACK (sweeper->partition), *src = dst; + while (src != end) { + const unsigned *end_src = src; + while (assert (end_src != end), *end_src != INVALID_LIT) + end_src++; + unsigned size = end_src - src; + assert (size > 1); + unsigned *q = dst; + for (const unsigned *p = src; p != end_src; p++) { + const unsigned lit = *p; + if (kitten_flip_literal (kitten, lit)) { + LOG ("flipping equivalence candidate %s succeeded", LOGLIT (lit)); +#ifdef LOGGING + total_flipped++; +#endif + flipped++; + if (--size < 2) + break; + } else { + LOG ("flipping equivalence candidate %s failed", LOGLIT (lit)); + *q++ = lit; + } + } + if (size > 1) { + *q++ = INVALID_LIT; + dst = q; + } + src = end_src + 1; + } + SET_END_OF_STACK (sweeper->partition, dst); + LOG ("flipped %u equivalence candidates in round %u", flipped, round); + + if (TERMINATED (sweep_terminated_2)) + break; + if (solver->statistics.kitten_ticks > sweeper->limit.ticks) + break; + } while (flipped && round < max_rounds); + LOG ("flipped %u equivalence candidates in total in %u rounds", + total_flipped, round); +} + +static bool sweep_equivalence_candidates (sweeper *sweeper, unsigned lit, + unsigned other) { + kissat *solver = sweeper->solver; + LOG ("trying equivalence candidates %s = %s", LOGLIT (lit), + LOGLIT (other)); + const unsigned not_other = NOT (other); + const unsigned not_lit = NOT (lit); + kitten *kitten = solver->kitten; + const unsigned *const begin = BEGIN_STACK (sweeper->partition); + unsigned *const end = END_STACK (sweeper->partition); + assert (begin + 3 <= end); + assert (end[-3] == lit); + assert (end[-2] == other); + const unsigned third = (end - begin == 3) ? INVALID_LIT : end[-4]; + const int status = kitten_status (kitten); + if (status == 10 && kitten_flip_literal (kitten, lit)) { + INC (sweep_flip_equivalences); + INC (sweep_flipped_equivalences); + LOG ("flipping %s succeeded", LOGLIT (lit)); + if (third == INVALID_LIT) { + LOG ("squashing equivalence class of %s", LOGLIT (lit)); + SET_END_OF_STACK (sweeper->partition, end - 3); + } else { + LOG ("removing %s from equivalence class of %s", LOGLIT (lit), + LOGLIT (other)); + end[-3] = other; + end[-2] = INVALID_LIT; + SET_END_OF_STACK (sweeper->partition, end - 1); + } + LOGPARTITION ("refined equivalence candidates"); + return false; + } else if (status == 10 && kitten_flip_literal (kitten, other)) { + ADD (sweep_flip_equivalences, 2); + INC (sweep_flipped_equivalences); + LOG ("flipping %s succeeded", LOGLIT (other)); + if (third == INVALID_LIT) { + LOG ("squashing equivalence class of %s", LOGLIT (lit)); + SET_END_OF_STACK (sweeper->partition, end - 3); + } else { + LOG ("removing %s from equivalence class of %s", LOGLIT (other), + LOGLIT (lit)); + end[-2] = INVALID_LIT; + SET_END_OF_STACK (sweeper->partition, end - 1); + } + LOGPARTITION ("refined equivalence candidates"); + return false; + } + if (status == 10) + ADD (sweep_flip_equivalences, 2); + LOG ("flipping %s and %s both failed", LOGLIT (lit), LOGLIT (other)); + kitten_assume (kitten, not_lit); + kitten_assume (kitten, other); + INC (sweep_solved_equivalences); + int res = sweep_solve (sweeper); + if (res == 10) { + INC (sweep_sat_equivalences); + LOG ("first sweeping implication %s -> %s failed", LOGLIT (other), + LOGLIT (lit)); + sweep_refine (sweeper); + } else if (!res) { + INC (sweep_unknown_equivalences); + LOG ("first sweeping implication %s -> %s hit ticks limit", + LOGLIT (other), LOGLIT (lit)); + } + + if (res != 20) + return false; + + INC (sweep_unsat_equivalences); + LOG ("first sweeping implication %s -> %s succeeded", LOGLIT (other), + LOGLIT (lit)); + + save_core (sweeper, 0); + + kitten_assume (kitten, lit); + kitten_assume (kitten, not_other); + res = sweep_solve (sweeper); + INC (sweep_solved_equivalences); + if (res == 10) { + INC (sweep_sat_equivalences); + LOG ("second sweeping implication %s <- %s failed", LOGLIT (other), + LOGLIT (lit)); + sweep_refine (sweeper); + } else if (!res) { + INC (sweep_unknown_equivalences); + LOG ("second sweeping implication %s <- %s hit ticks limit", + LOGLIT (other), LOGLIT (lit)); + } + + if (res != 20) { + CLEAR_STACK (sweeper->core[0]); + return false; + } + + INC (sweep_unsat_equivalences); + LOG ("second sweeping implication %s <- %s succeeded too", LOGLIT (other), + LOGLIT (lit)); + + save_core (sweeper, 1); + + LOG ("sweep equivalence %s = %s", LOGLIT (lit), LOGLIT (other)); + INC (sweep_equivalences); + + add_core (sweeper, 0); + add_binary (solver, lit, not_other); + clear_core (sweeper, 0); + + add_core (sweeper, 1); + add_binary (solver, not_lit, other); + clear_core (sweeper, 1); + + unsigned repr; + if (lit < other) { + repr = sweeper->reprs[other] = lit; + sweeper->reprs[not_other] = not_lit; + substitute_connected_clauses (sweeper, other, lit); + substitute_connected_clauses (sweeper, not_other, not_lit); + sweep_remove (sweeper, other); + } else { + repr = sweeper->reprs[lit] = other; + sweeper->reprs[not_lit] = not_other; + substitute_connected_clauses (sweeper, lit, other); + substitute_connected_clauses (sweeper, not_lit, not_other); + sweep_remove (sweeper, lit); + } + + const unsigned repr_idx = IDX (repr); + schedule_inner (sweeper, repr_idx); + + return true; +} + +static const char *sweep_variable (sweeper *sweeper, unsigned idx) { + kissat *solver = sweeper->solver; + assert (!solver->inconsistent); + if (!ACTIVE (idx)) + return "inactive variable"; + const unsigned start = LIT (idx); + if (sweeper->reprs[start] != start) + return "non-representative variable"; + assert (EMPTY_STACK (sweeper->vars)); + assert (EMPTY_STACK (sweeper->refs)); + assert (EMPTY_STACK (sweeper->backbone)); + assert (EMPTY_STACK (sweeper->partition)); + assert (!sweeper->encoded); + + INC (sweep_variables); + + LOG ("sweeping %s", LOGVAR (idx)); + assert (!VALUE (start)); + LOG ("starting sweeping[0]"); + add_literal_to_environment (sweeper, 0, start); + LOG ("finished sweeping[0]"); + LOG ("starting sweeping[1]"); + + bool limit_reached = false; + size_t expand = 0, next = 1; + bool success = false; + unsigned depth = 1; + + while (!limit_reached) { + if (sweeper->encoded >= sweeper->limit.clauses) { + LOG ("environment clause limit reached"); + limit_reached = true; + break; + } + if (expand == next) { + LOG ("finished sweeping[%u]", depth); + if (depth >= sweeper->limit.depth) { + LOG ("environment depth limit reached"); + break; + } + next = SIZE_STACK (sweeper->vars); + if (expand == next) { + LOG ("completely copied all clauses"); + break; + } + depth++; + LOG ("starting sweeping[%u]", depth); + } + const unsigned choices = next - expand; + if (GET_OPTION (sweeprand) && choices > 1) { + const unsigned swap = + kissat_pick_random (&solver->random, 0, choices); + if (swap) { + unsigned *vars = sweeper->vars.begin; + SWAP (unsigned, vars[expand], vars[expand + swap]); + } + } + const unsigned idx = PEEK_STACK (sweeper->vars, expand); + LOG ("traversing and adding clauses of %s", LOGVAR (idx)); + for (unsigned sign = 0; sign < 2; sign++) { + const unsigned lit = LIT (idx) + sign; + watches *watches = &WATCHES (lit); + for (all_binary_large_watches (watch, *watches)) { + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + sweep_binary (sweeper, depth, lit, other); + } else { + reference ref = watch.large.ref; + sweep_reference (sweeper, depth, ref); + } + if (SIZE_STACK (sweeper->vars) >= sweeper->limit.vars) { + LOG ("environment variable limit reached"); + limit_reached = true; + break; + } + } + if (limit_reached) + break; + } + expand++; + } + ADD (sweep_depth, depth); + ADD (sweep_clauses, sweeper->encoded); + ADD (sweep_environment, SIZE_STACK (sweeper->vars)); + kissat_extremely_verbose (solver, + "sweeping variable %d environment of " + "%zu variables %u clauses depth %u", + kissat_export_literal (solver, LIT (idx)), + SIZE_STACK (sweeper->vars), sweeper->encoded, + depth); + int res = sweep_solve (sweeper); + LOG ("sub-solver returns '%d'", res); + if (res == 10) { + init_backbone_and_partition (sweeper); +#ifndef QUIET + uint64_t units = solver->statistics.sweep_units; + uint64_t solved = solver->statistics.sweep_solved; +#endif + START (sweepbackbone); + while (!EMPTY_STACK (sweeper->backbone)) { + if (solver->inconsistent || TERMINATED (sweep_terminated_3) || + kitten_ticks_limit_hit (sweeper, "backbone refinement")) { + limit_reached = true; + STOP_SWEEP_BACKBONE: + STOP (sweepbackbone); + goto DONE; + } + flip_backbone_literals (sweeper); + if (TERMINATED (sweep_terminated_4) || + kitten_ticks_limit_hit (sweeper, "backbone refinement")) { + limit_reached = true; + goto STOP_SWEEP_BACKBONE; + } + if (EMPTY_STACK (sweeper->backbone)) + break; + const unsigned lit = POP_STACK (sweeper->backbone); + if (!ACTIVE (IDX (lit))) + continue; + if (sweep_backbone_candidate (sweeper, lit)) + success = true; + } + STOP (sweepbackbone); +#ifndef QUIET + units = solver->statistics.sweep_units - units; + solved = solver->statistics.sweep_solved - solved; + kissat_extremely_verbose ( + solver, + "complete swept variable %d backbone with %" PRIu64 + " units in %" PRIu64 " solver calls", + kissat_export_literal (solver, LIT (idx)), units, solved); +#endif + assert (EMPTY_STACK (sweeper->backbone)); +#ifndef QUIET + uint64_t equivalences = solver->statistics.sweep_equivalences; + solved = solver->statistics.sweep_solved; +#endif + START (sweepequivalences); + while (!EMPTY_STACK (sweeper->partition)) { + if (solver->inconsistent || TERMINATED (sweep_terminated_5) || + kitten_ticks_limit_hit (sweeper, "partition refinement")) { + limit_reached = true; + STOP_SWEEP_EQUIVALENCES: + STOP (sweepequivalences); + goto DONE; + } + flip_partition_literals (sweeper); + if (TERMINATED (sweep_terminated_6) || + kitten_ticks_limit_hit (sweeper, "backbone refinement")) { + limit_reached = true; + goto STOP_SWEEP_EQUIVALENCES; + } + if (EMPTY_STACK (sweeper->partition)) + break; + if (SIZE_STACK (sweeper->partition) > 2) { + const unsigned *end = END_STACK (sweeper->partition); + assert (end[-1] == INVALID_LIT); + unsigned lit = end[-3]; + unsigned other = end[-2]; + if (sweep_equivalence_candidates (sweeper, lit, other)) + success = true; + } else + CLEAR_STACK (sweeper->partition); + } + STOP (sweepequivalences); +#ifndef QUIET + equivalences = solver->statistics.sweep_equivalences - equivalences; + solved = solver->statistics.sweep_solved - solved; + if (equivalences) + kissat_extremely_verbose ( + solver, + "complete swept variable %d partition with %" PRIu64 + " equivalences in %" PRIu64 " solver calls", + kissat_export_literal (solver, LIT (idx)), equivalences, solved); +#endif + } else if (res == 20) + sweep_empty_clause (sweeper); + +DONE: + clear_sweeper (sweeper); + + if (!solver->inconsistent && !kissat_propagated (solver)) + (void) kissat_dense_propagate (solver); + + if (success && limit_reached) + return "successfully despite reaching limit"; + if (!success && !limit_reached) + return "unsuccessfully without reaching limit"; + else if (success && !limit_reached) + return "successfully without reaching limit"; + assert (!success && limit_reached); + return "unsuccessfully and reached limit"; +} + +typedef struct sweep_candidate sweep_candidate; + +struct sweep_candidate { + unsigned rank; + unsigned idx; +}; + +// clang-format off + +typedef STACK(sweep_candidate) sweep_candidates; + +// clang-format on + +#define RANK_SWEEP_CANDIDATE(CAND) (CAND).rank + +static bool scheduable_variable (sweeper *sweeper, unsigned idx, + size_t *occ_ptr) { + kissat *solver = sweeper->solver; + const unsigned lit = LIT (idx); + const size_t pos = SIZE_WATCHES (WATCHES (lit)); + if (!pos) + return false; + const unsigned max_occurrences = sweeper->limit.clauses; + if (pos > max_occurrences) + return false; + const unsigned not_lit = NOT (lit); + const size_t neg = SIZE_WATCHES (WATCHES (not_lit)); + if (!neg) + return false; + if (neg > max_occurrences) + return false; + *occ_ptr = pos + neg; + return true; +} + +static unsigned schedule_all_other_not_scheduled_yet (sweeper *sweeper) { + kissat *solver = sweeper->solver; + sweep_candidates fresh; + INIT_STACK (fresh); + flags *const flags = solver->flags; + const bool incomplete = solver->sweep_incomplete; + for (all_variables (idx)) { + struct flags *const f = flags + idx; + if (!f->active) + continue; + if (incomplete && !f->sweep) + continue; + if (scheduled_variable (sweeper, idx)) + continue; + size_t occ; + if (!scheduable_variable (sweeper, idx, &occ)) { + FLAGS (idx)->sweep = false; + continue; + } + sweep_candidate cand; + cand.rank = occ; + cand.idx = idx; + PUSH_STACK (fresh, cand); + } + const size_t size = SIZE_STACK (fresh); + assert (size <= UINT_MAX); + RADIX_STACK (sweep_candidate, unsigned, fresh, RANK_SWEEP_CANDIDATE); + for (all_stack (sweep_candidate, cand, fresh)) + schedule_outer (sweeper, cand.idx); + RELEASE_STACK (fresh); + return size; +} + +static unsigned reschedule_previously_remaining (sweeper *sweeper) { + kissat *solver = sweeper->solver; + flags *flags = solver->flags; + unsigned rescheduled = 0; + unsigneds *remaining = &solver->sweep_schedule; + for (all_stack (unsigned, idx, *remaining)) { + struct flags *f = flags + idx; + if (!f->active) + continue; + if (scheduled_variable (sweeper, idx)) + continue; + size_t occ; + if (!scheduable_variable (sweeper, idx, &occ)) { + f->sweep = false; + continue; + } + schedule_inner (sweeper, idx); + rescheduled++; + } + RELEASE_STACK (*remaining); + return rescheduled; +} + +static unsigned incomplete_variables (sweeper *sweeper) { + kissat *solver = sweeper->solver; + flags *flags = solver->flags; + unsigned res = 0; + for (all_variables (idx)) { + struct flags *f = flags + idx; + if (!f->active) + continue; + if (f->sweep) + res++; + } + return res; +} + +static void mark_incomplete (sweeper *sweeper) { + kissat *solver = sweeper->solver; + flags *flags = solver->flags; + unsigned marked = 0; + for (all_scheduled (idx)) + if (!flags[idx].sweep) { + flags[idx].sweep = true; + marked++; + } + solver->sweep_incomplete = true; +#ifndef QUIET + kissat_extremely_verbose ( + solver, "marked %u scheduled sweeping variables as incomplete", + marked); +#else + (void) marked; +#endif +} + +static unsigned schedule_sweeping (sweeper *sweeper) { + const unsigned rescheduled = reschedule_previously_remaining (sweeper); + const unsigned fresh = schedule_all_other_not_scheduled_yet (sweeper); + const unsigned scheduled = fresh + rescheduled; + const unsigned incomplete = incomplete_variables (sweeper); + kissat *solver = sweeper->solver; +#ifndef QUIET + kissat_phase (solver, "sweep", GET (sweep), + "scheduled %u variables %.0f%% " + "(%u rescheduled %.0f%%, %u incomplete %.0f%%)", + scheduled, + kissat_percent (scheduled, sweeper->solver->active), + rescheduled, kissat_percent (rescheduled, scheduled), + incomplete, kissat_percent (incomplete, scheduled)); +#endif + if (incomplete) + assert (solver->sweep_incomplete); + else { + if (solver->sweep_incomplete) + INC (sweep_completed); + mark_incomplete (sweeper); + } + return scheduled; +} + +static void unschedule_sweeping (sweeper *sweeper, unsigned swept, + unsigned scheduled) { + kissat *solver = sweeper->solver; +#ifdef QUIET + (void) scheduled, (void) swept; +#endif + assert (EMPTY_STACK (solver->sweep_schedule)); + assert (solver->sweep_incomplete); + flags *flags = solver->flags; + for (all_scheduled (idx)) + if (flags[idx].active) { + PUSH_STACK (solver->sweep_schedule, idx); + LOG ("untried scheduled %s", LOGVAR (idx)); + } +#ifndef QUIET + const unsigned retained = SIZE_STACK (solver->sweep_schedule); + kissat_extremely_verbose ( + solver, "retained %u variables %.0f%% to be swept next time", + retained, kissat_percent (retained, solver->active)); +#endif + const unsigned incomplete = incomplete_variables (sweeper); + if (incomplete) + kissat_extremely_verbose ( + solver, "need to sweep %u more variables %.0f%% for completion", + incomplete, kissat_percent (incomplete, solver->active)); + else { + kissat_extremely_verbose (solver, + "no more variables needed to complete sweep"); + solver->sweep_incomplete = false; + INC (sweep_completed); + } + kissat_phase (solver, "sweep", GET (sweep), + "swept %u variables (%u remain %.0f%%)", swept, incomplete, + kissat_percent (incomplete, scheduled)); +} + +bool kissat_sweep (kissat *solver) { + if (!GET_OPTION (sweep)) + return false; + if (solver->inconsistent) + return false; + if (TERMINATED (sweep_terminated_7)) + return false; + if (DELAYING (sweep)) + return false; + assert (!solver->level); + assert (!solver->unflushed); + START (sweep); + INC (sweep); + statistics *statistics = &solver->statistics; + uint64_t equivalences = statistics->sweep_equivalences; + uint64_t units = statistics->sweep_units; + sweeper sweeper; + init_sweeper (solver, &sweeper); + const unsigned scheduled = schedule_sweeping (&sweeper); + uint64_t swept = 0, limit = 10; + for (;;) { + if (solver->inconsistent) + break; + if (TERMINATED (sweep_terminated_8)) + break; + if (solver->statistics.kitten_ticks > sweeper.limit.ticks) + break; + unsigned idx = next_scheduled (&sweeper); + if (idx == INVALID_IDX) + break; + FLAGS (idx)->sweep = false; +#ifndef QUIET + const char *res = +#endif + sweep_variable (&sweeper, idx); + kissat_extremely_verbose ( + solver, "swept[%" PRIu64 "] external variable %d %s", swept, + kissat_export_literal (solver, LIT (idx)), res); + if (++swept == limit) { + kissat_very_verbose (solver, + "found %" PRIu64 " equivalences and %" PRIu64 + " units after sweeping %" PRIu64 " variables ", + statistics->sweep_equivalences - equivalences, + solver->statistics.sweep_units - units, swept); + limit *= 10; + } + } + kissat_very_verbose (solver, "swept %" PRIu64 " variables", swept); + equivalences = statistics->sweep_equivalences - equivalences, + units = solver->statistics.sweep_units - units; + kissat_phase (solver, "sweep", GET (sweep), + "found %" PRIu64 " equivalences and %" PRIu64 " units", + equivalences, units); + unschedule_sweeping (&sweeper, swept, scheduled); + unsigned inactive = release_sweeper (&sweeper); + + if (!solver->inconsistent) { + solver->propagate = solver->trail.begin; + kissat_probing_propagate (solver, 0, true); + } + + uint64_t eliminated = equivalences + units; +#ifndef QUIET + assert (solver->active >= inactive); + solver->active -= inactive; + REPORT (!eliminated, '='); + solver->active += inactive; +#else + (void) inactive; +#endif + if (kissat_average (eliminated, swept) < 0.001) + BUMP_DELAY (sweep); + else + REDUCE_DELAY (sweep); + STOP (sweep); + return eliminated; +} diff --git a/src/sat/kissat/sweep.h b/src/sat/kissat/sweep.h new file mode 100644 index 000000000..a0f81b8b0 --- /dev/null +++ b/src/sat/kissat/sweep.h @@ -0,0 +1,9 @@ +#ifndef _sweep_h_INCLUDED +#define _sweep_h_INCLUDED + +#include + +struct kissat; +bool kissat_sweep (struct kissat *); + +#endif diff --git a/src/sat/kissat/terminate.c b/src/sat/kissat/terminate.c new file mode 100644 index 000000000..c1cfe7549 --- /dev/null +++ b/src/sat/kissat/terminate.c @@ -0,0 +1,15 @@ +#include "terminate.h" +#include "print.h" + +#ifndef QUIET + +void kissat_report_termination (kissat *solver, const char *name, + const char *file, long lineno, + const char *fun) { + kissat_very_verbose (solver, "%s:%ld: %s: 'TERMINATED (%s)' triggered", + file, lineno, fun, name); +} + +#else +int kissat_terminate_dummy_to_avoid_warning; +#endif diff --git a/src/sat/kissat/terminate.h b/src/sat/kissat/terminate.h new file mode 100644 index 000000000..731f584b8 --- /dev/null +++ b/src/sat/kissat/terminate.h @@ -0,0 +1,86 @@ +#ifndef _terminate_h_INCLUDED +#define _terminate_h_INCLUDED + +#include "internal.h" + +#ifndef QUIET +void kissat_report_termination (kissat *, const char *name, + const char *file, long lineno, + const char *fun); +#endif + +static inline bool kissat_terminated (kissat *solver, int bit, + const char *name, const char *file, + long lineno, const char *fun) { + assert (0 <= bit), assert (bit < 64); +#ifdef COVERAGE + const uint64_t mask = (uint64_t) 1 << bit; + if (!(solver->termination.flagged & mask)) + return false; + solver->termination.flagged = ~(uint64_t) 0; +#else + if (!solver->termination.flagged) + return false; +#endif +#ifndef QUIET + kissat_report_termination (solver, name, file, lineno, fun); +#else + (void) file; + (void) fun; + (void) lineno; + (void) name; +#endif +#if !defined(COVERAGE) && defined(NDEBUG) + (void) bit; +#endif + return true; +} + +#define TERMINATED(BIT) \ + kissat_terminated (solver, BIT, #BIT, __FILE__, __LINE__, __func__) + +#define backbone_terminated_1 1 +#define backbone_terminated_2 2 +#define backbone_terminated_3 3 +#define congruence_terminated_1 4 +#define congruence_terminated_2 5 +#define congruence_terminated_3 6 +#define congruence_terminated_4 7 +#define congruence_terminated_5 8 +#define congruence_terminated_6 9 +#define congruence_terminated_7 10 +#define congruence_terminated_8 11 +#define congruence_terminated_9 12 +#define congruence_terminated_10 13 +#define congruence_terminated_11 14 +#define congruence_terminated_12 15 +#define eliminate_terminated_1 16 +#define eliminate_terminated_2 17 +#define factor_terminated_1 18 +#define fastel_terminated_1 19 +#define forward_terminated_1 20 +#define kitten_terminated_1 21 +#define kitten_terminated_2 22 +#define preprocess_terminated_1 23 +#define search_terminated_1 24 +#define substitute_terminated_1 25 +#define sweep_terminated_1 26 +#define sweep_terminated_2 27 +#define sweep_terminated_3 28 +#define sweep_terminated_4 29 +#define sweep_terminated_5 30 +#define sweep_terminated_6 31 +#define sweep_terminated_7 32 +#define sweep_terminated_8 33 +#define transitive_terminated_1 34 +#define transitive_terminated_2 35 +#define transitive_terminated_3 36 +#define vivify_terminated_1 37 +#define vivify_terminated_2 38 +#define vivify_terminated_3 39 +#define vivify_terminated_4 40 +#define vivify_terminated_5 41 +#define walk_terminated_1 42 +#define warmup_terminated_1 43 + +#endif diff --git a/src/sat/kissat/tiers.c b/src/sat/kissat/tiers.c new file mode 100644 index 000000000..3fb49e104 --- /dev/null +++ b/src/sat/kissat/tiers.c @@ -0,0 +1,161 @@ +#include "tiers.h" +#include "internal.h" +#include "logging.h" +#include "print.h" + +static void compute_tier_limits (kissat *solver, bool stable, + unsigned *tier1_ptr, unsigned *tier2_ptr) { + statistics *statistics = &solver->statistics; + uint64_t *used_stats = statistics->used[stable].glue; + uint64_t total_used = 0; + for (unsigned glue = 0; glue <= MAX_GLUE_USED; glue++) + total_used += used_stats[glue]; + int tier1 = -1, tier2 = -1; + if (total_used) { + uint64_t accumulated_tier1_limit = total_used * TIER1RELATIVE; + uint64_t accumulated_tier2_limit = total_used * TIER2RELATIVE; + uint64_t accumulated_used = 0; + unsigned glue; + for (glue = 0; glue <= MAX_GLUE_USED; glue++) { + uint64_t glue_used = used_stats[glue]; + accumulated_used += glue_used; + if (accumulated_used >= accumulated_tier1_limit) { + tier1 = glue; + break; + } + } + if (accumulated_used < accumulated_tier2_limit) { + for (glue = tier1 + 1; glue <= MAX_GLUE_USED; glue++) { + uint64_t glue_used = used_stats[glue]; + accumulated_used += glue_used; + if (accumulated_used >= accumulated_tier2_limit) { + tier2 = glue; + break; + } + } + } + } + if (tier1 < 0) { + tier1 = GET_OPTION (tier1); + tier2 = MAX (GET_OPTION (tier2), tier1); + } else if (tier2 < 0) + tier2 = tier1; + assert (0 <= tier1); + assert (0 <= tier2); + *tier1_ptr = tier1; + *tier2_ptr = tier2; + LOG ("%s tier1 limit %u", stable ? "stable" : "focused", tier1); + LOG ("%s tier2 limit %u", stable ? "stable" : "focused", tier2); +} + +void kissat_compute_and_set_tier_limits (struct kissat *solver) { + bool stable = solver->stable; + unsigned tier1, tier2; + compute_tier_limits (solver, stable, &tier1, &tier2); + solver->tier1[stable] = tier1; + solver->tier2[stable] = tier2; + kissat_phase (solver, "retiered", GET (retiered), + "recomputed %s tier1 limit %u and tier2 limit %u " + "after %" PRIu64 " conflicts", + stable ? "stable" : "focused", tier1, tier2, CONFLICTS); +} + +static unsigned decimal_digits (uint64_t i) { + unsigned res = 1; + uint64_t limit = 10; + for (;;) { + if (i < limit) + return res; + limit *= 10; + res++; + } +} + +void kissat_print_tier_usage_statistics (kissat *solver, bool stable) { + unsigned tier1, tier2; + compute_tier_limits (solver, stable, &tier1, &tier2); + statistics *statistics = &solver->statistics; + uint64_t *used_stats = statistics->used[stable].glue; + uint64_t total_used = 0; + for (unsigned glue = 0; glue <= MAX_GLUE_USED; glue++) + total_used += used_stats[glue]; + const char *mode = stable ? "stable" : "focused"; + assert (tier1 <= tier2); + unsigned span = tier2 - tier1 + 1; + const unsigned max_printed = 5; + assert (max_printed & 1), assert (max_printed / 2 > 0); + unsigned prefix, suffix; + if (span > max_printed) { + prefix = tier1 + max_printed / 2 - 1; + suffix = tier2 - max_printed / 2 + 1; + } else + prefix = UINT_MAX, suffix = 0; + uint64_t accumulated_middle = 0; + int glue_digits = 1, clauses_digits = 1; + for (unsigned glue = 0; glue <= MAX_GLUE_USED; glue++) { + if (glue < tier1) + continue; + uint64_t used = used_stats[glue]; + int tmp_glue = 0, tmp_clauses = 0; + if (glue <= prefix || suffix <= glue) { + tmp_glue = decimal_digits (glue); + tmp_clauses = decimal_digits (used); + } else { + accumulated_middle += used; + if (glue + 1 == suffix) { + tmp_glue = decimal_digits (prefix + 1) + decimal_digits (glue) + 1; + tmp_clauses = decimal_digits (accumulated_middle); + } + } + if (tmp_glue > glue_digits) + glue_digits = tmp_glue; + if (tmp_clauses > clauses_digits) + clauses_digits = tmp_clauses; + if (glue == tier2) + break; + } + char fmt[32]; + sprintf (fmt, "%%%d" PRIu64, clauses_digits); + accumulated_middle = 0; + uint64_t accumulated = 0; + for (unsigned glue = 0; glue <= MAX_GLUE_USED; glue++) { + uint64_t used = used_stats[glue]; + accumulated += used; + if (glue < tier1) + continue; + if (glue <= prefix || suffix <= glue + 1) { + fputs (solver->prefix, stdout); + fputs (mode, stdout); + fputs (" glue ", stdout); + } + if (glue <= prefix || suffix <= glue) { + int len = printf ("%u", glue); + while (len > 0 && len < glue_digits) + fputc (' ', stdout), len++; + fputs (" used ", stdout); + printf (fmt, used); + printf (" clauses %5.2f%% accumulated %5.2f%%", + kissat_percent (used, total_used), + kissat_percent (accumulated, total_used)); + if (glue == tier1) + fputs (" tier1", stdout); + if (glue == tier2) + fputs (" tier2", stdout); + fputc ('\n', stdout); + } else { + accumulated_middle += used; + if (glue + 1 == suffix) { + int len = printf ("%u-%u", prefix + 1, suffix - 1); + while (len > 0 && len < glue_digits) + fputc (' ', stdout), len++; + fputs (" used ", stdout); + printf (fmt, accumulated_middle); + printf (" clauses %5.2f%% accumulated %5.2f%%\n", + kissat_percent (accumulated_middle, total_used), + kissat_percent (accumulated, total_used)); + } + } + if (glue == tier2) + break; + } +} diff --git a/src/sat/kissat/tiers.h b/src/sat/kissat/tiers.h new file mode 100644 index 000000000..3b7acb284 --- /dev/null +++ b/src/sat/kissat/tiers.h @@ -0,0 +1,12 @@ +#ifndef _tiers_h_INCLUDED +#define _tiers_h_INCLUDED + +#include + +struct kissat; + +void kissat_compute_and_set_tier_limits (struct kissat *); +void kissat_print_tier_usage_statistics (struct kissat *solver, + bool stable); + +#endif diff --git a/src/sat/kissat/trail.c b/src/sat/kissat/trail.c new file mode 100644 index 000000000..8b1882f53 --- /dev/null +++ b/src/sat/kissat/trail.c @@ -0,0 +1,92 @@ +#include "trail.h" +#include "backtrack.h" +#include "inline.h" +#include "propsearch.h" + +void kissat_flush_trail (kissat *solver) { + assert (!solver->level); + assert (solver->unflushed); + assert (!solver->inconsistent); + assert (kissat_propagated (solver)); + assert (SIZE_ARRAY (solver->trail) == solver->unflushed); + LOG ("flushed %zu units from trail", SIZE_ARRAY (solver->trail)); + CLEAR_ARRAY (solver->trail); + kissat_reset_propagate (solver); + solver->unflushed = 0; +} + +void kissat_mark_reason_clauses (kissat *solver, reference start) { + LOG ("starting marking reason clauses at clause[%" REFERENCE_FORMAT "]", + start); + assert (!solver->unflushed); +#ifdef LOGGING + unsigned reasons = 0; +#endif + ward *arena = BEGIN_STACK (solver->arena); + for (all_stack (unsigned, lit, solver->trail)) { + assigned *a = ASSIGNED (lit); + assert (a->level > 0); + if (a->binary) + continue; + const reference ref = a->reason; + assert (ref != UNIT_REASON); + if (ref == DECISION_REASON) + continue; + if (ref < start) + continue; + clause *c = (clause *) (arena + ref); + assert (kissat_clause_in_arena (solver, c)); + c->reason = true; +#ifdef LOGGING + reasons++; +#endif + } + LOG ("marked %u reason clauses", reasons); +} + +bool kissat_flush_and_mark_reason_clauses (kissat *solver, + reference start) { + assert (solver->watching); + assert (!solver->inconsistent); + assert (kissat_propagated (solver)); + + if (solver->unflushed) { + LOG ("need to flush %u units from trail", solver->unflushed); + kissat_backtrack_propagate_and_flush_trail (solver); + } else { + LOG ("no need to flush units from trail (all units already flushed)"); + kissat_mark_reason_clauses (solver, start); + } + + return true; +} + +void kissat_unmark_reason_clauses (kissat *solver, reference start) { + LOG ("starting unmarking reason clauses at clause[%" REFERENCE_FORMAT "]", + start); + assert (!solver->unflushed); +#ifdef LOGGING + unsigned reasons = 0; +#endif + ward *arena = BEGIN_STACK (solver->arena); + for (all_stack (unsigned, lit, solver->trail)) { + assigned *a = ASSIGNED (lit); + assert (a->level > 0); + if (a->binary) + continue; + const reference ref = a->reason; + assert (ref != UNIT_REASON); + if (ref == DECISION_REASON) + continue; + if (ref < start) + continue; + clause *c = (clause *) (arena + ref); + assert (kissat_clause_in_arena (solver, c)); + assert (c->reason); + c->reason = false; +#ifdef LOGGING + reasons++; +#endif + } + LOG ("unmarked %u reason clauses", reasons); +} diff --git a/src/sat/kissat/trail.h b/src/sat/kissat/trail.h new file mode 100644 index 000000000..ca84203f2 --- /dev/null +++ b/src/sat/kissat/trail.h @@ -0,0 +1,16 @@ +#ifndef _trail_h_INLCUDED +#define _trail_h_INLCUDED + +#include "reference.h" + +#include + +struct kissat; + +void kissat_flush_trail (struct kissat *); +bool kissat_flush_and_mark_reason_clauses (struct kissat *, + reference start); +void kissat_unmark_reason_clauses (struct kissat *, reference start); +void kissat_mark_reason_clauses (struct kissat *, reference start); + +#endif diff --git a/src/sat/kissat/transitive.c b/src/sat/kissat/transitive.c new file mode 100644 index 000000000..2c06ff5e5 --- /dev/null +++ b/src/sat/kissat/transitive.c @@ -0,0 +1,389 @@ +#include "transitive.h" +#include "allocate.h" +#include "analyze.h" +#include "heap.h" +#include "inline.h" +#include "inlinevector.h" +#include "logging.h" +#include "print.h" +#include "proprobe.h" +#include "report.h" +#include "sort.h" +#include "terminate.h" +#include "trail.h" + +#include + +static void transitive_assign (kissat *solver, unsigned lit) { + LOG ("transitive assign %s", LOGLIT (lit)); + value *values = solver->values; + const unsigned not_lit = NOT (lit); + assert (!values[lit]); + assert (!values[not_lit]); + values[lit] = 1; + values[not_lit] = -1; + PUSH_ARRAY (solver->trail, lit); +} + +static void transitive_backtrack (kissat *solver, unsigned *saved) { + value *values = solver->values; + + unsigned *end_trail = END_ARRAY (solver->trail); + assert (saved <= end_trail); + + while (end_trail != saved) { + const unsigned lit = *--end_trail; + LOG ("transitive unassign %s", LOGLIT (lit)); + const unsigned not_lit = NOT (lit); + assert (values[lit] > 0); + assert (values[not_lit] < 0); + values[lit] = values[not_lit] = 0; + } + + SET_END_OF_ARRAY (solver->trail, saved); + solver->propagate = saved; + solver->level = 0; +} + +static void prioritize_binaries (kissat *solver) { + assert (solver->watching); + statches large; + INIT_STACK (large); + watches *all_watches = solver->watches; + for (all_literals (lit)) { + assert (EMPTY_STACK (large)); + watches *watches = all_watches + lit; + watch *begin_watches = BEGIN_WATCHES (*watches), *q = begin_watches; + const watch *const end_watches = END_WATCHES (*watches), *p = q; + while (p != end_watches) { + const watch head = *q++ = *p++; + if (head.type.binary) + continue; + const watch tail = *p++; + PUSH_STACK (large, head); + PUSH_STACK (large, tail); + q--; + } + const watch *const end_large = END_STACK (large); + watch const *r = BEGIN_STACK (large); + while (r != end_large) + *q++ = *r++; + assert (q == end_watches); + CLEAR_STACK (large); + } + RELEASE_STACK (large); +} + +static bool transitive_reduce (kissat *solver, unsigned src, uint64_t limit, + uint64_t *reduced_ptr, unsigned *units) { + bool res = false; + assert (!VALUE (src)); + LOG ("transitive reduce %s", LOGLIT (src)); + watches *all_watches = solver->watches; + watches *src_watches = all_watches + src; + watch *end_src = END_WATCHES (*src_watches); + watch *begin_src = BEGIN_WATCHES (*src_watches); + const size_t size_src_watches = SIZE_WATCHES (*src_watches); + const unsigned src_ticks = + 1 + kissat_cache_lines (size_src_watches, sizeof (watch)); + ADD (transitive_ticks, src_ticks); + ADD (probing_ticks, src_ticks); + ADD (ticks, src_ticks); + INC (transitive_probes); + const unsigned not_src = NOT (src); + unsigned reduced = 0; + bool failed = false; + for (watch *p = begin_src; p != end_src; p++) { + const watch src_watch = *p; + if (!src_watch.type.binary) + break; + const unsigned dst = src_watch.binary.lit; + if (dst < src) + continue; + if (VALUE (dst)) + continue; + assert (kissat_propagated (solver)); + unsigned *saved = solver->propagate; + assert (!solver->level); + solver->level = 1; + transitive_assign (solver, not_src); + bool transitive = false; + unsigned inner_ticks = 0; + unsigned *propagate = solver->propagate; + while (!transitive && !failed && + propagate != END_ARRAY (solver->trail)) { + const unsigned lit = *propagate++; + LOG ("transitive propagate %s", LOGLIT (lit)); + assert (VALUE (lit) > 0); + const unsigned not_lit = NOT (lit); + watches *lit_watches = all_watches + not_lit; + const watch *const end_lit = END_WATCHES (*lit_watches); + const watch *const begin_lit = BEGIN_WATCHES (*lit_watches); + const size_t size_lit_watches = SIZE_WATCHES (*lit_watches); + inner_ticks += + 1 + kissat_cache_lines (size_lit_watches, sizeof (watch)); + for (const watch *q = begin_lit; q != end_lit; q++) { + if (p == q) + continue; + const watch lit_watch = *q; + if (!lit_watch.type.binary) + break; + if (not_lit == src && lit_watch.binary.lit == ILLEGAL_LIT) + continue; + const unsigned other = lit_watch.binary.lit; + if (other == dst) { + transitive = true; + break; + } + const value value = VALUE (other); + if (value < 0) { + LOG ("both %s and %s reachable from %s", LOGLIT (NOT (other)), + LOGLIT (other), LOGLIT (src)); + failed = true; + break; + } + if (!value) + transitive_assign (solver, other); + } + } + + assert (solver->probing); + + assert (solver->propagate <= propagate); + const unsigned propagated = propagate - solver->propagate; + + ADD (transitive_propagations, propagated); + ADD (probing_propagations, propagated); + ADD (propagations, propagated); + + ADD (transitive_ticks, inner_ticks); + ADD (probing_ticks, inner_ticks); + ADD (ticks, inner_ticks); + + transitive_backtrack (solver, saved); + + if (transitive) { + LOGBINARY (src, dst, "transitive reduce"); + INC (transitive_reduced); + watches *dst_watches = all_watches + dst; + watch dst_watch = src_watch; + assert (dst_watch.binary.lit == dst); + dst_watch.binary.lit = src; + REMOVE_WATCHES (*dst_watches, dst_watch); + kissat_delete_binary (solver, src, dst); + p->binary.lit = ILLEGAL_LIT; + reduced++; + res = true; + } + + if (failed) + break; + if (solver->statistics.transitive_ticks > limit) + break; + if (TERMINATED (transitive_terminated_1)) + break; + } + + if (reduced) { + *reduced_ptr += reduced; + assert (begin_src == BEGIN_WATCHES (WATCHES (src))); + assert (end_src == END_WATCHES (WATCHES (src))); + watch *q = begin_src; + for (const watch *p = begin_src; p != end_src; p++) { + const watch src_watch = *q++ = *p; + if (!src_watch.type.binary) { + *q++ = *++p; + continue; + } + if (src_watch.binary.lit == ILLEGAL_LIT) + q--; + } + assert (end_src - q == (ptrdiff_t) reduced); + SET_END_OF_WATCHES (*src_watches, q); + } + + if (failed) { + LOG ("transitive failed literal %s", LOGLIT (not_src)); + INC (transitive_units); + *units += 1; + res = true; + + kissat_learned_unit (solver, src); + + assert (!solver->level); + (void) kissat_probing_propagate (solver, 0, true); + } + + return res; +} + +static inline bool less_stable_transitive (kissat *solver, + const flags *const flags, + const heap *scores, unsigned a, + unsigned b) { +#ifdef NDEBUG + (void) solver; +#endif + const unsigned i = IDX (a); + const unsigned j = IDX (b); + const bool p = flags[i].transitive; + const bool q = flags[j].transitive; + if (!p && q) + return true; + if (p && !q) + return false; + const double s = kissat_get_heap_score (scores, i); + const double t = kissat_get_heap_score (scores, j); + if (s < t) + return true; + if (s > t) + return false; + return i < j; +} + +static inline unsigned less_focused_transitive (kissat *solver, + const flags *const flags, + const links *links, + unsigned a, unsigned b) { +#ifdef NDEBUG + (void) solver; +#endif + const unsigned i = IDX (a); + const unsigned j = IDX (b); + const bool p = flags[i].transitive; + const bool q = flags[j].transitive; + if (!p && q) + return true; + if (p && !q) + return false; + const unsigned s = links[i].stamp; + const unsigned t = links[j].stamp; + return s < t; +} + +#define LESS_STABLE_PROBE(A, B) \ + less_stable_transitive (solver, flags, scores, (A), (B)) + +#define LESS_FOCUSED_PROBE(A, B) \ + less_focused_transitive (solver, flags, links, (A), (B)) + +static void sort_stable_transitive (kissat *solver, unsigneds *probes) { + const flags *const flags = solver->flags; + const heap *const scores = SCORES; + SORT_STACK (unsigned, *probes, LESS_STABLE_PROBE); +} + +static void sort_focused_transitive (kissat *solver, unsigneds *probes) { + const flags *const flags = solver->flags; + const links *const links = solver->links; + SORT_STACK (unsigned, *probes, LESS_FOCUSED_PROBE); +} + +static void sort_transitive (kissat *solver, unsigneds *probes) { + if (solver->stable) + sort_stable_transitive (solver, probes); + else + sort_focused_transitive (solver, probes); +} + +static void schedule_transitive (kissat *solver, unsigneds *probes) { + assert (EMPTY_STACK (*probes)); + for (all_variables (idx)) + if (ACTIVE (idx)) + PUSH_STACK (*probes, idx); + sort_transitive (solver, probes); + kissat_very_verbose (solver, "scheduled %zu transitive probes", + SIZE_STACK (*probes)); +} + +void kissat_transitive_reduction (kissat *solver) { + if (solver->inconsistent) + return; + assert (solver->watching); + assert (solver->probing); + assert (!solver->level); + if (!GET_OPTION (transitive)) + return; + if (TERMINATED (transitive_terminated_2)) + return; + START (transitive); + INC (transitive_reductions); +#if !defined(NDEBUG) || defined(METRICS) + assert (!solver->transitive_reducing); + solver->transitive_reducing = true; +#endif + prioritize_binaries (solver); + bool success = false; + uint64_t reduced = 0; + unsigned units = 0; + + SET_EFFORT_LIMIT (limit, transitive, transitive_ticks); + +#ifndef QUIET + const unsigned active = solver->active; + const uint64_t old_ticks = solver->statistics.transitive_ticks; + kissat_extremely_verbose ( + solver, "starting with %" PRIu64 " transitive ticks", old_ticks); + unsigned probed = 0; +#endif + unsigneds probes; + INIT_STACK (probes); + schedule_transitive (solver, &probes); + bool terminate = false; + while (!terminate && !EMPTY_STACK (probes)) { + const unsigned idx = POP_STACK (probes); + solver->flags[idx].transitive = false; + if (!ACTIVE (idx)) + continue; + for (unsigned sign = 0; !terminate && sign < 2; sign++) { + const unsigned lit = 2 * idx + sign; + if (solver->values[lit]) + continue; +#ifndef QUIET + probed++; +#endif + if (transitive_reduce (solver, lit, limit, &reduced, &units)) + success = true; + if (solver->inconsistent) + terminate = true; + else if (solver->statistics.transitive_ticks > limit) + terminate = true; + else if (TERMINATED (transitive_terminated_3)) + terminate = true; + } + } + const size_t remain = SIZE_STACK (probes); + if (remain) { + if (!GET_OPTION (transitivekeep)) { + kissat_very_verbose ( + solver, "dropping remaining %zu transitive candidates", remain); + while (!EMPTY_STACK (probes)) { + const unsigned idx = POP_STACK (probes); + solver->flags[idx].transitive = false; + } + } + } else + kissat_very_verbose (solver, "transitive reduction complete"); + RELEASE_STACK (probes); + +#ifndef QUIET + const uint64_t new_ticks = solver->statistics.transitive_ticks; + const uint64_t delta_ticks = new_ticks - old_ticks; + kissat_extremely_verbose ( + solver, "finished at %" PRIu64 " after %" PRIu64 " transitive ticks", + new_ticks, delta_ticks); +#endif + kissat_phase (solver, "transitive", GET (probings), + "probed %u (%.0f%%): reduced %" PRIu64 ", units %u", probed, + kissat_percent (probed, 2 * active), reduced, units); + +#if !defined(NDEBUG) || defined(METRICS) + assert (solver->transitive_reducing); + solver->transitive_reducing = false; +#endif + REPORT (!success, 't'); + STOP (transitive); +#ifdef QUIET + (void) success; +#endif +} diff --git a/src/sat/kissat/transitive.h b/src/sat/kissat/transitive.h new file mode 100644 index 000000000..ec0011460 --- /dev/null +++ b/src/sat/kissat/transitive.h @@ -0,0 +1,8 @@ +#ifndef _transitive_h_INCLUDED +#define _transitive_h_INCLUDED + +struct kissat; + +void kissat_transitive_reduction (struct kissat *); + +#endif diff --git a/src/sat/kissat/utilities.c b/src/sat/kissat/utilities.c new file mode 100644 index 000000000..4ea4e0b8f --- /dev/null +++ b/src/sat/kissat/utilities.c @@ -0,0 +1,16 @@ +#include "utilities.h" + +#include + +bool kissat_has_suffix (const char *str, const char *suffix) { + const char *p = str; + while (*p) + p++; + const char *q = suffix; + while (*q) + q++; + while (p > str && q > suffix) + if (*--p != *--q) + return false; + return q == suffix; +} diff --git a/src/sat/kissat/utilities.h b/src/sat/kissat/utilities.h new file mode 100644 index 000000000..392aaafef --- /dev/null +++ b/src/sat/kissat/utilities.h @@ -0,0 +1,136 @@ +#ifndef _utilities_h_INCLUDED +#define _utilities_h_INCLUDED + +#include +#include +#include +#include + +typedef uintptr_t word; +typedef uintptr_t w2rd[2]; + +#define WORD_ALIGNMENT_MASK (sizeof (word) - 1) +#define W2RD_ALIGNMENT_MASK (sizeof (w2rd) - 1) + +#define WORD_FORMAT PRIuPTR + +#define MAX_SIZE_T (~(size_t) 0) + +#define ASSUMED_LD_CACHE_LINE_BYTES 7u + +static inline word kissat_cache_lines (word n, size_t size) { + if (!n) + return 0; +#ifdef NDEBUG + (void) size; +#endif + assert (size == 4); + assert (ASSUMED_LD_CACHE_LINE_BYTES > 2); + const unsigned shift = ASSUMED_LD_CACHE_LINE_BYTES - 2u; + const word mask = (((word) 1) << shift) - 1; + const word masked = n + mask; + const word res = masked >> shift; + return res; +} + +static inline double kissat_average (double a, double b) { + return b ? a / b : 0.0; +} + +static inline double kissat_percent (double a, double b) { + return kissat_average (100.0 * a, b); +} + +static inline bool kissat_aligned_word (word word) { + return !(word & WORD_ALIGNMENT_MASK); +} + +static inline bool kissat_aligned_pointer (const void *p) { + return kissat_aligned_word ((word) p); +} + +static inline word kissat_align_word (word w) { + word res = w; + if (res & WORD_ALIGNMENT_MASK) + res = 1 + (res | WORD_ALIGNMENT_MASK); + return res; +} + +static inline word kissat_align_w2rd (word w) { + word res = w; + if (res & W2RD_ALIGNMENT_MASK) + res = 1 + (res | W2RD_ALIGNMENT_MASK); + return res; +} + +bool kissat_has_suffix (const char *str, const char *suffix); + +static inline bool kissat_is_power_of_two (uint64_t w) { + return w && !(w & (w - 1)); +} + +static inline bool kissat_is_zero_or_power_of_two (word w) { + return !(w & (w - 1)); +} + +static inline unsigned kissat_leading_zeroes_of_unsigned (unsigned x) { + return x ? __builtin_clz (x) : sizeof (unsigned) * 8; +} + +static inline unsigned kissat_leading_zeroes_of_word (word x) { + if (!x) + return sizeof (word) * 8; + if (sizeof (word) == sizeof (unsigned long long)) + return __builtin_clzll (x); + if (sizeof (word) == sizeof (unsigned long)) + return __builtin_clzl (x); + return __builtin_clz (x); +} + +static inline unsigned kissat_log2_floor_of_word (word x) { + return x ? sizeof (word) * 8 - 1 - kissat_leading_zeroes_of_word (x) : 0; +} + +static inline unsigned kissat_log2_ceiling_of_word (word x) { + if (!x) + return 0; + unsigned tmp = kissat_log2_floor_of_word (x); + return tmp + !!(x ^ (((word) 1) << tmp)); +} + +static inline unsigned kissat_leading_zeroes_of_uint64 (uint64_t x) { + if (!x) + return sizeof (uint64_t) * 8; + if (sizeof (uint64_t) == sizeof (unsigned long long)) + return __builtin_clzll (x); + if (sizeof (uint64_t) == sizeof (unsigned long)) + return __builtin_clzl (x); + return __builtin_clz (x); +} + +static inline unsigned kissat_log2_floor_of_uint64 (uint64_t x) { + return x ? sizeof (uint64_t) * 8 - 1 - kissat_leading_zeroes_of_uint64 (x) + : 0; +} + +static inline unsigned kissat_log2_ceiling_of_uint64 (uint64_t x) { + if (!x) + return 0; + unsigned tmp = kissat_log2_floor_of_uint64 (x); + return tmp + !!(x ^ (((uint64_t) 1) << tmp)); +} + +#define SWAP(TYPE, A, B) \ + do { \ + TYPE TMP_SWAP = (A); \ + (A) = (B); \ + (B) = (TMP_SWAP); \ + } while (0) + +#define MIN(A, B) ((A) > (B) ? (B) : (A)) + +#define MAX(A, B) ((A) < (B) ? (B) : (A)) + +#define ABS(A) (assert ((int) (A) != INT_MIN), (A) < 0 ? -(A) : (A)) + +#endif diff --git a/src/sat/kissat/value.h b/src/sat/kissat/value.h new file mode 100644 index 000000000..efea7391a --- /dev/null +++ b/src/sat/kissat/value.h @@ -0,0 +1,13 @@ +#ifndef _value_h_INCLUDED +#define _value_h_INCLUDED + +typedef signed char value; +typedef signed char mark; + +#define VALUE(LIT) (solver->values[assert ((LIT) < LITS), (LIT)]) + +#define MARK(LIT) (solver->marks[assert ((LIT) < LITS), (LIT)]) + +#define BOOL_TO_VALUE(B) ((signed char) ((B) ? -1 : 1)) + +#endif diff --git a/src/sat/kissat/vector.c b/src/sat/kissat/vector.c new file mode 100644 index 000000000..728a66f6d --- /dev/null +++ b/src/sat/kissat/vector.c @@ -0,0 +1,320 @@ +#include "allocate.h" +#include "collect.h" +#include "error.h" +#include "inlinevector.h" +#include "logging.h" +#include "print.h" +#include "rank.h" + +#include +#include + +#ifndef COMPACT + +static void fix_vector_pointers_after_moving_stack (kissat *solver, + ptrdiff_t moved) { +#ifdef LOGGING + uint64_t bytes = moved < 0 ? -moved : moved; + LOG ("fixing begin and end pointers of all watches " + "since the global watches stack has been moved by %s", + FORMAT_BYTES (bytes)); +#endif + struct vector *begin_watches = solver->watches; + struct vector *end_watches = begin_watches + LITS; + for (struct vector *p = begin_watches; p != end_watches; p++) { + +#define FIX_POINTER(PTR) \ + do { \ + char *old_char_ptr_value = (char *) (PTR); \ + if (!old_char_ptr_value) \ + break; \ + char *new_char_ptr_value = old_char_ptr_value + moved; \ + unsigned *new_unsigned_ptr_value = (unsigned *) new_char_ptr_value; \ + (PTR) = new_unsigned_ptr_value; \ + } while (0) + + FIX_POINTER (p->begin); + FIX_POINTER (p->end); + } +} + +#endif + +unsigned *kissat_enlarge_vector (kissat *solver, vector *vector) { + unsigneds *stack = &solver->vectors.stack; + const size_t old_vector_size = kissat_size_vector (vector); +#ifdef LOGGING + const size_t old_offset = kissat_offset_vector (solver, vector); + LOG2 ("enlarging vector %zu[%zu] at %p", old_offset, old_vector_size, + (void *) vector); +#endif + assert (old_vector_size < MAX_VECTORS / 2); + const size_t new_vector_size = old_vector_size ? 2 * old_vector_size : 1; + size_t old_stack_size = SIZE_STACK (*stack); + size_t capacity = CAPACITY_STACK (*stack); + assert (kissat_is_power_of_two (MAX_VECTORS)); + assert (capacity <= MAX_VECTORS); + size_t available = capacity - old_stack_size; + if (new_vector_size > available) { +#if !defined(QUIET) || !defined(COMPACT) + unsigned *old_begin_stack = BEGIN_STACK (*stack); +#endif + unsigned enlarged = 0; + do { + assert (kissat_is_zero_or_power_of_two (capacity)); + + if (capacity == MAX_VECTORS) + kissat_fatal ("maximum vector stack size " + "of 2^%u entries %s exhausted", + LD_MAX_VECTORS, + FORMAT_BYTES (MAX_VECTORS * sizeof (unsigned))); + enlarged++; + kissat_stack_enlarge (solver, (chars *) stack, sizeof (unsigned)); + + capacity = CAPACITY_STACK (*stack); + available = capacity - old_stack_size; + } while (new_vector_size > available); + + if (enlarged) { + INC (vectors_enlarged); +#if !defined(QUIET) || !defined(COMPACT) + unsigned *new_begin_stack = BEGIN_STACK (*stack); + const ptrdiff_t moved = + (char *) new_begin_stack - (char *) old_begin_stack; +#endif +#ifndef QUIET + kissat_phase (solver, "vectors", GET (vectors_enlarged), + "enlarged to %s entries %s (%s)", + FORMAT_COUNT (capacity), + FORMAT_BYTES (capacity * sizeof (unsigned)), + (moved ? "moved" : "in place")); +#endif +#ifndef COMPACT + if (moved) + fix_vector_pointers_after_moving_stack (solver, moved); +#endif + } + assert (capacity <= MAX_VECTORS); + assert (new_vector_size <= available); + } + unsigned *begin_old_vector = kissat_begin_vector (solver, vector); + unsigned *begin_new_vector = END_STACK (*stack); + unsigned *middle_new_vector = begin_new_vector + old_vector_size; + unsigned *end_new_vector = begin_new_vector + new_vector_size; + assert (end_new_vector <= stack->allocated); + const size_t old_bytes = old_vector_size * sizeof (unsigned); + const size_t delta_size = new_vector_size - old_vector_size; + assert (MAX_SIZE_T / sizeof (unsigned) >= delta_size); + const size_t delta_bytes = delta_size * sizeof (unsigned); + if (old_bytes) { + memcpy (begin_new_vector, begin_old_vector, old_bytes); + memset (begin_old_vector, 0xff, old_bytes); + } + solver->vectors.usable += old_vector_size; + kissat_add_usable (solver, delta_size); + if (delta_bytes) + memset (middle_new_vector, 0xff, delta_bytes); +#ifdef COMPACT + const uint64_t offset = SIZE_STACK (*stack); + assert (offset <= MAX_VECTORS); + vector->offset = offset; + LOG2 ("enlarged vector at %p to %u[%u]", (void *) vector, vector->offset, + vector->size); +#else + vector->begin = begin_new_vector; + vector->end = middle_new_vector; +#ifdef LOGGING + const size_t new_offset = vector->begin - stack->begin; + LOG2 ("enlarged vector at %p to %zu[%zu]", (void *) vector, new_offset, + old_vector_size); +#endif +#endif + stack->end = end_new_vector; + assert (begin_new_vector < end_new_vector); + assert (kissat_size_vector (vector) == old_vector_size); + return middle_new_vector; +} + +#ifdef COMPACT + +typedef unsigned rank; + +static inline rank rank_offset (vector *unsorted, unsigned i) { + return unsorted[i].offset; +} + +#else + +typedef uintptr_t rank; + +static inline rank rank_offset (vector *unsorted, unsigned i) { + const unsigned *begin = unsorted[i].begin; + return (uintptr_t) begin; +} + +#endif + +#define RANK_OFFSET(A) rank_offset (unsorted, (A)) + +void kissat_defrag_vectors (kissat *solver, size_t size_unsorted, + vector *unsorted) { + unsigneds *stack = &solver->vectors.stack; + const size_t size_vectors = SIZE_STACK (*stack); + if (size_vectors < 2) + return; + START (defrag); + INC (defragmentations); + LOG ("defragmenting vectors size %zu capacity %zu usable %zu", + size_vectors, CAPACITY_STACK (*stack), solver->vectors.usable); + size_t bytes = size_unsorted * sizeof (unsigned); + unsigned *sorted = kissat_malloc (solver, bytes); + unsigned size_sorted = 0; + for (unsigned i = 0; i < size_unsorted; i++) { + vector *vector = unsorted + i; + if (kissat_empty_vector (vector)) +#ifdef COMPACT + vector->offset = 0; +#else + vector->begin = vector->end = 0; +#endif + else + sorted[size_sorted++] = i; + } + RADIX_SORT (unsigned, rank, size_sorted, sorted, RANK_OFFSET); + unsigned *old_begin_stack = BEGIN_STACK (*stack); + unsigned *p = old_begin_stack + 1; + for (unsigned i = 0; i < size_sorted; i++) { + unsigned j = sorted[i]; + vector *vector = unsorted + j; + const size_t size = kissat_size_vector (vector); + unsigned *new_end_of_vector = p + size; +#ifdef COMPACT + const unsigned old_offset = vector->offset; + const unsigned new_offset = p - old_begin_stack; + assert (new_offset <= old_offset); + vector->offset = new_offset; + const unsigned *const q = old_begin_stack + old_offset; +#else + if (!size) { + vector->begin = vector->end = 0; + continue; + } + const unsigned *const q = vector->begin; + vector->begin = p; + vector->end = new_end_of_vector; +#endif + assert (MAX_SIZE_T / sizeof (unsigned) >= size); + memmove (p, q, size * sizeof (unsigned)); + p = new_end_of_vector; + } + kissat_free (solver, sorted, bytes); +#ifndef QUIET + const size_t freed = END_STACK (*stack) - p; + double freed_fraction = kissat_percent (freed, size_vectors); + kissat_phase (solver, "defrag", GET (defragmentations), + "freed %zu usable entries %.0f%% thus %s", freed, + freed_fraction, FORMAT_BYTES (freed * sizeof (unsigned))); + assert (freed == solver->vectors.usable); +#endif + SET_END_OF_STACK (*stack, p); +#ifndef COMPACT + assert (old_begin_stack == BEGIN_STACK (*stack)); +#endif + SHRINK_STACK (*stack); +#ifndef COMPACT + unsigned *new_begin_stack = BEGIN_STACK (*stack); + const ptrdiff_t moved = + (char *) new_begin_stack - (char *) old_begin_stack; + if (moved) + fix_vector_pointers_after_moving_stack (solver, moved); +#endif + solver->vectors.usable = 0; + kissat_check_vectors (solver); + STOP (defrag); +} + +void kissat_remove_from_vector (kissat *solver, vector *vector, + unsigned remove) { + unsigned *begin = kissat_begin_vector (solver, vector), *p = begin; + const unsigned *const end = kissat_end_vector (solver, vector); + assert (p != end); + while (*p != remove) + p++, assert (p != end); + while (++p != end) + p[-1] = *p; + p[-1] = INVALID_VECTOR_ELEMENT; +#ifdef COMPACT + vector->size--; +#else + assert (vector->begin < vector->end); + vector->end--; +#endif + kissat_inc_usable (solver); + kissat_check_vectors (solver); +#ifndef CHECK_VECTORS + (void) solver; +#endif +} + +void kissat_resize_vector (kissat *solver, vector *vector, + size_t new_size) { + const size_t old_size = kissat_size_vector (vector); + assert (new_size <= old_size); + if (new_size == old_size) + return; +#ifdef COMPACT + vector->size = new_size; +#else + vector->end = vector->begin + new_size; +#endif + unsigned *begin = kissat_begin_vector (solver, vector); + unsigned *end = begin + new_size; + size_t delta = old_size - new_size; + kissat_add_usable (solver, delta); + size_t bytes = delta * sizeof (unsigned); + memset (end, 0xff, bytes); + kissat_check_vectors (solver); +#ifndef CHECK_VECTORS + (void) solver; +#endif +} + +void kissat_release_vectors (kissat *solver) { + RELEASE_STACK (solver->vectors.stack); + solver->vectors.usable = 0; +} + +#ifdef CHECK_VECTORS + +void kissat_check_vector (kissat *solver, vector *vector) { + const unsigned *const begin = kissat_begin_vector (solver, vector); + const unsigned *const end = kissat_end_vector (solver, vector); + if (!solver->transitive_reducing) + for (const unsigned *p = begin; p != end; p++) + assert (*p != INVALID_VECTOR_ELEMENT); +#ifdef NDEBUG + (void) solver; +#endif +} + +void kissat_check_vectors (kissat *solver) { + if (solver->transitive_reducing) + return; + for (all_literals (lit)) { + vector *vector = &WATCHES (lit); + kissat_check_vector (solver, vector); + } + vectors *vectors = &solver->vectors; + unsigneds *stack = &vectors->stack; + const unsigned *const begin = BEGIN_STACK (*stack); + const unsigned *const end = END_STACK (*stack); + if (begin == end) + return; + size_t invalid = 0; + for (const unsigned *p = begin + 1; p != end; p++) + if (*p == INVALID_VECTOR_ELEMENT) + invalid++; + assert (invalid == solver->vectors.usable); +} + +#endif diff --git a/src/sat/kissat/vector.h b/src/sat/kissat/vector.h new file mode 100644 index 000000000..55ff2ac1d --- /dev/null +++ b/src/sat/kissat/vector.h @@ -0,0 +1,55 @@ +#ifndef _vector_h_INCLUDED +#define _vector_h_INCLUDED + +#include "stack.h" +#include "utilities.h" + +#include + +#ifdef COMPACT +#define LD_MAX_VECTORS (sizeof (word) == 8 ? 32u : 28u) +#else +#define LD_MAX_VECTORS (sizeof (word) == 8 ? 48u : 28u) +#endif + +#define MAX_VECTORS (((uint64_t) 1) << LD_MAX_VECTORS) + +#define INVALID_VECTOR_ELEMENT UINT_MAX + +#define MAX_SECTOR MAX_SIZE_T + +typedef struct vector vector; +typedef struct vectors vectors; + +struct vectors { + unsigneds stack; + size_t usable; +}; + +struct vector { +#ifdef COMPACT + unsigned offset; + unsigned size; +#else + unsigned *begin; + unsigned *end; +#endif +}; + +struct kissat; + +#ifdef CHECK_VECTORS +void kissat_check_vectors (struct kissat *); +#else +#define kissat_check_vectors(...) \ + do { \ + } while (0) +#endif + +unsigned *kissat_enlarge_vector (struct kissat *, vector *); +void kissat_defrag_vectors (struct kissat *, size_t, vector *); +void kissat_remove_from_vector (struct kissat *, vector *, unsigned); +void kissat_resize_vector (struct kissat *, vector *, size_t); +void kissat_release_vectors (struct kissat *); + +#endif diff --git a/src/sat/kissat/vivify.c b/src/sat/kissat/vivify.c new file mode 100644 index 000000000..6627c640e --- /dev/null +++ b/src/sat/kissat/vivify.c @@ -0,0 +1,1480 @@ +#include "vivify.h" +#include "allocate.h" +#include "backtrack.h" +#include "collect.h" +#include "colors.h" +#include "decide.h" +#include "deduce.h" +#include "inline.h" +#include "print.h" +#include "promote.h" +#include "proprobe.h" +#include "rank.h" +#include "report.h" +#include "sort.h" +#include "terminate.h" +#include "tiers.h" +#include "trail.h" + +#include "cover.h" + +#include +#include + +static inline bool more_occurrences (unsigned *counts, unsigned a, + unsigned b) { + const unsigned s = counts[a], t = counts[b]; + return ((t - s) | ((b - a) & ~(s - t))) >> 31; +} + +#define MORE_OCCURRENCES(A, B) more_occurrences (counts, (A), (B)) + +static void vivify_sort_lits_by_counts (kissat *solver, size_t size, + unsigned *lits, unsigned *counts) { + SORT (unsigned, size, lits, MORE_OCCURRENCES); +} + +static void vivify_sort_stack_by_counts (kissat *solver, unsigneds *stack, + unsigned *counts) { + const size_t size = SIZE_STACK (*stack); + unsigned *lits = BEGIN_STACK (*stack); + vivify_sort_lits_by_counts (solver, size, lits, counts); +} + +static void vivify_sort_clause_by_counts (kissat *solver, clause *c, + unsigned *counts) { + vivify_sort_lits_by_counts (solver, c->size, c->lits, counts); +} + +static inline void count_literal (unsigned lit, unsigned *counts) { + counts[lit] += counts[lit] < (unsigned) INT_MAX; +} + +static void count_clause (clause *c, unsigned *counts) { + for (all_literals_in_clause (lit, c)) + count_literal (lit, counts); +} + +static bool simplify_vivification_candidate (kissat *solver, + clause *const c) { + assert (!solver->level); + CLEAR_STACK (solver->clause); + + const value *const values = solver->values; + + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value > 0) { + LOGCLS (c, "vivification %s satisfied candidate", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + return true; + } + if (!value) + PUSH_STACK (solver->clause, lit); + } + + unsigned non_false = SIZE_STACK (solver->clause); + assert (non_false <= c->size); + if (non_false == c->size) + return false; + + if (non_false == 2) { + const unsigned first = PEEK_STACK (solver->clause, 0); + const unsigned second = PEEK_STACK (solver->clause, 1); + LOGBINARY (first, second, "vivification shrunken candidate"); + kissat_new_binary_clause (solver, first, second); + kissat_mark_clause_as_garbage (solver, c); + return true; + } + + assert (2 < non_false); + + CHECK_AND_ADD_STACK (solver->clause); + ADD_STACK_TO_PROOF (solver->clause); + + REMOVE_CHECKER_CLAUSE (c); + DELETE_CLAUSE_FROM_PROOF (c); + + const unsigned old_size = c->size; + unsigned new_size = 0, *lits = c->lits; + for (unsigned i = 0; i < old_size; i++) { + const unsigned lit = lits[i]; + const value value = values[lit]; + assert (value <= 0); + if (!value) + lits[new_size++] = lit; + } + + assert (2 < new_size); + assert (new_size == non_false); + assert (new_size < old_size); + + c->size = new_size; + c->searched = 2; + + if (c->redundant && c->glue >= new_size) + kissat_promote_clause (solver, c, new_size - 1); + if (!c->shrunken) { + c->shrunken = true; + lits[old_size - 1] = INVALID_LIT; + } + + LOGCLS (c, "vivification shrunken candidate"); + LOG ("vivification candidate does not need to be simplified"); + + return false; +} + +static unsigned vivify_tier1_limit (kissat *solver) { + return GET_OPTION (vivifyfocusedtiers) ? solver->tier1[0] : TIER1; +} + +static unsigned vivify_tier2_limit (kissat *solver) { + return GET_OPTION (vivifyfocusedtiers) ? solver->tier2[0] : TIER2; +} + +#define COUNTREF_COUNTS 1 +#define LD_MAX_COUNTREF_SIZE 31 +#define MAX_COUNTREF_SIZE ((1u << LD_MAX_COUNTREF_SIZE) - 1) + +struct countref { + unsigned vivify : 1; + unsigned size : LD_MAX_COUNTREF_SIZE; + unsigned count[COUNTREF_COUNTS]; + reference ref; +}; + +typedef struct countref countref; +typedef STACK (countref) countrefs; + +struct vivifier { + kissat *solver; + unsigned *counts; + references schedule; + size_t scheduled, tried, vivified; + countrefs countrefs; + unsigneds sorted; +#ifndef QUIET + const char *mode; + const char *name; + char tag; +#endif + int tier; +}; + +typedef struct vivifier vivifier; + +static void init_vivifier (kissat *solver, vivifier *vivifier) { + vivifier->solver = solver; + vivifier->counts = kissat_calloc (solver, LITS, sizeof (unsigned)); + INIT_STACK (vivifier->schedule); + INIT_STACK (vivifier->countrefs); + INIT_STACK (vivifier->sorted); + LOG ("initialized vivifier"); +} + +static void set_vivifier_mode (vivifier *vivifier, int tier) { + vivifier->tier = tier; +#ifndef QUIET + switch (tier) { + case 1: + vivifier->mode = "vivify-tier1"; + vivifier->name = "tier1"; + vivifier->tag = 'u'; + break; + case 2: + vivifier->mode = "vivify-tier2"; + vivifier->name = "tier2"; + vivifier->tag = 'v'; + break; + case 3: + assert (tier == 3); + vivifier->mode = "vivify-tier3"; + vivifier->name = "tier3"; + vivifier->tag = 'w'; + break; + default: + assert (tier == 0); + vivifier->mode = "vivify-irredundant"; + vivifier->name = "irredundant"; + vivifier->tag = 'x'; + break; + } +#ifdef LOGGING + kissat *solver = vivifier->solver; + LOG ("set vivifier tier %d mode '%s' with tag '%c'", tier, vivifier->mode, + vivifier->tag); +#endif +#endif +} + +static void clear_vivifier (vivifier *vivifier) { + kissat *solver = vivifier->solver; + LOG ("clearing vivifier"); + unsigned *counts = vivifier->counts; + memset (counts, 0, LITS * sizeof *counts); + CLEAR_STACK (vivifier->schedule); + CLEAR_STACK (vivifier->countrefs); + CLEAR_STACK (vivifier->sorted); +} + +static void release_vivifier (vivifier *vivifier) { + kissat *solver = vivifier->solver; + LOG ("releasing vivifier"); + unsigned *counts = vivifier->counts; + kissat_dealloc (solver, counts, LITS, sizeof *counts); + RELEASE_STACK (vivifier->schedule); + RELEASE_STACK (vivifier->countrefs); + RELEASE_STACK (vivifier->sorted); +} + +static void schedule_vivification_candidates (vivifier *vivifier) { + kissat *solver = vivifier->solver; + LOG ("scheduling vivification candidates"); + int tier = vivifier->tier; + unsigned lower_glue_limit, upper_glue_limit; + unsigned tier1 = vivify_tier1_limit (solver); + unsigned tier2 = MAX (tier1, vivify_tier2_limit (solver)); + assert (tier1 <= tier2); + switch (tier) { + case 1: + lower_glue_limit = 0; + upper_glue_limit = tier1; + break; + case 2: + lower_glue_limit = tier1 < tier2 ? tier1 + 1 : 0; + upper_glue_limit = tier2; + break; + case 3: + lower_glue_limit = tier2 + 1; + upper_glue_limit = UINT_MAX; + break; + default: + assert (tier == 0); + lower_glue_limit = 0; + upper_glue_limit = UINT_MAX; + break; + } + assert (lower_glue_limit <= upper_glue_limit); + ward *const arena = BEGIN_STACK (solver->arena); + size_t prioritized = 0; + unsigned *counts = vivifier->counts; + references *schedule = &vivifier->schedule; + for (unsigned prioritize = 0; prioritize < 2; prioritize++) { + for (all_clauses (c)) { + if (c->garbage) + continue; + if (prioritize) + count_clause (c, counts); + if (tier) { + if (!c->redundant) + continue; + if (c->glue < lower_glue_limit) + continue; + if (c->glue > upper_glue_limit) + continue; + } else if (c->redundant) + continue; + if (c->vivify != prioritize) + continue; + if (simplify_vivification_candidate (solver, c)) + continue; + if (prioritize) + prioritized++; + const reference ref = (ward *) c - arena; + PUSH_STACK (*schedule, ref); + } + } + CLEAR_STACK (solver->clause); + size_t scheduled = SIZE_STACK (*schedule); + if (prioritized) { + kissat_phase (solver, vivifier->mode, GET (vivifications), + "prioritized %zu clauses %.0f%%", prioritized, + kissat_percent (prioritized, scheduled)); + } else { + kissat_phase (solver, vivifier->mode, GET (vivifications), + "prioritizing all %zu scheduled clauses", scheduled); + for (all_stack (reference, ref, *schedule)) { + clause *c = (clause *) (arena + ref); + assert (kissat_clause_in_arena (solver, c)); + c->vivify = true; + } + } + vivifier->scheduled = scheduled; + vivifier->tried = vivifier->vivified = 0; +} + +static inline bool worse_candidate (kissat *solver, unsigned *counts, + reference r, reference s) { + const clause *const c = kissat_dereference_clause (solver, r); + const clause *const d = kissat_dereference_clause (solver, s); + + if (!c->vivify && d->vivify) + return true; + + if (c->vivify && !d->vivify) + return false; + + unsigned const *p = BEGIN_LITS (c); + unsigned const *q = BEGIN_LITS (d); + const unsigned *const e = END_LITS (c); + const unsigned *const f = END_LITS (d); + + unsigned a = INVALID_LIT, b = INVALID_LIT; + + while (p != e && q != f) { + a = *p++; + b = *q++; + const unsigned u = counts[a]; + const unsigned v = counts[b]; + if (u < v) + return true; + if (u > v) + return false; + } + + if (p != e && q == f) + return true; + + if (p == e && q != f) + return false; + + assert (p == e && q == f); + + if (a < b) + return true; + if (a > b) + return false; + + return r < s; +} + +#define WORSE_CANDIDATE(A, B) worse_candidate (solver, counts, (A), (B)) + +static void +sort_vivification_candidates_after_sorting_literals (vivifier *vivifier) { + kissat *solver = vivifier->solver; + unsigned *counts = vivifier->counts; + references *schedule = &vivifier->schedule; + SORT_STACK (reference, *schedule, WORSE_CANDIDATE); +} + +static void sort_scheduled_candidate_literals (vivifier *vivifier) { + kissat *solver = vivifier->solver; + unsigned *counts = vivifier->counts; + references *schedule = &vivifier->schedule; + for (all_stack (reference, ref, *schedule)) { + clause *c = kissat_dereference_clause (solver, ref); + vivify_sort_clause_by_counts (solver, c, counts); + } +} + +static inline void init_countref (countref *cr, clause *c, reference ref, + unsigned *counts) { + assert (COUNTREF_COUNTS <= c->size); + assert (c->size <= MAX_COUNTREF_SIZE); + cr->size = c->size; + cr->vivify = c->vivify; + cr->ref = ref; + unsigned lits[COUNTREF_COUNTS]; + for (unsigned i = 0; i != COUNTREF_COUNTS; i++) { + unsigned best = INVALID_LIT; + unsigned best_count = 0; + for (all_literals_in_clause (lit, c)) { + for (unsigned j = 0; j != i; j++) + if (lits[j] == lit) + goto CONTINUE_WITH_NEXT_LITERAL; + const unsigned lit_count = counts[lit]; + assert (lit_count); + if (lit_count <= best_count) + continue; + best_count = lit_count; + best = lit; + CONTINUE_WITH_NEXT_LITERAL:; + } + assert (best != INVALID_LIT); + assert (best_count); + cr->count[i] = best_count; + lits[i] = best; + } +} + +static void init_countrefs (vivifier *vivifier) { + kissat *const solver = vivifier->solver; + references *schedule = &vivifier->schedule; + countrefs *countrefs = &vivifier->countrefs; + unsigned *counts = vivifier->counts; + assert (EMPTY_STACK (*countrefs)); + for (all_stack (reference, ref, *schedule)) { + clause *c = kissat_dereference_clause (solver, ref); + countref countref; + init_countref (&countref, c, ref, counts); + PUSH_STACK (*countrefs, countref); + } + RELEASE_STACK (*schedule); +} + +#define RANK_COUNTREF_BY_INVERSE_SIZE(CR) ((unsigned) (~(CR).size)) +#define RANK_COUNTREF_BY_VIVIFY(CR) ((unsigned) ((CR).vivify)) +#define RANK_COUNTREF_BY_COUNT(CR) \ + ((unsigned) ((CR).count[COUNTREF_COUNTS - 1 - i])) + +static void rank_vivification_candidates (vivifier *vivifier) { + kissat *solver = vivifier->solver; + countrefs *countrefs = &vivifier->countrefs; + RADIX_STACK (countref, unsigned, *countrefs, + RANK_COUNTREF_BY_INVERSE_SIZE); + for (unsigned i = 0; i != COUNTREF_COUNTS; i++) + RADIX_STACK (countref, unsigned, *countrefs, RANK_COUNTREF_BY_COUNT); + RADIX_STACK (countref, unsigned, *countrefs, RANK_COUNTREF_BY_VIVIFY); +} + +static void copy_countrefs (vivifier *vivifier) { + kissat *solver = vivifier->solver; + references *schedule = &vivifier->schedule; + countrefs *countrefs = &vivifier->countrefs; + assert (EMPTY_STACK (*schedule)); + for (all_stack (countref, cr, *countrefs)) + PUSH_STACK (*schedule, cr.ref); + RELEASE_STACK (*countrefs); +} + +static void sort_vivification_candidates (vivifier *vivifier) { +#ifndef QUIET + kissat *solver = vivifier->solver; +#endif + START (vivifysort); + if (vivifier->tier) { + kissat_extremely_verbose ( + solver, "sorting %s vivification candidates precisely", + vivifier->name); + sort_scheduled_candidate_literals (vivifier); + sort_vivification_candidates_after_sorting_literals (vivifier); + } else { + kissat_extremely_verbose ( + solver, + "sorting %s vivification candidates imprecisely " + "by first %u literals", + vivifier->name, (unsigned) COUNTREF_COUNTS); + init_countrefs (vivifier); + rank_vivification_candidates (vivifier); + copy_countrefs (vivifier); + } + STOP (vivifysort); +} + +static void vivify_deduce (vivifier *vivifier, clause *candidate, + clause *conflict, unsigned implied, + clause **subsuming_ptr, bool *redundant_ptr) { + kissat *solver = vivifier->solver; + LOG ("starting vivification conflict analysis"); + bool redundant = false; + bool subsumes = false; + + assert (solver->level); + assert (EMPTY_STACK (solver->clause)); + assert (EMPTY_STACK (solver->analyzed)); + + if (implied != INVALID_LIT) { + unsigned not_implied = NOT (implied); + LOG ("vivify analyzing %s", LOGLIT (not_implied)); + assigned *const a = ASSIGNED (not_implied); + assert (a->level); + assert (!a->analyzed); + a->analyzed = true; + PUSH_STACK (solver->analyzed, not_implied); + PUSH_STACK (solver->clause, implied); + } else { + clause *reason = conflict ? conflict : candidate; + assert (reason), assert (!reason->garbage); + if (reason->redundant) + redundant = true; + subsumes = (reason != candidate); + for (all_literals_in_clause (other, reason)) { + assert (VALUE (other) < 0); + const value value = kissat_fixed (solver, other); + if (value < 0) + continue; + LOG ("vivify analyzing %s", LOGLIT (other)); + assert (!value); + assigned *const a = ASSIGNED (other); + assert (a->level); + assert (!a->analyzed); + a->analyzed = true; + PUSH_STACK (solver->analyzed, other); + if (solver->marks[other] <= 0) + subsumes = false; + } + if (reason != candidate && reason->redundant) + kissat_recompute_and_promote (solver, reason); + if (subsumes) { + LOGCLS (candidate, "vivify subsumed"); + LOGCLS (reason, "vivify subsuming"); + *subsuming_ptr = reason; + return; + } + } + + const mark *const marks = solver->marks; + size_t analyzed = 0; + while (analyzed < SIZE_STACK (solver->analyzed)) { + const unsigned not_lit = PEEK_STACK (solver->analyzed, analyzed); + const unsigned lit = NOT (not_lit); + assert (VALUE (lit) > 0); + analyzed++; + assigned *a = ASSIGNED (lit); + assert (a->level); + assert (a->analyzed); + if (a->reason == DECISION_REASON) { + LOG ("vivify analyzing decision %s", LOGLIT (not_lit)); + PUSH_STACK (solver->clause, not_lit); + } else if (a->binary) { + const unsigned other = a->reason; + if (marks[lit] > 0 && marks[other] > 0) { + LOGCLS (candidate, "vivify subsumed"); + LOGBINARY (lit, other, "vivify subsuming"); // Might be jumped! + *subsuming_ptr = kissat_binary_conflict (solver, lit, other); + return; + } + assert (VALUE (other) < 0); + assigned *b = ASSIGNED (other); + assert (b->level); + if (b->analyzed) + continue; + LOGBINARY (lit, other, "vivify analyzing %s reason", LOGLIT (lit)); + b->analyzed = true; + PUSH_STACK (solver->analyzed, other); + } else { + const reference ref = a->reason; + LOGREF (ref, "vivify analyzing %s reason", LOGLIT (lit)); + clause *reason = kissat_dereference_clause (solver, ref); + assert (reason != candidate); + if (reason->redundant) + redundant = true; + subsumes = true; + for (all_literals_in_clause (other, reason)) { + if (marks[other] <= 0) + subsumes = false; + if (other == lit) + continue; + assert (other != not_lit); + assert (VALUE (other) < 0); + assigned *b = ASSIGNED (other); + if (!b->level) + continue; + if (b->analyzed) + continue; + b->analyzed = true; + PUSH_STACK (solver->analyzed, other); + } + if (reason->redundant) + kissat_recompute_and_promote (solver, reason); + if (subsumes) { + LOGCLS (candidate, "vivify subsumed"); + LOGCLS (reason, "vivify subsuming"); + *subsuming_ptr = reason; + return; + } + } + } + + LOG ("vivification analysis %s redundant clause", + redundant ? "used" : "without"); + LOGTMP ("vivification analysis"); + + *redundant_ptr = redundant; +} + +static void reset_vivify_analyzed (vivifier *vivifier) { + kissat *solver = vivifier->solver; + LOG ("reset vivification conflict analysis"); + struct assigned *assigned = solver->assigned; + for (all_stack (unsigned, lit, solver->analyzed)) { + const unsigned idx = IDX (lit); + struct assigned *a = assigned + idx; + a->analyzed = false; + } + CLEAR_STACK (solver->analyzed); + CLEAR_STACK (solver->clause); +} + +static bool vivify_shrinkable (kissat *solver, unsigneds *sorted, + clause *conflict) { + assert (SIZE_STACK (solver->clause) <= SIZE_STACK (*sorted)); + if (SIZE_STACK (solver->clause) == SIZE_STACK (*sorted)) + return false; + const assigned *const assigned = solver->assigned; + const value *const values = solver->values; + unsigned count_implied = 0; + for (all_stack (unsigned, lit, *sorted)) { + value value = values[lit]; + if (!value) { + LOG ("vivification unassigned %s thus shrinking", LOGLIT (lit)); + return true; + } + if (value > 0) { + LOG ("vivification implied satisfied %s", LOGLIT (lit)); + if (conflict) { + LOG ("at least one implied literal with conflict thus shrinking"); + return true; + } + if (count_implied++) { + LOG ("at least two implied literals thus shrinking"); + return true; + } + } else { + assert (value < 0); + const unsigned idx = IDX (lit); + const struct assigned *const a = assigned + idx; + assert (a->level); + if (!a->analyzed) { + LOG ("vivification non-analyzed %s thus shrinking", LOGLIT (lit)); + return true; + } + if (a->reason != DECISION_REASON) { + LOG ("vivification implied falsified %s thus shrinking", + LOGLIT (lit)); + return true; + } + } + } + LOG ("vivification learned clause not shrinkable"); + return false; +} + +static void vivify_learn_unit (kissat *solver, clause *c) { + LOG ("vivification learns unit clause"); + assert (SIZE_STACK (solver->clause) == 1); + kissat_backtrack_without_updating_phases (solver, 0); + const unsigned unit = PEEK_STACK (solver->clause, 0); + kissat_learned_unit (solver, unit); + solver->iterating = true; + kissat_mark_clause_as_garbage (solver, c); + assert (!solver->level); + clause *conflict = kissat_probing_propagate (solver, 0, true); + assert (!conflict || solver->inconsistent); + INC (vivify_units); + (void) conflict; +} + +static void vivify_learn_binary (kissat *solver, clause *c) { + LOG ("vivification learns binary clause"); + kissat_backtrack_without_updating_phases (solver, 0); + assert (SIZE_STACK (solver->clause) == 2); + if (c->redundant) + (void) kissat_new_redundant_clause (solver, 1); + else + (void) kissat_new_irredundant_clause (solver); + kissat_mark_clause_as_garbage (solver, c); +} + +static void swap_first_literal_with_best_watch (kissat *solver, + unsigned *lits, + unsigned size) { + assert (size); + unsigned *best_ptr = lits; + unsigned first = *best_ptr, best = first; + signed char value = VALUE (best); + unsigned best_level = LEVEL (best); + const unsigned *const end = lits + size; + for (unsigned *p = lits + 1; value < 0 && p != end; p++) { + unsigned lit = *p; + const signed char value = VALUE (lit); + if (value < 0) { + const unsigned level = LEVEL (lit); + if (level <= best_level) + continue; + best_level = level; + } + best_ptr = p; + best = lit; + } + if (best_ptr == lits) + return; + LOG ("better watch %s instead of %s", LOGLIT (best), LOGLIT (first)); + *best_ptr = first; + *lits = best; +} + +static void vivify_unwatch_clause (kissat *solver, clause *c) { + unsigned *lits = c->lits; + const reference ref = kissat_reference_clause (solver, c); + kissat_unwatch_blocking (solver, lits[0], ref); + kissat_unwatch_blocking (solver, lits[1], ref); +} + +static void vivify_watch_clause (kissat *solver, clause *c) { + unsigned size = c->size; + unsigned *lits = c->lits; + const reference ref = kissat_reference_clause (solver, c); + swap_first_literal_with_best_watch (solver, lits, size); + swap_first_literal_with_best_watch (solver, lits + 1, size - 1); + kissat_watch_blocking (solver, lits[0], lits[1], ref); + kissat_watch_blocking (solver, lits[1], lits[0], ref); +} + +static void vivify_learn_large (kissat *solver, clause *c, + unsigned implied) { + assert (!c->garbage); + LOG ("vivification learns large clause"); + + CHECK_AND_ADD_STACK (solver->clause); + ADD_STACK_TO_PROOF (solver->clause); + + REMOVE_CHECKER_CLAUSE (c); + DELETE_CLAUSE_FROM_PROOF (c); + + vivify_unwatch_clause (solver, c); + + bool irredundant = !c->redundant; + + if (irredundant) { // TODO this could be made more precise. + for (all_literals_in_clause (lit, c)) + kissat_mark_removed_literal (solver, lit); + } + + unsigneds *learned = &solver->clause; + const unsigned old_size = c->size; + const unsigned new_size = SIZE_STACK (*learned); + assert (new_size <= old_size); + + unsigned *lits = c->lits; + memcpy (lits, BEGIN_STACK (*learned), new_size * sizeof *lits); + + if (irredundant) + for (all_literals_in_clause (lit, c)) + kissat_mark_added_literal (solver, lit); + + assert (new_size < old_size); + if (!c->shrunken) { + c->shrunken = true; + lits[old_size - 1] = INVALID_LIT; + } + c->size = new_size; + if (!irredundant && c->glue >= new_size) + kissat_promote_clause (solver, c, new_size - 1); + c->searched = 2; + + if (implied == INVALID_LIT) { + LOGCLS (c, "vivified shrunken after conflict"); + kissat_backtrack_without_updating_phases (solver, new_size - 2); + } else { + LOGCLS (c, "vivified shrunken after implied"); + assert (solver->level >= new_size - 1); + } + + vivify_watch_clause (solver, c); +} + +static void vivify_learn (kissat *solver, clause *c, unsigned implied) { + size_t size = SIZE_STACK (solver->clause); + if (size == 1) + vivify_learn_unit (solver, c); + else if (size == 2) + vivify_learn_binary (solver, c); + else + vivify_learn_large (solver, c, implied); +} + +static void binary_strengthen_after_instantiation (kissat *solver, + clause *c, + unsigned remove) { + LOG ("vivification instantiation yields binary clause"); + assert (solver->level == 3); + + unsigned first = INVALID_LIT, second = INVALID_LIT; + for (all_literals_in_clause (lit, c)) + if (lit != remove) { + assert (VALUE (lit) < 0); + if (LEVEL (lit)) { + if (first == INVALID_LIT) + first = lit; + else { + assert (second == INVALID_LIT); + second = lit; + } + } + } + assert (first != INVALID_LIT); + assert (second != INVALID_LIT); + LOGBINARY (first, second, "vivified strengthened through instantiation"); + CLEAR_STACK (solver->clause); + PUSH_STACK (solver->clause, first); + PUSH_STACK (solver->clause, second); + if (c->redundant) + (void) kissat_new_redundant_clause (solver, 1); + else + (void) kissat_new_irredundant_clause (solver); + + kissat_mark_clause_as_garbage (solver, c); + kissat_backtrack_without_updating_phases (solver, 0); +} + +static void large_strengthen_after_instantiation (kissat *solver, clause *c, + unsigned remove) { + LOG ("vivification instantiation yields large clause"); + assert (solver->level > 3); + + SHRINK_CLAUSE_IN_PROOF (c, remove, INVALID_LIT); + CHECK_SHRINK_CLAUSE (c, remove, INVALID_LIT); + + vivify_unwatch_clause (solver, c); + + bool irredundant = !c->redundant; + const unsigned old_size = c->size; + assert (old_size > 3); + unsigned new_size = 0, *lits = c->lits; + for (unsigned i = 0; i != old_size; i++) { + const unsigned lit = lits[i]; + if (lit == remove) { + if (irredundant) + kissat_mark_removed_literal (solver, lit); + } else if (kissat_fixed (solver, lit) >= 0) { + lits[new_size++] = lit; + if (irredundant) + kissat_mark_added_literal (solver, lit); + } + } + assert (2 < new_size); + assert (new_size < old_size); + if (!c->shrunken) { + c->shrunken = true; + lits[old_size - 1] = INVALID_LIT; + } + c->size = new_size; + if (!irredundant && c->glue >= new_size) + kissat_promote_clause (solver, c, new_size - 1); + c->searched = 2; + LOGCLS (c, "vivified strengthened through instantiation"); + + kissat_backtrack_without_updating_phases (solver, solver->level - 2); + vivify_watch_clause (solver, c); +} + +static void vivify_strengthen_after_instantiation (kissat *solver, + clause *c, + unsigned remove) { + assert (solver->level >= 3); + assert (VALUE (remove) > 0); + assert (LEVEL (remove) == solver->level); + + if (solver->level == 3) + binary_strengthen_after_instantiation (solver, c, remove); + else + large_strengthen_after_instantiation (solver, c, remove); +} + +static void vivify_mark_sorted_literals (vivifier *vivifier) { + kissat *solver = vivifier->solver; + LOG ("marking sorted literals"); + unsigneds *sorted = &vivifier->sorted; + value *marks = solver->marks; + for (all_stack (unsigned, lit, *sorted)) + assert (!marks[lit]), marks[lit] = 1, marks[NOT (lit)] = -1; +} + +static void vivify_unmark_sorted_literals (vivifier *vivifier) { + kissat *solver = vivifier->solver; + LOG ("unmarking sorted literals"); + unsigneds *sorted = &vivifier->sorted; + value *marks = solver->marks; + for (all_stack (unsigned, lit, *sorted)) + assert (solver->marks[lit] > 0), assert (marks[NOT (lit)] < 0), + marks[lit] = marks[NOT (lit)] = 0; +} + +static void reestablish_watch_invariant_for_candidate (kissat *solver, + clause *candidate) { + if (!solver->level) + return; + if (candidate->garbage) + return; + unsigned *lits = candidate->lits; + unsigned first = lits[0]; + unsigned second = lits[1]; + const signed char first_val = VALUE (first); + const signed char second_val = VALUE (second); + unsigned first_level = first_val ? LEVEL (first) : 0; + unsigned second_level = second_val ? LEVEL (second) : 0; + unsigned new_level; + if (first_val >= 0 && second_val >= 0) + return; + if (first_val < 0 && !second_val) + new_level = first_level; + else if (first_val < 0 && second_val > 0) { + if (first_level >= second_level) + return; + new_level = first_level; + } else if (second_val < 0 && !first_val) + new_level = second_level; + else if (second_val < 0 && first_val > 0) { + if (second_level >= first_level) + return; + new_level = second_level; + } else { + assert (first_val < 0), assert (second_val < 0); + new_level = MIN (first_level, second_level); + } + assert (new_level); + LOGCLS (candidate, "reestablish watch invariant by backtracking to %u", + new_level - 1); + kissat_backtrack_without_updating_phases (solver, new_level - 1); +} + +static bool vivify_clause (vivifier *vivifier, clause *candidate) { + + assert (!candidate->garbage); + + kissat *solver = vivifier->solver; + + assert (solver->probing); + assert (solver->watching); + assert (!solver->inconsistent); + + LOGCLS (candidate, "vivifying candidate"); + LOGCOUNTEDCLS (candidate, vivifier->counts, + "vivifying unsorted counted candidate"); + + unsigneds *sorted = &vivifier->sorted; + CLEAR_STACK (*sorted); + + for (all_literals_in_clause (lit, candidate)) { + const value value = kissat_fixed (solver, lit); + if (value < 0) + continue; + if (value > 0) { + LOGCLS (candidate, "%s satisfied", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, candidate); + return false; + } + PUSH_STACK (*sorted, lit); + } + + assert (!candidate->garbage); + + const unsigned non_false = SIZE_STACK (*sorted); + + assert (1 < non_false); + assert (non_false <= candidate->size); + +#ifdef LOGGING + if (non_false == candidate->size) + LOG ("all literals root level unassigned"); + else + LOG ("found %u root level non-falsified literals", non_false); +#endif + + if (non_false == 2) { + LOGCLS (candidate, "skipping actually binary"); + return false; + } + + INC (vivify_checks); + + unsigned unit = INVALID_LIT; + for (all_literals_in_clause (lit, candidate)) { + const value value = VALUE (lit); + if (value < 0) + continue; + if (!value) { + unit = INVALID_LIT; + break; + } + assert (value > 0); + if (unit != INVALID_LIT) { + unit = INVALID_LIT; + break; + } + unit = lit; + } + reference cand_ref = kissat_reference_clause (solver, candidate); + if (unit != INVALID_LIT) { + assigned *a = ASSIGNED (unit); + assert (a->level); + if (a->binary) + unit = INVALID_LIT; + else { + if (a->reason != cand_ref) + unit = INVALID_LIT; + } + } + if (unit == INVALID_LIT) + LOG ("non-reason candidate clause"); + else { + LOG ("candidate is the reason of %s", LOGLIT (unit)); + const unsigned level = LEVEL (unit); + assert (level > 0); + LOG ("forced to backtrack to level %u", level - 1); + kissat_backtrack_without_updating_phases (solver, level - 1); + } + + assert (EMPTY_STACK (solver->analyzed)); + assert (EMPTY_STACK (solver->clause)); + + unsigned *counts = vivifier->counts; + vivify_sort_stack_by_counts (solver, sorted, counts); + +#ifdef LOGGING + if (candidate->redundant) + LOGCOUNTEDREFLITS (cand_ref, SIZE_STACK (*sorted), sorted->begin, + counts, "vivifying sorted redundant glue %u", + candidate->glue); + else + LOGCOUNTEDREFLITS (cand_ref, SIZE_STACK (*sorted), sorted->begin, + counts, "vivifying sorted irredundant"); +#endif + + vivify_mark_sorted_literals (vivifier); + + unsigned implied = INVALID_LIT; + clause *conflict = 0; + unsigned level = 0; + + for (all_stack (unsigned, lit, *sorted)) { + if (level++ < solver->level) { + frame *frame = &FRAME (level); + const unsigned not_lit = NOT (lit); + if (frame->decision == not_lit) { + LOG ("reusing assumption %s", LOGLIT (not_lit)); + INC (vivify_reused); + INC (vivify_probes); + assert (VALUE (lit) < 0); + continue; + } + + LOG ("forced to backtrack to decision level %u", level - 1); + kissat_backtrack_without_updating_phases (solver, level - 1); + } + + const value value = VALUE (lit); + assert (!value || LEVEL (lit) <= level); + + if (value < 0) { + assert (LEVEL (lit)); + LOG ("literal %s already falsified", LOGLIT (lit)); + continue; + } + + if (value > 0) { + assert (LEVEL (lit)); + LOG ("literal %s already initially satisfied", LOGLIT (lit)); + implied = lit; + break; + } + + assert (!value); + + LOG ("literal %s unassigned", LOGLIT (lit)); + const unsigned not_lit = NOT (lit); + INC (vivify_probes); + + kissat_internal_assume (solver, not_lit); + assert (solver->level >= 1); + + const unsigned *p = solver->propagate; + conflict = kissat_probing_propagate (solver, candidate, true); + if (conflict) + break; + + const unsigned *end = END_ARRAY (solver->trail); + const signed char *const marks = solver->marks; + while (p != end) { + const unsigned other = *p++; + const signed char mark = marks[other]; + if (mark > 0) { + LOG ("literal %s already implied satisfied", LOGLIT (other)); + implied = other; + goto EXIT_ASSUMPTION_LOOP; + } + } + } +EXIT_ASSUMPTION_LOOP:; + +#ifdef LOGGING + if (!conflict && implied == INVALID_LIT) + LOG ("vivification assumptions propagate without conflict nor " + "producing an implied literal"); +#endif + assert (!conflict || implied == INVALID_LIT); + + if (implied != INVALID_LIT) { + unsigned better_implied = INVALID_LIT; + unsigned better_implied_trail = INVALID_TRAIL; + for (all_stack (unsigned, lit, *sorted)) { + if (VALUE (lit) <= 0) + continue; + unsigned lit_trail = TRAIL (lit); + if (lit_trail > better_implied_trail) + continue; + better_implied_trail = lit_trail; + better_implied = lit; + } + if (better_implied != implied) { + LOG ("better implied satisfied literal %s at level %u", + LOGLIT (better_implied), LEVEL (better_implied)); + implied = better_implied; + } + } + + unsigned level_after_assumptions = solver->level; + assert (level_after_assumptions); + + clause *subsuming = 0; + bool redundant = false; + vivify_deduce (vivifier, candidate, conflict, implied, &subsuming, + &redundant); + + vivify_unmark_sorted_literals (vivifier); + + bool res; + + if (subsuming) { + assert (!subsuming->garbage); + if (candidate->redundant) { + LOGCLS (candidate, "vivification subsumed"); + kissat_mark_clause_as_garbage (solver, candidate); + if (subsuming->redundant && candidate->glue < subsuming->glue) { + LOG ("vivify candidate with smaller glue than subsuming clause"); + kissat_promote_clause (solver, subsuming, candidate->glue); + } + INC (vivified_subred); + INC (vivified_subsumed); + res = true; + } else if (!subsuming->redundant) { + assert (!candidate->redundant); + LOGCLS (candidate, "vivification subsumed"); + kissat_mark_clause_as_garbage (solver, candidate); + INC (vivified_subirr); + INC (vivified_subsumed); + res = true; + } else { + assert (!candidate->redundant); + assert (subsuming->redundant); + LOGCLS (candidate, "vivification subsumed"); + kissat_mark_clause_as_garbage (solver, candidate); + subsuming->redundant = false; + assert (solver->statistics.clauses_redundant); + solver->statistics.clauses_redundant--; + assert (solver->statistics.clauses_irredundant < UINT64_MAX); + solver->statistics.clauses_irredundant++; + INC (vivified_promoted); + LOGCLS (subsuming, "vivification promoted from redundant to"); + kissat_update_last_irredundant (solver, subsuming); + kissat_mark_added_literals (solver, subsuming->size, subsuming->lits); + INC (vivified_subirr); + INC (vivified_subsumed); + res = true; + } + } else if (vivify_shrinkable (solver, sorted, conflict)) { + vivify_learn (solver, candidate, implied); + INC (vivified_shrunken); + if (candidate->redundant) + INC (vivified_shrunkred); + else + INC (vivified_shrunkirr); + res = true; + } else if (implied != INVALID_LIT && candidate->redundant) { + LOGCLS (candidate, "vivification implied"); + kissat_mark_clause_as_garbage (solver, candidate); + INC (vivified_implied); + res = true; + } else if ((conflict || implied != INVALID_LIT) && + !candidate->redundant && !redundant) { + LOGCLS (candidate, "vivification asymmetric tautology"); + kissat_mark_clause_as_garbage (solver, candidate); + INC (vivified_asym); + res = true; + } else if (implied != INVALID_LIT) { + LOGCLS (candidate, + "no vivification instantiation with implied literal %s", + LOGLIT (implied)); + assert (!candidate->redundant); + assert (redundant); + res = false; + } else { + assert (solver->level > 2); + assert (solver->level == SIZE_STACK (*sorted)); + unsigned lit = TOP_STACK (*sorted); + assert (VALUE (lit) < 0); + assert (LEVEL (lit) == solver->level); + LOG ("trying vivification instantiation of %s#%u respectively %s#%u", + LOGLIT (lit), counts[lit], LOGLIT (NOT (lit)), counts[NOT (lit)]); + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + conflict = 0; + assert (!VALUE (lit)); + kissat_internal_assume (solver, lit); + LOGCLS (candidate, "vivification instantiation candidate"); + conflict = kissat_probing_propagate (solver, candidate, true); + if (conflict) { + LOG ("vivification instantiation succeeded"); + vivify_strengthen_after_instantiation (solver, candidate, lit); + INC (vivified_instantiated); + if (candidate->redundant) + INC (vivified_instred); + else + INC (vivified_instirr); + res = true; + } else { + LOG ("vivification instantiation failed"); + kissat_backtrack_without_updating_phases (solver, solver->level - 2); + res = false; + } + } + + reset_vivify_analyzed (vivifier); + if (conflict && solver->level == level_after_assumptions) { + LOG ("forcing backtracking at least one level after conflict"); + kissat_backtrack_without_updating_phases (solver, solver->level - 1); + } + reestablish_watch_invariant_for_candidate (solver, candidate); + + if (res) { + INC (vivified); + switch (vivifier->tier) { + case 1: + INC (vivified_tier1); + break; + case 2: + INC (vivified_tier2); + break; + case 3: + INC (vivified_tier3); + break; + default: + assert (vivifier->tier == 0); + INC (vivified_irredundant); + break; + } + } + + LOG ("vivification %s", res ? "succeeded" : "failed"); + return res; +} + +static void vivify_round (vivifier *vivifier, uint64_t limit) { + int tier = vivifier->tier; + kissat *solver = vivifier->solver; + + if (tier && !REDUNDANT_CLAUSES) + return; + + assert (0 <= tier && tier <= 3); + assert (solver->watching); + assert (solver->probing); + + kissat_flush_large_watches (solver); + + schedule_vivification_candidates (vivifier); + if (GET_OPTION (vivifysort)) { + if (tier || IRREDUNDANT_CLAUSES / 10 <= REDUNDANT_CLAUSES) + sort_vivification_candidates (vivifier); + else + kissat_extremely_verbose ( + solver, "not sorting %s vivification candidates", vivifier->name); + } + + kissat_watch_large_clauses (solver); +#ifndef QUIET + uint64_t start = solver->statistics.probing_ticks; + uint64_t delta = limit - start; + kissat_very_verbose (solver, + "vivification %s effort limit %" PRIu64 " = %" PRIu64 + " + %" PRIu64 " 'probing_ticks'", + vivifier->name, limit, start, delta); + const size_t total = tier ? REDUNDANT_CLAUSES : IRREDUNDANT_CLAUSES; + const size_t scheduled = SIZE_STACK (vivifier->schedule); + kissat_phase (solver, vivifier->mode, GET (vivifications), + "scheduled %zu clauses %.0f%% of %zu", scheduled, + kissat_percent (scheduled, total), total); +#endif + assert (!vivifier->vivified); + assert (!vivifier->tried); + while (!EMPTY_STACK (vivifier->schedule)) { + const uint64_t probing_ticks = solver->statistics.probing_ticks; + if (probing_ticks > limit) { + kissat_extremely_verbose (solver, + "vivification %s ticks limit %" PRIu64 + " hit after %" PRIu64 " 'probing_ticks'", + vivifier->name, limit, probing_ticks); + break; + } + if (TERMINATED (vivify_terminated_1)) + break; + const reference ref = POP_STACK (vivifier->schedule); + clause *candidate = kissat_dereference_clause (solver, ref); + assert (!candidate->garbage); + vivifier->tried++; + if (vivify_clause (vivifier, candidate)) + vivifier->vivified++; + if (solver->inconsistent) + break; + candidate->vivify = false; + } + if (solver->level) + kissat_backtrack_without_updating_phases (solver, 0); +#ifndef QUIET + kissat_phase (solver, vivifier->mode, GET (vivifications), + "vivified %zu clauses %.0f%% out of %zu tried", + vivifier->vivified, + kissat_percent (vivifier->vivified, vivifier->tried), + vivifier->tried); + if (!solver->inconsistent) { + size_t remain = SIZE_STACK (vivifier->schedule); + if (remain) { + kissat_phase (solver, vivifier->mode, GET (vivifications), + "%zu clauses remain %.0f%% out of %zu scheduled", + remain, kissat_percent (remain, scheduled), scheduled); + + ward *const arena = BEGIN_STACK (solver->arena); + size_t prioritized = 0; + while (!EMPTY_STACK (vivifier->schedule)) { + const reference ref = POP_STACK (vivifier->schedule); + clause *c = (clause *) (arena + ref); + if (c->vivify) + prioritized++; + } + if (!prioritized) + kissat_phase (solver, vivifier->mode, GET (vivifications), + "no remaining prioritized clauses"); + else + kissat_phase (solver, vivifier->mode, GET (vivifications), + "keeping all %zu remaining clauses " + "prioritized %.0f%%", + prioritized, kissat_percent (prioritized, remain)); + } else + kissat_phase (solver, vivifier->mode, GET (vivifications), + "no untried clauses remain"); + } +#endif + REPORT (!vivifier->vivified, vivifier->tag); +} + +static void vivify_tier1 (vivifier *vivifier, uint64_t limit) { +#ifndef QUIET + kissat *solver = vivifier->solver; +#endif + START (vivify1); + set_vivifier_mode (vivifier, 1); + vivify_round (vivifier, limit); + STOP (vivify1); +} + +static void vivify_tier2 (vivifier *vivifier, uint64_t limit) { +#ifndef QUIET + kissat *solver = vivifier->solver; +#endif + START (vivify2); + clear_vivifier (vivifier); + set_vivifier_mode (vivifier, 2); + vivify_round (vivifier, limit); + STOP (vivify2); +} + +static void vivify_tier3 (vivifier *vivifier, uint64_t limit) { +#ifndef QUIET + kissat *solver = vivifier->solver; +#endif + START (vivify3); + clear_vivifier (vivifier); + set_vivifier_mode (vivifier, 3); + vivify_round (vivifier, limit); + STOP (vivify3); +} + +static void vivify_irredundant (vivifier *vivifier, uint64_t limit) { +#ifndef QUIET + kissat *solver = vivifier->solver; +#endif + START (vivify0); + clear_vivifier (vivifier); + set_vivifier_mode (vivifier, 0); + vivify_round (vivifier, limit); + STOP (vivify0); +} + +void kissat_vivify (kissat *solver) { + if (solver->inconsistent) + return; + assert (!solver->level); + assert (solver->probing); + assert (solver->watching); + if (!GET_OPTION (vivify)) + return; + if (TERMINATED (vivify_terminated_2)) + return; + double irr_budget = DELAYING (vivifyirr) ? 0 : GET_OPTION (vivifyirr); + double tier1_budget = GET_OPTION (vivifytier1); + double tier2_budget = GET_OPTION (vivifytier2); + double tier3_buget = GET_OPTION (vivifytier3); + if (!REDUNDANT_CLAUSES) + tier1_budget = tier2_budget = tier3_buget = 0; + double sum = irr_budget + tier1_budget + tier2_budget + tier3_buget; + if (!sum) + return; + + START (vivify); + INC (vivifications); +#if !defined(NDEBUG) || defined(METRICS) + assert (!solver->vivifying); + solver->vivifying = true; +#endif + + SET_EFFORT_LIMIT (limit, vivify, probing_ticks); + const uint64_t total = limit - solver->statistics.probing_ticks; + limit = solver->statistics.probing_ticks; + unsigned tier1_limit = vivify_tier1_limit (solver); + unsigned tier2_limit = vivify_tier2_limit (solver); + if (tier1_budget && tier2_budget && tier1_limit == tier2_limit) { + tier1_budget += tier2_budget, tier2_budget = 0; + LOG ("vivification tier1 matches tier2 " + "thus using tier2 budget for tier1"); + } + + { + vivifier vivifier; + init_vivifier (solver, &vivifier); + if (tier1_budget) { + limit += (total * tier1_budget) / sum; + vivify_tier1 (&vivifier, limit); + } + if (tier2_budget && !solver->inconsistent && + !TERMINATED (vivify_terminated_3)) { + limit += (total * tier2_budget) / sum; + vivify_tier2 (&vivifier, limit); + } + if (tier3_buget && !solver->inconsistent && + !TERMINATED (vivify_terminated_4)) { + limit += (total * tier3_buget) / sum; + vivify_tier3 (&vivifier, limit); + } + if (irr_budget && !solver->inconsistent && + !TERMINATED (vivify_terminated_5)) { + limit += (total * irr_budget) / sum; + vivify_irredundant (&vivifier, limit); + if (kissat_average (vivifier.vivified, vivifier.tried) < 0.01) + BUMP_DELAY (vivifyirr); + else + REDUCE_DELAY (vivifyirr); + } + release_vivifier (&vivifier); + } + +#ifndef QUIET + if (solver->statistics.probing_ticks < limit) { + uint64_t delta = limit - solver->statistics.probing_ticks; + kissat_phase (solver, "vivify-limit", GET (vivifications), + "has %" PRIu64 " ticks left %.2f%%", delta, + kissat_percent (delta, total)); + } else { + uint64_t delta = solver->statistics.probing_ticks - limit; + kissat_phase (solver, "vivify-limit", GET (vivifications), + "exceeded by %" PRIu64 " ticks %.2f%%", delta, + kissat_percent (delta, total)); + } +#endif +#if !defined(NDEBUG) || defined(METRICS) + assert (solver->vivifying); + solver->vivifying = false; +#endif + STOP (vivify); +} diff --git a/src/sat/kissat/vivify.h b/src/sat/kissat/vivify.h new file mode 100644 index 000000000..a83ac9303 --- /dev/null +++ b/src/sat/kissat/vivify.h @@ -0,0 +1,8 @@ +#ifndef _vivify_h_INCLUDED +#define _vivify_h_INCLUDED + +struct kissat; + +void kissat_vivify (struct kissat *); + +#endif diff --git a/src/sat/kissat/walk.c b/src/sat/kissat/walk.c new file mode 100644 index 000000000..88974ee7a --- /dev/null +++ b/src/sat/kissat/walk.c @@ -0,0 +1,966 @@ +#include "walk.h" +#include "allocate.h" +#include "decide.h" +#include "dense.h" +#include "inline.h" +#include "phases.h" +#include "print.h" +#include "rephase.h" +#include "report.h" +#include "terminate.h" +#include "warmup.h" + +#include + +typedef struct tagged tagged; +typedef struct counter counter; +typedef struct walker walker; + +#define LD_MAX_WALK_REF 31 +#define MAX_WALK_REF ((1u << LD_MAX_WALK_REF) - 1) + +struct tagged { + unsigned ref : LD_MAX_WALK_REF; + bool binary : 1; +}; + +static inline tagged make_tagged (bool binary, unsigned ref) { + assert (ref <= MAX_WALK_REF); + tagged res = {.binary = binary, .ref = ref}; + return res; +} + +struct counter { + unsigned count; + unsigned pos; +}; + +// clang-format off +typedef STACK (double) doubles; +// clang-format on + +#define INVALID_BEST_TRAIL_POS UINT_MAX + +struct walker { + kissat *solver; + + unsigned best_trail_pos; + unsigned clauses; + unsigned current; + unsigned exponents; + unsigned initial; + unsigned minimum; + unsigned offset; + + generator random; + + counter *counters; + litpairs *binaries; + tagged *refs; + double *table; + + value *original_values; + value *best_values; + + doubles scores; + unsigneds unsat; + unsigneds trail; + + double size; + double epsilon; + + uint64_t limit; + uint64_t flipped; +#ifndef QUIET + uint64_t start; + struct { + uint64_t flipped; + unsigned minimum; + } report; +#endif +}; + +static const unsigned *dereference_literals (kissat *solver, walker *walker, + unsigned counter_ref, + unsigned *size_ptr) { + assert (counter_ref < walker->clauses); + tagged tagged = walker->refs[counter_ref]; + unsigned const *lits; + if (tagged.binary) { + const unsigned binary_ref = tagged.ref; + lits = PEEK_STACK (*walker->binaries, binary_ref).lits; + *size_ptr = 2; + } else { + const reference clause_ref = tagged.ref; + clause *c = kissat_dereference_clause (solver, clause_ref); + *size_ptr = c->size; + lits = c->lits; + } + return lits; +} + +static void push_unsat (kissat *solver, walker *walker, counter *counters, + unsigned counter_ref) { + assert (counter_ref < walker->clauses); + counter *counter = counters + counter_ref; + assert (SIZE_STACK (walker->unsat) <= UINT_MAX); + counter->pos = SIZE_STACK (walker->unsat); + PUSH_STACK (walker->unsat, counter_ref); +#ifdef LOGGING + unsigned size; + const unsigned *const lits = + dereference_literals (solver, walker, counter_ref, &size); + LOGLITS (size, lits, "pushed unsatisfied[%u]", counter->pos); +#endif +} + +static bool pop_unsat (kissat *solver, walker *walker, counter *counters, + unsigned counter_ref, unsigned pos) { + assert (walker->current); + assert (counter_ref < walker->clauses); + assert (counters[counter_ref].pos == pos); + assert (walker->current == SIZE_STACK (walker->unsat)); + const unsigned other_counter_ref = POP_STACK (walker->unsat); + walker->current--; + bool res = false; + if (counter_ref != other_counter_ref) { + assert (other_counter_ref < walker->clauses); + counter *other_counter = counters + other_counter_ref; + assert (other_counter->pos == walker->current); + assert (pos < other_counter->pos); + other_counter->pos = pos; + POKE_STACK (walker->unsat, pos, other_counter_ref); + res = true; + } +#ifdef LOGGING + unsigned size; + const unsigned *const lits = + dereference_literals (solver, walker, counter_ref, &size); + LOGLITS (size, lits, "popped unsatisfied[%u]", pos); +#else + (void) solver; +#endif + return res; +} + +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 double fit_cbval (double size) { + const size_t num_cbvals = sizeof cbvals / sizeof *cbvals; + size_t i = 0; + while (i + 2 < num_cbvals && + (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; +} + +static void init_score_table (walker *walker) { + kissat *solver = walker->solver; + + const double cb = (GET (walks) & 1) ? fit_cbval (walker->size) : 2.0; + const double base = 1 / cb; + + double next; + unsigned exponents = 0; + for (next = 1; next; next *= base) + exponents++; + + walker->table = kissat_malloc (solver, exponents * sizeof (double)); + + unsigned i = 0; + double epsilon; + for (epsilon = next = 1; next; next = epsilon * base) + walker->table[i++] = epsilon = next; + + assert (i == exponents); + walker->exponents = exponents; + walker->epsilon = epsilon; + + kissat_phase (solver, "walk", GET (walks), + "CB %.2f with inverse %.2f as base", cb, base); + kissat_phase (solver, "walk", GET (walks), "table size %u and epsilon %g", + exponents, epsilon); +} + +static unsigned currently_unsatified (walker *walker) { + return SIZE_STACK (walker->unsat); +} + +static void import_decision_phases (walker *walker) { + kissat *solver = walker->solver; + INC (walk_decisions); + const flags *const flags = solver->flags; + value *values = solver->values; + walker->best_values = kissat_calloc (solver, VARS, 1); + value *best_values = walker->best_values; +#ifndef QUIET + unsigned imported = 0; +#endif + for (all_variables (idx)) { + if (!flags[idx].active) + continue; + value value = kissat_decide_phase (solver, idx); + assert (value); + best_values[idx] = value; + const unsigned lit = LIT (idx); + const unsigned not_lit = NOT (lit); + values[lit] = value; + values[not_lit] = -value; +#ifndef QUIET + imported++; +#endif + LOG ("copied %s decision phase %d", LOGVAR (idx), (int) value); + } + kissat_phase (solver, "walk", GET (walks), + "imported %u decision phases %.0f%%", imported, + kissat_percent (imported, solver->active)); +} + +static unsigned connect_binary_counters (walker *walker) { + kissat *solver = walker->solver; + value *values = solver->values; + tagged *refs = walker->refs; + watches *all_watches = solver->watches; + counter *counters = walker->counters; + + assert (SIZE_STACK (*walker->binaries) <= UINT_MAX); + const unsigned size = SIZE_STACK (*walker->binaries); + litpair *binaries = BEGIN_STACK (*walker->binaries); + unsigned unsat = 0, counter_ref = 0; + + for (unsigned binary_ref = 0; binary_ref < size; binary_ref++) { + const litpair *const litpair = binaries + binary_ref; + const unsigned first = litpair->lits[0]; + const unsigned second = litpair->lits[1]; + assert (first < LITS), assert (second < LITS); + const value first_value = values[first]; + const value second_value = values[second]; + if (!first_value || !second_value) + continue; + assert (counter_ref < walker->clauses); + refs[counter_ref] = make_tagged (true, binary_ref); + watches *first_watches = all_watches + first; + watches *second_watches = all_watches + second; + kissat_push_large_watch (solver, first_watches, counter_ref); + kissat_push_large_watch (solver, second_watches, counter_ref); + const unsigned count = (first_value > 0) + (second_value > 0); + counter *counter = counters + counter_ref; + counter->count = count; + if (!count) { + push_unsat (solver, walker, counters, counter_ref); + unsat++; + } + counter_ref++; + } + kissat_phase (solver, "walk", GET (walks), + "initially %u unsatisfied binary clauses %.0f%% out of %u", + unsat, kissat_percent (unsat, counter_ref), counter_ref); +#ifdef QUIET + (void) unsat; +#endif + walker->size += 2.0 * counter_ref; + return counter_ref; +} + +static void connect_large_counters (walker *walker, unsigned counter_ref) { + kissat *solver = walker->solver; + assert (!solver->level); + const value *const original_values = walker->original_values; + const value *const local_search_values = solver->values; + ward *const arena = BEGIN_STACK (solver->arena); + counter *counters = walker->counters; + tagged *refs = walker->refs; + + unsigned unsat = 0; + unsigned large = 0; + + clause *last_irredundant = kissat_last_irredundant_clause (solver); + + for (all_clauses (c)) { + if (last_irredundant && c > last_irredundant) + break; + if (c->garbage) + continue; + if (c->redundant) + continue; + bool continue_with_next_clause = false; + for (all_literals_in_clause (lit, c)) { + const value value = original_values[lit]; + if (value <= 0) + continue; + LOGCLS (c, "%s satisfied", LOGLIT (lit)); + kissat_mark_clause_as_garbage (solver, c); + assert (c->garbage); + continue_with_next_clause = true; + break; + } + if (continue_with_next_clause) + continue; + large++; + assert (kissat_clause_in_arena (solver, c)); + reference clause_ref = (ward *) c - arena; + assert (clause_ref <= MAX_WALK_REF); + assert (counter_ref < walker->clauses); + refs[counter_ref] = make_tagged (false, clause_ref); + unsigned count = 0, size = 0; + for (all_literals_in_clause (lit, c)) { + const value value = local_search_values[lit]; + if (!value) { + assert (original_values[lit] < 0); + continue; + } + watches *watches = &WATCHES (lit); + kissat_push_large_watch (solver, watches, counter_ref); + size++; + if (value > 0) + count++; + } + counter *counter = walker->counters + counter_ref; + counter->count = count; + + if (!count) { + push_unsat (solver, walker, counters, counter_ref); + unsat++; + } + counter_ref++; + walker->size += size; + } + kissat_phase (solver, "walk", GET (walks), + "initially %u unsatisfied large clauses %.0f%% out of %u", + unsat, kissat_percent (unsat, large), large); +#ifdef QUIET + (void) large; + (void) unsat; +#endif +} + +#ifndef QUIET + +static void report_initial_minimum (kissat *solver, walker *walker) { + walker->report.minimum = walker->minimum; + kissat_very_verbose (solver, "initial minimum of %u unsatisfied clauses", + walker->minimum); +} + +static void report_minimum (const char *type, kissat *solver, + walker *walker) { + assert (walker->minimum <= walker->report.minimum); + kissat_very_verbose (solver, + "%s minimum of %u unsatisfied clauses after %" PRIu64 + " flipped literals", + type, walker->minimum, walker->flipped); + walker->report.minimum = walker->minimum; +} +#else +#define report_initial_minimum(...) \ + do { \ + } while (0) +#define report_minimum(...) \ + do { \ + } while (0) +#endif + +static void init_walker (kissat *solver, walker *walker, + litpairs *binaries) { + uint64_t clauses = BINIRR_CLAUSES; + assert (clauses <= MAX_WALK_REF); + + memset (walker, 0, sizeof *walker); + + walker->solver = solver; + walker->clauses = clauses; + walker->binaries = binaries; + walker->random = solver->random ^ solver->statistics.walks; + + walker->original_values = solver->values; + solver->values = kissat_calloc (solver, LITS, 1); + + import_decision_phases (walker); + + walker->counters = kissat_malloc (solver, clauses * sizeof (counter)); + walker->refs = kissat_malloc (solver, clauses * sizeof (tagged)); + + assert (!walker->size); + const unsigned counter_ref = connect_binary_counters (walker); + connect_large_counters (walker, counter_ref); + + walker->current = walker->initial = currently_unsatified (walker); + + kissat_phase (solver, "walk", GET (walks), + "initially %u unsatisfied irredundant clauses %.0f%% " + "out of %" PRIu64, + walker->initial, kissat_percent (walker->initial, clauses), + clauses); + + walker->size = kissat_average (walker->size, clauses); + kissat_phase (solver, "walk", GET (walks), "average clause size %.2f", + walker->size); + + walker->minimum = walker->current; + init_score_table (walker); + + report_initial_minimum (solver, walker); +} + +static void init_walker_limit (kissat *solver, walker *walker) { + SET_EFFORT_LIMIT (limit, walk, walk_steps); + walker->limit = limit; + walker->flipped = 0; +#ifndef QUIET + walker->start = solver->statistics.walk_steps; + walker->report.minimum = UINT_MAX; + walker->report.flipped = 0; +#endif +} + +static void release_walker (walker *walker) { + kissat *solver = walker->solver; + kissat_dealloc (solver, walker->table, walker->exponents, + sizeof (double)); + unsigned clauses = walker->clauses; + kissat_dealloc (solver, walker->refs, clauses, sizeof (tagged)); + kissat_dealloc (solver, walker->counters, clauses, sizeof (counter)); + RELEASE_STACK (walker->unsat); + RELEASE_STACK (walker->scores); + RELEASE_STACK (walker->trail); + kissat_free (solver, solver->values, LITS); + kissat_free (solver, walker->best_values, VARS); + RELEASE_STACK (walker->unsat); + solver->values = walker->original_values; +} + +static unsigned break_value (kissat *solver, walker *walker, value *values, + unsigned lit) { + assert (values[lit] < 0); + const unsigned not_lit = NOT (lit); + watches *watches = &WATCHES (not_lit); + unsigned steps = 1; + unsigned res = 0; + for (all_binary_large_watches (watch, *watches)) { + steps++; + assert (!watch.type.binary); + reference counter_ref = watch.large.ref; + assert (counter_ref < walker->clauses); + counter *counter = walker->counters + counter_ref; + res += (counter->count == 1); + } + ADD (walk_steps, steps); +#ifdef NDEBUG + (void) values; +#endif + return res; +} + +static double scale_score (walker *walker, unsigned breaks) { + if (breaks < walker->exponents) + return walker->table[breaks]; + else + return walker->epsilon; +} + +static unsigned pick_literal (kissat *solver, walker *walker) { + assert (walker->current == SIZE_STACK (walker->unsat)); + const unsigned pos = walker->flipped++ % walker->current; + const unsigned counter_ref = PEEK_STACK (walker->unsat, pos); + unsigned size; + const unsigned *const lits = + dereference_literals (solver, walker, counter_ref, &size); + + LOGLITS (size, lits, "picked unsatisfied[%u]", pos); + assert (EMPTY_STACK (walker->scores)); + + value *values = solver->values; + + double sum = 0; + unsigned picked_lit = INVALID_LIT; + + const unsigned *const end_of_lits = lits + size; + for (const unsigned *p = lits; p != end_of_lits; p++) { + const unsigned lit = *p; + if (!values[lit]) + continue; + picked_lit = lit; + const unsigned breaks = break_value (solver, walker, values, lit); + const double score = scale_score (walker, breaks); + assert (score > 0); + LOG ("literal %s breaks %u score %g", LOGLIT (lit), breaks, score); + PUSH_STACK (walker->scores, score); + sum += score; + } + assert (picked_lit != INVALID_LIT); + assert (0 < sum); + + const double random = kissat_pick_double (&walker->random); + assert (0 <= random), assert (random < 1); + + const double threshold = sum * random; + LOG ("score sum %g and random threshold %g", sum, threshold); + + // assert (threshold < sum); // NOT TRUE!!!! + + double *scores = BEGIN_STACK (walker->scores); +#ifdef LOGGING + double picked_score = 0; +#endif + + sum = 0; + + for (const unsigned *p = lits; p != end_of_lits; p++) { + const unsigned lit = *p; + if (!values[lit]) + continue; + const double score = *scores++; + sum += score; + if (threshold < sum) { + picked_lit = lit; +#ifdef LOGGING + picked_score = score; +#endif + break; + } + } + assert (picked_lit != INVALID_LIT); + LOG ("picked literal %s with score %g", LOGLIT (picked_lit), + picked_score); + + CLEAR_STACK (walker->scores); + + return picked_lit; +} + +static void break_clauses (kissat *solver, walker *walker, + const value *const values, unsigned flipped) { +#ifdef LOGGING + unsigned broken = 0; +#endif + const unsigned not_flipped = NOT (flipped); + assert (values[not_flipped] < 0); + LOG ("breaking one-satisfied clauses containing negated flipped literal " + "%s", + LOGLIT (not_flipped)); + watches *watches = &WATCHES (not_flipped); + counter *counters = walker->counters; + unsigned steps = 1; + for (all_binary_large_watches (watch, *watches)) { + steps++; + assert (!watch.type.binary); + const unsigned counter_ref = watch.large.ref; + assert (counter_ref < walker->clauses); + counter *counter = counters + counter_ref; + assert (counter->count); + if (--counter->count) + continue; + push_unsat (solver, walker, counters, counter_ref); +#ifdef LOGGING + broken++; +#endif + } + LOG ("broken %u one-satisfied clauses containing " + "negated flipped literal %s", + broken, LOGLIT (not_flipped)); + ADD (walk_steps, steps); +#ifdef NDEBUG + (void) values; +#endif +} + +static void make_clauses (kissat *solver, walker *walker, + const value *const values, unsigned flipped) { + assert (values[flipped] > 0); + LOG ("making unsatisfied clauses containing flipped literal %s", + LOGLIT (flipped)); + watches *watches = &WATCHES (flipped); + counter *counters = walker->counters; + unsigned steps = 1; +#ifdef LOGGING + unsigned made = 0; +#endif + for (all_binary_large_watches (watch, *watches)) { + steps++; + assert (!watch.type.binary); + const unsigned counter_ref = watch.large.ref; + assert (counter_ref < walker->clauses); + counter *counter = counters + counter_ref; + assert (counter->count < UINT_MAX); + if (counter->count++) + continue; + if (pop_unsat (solver, walker, counters, counter_ref, counter->pos)) + steps++; +#ifdef LOGGING + made++; +#endif + } + LOG ("made %u unsatisfied clauses containing flipped literal %s", made, + LOGLIT (flipped)); + ADD (walk_steps, steps); +#ifdef NDEBUG + (void) values; +#endif +} + +static void save_all_values (kissat *solver, walker *walker) { + assert (EMPTY_STACK (walker->trail)); + assert (walker->best_trail_pos == INVALID_BEST_TRAIL_POS); + LOG ("copying all values as best phases since trail is invalid"); + const value *const current_values = solver->values; + value *best_values = walker->best_values; + for (all_variables (idx)) { + const unsigned lit = LIT (idx); + const value value = current_values[lit]; + if (value) + best_values[idx] = value; + } + LOG ("reset best trail position to 0"); + walker->best_trail_pos = 0; +} + +static void save_walker_trail (kissat *solver, walker *walker, bool keep) { +#if defined(LOGGING) || !defined(NDEBUG) + assert (walker->best_trail_pos != INVALID_BEST_TRAIL_POS); + assert (SIZE_STACK (walker->trail) <= UINT_MAX); + const unsigned size_trail = SIZE_STACK (walker->trail); + assert (walker->best_trail_pos <= size_trail); + const unsigned kept = size_trail - walker->best_trail_pos; + LOG ("saving %u values of flipped literals on trail of size %u", + walker->best_trail_pos, size_trail); +#else + (void) solver; +#endif + value *best_values = walker->best_values; + unsigned *begin = BEGIN_STACK (walker->trail); + const unsigned *const best = begin + walker->best_trail_pos; + for (const unsigned *p = begin; p != best; p++) { + const unsigned lit = *p; + const value value = NEGATED (lit) ? -1 : 1; + const unsigned idx = IDX (lit); + best_values[idx] = value; + } + if (!keep) { + LOG ("no need to shift and keep remaining %u literals", kept); + return; + } + LOG ("flushed %u literals %.0f%% from trail", walker->best_trail_pos, + kissat_percent (walker->best_trail_pos, size_trail)); + const unsigned *const end = END_STACK (walker->trail); + unsigned *q = begin; + for (const unsigned *p = best; p != end; p++) + *q++ = *p; + assert ((size_t) (end - q) == walker->best_trail_pos); + assert ((size_t) (q - begin) == kept); + SET_END_OF_STACK (walker->trail, q); + LOG ("keeping %u literals %.0f%% on trail", kept, + kissat_percent (kept, size_trail)); + LOG ("reset best trail position to 0"); + walker->best_trail_pos = 0; +} + +static void push_flipped (kissat *solver, walker *walker, + unsigned flipped) { + if (walker->best_trail_pos == INVALID_BEST_TRAIL_POS) { + LOG ("not pushing flipped %s to already invalid trail", + LOGLIT (flipped)); + assert (EMPTY_STACK (walker->trail)); + } else { + assert (SIZE_STACK (walker->trail) <= UINT_MAX); + const unsigned size_trail = SIZE_STACK (walker->trail); + assert (walker->best_trail_pos <= size_trail); + const unsigned limit = VARS / 4 + 1; + assert (limit < INVALID_BEST_TRAIL_POS); + if (size_trail < limit) { + PUSH_STACK (walker->trail, flipped); + LOG ("pushed flipped %s to trail which now has size %u", + LOGLIT (flipped), size_trail + 1); + } else if (walker->best_trail_pos) { + LOG ("trail reached limit %u but has best position %u", limit, + walker->best_trail_pos); + save_walker_trail (solver, walker, true); + PUSH_STACK (walker->trail, flipped); + assert (SIZE_STACK (walker->trail) <= UINT_MAX); + LOG ("pushed flipped %s to trail which now has size %zu", + LOGLIT (flipped), SIZE_STACK (walker->trail)); + } else { + LOG ("trail reached limit %u without best position", limit); + CLEAR_STACK (walker->trail); + LOG ("not pushing %s to invalidated trail", LOGLIT (flipped)); + walker->best_trail_pos = INVALID_BEST_TRAIL_POS; + LOG ("best trail position becomes invalid"); + } + } +} + +static void flip_literal (kissat *solver, walker *walker, unsigned flip) { + LOG ("flipping literal %s", LOGLIT (flip)); + value *values = solver->values; + const value value = values[flip]; + assert (value < 0); + values[flip] = -value; + values[NOT (flip)] = value; + make_clauses (solver, walker, values, flip); + break_clauses (solver, walker, values, flip); + walker->current = currently_unsatified (walker); +} + +static void update_best (kissat *solver, walker *walker) { + assert (walker->current < walker->minimum); + walker->minimum = walker->current; +#ifndef QUIET + int verbosity = kissat_verbosity (solver); + bool report = (verbosity > 2); + if (verbosity == 2) { + if (walker->flipped / 2 >= walker->report.flipped) + report = true; + else if (walker->minimum < 5 || walker->report.minimum == UINT_MAX || + walker->minimum <= walker->report.minimum / 2) + report = true; + if (report) { + walker->report.minimum = walker->minimum; + walker->report.flipped = walker->flipped; + } + } + if (report) + report_minimum ("new", solver, walker); +#endif + if (walker->best_trail_pos == INVALID_BEST_TRAIL_POS) + save_all_values (solver, walker); + else { + assert (SIZE_STACK (walker->trail) < INVALID_BEST_TRAIL_POS); + walker->best_trail_pos = SIZE_STACK (walker->trail); + LOG ("new best trail position %u", walker->best_trail_pos); + } +} + +static void local_search_step (kissat *solver, walker *walker) { + assert (walker->current); + INC (flipped); + assert (walker->flipped < UINT64_MAX); + walker->flipped++; + LOG ("starting local search flip %" PRIu64 " with %u unsatisfied clauses", + GET (flipped), walker->current); + unsigned lit = pick_literal (solver, walker); + flip_literal (solver, walker, lit); + push_flipped (solver, walker, lit); + if (walker->current < walker->minimum) + update_best (solver, walker); + LOG ("ending local search step %" PRIu64 " with %u unsatisfied clauses", + GET (flipped), walker->current); +} + +static void local_search_round (walker *walker) { + kissat *solver = walker->solver; +#ifndef QUIET + const unsigned before = walker->minimum; +#endif + statistics *statistics = &solver->statistics; + while (walker->minimum && walker->limit > statistics->walk_steps) { + if (TERMINATED (walk_terminated_1)) + break; + local_search_step (solver, walker); + } +#ifndef QUIET + report_minimum ("last", solver, walker); + assert (statistics->walk_steps >= walker->start); + const uint64_t steps = statistics->walk_steps - walker->start; + // clang-format off + kissat_very_verbose (solver, + "walking ends with %u unsatisfied clauses", walker->current); + kissat_very_verbose (solver, + "flipping %" PRIu64 " literals took %" PRIu64 " steps (%.2f per flipped)", + walker->flipped, steps, kissat_average (steps, walker->flipped)); + // clang-format on + const unsigned after = walker->minimum; + kissat_phase ( + solver, "walk", GET (walks), "%s minimum %u after %" PRIu64 " flips", + after < before ? "new" : "unchanged", after, walker->flipped); +#endif +} + +static void export_best_values (walker *walker) { + kissat *const solver = walker->solver; + const value *const best = walker->best_values; + value *const saved = solver->phases.saved; + assert (sizeof *saved == 1); + assert (sizeof *best == 1); + memcpy (saved, best, VARS); +} + +static bool save_final_minimum (walker *walker) { + kissat *solver = walker->solver; + + assert (walker->minimum <= walker->initial); + if (walker->minimum == walker->initial) { + kissat_phase (solver, "walk", GET (walks), + "no improvement thus keeping saved phases"); + return false; + } + + kissat_phase (solver, "walk", GET (walks), + "saving improved assignment of %u unsatisfied clauses", + walker->minimum); + + if (!walker->best_trail_pos || + walker->best_trail_pos == INVALID_BEST_TRAIL_POS) + LOG ("minimum already saved"); + else + save_walker_trail (solver, walker, false); + + export_best_values (walker); + INC (walk_improved); + + return true; +} + +#ifdef CHECK_WALK + +static void check_walk (kissat *solver, unsigned expected) { + unsigned unsatisfied = 0; + watches *all_watches = solver->watches; + const value *const saved = solver->phases.saved; + for (all_literals (lit)) { + assert (lit < LITS); + watches *watches = all_watches + lit; + if (kissat_empty_vector (watches)) + continue; + value value = solver->values[lit]; + if (!value) { + value = saved[IDX (lit)]; + assert (value); + if (NEGATED (lit)) + value = -value; + } + if (value > 0) + continue; + for (all_binary_blocking_watches (watch, *watches)) + if (watch.type.binary) { + const unsigned other = watch.binary.lit; + if (other < lit) + continue; + value = solver->values[other]; + if (!value) { + value = saved[IDX (other)]; + assert (value); + if (NEGATED (other)) + value = -value; + } + if (value > 0) + continue; + unsatisfied++; + LOGBINARY (lit, other, "unsat"); + } + } + for (all_clauses (c)) { + if (c->redundant) + continue; + if (c->garbage) + continue; + bool satisfied = false; + for (all_literals_in_clause (lit, c)) { + value value = solver->values[lit]; + if (!value) { + value = saved[IDX (lit)]; + assert (value); + if (NEGATED (lit)) + value = -value; + } + if (value > 0) + satisfied = true; + } + if (satisfied) + continue; + LOGCLS (c, "unsatisfied"); + unsatisfied++; + } + LOG ("expected %u unsatisfied", expected); + LOG ("actually %u unsatisfied", unsatisfied); + assert (expected == unsatisfied); +} + +#endif + +static void walking_phase (kissat *solver) { + INC (walks); + litpairs irredundant; + INIT_STACK (irredundant); + kissat_enter_dense_mode (solver, &irredundant); + walker walker; + init_walker (solver, &walker, &irredundant); + init_walker_limit (solver, &walker); + local_search_round (&walker); +#ifdef CHECK_WALK + bool improved = +#endif + save_final_minimum (&walker); +#ifdef CHECK_WALK + unsigned expected = walker.minimum; +#endif + release_walker (&walker); + kissat_resume_sparse_mode (solver, false, &irredundant); + RELEASE_STACK (irredundant); +#if CHECK_WALK + if (improved) + check_walk (solver, expected); +#endif +} + +bool kissat_walking (kissat *solver) { + reference last_irredundant = solver->last_irredundant; + if (last_irredundant == INVALID_REF) + last_irredundant = SIZE_STACK (solver->arena); + + if (last_irredundant > MAX_WALK_REF) { + kissat_extremely_verbose (solver, + "can not walk since last " + "irredundant clause reference %u too large", + last_irredundant); + return false; + } + + uint64_t clauses = BINIRR_CLAUSES; + if (clauses > MAX_WALK_REF) { + kissat_extremely_verbose (solver, + "can not walk due to " + "way too many irredundant clauses %" PRIu64, + clauses); + return false; + } + + return true; +} + +void kissat_walk (kissat *solver) { + assert (!solver->level); + assert (!solver->inconsistent); + assert (kissat_propagated (solver)); + assert (kissat_walking (solver)); + + reference last_irredundant = solver->last_irredundant; + if (last_irredundant == INVALID_REF) + last_irredundant = SIZE_STACK (solver->arena); + + if (last_irredundant > MAX_WALK_REF) { + kissat_phase (solver, "walk", GET (walks), + "last irredundant clause reference %u too large", + last_irredundant); + return; + } + + uint64_t clauses = BINIRR_CLAUSES; + if (clauses > MAX_WALK_REF) { + kissat_phase (solver, "walk", GET (walks), + "way too many irredundant clauses %" PRIu64, clauses); + return; + } + + if (GET_OPTION (warmup)) + kissat_warmup (solver); + + STOP_SEARCH_AND_START_SIMPLIFIER (walking); + walking_phase (solver); + STOP_SIMPLIFIER_AND_RESUME_SEARCH (walking); +} diff --git a/src/sat/kissat/walk.h b/src/sat/kissat/walk.h new file mode 100644 index 000000000..0acc19193 --- /dev/null +++ b/src/sat/kissat/walk.h @@ -0,0 +1,11 @@ +#ifndef _walk_h_INCLUDED +#define _walk_h_INCLUDED + +#include + +struct kissat; + +bool kissat_walking (struct kissat *); +void kissat_walk (struct kissat *); + +#endif diff --git a/src/sat/kissat/warmup.c b/src/sat/kissat/warmup.c new file mode 100644 index 000000000..d9fa9f534 --- /dev/null +++ b/src/sat/kissat/warmup.c @@ -0,0 +1,54 @@ +#include "warmup.h" +#include "backtrack.h" +#include "decide.h" +#include "internal.h" +#include "print.h" +#include "propbeyond.h" +#include "terminate.h" + +void kissat_warmup (kissat *solver) { + assert (!solver->level); + assert (solver->watching); + assert (!solver->inconsistent); + assert (GET_OPTION (warmup)); + START (warmup); + assert (!solver->warming); + solver->warming = true; + INC (warmups); +#ifndef QUIET + const statistics *stats = &solver->statistics; + uint64_t propagations = stats->warming_propagations; + uint64_t decisions = stats->warming_decisions; +#endif + while (solver->unassigned) { + if (TERMINATED (warmup_terminated_1)) + break; + kissat_decide (solver); + kissat_propagate_beyond_conflicts (solver); + } + assert (!solver->inconsistent); +#ifndef QUIET + decisions = stats->warming_decisions - decisions; + propagations = stats->warming_propagations - propagations; + + kissat_very_verbose (solver, + "warming-up needed %" PRIu64 + " decisions and %" PRIu64 " propagations", + decisions, propagations); + + if (solver->unassigned) + kissat_verbose (solver, + "reached decision level %u " + "during warming-up saved phases", + solver->level); + else + kissat_verbose (solver, + "all variables assigned at decision level %u " + "during warming-up saved phases", + solver->level); +#endif + kissat_backtrack_without_updating_phases (solver, 0); + assert (solver->warming); + solver->warming = false; + STOP (warmup); +} diff --git a/src/sat/kissat/warmup.h b/src/sat/kissat/warmup.h new file mode 100644 index 000000000..e0fd47122 --- /dev/null +++ b/src/sat/kissat/warmup.h @@ -0,0 +1,8 @@ +#ifndef _warmup_h_INCLUDED +#define _warmup_h_INCLUDED + +struct kissat; + +void kissat_warmup (struct kissat *); + +#endif diff --git a/src/sat/kissat/watch.c b/src/sat/kissat/watch.c new file mode 100644 index 000000000..39ceace6e --- /dev/null +++ b/src/sat/kissat/watch.c @@ -0,0 +1,223 @@ +#define INLINE_SORT + +#include "inline.h" +#include "sort.c" + +void kissat_remove_binary_watch (kissat *solver, watches *watches, + unsigned lit) { + watch *const begin = BEGIN_WATCHES (*watches); + watch *const end = END_WATCHES (*watches); + watch *q = begin; + watch const *p = q; +#ifndef NDEBUG + bool found = false; +#endif + while (p != end) { + const watch watch = *q++ = *p++; + if (!watch.type.binary) { + *q++ = *p++; + continue; + } + const unsigned other = watch.binary.lit; + if (other != lit) + continue; +#ifndef NDEBUG + assert (!found); + found = true; +#endif + q--; + } + assert (found); +#ifdef COMPACT + watches->size -= 1; +#else + assert (begin + 1 <= end); + watches->end -= 1; +#endif + const watch empty = {.raw = INVALID_VECTOR_ELEMENT}; + end[-1] = empty; + assert (solver->vectors.usable < MAX_SECTOR - 1); + solver->vectors.usable += 1; + kissat_check_vectors (solver); +} + +void kissat_remove_blocking_watch (kissat *solver, watches *watches, + reference ref) { + assert (solver->watching); + watch *const begin = BEGIN_WATCHES (*watches); + watch *const end = END_WATCHES (*watches); + watch *q = begin; + watch const *p = q; +#ifndef NDEBUG + bool found = false; +#endif + while (p != end) { + const watch head = *q++ = *p++; + if (head.type.binary) + continue; + const watch tail = *q++ = *p++; + if (tail.raw != ref) + continue; +#ifndef NDEBUG + assert (!found); + found = true; +#endif + q -= 2; + } + assert (found); +#ifdef COMPACT + watches->size -= 2; +#else + assert (begin + 2 <= end); + watches->end -= 2; +#endif + const watch empty = {.raw = INVALID_VECTOR_ELEMENT}; + end[-2] = end[-1] = empty; + assert (solver->vectors.usable < MAX_SECTOR - 2); + solver->vectors.usable += 2; + kissat_check_vectors (solver); +} + +void kissat_substitute_large_watch (kissat *solver, watches *watches, + watch src, watch dst) { + assert (!solver->watching); + watch *const begin = BEGIN_WATCHES (*watches); + const watch *const end = END_WATCHES (*watches); +#ifndef NDEBUG + bool found = false; +#endif + for (watch *p = begin; p != end; p++) { + const watch head = *p; + if (head.raw != src.raw) + continue; +#ifndef NDEBUG + found = true; +#endif + *p = dst; + break; + } + assert (found); +} + +void kissat_flush_all_connected (kissat *solver) { + assert (!solver->watching); + LOG ("flush all connected binaries and clauses"); + watches *all_watches = solver->watches; + for (all_literals (lit)) + RELEASE_WATCHES (all_watches[lit]); +} + +void kissat_flush_large_watches (kissat *solver) { + assert (solver->watching); + LOG ("flush large clause watches"); + watches *all_watches = solver->watches; + signed char *marks = solver->marks; + for (all_literals (lit)) { + watches *lit_watches = all_watches + lit; + watch *begin = BEGIN_WATCHES (*lit_watches), *q = begin; + const watch *const end = END_WATCHES (*lit_watches), *p = q; + while (p != end) { + const watch watch = *q++ = *p++; + if (!watch.type.binary) + p++, q--; + else { + const unsigned other = watch.binary.lit; + if (marks[other]) { + if (lit < other) { + LOGBINARY (lit, other, "flushing duplicated"); + kissat_delete_binary (solver, lit, other); + } + q--; + } else + marks[other] = 1; + } + } + SET_END_OF_WATCHES (*lit_watches, q); + for (p = begin; p != q; p++) { + assert (p->type.binary); + marks[p->binary.lit] = 0; + } + } +} + +void kissat_watch_large_clauses (kissat *solver) { + LOG ("watching all large clauses"); + assert (solver->watching); + + const value *const values = solver->values; + const assigned *const assigned = solver->assigned; + watches *watches = solver->watches; + ward *const arena = BEGIN_STACK (solver->arena); + + for (all_clauses (c)) { + if (c->garbage) + continue; + + unsigned *lits = c->lits; + kissat_sort_literals (solver, values, assigned, c->size, lits); + c->searched = 2; + + const reference ref = (ward *) c - arena; + const unsigned l0 = lits[0]; + const unsigned l1 = lits[1]; + + kissat_push_blocking_watch (solver, watches + l0, l1, ref); + kissat_push_blocking_watch (solver, watches + l1, l0, ref); + } +} + +void kissat_connect_irredundant_large_clauses (kissat *solver) { + assert (!solver->watching); + LOG ("connecting all large irredundant clauses"); + + clause *last_irredundant = kissat_last_irredundant_clause (solver); + + const value *const values = solver->values; + watches *all_watches = solver->watches; + ward *const arena = BEGIN_STACK (solver->arena); + + for (all_clauses (c)) { + if (last_irredundant && c > last_irredundant) + break; + if (c->redundant) + continue; + if (c->garbage) + continue; + bool satisfied = false; + assert (!solver->level); + for (all_literals_in_clause (lit, c)) { + const value value = values[lit]; + if (value <= 0) + continue; + satisfied = true; + break; + } + if (satisfied) { + kissat_mark_clause_as_garbage (solver, c); + continue; + } + const reference ref = (ward *) c - arena; + kissat_inlined_connect_clause (solver, all_watches, c, ref); + } +} + +void kissat_flush_large_connected (kissat *solver) { + assert (!solver->watching); + LOG ("flushing large connected clause references"); + size_t flushed = 0; + for (all_literals (lit)) { + watches *watches = &WATCHES (lit); + watch *begin = BEGIN_WATCHES (*watches), *q = begin; + const watch *const end_watches = END_WATCHES (*watches), *p = q; + while (p != end_watches) { + const watch head = *p++; + if (head.type.binary) + *q++ = head; + else + flushed++; + } + SET_END_OF_WATCHES (*watches, q); + } + LOG ("flushed %zu large clause references", flushed); + (void) flushed; +} diff --git a/src/sat/kissat/watch.h b/src/sat/kissat/watch.h new file mode 100644 index 000000000..50afdb083 --- /dev/null +++ b/src/sat/kissat/watch.h @@ -0,0 +1,176 @@ +#ifndef _watch_h_INCLUDED +#define _watch_h_INCLUDED + +#include "keatures.h" +#include "reference.h" +#include "stack.h" +#include "vector.h" + +#include + +typedef union watch watch; + +typedef struct binary_tagged_literal watch_type; +typedef struct binary_tagged_literal binary_watch; +typedef struct binary_tagged_literal blocking_watch; +typedef struct binary_tagged_reference large_watch; + +struct binary_tagged_literal { +#ifdef KISSAT_IS_BIG_ENDIAN + bool binary : 1; + unsigned lit : 31; +#else + unsigned lit : 31; + bool binary : 1; +#endif +}; + +struct binary_tagged_reference { +#ifdef KISSAT_IS_BIG_ENDIAN + bool binary : 1; + unsigned ref : 31; +#else + unsigned ref : 31; + bool binary : 1; +#endif +}; + +union watch { + watch_type type; + binary_watch binary; + blocking_watch blocking; + large_watch large; + unsigned raw; +}; + +typedef vector watches; + +typedef struct litwatch litwatch; +typedef struct litpair litpair; +typedef struct litriple litriple; + +typedef STACK (litwatch) litwatches; +typedef STACK (litpair) litpairs; +typedef STACK (litriple) litriples; + +struct litwatch { + unsigned lit; + watch watch; +}; + +struct litpair { + unsigned lits[2]; +}; + +struct litriple { + unsigned lits[3]; +}; + +static inline litpair kissat_litpair (unsigned lit, unsigned other) { + litpair res; + res.lits[0] = lit < other ? lit : other; + res.lits[1] = lit < other ? other : lit; + return res; +} + +static inline watch kissat_binary_watch (unsigned lit) { + watch res; + res.binary.lit = lit; + res.binary.binary = true; + assert (res.type.binary); + return res; +} + +static inline watch kissat_large_watch (reference ref) { + watch res; + res.large.ref = ref; + res.large.binary = false; + assert (!res.type.binary); + return res; +} + +static inline watch kissat_blocking_watch (unsigned lit) { + watch res; + res.blocking.lit = lit; + res.blocking.binary = false; + assert (!res.type.binary); + return res; +} + +#define EMPTY_WATCHES(W) kissat_empty_vector (&W) +#define SIZE_WATCHES(W) kissat_size_vector (&W) + +#define PUSH_WATCHES(W, E) \ + do { \ + assert (sizeof (E) == sizeof (unsigned)); \ + kissat_push_vectors (solver, &(W), (E).raw); \ + } while (0) + +#define LAST_WATCH_POINTER(WS) \ + (watch *) kissat_last_vector_pointer (solver, &WS) + +#define BEGIN_WATCHES(WS) \ + ((union watch *) kissat_begin_vector (solver, &(WS))) + +#define END_WATCHES(WS) ((union watch *) kissat_end_vector (solver, &(WS))) + +#define BEGIN_CONST_WATCHES(WS) \ + ((union watch *) kissat_begin_const_vector (solver, &(WS))) + +#define END_CONST_WATCHES(WS) \ + ((union watch *) kissat_end_const_vector (solver, &(WS))) + +#define RELEASE_WATCHES(WS) kissat_release_vector (solver, &(WS)) + +#define SET_END_OF_WATCHES(WS, P) \ + do { \ + size_t SIZE = (unsigned *) (P) - kissat_begin_vector (solver, &WS); \ + kissat_resize_vector (solver, &WS, SIZE); \ + } while (0) + +#define REMOVE_WATCHES(W, E) \ + kissat_remove_from_vector (solver, &(W), (E).raw) + +#define WATCHES(LIT) (solver->watches[assert ((LIT) < LITS), (LIT)]) + +// This iterator is currently only used in 'testreferences.c'. +// +#define all_binary_blocking_watch_ref(WATCH, REF, WATCHES) \ + watch WATCH, \ + *WATCH##_PTR = (assert (solver->watching), BEGIN_WATCHES (WATCHES)), \ + *const WATCH##_END = END_WATCHES (WATCHES); \ + WATCH##_PTR != WATCH##_END && \ + ((WATCH = *WATCH##_PTR), \ + (REF = WATCH.type.binary ? INVALID_REF : WATCH##_PTR[1].large.ref), \ + true); \ + WATCH##_PTR += 1u + !WATCH.type.binary + +#define all_binary_blocking_watches(WATCH, WATCHES) \ + watch WATCH, \ + *WATCH##_PTR = (assert (solver->watching), BEGIN_WATCHES (WATCHES)), \ + *const WATCH##_END = END_WATCHES (WATCHES); \ + WATCH##_PTR != WATCH##_END && ((WATCH = *WATCH##_PTR), true); \ + WATCH##_PTR += 1u + !WATCH.type.binary + +#define all_binary_large_watches(WATCH, WATCHES) \ + watch WATCH, \ + *WATCH##_PTR = \ + (assert (!solver->watching), BEGIN_WATCHES (WATCHES)), \ + *const WATCH##_END = END_WATCHES (WATCHES); \ + WATCH##_PTR != WATCH##_END && ((WATCH = *WATCH##_PTR), true); \ + ++WATCH##_PTR + +void kissat_remove_binary_watch (struct kissat *, watches *, unsigned); +void kissat_remove_blocking_watch (struct kissat *, watches *, reference); + +void kissat_substitute_large_watch (struct kissat *, watches *, watch src, + watch dst); + +void kissat_flush_all_connected (struct kissat *); +void kissat_flush_large_watches (struct kissat *); +void kissat_watch_large_clauses (struct kissat *); +void kissat_flush_large_connected (struct kissat *); + +void kissat_connect_irredundant_large_clauses (struct kissat *); + +#endif diff --git a/src/sat/kissat/weaken.c b/src/sat/kissat/weaken.c new file mode 100644 index 000000000..149616546 --- /dev/null +++ b/src/sat/kissat/weaken.c @@ -0,0 +1,60 @@ +#include "weaken.h" +#include "inline.h" + +static void push_witness_literal (kissat *solver, unsigned ilit) { + assert (!VALUE (ilit)); + int elit = kissat_export_literal (solver, ilit); + assert (elit); + LOG2 ("pushing external witness literal %d on extension stack", elit); + const extension ext = kissat_extension (true, elit); + PUSH_STACK (solver->extend, ext); +} + +static void push_clause_literal (kissat *solver, unsigned ilit) { + const value value = VALUE (ilit); + assert (value <= 0); + if (value < 0) + LOG ("not pushing internal falsified clause literal %s " + "on extension stack", + LOGLIT (ilit)); + else { + int elit = kissat_export_literal (solver, ilit); + assert (elit); + LOG2 ("pushing external clause literal %d on extension stack", elit); + const extension ext = kissat_extension (false, elit); + PUSH_STACK (solver->extend, ext); + } +} + +#define LOGPUSHED(SIZE) \ + do { \ + LOGEXT ((SIZE), END_STACK (solver->extend) - (SIZE), \ + "pushed size %zu witness labelled clause at", \ + (size_t) (SIZE)); \ + } while (0) + +void kissat_weaken_clause (kissat *solver, unsigned lit, clause *c) { + INC (weakened); + LOGCLS (c, "blocking on %s and weakening", LOGLIT (lit)); + push_witness_literal (solver, lit); + for (all_literals_in_clause (other, c)) + if (lit != other) + push_clause_literal (solver, other); + LOGPUSHED (c->size); +} + +void kissat_weaken_binary (kissat *solver, unsigned lit, unsigned other) { + INC (weakened); + LOGBINARY (lit, other, "blocking on %s and weakening", LOGLIT (lit)); + push_witness_literal (solver, lit); + push_clause_literal (solver, other); + LOGPUSHED (2); +} + +void kissat_weaken_unit (kissat *solver, unsigned lit) { + INC (weakened); + LOG ("blocking and weakening unit %s", LOGLIT (lit)); + push_witness_literal (solver, lit); + LOGEXT (1, END_STACK (solver->extend) - 1, + "pushed witness labelled unit clause at"); +} diff --git a/src/sat/kissat/weaken.h b/src/sat/kissat/weaken.h new file mode 100644 index 000000000..beabcb0ce --- /dev/null +++ b/src/sat/kissat/weaken.h @@ -0,0 +1,11 @@ +#ifndef _weaken_h_INCLUDED +#define _weaken_h_INCLUDED + +struct clause; +struct kissat; + +void kissat_weaken_unit (struct kissat *, unsigned lit); +void kissat_weaken_binary (struct kissat *, unsigned lit, unsigned other); +void kissat_weaken_clause (struct kissat *, unsigned lit, struct clause *); + +#endif diff --git a/src/sat/kissat/witness.h b/src/sat/kissat/witness.h new file mode 100644 index 000000000..2b0fdea63 --- /dev/null +++ b/src/sat/kissat/witness.h @@ -0,0 +1,10 @@ +#ifndef _witness_h_INCLUDED +#define _witness_h_INCLUDED + +#include + +struct kissat; + +void kissat_print_witness (struct kissat *, int max_var, bool partial); + +#endif