/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    sat_solver.cpp

Abstract:

    SAT solver main class.

Author:

    Leonardo de Moura (leonardo) 2011-05-21.

Revision History:

--*/


#include <cmath>
#ifndef SINGLE_THREAD
#include <thread>
#endif
#include "util/luby.h"
#include "util/trace.h"
#include "util/max_cliques.h"
#include "util/gparams.h"
#include "sat/sat_solver.h"
#include "sat/sat_integrity_checker.h"
#include "sat/sat_lookahead.h"
#include "sat/sat_ddfw_wrapper.h"
#include "sat/sat_prob.h"
#include "sat/sat_anf_simplifier.h"
#include "sat/sat_cut_simplifier.h"
#if defined(_MSC_VER) && !defined(_M_ARM) && !defined(_M_ARM64)
# include <xmmintrin.h>
#endif


namespace sat {


    solver::solver(params_ref const & p, reslimit& l):
        solver_core(l),
        m_checkpoint_enabled(true),
        m_config(p),
        m_par(nullptr),
        m_drat(*this),
        m_cls_allocator_idx(false),
        m_cleaner(*this),
        m_simplifier(*this, p),
        m_scc(*this, p),
        m_asymm_branch(*this, p),
        m_probing(*this, p),
        m_mus(*this),
        m_binspr(*this),
        m_inconsistent(false),
        m_searching(false),
        m_conflict(justification(0)),
        m_num_frozen(0),
        m_activity_inc(128),
        m_case_split_queue(m_activity),
        m_qhead(0),
        m_scope_lvl(0),
        m_search_lvl(0),
        m_fast_glue_avg(),
        m_slow_glue_avg(),
        m_fast_glue_backup(),
        m_slow_glue_backup(),
        m_trail_avg(),
        m_params(p),
        m_par_id(0),
        m_par_syncing_clauses(false) {
        init_reason_unknown();
        updt_params(p);
        m_best_phase_size         = 0;
        m_conflicts_since_gc      = 0;
        m_conflicts_since_init    = 0;
        m_next_simplify           = 0;
        m_num_checkpoints         = 0;
        m_simplifications         = 0;
        m_touch_index             = 0;
        m_ext                     = nullptr;
        m_cuber                   = nullptr;
        m_local_search            = nullptr;
        m_mc.set_solver(this);
        mk_var(false, false);
    }

    solver::~solver() {
        m_ext = nullptr;
        SASSERT(m_config.m_num_threads > 1 || m_trim || check_invariant());
        CTRACE("sat", !m_clauses.empty(), tout << "Delete clauses\n";);
        del_clauses(m_clauses);
        CTRACE("sat", !m_learned.empty(), tout << "Delete learned\n";);
        del_clauses(m_learned);
        dealloc(m_cuber);
        m_cuber = nullptr;
    }

    void solver::del_clauses(clause_vector& clauses) {
        for (clause * cp : clauses) 
            dealloc_clause(cp);
        clauses.reset();
        ++m_stats.m_non_learned_generation;
    }

    void solver::set_extension(extension* ext) {
        m_ext = ext;
        if (ext) {
            ext->set_solver(this);
            for (unsigned i = num_user_scopes(); i-- > 0;)
                ext->user_push();
            for (unsigned i = num_scopes(); i-- > 0;)
                ext->push();
        }
    }

    void solver::copy(solver const & src, bool copy_learned) {
        pop_to_base_level();
        del_clauses(m_clauses);
        del_clauses(m_learned);
        m_watches.reset();
        m_assignment.reset();
        m_justification.reset();
        m_decision.reset();
        m_eliminated.reset();
        m_external.reset();
        m_var_scope.reset();
        m_activity.reset();
        m_mark.reset();
        m_lit_mark.reset();
        m_best_phase.reset();
        m_phase.reset();
        m_prev_phase.reset();
        m_assigned_since_gc.reset();
        m_last_conflict.reset();
        m_last_propagation.reset();
        m_participated.reset();
        m_canceled.reset();
        m_reasoned.reset();
        m_case_split_queue.reset();
        m_simplifier.reset_todos();
        m_qhead = 0;
        m_trail.reset();
        m_scopes.reset();
        mk_var(false, false);

        if (src.inconsistent()) {
            set_conflict();
            return;
        }
        
        // create new vars
        for (bool_var v = num_vars(); v < src.num_vars(); v++) {
            bool ext  = src.m_external[v];
            bool dvar = src.m_decision[v];
            VERIFY(v == mk_var(ext, dvar));
            if (src.was_eliminated(v)) {
                set_eliminated(v, true);
            }
            m_phase[v] = src.m_phase[v];
            m_best_phase[v] = src.m_best_phase[v];
            m_prev_phase[v] = src.m_prev_phase[v];

            // inherit activity:
            m_activity[v] = src.m_activity[v];
            m_case_split_queue.activity_changed_eh(v, false);
        }

        //
        // register the extension before performing assignments.
        // the assignments may call back into the extension.
        //
        if (src.get_extension()) {
            m_ext = src.get_extension()->copy(this);
        }

        unsigned trail_sz = src.init_trail_size();
        for (unsigned i = 0; i < trail_sz; ++i) {
            assign_unit(src.m_trail[i]);
        }

        // copy binary clauses 
        {
            unsigned sz = src.m_watches.size();
            for (unsigned l_idx = 0; l_idx < sz; ++l_idx) {
                literal l = ~to_literal(l_idx);
                if (src.was_eliminated(l.var())) continue;
                watch_list const & wlist = src.m_watches[l_idx];
                for (auto & wi : wlist) {
                    if (!wi.is_binary_clause())
                        continue;
                    literal l2 = wi.get_literal();
                    if (l.index() > l2.index() ||
                        src.was_eliminated(l2.var()))
                        continue;
                                        
                    watched w1(l2, wi.is_learned());
                    watched w2(l, wi.is_learned());
                    m_watches[(~l).index()].push_back(w1);
                    m_watches[(~l2).index()].push_back(w2);
                }
            }
        }

        {
            literal_vector buffer;
            // copy clauses
            for (clause* c : src.m_clauses) {
                buffer.reset();
                for (literal l : *c) buffer.push_back(l);
                mk_clause_core(buffer);
            }

            // copy high quality lemmas
            unsigned num_learned = 0;
            for (clause* c : src.m_learned) {
                if (c->glue() <= 2 || (c->size() <= 40 && c->glue() <= 8) || copy_learned) {
                    buffer.reset();
                    for (literal l : *c) buffer.push_back(l);
                    clause* c1 = mk_clause_core(buffer.size(), buffer.data(), sat::status::redundant());
                    if (c1) {
                        ++num_learned;
                        c1->set_glue(c->glue());
                        c1->set_psm(c->psm());
                    }
                }
            }
            IF_VERBOSE(2, verbose_stream() << "(sat.copy :learned " << num_learned << ")\n";);
        }
        m_best_phase_size = src.m_best_phase_size;
        if (m_best_phase_size > 0) {
            for (bool_var v = 0; v < num_vars(); ++v) {
                m_best_phase[v] = src.m_best_phase[v];
            }
        }

        m_user_scope_literals.reset();
        for (auto lit : src.m_user_scope_literals) 
            assign_unit(~lit);

        m_mc = src.m_mc;
        m_stats.m_units = init_trail_size();
    }

    // -----------------------
    //
    // Variable & Clause creation
    //
    // -----------------------

    void solver::reset_var(bool_var v, bool ext, bool dvar) {
        m_watches[2*v].reset();
        m_watches[2*v+1].reset();
        m_assignment[2*v] = l_undef;
        m_assignment[2*v+1] = l_undef;
        m_justification[v] = justification(UINT_MAX);
        m_decision[v] = dvar;
        m_eliminated[v] = false;
        m_external[v] = ext;
        m_var_scope[v] = scope_lvl();
        m_touched[v] = 0;
        m_activity[v] = 0;
        m_mark[v] = false;
        m_lit_mark[2*v] = false;
        m_lit_mark[2*v+1] = false;
        m_phase[v] = false;
        m_best_phase[v] = false;
        m_prev_phase[v] = false;
        m_assigned_since_gc[v] = false;
        m_last_conflict[v] = 0;        
        m_last_propagation[v] = 0;
        m_participated[v] = 0;
        m_canceled[v] = 0;
        m_reasoned[v] = 0;
        m_case_split_queue.mk_var_eh(v);
        m_simplifier.insert_elim_todo(v);
    }

    bool_var solver::mk_var(bool ext, bool dvar) {
        m_model_is_current = false;
        m_stats.m_mk_var++;
        bool_var v = m_justification.size();
        
        if (!m_free_vars.empty()) {
            v = m_free_vars.back();
            m_free_vars.pop_back();
            m_active_vars.push_back(v);
            reset_var(v, ext, dvar);
            SASSERT(v < m_justification.size());
            return v;
        }
        m_active_vars.push_back(v);
        m_watches.push_back(watch_list());
        m_watches.push_back(watch_list());
        m_assignment.push_back(l_undef);
        m_assignment.push_back(l_undef);
        m_justification.push_back(justification(UINT_MAX));
        m_decision.push_back(dvar);
        m_eliminated.push_back(false);
        m_external.push_back(ext);
        m_var_scope.push_back(scope_lvl());
        m_touched.push_back(0);
        m_activity.push_back(0); 
        m_mark.push_back(false);
        m_lit_mark.push_back(false);
        m_lit_mark.push_back(false);
        m_phase.push_back(false);
        m_best_phase.push_back(false);
        m_prev_phase.push_back(false);
        m_assigned_since_gc.push_back(false);
        m_last_conflict.push_back(0);
        m_last_propagation.push_back(0);
        m_participated.push_back(0);
        m_canceled.push_back(0);
        m_reasoned.push_back(0);
        m_case_split_queue.mk_var_eh(v);
        m_simplifier.insert_elim_todo(v);
        SASSERT(!was_eliminated(v));
        return v;
    }

    void solver::set_non_external(bool_var v) {
        m_external[v] = false;
    }

    void solver::set_external(bool_var v) {
        m_external[v] = true;
    }

    void solver::set_eliminated(bool_var v, bool f) { 
        if (m_eliminated[v] == f)
            return;
        if (!f) 
            reset_var(v, m_external[v], m_decision[v]);
        else if (f && m_ext)
            m_ext->set_eliminated(v);
        m_eliminated[v] = f; 
    }


    clause* solver::mk_clause(unsigned num_lits, literal * lits, sat::status st) {
        m_model_is_current = false;
            

        DEBUG_CODE({
                for (unsigned i = 0; i < num_lits; i++) {
                    CTRACE("sat", was_eliminated(lits[i]), tout << lits[i] << " was eliminated\n";);
                    SASSERT(!was_eliminated(lits[i]));
                }
        });

        if (m_user_scope_literals.empty()) {
            return mk_clause_core(num_lits, lits, st);
        }
        else {
            m_aux_literals.reset();
            m_aux_literals.append(num_lits, lits);
            m_aux_literals.append(m_user_scope_literals);
            return mk_clause_core(m_aux_literals.size(), m_aux_literals.data(), st);
        }
    }

    clause* solver::mk_clause(literal l1, literal l2, sat::status st) {
        literal ls[2] = { l1, l2 };
        return mk_clause(2, ls, st);
    }

    clause* solver::mk_clause(literal l1, literal l2, literal l3, sat::status st) {
        literal ls[3] = { l1, l2, l3 };
        return mk_clause(3, ls, st);
    }

    void solver::del_clause(clause& c) {
        if (!c.is_learned()) 
            m_stats.m_non_learned_generation++;
        
        if (c.frozen()) 
            --m_num_frozen;
        
        if (!c.was_removed() && m_config.m_drat && !m_drat.is_cleaned(c)) 
            m_drat.del(c);
        
        dealloc_clause(&c);        
        if (m_searching) 
            m_stats.m_del_clause++;
    }

    void solver::drat_explain_conflict() {
        if (m_config.m_drat && m_ext) {
            extension::scoped_drating _sd(*m_ext);
            bool unique_max;
            m_conflict_lvl = get_max_lvl(m_not_l, m_conflict, unique_max);        
            resolve_conflict_for_unsat_core();                
        }
    }

    void solver::drat_log_unit(literal lit, justification j) {
        if (!m_ext)
            return;
        extension::scoped_drating _sd(*m_ext.get());
        if (j.get_kind() == justification::EXT_JUSTIFICATION) 
            fill_ext_antecedents(lit, j, false);
        else 
            m_drat.add(lit, m_searching);       
    }

    void solver::drat_log_clause(unsigned num_lits, literal const* lits, sat::status st) {
        m_drat.add(num_lits, lits, st);
    }

