/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    pdr_farkas_learner.cpp

Abstract:

    Proviced abstract interface and some inplementations of algorithms
    for strenghtning lemmas

Author:

    Krystof Hoder (t-khoder) 2011-11-1.

Revision History:

--*/

#include "ast_smt2_pp.h"
#include "array_decl_plugin.h"
#include "bool_rewriter.h"
#include "dl_decl_plugin.h"
#include "for_each_expr.h"
#include "dl_util.h"
#include "rewriter.h"
#include "rewriter_def.h"
#include "pdr_util.h"
#include "pdr_farkas_learner.h"
#include "th_rewriter.h"
#include "pdr_interpolant_provider.h"
#include "ast_ll_pp.h"
#include "arith_bounds_tactic.h"
#include "proof_utils.h"
#include "reg_decl_plugins.h"

#define PROOF_MODE PGM_FINE
//#define PROOF_MODE PGM_COARSE

namespace pdr {

    class farkas_learner::constr {

        ast_manager&     m;
        arith_util       a;
        app_ref_vector   m_ineqs;
        vector<rational> m_coeffs;

        void mk_coerce(expr*& e1, expr*& e2) {
            if (a.is_int(e1) && a.is_real(e2)) {
                e1 = a.mk_to_real(e1);
            }
            else if (a.is_int(e2) && a.is_real(e1)) {
                e2 = a.mk_to_real(e2);
            }
        }

        app* mk_add(expr* e1, expr* e2) {
            mk_coerce(e1, e2);
            return a.mk_add(e1, e2);
        }

        app* mk_mul(expr* e1, expr* e2) {
            mk_coerce(e1, e2);
            return a.mk_mul(e1, e2);
        }

        app* mk_le(expr* e1, expr* e2) {
            mk_coerce(e1, e2);
            return a.mk_le(e1, e2);
        }

        app* mk_ge(expr* e1, expr* e2) {
            mk_coerce(e1, e2);
            return a.mk_ge(e1, e2);
        }

        app* mk_gt(expr* e1, expr* e2) {
            mk_coerce(e1, e2);
            return a.mk_gt(e1, e2);
        }

        app* mk_lt(expr* e1, expr* e2) {
            mk_coerce(e1, e2);
            return a.mk_lt(e1, e2);
        }

        void mul(rational const& c, expr* e, expr_ref& res) {
            expr_ref tmp(m);
            if (c.is_one()) {
                tmp = e;
            }
            else {
                tmp = mk_mul(a.mk_numeral(c, c.is_int() && a.is_int(e)), e);
            }
            res = mk_add(res, tmp);
        }

        bool is_int_sort(app* c) {
            SASSERT(m.is_eq(c) || a.is_le(c) || a.is_lt(c) || a.is_gt(c) || a.is_ge(c));
            SASSERT(a.is_int(c->get_arg(0)) || a.is_real(c->get_arg(0)));
            return a.is_int(c->get_arg(0));
        }

        bool is_int_sort() {
            SASSERT(!m_ineqs.empty());
            return is_int_sort(m_ineqs[0].get());
        }

        void normalize_coeffs() {
            rational l(1);
            for (unsigned i = 0; i < m_coeffs.size(); ++i) {
                l = lcm(l, denominator(m_coeffs[i]));
            }
            if (!l.is_one()) {
                for (unsigned i = 0; i < m_coeffs.size(); ++i) {
                    m_coeffs[i] *= l;
                }
            }
        }

        app* mk_one() {
            return a.mk_numeral(rational(1), true);
        }

