/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    smt_context.cpp

Abstract:

    <abstract>

Author:

    Leonardo de Moura (leonardo) 2008-02-19.

Revision History:

--*/
#include<math.h>
#include "util/luby.h"
#include "util/warning.h"
#include "util/timeit.h"
#include "util/union_find.h"
#include "ast/ast_pp.h"
#include "ast/ast_ll_pp.h"
#include "ast/ast_smt2_pp.h"
#include "ast/ast_translation.h"
#include "ast/recfun_decl_plugin.h"
#include "ast/proofs/proof_checker.h"
#include "ast/ast_util.h"
#include "ast/well_sorted.h"
#include "model/model_params.hpp"
#include "model/model.h"
#include "model/model_pp.h"
#include "smt/smt_context.h"
#include "smt/smt_quick_checker.h"
#include "smt/uses_theory.h"
#include "smt/theory_special_relations.h"
#include "smt/theory_polymorphism.h"
#include "smt/theory_sls.h"
#include "smt/smt_for_each_relevant_expr.h"
#include "smt/smt_model_generator.h"
#include "smt/smt_model_checker.h"
#include "smt/smt_model_finder.h"
#include "smt/smt_parallel.h"
#include "smt/smt_arith_value.h"
#include <iostream>

namespace smt {

    context::context(ast_manager & m, smt_params & p, params_ref const & _p):
        m(m),
        m_fparams(p),
        m_params(_p),
        m_setup(*this, p),
        m_relevancy_lvl(m_fparams.m_relevancy_lvl),
        m_asserted_formulas(m, p, _p),
        m_rewriter(m),
        m_qmanager(alloc(quantifier_manager, *this, p, _p)),
        m_model_generator(alloc(model_generator, m)),
        m_relevancy_propagator(mk_relevancy_propagator(*this)),
        m_user_propagator(nullptr),
        m_random(p.m_random_seed),
        m_flushing(false),
        m_lemma_id(0),
        m_progress_callback(nullptr),
        m_next_progress_sample(0),
        m_clause_proof(*this),
        m_fingerprints(m, m_region),
        m_b_internalized_stack(m),
        m_e_internalized_stack(m),
        m_l_internalized_stack(m),
        m_final_check_idx(0),
        m_cg_table(m),
        m_conflict(null_b_justification),
        m_not_l(null_literal),
        m_conflict_resolution(mk_conflict_resolution(m, *this, m_dyn_ack_manager, p, m_assigned_literals, m_watches)),
        m_unsat_proof(m),
        m_dyn_ack_manager(*this, p),
        m_unknown("unknown"),
        m_unsat_core(m),
        m_mk_bool_var_trail(*this),
        m_mk_enode_trail(*this),
        m_mk_lambda_trail(*this),
        m_lemma_visitor(m) {

        SASSERT(m_scope_lvl == 0);
        SASSERT(m_base_lvl == 0);
        SASSERT(m_search_lvl == 0);

        m_case_split_queue = mk_case_split_queue(*this, p);
        m_rewriter.updt_params(m_asserted_formulas.get_params());

        init();

        if (!relevancy())
            m_fparams.m_relevancy_lemma = false;

        m_model_generator->set_context(this);
    }

    /**
       \brief retrieve flag for when cancelation is possible.
    */

    bool context::get_cancel_flag() {
        if (l_true == m_internal_completed && !m.limit().suspended()) {
            m_last_search_failure = CANCELED;
            return true;
        }
        if (m.limit().inc())
            return false;
        m_last_search_failure = CANCELED;
        return true;
    }

    void context::updt_params(params_ref const& p) {
        m_params.append(p);
        m_asserted_formulas.updt_params(p);
        if (!m_setup.already_configured()) {
            m_fparams.updt_params(p);
        }
    }

    unsigned context::relevancy_lvl() const {
        return std::min(m_relevancy_lvl, m_fparams.m_relevancy_lvl);
    }

    void context::copy(context& src_ctx, context& dst_ctx, bool override_base) {
        ast_manager& dst_m = dst_ctx.get_manager();
        ast_manager& src_m = src_ctx.get_manager();
        src_ctx.pop_to_base_lvl();

        if (!override_base && src_ctx.m_base_lvl > 0) {
            throw default_exception("Cloning contexts within a user-scope is not allowed");
        }
        SASSERT(src_ctx.m_base_lvl == 0 || override_base);

        ast_translation tr(src_m, dst_m, false);

        dst_ctx.set_logic(src_ctx.m_setup.get_logic());
        dst_ctx.copy_plugins(src_ctx, dst_ctx);

        asserted_formulas& src_af = src_ctx.m_asserted_formulas;
        asserted_formulas& dst_af = dst_ctx.m_asserted_formulas;

        // Copy asserted formulas.
        for (unsigned i = 0; i < src_af.get_num_formulas(); ++i) {
            expr_ref fml(dst_m);
            proof_ref pr(dst_m);
            if (src_m.is_true(src_af.get_formula(i)))
               continue;
            proof* pr_src = src_af.get_formula_proof(i);
            fml = tr(src_af.get_formula(i));
            if (pr_src) {
                pr = tr(pr_src);
            }
            dst_af.assert_expr(fml, pr);
        }

        src_af.get_macro_manager().copy_to(dst_af.get_macro_manager());

        if (!src_ctx.m_setup.already_configured()) 
            return;

        for (unsigned i = 0; !src_m.proofs_enabled() && i < src_ctx.m_assigned_literals.size(); ++i) {
            literal lit = src_ctx.m_assigned_literals[i];
            bool_var_data const & d = src_ctx.get_bdata(lit.var());
            if (d.is_theory_atom() && !src_ctx.m_theories.get_plugin(d.get_theory())->is_safe_to_copy(lit.var())) {
                continue;
            }
            expr_ref fml0(src_m), fml1(dst_m);
            src_ctx.literal2expr(lit, fml0);
            if (src_m.is_true(fml0))
                continue;
            fml1 = tr(fml0.get());
            dst_ctx.assert_expr(fml1);
        }

        dst_ctx.setup_context(dst_ctx.m_fparams.m_auto_config);
        dst_ctx.internalize_assertions();
        
        dst_ctx.copy_user_propagator(src_ctx, true);

        TRACE(smt_context,
              src_ctx.display(tout);
              dst_ctx.display(tout););
    }

    context::~context() {
        flush();
        m_asserted_formulas.finalize();
    }

    void context::copy_plugins(context& src, context& dst) {
        // copy theory plugins
        for (theory* old_th : src.m_theory_set) {
            theory * new_th = old_th->mk_fresh(&dst);
            if (!new_th)
                throw default_exception("theory cannot be copied");
            dst.register_plugin(new_th);
        }
    }

    void context::copy_user_propagator(context& src_ctx, bool copy_registered) {
        if (!src_ctx.m_user_propagator) 
            return;
        auto* p = get_theory(m.mk_family_id("user_propagator"));
        m_user_propagator = reinterpret_cast<theory_user_propagator*>(p);
        SASSERT(m_user_propagator);
        if (!copy_registered) {
            return;
        }
        ast_translation tr(src_ctx.m, m, false);
        for (unsigned i = 0; i < src_ctx.m_user_propagator->get_num_vars(); ++i) {
            app* e = src_ctx.m_user_propagator->get_expr(i);
            m_user_propagator->add_expr(tr(e), true);
        }
    }

    context * context::mk_fresh(symbol const * l, smt_params * p, params_ref const& pa) {
        context * new_ctx = alloc(context, m, p ? *p : m_fparams, pa);
        new_ctx->m_is_auxiliary = true;
        new_ctx->set_logic(l == nullptr ? m_setup.get_logic() : *l);
        copy_plugins(*this, *new_ctx);
        new_ctx->copy_user_propagator(*this, false);
        return new_ctx;
    }

    void context::init() {
        app * t       = m.mk_true();
        mk_bool_var(t);
        SASSERT(get_bool_var(t) == true_bool_var);
        SASSERT(true_literal.var() == true_bool_var);
        m_assignment[true_literal.index()]     = l_true;
        m_assignment[false_literal.index()]    = l_false;
        if (m.proofs_enabled()) {
            proof * pr = m.mk_true_proof();

            set_justification(true_bool_var, m_bdata[true_bool_var], b_justification(mk_justification(justification_proof_wrapper(*this, pr))));
        }
        else {
            m_bdata[true_bool_var].set_axiom();
        }
        m_true_enode  = mk_enode(t, true, true, false);
        // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant.
        // m_true_enode->mark_as_interpreted();
        app * f       = m.mk_false();
        m_false_enode = mk_enode(f, true, true, false);
        // m_false_enode->mark_as_interpreted();
    }

    void context::set_progress_callback(progress_callback *cb) {
        m_progress_callback = cb;
    }

    /**
       \brief This method should be used to create equality atoms during the search.
       See comments in theory::mk_eq_atom
    */
    app * context::mk_eq_atom(expr * lhs, expr * rhs) {
        family_id fid = lhs->get_sort()->get_family_id();
        theory * th   = get_theory(fid);
        if (th)
            return th->mk_eq_atom(lhs, rhs);
        if (lhs->get_id() > rhs->get_id())
            std::swap(lhs, rhs);
        return m.mk_eq(lhs, rhs);
    }

    void context::set_justification(bool_var v, bool_var_data& d, b_justification const& j) {
        SASSERT(validate_justification(v, d, j));
        d.set_justification(j);
    }

    void context::assign_core(literal l, b_justification j, bool decision) {
        m_assigned_literals.push_back(l);
        m_assignment[l.index()]    = l_true;
        m_assignment[(~l).index()] = l_false;
        bool_var_data & d          = get_bdata(l.var());
        set_justification(l.var(), d, j);
        d.m_scope_lvl              = m_scope_lvl;
        if (m_fparams.m_restart_adaptive && d.m_phase_available) {
            m_agility             *= m_fparams.m_agility_factor;
            if (!decision && d.m_phase == l.sign())
                m_agility         += (1.0 - m_fparams.m_agility_factor);
        }
        d.m_phase_available        = true;
        d.m_phase                  = !l.sign();
        TRACE(assign_core, tout << (decision?"decision: ":"propagating: ") << l << " ";
              display_literal_smt2(tout, l) << "\n";
              tout << "relevant: " << is_relevant_core(l) << " level: " << m_scope_lvl << " is atom " << d.is_atom() << "\n";
              display(tout, j);
              );
        TRACE(phase_selection, tout << "saving phase, is_pos: " << d.m_phase << " l: " << l << "\n";);

        if (d.is_atom() && (relevancy_lvl() == 0 || (relevancy_lvl() == 1 && !d.is_quantifier()) || is_relevant_core(l))) {
            m_atom_propagation_queue.push_back(l);
        }

        if (m.has_trace_stream())
            trace_assign(l, j, decision);

        m_case_split_queue->assign_lit_eh(l);
    }

    bool context::bcp() {
        SASSERT(!inconsistent());
        while (m_qhead < m_assigned_literals.size()) {
            if (get_cancel_flag()) {
                return true;
            }
            literal l      = m_assigned_literals[m_qhead];
            SASSERT(get_assignment(l) == l_true);
            m_qhead++;
            m_simp_counter--;
            literal not_l  = ~l;
            SASSERT(get_assignment(not_l) == l_false);
            watch_list & w = m_watches[l.index()];
            if (binary_clause_opt_enabled()) {
                // binary clause propagation
                b_justification js(l);
                literal * it  = w.begin_literals();
                literal * end = w.end_literals();
                for(; it != end; ++it) {
                    literal l = *it;
                    switch (get_assignment(l)) {
                    case l_false:
                        m_stats.m_num_bin_propagations++;
                        set_conflict(js, ~l);
                        return false;
                    case l_undef:
                        m_stats.m_num_bin_propagations++;
                        assign_core(l, js);
                        break;
                    case l_true:
                        break;
                    }
                }
            }

            // non-binary clause propagation
            watch_list::clause_iterator it  = w.begin_clause();
            watch_list::clause_iterator it2 = it;
            watch_list::clause_iterator end = w.end_clause();
            for(; it != end; ++it) {
                clause * cls = *it;
                CTRACE(bcp_bug, cls->get_literal(0) != not_l && cls->get_literal(1) != not_l, display_clause_detail(tout, cls);
                       tout << "not_l: "; display_literal(tout, not_l); tout << " " << not_l << "\n";);
                SASSERT(cls->get_literal(0) == not_l || cls->get_literal(1) == not_l);
                if (cls->get_literal(0) == not_l) {
                    cls->set_literal(0, cls->get_literal(1));
                    cls->set_literal(1, not_l);
                }

                SASSERT(cls->get_literal(1) == not_l);

                literal first_lit     = cls->get_literal(0);
                lbool   first_lit_val = get_assignment(first_lit);

                if (first_lit_val == l_true) {
                    *it2 = *it; // clause is already satisfied, keep it
                    it2++;
                }
                else {
                    literal * it3  = cls->begin() + 2;
                    literal * end3 = cls->end();
                    for(; it3 != end3; ++it3) {
                        if (get_assignment(*it3) != l_false) {
                            // swap literal *it3 with literal at position 0
                            // the negation of literal *it3 will watch clause cls.
                            m_watches[(~(*it3)).index()].insert_clause(cls);
                            cls->set_literal(1, *it3);
                            *it3   = not_l;
                            goto found_watch;
                        }
                    }
                    // did not find watch...
                    if (first_lit_val == l_false) {
                        // CONFLICT
                        // copy remaining watches
                        while (it < end) {
                            *it2 = *it;
                            it2++;
                            it++;
                        }
                        SASSERT(it2 <= end);
                        w.set_end_clause(it2);
                        SASSERT(is_empty_clause(cls));
                        set_conflict(b_justification(cls));
                        return false;
                    }
                    else {
                        // PROPAGATION
                        SASSERT(first_lit_val == l_undef);
                        SASSERT(get_assignment(first_lit) == l_undef);
                        SASSERT(is_unit_clause(cls));
                        *it2 = *it;
                        it2++; // keep clause
                        m_stats.m_num_propagations++;
                        // It is safe to call assign_core instead of assign because first_lit is unassigned
                        assign_core(first_lit, b_justification(cls));
                        if (m_fparams.m_relevancy_lemma && cls->is_lemma()) {
                            expr * e = bool_var2expr(first_lit.var());
                            // IMPORTANT: this kind of propagation asserts negative literals of the form (= A1 A2) where
                            // A1 and A2 are array terms. So, it may be interesting to disable it for array eqs.
                            //if (!(m.is_eq(e) && m.get_sort(to_app(e)->get_arg(0))->get_family_id() == m.get_family_id("array")))
                            mark_as_relevant(e);
                        }
                    }
                found_watch:;
                }
            }
            SASSERT(it2 <= end);
            w.set_end_clause(it2);
        }
        return true;
    }

    /**
       \brief Push a new equality for theory th, into the theory equality propagation queue.
    */
    void context::push_new_th_eq(theory_id th, theory_var lhs, theory_var rhs) {
        SASSERT(lhs != rhs);
        SASSERT(lhs != null_theory_var);
        SASSERT(rhs != null_theory_var);
        SASSERT(th != null_theory_id);
        SASSERT(get_theory(th)->get_enode(lhs) != get_theory(th)->get_enode(rhs));
        m_th_eq_propagation_queue.push_back(new_th_eq(th, lhs, rhs));
    }

    /**
       \brief Push a new disequality for theory th, into the theory disequality propagation queue.
    */
    void context::push_new_th_diseq(theory_id th, theory_var lhs, theory_var rhs) {
        SASSERT(lhs != rhs);
        SASSERT(lhs != null_theory_var);
        SASSERT(rhs != null_theory_var);
        SASSERT(th != null_theory_id);
        theory * t = get_theory(th);
        if (t->get_enode(lhs)->is_interpreted() && t->get_enode(rhs)->is_interpreted())
            return;
        TRACE(add_diseq,
              tout << "#" << t->get_enode(lhs)->get_owner_id() << " != "
              << "#" << t->get_enode(rhs)->get_owner_id() << "\n";);

        m_th_diseq_propagation_queue.push_back(new_th_eq(th, lhs, rhs));
    }

    class add_eq_trail : public trail {
        context& ctx;
        enode * m_r1;
        enode * m_n1;
        unsigned m_r2_num_parents;
    public:
        add_eq_trail(context& ctx, enode * r1, enode * n1, unsigned r2_num_parents):
            ctx(ctx),
            m_r1(r1),
            m_n1(n1),
            m_r2_num_parents(r2_num_parents) {
        }

        void undo() override {
            ctx.undo_add_eq(m_r1, m_n1, m_r2_num_parents);
        }
    };