    clause * solver::mk_clause_core(unsigned num_lits, literal * lits, sat::status st) {
        bool redundant = st.is_redundant();
        TRACE("sat", tout << "mk_clause: "  << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";);
        bool logged = false;
        if (!redundant || !st.is_sat()) {
            unsigned old_sz = num_lits;
            bool keep = m_trim || simplify_clause(num_lits, lits);
            TRACE("sat_mk_clause", tout << "mk_clause (after simp), keep: " << keep << "\n" << mk_lits_pp(num_lits, lits) << "\n";);
            if (!keep) {
                return nullptr; // clause is equivalent to true.
            }
            // if an input clause is simplified, then log the simplified version as learned
            if (m_config.m_drat && old_sz > num_lits) {
                drat_log_clause(num_lits, lits, st);
                logged = true;
            }

            ++m_stats.m_non_learned_generation;
            if (!m_searching) 
                m_mc.add_clause(num_lits, lits);            
        }       

        switch (num_lits) {
        case 0:
            set_conflict();
            return nullptr;
        case 1:
            if (!logged && m_config.m_drat)
                drat_log_clause(num_lits, lits, st);
            {
                flet<bool> _disable_drat(m_config.m_drat, false);
                assign(lits[0], justification(0));
            }
            return nullptr;
        case 2:
            mk_bin_clause(lits[0], lits[1], st);
            if (redundant && m_par) 
                m_par->share_clause(*this, lits[0], lits[1]);
            return nullptr;
        default:
            return mk_nary_clause(num_lits, lits, st);
        }
    }

    void solver::mk_bin_clause(literal l1, literal l2, sat::status st) {
        bool redundant = st.is_redundant();
        m_touched[l1.var()] = m_touch_index;
        m_touched[l2.var()] = m_touch_index;

        if (m_config.m_drat)
            m_drat.add(l1, l2, st);
        
        if (redundant && !m_trim && find_binary_watch(get_wlist(~l1), ~l2) && value(l1) == l_undef) {
            assign_unit(l1);
            return;
        }
        if (redundant && !m_trim && find_binary_watch(get_wlist(~l2), ~l1) && value(l2) == l_undef) {
            assign_unit(l2);
            return;
        }
        watched* w0 = redundant ? find_binary_watch(get_wlist(~l1), l2) : nullptr;
        if (w0 && !m_trim) {
            TRACE("sat", tout << "found binary " << l1 << " " << l2 << "\n";);
            if (w0->is_learned() && !redundant) {
                w0->set_learned(false);
                w0 = find_binary_watch(get_wlist(~l2), l1);            
                VERIFY(w0);
                w0->set_learned(false);                        
            }
            if (propagate_bin_clause(l1, l2) && !at_base_lvl() && !redundant)
                push_reinit_stack(l1, l2);
            else if (has_variables_to_reinit(l1, l2))
                push_reinit_stack(l1, l2);
            return;
        }

        if (propagate_bin_clause(l1, l2)) {
            if (!at_base_lvl())
                push_reinit_stack(l1, l2);
            else if (!m_trim)
                return;
        }
        else if (has_variables_to_reinit(l1, l2))
            push_reinit_stack(l1, l2);
        m_stats.m_mk_bin_clause++;
        get_wlist(~l1).push_back(watched(l2, redundant));
        get_wlist(~l2).push_back(watched(l1, redundant));
    }

    bool solver::has_variables_to_reinit(clause const& c) const {
        for (auto lit : c)
            if (m_var_scope[lit.var()] > 0)
                return true;
        return false;
    }

    bool solver::has_variables_to_reinit(literal l1, literal l2) const {
        if (at_base_lvl())
            return false;
        if (m_var_scope[l1.var()] > 0)
            return true;
        if (m_var_scope[l2.var()] > 0)
            return true;
        return false;
    }

    bool solver::propagate_bin_clause(literal l1, literal l2) {
        if (value(l2) == l_false && value(l1) != l_true) {
            m_stats.m_bin_propagate++;
            assign(l1, justification(lvl(l2), l2));            
            return true;
        }
        if (value(l1) == l_false && value(l2) != l_true) {
            m_stats.m_bin_propagate++;
            assign(l2, justification(lvl(l1), l1));
            return true;
        }
        return false;
    }

    void solver::push_reinit_stack(clause & c) {
        SASSERT(!at_base_lvl());
        TRACE("sat_reinit", tout << "adding to reinit stack: " << c << "\n";);
        m_clauses_to_reinit.push_back(clause_wrapper(c));
        c.set_reinit_stack(true);
    }

    void solver::push_reinit_stack(literal l1, literal l2) {
        TRACE("sat_reinit", tout << "adding to reinit stack: " << l1 << " " << l2 << "\n";);
        m_clauses_to_reinit.push_back(clause_wrapper(l1, l2));
    }

    clause * solver::mk_nary_clause(unsigned num_lits, literal * lits, sat::status st) {
        m_stats.m_mk_clause++;
        clause * r = alloc_clause(num_lits, lits, st.is_redundant());
        SASSERT(!st.is_redundant() || r->is_learned());
        bool reinit = attach_nary_clause(*r, st.is_sat() && st.is_redundant());
 
        if (reinit || has_variables_to_reinit(*r)) 
            push_reinit_stack(*r);
        if (st.is_redundant()) 
            m_learned.push_back(r);
        else 
            m_clauses.push_back(r);
        if (m_config.m_drat) 
            m_drat.add(*r, st);
        for (literal l : *r) 
            m_touched[l.var()] = m_touch_index;
        return r;
    }

    bool solver::attach_nary_clause(clause & c, bool is_asserting) {
        bool reinit = false;
        clause_offset cls_off = cls_allocator().get_offset(&c);
        if (!at_base_lvl()) {
            if (is_asserting) {
                unsigned w2_idx = select_learned_watch_lit(c);
                std::swap(c[1], c[w2_idx]);
            }
            else {
                unsigned w1_idx = select_watch_lit(c, 0);
                std::swap(c[0], c[w1_idx]);
                unsigned w2_idx = select_watch_lit(c, 1);
                std::swap(c[1], c[w2_idx]);
            }

            if (value(c[0]) == l_false) {
                m_stats.m_propagate++; 
                unsigned level = lvl(c[0]);
                for (unsigned i = c.size(); i-- > 2; ) {
                    level = std::max(level, lvl(c[i]));
                }
                assign(c[1], justification(level, cls_off));
                reinit |= !c.is_learned();
            }
            else if (value(c[1]) == l_false) {
                m_stats.m_propagate++;
                unsigned level = lvl(c[1]);
                for (unsigned i = c.size(); i-- > 2; ) {
                    level = std::max(level, lvl(c[i]));
                }
                assign(c[0], justification(level, cls_off));
                reinit |= !c.is_learned();
            }
        }
        unsigned some_idx = c.size() >> 1;
        literal block_lit = c[some_idx];
        VERIFY(!c.frozen());
        DEBUG_CODE(for (auto const& w : m_watches[(~c[0]).index()]) SASSERT(!w.is_clause() || w.get_clause_offset() != cls_off););
        DEBUG_CODE(for (auto const& w : m_watches[(~c[1]).index()]) SASSERT(!w.is_clause() || w.get_clause_offset() != cls_off););
        SASSERT(c[0] != c[1]);
        m_watches[(~c[0]).index()].push_back(watched(block_lit, cls_off));
        m_watches[(~c[1]).index()].push_back(watched(block_lit, cls_off));
        return reinit;
    }

    void solver::attach_clause(clause & c, bool & reinit) {
        SASSERT(c.size() > 2);
        reinit = attach_nary_clause(c, c.is_learned() && !c.on_reinit_stack());
    }

    void solver::set_learned(clause& c, bool redundant) {
        if (c.is_learned() != redundant) 
            c.set_learned(redundant);
    }

    void solver::set_learned1(literal l1, literal l2, bool redundant) {
        for (watched& w : get_wlist(~l1)) {
            if (w.is_binary_clause() && l2 == w.get_literal() && !w.is_learned()) {
                w.set_learned(redundant);
                break;
            }
        }
    }

    void solver::shrink(clause& c, unsigned old_sz, unsigned new_sz) {
        SASSERT(new_sz > 2);
        SASSERT(old_sz >= new_sz);
        if (old_sz != new_sz) {
            c.shrink(new_sz);
            for (literal l : c) {
                m_touched[l.var()] = m_touch_index;
            }
            if (m_config.m_drat) {
                m_drat.add(c, status::redundant());
                c.restore(old_sz);
                m_drat.del(c);
                c.shrink(new_sz);
            }
        }
    }

    bool solver::memory_pressure() {
        return 3*cls_allocator().get_allocation_size()/2 + memory::get_allocation_size() > memory::get_max_memory_size();
    }

    struct solver::cmp_activity {
        solver& s;
        cmp_activity(solver& s):s(s) {}
        bool operator()(bool_var v1, bool_var v2) const {
            return s.m_activity[v1] > s.m_activity[v2];
        }
    };

    bool solver::should_defrag() {
        if (m_defrag_threshold > 0) --m_defrag_threshold;
        return m_defrag_threshold == 0 && m_config.m_gc_defrag;
    }

    void solver::defrag_clauses() {
        m_defrag_threshold = 2;
        if (memory_pressure()) return;
        pop(scope_lvl());
        IF_VERBOSE(2, verbose_stream() << "(sat-defrag)\n");
        clause_allocator& alloc = m_cls_allocator[!m_cls_allocator_idx];
        ptr_vector<clause> new_clauses, new_learned;
        for (clause* c : m_clauses) c->unmark_used();
        for (clause* c : m_learned) c->unmark_used();

        svector<bool_var> vars;
        for (unsigned i = 0; i < num_vars(); ++i) vars.push_back(i);
        std::stable_sort(vars.begin(), vars.end(), cmp_activity(*this));
        literal_vector lits;
        for (bool_var v : vars) lits.push_back(literal(v, false)), lits.push_back(literal(v, true));
        // walk clauses, reallocate them in an order that defragments memory and creates locality.
        for (literal lit : lits) {
            watch_list& wlist = m_watches[lit.index()];
            for (watched& w : wlist) {
                if (w.is_clause()) {
                    clause& c1 = get_clause(w);
                    clause_offset offset;
                    if (c1.was_used()) {
                        offset = c1.get_new_offset();
                    }
                    else {
                        clause* c2 = alloc.copy_clause(c1); 
                        c1.mark_used();
                        if (c1.is_learned()) {
                            new_learned.push_back(c2);
                        }
                        else {
                            new_clauses.push_back(c2);
                        }
                        offset = get_offset(*c2);
                        c1.set_new_offset(offset);
                    }
                    w = watched(w.get_blocked_literal(), offset);
                }
            }
        }

        // reallocate ternary clauses.
        for (clause* c : m_clauses) {
            if (!c->was_used()) {
                SASSERT(c->size() == 3);
                new_clauses.push_back(alloc.copy_clause(*c));
            }
            dealloc_clause(c);
        }

        for (clause* c : m_learned) {
            if (!c->was_used()) {
                SASSERT(c->size() == 3);
                new_learned.push_back(alloc.copy_clause(*c));
            }
            dealloc_clause(c);
        }
        m_clauses.swap(new_clauses);
        m_learned.swap(new_learned);

        cls_allocator().finalize();
        m_cls_allocator_idx = !m_cls_allocator_idx;

        reinit_assumptions();
    }


    void solver::set_learned(literal l1, literal l2, bool redundant) {
        set_learned1(l1, l2, redundant);
        set_learned1(l2, l1, redundant);
    }

    /**
       \brief Select a watch literal starting the search at the given position.
       This method is only used for clauses created during the search.

       I use the following rules to select a watch literal.

       1- select a literal l in idx >= starting_at such that value(l) = l_true,
       and for all l' in idx' >= starting_at . value(l') = l_true implies lvl(l) <= lvl(l')

       The purpose of this rule is to make the clause inactive for as long as possible. A clause
       is inactive when it contains a literal assigned to true.

       2- if there isn't a literal assigned to true, then select an unassigned literal l in idx >= starting_at

       3- if there isn't a literal l in idx >= starting_at such that value(l) = l_true or
       value(l) = l_undef (that is, all literals at positions >= starting_at are assigned
       to false), then peek the literal l such that for all l' starting at starting_at
       lvl(l) >= lvl(l')

       Without rule 3, boolean propagation is incomplete, that is, it may miss possible propagations.

       \remark The method select_lemma_watch_lit is used to select the watch literal for regular learned clauses.
    */
    unsigned solver::select_watch_lit(clause const & cls, unsigned starting_at) const {
        SASSERT(cls.size() >= 2);
        unsigned min_true_idx  = UINT_MAX;
        unsigned max_false_idx = UINT_MAX;
        unsigned unknown_idx   = UINT_MAX;
        unsigned n = cls.size();
        for (unsigned i = starting_at; i < n; i++) {
            literal l   = cls[i];
            switch(value(l)) {
            case l_false:
                if (max_false_idx == UINT_MAX || lvl(l) > lvl(cls[max_false_idx]))
                    max_false_idx = i;
                break;
            case l_undef:
                unknown_idx = i;
                break;
            case l_true:
                if (min_true_idx == UINT_MAX || lvl(l) < lvl(cls[min_true_idx]))
                    min_true_idx = i;
                break;
            }
        }
        if (min_true_idx != UINT_MAX)
            return min_true_idx;
        if (unknown_idx != UINT_MAX)
            return unknown_idx;
        SASSERT(max_false_idx != UINT_MAX);
        return max_false_idx;
    }

    /**
       \brief The learned clauses (lemmas) produced by the SAT solver
       have the property that the first literal will be implied by it
       after backtracking. All other literals are assigned to (or
       implied to be) false when the learned clause is created. The
       first watch literal will always be the first literal.  The
       second watch literal is computed by this method. It should be
       the literal with the highest decision level.

       // TODO: do we really need this? strength the conflict resolution
    */
    unsigned solver::select_learned_watch_lit(clause const & cls) const {
        SASSERT(cls.size() >= 2);
        unsigned max_false_idx = UINT_MAX;
        unsigned num_lits = cls.size();
        for (unsigned i = 1; i < num_lits; i++) {
            literal l    = cls[i];
            CTRACE("sat", value(l) != l_false, tout << l << ":=" << value(l););
            SASSERT(value(l) == l_false);
            if (max_false_idx == UINT_MAX || lvl(l) > lvl(cls[max_false_idx]))
                max_false_idx = i;
        }
        return max_false_idx;
    }

    template<bool lvl0>
    bool solver::simplify_clause_core(unsigned & num_lits, literal * lits) const {
        std::sort(lits, lits+num_lits);
        literal prev = null_literal;
        unsigned i = 0;
        unsigned j = 0;
        for (; i < num_lits; i++) {
            literal curr = lits[i];
            lbool val = value(curr);
            if (!lvl0 && lvl(curr) > 0)
                val = l_undef;
            switch (val) {
            case l_false:
                break; // ignore literal
            case l_undef:
                if (curr == ~prev)
                    return false; // clause is equivalent to true
                if (curr != prev) {
                    prev = curr;
                    if (i != j)
                        std::swap(lits[j], lits[i]);
                    j++;
                }
                break;
            case l_true:
                return false; // clause is equivalent to true
            }
        }
        num_lits = j;
        return true;
    }

    bool solver::simplify_clause(unsigned & num_lits, literal * lits) const {
        if (at_base_lvl()) 
            return simplify_clause_core<true>(num_lits, lits);
        else
            return simplify_clause_core<false>(num_lits, lits);
    }

    void solver::detach_bin_clause(literal l1, literal l2, bool redundant) {
        get_wlist(~l1).erase(watched(l2, redundant));
        get_wlist(~l2).erase(watched(l1, redundant));
        if (m_config.m_drat) m_drat.del(l1, l2);       
    }

    void solver::detach_clause(clause& c) {
        detach_nary_clause(c);
    }

    void solver::detach_nary_clause(clause & c) {
        clause_offset cls_off = get_offset(c);
        erase_clause_watch(get_wlist(~c[0]), cls_off);
        erase_clause_watch(get_wlist(~c[1]), cls_off);
    }

    // -----------------------
    //
    // Basic
    //
    // -----------------------

    void solver::set_conflict(justification c, literal not_l) {
        if (m_inconsistent)
            return;
        m_inconsistent = true;
        m_conflict = c;
        m_not_l    = not_l;
        TRACE("sat", display(display_justification(tout << "conflict " << not_l << " ", c) << "\n"));
    }

    void solver::assign_core(literal l, justification j) {
        SASSERT(value(l) == l_undef);
        SASSERT(!m_trail.contains(l) && !m_trail.contains(~l));
        TRACE("sat_assign_core", tout << l << " " << j << "\n";);
        if (j.level() == 0) {
            if (m_config.m_drat) 
                drat_log_unit(l, j);
            if (!m_trim)
                j = justification(0); // erase justification for level 0
        }
        else {
            VERIFY(!at_base_lvl());
        }
        m_assignment[l.index()]    = l_true;
        m_assignment[(~l).index()] = l_false;
        bool_var v = l.var();
        m_justification[v]         = j;
        m_phase[v]                 = !l.sign();
        m_assigned_since_gc[v]     = true;
        m_trail.push_back(l);
        
        switch (m_config.m_branching_heuristic) {
        case BH_VSIDS: 
            break;
        case BH_CHB:
            m_last_propagation[v] = m_stats.m_conflict;
            break;
        }

        if (m_config.m_anti_exploration) {
            uint64_t age = m_stats.m_conflict - m_canceled[v];
            if (age > 0) {
                double decay = pow(0.95, static_cast<double>(age));
                set_activity(v, static_cast<unsigned>(m_activity[v] * decay));
                // NB. MapleSAT does not update canceled.
                m_canceled[v] = m_stats.m_conflict;
            }
        }
        
        if (m_config.m_propagate_prefetch) {
#if defined(__GNUC__) || defined(__clang__)
            __builtin_prefetch((const char*)((m_watches[l.index()].data())));
#else
    #if !defined(_M_ARM) && !defined(_M_ARM64)
            _mm_prefetch((const char*)((m_watches[l.index()].data())), _MM_HINT_T1);
    #endif
#endif
        }

        SASSERT(!l.sign() || !m_phase[v]);
        SASSERT(l.sign()  || m_phase[v]);
        SASSERT(!l.sign() || value(v) == l_false);
        SASSERT(l.sign()  || value(v) == l_true);
        SASSERT(value(l) == l_true);
        SASSERT(value(~l) == l_false);
    }

    lbool solver::status(clause const & c) const {
        bool found_undef = false;
        for (literal lit : c) {
            switch (value(lit)) {
            case l_true:
                return l_true;
            case l_undef:
                found_undef = true;
                break;
            default:
                break;
            }
        }
        return found_undef ? l_undef : l_false;
    }

    // -----------------------
    //
    // Propagation
    //
    // -----------------------

    bool solver::propagate_core(bool update) {
        if (m_ext && (!is_probing() || at_base_lvl())) 
            m_ext->unit_propagate();    
        while (m_qhead < m_trail.size() && !m_inconsistent) {
            do {
                checkpoint();
                m_cleaner.dec();
                literal l = m_trail[m_qhead];
                m_qhead++;
                if (!propagate_literal(l, update))
                    return false;
            } while (m_qhead < m_trail.size());

            if (m_ext && (!is_probing() || at_base_lvl())) 
                m_ext->unit_propagate();            
        }
        if (m_inconsistent)
            return false;

        SASSERT(m_qhead == m_trail.size());
        SASSERT(!m_inconsistent);
        return true;
    }

    bool solver::propagate(bool update) {
        unsigned qhead = m_qhead;
        bool r = propagate_core(update);
        if (m_config.m_branching_heuristic == BH_CHB) {
            update_chb_activity(r, qhead);
        }
        CASSERT("sat_propagate", check_invariant());
        CASSERT("sat_missed_prop", check_missed_propagation());
        return r;
    }

    void solver::propagate_clause(clause& c, bool update, unsigned assign_level, clause_offset cls_off) {
        unsigned glue;
        SASSERT(value(c[0]) == l_undef); 
            m_stats.m_propagate++;          
            c.mark_used();                                          
            assign_core(c[0], justification(assign_level, cls_off)); 
            if (update && c.is_learned() && c.glue() > 2 && num_diff_levels_below(c.size(), c.begin(), c.glue() - 1, glue)) 
                c.set_glue(glue);                                   \
    }

    void solver::set_watch(clause& c, unsigned idx, clause_offset cls_off) {
        std::swap(c[1], c[idx]);
        DEBUG_CODE(for (auto const& w : m_watches[(~c[1]).index()]) VERIFY(!w.is_clause() || w.get_clause_offset() != cls_off););
        m_watches[(~c[1]).index()].push_back(watched(c[0], cls_off));
    }

    bool solver::propagate_literal(literal l, bool update) {
        literal l1;

        bool keep;
        unsigned curr_level = lvl(l);
        TRACE("sat_propagate", tout << "propagating: " << l << "@" << curr_level << " " << m_justification[l.var()] << "\n"; );

        literal not_l = ~l;
        SASSERT(value(l) == l_true);
        SASSERT(value(not_l) == l_false);
        watch_list& wlist = m_watches[l.index()];
        m_asymm_branch.dec(wlist.size());
        m_probing.dec(wlist.size());
        watch_list::iterator it = wlist.begin();
        watch_list::iterator it2 = it;
        watch_list::iterator end = wlist.end();
#define CONFLICT_CLEANUP() {                    \
                for (; it != end; ++it, ++it2)  \
                    *it2 = *it;                 \
                wlist.set_end(it2);             \
            }
        for (; it != end; ++it) {
            switch (it->get_kind()) {
            case watched::BINARY:
                l1 = it->get_literal();
                switch (value(l1)) {
                case l_false:
                    CONFLICT_CLEANUP();
                    set_conflict(justification(curr_level, not_l), ~l1);
                    return false;
                case l_undef:
                    m_stats.m_bin_propagate++;
                    assign_core(l1, justification(curr_level, not_l));
                    break;
                case l_true:
                    break; // skip
                }
                *it2 = *it;
                it2++;
                break;
            case watched::CLAUSE: {
                if (value(it->get_blocked_literal()) == l_true) {
                    TRACE("propagate_clause_bug", tout << "blocked literal " << it->get_blocked_literal() << "\n";
                    tout << get_clause(it) << "\n";);
                    *it2 = *it;
                    it2++;
                    break;
                }
                clause_offset cls_off = it->get_clause_offset();
                clause& c = get_clause(cls_off);
                TRACE("propagate_clause_bug", tout << "processing... " << c << "\nwas_removed: " << c.was_removed() << "\n";);
                if (c[0] == not_l)
                    std::swap(c[0], c[1]);
                CTRACE("propagate_bug", c[1] != not_l, tout << "l: " << l << " " << c << "\n";);


                if (c.was_removed() || c.size() == 1 || c[1] != not_l) {
                    // Remark: this method may be invoked when the watch lists are not in a consistent state,
                    // and may contain dead/removed clauses, or clauses with removed literals.
                    // See: method propagate_unit at sat_simplifier.cpp
                    // So, we must check whether the clause was marked for deletion, or
                    // c[1] != not_l
                    *it2 = *it;
                    it2++;
                    break;
                }
                if (value(c[0]) == l_true) {
                    it2->set_clause(c[0], cls_off);
                    it2++;
                    break;
                }
                VERIFY(c[1] == not_l);
                
                unsigned undef_index = 0;
                unsigned assign_level = curr_level;
                unsigned max_index = 1;
                unsigned num_undef = 0;
                unsigned sz = c.size();

                for (unsigned i = 2; i < sz && num_undef <= 1; ++i) {
                    literal lit = c[i];
                    switch (value(lit)) {
                    case l_true:
                        it2->set_clause(lit, cls_off);
                        it2++;
                        goto end_clause_case;
                    case l_undef:
                        undef_index = i;
                        ++num_undef;
                        break;
                    case l_false: {
                        unsigned level = lvl(lit);
                        if (level > assign_level) {
                            assign_level = level;
                            max_index = i;
                        }
                        break;
                    }
                    }
                }

                if (value(c[0]) == l_false)
                    assign_level = std::max(assign_level, lvl(c[0]));

                if (undef_index != 0) {       
                    set_watch(c, undef_index, cls_off);
                    if (value(c[0]) == l_false && num_undef == 1) {   
                        std::swap(c[0], c[1]);
                        propagate_clause(c, update, assign_level, cls_off);
                    }
                    goto end_clause_case;
                }

                if (value(c[0]) == l_false) {
                    c.mark_used();
                    CONFLICT_CLEANUP();
                    set_conflict(justification(assign_level, cls_off));
                    return false;
                }

                // value(c[0]) == l_undef

                if (max_index != 1) {
                    IF_VERBOSE(20, verbose_stream() << "swap watch for: " << c[1] << " " << c[max_index] << "\n");
                    set_watch(c, max_index, cls_off);
                }
                else {
                    *it2 = *it;
                    it2++;
                }
                propagate_clause(c, update, assign_level, cls_off);
            end_clause_case:
                break;
            }
            case watched::EXT_CONSTRAINT:
                SASSERT(m_ext);
                keep = m_ext->propagated(l, it->get_ext_constraint_idx());
                if (m_inconsistent) {
                    if (!keep) {
                        ++it;
                    }
                    CONFLICT_CLEANUP();
                    return false;
                }
                if (keep) {
                    *it2 = *it;
                    it2++;
                }
                break;
            default:
                UNREACHABLE();
                break;
            }
        }
        wlist.set_end(it2);
        if (m_ext && m_external[l.var()] && (!is_probing() || at_base_lvl()))
            m_ext->asserted(l);

        return true;
    }

    void solver::display_lookahead_scores(std::ostream& out) {
        lookahead lh(*this);
        lh.display_lookahead_scores(out);
    }

    lbool solver::cube(bool_var_vector& vars, literal_vector& lits, unsigned backtrack_level) {
        bool is_first = !m_cuber;
        if (is_first) {
            m_cuber = alloc(lookahead, *this);
        }
        lbool result = m_cuber->cube(vars, lits, backtrack_level);
        m_cuber->update_cube_statistics(m_aux_stats);
        switch (result) {
        case l_false:
            dealloc(m_cuber);
            m_cuber = nullptr;
            if (is_first) {
                pop_to_base_level();
                set_conflict();
            }
            break;
        case l_true: {
            lits.reset();
            pop_to_base_level();
            model const& mdl = m_cuber->get_model();
            for (bool_var v = 0; v < mdl.size(); ++v) {
                if (value(v) != l_undef) {
                    continue;
                }
                literal l(v, false);
                if (mdl[v] != l_true) l.neg();
                if (inconsistent())
                    return l_undef;
                push();
                assign_core(l, justification(scope_lvl()));
                propagate(false);
            }
            mk_model();
            break;
        }
        default:
            break;
        }
        return result;
    }


