/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    smt_model_finder.cpp

Abstract:

    Model finding goodies for universally quantified formulas.

Author:

    Leonardo de Moura (leonardo) 2010-12-17.

Revision History:

--*/
#include "util/backtrackable_set.h"
#include "ast/ast_util.h"
#include "ast/macros/macro_util.h"
#include "ast/arith_decl_plugin.h"
#include "ast/bv_decl_plugin.h"
#include "ast/array_decl_plugin.h"
#include "ast/normal_forms/pull_quant.h"
#include "ast/rewriter/var_subst.h"
#include "ast/macros/cond_macro.h"
#include "ast/macros/quantifier_macro_info.h"
#include "ast/for_each_expr.h"
#include "ast/ast_pp.h"
#include "ast/ast_ll_pp.h"
#include "ast/well_sorted.h"
#include "ast/ast_smt2_pp.h"
#include "model/model_pp.h"
#include "model/model_macro_solver.h"
#include "smt/smt_model_finder.h"
#include "smt/smt_context.h"
#include "tactic/tactic_exception.h"

namespace smt {

    namespace mf {

        // -----------------------------------
        //
        // Auxiliary stuff
        //
        // -----------------------------------


        class evaluator {
        public:
            virtual ~evaluator() = default;
            virtual expr* eval(expr* n, bool model_completion) = 0;
        };

        // -----------------------------------
        //
        // Instantiation sets
        //
        // -----------------------------------

        /**
           \brief Instantiation sets are the S_{k,j} sets in the Complete quantifier instantiation paper.
        */
        class instantiation_set {
            ast_manager&            m;
            obj_map<expr, unsigned> m_elems; // and the associated generation
            obj_map<expr, expr*>    m_inv;
            expr_mark               m_visited;
        public:
            instantiation_set(ast_manager& m) :m(m) {}

            ~instantiation_set() {
                for (auto const& kv : m_elems) {
                    m.dec_ref(kv.m_key);
                }
            }

            obj_map<expr, unsigned> const& get_elems() const { return m_elems; }

            void insert(expr* n, unsigned generation) {
                if (m_elems.contains(n) || contains_model_value(n)) {
                    return;
                }
                m.inc_ref(n);
                m_elems.insert(n, generation);
                SASSERT(!m.is_model_value(n));
            }

            void remove(expr* n) {
                // We can only remove n if it is in m_elems, AND m_inv was not initialized yet.
                SASSERT(m_elems.contains(n));
                SASSERT(m_inv.empty());
                m_elems.erase(n);
                TRACE(model_finder, tout << mk_pp(n, m) << "\n";);
                m.dec_ref(n);
            }

            void display(std::ostream& out) const {
                for (auto const& kv : m_elems) {
                    out << mk_bounded_pp(kv.m_key, m) << " [" << kv.m_value << "]\n";
                }
                out << "inverse:\n";
                for (auto const& kv : m_inv) {
                    out << mk_bounded_pp(kv.m_key, m) << " -> " << mk_bounded_pp(kv.m_value, m) << "\n";
                }
            }

            expr* get_inv(expr* v) const {
                expr* t = nullptr;
                m_inv.find(v, t);
                return t;
            }

            unsigned get_generation(expr* t) const {
                unsigned gen = 0;
                m_elems.find(t, gen);
                return gen;
            }

            void mk_inverse(evaluator& ev) {
                for (auto const& kv : m_elems) {
                    expr* t = kv.m_key;
                    SASSERT(!contains_model_value(t));
                    unsigned gen = kv.m_value;
                    expr* t_val = ev.eval(t, true);
                    if (!t_val) break;
                    TRACE(model_finder, tout << mk_pp(t, m) << " " << mk_pp(t_val, m) << "\n";);

                    expr* old_t = nullptr;
                    if (m_inv.find(t_val, old_t)) {
                        unsigned old_t_gen = 0;
                        SASSERT(m_elems.contains(old_t));
                        m_elems.find(old_t, old_t_gen);
                        if (gen < old_t_gen) {
                            m_inv.insert(t_val, t);
                        }
                    }
                    else {
                        m_inv.insert(t_val, t);
                    }
                }
            }

            obj_map<expr, expr*> const& get_inv_map() const {
                return m_inv;
            }

            struct is_model_value : public std::exception {};
            void operator()(expr* n) {
                if (m.is_model_value(n)) {
                    throw is_model_value();
                }
            }

            bool contains_model_value(expr* n) {
                if (m.is_model_value(n)) {
                    return true;
                }
                if (is_app(n) && to_app(n)->get_num_args() == 0) {
                    return false;
                }
                m_visited.reset();
                try {
                    for_each_expr(*this, m_visited, n);
                }
                catch (const is_model_value&) {
                    return true;
                }
                return false;
            }
        };


        /**
           During model construction time,
           we solve several constraints that impose restrictions
           on how the model for the ground formulas may be extended to
           a model to the relevant universal quantifiers.

           The class node and its subclasses are used to solve
           these constraints.
        */

        /**
           \brief Base class used to solve model construction constraints.
        */
        class node {
            unsigned            m_id;
            node* m_find{ nullptr };
            unsigned            m_eqc_size{ 1 };

            sort* m_sort; // sort of the elements in the instantiation set.

            bool                m_mono_proj{ false };     // relevant for integers & reals & bit-vectors
            bool                m_signed_proj{ false };   // relevant for bit-vectors.
            ptr_vector<node>    m_avoid_set;
            ptr_vector<expr>    m_exceptions;

            scoped_ptr<instantiation_set> m_set;
            expr* m_else{ nullptr };
            func_decl* m_proj{ nullptr };

            // Append the new elements of v2 into v1. v2 should not be used after this operation, since it may suffer destructive updates.
            template<typename T>
            void dappend(ptr_vector<T>& v1, ptr_vector<T>& v2) {
                if (v2.empty())
                    return;
                if (v1.empty()) {
                    v1.swap(v2);
                    return;
                }
                for (T* t : v2) {
                    if (!v1.contains(t))
                        v1.push_back(t);
                }
                v2.finalize();
            }
        public:
            node(unsigned id, sort* s) :
                m_id(id),
                m_sort(s) {
            }

            unsigned get_id() const { return m_id; }

            sort* get_sort() const { return m_sort; }

            bool is_root() const { return m_find == nullptr; }

            node* get_root() const {
                node* curr = const_cast<node*>(this);
                while (!curr->is_root()) {
                    curr = curr->m_find;
                }
                SASSERT(curr->is_root());
                return curr;
            }

            void merge(node* other) {
                node* r1 = get_root();
                node* r2 = other->get_root();
                SASSERT(!r1->m_set);
                SASSERT(!r2->m_set);
                SASSERT(r1->get_sort() == r2->get_sort());
                if (r1 == r2)
                    return;
                if (r1->m_eqc_size > r2->m_eqc_size)
                    std::swap(r1, r2);
                r1->m_find = r2;
                r2->m_eqc_size    += r1->m_eqc_size;
                r2->m_mono_proj   |= r1->m_mono_proj;
                r2->m_signed_proj |= r1->m_signed_proj;
                dappend(r2->m_avoid_set, r1->m_avoid_set);
                dappend(r2->m_exceptions, r1->m_exceptions);
            }

            void insert_avoid(node* n) {
                ptr_vector<node>& as = get_root()->m_avoid_set;
                if (!as.contains(n))
                    as.push_back(n);
            }

            void insert_exception(expr* n) {
                ptr_vector<expr>& ex = get_root()->m_exceptions;
                if (!ex.contains(n))
                    ex.push_back(n);
            }

            void set_mono_proj() {
                get_root()->m_mono_proj = true;
            }

            bool is_mono_proj() const {
                return get_root()->m_mono_proj;
            }

            void set_signed_proj() {
                get_root()->m_signed_proj = true;
            }

            bool is_signed_proj() const {
                return get_root()->m_signed_proj;
            }

            void mk_instantiation_set(ast_manager& m) {
                SASSERT(is_root());
                SASSERT(!m_set);
                m_set = alloc(instantiation_set, m);
            }

            void insert(expr* n, unsigned generation) {
                SASSERT(is_ground(n));
                get_root()->m_set->insert(n, generation);
            }

            void display(std::ostream& out, ast_manager& m) const {
                if (is_root()) {
                    out << "root node ------\n";
                    out << "@" << m_id << " mono: " << m_mono_proj << " signed: " << m_signed_proj << ", sort: " << mk_pp(m_sort, m) << "\n";
                    out << "avoid-set: ";
                    for (node* n : m_avoid_set) {
                        out << "@" << n->get_root()->get_id() << " ";
                    }
                    out << "\n";
                    out << "exceptions: ";
                    for (expr* e : m_exceptions) {
                        out << mk_bounded_pp(e, m) << " ";
                    }
                    out << "\n";
                    if (m_else)
                        out << "else: " << mk_pp(m_else, m, 6) << "\n";
                    if (m_proj)
                        out << "projection: " << m_proj->get_name() << "\n";
                    if (m_set) {
                        out << "instantiation-set:\n";
                        m_set->display(out);
                    }
                    out << "----------------\n";
                }
                else {
                    out << "@" << m_id << " -> @" << get_root()->get_id() << "\n";
                }
            }

            instantiation_set const* get_instantiation_set() const { return get_root()->m_set.get(); }

            instantiation_set* get_instantiation_set() { return get_root()->m_set.get(); }

            ptr_vector<expr> const& get_exceptions() const { return get_root()->m_exceptions; }

            ptr_vector<node> const& get_avoid_set() const { return get_root()->m_avoid_set; }

            // return true if m_avoid_set.contains(this)
            bool must_avoid_itself() const {
                node* r = get_root();
                for (node* n : m_avoid_set) {
                    if (r == n->get_root())
                        return true;
                }
                return false;
            }

            void set_else(expr* e) {
                SASSERT(!is_mono_proj());
                SASSERT(get_root()->m_else == nullptr);
                get_root()->m_else = e;
            }

            expr* get_else() const {
                return get_root()->m_else;
            }

            void set_proj(func_decl* f) {
                SASSERT(get_root()->m_proj == nullptr);
                get_root()->m_proj = f;
            }

            func_decl* get_proj() const {
                return get_root()->m_proj;
            }
        };


        typedef std::pair<ast*, unsigned> ast_idx_pair;
        typedef pair_hash<obj_ptr_hash<ast>, unsigned_hash> ast_idx_pair_hash;
        typedef map<ast_idx_pair, node*, ast_idx_pair_hash, default_eq<ast_idx_pair> > key2node;


        /**
           \brief Auxiliary class for processing the "Almost uninterpreted fragment" described in the paper:
           Complete instantiation for quantified SMT formulas

           The idea is to create node objects based on the information produced by the quantifier_analyzer.
        */
        class auf_solver : public evaluator {
            ast_manager& m;
            arith_util                m_arith;
            bv_util                   m_bv;
            array_util                m_array;
            ptr_vector<node>          m_nodes;
            unsigned                  m_next_node_id{ 0 };
            key2node                  m_uvars;
            key2node                  m_A_f_is;