    /**
       \brief Add the equality n1 = n2 with justification js into the logical context.
    */
    void context::add_eq(enode * n1, enode * n2, eq_justification js) {
        unsigned old_trail_size = m_trail_stack.size();
        scoped_suspend_rlimit _suspend_cancel(m.limit());

        try {
            TRACE(add_eq, tout << "assigning: #" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n";);
            TRACE(add_eq_detail, tout << "assigning\n" << enode_pp(n1, *this) << "\n" << enode_pp(n2, *this) << "\n";
                  tout << "kind: " << js.get_kind() << "\n";);
            SASSERT(n1->get_sort() == n2->get_sort());

            m_stats.m_num_add_eq++;
            enode * r1 = n1->get_root();
            enode * r2 = n2->get_root();

            if (r1 == r2) {
                TRACE(add_eq, tout << "redundant constraint.\n";);
                return;
            }
            IF_VERBOSE(20, verbose_stream() << "merge " << mk_bounded_pp(n1->get_expr(), m) << " " << mk_bounded_pp(n2->get_expr(), m) << "\n");

            if (r1->is_interpreted() && r2->is_interpreted()) {
                TRACE(add_eq, tout << "interpreted roots conflict.\n";);
                set_conflict(mk_justification(eq_conflict_justification(n1, n2, js)));
                return;
            }

            // Swap r1 and r2:
            //  1. if the "equivalence" class of r1 is bigger than the equivalence class of r2
            //  OR
            //  2. r1 is interpreted but r2 is not.
            //
            // The second condition is used to enforce the invariant that if a class contain
            // an interpreted enode then the root is also interpreted.
            if ((r1->get_class_size() > r2->get_class_size() && !r2->is_interpreted()) || r1->is_interpreted()) {
                SASSERT(!r2->is_interpreted());
                std::swap(n1, n2);
                std::swap(r1, r2);
            }

            TRACE(add_eq, tout << "merging: #" << r1->get_owner_id() << " #" << r2->get_owner_id() <<
                  " n1: #" << n1->get_owner_id() << "\n";);

            // It is necessary to propagate relevancy to other elements of
            // the equivalence class. This is necessary to enforce the invariant
            // in the field m_parent of the enode class.
            if (is_relevant(r1)) { // && !m.is_eq(r1->get_owner())) !is_eq HACK
                // NOTE for !is_eq HACK... the !is_eq HACK does not propagate relevancy when two
                // equality enodes are congruent. I tested this optimization because in V1
                // relevancy is not propagated for congruent equalities.
                // This occurs in V2, because we use the congruence table to propagate disequalities
                // efficiently.
                // I disabled this optimization HACK because it breaks invariants in the rest of the code.
                // To use it, I need to go over the code and analyze all affected places.
                mark_as_relevant(r2);
            }
            else if (is_relevant(r2)) { // && !m.is_eq(r2->get_owner())) { // !is_eq HACK
                mark_as_relevant(r1);
            }

            push_trail(add_eq_trail(*this, r1, n1, r2->get_num_parents()));

            m_qmanager->add_eq_eh(r1, r2);


            merge_theory_vars(n2, n1, js);

            // 'Proof' tree
            // n1 -> ... -> r1
            // n2 -> ... -> r2
            SASSERT(n1->trans_reaches(r1));
            invert_trans(n1);
            n1->m_trans.m_target        = n2;
            n1->m_trans.m_justification = js;
            n1->m_proof_is_logged   = false;
            SASSERT(r1->trans_reaches(n1));
            // ---------------
            // r1 -> ..  -> n1 -> n2 -> ... -> r2

            remove_parents_from_cg_table(r1);

            enode * curr = r1;
            do {
                curr->m_root = r2;
                curr = curr->m_next;
            }
            while(curr != r1);

            SASSERT(r1->get_root() == r2);
            reinsert_parents_into_cg_table(r1, r2, n1, n2, js);

            if (n2->is_bool())
                propagate_bool_enode_assignment(r1, r2, n1, n2);

            // Merge "equivalence" classes
            std::swap(r1->m_next, r2->m_next);

            // Update "equivalence" class size
            r2->m_class_size += r1->m_class_size;
            r2->m_is_shared = 2;

            CASSERT("add_eq", check_invariant());
        }
        catch (...) {
            // Restore trail size since procedure was interrupted in the middle.
            // If the add_eq_trail remains on the trail stack, then Z3 may crash when the destructor is invoked.
            TRACE(add_eq, tout << "add_eq interrupted. This is unsafe " << m.limit().is_canceled() << "\n";);
            m_trail_stack.shrink(old_trail_size);
            throw;
        }
    }

    /**
       \brief When merging to equivalence classes, the parents of the smallest one (that are congruence roots),
       must be removed from the congruence table since their hash code will change.
    */
    void context::remove_parents_from_cg_table(enode * r1) {
        // Remove parents from the congruence table
        for (enode * parent : enode::parents(r1)) {
            CTRACE(add_eq, !parent->is_marked() && parent->is_cgc_enabled() && parent->is_true_eq() && m_cg_table.contains_ptr(parent), tout << parent->get_owner_id() << "\n";);
            CTRACE(add_eq, !parent->is_marked() && parent->is_cgc_enabled() && !parent->is_true_eq() &&  parent->is_cgr() && !m_cg_table.contains_ptr(parent), 
                   tout << "cgr !contains " << parent->get_owner_id() << " " << mk_pp(parent->get_decl(), m) << "\n";
                   for (enode* n : enode::args(parent))  tout << n->get_root()->get_owner_id() << " " << n->get_root()->hash() << " ";
                   tout << "\n";
                   tout << "contains: " << m_cg_table.contains(parent) << "\n";
                   if (m_cg_table.contains(parent)) {
                       tout << "owner: " << m_cg_table.find(parent)->get_owner_id() << "\n";
                   }
                   m_cg_table.display(tout);
                   );
            CTRACE(add_eq, !parent->is_marked() && parent->is_cgc_enabled() && !parent->is_true_eq() && !parent->is_cgr() &&  m_cg_table.contains_ptr(parent), tout << "!cgr contains " << parent->get_owner_id() << "\n";);
            SASSERT(parent->is_marked() || !parent->is_cgc_enabled() ||  parent->is_true_eq() || parent->is_cgr() == m_cg_table.contains_ptr(parent));
            SASSERT(parent->is_marked() || !parent->is_cgc_enabled() || !parent->is_true_eq() || !m_cg_table.contains_ptr(parent));
            if (!parent->is_marked() && parent->is_cgr() && !parent->is_true_eq()) {
                SASSERT(!parent->is_cgc_enabled() || m_cg_table.contains_ptr(parent));
                parent->set_mark();
                if (parent->is_cgc_enabled()) {
                    m_cg_table.erase(parent);
                    SASSERT(!m_cg_table.contains_ptr(parent));
                }
            }
        }
    }

    /**
       \brief Reinsert the parents of r1 that were removed from the
       cg_table at remove_parents_from_cg_table. Some of these parents will
       become congruent to other enodes, and a new equality will be propagated.
       Moreover, this method is also used for doing equality propagation.

       The parents of r1 that remain as congruence roots are copied to the
       r2->m_parents.

       The n1, n2, js arguments are used to implement dynamic ackermanization.
       js is a justification for n1 and n2 being equal, and the equality n1 = n2 is
       the one that implied r1 = r2.
    */
    void context::reinsert_parents_into_cg_table(enode * r1, enode * r2, enode * n1, enode * n2, eq_justification js) {
        enode_vector & r2_parents  = r2->m_parents;
        enode_vector & r1_parents  = r1->m_parents;
        unsigned num_r1_parents = r1_parents.size();
        for (unsigned i = 0; i < num_r1_parents; ++i) {
            enode* parent = r1_parents[i];
            if (!parent->is_marked())
                continue;
            parent->unset_mark();
            if (parent->is_eq()) {
                SASSERT(parent->get_num_args() == 2);
                TRACE(add_eq_bug, tout << "visiting: #" << parent->get_owner_id() << "\n";);
                enode * lhs = parent->get_arg(0);
                enode * rhs = parent->get_arg(1);
                if (lhs->get_root() == rhs->get_root()) {
                    SASSERT(parent->is_true_eq());
                    unsigned expr_id = parent->get_owner_id();
                    bool_var v = get_bool_var_of_id(expr_id);
                    lbool val  = get_assignment(v);
                    if (val != l_true) {
                        if (val == l_false && js.get_kind() == eq_justification::CONGRUENCE)
                            m_dyn_ack_manager.cg_conflict_eh(n1->get_expr(), n2->get_expr());
                        assign(literal(v), mk_justification(eq_propagation_justification(lhs, rhs)));
                    }
                    // It is not necessary to reinsert the equality to the congruence table
                    continue;
                }
            }
            if (parent->is_cgc_enabled()) {
                enode_bool_pair pair = m_cg_table.insert(parent);
                enode * parent_prime = pair.first;
                if (parent_prime == parent) {
                    SASSERT(parent);
                    SASSERT(parent->is_cgr());
                    SASSERT(m_cg_table.contains_ptr(parent));
                    r2_parents.push_back(parent);
                    continue;
                }
                parent->m_cg = parent_prime;
                SASSERT(!m_cg_table.contains_ptr(parent));
                if (parent_prime->m_root != parent->m_root) {
                    bool used_commutativity = pair.second;
                    TRACE(cg, tout << "found new congruence: #" << parent->get_owner_id() << " = #" << parent_prime->get_owner_id()
                          << " used_commutativity: " << used_commutativity << "\n";);
                    push_new_congruence(parent, parent_prime, used_commutativity);
                }
            }
            else {
                // If congruence closure is not enabled for parent, then I just copy it
                // to r2_parents
                r2_parents.push_back(parent);
            }
        }
    }

    /**
       \brief A transitivity 'proof' branch is represented by
       the following sequence starting at n and ending
       at n->get_root.

       N1      = n
       N_{i+1} = N_i->m_trans.m_target
       and, there is an k such that N_k = n->get_root()

       This method will invert this branch.
    */
    void context::invert_trans(enode * n) {
        enode * curr                      = n->m_trans.m_target;
        enode * prev                      = n;
        eq_justification js               = n->m_trans.m_justification;
        prev->m_trans.m_target            = nullptr;
        prev->m_trans.m_justification     = null_eq_justification;
        prev->m_proof_is_logged       = false;
        while (curr != nullptr) {
            SASSERT(prev->trans_reaches(n));
            enode * new_curr              = curr->m_trans.m_target;
            eq_justification new_js       = curr->m_trans.m_justification;
            curr->m_trans.m_target        = prev;
            curr->m_trans.m_justification = js;
            curr->m_proof_is_logged   = false;
            prev                          = curr;
            js                            = new_js;
            curr                          = new_curr;
        }
    }

    /**
       \brief Given that r is the root of n, and r contains a theory variable
       for theory th_id, this method returns a theory variable that is 'closer'
       to n in the 'proof branch' n -> ... -> r.

       This method is used to improve the quality of the conflict clauses produced
       by the logical context.

       Consider the following example:

       - Consider the following sequence of equalities:
       n1 = n2 = n3 = n4 = n5 = n6

       - Now, assume that n1 is the root of the equivalence class
       after each merge. So, the 'proof' branch will have the following
       shape:

       n1 <- n2 <- n3 <- n4 <- n5 <- n6

       - Assuming that all nodes are attached to theory variable, then the following
       sequence of equalities is sent to the theory if the method get_closest_var is not used:

       n1 = n2, n1 = n3, n1 = n4, n1 = n5, n1 = n6

       - This sequence is bad, and bad justifications may be produced by theory.
       For example, assume the following arithmetic constraint

       n5 < n6

       For the arithmetic module, the best justification will be:
       n1 = n5, n1 = n6 and n5 < n6

       This justification contains unnecessary 'junk' to justify that n5 = n6.
       That is, it proves n5 = n6 using the proofs for n1 = n5 and n1 = n6.

       When the method get_closest_var is used in the communication with theories,
       the logical context will send the natural sequence of equalities to the theories:

       n1 = n2 = n3 = n4 = n5 = n6
    */
    theory_var context::get_closest_var(enode * n, theory_id th_id) {
        if (th_id == null_theory_id)
            return null_theory_var;
        while (n != nullptr) {
            theory_var v = n->get_th_var(th_id);
            if (v != null_theory_var)
                return v;
            n = n->m_trans.m_target;
        }
        return null_theory_var;
    }

    /**
       \brief Merge the theory variables of n2->get_root() and n1->get_root(), the result is stored in n2->get_root().
       New th_var equalities are propagated to the theories.

       \remark In most cases, an enode is attached to at most one theory var.
    */
    void context::merge_theory_vars(enode * n2, enode * n1, eq_justification js) {
        enode * r2 = n2->get_root();
        enode * r1 = n1->get_root();
        if (!r1->has_th_vars() && !r2->has_th_vars()) {
            TRACE(merge_theory_vars, tout << "Neither have theory vars #" 
                  << mk_bounded_pp(n1->get_expr(), m) << " #" << mk_bounded_pp(n2->get_expr(), m) << "\n";);
            return;
        }

        theory_id from_th = null_theory_id;

        if (js.get_kind() == eq_justification::JUSTIFICATION)
            from_th = js.get_justification()->get_from_theory();

        if (r2->m_th_var_list.get_next() == nullptr && r1->m_th_var_list.get_next() == nullptr) {
            // Common case: r2 and r1 have at most one theory var.
            theory_id  t2 = r2->m_th_var_list.get_id();
            theory_id  t1 = r1->m_th_var_list.get_id();
            theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t2) : r2->m_th_var_list.get_var();
            theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : r1->m_th_var_list.get_var();
            TRACE(merge_theory_vars,
                  tout << "v2: " << v2 << " #" << r2->get_owner_id() << ", v1: " << v1 << " #" << r1->get_owner_id()
                  << ", t2: " << t2 << ", t1: " << t1 << "\n";);
            if (v2 != null_theory_var && v1 != null_theory_var) {
                if (t1 == t2) {
                    // only send the equality to the theory, if the equality was not propagated by it.
                    if (t1 != from_th)
                        push_new_th_eq(t1, v2, v1);
                }
                else {
                    // uncommon case: r2 will have two theory_vars attached to it.
                    r2->add_th_var(v1, t1, m_region);
                    push_new_th_diseqs(r2, v1, get_theory(t1));
                    push_new_th_diseqs(r1, v2, get_theory(t2));
                }
            }
            else if (v1 == null_theory_var && v2 != null_theory_var) {
                push_new_th_diseqs(r1, v2, get_theory(t2));
            }
            else if (v1 != null_theory_var && v2 == null_theory_var) {
                r2->m_th_var_list.set_var(v1);
                r2->m_th_var_list.set_id(t1);
                TRACE(merge_theory_vars, tout << "push_new_th_diseqs v1: " << v1 << ", t1: " << t1 << "\n";);
                push_new_th_diseqs(r2, v1, get_theory(t1));
            }
        }
        else {
            // r1 and/or r2 have more than one theory variable.
            TRACE(merge_theory_vars,
                  tout << "#" << r1->get_owner_id() << " == #" << r2->get_owner_id() << "\n";);


            theory_var_list * l2 = r2->get_th_var_list();
            while (l2) {
                theory_id  t2 = l2->get_id();
                theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t2) : l2->get_var();
                SASSERT(v2 != null_theory_var);
                SASSERT(t2 != null_theory_id);
                theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t2) : r1->get_th_var(t2);

                TRACE(merge_theory_vars, tout << get_theory(t2)->get_name() << ": " << v2 << " == " << v1 << "\n");

                if (v1 != null_theory_var) {
                    // only send the equality to the theory, if the equality was not propagated by it.
                    if (t2 != from_th)
                        push_new_th_eq(t2, v2, v1);
                }
                else {
                    push_new_th_diseqs(r1, v2, get_theory(t2));
                }
                l2 = l2->get_next();
            }

