/*++
Copyright (c) 2014 Microsoft Corporation

Module Name:

    pb_sls.cpp

Abstract:
   
    SLS for PB optimization.

Author:

    Nikolaj Bjorner (nbjorner) 2014-03-18

Notes:

--*/
#include "opt/pb_sls.h"
#include "smt/smt_literal.h"
#include "ast/ast_pp.h"
#include "ast/rewriter/th_rewriter.h"
#include "sat/sat_types.h"

namespace smt {

    class index_set {

        unsigned_vector m_elems;
        unsigned_vector m_index;        
    public:
        unsigned num_elems() const { return m_elems.size(); }       
        unsigned operator[](unsigned idx) const { return m_elems[idx]; }
        void reset() { m_elems.reset(); m_index.reset(); }        
        bool empty() const { return m_elems.empty(); }        

        bool contains(unsigned idx) const {
            return 
                (idx < m_index.size()) && 
                (m_index[idx] < m_elems.size()) && 
                (m_elems[m_index[idx]] == idx);
        }
        
        void insert(unsigned idx) {
            m_index.reserve(idx+1);
            if (!contains(idx)) {
                m_index[idx] = m_elems.size();
                m_elems.push_back(idx);
            }
        }
        
        void remove(unsigned idx) {
            if (!contains(idx)) return;
            unsigned pos = m_index[idx];
            m_elems[pos] = m_elems.back();
            m_index[m_elems[pos]] = pos;
            m_elems.pop_back();
        }
        
        unsigned choose(random_gen& rnd) const {
            SASSERT(!empty());
            return m_elems[rnd(num_elems())];
        }
    };

    struct pb_sls::imp {

        struct clause {
            literal_vector    m_lits;
            scoped_mpz_vector m_weights;
            scoped_mpz        m_k;
            scoped_mpz        m_value;
            bool              m_eq;
            clause(unsynch_mpz_manager& m):
                m_weights(m),
                m_k(m),
                m_value(m),
                m_eq(true)
            {}
        };

        struct stats {
            stats() { reset(); }
            void reset() { memset(this, 0, sizeof(*this)); }
            unsigned m_num_flips;
            unsigned m_num_improvements;
        };
        
        ast_manager&     m;
        pb_util          pb;
        unsynch_mpz_manager mgr;
        th_rewriter      m_rewrite;
        vector<clause>   m_clauses;      // clauses to be satisfied        
        expr_ref_vector  m_orig_clauses; // for debugging
        model_ref        m_orig_model;   // for debugging
        vector<clause>   m_soft;         // soft constraints
        vector<rational> m_weights;      // weights of soft constraints
        rational         m_penalty;      // current penalty of soft constraints
        rational         m_best_penalty;
        vector<unsigned_vector>  m_hard_occ, m_soft_occ;  // variable occurrence
        bool_vector    m_assignment;   // current assignment.
        bool_vector    m_best_assignment;
        expr_ref_vector  m_trail;
        obj_map<expr, unsigned> m_decl2var; // map declarations to Boolean variables.
        ptr_vector<expr> m_var2decl;        // reverse map
        index_set        m_hard_false;           // list of hard clauses that are false.
        index_set        m_soft_false;           // list of soft clauses that are false.
        unsigned         m_max_flips;            // maximal number of flips
        unsigned         m_non_greedy_percent;   // percent of moves to do non-greedy style
        random_gen       m_rng;
        scoped_mpz       one;
        stats            m_stats;

        imp(ast_manager& m):
            m(m),
            pb(m),
            m_rewrite(m),
            m_orig_clauses(m),
            m_trail(m),
            one(mgr)
        {
            reset();
            one = mpz(1);
        }

        void reset() {
            init_max_flips();
            m_non_greedy_percent = 30;
            m_decl2var.reset();
            m_var2decl.reset();
            m_assignment.reset();
            m_hard_occ.reset();
            m_soft_occ.reset();
            m_clauses.reset();
            m_orig_clauses.reset();
            m_soft.reset();
            m_weights.reset();
            m_trail.reset();
            m_decl2var.insert(m.mk_true(), 0);
            m_var2decl.push_back(m.mk_true());
            m_assignment.push_back(true);
            m_hard_occ.push_back(unsigned_vector());
            m_soft_occ.push_back(unsigned_vector());
        }