            // Mapping from sort to auxiliary constant.
            // This auxiliary constant is used as a "witness" that is asserted as different from a
            // finite number of terms.
            // It is only safe to use this constant for infinite sorts.
            obj_map<sort, app*>       m_sort2k;
            expr_ref_vector           m_ks; // range of m_sort2k

            // Support for evaluating expressions in the current model.
            proto_model*              m_model{ nullptr };
            obj_map<expr, expr*>      m_eval_cache[2];
            expr_ref_vector           m_eval_cache_range;

            ptr_vector<node>          m_root_nodes;

            expr_ref_vector*          m_new_constraints{ nullptr };
            random_gen                m_rand;
            func_decl_set             m_specrels;


            void reset_sort2k() {
                m_sort2k.reset();
                m_ks.reset();
            }

            void reset_eval_cache() {
                m_eval_cache[0].reset();
                m_eval_cache[1].reset();
                m_eval_cache_range.reset();
            }

            node* mk_node(key2node& map, ast* n, unsigned i, sort* s) {
                node* r = nullptr;
                ast_idx_pair k(n, i);
                if (map.find(k, r)) {
                    SASSERT(r->get_sort() == s);
                    return r;
                }
                r = alloc(node, m_next_node_id, s);
                m_next_node_id++;
                map.insert(k, r);
                m_nodes.push_back(r);
                return r;
            }

            void display_key2node(std::ostream& out, key2node const& m) const {
                for (auto const& kv : m) {
                    ast* a = kv.m_key.first;
                    unsigned i = kv.m_key.second;
                    node* n = kv.m_value;
                    out << "#" << a->get_id() << ":" << i << " -> @" << n->get_id() << "\n";
                }
            }

            void display_A_f_is(std::ostream& out) const {
                for (auto const& kv : m_A_f_is) {
                    func_decl* f = static_cast<func_decl*>(kv.m_key.first);
                    unsigned    i = kv.m_key.second;
                    node* n = kv.m_value;
                    out << f->get_name() << ":" << i << " -> @" << n->get_id() << "\n";
                }
            }

            void flush_nodes() {
                std::for_each(m_nodes.begin(), m_nodes.end(), delete_proc<node>());
            }

        public:
            auf_solver(ast_manager& m) :
                m(m),
                m_arith(m),
                m_bv(m),
                m_array(m),
                m_ks(m),
                m_eval_cache_range(m),
                m_rand(static_cast<unsigned>(m.limit().count())) {
                m.limit().inc();
            }

            ~auf_solver() override {
                flush_nodes();
                reset_eval_cache();
            }

            ast_manager& get_manager() const { return m; }

            void reset() {
                m_specrels.reset();
                flush_nodes();
                m_nodes.reset();
                m_next_node_id = 0;
                m_uvars.reset();
                m_A_f_is.reset();
                m_root_nodes.reset();
                reset_sort2k();
            }

            void set_specrels(context& c) {
                m_specrels.reset();
                c.get_specrels(m_specrels);
            }

            void set_model(proto_model* m) {
                reset_eval_cache();
                m_model = m;
            }

            proto_model* get_model() const {
                SASSERT(m_model);
                return m_model;
            }

            node* get_uvar(quantifier* q, unsigned i) {
                SASSERT(i < q->get_num_decls());
                sort* s = q->get_decl_sort(q->get_num_decls() - i - 1);
                return mk_node(m_uvars, q, i, s);
            }

            node* get_A_f_i(func_decl* f, unsigned i) {
                SASSERT(i < f->get_arity());
                sort* s = f->get_domain(i);
                return mk_node(m_A_f_is, f, i, s);
            }

            instantiation_set const* get_uvar_inst_set(quantifier* q, unsigned i) const {
                ast_idx_pair k(q, i);
                node* r = nullptr;
                if (m_uvars.find(k, r))
                    return r->get_instantiation_set();
                return nullptr;
            }

            void mk_instantiation_sets() {
                for (node* curr : m_nodes) {
                    if (curr->is_root()) {
                        curr->mk_instantiation_set(m);
                    }
                }
            }

            // For each instantiation_set, remove entries that do not evaluate to values.
            ptr_vector<expr> to_delete;

            bool should_cleanup(expr* e) {
                if (!e)
                    return true;
                if (m.is_value(e))
                    return false;
                if (m_array.is_array(e))
                    return false;
                if (!is_app(e))
                    return true;
                if (to_app(e)->get_num_args() == 0)
                    return true;
                return !contains_array(e);
            }

            struct found_array : public std::exception {};
            expr_mark m_visited;
            void operator()(expr* n) {
                if (m_array.is_array(n))
                    throw found_array();
            }
            bool contains_array(expr* e) {
                m_visited.reset();
                try {
                    for_each_expr(*this, m_visited, e);
                }
                catch (const found_array&) {
                    return true;
                }
                return false;                
            }

           
            void cleanup_instantiation_sets() {
                for (node* curr : m_nodes) {
                    if (curr->is_root()) {
                        instantiation_set* s = curr->get_instantiation_set();
                        to_delete.reset();
                        obj_map<expr, unsigned> const& elems = s->get_elems();
                        for (auto const& kv : elems) {
                            expr* n = kv.m_key;
                            expr* n_val = eval(n, true);
                            if (should_cleanup(n_val)) {
                                TRACE(model_finder, tout << "cleanup " << s << " " << mk_pp(n, m) << " " << mk_pp(n_val, m) << "\n";);
                                to_delete.push_back(n);
                            }
                        }
                        for (expr* e : to_delete) 
                            s->remove(e);
                        reset_eval_cache();
                    }
                }
            }

            void display_nodes(std::ostream& out) const {
                display_key2node(out, m_uvars);
                display_A_f_is(out);
                for (node* n : m_nodes) {
                    n->display(out, m);
                }
            }

            expr* eval(expr* n, bool model_completion) override {
                expr* r = nullptr;
                if (m_eval_cache[model_completion].find(n, r)) {
                    return r;
                }
                expr_ref tmp(m);
                if (!m_model->eval(n, tmp, model_completion)) {
                    r = nullptr;
                    TRACE(model_finder, tout << "eval\n" << mk_pp(n, m) << "\n-----> null\n";);
                }
                else {
                    r = tmp;
                    TRACE(model_finder, tout << "eval\n" << mk_pp(n, m) << "\n----->\n" << mk_pp(r, m) << "\n";);
                }
                m_eval_cache[model_completion].insert(n, r);
                m_eval_cache_range.push_back(r);
                return r;
            }
        private:

            /**
               \brief Collect the interpretations of n->get_exceptions()
               and the interpretations of the m_else of nodes in n->get_avoid_set()
            */
            void collect_exceptions_values(node* n, ptr_buffer<expr>& r) {

                for (expr* e : n->get_exceptions()) {
                    expr* val = eval(e, true);
                    if (val)
                        r.push_back(val);
                }

                for (node* a : n->get_avoid_set()) {
                    node* p = a->get_root();
                    if (!p->is_mono_proj() && p->get_else() != nullptr) {
                        expr* val = eval(p->get_else(), true);
                        if (val)
                            r.push_back(val);
                    }
                }
            }

            /**
               \brief Return an expr t from the instantiation set of \c n s.t. forall e in n.get_exceptions()
               eval(t) != eval(e) and forall m in n.get_avoid_set() eval(t) != eval(m.get_else())
               If there t1 and t2 satisfying this condition, break ties using the generation of them.

               Return 0 if such t does not exist.
            */
            expr* pick_instance_diff_exceptions(node* n, ptr_buffer<expr> const& ex_vals) {
                instantiation_set const* s = n->get_instantiation_set();
                obj_map<expr, unsigned> const& elems = s->get_elems();

                expr* t_result = nullptr;
                unsigned  gen_result = UINT_MAX;
                for (auto const& kv : elems) {
                    expr* t = kv.m_key;
                    unsigned gen = kv.m_value;
                    expr* t_val = eval(t, true);
                    if (!t_val)
                        return t_result;
                    bool found = false;
                    for (expr* v : ex_vals) {
                        if (!m.are_distinct(t_val, v)) {
                            found = true;
                            break;
                        }
                    }
                    if (!found && (t_result == nullptr || gen < gen_result)) {
                        t_result = t;
                        gen_result = gen;
                    }
                }
                return t_result;
            }

            // we should not assume that uninterpreted sorts are infinite in benchmarks with quantifiers.
            bool is_infinite(sort* s) const { return !m.is_uninterp(s) && s->is_infinite(); }

            /**
               \brief Return a fresh constant k that is used as a witness for elements that must be different from
               a set of values.
            */
            app* get_k_for(sort* s) {
                SASSERT(is_infinite(s));
                app* r = nullptr;
                if (m_sort2k.find(s, r))
                    return r;
                r = m.mk_fresh_const("k", s);
                m_model->register_aux_decl(r->get_decl());
                m_sort2k.insert(s, r);
                m_ks.push_back(r);
                TRACE(model_finder, tout << sort_ref(s, m) << " := " << "\n";);
                return r;
            }

            /**
               \brief Get the interpretation for k in m_model.
               If m_model does not provide an interpretation for k, then
               create a fresh one.

               Remark: this method uses get_fresh_value, so it may fail.
            */
            expr* get_k_interp(app* k) {
                sort* s = k->get_sort();
                SASSERT(is_infinite(s));
                func_decl* k_decl = k->get_decl();
                expr* r = m_model->get_const_interp(k_decl);
                if (r != nullptr)
                    return r;
                r = m_model->get_fresh_value(s);
                if (r == nullptr)
                    return nullptr;
                m_model->register_decl(k_decl, r);
                SASSERT(m_model->get_const_interp(k_decl) == r);
                TRACE(model_finder, tout << mk_pp(r, m) << "\n";);
                return r;
            }

            /**
               \brief Assert k to be different from the set of exceptions.

               It invokes get_k_interp that may fail.
            */
            bool assert_k_diseq_exceptions(app* k, ptr_vector<expr> const& exceptions) {
                TRACE(assert_k_diseq_exceptions, tout << "assert_k_diseq_exceptions, " << "k: " << mk_pp(k, m) << "\nexceptions:\n";
                for (expr* e : exceptions) tout << mk_pp(e, m) << "\n";);
                expr* k_interp = get_k_interp(k);
                if (k_interp == nullptr)
                    return false;
                for (expr* ex : exceptions) {
                    expr* ex_val = eval(ex, true);
                    if (ex_val && !m.are_distinct(k_interp, ex_val)) {
                        SASSERT(m_new_constraints);
                        // This constraint cannot be asserted into m_context during model construction.
                        // We must save it, and assert it during a restart.
                        m_new_constraints->push_back(m.mk_not(m.mk_eq(k, ex)));
                    }
                }
                return true;
            }