    // -----------------------
    //
    // Search
    //
    // -----------------------
    lbool solver::check(unsigned num_lits, literal const* lits) {
        init_reason_unknown();
        pop_to_base_level();
        m_stats.m_units = init_trail_size();
        IF_VERBOSE(2, verbose_stream() << "(sat.solver)\n";);
        SASSERT(at_base_lvl());

        if (m_config.m_ddfw_search) {
            m_cleaner(true);
            return do_ddfw_search(num_lits, lits);
        }
        if (m_config.m_prob_search) {
            m_cleaner(true);
            return do_prob_search(num_lits, lits);
        }
        if (m_config.m_local_search) {
            m_cleaner(true);
            return do_local_search(num_lits, lits);
        }
        if ((m_config.m_num_threads > 1 || m_config.m_ddfw_threads > 0) && !m_par && !m_ext) {
            SASSERT(scope_lvl() == 0);
            return check_par(num_lits, lits);
        }
        if (m_config.m_local_search_threads > 0 && !m_par && (!m_ext || m_ext->is_pb())) {
            SASSERT(scope_lvl() == 0);
            return check_par(num_lits, lits);
        }
        flet<bool> _searching(m_searching, true);
        m_clone = nullptr;
        if (m_mc.empty() && gparams::get_ref().get_bool("model_validate", false)) {
            
            m_clone = alloc(solver, m_no_drat_params, m_rlimit);
            m_clone->copy(*this);
            m_clone->set_extension(nullptr);
        }
        try {
            init_search();
            if (check_inconsistent()) return l_false;
            propagate(false);
            if (check_inconsistent()) return l_false;
            init_assumptions(num_lits, lits);
            propagate(false);
            if (check_inconsistent()) return l_false;
            if (m_config.m_force_cleanup) do_cleanup(true);
            TRACE("sat", display(tout););
            TRACE("before_search", display(tout););

            if (m_config.m_gc_burst) {
                // force gc
                m_conflicts_since_gc = m_gc_threshold + 1;
                do_gc();
            }

            if (m_config.m_enable_pre_simplify) {
                do_simplify();
                if (check_inconsistent()) return l_false;
            }

            if (m_config.m_max_conflicts == 0) {
                IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort: max-conflicts = 0\")\n";);
                TRACE("sat", display(tout); m_mc.display(tout););
                return l_undef;
            }

            if (m_config.m_phase == PS_LOCAL_SEARCH && m_ext) {                
                bounded_local_search();
                // exit(0);
            }

            log_stats();
            if (m_config.m_max_conflicts > 0 && m_config.m_burst_search > 0) {               
                m_restart_threshold = m_config.m_burst_search;
                lbool r = bounded_search();
                log_stats();
                if (r != l_undef) 
                    return r;
                
                pop_reinit(scope_lvl());
                m_conflicts_since_restart = 0;
                m_restart_threshold = m_config.m_restart_initial;
            }

            lbool is_sat = search();
            log_stats();
            return is_sat;
        }
        catch (const abort_solver &) {
            IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort giveup\")\n";);
            return l_undef;
        }
    }

    bool solver::should_cancel() {
        if (limit_reached() || memory_exceeded() || m_solver_canceled) {
            return true;
        }
        if (m_config.m_restart_max <= m_restarts) {
            m_reason_unknown = "sat.max.restarts";
            IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort: max-restarts\")\n";);
            return true;
        }
        if (m_config.m_inprocess_max <= m_simplifications) {
            m_reason_unknown = "sat.max.inprocess";
            IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort: max-inprocess\")\n";);
            return true;
        }
        if (reached_max_conflicts()) {
            return true;
        }
        return false;
    }

    enum par_exception_kind {
        DEFAULT_EX,
        ERROR_EX
    };

    struct solver::scoped_ls {
        solver& s;
        scoped_ls(solver& s): s(s) {}
        ~scoped_ls() { 
            dealloc(s.m_local_search); 
            s.m_local_search = nullptr; 
        }
    };

    void solver::bounded_local_search() {
        if (m_ext) {
            IF_VERBOSE(0, verbose_stream() << "WARNING: local search with theories is in testing mode\n");
            do_restart(true);
            lbool r = m_ext->local_search(m_best_phase);
            verbose_stream() << r << "\n";
            if (r == l_true) {
                m_conflicts_since_restart = 0;
                m_conflicts_since_gc = 0;
                m_next_simplify = std::max(m_next_simplify, m_conflicts_since_init + 1);
            }
            return;
        }
        literal_vector _lits;
        scoped_limits scoped_rl(rlimit());
        m_local_search = alloc(ddfw_wrapper);
        scoped_ls _ls(*this);
        SASSERT(m_local_search);
        m_local_search->add(*this);
        m_local_search->updt_params(m_params);
        m_local_search->set_seed(m_rand());
        scoped_rl.push_child(&(m_local_search->rlimit()));

        m_local_search_lim.inc(num_clauses());
        m_local_search->rlimit().push(m_local_search_lim.limit);

        m_local_search->reinit(*this, m_best_phase);
        lbool r = m_local_search->check(_lits.size(), _lits.data(), nullptr);
        auto const& mdl = m_local_search->get_model();
        if (mdl.size() == m_best_phase.size()) {
            for (unsigned i = 0; i < m_best_phase.size(); ++i)
                m_best_phase[i] = l_true == mdl[i];

            if (r == l_true) {
                m_conflicts_since_restart = 0;
                m_conflicts_since_gc = 0;
                m_next_simplify = std::max(m_next_simplify, m_conflicts_since_init + 1);
            }
            do_restart(true);
#if 0
            // move higher priority variables to front
            // eg., move the first 10% variables to front
            svector<std::pair<double, bool_var>> priorities(mdl.size());
            for (unsigned i = 0; i < mdl.size(); ++i) 
                priorities[i] = { m_local_search->get_priority(i), i };
            std::sort(priorities.begin(), priorities.end(), [](auto& x, auto& y) { return x.first > y.first; });
            for (unsigned i = priorities.size() / 10; i-- > 0; )
                move_to_front(priorities[i].second);
#endif


            if (l_true == r) {
                for (clause const* cp : m_clauses) {
                    bool is_true = any_of(*cp, [&](auto lit) { return lit.sign() != m_best_phase[lit.var()]; });
                    if (!is_true) {
                        verbose_stream() << "clause is false " << *cp << "\n";
                    }
                }
            }
        }
    }


    lbool solver::invoke_local_search(unsigned num_lits, literal const* lits) {
        literal_vector _lits(num_lits, lits);
        for (literal lit : m_user_scope_literals) 
            _lits.push_back(~lit);
        scoped_ls _ls(*this);
        if (inconsistent()) 
            return l_false;
        scoped_limits scoped_rl(rlimit());
        SASSERT(m_local_search);
        m_local_search->add(*this);
        m_local_search->updt_params(m_params);
        scoped_rl.push_child(&(m_local_search->rlimit()));
        lbool r = m_local_search->check(_lits.size(), _lits.data(), nullptr);
        if (r == l_true) {
            m_model = m_local_search->get_model();
            m_model_is_current = true;
        }
        return r;
    }

    lbool solver::do_local_search(unsigned num_lits, literal const* lits) {
        SASSERT(!m_local_search);
        m_local_search = alloc(local_search);
        return invoke_local_search(num_lits, lits);
    }

    lbool solver::do_ddfw_search(unsigned num_lits, literal const* lits) {
        if (m_ext) return l_undef;
        SASSERT(!m_local_search);
        m_local_search = alloc(ddfw_wrapper);
        return invoke_local_search(num_lits, lits);
    }

    lbool solver::do_prob_search(unsigned num_lits, literal const* lits) {
        if (m_ext) return l_undef;
        if (num_lits > 0 || !m_user_scope_literals.empty()) return l_undef;
        SASSERT(!m_local_search);
        m_local_search = alloc(prob);
        return invoke_local_search(num_lits, lits);
    }

#ifdef SINGLE_THREAD
    lbool solver::check_par(unsigned num_lits, literal const* lits) {
        return l_undef;
    }
#else
    lbool solver::check_par(unsigned num_lits, literal const* lits) {
        if (!rlimit().inc()) {
            return l_undef;
        }
        if (m_ext && !m_ext->is_pb())
            return l_undef;


        int num_extra_solvers = m_config.m_num_threads - 1;
        int num_local_search  = static_cast<int>(m_config.m_local_search_threads);
        int num_ddfw      = m_ext ? 0 : static_cast<int>(m_config.m_ddfw_threads);
        int num_threads = num_extra_solvers + 1 + num_local_search + num_ddfw;        
        vector<reslimit> lims(num_ddfw);
        scoped_ptr_vector<i_local_search> ls;
        scoped_ptr_vector<solver> uw;
        for (int i = 0; i < num_local_search; ++i) {
            local_search* l = alloc(local_search);
            l->updt_params(m_params);
            l->add(*this);
            l->set_seed(m_config.m_random_seed + i);
            ls.push_back(l);
        }

           
        // set up ddfw search
        for (int i = 0; i < num_ddfw; ++i) {
            ddfw_wrapper* d = alloc(ddfw_wrapper);
            d->updt_params(m_params);
            d->set_seed(m_config.m_random_seed + i);
            d->add(*this);
            ls.push_back(d);
        }
        int local_search_offset = num_extra_solvers;
        int main_solver_offset = num_extra_solvers + num_local_search + num_ddfw;

#define IS_AUX_SOLVER(i)   (0 <= i && i < num_extra_solvers)
#define IS_LOCAL_SEARCH(i) (local_search_offset <= i && i < main_solver_offset)
#define IS_MAIN_SOLVER(i)  (i == main_solver_offset)

        sat::parallel par(*this);
        par.reserve(num_threads, 1 << 12);
        par.init_solvers(*this, num_extra_solvers);
        for (unsigned i = 0; i < ls.size(); ++i) {
            par.push_child(ls[i]->rlimit());
        }
        for (reslimit& rl : lims) {
            par.push_child(rl);
        }
        for (unsigned i = 0; i < uw.size(); ++i) {
            uw[i]->set_par(&par, 0);
        }
        int finished_id = -1;
        std::string        ex_msg;
        par_exception_kind ex_kind = DEFAULT_EX;
        unsigned error_code = 0;
        lbool result = l_undef;
        bool canceled = false;
        std::mutex mux;

        auto worker_thread = [&](int i) {
            try {
                lbool r = l_undef;
                if (IS_AUX_SOLVER(i)) {
                    r = par.get_solver(i).check(num_lits, lits);
                }
                else if (IS_LOCAL_SEARCH(i)) {
                    r = ls[i-local_search_offset]->check(num_lits, lits, &par);
                }
                else {
                    r = check(num_lits, lits);
                }
                bool first = false;
                {
                    std::lock_guard<std::mutex> lock(mux);
                    if (finished_id == -1) {
                        finished_id = i;
                        first = true;
                        result = r;
                    }
                }
                if (first) {
                    for (unsigned j = 0; j < ls.size(); ++j) {
                        ls[j]->rlimit().cancel();
                    }
                    for (auto& rl : lims) {
                        rl.cancel();
                    }
                    for (int j = 0; j < num_extra_solvers; ++j) {
                        if (i != j) {
                            par.cancel_solver(j);
                        }
                    }
                    if (!IS_MAIN_SOLVER(i)) {
                        canceled = !rlimit().inc();
                        if (!canceled) {
                            rlimit().cancel();
                        }
                    }
                }
            }
            catch (z3_error & err) {
                error_code = err.error_code();
                ex_kind = ERROR_EX;                
            }
            catch (z3_exception & ex) {
                ex_msg = ex.what();
                ex_kind = DEFAULT_EX;    
            }
        };

        if (!rlimit().inc()) {
            set_par(nullptr, 0);
            return l_undef;
        }

        vector<std::thread> threads(num_threads);
        for (int i = 0; i < num_threads; ++i) {
            threads[i] = std::thread([&, i]() { worker_thread(i); });
        }
        for (auto & th : threads) {
            th.join();
        }
        
        if (IS_AUX_SOLVER(finished_id)) {
            m_stats = par.get_solver(finished_id).m_stats;
        }
        if (result == l_true && IS_AUX_SOLVER(finished_id)) {
            set_model(par.get_solver(finished_id).get_model(), true);
        }
        else if (result == l_false && IS_AUX_SOLVER(finished_id)) {
            m_core.reset();
            m_core.append(par.get_solver(finished_id).get_core());
        }
        if (result == l_true && IS_LOCAL_SEARCH(finished_id)) {
            set_model(ls[finished_id - local_search_offset]->get_model(), true);
        }
        if (!canceled) {
            rlimit().reset_cancel();
        }
        par.reset();
        set_par(nullptr, 0);
        ls.reset();
        uw.reset();
        if (finished_id == -1) {
            switch (ex_kind) {
            case ERROR_EX: throw z3_error(error_code);
            default: throw default_exception(std::move(ex_msg));
            }
        }
        return result;

    }