        void init_max_flips() {
            m_max_flips = 200;
        }

        void add(expr* f) {
            clause cls(mgr);
            if (compile_clause(f, cls)) {
                m_clauses.push_back(cls);
                m_orig_clauses.push_back(f);
            }
        }
        void add(expr* f, rational const& w) {
            clause cls(mgr);
            if (compile_clause(f, cls)) {
                m_soft.push_back(cls);
                m_weights.push_back(w);
            }
        }

        void set_model(model_ref & mdl) {
            m_orig_model = mdl;
            for (unsigned i = 0; i < m_var2decl.size(); ++i) {
                expr_ref tmp(m);
                m_assignment[i] = mdl->is_true(m_var2decl[i]);
            }
        }

        lbool operator()() {
            init();
            IF_VERBOSE(1, verbose_stream() << "(pb.sls initial penalty: " << m_best_penalty << ")\n";
                       verbose_stream() << "(pb.sls violated: " << m_hard_false.num_elems()
                       << " penalty: " << m_penalty << ")\n";);
            bool_vector assignment(m_assignment);
            for (unsigned round = 0; round < 40; ++round) {
                init_max_flips();
                while (m_max_flips > 0) {
                    --m_max_flips;
                    literal lit = flip();
                    if (!m.inc()) {
                        return l_undef;
                    }
                    IF_VERBOSE(3, verbose_stream() 
                               << "(pb.sls violated: " << m_hard_false.num_elems()
                               << " penalty: " << m_penalty << " " << lit << ")\n";);
                    if (m_hard_false.empty() && m_best_penalty.is_zero()) {
                        break;
                    }
                }
                if (m_hard_false.empty() && m_best_penalty.is_zero()) {
                    break;
                }
                IF_VERBOSE(1, verbose_stream() << "(pb.sls best penalty " << m_best_penalty << ")\n";);
                if (!m_best_assignment.empty()) {
                    assignment.reset();
                    assignment.append(m_best_assignment);
                    round = 0;
                }
                m_assignment.reset();
                m_assignment.append(assignment);
                m_best_assignment.reset();      
                m_soft_false.reset();
                m_hard_false.reset();
                m_penalty.reset();
                for (unsigned i = 0; i < m_soft.size(); ++i) {
                    if (!eval(m_soft[i])) {
                        m_soft_false.insert(i);
                        m_penalty += m_weights[i];
                    }
                }
                for (unsigned i = 0; i < m_clauses.size(); ++i) {
                    if (!eval(m_clauses[i])) {
                        m_hard_false.insert(i);
                    }
                }
            }
            m_assignment.reset();
            m_assignment.append(assignment);
            m_penalty = m_best_penalty;
            return l_true;
        }

        bool get_value(literal l) {
            return l.sign() ^ m_assignment[l.var()];
        }
        void get_model(model_ref& mdl) {
            mdl = alloc(model, m);
            for (unsigned i = 1; i < m_var2decl.size(); ++i) {
                expr* d = m_var2decl[i];
                if (is_uninterp_const(d)) {
                    mdl->register_decl(to_app(d)->get_decl(), m_assignment[i] ? m.mk_true() : m.mk_false());
                }
            }
        }

        void collect_statistics(::statistics& st) const {
            st.update("sls.num_flips", m_stats.m_num_flips);
            st.update("sls.num_improvements", m_stats.m_num_improvements);
        }

        void updt_params(params_ref& p) {
        }

        bool soft_holds(unsigned index) {
            return eval(m_soft[index]);
        }

        void display(std::ostream& out, clause const& cls) {
            scoped_mpz w(mgr);
            for (unsigned i = 0; i < cls.m_lits.size(); ++i) {
                w = cls.m_weights[i];
                out << w << "*" << cls.m_lits[i] << " ";
                out << "(" << mk_pp(m_var2decl[cls.m_lits[i].var()], m) << ") ";
                if (i + 1 < cls.m_lits.size()) {
                    out << "+ ";
                }
            }
            out << "(" << cls.m_value << ") ";
            if (cls.m_eq) {
                out << "= ";
            }
            else {
                out << ">= ";
            }
            out << cls.m_k << "\n";
        }