        app* fix_sign(bool is_pos, app* c) {
            expr* x, *y;
            SASSERT(m.is_eq(c) || a.is_le(c) || a.is_lt(c) || a.is_gt(c) || a.is_ge(c));
            bool is_int = is_int_sort(c);
            if (is_int && is_pos && (a.is_lt(c, x, y) || a.is_gt(c, y, x))) {
                return mk_le(mk_add(x, mk_one()), y);
            }
            if (is_int && !is_pos && (a.is_le(c, x, y) || a.is_ge(c, y, x))) {
                // !(x <= y) <=> x > y <=> x >= y + 1
                return mk_ge(x, mk_add(y, mk_one()));
            }
            if (is_pos) {
                return c;
            }
            if (a.is_le(c, x, y)) return mk_gt(x, y);
            if (a.is_lt(c, x, y)) return mk_ge(x, y);
            if (a.is_ge(c, x, y)) return mk_lt(x, y);
            if (a.is_gt(c, x, y)) return mk_le(x, y);
            UNREACHABLE();
            return c;
        }
        
    public:
        constr(ast_manager& m) : m(m), a(m), m_ineqs(m) {}

        /** add a multiple of constraint c to the current constr */
        void add(rational const & coef, app * c) {
            bool is_pos = true;
            expr* e;
            while (m.is_not(c, e)) {
                is_pos = !is_pos;
                c = to_app(e);
            }

            if (!coef.is_zero() && !m.is_true(c)) {
                m_coeffs.push_back(coef);                
                m_ineqs.push_back(fix_sign(is_pos, c));                
            }
        }

        //
        // Extract the complement of premises multiplied by Farkas coefficients.
        //
        void get(expr_ref& res) {
            if (m_coeffs.empty()) {
                res = m.mk_false();
                return;
            }
            bool is_int = is_int_sort();
            if (is_int) {                
                normalize_coeffs();
            }
            TRACE("pdr", 
                  for (unsigned i = 0; i < m_coeffs.size(); ++i) {
                      tout << m_coeffs[i] << ": " << mk_pp(m_ineqs[i].get(), m) << "\n";
                  }
                  );
            app_ref zero(a.mk_numeral(rational::zero(), is_int), m);
            res = zero;
            bool is_strict = false;
            bool is_eq     = true;
            expr* x, *y;
            for (unsigned i = 0; i < m_coeffs.size(); ++i) {
                app* c = m_ineqs[i].get();
                if (m.is_eq(c, x, y)) {
                    mul(m_coeffs[i],  x, res);
                    mul(-m_coeffs[i], y, res);
                }
                if (a.is_lt(c, x, y) || a.is_gt(c, y, x)) {
                    mul(m_coeffs[i],  x, res);
                    mul(-m_coeffs[i], y, res);
                    is_strict = true;
                    is_eq = false;
                }
                if (a.is_le(c, x, y) || a.is_ge(c, y, x)) {
                    mul(m_coeffs[i],  x, res);
                    mul(-m_coeffs[i], y, res);
                    is_eq = false;
                }
            }

            zero = a.mk_numeral(rational::zero(), a.is_int(res));
            if (is_eq) {
                res = m.mk_eq(res, zero);
            }
            else if (is_strict) {
                res = mk_lt(res, zero);
            }
            else {
                res = mk_le(res, zero);
            }            
            res = m.mk_not(res);
            th_rewriter rw(m);
            proof_ref pr(m);
            expr_ref tmp(m);
            rw(res, tmp, pr);
            fix_dl(tmp);
            res = tmp;
        }

        // patch: swap addends to make static 
        // features recognize difference constraint.
        void fix_dl(expr_ref& r) {
            expr* e;
            if (m.is_not(r, e)) {
                r = e;
                fix_dl(r);
                r = m.mk_not(r);
                return;
            }
            expr* e1, *e2, *e3, *e4;
            if ((m.is_eq(r, e1, e2) || a.is_lt(r, e1, e2) || a.is_gt(r, e1, e2) || 
                 a.is_le(r, e1, e2) || a.is_ge(r, e1, e2))) {
                if (a.is_add(e1, e3, e4) && a.is_mul(e3)) {
                    r = m.mk_app(to_app(r)->get_decl(), a.mk_add(e4,e3), e2);
                }
            }
        }
    };
    