            theory_var_list * l1 = r1->get_th_var_list();
            while (l1) {
                theory_id  t1 = l1->get_id();
                theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : l1->get_var();
                SASSERT(v1 != null_theory_var);
                SASSERT(t1 != null_theory_id);
                theory_var v2 = r2->get_th_var(t1);
                TRACE(merge_theory_vars, tout << get_theory(t1)->get_name() << ": " << v2 << " == " << v1 << "\n");
                if (v2 == null_theory_var) {
                    r2->add_th_var(v1, t1, m_region);
                    push_new_th_diseqs(r2, v1, get_theory(t1));
                }
                l1 = l1->get_next();
            }
        }
    }

    /**
       \brief Propagate the boolean assignment when two equivalence classes are merged.
    */
    void context::propagate_bool_enode_assignment(enode * r1, enode * r2, enode * n1, enode * n2) {
        SASSERT(n1->is_bool());
        SASSERT(n2->is_bool());
        SASSERT(r1->is_bool());
        SASSERT(r2->is_bool());
        if (r2 == m_false_enode || r2 == m_true_enode) {
            bool sign = r2 == m_false_enode;
            enode * curr = r1;
            do {
                SASSERT(curr != m_false_enode);
                bool_var v = enode2bool_var(curr);
                literal l(v, sign);
                CTRACE(propagate, (get_assignment(l) != l_true), tout << enode_pp(curr, *this) << " " << l << "\n");
                if (get_assignment(l) != l_true)
                    assign(l, mk_justification(eq_root_propagation_justification(curr)));
                curr = curr->m_next;
            }
            while (curr != r1);
        }
        else {
            bool_var v1 = enode2bool_var(n1);
            bool_var v2 = enode2bool_var(n2);
            lbool val1  = get_assignment(v1);
            lbool val2  = get_assignment(v2);
            if (val1 != val2) {
                if (val2 == l_undef)
                    propagate_bool_enode_assignment_core(n1, n2);
                else
                    propagate_bool_enode_assignment_core(n2, n1);
            }
        }
    }

    /**
       \brief source and target are boolean enodes, they were proved to be equal,
       and the boolean variable associated with source is assigned. Then,
       copy the assignment to the boolean variables associated with target.
    */
    void context::propagate_bool_enode_assignment_core(enode * source, enode * target) {
        SASSERT(source->is_bool());
        SASSERT(target->is_bool());
        SASSERT(source->get_root() == target->get_root());
        bool_var v_source = enode2bool_var(source);
        lbool    val      = get_assignment(v_source);
        SASSERT(val != l_undef);
        bool     sign     = val == l_false;
        enode * first = target;
        do {
            bool_var v2 = enode2bool_var(target);
            lbool val2  = get_assignment(v2);
            if (val2 != val) {
                if (val2 != l_undef && congruent(source, target) && source->get_num_args() > 0)
                    m_dyn_ack_manager.cg_conflict_eh(source->get_expr(), target->get_expr());
                assign(literal(v2, sign), mk_justification(mp_iff_justification(source, target)));
            }
            target = target->get_next();
        }
        while (first != target);
    }

    void context::undo_add_eq(enode * r1, enode * n1, unsigned r2_num_parents) {
        enode * r2 = r1->get_root();
        TRACE(add_eq, tout << "undo_add_eq #" << r1->get_owner_id() << " #" << r2->get_owner_id() << "\n";);

        // restore r2 class size
        r2->m_class_size -= r1->m_class_size;
        r2->m_is_shared = 2;

        // unmerge "equivalence" classes
        std::swap(r1->m_next, r2->m_next);

        // remove the parents of r1 that remained as congruence roots
        enode_vector::iterator it  = r2->begin_parents();
        enode_vector::iterator end = r2->end_parents();
        it += r2_num_parents;
        for (; it != end; ++it) {
            enode * parent = *it;
            if (parent->is_cgc_enabled()) {
                CTRACE(add_eq, !parent->is_cgr() || !m_cg_table.contains_ptr(parent),
                       tout << "old num_parents: " << r2_num_parents 
                            << "\nnum_parents: " << r2->m_parents.size() 
                            << "\nparent: #" << parent->get_owner_id() 
                            << "\nparents: ";
                       for (enode* p : r2->m_parents) tout << "#" << p->get_owner_id() << " ";
                       display(tout << "\n"););
                SASSERT(parent->is_cgr());
                SASSERT(m_cg_table.contains_ptr(parent));
                m_cg_table.erase(parent);
            }
        }

        enode * curr = r1;
        do {
            curr->m_root = r1;
            curr = curr->m_next;
        }
        while (curr != r1);

        // restore parents of r2
        r2->m_parents.shrink(r2_num_parents);

        // try to reinsert parents of r1 that are not cgr
        for (enode * parent : enode::parents(r1)) {
            TRACE(add_eq_parents, tout << "visiting: #" << parent->get_owner_id() << "\n";);
            if (parent->is_cgc_enabled()) {
                
                enode * cg = parent->m_cg;
                if (!parent->is_true_eq() &&
                    (parent == cg ||           // parent was root of the congruence class before and after the merge
                     !congruent(parent, cg)    // parent was root of the congruence class before but not after the merge
                     )) {
                    enode_bool_pair p = m_cg_table.insert(parent);
                    parent->m_cg = p.first;
                }
            }
        }

        // restore theory vars
        if (r2->m_th_var_list.get_next() == nullptr) {
            // common case: r2 has at most one variable
            theory_var v2 = r2->m_th_var_list.get_var();
            if (v2 != null_theory_var) {
                theory_id  t2 = r2->m_th_var_list.get_id();
                if (get_theory(t2)->get_enode(v2)->get_root() != r2) {
                    SASSERT(get_theory(t2)->get_enode(v2)->get_root() == r1);
                    r2->m_th_var_list.set_var(null_theory_var); //remove variable from r2
                    r2->m_th_var_list.set_id(null_theory_id);
                }
            }
        }
        else {
            restore_theory_vars(r2, r1);
        }

        // 'Proof' tree
        // r1 -> ..  -> n1 -> n2 -> ... -> r2
        SASSERT(r1->trans_reaches(r2));
        SASSERT(r1->trans_reaches(n1));
        n1->m_trans.m_target        = nullptr;
        n1->m_trans.m_justification = null_eq_justification;
        n1->m_proof_is_logged   = false;
        invert_trans(r1);
        // ---------------
        // n1 -> ... -> r1
        // n2 -> ... -> r2
        SASSERT(n1->trans_reaches(r1));
        SASSERT(r1->m_trans.m_target == 0);

        CASSERT("add_eq", check_invariant());
    }

    /**
       \brief Auxiliary method for undo_add_eq.
       It restores the theory variables of a given root enode.
       This method deletes any theory variable v2 of r2 (for a theory t2)
       whenever:

       get_theory(t2)->get_enode(v2)->get_root() != r2

       That is, v2 is not equivalent to r2 anymore.
    */
    void context::restore_theory_vars(enode * r2, enode * r1) {
        SASSERT(r2->get_root() == r2);
        theory_var_list * new_l2  = nullptr;
        theory_var_list * l2      = r2->get_th_var_list();
        while (l2) {
            theory_var v2 = l2->get_var();
            theory_id t2  = l2->get_id();

            if (get_theory(t2)->get_enode(v2)->get_root() != r2) {
                SASSERT(get_theory(t2)->get_enode(v2)->get_root() == r1);
                l2 = l2->get_next();
            }
            else {
                if (new_l2) {
                    new_l2->set_next(l2);
                    new_l2 = l2;
                }
                else {
                    r2->m_th_var_list = *l2;
                    new_l2 = &(r2->m_th_var_list);
                }
                l2 = l2->get_next();
            }
        }

        if (new_l2) {
            new_l2->set_next(nullptr);
        }
        else {
            r2->m_th_var_list.set_var(null_theory_var);
            r2->m_th_var_list.set_next(nullptr);
        }
    }

    /**
       \brief This method is invoked when a new disequality is asserted.
       The disequality is propagated to the theories.
    */
    bool context::add_diseq(enode * n1, enode * n2) {
        enode * r1 = n1->get_root();
        enode * r2 = n2->get_root();
        TRACE(add_diseq, tout << "assigning: #" << n1->get_owner_id() << " != #" << n2->get_owner_id() << "\n";
              tout << mk_ll_pp(n1->get_expr(), m) << " != ";
              tout << mk_ll_pp(n2->get_expr(), m) << "\n";
              tout << mk_ll_pp(r1->get_expr(), m) << " != ";
              tout << mk_ll_pp(r2->get_expr(), m) << "\n";
              );

        DEBUG_CODE(
            push_trail(push_back_trail<enode_pair, false>(m_diseq_vector));
            m_diseq_vector.push_back(enode_pair(n1, n2)););

        if (r1 == r2) {
            TRACE(add_diseq_inconsistent, tout << "add_diseq #" << n1->get_owner_id() << " #" << n2->get_owner_id() << " inconsistency, scope_lvl: " << m_scope_lvl << "\n";);
            //return false;

            theory_id  t1 = r1->m_th_var_list.get_id();
            if (t1 == null_theory_id) return false;
            return get_theory(t1)->use_diseqs();
        }

        // Propagate disequalities to theories
        if (r1->m_th_var_list.get_next() == nullptr && r2->m_th_var_list.get_next() == nullptr) {
            // common case: r2 and r1 have at most one theory var.
            theory_id  t1 = r1->m_th_var_list.get_id();
            theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : r1->m_th_var_list.get_var();
            theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t1) : r2->m_th_var_list.get_var();
            TRACE(add_diseq, tout << "one theory diseq\n";
                  tout << v1 << " != " << v2 << "\n";
                  tout << "th1: " << t1 << " th2: " << r2->m_th_var_list.get_id() << "\n";
                  );
            if (t1 != null_theory_id && v1 != null_theory_var && v2 != null_theory_var &&
                t1 == r2->m_th_var_list.get_id()) {
                if (get_theory(t1)->use_diseqs())
                    push_new_th_diseq(t1, v1, v2);
            }
        }
        else {
            theory_var_list * l1 = r1->get_th_var_list();
            while (l1) {
                theory_id  t1 = l1->get_id();
                theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : l1->get_var();
                theory * th   = get_theory(t1);
                TRACE(add_diseq, tout << m.get_family_name(t1) << "\n";);
                if (th->use_diseqs()) {
                    theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t1) : r2->get_th_var(t1);
                    if (v2 != null_theory_var)
                        push_new_th_diseq(t1, v1, v2);
                }
                l1 = l1->get_next();
            }
        }
        return true;
    }

    /**
       \brief Return true if n1 and n2 are known to be disequal in the logical
       context.
    */
    bool context::is_diseq(enode * n1, enode * n2) const {
        SASSERT(n1->get_sort() == n2->get_sort());
        if (m.are_distinct(n1->get_root()->get_expr(), n2->get_root()->get_expr()))
            return true;
        context * _this = const_cast<context*>(this);
        if (!m_is_diseq_tmp) {
            app * eq       = m.mk_eq(n1->get_expr(), n2->get_expr());
            m.inc_ref(eq);
            _this->m_is_diseq_tmp = enode::mk_dummy(m, m_app2enode, eq);
        }
        else if (m_is_diseq_tmp->get_expr()->get_arg(0)->get_sort() != n1->get_sort()) {
            m.dec_ref(m_is_diseq_tmp->get_expr());
            app * eq = m.mk_eq(n1->get_expr(), n2->get_expr());
            m.inc_ref(eq);
            m_is_diseq_tmp->m_func_decl_id = UINT_MAX;
            m_is_diseq_tmp->m_owner = eq;
        }
        m_is_diseq_tmp->m_args[0] = n1;
        m_is_diseq_tmp->m_args[1] = n2;
        SASSERT(m_is_diseq_tmp->get_num_args() == 2);
        enode * r = m_cg_table.find(m_is_diseq_tmp);
        SASSERT((r != 0) == m_cg_table.contains(m_is_diseq_tmp));
        TRACE(is_diseq, tout << "r: " << r << "\n";);
        if (r) {
            SASSERT(r->is_eq());
            literal l = enode2literal(r->get_root());
            // SASSERT(result == is_diseq_slow(n1, n2));
            return l != true_literal && (l == false_literal || (is_relevant(l) && get_assignment(l) == l_false));
        }
        CTRACE(is_diseq_bug, is_diseq_slow(n1, n2), tout << "#" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n";);
        return false;
    }

    /**
       \brief Slow version of is_diseq
    */
    bool context::is_diseq_slow(enode * n1, enode * n2) const {
        if (n1->get_num_parents() > n2->get_num_parents())
            std::swap(n1, n2);
        for (enode * parent : enode::parents(n1)) {
            if (parent->is_eq() && is_relevant(parent->get_expr()) && get_assignment(enode2bool_var(parent)) == l_false &&
                ((parent->get_arg(0)->get_root() == n1->get_root() && parent->get_arg(1)->get_root() == n2->get_root()) ||
                 (parent->get_arg(1)->get_root() == n1->get_root() && parent->get_arg(0)->get_root() == n2->get_root()))) {
                TRACE(is_diseq_bug, tout << "parent: #" << parent->get_owner_id() << ", parent->root: #" <<
                      parent->get_root()->get_owner_id() << " assignment(parent): " << get_assignment(enode2bool_var(parent)) <<
                      " args: #" << parent->get_arg(0)->get_owner_id() << " #" << parent->get_arg(1)->get_owner_id() << "\n";);
                return true;
            }
        }
        return false;
    }

#define SMALL_NUM_PARENTS 3

    bool context::is_ext_diseq(enode * n1, enode * n2, unsigned depth) {
        enode * r1 = n1->get_root();
        enode * r2 = n2->get_root();
        if (r1 == r2)
            return false;
        if (r1->is_interpreted() && r2->is_interpreted())
            return true;
        if (is_diseq(n1, n2))
            return true;
        if (r1->get_num_parents() > r2->get_num_parents()) {
            std::swap(n1, n2);
            std::swap(r1, r2);
        }
        if (depth == 0)
            return false;
        if (r1->get_num_parents() < SMALL_NUM_PARENTS) {
            TRACE(is_ext_diseq, tout << enode_pp(n1, *this) << " " << enode_pp(n2, *this) << " " << depth << "\n";);
            for (enode * p1 : enode::parents(r1)) {
                if (!is_relevant(p1))
                    continue;
                if (p1->is_eq())
                    continue;
                if (!p1->is_cgr())
                    continue;
                func_decl * f     = p1->get_decl();
                TRACE(is_ext_diseq, tout << "p1: " << enode_pp(p1, *this) << "\n";);
                unsigned num_args = p1->get_num_args();
                for (enode * p2 : enode::parents(r2)) {
                    if (!is_relevant(p2))
                        continue;
                    if (p2->is_eq())
                        continue;
                    if (!p2->is_cgr())
                        continue;
                    TRACE(is_ext_diseq, tout << "p2: " << enode_pp(p2, *this) << "\n";);
                    if (p1->get_root() != p2->get_root() && p2->get_decl() == f && p2->get_num_args() == num_args) {
                        unsigned j = 0;
                        for (j = 0; j < num_args; j++) {
                            enode * arg1 = p1->get_arg(j)->get_root();
                            enode * arg2 = p2->get_arg(j)->get_root();
                            if (arg1 == arg2)
                                continue;
                            if ((arg1 == r1 || arg1 == r2) &&
                                (arg2 == r1 || arg2 == r2))
                                continue;
                            break;
                        }
                        if (j == num_args) {
                            TRACE(is_ext_diseq, tout << "found parents: " << enode_pp(p1, *this) << " " << enode_pp(p2, *this) << "\n";);
                            if (is_ext_diseq(p1, p2, depth - 1))
                                return true;
                        }
                    }
                }
            }
        }
        else {
            if (depth >= m_almost_cg_tables.size()) {
                unsigned old_sz = m_almost_cg_tables.size();
                m_almost_cg_tables.resize(depth+1);
                for (unsigned i = old_sz; i < depth + 1; i++)
                    m_almost_cg_tables[i] = alloc(almost_cg_table);
            }
            almost_cg_table & table = *(m_almost_cg_tables[depth]);
            table.reset(r1, r2);
            for (enode * p1 : enode::parents(r1)) {
                if (!is_relevant(p1))
                    continue;
                if (p1->is_eq())
                    continue;
                if (!p1->is_cgr())
                    continue;
                table.insert(p1);
            }
            if (table.empty())
                return false;
            for (enode * p2 : enode::parents(r2)) {
                if (!is_relevant(p2))
                    continue;
                if (p2->is_eq())
                    continue;
                if (!p2->is_cgr())
                    continue;
                list<enode*> * ps = table.find(p2);
                while (ps) {
                    enode * p1 = ps->head();
                    if (p1->get_root() != p2->get_root() && is_ext_diseq(p1, p2, depth - 1))
                        return true;
                    ps = ps->tail();
                }
            }
        }
        return false;
    }

    /**
       \brief Return an enode n congruent to (f args). If there is no such enode in the E-graph, then return 0.
     */
    enode * context::get_enode_eq_to(func_decl * f, unsigned num_args, enode * const * args) {
        enode * tmp = m_tmp_enode.set(f, num_args, args);
        enode * r   = m_cg_table.find(tmp);
#ifdef Z3DEBUG
        if (r != nullptr) {
            SASSERT(r->get_expr()->get_decl() == f);
            SASSERT(r->get_num_args() == num_args);
            if (r->is_commutative()) {
                // TODO
            }
            else {
                for (unsigned i = 0; i < num_args; i++) {
                    expr * arg   = r->get_expr()->get_arg(i);
                    SASSERT(e_internalized(arg));
                    enode * _arg = get_enode(arg);
                    CTRACE(eq_to_bug, args[i]->get_root() != _arg->get_root(),
                           tout << "#" << args[i]->get_expr_id() << " #" << args[i]->get_root()->get_expr_id()
                           << " #" << _arg->get_expr_id() << " #" << _arg->get_root()->get_expr_id() << "\n";
                           tout << "#" << r->get_expr_id() << "\n";
                           display(tout););
                    SASSERT(args[i]->get_root() == _arg->get_root());
                }
            }
        }
#endif
        return r;
    }

    /**
       \brief Process the equality propagation queue.

       \remark The method assign_eq adds a new entry on this queue.
    */
    bool context::propagate_eqs() {
        unsigned i = 0;
        for (; i < m_eq_propagation_queue.size() && !get_cancel_flag(); i++) {
            new_eq & entry = m_eq_propagation_queue[i];
            add_eq(entry.m_lhs, entry.m_rhs, entry.m_justification);
            if (inconsistent()) {
                m_eq_propagation_queue.reset();
                return false;
            }
        }
        m_eq_propagation_queue.reset();
        return true;
    }

    /**
       \brief Process equalities, theory atoms, etc.
    */
    bool context::propagate_atoms() {
        SASSERT(!inconsistent());
        CTRACE(propagate_atoms, !m_atom_propagation_queue.empty(), tout << m_atom_propagation_queue << "\n";);
        for (unsigned i = 0; i < m_atom_propagation_queue.size() && !get_cancel_flag(); i++) {
            SASSERT(!inconsistent());
            literal  l = m_atom_propagation_queue[i];
            bool_var v = l.var();
            lbool val  = get_assignment(v);
            TRACE(propagate_atoms, tout << "propagating atom, #" << bool_var2expr(v)->get_id() 
                  << ", is_enode(): " << get_bdata(v).is_enode() << " tag: " 
                  << (get_bdata(v).is_eq()?"eq":"") 
                  << (get_bdata(v).is_theory_atom()?"th":"") 
                  << (get_bdata(v).is_quantifier()?"q":"") << " " << l << "\n";);
            SASSERT(val != l_undef);
            if (get_bdata(v).is_enode())
                propagate_bool_var_enode(v);
            if (inconsistent())
                return false;
            bool_var_data & d = get_bdata(v);
            if (d.is_eq()) {
                app * n = to_app(m_bool_var2expr[v]);
                SASSERT(m.is_eq(n));
                expr * lhs = n->get_arg(0);
                expr * rhs = n->get_arg(1);
                if (m.is_bool(lhs)) {
                    // no-op
                }
                else if (val == l_true) {
                    add_eq(get_enode(lhs), get_enode(rhs), eq_justification(l));
                }
                else {
                    TRACE(add_diseq, display_eq_detail(tout, bool_var2enode(v)););
                    if (!add_diseq(get_enode(lhs), get_enode(rhs)) && !inconsistent()) {
                        literal n_eq = literal(l.var(), true);
                        set_conflict(b_justification(mk_justification(eq_propagation_justification(get_enode(lhs), get_enode(rhs)))), n_eq);
                    }
                }
            }
            else if (d.is_theory_atom()) {
                theory * th = m_theories.get_plugin(d.get_theory());
                SASSERT(th);
                th->assign_eh(v, val == l_true);                
            }
            else if (d.is_quantifier()) {
                // Remark: when RELEVANCY_LEMMA is true, a quantifier can be asserted to false and marked as relevant.
                // This happens when a quantifier is part of a lemma (conflict-clause), and this conflict clause
                // becomes an unit-clause and the remaining literal is the negation of a quantifier.
                CTRACE(assign_quantifier_bug, get_assignment(v) != l_true,
                       tout << "#" << bool_var2expr(v)->get_id() << " val: " << get_assignment(v) << "\n";
                       tout << mk_pp(bool_var2expr(v), m) << "\n";
                       display(tout););
                SASSERT(is_quantifier(m_bool_var2expr[v]));
                if (get_assignment(v) == l_true) {
                    // All universal quantifiers have positive polarity in the input formula.
                    // So, we can ignore quantifiers assigned to false.
                    assign_quantifier(to_quantifier(m_bool_var2expr[v]));
                }
            }
            if (inconsistent())
                return false;
        }
        m_atom_propagation_queue.reset();
        return true;
    }

    class set_var_theory_trail : public trail {
        context& ctx;
        bool_var m_var;
    public:
        set_var_theory_trail(context& ctx, bool_var v): ctx(ctx), m_var(v) {}
        void undo() override {
            bool_var_data & d = ctx.m_bdata[m_var];
            d.reset_notify_theory();
        }
    };

    void context::set_var_theory(bool_var v, theory_id tid) {
        SASSERT(get_var_theory(v) == null_theory_var);
        SASSERT(tid > 0 && tid <= 255);
        SASSERT(get_intern_level(v) <= m_scope_lvl);
        if (m_scope_lvl > get_intern_level(v))
            push_trail(set_var_theory_trail(*this, v));
        bool_var_data & d = m_bdata[v];
        d.set_notify_theory(tid);
    }

    /**
       \brief Propagate the truth value assigned to v, to the enode
       associated with v.  Let n be the enode associated with v. Then,
       this method merges n = true_term (n = false_term) if v was
       assigned to true (false).
    */
    void context::propagate_bool_var_enode(bool_var v) {
        SASSERT(get_assignment(v) != l_undef);
        SASSERT(get_bdata(v).is_enode());
        lbool val  = get_assignment(v);
        TRACE(propagate_bool_var_enode_bug, tout << "var: " << v << " #" << bool_var2expr(v)->get_id() << "\n";);
        SASSERT(v < m_b_internalized_stack.size());
        enode * n  = bool_var2enode(v);

        CTRACE(mk_bool_var, !n, tout << "No enode for " << v << "\n";);
        bool sign  = val == l_false;
        if (n->merge_tf())
            add_eq(n, sign ? m_false_enode : m_true_enode, eq_justification(literal(v, sign)));
        if (watches_fixed(n)) 
            assign_fixed(n, sign ? m.mk_false() : m.mk_true(), literal(v, sign));
        enode * r = n->get_root();
        if (r == m_true_enode || r == m_false_enode)
            return;
        // Move truth value to other elements of the equivalence class if:
        //   1) n is the root of the equivalence class
        //   2) n is not the root, but the variable associated with the root is unassigned.
        if (n == r ||
            !is_relevant(r) || // <<<< added to fix propagation bug.
            get_assignment(enode2bool_var(r)) != val) {
            enode * first = n;
            n = n->get_next();
            while (n != first) {
                bool_var v2 = enode2bool_var(n);
                if (get_assignment(v2) != val)
                    assign(literal(v2, sign), mk_justification(mp_iff_justification(first, n)));
                n = n->get_next();
            }
        }
    }

    /**
       \brief Traverse the disequalities of r's equivalence class, and
       propagate them to the theory.
    */
    void context::push_new_th_diseqs(enode * r, theory_var v, theory * th) {
        if (!th->use_diseqs()) {
            TRACE(push_new_th_diseqs, tout << m.get_family_name(th->get_id()) << " not using diseqs\n";);
            return;
        }
        theory_id th_id = th->get_id();
        TRACE(push_new_th_diseqs, tout << "#" << r->get_owner_id() << " " << mk_bounded_pp(r->get_expr(), m) << " v" << v << " th: " << th_id << "\n";);
        for (enode * parent : r->get_parents()) {
            CTRACE(parent_bug, parent == 0, tout << "#" << r->get_owner_id() << ", num_parents: " << r->get_num_parents() << "\n"; display(tout););
            if (parent->is_eq()) {
                bool_var bv = get_bool_var_of_id(parent->get_owner_id());
                if (get_assignment(bv) == l_false) {
                    enode * lhs = parent->get_arg(0);
                    enode * rhs = parent->get_arg(1);
                    TRACE(push_new_th_diseqs,
                          tout << "#" << parent->get_owner_id() << " ";
                           tout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() <<
                           ", lhs->root: #" << lhs->get_root()->get_owner_id() << ", rhs->root: #" << rhs->get_root()->get_owner_id() <<
                           ", r: #" << r->get_owner_id() << ", r->root: #" << r->get_root()->get_owner_id() << "\n";
                          );
                    CTRACE(push_new_th_diseqs, lhs->get_root() != r->get_root() && rhs->get_root() != r->get_root(),
                           tout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() <<
                           ", lhs->root: #" << lhs->get_root()->get_owner_id() << ", rhs->root: #" << rhs->get_root()->get_owner_id() <<
                           ", r: #" << r->get_owner_id() << ", r->root: #" << r->get_root()->get_owner_id() << "\n";
                          display(tout););
                    SASSERT(lhs->get_root() == r->get_root() || rhs->get_root() == r->get_root());
                    if (rhs->get_root() == r->get_root())
                        std::swap(lhs, rhs);
                    enode * rhs_root   = rhs->get_root();
                    theory_var rhs_var = m_fparams.m_new_core2th_eq ? get_closest_var(rhs, th_id) : rhs_root->get_th_var(th_id);
                    if (m_fparams.m_new_core2th_eq) {
                        theory_var _v = get_closest_var(lhs, th_id);
                        if (_v != null_theory_var)
                            v = _v;
                    }
                    if (rhs_var != null_theory_var && v != rhs_var /* if v == rhs_var then the context will detect the inconsistency. */)
                        push_new_th_diseq(th_id, v, rhs_var);
                }
            }
        }
    }

    /**
       \brief Return the truth assignment for an expression
       that is attached to a boolean variable.

       \pre The expression must be attached to a boolean variable.
    */
    inline lbool context::get_assignment_core(expr * n) const {
        CTRACE(get_assignment_bug, !b_internalized(n), tout << "n:\n" << mk_pp(n, m) << "\n"; display(tout););
        SASSERT(b_internalized(n));
        unsigned id  = n->get_id();
        bool_var var = get_bool_var_of_id(id);
        SASSERT(var != null_bool_var);
        return get_assignment(var);
    }

    /**
       \brief Return the truth assignment for an expression.
       If the expression is a not-application, then its child
       is inspected instead.
    */
    lbool context::get_assignment(expr * n) const {
        if (m.is_false(n))
            return l_false;
        expr* arg = nullptr;
        if (m.is_not(n, arg))
            return ~get_assignment_core(arg);
        return get_assignment_core(n);
    }

    lbool context::find_assignment(expr * n) const {
        if (m.is_false(n))
            return l_false;
        expr* arg = nullptr;
        if (m.is_not(n, arg)) {
            if (b_internalized(arg))
                return ~get_assignment_core(arg);
            return l_undef;
        }
        if (b_internalized(n))
            return get_assignment(n);
        return l_undef;
    }

    /**
       \brief Return the assignment of a 'boolean' enode.
       If the enode is not boolean, then return l_undef.
    */
    lbool context::get_assignment(enode * n) const {
        expr * owner = n->get_expr();
        if (!m.is_bool(owner))
            return l_undef;
        if (n == m_false_enode)
            return l_false;
        bool_var v = get_bool_var_of_id(owner->get_id());
        CTRACE(missing_propagation, v == null_bool_var, tout << mk_pp(owner, m) << "\n";);
        return get_assignment(v);
    }

    /**
       \brief Return set of assigned literals as expressions.
    */
    void context::get_assignments(expr_ref_vector& assignments) {
        for (literal lit : m_assigned_literals) {
            expr_ref e(m);
            literal2expr(lit, e);
            assignments.push_back(std::move(e));
        }
    }

    void context::get_specrels(func_decl_set& rels) const {
        family_id fid = m.get_family_id("specrels");
        theory* th = get_theory(fid);
        if (th)
            static_cast<theory_special_relations*>(th)->get_specrels(rels);
    }


    void context::relevant_eh(expr * n) {
        if (b_internalized(n)) {
            bool_var v        = get_bool_var(n);
            bool_var_data & d = get_bdata(v);
            SASSERT(relevancy());
            // Quantifiers are only asserted when marked as relevant.
            // Other atoms are only asserted when marked as relevant if relevancy_lvl >= 2
            if (d.is_atom() && (d.is_quantifier() || relevancy_lvl() >= 2)) {
                lbool val  = get_assignment(v);
                if (val != l_undef)
                    m_atom_propagation_queue.push_back(literal(v, val == l_false));
            }
        }
        TRACE(propagate_relevancy, tout << "marking as relevant:\n" << mk_bounded_pp(n, m) << " " << m_scope_lvl << "\n";);
        m_case_split_queue->relevant_eh(n);

        if (is_app(n)) {
            if (e_internalized(n)) {
                SASSERT(relevancy());
                enode * e = get_enode(n);
                m_qmanager->relevant_eh(e);
            }

            theory * propagated_th = nullptr;
            family_id fid = to_app(n)->get_family_id();
            if (fid != m.get_basic_family_id()) {
                theory * th = get_theory(fid);
                if (th != nullptr) {
                    th->relevant_eh(to_app(n));
                    propagated_th = th; // <<< mark that relevancy_eh was already invoked for theory th.
                }
            }

            if (e_internalized(n)) {
                enode *           e = get_enode(n);
                theory_var_list * l = e->get_th_var_list();
                while (l) {
                    theory_id  th_id = l->get_id();
                    theory *   th    = get_theory(th_id);
                    // I don't want to invoke relevant_eh twice for the same n.
                    if (th != propagated_th)
                        th->relevant_eh(to_app(n));
                    l = l->get_next();
                }
            }
        }
    }

    /**
       \brief Propagate relevancy using the queue of new assigned literals
       located at [qhead, m_assigned_literals.size()).
    */
    void context::propagate_relevancy(unsigned qhead) {
        if (!relevancy())
            return;
        unsigned sz = m_assigned_literals.size();
        while (qhead < sz) {
            literal l      = m_assigned_literals[qhead];
            SASSERT(get_assignment(l) == l_true);
            qhead++;
            bool_var var   = l.var();
            expr * n       = m_bool_var2expr[var];
            m_relevancy_propagator->assign_eh(n, !l.sign());
        }
        m_relevancy_propagator->propagate();
    }

    bool context::propagate_theories() {
        for (theory * t : m_theory_set) {
            t->propagate();
            if (inconsistent())
                return false;
        }
#if 0
        if (at_search_level() && induction::should_try(*this)) {
            get_induction()();
        }
#endif
        return true;
    }

    void context::propagate_th_eqs() {
        for (unsigned i = 0; i < m_th_eq_propagation_queue.size() && !inconsistent(); i++) {
            new_th_eq curr = m_th_eq_propagation_queue[i];
            theory * th = get_theory(curr.m_th_id);
            SASSERT(th);
            th->new_eq_eh(curr.m_lhs, curr.m_rhs);
            DEBUG_CODE(
                push_trail(push_back_trail<new_th_eq, false>(m_propagated_th_eqs));
                m_propagated_th_eqs.push_back(curr););
        }
        m_th_eq_propagation_queue.reset();
    }

    void context::propagate_th_diseqs() {
        for (unsigned i = 0; i < m_th_diseq_propagation_queue.size() && !inconsistent(); i++) {
            new_th_eq curr = m_th_diseq_propagation_queue[i];
            theory * th = get_theory(curr.m_th_id);
            SASSERT(th);
            th->new_diseq_eh(curr.m_lhs, curr.m_rhs);
            DEBUG_CODE(
                push_trail(push_back_trail<new_th_eq, false>(m_propagated_th_diseqs));
                m_propagated_th_diseqs.push_back(curr););
        }
        m_th_diseq_propagation_queue.reset();
    }

    bool context::can_theories_propagate() const {
        return any_of(m_theory_set, [&](theory* t) { return t->can_propagate(); });
    }

    bool context::can_propagate() const {
        return
            m_qhead != m_assigned_literals.size() ||
            m_relevancy_propagator->can_propagate() ||
            !m_atom_propagation_queue.empty() ||
            m_qmanager->can_propagate() ||
            can_theories_propagate() ||
            !m_eq_propagation_queue.empty() ||
            !m_th_eq_propagation_queue.empty() ||
            !m_th_diseq_propagation_queue.empty();
    }

    /**
       \brief unit propagation.
       Cancelation is not safe during propagation at base level because
       congruences cannot be retracted to a consistent state.
     */
    bool context::propagate() {
        TRACE(propagate, tout << "propagating... " << m_qhead << ":" << m_assigned_literals.size() << "\n");
        
        while (true) {
            if (inconsistent())
                return false;
            unsigned qhead = m_qhead;
            {
                scoped_suspend_rlimit _suspend_cancel(m.limit(), at_base_level());
                if (!bcp())
                    return false;
                if (!propagate_th_case_split(qhead))
                    return false;
                SASSERT(!inconsistent());
                propagate_relevancy(qhead);
                if (inconsistent())
                    return false;
                if (!propagate_atoms())
                    return false;
                if (!propagate_eqs())
                    return false;
                propagate_th_eqs();
                propagate_th_diseqs();
                if (inconsistent())
                    return false;
                if (!propagate_theories())
                    return false;
            }
            if (!get_cancel_flag()) {
//                scoped_suspend_rlimit _suspend_cancel(m.limit(), at_base_level());
                m_qmanager->propagate();
            }
            if (inconsistent())
                return false;
            if (resource_limits_exceeded()) {
                m_qhead = qhead;
                return true;
            }
            if (!can_propagate()) {
                CASSERT("diseq_bug", inconsistent() || check_missing_diseq_conflict());
                CASSERT("eqc_bool", check_eqc_bool_assignment());
                return true;
            }
        }
    }

    void context::set_conflict(const b_justification & js, literal not_l) {
        if (!inconsistent()) {
            TRACE(set_conflict, display_literal_verbose(tout << m_scope_lvl << " ", not_l); display(tout << " ", js); );
            m_conflict = js;
            m_not_l    = not_l;
        }
    }

    void context::assign_quantifier(quantifier * q) {
        TRACE(assumption, tout << mk_pp(q, m) << "\n";);
        m_qmanager->assign_eh(q);
    }

    bool context::contains_instance(quantifier * q, unsigned num_bindings, enode * const * bindings) {
        return m_fingerprints.contains(q, q->get_id(), num_bindings, bindings);
    }

    bool context::add_instance(quantifier * q, app * pat, unsigned num_bindings, enode * const * bindings, expr* def, unsigned max_generation,
                               unsigned min_top_generation, unsigned max_top_generation, vector<std::tuple<enode *, enode *>> & used_enodes) {
        return m_qmanager->add_instance(q, pat, num_bindings, bindings, def, max_generation, min_top_generation, max_top_generation, used_enodes);
    }

    void context::rescale_bool_var_activity() {
        TRACE(case_split, tout << "rescale\n";);        
        svector<double>::iterator it  = m_activity.begin();
        svector<double>::iterator end = m_activity.end();
        for (; it != end; ++it)
            *it *= INV_ACTIVITY_LIMIT;
        m_bvar_inc *= INV_ACTIVITY_LIMIT;
    }

    /**
       \brief Returns a truth value for the given variable
    */
    bool context::guess(bool_var var, lbool phase) {
        if (is_quantifier(m_bool_var2expr[var])) {
            // Overriding any decision on how to assign the quantifier.
            // assigning a quantifier to false is equivalent to make it irrelevant.
            phase = l_false;
        }
        literal l(var, false);

        if (phase != l_undef)
            return phase == l_true;

        bool_var_data & d = m_bdata[var];
        if (d.try_true_first())
            return true;
        switch (m_fparams.m_phase_selection) {
        case PS_THEORY:
            if (m_phase_cache_on && d.m_phase_available) {
                return m_bdata[var].m_phase;
            }
            if (!m_phase_cache_on && d.is_theory_atom()) {
                theory * th = m_theories.get_plugin(d.get_theory());
                lbool th_phase = th->get_phase(var);
                if (th_phase != l_undef) {
                    return th_phase == l_true;
                }
            }
            if (track_occs()) {
                if (m_lit_occs[l.index()] == 0) {
                    return false;
                }
                if (m_lit_occs[(~l).index()] == 0) {
                    return true;
                }
            }
            return m_phase_default;
        case PS_CACHING:
        case PS_CACHING_CONSERVATIVE:
        case PS_CACHING_CONSERVATIVE2:
            if (m_phase_cache_on && d.m_phase_available) {
                TRACE(phase_selection, tout << "using cached value, is_pos: " << m_bdata[var].m_phase << ", var: p" << var << "\n";);
                return m_bdata[var].m_phase;
            }
            else {
                TRACE(phase_selection, tout << "setting to false\n";);
                return m_phase_default;
            }
        case PS_ALWAYS_FALSE:
            return false;
        case PS_ALWAYS_TRUE:
            return true;
        case PS_RANDOM:
            return m_random() % 2 == 0;
        case PS_OCCURRENCE: {
            return m_lit_occs[l.index()] > m_lit_occs[(~l).index()];
        }
        default:
            UNREACHABLE();
            return false;
        }
    }

    /**
       \brief Execute next case split, return false if there are no
       more case splits to be performed.
    */
    bool context::decide() {

        if (at_search_level() && !m_tmp_clauses.empty()) {
            switch (decide_clause()) {
            case l_true:  // already satisfied
                break;
            case l_undef: // made a decision
                return true;
            case l_false: // inconsistent
                return false;
            }
        }
        bool_var var;
        bool is_pos;
        bool used_queue = false;
        
        if (!has_split_candidate(var, is_pos)) {
            lbool phase = l_undef;
            m_case_split_queue->next_case_split(var, phase);
            used_queue = true;
            if (var == null_bool_var) {
                push_trail(value_trail(m_has_case_split, false));
                return false;
            }

            TRACE_CODE({
                static unsigned counter = 0;
                counter++;
                if (counter % 100 == 0) {
                    TRACE(activity_profile,
                          for (unsigned i=0; i<get_num_bool_vars(); i++) {
                              tout << get_activity(i) << " ";
                          }
                          tout << "\n";);
                }});
        
            is_pos = guess(var, phase);
        }

        m_stats.m_num_decisions++;

        push_scope();
        TRACE(decide, tout << "splitting, lvl: " << m_scope_lvl << "\n";);

        TRACE(decide_detail, tout << mk_pp(bool_var2expr(var), m) << "\n";);
        
        literal l(var, false);

        bool_var original_choice = var;

        if (decide_user_interference(var, is_pos)) {
            if (used_queue)
                m_case_split_queue->unassign_var_eh(original_choice);
            l = literal(var, false);
        }

        if (!is_pos) l.neg();
        TRACE(decide, tout << "case split " << l << "\n" << "activity: " << get_activity(var) << "\n";);
        assign(l, b_justification::mk_axiom(), true);
        return true;
    }
    
    /**
       \brief Update counter that is used to enable/disable phase caching.
    */
    void context::update_phase_cache_counter() {
        m_phase_counter++;
        if (m_phase_cache_on) {
            if (m_phase_counter >= m_fparams.m_phase_caching_on) {
                m_phase_counter  = 0;
                m_phase_cache_on = false;
                if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2)
                    m_phase_default = !m_phase_default;
            }
        }
        else {
            if (m_phase_counter >= m_fparams.m_phase_caching_off) {
                m_phase_counter  = 0;
                m_phase_cache_on = true;
                if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2)
                    m_phase_default = !m_phase_default;
            }
        }
    }

    /**
       \brief Create an internal backtracking point
    */
    void context::push_scope() {

        if (m.has_trace_stream() && !m_is_auxiliary)
            m.trace_stream() << "[push] " << m_scope_lvl << "\n";

        m_scope_lvl++;
        m_region.push_scope();
        m_scopes.push_back(scope());
        scope & s = m_scopes.back();
        // TRACE(context, tout << "push " << m_scope_lvl << "\n";);

        m_relevancy_propagator->push();
        s.m_assigned_literals_lim    = m_assigned_literals.size();
        s.m_trail_stack_lim          = m_trail_stack.size();
        s.m_aux_clauses_lim          = m_aux_clauses.size();
        s.m_justifications_lim       = m_justifications.size();
        s.m_units_to_reassert_lim    = m_units_to_reassert.size();

        m_qmanager->push();

        m_fingerprints.push_scope();
        m_case_split_queue->push_scope();
        m_asserted_formulas.push_scope();

        for (theory* t : m_theory_set) 
            t->push_scope_eh();
        CASSERT("context", check_invariant());
    }

    /**
       \brief Execute generic undo-objects.
    */
    void context::undo_trail_stack(unsigned old_size) {
        ::undo_trail_stack(m_trail_stack, old_size);
    }

    /**
       \brief Remove watch literal idx from the given clause.

       \pre idx must be 0 or 1.
    */
    void context::remove_watch_literal(clause * cls, unsigned idx) {
        m_watches[(~cls->get_literal(idx)).index()].remove_clause(cls);
    }

    /**
       \brief Remove boolean variable from watch lists.
    */
    void context::remove_watch(bool_var v) {
        literal lit(v);
        m_watches[lit.index()].reset();
        m_watches[(~lit).index()].reset();
    }

    /**
       \brief Remove clause
    */

    void context::remove_cls_occs(clause * cls) {
        remove_watch_literal(cls, 0);
        remove_watch_literal(cls, 1);
        remove_lit_occs(*cls, get_num_bool_vars());
    }

    /**
       \brief Update occurrence count of literals
    */

    void context::add_lit_occs(clause const& cls) {
        if (!track_occs()) return;
        for (literal l : cls) {
            inc_ref(l);
        }
    }

    void context::remove_lit_occs(clause const& cls, unsigned nbv) {
        if (!track_occs()) return;
        for (literal l : cls) {
            if (l.var() < nbv) 
                dec_ref(l);
        }
    }

    // TBD: enable as assertion when ready to re-check
    void context::dec_ref(literal l) { if (track_occs() && m_lit_occs[l.index()] > 0) m_lit_occs[l.index()]--; }

    void context::inc_ref(literal l) { if (track_occs()) m_lit_occs[l.index()]++; }

    /**
       \brief Delete the given clause.

       \pre Clause is not in the reinit stack.
    */
    void context::del_clause(bool log, clause * cls) {
        SASSERT(m_flushing || !cls->in_reinit_stack());
        if (log) 
            m_clause_proof.del(*cls);
        CTRACE(context, !m_flushing, display_clause_smt2(tout << "deleting ", *cls) << "\n";);
        if (!cls->deleted())
            remove_cls_occs(cls);
        cls->deallocate(m);
        m_stats.m_num_del_clause++;
    }

    /**
       \brief Delete the clauses in v at locations [old_size .. v.size())
       Reduce the size of v to old_size.
    */
    void context::del_clauses(clause_vector & v, unsigned old_size) {
        unsigned num_collect = v.size() - old_size;
        if (num_collect == 0)
            return;
        
        clause_vector::iterator begin = v.begin() + old_size;
        clause_vector::iterator it    = v.end();
        if (num_collect > 1000) {
            uint_set watches;
            while (it != begin) {
                --it;
                clause* c = *it;
                remove_lit_occs(*c, get_num_bool_vars());
                if (!c->deleted()) {
                    c->mark_as_deleted(m);                    
                }
                watches.insert((~c->get_literal(0)).index());
                watches.insert((~c->get_literal(1)).index());
            }
            for (auto w: watches) {
                m_watches[w].remove_deleted();
            }
            for (it = v.end(); it != begin; ) {
                --it;
                (*it)->deallocate(m);
            }
            m_stats.m_num_del_clause += (v.size() - old_size);
        }
        else {
            while (it != begin) {
                --it;
                del_clause(false, *it);
            }
        }
        v.shrink(old_size);
    }


    /**
       \brief Undo variable assignments.
    */
    void context::unassign_vars(unsigned old_lim) {
        SASSERT(old_lim <= m_assigned_literals.size());

        unsigned i = m_assigned_literals.size();
        while (i != old_lim) {
            --i;
            literal l                  = m_assigned_literals[i];
            CTRACE(assign_core, l.var() == 13, tout << "unassign " << l << "\n";);
            m_assignment[l.index()]    = l_undef;
            m_assignment[(~l).index()] = l_undef;
            bool_var v                 = l.var();
            bool_var_data & d          = get_bdata(v);
            d.set_null_justification();
            m_case_split_queue->unassign_var_eh(v);
        }

        m_assigned_literals.shrink(old_lim);
        m_qhead = old_lim;
        SASSERT(m_qhead == m_assigned_literals.size());
    }

    /**
       \brief Invoke method del_eh for the justification that will be deleted.
       If the method in_region() returns false, then delete operator is invoked.
    */
    void context::del_justifications(ptr_vector<justification> & justifications, unsigned old_lim) {
        SASSERT(old_lim <= justifications.size());
        unsigned i = justifications.size();
        while (i != old_lim) {
            --i;
            justification * js = justifications[i];
            js->del_eh(m);
            if (!js->in_region()) {
                dealloc(js);
            }
            else {
                // If the justification is in a region, then explicitly invoke the destructor.
                // This is needed because some justification objects contains vectors.
                // The destructors of these vectors need to be invoked.
                js->~justification();
            }
        }
        justifications.shrink(old_lim);
    }

    /**
       \brief Return true if all literals of c are assigned to false.
    */
    bool context::is_empty_clause(clause const * c) const {
        unsigned num_lits = c->get_num_literals();
        for(unsigned i = 0; i < num_lits; i++) {
            literal l = c->get_literal(i);
            if (get_assignment(l) != l_false)
                return false;
        }
        return true;
    }

    /**
       \brief Return true if the given clause contains one and only one unassigned literal.
    */
    bool context::is_unit_clause(clause const * c) const {
        bool found        = false;
        unsigned num_lits = c->get_num_literals();
        for(unsigned i = 0; i < num_lits; i++) {
            literal l = c->get_literal(i);
            switch (get_assignment(l)) {
            case l_false:
                break; // skip
            case l_undef:
                if (found)
                    return false;
                else
                    found = true;
                break;
            case l_true:
                return false; // clause is already satisfied.
            }
        }
        return found;
    }

    /**
       \brief When a clause is reinitialized (see reinit_clauses) enodes and literals may
       need to be recreated. When an enode is recreated, I want to use the same generation
       number it had before being deleted. Otherwise the generation will be 0, and will affect
       the loop prevention heuristics used to control quantifier instantiation.
       Thus, I cache the generation number of enodes that will be deleted during backtracking
       and recreated by reinit_clauses.
    */
    void context::cache_generation(unsigned new_scope_lvl) {
        if (!m_clauses_to_reinit.empty()) {
            unsigned lim = m_scope_lvl;
            if (m_clauses_to_reinit.size() <= lim) {
                SASSERT(!m_clauses_to_reinit.empty());
                lim      = m_clauses_to_reinit.size() - 1;
            }
            for (unsigned i = new_scope_lvl; i <= lim; i++) {
                clause_vector & v = m_clauses_to_reinit[i];
                for (clause* cls : v) {
                    cache_generation(cls, new_scope_lvl);
                }
            }
        }
        if (!m_units_to_reassert.empty()) {
            scope & s   = m_scopes[new_scope_lvl];
            unsigned i  = s.m_units_to_reassert_lim;
            unsigned sz = m_units_to_reassert.size();
            for (; i < sz; i++) {
                expr* unit = m_units_to_reassert[i].m_unit.get();
                cache_generation(unit, new_scope_lvl);
            }
        }
    }

    /**
       \brief See cache_generation(unsigned new_scope_lvl)
    */
    void context::cache_generation(clause const * cls, unsigned new_scope_lvl) {
        cache_generation(cls->get_num_literals(), cls->begin(), new_scope_lvl);
    }

    /**
       \brief See cache_generation(unsigned new_scope_lvl)
    */
    void context::cache_generation(unsigned num_lits, literal const * lits, unsigned new_scope_lvl) {
        for(unsigned i = 0; i < num_lits; i++) {
            bool_var v          = lits[i].var();
            unsigned ilvl       = get_intern_level(v);
            if (ilvl > new_scope_lvl)
                cache_generation(bool_var2expr(v), new_scope_lvl);
        }
    }

    /**
       \brief See cache_generation(unsigned new_scope_lvl)
    */
    void context::cache_generation(expr * n, unsigned new_scope_lvl) {
        ptr_buffer<expr> todo;
        todo.push_back(n);
        while (!todo.empty()) {
            expr * n = todo.back();
            todo.pop_back();
            if (m_cache_generation_visited.contains(n))
                continue;
            m_cache_generation_visited.insert(n);
            if (is_app(n)) {
                if (e_internalized(n)) {
                    enode * e     = get_enode(n);
                    unsigned ilvl = e->get_iscope_lvl();
                    if (ilvl <= new_scope_lvl)
                        continue; // node and its children will not be recreated during backtracking
                    TRACE(cached_generation, tout << "caching: #" << n->get_id() << " " << e->get_generation() << "\n";);
                    m_cached_generation.insert(n, e->get_generation());
                }
                for (expr * arg : *to_app(n)) {
                    if (is_app(arg) || is_quantifier(arg))
                        todo.push_back(arg);
                }
            }
            else if (is_quantifier(n) && b_internalized(n)) {
                m_cached_generation.insert(n, m_qmanager->get_generation(to_quantifier(n)));
                todo.push_back(to_quantifier(n)->get_expr());
            }
        }
    }

    /**
       \brief See cache_generation(unsigned new_scope_lvl)
    */
    void context::reset_cache_generation() {
        m_cache_generation_visited.reset();
        m_cached_generation.reset();
    }

    /**
       \brief Reinitialize learned clauses (lemmas) that contain boolean variables
       that were deleted during backtracking.

       \remark num_bool_vars contains the number of boolean variables alive
       after backtracking. So, a clause contains a dead variable if it
       contains a literal l where l.var() >= num_bool_vars.
    */
    void context::reinit_clauses(unsigned num_scopes, unsigned num_bool_vars) {
        if (m_clauses_to_reinit.empty())
            return;
        unsigned lim = m_scope_lvl + num_scopes;
        if (m_clauses_to_reinit.size() <= lim) {
            SASSERT(!m_clauses_to_reinit.empty());
            lim      = m_clauses_to_reinit.size() - 1;
        }
        for (unsigned i = m_scope_lvl+1; i <= lim; i++) {
            clause_vector & v = m_clauses_to_reinit[i];
            for (clause* cls : v) {
                if (cls->deleted()) {
                    cls->release_atoms(m);
                    cls->m_reinit              = false;
                    cls->m_reinternalize_atoms = false;
                    continue;
                }
                SASSERT(cls->in_reinit_stack());
                bool keep = false;
                if (cls->reinternalize_atoms()) {
                    SASSERT(cls->get_num_atoms() == cls->get_num_literals());
                    for (unsigned j = 0; j < 2; j++) {
                        literal l           = cls->get_literal(j);
                        if (l.var() < num_bool_vars) {
                            // This boolean variable was not deleted during backtracking
                            //
                            // So, it is still a watch literal. I remove the watch, since
                            // the clause may have new watch-literals after reinitialization.
                            remove_watch_literal(cls, j);
                        }
                    }

                    unsigned num = cls->get_num_literals();

                    remove_lit_occs(*cls, num_bool_vars);

                    unsigned ilvl       = 0;
                    (void)ilvl;
                    for (unsigned j = 0; j < num; j++) {
                        expr * atom     = cls->get_atom(j);
                        bool   sign     = cls->get_atom_sign(j);
                        // Atom can be (NOT foo). This can happen, for example, when
                        // the NOT-application is a child of an uninterpreted function symbol.
                        // So, when reinternalizing the NOT-atom I should set the gate_ctx to false,
                        // and force expression to be reinternalized.
                        // Otherwise I set gate_ctx to true
                        bool gate_ctx = !m.is_not(atom);
                        internalize(atom, gate_ctx);
                        SASSERT(b_internalized(atom));
                        bool_var v      = get_bool_var(atom);
                        DEBUG_CODE({
                            if (get_intern_level(v) > ilvl)
                                ilvl = get_intern_level(v);
                        });
                        literal l(v, sign);
                        cls->set_literal(j, l);
                        if (cls->get_kind() == CLS_TH_LEMMA)
                            mark_as_relevant(l);
                    }
                    SASSERT(ilvl <= m_scope_lvl);
                    int w1_idx = select_watch_lit(cls, 0);
                    cls->swap_lits(0, w1_idx);
                    int w2_idx = select_watch_lit(cls, 1);
                    cls->swap_lits(1, w2_idx);
                    add_watch_literal(cls, 0);
                    add_watch_literal(cls, 1);

                    add_lit_occs(*cls);

                    literal l1 = cls->get_literal(0);
                    literal l2 = cls->get_literal(1);

                    if (get_assignment(l1) == l_false)
                        set_conflict(b_justification(cls));
                    else if (get_assignment(l2) == l_false)
                        assign(l1, b_justification(cls));

                    TRACE(reinit_clauses, tout << "reinit clause:\n"; display_clause_detail(tout, cls); tout << "\n";
                          tout << "activity: " << cls->get_activity() << ", num_bool_vars: " << num_bool_vars << ", scope_lvl: "
                          << m_scope_lvl << "\n";);
                    keep = true;
                }
                else {
                    SASSERT(!cls->reinternalize_atoms());
                    literal l1 = cls->get_literal(0);
                    literal l2 = cls->get_literal(1);
                    if (cls->get_kind() == CLS_TH_LEMMA) {
                        mark_as_relevant(l1);
                        mark_as_relevant(l2);
                    }
                    if (get_assignment(l1) == l_false && is_empty_clause(cls)) {
                        set_conflict(b_justification(cls));
                        keep = true;
                    }
                    else if (get_assignment(l2) == l_false && get_assignment(l1) == l_undef && is_unit_clause(cls)) {
                        assign(l1, b_justification(cls));
                        keep = true;
                    }
                }

                if (keep && m_scope_lvl > m_base_lvl) {
                    m_clauses_to_reinit[m_scope_lvl].push_back(cls);
                }
                else {
                    // clause do not need to be in the reinit stack anymore,
                    // because it will be deleted when the base level is
                    // backtracked.
                    cls->release_atoms(m);
                    cls->m_reinit              = false;
                    cls->m_reinternalize_atoms = false;
                }
            }
            v.reset();
        }
        CASSERT("reinit_clauses", check_clauses(m_lemmas));
        TRACE(reinit_clauses_bug, display_watch_lists(tout););
    }

    void context::reassert_units(unsigned units_to_reassert_lim) {
        unsigned i  = units_to_reassert_lim;
        unsigned sz = m_units_to_reassert.size();
        for (; i < sz; i++) {
            auto [unit, sign, is_relevant] = m_units_to_reassert[i];
            bool gate_ctx = true;
            internalize(unit, gate_ctx);
            bool_var v    = get_bool_var(unit);
            literal l(v, sign);
            assign(l, b_justification::mk_axiom());
            if (is_relevant)
                mark_as_relevant(l);
            TRACE(reassert_units, tout << "reasserting #" << unit->get_id() << " " << sign << " @ " << m_scope_lvl << "\n";);            
        }
        if (at_base_level()) 
            m_units_to_reassert.reset();        
    }

    /**
       \brief Backtrack 'num_scopes' scope levels. Return the number
       of boolean variables before reinitializing clauses. This value
       is useful because it can be used to detect which boolean variables
       were deleted.

       \warning This method will not invoke reset_cache_generation.
    */
    unsigned context::pop_scope_core(unsigned num_scopes) {
        unsigned units_to_reassert_lim;

        try {
            if (m.has_trace_stream() && !m_is_auxiliary)
                m.trace_stream() << "[pop] " << num_scopes << " " << m_scope_lvl << "\n";

            // TRACE(context, tout << "backtracking: " << num_scopes << " from " << m_scope_lvl << "\n";);
            TRACE(pop_scope_detail, display(tout););
            SASSERT(num_scopes > 0);
            SASSERT(num_scopes <= m_scope_lvl);
            SASSERT(m_scopes.size() == m_scope_lvl);

            unsigned new_lvl = m_scope_lvl - num_scopes;

            cache_generation(new_lvl);
            m_qmanager->pop(num_scopes);
            m_case_split_queue->pop_scope(num_scopes);

            TRACE(pop_scope, tout << "backtracking: " << num_scopes << ", new_lvl: " << new_lvl << "\n";);
            scope & s = m_scopes[new_lvl];
            TRACE(context, tout << "backtracking new_lvl: " << new_lvl << "\n";);

            units_to_reassert_lim = s.m_units_to_reassert_lim;

            if (new_lvl < m_base_lvl) {
                base_scope & bs = m_base_scopes[new_lvl];
                del_clauses(m_lemmas, bs.m_lemmas_lim);
                m_simp_qhead = bs.m_simp_qhead_lim;
                if (!bs.m_inconsistent) {
                    m_conflict = null_b_justification;
                    m_not_l = null_literal;
                    m_unsat_proof = nullptr;
                }
                m_base_scopes.shrink(new_lvl);
            }
            else {
                m_conflict = null_b_justification;
                m_not_l = null_literal;
            }
            del_clauses(m_aux_clauses, s.m_aux_clauses_lim);

            m_relevancy_propagator->pop(num_scopes);

            m_fingerprints.pop_scope(num_scopes);
            unassign_vars(s.m_assigned_literals_lim);
            undo_trail_stack(s.m_trail_stack_lim);

            for (theory* th : m_theory_set) 
                th->pop_scope_eh(num_scopes);

            del_justifications(m_justifications, s.m_justifications_lim);

            m_asserted_formulas.pop_scope(num_scopes);

            CTRACE(propagate_atoms, !m_atom_propagation_queue.empty(), tout << m_atom_propagation_queue << "\n";);

            m_eq_propagation_queue.reset();
            m_th_eq_propagation_queue.reset();
            m_th_diseq_propagation_queue.reset();
            m_atom_propagation_queue.reset();
            m_region.pop_scope(num_scopes);
            m_scopes.shrink(new_lvl);
            m_conflict_resolution->reset();

            m_scope_lvl = new_lvl;
            if (new_lvl < m_base_lvl) {
                m_base_lvl = new_lvl;
                m_search_lvl = new_lvl; // Remark: not really necessary
            }
        }
        catch (...) {
            // throwing inside pop is just not cool.
            UNREACHABLE();
        }

        // an exception can happen when axioms are reinitialized (because they are rewritten).

        unsigned num_bool_vars = get_num_bool_vars();
        // any variable >= num_bool_vars was deleted during backtracking.
        reinit_clauses(num_scopes, num_bool_vars);
        reassert_units(units_to_reassert_lim);
        TRACE(pop_scope_detail, tout << "end of pop_scope: \n"; display(tout););
        CASSERT("context", check_invariant());
        return num_bool_vars;
    }

    void context::pop_scope(unsigned num_scopes) {
        pop_scope_core(num_scopes);
        reset_cache_generation();
    }

    void context::pop_to_base_lvl() {
        SASSERT(m_scope_lvl >= m_base_lvl);
        if (!at_base_level()) {
            unsigned num_lvls = m_scope_lvl - m_base_lvl;
            pop_scope(num_lvls);
        }
        SASSERT(m_scope_lvl == m_base_lvl);
    }

    void context::pop_to_search_lvl() {
        if (m_scope_lvl > get_search_level()) {
            pop_scope(m_scope_lvl - get_search_level());
        }
    }

    /**
       \brief Simplify the given clause using the assignment.  Return
       true if the clause was already satisfied, and false otherwise.

       \remark This method should only be invoked if we are at the
       base level.
    */
    bool context::simplify_clause(clause& cls) {
        SASSERT(m_scope_lvl == m_base_lvl);
        unsigned s = cls.get_num_literals();
        if (get_assignment(cls[0]) == l_true ||
            get_assignment(cls[1]) == l_true) {
            // clause is already satisfied.
            return true;
        }

        literal_buffer simp_lits;

        unsigned i = 2;
        unsigned j = i;
        bool is_taut = false;
        for(; i < s; i++) {
            literal l = cls[i];
            switch(get_assignment(l)) {
            case l_false:
                if (m.proofs_enabled())
                    simp_lits.push_back(~l);
                dec_ref(l);
                break;
            case l_true:
                is_taut = true;
                // fallthrough
            case l_undef:
                if (i != j) {
                    cls.swap_lits(i, j);
                }
                j++;
                break;
            }
        }

        if (j < s) {
            m_clause_proof.shrink(cls, j);
            cls.set_num_literals(j);
            SASSERT(j >= 2);
        }

        if (is_taut) {
            return true;
        }

        if (m.proofs_enabled() && !simp_lits.empty()) {
            SASSERT(m_scope_lvl == m_base_lvl);
            justification * js = cls.get_justification();
            justification * new_js = nullptr;
            if (js->in_region())
                new_js = mk_justification(unit_resolution_justification(*this,
                                                                        js,
                                                                        simp_lits.size(),
                                                                        simp_lits.data()));
            else
                new_js = alloc(unit_resolution_justification, js, simp_lits.size(), simp_lits.data());
            cls.set_justification(new_js);
        }
        return false;
    }

    /**
       \brief Simplify the given vector of clauses starting at the given position.
       Return the number of deleted (already satisfied) clauses.
    */
    unsigned context::simplify_clauses(clause_vector & clauses, unsigned starting_at) {
        unsigned num_del_clauses = 0;
        clause_vector::iterator it  = clauses.begin();
        clause_vector::iterator end = clauses.end();
        it += starting_at;
        clause_vector::iterator it2 = it;
        for(; it != end; ++it) {
            clause * cls = *it;
            SASSERT(!cls->in_reinit_stack());
            
            if (cls->deleted()) {
                TRACE(simplify_clauses_bug, display_clause(tout << "deleted\n", cls) << "\n";);
                del_clause(true, cls);
                num_del_clauses++;
            }
            else if (simplify_clause(*cls)) {
                TRACE(simplify_clauses_bug, display_clause_smt2(tout << "simplified\n", *cls) << "\n";);
                for (unsigned idx = 0; idx < 2; idx++) {
                    literal     l0        = (*cls)[idx];
                    b_justification l0_js = get_justification(l0.var());
                    if (l0_js != null_b_justification &&
                        l0_js.get_kind() == b_justification::CLAUSE &&
                        l0_js.get_clause() == cls) {
                        // cls is the explanation of l0
                        // it is safe to replace with axiom, we are at the base level.
                        SASSERT(m_scope_lvl == m_base_lvl);
                        bool_var v0 = l0.var();
                        if (m.proofs_enabled()) {
                            SASSERT(m_search_lvl == m_base_lvl);
                            literal_buffer simp_lits;
                            unsigned num_lits = cls->get_num_literals();
                            for(unsigned i = 0; i < num_lits; i++) {
                                if (i != idx) {
                                    literal l = (*cls)[i];
                                    SASSERT(l != l0);
                                    simp_lits.push_back(~l);
                                }
                            }
                            justification * cls_js = cls->get_justification();
                            justification * js = nullptr;
                            if (!cls_js || cls_js->in_region()) {
                                // If cls_js is 0 or is allocated in a region, then
                                // we can allocate the new justification in a region too.
                                js = mk_justification(unit_resolution_justification(*this,
                                                                                    cls_js,
                                                                                    simp_lits.size(),
                                                                                    simp_lits.data()));
                            }
                            else {
                                js = alloc(unit_resolution_justification, cls_js, simp_lits.size(), simp_lits.data());
                                // js took ownership of the justification object.
                                cls->set_justification(nullptr);
                                m_justifications.push_back(js);
                            }
                            set_justification(v0, m_bdata[v0], b_justification(js));
                        }
                        else
                            m_bdata[v0].set_axiom();
                    }
                }
                del_clause(true, cls);
                num_del_clauses++;
            }
            else {
                *it2 = *it;
                ++it2;
                m_simp_counter += cls->get_num_literals();
            }
        }
        clauses.set_end(it2);
        CASSERT("simplify_clauses", check_invariant());
        return num_del_clauses;
    }

    /**
       \brief Simplify the set of clauses if possible (solver is at base level).
    */
    void context::simplify_clauses() {
        // Remark: when assumptions are used m_scope_lvl >= m_search_lvl > m_base_lvl. Therefore, no simplification is performed.
        if (m_scope_lvl > m_base_lvl)
            return;

        unsigned sz = m_assigned_literals.size();
        SASSERT(m_simp_qhead <= sz);

        if (m_simp_qhead == sz || m_simp_counter > 0) {
            TRACE(simplify_clauses, tout << "m_simp_qhead: " << m_simp_qhead << " m_simp_counter: " << m_simp_counter << "\n";);
            return;
        }

        if (m_aux_clauses.empty() && m_lemmas.empty()) {
            TRACE(simplify_clauses, tout << "no clauses to simplify\n";);
            return;
        }

        TRACE(simplify_clauses_detail, tout << "before:\n"; display_clauses(tout, m_lemmas););

        SASSERT(check_clauses(m_lemmas));
        SASSERT(check_clauses(m_aux_clauses));


        // m_simp_counter is used to balance the cost of simplify_clause.
        //
        // After executing simplify_clauses, the counter will contain
        // an approximation of the cost of executing simplify_clauses again.
        // That is, the number of literals that will need to be visited.
        //
        // The value of the counter is decremented each time we visit
        // a variable during propagation.
        //
        m_simp_counter = 0;
        // the field m_simp_qhead is used to check whether there are
        // new assigned literals at the base level.
        m_simp_qhead = m_assigned_literals.size();

        unsigned num_del_clauses = 0;

        SASSERT(m_scope_lvl == m_base_lvl);
        if (m_base_lvl == 0) {
            num_del_clauses += simplify_clauses(m_aux_clauses, 0);
            num_del_clauses += simplify_clauses(m_lemmas, 0);
        }
        else {
            scope & s       = m_scopes[m_base_lvl - 1];
            base_scope & bs = m_base_scopes[m_base_lvl - 1];
            num_del_clauses += simplify_clauses(m_aux_clauses, s.m_aux_clauses_lim);
            num_del_clauses += simplify_clauses(m_lemmas, bs.m_lemmas_lim);
        }
        m_stats.m_num_del_clauses += num_del_clauses;
        m_stats.m_num_simplifications++;
        TRACE(simp_counter, tout << "simp_counter: " << m_simp_counter << " scope_lvl: " << m_scope_lvl << "\n";);
        TRACE(simplify_clauses_detail, tout << "after:\n"; display_clauses(tout, m_lemmas););
        SASSERT(check_clauses(m_lemmas) && check_clauses(m_aux_clauses));
    }

    struct clause_lt {
        bool operator()(clause * cls1, clause * cls2) const { return cls1->get_activity() > cls2->get_activity(); }
    };

    /**
       \brief Delete low activity lemmas
    */
    inline void context::del_inactive_lemmas() {
        if (m_fparams.m_lemma_gc_strategy == LGC_NONE)
            return;
        else if (m_fparams.m_lemma_gc_half)
            del_inactive_lemmas1();
        else
            del_inactive_lemmas2();

        m_num_conflicts_since_lemma_gc = 0;
        if (m_fparams.m_lemma_gc_strategy == LGC_GEOMETRIC)
            m_lemma_gc_threshold = static_cast<unsigned>(m_lemma_gc_threshold * m_fparams.m_lemma_gc_factor);
    }

    /**
       \brief Delete (approx.) half of low activity lemmas
    */
    void context::del_inactive_lemmas1() {
        unsigned sz            = m_lemmas.size();
        unsigned start_at      = m_base_lvl == 0 ? 0 : m_base_scopes[m_base_lvl - 1].m_lemmas_lim;
        SASSERT(start_at <= sz);
        if (start_at + m_fparams.m_recent_lemmas_size >= sz)
            return;
        IF_VERBOSE(2, verbose_stream() << "(smt.delete-inactive-lemmas"; verbose_stream().flush(););
        SASSERT (m_fparams.m_recent_lemmas_size < sz);
        unsigned end_at        = sz - m_fparams.m_recent_lemmas_size;
        SASSERT(start_at < end_at);
        std::stable_sort(m_lemmas.begin() + start_at, m_lemmas.begin() + end_at, clause_lt());
        unsigned start_del_at  = (start_at + end_at) / 2;
        unsigned i             = start_del_at;
        unsigned j             = i;
        unsigned num_del_cls   = 0;
        TRACE(del_inactive_lemmas, tout << "sz: " << sz << ", start_at: " << start_at << ", end_at: " << end_at
              << ", start_del_at: " << start_del_at << "\n";);
        for (; i < end_at; i++) {
            clause * cls = m_lemmas[i];
            if (can_delete(cls)) {
                TRACE(del_inactive_lemmas, tout << "deleting: "; display_clause(tout, cls); tout << ", activity: " <<
                      cls->get_activity() << "\n";);
                del_clause(true, cls);
                num_del_cls++;
            }
            else {
                m_lemmas[j] = cls;
                j++;
            }
        }
        // keep recent clauses
        for (; i < sz; i++) {
            clause * cls = m_lemmas[i];
            if (cls->deleted() && can_delete(cls)) {
                del_clause(true, cls);
                num_del_cls++;
            }
            else {
                m_lemmas[j++] = cls;
            }
        }
        m_lemmas.shrink(j);
        if (m_fparams.m_clause_decay > 1) {
            // rescale activity
            for (i = start_at; i < j; i++) {
                clause * cls = m_lemmas[i];
                cls->set_activity(cls->get_activity() / m_fparams.m_clause_decay);
            }
        }
        IF_VERBOSE(2, verbose_stream() << " :num-deleted-clauses " << num_del_cls << ")" << std::endl;);
    }

    /**
       \brief More sophisticated version of del_inactive_lemmas. Here the lemmas are divided in two
       groups (old and new) based on the value of m_new_old_ratio parameter.
       A clause is deleted/retained based on its activity and relevancy. Clauses with several
       unassigned literals are considered less relevant. The threshold used for activity and relevancy
       depends on which group the clauses is in.
    */
    void context::del_inactive_lemmas2() {
        IF_VERBOSE(2, verbose_stream() << "(smt.delete-inactive-clauses "; verbose_stream().flush(););
        unsigned sz            = m_lemmas.size();
        unsigned start_at      = m_base_lvl == 0 ? 0 : m_base_scopes[m_base_lvl - 1].m_lemmas_lim;
        SASSERT(start_at <= sz);
        unsigned real_sz       = sz - start_at;
        // idx of the first learned clause considered "new"
        unsigned new_first_idx = start_at + (real_sz / m_fparams.m_new_old_ratio) * (m_fparams.m_new_old_ratio - 1);
        SASSERT(new_first_idx <= sz);
        unsigned i             = start_at;
        unsigned j             = i;
        unsigned num_del_cls   = 0;
        for (; i < sz; i++) {
            clause * cls = m_lemmas[i];
            if (can_delete(cls)) {
                if (cls->deleted()) {
                    // clause is already marked for deletion
                    del_clause(true, cls);
                    num_del_cls++;
                    continue;
                }
                // A clause is deleted if it has low activity and the number of unknowns is greater than a threshold.
                // The activity threshold depends on how old the clause is.
                unsigned act_threshold = m_fparams.m_old_clause_activity -
                    (m_fparams.m_old_clause_activity - m_fparams.m_new_clause_activity) * ((i - start_at) / real_sz);
                if (cls->get_activity() < act_threshold) {
                    unsigned rel_threshold = (i >= new_first_idx ? m_fparams.m_new_clause_relevancy : m_fparams.m_old_clause_relevancy);
                    if (more_than_k_unassigned_literals(cls, rel_threshold)) {
                        del_clause(true, cls);
                        num_del_cls++;
                        continue;
                    }
                }
            }
            m_lemmas[j] = cls;
            j++;
            cls->set_activity(static_cast<unsigned>(cls->get_activity() / m_fparams.m_inv_clause_decay));
        }
        SASSERT(j <= sz);
        m_lemmas.shrink(j);
        IF_VERBOSE(2, verbose_stream() << " :num-deleted-clauses " << num_del_cls << ")" << std::endl;);
    }

    /**
       \brief Return true if "cls" has more than (or equal to) k unassigned literals.
    */
    bool context::more_than_k_unassigned_literals(clause * cls, unsigned k) {
        SASSERT(k > 0);
        for (literal l : *cls) {
            if (get_assignment(l) == l_undef) {
                k--;
                if (k == 0) {
                    return true;
                }
            }
        }
        return false;
    }