        void display(std::ostream& out) {
            for (unsigned i = 0; i < m_clauses.size(); ++i) {
                display(out, m_clauses[i]);
            }
            out << "soft:\n";
            for (unsigned i = 0; i < m_soft.size(); ++i) {
                display(out << m_weights[i] << ": ", m_soft[i]);
            }
            for (unsigned i = 0; i < m_assignment.size(); ++i) {
                out << literal(i) << ": " << mk_pp(m_var2decl[i], m) << " |-> " << (m_assignment[i]?"true":"false") << "\n";
            }
        }

        bool eval(clause& cls) {
            unsigned sz = cls.m_lits.size();
            cls.m_value.reset();
            for (unsigned i = 0; i < sz; ++i) {
                if (get_value(cls.m_lits[i])) {
                    cls.m_value += cls.m_weights[i];
                }
            }
            if (cls.m_eq) {
                return cls.m_value == cls.m_k;
            }
            else {
                return cls.m_value >= cls.m_k;
            }
        }

        void init_occ(vector<clause> const& clauses, vector<unsigned_vector> & occ) {
            for (unsigned i = 0; i < clauses.size(); ++i) {
                clause const& cls = clauses[i];
                for (unsigned j = 0; j < cls.m_lits.size(); ++j) {
                    literal lit = cls.m_lits[j];
                    if (occ.size() <= static_cast<unsigned>(lit.var())) occ.resize(lit.var() + 1);
                    occ[lit.var()].push_back(i);
                }
            }
        }
        
        void init() {
            m_best_assignment.reset();
            m_best_penalty.reset();
            m_hard_false.reset();
            m_hard_occ.reset();
            m_soft_false.reset();
            m_soft_occ.reset();
            m_penalty.reset();
            for (unsigned i = 0; i <= m_var2decl.size(); ++i) {
                m_soft_occ.push_back(unsigned_vector());
                m_hard_occ.push_back(unsigned_vector());
            }

            // initialize the occurs vectors.
            init_occ(m_clauses, m_hard_occ);
            init_occ(m_soft, m_soft_occ);

            // add clauses that are false.
            for (unsigned i = 0; i < m_clauses.size(); ++i) {
                if (!eval(m_clauses[i])) {
                    m_hard_false.insert(i);
                    expr_ref tmp = (*m_orig_model)(m_orig_clauses[i].get());
                    IF_VERBOSE(0,                               
                               verbose_stream() << "original evaluation: " << tmp << "\n";
                               verbose_stream() << mk_pp(m_orig_clauses[i].get(), m) << "\n";
                               display(verbose_stream(), m_clauses[i]););
                }
            }
            for (unsigned i = 0; i < m_soft.size(); ++i) {
                if (!eval(m_soft[i])) {
                    m_soft_false.insert(i);
                    m_penalty += m_weights[i];
                }
            }
            m_best_penalty = m_penalty;
            TRACE("opt", display(tout););
        }
        
        literal flip() {
            m_stats.m_num_flips++;
            literal result;
            if (m_hard_false.empty()) {
                result = flip_soft();
            }
            else {
                result = flip_hard();
            }            
            if (m_hard_false.empty() && m_best_penalty > m_penalty) {
                IF_VERBOSE(1, verbose_stream() << "(pb.sls improved bound " << m_penalty << ")\n";);
                m_best_assignment.reset();
                m_best_assignment.append(m_assignment);
                m_best_penalty = m_penalty;
                m_stats.m_num_improvements++;
                init_max_flips();
            }
            if (!m_assignment[result.var()]) {
                result.neg();
            }
            return result;
        }
        