            void set_projection_else(node* n) {
                TRACE(model_finder, n->display(tout, m););
                SASSERT(n->is_root());
                SASSERT(!n->is_mono_proj());
                instantiation_set const* s = n->get_instantiation_set();
                ptr_vector<expr> const& exceptions = n->get_exceptions();
                ptr_vector<node> const& avoid_set = n->get_avoid_set();
                obj_map<expr, unsigned> const& elems = s->get_elems();
                if (elems.empty()) return;
                if (!exceptions.empty() || !avoid_set.empty()) {
                    ptr_buffer<expr> ex_vals;
                    collect_exceptions_values(n, ex_vals);
                    expr* e = pick_instance_diff_exceptions(n, ex_vals);
                    if (e != nullptr) {
                        n->set_else(e);
                        return;
                    }
                    sort* s = n->get_sort();
                    TRACE(model_finder, tout << "trying to create k for " << mk_pp(s, m) << ", is_infinite: " << is_infinite(s) << "\n";);
                    if (is_infinite(s)) {
                        app* k = get_k_for(s);
                        if (assert_k_diseq_exceptions(k, exceptions)) {
                            n->insert(k, 0); // add k to the instantiation set
                            n->set_else(k);
                            return;
                        }
                    }
                    // TBD: add support for the else of bitvectors.
                    // Idea: get the term t with the minimal interpretation and use t - 1.
                }
                n->set_else((*(elems.begin())).m_key);
            }

            /**
               \brief If m_mono_proj is true and n is int or bv, then for each e in n->get_exceptions(),
               we must add e-1 and e+1 to the instantiation set.
               If sort->get_sort() is real, then we do nothing and hope for the best.
            */
            void add_mono_exceptions(node* n) {
                SASSERT(n->is_mono_proj());
                sort* s = n->get_sort();
                arith_rewriter arw(m);
                bv_rewriter brw(m);
                ptr_vector<expr> const& exceptions = n->get_exceptions();
                expr_ref e_minus_1(m), e_plus_1(m);
                if (m_arith.is_int(s)) {
                    expr_ref one(m_arith.mk_int(1), m);
                    arith_rewriter arith_rw(m);
                    for (expr* e : exceptions) {
                        arith_rw.mk_sub(e, one, e_minus_1);
                        arith_rw.mk_add(e, one, e_plus_1);
                        TRACE(mf_simp_bug, tout << "e:\n" << mk_ismt2_pp(e, m) << "\none:\n" << mk_ismt2_pp(one, m) << "\n";);
                        // Note: exceptions come from quantifiers bodies. So, they have generation 0.
                        n->insert(e_plus_1, 0);
                        n->insert(e_minus_1, 0);
                    }
                }
                else if (m_bv.is_bv_sort(s)) {
                    expr_ref one(m_bv.mk_numeral(rational(1), s), m);
                    bv_rewriter bv_rw(m);
                    for (expr* e : exceptions) {
                        bv_rw.mk_add(e, one, e_plus_1);
                        bv_rw.mk_sub(e, one, e_minus_1);
                        TRACE(mf_simp_bug, tout << "e:\n" << mk_ismt2_pp(e, m) << "\none:\n" << mk_ismt2_pp(one, m) << "\n";);
                        // Note: exceptions come from quantifiers bodies. So, they have generation 0.
                        n->insert(e_plus_1, 0);
                        n->insert(e_minus_1, 0);
                    }
                }
                else {
                    return;
                }
            }

            void get_instantiation_set_values(node* n, ptr_buffer<expr>& values) {
                instantiation_set const* s = n->get_instantiation_set();
                obj_hashtable<expr> already_found;
                obj_map<expr, unsigned> const& elems = s->get_elems();
                for (auto const& kv : elems) {
                    expr* t = kv.m_key;
                    expr* t_val = eval(t, true);
                    bool found = false;
                    if (t_val && !m.is_unique_value(t_val))                         
                        for (expr* v : values)
                            found |= m.are_equal(v, t_val);
                    if (t_val && !found && !already_found.contains(t_val)) {
                        values.push_back(t_val);
                        already_found.insert(t_val);
                    }
                }
                TRACE(model_finder_bug, tout << "values for the instantiation_set of @" << n->get_id() << "\n";
                for (expr* v : values) {
                    tout << mk_pp(v, m) << "\n";
                });
            }

            template<class T>
            struct numeral_lt {
                T& m_util;
                numeral_lt(T& a) : m_util(a) {}
                bool operator()(expr* e1, expr* e2) {
                    rational v1, v2;
                    if (m_util.is_numeral(e1, v1) && m_util.is_numeral(e2, v2)) {
                        return v1 < v2;
                    }
                    else {
                        return e1->get_id() < e2->get_id();
                    }
                }
            };


            struct signed_bv_lt {
                bv_util& m_bv;
                unsigned               m_bv_size;
                signed_bv_lt(bv_util& bv, unsigned sz) :m_bv(bv), m_bv_size(sz) {}
                bool operator()(expr* e1, expr* e2) {
                    rational v1, v2;
                    if (m_bv.is_numeral(e1, v1) && m_bv.is_numeral(e2, v2)) {
                        v1 = m_bv.norm(v1, m_bv_size, true);
                        v2 = m_bv.norm(v2, m_bv_size, true);
                        return v1 < v2;
                    }
                    else {
                        return e1->get_id() < e2->get_id();
                    }
                }
            };

            void sort_values(node* n, ptr_buffer<expr>& values) {
                sort* s = n->get_sort();
                if (m_arith.is_int(s) || m_arith.is_real(s)) {
                    std::sort(values.begin(), values.end(), numeral_lt<arith_util>(m_arith));
                }
                else if (!n->is_signed_proj()) {
                    std::sort(values.begin(), values.end(), numeral_lt<bv_util>(m_bv));
                }
                else {
                    std::sort(values.begin(), values.end(), signed_bv_lt(m_bv, m_bv.get_bv_size(s)));
                }
            }

            void mk_mono_proj(node* n) {
                add_mono_exceptions(n);
                ptr_buffer<expr> values;
                get_instantiation_set_values(n, values);
                if (values.empty()) return;
                sort_values(n, values);
                sort* s = n->get_sort();
                bool is_arith = m_arith.is_int(s) || m_arith.is_real(s);
                bool is_signed = n->is_signed_proj();
                unsigned sz = values.size();
                SASSERT(sz > 0);
                expr* pi = values[sz - 1];
                expr_ref var(m);
                var = m.mk_var(0, s);
                for (unsigned i = sz - 1; i >= 1; i--) {
                    expr_ref c(m);
                    if (is_arith)
                        c = m_arith.mk_lt(var, values[i]);
                    else if (!is_signed)
                        c = m.mk_not(m_bv.mk_ule(values[i], var));
                    else
                        c = m.mk_not(m_bv.mk_sle(values[i], var));
                    pi = m.mk_ite(c, values[i - 1], pi);
                }
                func_interp* rpi = alloc(func_interp, m, 1);
                rpi->set_else(pi);
                func_decl* p = m.mk_fresh_func_decl(1, &s, s);
                m_model->register_aux_decl(p, rpi);
                n->set_proj(p);
                TRACE(model_finder, n->display(tout << p->get_name() << "\n", m););
            }

            void mk_simple_proj(node* n) {
                set_projection_else(n);
                ptr_buffer<expr> values;
                get_instantiation_set_values(n, values);
                sort* s = n->get_sort();
                func_decl* p = m.mk_fresh_func_decl(1, &s, s);
                func_interp* pi = alloc(func_interp, m, 1);
                m_model->register_aux_decl(p, pi);
                if (n->get_else()) {
                    expr* else_val = eval(n->get_else(), true);
                    if (else_val)
                        pi->set_else(else_val);
                }
                for (expr* v : values) 
                    pi->insert_new_entry(&v, v);
                
                n->set_proj(p);
                TRACE(model_finder, n->display(tout << p->get_name() << "\n", m););
            }

            void mk_projections() {
                for (node* n : m_root_nodes) {
                    SASSERT(n->is_root());
                    if (n->is_mono_proj())
                        mk_mono_proj(n);
                    else
                        mk_simple_proj(n);
                }
            }

            /**
               \brief Store in r the partial functions that have A_f_i nodes.
            */
            void collect_partial_funcs(func_decl_set& r) {
                for (auto const& kv : m_A_f_is) {
                    func_decl* f = to_func_decl(kv.m_key.first);
                    if (!r.contains(f)) {
                        func_interp* fi = m_model->get_func_interp(f);
                        if (fi == nullptr) {
                            fi = alloc(func_interp, m, f->get_arity());
                            TRACE(model_finder, tout << "register " << f->get_name() << "\n";);
                            m_model->register_decl(f, fi);
                            SASSERT(fi->is_partial());
                        }
                        if (fi->is_partial()) {
                            r.insert(f);
                        }
                    }
                }
            }

            /**
               \brief Make sorts associated with nodes that must avoid themselves finite.
               Only uninterpreted sorts are considered.
               This is a trick to be able to handle atoms of the form X = Y
               where X and Y are variables. See paper "Complete Quantifier Instantiation"
               for more details.
            */
            void mk_sorts_finite() {
                for (node* n : m_root_nodes) {
                    SASSERT(n->is_root());
                    sort* s = n->get_sort();
                    if (m.is_uninterp(s) &&
                        // Making all uninterpreted sorts finite.
                        // n->must_avoid_itself() &&
                        !m_model->is_finite(s)) {
                        m_model->freeze_universe(s);
                    }
                }
            }

            void add_elem_to_empty_inst_sets() {
                obj_map<sort, expr*> sort2elems;
                ptr_vector<node> need_fresh;
                for (node* n : m_root_nodes) {
                    SASSERT(n->is_root());
                    instantiation_set const* s = n->get_instantiation_set();
                    TRACE(model_finder, s->display(tout););
                    obj_map<expr, unsigned> const& elems = s->get_elems();
                    if (elems.empty()) {
                        // The method get_some_value cannot be used if n->get_sort() is an uninterpreted sort or is a sort built using uninterpreted sorts
                        // (e.g., (Array S S) where S is uninterpreted). The problem is that these sorts do not have a fixed interpretation.
                        // Moreover, a model assigns an arbitrary interpretation to these sorts using "model_values" a model value.
                        // If these module values "leak" inside the logical context, they may affect satisfiability.
                        //
                        sort* ns = n->get_sort();
                        if (m.is_fully_interp(ns)) {
                            n->insert(m_model->get_some_value(ns), 0);
                        }
                        else {
                            need_fresh.push_back(n);
                        }
                    }
                    else {
                        sort2elems.insert(n->get_sort(), elems.begin()->m_key);
                    }
                }
                expr_ref_vector trail(m);
                for (node* n : need_fresh) {
                    expr* e;
                    sort* s = n->get_sort();
                    if (!sort2elems.find(s, e)) {
                        e = m.mk_fresh_const("elem", s);
                        trail.push_back(e);
                        sort2elems.insert(s, e);
                    }
                    n->insert(e, 0);
                    TRACE(model_finder, tout << "fresh constant: " << mk_pp(e, m) << "\n";);
                }
            }

            /**
               \brief Store in m_root_nodes the roots from m_nodes.
             */
            void collect_root_nodes() {
                m_root_nodes.reset();
                for (node* n : m_nodes) {
                    if (n->is_root())
                        m_root_nodes.push_back(n);
                }
            }

