#include "allocate.h" #include "backtrack.h" #include "colors.h" #include "decide.h" #include "inline.h" #include "print.h" #include "prophyper.h" #include "proprobe.h" #include "promote.h" #include "report.h" #include "sort.h" #include "trail.h" #include "terminate.h" #include "vivify.h" #include static inline bool more_occurrences (unsigned *counts, unsigned a, unsigned b) { const unsigned s = counts[a]; const unsigned t = counts[b]; if (s > t) return true; if (s < t) return false; return a < b; } #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 void count_literal (unsigned lit, unsigned *counts) { const unsigned old_count = counts[lit]; const unsigned new_count = (old_count < UINT_MAX) ? old_count + 1 : UINT_MAX; counts[lit] = new_count; } 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 * c) { assert (!solver->level); bool satisfied = false; assert (EMPTY_STACK (solver->clause.lits)); const value *values = solver->values; for (all_literals_in_clause (lit, c)) { const value value = values[lit]; if (value > 0) { satisfied = true; LOGCLS (c, "vivification %s satisfied candidate", LOGLIT (lit)); kissat_mark_clause_as_garbage (solver, c); break; } if (!value) PUSH_STACK (solver->clause.lits, lit); } unsigned non_false = SIZE_STACK (solver->clause.lits); if (satisfied) { CLEAR_STACK (solver->clause.lits); return true; } if (non_false == c->size) { CLEAR_STACK (solver->clause.lits); return false; } assert (1 < non_false); assert (non_false <= c->size); if (non_false == 2) { const unsigned first = PEEK_STACK (solver->clause.lits, 0); const unsigned second = PEEK_STACK (solver->clause.lits, 1); LOGBINARY (first, second, "vivification shrunken candidate"); kissat_new_binary_clause (solver, c->redundant, first, second); kissat_mark_clause_as_garbage (solver, c); } else { CHECK_AND_ADD_STACK (solver->clause.lits); ADD_STACK_TO_PROOF (solver->clause.lits); 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 = kissat_fixed (solver, lit); assert (value <= 0); if (value < 0) continue; 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"); } CLEAR_STACK (solver->clause.lits); return false; } static void schedule_vivification_candidates (kissat * solver, #ifndef QUIET const char *mode, const char *type, #endif references * schedule, unsigned *counts, bool redundant, bool tier2) { unsigned lower_glue_limit, upper_glue_limit; if (tier2) { lower_glue_limit = GET_OPTION (tier1) + 1; upper_glue_limit = GET_OPTION (tier2); } else { lower_glue_limit = 0; upper_glue_limit = GET_OPTION (tier1); } const word *arena = BEGIN_STACK (solver->arena); size_t prioritized = 0; for (unsigned prioritize = 0; prioritize < 2; prioritize++) { for (all_clauses (c)) { if (c->garbage) continue; if (redundant) { if (!c->redundant) continue; if (c->hyper) 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++; count_clause (c, counts); const reference ref = (word *) c - arena; PUSH_STACK (*schedule, ref); } } #ifndef QUIET size_t scheduled = SIZE_STACK (*schedule); #endif if (prioritized) { kissat_phase (solver, mode, GET (probings), "prioritized %zu %s clauses %.0f%%", prioritized, type, kissat_percent (prioritized, scheduled)); } else { kissat_phase (solver, mode, GET (probings), "prioritizing all %zu scheduled %s clauses", scheduled, type); for (all_stack (reference, ref, *schedule)) { clause *c = (clause *) (arena + ref); assert (kissat_clause_in_arena (solver, c)); c->vivify = true; } } } static unsigned * new_vivification_candidates_counts (kissat * solver) { return kissat_calloc (solver, LITS, sizeof (unsigned)); } static inline bool worse_candidate (kissat * solver, unsigned *counts, reference r, reference s) { const clause *c = kissat_dereference_clause (solver, r); const clause *d = kissat_dereference_clause (solver, s); if (!c->vivify && d->vivify) return true; if (c->vivify && !d->vivify) return false; const unsigned *p = BEGIN_LITS (c); const unsigned *q = BEGIN_LITS (d); const unsigned *e = END_LITS (c); const unsigned *f = END_LITS (d); while (p != e && q != f) { const unsigned a = *p++; const unsigned b = *q++; if (a == b) continue; const unsigned u = counts[a]; const unsigned v = counts[b]; if (u < v) return true; if (u > v) return false; return a < b; } if (p != e && q == f) return true; if (p == e && q != f) return false; return r < s; } #define WORSE_CANDIDATE(A,B) \ worse_candidate (solver, counts, (A), (B)) static void sort_vivification_candidates (kissat * solver, references * schedule, unsigned *counts) { for (all_stack (reference, ref, *schedule)) { clause *c = kissat_dereference_clause (solver, ref); vivify_sort_clause_by_counts (solver, c, counts); } SORT_STACK (reference, *schedule, WORSE_CANDIDATE); } static clause * vivify_unit_conflict (kissat * solver, unsigned lit) { assert (VALUE (lit) > 0); LOG ("vivify analyzing conflict unit %s", LOGLIT (NOT (lit))); assigned *a = ASSIGNED (lit); assert (!a->analyzed); assert (a->analyzed != DECISION); clause *conflict; if (a->binary) { const unsigned other = a->reason; conflict = kissat_binary_conflict (solver, a->redundant, lit, other); } else { const reference ref = a->reason; conflict = kissat_dereference_clause (solver, ref); } a->analyzed = true; PUSH_STACK (solver->analyzed, lit); PUSH_STACK (solver->clause.lits, lit); return conflict; } static void vivify_binary_or_large_conflict (kissat * solver, clause * conflict) { assert (conflict->size >= 2); LOGCLS (conflict, "vivify analyzing conflict"); for (all_literals_in_clause (lit, conflict)) { assert (VALUE (lit) < 0); assigned *a = ASSIGNED (lit); if (!a->level) continue; assert (!a->analyzed); a->analyzed = ANALYZED; PUSH_STACK (solver->analyzed, lit); } } static bool vivify_analyze (kissat * solver, clause * c, clause * conflict, bool * irredundant_ptr) { assert (conflict); assert (!EMPTY_STACK (solver->analyzed)); value *marks = solver->marks; for (all_literals_in_clause (lit, c)) { assert (!marks[lit]); marks[lit] = 1; } bool subsumed = false; if (c->redundant || !conflict->redundant) { subsumed = true; for (all_literals_in_clause (lit, conflict)) { const value value = kissat_fixed (solver, lit); if (value < 0) continue; assert (!value); if (marks[lit]) continue; subsumed = false; break; } if (subsumed) LOGCLS (conflict, "vivify subsuming"); } size_t analyzed = 0; bool irredundant = conflict && !conflict->redundant; while (!subsumed && analyzed < SIZE_STACK (solver->analyzed)) { const unsigned not_lit = PEEK_STACK (solver->analyzed, analyzed); const unsigned lit = NOT (not_lit); analyzed++; assigned *a = ASSIGNED (lit); assert (a->level); assert (a->analyzed); if (a->reason == DECISION) { LOG ("vivify analyzing decision %s", LOGLIT (not_lit)); PUSH_STACK (solver->clause.lits, not_lit); } else if (a->binary) { const unsigned other = a->reason; if (a->redundant) irredundant = false; assert (VALUE (other) < 0); assigned *b = ASSIGNED (other); assert (b->level); if (c->redundant || !a->redundant) { if (marks[lit] && marks[other]) { LOGBINARY (lit, other, "vivify subsuming"); subsumed = true; break; } } if (b->analyzed) continue; LOGBINARY (lit, other, "vivify analyzing %s reason", LOGLIT (lit)); b->analyzed = ANALYZED; 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); if (reason->redundant) irredundant = false; subsumed = marks[lit]; for (all_literals_in_clause (other, reason)) { if (other == lit) continue; if (other == not_lit) continue; assert (VALUE (other) < 0); assigned *b = ASSIGNED (other); if (!b->level) continue; if (!marks[other]) subsumed = false; if (b->analyzed) continue; b->analyzed = ANALYZED; PUSH_STACK (solver->analyzed, other); } if (subsumed && (c->redundant || !reason->redundant)) { LOGCLS (reason, "vivify subsuming"); break; } subsumed = false; } } for (all_literals_in_clause (lit, c)) { assert (marks[lit]); marks[lit] = 0; } if (!subsumed) { *irredundant_ptr = irredundant; LOGTMP ("vivify learned"); } return subsumed; } static void reset_vivify_analyzed (kissat * solver) { for (all_stack (unsigned, lit, solver->analyzed)) { assigned *a = ASSIGNED (lit); assert (a->analyzed); a->analyzed = 0; } CLEAR_STACK (solver->clause.lits); CLEAR_STACK (solver->analyzed); } static void vivify_inc_subsume (kissat * solver, clause * c) { if (c->redundant) INC (vivify_sub_red); else INC (vivify_sub_irr); INC (vivify_subsumed); INC (subsumed); } static void vivify_inc_strengthened (kissat * solver, clause * c) { if (c->redundant) INC (vivify_str_red); else INC (vivify_str_irr); INC (vivify_strengthened); INC (strengthened); } static bool vivify_learn (kissat * solver, clause * c, unsigned non_false, bool irredundant, unsigned implied) { bool res; size_t size = SIZE_STACK (solver->clause.lits); assert (size <= non_false); assert (2 < non_false); if (size < non_false) { if (solver->level) kissat_backtrack (solver, 0); LOGTMP ("vivified"); } if (size == 1) { const unsigned unit = PEEK_STACK (solver->clause.lits, 0); kissat_assign_unit (solver, unit); solver->iterating = true; CHECK_AND_ADD_UNIT (unit); ADD_UNIT_TO_PROOF (unit); kissat_mark_clause_as_garbage (solver, c); clause *conflict = kissat_probing_propagate (solver, 0); if (conflict) { CHECK_AND_ADD_EMPTY (); ADD_EMPTY_TO_PROOF (); LOG ("propagating vivified unit failed"); solver->inconsistent = true; } else { assert (solver->unflushed); kissat_flush_trail (solver); } vivify_inc_strengthened (solver, c); INC (failed); res = true; } else if (size == 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); vivify_inc_strengthened (solver, c); res = true; } else if (size < non_false) { CHECK_AND_ADD_STACK (solver->clause.lits); ADD_STACK_TO_PROOF (solver->clause.lits); REMOVE_CHECKER_CLAUSE (c); DELETE_CLAUSE_FROM_PROOF (c); assert (size > 2); const unsigned old_size = c->size; unsigned new_size = 0, *lits = c->lits; unsigned watched[2] = { lits[0], lits[1] }; for (unsigned i = 0; i < old_size; i++) { const unsigned lit = lits[i]; bool keep = true; if (lit != implied) { assigned *a = ASSIGNED (lit); if (!a->analyzed) keep = false; else if (a->reason != DECISION) keep = false; } if (!c->redundant) { if (keep) kissat_mark_added_literal (solver, lit); else kissat_mark_removed_literal (solver, lit); } if (keep) lits[new_size++] = lit; } assert (new_size < old_size); assert (new_size == size); if (!c->shrunken) { c->shrunken = true; lits[old_size - 1] = INVALID_LIT; } c->size = new_size; if (c->redundant && c->glue >= new_size) kissat_promote_clause (solver, c, new_size - 1); c->searched = 2; LOGCLS (c, "vivified shrunken"); const reference ref = kissat_reference_clause (solver, c); // Beware of 'stale blocking literals' ... so rewatch if shrunken. kissat_unwatch_blocking (solver, watched[0], ref); kissat_unwatch_blocking (solver, watched[1], ref); kissat_watch_blocking (solver, lits[0], lits[1], ref); kissat_watch_blocking (solver, lits[1], lits[0], ref); vivify_inc_strengthened (solver, c); res = true; } else if (irredundant && !c->redundant) { LOGCLS (c, "vivify subsumed"); vivify_inc_subsume (solver, c); kissat_mark_clause_as_garbage (solver, c); res = true; } else { LOG ("vivify failed"); res = false; } return res; } static bool vivify_clause (kissat * solver, clause * c, unsigneds * sorted, unsigned *counts) { assert (!c->garbage); assert (solver->probing); assert (solver->watching); assert (!solver->inconsistent); LOGCLS (c, "vivify candidate"); CLEAR_STACK (*sorted); for (all_literals_in_clause (lit, c)) { const value value = kissat_fixed (solver, lit); if (value < 0) continue; if (value > 0) { LOGCLS (c, "%s satisfied", LOGLIT (lit)); kissat_mark_clause_as_garbage (solver, c); break; } PUSH_STACK (*sorted, lit); } if (c->garbage) return false; const unsigned non_false = SIZE_STACK (*sorted); assert (1 < non_false); assert (non_false <= c->size); #ifdef LOGGING if (!non_false) LOG ("no root level falsified literal"); else if (non_false == c->size) LOG ("all literals root level unassigned"); else LOG ("found %u root level non-falsified literals"); #endif if (non_false == 2) { LOGCLS (c, "skipping actually binary"); return false; } INC (vivification_checks); unsigned unit = INVALID_LIT; for (all_literals_in_clause (lit, c)) { 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; } if (unit != INVALID_LIT) { assigned *a = ASSIGNED (unit); assert (a->level); if (a->binary) unit = INVALID_LIT; else { reference ref = kissat_reference_clause (solver, c); if (a->reason != 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 (solver, level - 1); } assert (EMPTY_STACK (solver->analyzed)); assert (EMPTY_STACK (solver->clause.lits)); vivify_sort_stack_by_counts (solver, sorted, counts); #if defined(LOGGING) && !defined(NOPTIONS) if (solver->options.log) { TERMINAL (stdout, 1); COLOR (MAGENTA); printf ("c LOG %u vivify sorted", solver->level); heap *scores = solver->heuristic==0?&solver->scores:&solver->scores_chb; links *links = solver->links; for (all_stack (unsigned, lit, *sorted)) { printf (" %s", LOGLIT (lit)); if (counts) printf ("#%u", counts[lit]); else { const unsigned idx = IDX (lit); if (solver->stable) printf ("[%g]", kissat_get_heap_score (scores, idx)); else printf ("{%u}", links[idx].stamp); } } COLOR (NORMAL); printf ("\n"); fflush (stdout); } #endif unsigned implied = INVALID_LIT; clause *conflict = 0; unsigned level = 0; bool res = false; 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 (solver, level - 1); } const value value = VALUE (lit); assert (!value || LEVEL (lit) <= level); if (!value) { LOG ("literal %s unassigned", LOGLIT (lit)); const unsigned not_lit = NOT (lit); INC (vivify_assumed); INC (vivify_probes); kissat_internal_assume (solver, not_lit); if (solver->level == 1) conflict = kissat_hyper_propagate (solver, c); else conflict = kissat_probing_propagate (solver, c); if (!conflict) continue; vivify_binary_or_large_conflict (solver, conflict); assert (!EMPTY_STACK (solver->analyzed)); break; } if (value < 0) { assert (LEVEL (lit)); LOG ("literal %s already falsified", LOGLIT (lit)); continue; } assert (value > 0); assert (LEVEL (lit)); LOG ("literal %s already satisfied", LOGLIT (lit)); if (!c->redundant || GET_OPTION (vivifyimply) == 1) { implied = lit; conflict = vivify_unit_conflict (solver, lit); assert (!EMPTY_STACK (solver->analyzed)); } else if (GET_OPTION (vivifyimply) == 2) { LOGCLS (c, "vivify implied"); kissat_mark_clause_as_garbage (solver, c); INC (vivify_implied); res = true; } break; } if (c->garbage) assert (EMPTY_STACK (solver->analyzed)); else if (conflict) { assert (!EMPTY_STACK (solver->analyzed)); assert (solver->level); bool irredundant; const bool subsumed = vivify_analyze (solver, c, conflict, &irredundant); if (subsumed) { LOGCLS (c, "vivify subsumed"); kissat_mark_clause_as_garbage (solver, c); vivify_inc_subsume (solver, c); res = true; } else res = vivify_learn (solver, c, non_false, irredundant, implied); reset_vivify_analyzed (solver); } else { assert (EMPTY_STACK (solver->analyzed)); LOG ("vivify failed"); } if (!res) return false; INC (vivified); if (c->redundant) INC (vivify_redundant); else INC (vivify_irredundant); return true; } enum round { REDUNDANT_TIER1_ROUND = 1, REDUNDANT_TIER2_ROUND = 2, IRREDUNDANT_ROUND = 3 }; typedef enum round round; static void vivify_round (kissat * solver, round round) { assert (solver->watching); assert (solver->probing); #ifndef QUIET const char *mode; const char *type; char tag; if (round == REDUNDANT_TIER2_ROUND) { mode = "vivify-redundant-tier2"; type = "redundant"; tag = 'u'; } else if (round == REDUNDANT_TIER1_ROUND) { mode = "vivify-redundant-tier1"; type = "redundant"; tag = 'v'; } else { assert (round == IRREDUNDANT_ROUND); mode = "vivify-irredundant"; type = "irredundant"; tag = 'w'; } #endif references schedule; INIT_STACK (schedule); unsigned *counts = 0; kissat_flush_large_watches (solver); counts = new_vivification_candidates_counts (solver); { bool redundant, tier2; if (round == REDUNDANT_TIER1_ROUND) redundant = true, tier2 = false; else if (round == REDUNDANT_TIER2_ROUND) redundant = tier2 = true; else { assert (round == IRREDUNDANT_ROUND); redundant = tier2 = false; } schedule_vivification_candidates (solver, #ifndef QUIET mode, type, #endif &schedule, counts, redundant, tier2); } sort_vivification_candidates (solver, &schedule, counts); kissat_watch_large_clauses (solver); const size_t scheduled = SIZE_STACK (schedule); #ifndef QUIET const size_t total = (round == IRREDUNDANT_ROUND) ? IRREDUNDANT_CLAUSES : REDUNDANT_CLAUSES; kissat_phase (solver, mode, GET (probings), "scheduled %zu %s clauses %.0f%% of %zu", scheduled, type, kissat_percent (scheduled, total), total); #endif SET_EFFICIENCY_BOUND (ticks_limit, vivify, probing_ticks, search_ticks, kissat_nlogn (scheduled)); if (round == REDUNDANT_TIER2_ROUND) { const uint64_t delta = ticks_limit - solver->statistics.probing_ticks; ticks_limit += 3 * delta; kissat_very_verbose (solver, "increasing redundant tier2 efficiency limit to %" PRIu64 " by %" PRIu64 " = 3 * %" PRIu64, ticks_limit, 3 * delta, delta); } size_t vivified = 0, tried = 0; unsigneds sorted; INIT_STACK (sorted); while (!EMPTY_STACK (schedule)) { if (solver->statistics.probing_ticks > ticks_limit) break; if (TERMINATED (19)) break; const reference ref = POP_STACK (schedule); clause *c = kissat_dereference_clause (solver, ref); if (c->garbage) continue; tried++; if (vivify_clause (solver, c, &sorted, counts)) vivified++; c->vivify = false; if (solver->inconsistent) break; } if (solver->level) kissat_backtrack (solver, 0); kissat_dealloc (solver, counts, LITS, sizeof *counts); RELEASE_STACK (sorted); #ifndef QUIET kissat_phase (solver, mode, GET (probings), "vivified %zu %s clauses %.0f%% out of %zu tried", vivified, type, kissat_percent (vivified, tried), tried); if (!solver->inconsistent) { size_t remain = SIZE_STACK (schedule); if (remain) { kissat_phase (solver, mode, GET (probings), "%zu %s clauses remain %.0f%% out of %zu scheduled", remain, type, kissat_percent (remain, scheduled), scheduled); const word *arena = BEGIN_STACK (solver->arena); size_t prioritized = 0; while (!EMPTY_STACK (schedule)) { const unsigned ref = POP_STACK (schedule); clause *c = (clause *) (arena + ref); if (c->vivify) prioritized++; } if (prioritized) kissat_phase (solver, mode, GET (probings), "keeping %zu %s clauses prioritized %.0f%%", prioritized, type, kissat_percent (prioritized, remain)); else kissat_phase (solver, mode, GET (probings), "no prioritized %s clauses left", type); } else kissat_phase (solver, mode, GET (probings), "all scheduled %s clauses tried", type); } #endif RELEASE_STACK (schedule); REPORT (!vivified, tag); } static void vivify_redundant_tier1 (kissat * solver) { if (TERMINATED (20)) return; vivify_round (solver, REDUNDANT_TIER1_ROUND); } static void vivify_redundant_tier2 (kissat * solver) { if (TERMINATED (21)) return; vivify_round (solver, REDUNDANT_TIER2_ROUND); } static void vivify_irredundant (kissat * solver) { if (TERMINATED (22)) return; vivify_round (solver, IRREDUNDANT_ROUND); } static bool really_vivify (kissat * solver) { if (!GET_OPTION (really)) return true; const uint64_t limit = IRREDUNDANT_CLAUSES + 5 * REDUNDANT_CLAUSES; statistics *statistics = &solver->statistics; const uint64_t visits = statistics->search_ticks; return limit < visits + GET_OPTION (vivifymineff); } void kissat_vivify (kissat * solver) { if (solver->inconsistent) return; assert (!solver->level); assert (solver->probing); assert (solver->watching); if (!GET_OPTION (vivify)) return; if (!solver->statistics.clauses_redundant) return; if (!really_vivify (solver)) return; START (vivify); vivify_redundant_tier2 (solver); if (!solver->inconsistent) { vivify_redundant_tier1 (solver); if (!solver->inconsistent && IRREDUNDANT_CLAUSES / 10 < REDUNDANT_CLAUSES) vivify_irredundant (solver); } STOP (vivify); }