#endif

    /*
      \brief import lemmas/units from parallel sat solvers.
     */
    void solver::exchange_par() {
        if (m_par && at_base_lvl() && m_config.m_num_threads > 1) m_par->get_clauses(*this);
        if (m_par && at_base_lvl() && m_config.m_num_threads > 1) {
            // SASSERT(scope_lvl() == search_lvl());
            // TBD: import also dependencies of assumptions.
            unsigned sz = init_trail_size();
            unsigned num_in = 0, num_out = 0;
            literal_vector in, out;
            for (unsigned i = m_par_limit_out; i < sz; ++i) {
                literal lit = m_trail[i];
                if (lit.var() < m_par_num_vars) {
                    ++num_out;
                    out.push_back(lit);
                }
            }
            m_par_limit_out = sz;
            m_par->exchange(*this, out, m_par_limit_in, in);
            for (unsigned i = 0; !inconsistent() && i < in.size(); ++i) {
                literal lit = in[i];
                SASSERT(lit.var() < m_par_num_vars);
                if (lvl(lit.var()) != 0 || value(lit) != l_true) {
                    ++num_in;
                    assign_unit(lit);
                }
            }
            if (num_in > 0 || num_out > 0) {
                IF_VERBOSE(2, verbose_stream() << "(sat-sync out: " << num_out << " in: " << num_in << ")\n";);
            }
        }
    }

    void solver::set_par(parallel* p, unsigned id) {
        m_par = p;
        m_par_num_vars = num_vars();
        m_par_limit_in = 0;
        m_par_limit_out = 0;
        m_par_id = id; 
        m_par_syncing_clauses = false;
    }

    bool_var solver::next_var() {
        bool_var next;

        if (m_rand() < static_cast<int>(m_config.m_random_freq * random_gen::max_value())) {
            if (num_vars() == 0)
                return null_bool_var;
            next = m_rand() % num_vars();
            TRACE("random_split", tout << "next: " << next << " value(next): " << value(next) << "\n";);
            if (value(next) == l_undef && !was_eliminated(next))
                return next;
        }

        while (!m_case_split_queue.empty()) {
            if (m_config.m_anti_exploration) {
                next = m_case_split_queue.min_var();
                auto age = m_stats.m_conflict - m_canceled[next];
                while (age > 0) {
                    set_activity(next, static_cast<unsigned>(m_activity[next] * pow(0.95, static_cast<double>(age))));
                    m_canceled[next] = m_stats.m_conflict;
                    next = m_case_split_queue.min_var();
                    age = m_stats.m_conflict - m_canceled[next];                    
                }
            }
            next = m_case_split_queue.next_var();
            if (value(next) == l_undef && !was_eliminated(next))
                return next;
        }

        return null_bool_var;
    }
    
    bool solver::guess(bool_var next) {
        lbool lphase = m_ext ? m_ext->get_phase(next) : l_undef;
        
        if (lphase != l_undef)
            return lphase == l_true;
        switch (m_config.m_phase) {
        case PS_ALWAYS_TRUE:
            return true;
        case PS_ALWAYS_FALSE:
            return false;
        case PS_BASIC_CACHING:
            return m_phase[next];
        case PS_FROZEN:
            return m_best_phase[next];
        case PS_SAT_CACHING:
        case PS_LOCAL_SEARCH:
            if (m_search_state == s_unsat)
                return m_phase[next];
            return m_best_phase[next];
        case PS_RANDOM:
            return (m_rand() % 2) == 0;
        default:
            UNREACHABLE();
            return false;
        }
    }

    bool solver::decide() {
        bool_var next;
        lbool phase = l_undef;
        bool is_pos;
        bool used_queue = false;
        if (!m_ext || !m_ext->get_case_split(next, phase)) {
            used_queue = true;
            next = next_var();
            if (next == null_bool_var)
                return false;
        }
        else {
            SASSERT(value(next) == l_undef);
        }
        push();
        m_stats.m_decision++;
        
        CTRACE("sat", m_best_phase[next] != guess(next), tout << "phase " << phase << " " << m_best_phase[next] << " " << guess(next) << "\n");

        if (phase == l_undef)
            phase = guess(next) ? l_true: l_false;
        
        literal next_lit(next, false);
        SASSERT(value(next_lit) == l_undef);
        
        if (m_ext && m_ext->decide(next, phase)) {

            if (used_queue)
                m_case_split_queue.unassign_var_eh(next);
            next_lit = literal(next, false);
            SASSERT(value(next_lit) == l_undef);
        }
                
        if (phase == l_undef)
            is_pos = guess(next);
        else
            is_pos = phase == l_true;

        if (!is_pos)
            next_lit.neg();

        TRACE("sat_decide", tout << scope_lvl() << ": next-case-split: " << next_lit << "\n";);
        assign_scoped(next_lit);
        return true;
    }

    lbool solver::bounded_search() {
        flet<bool> _disable_simplify(m_simplify_enabled, false);
        flet<bool> _restart_enabled(m_restart_enabled, false);
        return search();
    }

    lbool solver::basic_search() {
        lbool is_sat = l_undef;
        while (is_sat == l_undef && !should_cancel()) {
            if (inconsistent()) is_sat = resolve_conflict_core();
            else if (should_propagate()) propagate(true);
            else if (do_cleanup(false)) continue;
            else if (should_gc()) do_gc();
            else if (should_rephase()) do_rephase();
            else if (should_restart()) { if (!m_restart_enabled) return l_undef; do_restart(!m_config.m_restart_fast); }
            else if (should_simplify()) do_simplify();
            else if (!decide()) is_sat = final_check();
        }
        return is_sat;
    }

    lbool solver::search() {
        if (!m_ext || !m_ext->tracking_assumptions())
            return basic_search();
        while (true) {
            pop_to_base_level();
            reinit_assumptions();
            lbool r = basic_search();
            if (r != l_false) 
                return r;
            if (!m_ext->should_research(m_core))
                return r;
        }
    }

    bool solver::should_propagate() const {        
        return !inconsistent() && (m_qhead < m_trail.size() || (m_ext && m_ext->can_propagate()));
    }

    lbool solver::final_check() {
        if (m_ext) {
            switch (m_ext->check()) {
            case check_result::CR_DONE:
                mk_model();
                return l_true;
            case check_result::CR_CONTINUE:
                break;
            case check_result::CR_GIVEUP:
                m_reason_unknown = m_ext->reason_unknown();                
                throw abort_solver();
            }
            return l_undef;
        }
        else {
            mk_model();
            return l_true;
        }
    }


    bool solver::check_inconsistent() {
        if (inconsistent()) {
            if (tracking_assumptions() && at_search_lvl())
                resolve_conflict();
            else if (m_config.m_drat && at_base_lvl())
                resolve_conflict();
            return true;
        }
        else {
            return false;
        }
    }    


    void solver::init_assumptions(unsigned num_lits, literal const* lits) {
        if (num_lits == 0 && m_user_scope_literals.empty()) 
            return;        

        SASSERT(at_base_lvl());
        reset_assumptions();
        push();

        propagate(false);
        if (inconsistent()) 
            return;        

        TRACE("sat",
              tout << literal_vector(num_lits, lits) << "\n";
              if (!m_user_scope_literals.empty()) 
                  tout << "user literals: " << m_user_scope_literals << "\n";              
              m_mc.display(tout);
              );

        for (unsigned i = 0; !inconsistent() && i < m_user_scope_literals.size(); ++i) {
            literal nlit = ~m_user_scope_literals[i];
            assign_scoped(nlit);
        }

        for (unsigned i = 0; !inconsistent() && i < num_lits; ++i) {
            literal lit = lits[i];
            set_external(lit.var());
            SASSERT(is_external(lit.var()));
            add_assumption(lit);
            assign_scoped(lit);
        }

        m_search_lvl = scope_lvl(); 
        SASSERT(m_search_lvl == 1);
    }

    void solver::update_min_core() {
        if (!m_min_core_valid || m_core.size() < m_min_core.size()) {
            m_min_core.reset();
            m_min_core.append(m_core);
            m_min_core_valid = true;
        }
    }

    void solver::reset_assumptions() {
        m_assumptions.reset();
        m_assumption_set.reset();
        m_ext_assumption_set.reset();
    }

    void solver::add_assumption(literal lit) {
        m_assumption_set.insert(lit);
        m_assumptions.push_back(lit);
        set_external(lit.var());
    }

    void solver::reassert_min_core() {
        SASSERT(m_min_core_valid);
        pop_to_base_level();
        push();
        reset_assumptions();
        TRACE("sat", tout << "reassert: " << m_min_core << "\n";);
        for (literal lit : m_min_core) {
            SASSERT(is_external(lit.var()));
            add_assumption(lit);
            assign_scoped(lit);
        }
        propagate(false);
        SASSERT(inconsistent());
    }

    void solver::reinit_assumptions() {
        if (tracking_assumptions() && at_base_lvl() && !inconsistent()) {
            TRACE("sat", tout << "assumptions: " << m_assumptions << " user scopes: " << m_user_scope_literals << "\n";);
            if (!propagate(false)) return;
            push();
            for (literal lit : m_user_scope_literals) {
                if (inconsistent()) break;
                assign_scoped(~lit);
            }
            for (literal lit : m_assumptions) {
                if (inconsistent()) break;
                assign_scoped(lit);
            }
            init_ext_assumptions();

            if (!inconsistent()) 
                propagate(false);
            TRACE("sat",
                  tout << "consistent: " << !inconsistent() << "\n";
                  for (literal a : m_assumptions) {
                      index_set s;
                      if (m_antecedents.find(a.var(), s)) 
                          tout << a << ": "; display_index_set(tout, s) << "\n";                      
                  }
                  for (literal lit : m_user_scope_literals) 
                      tout << "user " << lit << "\n";                   
                  );
        }
    }

    void solver::init_ext_assumptions() {
        if (m_ext && m_ext->tracking_assumptions()) {
            m_ext_assumption_set.reset();
            if (!inconsistent())
                m_ext->add_assumptions(m_ext_assumption_set);
        }
    }

    bool solver::tracking_assumptions() const {
        return !m_assumptions.empty() || !m_user_scope_literals.empty() || (m_ext && m_ext->tracking_assumptions());
    }

    bool solver::is_assumption(literal l) const {
        return tracking_assumptions() && (m_assumption_set.contains(l) || m_ext_assumption_set.contains(l));
    }

    void solver::set_activity(bool_var v, unsigned new_act) {
        unsigned old_act = m_activity[v];
        m_activity[v] = new_act; 
        if (!was_eliminated(v) && value(v) == l_undef && new_act != old_act) {
            m_case_split_queue.activity_changed_eh(v, new_act > old_act);
        }
    }

    bool solver::is_assumption(bool_var v) const {
        return is_assumption(literal(v, false)) || is_assumption(literal(v, true));
    }

    void solver::init_search() {
        m_model_is_current        = false;
        m_solver_canceled         = false;
        m_phase_counter           = 0;
        m_search_state            = s_unsat;
        m_search_unsat_conflicts  = m_config.m_search_unsat_conflicts;
        m_search_sat_conflicts    = m_config.m_search_sat_conflicts;
        m_search_next_toggle      = m_search_unsat_conflicts;
        m_best_phase_size         = 0;

        m_reorder.lo              = m_config.m_reorder_base;
        m_rephase.base            = m_config.m_rephase_base;
        m_rephase_lim             = 0;
        m_rephase_inc             = 0;
        m_local_search_lim.base   = 500;        

        m_conflicts_since_restart = 0;
        m_force_conflict_analysis = false;
        m_restart_threshold       = m_config.m_restart_initial;
        m_luby_idx                = 1;
        m_gc_threshold            = m_config.m_gc_initial;
        m_defrag_threshold        = 2;
        m_restarts                = 0;
        m_last_position_log       = 0;
        m_restart_logs            = 0;
        m_simplifications         = 0;
        m_conflicts_since_init    = 0;
        m_next_simplify           = m_config.m_simplify_delay;
        m_min_d_tk                = 1.0;
        m_search_lvl              = 0;

        if (m_learned.size() <= 2*m_clauses.size())
            m_conflicts_since_gc      = 0;
        m_restart_next_out        = 0;
        m_asymm_branch.init_search();
        m_stopwatch.reset();
        m_stopwatch.start();
        m_core.reset();
        m_min_core_valid = false;
        m_min_core.reset();
        m_simplifier.init_search();
        m_mc.init_search(*this);
        if (m_ext)
            m_ext->init_search();
        TRACE("sat", display(tout););
    }

    bool solver::should_simplify() const {
        return m_conflicts_since_init >= m_next_simplify && m_simplify_enabled;
    }
    /**
       \brief Apply all simplifications.
    */
    void solver::do_simplify() {
        if (!should_simplify()) {
            return;
        }
        log_stats();
        m_simplifications++;

        TRACE("sat", tout << "simplify\n";);

        pop(scope_lvl());
        struct report {
            solver&   s;
            stopwatch m_watch;
            report(solver& s):s(s) { 
                m_watch.start(); 
                s.log_stats();
                IF_VERBOSE(2, verbose_stream() << "(sat.simplify :simplifications " << s.m_simplifications << ")\n";);
            }
            ~report() { 
                m_watch.stop(); 
                s.log_stats();
            }
        };
        report _rprt(*this);
        SASSERT(at_base_lvl());

        m_cleaner(m_config.m_force_cleanup);
        CASSERT("sat_simplify_bug", check_invariant());

        m_scc();
        CASSERT("sat_simplify_bug", check_invariant());

        if (m_ext) {
            m_ext->pre_simplify();
        }
      
        m_simplifier(false);

        CASSERT("sat_simplify_bug", check_invariant());
        CASSERT("sat_missed_prop", check_missed_propagation());
        if (!m_learned.empty()) {
            m_simplifier(true);
            CASSERT("sat_missed_prop", check_missed_propagation());
            CASSERT("sat_simplify_bug", check_invariant());
        }
        sort_watch_lits();
        CASSERT("sat_simplify_bug", check_invariant());

        CASSERT("sat_missed_prop", check_missed_propagation());
        CASSERT("sat_simplify_bug", check_invariant());
        if (m_ext) {
            m_ext->clauses_modifed();
            m_ext->simplify();
        }

        m_probing();
        CASSERT("sat_missed_prop", check_missed_propagation());
        CASSERT("sat_simplify_bug", check_invariant());
        m_asymm_branch(false);

        if (m_config.m_lookahead_simplify && !m_ext) {
            lookahead lh(*this);
            lh.simplify(true);
            lh.collect_statistics(m_aux_stats);
        }

        reinit_assumptions();
        if (inconsistent()) return;

        if (m_next_simplify == 0) {
            m_next_simplify = m_config.m_next_simplify1;
        }
        else {
            m_next_simplify = static_cast<unsigned>(m_conflicts_since_init * m_config.m_simplify_mult2);
            if (m_next_simplify > m_conflicts_since_init + m_config.m_simplify_max)
                m_next_simplify = m_conflicts_since_init + m_config.m_simplify_max;
        }

        if (m_par) {
            m_par->from_solver(*this);
            m_par->to_solver(*this);
        }

        if (m_config.m_binspr && !inconsistent()) {
            m_binspr();
        }

        if (m_config.m_anf_simplify && m_simplifications > m_config.m_anf_delay && !inconsistent()) {
            anf_simplifier anf(*this);
            anf_simplifier::config cfg;
            cfg.m_enable_exlin = m_config.m_anf_exlin;
            anf();
            anf.collect_statistics(m_aux_stats);
            // TBD: throttle anf_delay based on yield
        }
        
        if (m_cut_simplifier && m_simplifications > m_config.m_cut_delay && !inconsistent()) {
            (*m_cut_simplifier)();
        }

        if (m_config.m_inprocess_out.is_non_empty_string()) {
            std::ofstream fout(m_config.m_inprocess_out.str());
            if (fout) {
                display_dimacs(fout);
            }
            throw solver_exception("output generated");
        }
    }

    bool solver::set_root(literal l, literal r) {
        return !m_ext || m_ext->set_root(l, r);
    }

    void solver::flush_roots() {
        if (m_ext) m_ext->flush_roots();
    }

    void solver::sort_watch_lits() {
        for (watch_list & wlist : m_watches) {
            std::stable_sort(wlist.begin(), wlist.end(), watched_lt());
        }
    }

    void solver::set_model(model const& mdl, bool is_current) {
        m_model.reset();
        m_model.append(mdl);
        m_model_is_current = is_current;
    }

    void solver::mk_model() {
        m_model.reset();
        m_model_is_current = true;
        unsigned num = num_vars();
        m_model.resize(num, l_undef);
        for (bool_var v = 0; v < num; v++) {
            if (!was_eliminated(v)) {
                m_model[v] = value(v);
                m_phase[v] = value(v) == l_true;
                m_best_phase[v] = value(v) == l_true;
            }
        }
        TRACE("sat_mc_bug", m_mc.display(tout););

#if 0
        IF_VERBOSE(2, for (bool_var v = 0; v < num; v++) verbose_stream() << v << ": " << m_model[v] << "\n";);
        for (auto p : big::s_del_bin) {
            if (value(p.first) != l_true && value(p.second) != l_true) {
                IF_VERBOSE(2, verbose_stream() << "binary violation: " << p.first << " " << p.second << "\n");
            }
        }
#endif

        if (m_clone) {
            IF_VERBOSE(10, verbose_stream() << "\"checking model\"\n";);
            if (!check_clauses(m_model)) {
                throw solver_exception("check model failed");
            }
        }
        
        if (m_config.m_drat) {
            m_drat.check_model(m_model);
        }

        m_mc(m_model);

        if (m_clone && !check_clauses(m_model)) {
            IF_VERBOSE(1, verbose_stream() << "failure checking clauses on transformed model\n";);
            IF_VERBOSE(10, m_mc.display(verbose_stream()));
            IF_VERBOSE(1, for (bool_var v = 0; v < num; v++) verbose_stream() << v << ": " << m_model[v] << "\n";);

            throw solver_exception("check model failed");
        }

        TRACE("sat", for (bool_var v = 0; v < num; v++) tout << v << ": " << m_model[v] << "\n";);

        if (m_clone) {
            IF_VERBOSE(1, verbose_stream() << "\"checking model (on original set of clauses)\"\n";);
            if (!m_clone->check_model(m_model)) {
                IF_VERBOSE(1, m_mc.display(verbose_stream()));
                IF_VERBOSE(1, display_units(verbose_stream()));
                throw solver_exception("check model failed (for cloned solver)");
            }
        }
    }

    bool solver::check_clauses(model const& m) const {
        bool ok = true;
        for (clause const* cp : m_clauses) {
            clause const & c = *cp;
            if (!c.satisfied_by(m)) {
                IF_VERBOSE(1, verbose_stream() << "failed clause " << c.id() << ": " << c << "\n";);
                TRACE("sat", tout << "failed: " << c << "\n";
                      tout << "assumptions: " << m_assumptions << "\n";
                      tout << "trail: " << m_trail << "\n";
                      tout << "model: " << m << "\n";
                      m_mc.display(tout);
                      );
                for (literal l : c) {
                    if (was_eliminated(l.var())) IF_VERBOSE(1, verbose_stream() << "eliminated: " << l << "\n";);
                }
                ok = false;
            }
        }
        unsigned l_idx = 0;
        for (watch_list const& wlist : m_watches) {
            literal l = ~to_literal(l_idx);
            if (value_at(l, m) != l_true) {
                for (watched const& w : wlist) {
                    if (!w.is_binary_non_learned_clause())
                        continue;
                    literal l2 = w.get_literal();
                    if (l.index() > l2.index()) 
                        continue;
                    if (value_at(l2, m) != l_true) {
                        IF_VERBOSE(1, verbose_stream() << "failed binary: " << l << " := " << value_at(l, m) << " " << l2 <<  " := " << value_at(l2, m) << "\n");
                        IF_VERBOSE(1, verbose_stream() << "elim l1: " << was_eliminated(l.var()) << " elim l2: " << was_eliminated(l2) << "\n");
                        TRACE("sat", m_mc.display(tout << "failed binary: " << l << " " << l2 << "\n"););
                        ok = false;
                    }
                }
            }
            ++l_idx;
        }
        for (literal l : m_assumptions) {
            if (value_at(l, m) != l_true) {
                VERIFY(is_external(l.var()));
                IF_VERBOSE(1, verbose_stream() << "assumption: " << l << " does not model check " << value_at(l, m) << "\n";);
                TRACE("sat",
                      tout << l << " does not model check\n";
                      tout << "trail: " << m_trail << "\n";
                      tout << "model: " << m << "\n";
                      m_mc.display(tout);
                      );
                ok = false;
            }
        }
        if (m_ext && !m_ext->check_model(m)) {
            ok = false;
        }
        return ok;
    }

    bool solver::check_model(model const & m) const {
        bool ok = check_clauses(m);
        if (ok && !m_mc.check_model(m)) {
            ok = false;
            TRACE("sat", tout << "model: " << m << "\n"; m_mc.display(tout););
            IF_VERBOSE(0, verbose_stream() << "model check failed\n");
        }
        return ok;
    }

    bool solver::should_restart() const {
        if (m_conflicts_since_restart <= m_restart_threshold) return false;
        if (scope_lvl() < 2 + search_lvl()) return false;
        if (m_case_split_queue.empty()) return false;
        if (m_config.m_restart != RS_EMA) return true;
        return 
            m_fast_glue_avg + search_lvl() <= scope_lvl() && 
            m_config.m_restart_margin * m_slow_glue_avg <= m_fast_glue_avg;
    }

    void solver::log_stats() {
        m_restart_logs++;
        
        std::stringstream strm;
        strm << "(sat.stats " << std::setw(6) << m_stats.m_conflict << " " 
             << std::setw(6) << m_stats.m_decision << " "
             << std::setw(4) << m_stats.m_restart 
             << mk_stat(*this)
             << " " << std::setw(6) << std::setprecision(2) << m_stopwatch.get_current_seconds() << ")\n";
        std::string str = std::move(strm).str();
        svector<size_t> nums;
        for (size_t i = 0; i < str.size(); ++i) {
            while (i < str.size() && str[i] != ' ') ++i;
            while (i < str.size() && str[i] == ' ') ++i;
            // position of first character after space
            if (i < str.size()) {
                nums.push_back(i);
            }
        }   
        bool same = m_last_positions.size() == nums.size();
        size_t diff = 0;
        for (unsigned i = 0; i < nums.size() && same; ++i) {
            if (m_last_positions[i] > nums[i]) diff += m_last_positions[i] - nums[i];
            if (m_last_positions[i] < nums[i]) diff += nums[i] - m_last_positions[i];
        }
        if (m_last_positions.empty() || 
            m_restart_logs >= 20 + m_last_position_log || 
            (m_restart_logs >= 6 + m_last_position_log && (!same || diff > 3))) {
            m_last_position_log = m_restart_logs;
            //           conflicts          restarts          learned            gc               time
            //                     decisions         clauses            units          memory
            int adjust[9] = { -3,      -3,      -3,      -1,      -3,      -2,   -1,     -2,       -1 };
            char const* tag[9]  = { ":conflicts ", ":decisions ", ":restarts ", ":clauses/bin ", ":learned/bin ", ":units ", ":gc ", ":memory ", ":time" };
            std::stringstream l1, l2;
            l1 << "(sat.stats ";
            l2 << "(sat.stats ";
            size_t p1 = 11, p2 = 11;
            SASSERT(nums.size() == 9);
            for (unsigned i = 0; i < 9 && i < nums.size(); ++i) {
                size_t p = nums[i];
                if (i & 0x1) {
                    // odd positions
                    for (; p2 < p + adjust[i]; ++p2) l2 << " ";
                    p2 += strlen(tag[i]);
                    l2 << tag[i];
                }
                else {
                    // even positions
                    for (; p1 < p + adjust[i]; ++p1) l1 << " ";
                    p1 += strlen(tag[i]);
                    l1 << tag[i];
                }                               
            }
            for (; p1 + 2 < str.size(); ++p1) l1 << " ";            
            for (; p2 + 2 < str.size(); ++p2) l2 << " ";            
            l1 << ")\n";
            l2 << ")\n";
            IF_VERBOSE(1, verbose_stream() << l1.str() << l2.str());
            m_last_positions.reset();
            m_last_positions.append(nums);
        }
        IF_VERBOSE(1, verbose_stream() << str);            
    }

    void solver::do_restart(bool to_base) {        
        m_stats.m_restart++;
        m_restarts++;
        if (m_conflicts_since_init >= m_restart_next_out && get_verbosity_level() >= 1) {
            if (0 == m_restart_next_out) {
                m_restart_next_out = 1;
            }
            else {
                m_restart_next_out = std::min(m_conflicts_since_init + 50000, (3*m_restart_next_out)/2 + 1); 
            }
            log_stats();
        }
        TRACE("sat", tout << "restart " << restart_level(to_base) << "\n";);
        IF_VERBOSE(30, display_status(verbose_stream()););
        TRACE("sat", tout << "restart " << restart_level(to_base) << "\n";);
        pop_reinit(restart_level(to_base));
        set_next_restart();        
    }

    unsigned solver::restart_level(bool to_base) {
        SASSERT(!m_case_split_queue.empty());
        if (to_base || scope_lvl() == search_lvl()) 
            return scope_lvl() - search_lvl();        
        else {
            bool_var next = m_case_split_queue.min_var();

            // Implementations of Marijn's idea of reusing the 
            // trail when the next decision literal has lower precedence.
            // pop trail from top
#if 0
            unsigned n = 0;
            do {
                bool_var prev = scope_literal(scope_lvl() - n - 1).var();
                if (m_case_split_queue.more_active(prev, next)) break;                
                ++n;
            }
            while (n < scope_lvl() - search_lvl());
            return n;
#endif
            // pop trail from bottom
            unsigned n = search_lvl();
            for (; n < scope_lvl() && m_case_split_queue.more_active(scope_literal(n).var(), next); ++n) {
            }
            return n - search_lvl();
        }
    }

    void solver::update_activity(bool_var v, double p) {
        unsigned new_act = (unsigned) (num_vars() * m_config.m_activity_scale *  p);
        set_activity(v, new_act);
    }

    void solver::set_next_restart() {
        m_conflicts_since_restart = 0;
        switch (m_config.m_restart) {
        case RS_GEOMETRIC:
            m_restart_threshold = static_cast<unsigned>(m_restart_threshold * m_config.m_restart_factor);
            break;
        case RS_LUBY:
            m_luby_idx++;
            m_restart_threshold = m_config.m_restart_initial * get_luby(m_luby_idx);
            break;
        case RS_EMA:
            m_restart_threshold = m_config.m_restart_initial;
            break;
        case RS_STATIC:
            break;
        default:
            UNREACHABLE();
            break;
        }
        CASSERT("sat_restart", check_invariant());
    }


    // -----------------------
    //
    // Conflict resolution
    //
    // -----------------------

    bool solver::resolve_conflict() {
        while (true) {
            lbool r = resolve_conflict_core();
            CASSERT("sat_check_marks", check_marks());
            // after pop, clauses are reinitialized, this may trigger another conflict.
            if (r == l_false)
                return false;
            if (!inconsistent())
                return true;
        }
    }


    lbool solver::resolve_conflict_core() {
        m_conflicts_since_init++;
        m_conflicts_since_restart++;
        m_conflicts_since_gc++;
        m_stats.m_conflict++;
        if (m_step_size > m_config.m_step_size_min)
            m_step_size -= m_config.m_step_size_dec;        

        bool unique_max;
        m_conflict_lvl = get_max_lvl(m_not_l, m_conflict, unique_max);        
        justification js = m_conflict;

        if (m_conflict_lvl <= 1 && (!m_assumptions.empty() || 
                                    !m_ext_assumption_set.empty() || 
                                    !m_user_scope_literals.empty())) {
            TRACE("sat", tout << "unsat core\n";);
            resolve_conflict_for_unsat_core();
            return l_false;
        }

        if (m_conflict_lvl == 0) {
            drat_explain_conflict();
            if (m_config.m_drat)
                drat_log_clause(0, nullptr, sat::status::redundant());
            TRACE("sat", tout << "conflict level is 0\n";);
            return l_false;
        }

        // force_conflict_analysis is used instead of relying on normal propagation to assign m_not_l 
        // at the backtracking level. This is the case where the external theories miss propagations
        // that only get triggered after decisions.
        
        if (allow_backtracking() && unique_max && !m_force_conflict_analysis) {
            TRACE("sat", tout << "unique max " << js << " " << m_not_l << "\n";);
            pop_reinit(m_scope_lvl - m_conflict_lvl + 1);
            m_force_conflict_analysis = true;
            ++m_stats.m_backtracks;
            return l_undef;
        }
        m_force_conflict_analysis = false;

        updt_phase_of_vars();

        if (m_ext) {
            switch (m_ext->resolve_conflict()) {
            case l_true:                
                learn_lemma_and_backjump();
                return l_undef;
            case l_undef:
                break;
            case l_false:
                // backjumping was taken care of internally.
                return l_undef;
            }
        }

        m_lemma.reset();

        unsigned idx = skip_literals_above_conflict_level();

        // save space for first uip
        m_lemma.push_back(null_literal);

        TRACE("sat_conflict_detail", 
              tout << "resolve: " << m_not_l << " " 
              << " js: " << js 
              << " idx: " << idx 
              << " trail: " << m_trail.size() 
              << " @" << m_conflict_lvl << "\n";);

        unsigned num_marks = 0;
        literal consequent = null_literal;
        if (m_not_l != null_literal) {
            TRACE("sat_conflict_detail", tout << "not_l: " << m_not_l << "\n";);
            process_antecedent(m_not_l, num_marks);
            consequent = ~m_not_l;
        }

        do {
            TRACE("sat_conflict_detail", tout << "processing consequent: " << consequent << " @" << (consequent==null_literal?m_conflict_lvl:lvl(consequent)) << "\n";
                  tout << "num_marks: " << num_marks << "\n";
                  display_justification(tout, js) << "\n";);

            switch (js.get_kind()) {
            case justification::NONE:
                break;
            case justification::BINARY:
                process_antecedent(~(js.get_literal()), num_marks);
                break;
            case justification::CLAUSE: {
                clause & c = get_clause(js);
                unsigned i = 0;
                if (consequent != null_literal) {
                    SASSERT(c[0] == consequent || c[1] == consequent);
                    if (c[0] == consequent) {
                        i = 1;
                    }
                    else {
                        process_antecedent(~c[0], num_marks);
                        i = 2;
                    }
                }
                unsigned sz = c.size();
                for (; i < sz; i++)
                    process_antecedent(~c[i], num_marks);
                break;
            }
            case justification::EXT_JUSTIFICATION: {
                fill_ext_antecedents(consequent, js, false);
                TRACE("sat", tout << "ext antecedents: " << m_ext_antecedents << "\n";);
                for (literal l : m_ext_antecedents)                     
                    process_antecedent(l, num_marks);                
                break;
            }
            default:
                UNREACHABLE();
                break;
            }
            
            bool_var c_var;
            while (true) {
                consequent = m_trail[idx];
                c_var = consequent.var();
                if (is_marked(c_var)) {
                    if (lvl(c_var) == m_conflict_lvl) {
                        break;
                    }
                    SASSERT(lvl(c_var) < m_conflict_lvl);
                }
                CTRACE("sat", idx == 0,
                       tout << "conflict level " << m_conflict_lvl << "\n";
                       for (literal lit : m_trail)
                           if (is_marked(lit.var()))
                               tout << "missed " << lit << "@" << lvl(lit) << "\n";);
                CTRACE("sat", idx == 0, display(tout););
                if (idx == 0)
                    IF_VERBOSE(0, verbose_stream() << "num-conflicts: " << m_stats.m_conflict << "\n");
                VERIFY(idx > 0);
                idx--;
            }
            SASSERT(lvl(consequent) == m_conflict_lvl);
            js             = m_justification[c_var];
            idx--;
            num_marks--;
            reset_mark(c_var);

            TRACE("sat", display_justification(tout << consequent << " ", js) << "\n";);            
        }
        while (num_marks > 0);

        m_lemma[0] = ~consequent;
        learn_lemma_and_backjump();

        return l_undef;
    }

    void solver::learn_lemma_and_backjump() {
        TRACE("sat_lemma", tout << "new lemma size: " << m_lemma.size() << "\n" << m_lemma << "\n";);
        
        if (m_lemma.empty()) {
            pop_reinit(m_scope_lvl);
            mk_clause_core(0, nullptr, sat::status::redundant());
            return;
        }
        
        if (m_config.m_minimize_lemmas) {
            minimize_lemma();
            reset_lemma_var_marks();
            if (m_config.m_dyn_sub_res)
                dyn_sub_res();
            TRACE("sat_lemma", tout << "new lemma (after minimization) size: " << m_lemma.size() << "\n" << m_lemma << "\n";);
        }
        else {
            reset_lemma_var_marks();
        }
        
        unsigned backtrack_lvl = lvl(m_lemma[0]);
        unsigned backjump_lvl  = 0;
        for (unsigned i = m_lemma.size(); i-- > 1;) {
            unsigned level = lvl(m_lemma[i]);
            backjump_lvl = std::max(level, backjump_lvl);
        }    
        // with scope tracking and chronological backtracking, 
        // consequent may not be at highest decision level.
        if (backtrack_lvl < backjump_lvl) {
            backtrack_lvl = backjump_lvl;
            for (unsigned i = m_lemma.size(); i-- > 1;) {
                if (lvl(m_lemma[i]) == backjump_lvl) {
                    TRACE("sat", tout << "swap " << m_lemma[0] << "@" << lvl(m_lemma[0]) << m_lemma[1] << "@" << backjump_lvl << "\n";);
                    std::swap(m_lemma[i], m_lemma[0]);
                    break;
                }
            }
        }
        
        unsigned glue = num_diff_levels(m_lemma.size(), m_lemma.data());        
        m_fast_glue_avg.update(glue);
        m_slow_glue_avg.update(glue);
    
        // compute whether to use backtracking or backjumping
        unsigned num_scopes = m_scope_lvl - backjump_lvl;
        
        if (use_backjumping(num_scopes)) {
            ++m_stats.m_backjumps;
            pop_reinit(num_scopes);
        }
        else {
            TRACE("sat", tout << "backtrack " << (m_scope_lvl - backtrack_lvl + 1) << " scopes\n";);
            ++m_stats.m_backtracks;
            pop_reinit(m_scope_lvl - backtrack_lvl + 1);
        }
        clause * lemma = mk_clause_core(m_lemma.size(), m_lemma.data(), sat::status::redundant());
        if (lemma) {
            lemma->set_glue(glue);
        }
        if (m_par && lemma) {
            m_par->share_clause(*this, *lemma);
        }
        m_lemma.reset();
        TRACE("sat_conflict_detail", tout << "consistent " << (!m_inconsistent) << " scopes: " << scope_lvl() << " backtrack: " << backtrack_lvl << " backjump: " << backjump_lvl << "\n";);
        decay_activity();
        updt_phase_counters();
    }

    bool solver::use_backjumping(unsigned num_scopes) const {
        return 
            num_scopes > 0 && 
            (num_scopes <= m_config.m_backtrack_scopes || !allow_backtracking());
    }

    bool solver::allow_backtracking() const {
        return m_conflicts_since_init > m_config.m_backtrack_init_conflicts;
    }

    void solver::process_antecedent_for_unsat_core(literal antecedent) {
        bool_var var     = antecedent.var();
        SASSERT(var < num_vars());
        TRACE("sat", tout << antecedent << " " << (is_marked(var)?"+":"-") << "\n";);
        if (!is_marked(var)) {
            mark(var);
            m_unmark.push_back(var);
            if (is_assumption(antecedent)) {
                m_core.push_back(antecedent);
            }
        }
    }

    void solver::process_consequent_for_unsat_core(literal consequent, justification const& js) {
        TRACE("sat", tout << "processing consequent: ";
              if (consequent == null_literal) tout << "null\n";
              else tout << consequent << "\n";
              display_justification(tout << "js kind: ", js) << "\n";);
        switch (js.get_kind()) {
        case justification::NONE:
            break;
        case justification::BINARY:
            SASSERT(consequent != null_literal);
            process_antecedent_for_unsat_core(~(js.get_literal()));
            break;
        case justification::CLAUSE: {
            clause & c = get_clause(js);
            unsigned i = 0;
            if (consequent != null_literal) {
                SASSERT(c[0] == consequent || c[1] == consequent);
                if (c[0] == consequent) {
                    i = 1;
                }
                else {
                    process_antecedent_for_unsat_core(~c[0]);
                    i = 2;
                }
            }
            unsigned sz = c.size();
            for (; i < sz; i++)
                process_antecedent_for_unsat_core(~c[i]);
            break;
        }
        case justification::EXT_JUSTIFICATION: {
            fill_ext_antecedents(consequent, js, false);
            for (literal l : m_ext_antecedents) {
                process_antecedent_for_unsat_core(l);
            }
            break;
        }
        default:
            UNREACHABLE();
            break;
        }
    }

    void solver::resolve_conflict_for_unsat_core() {
        TRACE("sat_verbose", display(tout);
              unsigned level = 0;
              for (literal l : m_trail) {
                  if (level != lvl(l)) {
                      level = lvl(l);
                      tout << level << ": ";
                  }
                  tout << l;
                  if (m_mark[l.var()]) {
                      tout << "*";
                  }
                  tout << " ";
              }
              tout << "\n";
              tout << "conflict level: " << m_conflict_lvl << "\n";
              );

        m_core.reset();
        if (!m_config.m_drat && m_conflict_lvl == 0) {
            return;
        }
        SASSERT(m_unmark.empty());
        DEBUG_CODE({
                for (literal lit : m_trail) {
                    SASSERT(!is_marked(lit.var()));
                }});

        unsigned old_size = m_unmark.size();
        int idx = skip_literals_above_conflict_level();

        literal consequent = m_not_l;
        if (m_not_l != null_literal) {
            justification js = m_justification[m_not_l.var()];
            TRACE("sat", tout << "not_l: " << m_not_l << "\n";
                  display_justification(tout, js) << "\n";);

            process_antecedent_for_unsat_core(m_not_l);
            if (is_assumption(~m_not_l)) {
                m_core.push_back(~m_not_l);
            }
            else {
                process_consequent_for_unsat_core(m_not_l, js);
            }
            consequent = ~m_not_l;
        }

        justification js = m_conflict;

        int init_sz = init_trail_size();
        while (true) {
            process_consequent_for_unsat_core(consequent, js);
            while (idx >= init_sz) {
                consequent = m_trail[idx];
                if (is_marked(consequent.var()) && lvl(consequent) == m_conflict_lvl)
                    break;
                idx--;
            }
            if (idx < init_sz) {
                break;
            }
            SASSERT(lvl(consequent) == m_conflict_lvl);
            js = m_justification[consequent.var()];
            idx--;
        }
        reset_unmark(old_size);
        if (m_core.size() > 1) {
            unsigned j = 0;
            for (unsigned i = 0; i < m_core.size(); ++i) {
                if (lvl(m_core[i]) > 0) m_core[j++] = m_core[i];            
            }
            m_core.shrink(j);
        }

        if (m_config.m_core_minimize) {
            if (m_min_core_valid && m_min_core.size() < m_core.size()) {
                IF_VERBOSE(2, verbose_stream() << "(sat.updating core " << m_min_core.size() << " " << m_core.size() << ")\n";);
                m_core.reset();
                m_core.append(m_min_core);
            }
            // TBD:
            // apply optional clause minimization by detecting subsumed literals.
            // initial experiment suggests it has no effect.
            m_mus(); // ignore return value on cancelation.
            set_model(m_mus.get_model(), !m_mus.get_model().empty());
            IF_VERBOSE(2, verbose_stream() << "(sat.core: " << m_core << ")\n";);
        }
    }


    unsigned solver::get_max_lvl(literal not_l, justification js, bool& unique_max) {
        unique_max = true;
        unsigned level = 0;

        if (not_l != null_literal) {
            level = lvl(not_l);
        }      
        TRACE("sat", tout << "level " << not_l << " is " << level << " " << js << "\n");

        switch (js.get_kind()) {
        case justification::NONE:
            level = std::max(level, js.level());
            break;
        case justification::BINARY:
            level = update_max_level(js.get_literal(), level, unique_max);
            break;
        case justification::CLAUSE: 
            for (literal l : get_clause(js)) 
                level = update_max_level(l, level, unique_max);
            break;
        case justification::EXT_JUSTIFICATION:
            if (not_l != null_literal) 
                not_l.neg();
            fill_ext_antecedents(not_l, js, true);
            for (literal l : m_ext_antecedents) 
                level = update_max_level(l, level, unique_max);
            break;
        default:
            UNREACHABLE();
            break;
        }
        TRACE("sat", tout << "max-level " << level << " " << unique_max << "\n");
        return level;
    }

    /**
       \brief Skip literals from levels above m_conflict_lvl.
       It returns an index idx such that lvl(m_trail[idx]) <= m_conflict_lvl, and
       for all idx' > idx, lvl(m_trail[idx']) > m_conflict_lvl
    */
    unsigned solver::skip_literals_above_conflict_level() {
        unsigned idx = m_trail.size();
        if (idx == 0) {
            return idx;
        }
        idx--;
        // skip literals from levels above the conflict level
        while (lvl(m_trail[idx]) > m_conflict_lvl) {
            SASSERT(idx > 0);
            idx--;
        }
        return idx;
    }

    void solver::process_antecedent(literal antecedent, unsigned & num_marks) {
        bool_var var     = antecedent.var();
        unsigned var_lvl = lvl(var);
        SASSERT(var < num_vars());
        TRACE("sat_verbose", tout << "process " << var << "@" << var_lvl << " marked " << is_marked(var) << " conflict " << m_conflict_lvl << "\n";);
        if (!is_marked(var) && var_lvl > 0) {
            mark(var);
            switch (m_config.m_branching_heuristic) {
            case BH_VSIDS:
                inc_activity(var);
                break;
            case BH_CHB:
                m_last_conflict[var] = m_stats.m_conflict;
                break;
            default:
                break;
            }
            if (var_lvl == m_conflict_lvl)
                num_marks++;
            else
                m_lemma.push_back(~antecedent);
        }
    }


    /**
       \brief js is an external justification. Collect its antecedents and store at m_ext_antecedents.
    */
    void solver::fill_ext_antecedents(literal consequent, justification js, bool probing) {
        SASSERT(js.is_ext_justification());
        SASSERT(m_ext);
        auto idx = js.get_ext_justification_idx();
        m_ext_antecedents.reset();
        m_ext->get_antecedents(consequent, idx, m_ext_antecedents, probing);
    }

    bool solver::is_two_phase() const {
        return m_config.m_phase == PS_SAT_CACHING || m_config.m_phase == PS_LOCAL_SEARCH;
    }

    bool solver::is_sat_phase() const {
        return is_two_phase() && m_search_state == s_sat;
    }

    void solver::updt_phase_of_vars() {
        if (m_config.m_phase == PS_FROZEN)
            return;
        unsigned from_lvl = m_conflict_lvl;
        unsigned head = from_lvl == 0 ? 0 : m_scopes[from_lvl - 1].m_trail_lim;
        unsigned sz   = m_trail.size();
        for (unsigned i = head; i < sz; i++) {
            bool_var v = m_trail[i].var();
            TRACE("forget_phase", tout << "forgetting phase of v" << v << "\n";);
            m_phase[v] = m_rand() % 2 == 0;
        }
        if (is_sat_phase() && head >= m_best_phase_size) {
            m_best_phase_size = head;
            IF_VERBOSE(12, verbose_stream() << "sticky trail: " << head << "\n");
            for (unsigned i = 0; i < head; ++i) {
                bool_var v = m_trail[i].var();
                m_best_phase[v] = m_phase[v];
            }
            set_has_new_best_phase(true);
        }
    }

    bool solver::should_toggle_search_state() {
        if (m_search_state == s_unsat) {
            m_trail_avg.update(m_trail.size());
        }
        return 
            (m_phase_counter >= m_search_next_toggle) &&
            (m_search_state == s_sat || m_trail.size() > 0.50*m_trail_avg);  
    }

    void solver::do_toggle_search_state() {

        if (is_two_phase()) {
            m_best_phase_size = 0;
            std::swap(m_fast_glue_backup, m_fast_glue_avg);
            std::swap(m_slow_glue_backup, m_slow_glue_avg);
            if (m_search_state == s_sat) {
                m_search_unsat_conflicts += m_config.m_search_unsat_conflicts;   
            }
            else {
                m_search_sat_conflicts += m_config.m_search_sat_conflicts;
            }
        }

        if (m_search_state == s_unsat) {
            m_search_state = s_sat;
            m_search_next_toggle = m_search_sat_conflicts;
        }
        else {
            m_search_state = s_unsat;
            m_search_next_toggle = m_search_unsat_conflicts;
        }

        m_phase_counter = 0;
    }

    bool solver::should_rephase() {
        return m_conflicts_since_init > m_rephase_lim;
//        return m_rephase.should_apply(m_conflicts_since_init);
    }

    void solver::do_rephase() {
        switch (m_config.m_phase) {
        case PS_ALWAYS_TRUE:
            for (auto& p : m_phase) p = true;
            break;
        case PS_ALWAYS_FALSE:
            for (auto& p : m_phase) p = false;
            break;
        case PS_FROZEN:
            break;
        case PS_BASIC_CACHING:
            switch (m_rephase.count % 4) {
            case 0:
                for (auto& p : m_phase) p = (m_rand() % 2) == 0;
                break;
            case 1:
                for (auto& p : m_phase) p = false;
                break;
            case 2:
                for (auto& p : m_phase) p = !p;
                break;
            default:
                break;
            }
            break;
        case PS_SAT_CACHING:
            if (m_search_state == s_sat) 
                for (unsigned i = 0; i < m_phase.size(); ++i) 
                    m_phase[i] = m_best_phase[i];  
            break;
        case PS_RANDOM:
            for (auto& p : m_phase) p = (m_rand() % 2) == 0;
            break;
        case PS_LOCAL_SEARCH:
            if (m_search_state == s_sat) {
                if (m_rand() % 2 == 0)
                    bounded_local_search();
                for (unsigned i = 0; i < m_phase.size(); ++i) 
                    m_phase[i] = m_best_phase[i];              
            }

            break;
        default:
            UNREACHABLE();
            break;
        }
        m_rephase_inc += m_config.m_rephase_base;
        m_rephase_lim += m_rephase_inc;
        m_rephase.inc(m_conflicts_since_init, num_clauses());
    }

    bool solver::should_reorder() {
        return m_reorder.should_apply(m_conflicts_since_init);
    }

    void solver::do_reorder() {
        IF_VERBOSE(1, verbose_stream() << "(reorder)\n");
        m_activity_inc = 128;
        svector<bool_var> vars;
        for (bool_var v = num_vars(); v-- > 0; ) {
            if (!was_eliminated(v) && value(v) == l_undef) {            
                vars.push_back(v);
            }
        }
#if 1
        // 
        //   exp(logits[i]) / sum(exp(logits)) 
        // = 
        //   exp(log(exp(logits[i]) / sum(exp(logits)))) 
        // = 
        //   exp(log(exp(logits[i])) - log(sum(exp(logits)))) 
        // =
        //   exp(logits[i] - lse)
        svector<double> logits(vars.size(), 0.0);
        double itau = m_config.m_reorder_itau;
        double lse = 0;
        double mid = (double)(m_rand.max_value()/2);
        double max = 0;
        for (double& f : logits) {
            f = itau * (m_rand() - mid)/mid;
            if (f > max) max = f;
        }
        for (double f : logits) {
            lse += log(f - max);
        }
        lse = max + exp(lse);

        for (unsigned i = 0; i < vars.size(); ++i) {
            update_activity(vars[i], exp(logits[i] - lse));
        }
#else
        shuffle(vars.size(), vars.c_ptr(), m_rand);
        for (bool_var v : vars) {
            update_activity(v, m_rand(10)/10.0);
        }
#endif
        m_reorder.inc(m_conflicts_since_init, num_clauses());
    }

    void solver::updt_phase_counters() {
        m_phase_counter++;
        if (should_toggle_search_state()) {
            do_toggle_search_state();
        }
    }

    /**
       \brief Return the number of different levels in lits.
       All literals in lits must be assigned.
    */
    unsigned solver::num_diff_levels(unsigned num, literal const * lits) {
        m_diff_levels.reserve(scope_lvl() + 1, false);
        unsigned r = 0;
        for (unsigned i = 0; i < num; i++) {
            SASSERT(value(lits[i]) != l_undef);
            unsigned lit_lvl = lvl(lits[i]);
            if (!m_diff_levels[lit_lvl]) {
                m_diff_levels[lit_lvl] = true;
                r++;
            }
        }
        // reset m_diff_levels.
        for (unsigned i = 0; i < num; i++)
            m_diff_levels[lvl(lits[i])] = false;
        return r;
    }

    bool solver::num_diff_levels_below(unsigned num, literal const* lits, unsigned max_glue, unsigned& glue) {
        m_diff_levels.reserve(scope_lvl() + 1, false);
        glue = 0;
        unsigned i = 0;
        for (; i < num && glue < max_glue; i++) {
            SASSERT(value(lits[i]) != l_undef);
            unsigned lit_lvl = lvl(lits[i]);
            if (!m_diff_levels[lit_lvl]) {
                m_diff_levels[lit_lvl] = true;
                glue++;
            }
        }       
        // reset m_diff_levels.
        for (; i-- > 0; )
            m_diff_levels[lvl(lits[i])] = false;
        return glue < max_glue;        
    }

    bool solver::num_diff_false_levels_below(unsigned num, literal const* lits, unsigned max_glue, unsigned& glue) {
        m_diff_levels.reserve(scope_lvl() + 1, false);
        glue = 0;
        unsigned i = 0;
        for (; i < num && glue < max_glue; i++) {
            if (value(lits[i]) == l_false) {
                unsigned lit_lvl = lvl(lits[i]);
                if (!m_diff_levels[lit_lvl]) {
                    m_diff_levels[lit_lvl] = true;
                    glue++;
                }
            }
        }
        // reset m_diff_levels.
        for (; i-- > 0;) {
            literal lit = lits[i];
            if (value(lit) == l_false) {
                VERIFY(lvl(lit) < m_diff_levels.size());
                m_diff_levels[lvl(lit)] = false;
            }
        }
        return glue < max_glue;        
    }


    /**
       \brief Process an antecedent for lemma minimization.
    */
    bool solver::process_antecedent_for_minimization(literal antecedent) {
        bool_var var = antecedent.var();
        unsigned var_lvl = lvl(var);
        if (!is_marked(var) && var_lvl > 0) {
            if (m_lvl_set.may_contain(var_lvl)) {
                mark(var);
                m_unmark.push_back(var);
                m_lemma_min_stack.push_back(antecedent);
            }
            else {
                return false;
            }
        }
        return true;
    }

    /**
       \brief Return true if lit is implied by other marked literals
       and/or literals assigned at the base level.
       The set lvl_set is used as an optimization.
       The idea is to stop the recursive search with a failure
       as soon as we find a literal assigned in a level that is not in lvl_set.
    */
    bool solver::implied_by_marked(literal lit) {
        m_lemma_min_stack.reset();  // avoid recursive function
        m_lemma_min_stack.push_back(lit);
        unsigned old_size = m_unmark.size();

        while (!m_lemma_min_stack.empty()) {
            lit = m_lemma_min_stack.back();
            bool_var var = lit.var();
            m_lemma_min_stack.pop_back();
            justification const& js   = m_justification[var];
            switch(js.get_kind()) {
            case justification::NONE:
                // it is a decision variable from a previous scope level
                if (lvl(var) > 0) {
                    reset_unmark(old_size);
                    return false;
                }
                break;
            case justification::BINARY:
                if (!process_antecedent_for_minimization(~(js.get_literal()))) {
                    reset_unmark(old_size);
                    return false;
                }
                break;
            case justification::CLAUSE: {
                clause & c = get_clause(js);
                unsigned i   = 0;
                if (c[0].var() == var) {
                    i = 1;
                }
                else {
                    SASSERT(c[1].var() == var);
                    if (!process_antecedent_for_minimization(~c[0])) {
                        reset_unmark(old_size);
                        return false;
                    }
                    i = 2;
                }
                unsigned sz = c.size();
                for (; i < sz; i++) {
                    if (!process_antecedent_for_minimization(~c[i])) {
                        reset_unmark(old_size);
                        return false;
                    }
                }
                break;
            }
            case justification::EXT_JUSTIFICATION: {
                literal consequent(var, value(var) == l_false);
                fill_ext_antecedents(consequent, js, false);
                for (literal l : m_ext_antecedents) {
                    if (!process_antecedent_for_minimization(l)) {
                        reset_unmark(old_size);
                        return false;
                    }
                }
                break;
            }
            default:
                UNREACHABLE();
                break;
            }
            TRACE("sat_conflict", 
                  display_justification(tout << var << " ",js) << "\n";);
        }
        return true;
    }

    /**
       \brief Restore the size of m_unmark to old_size, and
       unmark variables at positions [old_size, m_unmark.size()).
    */
    void solver::reset_unmark(unsigned old_size) {
        unsigned curr_size = m_unmark.size();
        for(unsigned i = old_size; i < curr_size; i++)
            reset_mark(m_unmark[i]);
        m_unmark.shrink(old_size);
    }

    /**
       \brief Store the levels of the literals at m_lemma in the
       approximated set m_lvl_set.
    */
    void solver::updt_lemma_lvl_set() {
        m_lvl_set.reset();
        for (literal l : m_lemma) 
            m_lvl_set.insert(lvl(l));
    }

    /**
       \brief Minimize lemma using binary resolution
    */
    bool solver::minimize_lemma_binres() {
        SASSERT(!m_lemma.empty());
        SASSERT(m_unmark.empty());
        unsigned sz   = m_lemma.size();
        unsigned num_reduced = 0;
        for (unsigned i = 1; i < sz; ++i) {
            mark_lit(m_lemma[i]);            
        }
        watch_list const& wlist = get_wlist(m_lemma[0]);
        for (watched const& w : wlist) {
            if (w.is_binary_clause() && is_marked_lit(w.get_literal())) {
                unmark_lit(~w.get_literal());
                num_reduced++;
            }
        }
        if (num_reduced > 0) {
            unsigned j = 1;
            for (unsigned i = 1; i < sz; ++i) {
                if (is_marked_lit(m_lemma[i])) {
                    m_lemma[j++] = m_lemma[i];
                    unmark_lit(m_lemma[i]);
                }
            }
            m_lemma.shrink(j);
        }

        return num_reduced > 0;
    }

    /**
       \brief Minimize the number of literals in m_lemma. The main idea is to remove
       literals that are implied by other literals in m_lemma and/or literals
       assigned at level 0.
    */
    bool solver::minimize_lemma() {
        SASSERT(!m_lemma.empty());
        SASSERT(m_unmark.empty());
        updt_lemma_lvl_set();

        unsigned sz   = m_lemma.size();
        unsigned i    = 1; // the first literal is the FUIP
        unsigned j    = 1;
        for (; i < sz; i++) {
            literal l = m_lemma[i];
            if (implied_by_marked(l)) {
                m_unmark.push_back(l.var());
            }
            else {
                m_lemma[j++] = m_lemma[i];
            }
        }

        reset_unmark(0);
        m_lemma.shrink(j);
        m_stats.m_minimized_lits += sz - j;
        return j < sz;
    }

    /**
       \brief Reset the mark of the variables in the current lemma.
    */
    void solver::reset_lemma_var_marks() {
        if (m_config.m_branching_heuristic == BH_VSIDS) {
            update_lrb_reasoned();
        }        
        literal_vector::iterator it  = m_lemma.begin();
        literal_vector::iterator end = m_lemma.end();
        SASSERT(!is_marked((*it).var()));
        ++it;
        for(; it != end; ++it) {
            bool_var var = (*it).var();
            reset_mark(var);
        }
    }

    void solver::update_lrb_reasoned() {
        unsigned sz = m_lemma.size();
        SASSERT(!is_marked(m_lemma[0].var()));
        mark(m_lemma[0].var());
        for (unsigned i = m_lemma.size(); i-- > 0; ) {
            justification js = m_justification[m_lemma[i].var()];
            switch (js.get_kind()) {
            case justification::NONE:
                break;                    
            case justification::BINARY:
                update_lrb_reasoned(js.get_literal());
                break;
            case justification::CLAUSE: {
                clause & c = get_clause(js);
                for (literal l : c) {
                    update_lrb_reasoned(l);
                }
                break;
            }
            case justification::EXT_JUSTIFICATION: {
                fill_ext_antecedents(~m_lemma[i], js, true);
                for (literal l : m_ext_antecedents) {
                    update_lrb_reasoned(l);
                }
                break;
            }
            }
        }
        reset_mark(m_lemma[0].var());
        for (unsigned i = m_lemma.size(); i-- > sz; ) {
            reset_mark(m_lemma[i].var());
        }
        m_lemma.shrink(sz);
    }

    void solver::update_lrb_reasoned(literal lit) {
        bool_var v = lit.var();
        if (!is_marked(v)) {
            mark(v);
            m_reasoned[v]++;
            inc_activity(v);
            m_lemma.push_back(lit);
        }
    }

    /**
       \brief Apply dynamic subsumption resolution to new lemma.
       Only binary and ternary clauses are used.
    */
    bool solver::dyn_sub_res() {
        unsigned sz = m_lemma.size();
        for (unsigned i = 0; i < sz; i++) {
            mark_lit(m_lemma[i]);
        }

        literal l0 = m_lemma[0];
        // l0 is the FUIP, and we never remove the FUIP.
        //
        // In the following loop, we use unmark_lit(l) to remove a
        // literal from m_lemma.

        for (unsigned i = 0; i < sz; i++) {
            literal l = m_lemma[i];
            if (!is_marked_lit(l))
                continue; // literal was eliminated
            // first use watch lists
            watch_list const & wlist = get_wlist(~l);
            for (watched const& w : wlist) {
                // In this for-loop, the conditions l0 != ~l2 and l0 != ~l3
                // are not really needed if the solver does not miss unit propagations.
                // However, we add them anyway because we don't want to rely on this
                // property of the propagator.
                // For example, if this property is relaxed in the future, then the code
                // without the conditions l0 != ~l2 and l0 != ~l3 may remove the FUIP
                if (w.is_binary_clause()) {
                    literal l2 = w.get_literal();
                    if (is_marked_lit(~l2) && l0 != ~l2) {
                        // eliminate ~l2 from lemma because we have the clause l \/ l2
                        unmark_lit(~l2);
                    }
                }
                else {
                    // May miss some binary/ternary clauses, but that is ok.
                    // I sort the watch lists at every simplification round.
                    break;
                }
            }
            // try to use cached implication if available
            literal_vector * implied_lits = m_probing.cached_implied_lits(~l);
            if (implied_lits) {
                for (literal l2 : *implied_lits) {
                    // Here, we must check l0 != ~l2.
                    // l \/ l2 is an implied binary clause.
                    // However, it may have been deduced using a lemma that has been deleted.
                    // For example, consider the following sequence of events:
                    //
                    // 1. Initial clause database:
                    //
                    //    l  \/ ~p1
                    //    p1 \/ ~p2
                    //    p2 \/ ~p3
                    //    p3 \/ ~p4
                    //    q1  \/ q2  \/ p1 \/ p2 \/ p3 \/ p4 \/ l2
                    //    q1  \/ ~q2 \/ p1 \/ p2 \/ p3 \/ p4 \/ l2
                    //    ~q1 \/ q2  \/ p1 \/ p2 \/ p3 \/ p4 \/ l2
                    //    ~q1 \/ ~q2 \/ p1 \/ p2 \/ p3 \/ p4 \/ l2
                    //    ...
                    //
                    // 2. Now suppose we learned the lemma
                    //
                    //    p1 \/ p2 \/ p3 \/ p4 \/ l2   (*)
                    //
                    // 3. Probing is executed and we notice hat (~l => l2) when we assign l to false.
                    //    That is, l \/ l2 is an implied clause. Note that probing does not add
                    //    this clause to the clause database (there are too many).
                    //
                    // 4. Lemma (*) is deleted (garbage collected).
                    //
                    // 5. l is decided to be false, p1, p2, p3 and p4 are propagated using BCP,
                    //    but l2 is not since the lemma (*) was deleted.
                    //
                    //    Probing module still "knows" that l \/ l2 is valid binary clause
                    //
                    // 6. A new lemma is created where ~l2 is the FUIP and the lemma also contains l.
                    //    If we remove l0 != ~l2 may try to delete the FUIP.
                    if (is_marked_lit(~l2) && l0 != ~l2) {
                        // eliminate ~l2 from lemma because we have the clause l \/ l2
                        unmark_lit(~l2);
                    }
                }
            }
        }

        // can't eliminate FUIP
        SASSERT(is_marked_lit(m_lemma[0]));

        unsigned j = 0;
        for (unsigned i = 0; i < sz; i++) {
            literal l = m_lemma[i];
            if (is_marked_lit(l)) {
                unmark_lit(l);
                m_lemma[j] = l;
                j++;
            }
        }

        m_stats.m_dyn_sub_res += sz - j;

        SASSERT(j >= 1);
        m_lemma.shrink(j);
        return j < sz;
    }


    // -----------------------
    //
    // Backtracking
    //
    // -----------------------
    void solver::push() {
        SASSERT(!m_ext || !m_ext->can_propagate());
        SASSERT(!inconsistent());
        TRACE("sat_verbose", tout << "q:" << m_qhead << " trail: " << m_trail.size() << "\n";);
        SASSERT(m_qhead == m_trail.size());
        m_scopes.push_back(scope());
        scope & s = m_scopes.back();
        m_scope_lvl++;
        s.m_trail_lim = m_trail.size();
        s.m_clauses_to_reinit_lim = m_clauses_to_reinit.size();
        s.m_inconsistent = m_inconsistent;
        if (m_ext) {
            m_vars_lim.push(m_active_vars.size());
            m_ext->push();
        }
    }

    void solver::pop_reinit(unsigned num_scopes) {
        pop(num_scopes);
        exchange_par();
        reinit_assumptions();
        m_stats.m_units = init_trail_size();
    }

    void solver::pop_vars(unsigned num_scopes) {
        //integrity_checker check(*this);
        //check.check_reinit_stack();
        m_vars_to_reinit.reset();
        unsigned old_num_vars = m_vars_lim.pop(num_scopes);
        if (old_num_vars == m_active_vars.size())
            return;
        unsigned sz = m_active_vars.size(), j = old_num_vars;
        unsigned new_lvl = m_scopes.size() - num_scopes;

        gc_reinit_stack(num_scopes);        

        // check.check_reinit_stack();
        init_visited();
        unsigned old_sz = m_scopes[new_lvl].m_clauses_to_reinit_lim;
        for (unsigned i = m_clauses_to_reinit.size(); i-- > old_sz; ) {
            clause_wrapper const& cw = m_clauses_to_reinit[i];
            for (unsigned j = cw.size(); j-- > 0; )
                mark_visited(cw[j].var());
        }
        for (literal lit : m_lemma)
           mark_visited(lit.var());

        auto is_active = [&](bool_var v) {
            return value(v) != l_undef && lvl(v) <= new_lvl;
        };

        for (unsigned i = old_num_vars; i < sz; ++i) {
            bool_var v = m_active_vars[i];
            if (is_external(v) || is_visited(v) || is_active(v)) {
                m_vars_to_reinit.push_back(v);
                m_active_vars[j++] = v;
                m_var_scope[v] = new_lvl;
            }
            else {
                set_eliminated(v, true);
                m_vars_to_free.push_back(v);                                   
            }
        }
        m_active_vars.shrink(j);

        auto cleanup_watch = [&](literal lit) {
            for (auto const& w : get_wlist(lit)) {
                IF_VERBOSE(1, verbose_stream() << "cleanup: " << lit << " " << w.is_binary_clause() << "\n");
            }
        };
        for (bool_var v : m_vars_to_free) {
            cleanup_watch(literal(v, false));
            cleanup_watch(literal(v, true));
            
        }
        TRACE("sat",
            tout << "clauses to reinit: " << (m_clauses_to_reinit.size() - old_sz) << "\n";
            tout << "new level:         " << new_lvl << "\n";
            tout << "vars to reinit:    " << m_vars_to_reinit << "\n";
            tout << "free vars:         " << bool_var_vector(m_vars_to_free) << "\n";
            for (unsigned i = m_clauses_to_reinit.size(); i-- > old_sz; )
                tout << "reinit:           " << m_clauses_to_reinit[i] << "\n";
            display(tout););        
    }

    void solver::shrink_vars(unsigned v) {
        unsigned j = 0; 
        for (bool_var w : m_free_vars) 
            if (w < v)
                m_free_vars[j++] = w;
        m_free_vars.shrink(j);

        for (bool_var w = m_justification.size(); w-- > v;) {
            m_case_split_queue.del_var_eh(w);
            m_probing.reset_cache(literal(w, true));
            m_probing.reset_cache(literal(w, false));
        }
        m_watches.shrink(2*v);
        m_assignment.shrink(2*v);
        m_justification.shrink(v);
        m_decision.shrink(v);
        m_eliminated.shrink(v);
        m_external.shrink(v);
        m_var_scope.shrink(v);
        m_touched.shrink(v);
        m_activity.shrink(v);
        m_mark.shrink(v);
        m_lit_mark.shrink(2*v);
        m_phase.shrink(v);
        m_best_phase.shrink(v);
        m_prev_phase.shrink(v);
        m_assigned_since_gc.shrink(v);
        m_simplifier.reset_todos();
    }

    void solver::pop(unsigned num_scopes) {
        if (num_scopes == 0)
            return;
        if (m_ext) {
            pop_vars(num_scopes);
            m_ext->pop(num_scopes);
        }
        SASSERT(num_scopes <= scope_lvl());
        unsigned new_lvl = scope_lvl() - num_scopes;
        scope & s        = m_scopes[new_lvl];
        m_inconsistent   = false; // TBD: use model seems to make this redundant: s.m_inconsistent;
        unassign_vars(s.m_trail_lim, new_lvl);
        for (bool_var v : m_vars_to_free)
            m_case_split_queue.del_var_eh(v);
        m_scope_lvl -= num_scopes;
        reinit_clauses(s.m_clauses_to_reinit_lim);
        m_scopes.shrink(new_lvl);
        if (m_ext) {
            m_ext->pop_reinit();
            m_free_vars.append(m_vars_to_free);
            m_vars_to_free.reset();
        }
    }

    void solver::unassign_vars(unsigned old_sz, unsigned new_lvl) {
        SASSERT(old_sz <= m_trail.size());
        SASSERT(m_replay_assign.empty());
        for (unsigned i = m_trail.size(); i-- > old_sz; ) {
            literal l  = m_trail[i];
            bool_var v = l.var();
            if (lvl(v) <= new_lvl) {
                m_replay_assign.push_back(l);
                continue;
            }
            m_assignment[l.index()]    = l_undef;
            m_assignment[(~l).index()] = l_undef;
            SASSERT(value(v) == l_undef);
            m_case_split_queue.unassign_var_eh(v);
            if (m_config.m_anti_exploration) {
                m_canceled[v] = m_stats.m_conflict;
            }
        }
        m_trail.shrink(old_sz);        
        DEBUG_CODE(for (literal l : m_trail) SASSERT(lvl(l.var()) <= new_lvl););
        m_qhead = m_trail.size();
        if (!m_replay_assign.empty()) IF_VERBOSE(20, verbose_stream() << "replay assign: " << m_replay_assign.size() << "\n");
        CTRACE("sat", !m_replay_assign.empty(), tout << "replay-assign: " << m_replay_assign << "\n";);
        for (unsigned i = m_replay_assign.size(); i-- > 0; ) {
            literal lit = m_replay_assign[i];
            SASSERT(value(lit) == l_true);
            SASSERT(!m_trail.contains(lit) && !m_trail.contains(~lit));
            m_trail.push_back(lit);            
        }
        
        m_replay_assign.reset();
    }

    void solver::reinit_clauses(unsigned old_sz) {
        unsigned sz = m_clauses_to_reinit.size();
        SASSERT(old_sz <= sz);
        unsigned j = old_sz;
        for (unsigned i = old_sz; i < sz; i++) {
            clause_wrapper cw = m_clauses_to_reinit[i];
            bool reinit = false;
            if (cw.is_binary()) {
                if (propagate_bin_clause(cw[0], cw[1]) && !at_base_lvl())
                    m_clauses_to_reinit[j++] = cw;
                else if (has_variables_to_reinit(cw[0], cw[1]) && !at_base_lvl())
                    m_clauses_to_reinit[j++] = cw;
            }
            else {
                clause & c = *(cw.get_clause());
                detach_clause(c);
                attach_clause(c, reinit);
                if (reinit && !at_base_lvl()) 
                    // clause propagated literal, must keep it in the reinit stack.
                    m_clauses_to_reinit[j++] = cw;                
                else if (has_variables_to_reinit(c) && !at_base_lvl())
                    m_clauses_to_reinit[j++] = cw;
                else 
                    c.set_reinit_stack(false);   
            }
        }
        m_clauses_to_reinit.shrink(j);
    }

    //
    // All new clauses that are added to the solver
    // are relative to the user-scope literals.
    //

    void solver::user_push() {
        pop_to_base_level();
        m_free_var_freeze.push_back(m_free_vars);
        m_free_vars.reset(); // resetting free_vars forces new variables to be assigned above new_v
        bool_var new_v = mk_var(true, false);
        SASSERT(new_v + 1 == m_justification.size()); // there are no active variables that have higher values
        literal lit = literal(new_v, false);
        m_user_scope_literals.push_back(lit);
        m_cut_simplifier = nullptr; // for simplicity, wipe it out
        if (m_ext)
            m_ext->user_push();
        TRACE("sat", tout << "user_push: " << lit << "\n";);
    }

    void solver::user_pop(unsigned num_scopes) {
        if (m_user_scope_literals.empty())
            return;
        unsigned old_sz = m_user_scope_literals.size() - num_scopes;
        bool_var max_var = m_user_scope_literals[old_sz].var();        
        m_user_scope_literals.shrink(old_sz);

        pop_to_base_level();
        if (m_ext)
            m_ext->user_pop(num_scopes);
    
        gc_vars(max_var);
        TRACE("sat", display(tout););

        m_qhead = 0;
        unsigned j = 0;
        for (bool_var v : m_free_vars) 
            if (v < max_var)
                m_free_vars[j++] = v;
        m_free_vars.shrink(j);
        m_free_vars.append(m_free_var_freeze[old_sz]); 
        m_free_var_freeze.shrink(old_sz);
        scoped_suspend_rlimit _sp(m_rlimit);
        propagate(false);     
    }

    void solver::pop_to_base_level() {
        reset_assumptions();
        pop(scope_lvl());
    }

    // -----------------------
    //
    // Misc
    //
    // -----------------------

    void solver::updt_params(params_ref const & p) {
        m_params.append(p);
        m_config.updt_params(p);
        m_simplifier.updt_params(p);
        m_asymm_branch.updt_params(p);
        m_probing.updt_params(p);
        m_scc.updt_params(p);
        m_rand.set_seed(m_config.m_random_seed);
        m_step_size = m_config.m_step_size_init;
        m_drat.updt_config();
        m_fast_glue_avg.set_alpha(m_config.m_fast_glue_avg);
        m_slow_glue_avg.set_alpha(m_config.m_slow_glue_avg);
        m_fast_glue_backup.set_alpha(m_config.m_fast_glue_avg);
        m_slow_glue_backup.set_alpha(m_config.m_slow_glue_avg);
        m_trail_avg.set_alpha(m_config.m_slow_glue_avg);

        if (m_config.m_cut_simplify && !m_cut_simplifier && m_user_scope_literals.empty()) {
            m_cut_simplifier = alloc(cut_simplifier, *this);
        }
    }

    void solver::collect_param_descrs(param_descrs & d) {
        config::collect_param_descrs(d);
        simplifier::collect_param_descrs(d);
        asymm_branch::collect_param_descrs(d);
        probing::collect_param_descrs(d);
        scc::collect_param_descrs(d);
    }

    void solver::collect_statistics(statistics & st) const {
        m_stats.collect_statistics(st);
        m_cleaner.collect_statistics(st);
        m_simplifier.collect_statistics(st);
        m_scc.collect_statistics(st);
        m_asymm_branch.collect_statistics(st);
        m_probing.collect_statistics(st);
        if (m_ext) m_ext->collect_statistics(st);
        if (m_local_search) m_local_search->collect_statistics(st);
        if (m_cut_simplifier) m_cut_simplifier->collect_statistics(st);
        st.copy(m_aux_stats);
    }

    void solver::reset_statistics() {
        m_stats.reset();
        m_cleaner.reset_statistics();
        m_simplifier.reset_statistics();
        m_asymm_branch.reset_statistics();
        m_probing.reset_statistics();
        m_aux_stats.reset();
    }

    // -----------------------
    //
    // Activity related stuff
    //
    // -----------------------

    void solver::rescale_activity() {
        SASSERT(m_config.m_branching_heuristic == BH_VSIDS);
        for (unsigned& act : m_activity) {
            act >>= 14;
        }
        m_activity_inc >>= 14;
    }

    void solver::update_chb_activity(bool is_sat, unsigned qhead) {
        SASSERT(m_config.m_branching_heuristic == BH_CHB);
        double multiplier = m_config.m_reward_offset * (is_sat ? m_config.m_reward_multiplier : 1.0);
        for (unsigned i = qhead; i < m_trail.size(); ++i) {
            auto v = m_trail[i].var();
            auto d = m_stats.m_conflict - m_last_conflict[v] + 1;
            if (d == 0) d = 1;
            auto reward = multiplier / d;            
            auto activity = m_activity[v];
            set_activity(v, static_cast<unsigned>(m_step_size * reward + ((1.0 - m_step_size) * activity)));
        }
    }

    void solver::move_to_front(bool_var b) {
        if (b >= num_vars())
            return;
        if (m_case_split_queue.empty())
            return;
        bool_var next = m_case_split_queue.min_var();
        auto next_act = m_activity[next];
        set_activity(b, next_act + 1);
    }

    // -----------------------
    //
    // Iterators
    //
    // -----------------------
    void solver::collect_bin_clauses(svector<bin_clause> & r, bool redundant, bool learned_only) const {
        SASSERT(redundant || !learned_only);  
        unsigned sz = m_watches.size();
        for (unsigned l_idx = 0; l_idx < sz; l_idx++) {
            literal l = to_literal(l_idx);
            l.neg();
            for (watched const& w : m_watches[l_idx]) {
                if (!w.is_binary_clause())
                    continue;
                if (!redundant && w.is_learned())
                    continue;
                else if (redundant && learned_only && !w.is_learned()) 
                    continue;
                literal l2 = w.get_literal();
                if (l.index() > l2.index())
                    continue;
                TRACE("cleanup_bug", tout << "collected: " << l << " " << l2 << "\n";);
                r.push_back(bin_clause(l, l2));
            }
        }
    }

    // -----------------------
    //
    // Debugging
    //
    // -----------------------
    bool solver::check_invariant() const {
        if (!m_rlimit.inc()) return true;
        integrity_checker checker(*this);
        VERIFY(checker());
        VERIFY(!m_ext || m_ext->validate());
        return true;
    }

    bool solver::check_marks() const {
        for (bool_var v = 0; v < num_vars(); v++) {
            SASSERT(!is_marked(v));
        }
        return true;
    }

    std::ostream& solver::display_model(std::ostream& out) const {
        unsigned num = num_vars();
        for (bool_var v = 0; v < num; v++) {
            out << v << ": " << m_model[v] << "\n";
        }
        return out;
}

    void solver::display_binary(std::ostream & out) const {
        unsigned sz = m_watches.size();
        for (unsigned l_idx = 0; l_idx < sz; l_idx++) {
            literal l = to_literal(l_idx);
            l.neg();
            for (watched const& w : m_watches[l_idx]) {
                if (!w.is_binary_clause())
                    continue;
                literal l2 = w.get_literal();
                if (l.index() > l2.index())
                    continue;
                out << "(" << l << " " << l2 << ")";
                if (w.is_learned()) out << "*";
                out << "\n";
            }
        }
    }

    void solver::display_units(std::ostream & out) const {
        unsigned level = 0;
        for (literal lit : m_trail) {            
            if (lvl(lit) > level) {
                level = lvl(lit);
                out << level << ": ";
            }
            else {
                out << "    ";
            }
            out << lit << " ";
            if (lvl(lit) < level) {
                out << "@" << lvl(lit) << " ";
            }
            display_justification(out, m_justification[lit.var()]) << "\n";
        }
    }

    void solver::display(std::ostream & out) const {
        out << "(sat\n";
        display_units(out);
        display_binary(out);
        out << m_clauses << m_learned;
        if (m_ext) {
            m_ext->display(out);
        }
        out << ")\n";
    }

    std::ostream& solver::display_justification(std::ostream & out, justification const& js) const {
        switch (js.get_kind()) {
        case justification::NONE:
            out << "none @" << js.level();
            break;
        case justification::BINARY:
            out << "binary " << js.get_literal() << "@" << lvl(js.get_literal());
            break;
        case justification::CLAUSE: {
            out << "(";
            bool first = true;
            for (literal l : get_clause(js)) {
                if (first) first = false; else out << " ";
                out << l << "@" << lvl(l);
            }
            out << ")";
            break;
        }
        case justification::EXT_JUSTIFICATION:
            if (m_ext) 
                m_ext->display_justification(out << "ext ", js.get_ext_justification_idx());            
            break;
        default:
            break;
        }
        return out;
    }


    unsigned solver::num_clauses() const {
        unsigned num_cls = m_trail.size(); // units;
        unsigned l_idx = 0;
        for (auto const& wl : m_watches) {
            literal l = ~to_literal(l_idx++);
            for (auto const& w : wl) {
                if (w.is_binary_clause() && l.index() < w.get_literal().index())
                    num_cls++;
            }
        }
        return num_cls + m_clauses.size() + m_learned.size();
    }

    void solver::num_binary(unsigned& given, unsigned& redundant) const {
        given = redundant = 0;
        unsigned l_idx = 0;
        for (auto const& wl : m_watches) {
            literal l = ~to_literal(l_idx++);
            for (auto const& w : wl) {
                if (w.is_binary_clause() && l.index() < w.get_literal().index()) {
                    if (w.is_learned()) ++redundant; else ++given;
                }
            }
        }
    }

    void solver::display_dimacs(std::ostream & out) const {
        out << "p cnf " << num_vars() << " " << num_clauses() << "\n";
        for (literal lit : m_trail) {
            out << dimacs_lit(lit) << " 0\n";
        }
        unsigned l_idx = 0;
        for (auto const& wlist : m_watches) {
            literal l = ~to_literal(l_idx++);
            for (auto const& w : wlist) {
                if (w.is_binary_clause() && l.index() < w.get_literal().index())
                    out << dimacs_lit(l) << " " << dimacs_lit(w.get_literal()) << " 0\n";
            }
        }
        clause_vector const * vs[2] = { &m_clauses, &m_learned };
        for (unsigned i = 0; i < 2; i++) {
            clause_vector const & cs = *(vs[i]);
            for (auto cp : cs) {
                for (literal l : *cp) {
                    out << dimacs_lit(l) << " ";
                }
                out << "0\n";
            }
        }
    }

    void solver::display_wcnf(std::ostream & out, unsigned sz, literal const* lits, unsigned const* weights) const {
        unsigned max_weight = 0;
        for (unsigned i = 0; i < sz; ++i) 
            max_weight += weights[i];
        ++max_weight;

        if (m_ext)
            throw default_exception("wcnf is only supported for pure CNF problems");

        out << "p wcnf " << num_vars() << " " << num_clauses() + sz << " " << max_weight << "\n";
        out << "c soft " << sz << "\n";

        for (literal lit : m_trail) 
            out << max_weight << " " << dimacs_lit(lit) << " 0\n";
        unsigned l_idx = 0;
        for (watch_list const& wlist : m_watches) {
            literal l = ~to_literal(l_idx);
            for (watched const& w : wlist) {
                if (w.is_binary_clause() && l.index() < w.get_literal().index())
                    out << max_weight << " " << dimacs_lit(l) << " " << dimacs_lit(w.get_literal()) << " 0\n";
            }
            ++l_idx;
        }
        clause_vector const * vs[2] = { &m_clauses, &m_learned };
        for (unsigned i = 0; i < 2; i++) {
            clause_vector const & cs = *(vs[i]);
            for (clause const* cp : cs) {
                clause const & c = *cp;
                out << max_weight << " ";
                for (literal l : c)
                    out << dimacs_lit(l) << " ";
                out << "0\n";
            }
        }
        for (unsigned i = 0; i < sz; ++i) {
            out << weights[i] << " " << lits[i] << " 0\n";
        }
        out.flush();
    }

    void solver::display_watches(std::ostream & out, literal lit) const {
        display_watch_list(out << lit << ": ", get_wlist(lit)) << "\n";
    }

    void solver::display_watches(std::ostream & out) const {
        unsigned l_idx = 0;
        for (watch_list const& wlist : m_watches) {
            literal l = to_literal(l_idx++);
            if (!wlist.empty()) 
                display_watch_list(out << l << ": ", wlist) << "\n";
        }
    }

    std::ostream& solver::display_watch_list(std::ostream& out, watch_list const& wl) const {
        return sat::display_watch_list(out, cls_allocator(), wl, m_ext.get());
    }

    void solver::display_assignment(std::ostream & out) const {
        out << m_trail << "\n";
    }

    /**
       \brief Return true, if c is a clause containing one unassigned literal.
    */
    bool solver::is_unit(clause const & c) const {
        bool found_undef = false;
        for (literal l : c) {
            switch (value(l)) {
            case l_undef:
                if (found_undef)
                    return false;
                found_undef = true;
                break;
            case l_true:
                return false;
            case l_false:
                break;
            }
        }
        return found_undef;
    }

    /**
       \brief Return true, if all literals in c are assigned to false.
    */
    bool solver::is_empty(clause const & c) const {
        for (literal lit : c) 
            if (value(lit) != l_false)
                return false;
        return true;
    }

    bool solver::check_missed_propagation(clause_vector const & cs) const {
        for (clause* cp : cs) {
            clause const & c = *cp;
            if (c.frozen())
                continue;
            if (is_empty(c) || is_unit(c)) {
                TRACE("sat_missed_prop", tout << "missed_propagation: " << c << "\n";
                      for (literal l : c) tout << l << ": " << value(l) << "\n";);
                UNREACHABLE();
            }
            SASSERT(!is_empty(c));
            SASSERT(!is_unit(c));
        }
        return true;
    }

    bool solver::check_missed_propagation() const {
        if (inconsistent())
            return true;
        return check_missed_propagation(m_clauses) && check_missed_propagation(m_learned);
    }

    // -----------------------
    //
    // Simplification
    //
    // -----------------------
    bool solver::do_cleanup(bool force) {
        if (m_conflicts_since_init == 0 && !force)
            return false;
        if (at_base_lvl() && !inconsistent() && m_cleaner(force)) {
            if (m_ext)
                m_ext->clauses_modifed();
            return true;
        }
        return false;
    }

    void solver::simplify(bool redundant) {
        if (!at_base_lvl() || inconsistent())
            return;
        m_simplifier(redundant);
        m_simplifier.finalize();
        if (m_ext)
            m_ext->clauses_modifed();
    }

    unsigned solver::scc_bin() {
        if (!at_base_lvl() || inconsistent())
            return 0;
        unsigned r = m_scc();
        if (r > 0 && m_ext)
            m_ext->clauses_modifed();
        return r;
    }

    // -----------------------
    //
    // Extraction of mutexes
    //
    // -----------------------

    struct neg_literal {
        unsigned negate(unsigned idx) {
            return (~to_literal(idx)).index();
        }
    };

    lbool solver::find_mutexes(literal_vector const& lits, vector<literal_vector> & mutexes) {
        max_cliques<neg_literal> mc;
        m_user_bin_clauses.reset();
        // m_binary_clause_graph.reset();
        collect_bin_clauses(m_user_bin_clauses, true, false);
        hashtable<literal_pair, pair_hash<literal_hash, literal_hash>, default_eq<literal_pair> > seen_bc;
        for (auto const& b : m_user_bin_clauses) {
            literal l1 = b.first;
            literal l2 = b.second;
            literal_pair p(l1, l2);
            if (!seen_bc.contains(p)) {
                seen_bc.insert(p);
                mc.add_edge(l1.index(), l2.index());
            }
        }
        vector<unsigned_vector> _mutexes;
        literal_vector _lits(lits);
        if (m_ext) {
            m_ext->find_mutexes(_lits, mutexes);
        }
        unsigned_vector ps;
        for (literal lit : _lits) 
            ps.push_back(lit.index());
        mc.cliques2(ps, _mutexes);
        vector<vector<literal_vector>> sorted;
        for (auto const& mux : _mutexes) {
            literal_vector clique;
            sorted.reserve(mux.size() + 1);
            for (auto const& idx : mux) 
                clique.push_back(to_literal(idx));
            sorted[mux.size()].push_back(clique);
        }
        for (unsigned i = sorted.size(); i-- > 0; ) 
            mutexes.append(sorted[i]);
        return l_true;
    }

    // -----------------------
    //
    // Consequence generation.
    //
    // -----------------------

    static void prune_unfixed(sat::literal_vector& lambda, sat::model const& m) {
        for (unsigned i = 0; i < lambda.size(); ++i) {
            if ((m[lambda[i].var()] == l_false) != lambda[i].sign()) {
                lambda[i] = lambda.back();
                lambda.pop_back();
                --i;
            }
        }
    }

    // Algorithm 7: Corebased Algorithm with Chunking

    static void back_remove(sat::literal_vector& lits, sat::literal l) {
        for (unsigned i = lits.size(); i > 0; ) {
            --i;
            if (lits[i] == l) {
                lits[i] = lits.back();
                lits.pop_back();
                return;
            }
        }
        UNREACHABLE();
    }

    static void brute_force_consequences(sat::solver& s, sat::literal_vector const& asms, sat::literal_vector const& gamma, vector<sat::literal_vector>& conseq) {
        for (literal lit : gamma) {
            sat::literal_vector asms1(asms);
            asms1.push_back(~lit);
            lbool r = s.check(asms1.size(), asms1.data());
            if (r == l_false) {
                conseq.push_back(s.get_core());
            }
        }
    }

    static lbool core_chunking(sat::solver& s, model const& m, sat::bool_var_vector const& vars, sat::literal_vector const& asms, vector<sat::literal_vector>& conseq, unsigned K) {
        sat::literal_vector lambda;
        for (bool_var v : vars) {
            lambda.push_back(sat::literal(v, m[v] == l_false));
        }
        while (!lambda.empty()) {
            IF_VERBOSE(1, verbose_stream() << "(sat-backbone-core " << lambda.size() << " " << conseq.size() << ")\n";);
            unsigned k = std::min(K, lambda.size());
            sat::literal_vector gamma, omegaN;
            for (unsigned i = 0; i < k; ++i) {
                sat::literal l = lambda[lambda.size() - i - 1];
                gamma.push_back(l);
                omegaN.push_back(~l);
            }
            while (true) {
                sat::literal_vector asms1(asms);
                asms1.append(omegaN);
                lbool r = s.check(asms1.size(), asms1.data());
                if (r == l_true) {
                    IF_VERBOSE(1, verbose_stream() << "(sat) " << omegaN << "\n";);
                    prune_unfixed(lambda, s.get_model());
                    break;
                }
                sat::literal_vector const& core = s.get_core();
                sat::literal_vector occurs;
                IF_VERBOSE(1, verbose_stream() << "(core " << core.size() << ")\n";);
                for (unsigned i = 0; i < omegaN.size(); ++i) {
                    if (core.contains(omegaN[i])) {
                        occurs.push_back(omegaN[i]);
                    }
                }
                if (occurs.size() == 1) {
                    sat::literal lit = occurs.back();
                    sat::literal nlit = ~lit;
                    conseq.push_back(core);
                    back_remove(lambda, ~lit);
                    back_remove(gamma, ~lit);
                    s.mk_clause(1, &nlit);
                }
                for (unsigned i = 0; i < omegaN.size(); ++i) {
                    if (occurs.contains(omegaN[i])) {
                        omegaN[i] = omegaN.back();
                        omegaN.pop_back();
                        --i;
                    }
                }
                if (omegaN.empty() && occurs.size() > 1) {
                    brute_force_consequences(s, asms, gamma, conseq);
                    for (unsigned i = 0; i < gamma.size(); ++i) {
                        back_remove(lambda, gamma[i]);
                    }
                    break;
                }
            }
        }
        return l_true;
    }


    lbool solver::get_consequences(literal_vector const& asms, bool_var_vector const& vars, vector<literal_vector>& conseq) {
        literal_vector lits;
        lbool is_sat = l_true;

        if (m_config.m_restart_max != UINT_MAX && !m_model_is_current) {
            return get_bounded_consequences(asms, vars, conseq);
        }
        if (!m_model_is_current) {
            is_sat = check(asms.size(), asms.data());
        }
        if (is_sat != l_true) {
            return is_sat;
        }
        model mdl = get_model();
        for (unsigned i = 0; i < vars.size(); ++i) {
            bool_var v = vars[i];
            switch (get_model()[v]) {
            case l_true: lits.push_back(literal(v, false)); break;
            case l_false: lits.push_back(literal(v, true)); break;
            default: break;
            }
        }

        if (false && asms.empty()) {
            is_sat = core_chunking(*this, mdl, vars, asms, conseq, 100);
        }
        else {
            is_sat = get_consequences(asms, lits, conseq);
        }
        set_model(mdl, !mdl.empty());
        return is_sat;
    }

    void solver::fixup_consequence_core() {
        index_set s;
        TRACE("sat", tout << m_core << "\n";);
        for (unsigned i = 0; i < m_core.size(); ++i) {
            TRACE("sat", tout << m_core[i] << ": "; display_index_set(tout, m_antecedents.find(m_core[i].var())) << "\n";);
            s |= m_antecedents.find(m_core[i].var());
        }
        m_core.reset();
        for (unsigned idx : s) {
            m_core.push_back(to_literal(idx));
        }
        TRACE("sat", tout << m_core << "\n";);
    }

    bool solver::reached_max_conflicts() {
        if (m_config.m_max_conflicts == 0 || m_conflicts_since_init > m_config.m_max_conflicts) {
            if (m_reason_unknown != "sat.max.conflicts") {
                m_reason_unknown = "sat.max.conflicts";
                IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort: max-conflicts = " << m_conflicts_since_init << "\")\n";);
            }
            return !inconsistent();
        }
        return false;
    }


    lbool solver::get_bounded_consequences(literal_vector const& asms, bool_var_vector const& vars, vector<literal_vector>& conseq) {
        bool_var_set unfixed_vars;
        unsigned num_units = 0;
        for (bool_var v : vars) {
            unfixed_vars.insert(v);
        }
        TRACE("sat", tout << asms << "\n";);
        m_antecedents.reset();
        pop_to_base_level();
        if (inconsistent()) return l_false;
        flet<bool> _searching(m_searching, true);
        init_search();
        propagate(false);
        if (inconsistent()) return l_false;
        if (asms.empty()) {
            bool_var v = mk_var(true, false);
            literal lit(v, false);
            init_assumptions(1, &lit);
        }
        else {
            init_assumptions(asms.size(), asms.data());
        }
        propagate(false);
        if (check_inconsistent()) return l_false;

        extract_fixed_consequences(num_units, asms, unfixed_vars, conseq);

        do_simplify();
        if (check_inconsistent()) {
            fixup_consequence_core();
            return l_false;
        }

        while (true) {
            SASSERT(!inconsistent());

            lbool r = bounded_search();
            if (r != l_undef) {
                fixup_consequence_core();
                return r;
            }

            extract_fixed_consequences(num_units, asms, unfixed_vars, conseq);

            do_restart(true);
            do_simplify();
            if (check_inconsistent()) {
                fixup_consequence_core();
                return l_false;
            }
            do_gc();

            if (should_cancel()) {
                return l_undef;
            }
        }
    }

    lbool solver::get_consequences(literal_vector const& asms, literal_vector const& lits, vector<literal_vector>& conseq) {
        TRACE("sat", tout << asms << "\n";);
        m_antecedents.reset();
        literal_set unfixed_lits(lits), assumptions(asms);
        bool_var_set unfixed_vars;
        for (literal lit : lits) {
            unfixed_vars.insert(lit.var());
        }

        pop_to_base_level();
        if (inconsistent()) return l_false;
        init_search();
        propagate(false);
        if (inconsistent()) return l_false;
        if (asms.empty()) {
            bool_var v = mk_var(true, false);
            literal lit(v, false);
            init_assumptions(1, &lit);
        }
        else {
            init_assumptions(asms.size(), asms.data());
        }
        propagate(false);
        if (check_inconsistent()) return l_false;
        SASSERT(search_lvl() == 1);

        unsigned num_iterations = 0;
        extract_fixed_consequences(unfixed_lits, assumptions, unfixed_vars, conseq);
        update_unfixed_literals(unfixed_lits, unfixed_vars);
        while (!unfixed_lits.empty()) {
            if (scope_lvl() > search_lvl()) {
                pop(scope_lvl() - search_lvl());
            }
            propagate(false);
            ++num_iterations;
            checkpoint();
            unsigned num_resolves = 0;
            unsigned num_fixed = 0;
            lbool is_sat = l_true;
            for (literal lit : unfixed_lits) {
                if (value(lit) != l_undef) {
                    ++num_fixed;
                    if (lvl(lit) <= 1 && value(lit) == l_true) {
                        extract_fixed_consequences(lit, assumptions, unfixed_vars, conseq);
                    }
                    continue;
                }
                push();
                assign_scoped(~lit);
                propagate(false);
                while (inconsistent()) {
                    if (!resolve_conflict()) {
                        TRACE("sat", display(tout << "inconsistent\n"););
                        m_inconsistent = false;
                        is_sat = l_undef;
                        break;
                    }
                    propagate(false);
                    ++num_resolves;
                }
            }

            extract_fixed_consequences(unfixed_lits, assumptions, unfixed_vars, conseq);

            if (is_sat == l_true) {
                if (scope_lvl() == search_lvl() && num_resolves > 0) {
                    IF_VERBOSE(1, verbose_stream() << "(sat.get-consequences backjump)\n";);
                    is_sat = l_undef;
                }
                else {
                    is_sat = bounded_search();
                    if (is_sat == l_undef) {
                        do_restart(true);                        
                        propagate(false);
                    }
                    extract_fixed_consequences(unfixed_lits, assumptions, unfixed_vars, conseq);
                }
            }
            if (is_sat == l_false) {
                TRACE("sat", tout << "unsat\n";);
                m_inconsistent = false;
            }
            if (is_sat == l_true) {
                delete_unfixed(unfixed_lits, unfixed_vars);
            }
            update_unfixed_literals(unfixed_lits, unfixed_vars);
            IF_VERBOSE(1, verbose_stream() << "(sat.get-consequences"
                       << " iterations: " << num_iterations
                       << " variables: " << unfixed_lits.size()
                       << " fixed: " << conseq.size()
                       << " status: " << is_sat
                       << " pre-assigned: " << num_fixed
                       << " unfixed: " << lits.size() - conseq.size() - unfixed_lits.size()
                       << ")\n";);

            if (!unfixed_lits.empty() && m_config.m_restart_max <= num_iterations) {
                return l_undef;
            }
        }
        return l_true;
    }

    void solver::delete_unfixed(literal_set& unfixed_lits, bool_var_set& unfixed_vars) {
        literal_set to_keep;
        for (literal lit : unfixed_lits) {
            if (value(lit) == l_true) {
                to_keep.insert(lit);
            }
            else {
                unfixed_vars.remove(lit.var());
            }
        }
        unfixed_lits = to_keep;
    }

    void solver::update_unfixed_literals(literal_set& unfixed_lits, bool_var_set& unfixed_vars) {
        literal_vector to_delete;
        for (literal lit : unfixed_lits) {
            if (!unfixed_vars.contains(lit.var())) {
                to_delete.push_back(lit);
            }
        }
        for (unsigned i = 0; i < to_delete.size(); ++i) {
            unfixed_lits.remove(to_delete[i]);
        }
    }


    void solver::extract_fixed_consequences(unsigned& start, literal_set const& assumptions, bool_var_set& unfixed, vector<literal_vector>& conseq) {
        SASSERT(!inconsistent());
        unsigned sz = m_trail.size();
        for (unsigned i = start; i < sz && lvl(m_trail[i]) <= 1; ++i) {
            extract_fixed_consequences(m_trail[i], assumptions, unfixed, conseq);
        }
        start = sz;
    }

    void solver::extract_fixed_consequences(literal_set const& unfixed_lits, literal_set const& assumptions, bool_var_set& unfixed_vars, vector<literal_vector>& conseq) {
        for (literal lit: unfixed_lits) {
            TRACE("sat", tout << "extract: " << lit << " " << value(lit) << " " << lvl(lit) << "\n";);
            if (lvl(lit) <= 1 && value(lit) == l_true) {
                extract_fixed_consequences(lit, assumptions, unfixed_vars, conseq);
            }
        }
    }

    bool solver::check_domain(literal lit, literal lit2) {
        if (!m_antecedents.contains(lit2.var())) {
            SASSERT(value(lit2) == l_true);
            SASSERT(m_todo_antecedents.empty() || m_todo_antecedents.back() != lit2);
            m_todo_antecedents.push_back(lit2);
            return false;
        }
        else {
            return true;
        }
    }

    bool solver::extract_assumptions(literal lit, index_set& s) {
        justification js = m_justification[lit.var()];
        TRACE("sat", tout << lit << " " << js << "\n";);
        bool all_found = true;
        switch (js.get_kind()) {
        case justification::NONE:
            break;
        case justification::BINARY:
            if (!check_domain(lit, ~js.get_literal())) 
                return false;
            s |= m_antecedents.find(js.get_literal().var());
            break;
        case justification::CLAUSE: {
            clause & c = get_clause(js);
            for (literal l : c) {
                if (l != lit) {
                    if (check_domain(lit, ~l) && all_found) 
                        s |= m_antecedents.find(l.var());                    
                    else 
                        all_found = false;                    
                }
            }
            break;
        }
        case justification::EXT_JUSTIFICATION: {
            fill_ext_antecedents(lit, js, true);
            for (literal l : m_ext_antecedents) {
                if (check_domain(lit, l) && all_found) {
                    s |= m_antecedents.find(l.var());
                }
                else {
                    all_found = false;
                }
            }
            break;
        }
        default:
            UNREACHABLE();
            break;
        }
        TRACE("sat", display_index_set(tout << lit << ": " , s) << "\n";);
        return all_found;
    }

    std::ostream& solver::display_index_set(std::ostream& out, index_set const& s) const {
        for (unsigned idx : s) {
            out << to_literal(idx) << " ";
        }
        return out;
    }


    bool solver::extract_fixed_consequences1(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector<literal_vector>& conseq) {
        index_set s;
        if (m_antecedents.contains(lit.var())) 
            return true;
        
        if (assumptions.contains(lit)) 
            s.insert(lit.index());        
        else {
            if (!extract_assumptions(lit, s)) {
                SASSERT(!m_todo_antecedents.empty());
                return false;
            }
            add_assumption(lit);
        }
        m_antecedents.insert(lit.var(), s);
        if (unfixed.contains(lit.var())) {
            literal_vector cons;
            cons.push_back(lit);
            for (unsigned idx : s) {
                cons.push_back(to_literal(idx));
            }
            unfixed.remove(lit.var());
            conseq.push_back(cons);
        }
        return true;
    }

    void solver::extract_fixed_consequences(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector<literal_vector>& conseq) {
        SASSERT(m_todo_antecedents.empty());
        m_todo_antecedents.push_back(lit);
        while (!m_todo_antecedents.empty()) {
            auto lit = m_todo_antecedents.back();
            if (extract_fixed_consequences1(lit, assumptions, unfixed, conseq)) 
                m_todo_antecedents.pop_back();
        }
    }

    // -----------------------
    //
    // Statistics
    //
    // -----------------------

    void solver::display_status(std::ostream & out) const {
        unsigned num_bin = 0;
        unsigned num_ext = 0;
        unsigned num_lits = 0;
        unsigned l_idx = 0;
        for (watch_list const& wlist : m_watches) {
            literal l = ~to_literal(l_idx++);
            for (watched const& w : wlist) {
                switch (w.get_kind()) {
                case watched::BINARY:
                    if (l.index() < w.get_literal().index()) {
                        num_lits += 2;
                        num_bin++;
                    }
                    break;
                case watched::EXT_CONSTRAINT:
                    num_ext++;
                    break;
                default:
                    break;
                }
            }
        }
        unsigned num_elim = 0;
        for (bool_var v = 0; v < num_vars(); v++) {
            if (m_eliminated[v])
                num_elim++;
        }
        unsigned num_ter  = 0;
        unsigned num_cls  = 0;
        clause_vector const * vs[2] = { &m_clauses, &m_learned };
        for (unsigned i = 0; i < 2; i++) {
            clause_vector const & cs = *(vs[i]);
            for (clause* cp : cs) {
                clause & c = *cp;
                if (c.size() == 3)
                    num_ter++;
                else
                    num_cls++;
                num_lits += c.size();
            }
        }
        unsigned total_cls = num_cls + num_ter + num_bin + num_ext;
        double mem = static_cast<double>(memory::get_allocation_size())/static_cast<double>(1024*1024);
        out << "(sat-status\n";
        out << "  :inconsistent    " << (m_inconsistent ? "true" : "false") << "\n";
        out << "  :vars            " << num_vars() << "\n";
        out << "  :elim-vars       " << num_elim << "\n";
        out << "  :lits            " << num_lits << "\n";
        out << "  :assigned        " << m_trail.size() << "\n";
        out << "  :binary-clauses  " << num_bin << "\n";
        out << "  :ternary-clauses " << num_ter << "\n";
        out << "  :clauses         " << num_cls << "\n";
        out << "  :del-clause      " << m_stats.m_del_clause << "\n";
        out << "  :avg-clause-size " << (total_cls == 0 ? 0.0 : static_cast<double>(num_lits) / static_cast<double>(total_cls)) << "\n";
        out << "  :memory          " << std::fixed << std::setprecision(2) << mem << ")" << std::endl;
    }

    void stats::collect_statistics(statistics & st) const {
        st.update("sat mk clause 2ary", m_mk_bin_clause);
        st.update("sat mk clause 3ary", m_mk_ter_clause);
        st.update("sat mk clause nary", m_mk_clause);
        st.update("sat mk var", m_mk_var);
        st.update("sat gc clause", m_gc_clause);
        st.update("sat del clause", m_del_clause);
        st.update("sat conflicts", m_conflict);
        st.update("sat decisions", m_decision);
        st.update("sat propagations 2ary", m_bin_propagate);
        st.update("sat propagations 3ary", m_ter_propagate);
        st.update("sat propagations nary", m_propagate);
        st.update("sat restarts", m_restart);
        st.update("sat minimized lits", m_minimized_lits);
        st.update("sat subs resolution dyn", m_dyn_sub_res);
        st.update("sat blocked correction sets", m_blocked_corr_sets);
        st.update("sat units", m_units);
        st.update("sat elim bool vars res", m_elim_var_res);
        st.update("sat elim bool vars bdd", m_elim_var_bdd);
        st.update("sat backjumps", m_backjumps);
        st.update("sat backtracks", m_backtracks);
    }

    void stats::reset() {
        memset(this, 0, sizeof(*this));
    }

    void mk_stat::display(std::ostream & out) const {
        unsigned given, redundant;
        m_solver.num_binary(given, redundant);
        out << " " << std::setw(5) << m_solver.m_clauses.size() + given << "/" << given;
        out << " " << std::setw(5) << (m_solver.m_learned.size() + redundant - m_solver.m_num_frozen) << "/" << redundant;
        out << " " << std::setw(3)  << m_solver.init_trail_size();
        out << " " << std::setw(7)  << m_solver.m_stats.m_gc_clause << " ";
        out << " " << std::setw(7)  << mem_stat();
    }

    std::ostream & operator<<(std::ostream & out, mk_stat const & stat) {
        stat.display(out);
        return out;
    }

    bool solver::all_distinct(literal_vector const& lits) {
        init_visited();
        for (literal l : lits) {
            if (is_visited(l.var())) {
                return false;
            }
            mark_visited(l.var());
        }
        return true;
    }

    bool solver::all_distinct(clause const& c) {
        init_visited();
        for (literal l : c) {
            if (is_visited(l.var())) {
                return false;
            }
            mark_visited(l.var());
        }
        return true;
    }

};