            /**
               \brief Return the projection function for f at argument i.
               Return 0, if there is none.

               \remark This method assumes that mk_projections was already
               invoked.

               \remark f may have a non partial interpretation on m_model.
               This may happen because the evaluator uses model_completion.
               In the beginning of fix_model() we collected all f with
               partial interpretations. During the process of computing the
               projections we used the evaluator with model_completion,
               and it may have fixed the "else" case of some partial interpretations.
               This is ok, because in the "limit" the "else" of the interpretation
               is irrelevant after the projections are applied.
            */
            func_decl* get_f_i_proj(func_decl* f, unsigned i) {
                node* r = nullptr;
                ast_idx_pair k(f, i);
                if (!m_A_f_is.find(k, r))
                    return nullptr;
                return r->get_proj();
            }

            /**
               \brief Complete the interpretation of the functions that were
               collected in the beginning of fix_model().
             */
            void complete_partial_funcs(func_decl_set const& partial_funcs) {
                for (func_decl* f : partial_funcs) {

                    // Complete the current interpretation
                    m_model->complete_partial_func(f, true);

                    if (m_specrels.contains(f))
                        continue;

                    unsigned arity = f->get_arity();
                    func_interp* fi = m_model->get_func_interp(f);
                    if (fi->is_constant())
                        continue; // there is no point in using the projection for fi, since fi is the constant function.

                    expr_ref_vector args(m);
                    bool has_proj = false;
                    for (unsigned i = 0; i < arity; i++) {
                        var* v = m.mk_var(i, f->get_domain(i));
                        func_decl* pi = get_f_i_proj(f, i);
                        if (pi != nullptr) {
                            args.push_back(m.mk_app(pi, v));
                            has_proj = true;
                        }
                        else {
                            args.push_back(v);
                        }
                    }
                    if (has_proj) {
                        // f_aux will be assigned to the old interpretation of f.
                        func_decl* f_aux = m.mk_fresh_func_decl(f->get_name(), symbol::null, arity, f->get_domain(), f->get_range());
                        func_interp* new_fi = alloc(func_interp, m, arity);
                        new_fi->set_else(m.mk_app(f_aux, args.size(), args.data()));
                        TRACE(model_finder, tout << "Setting new interpretation for " << f->get_name() << "\n" <<
                            mk_pp(new_fi->get_else(), m) << "\n";
                        tout << "old interpretation: " << mk_pp(fi->get_interp(), m) << "\n";);
                        m_model->reregister_decl(f, new_fi, f_aux);
                    }
                }
            }

            void mk_inverse(node* n) {
                SASSERT(n->is_root());
                instantiation_set* s = n->get_instantiation_set();
                s->mk_inverse(*this);
            }

            void mk_inverses() {
                unsigned offset = m_rand();
                for (unsigned i = m_root_nodes.size(); i-- > 0; ) {
                    node* n = m_root_nodes[(i + offset) % m_root_nodes.size()];
                    SASSERT(n->is_root());
                    mk_inverse(n);
                }
            }

        public:
            void fix_model(expr_ref_vector& new_constraints) {
                cleanup_instantiation_sets();
                m_new_constraints = &new_constraints;
                func_decl_set partial_funcs;
                collect_partial_funcs(partial_funcs);
                reset_eval_cache(); // will start using model_completion
                collect_root_nodes();
                add_elem_to_empty_inst_sets();
                mk_sorts_finite();
                mk_projections();
                mk_inverses();
                complete_partial_funcs(partial_funcs);
                TRACE(model_finder, tout << "after auf_solver fixing the model\n";
                      display_nodes(tout);
                      tout << "NEW MODEL:\n";
                      model_pp(tout, *m_model););
            }

            bool is_default_representative(expr* t) {
                app* tt = nullptr;
                return t && m_sort2k.find(t->get_sort(), tt) && (tt == t);
            }
        };


        // -----------------------------------
        //
        // qinfo class & subclasses
        //
        // -----------------------------------

        /*
           During quantifier internalizations time, we collect bits of
           information about the quantifier structure. These bits are
           instances of subclasses of qinfo.
        */

        /**
           \brief Generic bit of information collected when analyzing quantifiers.
           The subclasses are defined in the .cpp file.
        */
        class qinfo {
        protected:
            ast_manager& m;
        public:
            qinfo(ast_manager& m) :m(m) {}
            virtual ~qinfo() = default;
            virtual char const* get_kind() const = 0;
            virtual bool is_equal(qinfo const* qi) const = 0;
            virtual void display(std::ostream& out) const { out << "[" << get_kind() << "]"; }

            // AUF fragment solver
            virtual void process_auf(quantifier* q, auf_solver& s, context* ctx) = 0;
            virtual void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) = 0;
            // second pass... actually we may need to reach a fixpoint, but if it cannot be found
            // in two passes, the fixpoint is not finite.
            virtual void populate_inst_sets2(quantifier* q, auf_solver& s, context* ctx) {}

            // Macro/Hint support
            virtual void populate_inst_sets(quantifier* q, func_decl* mhead, ptr_vector<instantiation_set>& uvar_inst_sets, context* ctx) {}
        };

        class f_var : public qinfo {
        protected:
            func_decl* m_f;
            unsigned    m_arg_i;
            unsigned    m_var_j;
        public:
            f_var(ast_manager& m, func_decl* f, unsigned i, unsigned j) : qinfo(m), m_f(f), m_arg_i(i), m_var_j(j) {}

            char const* get_kind() const override {
                return "f_var";
            }

            bool is_equal(qinfo const* qi) const override {
                if (qi->get_kind() != get_kind())
                    return false;
                f_var const* other = static_cast<f_var const*>(qi);
                return m_f == other->m_f && m_arg_i == other->m_arg_i && m_var_j == other->m_var_j;
            }

            void display(std::ostream& out) const override {
                out << "(" << m_f->get_name() << ":" << m_arg_i << " -> v!" << m_var_j << ")";
            }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                node* n1 = s.get_A_f_i(m_f, m_arg_i);
                node* n2 = s.get_uvar(q, m_var_j);
                CTRACE(model_finder, n1->get_sort() != n2->get_sort(),
                       tout << "sort bug:\n" << mk_ismt2_pp(q->get_expr(), m) << "\n" << mk_ismt2_pp(q, m) << "\n";
                       tout << "decl(0): " << q->get_decl_name(0) << "\n";
                       tout << "f: " << m_f->get_name() << " i: " << m_arg_i << "\n";
                       tout << "v: " << m_var_j << "\n";
                       n1->get_root()->display(tout, m);
                       n2->get_root()->display(tout, m);
                       tout << "f signature: ";
                       for (unsigned i = 0; i < m_f->get_arity(); i++) tout << mk_pp(m_f->get_domain(i), m) << " ";
                       tout << "-> " << mk_pp(m_f->get_range(), m) << "\n";
                );

                n1->merge(n2);
            }