    farkas_learner::farkas_learner(front_end_params& params, ast_manager& outer_mgr) 
        : m_proof_params(get_proof_params(params)), 
          m_pr(PROOF_MODE),
          p2o(m_pr, outer_mgr),
          o2p(outer_mgr, m_pr)
    {
        reg_decl_plugins(m_pr);
        m_ctx = alloc(smt::kernel, m_pr, m_proof_params);
    }

    front_end_params farkas_learner::get_proof_params(front_end_params& orig_params) {
        front_end_params res(orig_params);
        res.m_proof_mode = PROOF_MODE;
        res.m_arith_bound_prop = BP_NONE;
        // temp hack to fix the build
        // res.m_conflict_resolution_strategy = CR_ALL_DECIDED;
        res.m_arith_auto_config_simplex = true;
        res.m_arith_propagate_eqs = false;
        res.m_arith_eager_eq_axioms = false;
        res.m_arith_eq_bounds = false;
        return res;
    }

    class farkas_learner::equality_expander_cfg : public default_rewriter_cfg
    {
        ast_manager& m;
        arith_util   m_ar;
    public:
        equality_expander_cfg(ast_manager& m) : m(m), m_ar(m) {}

        bool get_subst(expr * s, expr * & t, proof * & t_pr) {
            expr * x, *y;
            if (m.is_eq(s, x, y) && (m_ar.is_int(x) || m_ar.is_real(x))) {
                t = m.mk_and(m_ar.mk_ge(x, y), m_ar.mk_le(x, y));
                return true;
            }
            else {
                return false;
            }
        }
    };

    class collect_pure_proc {
        func_decl_set& m_symbs;
    public:
        collect_pure_proc(func_decl_set& s):m_symbs(s) {}

        void operator()(app* a) {
            if (a->get_family_id() == null_family_id) {
                m_symbs.insert(a->get_decl());
            }
        }
        void operator()(var*) {}
        void operator()(quantifier*) {}
    };


    bool farkas_learner::get_lemma_guesses(expr * A_ext, expr * B_ext, expr_ref_vector& lemmas)
    {
        expr_ref A(o2p(A_ext), m_pr);
        expr_ref B(o2p(B_ext), m_pr);
        proof_ref pr(m_pr);
        expr_ref tmp(m_pr);
        expr_ref_vector ilemmas(m_pr);
        equality_expander_cfg ee_rwr_cfg(m_pr);
        rewriter_tpl<equality_expander_cfg> ee_rwr(m_pr, false, ee_rwr_cfg);

        lemmas.reset();

        ee_rwr(A, A);
        ee_rwr(B, B);

        expr_set bs;
        expr_ref_vector blist(m_pr);
        datalog::flatten_and(B, blist);
        for (unsigned i = 0; i < blist.size(); ++i) {
            bs.insert(blist[i].get());
        }


        if (!m_ctx) {
            m_ctx = alloc(smt::kernel, m_pr, m_proof_params);
        }

        m_ctx->push();
        m_ctx->assert_expr(A);
        expr_set::iterator it = bs.begin(), end = bs.end();
        for (; it != end; ++it) {
            m_ctx->assert_expr(*it);
        }
        lbool res = m_ctx->check();
        bool is_unsat = res == l_false;
        if (is_unsat) {
            pr = m_ctx->get_proof();
            get_lemmas(m_ctx->get_proof(), bs, ilemmas);
            for (unsigned i = 0; i < ilemmas.size(); ++i) {
                lemmas.push_back(p2o(ilemmas[i].get()));
            }
        }
        m_ctx->pop(1);

        IF_VERBOSE(3, {
                for (unsigned i = 0; i < ilemmas.size(); ++i) {
                    verbose_stream() << "B': " << mk_pp(ilemmas[i].get(), m_pr) << "\n";
                }
            });

        TRACE("farkas_learner",
              tout << (is_unsat?"unsat":"sat") << "\n";
              tout << "A: " << mk_pp(A_ext, m_ctx->m()) << "\n";
              tout << "B: " << mk_pp(B_ext, m_ctx->m()) << "\n";
              for (unsigned i = 0; i < lemmas.size(); ++i) {
                  tout << "B': " << mk_pp(ilemmas[i].get(), m_pr) << "\n";
              });
        DEBUG_CODE(
            if (is_unsat) {
                m_ctx->push();
                m_ctx->assert_expr(A);
                for (unsigned i = 0; i < ilemmas.size(); ++i) {
                    m_ctx->assert_expr(ilemmas[i].get());
                }
                lbool res2 = m_ctx->check();
                SASSERT(l_false == res2);
                m_ctx->pop(1);
            }
            );
        return is_unsat;
    }