        literal flip_hard() {
            SASSERT(!m_hard_false.empty());
            literal lit;
            clause const& cls = pick_hard_clause();
            // IF_VERBOSE(1, display(verbose_stream(), cls);); 
            int break_count;
            int min_bc = INT_MAX;
            unsigned min_bc_index = 0;
            for (unsigned i = 0; i < cls.m_lits.size(); ++i) {
                lit = cls.m_lits[i];
                break_count = flip(lit);
                if (break_count < min_bc) {
                    min_bc = break_count;
                    min_bc_index = i;
                }
                else if (break_count == min_bc && m_rng(5) == 1) {
                    min_bc_index = i;
                }
                int new_break_count = flip(~lit);
                if (-break_count != new_break_count) {
                    IF_VERBOSE(0, display(verbose_stream() << lit << "\n", cls);
                               display(verbose_stream()));
                }
                // VERIFY(-break_count == flip(~lit));
            }            
            if (m_rng(100) <= m_non_greedy_percent) {
                lit = cls.m_lits[m_rng(cls.m_lits.size())];
            }
            else {
                lit = cls.m_lits[min_bc_index];
            }
            flip(lit);
            return lit;
        }
        
        literal flip_soft() {
            literal lit;
            clause const& cls = pick_soft_clause();
            int break_count;
            int min_bc = INT_MAX;
            unsigned min_bc_index = 0;
            rational penalty = m_penalty;
            rational min_penalty = penalty;
            for (unsigned i = 0; i < cls.m_lits.size(); ++i) {
                lit = cls.m_lits[i];
                break_count = flip(lit);
                SASSERT(break_count >= 0);
                if (break_count == 0 && penalty > m_penalty) {
                    return lit;
                }
                if ((break_count < min_bc) ||
                    (break_count == min_bc && m_penalty < min_penalty)) {
                    min_bc = break_count;
                    min_bc_index = i;
                    min_penalty = m_penalty;
                }
                VERIFY(-break_count == flip(~lit));
            }            
            if (m_rng(100) <= m_non_greedy_percent) {
                lit = cls.m_lits[m_rng(cls.m_lits.size())];
            }
            else {
                // just do a greedy move:
                lit = cls.m_lits[min_bc_index];
            }
            flip(lit);
            return lit;
        }

        //
        // TODO: alternate version: loop over soft clauses and see if there is a flip that 
        // reduces the penalty while preserving the hard constraints.
        // 

        // crude selection strategy.
        clause const& pick_hard_clause() {
            SASSERT(!m_hard_false.empty());
            return m_clauses[m_hard_false.choose(m_rng)];
        }

        clause const& pick_soft_clause() {
            SASSERT(!m_soft_false.empty());
            return m_soft[m_soft_false.choose(m_rng)];
        }

        int flip(literal l) {
            m_assignment[l.var()] = !m_assignment[l.var()];
            int break_count = 0;
            unsigned_vector const& occh = m_hard_occ[l.var()];
            scoped_mpz value(mgr);            
            for (unsigned i = 0; i < occh.size(); ++i) {
                unsigned j = occh[i];
                clause& cls = m_clauses[j];
                value = cls.m_value;
                if (eval(cls)) {
                    if (m_hard_false.contains(j)) {
                        break_count--;
                        m_hard_false.remove(j);
                    }
                }
                else {
                    if (!m_hard_false.contains(j)) {
                        break_count++;
                        m_hard_false.insert(j);
                    }
                    else if (value < m_clauses[j].m_value) {
                    }
                }
            }
            unsigned_vector const& occs = m_soft_occ[l.var()];
            for (unsigned i = 0; i < occs.size(); ++i) {
                unsigned j = occs[i];
                if (eval(m_soft[j])) {
                    if (m_soft_false.contains(j)) {
                        m_penalty -= m_weights[j];
                        m_soft_false.remove(j);
                    }
                }
                else {
                    if (!m_soft_false.contains(j)) {
                        m_penalty += m_weights[j];
                        m_soft_false.insert(j);
                    }
                }
            }                

            TRACE("opt", tout << "flip: " << l << " num false: " << m_hard_false.num_elems() 
                  << " penalty: " << m_penalty << " break count: " << break_count << "\n";);
            return break_count;
        }