            void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) override {
                node* A_f_i = s.get_A_f_i(m_f, m_arg_i);
                for (enode* n : ctx->enodes_of(m_f)) {
                    if (ctx->is_relevant(n)) {
                        // Remark: it is incorrect to use
                        // n->get_arg(m_arg_i)->get_root()
                        // instead of
                        // n->get_arg(m_arg_i)
                        //
                        // Due to model based quantifier instantiation, some equivalence
                        // classes are merged by accident.
                        // So, using n->get_arg(m_arg_i)->get_root(), we may miss
                        // a necessary instantiation.
                        enode* e_arg = n->get_arg(m_arg_i);
                        expr* arg = e_arg->get_expr();
                        A_f_i->insert(arg, e_arg->get_generation());
                    }
                }
            }

            void populate_inst_sets(quantifier* q, func_decl* mhead, ptr_vector<instantiation_set>& uvar_inst_sets, context* ctx) override {
                if (m_f != mhead)
                    return;
                uvar_inst_sets.reserve(m_var_j + 1, 0);
                if (uvar_inst_sets[m_var_j] == 0)
                    uvar_inst_sets[m_var_j] = alloc(instantiation_set, ctx->get_manager());
                instantiation_set* s = uvar_inst_sets[m_var_j];
                SASSERT(s != nullptr);

                for (enode* n : ctx->enodes_of(m_f)) {
                    if (ctx->is_relevant(n)) {
                        enode* e_arg = n->get_arg(m_arg_i);
                        expr* arg = e_arg->get_expr();
                        s->insert(arg, e_arg->get_generation());
                    }
                }
            }
        };

        class f_var_plus_offset : public f_var {
            expr_ref    m_offset;
        public:
            f_var_plus_offset(ast_manager& m, func_decl* f, unsigned i, unsigned j, expr* offset) :
                f_var(m, f, i, j),
                m_offset(offset, m) {
            }

            char const* get_kind() const override {
                return "f_var_plus_offset";
            }

            bool is_equal(qinfo const* qi) const override {
                if (qi->get_kind() != get_kind())
                    return false;
                f_var_plus_offset const* other = static_cast<f_var_plus_offset const*>(qi);
                return m_f == other->m_f && m_arg_i == other->m_arg_i && m_var_j == other->m_var_j && m_offset.get() == other->m_offset.get();
            }

            void display(std::ostream& out) const override {
                out << "(" << m_f->get_name() << ":" << m_arg_i << " - " <<
                    mk_bounded_pp(m_offset.get(), m_offset.get_manager()) << " -> v!" << m_var_j << ")";
            }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                // just create the nodes
                /* node * A_f_i = */ s.get_A_f_i(m_f, m_arg_i);
                /* node * S_j   = */ s.get_uvar(q, m_var_j);
            }

            void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) override {
                // S_j is not necessary equal to A_f_i.
                node* A_f_i = s.get_A_f_i(m_f, m_arg_i)->get_root();
                node* S_j = s.get_uvar(q, m_var_j)->get_root();
                if (A_f_i == S_j) {
                    // there is no finite fixpoint... we just copy the i-th arguments of A_f_i - m_offset
                    // hope for the best...
                    node* S_j = s.get_uvar(q, m_var_j);
                    for (enode* n : ctx->enodes_of(m_f)) {
                        if (ctx->is_relevant(n)) {
                            arith_rewriter arith_rw(m);
                            bv_util bv(m);
                            bv_rewriter bv_rw(m);
                            enode* e_arg = n->get_arg(m_arg_i);
                            expr* arg = e_arg->get_expr();
                            expr_ref arg_minus_k(m);
                            if (bv.is_bv(arg))
                                bv_rw.mk_sub(arg, m_offset, arg_minus_k);
                            else
                                arith_rw.mk_sub(arg, m_offset, arg_minus_k);
                            S_j->insert(arg_minus_k, e_arg->get_generation());
                        }
                    }
                }
                else {
                    // A_f_i != S_j, there is hope for a finite fixpoint.
                    // So, we just populate A_f_i
                    f_var::populate_inst_sets(q, s, ctx);
                    // I must also propagate the monotonicity flag since A_f_i and S_j are not in the
                    // same equivalence class.
                    if (A_f_i->is_mono_proj())
                        S_j->set_mono_proj();
                    if (S_j->is_mono_proj())
                        A_f_i->set_mono_proj();
                }
            }

            template<bool PLUS>
            void copy_instances(node* from, node* to, auf_solver& s) {
                instantiation_set const* from_s = from->get_instantiation_set();
                obj_map<expr, unsigned> const& elems_s = from_s->get_elems();

                arith_rewriter arith_rw(m_offset.get_manager());
                bv_rewriter bv_rw(m_offset.get_manager());
                bool is_bv = bv_util(m_offset.get_manager()).is_bv_sort(from->get_sort());

                for (auto const& kv : elems_s) {
                    expr* n = kv.m_key;
                    expr_ref n_k(m_offset.get_manager());
                    if (PLUS) {
                        if (is_bv) {
                            bv_rw.mk_add(n, m_offset, n_k);
                        }
                        else {
                            arith_rw.mk_add(n, m_offset, n_k);
                        }
                    }
                    else {
                        if (is_bv) {
                            bv_rw.mk_sub(n, m_offset, n_k);
                        }
                        else {
                            arith_rw.mk_sub(n, m_offset, n_k);
                        }
                    }
                    to->insert(n_k, kv.m_value);
                }
            }

            void populate_inst_sets2(quantifier* q, auf_solver& s, context* ctx) override {
                node* A_f_i = s.get_A_f_i(m_f, m_arg_i)->get_root();
                node* S_j = s.get_uvar(q, m_var_j)->get_root();
                // If A_f_i == S_j, then there is no finite fixpoint, so we do nothing here.
                if (A_f_i != S_j) {
                    //  enforce
                    //  A_f_i - k \subset S_j
                    //  S_j + k   \subset A_f_i
                    copy_instances<false>(A_f_i, S_j, s);
                    copy_instances<true>(S_j, A_f_i, s);
                }
            }

            void populate_inst_sets(quantifier* q, func_decl* mhead, ptr_vector<instantiation_set>& uvar_inst_sets, context* ctx) override {
                // ignored when in macro
            }

        };


        /**
           \brief auf_arr is a term (pattern) of the form:

           FORM :=  GROUND-TERM
                |   (select FORM VAR)

           Store in arrays, all enodes that match the pattern
        */
        void get_auf_arrays(app* auf_arr, context* ctx, ptr_buffer<enode>& arrays) {
            if (is_ground(auf_arr)) {
                if (ctx->e_internalized(auf_arr)) {
                    enode* e = ctx->get_enode(auf_arr);
                    if (ctx->is_relevant(e)) {
                        arrays.push_back(e);
                    }
                }
            }
            else {
                app* nested_array = to_app(auf_arr->get_arg(0));
                ptr_buffer<enode> nested_arrays;
                get_auf_arrays(nested_array, ctx, nested_arrays);
                for (enode* curr : nested_arrays) {
                    enode_vector::iterator it2 = curr->begin_parents();
                    enode_vector::iterator end2 = curr->end_parents();
                    for (; it2 != end2; ++it2) {
                        enode* p = *it2;
                        if (ctx->is_relevant(p) && p->get_expr()->get_decl() == auf_arr->get_decl()) {
                            arrays.push_back(p);
                        }
                    }
                }
            }
        }

        class select_var : public qinfo {
        protected:
            array_util    m_array;
            app* m_select; // It must satisfy is_auf_select... see bool is_auf_select(expr * t) const
            unsigned      m_arg_i;
            unsigned      m_var_j;

            app* get_array() const { return to_app(m_select->get_arg(0)); }

            func_decl* get_array_func_decl(app* ground_array, auf_solver& s) {
                TRACE(model_evaluator, tout << expr_ref(ground_array, m) << "\n";);
                expr* ground_array_interp = s.eval(ground_array, false);
                if (ground_array_interp && m_array.is_as_array(ground_array_interp))
                    return m_array.get_as_array_func_decl(ground_array_interp);
                return nullptr;
            }

        public:
            select_var(ast_manager& m, app* s, unsigned i, unsigned j) :qinfo(m), m_array(m), m_select(s), m_arg_i(i), m_var_j(j) {}

            char const* get_kind() const override {
                return "select_var";
            }

            bool is_equal(qinfo const* qi) const override {
                if (qi->get_kind() != get_kind())
                    return false;
                select_var const* other = static_cast<select_var const*>(qi);
                return m_select == other->m_select && m_arg_i == other->m_arg_i && m_var_j == other->m_var_j;
            }

            void display(std::ostream& out) const override {
                out << "(" << mk_bounded_pp(m_select, m) << ":" << m_arg_i << " -> v!" << m_var_j << ")";
            }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                ptr_buffer<enode> arrays;
                get_auf_arrays(get_array(), ctx, arrays);
                TRACE(select_var,
                    tout << "enodes matching: "; display(tout); tout << "\n";
                for (enode* n : arrays) {
                    tout << "#" << n->get_expr_id() << "\n" << mk_pp(n->get_expr(), m) << "\n";
                });
                node* n1 = s.get_uvar(q, m_var_j);
                for (enode* n : arrays) {
                    app* ground_array = n->get_expr();
                    func_decl* f = get_array_func_decl(ground_array, s);
                    if (f) {
                        SASSERT(m_arg_i >= 1);
                        node* n2 = s.get_A_f_i(f, m_arg_i - 1);
                        n1->merge(n2);
                    }
                }
            }

            void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) override {
                ptr_buffer<enode> arrays;
                get_auf_arrays(get_array(), ctx, arrays);
                for (enode* curr : arrays) {
                    app* ground_array = curr->get_expr();
                    func_decl* f = get_array_func_decl(ground_array, s);
                    if (f) {
                        node* A_f_i = s.get_A_f_i(f, m_arg_i - 1);
                        enode_vector::iterator it2 = curr->begin_parents();
                        enode_vector::iterator end2 = curr->end_parents();
                        for (; it2 != end2; ++it2) {
                            enode* p = *it2;
                            if (ctx->is_relevant(p) && p->get_expr()->get_decl() == m_select->get_decl()) {
                                SASSERT(m_arg_i < p->get_expr()->get_num_args());
                                enode* e_arg = p->get_arg(m_arg_i);
                                A_f_i->insert(e_arg->get_expr(), e_arg->get_generation());
                            }
                        }
                    }
                }
            }
        };

        class var_pair : public qinfo {
        protected:
            unsigned    m_var_i;
            unsigned    m_var_j;
        public:
            var_pair(ast_manager& m, unsigned i, unsigned j) : qinfo(m), m_var_i(i), m_var_j(j) {
                if (m_var_i > m_var_j)
                    std::swap(m_var_i, m_var_j);
            }

            bool is_equal(qinfo const* qi) const override {
                if (qi->get_kind() != get_kind())
                    return false;
                var_pair const* other = static_cast<var_pair const*>(qi);
                return m_var_i == other->m_var_i && m_var_j == other->m_var_j;
            }

            void display(std::ostream& out) const override {
                out << "(" << get_kind() << ":v!" << m_var_i << ":v!" << m_var_j << ")";
            }

            void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) override {
                // do nothing
            }
        };

        class x_eq_y : public var_pair {
        public:
            x_eq_y(ast_manager& m, unsigned i, unsigned j) :var_pair(m, i, j) {}
            char const* get_kind() const override { return "x_eq_y"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                node* n1 = s.get_uvar(q, m_var_i);
                node* n2 = s.get_uvar(q, m_var_j);
                n1->insert_avoid(n2);
                if (n1 != n2)
                    n2->insert_avoid(n1);
            }
        };

        class x_neq_y : public var_pair {
        public:
            x_neq_y(ast_manager& m, unsigned i, unsigned j) :var_pair(m, i, j) {}
            char const* get_kind() const override { return "x_neq_y"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                node* n1 = s.get_uvar(q, m_var_i);
                node* n2 = s.get_uvar(q, m_var_j);
                n1->merge(n2);
            }
        };

        class x_leq_y : public var_pair {
        public:
            x_leq_y(ast_manager& m, unsigned i, unsigned j) :var_pair(m, i, j) {}
            char const* get_kind() const override { return "x_leq_y"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                node* n1 = s.get_uvar(q, m_var_i);
                node* n2 = s.get_uvar(q, m_var_j);
                n1->merge(n2);
                n1->set_mono_proj();
            }
        };

        // signed bit-vector comparison
        class x_sleq_y : public x_leq_y {
        public:
            x_sleq_y(ast_manager& m, unsigned i, unsigned j) :x_leq_y(m, i, j) {}
            char const* get_kind() const override { return "x_sleq_y"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                node* n1 = s.get_uvar(q, m_var_i);
                node* n2 = s.get_uvar(q, m_var_j);
                n1->merge(n2);
                n1->set_mono_proj();
                n1->set_signed_proj();
            }
        };

        class var_expr_pair : public qinfo {
        protected:
            unsigned    m_var_i;
            expr_ref    m_t;
        public:
            var_expr_pair(ast_manager& m, unsigned i, expr* t) :
                qinfo(m),
                m_var_i(i), m_t(t, m) {}

            bool is_equal(qinfo const* qi) const override {
                if (qi->get_kind() != get_kind())
                    return false;
                var_expr_pair const* other = static_cast<var_expr_pair const*>(qi);
                return m_var_i == other->m_var_i && m_t.get() == other->m_t.get();
            }

            void display(std::ostream& out) const override {
                out << "(" << get_kind() << ":v!" << m_var_i << ":" << mk_bounded_pp(m_t.get(), m) << ")";
            }
        };

        class x_eq_t : public var_expr_pair {
        public:
            x_eq_t(ast_manager& m, unsigned i, expr* t) :
                var_expr_pair(m, i, t) {}
            char const* get_kind() const override { return "x_eq_t"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                node* n1 = s.get_uvar(q, m_var_i);
                n1->insert_exception(m_t);
            }

            void populate_inst_sets(quantifier* q, auf_solver& slv, context* ctx) override {
                unsigned num_vars = q->get_num_decls();
                sort* s = q->get_decl_sort(num_vars - m_var_i - 1);
                if (m.is_uninterp(s)) {
                    // For uninterpreted sorts, we add all terms in the context.
                    // See Section 4.1 in the paper "Complete Quantifier Instantiation"
                    node* S_q_i = slv.get_uvar(q, m_var_i);
                    for (enode* n : ctx->enodes()) {
                        if (ctx->is_relevant(n) && n->get_expr()->get_sort() == s) {
                            S_q_i->insert(n->get_expr(), n->get_generation());
                        }
                    }
                }
            }
        };

        class x_neq_t : public var_expr_pair {
        public:
            x_neq_t(ast_manager& m, unsigned i, expr* t) :
                var_expr_pair(m, i, t) {}
            char const* get_kind() const override { return "x_neq_t"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                // make sure that S_q_i is create.
                s.get_uvar(q, m_var_i);
            }

            void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) override {
                node* S_q_i = s.get_uvar(q, m_var_i);
                S_q_i->insert(m_t, 0);
            }
        };

        class x_gle_t : public var_expr_pair {
        public:
            x_gle_t(ast_manager& m, unsigned i, expr* t) :
                var_expr_pair(m, i, t) {}
            char const* get_kind() const override { return "x_gle_t"; }

            void process_auf(quantifier* q, auf_solver& s, context* ctx) override {
                // make sure that S_q_i is create.
                node* n1 = s.get_uvar(q, m_var_i);
                n1->set_mono_proj();
            }

            void populate_inst_sets(quantifier* q, auf_solver& s, context* ctx) override {
                node* S_q_i = s.get_uvar(q, m_var_i);
                S_q_i->insert(m_t, 0);
            }
        };


        // -----------------------------------
        //
        // quantifier_info & quantifier_analyzer
        //
        // -----------------------------------

        class quantifier_analyzer;

        /**
           \brief Store relevant information regarding a particular universal quantifier.
           This information is populated by quantifier_analyzer.
           The information is used to (try to) build a model that satisfy the universal quantifier
           (when it is marked as relevant in the end of the search).
        */
        class quantifier_info : public quantifier_macro_info {
            model_finder& m_mf;
            quantifier_ref           m_q;      // original quantifier

            ptr_vector<qinfo>        m_qinfo_vect;
            ptr_vector<instantiation_set>* m_uvar_inst_sets;

            friend class quantifier_analyzer;

            void checkpoint() {
                m_mf.checkpoint("quantifier_info");
            }

            void insert_qinfo(qinfo* qi) {
                // I'm assuming the number of qinfo's per quantifier is small. So, the linear search is not a big deal.
                scoped_ptr<qinfo> q(qi);
                for (qinfo* qi2 : m_qinfo_vect) {
                    checkpoint();
                    if (qi->is_equal(qi2)) {
                        return;
                    }
                }
                m_qinfo_vect.push_back(q.detach());
                TRACE(model_finder, tout << "new quantifier qinfo: "; qi->display(tout); tout << "\n";);
            }

        public:
            typedef ptr_vector<cond_macro>::const_iterator macro_iterator;

            static quantifier_ref mk_flat(ast_manager& m, quantifier* q) {
                if (has_quantifiers(q->get_expr()) && !m.is_lambda_def(q)) {
                    proof_ref pr(m);
                    expr_ref  new_q(m);
                    pull_quant pull(m);
                    pull(q, new_q, pr);
                    SASSERT(is_well_sorted(m, new_q));
                    return quantifier_ref(to_quantifier(new_q), m);
                }
                else
                    return quantifier_ref(q, m);
            }

            quantifier_info(model_finder& mf, ast_manager& m, quantifier* q) :
                quantifier_macro_info(m, mk_flat(m, q)),
                m_mf(mf),
                m_q(q, m),
                m_uvar_inst_sets(nullptr) {
                CTRACE(model_finder_bug, has_quantifiers(m_flat_q->get_expr()),
                    tout << mk_pp(q, m) << "\n" << mk_pp(m_flat_q, m) << "\n";);
            }

            ~quantifier_info() override {
                std::for_each(m_qinfo_vect.begin(), m_qinfo_vect.end(), delete_proc<qinfo>());
                reset_the_one();
            }

            std::ostream& display(std::ostream& out) const override {
                quantifier_macro_info::display(out);
                out << "\ninfo bits:\n";
                for (qinfo* qi : m_qinfo_vect) {
                    out << "  "; qi->display(out); out << "\n";
                }
                return out;
            }

            void process_auf(auf_solver& s, context* ctx) {
                for (unsigned i = 0; i < m_flat_q->get_num_decls(); i++) {
                    // make sure a node exists for each variable.
                    s.get_uvar(m_flat_q, i);
                }
                for (qinfo* qi : m_qinfo_vect) {
                    qi->process_auf(m_flat_q, s, ctx);
                }
            }

            void populate_inst_sets(auf_solver& s, context* ctx) {
                for (qinfo* qi : m_qinfo_vect) {
                    qi->populate_inst_sets(m_flat_q, s, ctx);
                }
                // second pass
                for (qinfo* qi : m_qinfo_vect) {
                    checkpoint();
                    qi->populate_inst_sets2(m_flat_q, s, ctx);
                }
            }


            void populate_macro_based_inst_sets(context* ctx, evaluator& ev) {
                SASSERT(m_the_one != 0);
                if (m_uvar_inst_sets != nullptr)
                    return;
                m_uvar_inst_sets = alloc(ptr_vector<instantiation_set>);
                for (qinfo* qi : m_qinfo_vect)
                    qi->populate_inst_sets(m_flat_q, m_the_one, *m_uvar_inst_sets, ctx);
                for (instantiation_set* s : *m_uvar_inst_sets) {
                    if (s != nullptr)
                        s->mk_inverse(ev);
                }
            }

            instantiation_set* get_macro_based_inst_set(unsigned vidx, context* ctx, evaluator& ev) {
                if (m_the_one == nullptr)
                    return nullptr;
                populate_macro_based_inst_sets(ctx, ev);
                return m_uvar_inst_sets->get(vidx, 0);
            }

            void reset_the_one() override {
                quantifier_macro_info::reset_the_one();
                if (m_uvar_inst_sets) {
                    std::for_each(m_uvar_inst_sets->begin(), m_uvar_inst_sets->end(), delete_proc<instantiation_set>());
                    dealloc(m_uvar_inst_sets);
                    m_uvar_inst_sets = nullptr;
                }
            }
        };

        /**
           \brief Functor used to traverse/analyze a quantifier and
           fill the structure quantifier_info.
        */
        class quantifier_analyzer {
            model_finder& m_mf;
            ast_manager& m;
            macro_util           m_mutil;
            array_util           m_array_util;
            arith_util           m_arith_util;
            bv_util              m_bv_util;

            quantifier_info* m_info;

            typedef enum { POS, NEG } polarity;

            polarity neg(polarity p) { return p == POS ? NEG : POS; }

            obj_hashtable<expr>  m_pos_cache;
            obj_hashtable<expr>  m_neg_cache;
            typedef std::pair<expr*, polarity> entry;
            svector<entry>       m_ftodo;
            ptr_vector<expr>     m_ttodo;

            void insert_qinfo(qinfo* qi) {
                SASSERT(m_info);
                m_info->insert_qinfo(qi);
            }

            bool is_var_plus_ground(expr* n, bool& inv, var*& v, expr_ref& t) {
                return m_mutil.is_var_plus_ground(n, inv, v, t);
            }

            bool is_var_plus_ground(expr* n, var*& v, expr_ref& t) {
                bool inv;
                TRACE(is_var_plus_ground, tout << mk_pp(n, m) << "\n";
                tout << "is_var_plus_ground: " << is_var_plus_ground(n, inv, v, t) << "\n";
                tout << "inv: " << inv << "\n";);
                return is_var_plus_ground(n, inv, v, t) && !inv;
            }

            bool is_add(expr* n) const {
                return m_mutil.is_add(n);
            }

            bool is_zero(expr* n) const {
                return m_mutil.is_zero_safe(n);
            }

            bool is_times_minus_one(expr* n, expr*& arg) const {
                return m_mutil.is_times_minus_one(n, arg);
            }

            bool is_le(expr* n) const {
                return m_mutil.is_le(n);
            }

            bool is_le_ge(expr* n) const {
                return m_mutil.is_le_ge(n);
            }

            bool is_signed_le(expr* n) const {
                return m_bv_util.is_bv_sle(n);
            }

            expr* mk_one(sort* s) {
                return m_bv_util.is_bv_sort(s) ? m_bv_util.mk_numeral(rational(1), s) : m_arith_util.mk_numeral(rational(1), s);
            }

            void mk_sub(expr* t1, expr* t2, expr_ref& r) const {
                m_mutil.mk_sub(t1, t2, r);
            }

            void mk_add(expr* t1, expr* t2, expr_ref& r) const {
                m_mutil.mk_add(t1, t2, r);
            }

            bool is_var_and_ground(expr* lhs, expr* rhs, var*& v, expr_ref& t, bool& inv) {
                inv = false; // true if invert the sign
                TRACE(is_var_and_ground, tout << "is_var_and_ground: " << mk_ismt2_pp(lhs, m) << " " << mk_ismt2_pp(rhs, m) << "\n";);
                if (is_var(lhs) && is_ground(rhs)) {
                    v = to_var(lhs);
                    t = rhs;
                    TRACE(is_var_and_ground, tout << "var and ground\n";);
                    return true;
                }
                else if (is_var(rhs) && is_ground(lhs)) {
                    v = to_var(rhs);
                    t = lhs;
                    TRACE(is_var_and_ground, tout << "ground and var\n";);
                    return true;
                }
                else {
                    expr_ref tmp(m);
                    if (is_var_plus_ground(lhs, inv, v, tmp) && is_ground(rhs)) {
                        if (inv)
                            mk_sub(tmp, rhs, t);
                        else
                            mk_sub(rhs, tmp, t);
                        return true;
                    }
                    if (is_var_plus_ground(rhs, inv, v, tmp) && is_ground(lhs)) {
                        if (inv)
                            mk_sub(tmp, lhs, t);
                        else
                            mk_sub(lhs, tmp, t);
                        return true;
                    }
                }
                return false;
            }

            bool is_var_and_ground(expr* lhs, expr* rhs, var*& v, expr_ref& t) {
                bool inv;
                return is_var_and_ground(lhs, rhs, v, t, inv);
            }

            bool is_x_eq_t_atom(expr* n, var*& v, expr_ref& t) {
                if (!is_app(n))
                    return false;
                if (m.is_eq(n))
                    return is_var_and_ground(to_app(n)->get_arg(0), to_app(n)->get_arg(1), v, t);
                return false;
            }

            bool is_var_minus_var(expr* n, var*& v1, var*& v2) {
                if (!is_add(n))
                    return false;
                expr* arg1 = to_app(n)->get_arg(0);
                expr* arg2 = to_app(n)->get_arg(1);
                if (!is_var(arg1))
                    std::swap(arg1, arg2);
                if (!is_var(arg1))
                    return false;
                expr* arg2_2;
                if (!is_times_minus_one(arg2, arg2_2))
                    return false;
                if (!is_var(arg2_2))
                    return false;
                v1 = to_var(arg1);
                v2 = to_var(arg2_2);
                return true;
            }

            bool is_var_and_var(expr* lhs, expr* rhs, var*& v1, var*& v2) {
                if (is_var(lhs) && is_var(rhs)) {
                    v1 = to_var(lhs);
                    v2 = to_var(rhs);
                    return true;
                }
                return
                    (is_var_minus_var(lhs, v1, v2) && is_zero(rhs)) ||
                    (is_var_minus_var(rhs, v1, v2) && is_zero(lhs));
            }

            bool is_x_eq_y_atom(expr* n, var*& v1, var*& v2) {
                return m.is_eq(n) && is_var_and_var(to_app(n)->get_arg(0), to_app(n)->get_arg(1), v1, v2);
            }

            bool is_x_gle_y_atom(expr* n, var*& v1, var*& v2) {
                return is_le_ge(n) && is_var_and_var(to_app(n)->get_arg(0), to_app(n)->get_arg(1), v1, v2);
            }

            bool is_x_gle_t_atom(expr* atom, bool sign, var*& v, expr_ref& t) {
                if (!is_app(atom))
                    return false;
                if (sign) {
                    bool r = is_le_ge(atom) && is_var_and_ground(to_app(atom)->get_arg(0), to_app(atom)->get_arg(1), v, t);
                    CTRACE(is_x_gle_t, r, tout << "is_x_gle_t: " << mk_ismt2_pp(atom, m) << "\n--->\n"
                        << mk_ismt2_pp(v, m) << " " << mk_ismt2_pp(t, m) << "\n";
                    tout << "sign: " << sign << "\n";);
                    return r;
                }
                else {
                    if (is_le_ge(atom)) {
                        expr_ref tmp(m);
                        bool le = is_le(atom);
                        bool inv = false;
                        if (is_var_and_ground(to_app(atom)->get_arg(0), to_app(atom)->get_arg(1), v, tmp, inv)) {
                            if (inv)
                                le = !le;
                            sort* s = tmp->get_sort();
                            expr_ref one(m);
                            one = mk_one(s);
                            if (le)
                                mk_add(tmp, one, t);
                            else
                                mk_sub(tmp, one, t);
                            TRACE(is_x_gle_t, tout << "is_x_gle_t: " << mk_ismt2_pp(atom, m) << "\n--->\n"
                                << mk_ismt2_pp(v, m) << " " << mk_ismt2_pp(t, m) << "\n";
                            tout << "sign: " << sign << "\n";);
                            return true;
                        }
                    }
                    return false;
                }
            }

            void reset_cache() {
                m_pos_cache.reset();
                m_neg_cache.reset();
            }

            obj_hashtable<expr>& get_cache(polarity pol) {
                return pol == POS ? m_pos_cache : m_neg_cache;
            }

            void visit_formula(expr* n, polarity pol) {
                if (is_ground(n))
                    return; // ground terms do not need to be visited.
                obj_hashtable<expr>& c = get_cache(pol);
                if (!c.contains(n)) {
                    m_ftodo.push_back(entry(n, pol));
                    c.insert(n);
                }
            }

            void visit_term(expr* n) {
                // ground terms do not need to be visited.
                if (!is_ground(n) && !m_pos_cache.contains(n)) {
                    m_ttodo.push_back(n);
                    m_pos_cache.insert(n);
                }
            }

            /**
               \brief Process uninterpreted applications.
            */
            void process_u_app(app* t) {
                unsigned num_args = t->get_num_args();
                for (unsigned i = 0; i < num_args; i++) {
                    expr* arg = t->get_arg(i);
                    if (is_var(arg)) {
                        SASSERT(t->get_decl()->get_domain(i) == to_var(arg)->get_sort());
                        insert_qinfo(alloc(f_var, m, t->get_decl(), i, to_var(arg)->get_idx()));
                        continue;
                    }

                    var* v;
                    expr_ref k(m);
                    if (is_var_plus_ground(arg, v, k)) {
                        insert_qinfo(alloc(f_var_plus_offset, m, t->get_decl(), i, v->get_idx(), k.get()));
                        continue;
                    }

                    visit_term(arg);
                }
            }


            /**
               \brief A term \c t is said to be a auf_select if
               it is of the form

               (select a i) Where:

               where a is ground or is_auf_select(a) == true
               and the indices are ground terms or variables.
            */
            bool is_auf_select(expr* t) const {
                if (!m_array_util.is_select(t))
                    return false;
                expr* a = to_app(t)->get_arg(0);
                if (!is_ground(a) && !is_auf_select(a))
                    return false;
                for (expr* arg : *to_app(t)) {
                    if (!is_ground(arg) && !is_var(arg))
                        return false;
                }
                return true;
            }

            /**
               \brief Process interpreted applications.
            */
            void process_i_app(app* t) {
                if (is_auf_select(t)) {
                    unsigned num_args = t->get_num_args();
                    app* array = to_app(t->get_arg(0));
                    visit_term(array); // array may be a nested array.
                    for (unsigned i = 1; i < num_args; i++) {
                        expr* arg = t->get_arg(i);
                        if (is_var(arg)) {
                            insert_qinfo(alloc(select_var, m, t, i, to_var(arg)->get_idx()));
                        }
                        else {
                            SASSERT(is_ground(arg));
                        }
                    }
                }
                else {
                    for (expr* arg : *t) {
                        visit_term(arg);
                    }
                }
            }

            void process_app(app* t) {
                SASSERT(!is_ground(t));

                if (t->get_family_id() != m.get_basic_family_id()) {
                    m_info->m_ng_decls.insert(t->get_decl());
                }

                if (is_uninterp(t)) {
                    process_u_app(t);
                }
                else {
                    process_i_app(t);
                }
            }

            void process_terms_on_stack() {
                while (!m_ttodo.empty()) {
                    expr* curr = m_ttodo.back();
                    m_ttodo.pop_back();

                    if (m.is_bool(curr)) {
                        // formula nested in a term.
                        visit_formula(curr, POS);
                        visit_formula(curr, NEG);
                        continue;
                    }

                    if (is_app(curr)) {
                        process_app(to_app(curr));
                    }
                    else if (is_var(curr)) {
                        m_info->m_is_auf = false; // unexpected occurrence of variable.
                    }
                    else {
                        SASSERT(is_lambda(curr));
                    }
                }
            }

            void process_literal(expr* atom, bool sign) {
                CTRACE(model_finder_bug, is_ground(atom), tout << mk_pp(atom, m) << "\n";);
                SASSERT(!is_ground(atom));
                SASSERT(m.is_bool(atom));

                if (is_var(atom)) {
                    if (sign) {
                        // atom (not X) can be viewed as X != true
                        insert_qinfo(alloc(x_neq_t, m, to_var(atom)->get_idx(), m.mk_true()));
                    }
                    else {
                        // atom X can be viewed as X != false
                        insert_qinfo(alloc(x_neq_t, m, to_var(atom)->get_idx(), m.mk_false()));
                    }
                    return;
                }

                if (is_app(atom)) {
                    var* v, * v1, * v2;
                    expr_ref t(m);
                    if (is_x_eq_t_atom(atom, v, t)) {
                        if (sign)
                            insert_qinfo(alloc(x_neq_t, m, v->get_idx(), t));
                        else
                            insert_qinfo(alloc(x_eq_t, m, v->get_idx(), t));
                    }
                    else if (is_x_eq_y_atom(atom, v1, v2)) {
                        if (sign)
                            insert_qinfo(alloc(x_neq_y, m, v1->get_idx(), v2->get_idx()));
                        else {
                            m_info->m_has_x_eq_y = true; // this atom is in the fringe of AUF
                            insert_qinfo(alloc(x_eq_y, m, v1->get_idx(), v2->get_idx()));
                        }
                    }
                    else if (sign && is_x_gle_y_atom(atom, v1, v2)) {
                        if (is_signed_le(atom))
                            insert_qinfo(alloc(x_sleq_y, m, v1->get_idx(), v2->get_idx()));
                        else
                            insert_qinfo(alloc(x_leq_y, m, v1->get_idx(), v2->get_idx()));
                    }
                    else if (is_x_gle_t_atom(atom, sign, v, t)) {
                        insert_qinfo(alloc(x_gle_t, m, v->get_idx(), t));
                    }
                    else {
                        process_app(to_app(atom));
                    }
                    return;
                }

                SASSERT(is_quantifier(atom));
                UNREACHABLE();
            }

            void process_literal(expr* atom, polarity pol) {
                process_literal(atom, pol == NEG);
            }

            void process_and_or(app* n, polarity p) {
                for (expr* arg : *n)
                    visit_formula(arg, p);
            }

            void process_ite(app* n, polarity p) {
                visit_formula(n->get_arg(0), p);
                visit_formula(n->get_arg(0), neg(p));
                visit_formula(n->get_arg(1), p);
                visit_formula(n->get_arg(2), p);
            }

            void process_iff(app* n) {
                visit_formula(n->get_arg(0), POS);
                visit_formula(n->get_arg(0), NEG);
                visit_formula(n->get_arg(1), POS);
                visit_formula(n->get_arg(1), NEG);
            }

            void checkpoint() {
                m_mf.checkpoint("quantifier_analyzer");
            }

            void process_formulas_on_stack() {
                while (!m_ftodo.empty()) {
                    checkpoint();
                    entry& e = m_ftodo.back();
                    expr* curr = e.first;
                    polarity pol = e.second;
                    m_ftodo.pop_back();
                    if (is_app(curr)) {
                        if (to_app(curr)->get_family_id() == m.get_basic_family_id() && m.is_bool(curr)) {
                            switch (static_cast<basic_op_kind>(to_app(curr)->get_decl_kind())) {
                            case OP_IMPLIES:
                            case OP_XOR:
                                UNREACHABLE(); // simplifier eliminated ANDs, IMPLIEs, and XORs
                                break;
                            case OP_OR:
                            case OP_AND:
                                process_and_or(to_app(curr), pol);
                                break;
                            case OP_NOT:
                                visit_formula(to_app(curr)->get_arg(0), neg(pol));
                                break;
                            case OP_ITE:
                                process_ite(to_app(curr), pol);
                                break;
                            case OP_EQ:
                                if (m.is_bool(to_app(curr)->get_arg(0))) {
                                    process_iff(to_app(curr));
                                }
                                else {
                                    process_literal(curr, pol);
                                }
                                break;
                            default:
                                process_literal(curr, pol);
                                break;
                            }
                        }
                        else {
                            process_literal(curr, pol);
                        }
                    }
                    else if (is_var(curr)) {
                        SASSERT(m.is_bool(curr));
                        process_literal(curr, pol);
                    }
                    else {
                        SASSERT(is_quantifier(curr));
                    }
                }
            }

            void process_formula(expr* n) {
                SASSERT(m.is_bool(n));
                visit_formula(n, POS);
            }

            void process_clause(expr* cls) {
                SASSERT(is_clause(m, cls));
                unsigned num_lits = get_clause_num_literals(m, cls);
                for (unsigned i = 0; i < num_lits; i++) {
                    expr* lit = get_clause_literal(m, cls, i);
                    SASSERT(is_literal(m, lit));
                    expr* atom;
                    bool   sign;
                    get_literal_atom_sign(m, lit, atom, sign);
                    if (!is_ground(atom))
                        process_literal(atom, sign);
                }
            }


        public:
            quantifier_analyzer(model_finder& mf, ast_manager& m) :
                m_mf(mf),
                m(m),
                m_mutil(m),
                m_array_util(m),
                m_arith_util(m),
                m_bv_util(m),
                m_info(nullptr) {
            }


            void operator()(quantifier_info* d) {
                m_info = d;
                quantifier* q = d->get_flat_q();
                if (m.is_lambda_def(q)) return;
                expr* e = q->get_expr();
                reset_cache();
                if (!m.inc()) return;
                m_ttodo.reset();
                m_ftodo.reset();

                if (is_clause(m, e)) {
                    process_clause(e);
                }
                else {
                    process_formula(e);
                }

                while (!m_ftodo.empty() || !m_ttodo.empty()) {
                    process_formulas_on_stack();
                    process_terms_on_stack();
                }

                m_info = nullptr;
            }

        };
    }


    // -----------------------------------
    //
    // model finder
    //
    // -----------------------------------

    model_finder::model_finder(ast_manager& m) :
        m(m),
        m_context(nullptr),
        m_analyzer(alloc(quantifier_analyzer, *this, m)),
        m_auf_solver(alloc(auf_solver, m)),
        m_dependencies(m),
        m_new_constraints(m) {
    }

    model_finder::~model_finder() {
        reset();
    }

    void model_finder::checkpoint() {
        checkpoint("model_finder");
    }

    void model_finder::checkpoint(char const* msg) {
        if (m_context && m_context->get_cancel_flag())
            throw tactic_exception(m_context->get_manager().limit().get_cancel_msg());
    }

    mf::quantifier_info* model_finder::get_quantifier_info(quantifier* q) {
        mf::quantifier_info* qi = nullptr;
        if (!m_q2info.find(q, qi)) {
            register_quantifier(q);
            qi = m_q2info[q];
        }
        return qi;
    }

    void model_finder::set_context(context* ctx) {
        SASSERT(m_context == nullptr);
        m_context = ctx;
    }

    void model_finder::register_quantifier(quantifier* q) {
        TRACE(model_finder, tout << "registering:\n" << q->get_id() << ": " << q << " " << &m_q2info << " " << mk_pp(q, m) << "\n";);
        quantifier_info* new_info = alloc(quantifier_info, *this, m, q);
        m_q2info.insert(q, new_info);
        m_quantifiers.push_back(q);
        m_analyzer->operator()(new_info);
        TRACE(model_finder, tout << "after analyzer:\n"; new_info->display(tout););
    }

    void model_finder::push_scope() {
        m_scopes.push_back(scope());
        scope& s = m_scopes.back();
        s.m_quantifiers_lim = m_quantifiers.size();
    }

    void model_finder::restore_quantifiers(unsigned old_size) {
        unsigned curr_size = m_quantifiers.size();
        SASSERT(old_size <= curr_size);
        for (unsigned i = old_size; i < curr_size; i++) {
            quantifier* q = m_quantifiers[i];
            SASSERT(m_q2info.contains(q));
            quantifier_info* info = get_quantifier_info(q);
            m_q2info.erase(q);
            dealloc(info);
        }
        m_quantifiers.shrink(old_size);
    }

    void model_finder::pop_scope(unsigned num_scopes) {
        unsigned lvl = m_scopes.size();
        SASSERT(num_scopes <= lvl);
        unsigned new_lvl = lvl - num_scopes;
        scope& s = m_scopes[new_lvl];
        restore_quantifiers(s.m_quantifiers_lim);
        m_scopes.shrink(new_lvl);
    }

    void model_finder::reset() {
        m_scopes.reset();
        m_dependencies.reset();
        restore_quantifiers(0);
        SASSERT(m_q2info.empty());
        SASSERT(m_quantifiers.empty());
    }

    void model_finder::init_search_eh() {
        // do nothing in the current version
    }

    void model_finder::collect_relevant_quantifiers(ptr_vector<quantifier>& qs) const {
        for (quantifier* q : m_quantifiers) {
            if (m_context->is_relevant(q) && m_context->get_assignment(q) == l_true)
                qs.push_back(q);
        }
    }


    void model_finder::process_auf(ptr_vector<quantifier> const& qs, proto_model* mdl) {
        m_auf_solver->reset();
        m_auf_solver->set_model(mdl);
        m_auf_solver->set_specrels(*m_context);

        for (quantifier* q : qs) {
            quantifier_info* qi = get_quantifier_info(q);
            qi->process_auf(*(m_auf_solver.get()), m_context);
        }
        m_auf_solver->mk_instantiation_sets();

        for (quantifier* q : qs) {
            quantifier_info* qi = get_quantifier_info(q);
            qi->populate_inst_sets(*(m_auf_solver.get()), m_context);
        }
        m_auf_solver->fix_model(m_new_constraints);
        TRACE(model_finder,
            for (quantifier* q : qs) {
                quantifier_info* qi = get_quantifier_info(q);
                quantifier* fq = qi->get_flat_q();
                tout << "#" << fq->get_id() << " ->\n" << mk_pp(fq, m) << "\n";
            }
        m_auf_solver->display_nodes(tout););
    }

    quantifier_macro_info* model_finder::operator()(quantifier* q) {
        return m_q2info[q];
    }

    void model_finder::process_simple_macros(ptr_vector<quantifier>& qs, ptr_vector<quantifier>& residue, proto_model* mdl) {
        simple_macro_solver sms(m, *this);
        sms(*mdl, qs, residue);
        TRACE(model_finder, tout << "model after processing simple macros:\n"; model_pp(tout, *mdl););
    }

    void model_finder::process_hint_macros(ptr_vector<quantifier>& qs, ptr_vector<quantifier>& residue, proto_model* mdl) {
        hint_macro_solver hms(m, *this);
        hms(*mdl, qs, residue);
        TRACE(model_finder, tout << "model after processing simple macros:\n"; model_pp(tout, *mdl););
    }

    void model_finder::process_non_auf_macros(ptr_vector<quantifier>& qs, ptr_vector<quantifier>& residue, proto_model* mdl) {
        non_auf_macro_solver nas(m, *this, m_dependencies);
        nas.set_mbqi_force_template(m_context->get_fparams().m_mbqi_force_template);
        nas(*mdl, qs, residue);
        TRACE(model_finder, tout << "model after processing non auf macros:\n"; model_pp(tout, *mdl););
    }

    /**
       \brief Clean leftovers from previous invocations to fix_model.
    */
    void model_finder::cleanup_quantifier_infos(ptr_vector<quantifier> const& qs) {
        for (quantifier* q : qs) {
            get_quantifier_info(q)->reset_the_one();
        }
    }

    /**
       \brief Try to satisfy quantifiers by modifying the model while preserving the satisfiability
       of all ground formulas asserted into the logical context.
    */
    void model_finder::fix_model(proto_model* m) {
        if (m_quantifiers.empty())
            return;
        ptr_vector<quantifier> qs;
        ptr_vector<quantifier> residue;
        collect_relevant_quantifiers(qs);
        if (qs.empty())
            return;
        TRACE(model_finder, tout << "trying to satisfy quantifiers, given model:\n"; model_pp(tout, *m););
        cleanup_quantifier_infos(qs);
        m_dependencies.reset();

        process_simple_macros(qs, residue, m);
        process_hint_macros(qs, residue, m);
        process_non_auf_macros(qs, residue, m);
        qs.append(residue);
        process_auf(qs, m);
    }

    quantifier* model_finder::get_flat_quantifier(quantifier* q) {
        quantifier_info* qinfo = get_quantifier_info(q);
        return qinfo->get_flat_q();
    }

    /**
       \brief Return the instantiation set associated with var i of q.

       \remark q is the quantifier before flattening.
    */
    mf::instantiation_set const* model_finder::get_uvar_inst_set(quantifier* q, unsigned i) {
        quantifier* flat_q = get_flat_quantifier(q);
        SASSERT(flat_q->get_num_decls() >= q->get_num_decls());
        mf::instantiation_set const* r = m_auf_solver->get_uvar_inst_set(flat_q, flat_q->get_num_decls() - q->get_num_decls() + i);
        TRACE(model_finder, tout << "q: #" << q->get_id() << "\n" << mk_pp(q, m) << "\nflat_q: " << mk_pp(flat_q, m)
            << "\ni: " << i << " " << flat_q->get_num_decls() - q->get_num_decls() + i << "\n";);
        if (r != nullptr)
            return r;
        // quantifier was not processed by AUF solver...
        // it must have been satisfied by "macro"/"hint".
        quantifier_info* qinfo = get_quantifier_info(q);
        SASSERT(qinfo);
        return qinfo->get_macro_based_inst_set(i, m_context, *(m_auf_solver.get()));
    }

    /**
       \brief Return an expression in the instantiation-set of q:i that evaluates to val.

       \remark q is the quantifier before flattening.

       Store in generation the generation of the result
    */
    expr* model_finder::get_inv(quantifier* q, unsigned i, expr* val, unsigned& generation) {
        instantiation_set const* s = get_uvar_inst_set(q, i);
        if (s == nullptr)
            return nullptr;
        expr* t = s->get_inv(val);
        if (m_auf_solver->is_default_representative(t))
            return val;
        if (t != nullptr) {
            generation = s->get_generation(t);
        }
        return t;
    }

    /**
       \brief Assert constraints restricting the possible values of the skolem constants can be assigned to.
       The idea is to restrict them to the values in the instantiation sets.

       \remark q is the quantifier before flattening.

       Return true if something was asserted.
    */
    bool model_finder::restrict_sks_to_inst_set(context* aux_ctx, quantifier* q, expr_ref_vector const& sks) {
        // Note: we currently add instances of q instead of flat_q.
        // If the user wants instances of flat_q, it should use PULL_NESTED_QUANTIFIERS=true. This option
        // will guarantee that q == flat_q.
        //
        // Since we only care about q (and its bindings), it only makes sense to restrict the variables of q.
        bool asserted_something = false;
        unsigned num_decls = q->get_num_decls();
        // Remark: sks were created for the flat version of q.
        SASSERT(get_flat_quantifier(q)->get_num_decls() == sks.size());
        SASSERT(sks.size() >= num_decls);
        for (unsigned i = 0; i < num_decls; i++) {
            expr* sk = sks.get(num_decls - i - 1);
            instantiation_set const* s = get_uvar_inst_set(q, i);
            if (s == nullptr)
                continue; // nothing to do
            obj_map<expr, expr*> const& inv = s->get_inv_map();
            if (inv.empty())
                continue; // nothing to do
            ptr_buffer<expr> eqs;
            for (auto const& kv : inv) {
                expr* val = kv.m_key;
                eqs.push_back(m.mk_eq(sk, val));
            }
            expr_ref new_cnstr(m);
            new_cnstr = m.mk_or(eqs.size(), eqs.data());
            TRACE(model_finder, tout << "assert_restriction:\n" << mk_pp(new_cnstr, m) << "\n";);
            aux_ctx->assert_expr(new_cnstr);
            asserted_something = true;
        }
        return asserted_something;
    }

    void model_finder::restart_eh() {
        unsigned sz = m_new_constraints.size();
        if (sz > 0) {
            for (unsigned i = 0; i < sz; i++) {
                expr* c = m_new_constraints.get(i);
                TRACE(model_finder_bug_detail, tout << "asserting new constraint: " << mk_pp(c, m) << "\n";);
                m_context->internalize(c, true);
                literal l(m_context->get_literal(c));
                m_context->mark_as_relevant(l);
                // asserting it as an AXIOM
                m_context->assign(l, b_justification());
            }
            m_new_constraints.reset();
        }
    }
}