    //
    // Perform simple subsumption check of lemmas.
    //
    void farkas_learner::simplify_lemmas(expr_ref_vector& lemmas) {
        ast_manager& m = lemmas.get_manager();
        goal_ref g(alloc(goal, m, false, false, false));
        TRACE("farkas_simplify_lemmas",            
              for (unsigned i = 0; i < lemmas.size(); ++i) {
                  tout << mk_pp(lemmas[i].get(), m) << "\n";
              });

        for (unsigned i = 0; i < lemmas.size(); ++i) {
            g->assert_expr(lemmas[i].get()); 
        }
        expr_ref tmp(m);
        model_converter_ref mc;
        proof_converter_ref pc;
        expr_dependency_ref core(m);
        goal_ref_buffer result;
        tactic_ref simplifier = mk_arith_bounds_tactic(m);
        (*simplifier)(g, result, mc, pc, core);
        lemmas.reset();
        SASSERT(result.size() == 1);
        goal* r = result[0];
        for (unsigned i = 0; i < r->size(); ++i) {
            lemmas.push_back(r->form(i));
        }
    }


    void farkas_learner::combine_constraints(unsigned n, app * const * lits, rational const * coeffs, expr_ref& res)
    {
        ast_manager& m = res.get_manager();
        constr res_c(m);
        for(unsigned i = 0; i < n; ++i) {
            res_c.add(coeffs[i], lits[i]);
        }
        res_c.get(res);
    }

    class farkas_learner::constant_replacer_cfg : public default_rewriter_cfg
    {
        ast_manager& m;
        const obj_map<expr, expr *>& m_translation;
    public:
        constant_replacer_cfg(ast_manager& m, const obj_map<expr, expr *>& translation)
            : m(m), m_translation(translation)
        { }

        bool get_subst(expr * s, expr * & t, proof * & t_pr) {
            return m_translation.find(s, t);
        }
    };

    // every uninterpreted symbol is in symbs
    class is_pure_expr_proc {
        func_decl_set const& m_symbs;
    public:
        struct non_pure {};

        is_pure_expr_proc(func_decl_set const& s):m_symbs(s) {}

        void operator()(app* a) {
            if (a->get_family_id() == null_family_id) {
                if (!m_symbs.contains(a->get_decl())) {
                    throw non_pure();
                }
            }
        }
        void operator()(var*) {}
        void operator()(quantifier*) {}
    };

    bool farkas_learner::is_pure_expr(func_decl_set const& symbs, expr* e) const {
        is_pure_expr_proc proc(symbs);
        try {
            for_each_expr(proc, e);
        }
        catch (is_pure_expr_proc::non_pure) {
            return false;
        }
        return true;
    };