        literal mk_aux_literal(expr* f) {           
            unsigned var;
            if (!m_decl2var.find(f, var)) {
                var = m_hard_occ.size();
                SASSERT(m_var2decl.size() == var);
                SASSERT(m_soft_occ.size() == var);
                m_hard_occ.push_back(unsigned_vector());
                m_soft_occ.push_back(unsigned_vector());
                m_assignment.push_back(m_orig_model->is_true(f));
                m_decl2var.insert(f, var);
                m_var2decl.push_back(f);
            }
            return literal(var);            
        }
        void pad(scoped_mpz_vector& vec, unsigned sz, mpz& val) {
            for (unsigned i = 0; i < sz; ++i) {
                vec.push_back(val);
            }
        }
        literal mk_literal(expr* f) {
            if (m.is_not(f, f)) {
                literal result = mk_literal(f);
                if (result != null_literal) {
                    result.neg();
                }
                return result;
            }
            if (is_uninterp_const(f)) 
                return mk_aux_literal(to_app(f));            
            if (m.is_true(f))
                return true_literal;
            if (m.is_false(f))
                return false_literal;
            if (m.is_and(f)) {
                literal_vector lits;
                app* g = to_app(f);
                for (unsigned i = 0; i < g->get_num_args(); ++i) {
                    lits.push_back(mk_literal(g->get_arg(i)));
                }
                literal result = mk_aux_literal(f);
                for (unsigned i = 0; i < lits.size(); ++i) {
                    clause cls(mgr);
                    cls.m_lits.push_back(~result);
                    cls.m_weights.push_back(one);
                    cls.m_lits.push_back(lits[i]);
                    cls.m_weights.push_back(one);
                    cls.m_k = one;
                    cls.m_eq = false;
                    m_clauses.push_back(cls);  
                    m_orig_clauses.push_back(f);
                    lits[i].neg();
                }
                lits.push_back(result);
                clause cls(mgr);
                cls.m_lits.append(lits);
                pad(cls.m_weights, lits.size(), one);
                cls.m_k = one;
                cls.m_eq = false;
                m_clauses.push_back(cls); 
                m_orig_clauses.push_back(f);
                return result;
            }
            if (m.is_or(f)) {
                literal_vector lits;
                app* g = to_app(f);
                for (unsigned i = 0; i < g->get_num_args(); ++i) {
                    lits.push_back(mk_literal(g->get_arg(i)));
                }
                literal result = mk_aux_literal(f);
                for (unsigned i = 0; i < lits.size(); ++i) {
                    clause cls(mgr);
                    cls.m_lits.push_back(result);
                    cls.m_weights.push_back(one);
                    cls.m_lits.push_back(~lits[i]);
                    cls.m_weights.push_back(one);
                    cls.m_k = one;
                    cls.m_eq = false;
                    m_clauses.push_back(cls);  
                    m_orig_clauses.push_back(f);
                }
                lits.push_back(~result);
                clause cls(mgr);
                cls.m_lits.append(lits);
                pad(cls.m_weights, lits.size(), one);
                cls.m_k = one;
                cls.m_eq = false;
                m_clauses.push_back(cls);       
                m_orig_clauses.push_back(f);
                return result;
            }
            expr* x, *y;
            if ((m.is_eq(f, x, y) && m.is_bool(x)) || m.is_iff(f, x, y)) {                
                literal a = mk_literal(x);
                literal b = mk_literal(y);
                literal result = mk_aux_literal(f);
                clause cls(mgr);
                cls.m_lits.push_back(~result);
                cls.m_lits.push_back(~a);
                cls.m_lits.push_back(b);
                pad(cls.m_weights, 3, one);
                cls.m_k = one;
                cls.m_eq = false;
                m_clauses.push_back(cls);
                m_orig_clauses.push_back(f); // actually, the clause that defines f
                cls.m_lits[0] = ~result;
                cls.m_lits[1] = a;
                cls.m_lits[2] = ~b;
                m_clauses.push_back(cls);
                m_orig_clauses.push_back(f);
                cls.m_lits[0] = result;
                cls.m_lits[1] = a;
                cls.m_lits[2] = b;
                m_clauses.push_back(cls);
                m_orig_clauses.push_back(f);
                cls.m_lits[0] = result;
                cls.m_lits[1] = ~a;
                cls.m_lits[2] = ~b;
                m_clauses.push_back(cls);
                m_orig_clauses.push_back(f);
                return result;
            }            
            if (pb.is_ge(f)) {
                
            }
            IF_VERBOSE(0, verbose_stream() << "not handled: " << mk_pp(f, m) << "\n";);
            return mk_aux_literal(f);
        }