#ifdef Z3DEBUG
    /**
       \brief Return true if a symbol of the given theory was already internalized.
    */
    bool context::already_internalized_theory(theory * th) const {
        return already_internalized_theory_core(th, m_b_internalized_stack) || already_internalized_theory_core(th, m_e_internalized_stack);
    }

    /**
       \brief Auxiliary method for #already_internalized_theory.
    */
    bool context::already_internalized_theory_core(theory * th, expr_ref_vector const & s) const {        
        expr_mark visited;
        family_id fid = th->get_id();
        unsigned sz = s.size();
        for (unsigned i = 0; i < sz; i++) {
            expr * n = s.get(i);
            if (uses_theory(n, fid, visited)) {
                return true;
            }
        }
        return false;
    }
#endif

    void context::register_plugin(theory * th) {
        if (m_theories.get_plugin(th->get_family_id()) != nullptr) {
            dealloc(th);
            return; // context already has a theory for the given family id.
        }
        TRACE(internalize, tout << this << " " << th->get_family_id() << "\n";);
        SASSERT(std::find(m_theory_set.begin(), m_theory_set.end(), th) == m_theory_set.end());
        m_theories.register_plugin(th);
        th->init();
        m_theory_set.push_back(th);
        {
#ifdef Z3DEBUG
            // It is unsafe to invoke push_trail from the method push_scope_eh.
            flet<bool> l(m_trail_enabled, false);
#endif
            for (unsigned i = 0; i < m_scope_lvl; ++i)
                th->push_scope_eh();
        }
    }

    void context::user_propagate_init(
        void*                    ctx, 
        user_propagator::push_eh_t&       push_eh,
        user_propagator::pop_eh_t&        pop_eh,
        user_propagator::fresh_eh_t&      fresh_eh) {
        setup_context(false);
        m_user_propagator = alloc(theory_user_propagator, *this);
        m_user_propagator->add(ctx, push_eh, pop_eh, fresh_eh);
        for (unsigned i = m_scopes.size(); i-- > 0; ) 
            m_user_propagator->push_scope_eh();
        register_plugin(m_user_propagator);
    }

    void context::user_propagate_initialize_value(expr* var, expr* value) {
        m_values.push_back({expr_ref(var, m), expr_ref(value, m)});
        push_trail(push_back_vector(m_values));
    }

    void context::initialize_value(expr* var, expr* value) {
        IF_VERBOSE(10, verbose_stream() << "initialize " << mk_pp(var, m) << " := " << mk_pp(value, m) << "\n");
        sort* s = var->get_sort();
        ensure_internalized(var);
            
        if (m.is_bool(s)) {
            auto v = get_bool_var_of_id_option(var->get_id());
            if (v == null_bool_var) {
                IF_VERBOSE(5, verbose_stream() << "Boolean variable has no literal " << mk_pp(var, m) << " := " << mk_pp(value, m) << "\n");
                return;
            }
            m_bdata[v].m_phase_available = true;         
            if (m.is_true(value))
                m_bdata[v].m_phase = true;
            else if (m.is_false(value))
                m_bdata[v].m_phase = false;
            else
                IF_VERBOSE(5, verbose_stream() << "Boolean value is not constant " << mk_pp(var, m) << " := " << mk_pp(value, m) << "\n");
            return;                
        }

        if (!e_internalized(var))
            return;
        theory* th = m_theories.get_plugin(s->get_family_id());
        if (!th) {
            IF_VERBOSE(5, verbose_stream() << "No theory is attached to variable " << mk_pp(var, m) << " := " << mk_pp(value, m) << "\n");
            return;
        }
        th->initialize_value(var, value);

    }

    bool context::watches_fixed(enode* n) const {
        return m_user_propagator && m_user_propagator->has_fixed() && n->get_th_var(m_user_propagator->get_family_id()) != null_theory_var;
    }

    bool context::has_split_candidate(bool_var& var, bool& is_pos) {
        if (!m_user_propagator)
            return false;
        if (!m_user_propagator->get_case_split(var, is_pos))
            return false;
        return get_assignment(var) == l_undef;
    }
    
    bool context::decide_user_interference(bool_var& var, bool& is_pos) {
        if (!m_user_propagator)
            return false;
        bool_var old = var;
        m_user_propagator->decide(var, is_pos);
        return old != var;
    }

    void context::assign_fixed(enode* n, expr* val, unsigned sz, literal const* explain) {
        theory_var v = n->get_th_var(m_user_propagator->get_family_id());
        m_user_propagator->new_fixed_eh(v, val, sz, explain);
    }

    bool context::is_fixed(enode* n, expr_ref& val, literal_vector& explain) {
        if (m.is_bool(n->get_expr())) {
            literal lit = get_literal(n->get_expr());
            switch (get_assignment(lit)) {
            case l_true:
                val = m.mk_true(); explain.push_back(lit); return true;
            case l_false:
                val = m.mk_false(); explain.push_back(~lit); return true;
            default:
                return false;
            }
        }
        theory_var_list * l = n->get_th_var_list();
        while (l) {
            theory_id tid = l->get_id();
            auto* p = m_theories.get_plugin(tid);
            if (p && p->is_fixed_propagated(l->get_var(), val, explain))
                return true;
            l = l->get_next();
        }
        return false;
    }


    void context::push() {       
        pop_to_base_lvl();
        setup_context(false);
        bool was_consistent = !inconsistent();
        try {
            internalize_assertions(); // internalize assertions before invoking m_asserted_formulas.push_scope
        } catch (oom_exception&) {
            throw default_exception("Resource limits hit in push");
        }
        if (!m.inc())
            throw default_exception("push canceled");
        scoped_suspend_rlimit _suspend_cancel(m.limit());
        propagate();
        if (was_consistent && inconsistent() && !m_asserted_formulas.inconsistent()) {
            // logical context became inconsistent during user PUSH
            VERIFY(!resolve_conflict()); // build the proof
        }
        push_scope();
        m_base_scopes.push_back(base_scope());
        base_scope & bs = m_base_scopes.back();
        bs.m_lemmas_lim = m_lemmas.size();
        bs.m_inconsistent = inconsistent();
        bs.m_simp_qhead_lim = m_simp_qhead;
        m_base_lvl++;
        m_search_lvl++; // Not really necessary. But, it is useful to enforce the invariant m_search_lvl >= m_base_lvl
        SASSERT(m_base_lvl <= m_scope_lvl);
    }

    void context::pop(unsigned num_scopes) {
        SASSERT (num_scopes > 0);
        if (num_scopes > m_scope_lvl) return;
        pop_to_base_lvl();
        pop_scope(num_scopes);
    }

    /**
       \brief Free memory allocated by logical context.
    */
    void context::flush() {
        flet<bool> l1(m_flushing, true);
        TRACE(flush, tout << "m_scope_lvl: " << m_scope_lvl << "\n";);
        m_relevancy_propagator = nullptr;
        m_model_generator->reset();
        for (theory* t : m_theory_set) {
            t->flush_eh();
        }
        del_clauses(m_aux_clauses, 0);
        del_clauses(m_lemmas, 0);
        del_justifications(m_justifications, 0);
        reset_tmp_clauses();
        undo_trail_stack(0);
        m_qmanager = nullptr;
        if (m_is_diseq_tmp) {
            m_is_diseq_tmp->del_eh(m, false);
            m.dec_ref(m_is_diseq_tmp->get_expr());
            enode::del_dummy(m_is_diseq_tmp);
            m_is_diseq_tmp = nullptr;
        }
        std::for_each(m_almost_cg_tables.begin(), m_almost_cg_tables.end(), delete_proc<almost_cg_table>());
    }

    void context::assert_expr_core(expr * e, proof * pr) {
        if (get_cancel_flag()) return;
        SASSERT(is_well_sorted(m, e));
        TRACE(begin_assert_expr, tout << mk_pp(e, m) << " " << mk_pp(pr, m) << "\n";);
        TRACE(begin_assert_expr_ll, tout << mk_ll_pp(e, m) << "\n";);
        if (!m_searching)
            pop_to_base_lvl();
        if (pr == nullptr)
            m_asserted_formulas.assert_expr(e);
        else
            m_asserted_formulas.assert_expr(e, pr);
        TRACE(end_assert_expr_ll, ast_mark m; m_asserted_formulas.display_ll(tout, m););
    }

    void context::add_asserted(expr* e) {
        m_asserted_formulas.assert_expr(e);
    }

    void context::assert_expr(expr * e) {
        assert_expr(e, nullptr);
    }

    void context::assert_expr(expr * e, proof * pr) {
        timeit tt(get_verbosity_level() >= 100, "smt.simplifying");
        assert_expr_core(e, pr);
    }

    class case_split_insert_trail : public trail {
        context& ctx;
        literal l;
    public:
        case_split_insert_trail(context& ctx, literal l):
            ctx(ctx),
            l(l) {
        }
        void undo() override {
            ctx.undo_th_case_split(l);
        }
    };

    void context::mk_th_case_split(unsigned num_lits, literal * lits) {
        TRACE(theory_case_split, display_literals_verbose(tout << "theory case split: ", num_lits, lits); tout << std::endl;);
        // If we don't use the theory case split heuristic,
        // for each pair of literals (l1, l2) we add the clause (~l1 OR ~l2)
        // to enforce the condition that at most one literal can be assigned 'true'.
        if (!m_fparams.m_theory_case_split) {
            if (!m.proofs_enabled()) {
                for (unsigned i = 0; i < num_lits; ++i) {
                    for (unsigned j = i + 1; j < num_lits; ++j) {
                        literal l1 = lits[i];
                        literal l2 = lits[j];
                        mk_clause(~l1, ~l2, (justification*) nullptr);
                    }
                }
            }
        }
        else {
            literal_vector new_case_split;
            for (unsigned i = 0; i < num_lits; ++i) {
                literal l = lits[i];
                SASSERT(!m_all_th_case_split_literals.contains(l.index()));
                m_all_th_case_split_literals.insert(l.index());
                push_trail(case_split_insert_trail(*this, l));
                new_case_split.push_back(l);
            }
            m_th_case_split_sets.push_back(new_case_split);
            push_trail(push_back_vector<vector<literal_vector> >(m_th_case_split_sets));
            for (unsigned i = 0; i < num_lits; ++i) {
                literal l = lits[i];
                if (!m_literal2casesplitsets.contains(l.index())) {
                    m_literal2casesplitsets.insert(l.index(), vector<literal_vector>());
                }
                m_literal2casesplitsets[l.index()].push_back(new_case_split);
            }
            TRACE(theory_case_split, tout << "tracking case split literal set { ";
                  for (unsigned i = 0; i < num_lits; ++i) {
                      tout << lits[i].index() << " ";
                  }
                  tout << "}" << std::endl;
                  );
        }
    }

    void context::add_theory_aware_branching_info(bool_var v, double priority, lbool phase) {
        m_case_split_queue->add_theory_aware_branching_info(v, priority, phase);
    }

    void context::undo_th_case_split(literal l) {
        m_all_th_case_split_literals.remove(l.index());
        if (m_literal2casesplitsets.contains(l.index())) {
            if (!m_literal2casesplitsets[l.index()].empty()) {
                m_literal2casesplitsets[l.index()].pop_back();
            }
        }
    }

    bool context::propagate_th_case_split(unsigned qhead) {
        if (m_all_th_case_split_literals.empty())
            return true;

        // iterate over all literals assigned since the last time this method was called,
        // not counting any literals that get assigned by this method
        // this relies on bcp() to give us its old m_qhead and therefore
        // bcp() should always be called before this method

        unsigned assigned_literal_end = m_assigned_literals.size();
        for (; qhead < assigned_literal_end; ++qhead) {
            literal l = m_assigned_literals[qhead];
            TRACE(theory_case_split, tout << "check literal " << l.index() << std::endl; display_literal_verbose(tout, l); tout << std::endl;);
            // check if this literal participates in any theory case split
            if (!m_all_th_case_split_literals.contains(l.index())) {
                continue;
            }
            TRACE(theory_case_split, tout << "assigned literal " << l.index() << " is a theory case split literal" << std::endl;);
            // now find the sets of literals which contain l
            vector<literal_vector> const& case_split_sets = m_literal2casesplitsets[l.index()];
            for (const auto& case_split_set : case_split_sets) {
                TRACE(theory_case_split, tout << "found case split set { ";
                      for (const auto& set_elem : case_split_set) {
                          tout << set_elem.index() << " ";
                      }
                      tout << "}" << std::endl;);
                for (const auto& l2 : case_split_set) {
                    if (l2 != l) {
                        b_justification js(l);
                        TRACE(theory_case_split, tout << "case split literal "; smt::display(tout, l2, m, m_bool_var2expr.data()); tout << std::endl;);
                        if (l2 == true_literal || l2 == false_literal || l2 == null_literal) continue;
                        assign(~l2, js);
                        if (inconsistent()) {
                            TRACE(theory_case_split, tout << "conflict detected!" << std::endl;);
                            return false;
                        }
                    }
                }
            }
        }
        // if we get here without detecting a conflict, we're fine
        return true;
    }

    void context::reduce_assertions() {
        if (!m_asserted_formulas.inconsistent()) {
            // SASSERT(at_base_level());
            m_asserted_formulas.reduce();
        }
    }

    static bool is_valid_assumption(ast_manager & m, expr * a) {
        expr* arg;
        if (!m.is_bool(a))
            return false;
        if (is_uninterp_const(a))
            return true;
        if (m.is_not(a, arg) && is_uninterp_const(arg))
            return true;
        if (!is_app(a))
            return false;
        if (m.is_true(a) || m.is_false(a))
            return true;
        if (is_app(a) && to_app(a)->get_family_id() == m.get_basic_family_id())
            return false;
        if (is_app(a) && to_app(a)->get_num_args() == 0)
            return true;
        return false;
    }

    void context::internalize_proxies(expr_ref_vector const& asms, vector<std::pair<expr*,expr_ref>>& asm2proxy) {
        for (expr* e : asms) {
            if (is_valid_assumption(m, e)) {
                asm2proxy.push_back(std::make_pair(e, expr_ref(e, m)));
            }
            else {
                expr_ref proxy(m), fml(m);
                proxy = m.mk_fresh_const(symbol(), m.mk_bool_sort());
                fml = m.mk_implies(proxy, e);
                m_asserted_formulas.assert_expr(fml);
                asm2proxy.push_back(std::make_pair(e, proxy));
            }
        }
        // The new assertions are of the form 'proxy => assumption'
        // so clause simplification is sound even as these are removed after pop_scope.
        internalize_assertions();
    }

    void context::internalize_assertions() {
        if (get_cancel_flag()) return;
        if (m_internalizing_assertions) return;
        flet<bool> _internalizing(m_internalizing_assertions, true);
        TRACE(internalize_assertions, tout << "internalize_assertions()...\n";);
        timeit tt(get_verbosity_level() >= 100, "smt.preprocessing");
        unsigned qhead = 0;
        do {
            reduce_assertions();
            if (get_cancel_flag()) 
                return;
            if (m_asserted_formulas.inconsistent()) {
                if (!inconsistent())
                    asserted_inconsistent();
                break;
            }
            qhead = m_asserted_formulas.get_qhead();
            unsigned sz = m_asserted_formulas.get_num_formulas();
            while (qhead < sz) {
                if (get_cancel_flag()) {
                    m_asserted_formulas.commit(qhead);
                    return;
                }
                expr * f   = m_asserted_formulas.get_formula(qhead);
                proof * pr = m_asserted_formulas.get_formula_proof(qhead);
                SASSERT(!pr || f == m.get_fact(pr));                    
                internalize_assertion(f, pr, 0);
                ++qhead;
            }
            m_asserted_formulas.commit();
        }
        while (qhead < m_asserted_formulas.get_num_formulas());

        TRACE(internalize_assertions, tout << "after internalize_assertions()...\n";
              tout << "inconsistent: " << inconsistent() << "\n";);
        TRACE(after_internalize_assertions, display(tout););
    }

    void context::asserted_inconsistent() {
        proof * pr = m_asserted_formulas.get_inconsistency_proof();
        m_unsat_proof = pr;
        if (!pr) {
            set_conflict(b_justification::mk_axiom());
        }
        else {
            set_conflict(mk_justification(justification_proof_wrapper(*this, pr)));
        }
    }

    /**
       \brief Assumptions must be uninterpreted boolean constants (aka propositional variables).
    */
    bool context::validate_assumptions(expr_ref_vector const& asms) {
        for (expr* a : asms) {
            SASSERT(a);
            if (!is_valid_assumption(m, a)) {
                warning_msg("an assumption must be a propositional variable or the negation of one");
                return false;
            }
        }
        return true;
    }

    void context::init_clause(expr_ref_vector const& _clause) {
        literal_vector lits;
        for (expr* lit : _clause) {
            internalize_formula(lit, true);
            mark_as_relevant(lit);
            lits.push_back(get_literal(lit));
        }
        clause* clausep = nullptr;
        if (lits.size() >= 2) {
            justification* js = nullptr;
            if (m.proofs_enabled()) {
                proof * pr = mk_clause_def_axiom(lits.size(), lits.data(), nullptr);
                js = mk_justification(justification_proof_wrapper(*this, pr));
            }
            clausep = clause::mk(m, lits.size(), lits.data(), CLS_AUX, js);
        }
        m_tmp_clauses.push_back(std::make_pair(clausep, lits));
    }

    void context::reset_tmp_clauses() {
        for (auto& p : m_tmp_clauses) {
            if (p.first) del_clause(false, p.first);
        }
        m_tmp_clauses.reset();
    }

    lbool context::decide_clause() {
        if (m_tmp_clauses.empty()) return l_true;
        for (auto & tmp_clause : m_tmp_clauses) {
            literal_vector& lits = tmp_clause.second;
            literal unassigned = null_literal;
            for (literal l : lits) {
                switch (get_assignment(l)) {
                case l_false:
                    break;
                case l_true:
                    goto next_clause;
                default:
                    unassigned = l;
                }         
            }

            if (unassigned != null_literal) {
                shuffle(lits.size(), lits.data(), m_random);
                push_scope();
                assign(unassigned, b_justification::mk_axiom(), true);
                return l_undef;
            }

            if (lits.size() == 1) {
                set_conflict(b_justification(), ~lits[0]);
            }
            else {
                set_conflict(b_justification(tmp_clause.first), null_literal);
            }
            VERIFY(!resolve_conflict());
            return l_false;
        next_clause:
            ;
        }
        return l_true;
    }

    void context::init_assumptions(expr_ref_vector const& asms) {
        reset_assumptions();
        m_literal2assumption.reset();
        m_unsat_core.reset();

        if (!asms.empty()) {
            // We must give a chance to the theories to propagate before we create a new scope...
            propagate();
            // Internal backtracking scopes (created with push_scope()) must only be created when we are
            // in a consistent context.
            if (inconsistent())
                return;
            if (get_cancel_flag())
                return;
            del_inactive_lemmas();
            push_scope();
            vector<std::pair<expr*,expr_ref>> asm2proxy;
            internalize_proxies(asms, asm2proxy);
            for (auto const& p: asm2proxy) {
                if (inconsistent())
                    break;
                expr_ref curr_assumption = p.second;
                expr* orig_assumption = p.first;
                if (m.is_true(curr_assumption)) continue;
                SASSERT(is_valid_assumption(m, curr_assumption));
                proof * pr = m.mk_asserted(curr_assumption);
                internalize_assertion(curr_assumption, pr, 0);
                literal l = get_literal(curr_assumption);
                SASSERT(get_assignment(l) != l_undef);
                SASSERT(l != false_literal || inconsistent());
                if (l == true_literal || l == false_literal) {
                    continue;
                }
                m_literal2assumption.insert(l.index(), orig_assumption);
                m_assumptions.push_back(l);
                get_bdata(l.var()).m_assumption = true;                
                SASSERT(is_relevant(l));
                TRACE(assumptions, tout << l << ":" << curr_assumption << " " << mk_pp(orig_assumption, m) << "\n";);
            }
        }
        m_search_lvl = m_scope_lvl;
        SASSERT(asms.empty() || m_search_lvl > m_base_lvl);
        SASSERT(!asms.empty() || m_search_lvl == m_base_lvl);
        TRACE(after_internalization, display(tout););
    }

    void context::reset_assumptions() {
        TRACE(unsat_core_bug, tout << "reset " << m_assumptions << "\n";);
        for (literal lit : m_assumptions) 
            get_bdata(lit.var()).m_assumption = false;
        m_assumptions.reset();
    }

    bool context::should_research(lbool r) {
        if (r != l_false || m_unsat_core.empty()) { 
            return false;
        }
        for (theory* th : m_theory_set) {
            if (th->should_research(m_unsat_core)) {
                return true;
            }
        }
        return false;
    }

    lbool context::mk_unsat_core(lbool r) {        
        if (r != l_false) return r;
        SASSERT(inconsistent());
        if (!tracking_assumptions()) {
            SASSERT(m_assumptions.empty());
            return l_false;
        }
        uint_set already_found_assumptions;
        literal_vector::const_iterator it  = m_conflict_resolution->begin_unsat_core();
        literal_vector::const_iterator end = m_conflict_resolution->end_unsat_core();
        for (; it != end; ++it) {
            literal l = *it;
            TRACE(unsat_core_bug, tout << "answer literal: " << l << "\n";);
            SASSERT(get_bdata(l.var()).m_assumption);
            if (!m_literal2assumption.contains(l.index())) l.neg();
            SASSERT(m_literal2assumption.contains(l.index()));
            if (!already_found_assumptions.contains(l.index())) {
                already_found_assumptions.insert(l.index());
                expr* orig_assumption = m_literal2assumption[l.index()];
                m_unsat_core.push_back(orig_assumption);
                TRACE(assumptions, tout << l << ": " << mk_pp(orig_assumption, m) << "\n";);
            }
        }
        reset_assumptions();
        pop_to_base_lvl(); // undo the push_scope() performed by init_assumptions
        m_search_lvl = m_base_lvl;
        std::sort(m_unsat_core.data(), m_unsat_core.data() + m_unsat_core.size(), ast_lt_proc());
        TRACE(unsat_core_bug, tout << "unsat core:\n" << m_unsat_core << "\n";);
        validate_unsat_core();
        // theory validation of unsat core
        for (theory* th : m_theory_set) {
            lbool theory_result = th->validate_unsat_core(m_unsat_core);
            if (theory_result == l_undef) {
                return l_undef;
            }
        }
        return l_false;
    }

    /**
       \brief Make some checks before starting the search.
       Return true if succeeded.
    */
    bool context::check_preamble(bool reset_cancel) {
        if (m.has_trace_stream() && !m_is_auxiliary)
            m.trace_stream() << "[begin-check] " << m_scope_lvl << "\n";

        if (memory::above_high_watermark()) {
            m_last_search_failure = MEMOUT;
            return false;
        }
        reset_tmp_clauses();
        m_unsat_core.reset();
        m_stats.m_num_checks++;
        pop_to_base_lvl();      
        m_conflict_resolution->reset();
        return true;
    }

    /**
       \brief Execute some finalization code after performing the search.
    */
    lbool context::check_finalize(lbool r) {
        TRACE(after_search, display(tout << "result: " << r << "\n");
              m_case_split_queue->display(tout << "case splits\n");
              );
        m_search_finalized = true;
        display_profile(verbose_stream());
        if (r == l_true && get_cancel_flag()) 
            r = l_undef;
        if (r == l_undef && m_internal_completed == l_true && has_sls_model()) {
            m_last_search_failure = OK;
            m_conflict = null_b_justification;
            r = l_true;
        }
        m_internal_completed = l_false;
        if (r == l_true && gparams::get_value("model_validate") == "true") {
            recfun::util u(m);
            if (u.get_rec_funs().empty() && m_proto_model) {
                for (theory* t : m_theory_set) {
                    t->validate_model(*m_proto_model);
                }
            }
#if 0
            for (literal lit : m_assigned_literals) {
                if (!is_relevant(lit)) continue;
                expr* v = m_bool_var2expr[lit.var()];
                if (lit.sign() ? m_model->is_true(v) : m_model->is_false(v)) {
                    IF_VERBOSE(10, verbose_stream() 
                               << "invalid assignment " << (lit.sign() ? "true" : "false") 
                               << " to #" << v->get_id() << " := " << mk_bounded_pp(v, m, 3) << "\n");
                }
            }
            for (clause* cls : m_aux_clauses) {
                bool found = false;
                IF_VERBOSE(10, display_clause_smt2(verbose_stream() << "check:\n", *cls) << "\n");                    
                for (literal lit : *cls) {
                    expr* v = m_bool_var2expr[lit.var()];
                    if (lit.sign() ? !m_model->is_true(v) : !m_model->is_false(v)) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    IF_VERBOSE(10, display_clause_smt2(verbose_stream() << "not satisfied:\n", *cls) << "\n");                    
                }
            }
            for (clause* cls : m_lemmas) {
                bool found = false;
                IF_VERBOSE(10, display_clause_smt2(verbose_stream() << "check:\n", *cls) << "\n");                    
                for (literal lit : *cls) {
                    expr* v = m_bool_var2expr[lit.var()];
                    if (lit.sign() ? !m_model->is_true(v) : !m_model->is_false(v)) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    IF_VERBOSE(10, display_clause_smt2(verbose_stream() << "not satisfied:\n", *cls) << "\n");                    
                }
            }

            unsigned l_idx = 0;
            for (watch_list const& wl : m_watches) {
                literal l1 = to_literal(l_idx++);
                literal neg_l1 = ~l1;
                expr* v = m_bool_var2expr[l1.var()];
                if (neg_l1.sign() ? !m_model->is_true(v) : !m_model->is_false(v)) {
                    continue;
                }
                literal const * it2  = wl.begin_literals();
                literal const * end2 = wl.end_literals();
                for (; it2 != end2; ++it2) {
                    literal l2 = *it2;
                    if (l1.index() >= l2.index()) {
                        continue;
                    }
                    literal lits[2] = { neg_l1, l2 };
                    IF_VERBOSE(10, display_literals_smt2(verbose_stream() << "check: ", 2, lits) << "\n";);
                    v = m_bool_var2expr[l2.var()];
                    if (l2.sign() ? !m_model->is_true(v) : !m_model->is_false(v)) {
                        continue;
                    }
                    IF_VERBOSE(10, display_literals_smt2(verbose_stream() << "not satisfied: ", 2, lits) << "\n";);
                }
            }
#endif
        }
        return r;
    }

    bool context::has_sls_model() {
        if (!m_fparams.m_sls_enable)
            return false;
        auto tid = m.get_family_id("sls");
        auto p = m_theories.get_plugin(tid);
        if (!p)
            return false;
        m_model = static_cast<theory_sls*>(p)->get_model();
        return m_model.get() != nullptr;
    }

    /**
       \brief Setup the logical context based on the current set of
       asserted formulas and execute the check command.

       \remark A logical context can only be configured at scope level 0,
       and before internalizing any formulas.
    */
    lbool context::setup_and_check(bool reset_cancel) {
        if (!check_preamble(reset_cancel)) return l_undef;
        SASSERT(m_scope_lvl == 0);
        SASSERT(!m_setup.already_configured());
        setup_context(m_fparams.m_auto_config);

        if (m_fparams.m_threads > 1 && !m.has_trace_stream()) {
            parallel p(*this);
            expr_ref_vector asms(m);
            return p(asms);
        }

        try {
            internalize_assertions();
        } catch (oom_exception&) {
            return l_undef;
        }
        expr_ref_vector theory_assumptions(m);
        add_theory_assumptions(theory_assumptions);
        if (!theory_assumptions.empty()) {
            TRACE(search, tout << "Adding theory assumptions to context" << std::endl;);
            return check(0, nullptr, reset_cancel);
        }
        else {
            search_completion sc(*this);
            TRACE(before_search, display(tout););
            return check_finalize(search());
        }
    }

    config_mode context::get_config_mode(bool use_static_features) const {
        if (!m_fparams.m_auto_config)
            return CFG_BASIC;
        if (use_static_features)
            return CFG_AUTO;
        return CFG_LOGIC;
    }

    void context::setup_context(bool use_static_features) {
        if (m_setup.already_configured() || inconsistent()) {
            m_relevancy_lvl = std::min(m_fparams.m_relevancy_lvl, m_relevancy_lvl);
            return;
        }
        m_setup(get_config_mode(use_static_features));
        m_relevancy_lvl = m_fparams.m_relevancy_lvl;
        setup_components();
    }

    void context::setup_components() {
        m_asserted_formulas.setup();
        m_random.set_seed(m_fparams.m_random_seed);
        m_dyn_ack_manager.setup();
        m_conflict_resolution->setup();

        if (!relevancy())
            m_fparams.m_relevancy_lemma = false;

        // setup all the theories
        for (theory* th : m_theory_set) 
            th->setup();
    }

    void context::add_theory_assumptions(expr_ref_vector & theory_assumptions) {
        for (theory* th : m_theory_set) {
            th->add_theory_assumptions(theory_assumptions);
        }
    }

    lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel) {
        if (!check_preamble(reset_cancel)) return l_undef;
        SASSERT(at_base_level());
        setup_context(false);
        search_completion sc(*this);
        if (m_fparams.m_threads > 1 && !m.has_trace_stream()) {            
            expr_ref_vector asms(m, num_assumptions, assumptions);
            parallel p(*this);
            return p(asms);
        }
        lbool r = l_undef;
        do {
            pop_to_base_lvl();
            expr_ref_vector asms(m, num_assumptions, assumptions);
            try {
                internalize_assertions();
                add_theory_assumptions(asms);
                TRACE(unsat_core_bug, tout << asms << '\n';);
                init_assumptions(asms);
            } catch (oom_exception&) {
                return l_undef;
            }
            TRACE(before_search, display(tout););
            r = search();
            r = mk_unsat_core(r);        
        }
        while (should_research(r));
        r = check_finalize(r);
        return r;
    }

    lbool context::check(expr_ref_vector const& cube, vector<expr_ref_vector> const& clauses) {
        if (!check_preamble(true)) return l_undef;
        TRACE(before_search, display(tout););
        setup_context(false);
        lbool r = l_undef;
        search_completion sc(*this);
        do {
            pop_to_base_lvl();
            expr_ref_vector asms(cube);
            try {
                internalize_assertions();
                add_theory_assumptions(asms);
                // introducing proxies: if (!validate_assumptions(asms)) return l_undef;
                for (auto const& clause : clauses) if (!validate_assumptions(clause)) return l_undef;
                init_assumptions(asms);
            } catch (oom_exception&) {
                return l_undef;
            }
            for (auto const& clause : clauses) init_clause(clause);
            r = search();   
            r = mk_unsat_core(r);             
        }
        while (should_research(r));
        r = check_finalize(r);
        return r;           
    }

    void context::init_search() {
        for (theory* th : m_theory_set) {
            th->init_search_eh();
        }
        m_qmanager->init_search_eh();
        m_incomplete_theories.reset();
        m_num_conflicts                = 0;
        m_num_conflicts_since_restart  = 0;
        m_num_conflicts_since_lemma_gc = 0;
        m_num_restarts                 = 0;
        m_restart_threshold            = m_fparams.m_restart_initial;
        m_restart_outer_threshold      = m_fparams.m_restart_initial;
        m_agility                      = 0.0;
        m_luby_idx                     = 1;
        m_lemma_gc_threshold           = m_fparams.m_lemma_gc_initial;
        m_last_search_failure          = OK;
        m_unsat_proof                  = nullptr;
        m_unsat_core                   .reset();
        m_dyn_ack_manager              .init_search_eh();
        m_final_check_idx              = 0;
        m_phase_default                = false;
        m_case_split_queue             ->init_search_eh();
        m_next_progress_sample         = 0;
        m_internal_completed                = l_undef;
        if (m.has_type_vars() && !m_theories.get_plugin(poly_family_id))
            register_plugin(alloc(theory_polymorphism, *this));
        TRACE(literal_occ, display_literal_num_occs(tout););
    }

    void context::end_search() {
        m_case_split_queue->end_search_eh();
    }

    void context::inc_limits() {
        if (m_num_conflicts_since_restart >= m_restart_threshold) {
            switch (m_fparams.m_restart_strategy) {
            case RS_GEOMETRIC:
                m_restart_threshold = static_cast<unsigned>(m_restart_threshold * m_fparams.m_restart_factor);
                break;
            case RS_IN_OUT_GEOMETRIC:
                m_restart_threshold = static_cast<unsigned>(m_restart_threshold * m_fparams.m_restart_factor);
                if (m_restart_threshold > m_restart_outer_threshold) {
                    m_restart_threshold = m_fparams.m_restart_initial;
                    m_restart_outer_threshold = static_cast<unsigned>(m_restart_outer_threshold * m_fparams.m_restart_factor);
                }
                break;
            case RS_LUBY:
                m_luby_idx ++;
                m_restart_threshold = static_cast<unsigned>(get_luby(m_luby_idx) * m_fparams.m_restart_initial);
                break;
            case RS_FIXED:
                break;
            case RS_ARITHMETIC:
                m_restart_threshold = static_cast<unsigned>(m_restart_threshold + m_fparams.m_restart_factor);
                break;
            default:
                break;
            }
        }
        m_num_conflicts_since_restart = 0;
    }


    lbool context::search() {
        if (m_asserted_formulas.inconsistent()) {
            asserted_inconsistent();
            return l_false;
        }
        if (inconsistent()) {
            VERIFY(!resolve_conflict());
            return l_false;
        }
        if (get_cancel_flag()) 
            return l_undef;
        
        timeit tt(get_verbosity_level() >= 100, "smt.stats");
        reset_model();
        SASSERT(at_search_level());
        TRACE(search, display(tout); display_enodes_lbls(tout););
        TRACE(search_detail, m_asserted_formulas.display(tout););
        init_search();
        for (auto const& [var, value] : m_values)
            initialize_value(var, value);
            
        flet<bool> l(m_searching, true);
        TRACE(after_init_search, display(tout););
        IF_VERBOSE(2, verbose_stream() << "(smt.searching)\n";);
        log_stats();
        TRACE(search_lite, tout << "searching...\n";);
        lbool    status            = l_undef;
        unsigned curr_lvl          = m_scope_lvl;

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

            status = bounded_search();
            TRACE(search_bug, tout << "status: " << status << ", inconsistent: " << inconsistent() << "\n";);
            TRACE(assigned_literals_per_lvl, display_num_assigned_literals_per_lvl(tout);
                  tout << ", num_assigned: " << m_assigned_literals.size() << "\n";);

            if (!restart(status, curr_lvl)) {
                break;
            }            
        }

        TRACE(guessed_literals,
              expr_ref_vector guessed_lits(m);
              get_guessed_literals(guessed_lits);
              tout << guessed_lits << "\n";);
        end_search();
        return status;
    }

    bool context::restart(lbool& status, unsigned curr_lvl) {
        SASSERT(status != l_true || !inconsistent());

        reset_model();

        if (m_last_search_failure != OK) 
            return false;
        if (status == l_false) 
            return false;
        if (status == l_true && !m_qmanager->has_quantifiers() && !has_lambda()) 
            return false;
        if (status == l_true && m_qmanager->has_quantifiers()) {
            // possible outcomes   DONE l_true, DONE l_undef, CONTINUE
            mk_proto_model();
            quantifier_manager::check_model_result cmr = quantifier_manager::UNKNOWN;
            if (m_proto_model.get()) {
                cmr = m_qmanager->check_model(m_proto_model.get(), m_model_generator->get_root2value());
            }
            switch (cmr) {
            case quantifier_manager::SAT:
                return false;
            case quantifier_manager::UNKNOWN:
                IF_VERBOSE(2, verbose_stream() << "(smt.giveup quantifiers)\n";);
                // giving up
                m_last_search_failure = QUANTIFIERS;
                status = l_undef;
                return false;
            default:
                break;
            }
        }
        if (status == l_true && has_lambda()) {
            m_last_search_failure = LAMBDAS;
            status = l_undef;
            return false;
        }
        inc_limits();
        if (status == l_true || !m_fparams.m_restart_adaptive || m_agility < m_fparams.m_restart_agility_threshold) {
            SASSERT(!inconsistent());
            log_stats();
            // execute the restart
            m_stats.m_num_restarts++;
            m_num_restarts++;
            if (m_scope_lvl > curr_lvl) {
                pop_scope(m_scope_lvl - curr_lvl);
                SASSERT(at_search_level());
            }
            for (theory* th : m_theory_set) 
                if (!inconsistent()) 
                    th->restart_eh();

            TRACE(mbqi_bug_detail, tout << "before instantiating quantifiers...\n";);
            if (!inconsistent()) 
                m_qmanager->restart_eh();
            if (inconsistent()) {
                VERIFY(!resolve_conflict());
                status = l_false;
                return false;
            }
            if (m_num_restarts >= m_fparams.m_restart_max) {
                status = l_undef;
                m_last_search_failure = NUM_CONFLICTS;
                return false;
            }
        }
        if (m_fparams.m_simplify_clauses)
            simplify_clauses();
        if (m_fparams.m_lemma_gc_strategy == LGC_AT_RESTART)
            del_inactive_lemmas();

        status = l_undef;
        return true;
    }

    void context::tick(unsigned & counter) const {
        counter++;
        if (counter > m_fparams.m_tick) {
            IF_VERBOSE(3, verbose_stream() << "(smt.working";
                       verbose_stream() << " :conflicts " << m_num_conflicts;
                       // verbose_stream() << " lemma avg. activity: " << get_lemma_avg_activity();
                       if (m_fparams.m_restart_adaptive)
                       verbose_stream() << " :agility " << m_agility;
                       verbose_stream() << ")" << std::endl; verbose_stream().flush(););
            TRACE(assigned_literals_per_lvl, display_num_assigned_literals_per_lvl(tout); tout << "\n";);
            counter = 0;
        }
    }

    lbool context::bounded_search() {
        unsigned counter = 0;

        TRACE(bounded_search, tout << "starting bounded search...\n";);

        while (true) {
            while (!propagate()) {
                TRACE_CODE({
                    static bool first_propagate = true;
                    if (first_propagate) {
                        first_propagate = false;
                        TRACE(after_first_propagate, display(tout););
                    }
                });

                tick(counter);

                if (!resolve_conflict())
                    return l_false;

                SASSERT(m_scope_lvl >= m_base_lvl);

                if (!inconsistent()) {
                    if (resource_limits_exceeded())
                        return l_undef;

                    if (get_cancel_flag())
                        return l_undef;

                    if (m_num_conflicts_since_restart > m_restart_threshold && m_scope_lvl - m_base_lvl > 2) {
                        TRACE(search_bug, tout << "bounded-search return undef, inconsistent: " << inconsistent() << "\n";);
                        return l_undef; // restart
                    }

                    if (m_num_conflicts > m_fparams.m_max_conflicts) {
                        TRACE(search_bug, tout << "bounded-search return undef, inconsistent: " << inconsistent() << "\n";);
                        m_last_search_failure = NUM_CONFLICTS;
                        return l_undef;
                    }
                }

                if (m_num_conflicts_since_lemma_gc > m_lemma_gc_threshold &&
                    (m_fparams.m_lemma_gc_strategy == LGC_FIXED || m_fparams.m_lemma_gc_strategy == LGC_GEOMETRIC)) {
                    del_inactive_lemmas();
                }

                m_dyn_ack_manager.propagate_eh();
                CASSERT("dyn_ack", check_clauses(m_lemmas) && check_clauses(m_aux_clauses));
            }

            if (resource_limits_exceeded() && !inconsistent()) {
                return l_undef;
            }

            if (get_cancel_flag())
                return l_undef;

            if (m_base_lvl == m_scope_lvl && m_fparams.m_simplify_clauses)
                simplify_clauses();

            if (!decide()) {
                if (inconsistent()) 
                    return l_false;
                final_check_status fcs = final_check();
                TRACE(final_check_result, tout << "fcs: " << fcs << " last_search_failure: " << m_last_search_failure << "\n";);
                switch (fcs) {
                case FC_DONE:
                    log_stats();
                    return l_true;
                case FC_CONTINUE:
                    break;
                case FC_GIVEUP:
                    return l_undef;
                }
            }

            if (resource_limits_exceeded() && !inconsistent()) {
                return l_undef;
            }
        }
    }

    bool context::resource_limits_exceeded() {
        if (m_searching) {
            // Some of the flags only make sense to check when searching.
            // For example, the timer is only started in init_search().
            if (m_last_search_failure != OK)
                return true;

            if (get_cancel_flag()) 
                return true;            

            if (m_progress_callback) {
                m_progress_callback->fast_progress_sample();
                if (m_fparams.m_progress_sampling_freq > 0 && m_timer.ms_timeout(m_next_progress_sample + 1)) {
                    m_progress_callback->slow_progress_sample();
                    m_next_progress_sample = (unsigned)(m_timer.get_seconds() * 1000) + m_fparams.m_progress_sampling_freq;
                }
            }
        }

        if (get_cancel_flag()) 
            return true;        

        if (memory::above_high_watermark()) {
            m_last_search_failure = MEMOUT;
            return true;
        }

        return false;
    }

    final_check_status context::final_check() {
        TRACE(final_check, tout << "final_check inconsistent: " << inconsistent() << "\n"; display(tout); display_normalized_enodes(tout););
        CASSERT("relevancy", check_relevancy());
        
        if (m_fparams.m_model_on_final_check) {
            mk_proto_model();
            model_pp(std::cout, *m_proto_model);
            std::cout << "END_OF_MODEL" << std::endl;
        }

        m_stats.m_num_final_checks++;
        TRACE(final_check_stats, tout << "m_stats.m_num_final_checks = " << m_stats.m_num_final_checks << "\n";);

        final_check_status ok = m_qmanager->final_check_eh(false);
        if (ok != FC_DONE)
            return ok;

        m_incomplete_theories.reset();

        unsigned old_idx          = m_final_check_idx;
        unsigned num_th           = m_theory_set.size();
        unsigned range            = num_th + 1;
        final_check_status result = FC_DONE;
        failure  f                = OK;

        do {
            TRACE(final_check_step, tout << "processing: " << m_final_check_idx << ", result: " << result << "\n";);
            final_check_status ok;
            if (m_final_check_idx < num_th) {
                theory * th = m_theory_set[m_final_check_idx];
                IF_VERBOSE(100, verbose_stream() << "(smt.final-check \"" << th->get_name() << "\")\n";);
                ok = th->final_check_eh();
                TRACE(final_check_step, tout << "final check '" << th->get_name() << " ok: " << ok << " inconsistent " << inconsistent() << "\n";);
                if (get_cancel_flag()) {
                    f = CANCELED;
                    ok = FC_GIVEUP;
                }
                else if (ok == FC_GIVEUP) {
                    f  = THEORY;
                    m_incomplete_theories.push_back(th);
                }
            }
            else {
                ok = m_qmanager->final_check_eh(true);
                TRACE(final_check_step, tout << "quantifier  ok: " << ok << " " << "inconsistent " << inconsistent() << "\n";);
            }

            m_final_check_idx = (m_final_check_idx + 1) % range;
            // IF_VERBOSE(1000, verbose_stream() << "final check status: " << ok << "\n";);

            switch (ok) {
            case FC_DONE:
                break;
            case FC_GIVEUP:
                result = FC_GIVEUP;
                break;
            case FC_CONTINUE:
                return FC_CONTINUE;
                break;
            }
        }
        while (m_final_check_idx != old_idx);

        TRACE(final_check_step, tout << "result: " << result << "\n";);

        if (can_propagate()) {
            TRACE(final_check_step, tout << "can propagate: continue...\n";);
            return FC_CONTINUE;
        }

        SASSERT(result != FC_DONE || check_th_diseq_propagation());
        TRACE(final_check_step, tout << "RESULT final_check: " << result << "\n";);
        if (result == FC_GIVEUP && f != OK)
            m_last_search_failure = f;
        if (result == FC_DONE && has_lambda()) {
            m_last_search_failure = LAMBDAS;
            result = FC_GIVEUP;
        }
        return result;
    }

    void context::check_proof(proof * pr) {
        if (m.proofs_enabled() && m_fparams.m_check_proof) {
            proof_checker pf(m);
            expr_ref_vector side_conditions(m);
            pf.check(pr, side_conditions);
        }
    }

    void context::forget_phase_of_vars_in_current_level() {
        unsigned head = m_scope_lvl == 0 ? 0 : m_scopes[m_scope_lvl - 1].m_assigned_literals_lim;
        unsigned sz   = m_assigned_literals.size();
        for (unsigned i = head; i < sz; i++) {
            literal l  = m_assigned_literals[i];
            bool_var v = l.var();
            TRACE(forget_phase, tout << "forgetting phase of l: " << l << "\n";);
            m_bdata[v].m_phase_available = false;
        }
    }


    bool context::resolve_conflict() {
        m_stats.m_num_conflicts++;
        m_num_conflicts ++;
        m_num_conflicts_since_restart ++;
        m_num_conflicts_since_lemma_gc ++;
        switch (m_conflict.get_kind()) {
        case b_justification::CLAUSE:
        case b_justification::BIN_CLAUSE:
            m_stats.m_num_sat_conflicts++;
            break;
        default:
            break;
        }
        if (m_fparams.m_phase_selection == PS_THEORY || 
            m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE || 
            m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2)
            forget_phase_of_vars_in_current_level();
        m_atom_propagation_queue.reset();
        m_eq_propagation_queue.reset();
        m_th_eq_propagation_queue.reset();
        m_th_diseq_propagation_queue.reset();
        if (m_conflict_resolution->resolve(m_conflict, m_not_l)) {
            unsigned new_lvl = m_conflict_resolution->get_new_scope_lvl();
            unsigned num_lits = m_conflict_resolution->get_lemma_num_literals();
            literal * lits    = m_conflict_resolution->get_lemma_literals();

            SASSERT(num_lits > 0);
            unsigned conflict_lvl = get_assign_level(lits[0]);
            SASSERT(conflict_lvl <= m_scope_lvl);

            // When num_lits == 1, then the default behavior is to go
            // to base-level. If the problem has quantifiers, it may be
            // too expensive to do that, since all instances will need to
            // be recreated. If that is the case, I store the assertions in
            // a special vector and keep reasserting whenever I backtrack.
            // Moreover, I backtrack only one level.
            bool delay_forced_restart =
                m_fparams.m_delay_units &&
                num_lits == 1 &&
                conflict_lvl > m_search_lvl + 1 &&
                !m.proofs_enabled() &&
                m_units_to_reassert.size() < m_fparams.m_delay_units_threshold;

            if (delay_forced_restart) {
                new_lvl = conflict_lvl - 1;
            }

            // Some of the literals/enodes of the conflict clause will be destroyed during
            // backtracking, and will need to be recreated. However, I want to keep
            // the generation number for enodes that are going to be recreated. See
            // comment in cache_generation(unsigned).
            if (m_conflict_resolution->get_lemma_intern_lvl() > new_lvl)
                cache_generation(num_lits, lits, new_lvl);

            SASSERT(new_lvl < m_scope_lvl);
            TRACE(resolve_conflict_bug,
                  tout << "m_scope_lvl: " << m_scope_lvl << ", new_lvl: " << new_lvl << ", lemma_intern_lvl: " << m_conflict_resolution->get_lemma_intern_lvl() << "\n";
                  tout << "num_lits: " << num_lits << "\n";
                  for (unsigned i = 0; i < num_lits; i++) {
                      literal l = lits[i];
                      tout << l << " ";
                      display_literal_smt2(tout, l);
                      tout << ", ilvl: " << get_intern_level(l.var()) << "\n"
                           << mk_pp(bool_var2expr(l.var()), m) << "\n";
                  });

            if (m.has_trace_stream() && !m_is_auxiliary) {
                m.trace_stream() << "[conflict] ";
                display_literals(m.trace_stream(), num_lits, lits);
                m.trace_stream() << "\n";
            }

#ifdef Z3DEBUG
            expr_ref_vector expr_lits(m);
            bool_vector   expr_signs;
            for (unsigned i = 0; i < num_lits; i++) {
                literal l = lits[i];
                if (get_assignment(l) != l_false) {
                    std::cout << l << " " << get_assignment(l) << "\n";
                }
                SASSERT(get_assignment(l) == l_false);
                expr_lits.push_back(bool_var2expr(l.var()));
                expr_signs.push_back(l.sign());
            }
#endif
            proof * pr = nullptr;
            if (m.proofs_enabled()) {
                pr = m_conflict_resolution->get_lemma_proof();
                // check_proof(pr);
                TRACE(context_proof, tout << mk_ll_pp(pr, m););
                TRACE(context_proof_hack,
                      static ast_mark visited;
                      ast_ll_pp(tout, m, pr, visited););
            }
            // I invoke pop_scope_core instead of pop_scope because I don't want
            // to reset cached generations... I need them to rebuild the literals
            // of the new conflict clause.
            if (relevancy()) record_relevancy(num_lits, lits);
            unsigned num_bool_vars = pop_scope_core(m_scope_lvl - new_lvl);
            SASSERT(m_scope_lvl == new_lvl);
            // the logical context may still be in conflict after
            // clauses are reinitialized in pop_scope.
            if (m_conflict_resolution->get_lemma_intern_lvl() > m_scope_lvl) {
                expr * * atoms         = m_conflict_resolution->get_lemma_atoms();
                for (unsigned i = 0; i < num_lits; i++) {
                    literal l   = lits[i];
                    if (l.var() >= num_bool_vars) {
                        // This boolean variable was deleted during backtracking, it need to be recreated.
                        // Remark: atom may be a negative literal (not a). Z3 creates Boolean variables for not-gates that
                        // are nested in terms. Example: let f be a uninterpreted function from Bool -> Int.
                        // Then, given the term (f (not a)), Z3 will create a boolean variable for (not a) when internalizing (f (not a)).
                        expr * atom     = atoms[i];
                        internalize(atom, true);
                        // If atom is actually a negative literal (not a), then get_bool_var will return return null_bool_var.
                        // Thus, we must use get_literal instead. This was a bug/crash in Z3 <= 4.0
                        literal new_l = get_literal(atom);
                        if (l.sign())
                            new_l.neg();
                        // For reference, here is the buggy version
                        // BEGIN BUGGY VERSION
                        // bool_var v = get_bool_var(atom);
                        // CTRACE(resolve_conflict_crash, v == null_bool_var, tout << mk_ismt2_pp(atom, m) << "\n";);
                        // SASSERT(v != null_bool_var);
                        // literal new_l   = literal(v, l.sign());
                        // END BUGGY VERSION
                        lits[i]         = new_l;
                    }
                }
            }
            if (relevancy()) restore_relevancy(num_lits, lits);
            // Resetting the cache manually because I did not invoke pop_scope, but pop_scope_core
            reset_cache_generation();
            TRACE(resolve_conflict_bug,
                  tout << "AFTER m_scope_lvl: " << m_scope_lvl << ", new_lvl: " << new_lvl << ", lemma_intern_lvl: " <<
                  m_conflict_resolution->get_lemma_intern_lvl() << "\n";
                  tout << "num_lits: " << num_lits << "\n";
                  for (unsigned i = 0; i < num_lits; i++) {
                      literal l = lits[i];
                      tout << l << " ";
                      display_literal(tout, l);
                      tout << ", ilvl: " << get_intern_level(l.var()) << "\n"
                           << mk_pp(bool_var2expr(l.var()), m) << "\n";
                  });
#ifdef Z3DEBUG
            for (unsigned i = 0; i < num_lits; i++) {
                literal l = lits[i];
                if (expr_signs[i] != l.sign()) {
                    expr* real_atom;
                    VERIFY(m.is_not(expr_lits.get(i), real_atom));
                    // the sign must have flipped when internalizing
                    CTRACE(resolve_conflict_bug, real_atom != bool_var2expr(l.var()), tout << mk_pp(real_atom, m) << "\n" << mk_pp(bool_var2expr(l.var()), m) << "\n";);
                    SASSERT(real_atom == bool_var2expr(l.var()));
                }
                else {
                    SASSERT(expr_lits.get(i) == bool_var2expr(l.var()));
                }
            }
#endif
            justification * js = nullptr;
            if (m.proofs_enabled()) {
                js = alloc(justification_proof_wrapper, *this, pr, false);
            }
#if 0
            {
                static unsigned counter = 0;
                static uint64_t total = 0;
                static unsigned max = 0;
                counter++;
                total += num_lits;
                if (num_lits > max) {
                    max = num_lits;
                }
                if (counter % 1000 == 0) {
                    verbose_stream() << "[sat] avg. clause size: " << ((double) total/(double) counter) << ", max: " << max << std::endl;
                    for (unsigned i = 0; i < num_lits; i++) {
                        literal l = lits[i];
                        verbose_stream() << l.sign() << " " << mk_pp(bool_var2expr(l.var()), m) << "\n";
                    }
                }
            }
#endif
            mk_clause(num_lits, lits, js, CLS_LEARNED);
            if (delay_forced_restart) {
                SASSERT(num_lits == 1);
                expr * unit     = bool_var2expr(lits[0].var());
                bool unit_sign  = lits[0].sign();
                while (m.is_not(unit, unit))
                    unit_sign = !unit_sign;
                m_units_to_reassert.push_back({ expr_ref(unit, m), unit_sign, is_relevant(unit) });
                TRACE(reassert_units, tout << "asserting " << mk_pp(unit, m) << " #" << unit->get_id() << " " << unit_sign << " @ " << m_scope_lvl << "\n";);
            }

            m_conflict_resolution->release_lemma_atoms();
            TRACE(context_lemma, tout << "new lemma: ";
                  literal_vector v(num_lits, lits);
                  std::sort(v.begin(), v.end());
                  for (unsigned i = 0; i < num_lits; i++) {
                      display_literal(tout, v[i]);
                      tout << "\n";
                      smt::display(tout, v[i], m, m_bool_var2expr.data());
                      tout << "\n\n";
                  }
                  tout << "\n";);
            decay_bvar_activity();
            update_phase_cache_counter();
            return true;
        }
        else if (m_fparams.m_clause_proof && !m.proofs_enabled()) {
            m_unsat_proof = m_clause_proof.get_proof(inconsistent());
        }
        else if (m.proofs_enabled()) {
            m_unsat_proof = m_conflict_resolution->get_lemma_proof();
            check_proof(m_unsat_proof);
        }
        return false;
    }

    /*
      \brief we record and restore relevancy information for literals in conflict clauses.
      A literal may have been marked relevant within the scope that gets popped during
      conflict resolution. In this case, the literal is no longer marked as relevant after
      the pop. This can cause quantifier instantiation to miss relevant triggers and thereby
      cause incompleteness.
     */
    void context::record_relevancy(unsigned n, literal const* lits) {
        m_relevant_conflict_literals.reset();
        for (unsigned i = 0; i < n; ++i) {
            m_relevant_conflict_literals.push_back(is_relevant(lits[i]));
        }
    }

    void context::restore_relevancy(unsigned n, literal const* lits) {
        for (unsigned i = 0; i < n; ++i) {
            if (m_relevant_conflict_literals[i] && !is_relevant(lits[i])) {
                mark_as_relevant(lits[i]);
            }
        }
    }

    void context::get_relevant_labels(expr* cnstr, buffer<symbol> & result) {
        if (m_fparams.m_check_at_labels) {
            check_at_labels checker(m);
            if (cnstr && !checker.check(cnstr)) {
                warning_msg("Boogie generated formula that can require multiple '@' labels in a counter-example");
            }
            else {
                unsigned nf = m_asserted_formulas.get_num_formulas();
                for (unsigned i = 0; i < nf; ++i) {
                    expr* fml = m_asserted_formulas.get_formula(i);
                    if (!checker.check(fml)) {
                        warning_msg("Boogie generated formula that can require multiple '@' labels in a counter-example");
                        break;
                    }
                }
            }
        }

        SASSERT(!inconsistent());
        for (expr * curr : m_b_internalized_stack) { 
            if (is_relevant(curr) && get_assignment(curr) == l_true) {
                // if curr is a label literal, then its tags will be copied to result.
                m.is_label_lit(curr, result);
            }
        }
    }

    /**
       \brief Collect relevant literals that may be used to block the current assignment.
       If at_lbls is true, then only labels that contains '@' are considered. (This is a hack for Boogie).
       This hack is also available in the Simplify theorem prover.
    */
    void context::get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) {
        SASSERT(!inconsistent());
        buffer<symbol> lbls;
        for (expr * curr : m_b_internalized_stack) {
            if (is_relevant(curr) && get_assignment(curr) == l_true) {
                lbls.reset();
                if (m.is_label_lit(curr, lbls)) {
                    bool include = false;
                    if (at_lbls) {
                        // include if there is a label with the '@' sign.
                        for (symbol const& s : lbls) {
                            if (s.contains('@')) {
                                include = true;
                                break;
                            }
                        }
                    }
                    else {
                        include = true;
                    }
                    if (include)
                        result.push_back(curr);
                }
            }
        }
    }

    /**
       \brief Store in result the (relevant) literal assigned by the
       logical context.
    */
    void context::get_relevant_literals(expr_ref_vector & result) {
        SASSERT(!inconsistent());
        unsigned sz = m_b_internalized_stack.size();
        for (unsigned i = 0; i < sz; i++) {
            expr * curr = m_b_internalized_stack.get(i);
            if (is_relevant(curr)) {
                switch (get_assignment(curr)) {
                case l_true:
                    result.push_back(curr);
                    break;
                case l_false:
                    result.push_back(m.mk_not(curr));
                    break;
                default:
                    break;
                }
            }
        }
    }

    /**
       \brief Store the current set of guessed literals (i.e., case splits).
    */
    void context::get_guessed_literals(expr_ref_vector & result) {
        // The literals between [m_base_lvl, m_search_lvl) are not guesses but assumptions.
        SASSERT(m_base_lvl <= m_scopes.size());
        if (m_search_lvl == m_scopes.size()) {
            // do nothing... there are guesses...
        }
        for (unsigned i = m_search_lvl; i < m_scope_lvl; i++) {
            // This method assumes the first literal assigned in a non base scope level is a guess.
            scope & s          = m_scopes[i];
            unsigned guess_idx = s.m_assigned_literals_lim;
            literal guess      = m_assigned_literals[guess_idx];
            SASSERT(get_justification(guess.var()).get_kind() == b_justification::AXIOM);
            expr_ref lit(m);
            literal2expr(guess, lit);
            result.push_back(std::move(lit));
        }
    }

    /**
       \brief Undo object for bool var m_true_first field update.
    */
    class set_true_first_trail : public trail {
        context& ctx;
        bool_var m_var;
    public:
        set_true_first_trail(context& ctx, bool_var v): ctx(ctx), m_var(v) {}
        void undo() override {
            ctx.m_bdata[m_var].reset_true_first_flag();
        }
    };

    void context::set_true_first_flag(bool_var v) {
        push_trail(set_true_first_trail(*this, v));
        bool_var_data & d = m_bdata[v];
        d.set_true_first_flag();
    }

    bool context::assume_eq(enode * lhs, enode * rhs) {
        if (lhs->get_root() == rhs->get_root())
            return false; // it is not necessary to assume the eq.
        expr * _lhs = lhs->get_expr();
        expr * _rhs = rhs->get_expr();
        expr * eq = mk_eq_atom(_lhs, _rhs);
        TRACE(assume_eq, tout << "creating interface eq:\n" << mk_pp(eq, m) << "\n";);
        if (m.is_false(eq)) {
            return false;
        }
        bool r = false;
        if (!b_internalized(eq)) {
            // I do not invoke internalize(eq, true), because I want to
            // mark the try_true_first flag before invoking theory::internalize_eq_eh.
            // Reason: Theories like arithmetic should be able to know if the try_true_first flag is
            // marked or not. They use this information to also mark auxiliary atoms such as:
            //  (<= (- x y) 0)
            //  (>= (- y x) 0)
            // for the new equality atom (= x y).
            if (m.is_eq(eq)) {
                internalize_formula_core(to_app(eq), true);
                bool_var v        = get_bool_var(eq);
                bool_var_data & d = get_bdata(v);
                d.set_eq_flag();
                set_true_first_flag(v);
                sort * s    = to_app(eq)->get_arg(0)->get_sort();
                theory * th = m_theories.get_plugin(s->get_family_id());
                if (th)
                    th->internalize_eq_eh(to_app(eq), v);
            }
            else {
                internalize(eq, true);
            }
            r = true;
            m_stats.m_num_interface_eqs++;
            TRACE(assume_eq, tout << "new internalization.\n";);
        }
        bool_var v        = get_bool_var(eq);
        bool_var_data & d = m_bdata[v];
        if (!d.try_true_first()) {
            set_true_first_flag(v);
            r = true;
            TRACE(assume_eq, tout << "marked as ieq.\n";);
        }
        if (get_assignment(v) == l_undef) {
            TRACE(assume_eq, tout << "variable is unassigned.\n";);
            r = true;
        }
        if (relevancy() && !is_relevant(eq)) {
            TRACE(assume_eq, tout << "marking eq as relevant.\n";);
            mark_as_relevant(eq);
            r = true;
        }
        TRACE(assume_eq, tout << "variable value: " << get_assignment(v) << "\n";);
        TRACE(assume_eq, tout << "assume_eq result: " << r << "\n";);
        return r;
    }

    bool context::is_shared(enode * n) const {
        n = n->get_root();
        switch (n->is_shared()) {
        case l_true: return true;
        case l_false: return false;
        default: break;
        }

        unsigned num_th_vars = n->get_num_th_vars();
        if (m.is_ite(n->get_expr())) {
            n->set_is_shared(l_true);
            return true;
        }
        switch (num_th_vars) {
        case 0: {
            return false;
        }
        case 1: {
            if (m_qmanager->is_shared(n) && !m.is_lambda_def(n->get_expr()) && !m_lambdas.contains(n)) 
                return true;

            // the variable is shared if the equivalence class of n
            // contains a parent application.

            theory_var_list * l = n->get_th_var_list();
            theory_id th_id     = l->get_id();

            for (enode * parent : enode::parents(n)) {
                app* p = parent->get_expr();
                family_id fid = p->get_family_id();
                if (fid != th_id && fid != m.get_basic_family_id()) {
                    if (is_beta_redex(parent, n))
                        continue;
                    TRACE(is_shared, tout << enode_pp(n, *this) 
                          << "\nis shared because of:\n" 
                          << enode_pp(parent, *this) << "\n";);
                    n->set_is_shared(l_true);
                    return true;
                }
            }

            // Some theories implement families of theories. Examples:
            // Arrays and Tuples.  For example, array theory is a
            // parametric theory, that is, it implements several theories:
            // (array int int), (array int (array int int)), ...
            //
            // Example:
            //
            // a : (array int int)
            // b : (array int int)
            // x : int
            // y : int
            // v : int
            // w : int
            // A : (array (array int int) int)
            //
            // assert (= b (store a x v))
            // assert (= b (store a y w))
            // assert (not (= x y))
            // assert (not (select A a))
            // assert (not (select A b))
            // check
            //
            // In the example above, 'a' and 'b' are shared variables between
            // the theories of (array int int) and (array (array int int) int).
            // Remark: The inconsistency is not going to be detected if they are
            // not marked as shared.
            bool r = get_theory(th_id)->is_shared(l->get_var());
            n->set_is_shared(to_lbool(r));
            return r;
        }
        default:
            return true;
        }
    }

    bool context::is_beta_redex(enode* p, enode* n) const {
        family_id th_id = p->get_expr()->get_family_id();
        theory * th = get_theory(th_id);
        return th && th->is_beta_redex(p, n);
    }

    bool context::get_value(enode * n, expr_ref & value) {
        sort * s      = n->get_sort();
        family_id fid = s->get_family_id();
        theory * th   = get_theory(fid);
        if (th == nullptr)
            return false;
        return th->get_value(n, value);
    }   

    void context::solve_for(vector<solution>& sol) {
        for (auto th : m_theories)
            if (th)
                th->solve_for(sol);
    }

    bool context::update_model(bool refinalize) {
        final_check_status fcs = FC_DONE;
        if (refinalize) {
            if (has_case_splits())
                return false;
            fcs = final_check();
            TRACE(opt, tout << (refinalize?"refinalize":"no-op") << " " << fcs << "\n";);
        }
        if (fcs == FC_DONE) {
            reset_model();
        }
        return false;
    }

    void context::mk_proto_model() {
        if (m_model || m_proto_model || has_case_splits()) return;
        TRACE(get_model,
              display(tout);
              display_normalized_enodes(tout);
              display_enodes_lbls(tout);
              m_fingerprints.display(tout);
              );
        failure fl = get_last_search_failure();
        if (fl == MEMOUT || fl == CANCELED || fl == NUM_CONFLICTS || fl == RESOURCE_LIMIT) {
            TRACE(get_model, tout << "last search failure: " << fl << "\n";);   
        }     
        else if (m_fparams.m_model || m_fparams.m_model_on_final_check || 
                 (m_qmanager->has_quantifiers() && m_qmanager->model_based())) {
            m_model_generator->reset();
            m_proto_model = m_model_generator->mk_model();
            m_qmanager->adjust_model(m_proto_model.get());
            TRACE(mbqi_bug, tout << "before complete_partial_funcs:\n"; model_pp(tout, *m_proto_model););
            m_proto_model->complete_partial_funcs(false);
            TRACE(mbqi_bug, tout << "before cleanup:\n"; model_pp(tout, *m_proto_model););
            m_proto_model->cleanup();
            TRACE(mbqi_bug, tout << "after cleanup:\n"; model_pp(tout, *m_proto_model););
            IF_VERBOSE(11, model_pp(verbose_stream(), *m_proto_model););
        }
    }

    proof * context::get_proof() {        
        if (!m_unsat_proof) {
            m_unsat_proof = m_clause_proof.get_proof(inconsistent());
        }
        TRACE(context, tout << m_unsat_proof << "\n";);
        return m_unsat_proof;
    }

    bool context::has_case_splits() {
        if (!m_has_case_split)
            return false;
        
        for (unsigned i = get_num_b_internalized(); i-- > 0; ) {
            if (is_relevant(i) && get_assignment(i) == l_undef)
                return true;
        }
        return false;
    }

    void context::get_model(model_ref & mdl) {
        if (inconsistent())
            mdl = nullptr;
        else if (m_model.get())
            mdl = m_model.get();
        else if (!m.inc())
            mdl = nullptr;
        else if (!m_search_finalized)
            mdl = nullptr;
        else {
            mk_proto_model();
            if (!m_model && m_proto_model) {
                m_model = m_proto_model->mk_model();
                try {
                    add_rec_funs_to_model();
                }
                catch (...) {
                    // no op
                }                
            }
            mdl = m_model.get();
        }
    }

    void context::get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) {
        unsigned sz = vars.size(); 
        depth.resize(sz);
        for (unsigned i = 0; i < sz; ++i) {
            expr* v = vars[i];
            bool_var bv = m_expr2bool_var.get(v->get_id(), null_bool_var);
            depth[i] = bv == null_bool_var ? UINT_MAX : get_assign_level(bv);            
        }
    }

    expr_ref_vector context::get_trail(unsigned max_level) {        
        expr_ref_vector result(get_manager());
        for (literal lit : m_assigned_literals) {
            if (get_assign_level(lit) > max_level + m_base_lvl)
                continue;
            expr_ref e(m);
            literal2expr(lit, e);
            result.push_back(std::move(e));
        }
        return result;
    }

    void context::get_units(expr_ref_vector& result) {
        expr_mark visited;
        for (expr* fml : result)
            visited.mark(fml);
        expr_ref_vector trail = get_trail(0);
        for (expr* t : trail) 
            if (!visited.is_marked(t))
                result.push_back(t);
    }


    failure context::get_last_search_failure() const {
        return m_last_search_failure;
    }

    void context::add_rec_funs_to_model() {
        model_params p;
        auto smtlib2_compliant = gparams::get_value("smtlib2_compliant");
        
        if (m_model && p.user_functions() && smtlib2_compliant != "true")
            m_model->add_rec_funs();
    }

};


#ifdef Z3DEBUG
void pp(smt::context & c) {
    c.display(std::cout);
}
#endif