    /**
       Revised version of Farkas strengthener.
       1. Mark B-pure nodes as derivations that depend only on B.
       2. Collect B-influenced nodes
       3. (optional) Permute B-pure units over resolution steps to narrow dependencies on B.
       4. Weaken B-pure units for resolution with Farkas Clauses.
       5. Add B-pure units elsewhere.

       Rules:
       - hypothesis h |- h

                    H |- false
       - lemma      ----------
                     |- not H

                    Th |- L \/ C   H |- not L
       - th-lemma   -------------------------
                           H  |- C

         Note: C is false for theory axioms, C is unit literal for propagation.

       - rewrite        |- t = s

                        H |- t = s
       - monotonicity   ----------------
                       H |- f(t) = f(s)

                        H |- t = s H' |- s = u
       - trans          ----------------------
                            H, H' |- t = u

                        H |- C \/ L  H' |- not L
       - unit_resolve   ------------------------
                                H, H' |- C

                        H |- a ~ b   H' |- a
       - mp             --------------------
                             H, H' |- b

       - def-axiom       |- C
       
       - asserted        |- f

       Mark nodes by:
          - Hypotheses
          - Dependency on bs
          - Dependency on A

       A node is unit derivable from bs if:
          - It has no hypotheses.
          - It depends on bs.
          - It does not depend on A.

       NB: currently unit derivable is not symmetric: A clause can be 
       unit derivable, but a unit literal with hypotheses is not.
       This is clearly wrong, because hypotheses are just additional literals
       in a clausal version.

       NB: the routine is not interpolating, though an interpolating variant would 
       be preferrable because then we can also use it for model propagation.

       We collect the unit derivable nodes from bs.
       These are the weakenings of bs, besides the 
       units under Farkas.
                    
    */

#define INSERT(_x_) if (!lemma_set.contains(_x_)) { lemma_set.insert(_x_); lemmas.push_back(_x_); }