        bool compile_clause(expr* _f, clause& cls) {
            expr_ref tmp(m);
            m_rewrite(_f, tmp);
            if (!is_app(tmp)) return false;            
            app* f = to_app(tmp);
            expr* f2;
            unsigned sz = f->get_num_args();
            expr* const* args = f->get_args();
            literal lit;
            rational coeff, k;
            if (m.is_not(f, f2) && pb.is_ge(f2)) {
                // ~(ax+by >= k) 
                // <=>
                // ax + by < k
                // <=>
                // -ax - by >= -k + 1
                // <=>
                // a(1-x) + b(1-y) >= -k + a + b + 1
                sz = to_app(f2)->get_num_args();
                args = to_app(f2)->get_args();
                k = pb.get_k(f2);
                SASSERT(k.is_int());
                k.neg();
                k += rational::one();
                expr_ref_vector args(m);
                vector<rational> coeffs;
                for (unsigned i = 0; i < sz; ++i) {
                    args.push_back(m.mk_not(to_app(f2)->get_arg(i)));
                    coeffs.push_back(pb.get_coeff(f2, i));
                    k += pb.get_coeff(f2, i);
                }
                tmp = pb.mk_ge(coeffs.size(), coeffs.data(), args.data(), k);
                return compile_clause(tmp, cls);
            }
            else if (pb.is_ge(f) || pb.is_eq(f)) {
                k = pb.get_k(f);
                SASSERT(k.is_int());
                cls.m_k = k.to_mpq().numerator();
                for (unsigned i = 0; i < sz; ++i) {
                    coeff = pb.get_coeff(f, i);                    
                    SASSERT(coeff.is_int());
                    lit = mk_literal(args[i]);
                    if (lit == null_literal) return false;
                    if (lit == false_literal) continue;
                    if (lit == true_literal) {
                        cls.m_k -= coeff.to_mpq().numerator();
                        continue;
                    }
                    cls.m_lits.push_back(lit);
                    cls.m_weights.push_back(coeff.to_mpq().numerator());
                    if (get_value(lit)) {
                        cls.m_value += coeff.to_mpq().numerator();
                    }
                }
                cls.m_eq = pb.is_eq(f);
            }
            else if (m.is_or(f)) {
                for (unsigned i = 0; i < sz; ++i) {
                    lit = mk_literal(args[i]);
                    if (lit == null_literal) return false;
                    if (lit == false_literal) continue;
                    if (lit == true_literal) return false;
                    cls.m_lits.push_back(lit);
                    cls.m_weights.push_back(mpz(1));
                    if (get_value(lit)) {
                        cls.m_value += mpz(1);
                    }
                }
                cls.m_eq = false;
                cls.m_k = mpz(1);
            }
            else if (m.is_true(f)) {
                return false;
            }
            else {
                lit = mk_literal(f);
                if (lit == null_literal) return false;
                SASSERT(lit != false_literal && lit != true_literal);
                cls.m_lits.push_back(lit);
                cls.m_weights.push_back(mpz(1));
                cls.m_eq = true;
                cls.m_k = mpz(1);
            }
            return true;
        }
        
    };

    pb_sls::pb_sls(ast_manager& m) {
        m_imp = alloc(imp, m);
    }       
    pb_sls::~pb_sls() {
        dealloc(m_imp);
    }
    void pb_sls::add(expr* f) {
        m_imp->add(f);
    }
    void pb_sls::add(expr* f, rational const& w) {
        m_imp->add(f, w);
    }
    void pb_sls::set_model(model_ref& mdl) {
        m_imp->set_model(mdl);
    }
    lbool pb_sls::operator()() {
        return (*m_imp)();
    }
    void pb_sls::collect_statistics(statistics& st) const {
        m_imp->collect_statistics(st);
    }
    void pb_sls::get_model(model_ref& mdl) {
        m_imp->get_model(mdl);
    }
    void pb_sls::reset() {
        m_imp->reset();
    }
    bool pb_sls::soft_holds(unsigned index) {
        return m_imp->soft_holds(index);
    }
    void pb_sls::updt_params(params_ref& p) {
        m_imp->updt_params(p);
    }

}