    void farkas_learner::get_lemmas(proof* root, expr_set const& bs, expr_ref_vector& lemmas) {
        ast_manager& m = lemmas.get_manager();
        bool_rewriter brwr(m);
        func_decl_set Bsymbs;
        collect_pure_proc collect_proc(Bsymbs);
        expr_set::iterator it = bs.begin(), end = bs.end();
        for (; it != end; ++it) {
            for_each_expr(collect_proc, *it);
        }

        proof_ref pr(root, m);
        proof_utils::reduce_hypotheses(pr);
        proof_utils::permute_unit_resolution(pr);
        IF_VERBOSE(3, verbose_stream() << "Reduced proof:\n" << mk_ismt2_pp(pr, m) << "\n";);
        
        ptr_vector<expr_set> hyprefs;
        obj_map<expr, expr_set*> hypmap;
        obj_hashtable<expr> lemma_set;
        ast_mark b_depend, a_depend, visited, b_closed;
        expr_set* empty_set = alloc(expr_set);
        hyprefs.push_back(empty_set); 
        ptr_vector<proof> todo;
        TRACE("pdr_verbose", tout << mk_pp(pr, m) << "\n";);
        todo.push_back(pr);
        while (!todo.empty()) {
            proof* p = todo.back();
            SASSERT(m.is_proof(p));
            if (visited.is_marked(p)) {
                todo.pop_back();
                continue;
            }
            bool all_visit = true;
            for (unsigned i = 0; i < m.get_num_parents(p); ++i) {
                expr* arg = p->get_arg(i);
                SASSERT(m.is_proof(arg));
                if (!visited.is_marked(arg)) {
                    all_visit = false;
                    todo.push_back(to_app(arg));
                }
            }
            if (!all_visit) {
                continue;
            }
            visited.mark(p, true);
            todo.pop_back();

            // retrieve hypotheses and dependencies on A, bs.
            bool b_dep = false, a_dep = false;
            expr_set* hyps = empty_set;
            for (unsigned i = 0; i < m.get_num_parents(p); ++i) {
                expr* arg = p->get_arg(i);
                a_dep = a_dep || a_depend.is_marked(arg);
                b_dep = b_dep || b_depend.is_marked(arg);
                expr_set* hyps2 = hypmap.find(arg);
                if (hyps != hyps2 && !hyps2->empty()) {
                    if (hyps->empty()) {
                        hyps = hyps2;
                    }
                    else {
                        expr_set* hyps3 = alloc(expr_set);
                        datalog::set_union(*hyps3, *hyps);
                        datalog::set_union(*hyps3, *hyps2);
                        hyps = hyps3;
                        hyprefs.push_back(hyps);
                    }
                }
            }
            hypmap.insert(p, hyps);
            a_depend.mark(p, a_dep);
            b_depend.mark(p, b_dep);

#define IS_B_PURE(_p) (b_depend.is_marked(_p) && !a_depend.is_marked(_p) && hypmap.find(_p)->empty())


            // Add lemmas that depend on bs, have no hypotheses, don't depend on A.
            if ((!hyps->empty() || a_depend.is_marked(p)) && 
                b_depend.is_marked(p) && !is_farkas_lemma(m, p)) {
                for (unsigned i = 0; i < m.get_num_parents(p); ++i) {                
                    app* arg = to_app(p->get_arg(i));
                    if (IS_B_PURE(arg)) {
                        expr* fact = m.get_fact(arg);
                        if (is_pure_expr(Bsymbs, fact)) {
                            TRACE("farkas_learner", 
                                  tout << "Add: " << mk_pp(m.get_fact(arg), m) << "\n";
                                  tout << mk_pp(arg, m) << "\n";
                                  );
                            INSERT(fact);
                        }
                        else {
                            get_asserted(p, bs, b_closed, lemma_set, lemmas);
                            b_closed.mark(p, true);
                        }
                    }
                }
            }
            
            switch(p->get_decl_kind()) {
            case PR_ASSERTED:
                if (bs.contains(m.get_fact(p))) {
                    b_depend.mark(p, true);
                }
                else {
                    a_depend.mark(p, true);
                }
                break;
            case PR_HYPOTHESIS: {
                SASSERT(hyps == empty_set);
                hyps = alloc(expr_set);
                hyps->insert(m.get_fact(p));
                hyprefs.push_back(hyps);
                hypmap.insert(p, hyps);
                break;
            }
            case PR_DEF_AXIOM: {
                if (!is_pure_expr(Bsymbs, m.get_fact(p))) {
                    a_depend.mark(p, true);
                }
                break;
            }
            case PR_LEMMA: {
                expr_set* hyps2 = alloc(expr_set);
                hyprefs.push_back(hyps2);
                datalog::set_union(*hyps2, *hyps); 
                hyps = hyps2;
                expr* fml = m.get_fact(p);
                hyps->remove(fml);
                if (m.is_or(fml)) {
                    for (unsigned i = 0; i < to_app(fml)->get_num_args(); ++i) {
                        expr* f = to_app(fml)->get_arg(i);
                        expr_ref hyp(m);
                        brwr.mk_not(f, hyp);
                        hyps->remove(hyp);
                    }
                }
                hypmap.insert(p, hyps);
                break;
            }
            case PR_TH_LEMMA: {
                if (!is_farkas_lemma(m, p)) break;
               
                SASSERT(m.has_fact(p));
                unsigned prem_cnt = m.get_num_parents(p);
                func_decl * d = p->get_decl();
                SASSERT(d->get_num_parameters() >= prem_cnt+2);
                SASSERT(d->get_parameter(0).get_symbol() == "arith");
                SASSERT(d->get_parameter(1).get_symbol() == "farkas");
                parameter const* params = d->get_parameters() + 2;

                app_ref_vector lits(m);
                expr_ref tmp(m);
                unsigned num_b_pures = 0;
                rational coef;
                vector<rational> coeffs;

                TRACE("farkas_learner", 
                        for (unsigned i = 0; i < prem_cnt; ++i) {
                            VERIFY(params[i].is_rational(coef));
                            proof* prem = to_app(p->get_arg(i));
                            bool b_pure = IS_B_PURE(prem);
                            tout << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m.get_fact(prem), m) << "\n";
                        }
                        tout << mk_pp(m.get_fact(p), m) << "\n";
                        );

                // NB. Taking 'abs' of coefficients is a workaround.
                // The Farkas coefficient extraction in arith_core must be wrong.
                // The coefficients would be always positive relative to the theory lemma.

                for(unsigned i = 0; i < prem_cnt; ++i) {                    
                    expr * prem_e = p->get_arg(i);
                    SASSERT(is_app(prem_e));
                    proof * prem = to_app(prem_e);
                   
                    if(IS_B_PURE(prem)) {
                        ++num_b_pures;
                    }
                    else {
                        VERIFY(params[i].is_rational(coef));
                        lits.push_back(to_app(m.get_fact(prem)));
                        coeffs.push_back(abs(coef));
                    }
                }
                params += prem_cnt;
                if (prem_cnt + 2 < d->get_num_parameters()) {
                    unsigned num_args = 1;
                    expr* fact = m.get_fact(p);
                    expr* const* args = &fact;
                    if (m.is_or(fact)) {
                        app* _or = to_app(fact);
                        num_args = _or->get_num_args();
                        args = _or->get_args();                        
                    }
                    SASSERT(prem_cnt + 2 + num_args == d->get_num_parameters());
                    for (unsigned i = 0; i < num_args; ++i) {
                        expr* prem_e = args[i];
                        brwr.mk_not(prem_e, tmp);
                        VERIFY(params[i].is_rational(coef));
                        SASSERT(is_app(tmp));
                        lits.push_back(to_app(tmp));
                        coeffs.push_back(abs(coef));
                    }

                }
                SASSERT(coeffs.size() == lits.size());
                if (num_b_pures > 0) {
                    expr_ref res(m);
                    combine_constraints(coeffs.size(), lits.c_ptr(), coeffs.c_ptr(), res);
                    TRACE("farkas_learner", tout << "Add: " << mk_pp(res, m) << "\n";);
                    INSERT(res);
                    b_closed.mark(p, true);
                }
            }
            default:
                break;
            }
        }

        std::for_each(hyprefs.begin(), hyprefs.end(), delete_proc<expr_set>());
        simplify_lemmas(lemmas);
    }

    void farkas_learner::get_asserted(proof* p, expr_set const& bs, ast_mark& b_closed, obj_hashtable<expr>& lemma_set, expr_ref_vector& lemmas) {
        ast_manager& m = lemmas.get_manager();
        ast_mark visited;
        proof* p0 = p;
        ptr_vector<proof> todo;        
        todo.push_back(p);
                      
        while (!todo.empty()) {
            p = todo.back();
            todo.pop_back();
            if (visited.is_marked(p) || b_closed.is_marked(p)) {
                continue;
            }
            visited.mark(p, true);
            for (unsigned i = 0; i < m.get_num_parents(p); ++i) {
                expr* arg = p->get_arg(i);
                SASSERT(m.is_proof(arg));
                todo.push_back(to_app(arg));
            }
            if (p->get_decl_kind() == PR_ASSERTED &&
                bs.contains(m.get_fact(p))) {
                expr* fact = m.get_fact(p);
                TRACE("farkas_learner", 
                      tout << mk_ll_pp(p0,m) << "\n";
                      tout << "Add: " << mk_pp(p,m) << "\n";);
                INSERT(fact);
                b_closed.mark(p, true);
            }
        }
    }


    bool farkas_learner::is_farkas_lemma(ast_manager& m, expr* e) {
        app * a;
        func_decl* d;
        symbol sym;
        return 
            is_app(e) && 
            (a = to_app(e), d = a->get_decl(), true) &&
            PR_TH_LEMMA == a->get_decl_kind() &&
            d->get_num_parameters() >= 2 &&
            d->get_parameter(0).is_symbol(sym) && sym == "arith" &&
            d->get_parameter(1).is_symbol(sym) && sym == "farkas" &&
            d->get_num_parameters() >= m.get_num_parents(to_app(e)) + 2;
    };


    void farkas_learner::test()  {
        front_end_params params;
        enable_trace("farkas_learner");
               
        bool res;
        ast_manager m;
        reg_decl_plugins(m);        
        arith_util a(m);
        pdr::farkas_learner fl(params, m);
        expr_ref_vector lemmas(m);
        
        sort_ref int_s(a.mk_int(), m);
        expr_ref x(m.mk_const(symbol("x"), int_s), m);
        expr_ref y(m.mk_const(symbol("y"), int_s), m);
        expr_ref z(m.mk_const(symbol("z"), int_s), m);    
        expr_ref u(m.mk_const(symbol("u"), int_s), m);  
        expr_ref v(m.mk_const(symbol("v"), int_s), m);

        // A: x > y >= z
        // B: x < z
        // Farkas: x <= z
        expr_ref A(m.mk_and(a.mk_gt(x,y), a.mk_ge(y,z)),m);        
        expr_ref B(a.mk_gt(z,x),m);        
        res = fl.get_lemma_guesses(A, B, lemmas);        
        std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n";

        // A: x > y >= z + 2
        // B: x = 1, z = 8
        // Farkas: x <= z + 2
        expr_ref one(a.mk_numeral(rational(1), true), m);
        expr_ref two(a.mk_numeral(rational(2), true), m);
        expr_ref eight(a.mk_numeral(rational(8), true), m);
        A = m.mk_and(a.mk_gt(x,y),a.mk_ge(y,a.mk_add(z,two)));
        B = m.mk_and(m.mk_eq(x,one), m.mk_eq(z, eight));
        res = fl.get_lemma_guesses(A, B, lemmas);        
        std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n";

        // A: x > y >= z \/ x >= u > z
        // B: z > x + 1
        // Farkas: z >= x
        A = m.mk_or(m.mk_and(a.mk_gt(x,y),a.mk_ge(y,z)),m.mk_and(a.mk_ge(x,u),a.mk_gt(u,z)));
        B = a.mk_gt(z, a.mk_add(x,one));
        res = fl.get_lemma_guesses(A, B, lemmas);        
        std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n";

        // A: (x > y >= z \/ x >= u > z \/ u > v)
        // B: z > x + 1 & not (u > v)
        // Farkas: z >= x & not (u > v)
        A = m.mk_or(m.mk_and(a.mk_gt(x,y),a.mk_ge(y,z)),m.mk_and(a.mk_ge(x,u),a.mk_gt(u,z)), a.mk_gt(u, v));
        B = m.mk_and(a.mk_gt(z, a.mk_add(x,one)), m.mk_not(a.mk_gt(u, v)));
        res = fl.get_lemma_guesses(A, B, lemmas);        
        std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n";
        
    }

    void farkas_learner::collect_statistics(statistics& st) const {
        if (m_ctx) {
            m_ctx->collect_statistics(st);
        }
    }


    void farkas_learner::test(char const* filename)  {
#if 0
        // [Leo]: disabled because it uses an external component: SMT 1.0 parser
        if (!filename) {
            test();
            return;
        }
        ast_manager m;
        reg_decl_plugins(m);  
        scoped_ptr<smtlib::parser> p = smtlib::parser::create(m);
        p->initialize_smtlib();

        if (!p->parse_file(filename)) {
            warning_msg("Failed to parse file %s\n", filename);
            return;
        }
        expr_ref A(m), B(m);

        smtlib::theory::expr_iterator it = p->get_benchmark()->begin_axioms();
        smtlib::theory::expr_iterator end = p->get_benchmark()->end_axioms();
        A = m.mk_and(static_cast<unsigned>(end-it), it);

        it = p->get_benchmark()->begin_formulas();
        end = p->get_benchmark()->end_formulas();
        B = m.mk_and(static_cast<unsigned>(end-it), it);

        front_end_params params;
        pdr::farkas_learner fl(params, m);
        expr_ref_vector lemmas(m);
        bool res = fl.get_lemma_guesses(A, B, lemmas);        
        std::cout << "lemmas: " << pp_cube(lemmas, m) << "\n";
#endif
    }
};

