From d02b0cde7ad221f44aaa232b2e4a4fdc4fd66335 Mon Sep 17 00:00:00 2001
From: Nikolaj Bjorner <nbjorner@microsoft.com>
Date: Mon, 7 Sep 2020 20:35:32 -0700
Subject: [PATCH] running updates to bv_solver (#4674)

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* dbg

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* bv

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* drat and fresh

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* move ackerman functionality

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* debugability

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* towards debugability

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* missing file

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* na

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* remove csp

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
---
 src/ast/CMakeLists.txt                |    1 -
 src/ast/ast_smt2_pp.cpp               |   13 +
 src/ast/ast_smt2_pp.h                 |    8 +
 src/ast/bv_decl_plugin.cpp            |   14 +-
 src/ast/bv_decl_plugin.h              |    4 +
 src/ast/csp_decl_plugin.cpp           |  386 --------
 src/ast/csp_decl_plugin.h             |  163 ----
 src/ast/euf/euf_egraph.cpp            |  231 ++---
 src/ast/euf/euf_egraph.h              |   81 +-
 src/ast/euf/euf_enode.h               |   15 +-
 src/ast/pattern/expr_pattern_match.h  |   16 +-
 src/cmd_context/cmd_context.cpp       |    3 -
 src/sat/dimacs.cpp                    |  221 ++++-
 src/sat/dimacs.h                      |   86 ++
 src/sat/sat_aig_cuts.h                |   10 +-
 src/sat/sat_binspr.h                  |    8 +-
 src/sat/sat_drat.cpp                  |  257 +++---
 src/sat/sat_drat.h                    |   39 +-
 src/sat/sat_elim_eqs.cpp              |    1 -
 src/sat/sat_extension.h               |    3 +-
 src/sat/sat_simplifier.cpp            |    3 +-
 src/sat/sat_solver.cpp                |  124 ++-
 src/sat/sat_solver.h                  |   18 +-
 src/sat/sat_solver/inc_sat_solver.cpp |    2 +-
 src/sat/sat_solver_core.h             |    3 +
 src/sat/sat_types.h                   |    6 +-
 src/sat/smt/CMakeLists.txt            |    2 +
 src/sat/smt/ba_internalize.cpp        |    4 +
 src/sat/smt/ba_solver.cpp             |    8 +-
 src/sat/smt/ba_solver.h               |    6 +-
 src/sat/smt/bv_ackerman.cpp           |  153 ++++
 src/sat/smt/bv_ackerman.h             |   77 ++
 src/sat/smt/bv_internalize.cpp        |  690 +++++++-------
 src/sat/smt/bv_solver.cpp             |  649 ++++++++++++--
 src/sat/smt/bv_solver.h               |  186 ++--
 src/sat/smt/euf_ackerman.cpp          |   58 +-
 src/sat/smt/euf_ackerman.h            |    9 +-
 src/sat/smt/euf_internalize.cpp       |   82 +-
 src/sat/smt/euf_invariant.cpp         |   48 +
 src/sat/smt/euf_model.cpp             |   13 +-
 src/sat/smt/euf_proof.cpp             |   69 +-
 src/sat/smt/euf_solver.cpp            |  258 ++++--
 src/sat/smt/euf_solver.h              |  111 ++-
 src/sat/smt/sat_smt.h                 |    3 +
 src/sat/smt/sat_th.cpp                |   46 +-
 src/sat/smt/sat_th.h                  |   30 +-
 src/sat/tactic/goal2sat.cpp           |  146 ++-
 src/sat/tactic/sat_tactic.cpp         |   12 +-
 src/shell/CMakeLists.txt              |    1 +
 src/shell/drat_frontend.cpp           |  197 ++++
 src/shell/drat_frontend.h             |    8 +
 src/shell/main.cpp                    |   19 +-
 src/shell/opt_frontend.cpp            |    1 -
 src/smt/CMakeLists.txt                |    1 -
 src/smt/smt_setup.cpp                 |   10 -
 src/smt/theory_jobscheduler.cpp       | 1183 -------------------------
 src/smt/theory_jobscheduler.h         |  247 ------
 src/test/egraph.cpp                   |    2 +
 src/util/dlist.h                      |   50 ++
 src/util/scoped_limit_trail.h         |   36 +
 src/util/statistics.cpp               |    6 +-
 src/util/statistics.h                 |    6 +-
 src/util/union_find.h                 |   12 +-
 63 files changed, 3060 insertions(+), 3095 deletions(-)
 delete mode 100644 src/ast/csp_decl_plugin.cpp
 delete mode 100644 src/ast/csp_decl_plugin.h
 create mode 100644 src/sat/smt/bv_ackerman.cpp
 create mode 100644 src/sat/smt/bv_ackerman.h
 create mode 100644 src/sat/smt/euf_invariant.cpp
 create mode 100644 src/shell/drat_frontend.cpp
 create mode 100644 src/shell/drat_frontend.h
 delete mode 100644 src/smt/theory_jobscheduler.cpp
 delete mode 100644 src/smt/theory_jobscheduler.h
 create mode 100644 src/util/scoped_limit_trail.h

diff --git a/src/ast/CMakeLists.txt b/src/ast/CMakeLists.txt
index aad9e1826..d4c7291f4 100644
--- a/src/ast/CMakeLists.txt
+++ b/src/ast/CMakeLists.txt
@@ -14,7 +14,6 @@ z3_add_component(ast
     ast_translation.cpp
     ast_util.cpp
     bv_decl_plugin.cpp
-    csp_decl_plugin.cpp
     datatype_decl_plugin.cpp
     decl_collector.cpp
     display_dimacs.cpp
diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp
index 4ef42d62c..e2767a687 100644
--- a/src/ast/ast_smt2_pp.cpp
+++ b/src/ast/ast_smt2_pp.cpp
@@ -1342,6 +1342,17 @@ std::ostream& operator<<(std::ostream& out, mk_ismt2_pp const & p) {
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, mk_ismt2_func const& p) {
+    smt2_pp_environment_dbg env(p.m);        
+    format_ref r(fm(p.m));
+    unsigned len = 0;
+    r = env.pp_fdecl(p.m_fn, len);
+    params_ref pa;
+    pp(out, r.get(), p.m, pa);
+    return out;
+}
+
+
 std::ostream& operator<<(std::ostream& out, expr_ref const&  e) {
     return out << mk_ismt2_pp(e.get(), e.get_manager());
 }
@@ -1388,6 +1399,8 @@ std::ostream& operator<<(std::ostream& out, sort_ref_vector const&  e) {
     return out;
 }
 
+
+
 #ifdef Z3DEBUG
 void pp(expr const * n, ast_manager & m) {
     std::cout << mk_ismt2_pp(const_cast<expr*>(n), m) << std::endl;
diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h
index d9a1aff57..6b4d221af 100644
--- a/src/ast/ast_smt2_pp.h
+++ b/src/ast/ast_smt2_pp.h
@@ -122,6 +122,7 @@ struct mk_ismt2_pp {
     mk_ismt2_pp(ast * t, ast_manager & m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = nullptr);
 };
 
+
 std::ostream& operator<<(std::ostream& out, mk_ismt2_pp const & p);
 
 std::ostream& operator<<(std::ostream& out, expr_ref const& e);
@@ -136,3 +137,10 @@ std::ostream& operator<<(std::ostream& out, var_ref_vector const& e);
 std::ostream& operator<<(std::ostream& out, func_decl_ref_vector const& e);
 std::ostream& operator<<(std::ostream& out, sort_ref_vector const& e);
 
+struct mk_ismt2_func {
+    func_decl* m_fn;
+    ast_manager &m;
+    mk_ismt2_func(func_decl* f, ast_manager& m): m_fn(f), m(m) {}
+};
+
+std::ostream& operator<<(std::ostream& out, mk_ismt2_func const& f);
diff --git a/src/ast/bv_decl_plugin.cpp b/src/ast/bv_decl_plugin.cpp
index dfcb70537..cf53d59bf 100644
--- a/src/ast/bv_decl_plugin.cpp
+++ b/src/ast/bv_decl_plugin.cpp
@@ -131,12 +131,8 @@ void bv_decl_plugin::finalize() {
 
     DEC_REF(m_int2bv);
     DEC_REF(m_bv2int);
-    vector<ptr_vector<func_decl> >::iterator it  = m_bit2bool.begin();
-    vector<ptr_vector<func_decl> >::iterator end = m_bit2bool.end();
-    for (; it != end; ++it) {
-        ptr_vector<func_decl> & ds = *it;
+    for (auto& ds : m_bit2bool)
         DEC_REF(ds);
-    }
     DEC_REF(m_mkbv);
 }
 
@@ -829,6 +825,14 @@ bool bv_recognizers::is_bv2int(expr const* e, expr*& r) const {
     return true;
 }
 
+bool bv_recognizers::is_bit2bool(expr* e, expr*& bv, unsigned& idx) const {
+    if (!is_bit2bool(e))
+        return false;
+    bv = to_app(e)->get_arg(0);
+    idx = to_app(e)->get_parameter(0).get_int();
+    return true;
+}
+
 bool bv_recognizers::mult_inverse(rational const & n, unsigned bv_size, rational & result) {
     if (n.is_one()) {
         result = n;
diff --git a/src/ast/bv_decl_plugin.h b/src/ast/bv_decl_plugin.h
index 95982cf9f..2f3242dff 100644
--- a/src/ast/bv_decl_plugin.h
+++ b/src/ast/bv_decl_plugin.h
@@ -368,6 +368,9 @@ public:
     MATCH_BINARY(is_bv_sdivi);
     MATCH_BINARY(is_bv_udivi);
     MATCH_BINARY(is_bv_smodi);
+    MATCH_UNARY(is_bit2bool);
+    MATCH_UNARY(is_int2bv);
+    bool is_bit2bool(expr* e, expr*& bv, unsigned& idx) const;
 
     rational norm(rational const & val, unsigned bv_size, bool is_signed) const ;
     rational norm(rational const & val, unsigned bv_size) const { return norm(val, bv_size, false); }
@@ -432,6 +435,7 @@ public:
     app * mk_bvsmul_no_ovfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BSMUL_NO_OVFL, n, m); }
     app * mk_bvsmul_no_udfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BSMUL_NO_UDFL, n, m); }
     app * mk_bvumul_no_ovfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BUMUL_NO_OVFL, n, m); }
+    app * mk_bit2bool(expr* e, unsigned idx) { parameter p(idx); return m_manager.mk_app(get_fid(), OP_BIT2BOOL, 1, &p, 1, &e); }
 
     private:
     void log_bv_from_exprs(app * r, unsigned n, expr* const* es) {
diff --git a/src/ast/csp_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp
deleted file mode 100644
index 0e7688969..000000000
--- a/src/ast/csp_decl_plugin.cpp
+++ /dev/null
@@ -1,386 +0,0 @@
-/*++
-Copyright (c) 2018 Microsoft Corporation
-
-Module Name:
-
-    csp_decl_plugin.h
-
-Abstract:
-
-    Declarations used for a job-shop scheduling domain.
-
-Author:
-
-    Nikolaj Bjorner (nbjorner) 2018-8-9 
-
-Revision History:
-
-
---*/
-#include "ast/csp_decl_plugin.h"
-#include "ast/arith_decl_plugin.h"
-
-void csp_decl_plugin::set_manager(ast_manager* m, family_id fid) {
-    decl_plugin::set_manager(m, fid);
-    m_int_sort = nullptr;
-    m_alist_sort = nullptr;
-    m_job_sort = nullptr;
-    m_resource_sort = nullptr;
-}
-
-void csp_decl_plugin::init() {
-    if (!m_int_sort) {
-        m_int_sort = m_manager->mk_sort(m_manager->mk_family_id("arith"), INT_SORT);
-        m_alist_sort = m_manager->mk_sort(symbol("AList"), sort_info(m_family_id, ALIST_SORT));
-        m_job_sort = m_manager->mk_sort(symbol("Job"), sort_info(m_family_id, JOB_SORT));
-        m_resource_sort = m_manager->mk_sort(symbol("Resource"), sort_info(m_family_id, RESOURCE_SORT));
-        m_manager->inc_ref(m_int_sort);
-        m_manager->inc_ref(m_resource_sort);
-        m_manager->inc_ref(m_job_sort);
-        m_manager->inc_ref(m_alist_sort);
-    }
-}
-
-void csp_decl_plugin::finalize() {
-    m_manager->dec_ref(m_alist_sort);
-    m_manager->dec_ref(m_job_sort);
-    m_manager->dec_ref(m_resource_sort);
-    m_manager->dec_ref(m_int_sort);
-}
-
-sort * csp_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) {
-    init();
-    if (num_parameters != 0) {
-        m_manager->raise_exception("no parameters expected with job-shop sort");
-    }
-    switch (static_cast<js_sort_kind>(k)) {
-    case JOB_SORT: return m_job_sort;
-    case RESOURCE_SORT: return m_resource_sort;
-    case ALIST_SORT: return m_alist_sort;
-    default: UNREACHABLE(); return nullptr;
-    }    
-}
-
-func_decl * csp_decl_plugin::mk_func_decl(
-    decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort *) {
-    init();
-    symbol name;
-    sort* rng = nullptr;
-    switch (static_cast<js_op_kind>(k)) {
-    case OP_JS_JOB:       
-        check_arity(arity);
-        check_index1(num_parameters, parameters);
-        name = symbol("job");
-        rng = m_job_sort;
-        break;
-    case OP_JS_RESOURCE:
-        check_arity(arity);
-        check_index1(num_parameters, parameters);
-        name = symbol("resource");
-        rng = m_resource_sort;
-        break;
-    case OP_JS_RESOURCE_MAKESPAN:
-        if (arity != 1 || domain[0] != m_resource_sort) m_manager->raise_exception("makespan expects a resource argument");
-        name = symbol("makespan");
-        rng = m_int_sort;
-        break;
-    case OP_JS_START:
-        if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("start expects a job argument");
-        if (num_parameters > 0) m_manager->raise_exception("no parameters");
-        name = symbol("job-start");
-        rng = m_int_sort;
-        break;
-    case OP_JS_END:
-        if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("resource expects a job argument");
-        if (num_parameters > 0) m_manager->raise_exception("no parameters");
-        name = symbol("job-end");
-        rng = m_int_sort;
-        break;
-    case OP_JS_JOB2RESOURCE:
-        if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("job2resource expects a job argument");
-        if (num_parameters > 0) m_manager->raise_exception("no parameters");
-        name = symbol("job2resource");
-        rng = m_resource_sort;
-        break;
-    case OP_JS_MODEL:
-        // has no parameters
-        // all arguments are of sort alist
-        name = symbol("js-model");
-        rng = m_manager->mk_bool_sort();
-        break;
-    case OP_JS_JOB_RESOURCE:        
-        if (arity != 6) m_manager->raise_exception("add-job-resource expects 6 arguments");
-        if (domain[0] != m_job_sort) m_manager->raise_exception("first argument of add-job-resource expects should be a job");
-        if (domain[1] != m_resource_sort) m_manager->raise_exception("second argument of add-job-resource expects should be a resource");
-        if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-job-resource expects should be an integer");
-        if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-job-resource expects should be an integer");
-        if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-job-resource expects should be an integer");
-        if (domain[5] != m_alist_sort) m_manager->raise_exception("6th argument of add-job-resource should be a list of properties");
-        name = symbol("add-job-resource");
-        rng = m_alist_sort;
-        break;
-    case OP_JS_RESOURCE_AVAILABLE:
-        if (arity != 6) m_manager->raise_exception("add-resource-available expects 6 arguments");        
-        if (domain[0] != m_resource_sort) m_manager->raise_exception("first argument of add-resource-available expects should be a resource");
-        if (domain[1] != m_int_sort) m_manager->raise_exception("2nd argument of add-resource-available expects should be an integer");
-        if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-resource-available expects should be an integer");
-        if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-resource-available expects should be an integer");
-        if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-resource-available expects should be an integer");
-        if (domain[5] != m_alist_sort) m_manager->raise_exception("6th argument of add-resource-available should be a list of properties");
-        name = symbol("add-resource-available");
-        rng = m_alist_sort;
-        break;
-    case OP_JS_JOB_PREEMPTABLE:
-        if (arity != 1 || domain[0] != m_job_sort) 
-            m_manager->raise_exception("set-preemptable expects one argument, which is a job");
-        name = symbol("set-preemptable");
-        rng = m_alist_sort;
-        break;
-    case OP_JS_PROPERTIES:
-        if (arity != 0) m_manager->raise_exception("js-properties takes no arguments");
-        for (unsigned i = 0; i < num_parameters; ++i) {
-            if (!parameters[i].is_symbol()) m_manager->raise_exception("js-properties expects a list of keyword parameters");
-        }
-        name = symbol("js-properties");
-        rng = m_alist_sort;
-        break;
-    case OP_JS_JOB_GOAL:
-        if (arity != 1 || domain[0] != m_job_sort) 
-            m_manager->raise_exception("add-job-goal expects one argument, which is a job");
-        if (num_parameters != 2 || !parameters[0].is_symbol() || !parameters[1].is_int())
-            m_manager->raise_exception("add-job-goal expects one symbol and one integer parameter");
-        name = symbol("add-job-goal");
-        rng = m_alist_sort;
-        break;
-    case OP_JS_OBJECTIVE:
-        if (arity != 0)
-            m_manager->raise_exception("add-optimization-objective expects no arguments");
-        if (num_parameters != 1 || !parameters[0].is_symbol())
-            m_manager->raise_exception("add-optimization-objective expects one symbol parameter");
-        name = symbol("add-optimization-objective");
-        rng = m_alist_sort;
-        break;
-    default: 
-        UNREACHABLE(); 
-        return nullptr;        
-    }    
-    return m_manager->mk_func_decl(name, arity, domain, rng, func_decl_info(m_family_id, k, num_parameters, parameters));
-
-}
-
-void csp_decl_plugin::check_arity(unsigned arity) {
-    if (arity > 0)
-        m_manager->raise_exception("csp variables use parameters only and take no arguments");
-}
-
-void csp_decl_plugin::check_index1(unsigned num_parameters, parameter const* ps) {
-    if (num_parameters != 1 || !ps[0].is_int())
-        m_manager->raise_exception("csp variable expects a single integer parameter");
-}
-
-void csp_decl_plugin::check_index2(unsigned num_parameters, parameter const* ps) {
-    if (num_parameters != 2 || !ps[0].is_int() || !ps[1].is_int())
-        m_manager->raise_exception("csp variable expects two integer parameters");
-}
-
-
-bool csp_decl_plugin::is_value(app * e) const {
-    return is_app_of(e, m_family_id, OP_JS_JOB) || is_app_of(e, m_family_id, OP_JS_RESOURCE);
-}
-
-void csp_decl_plugin::get_op_names(svector<builtin_name> & op_names, symbol const & logic) {
-    if (logic == symbol("CSP")) {
-        op_names.push_back(builtin_name("job", OP_JS_JOB));
-        op_names.push_back(builtin_name("resource", OP_JS_RESOURCE));
-        op_names.push_back(builtin_name("makespan", OP_JS_RESOURCE_MAKESPAN));
-        op_names.push_back(builtin_name("job-start", OP_JS_START));
-        op_names.push_back(builtin_name("job-end", OP_JS_END));
-        op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE));
-        op_names.push_back(builtin_name("js-model", OP_JS_MODEL));
-        op_names.push_back(builtin_name("add-job-resource", OP_JS_JOB_RESOURCE));
-        op_names.push_back(builtin_name("add-resource-available", OP_JS_RESOURCE_AVAILABLE));
-        op_names.push_back(builtin_name("set-preemptable", OP_JS_JOB_PREEMPTABLE));
-        op_names.push_back(builtin_name("js-properties", OP_JS_PROPERTIES));
-        op_names.push_back(builtin_name("add-job-goal", OP_JS_JOB_GOAL));
-        op_names.push_back(builtin_name("add-optimization-objective", OP_JS_OBJECTIVE));
-    }
-}
-
-void csp_decl_plugin::get_sort_names(svector<builtin_name> & sort_names, symbol const & logic) {
-    if (logic == symbol("CSP")) {
-        sort_names.push_back(builtin_name("Job", JOB_SORT));
-        sort_names.push_back(builtin_name("Resource", RESOURCE_SORT));
-    }
-}
-
-expr * csp_decl_plugin::get_some_value(sort * s) {
-    init();
-    parameter p(0);
-    if (is_sort_of(s, m_family_id, JOB_SORT))
-        return m_manager->mk_const(mk_func_decl(OP_JS_JOB, 1, &p, 0, nullptr, nullptr));
-    if (is_sort_of(s, m_family_id, RESOURCE_SORT))
-        return m_manager->mk_const(mk_func_decl(OP_JS_RESOURCE, 1, &p, 0, nullptr, nullptr));
-    UNREACHABLE();
-    return nullptr;
-}
-
-
-csp_util::csp_util(ast_manager& m): m(m) {
-    m_fid = m.mk_family_id("csp");
-    m_plugin = static_cast<csp_decl_plugin*>(m.get_plugin(m_fid));
-}
-
-sort* csp_util::mk_job_sort() { 
-    return m_plugin->mk_job_sort(); 
-}
-
-sort* csp_util::mk_resource_sort() { 
-    return m_plugin->mk_resource_sort(); 
-}
-
-app* csp_util::mk_job(unsigned j) {
-    parameter p(j);
-    return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB, 1, &p, 0, (sort*const*)nullptr, nullptr));
-}
-
-unsigned csp_util::job2id(expr* j) {
-    if (is_app_of(j, m_fid, OP_JS_JOB)) {
-        return to_app(j)->get_decl()->get_parameter(0).get_int();
-    }
-    SASSERT(is_app_of(j, m_fid, OP_JS_START) || 
-            is_app_of(j, m_fid, OP_JS_END) ||
-            is_app_of(j, m_fid, OP_JS_JOB2RESOURCE));
-    return job2id(to_app(j)->get_arg(0));
-}
-
-app* csp_util::mk_resource(unsigned r) {
-    parameter p(r);
-    return m.mk_const(m.mk_func_decl(m_fid, OP_JS_RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr));
-}
-
-unsigned csp_util::resource2id(expr* r) {
-    SASSERT(is_app_of(r, m_fid, OP_JS_RESOURCE));
-    return to_app(r)->get_decl()->get_parameter(0).get_int();
-}
-
-app* csp_util::mk_start(unsigned j) { 
-    app_ref job(mk_job(j), m);
-    sort* js = m.get_sort(job);
-    return m.mk_app(m.mk_func_decl(m_fid, OP_JS_START, 0, nullptr, 1, &js, nullptr), job);
-}
-        
-app* csp_util::mk_end(unsigned j) {    
-    app_ref job(mk_job(j), m);
-    sort* js = m.get_sort(job);
-    return m.mk_app(m.mk_func_decl(m_fid, OP_JS_END, 0, nullptr, 1, &js, nullptr), job);
-}
-
-app* csp_util::mk_job2resource(unsigned j) { 
-    app_ref job(mk_job(j), m);
-    sort* js = m.get_sort(job);
-    return m.mk_app(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 0, nullptr, 1, &js, nullptr), job);
-}
-
-app* csp_util::mk_makespan(unsigned r) {
-    app_ref resource(mk_resource(r), m);
-    sort* rs = m.get_sort(resource);
-    return m.mk_app(m.mk_func_decl(m_fid, OP_JS_RESOURCE_MAKESPAN, 0, nullptr, 1, &rs, nullptr), resource);
-}
-
-bool csp_util::is_resource(expr* e, unsigned& r) {
-    return is_app_of(e, m_fid, OP_JS_RESOURCE) && (r = resource2id(e), true);
-}
-
-bool csp_util::is_makespan(expr * e, unsigned& r) {
-    return is_app_of(e, m_fid, OP_JS_RESOURCE_MAKESPAN) && is_resource(to_app(e)->get_arg(0), r);
-}
-
-bool csp_util::is_job(expr* e, unsigned& j) {
-    return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true);
-}
-
-bool csp_util::is_job2resource(expr* e, unsigned& j) {
-    return is_app_of(e, m_fid, OP_JS_JOB2RESOURCE) && (j = job2id(e), true);
-}
-
-bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, unsigned& cap_time, uint64_t& start, uint64_t& end, svector<symbol>& properties) {
-    if (!is_app_of(e, m_fid, OP_JS_RESOURCE_AVAILABLE)) return false;
-    res = to_app(e)->get_arg(0);
-    arith_util a(m);
-    rational r;
-    if (!a.is_numeral(to_app(e)->get_arg(1), r) || !r.is_unsigned()) return false;
-    loadpct = r.get_unsigned();
-    if (!a.is_numeral(to_app(e)->get_arg(2), r) || !r.is_unsigned()) return false;
-    cap_time = r.get_unsigned();
-    if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false;
-    start = r.get_uint64();
-    if (!a.is_numeral(to_app(e)->get_arg(4), r) || !r.is_uint64()) return false;
-    end = r.get_uint64();
-    if (!is_js_properties(to_app(e)->get_arg(5), properties)) return false;
-    return true;
-}
-
-bool csp_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end, svector<symbol>& properties) {
-    if (!is_app_of(e, m_fid, OP_JS_JOB_RESOURCE)) return false;
-    job = to_app(e)->get_arg(0);
-    res = to_app(e)->get_arg(1);
-    arith_util a(m);
-    rational r;
-    if (!a.is_numeral(to_app(e)->get_arg(2), r) || !r.is_unsigned()) return false;
-    loadpct = r.get_unsigned();
-    if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false;
-    capacity = r.get_uint64();
-    if (!a.is_numeral(to_app(e)->get_arg(4), r) || !r.is_uint64()) return false;
-    end = r.get_uint64();
-    if (!is_js_properties(to_app(e)->get_arg(5), properties)) return false;
-    return true;
-}
-
-bool csp_util::is_set_preemptable(expr* e, expr *& job) {
-    if (!is_app_of(e, m_fid, OP_JS_JOB_PREEMPTABLE)) return false;
-    job = to_app(e)->get_arg(0);
-    return true;
-}
-
-bool csp_util::is_js_properties(expr* e, svector<symbol>& properties) {
-    if (!is_app_of(e, m_fid, OP_JS_PROPERTIES)) 
-        return false;
-    unsigned sz = to_app(e)->get_decl()->get_num_parameters();
-    for (unsigned i = 0; i < sz; ++i) {
-        properties.push_back(to_app(e)->get_decl()->get_parameter(i).get_symbol());
-    }
-    return true;
-}
-
-bool csp_util::is_job_goal(expr* e, js_job_goal& goal, unsigned& level, expr*& job) {
-    if (!is_app_of(e, m_fid, OP_JS_JOB_GOAL)) 
-        return false;
-    SASSERT(2 == to_app(e)->get_decl()->get_num_parameters());
-    SASSERT(1 == to_app(e)->get_num_args());
-    symbol g = to_app(e)->get_decl()->get_parameter(0).get_symbol();
-    level = to_app(e)->get_decl()->get_parameter(1).get_int();
-    if (g == ":earliest-end-time" || g == "earliest-end-time")
-        goal = JS_JOB_GOAL_EARLIEST_END_TIME;
-    else if (g == ":latest-start-time" || g == "latest-start-time")
-        goal = JS_JOB_GOAL_LATEST_START_TIME;
-    else 
-        return false;
-    job = to_app(e)->get_arg(0);
-    return true;
-}
-
-bool csp_util::is_objective(expr* e, js_optimization_objective& objective) {
-    if (!is_app_of(e, m_fid, OP_JS_OBJECTIVE))
-        return false;
-    SASSERT(1 == to_app(e)->get_decl()->get_num_parameters());    
-    symbol obj = to_app(e)->get_decl()->get_parameter(0).get_symbol();
-    if (obj == ":duration" || obj == "duration")
-        objective = JS_OBJECTIVE_DURATION;
-    else if (obj == ":priority" || obj == "priority") 
-        objective = JS_OBJECTIVE_PRIORITY;
-    else 
-        return false;
-    return true;
-}
-
-
diff --git a/src/ast/csp_decl_plugin.h b/src/ast/csp_decl_plugin.h
deleted file mode 100644
index fcbe4a443..000000000
--- a/src/ast/csp_decl_plugin.h
+++ /dev/null
@@ -1,163 +0,0 @@
-/*++
-Copyright (c) 2018 Microsoft Corporation
-
-Module Name:
-
-    csp_decl_plugin.h
-
-Abstract:
-
-    Declarations used for a job-shop scheduling domain.
-
-    The job-shop domain comprises of constants job(j), resource(r)
-
-    It finds values to variables:
-    - start(j), end(j), job2resource(j)
-
-    It assumes a background of:
-    - resources : Job -> Resource -> Int * LoadPct - time to run job j on resource r assuming LoadPct
-    - runtime   : Job -> Int                       - time to run job j if not associated with any resource
-    - capacity  : Resource -> Int -> LoadPct       - capacity of resource r at time t, given as sequence of time intervals
-    // assume each job has at least one resource associated with it.    
-    // introduce a dummy resource if needed.
-
-    // Theory:
-    end(j) - start(j) = time-to-execute(j)
-    time-to-execute(j) := time-to-execute(j, resource(j))  otherwise
-
-    time-to-execute(j, r) := (T - start(j)) 
-            where capacity(j,r) = sum_{t = start(j)}^{T} load(loadpct(j,r), r, t)
-
-    capacity(j, r) := cap where (cap, loadpct) = resources j r
-    loadpct(j, r)  := loadpct where (cap, loadpct) = resources j r
-
-    load(loadpct, r, t) := min(capacity r t, loadpct) / loadpct
-
-    capacity(r, t) >= sum_{j | job-on-resource(j, r, t) } min(capacity r t, loadpct(j, r))
-
-    // Macros:
-    job-on-resource(j, r)              := r = resource(j);
-    job-on-resource(j, r, t)           := (job-on-resource(j, r) & start(j) <= t <= end(j));
-    start_min(j, t)                    := start(j) >= t;
-    end_max(j, t)                      := end(j) <= t;
-    job_link(j1, j2, startstart, hard) := start(j1) = start(j2);
-    job_link(j1, j2, startstart, soft) := start(j1) <= start(j2);
-    job_link(j1, j2, endend, hard)     := end(j1) =  end(j2);
-    job_link(j1, j2, endend, soft)     := end(j2) <= end(j1);
-    job_link(j1, j2, endstart, hard)   := end(j1) =  start(j2);
-    job_link(j1, j2, endstart, soft)   := end(j2) <= start(j1);
-    job_link(j1, j2, startend, hard)   := end(j2) =  start(j1);
-    job_link(j1, j2, startend, soft)   := end(j1) <= start(j2);
-    job_delay(j1, j2, t)               := end(j1) + t <= end(j2);
-    job_on_same_resource(j1, j2)       := resource(j1) = resource(j2);
-    job_not_on_same_resource(j1, j2)   := resource(j1) != resource(j2);
-    job_time_intersect(j1, j2)         := start(j1) <= end(j2) <= end(j1) || start(j2) <= end(j1) <= end(j2);
-    
-    job-on-resource(j, r, t) => job-property(j) = null or job_property(j) in working_time_property(r, t);
-        
-Author:
-
-    Nikolaj Bjorner (nbjorner) 2018-8-9 
-
-Revision History:
-
-
---*/
-#pragma once
-#include "ast/ast.h"
-
-enum js_sort_kind {
-    JOB_SORT,
-    RESOURCE_SORT,
-    ALIST_SORT
-};
-
-enum js_op_kind {
-    OP_JS_JOB,               // value of type job
-    OP_JS_RESOURCE,          // value of type resource
-    OP_JS_RESOURCE_MAKESPAN, // makespan of resource: the minimal resource time required for assigned jobs.
-    OP_JS_START,             // start time of a job
-    OP_JS_END,               // end time of a job
-    OP_JS_JOB2RESOURCE,      // resource associated with job
-    OP_JS_MODEL,             // jobscheduler model
-    OP_JS_JOB_RESOURCE,      // model declaration for job assignment to resource
-    OP_JS_JOB_PREEMPTABLE,   // model declaration for whether job is pre-emptable
-    OP_JS_RESOURCE_AVAILABLE, // model declaration for availability intervals of resource
-    OP_JS_PROPERTIES,         // model declaration of a set of properties. Each property is a keyword.
-    OP_JS_JOB_GOAL,           // job goal objective :earliest-end-time or :latest-start-time
-    OP_JS_OBJECTIVE           // duration or completion-time
-};
-
-enum js_job_goal {
-    JS_JOB_GOAL_EARLIEST_END_TIME,
-    JS_JOB_GOAL_LATEST_START_TIME
-};
-
-enum js_optimization_objective {
-    JS_OBJECTIVE_DURATION,
-    JS_OBJECTIVE_PRIORITY
-};
-
-class csp_decl_plugin : public decl_plugin {
-    void init();
-public:
-    csp_decl_plugin() {}
-    ~csp_decl_plugin() override {}
-    void finalize() override;
-    void set_manager(ast_manager* m, family_id fid) override;
-    decl_plugin * mk_fresh() override { return alloc(csp_decl_plugin); }
-    sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override;
-    func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters,
-                             unsigned arity, sort * const * domain, sort * range) override;	
-    bool is_value(app * e) const override;
-    bool is_unique_value(app * e) const override { return is_value(e); }
-    void get_op_names(svector<builtin_name> & op_names, symbol const & logic) override;
-    void get_sort_names(svector<builtin_name> & sort_names, symbol const & logic) override;
-    expr * get_some_value(sort * s) override;
-    sort * mk_job_sort() const { return m_job_sort; }
-    sort * mk_resource_sort() const { return m_resource_sort; }
-    sort * mk_alist_sort() const { return m_alist_sort; }
-private:
-    sort* m_job_sort;
-    sort* m_resource_sort;
-    sort* m_alist_sort;
-    sort* m_int_sort;
-
-    void check_arity(unsigned arity);
-    void check_index1(unsigned n, parameter const* ps);
-    void check_index2(unsigned n, parameter const* ps);
-};
-
-class csp_util {
-    ast_manager&         m;
-    family_id            m_fid;
-    csp_decl_plugin*     m_plugin;
-public:
-    csp_util(ast_manager& m);
-    sort* mk_job_sort();
-    sort* mk_resource_sort();
-
-    app* mk_job(unsigned j);
-    app* mk_resource(unsigned r);
-    app* mk_start(unsigned j);
-    app* mk_end(unsigned j);
-    app* mk_job2resource(unsigned j);
-    app* mk_makespan(unsigned r);
-
-    bool is_job(expr* e, unsigned& j);
-    bool is_job2resource(expr* e, unsigned& j);
-    bool is_resource(expr* e, unsigned& r);
-    bool is_makespan(expr* e, unsigned& r);
-    bool is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, unsigned& cap_time, uint64_t& start, uint64_t& end, svector<symbol>& properites);
-    bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& finite_capacity_end, svector<symbol>& properites); 
-    bool is_set_preemptable(expr* e, expr *& job);
-    bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); }
-    bool is_js_properties(expr* e, svector<symbol>& properties);
-    bool is_job_goal(expr* e, js_job_goal& goal, unsigned& level, expr*& job);
-    bool is_objective(expr* e, js_optimization_objective& objective);
-
-private:
-    unsigned job2id(expr* j);
-    unsigned resource2id(expr* r);
-
-};
diff --git a/src/ast/euf/euf_egraph.cpp b/src/ast/euf/euf_egraph.cpp
index 4f1e3d109..74c0c0b0f 100644
--- a/src/ast/euf/euf_egraph.cpp
+++ b/src/ast/euf/euf_egraph.cpp
@@ -17,52 +17,11 @@ Author:
 
 #include "ast/euf/euf_egraph.h"
 #include "ast/ast_pp.h"
+#include "ast/ast_ll_pp.h"
 #include "ast/ast_translation.h"
 
 namespace euf {
 
-    /**
-       \brief Trail for add_th_var
-    */
-    class add_th_var_trail : public trail<egraph> {
-        enode *    m_enode;
-        theory_id  m_th_id;
-    public:
-        add_th_var_trail(enode * n, theory_id th_id):
-            m_enode(n),
-            m_th_id(th_id) {
-        }
-        
-        void undo(egraph & ctx) override {
-            theory_var v = m_enode->get_th_var(m_th_id);
-            SASSERT(v != null_theory_var);
-            m_enode->del_th_var(m_th_id);
-            enode * root = m_enode->get_root();
-            if (root != m_enode && root->get_th_var(m_th_id) == v) 
-                root->del_th_var(m_th_id);
-        }
-    };
-
-    /**
-       \brief Trail for replace_th_var
-    */
-    class replace_th_var_trail : public trail<egraph> {
-        enode *    m_enode;
-        unsigned   m_th_id:8;
-        unsigned   m_old_th_var:24;
-    public:
-        replace_th_var_trail(enode * n, theory_id th_id, theory_var old_var):
-            m_enode(n),
-            m_th_id(th_id),
-            m_old_th_var(old_var) {
-        }
-        
-        void undo(egraph & ctx) override {
-            SASSERT(m_enode->get_th_var(m_th_id) != null_theory_var);
-            m_enode->replace_th_var(m_old_th_var, m_th_id);
-        }
-    };
-
     void egraph::undo_eq(enode* r1, enode* n1, unsigned r2_num_parents) {
         enode* r2 = r1->get_root();
         r2->dec_class_size(r1->class_size());
@@ -82,6 +41,9 @@ namespace euf {
         enode* n = enode::mk(m_region, f, num_args, args);
         m_nodes.push_back(n);
         m_exprs.push_back(f);
+        push_node(n);
+        for (unsigned i = 0; i < num_args; ++i)
+            set_merge_enabled(args[i], true);
         return n;
     }
 
@@ -104,8 +66,7 @@ namespace euf {
     void egraph::reinsert_equality(enode* p) {
         SASSERT(is_equality(p));
         if (p->get_arg(0)->get_root() == p->get_arg(1)->get_root()) {
-            m_new_lits.push_back(enode_bool_pair(p, true));
-            ++m_stats.m_num_eqs;
+            add_literal(p, true);
         }
     }
 
@@ -114,19 +75,14 @@ namespace euf {
     }
 
     void egraph::force_push() {
+        if (m_num_scopes == 0)
+            return;
         for (; m_num_scopes > 0; --m_num_scopes) {
-            scope s;
-            s.m_inconsistent = m_inconsistent;
-            s.m_num_eqs = m_eqs.size();
-            s.m_num_nodes = m_nodes.size();
-            s.m_trail_sz  = m_trail.size();
-            s.m_new_lits_sz   = m_new_lits.size();
-            s.m_new_th_eqs_sz = m_new_th_eqs.size();
-            s.m_new_lits_qhead = m_new_lits_qhead;
-            s.m_new_th_eqs_qhead = m_new_th_eqs_qhead;
-            m_scopes.push_back(s);
+            m_scopes.push_back(m_updates.size());
             m_region.push_scope();
         }
+        m_updates.push_back(update_record(m_new_lits_qhead, update_record::new_lits_qhead()));
+        m_updates.push_back(update_record(m_new_th_eqs_qhead, update_record::new_th_eq_qhead()));
     }
 
     void egraph::update_children(enode* n) {
@@ -147,6 +103,7 @@ namespace euf {
             return n;
         if (is_equality(n)) {
             update_children(n);
+            reinsert_equality(n);
             return n;
         }
         enode_bool_pair p = m_table.insert(n);
@@ -170,6 +127,18 @@ namespace euf {
             n->m_parents.finalize();
     }
 
+    void egraph::add_th_eq(theory_id id, theory_var v1, theory_var v2, enode* c, enode* r) {
+        m_new_th_eqs.push_back(th_eq(id, v1, v2, c, r));
+        m_updates.push_back(update_record(update_record::new_th_eq()));
+        ++m_stats.m_num_th_eqs;
+    }
+
+    void egraph::add_literal(enode* n, bool is_eq) {
+        m_new_lits.push_back(enode_bool_pair(n, is_eq));
+        m_updates.push_back(update_record(update_record::new_lit()));
+        if (is_eq) ++m_stats.m_num_eqs; else ++m_stats.m_num_lits;
+    }
+
     void egraph::add_th_var(enode* n, theory_var v, theory_id id) {
         force_push();
         theory_var w = n->get_th_var(id);
@@ -177,21 +146,37 @@ namespace euf {
 
         if (w == null_theory_var) {
             n->add_th_var(v, id, m_region);
-            m_trail.push_back(new (m_region) add_th_var_trail(n, id));
+            m_updates.push_back(update_record(n, id, update_record::add_th_var()));
             if (r != n) {
                 theory_var u = r->get_th_var(id);
                 if (u == null_theory_var) 
                     r->add_th_var(v, id, m_region);
                 else
-                    m_new_th_eqs.push_back(th_eq(id, v, u, n, r));
+                    add_th_eq(id, v, u, n, r);
             }
         }
         else {
             theory_var u = r->get_th_var(id);
             SASSERT(u != v && u != null_theory_var);
             n->replace_th_var(v, id);
-            m_trail.push_back(new (m_region) replace_th_var_trail(n, id, u));
-            m_new_th_eqs.push_back(th_eq(id, v, u, n, r));
+            m_updates.push_back(update_record(n, id, u, update_record::replace_th_var()));
+            add_th_eq(id, v, u, n, r);
+        }
+    }
+
+    void egraph::undo_add_th_var(enode* n, theory_id tid) {
+        theory_var v = n->get_th_var(tid);
+        SASSERT(v != null_theory_var);
+        n->del_th_var(tid);
+        enode* root = n->get_root();
+        if (root != n && root->get_th_var(tid) == v)
+            root->del_th_var(tid);
+    }
+
+    void egraph::set_merge_enabled(enode* n, bool enable_merge) {
+        if (enable_merge != n->merge_enabled()) {
+            m_updates.push_back(update_record(n, update_record::toggle_merge()));
+            n->set_merge_enabled(enable_merge);
         }
     }
 
@@ -202,29 +187,59 @@ namespace euf {
         }
         num_scopes -= m_num_scopes;
         unsigned old_lim = m_scopes.size() - num_scopes;
-        scope s = m_scopes[old_lim];
-        for (unsigned i = m_eqs.size(); i-- > s.m_num_eqs; ) {
-            auto const& p = m_eqs[i];
-            undo_eq(p.r1, p.n1, p.r2_num_parents);
-        }        
-        for (unsigned i = m_nodes.size(); i-- > s.m_num_nodes; ) {
-            enode* n = m_nodes[i];
+        unsigned num_updates = m_scopes[old_lim];
+        auto undo_node = [&](enode* n) {
             if (n->num_args() > 1)
                 m_table.erase(n);
             m_expr2enode[n->get_owner_id()] = nullptr;
             n->~enode();
-        }
-        undo_trail_stack<egraph>(*this, m_trail, s.m_trail_sz);
-        m_inconsistent = s.m_inconsistent;
-        m_new_lits_qhead = s.m_new_lits_qhead;
-        m_new_th_eqs_qhead = s.m_new_th_eqs_qhead;
-        m_eqs.shrink(s.m_num_eqs);
-        m_nodes.shrink(s.m_num_nodes);
-        m_exprs.shrink(s.m_num_nodes);
-        m_new_lits.shrink(s.m_new_lits_sz);
-        m_new_th_eqs.shrink(s.m_new_th_eqs_sz);
+            m_nodes.pop_back();
+            m_exprs.pop_back();
+        };
+        for (unsigned i = m_updates.size(); i-- > num_updates; ) {
+            auto const& p = m_updates[i];
+            switch (p.tag) {
+            case update_record::tag_t::is_add_node:
+                undo_node(p.r1);
+                break;
+            case update_record::tag_t::is_toggle_merge:
+                p.r1->set_merge_enabled(!p.r1->merge_enabled());
+                break;
+            case update_record::tag_t::is_set_parent:
+                undo_eq(p.r1, p.n1, p.r2_num_parents);
+                break;
+            case update_record::tag_t::is_add_th_var:
+                undo_add_th_var(p.r1, p.r2_num_parents);
+                break;
+            case update_record::tag_t::is_replace_th_var:
+                SASSERT(p.r1->get_th_var(p.m_th_id) != null_theory_var);
+                p.r1->replace_th_var(p.m_old_th_var, p.m_th_id);
+                break;
+            case update_record::tag_t::is_new_lit:
+                m_new_lits.pop_back();
+                break;
+            case update_record::tag_t::is_new_th_eq:
+                m_new_th_eqs.pop_back();
+                break;
+            case update_record::tag_t::is_new_th_eq_qhead:
+                m_new_th_eqs_qhead = p.qhead;
+                break;
+            case update_record::tag_t::is_new_lits_qhead:
+                m_new_lits_qhead = p.qhead;
+                break;
+            case update_record::tag_t::is_inconsistent:
+                m_inconsistent = p.m_inconsistent;
+                break;
+            default:
+                UNREACHABLE();
+                break;
+            }                
+        }        
+       
+        m_updates.shrink(num_updates);
         m_scopes.shrink(old_lim);        
         m_region.pop_scope(num_scopes);  
+        m_worklist.reset();
     }
 
     void egraph::merge(enode* n1, enode* n2, justification j) {
@@ -245,8 +260,7 @@ namespace euf {
             std::swap(n1, n2);
         }
         if ((m.is_true(r2->get_owner()) || m.is_false(r2->get_owner())) && j.is_congruence()) {
-            m_new_lits.push_back(enode_bool_pair(n1, false));
-            ++m_stats.m_num_lits;
+            add_literal(n1, false);
         }
         for (enode* p : enode_parents(n1)) 
             m_table.erase(p);            
@@ -268,13 +282,13 @@ namespace euf {
         for (auto iv : enode_th_vars(n)) {
             theory_id id = iv.get_id();
             theory_var v = root->get_th_var(id);
-            if (v == null_theory_var) {
-                root->add_th_var(iv.get_var(), id, m_region);                
-                m_trail.push_back(new (m_region) add_th_var_trail(root, id));
+            if (v == null_theory_var) {                
+                root->add_th_var(iv.get_var(), id, m_region);   
+                m_updates.push_back(update_record(root, id, update_record::add_th_var()));
             }
             else {
                 SASSERT(v != iv.get_var());
-                m_new_th_eqs.push_back(th_eq(id, v, iv.get_var(), n, root));
+                add_th_eq(id, v, iv.get_var(), n, root);
             }
         }
     }
@@ -308,6 +322,7 @@ namespace euf {
         if (m_inconsistent)
             return;
         m_inconsistent = true;
+        m_updates.push_back(update_record(false, update_record::inconsistent()));
         m_n1 = n1;
         m_n2 = n2;
         m_justification = j;
@@ -387,10 +402,19 @@ namespace euf {
         }
     }
 
+    void egraph::begin_explain() {
+        SASSERT(m_todo.empty());
+    }
+
+    void egraph::end_explain() {
+        for (enode* n : m_todo) 
+            n->unmark1();
+        m_todo.reset();        
+    }
+
     template <typename T>
     void egraph::explain(ptr_vector<T>& justifications) {
         SASSERT(m_inconsistent);
-        SASSERT(m_todo.empty());
         push_todo(m_n1);
         push_todo(m_n2);
         explain_eq(justifications, m_n1, m_n2, m_justification);
@@ -399,7 +423,6 @@ namespace euf {
 
     template <typename T>
     void egraph::explain_eq(ptr_vector<T>& justifications, enode* a, enode* b) {
-        SASSERT(m_todo.empty());
         SASSERT(a->get_root() == b->get_root());
         enode* lca = find_lca(a, b);
         push_to_lca(a, lca);
@@ -418,9 +441,6 @@ namespace euf {
                 explain_eq(justifications, n, n->m_target, n->m_justification);
             }
         }
-        for (enode* n : m_todo) 
-            n->unmark1();
-        m_todo.reset();        
     }
 
     void egraph::invariant() {
@@ -429,25 +449,29 @@ namespace euf {
     }
 
     std::ostream& egraph::display(std::ostream& out, unsigned max_args, enode* n) const {
-        out << std::setw(5)
-            << n->get_owner_id() << " := ";
+        out << n->get_owner_id() << " := ";
         if (!n->is_root()) 
             out << "[" << n->get_root()->get_owner_id() << "] ";
         expr* f = n->get_owner();
         if (is_app(f))
-            out << to_app(f)->get_decl()->get_name() << " ";
+            out << mk_bounded_pp(f, m, 1);
         else if (is_quantifier(f))
-            out << "q ";
+            out << "q:" << f->get_id();
         else
-            out << "v ";
-        for (enode* arg : enode_args(n))
-            out << arg->get_owner_id() << " ";
-        for (unsigned i = n->num_args(); i < max_args; ++i)
-            out << "   ";
-        out << "\t";
-        for (enode* p : enode_parents(n))
-            out << p->get_owner_id() << " ";
+            out << "v:" << f->get_id();
         out << "\n";
+        if (!n->m_parents.empty()) {
+            out << "    ";
+            for (enode* p : enode_parents(n))
+                out << p->get_owner_id() << " ";
+            out << "\n";
+        }
+        if (n->has_th_vars()) {
+            out << "    ";
+            for (auto v : enode_th_vars(n))
+                out << v.get_id() << ":" << v.get_var() << " ";
+            out << "\n";
+        }
         return out;
     }
 
@@ -464,8 +488,9 @@ namespace euf {
     void egraph::collect_statistics(statistics& st) const {
         st.update("euf merge", m_stats.m_num_merge);
         st.update("euf conflicts", m_stats.m_num_conflicts);
-        st.update("euf eq prop", m_stats.m_num_eqs);
-        st.update("euf lit prop", m_stats.m_num_lits);
+        st.update("euf equality propagations", m_stats.m_num_eqs);
+        st.update("euf theory equality propagations", m_stats.m_num_th_eqs);
+        st.update("euf literal propagations", m_stats.m_num_lits);
     }
 
     void egraph::copy_from(egraph const& src, std::function<void*(void*)>& copy_justification) {
@@ -504,7 +529,7 @@ template void euf::egraph::explain(ptr_vector<int>& justifications);
 template void euf::egraph::explain_todo(ptr_vector<int>& justifications);
 template void euf::egraph::explain_eq(ptr_vector<int>& justifications, enode* a, enode* b);
 
-template void euf::egraph::explain(ptr_vector<unsigned>& justifications);
-template void euf::egraph::explain_todo(ptr_vector<unsigned>& justifications);
-template void euf::egraph::explain_eq(ptr_vector<unsigned>& justifications, enode* a, enode* b);
+template void euf::egraph::explain(ptr_vector<size_t>& justifications);
+template void euf::egraph::explain_todo(ptr_vector<size_t>& justifications);
+template void euf::egraph::explain_eq(ptr_vector<size_t>& justifications, enode* a, enode* b);
 
diff --git a/src/ast/euf/euf_egraph.h b/src/ast/euf/euf_egraph.h
index 0fd5c4189..63265605d 100644
--- a/src/ast/euf/euf_egraph.h
+++ b/src/ast/euf/euf_egraph.h
@@ -31,14 +31,6 @@ Notes:
 
 namespace euf {
 
-    struct add_eq_record {
-        enode* r1;
-        enode* n1;
-        unsigned r2_num_parents;
-        add_eq_record(enode* r1, enode* n1, unsigned r2_num_parents):
-            r1(r1), n1(n1), r2_num_parents(r2_num_parents) {}
-    };
-
     /***
       \brief store derived theory equalities.
       Theory 'id' is notified with the equality of theory variables v1, v2
@@ -58,31 +50,66 @@ namespace euf {
     
     class egraph {        
         typedef ptr_vector<trail<egraph> > trail_stack;
-        struct scope {
-            bool     m_inconsistent;
-            unsigned m_num_eqs;
-            unsigned m_num_nodes;
-            unsigned m_trail_sz;
-            unsigned m_new_lits_sz;
-            unsigned m_new_th_eqs_sz;
-            unsigned m_new_lits_qhead;
-            unsigned m_new_th_eqs_qhead;
-        };
         struct stats {
             unsigned m_num_merge;
+            unsigned m_num_th_eqs;
             unsigned m_num_lits;
             unsigned m_num_eqs;
             unsigned m_num_conflicts;
             stats() { reset(); }
             void reset() { memset(this, 0, sizeof(*this)); }
         };
+        struct update_record {
+            struct toggle_merge {};
+            struct add_th_var {};
+            struct replace_th_var {};
+            struct new_lit {};
+            struct new_th_eq {};
+            struct new_th_eq_qhead {};
+            struct new_lits_qhead {};
+            struct inconsistent {};
+            enum tag_t { is_set_parent, is_add_node, is_toggle_merge, 
+                         is_add_th_var, is_replace_th_var, is_new_lit, is_new_th_eq,
+                         is_new_th_eq_qhead, is_new_lits_qhead, is_inconsistent };
+            tag_t  tag;
+            enode* r1;
+            enode* n1;
+            union {
+                unsigned r2_num_parents;
+                struct {
+                    unsigned   m_th_id : 8;
+                    unsigned   m_old_th_var : 24;
+                };
+                unsigned qhead;
+                bool     m_inconsistent;
+            };
+            update_record(enode* r1, enode* n1, unsigned r2_num_parents) :
+                tag(tag_t::is_set_parent), r1(r1), n1(n1), r2_num_parents(r2_num_parents) {}
+            update_record(enode* n) :
+                tag(tag_t::is_add_node), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {}
+            update_record(enode* n, toggle_merge) :
+                tag(tag_t::is_toggle_merge), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {}
+            update_record(enode* n, unsigned id, add_th_var) :
+                tag(tag_t::is_add_th_var), r1(n), n1(nullptr), r2_num_parents(id) {}
+            update_record(enode* n, theory_id id, theory_var v, replace_th_var) :
+                tag(tag_t::is_replace_th_var), r1(n), n1(nullptr), m_th_id(id), m_old_th_var(v) {}
+            update_record(new_lit) :
+                tag(tag_t::is_new_lit), r1(nullptr), n1(nullptr), r2_num_parents(0) {}
+            update_record(new_th_eq) :
+                tag(tag_t::is_new_th_eq), r1(nullptr), n1(nullptr), r2_num_parents(0) {}
+            update_record(unsigned qh, new_th_eq_qhead):
+                tag(tag_t::is_new_th_eq_qhead), r1(nullptr), n1(nullptr), qhead(qh) {}
+            update_record(unsigned qh, new_lits_qhead):
+                tag(tag_t::is_new_lits_qhead), r1(nullptr), n1(nullptr), qhead(qh) {}
+            update_record(bool inc, inconsistent) :
+                tag(tag_t::is_inconsistent), m_inconsistent(inc) {}
+        };
         ast_manager&           m;
-        trail_stack            m_trail;
-        region                 m_region;
         enode_vector           m_worklist;
         etable                 m_table;
-        svector<add_eq_record> m_eqs;
-        svector<scope>         m_scopes;
+        region                 m_region;
+        svector<update_record> m_updates;
+        unsigned_vector        m_scopes;
         enode_vector           m_expr2enode;
         enode_vector           m_nodes;
         expr_ref_vector        m_exprs;
@@ -101,9 +128,14 @@ namespace euf {
         std::function<void(app*,app*)>        m_used_cc;  
 
         void push_eq(enode* r1, enode* n1, unsigned r2_num_parents) {
-            m_eqs.push_back(add_eq_record(r1, n1, r2_num_parents));
+            m_updates.push_back(update_record(r1, n1, r2_num_parents));
         }
+        void push_node(enode* n) { m_updates.push_back(update_record(n)); }
+
+        void add_th_eq(theory_id id, theory_var v1, theory_var v2, enode* c, enode* r);
+        void add_literal(enode* n, bool is_eq);
         void undo_eq(enode* r1, enode* n1, unsigned r2_num_parents);
+        void undo_add_th_var(enode* n, theory_id id);
         enode* mk_enode(expr* f, unsigned num_args, enode * const* args);
         void reinsert(enode* n);
         void force_push();
@@ -173,10 +205,13 @@ namespace euf {
 
 
         void add_th_var(enode* n, theory_var v, theory_id id);
+        void set_merge_enabled(enode* n, bool enable_merge);
 
         void set_used_eq(std::function<void(expr*,expr*,expr*)>& used_eq) { m_used_eq = used_eq; }
         void set_used_cc(std::function<void(app*,app*)>& used_cc) { m_used_cc = used_cc; }
 
+        void begin_explain();
+        void end_explain();
         template <typename T>
         void explain(ptr_vector<T>& justifications);
         template <typename T>
diff --git a/src/ast/euf/euf_enode.h b/src/ast/euf/euf_enode.h
index 064576119..41bab679e 100644
--- a/src/ast/euf/euf_enode.h
+++ b/src/ast/euf/euf_enode.h
@@ -36,21 +36,22 @@ namespace euf {
     const theory_id null_theory_id = -1;
 
     class enode {
-        expr*         m_owner;
+        expr*         m_owner{ nullptr };
         bool          m_mark1 { false };
         bool          m_mark2 { false };
         bool          m_commutative { false };
         bool          m_update_children { false };
         bool          m_interpreted { false };
+        bool          m_merge_enabled { true };
         unsigned      m_class_size { 1 };
         unsigned      m_table_id { UINT_MAX };
         enode_vector  m_parents;
-        enode*        m_next;
-        enode*        m_root;
+        enode*        m_next{ nullptr };
+        enode*        m_root{ nullptr };
         enode*        m_target { nullptr };
         th_var_list   m_th_vars;
         justification m_justification;
-        unsigned      m_num_args;
+        unsigned      m_num_args { 0 };
         enode*        m_args[0];        
 
         friend class enode_args;
@@ -73,6 +74,7 @@ namespace euf {
             n->m_root = n;
             n->m_commutative = num_args == 2 && is_app(f) && to_app(f)->get_decl()->is_commutative();
             n->m_num_args = num_args;
+            n->m_merge_enabled = true;
             for (unsigned i = 0; i < num_args; ++i) {
                 SASSERT(to_app(f)->get_arg(i) == args[i]->get_owner());
                 n->m_args[i] = args[i];
@@ -86,7 +88,8 @@ namespace euf {
         friend class replace_th_var_trail;
         void add_th_var(theory_var v, theory_id id, region & r) { m_th_vars.add_var(v, id, r); }
         void replace_th_var(theory_var v, theory_id id) { m_th_vars.replace(v, id); }
-        void del_th_var(theory_id id) { m_th_vars.del_var(id); }        
+        void del_th_var(theory_id id) { m_th_vars.del_var(id); }   
+        void set_merge_enabled(bool m) { m_merge_enabled = m; }
 
     public:
         ~enode() { 
@@ -106,6 +109,7 @@ namespace euf {
         bool interpreted() const { return m_interpreted; }
         bool commutative() const { return m_commutative; }
         void mark_interpreted() { SASSERT(num_args() == 0); m_interpreted = true; }
+        bool merge_enabled() { return m_merge_enabled; }
 
         enode* get_arg(unsigned i) const { SASSERT(i < num_args()); return m_args[i]; }        
         unsigned hash() const { return m_owner->hash(); }
@@ -143,6 +147,7 @@ namespace euf {
         unsigned get_owner_id() const { return m_owner->get_id(); }
         unsigned get_root_id() const { return m_root->m_owner->get_id(); }
         theory_var get_th_var(theory_id id) const { return m_th_vars.find(id); }
+        bool is_attached_to(theory_id id) const { return get_th_var(id) != null_theory_var; }
         bool has_th_vars() const { return !m_th_vars.empty(); }
 
         void inc_class_size(unsigned n) { m_class_size += n; }
diff --git a/src/ast/pattern/expr_pattern_match.h b/src/ast/pattern/expr_pattern_match.h
index 3fc226e03..272610d4e 100644
--- a/src/ast/pattern/expr_pattern_match.h
+++ b/src/ast/pattern/expr_pattern_match.h
@@ -45,14 +45,14 @@ class expr_pattern_match {
             m_kind(k), m_offset(o), m_next(next), m_app(app), m_count(count) {}
 
         instr_kind      m_kind;
-        unsigned        m_offset;
-        unsigned        m_next;
-        app*            m_app;
-        expr*           m_pat;
-        unsigned        m_reg;
-        unsigned        m_other_reg;
-        unsigned        m_count;
-        unsigned        m_num_bound;
+        unsigned        m_offset{ 0 };
+        unsigned        m_next{ 0 };
+        app*            m_app{ nullptr };
+        expr*           m_pat{ nullptr };
+        unsigned        m_reg{ 0 };
+        unsigned        m_other_reg{ 0 };
+        unsigned        m_count{ 0 };
+        unsigned        m_num_bound{ 0 };
     };
 
     typedef obj_map<func_decl, unsigned> subst;
diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp
index 387e10edd..f86cc6cb6 100644
--- a/src/cmd_context/cmd_context.cpp
+++ b/src/cmd_context/cmd_context.cpp
@@ -30,7 +30,6 @@ Notes:
 #include "ast/seq_decl_plugin.h"
 #include "ast/pb_decl_plugin.h"
 #include "ast/fpa_decl_plugin.h"
-#include "ast/csp_decl_plugin.h"
 #include "ast/special_relations_decl_plugin.h"
 #include "ast/ast_pp.h"
 #include "ast/rewriter/var_subst.h"
@@ -698,7 +697,6 @@ void cmd_context::init_manager_core(bool new_manager) {
         register_plugin(symbol("pb"),       alloc(pb_decl_plugin), logic_has_pb());
         register_plugin(symbol("fpa"),      alloc(fpa_decl_plugin), logic_has_fpa());
         register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin), !has_logic());
-        register_plugin(symbol("csp"),      alloc(csp_decl_plugin), smt_logics::logic_is_csp(m_logic));
         register_plugin(symbol("specrels"), alloc(special_relations_decl_plugin), !has_logic());
     }
     else {
@@ -715,7 +713,6 @@ void cmd_context::init_manager_core(bool new_manager) {
         load_plugin(symbol("seq"),      logic_has_seq(), fids);
         load_plugin(symbol("fpa"),      logic_has_fpa(), fids);
         load_plugin(symbol("pb"),       logic_has_pb(), fids);
-        load_plugin(symbol("csp"),      smt_logics::logic_is_csp(m_logic), fids);
 
         for (family_id fid : fids) {
             decl_plugin * p = m_manager->get_plugin(fid);
diff --git a/src/sat/dimacs.cpp b/src/sat/dimacs.cpp
index ea8789958..027186fb7 100644
--- a/src/sat/dimacs.cpp
+++ b/src/sat/dimacs.cpp
@@ -20,43 +20,21 @@ Revision History:
 #undef max
 #undef min
 #include "sat/sat_solver.h"
-
-namespace {
-struct lex_error {};
-
-class stream_buffer {
-    std::istream & m_stream;
-    int            m_val;
-    unsigned       m_line;
-public:
-    
-    stream_buffer(std::istream & s):
-        m_stream(s),
-        m_line(0) {
-        m_val = m_stream.get();
-    }
-
-    int  operator *() const { 
-        return m_val;
-    }
-
-    void operator ++() { 
-        m_val = m_stream.get();
-        if (m_val == '\n') ++m_line;
-    }
-
-    unsigned line() const { return m_line; }
-};
+#include "sat/sat_drat.h"
 
 template<typename Buffer>
-void skip_whitespace(Buffer & in) {
-    while ((*in >= 9 && *in <= 13) || *in == 32) {
-        ++in; 
-    }
+static bool is_whitespace(Buffer & in) {
+    return (*in >= 9 && *in <= 13) || *in == 32;
 }
 
 template<typename Buffer>
-void skip_line(Buffer & in) {
+static void skip_whitespace(Buffer & in) {
+    while (is_whitespace(in))
+        ++in; 
+}
+
+template<typename Buffer>
+static void skip_line(Buffer & in) {
     while(true) {
         if (*in == EOF) {
             return;
@@ -70,7 +48,7 @@ void skip_line(Buffer & in) {
 }
 
 template<typename Buffer>
-int parse_int(Buffer & in, std::ostream& err) {
+static int parse_int(Buffer & in, std::ostream& err) {
     int     val = 0;
     bool    neg = false;
     skip_whitespace(in);
@@ -84,8 +62,11 @@ int parse_int(Buffer & in, std::ostream& err) {
     }
 
     if (*in < '0' || *in > '9') {
-        err << "(error, \"unexpected char: " << *in << " line: " << in.line() << "\")\n";
-        throw lex_error();
+        if (20 <= *in && *in < 128) 
+            err << "(error, \"unexpected char: " << ((char)*in) << " line: " << in.line() << "\")\n";
+        else
+            err << "(error, \"unexpected char: " << *in << " line: " << in.line() << "\")\n";
+        throw dimacs::lex_error();
     }
 
     while (*in >= '0' && *in <= '9') {
@@ -97,7 +78,7 @@ int parse_int(Buffer & in, std::ostream& err) {
 }
 
 template<typename Buffer>
-void read_clause(Buffer & in, std::ostream& err, sat::solver & solver, sat::literal_vector & lits) {
+static void read_clause(Buffer & in, std::ostream& err, sat::solver & solver, sat::literal_vector & lits) {
     int     parsed_lit;
     int     var;
     
@@ -116,7 +97,24 @@ void read_clause(Buffer & in, std::ostream& err, sat::solver & solver, sat::lite
 }
 
 template<typename Buffer>
-bool parse_dimacs_core(Buffer & in, std::ostream& err, sat::solver & solver) {
+static void read_clause(Buffer & in, std::ostream& err, sat::literal_vector & lits) {
+    int     parsed_lit;
+    int     var;
+    
+    lits.reset();
+
+    while (true) { 
+        parsed_lit = parse_int(in, err);
+        if (parsed_lit == 0)
+            break;
+        var = abs(parsed_lit);
+        SASSERT(var > 0);
+        lits.push_back(sat::literal(var, parsed_lit < 0));
+    }
+}
+
+template<typename Buffer>
+static bool parse_dimacs_core(Buffer & in, std::ostream& err, sat::solver & solver) {
     sat::literal_vector lits;
     try {
         while (true) {
@@ -133,15 +131,158 @@ bool parse_dimacs_core(Buffer & in, std::ostream& err, sat::solver & solver) {
             }
         }
     }
-    catch (lex_error) {
+    catch (dimacs::lex_error) {
         return false;
     }
     return true;
 }
 
-}
 
 bool parse_dimacs(std::istream & in, std::ostream& err, sat::solver & solver) {
-    stream_buffer _in(in);
+    dimacs::stream_buffer _in(in);
     return parse_dimacs_core(_in, err, solver);
 }
+
+
+namespace dimacs {
+
+    std::ostream& operator<<(std::ostream& out, drat_record const& r) {
+        switch (r.m_tag) {
+        case drat_record::tag_t::is_clause:
+            return out << r.m_status << " " << r.m_lits << "\n";
+        case drat_record::tag_t::is_node:
+            return out << "e " << r.m_node_id << " " << r.m_name << " " << r.m_args << "\n"; 
+        case drat_record::is_bool_def:
+            return out << "b " << r.m_node_id << " " << r.m_args << "\n";
+        }
+        return out;
+    }
+
+    char const* drat_parser::parse_identifier() {
+        m_buffer.reset();
+        while (!is_whitespace(in)) {
+            m_buffer.push_back(*in);
+            ++in;
+        }
+        m_buffer.push_back(0);
+        return m_buffer.c_ptr();
+    }
+
+    char const* drat_parser::parse_sexpr() {
+        m_buffer.reset();
+        unsigned lp = 0;
+        while (!is_whitespace(in) || lp > 0) {
+            m_buffer.push_back(*in);
+            if (*in == '(') 
+                ++lp;
+            else if (*in == ')') {
+                if (lp == 0) { 
+                    throw lex_error(); 
+                } 
+                else --lp;
+            }
+            ++in;
+        }
+        m_buffer.push_back(0);
+        return m_buffer.c_ptr();        
+    }
+
+    int drat_parser::read_theory_id() {
+        skip_whitespace(in);
+        if ('a' <= *in && *in <= 'z') {
+            if (!m_read_theory_id)
+                throw lex_error();
+            return m_read_theory_id(parse_identifier());
+        }
+        else {
+            return -1;
+        }
+    }
+
+    bool drat_parser::next() {
+        int n, b, e, theory_id;
+        try {
+        loop:
+            skip_whitespace(in);
+            switch (*in) {
+            case EOF:
+                return false;
+            case 'c':
+            case 'p':
+                skip_line(in);
+                goto loop;
+            case 'i':
+                ++in;
+                skip_whitespace(in);
+                read_clause(in, err, m_record.m_lits);
+                m_record.m_tag = drat_record::tag_t::is_clause;
+                m_record.m_status = sat::status::input();
+                break;
+            case 'a':
+                ++in;
+                skip_whitespace(in);
+                theory_id = read_theory_id();
+                skip_whitespace(in);
+                read_clause(in, err, m_record.m_lits);
+                m_record.m_tag = drat_record::tag_t::is_clause;
+                m_record.m_status = sat::status::th(false, theory_id);
+                break;
+            case 'e':
+                ++in;
+                skip_whitespace(in);
+                n = parse_int(in, err);                    
+                skip_whitespace(in);
+                m_record.m_name = parse_sexpr();
+                m_record.m_tag = drat_record::tag_t::is_node;
+                m_record.m_node_id = n;
+                m_record.m_args.reset();
+                while (true) {
+                    n = parse_int(in, err);
+                    if (n == 0)
+                        break;
+                    if (n < 0)
+                        throw lex_error();
+                    m_record.m_args.push_back(n);
+                }
+                break;
+            case 'b':
+                ++in;
+                skip_whitespace(in);
+                b = parse_int(in, err);
+                n = parse_int(in, err);
+                e = parse_int(in, err);
+                if (e != 0)
+                    throw lex_error();
+                m_record.m_tag = drat_record::tag_t::is_bool_def;
+                m_record.m_node_id = b;
+                m_record.m_args.reset();
+                m_record.m_args.push_back(n);
+                break;
+            case 'd':
+                ++in;
+                skip_whitespace(in);
+                read_clause(in, err, m_record.m_lits);
+                m_record.m_tag = drat_record::tag_t::is_clause;
+                m_record.m_status = sat::status::deleted();
+                break;
+            case 'r':
+                ++in;
+                skip_whitespace(in);
+                theory_id = read_theory_id();
+                read_clause(in, err, m_record.m_lits);
+                m_record.m_tag = drat_record::tag_t::is_clause;
+                m_record.m_status = sat::status::th(true, theory_id);
+                break;
+            default:
+                read_clause(in, err, m_record.m_lits);
+                m_record.m_tag = drat_record::tag_t::is_clause;
+                m_record.m_status = sat::status::redundant();
+                break;                
+            }    
+            return true;
+        }
+        catch (lex_error) {
+            return false;
+        }
+    }
+}
diff --git a/src/sat/dimacs.h b/src/sat/dimacs.h
index 06629d26d..eed385556 100644
--- a/src/sat/dimacs.h
+++ b/src/sat/dimacs.h
@@ -15,6 +15,9 @@ Author:
 
 Revision History:
 
+    Nikolaj Bjorner (nbjorner) 2020-09-07
+    Add parser to consume extended DRAT format.
+
 --*/
 #pragma once
 
@@ -22,4 +25,87 @@ Revision History:
 
 bool parse_dimacs(std::istream & s, std::ostream& err, sat::solver & solver);
 
+namespace dimacs {
+    struct lex_error {};
+
+    class stream_buffer {
+        std::istream & m_stream;
+        int            m_val;
+        unsigned       m_line;
+    public:
+        
+    stream_buffer(std::istream & s):
+        m_stream(s),
+            m_line(0) {
+            m_val = m_stream.get();
+        }
+        
+        int  operator *() const { 
+            return m_val;
+        }
+        
+        void operator ++() { 
+            m_val = m_stream.get();
+            if (m_val == '\n') ++m_line;
+        }
+        
+        unsigned line() const { return m_line; }
+    };
+
+    struct drat_record {
+        enum tag_t { is_clause, is_node, is_bool_def };
+        tag_t            m_tag;
+        // a clause populates m_lits and m_status
+        // a node populates m_node_id, m_name, m_args
+        // a bool def populates m_node_id and one element in m_args
+        sat::literal_vector  m_lits;
+        sat::status     m_status;
+        unsigned        m_node_id;
+        std::string     m_name;
+        unsigned_vector m_args;
+        drat_record():
+            m_tag(is_clause),
+            m_status(sat::status::redundant())
+        {}
+    };
+
+    std::ostream& operator<<(std::ostream& out, drat_record const& r);
+
+    class drat_parser {
+        dimacs::stream_buffer      in;
+        std::ostream&      err;
+        drat_record        m_record;
+        std::function<int(char const*)> m_read_theory_id;
+        svector<char>      m_buffer;
+
+        char const* parse_sexpr();
+        char const* parse_identifier();
+        int read_theory_id();
+        bool next();
+
+    public:
+        drat_parser(std::istream & _in, std::ostream& err):
+            in(_in), err(err)
+        {}
+
+        class iterator {
+            drat_parser& p;
+            bool m_eof;
+        public:
+            iterator(drat_parser& p, bool is_eof):p(p), m_eof(is_eof) { if (!m_eof) m_eof = !p.next(); }
+            drat_record const& operator*() { return p.m_record; }
+            iterator& operator++() { if (!p.next()) m_eof = true; return *this;}
+            bool operator==(iterator const& other) const { return m_eof == other.m_eof; }
+            bool operator!=(iterator const& other) const { return m_eof != other.m_eof; }
+        };
+        
+        iterator begin() { return iterator(*this, false); };
+        iterator end() { return iterator(*this, true); }
+
+        void set_read_theory(std::function<int(char const*)>& r) { m_read_theory_id = r; }
+
+    };
+};
+
+
 
diff --git a/src/sat/sat_aig_cuts.h b/src/sat/sat_aig_cuts.h
index 9ae00f7d0..9e60ce9ec 100644
--- a/src/sat/sat_aig_cuts.h
+++ b/src/sat/sat_aig_cuts.h
@@ -69,11 +69,11 @@ namespace sat {
 
         // encodes one of var, and, !and, xor, !xor, ite, !ite.
         class node {
-            bool     m_sign;
-            bool_op  m_op;
-            uint64_t m_lut;
-            unsigned m_size;
-            unsigned m_offset;
+            bool     m_sign{ false };
+            bool_op  m_op{ no_op };
+            uint64_t m_lut{ 0 };
+            unsigned m_size{ 0 };
+            unsigned m_offset{ 0 };
         public:
             node(): 
                 m_sign(false), m_op(no_op), m_size(UINT_MAX), m_offset(UINT_MAX) {}
diff --git a/src/sat/sat_binspr.h b/src/sat/sat_binspr.h
index 8dab8305e..5b4f9a0d0 100644
--- a/src/sat/sat_binspr.h
+++ b/src/sat/sat_binspr.h
@@ -38,13 +38,13 @@ namespace sat {
 
         solver&                              m_solver;
         scoped_ptr<solver>                   s;
-        unsigned                             m_bin_clauses;
-        unsigned                             m_stopped_at;
+        unsigned                             m_bin_clauses{ 0 };
+        unsigned                             m_stopped_at{ 0 };
         vector<clause_vector>                m_use_list;
-        unsigned                             m_limit1, m_limit2;
+        unsigned                             m_limit1{ 0 }, m_limit2{ 0 };
         bool_vector                        m_mark, m_mark2;
         literal_vector                       m_must_candidates, m_may_candidates;
-        unsigned                             m_state;
+        unsigned                             m_state{ 0 };
 
         void init_g() { m_state = 0x7; }
         void g_add_binary(literal l1, literal l2, bool flip2);
diff --git a/src/sat/sat_drat.cpp b/src/sat/sat_drat.cpp
index b1de72147..b924f378a 100644
--- a/src/sat/sat_drat.cpp
+++ b/src/sat/sat_drat.cpp
@@ -6,10 +6,10 @@ Module Name:
     sat_drat.cpp
 
 Abstract:
-   
+
     Produce DRAT proofs.
 
-    Check them using a very simple forward checker 
+    Check them using a very simple forward checker
     that interacts with external plugins.
 
 Author:
@@ -24,19 +24,18 @@ Notes:
 
 
 namespace sat {
-    drat::drat(solver& s):
+    drat::drat(solver& s) :
         s(s),
         m_out(nullptr),
         m_bout(nullptr),
         m_inconsistent(false),
-        m_num_add(0), 
-        m_num_del(0),
         m_check_unsat(false),
         m_check_sat(false),
         m_check(false),
         m_activity(false)
     {
-        if (s.get_config().m_drat && s.get_config().m_drat_file != symbol()) {
+        if (s.get_config().m_drat && s.get_config().m_drat_file.is_non_empty_string()) {
+            std::cout << "DRAT " << s.get_config().m_drat_file << "\n";
             auto mode = s.get_config().m_drat_binary ? (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc) : std::ios_base::out;
             m_out = alloc(std::ofstream, s.get_config().m_drat_file.str(), mode);
             if (s.get_config().m_drat_binary) {
@@ -61,7 +60,7 @@ namespace sat {
         m_bout = nullptr;
     }
 
-    void drat::updt_config() {
+    void drat::updt_config() {            
         m_check_unsat = s.get_config().m_drat_check_unsat;
         m_check_sat = s.get_config().m_drat_check_sat;
         m_check = m_check_unsat || m_check_sat;
@@ -75,31 +74,41 @@ namespace sat {
             out << "d";
         else if (st.is_asserted())
             out << "a";
-        
+        else if (st.is_input())
+            out << "i";
+
         if (!st.is_sat())
             out << " " << m_theory[st.get_th()];
-        return out;        
+        return out;
     }
 
     void drat::dump(unsigned n, literal const* c, status st) {
         if (st.is_asserted() && !s.m_ext)
             return;
-        if (m_activity && ((m_num_add % 1000) == 0)) 
+        if (m_activity && ((m_stats.m_num_add % 1000) == 0))
             dump_activity();
-        
+
         char buffer[10000];
         char digits[20];     // enough for storing unsigned
         char* lastd = digits + sizeof(digits);
-        
+
         unsigned len = 0;
         if (st.is_asserted()) {
             buffer[len++] = 'a';
-            buffer[len++] = ' ';           
+            buffer[len++] = ' ';
         }
         else if (st.is_deleted()) {
             buffer[len++] = 'd';
             buffer[len++] = ' ';
         }
+        else if (st.is_input()) {
+            buffer[len++] = 'i';
+            buffer[len++] = ' ';
+        }
+        else if (st.is_redundant() && !st.is_sat()) {
+            buffer[len++] = 'r';
+            buffer[len++] = ' ';
+        }
 
         if (!st.is_sat()) {
             for (char ch : m_theory[st.get_th()])
@@ -108,28 +117,28 @@ namespace sat {
         }
         for (unsigned i = 0; i < n; ++i) {
             literal lit = c[i];
-            unsigned v = lit.var();            
+            unsigned v = lit.var();
             if (lit.sign()) buffer[len++] = '-';
             char* d = lastd;
             SASSERT(v > 0);
-            while (v > 0) {                
+            while (v > 0) {
                 d--;
                 *d = (v % 10) + '0';
                 v /= 10;
                 SASSERT(d > digits);
             }
-	    SASSERT(len + lastd < sizeof(buffer) + d);
-	    memcpy(buffer + len, d, lastd - d);
-	    len += static_cast<unsigned>(lastd - d);            
-	    buffer[len++] = ' ';
-	    if (len + 50 > sizeof(buffer)) {
-	        m_out->write(buffer, len);
-	        len = 0;
+            SASSERT(len + lastd < sizeof(buffer) + d);
+            memcpy(buffer + len, d, lastd - d);
+            len += static_cast<unsigned>(lastd - d);
+            buffer[len++] = ' ';
+            if (len + 50 > sizeof(buffer)) {
+                m_out->write(buffer, len);
+                len = 0;
             }
-        }        
-	buffer[len++] = '0';
-	buffer[len++] = '\n';
-	m_out->write(buffer, len);               
+        }
+        buffer[len++] = '0';
+        buffer[len++] = '\n';
+        m_out->write(buffer, len);
 
     }
 
@@ -144,9 +153,9 @@ namespace sat {
     void drat::bdump(unsigned n, literal const* c, status st) {
         unsigned char ch = 0;
         if (st.is_redundant())
-            ch = 'a'; 
+            ch = 'a';
         else if (st.is_deleted())
-            ch = 'd'; 
+            ch = 'd';
         else return;
         char buffer[10000];
         int len = 0;
@@ -164,8 +173,7 @@ namespace sat {
                     m_bout->write(buffer, len);
                     len = 0;
                 }
-            }
-            while (v);
+            } while (v);
         }
         buffer[len++] = 0;
         m_bout->write(buffer, len);
@@ -188,7 +196,7 @@ namespace sat {
             if (c[i] != last) {
                 out << c[i] << " ";
                 last = c[i];
-            }            
+            }
         }
         out << "\n";
     }
@@ -198,7 +206,7 @@ namespace sat {
 
         declare(l);
         IF_VERBOSE(20, trace(verbose_stream(), 1, &l, st););
-        if (st.is_redundant()) {
+        if (st.is_redundant() && st.is_sat()) {
             verify(1, &l);
         }
         if (st.is_deleted()) {
@@ -213,17 +221,17 @@ namespace sat {
 
     void drat::append(literal l1, literal l2, status st) {
         TRACE("sat_drat", pp(tout, st) << " " << l1 << " " << l2 << "\n";);
-        declare(l1); 
+        declare(l1);
         declare(l2);
         literal lits[2] = { l1, l2 };
-        
+
         IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st););
         if (st.is_deleted()) {
             // noop
             // don't record binary as deleted.
         }
         else {
-            if (st.is_redundant()) {
+            if (st.is_redundant() && st.is_sat()) {
                 verify(2, lits);
             }
             clause* c = m_alloc.mk_clause(2, lits, st.is_redundant());
@@ -252,18 +260,18 @@ namespace sat {
             (*m_out) << "b " << v << " " << n << " 0\n";
     }
 
-    void drat::def_begin(unsigned n, symbol const& name) {
-        if (m_out) 
-            (*m_out) << "n " << n << " " << name;
+    void drat::def_begin(unsigned n, std::string const& name) {
+        if (m_out)
+            (*m_out) << "e " << n << " " << name;
     }
 
     void drat::def_add_arg(unsigned arg) {
-        if (m_out) 
+        if (m_out)
             (*m_out) << " " << arg;
     }
 
     void drat::def_end() {
-        if (m_out) 
+        if (m_out)
             (*m_out) << " 0\n";
     }
 
@@ -300,12 +308,12 @@ namespace sat {
         unsigned n = c.size();
         IF_VERBOSE(20, trace(verbose_stream(), n, c.begin(), st););
 
-        if (st.is_redundant()) {
+        if (st.is_redundant() && st.is_sat()) {
             verify(c);
         }
-           
+
         m_status.push_back(st);
-        m_proof.push_back(&c); 
+        m_proof.push_back(&c);
         if (st.is_deleted()) {
             if (n > 0) del_watch(c, c[0]);
             if (n > 1) del_watch(c, c[1]);
@@ -327,11 +335,11 @@ namespace sat {
             }
         }
         switch (num_watch) {
-        case 0: 
-            m_inconsistent = true; 
+        case 0:
+            m_inconsistent = true;
             break;
-        case 1: 
-            assign_propagate(l1); 
+        case 1:
+            assign_propagate(l1);
             break;
         default: {
             SASSERT(num_watch == 2);
@@ -345,7 +353,7 @@ namespace sat {
     }
 
     void drat::del_watch(clause& c, literal l) {
-        watch& w = m_watches[(~l).index()];      
+        watch& w = m_watches[(~l).index()];
         for (unsigned i = 0; i < w.size(); ++i) {
             if (m_watched_clauses[w[i]].m_clause == &c) {
                 w[i] = w.back();
@@ -368,7 +376,7 @@ namespace sat {
     bool drat::is_drup(unsigned n, literal const* c) {
         if (m_inconsistent || n == 0) return true;
         unsigned num_units = m_units.size();
-        for (unsigned i = 0; !m_inconsistent && i < n; ++i) {            
+        for (unsigned i = 0; !m_inconsistent && i < n; ++i) {
             assign_propagate(~c[i]);
         }
         if (!m_inconsistent) {
@@ -417,7 +425,7 @@ namespace sat {
             }
             svector<sat::solver::bin_clause> bin;
             s.collect_bin_clauses(bin, true);
-            for (auto & b : bin) {
+            for (auto& b : bin) {
                 bool found = false;
                 if (m_assignment[b.first.var()] != (b.first.sign() ? l_true : l_false)) found = true;
                 if (m_assignment[b.second.var()] != (b.second.sign() ? l_true : l_false)) found = true;
@@ -435,17 +443,16 @@ namespace sat {
         }
         m_units.shrink(num_units);
         bool ok = m_inconsistent;
-        // IF_VERBOSE(9, verbose_stream() << "is-drup " << m_inconsistent << "\n");
         m_inconsistent = false;
-
         return ok;
     }
 
     bool drat::is_drat(unsigned n, literal const* c) {
-        if (m_inconsistent || n == 0) return true;
-        for (unsigned i = 0; i < n; ++i) {
-            if (is_drat(n, c, i)) return true;
-        }
+        if (m_inconsistent || n == 0) 
+            return true;
+        for (unsigned i = 0; i < n; ++i) 
+            if (is_drat(n, c, i)) 
+                return true;
         return false;
     }
 
@@ -482,7 +489,7 @@ namespace sat {
                 if (st.is_sat() && j != c.size()) {
                     lits.append(j, c.begin());
                     lits.append(c.size() - j - 1, c.begin() + j + 1);
-                    if (!is_drup(lits.size(), lits.c_ptr())) 
+                    if (!is_drup(lits.size(), lits.c_ptr()))
                         return false;
                     lits.resize(n);
                 }
@@ -496,26 +503,33 @@ namespace sat {
         if (!m_check_unsat) {
             return;
         }
-        for (unsigned i = 0; i < n; ++i) { 
+        for (unsigned i = 0; i < n; ++i) {
             declare(c[i]);
-        } 
-        if (!is_drup(n, c) && !is_drat(n, c)) {
-            literal_vector lits(n, c);
-            IF_VERBOSE(0, verbose_stream() << "Verification of " << lits << " failed\n");
-            // s.display(std::cout);
-            std::string line;
-            std::getline(std::cin, line);                
-            SASSERT(false);
-            INVOKE_DEBUGGER();
-            exit(0);
-            UNREACHABLE();
-            //display(std::cout);
-            TRACE("sat_drat", 
-                  tout << literal_vector(n, c) << "\n";
-                  display(tout); 
-                  s.display(tout););
-            UNREACHABLE();
         }
+        if (is_drup(n, c)) {
+            ++m_stats.m_num_drup;
+            return;
+        }
+        if (is_drat(n, c)) {
+            ++m_stats.m_num_drat;
+            return;
+        }
+
+        literal_vector lits(n, c);
+        IF_VERBOSE(0, verbose_stream() << "Verification of " << lits << " failed\n");
+        // s.display(std::cout);
+        std::string line;
+        std::getline(std::cin, line);
+        SASSERT(false);
+        INVOKE_DEBUGGER();
+        exit(0);
+        UNREACHABLE();
+        //display(std::cout);
+        TRACE("sat_drat",
+              tout << literal_vector(n, c) << "\n";
+              display(tout);
+              s.display(tout););
+        UNREACHABLE();
     }
 
     bool drat::contains(literal c, justification const& j) {
@@ -529,7 +543,7 @@ namespace sat {
             return contains(c, j.get_literal());
         case justification::TERNARY:
             return contains(c, j.get_literal1(), j.get_literal2());
-        case justification::CLAUSE: 
+        case justification::CLAUSE:
             return contains(s.get_clause(j));
         default:
             return true;
@@ -546,7 +560,7 @@ namespace sat {
             if (match(n, lits, c)) {
                 if (st.is_deleted()) {
                     num_del++;
-                } 
+                }
                 else {
                     num_add++;
                 }
@@ -578,7 +592,7 @@ namespace sat {
     void drat::display(std::ostream& out) const {
         out << "units: " << m_units << "\n";
         for (unsigned i = 0; i < m_assignment.size(); ++i) {
-            lbool v = value(literal(i, false));            
+            lbool v = value(literal(i, false));
             if (v != l_undef) out << i << ": " << v << "\n";
         }
         for (unsigned i = 0; i < m_proof.size(); ++i) {
@@ -599,12 +613,12 @@ namespace sat {
                 if (num_true == 0 && num_undef == 1) {
                     out << "Unit ";
                 }
-                pp(out, m_status[i]) <<  " " << i << ": " << *c << "\n";
+                pp(out, m_status[i]) << " " << i << ": " << *c << "\n";
             }
         }
         for (unsigned i = 0; i < m_assignment.size(); ++i) {
-            watch const& w1 = m_watches[2*i];
-            watch const& w2 = m_watches[2*i + 1];
+            watch const& w1 = m_watches[2 * i];
+            watch const& w2 = m_watches[2 * i + 1];
             if (!w1.empty()) {
                 out << i << " |-> ";
                 for (unsigned i = 0; i < w1.size(); ++i) out << *(m_watched_clauses[w1[i]].m_clause) << " ";
@@ -626,7 +640,7 @@ namespace sat {
     void drat::assign(literal l) {
         lbool new_value = l.sign() ? l_false : l_true;
         lbool old_value = value(l);
-//        TRACE("sat_drat", tout << "assign " << l << " := " << new_value << " from " << old_value << "\n";);
+        //        TRACE("sat_drat", tout << "assign " << l << " := " << new_value << " from " << old_value << "\n";);
         switch (old_value) {
         case l_false:
             m_inconsistent = true;
@@ -645,7 +659,7 @@ namespace sat {
         assign(l);
         for (unsigned i = num_units; !m_inconsistent && i < m_units.size(); ++i) {
             propagate(m_units[i]);
-        }        
+        }
     }
 
     void drat::propagate(literal l) {
@@ -676,10 +690,10 @@ namespace sat {
                         wc.m_l2 = lit;
                         m_watches[(~lit).index()].push_back(idx);
                         done = true;
-                    } 
+                    }
                 }
                 if (done) {
-                    continue;                
+                    continue;
                 }
                 else if (value(wc.m_l1) == l_false) {
                     m_inconsistent = true;
@@ -693,19 +707,19 @@ namespace sat {
             }
         }
     end_process_watch:
-        for (; it != end; ++it, ++it2)          
-            *it2 = *it;                         
-        clauses.set_end(it2);                     
+        for (; it != end; ++it, ++it2)
+            *it2 = *it;
+        clauses.set_end(it2);
     }
 
     status drat::get_status(bool learned) const {
         if (learned || s.m_searching)
             return status::redundant();
-        return status::asserted(); 
+        return status::asserted();
     }
 
     void drat::add() {
-        ++m_num_add;
+        ++m_stats.m_num_add;
         if (m_out) (*m_out) << "0\n";
         if (m_bout) bdump(0, nullptr, status::redundant());
         if (m_check_unsat) {
@@ -713,7 +727,7 @@ namespace sat {
         }
     }
     void drat::add(literal l, bool learned) {
-        ++m_num_add;
+        ++m_stats.m_num_add;
         status st = get_status(learned);
         if (m_out) dump(1, &l, st);
         if (m_bout) bdump(1, &l, st);
@@ -721,19 +735,19 @@ namespace sat {
     }
     void drat::add(literal l1, literal l2, status st) {
         if (st.is_deleted())
-            ++m_num_del;
+            ++m_stats.m_num_del;
         else
-            ++m_num_add;
-        literal ls[2] = {l1, l2};
+            ++m_stats.m_num_add;
+        literal ls[2] = { l1, l2 };
         if (m_out) dump(2, ls, st);
         if (m_bout) bdump(2, ls, st);
         if (m_check) append(l1, l2, st);
     }
     void drat::add(clause& c, status st) {
         if (st.is_deleted())
-            ++m_num_del;
+            ++m_stats.m_num_del;
         else
-            ++m_num_add;
+            ++m_stats.m_num_add;
         if (m_out) dump(c.size(), c.begin(), st);
         if (m_bout) bdump(c.size(), c.begin(), st);
         if (m_check) {
@@ -743,9 +757,9 @@ namespace sat {
     }
     void drat::add(literal_vector const& lits, status st) {
         if (st.is_deleted())
-            ++m_num_del;
-        else 
-            ++m_num_add;
+            ++m_stats.m_num_del;
+        else
+            ++m_stats.m_num_add;
         if (m_check) {
             switch (lits.size()) {
             case 0: add(); break;
@@ -756,12 +770,12 @@ namespace sat {
                 break;
             }
             }
-        }              
-        if (m_out) 
+        }
+        if (m_out)
             dump(lits.size(), lits.c_ptr(), st);
     }
     void drat::add(literal_vector const& c) {
-        ++m_num_add;
+        ++m_stats.m_num_add;
         if (m_out) dump(c.size(), c.begin(), status::redundant());
         if (m_bout) bdump(c.size(), c.begin(), status::redundant());
         if (m_check) {
@@ -780,15 +794,15 @@ namespace sat {
     }
 
     void drat::del(literal l) {
-        ++m_num_del;
+        ++m_stats.m_num_del;
         if (m_out) dump(1, &l, status::deleted());
         if (m_bout) bdump(1, &l, status::deleted());
         if (m_check_unsat) append(l, status::deleted());
     }
 
     void drat::del(literal l1, literal l2) {
-        ++m_num_del;
-        literal ls[2] = {l1, l2};
+        ++m_stats.m_num_del;
+        literal ls[2] = { l1, l2 };
         if (m_out) dump(2, ls, status::deleted());
         if (m_bout) bdump(2, ls, status::deleted());
         if (m_check) append(l1, l2, status::deleted());
@@ -806,26 +820,47 @@ namespace sat {
             m_seen[lit.index()] = false;
         }
 #endif
-        ++m_num_del;
+        ++m_stats.m_num_del;
         if (m_out) dump(c.size(), c.begin(), status::deleted());
         if (m_bout) bdump(c.size(), c.begin(), status::deleted());
         if (m_check) {
-            clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), c.is_learned()); 
+            clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), c.is_learned());
             append(*c1, status::deleted());
         }
     }
 
     void drat::del(literal_vector const& c) {
-        ++m_num_del;
+        ++m_stats.m_num_del;
         if (m_out) dump(c.size(), c.begin(), status::deleted());
         if (m_bout) bdump(c.size(), c.begin(), status::deleted());
         if (m_check) {
-            clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), true); 
+            clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), true);
             append(*c1, status::deleted());
         }
     }
-    
-    void drat::check_model(model const& m) {        
+
+    void drat::check_model(model const& m) {
+    }
+
+    std::ostream& operator<<(std::ostream& out, status const& st) {
+        if (st.is_deleted())
+            out << "d";
+        else if (st.is_input())
+            out << "i";
+        else if (st.is_asserted())
+            out << "a";
+        else if (st.is_redundant() && !st.is_sat())
+            out << "r";
+        if (!st.is_sat())
+            out << " th" << st.m_orig;
+        return out;
+    }
+
+    void drat::collect_statistics(statistics& st) const {
+        st.update("num-drup", m_stats.m_num_drup);
+        st.update("num-drat", m_stats.m_num_drat);
+        st.update("num-add", m_stats.m_num_add);
+        st.update("num-del", m_stats.m_num_del);
     }
 
 }
diff --git a/src/sat/sat_drat.h b/src/sat/sat_drat.h
index f41d5b103..57ffab472 100644
--- a/src/sat/sat_drat.h
+++ b/src/sat/sat_drat.h
@@ -19,18 +19,24 @@ Notes:
 
     For SMT extensions are as follows:
 
-    Assertion (trusted modulo internalizer):
+    Input assertion:
+      i <literal>* 0
+
+    Assertion (true modulo a theory):
       a [<theory-id>] <literal>* 0
-    The optional theory id indicates the assertion is irredundant
+    The if no theory id is given, the assertion is a tautology
+    modulo Tseitin converison. Theory ids track whether the
+    tautology is modulo a theory.
+    Assertions are irredundant.
 
     Bridge from ast-node to boolean variable:
       b <bool-var-id> <ast-node-id> 0
 
-    Definition of an ast node:
-      n <ast-node-id> <name> <ast-node-id>* 0
+    Definition of an expression (ast-node):
+      e <ast-node-id> <name> <ast-node-id>* 0
 
-    Theory lemma
-      <theory-id> <literal>* 0
+    Redundant clause (theory lemma if theory id is given)
+      [r [<theory-id>]] <literal>* 0
 
     Available theories are:
       - euf   The theory lemma should be a consequence of congruence closure.
@@ -45,8 +51,16 @@ Notes:
 #include "sat_types.h"
 
 namespace sat {
+    class justification;
+    class clause;
+
     class drat {
-    private:
+        struct stats {
+            unsigned m_num_drup { 0 };
+            unsigned m_num_drat { 0 };
+            unsigned m_num_add { 0 };
+            unsigned m_num_del { 0 };
+        };
         struct watched_clause {
             clause* m_clause;
             literal m_l1, m_l2;
@@ -64,10 +78,10 @@ namespace sat {
         literal_vector          m_units;
         vector<watch>           m_watches;
         svector<lbool>          m_assignment;
-        svector<std::string>    m_theory;
+        vector<std::string>     m_theory;
         bool                    m_inconsistent;
-        unsigned                m_num_add, m_num_del;
         bool                    m_check_unsat, m_check_sat, m_check, m_activity;
+        stats                   m_stats;
 
         void dump_activity();
         void dump(unsigned n, literal const* c, status st);
@@ -96,6 +110,7 @@ namespace sat {
         bool match(unsigned n, literal const* lits, clause const& c) const;
 
     public:
+
         drat(solver& s);
         ~drat();  
 
@@ -114,7 +129,7 @@ namespace sat {
         void bool_def(bool_var v, unsigned n);
 
         // declare AST node n with 'name' and arguments arg
-        void def_begin(unsigned n, symbol const& name);
+        void def_begin(unsigned n, std::string const& name);
         void def_add_arg(unsigned arg);
         void def_end();
 
@@ -139,7 +154,11 @@ namespace sat {
         bool contains(literal c, justification const& j);
 
         void check_model(model const& m);
+
+        void collect_statistics(statistics& st) const;
     };
 
+    std::ostream& operator<<(std::ostream& out, status const& st);
+
 };
 
diff --git a/src/sat/sat_elim_eqs.cpp b/src/sat/sat_elim_eqs.cpp
index baca88229..94aeea996 100644
--- a/src/sat/sat_elim_eqs.cpp
+++ b/src/sat/sat_elim_eqs.cpp
@@ -236,7 +236,6 @@ namespace sat {
             if (m_solver.is_assumption(v) || (m_solver.is_external(v) && (m_solver.is_incremental() || !root_ok))) {
                 // cannot really eliminate v, since we have to notify extension of future assignments
                 if (m_solver.m_config.m_drat && m_solver.m_config.m_drat_file.is_null()) {
-                    std::cout << "DRAT\n";
                     m_solver.m_drat.add(~l, r, sat::status::redundant());
                     m_solver.m_drat.add(l, ~r, sat::status::redundant());
                 }
diff --git a/src/sat/sat_extension.h b/src/sat/sat_extension.h
index dac8ec8d1..e248ddbef 100644
--- a/src/sat/sat_extension.h
+++ b/src/sat/sat_extension.h
@@ -61,9 +61,10 @@ namespace sat {
         virtual void set_lookahead(lookahead* s) = 0;
         virtual void init_search() {}
         virtual bool propagate(literal l, ext_constraint_idx idx) = 0;
+        virtual bool unit_propagate() = 0;
         virtual bool is_external(bool_var v) = 0;
         virtual double get_reward(literal l, ext_constraint_idx idx, literal_occs_fun& occs) const = 0;
-        virtual void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) = 0;
+        virtual void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) = 0;
         virtual bool is_extended_binary(ext_justification_idx idx, literal_vector & r) = 0;
         virtual void asserted(literal l) = 0;
         virtual check_result check() = 0;
diff --git a/src/sat/sat_simplifier.cpp b/src/sat/sat_simplifier.cpp
index 09dff101c..f3c09ffb7 100644
--- a/src/sat/sat_simplifier.cpp
+++ b/src/sat/sat_simplifier.cpp
@@ -1900,7 +1900,6 @@ namespace sat {
     }
 
     bool simplifier::try_eliminate(bool_var v) {
-        TRACE("sat_simplifier", tout << "processing: " << v << "\n";);
         if (value(v) != l_undef)
             return false;
 
@@ -1963,7 +1962,7 @@ namespace sat {
                 }
             }
         }
-        TRACE("sat_simplifier", tout << "found var to eliminate, before: " << before_clauses << " after: " << after_clauses << "\n";);
+        TRACE("sat_simplifier", tout << "eliminate " << v << ", before: " << before_clauses << " after: " << after_clauses << "\n";);
         m_elim_counter -= num_pos * num_neg + before_lits;
 
         m_elim_counter -= num_pos * num_neg + before_lits;
diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp
index 3474fb2b7..d331b787a 100644
--- a/src/sat/sat_solver.cpp
+++ b/src/sat/sat_solver.cpp
@@ -86,7 +86,7 @@ namespace sat {
         m_cuber                   = nullptr;
         m_local_search            = nullptr;
         m_mc.set_solver(this);
-        //mk_var(false, false);
+        mk_var(false, false);
     }
 
     solver::~solver() {
@@ -138,7 +138,7 @@ namespace sat {
         m_qhead = 0;
         m_trail.reset();
         m_scopes.reset();
-        //mk_var(false, false);
+        mk_var(false, false);
 
         if (src.inconsistent()) {
             set_conflict();
@@ -353,7 +353,7 @@ namespace sat {
     clause * solver::mk_clause_core(unsigned num_lits, literal * lits, sat::status st) {
         bool redundant = st.is_redundant();
         TRACE("sat", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";);
-        if (!redundant) {
+        if (!redundant || !st.is_sat()) {
             unsigned old_sz = num_lits;
             bool keep = simplify_clause(num_lits, lits);
             TRACE("sat_mk_clause", tout << "mk_clause (after simp), keep: " << keep << "\n" << mk_lits_pp(num_lits, lits) << "\n";);
@@ -1103,6 +1103,11 @@ namespace sat {
                 }
             }
             wlist.set_end(it2);
+            if (m_ext) {
+                m_ext->unit_propagate();
+                if (inconsistent())
+                    return false;
+            }
         }
         SASSERT(m_qhead == m_trail.size());
         SASSERT(!m_inconsistent);
@@ -1205,9 +1210,9 @@ namespace sat {
         }
         try {
             init_search();
-            if (inconsistent()) return l_false;
+            if (check_inconsistent()) return l_false;
             propagate(false);
-            if (inconsistent()) return l_false;
+            if (check_inconsistent()) return l_false;
             init_assumptions(num_lits, lits);
             propagate(false);
             if (check_inconsistent()) return l_false;
@@ -1644,7 +1649,9 @@ namespace sat {
 
     bool solver::check_inconsistent() {
         if (inconsistent()) {
-            if (tracking_assumptions())
+            if (tracking_assumptions() && at_search_lvl())
+                resolve_conflict();
+            else if (m_config.m_drat && at_base_lvl())
                 resolve_conflict();
             return true;
         }
@@ -2576,13 +2583,6 @@ namespace sat {
         if (m_step_size > m_config.m_step_size_min) {
             m_step_size -= m_config.m_step_size_dec;
         }
-        struct reset_cache {
-            solver& s;
-            bool active;
-            reset_cache(solver& s) :s(s), active(true) {}
-            ~reset_cache() { if (active) s.m_cached_antecedent_js = 0; }
-        };
-        reset_cache _reset(*this);
         bool unique_max;
         m_conflict_lvl = get_max_lvl(m_not_l, m_conflict, unique_max);        
         justification js = m_conflict;
@@ -2609,7 +2609,6 @@ namespace sat {
             pop_reinit(m_scope_lvl - m_conflict_lvl + 1);
             m_force_conflict_analysis = true;
             ++m_stats.m_backtracks;
-            _reset.active = false;
             return l_undef;
         }
         m_force_conflict_analysis = false;
@@ -2685,7 +2684,7 @@ namespace sat {
                 break;
             }
             case justification::EXT_JUSTIFICATION: {
-                fill_ext_antecedents(consequent, js);
+                fill_ext_antecedents(consequent, js, false);
                 for (literal l : m_ext_antecedents) 
                     process_antecedent(l, num_marks);
                 break;
@@ -2786,6 +2785,7 @@ namespace sat {
         if (m_par && lemma) {
             m_par->share_clause(*this, *lemma);
         }
+        m_lemma.reset();
         TRACE("sat_conflict_detail", tout << "consistent " << (!m_inconsistent) << " scopes: " << scope_lvl() << " backtrack: " << backtrack_lvl << " backjump: " << backjump_lvl << "\n";);
         decay_activity();
         updt_phase_counters();
@@ -2847,7 +2847,7 @@ namespace sat {
             break;
         }
         case justification::EXT_JUSTIFICATION: {
-            fill_ext_antecedents(consequent, js);
+            fill_ext_antecedents(consequent, js, true);
             for (literal l : m_ext_antecedents) {
                 process_antecedent_for_unsat_core(l);
             }
@@ -2975,7 +2975,7 @@ namespace sat {
         case justification::EXT_JUSTIFICATION:
             if (not_l != null_literal) 
                 not_l.neg();
-            fill_ext_antecedents(not_l, js);
+            fill_ext_antecedents(not_l, js, true);
             for (literal l : m_ext_antecedents) 
                 level = update_max_level(l, level, unique_max);
             return level;
@@ -3031,16 +3031,12 @@ namespace sat {
     /**
        \brief js is an external justification. Collect its antecedents and store at m_ext_antecedents.
     */
-    void solver::fill_ext_antecedents(literal consequent, justification js) {
+    void solver::fill_ext_antecedents(literal consequent, justification js, bool probing) {
         SASSERT(js.is_ext_justification());
         SASSERT(m_ext);
         auto idx = js.get_ext_justification_idx();
-        if (consequent == m_cached_antecedent_consequent && idx == m_cached_antecedent_js)
-            return;
         m_ext_antecedents.reset();
-        m_ext->get_antecedents(consequent, idx, m_ext_antecedents);
-        m_cached_antecedent_consequent = consequent;
-        m_cached_antecedent_js = idx;
+        m_ext->get_antecedents(consequent, idx, m_ext_antecedents, probing);
     }
 
     bool solver::is_two_phase() const {
@@ -3354,7 +3350,7 @@ namespace sat {
             }
             case justification::EXT_JUSTIFICATION: {
                 literal consequent(var, value(var) == l_false);
-                fill_ext_antecedents(consequent, js);
+                fill_ext_antecedents(consequent, js, false);
                 for (literal l : m_ext_antecedents) {
                     if (!process_antecedent_for_minimization(l)) {
                         reset_unmark(old_size);
@@ -3496,7 +3492,7 @@ namespace sat {
                 break;
             }
             case justification::EXT_JUSTIFICATION: {
-                fill_ext_antecedents(~m_lemma[i], js);
+                fill_ext_antecedents(~m_lemma[i], js, true);
                 for (literal l : m_ext_antecedents) {
                     update_lrb_reasoned(l);
                 }
@@ -3657,6 +3653,7 @@ namespace sat {
         s.m_trail_lim = m_trail.size();
         s.m_clauses_to_reinit_lim = m_clauses_to_reinit.size();
         s.m_inconsistent = m_inconsistent;
+        // m_vars_lim.push(num_vars());
         if (m_ext)
             m_ext->push();
     }
@@ -3668,11 +3665,46 @@ namespace sat {
         m_stats.m_units = init_trail_size();
     }
 
+    void solver::pop_vars(unsigned num_scopes) {
+        unsigned old_num_vars = m_vars_lim.pop(num_scopes);
+        if (old_num_vars == num_vars()) 
+            return;
+        IF_VERBOSE(0, verbose_stream() << "new variables created under scope\n";);
+        for (unsigned v = old_num_vars; v < num_vars(); ++v) {
+            
+        }
+    }
+
+    void solver::shrink_vars(unsigned v) {
+        for (bool_var i = v; i < m_justification.size(); ++i) {
+            m_case_split_queue.del_var_eh(i);
+            m_probing.reset_cache(literal(i, true));
+            m_probing.reset_cache(literal(i, false));
+        }
+        m_watches.shrink(2*v);
+        m_assignment.shrink(2*v);
+        m_justification.shrink(v);
+        m_decision.shrink(v);
+        m_eliminated.shrink(v);
+        m_external.shrink(v);
+        m_touched.shrink(v);
+        m_activity.shrink(v);
+        m_mark.shrink(v);
+        m_lit_mark.shrink(2*v);
+        m_phase.shrink(v);
+        m_best_phase.shrink(v);
+        m_prev_phase.shrink(v);
+        m_assigned_since_gc.shrink(v);
+        m_simplifier.reset_todos();
+    }
+
     void solver::pop(unsigned num_scopes) {
         if (num_scopes == 0)
             return;
-        if (m_ext)
+        if (m_ext) {
+            // pop_vars(num_scopes);
             m_ext->pop(num_scopes);
+        }
         SASSERT(num_scopes <= scope_lvl());
         unsigned new_lvl = scope_lvl() - num_scopes;
         scope & s        = m_scopes[new_lvl];
@@ -3709,12 +3741,27 @@ namespace sat {
         m_qhead = m_trail.size();
         if (!m_replay_assign.empty()) IF_VERBOSE(20, verbose_stream() << "replay assign: " << m_replay_assign.size() << "\n");
         for (unsigned i = m_replay_assign.size(); i-- > 0; ) {
-            m_trail.push_back(m_replay_assign[i]);            
+            literal lit = m_replay_assign[i];
+            if (m_ext && m_external[lit.var()])
+                m_ext->asserted(lit);
+            m_trail.push_back(lit);            
         }
         
         m_replay_assign.reset();
     }
 
+    void solver::get_reinit_literals(unsigned n, literal_vector& r) {
+        unsigned new_lvl = scope_lvl() - n;
+        unsigned old_sz = m_scopes[new_lvl].m_clauses_to_reinit_lim;
+        for (unsigned i = m_clauses_to_reinit.size(); i-- > old_sz; ) {
+            clause_wrapper cw = m_clauses_to_reinit[i];
+            for (unsigned j = cw.size(); j-- > 0; )
+                r.push_back(cw[j]);
+        }
+        for (literal lit : m_lemma)
+            r.push_back(lit);
+    }
+
     void solver::reinit_clauses(unsigned old_sz) {
         unsigned sz = m_clauses_to_reinit.size();
         SASSERT(old_sz <= sz);
@@ -3834,26 +3881,7 @@ namespace sat {
         
         // v is an index of a variable that does not occur in solver state.
         if (v < m_justification.size()) {
-            for (bool_var i = v; i < m_justification.size(); ++i) {
-                m_case_split_queue.del_var_eh(i);
-                m_probing.reset_cache(literal(i, true));
-                m_probing.reset_cache(literal(i, false));
-            }
-            m_watches.shrink(2*v);
-            m_assignment.shrink(2*v);
-            m_justification.shrink(v);
-            m_decision.shrink(v);
-            m_eliminated.shrink(v);
-            m_external.shrink(v);
-            m_touched.shrink(v);
-            m_activity.shrink(v);
-            m_mark.shrink(v);
-            m_lit_mark.shrink(2*v);
-            m_phase.shrink(v);
-            m_best_phase.shrink(v);
-            m_prev_phase.shrink(v);
-            m_assigned_since_gc.shrink(v);
-            m_simplifier.reset_todos();
+            shrink_vars(v);
         }
     }
 
@@ -4766,7 +4794,7 @@ namespace sat {
             break;
         }
         case justification::EXT_JUSTIFICATION: {
-            fill_ext_antecedents(lit, js);
+            fill_ext_antecedents(lit, js, true);
             for (literal l : m_ext_antecedents) {
                 if (check_domain(lit, l) && all_found) {
                     s |= m_antecedents.find(l.var());
diff --git a/src/sat/sat_solver.h b/src/sat/sat_solver.h
index 2fd9cbbc1..79eddffac 100644
--- a/src/sat/sat_solver.h
+++ b/src/sat/sat_solver.h
@@ -45,6 +45,7 @@ Revision History:
 #include "util/trace.h"
 #include "util/rlimit.h"
 #include "util/scoped_ptr_vector.h"
+#include "util/scoped_limit_trail.h"
 
 namespace sat {
 
@@ -172,6 +173,7 @@ namespace sat {
             bool     m_inconsistent;
         };
         svector<scope>          m_scopes;
+        scoped_limit_trail      m_vars_lim;
         stopwatch               m_stopwatch;
         params_ref              m_params;
         scoped_ptr<solver>      m_clone; // for debugging purposes
@@ -399,6 +401,7 @@ namespace sat {
         bool canceled() { return !m_rlimit.inc(); }
         config const& get_config() const { return m_config; }
         drat& get_drat() { return m_drat; }
+        drat* get_drat_ptr() override { return &m_drat;  }
         void set_incremental(bool b) { m_config.m_incremental = b; }
         bool is_incremental() const { return m_config.m_incremental; }
         extension* get_extension() const override { return m_ext.get(); }
@@ -565,8 +568,6 @@ namespace sat {
         unsigned       m_conflict_lvl;
         literal_vector m_lemma;
         literal_vector m_ext_antecedents;
-        literal        m_cached_antecedent_consequent;
-        ext_justification_idx m_cached_antecedent_js { 0 };
         bool use_backjumping(unsigned num_scopes);
         bool resolve_conflict();
         lbool resolve_conflict_core();
@@ -582,7 +583,7 @@ namespace sat {
         void resolve_conflict_for_unsat_core();
         void process_antecedent_for_unsat_core(literal antecedent);
         void process_consequent_for_unsat_core(literal consequent, justification const& js);
-        void fill_ext_antecedents(literal consequent, justification js);
+        void fill_ext_antecedents(literal consequent, justification js, bool probing);
         unsigned skip_literals_above_conflict_level();
         void updt_phase_of_vars();
         void updt_phase_counters();
@@ -621,6 +622,8 @@ namespace sat {
         void push();
         void pop(unsigned num_scopes);
         void pop_reinit(unsigned num_scopes);
+        void shrink_vars(unsigned v);
+        void pop_vars(unsigned num_scopes);
 
         void unassign_vars(unsigned old_sz, unsigned new_lvl);
         void reinit_clauses(unsigned old_sz);
@@ -635,6 +638,14 @@ namespace sat {
         bool_var max_var(clause_vector& clauses, bool_var v);
         bool_var max_var(bool learned, bool_var v);
 
+        // -----------------------
+        //
+        // External
+        //
+        // -----------------------
+    public:
+        void set_should_simplify() { m_next_simplify = m_conflicts_since_init; }
+        void get_reinit_literals(unsigned num_scopes, literal_vector& r);
     public:
         void user_push() override;
         void user_pop(unsigned num_scopes) override;
@@ -798,4 +809,3 @@ namespace sat {
 
     std::ostream & operator<<(std::ostream & out, mk_stat const & stat);
 };
-
diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp
index bc8170183..d5de7f619 100644
--- a/src/sat/sat_solver/inc_sat_solver.cpp
+++ b/src/sat/sat_solver/inc_sat_solver.cpp
@@ -126,7 +126,7 @@ public:
         auto* ext = dynamic_cast<euf::solver*>(m_solver.get_extension());
         if (ext) {
             auto& si = result->m_goal2sat.si(dst_m, m_params, result->m_solver, result->m_map, result->m_dep2asm, is_incremental());
-            euf::solver::scoped_set_translate st(*ext, dst_m, result->m_map, si);  
+            euf::solver::scoped_set_translate st(*ext, dst_m, si);  
             result->m_solver.copy(m_solver);
         }        
         else {
diff --git a/src/sat/sat_solver_core.h b/src/sat/sat_solver_core.h
index 372b1308e..f6beb72ec 100644
--- a/src/sat/sat_solver_core.h
+++ b/src/sat/sat_solver_core.h
@@ -25,6 +25,7 @@ namespace sat {
 
     class cut_simplifier;
     class extension;
+    class drat;
 
     class solver_core {
     protected:
@@ -95,6 +96,8 @@ namespace sat {
 
         virtual cut_simplifier* get_cut_simplifier() { return nullptr; }
 
+        virtual drat* get_drat_ptr() { return nullptr;  }
+
 
         // The following methods are used when converting the state from the SAT solver back
         // to a set of assertions. 
diff --git a/src/sat/sat_types.h b/src/sat/sat_types.h
index c16989292..67863c8f3 100644
--- a/src/sat/sat_types.h
+++ b/src/sat/sat_types.h
@@ -254,19 +254,22 @@ namespace sat {
 
     class status {
     public:
-        enum class st { asserted, redundant, deleted };
+        enum class st { input, asserted, redundant, deleted };
         st m_st;
         int m_orig;
     public:
         status(enum st s, int o) : m_st(s), m_orig(o) {};
         status(status const& s) : m_st(s.m_st), m_orig(s.m_orig) {}
         status(status&& s) noexcept { m_st = st::asserted; m_orig = -1; std::swap(m_st, s.m_st); std::swap(m_orig, s.m_orig); }
+        status& operator=(status const& other) { m_st = other.m_st; m_orig = other.m_orig; return *this; }
         static status redundant() { return status(status::st::redundant, -1); }
         static status asserted() { return status(status::st::asserted, -1); }
         static status deleted() { return status(status::st::deleted, -1); }
+        static status input() { return status(status::st::input, -1); }
 
         static status th(bool redundant, int id) { return status(redundant ? st::redundant : st::asserted, id); }
 
+        bool is_input() const { return st::input == m_st; }
         bool is_redundant() const { return st::redundant == m_st; }
         bool is_asserted() const { return st::asserted == m_st; }
         bool is_deleted() const { return st::deleted == m_st; }
@@ -275,6 +278,5 @@ namespace sat {
         int  get_th() const { return m_orig;  }
     };
 
-
 };
 
diff --git a/src/sat/smt/CMakeLists.txt b/src/sat/smt/CMakeLists.txt
index 145c1a3fc..6555749d9 100644
--- a/src/sat/smt/CMakeLists.txt
+++ b/src/sat/smt/CMakeLists.txt
@@ -3,10 +3,12 @@ z3_add_component(sat_smt
     atom2bool_var.cpp
     ba_internalize.cpp
     ba_solver.cpp
+    bv_ackerman.cpp
     bv_internalize.cpp
     bv_solver.cpp
     euf_ackerman.cpp
     euf_internalize.cpp
+    euf_invariant.cpp
     euf_model.cpp
     euf_proof.cpp
     euf_solver.cpp
diff --git a/src/sat/smt/ba_internalize.cpp b/src/sat/smt/ba_internalize.cpp
index e28b1dd92..2aed982a1 100644
--- a/src/sat/smt/ba_internalize.cpp
+++ b/src/sat/smt/ba_internalize.cpp
@@ -21,6 +21,10 @@ Author:
 
 namespace sat {
 
+    void ba_solver::internalize(expr* e, bool redundant) {
+        internalize(e, false, false, redundant);
+    }
+
     literal ba_solver::internalize(expr* e, bool sign, bool root, bool redundant) {
         flet<bool> _redundant(m_is_redundant, redundant);
         if (m_pb.is_pb(e)) 
diff --git a/src/sat/smt/ba_solver.cpp b/src/sat/smt/ba_solver.cpp
index ec2e2554c..482834b76 100644
--- a/src/sat/smt/ba_solver.cpp
+++ b/src/sat/smt/ba_solver.cpp
@@ -2107,8 +2107,8 @@ namespace sat {
     // ----------------------------
     // constraint generic methods
 
-    void ba_solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) {
-        get_antecedents(l, index2constraint(idx), r);
+    void ba_solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) {
+        get_antecedents(l, index2constraint(idx), r, probing);
     }
 
     bool ba_solver::is_watched(literal lit, constraint const& c) const {
@@ -2128,14 +2128,14 @@ namespace sat {
         get_wlist(~lit).push_back(w);
     }
 
-    void ba_solver::get_antecedents(literal l, constraint const& c, literal_vector& r) {
+    void ba_solver::get_antecedents(literal l, constraint const& c, literal_vector& r, bool probing) {
         switch (c.tag()) {
         case card_t: get_antecedents(l, c.to_card(), r); break;
         case pb_t: get_antecedents(l, c.to_pb(), r); break;
         case xr_t: get_antecedents(l, c.to_xr(), r); break;
         default: UNREACHABLE(); break;            
         }
-        if (get_config().m_drat && m_solver) {
+        if (get_config().m_drat && m_solver && !probing) {
             literal_vector lits;
             for (literal lit : r) 
                 lits.push_back(~lit);
diff --git a/src/sat/smt/ba_solver.h b/src/sat/smt/ba_solver.h
index 38d682be3..0ae2210b5 100644
--- a/src/sat/smt/ba_solver.h
+++ b/src/sat/smt/ba_solver.h
@@ -361,7 +361,7 @@ namespace sat {
         void set_conflict(constraint& c, literal lit);
         void assign(constraint& c, literal lit);
         bool assigned_above(literal above, literal below);
-        void get_antecedents(literal l, constraint const& c, literal_vector & r);
+        void get_antecedents(literal l, constraint const& c, literal_vector & r, bool probing);
         bool validate_conflict(constraint const& c) const;
         bool validate_unit_propagation(constraint const& c, literal alit) const;
         void validate_eliminated();
@@ -576,8 +576,9 @@ namespace sat {
 
         bool is_external(bool_var v) override;
         bool propagate(literal l, ext_constraint_idx idx) override;
+        bool unit_propagate() override { return false; }
         lbool resolve_conflict() override;
-        void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) override;
+        void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) override;
         void asserted(literal l) override;
         check_result check() override;
         void push() override;
@@ -604,6 +605,7 @@ namespace sat {
         bool check_model(model const& m) const override;
 
         literal internalize(expr* e, bool sign, bool root, bool redundant) override;
+        void internalize(expr* e, bool redundant) override;
         bool to_formulas(std::function<expr_ref(sat::literal)>& l2e, expr_ref_vector& fmls) override;
         euf::th_solver* fresh(solver* s, euf::solver& ctx) override;
 
diff --git a/src/sat/smt/bv_ackerman.cpp b/src/sat/smt/bv_ackerman.cpp
new file mode 100644
index 000000000..669ebf0b8
--- /dev/null
+++ b/src/sat/smt/bv_ackerman.cpp
@@ -0,0 +1,153 @@
+/*++
+Copyright (c) 2020 Microsoft Corporation
+
+Module Name:
+
+    bv_ackerman.cpp
+
+Abstract:
+
+    Ackerman reduction plugin for bit-vectors
+
+Author:
+
+    Nikolaj Bjorner (nbjorner) 2020-08-28
+
+--*/
+
+#include "sat/smt/bv_solver.h"
+#include "sat/smt/bv_ackerman.h"
+
+namespace bv {
+
+    ackerman::ackerman(solver& s): s(s) {
+        new_tmp();
+        m_propagate_low_watermark = s.get_config().m_dack_threshold;
+    }
+
+    ackerman::~ackerman() {
+        reset();
+        dealloc(m_tmp_vv);
+    }
+
+    void ackerman::reset() {
+        m_table.reset();
+        m_queue = nullptr;        
+    }
+
+    void ackerman::used_eq_eh(euf::theory_var v1, euf::theory_var v2) {
+        if (v1 == v2)
+            return;
+        if (v1 > v2)
+            std::swap(v1, v2);
+        vv* n = m_tmp_vv;
+        n->v1 = v1;
+        n->v2 = v2;
+        vv* other = m_table.insert_if_not_there(n);        
+        other->m_count++;
+        update_glue(*other);
+        if (other->m_count > m_propagate_high_watermark || other->m_glue == 0)
+            s.s().set_should_simplify();
+        vv::push_to_front(m_queue, other);
+        if (other == n) {
+            new_tmp();        
+            gc();
+        }
+    }
+
+    void ackerman::update_glue(vv& v) {
+        unsigned sz = s.m_bits[v.v1].size();
+        m_diff_levels.reserve(s.s().scope_lvl() + 1, false);
+        unsigned glue = 0;
+        unsigned max_glue = v.m_glue;
+        auto const& bitsa = s.m_bits[v.v1];
+        auto const& bitsb = s.m_bits[v.v2];
+        unsigned i = 0;
+        for (; i < sz && i < max_glue; ++i) {
+            sat::literal a = bitsa[i];
+            sat::literal b = bitsb[i];
+            if (a == b)
+                continue;
+            SASSERT(s.s().value(a) != l_undef);
+            SASSERT(s.s().value(b) == s.s().value(a));
+            unsigned lvl_a = s.s().lvl(a);
+            unsigned lvl_b = s.s().lvl(b);
+            if (!m_diff_levels[lvl_a]) {
+                m_diff_levels[lvl_a] = true;
+                ++glue;
+            }
+            if (!m_diff_levels[lvl_b]) {
+                m_diff_levels[lvl_b] = true;
+                ++glue;
+            }            
+        }
+        for (; i-- > 0; ) {
+            sat::literal a = bitsa[i];
+            sat::literal b = bitsb[i];
+            if (a != b) {
+                m_diff_levels[s.s().lvl(a)] = false;
+                m_diff_levels[s.s().lvl(b)] = false;
+            }
+        }
+        
+        if (glue < max_glue) 
+            v.m_glue = glue <= sz ? 0 : glue;
+    }
+
+    void ackerman::remove(vv* p) {
+        vv::remove_from(m_queue, p);
+        m_table.erase(p);
+        dealloc(p);
+    }
+
+    void ackerman::new_tmp() {
+        m_tmp_vv = alloc(vv);
+        m_tmp_vv->init(m_tmp_vv);
+        m_tmp_vv->m_count = 0;
+        m_tmp_vv->m_glue = UINT_MAX;
+    }
+        
+    void ackerman::gc() {
+        m_num_propagations_since_last_gc++;
+        if (m_num_propagations_since_last_gc <= s.get_config().m_dack_gc) 
+            return;
+        m_num_propagations_since_last_gc = 0;
+        
+        while (m_table.size() > m_gc_threshold) 
+            remove(m_queue->prev());     
+
+        m_gc_threshold *= 110;
+        m_gc_threshold /= 100;
+        m_gc_threshold++;
+    }
+
+    void ackerman::propagate() {
+        SASSERT(s.s().at_base_lvl());
+        auto* n = m_queue;
+        vv* k = nullptr;
+        unsigned num_prop = static_cast<unsigned>(s.s().get_stats().m_conflict * s.get_config().m_dack_factor);
+        num_prop = std::min(num_prop, m_table.size());
+        for (unsigned i = 0; i < num_prop; ++i, n = k) {
+            k = n->next();
+            if (n->m_count < m_propagate_low_watermark && n->m_glue != 0)
+                continue;            
+            add_cc(n->v1, n->v2);
+            remove(n);
+        }
+    }
+
+    void ackerman::add_cc(euf::theory_var v1, euf::theory_var v2) {
+        SASSERT(v1 < v2);
+        if (static_cast<unsigned>(v2) >= s.get_num_vars())
+            return;
+        if (!s.var2enode(v1) || !s.var2enode(v2))
+            return;
+        sort* s1 = s.m.get_sort(s.var2expr(v1));
+        sort* s2 = s.m.get_sort(s.var2expr(v2));
+        if (s1 != s2 || !s.bv.is_bv_sort(s1))
+            return;
+        IF_VERBOSE(0, verbose_stream() << "assert ackerman " << v1 << " " << v2 << "\n");
+        s.assert_ackerman(v1, v2);
+    }
+
+}
diff --git a/src/sat/smt/bv_ackerman.h b/src/sat/smt/bv_ackerman.h
new file mode 100644
index 000000000..0b5b28a8c
--- /dev/null
+++ b/src/sat/smt/bv_ackerman.h
@@ -0,0 +1,77 @@
+/*++
+Copyright (c) 2020 Microsoft Corporation
+
+Module Name:
+
+    euf_ackerman.h
+
+Abstract:
+
+    Ackerman reduction plugin for EUF
+
+Author:
+
+    Nikolaj Bjorner (nbjorner) 2020-08-25
+
+--*/
+#pragma once
+
+#include "util/dlist.h"
+#include "sat/smt/atom2bool_var.h"
+#include "sat/smt/sat_th.h"
+
+namespace bv {
+
+    class solver;
+
+    class ackerman {
+
+        struct vv : dll_base<vv> {
+            euf::theory_var v1, v2;
+            unsigned   m_count{ 0 };
+            unsigned   m_glue{ UINT_MAX };
+            vv():v1(euf::null_theory_var), v2(euf::null_theory_var) {}
+            vv(euf::theory_var v1, euf::theory_var v2):v1(v1), v2(v2) {}
+        };
+
+        struct vv_eq {
+            bool operator()(vv const* a, vv const* b) const {
+                return a->v1 == b->v1 && a->v2 == b->v2;
+            }
+        };
+        
+        struct vv_hash {
+            unsigned operator()(vv const* a) const {
+                return mk_mix(a->v1, a->v2, 0);
+            }
+        };
+
+        typedef hashtable<vv*, vv_hash, vv_eq> table_t;
+
+        solver&       s;
+        table_t       m_table;
+        vv*           m_queue { nullptr };
+        vv*           m_tmp_vv { nullptr };
+        unsigned      m_gc_threshold { 1 };
+        unsigned      m_propagate_high_watermark { 10000 };
+        unsigned      m_propagate_low_watermark { 10 };
+        unsigned      m_num_propagations_since_last_gc { 0 };
+        bool_vector   m_diff_levels;
+
+        void update_glue(vv& v);
+        void reset();
+        void new_tmp();
+        void remove(vv* inf);
+        void gc();
+        void add_cc(euf::theory_var v1, euf::theory_var v2);
+
+    public:
+        ackerman(solver& s);
+        ~ackerman();
+
+        void used_eq_eh(euf::theory_var v1, euf::theory_var v2);
+
+        void propagate();
+    };
+
+};
diff --git a/src/sat/smt/bv_internalize.cpp b/src/sat/smt/bv_internalize.cpp
index 0c9cfac03..9029c3082 100644
--- a/src/sat/smt/bv_internalize.cpp
+++ b/src/sat/smt/bv_internalize.cpp
@@ -22,23 +22,33 @@ Author:
 
 namespace bv {
 
-    class add_var_pos_trail : public trail<euf::solver> {
-        solver::bit_atom * m_atom;
+    solver::bit_atom& solver::atom::to_bit() {
+        SASSERT(is_bit());
+        return dynamic_cast<bit_atom&>(*this);
+    }
+
+    solver::def_atom& solver::atom::to_def() {
+        SASSERT(!is_bit());
+        return dynamic_cast<def_atom&>(*this);
+    }
+
+    class solver::add_var_pos_trail : public trail<euf::solver> {
+        solver::bit_atom* m_atom;
     public:
-        add_var_pos_trail(solver::bit_atom * a):m_atom(a) {}
-        void undo(euf::solver & euf) override {
+        add_var_pos_trail(solver::bit_atom* a) :m_atom(a) {}
+        void undo(euf::solver& euf) override {
             SASSERT(m_atom->m_occs);
             m_atom->m_occs = m_atom->m_occs->m_next;
         }
     };
 
-    class mk_atom_trail : public trail<euf::solver> {
+    class solver::mk_atom_trail : public trail<euf::solver> {
         solver& th;
         sat::bool_var m_var;
     public:
-        mk_atom_trail(sat::bool_var v, solver& th):m_var(v), th(th) {}
-        void undo(euf::solver & euf) override {
-            solver::atom * a = th.get_bv2a(m_var);
+        mk_atom_trail(sat::bool_var v, solver& th) : th(th), m_var(v) {}
+        void undo(euf::solver& euf) override {
+            solver::atom* a = th.get_bv2a(m_var);
             a->~atom();
             th.erase_bv2a(m_var);
         }
@@ -49,162 +59,152 @@ namespace bv {
         m_find.mk_var();
         m_bits.push_back(sat::literal_vector());
         m_wpos.push_back(0);
-        m_zero_one_bits.push_back(zero_one_bits());
+        m_zero_one_bits.push_back(zero_one_bits());       
         ctx.attach_th_var(n, this, r);
+        
+        TRACE("bv", tout << "mk-var: " << r << " " << n->get_owner_id() << " " << mk_pp(n->get_owner(), m) << "\n";);
         return r;
     }
 
-    sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) {
-        if (!visit_rec(m, e, sign, root, redundant))
-            return sat::null_literal;
-        return sat::null_literal;
+    void solver::apply_sort_cnstr(euf::enode * n, sort * s) {
+        get_var(n);
     }
 
-    bool solver::visit(expr* e) {
-        if (!bv.is_bv(e)) {
-            ctx.internalize(e, false, false, m_is_redundant);
+    sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) {
+        SASSERT(m.is_bool(e));
+        if (!visit_rec(m, e, sign, root, redundant))
+            return sat::null_literal;
+        return expr2literal(e);
+    }
+
+    void solver::internalize(expr* e, bool redundant) {
+        visit_rec(m, e, false, false, redundant);
+    }
+
+    bool solver::visit(expr* e) {      
+        if (!is_app(e) || to_app(e)->get_family_id() != get_id()) {
+            ctx.internalize(e, m_is_redundant);
             return true;
         }
-        m_args.reset();
+        m_stack.push_back(sat::eframe(e));
+        return false;
+    }
+
+    bool solver::visited(expr* e) {
+        euf::enode* n = expr2enode(e);
+        return n && n->is_attached_to(get_id());        
+    }
+
+    bool solver::post_visit(expr* e, bool sign, bool root) {
+        euf::enode* n = expr2enode(e);
         app* a = to_app(e);
-        for (expr* arg : *a) {
-            euf::enode* n = get_enode(arg);
-            if (n)
-                m_args.push_back(n);
-            else
-                m_stack.push_back(sat::eframe(arg));
+        
+        SASSERT(!n || !n->is_attached_to(get_id()));
+        if (!n) {
+            m_args.reset();            
+            if (get_config().m_bv_reflect || m.is_considered_uninterpreted(a->get_decl())) {
+                for (expr* arg : *a)
+                    m_args.push_back(expr2enode(arg));
+            }
+            DEBUG_CODE(for (auto* n : m_args) VERIFY(n););
+            n = ctx.mk_enode(e, m_args.size(), m_args.c_ptr());
+            SASSERT(!n->is_attached_to(get_id()));
+            ctx.attach_node(n);
         }
-        if (m_args.size() != a->get_num_args())
-            return false;
-        if (!smt_params().m_bv_reflect)
-            m_args.reset();
-        euf::enode* n = mk_enode(a, m_args);
-        SASSERT(n->interpreted());
-        theory_var v = n->get_th_var(get_id());
+
+        SASSERT(!n->is_attached_to(get_id()));
+        theory_var v = mk_var(n);
+        SASSERT(n->is_attached_to(get_id()));
+
+        std::function<void(unsigned sz, expr* const* xs, expr* const* ys, expr_ref_vector& bits)> bin;
+        std::function<void(unsigned sz, expr* const* xs, expr* const* ys, expr_ref& bit)> ebin;
+        std::function<void(unsigned sz, expr* const* xs, expr_ref_vector& bits)> un;
+        std::function<void(unsigned sz, expr* const* xs, unsigned p, expr_ref_vector& bits)> pun;
+#define internalize_bin(F) bin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref_vector& bits) { m_bb.F(sz, xs, ys, bits); }; internalize_binary(a, bin);
+#define internalize_un(F) un = [&](unsigned sz, expr* const* xs, expr_ref_vector& bits) { m_bb.F(sz, xs, bits);}; internalize_unary(a, un);
+#define internalize_ac(F) bin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref_vector& bits) { m_bb.F(sz, xs, ys, bits); }; internalize_ac_binary(a, bin);
+#define internalize_pun(F) pun = [&](unsigned sz, expr* const* xs, unsigned p, expr_ref_vector& bits) { m_bb.F(sz, xs, p, bits);}; internalize_par_unary(a, pun);
+#define internalize_nfl(F) ebin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref& out) { m_bb.F(sz, xs, ys, out);}; internalize_novfl(a, ebin);
 
         switch (a->get_decl_kind()) {
-        case OP_BV_NUM:         internalize_num(a, v); break;
-        default:                break;
+        case OP_BV_NUM:           internalize_num(a, v); break;
+        case OP_BNOT:             internalize_un(mk_not); break;
+        case OP_BREDAND:          internalize_un(mk_redand); break;
+        case OP_BREDOR:           internalize_un(mk_redor); break;
+        case OP_BSDIV_I:          internalize_bin(mk_sdiv); break;
+        case OP_BUDIV_I:          internalize_bin(mk_udiv); break;
+        case OP_BUREM_I:          internalize_bin(mk_urem); break;
+        case OP_BSREM_I:          internalize_bin(mk_srem); break;
+        case OP_BSMOD_I:          internalize_bin(mk_smod); break;
+        case OP_BSHL:             internalize_bin(mk_shl); break;
+        case OP_BLSHR:            internalize_bin(mk_lshr); break;
+        case OP_BASHR:            internalize_bin(mk_ashr); break;
+        case OP_EXT_ROTATE_LEFT:  internalize_bin(mk_ext_rotate_left); break;
+        case OP_EXT_ROTATE_RIGHT: internalize_bin(mk_ext_rotate_right); break;
+        case OP_BADD:             internalize_ac(mk_adder); break;
+        case OP_BMUL:             internalize_ac(mk_multiplier); break;
+        case OP_BAND:             internalize_ac(mk_and); break;
+        case OP_BOR:              internalize_ac(mk_or); break;
+        case OP_BXOR:             internalize_ac(mk_xor); break;
+        case OP_BNAND:            internalize_bin(mk_nand); break;
+        case OP_BNOR:             internalize_bin(mk_nor); break;
+        case OP_BXNOR:            internalize_bin(mk_xnor); break;
+        case OP_BCOMP:            internalize_bin(mk_comp); break;
+        case OP_SIGN_EXT:         internalize_pun(mk_sign_extend); break;
+        case OP_ZERO_EXT:         internalize_pun(mk_zero_extend); break;
+        case OP_ROTATE_LEFT:      internalize_pun(mk_rotate_left); break;
+        case OP_ROTATE_RIGHT:     internalize_pun(mk_rotate_right); break;
+        case OP_BUMUL_NO_OVFL:    internalize_nfl(mk_umul_no_overflow); break;
+        case OP_BSMUL_NO_OVFL:    internalize_nfl(mk_smul_no_overflow); break;
+        case OP_BSMUL_NO_UDFL:    internalize_nfl(mk_smul_no_underflow); break;
+        case OP_BIT2BOOL:         internalize_bit2bool(a); break;
+        case OP_ULEQ:             internalize_le<false>(a); break;
+        case OP_SLEQ:             internalize_le<true>(a); break;
+        case OP_XOR3:             internalize_xor3(a); break;
+        case OP_CARRY:            internalize_carry(a); break;
+        case OP_BSUB:             internalize_sub(a); break;
+        case OP_CONCAT:           internalize_concat(a); break;
+        case OP_EXTRACT:          internalize_extract(a); break;
+        case OP_MKBV:             internalize_mkbv(a); break;
+        case OP_INT2BV:           internalize_int2bv(a); break;
+        case OP_BV2INT:           internalize_bv2int(a); break;
+        case OP_BSDIV0:           break;
+        case OP_BUDIV0:           break;
+        case OP_BSREM0:           break;
+        case OP_BUREM0:           break;
+        case OP_BSMOD0:           break;
+        default:
+            UNREACHABLE();
+            break;
         }
         return true;
     }
 
-    bool solver::visited(expr* e) {
-        return get_enode(e) != nullptr;
-    }
-
     void solver::mk_bits(theory_var v) {
         TRACE("bv", tout << "v" << v << "\n";);
-        expr* e = get_expr(v);
+        expr* e = var2expr(v);
         unsigned bv_size = get_bv_size(v);
-        literal_vector& bits = m_bits[v];
-        bits.reset();
+        m_bits[v].reset();
         for (unsigned i = 0; i < bv_size; i++) {
-            expr_ref b2b = mk_bit2bool(e, i);
-            bits.push_back(ctx.internalize(b2b, false, false, m_is_redundant));
+            expr_ref b2b(bv.mk_bit2bool(e, i), m);
+            sat::literal lit = ctx.internalize(b2b, false, false, m_is_redundant);
+            m_bits[v].push_back(lit);
         }
     }
 
-    expr_ref solver::mk_bit2bool(expr* e, unsigned idx) {
-        parameter p(idx);
-        expr* args[1] = { e };
-        return expr_ref(m.mk_app(get_id(), OP_BIT2BOOL, 1, &p, 1, args), m);
-    }
-
-#if 0
-        SASSERT(!ctx.b_internalized(n));
-
-        TRACE("bv", tout << "bit2bool: " << mk_pp(n, ctx.get_manager()) << "\n";);
-        expr* first_arg = n->get_arg(0);
-
-        if (!ctx.e_internalized(first_arg)) {
-            // This may happen if bit2bool(x) is in a conflict
-            // clause that is being reinitialized, and x was not reinitialized
-            // yet.
-            // So, we internalize x (i.e., arg)
-            ctx.internalize(first_arg, false);
-            SASSERT(ctx.e_internalized(first_arg));
-            // In most cases, when x is internalized, its bits are created.
-            // They are created because x is a bit-vector operation or apply_sort_cnstr is invoked.
-            // However, there is an exception. The method apply_sort_cnstr is not invoked for ite-terms.
-            // So, I execute get_var on the enode attached to first_arg. 
-            // This will force a theory variable to be created if it does not already exist.
-            // This will also force the creation of all bits for x.
-            enode* first_arg_enode = ctx.get_enode(first_arg);
-            get_var(first_arg_enode);
-        }
-
-        enode* arg = ctx.get_enode(first_arg);
-        // The argument was already internalized, but it may not have a theory variable associated with it.
-        // For example, for ite-terms the method apply_sort_cnstr is not invoked.
-        // See comment in the then-branch.
-        theory_var v_arg = arg->get_th_var(get_id());
-        if (v_arg == null_theory_var) {
-            // The method get_var will create a theory variable for arg. 
-            // As a side-effect the bits for arg will also be created.
-            get_var(arg);
-            SASSERT(ctx.b_internalized(n));
-        }
-        else if (!ctx.b_internalized(n)) {
-            SASSERT(v_arg != null_theory_var);
-            bool_var bv = ctx.mk_bool_var(n);
-            ctx.set_var_theory(bv, get_id());
-            bit_atom* a = new (get_region()) bit_atom();
-            insert_bv2a(bv, a);
-            m_trail_stack.push(mk_atom_trail(bv));
-            unsigned idx = n->get_decl()->get_parameter(0).get_int();
-            SASSERT(a->m_occs == 0);
-            a->m_occs = new (get_region()) var_pos_occ(v_arg, idx);
-            // Fix for #2182, and SPACER bit-vector
-            if (idx < m_bits[v_arg].size()) {
-                ctx.mk_th_axiom(get_id(), m_bits[v_arg][idx], literal(bv, true));
-                ctx.mk_th_axiom(get_id(), ~m_bits[v_arg][idx], literal(bv, false));
-            }
-        }
-        // axiomatize bit2bool on constants.
-        rational val;
-        unsigned sz;
-        if (m_util.is_numeral(first_arg, val, sz)) {
-            rational bit;
-            unsigned idx = n->get_decl()->get_parameter(0).get_int();
-            div(val, rational::power_of_two(idx), bit);
-            mod(bit, rational(2), bit);
-            literal lit = ctx.get_literal(n);
-            if (bit.is_zero()) lit.neg();
-            ctx.mark_as_relevant(lit);
-            ctx.mk_th_axiom(get_id(), 1, &lit);
-        }
-
-    }
-#endif
-
-
-    euf::enode * solver::mk_enode(expr * n, ptr_vector<euf::enode> const& args) {
-        euf::enode * e = ctx.get_enode(n);
-        if (!e) {
-            e = ctx.mk_enode(n, args.size(), args.c_ptr());
-            mk_var(e);
-        }
-        return e;
-    }
-
     euf::theory_var solver::get_var(euf::enode* n) {
         theory_var v = n->get_th_var(get_id());
         if (v == euf::null_theory_var) {
             v = mk_var(n);
-            mk_bits(v);
+            if (bv.is_bv(n->get_owner())) 
+                mk_bits(v);            
         }
         return v;
     }
 
     euf::enode* solver::get_arg(euf::enode* n, unsigned idx) {
-        if (get_config().m_bv_reflect) {
-            return n->get_arg(idx);
-        }
-        else {
-            expr* arg = n->get_arg(idx)->get_owner();
-            return ctx.get_enode(arg);
-        }
+        app* o = to_app(n->get_owner());
+        return ctx.get_enode(o->get_arg(idx));
     }
 
     inline euf::theory_var solver::get_arg_var(euf::enode* n, unsigned idx) {
@@ -212,20 +212,14 @@ namespace bv {
     }
 
     void solver::get_bits(theory_var v, expr_ref_vector& r) {
-        literal_vector& bits = m_bits[v];
-        for (literal lit : bits) {
-            r.push_back(get_expr(lit));
-        }
+        for (literal lit : m_bits[v]) 
+            r.push_back(literal2expr(lit));        
     }
 
     inline void solver::get_bits(euf::enode* n, expr_ref_vector& r) {
         get_bits(get_var(n), r);
     }
 
-    inline void solver::get_arg_bits(euf::enode* n, unsigned idx, expr_ref_vector& r) {
-        get_bits(get_arg_var(n, idx), r);
-    }
-
     inline void solver::get_arg_bits(app* n, unsigned idx, expr_ref_vector& r) {
         app* arg = to_app(n->get_arg(idx));
         get_bits(ctx.get_enode(arg), r);
@@ -234,7 +228,7 @@ namespace bv {
     void solver::register_true_false_bit(theory_var v, unsigned idx) {
         SASSERT(s().value(m_bits[v][idx]) != l_undef);
         bool is_true = (s().value(m_bits[v][idx]) == l_true);
-        zero_one_bits & bits = m_zero_one_bits[v];
+        zero_one_bits& bits = m_zero_one_bits[v];
         bits.push_back(zero_one_bit(v, idx, is_true));
     }
 
@@ -242,18 +236,22 @@ namespace bv {
        \brief Add bit l to the given variable.
     */
     void solver::add_bit(theory_var v, literal l) {
-        literal_vector & bits = m_bits[v];
-        unsigned idx          = bits.size();
-        bits.push_back(l);
-        if (s().value(l) != l_undef && s().lvl(l) == 0) {
+        unsigned idx = m_bits[v].size();
+        m_bits[v].push_back(l);
+        s().set_external(l.var());
+        set_bit_eh(v, l, idx);
+    }
+
+    void solver::set_bit_eh(theory_var v, literal l, unsigned idx) {
+        if (s().value(l) != l_undef && s().lvl(l) == 0) 
             register_true_false_bit(v, idx);
-        }
         else {
-            atom * a = get_bv2a(l.var());
-            SASSERT(!a || a->is_bit());
-            if (a) {
-                bit_atom* b = static_cast<bit_atom*>(a);
-                find_new_diseq_axioms(b->m_occs, v, idx);
+            atom* a = get_bv2a(l.var());
+            if (a && !a->is_bit()) 
+                ;
+            else if (a) {
+                bit_atom* b = &a->to_bit();
+                find_new_diseq_axioms(*b, v, idx);
                 ctx.push(add_var_pos_trail(b));
                 b->m_occs = new (get_region()) var_pos_occ(v, idx, b->m_occs);
             }
@@ -261,24 +259,23 @@ namespace bv {
                 bit_atom* b = new (get_region()) bit_atom();
                 insert_bv2a(l.var(), b);
                 ctx.push(mk_atom_trail(l.var(), *this));
-                SASSERT(b->m_occs == 0);
+                SASSERT(!b->m_occs);
                 b->m_occs = new (get_region()) var_pos_occ(v, idx);
             }
         }
     }
 
-    void solver::add_unit(sat::literal lit) {
-        s().add_clause(1, &lit, status());
-    }
-
-    void solver::init_bits(euf::enode * n, expr_ref_vector const & bits) {
+    void solver::init_bits(expr* e, expr_ref_vector const& bits) {
+        euf::enode* n = expr2enode(e);
         SASSERT(get_bv_size(n) == bits.size());
         SASSERT(euf::null_theory_var != n->get_th_var(get_id()));
         theory_var v = n->get_th_var(get_id());
-        unsigned sz = bits.size();
         m_bits[v].reset();
+        for (expr* bit : bits) 
+            add_bit(v, ctx.internalize(bit, false, false, m_is_redundant));        
         for (expr* bit : bits)
-            add_bit(v, get_literal(bit));
+            get_var(expr2enode(bit));
+        SASSERT(get_bv_size(n) == bits.size());
         find_wpos(v);
     }
 
@@ -287,22 +284,23 @@ namespace bv {
     }
 
     unsigned solver::get_bv_size(theory_var v) {
-        return get_bv_size(get_enode(v));
+        return get_bv_size(var2enode(v));
     }
 
     void solver::internalize_num(app* n, theory_var v) {
         numeral val;
         unsigned sz = 0;
+        SASSERT(expr2enode(n)->interpreted());
         VERIFY(bv.is_numeral(n, val, sz));
         expr_ref_vector bits(m);
-        m_bb.num2bits(val, sz, bits); 
-        literal_vector & c_bits = m_bits[v];
+        m_bb.num2bits(val, sz, bits);
         SASSERT(bits.size() == sz);
-        SASSERT(c_bits.empty());
+        SASSERT(m_bits[v].empty());
+        sat::literal true_literal = ctx.internalize(m.mk_true(), false, false, false);
         for (unsigned i = 0; i < sz; i++) {
-            expr * l = bits.get(i);
+            expr* l = bits.get(i);
             SASSERT(m.is_true(l) || m.is_false(l));
-            c_bits.push_back(m.is_true(l) ? true_literal : false_literal);
+            m_bits[v].push_back(m.is_true(l) ? true_literal : ~true_literal);
             register_true_false_bit(v, i);
         }
         fixed_var_eh(v);
@@ -310,14 +308,12 @@ namespace bv {
 
     void solver::internalize_mkbv(app* n) {
         expr_ref_vector bits(m);
-        euf::enode * e = mk_enode(n, m_args);
         bits.append(n->get_num_args(), n->get_args());
-        init_bits(e, bits);
+        init_bits(n, bits);
     }
 
     void solver::internalize_bv2int(app* n) {
-        mk_enode(n, m_args);
-        assert_bv2int_axiom(n);        
+        assert_bv2int_axiom(n);
     }
 
     /**
@@ -325,172 +321,270 @@ namespace bv {
      * n = bv2int(k) = ite(bit2bool(k[sz-1],2^{sz-1},0) + ... + ite(bit2bool(k[0],1,0))
      */
 
-    void solver::assert_bv2int_axiom(app * n) {
-        expr* k = nullptr;
-        sort * int_sort = m.get_sort(n);
-        SASSERT(bv.is_bv2int(n, k));
+    void solver::assert_bv2int_axiom(app* n) {
+        expr* k = nullptr;        
+        VERIFY(bv.is_bv2int(n, k));
         SASSERT(bv.is_bv_sort(m.get_sort(k)));
         expr_ref_vector k_bits(m);
-        euf::enode * k_enode = mk_enode(k, m_args);
+        euf::enode* k_enode = expr2enode(k);
         get_bits(k_enode, k_bits);
         unsigned sz = bv.get_bv_size(k);
         expr_ref_vector args(m);
-        expr_ref zero(bv.mk_numeral(numeral(0), int_sort), m);
-        numeral num(1);
-        for (expr* b : k_bits) {
-            expr_ref n(m_autil.mk_numeral(num, int_sort), m);
-            args.push_back(m.mk_ite(b, n, zero));
-            num *= numeral(2);
-        }
+        expr_ref zero(m_autil.mk_int(0), m);
+        unsigned i = 0;        
+        for (expr* b : k_bits) 
+            args.push_back(m.mk_ite(b, m_autil.mk_int(power2(i++)), zero));        
         expr_ref sum(m_autil.mk_add(sz, args.c_ptr()), m);
         expr_ref eq(m.mk_eq(n, sum), m);
         sat::literal lit = ctx.internalize(eq, false, false, m_is_redundant);
         add_unit(lit);
     }
 
-
     void solver::internalize_int2bv(app* n) {
         SASSERT(bv.is_int2bv(n));
-        euf::enode* e = mk_enode(n, m_args);
-        theory_var v = e->get_th_var(get_id());
-        mk_bits(v);
-        assert_int2bv_axiom(n);        
+        euf::enode* e = expr2enode(n);
+        mk_bits(e->get_th_var(get_id()));
+        assert_int2bv_axiom(n);
     }
 
     /**
      * create the axiom:
-     *   bv2int(n) = e mod 2^bit_width 
+     *   bv2int(n) = e mod 2^bit_width
      * where n = int2bv(e)
      *
      * Create the axioms:
      *   bit2bool(i,n) == ((e div 2^i) mod 2 != 0)
      * for i = 0,.., sz-1
      */
-    void solver::assert_int2bv_axiom(app* n) {        
-        SASSERT(bv.is_int2bv(n));
-        expr* e = n->get_arg(0);
-        euf::enode* n_enode = mk_enode(n, m_args);
-        parameter param(m_autil.mk_int());
-        expr* n_expr = n;
+    void solver::assert_int2bv_axiom(app* n) {
+        expr* e = nullptr;
+        VERIFY(bv.is_int2bv(n, e));      
+        euf::enode* n_enode = expr2enode(n);
         expr_ref lhs(m), rhs(m);
-        lhs = m.mk_app(get_id(), OP_BV2INT, 1, &param, 1, &n_expr);
+        lhs = bv.mk_bv2int(n);
         unsigned sz = bv.get_bv_size(n);
         numeral mod = power(numeral(2), sz);
-        rhs = m_autil.mk_mod(e, m_autil.mk_numeral(mod, true));
+        rhs = m_autil.mk_mod(e, m_autil.mk_int(mod));
         expr_ref eq(m.mk_eq(lhs, rhs), m);
-        literal l = ctx.internalize(eq, false, false, m_is_redundant);
-        add_unit(l);
-
         TRACE("bv", tout << eq << "\n";);
-
+        add_unit(ctx.internalize(eq, false, false, m_is_redundant));
+       
         expr_ref_vector n_bits(m);
-        
         get_bits(n_enode, n_bits);
 
         for (unsigned i = 0; i < sz; ++i) {
-            numeral div = power(numeral(2), i);
-            mod = numeral(2);
-            rhs = m_autil.mk_idiv(e, m_autil.mk_numeral(div, true));
-            rhs = m_autil.mk_mod(rhs, m_autil.mk_numeral(mod, true));
-            rhs = m.mk_eq(rhs, m_autil.mk_numeral(rational(1), true));
-            lhs = n_bits.get(i);            
+            numeral div = power2(i);
+            rhs = m_autil.mk_idiv(e, m_autil.mk_int(div));
+            rhs = m_autil.mk_mod(rhs, m_autil.mk_int(2));
+            rhs = m.mk_eq(rhs, m_autil.mk_int(1));
+            lhs = n_bits.get(i);
             expr_ref eq(m.mk_eq(lhs, rhs), m);
             TRACE("bv", tout << eq << "\n";);
-            l = ctx.internalize(eq, false, false, m_is_redundant);
-            add_unit(l);
+            add_unit(ctx.internalize(eq, false, false, m_is_redundant));
         }
     }
 
-
-    void solver::internalize_add(app* n) {}
-    void solver::internalize_sub(app* n) {}
-    void solver::internalize_mul(app* n) {}
-    void solver::internalize_udiv(app* n) {}
-    void solver::internalize_sdiv(app* n) {}
-    void solver::internalize_urem(app* n) {}
-    void solver::internalize_srem(app* n) {}
-    void solver::internalize_smod(app* n) {}
-    void solver::internalize_shl(app* n) {}
-    void solver::internalize_lshr(app* n) {}
-    void solver::internalize_ashr(app* n) {}
-    void solver::internalize_ext_rotate_left(app* n) {}
-    void solver::internalize_ext_rotate_right(app* n) {}
-    void solver::internalize_and(app* n) {}
-    void solver::internalize_or(app* n) {}
-    void solver::internalize_not(app* n) {}
-    void solver::internalize_nand(app* n) {}
-    void solver::internalize_nor(app* n) {}
-    void solver::internalize_xor(app* n) {}
-    void solver::internalize_xnor(app* n) {}
-    void solver::internalize_concat(app* n) {}
-    void solver::internalize_sign_extend(app* n) {}
-    void solver::internalize_zero_extend(app* n) {}
-    void solver::internalize_extract(app* n) {}
-    void solver::internalize_redand(app* n) {}
-    void solver::internalize_redor(app* n) {}
-    void solver::internalize_comp(app* n) {}
-    void solver::internalize_rotate_left(app* n) {}
-    void solver::internalize_rotate_right(app* n) {}
-    void solver::internalize_umul_no_overflow(app* n) {}
-    void solver::internalize_smul_no_overflow(app* n) {}
-    void solver::internalize_smul_no_underflow(app* n) {}
-
-}
-
-
-
-#if 0
-
-        case OP_BADD:           internalize_add(term); return true;
-        case OP_BSUB:           internalize_sub(term); return true;
-        case OP_BMUL:           internalize_mul(term); return true;
-        case OP_BSDIV_I:        internalize_sdiv(term); return true;
-        case OP_BUDIV_I:        internalize_udiv(term); return true;
-        case OP_BSREM_I:        internalize_srem(term); return true;
-        case OP_BUREM_I:        internalize_urem(term); return true;
-        case OP_BSMOD_I:        internalize_smod(term); return true;
-        case OP_BAND:           internalize_and(term); return true;
-        case OP_BOR:            internalize_or(term); return true;
-        case OP_BNOT:           internalize_not(term); return true;
-        case OP_BXOR:           internalize_xor(term); return true;
-        case OP_BNAND:          internalize_nand(term); return true;
-        case OP_BNOR:           internalize_nor(term); return true;
-        case OP_BXNOR:          internalize_xnor(term); return true;
-        case OP_CONCAT:         internalize_concat(term); return true;
-        case OP_SIGN_EXT:       internalize_sign_extend(term); return true;
-        case OP_ZERO_EXT:       internalize_zero_extend(term); return true;
-        case OP_EXTRACT:        internalize_extract(term); return true;
-        case OP_BREDOR:         internalize_redor(term); return true;
-        case OP_BREDAND:        internalize_redand(term); return true;
-        case OP_BCOMP:          internalize_comp(term); return true;
-        case OP_BSHL:           internalize_shl(term); return true;
-        case OP_BLSHR:          internalize_lshr(term); return true;
-        case OP_BASHR:          internalize_ashr(term); return true;
-        case OP_ROTATE_LEFT:    internalize_rotate_left(term); return true;
-        case OP_ROTATE_RIGHT:   internalize_rotate_right(term); return true;
-        case OP_EXT_ROTATE_LEFT:  internalize_ext_rotate_left(term); return true;
-        case OP_EXT_ROTATE_RIGHT: internalize_ext_rotate_right(term); return true;
-        case OP_BSDIV0:         return false;
-        case OP_BUDIV0:         return false;
-        case OP_BSREM0:         return false;
-        case OP_BUREM0:         return false;
-        case OP_BSMOD0:         return false;
-        case OP_MKBV:           internalize_mkbv(term); return true;
-        case OP_INT2BV:
-            if (params().m_bv_enable_int2bv2int) {
-                internalize_int2bv(term);
-            }
-            return params().m_bv_enable_int2bv2int;
-        case OP_BV2INT:
-            if (params().m_bv_enable_int2bv2int) {
-                internalize_bv2int(term);
-            }
-            return params().m_bv_enable_int2bv2int;
-        default:
-            TRACE("bv_op", tout << "unsupported operator: " << mk_ll_pp(term, m) << "\n";);
-            UNREACHABLE();
-            return false;
+    template<bool Signed>
+    void solver::internalize_le(app* n) {
+        SASSERT(n->get_num_args() == 2);
+        expr_ref_vector arg1_bits(m), arg2_bits(m);
+        get_arg_bits(n, 0, arg1_bits);
+        get_arg_bits(n, 1, arg2_bits);
+        expr_ref le(m);
+        if (Signed)
+            m_bb.mk_sle(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le);
+        else
+            m_bb.mk_ule(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le);
+        literal def = ctx.internalize(le, false, false, m_is_redundant);
+        add_def(def, expr2literal(n));
     }
 
+    void solver::internalize_carry(app* n) {
+        SASSERT(n->get_num_args() == 3);
+        literal r = ctx.get_literal(n);
+        literal l1 = ctx.get_literal(n->get_arg(0));
+        literal l2 = ctx.get_literal(n->get_arg(1));
+        literal l3 = ctx.get_literal(n->get_arg(2));
+        add_clause(~r, l1, l2);
+        add_clause(~r, l1, l3);
+        add_clause(~r, l2, l3);
+        add_clause(r, ~l1, ~l2);
+        add_clause(r, ~l1, ~l3);
+        add_clause(r, ~l2, ~l3);
+    }
+
+    void solver::internalize_xor3(app* n) {
+        SASSERT(n->get_num_args() == 3);
+        literal r = ctx.get_literal(n);
+        literal l1 = expr2literal(n->get_arg(0));
+        literal l2 = expr2literal(n->get_arg(1));
+        literal l3 = expr2literal(n->get_arg(2));
+        add_clause(~r, l1, l2, l3);
+        add_clause(~r, ~l1, ~l2, l3);
+        add_clause(~r, ~l1, l2, ~l3);
+        add_clause(~r, l1, ~l2, ~l3);
+        add_clause(r, ~l1, l2, l3);
+        add_clause(r, l1, ~l2, l3);
+        add_clause(r, l1, l2, ~l3);
+        add_clause(r, ~l1, ~l2, ~l3);
+    }
+
+    void solver::internalize_unary(app* n, std::function<void(unsigned, expr* const*, expr_ref_vector&)>& fn) {
+        SASSERT(n->get_num_args() == 1);
+        expr_ref_vector arg1_bits(m), bits(m);
+        get_arg_bits(n, 0, arg1_bits);
+        fn(arg1_bits.size(), arg1_bits.c_ptr(), bits);
+        init_bits(n, bits);
+    }
+
+    void solver::internalize_par_unary(app* n, std::function<void(unsigned, expr* const*, unsigned p, expr_ref_vector&)>& fn) {
+        SASSERT(n->get_num_args() == 1);
+        expr_ref_vector arg1_bits(m), bits(m);
+        get_arg_bits(n, 0, arg1_bits);
+        unsigned param = n->get_decl()->get_parameter(0).get_int();
+        fn(arg1_bits.size(), arg1_bits.c_ptr(), param, bits);
+        init_bits(n, bits);
+    }
+
+    void solver::internalize_binary(app* n, std::function<void(unsigned, expr* const*, expr* const*, expr_ref_vector&)>& fn) {
+        SASSERT(n->get_num_args() == 2);
+        expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m);
+        get_arg_bits(n, 0, arg1_bits);
+        get_arg_bits(n, 1, arg2_bits);
+        SASSERT(arg1_bits.size() == arg2_bits.size());
+        fn(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits);
+        init_bits(n, bits);
+    }
+
+    void solver::internalize_ac_binary(app* n, std::function<void(unsigned, expr* const*, expr* const*, expr_ref_vector&)>& fn) {
+        SASSERT(n->get_num_args() >= 2);
+        expr_ref_vector bits(m), new_bits(m), arg_bits(m);
+        unsigned i = n->get_num_args() - 1;        
+        get_arg_bits(n, i, bits);
+        for (; i-- > 0; ) {
+            arg_bits.reset();
+            get_arg_bits(n, i, arg_bits);
+            SASSERT(arg_bits.size() == bits.size());
+            new_bits.reset();
+            fn(arg_bits.size(), arg_bits.c_ptr(), bits.c_ptr(), new_bits);
+            bits.swap(new_bits);
+        }
+        init_bits(n, bits);
+        TRACE("bv_verbose", tout << arg_bits << " " << bits << " " << new_bits << "\n";);
+    }
+
+    void solver::internalize_novfl(app* n, std::function<void(unsigned, expr* const*, expr* const*, expr_ref&)>& fn) {
+        SASSERT(n->get_num_args() == 2);
+        expr_ref_vector arg1_bits(m), arg2_bits(m);
+        get_arg_bits(n, 0, arg1_bits);
+        get_arg_bits(n, 1, arg2_bits);
+        expr_ref out(m);
+        fn(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), out);
+        sat::literal def = ctx.internalize(out, false, false, m_is_redundant);
+        add_def(def, ctx.get_literal(n));
+    }
+
+    void solver::add_def(sat::literal def, sat::literal l) {        
+        def_atom* a = new (get_region()) def_atom(l, def);
+        insert_bv2a(l.var(), a);
+        ctx.push(mk_atom_trail(l.var(), *this));
+        add_clause(l, ~def);
+        add_clause(def, ~l);
+    }
+
+    void solver::internalize_concat(app* n) {
+        euf::enode* e = expr2enode(n);
+        theory_var v = e->get_th_var(get_id());
+        m_bits[v].reset();
+        for (unsigned i = n->get_num_args(); i-- > 0; ) 
+            for (literal lit : m_bits[get_arg_var(e, i)]) 
+                add_bit(v, lit);                    
+        find_wpos(v);
+    }
+
+    void solver::internalize_sub(app* n) {
+        SASSERT(n->get_num_args() == 2);
+        expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m);
+        get_arg_bits(n, 0, arg1_bits);
+        get_arg_bits(n, 1, arg2_bits);
+        SASSERT(arg1_bits.size() == arg2_bits.size());
+        expr_ref carry(m);
+        m_bb.mk_subtracter(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits, carry);
+        init_bits(n, bits);
+    }
+
+    void solver::internalize_extract(app* n) {
+        SASSERT(n->get_num_args() == 1);
+        euf::enode* e = expr2enode(n);
+        theory_var v = e->get_th_var(get_id());
+        theory_var arg = get_arg_var(e, 0);
+        unsigned start = n->get_decl()->get_parameter(1).get_int();
+        unsigned end = n->get_decl()->get_parameter(0).get_int();
+        SASSERT(start <= end && end < get_bv_size(v));
+        m_bits[v].reset();
+        for (unsigned i = start; i <= end; ++i)
+            add_bit(v, m_bits[arg][i]);
+        find_wpos(v);
+    }
+
+    void solver::internalize_bit2bool(app* n) {
+        unsigned idx = 0;
+        expr* arg = nullptr;
+        VERIFY(bv.is_bit2bool(n, arg, idx));
+        euf::enode* argn = expr2enode(arg);
+        if (!argn->is_attached_to(get_id())) {
+            mk_var(argn);
+        }        
+        theory_var v_arg = argn->get_th_var(get_id());
+        sat::literal lit = expr2literal(n);
+        sat::bool_var b = lit.var();
+        bit_atom* a = new (get_region()) bit_atom();
+        SASSERT(idx < get_bv_size(v_arg));
+        a->m_occs = new (get_region()) var_pos_occ(v_arg, idx);
+        insert_bv2a(b, a);
+        ctx.push(mk_atom_trail(b, *this));           
+        if (idx < m_bits[v_arg].size() && lit != m_bits[v_arg][idx]) {
+            add_clause(m_bits[v_arg][idx], ~lit);
+            add_clause(~m_bits[v_arg][idx], lit);
+        }
+        // axiomatize bit2bool on constants.
+        rational val;
+        unsigned sz;
+        if (bv.is_numeral(arg, val, sz)) {
+            rational bit;
+            div(val, rational::power_of_two(idx), bit);
+            mod(bit, rational(2), bit);
+            if (bit.is_zero()) lit.neg();
+            add_unit(lit);
+        }
+    }
+
+    void solver::assert_ackerman(theory_var v1, theory_var v2) {
+        flet<bool> _red(m_is_redundant, true);
+        ++m_stats.m_ackerman;
+        expr* o1 = var2expr(v1);
+        expr* o2 = var2expr(v2);
+        expr_ref oe(m.mk_eq(o1, o2), m);
+        literal oeq = ctx.internalize(oe, false, false, m_is_redundant);
+        unsigned sz = get_bv_size(v1);
+        TRACE("bv", tout << oe << "\n";);
+        literal_vector eqs;
+        for (unsigned i = 0; i < sz; ++i) {
+            literal l1 = m_bits[v1][i];
+            literal l2 = m_bits[v2][i];
+            expr_ref e1(m), e2(m);
+            e1 = bv.mk_bit2bool(o1, i);
+            e2 = bv.mk_bit2bool(o2, i);
+            expr_ref e(m.mk_eq(e1, e2), m);
+            literal eq = ctx.internalize(e, false, false, m_is_redundant);
+            add_clause(l1, ~l2, ~eq);
+            add_clause(~l1, l2, ~eq);
+            add_clause(l1, l2, eq);
+            add_clause(~l1, ~l2, eq);
+            add_clause(eq, ~oeq);
+            eqs.push_back(~eq);
+        }
+        eqs.push_back(oeq);
+        s().add_clause(eqs.size(), eqs.c_ptr(), sat::status::th(m_is_redundant, get_id()));
+    }
 }
-#endif
diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp
index 4555a37cb..564d1db98 100644
--- a/src/sat/smt/bv_solver.cpp
+++ b/src/sat/smt/bv_solver.cpp
@@ -3,18 +3,20 @@ Copyright (c) 2020 Microsoft Corporation
 
 Module Name:
 
-    bv_internalize.cpp
+    bv_solver.cpp
 
 Abstract:
 
-    Internalize utilities for bit-vector solver plugin.
+    Solving utilities for bit-vectors.
 
 Author:
 
     Nikolaj Bjorner (nbjorner) 2020-09-02
+    based on smt/theory_bv
 
 --*/
 
+#include "ast/ast_ll_pp.h"
 #include "sat/smt/bv_solver.h"
 #include "sat/smt/euf_solver.h"
 #include "sat/smt/sat_th.h"
@@ -22,52 +24,110 @@ Author:
 
 namespace bv {
 
-    void solver::fixed_var_eh(theory_var v) {
-        
+    class solver::bit_trail : public trail<euf::solver> {
+        solver& s;
+        solver::var_pos vp;
+        sat::literal lit;
+    public:
+        bit_trail(solver& s, var_pos vp) : s(s), vp(vp), lit(s.m_bits[vp.first][vp.second]) {}
+
+        virtual void undo(euf::solver& euf) {
+            s.m_bits[vp.first][vp.second] = lit;
+        }
+    };
+
+    solver::solver(euf::solver& ctx, theory_id id) :
+        euf::th_euf_solver(ctx, id),
+        bv(m),
+        m_autil(m),
+        m_ackerman(*this),
+        m_bb(m, get_config()),
+        m_find(*this) {
+    }
+
+    void solver::fixed_var_eh(theory_var v1) {
+        numeral val1, val2;
+        VERIFY(get_fixed_value(v1, val1));
+        unsigned sz = m_bits[v1].size();
+        value_sort_pair key(val1, sz);
+        theory_var v2;
+        bool is_current =
+            m_fixed_var_table.find(key, v2) &&
+            v2 < static_cast<int>(get_num_vars()) &&
+            is_bv(v2) &&
+            get_bv_size(v2) == sz &&
+            get_fixed_value(v2, val2) && val1 == val2;
+
+        if (!is_current)
+            m_fixed_var_table.insert(key, v1);
+        else if (var2enode(v1)->get_root() != var2enode(v2)->get_root()) {
+            SASSERT(get_bv_size(v1) == get_bv_size(v2));
+            TRACE("bv", tout << "detected equality: v" << v1 << " = v" << v2 << "\n" << pp(v1) << pp(v2););
+            m_stats.m_num_th2core_eq++;
+            add_fixed_eq(v1, v2);
+            ctx.propagate(var2enode(v1), var2enode(v2), mk_bit2bv_justification(v1, v2));
+        }
+    }
+
+    void solver::add_fixed_eq(theory_var v1, theory_var v2) {
+        if (!get_config().m_bv_eq_axioms)
+            return;
+        m_ackerman.used_eq_eh(v1, v2);
+    }
+
+    bool solver::get_fixed_value(theory_var v, numeral& result) const {
+        result.reset();
+        unsigned i = 0;
+        for (literal b : m_bits[v]) {
+            switch (ctx.s().value(b)) {
+            case l_false:
+                break;
+            case l_undef:
+                return false;
+            case l_true:
+                result += power2(i);
+                break;
+            }
+            ++i;
+        }
+        return true;
     }
 
     /**
        \brief Find an unassigned bit for m_wpos[v], if such bit cannot be found invoke fixed_var_eh
     */
     void solver::find_wpos(theory_var v) {
-        literal_vector const & bits = m_bits[v];
-        unsigned sz                 = bits.size();
-        unsigned & wpos             = m_wpos[v];
-        unsigned init               = wpos;
-        for (; wpos < sz; wpos++) {
-            TRACE("find_wpos", tout << "curr bit: " << bits[wpos] << "\n";);
-            if (s().value(bits[wpos]) == l_undef) {
-                TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";);
+        literal_vector const& bits = m_bits[v];
+        unsigned sz = bits.size();
+        unsigned& wpos = m_wpos[v];
+        for (unsigned i = 0; i < sz; ++i) {
+            unsigned idx = (i + wpos) % sz;
+            if (s().value(bits[idx]) == l_undef) {
+                wpos = idx;
+                TRACE("bv", tout << "moved wpos of v" << v << " to " << wpos << "\n";);
                 return;
             }
         }
-        wpos = 0;
-        for (; wpos < init; wpos++) {
-            if (s().value(bits[wpos]) == l_undef) {
-                TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";);
-                return;
-            }
-        }
-        TRACE("find_wpos", tout << "v" << v << " is a fixed variable.\n";);
+        TRACE("bv", tout << "v" << v << " is a fixed variable.\n";);
         fixed_var_eh(v);
     }
 
     /**
-   \brief v[idx] = ~v'[idx], then v /= v' is a theory axiom.
-*/
-    void solver::find_new_diseq_axioms(var_pos_occ* occs, theory_var v, unsigned idx) {
+     *\brief v[idx] = ~v'[idx], then v /= v' is a theory axiom.
+    */
+    void solver::find_new_diseq_axioms(bit_atom& a, theory_var v, unsigned idx) {
+        if (!get_config().m_bv_eq_axioms)
+            return;
         literal l = m_bits[v][idx];
         l.neg();
-        while (occs) {
-            theory_var v2 = occs->m_var;
-            unsigned   idx2 = occs->m_idx;
+        for (auto vp : a) {
+            theory_var v2 = vp.first;
+            unsigned   idx2 = vp.second;
             if (idx == idx2 && m_bits[v2][idx2] == l && get_bv_size(v2) == get_bv_size(v))
                 mk_new_diseq_axiom(v, v2, idx);
-            occs = occs->m_next;
         }
     }
 
-
     /**
        \brief v1[idx] = ~v2[idx], then v1 /= v2 is a theory axiom.
     */
@@ -78,60 +138,533 @@ namespace bv {
         // TBD: disabled until new literal creation is supported
         return;
         SASSERT(m_bits[v1][idx] == ~m_bits[v2][idx]);
-        TRACE("bv_solver", tout << "found new diseq axiom\n" << pp(v1) << pp(v2);); 
+        TRACE("bv", tout << "found new diseq axiom\n" << pp(v1) << pp(v2););
         m_stats.m_num_diseq_static++;
-        expr_ref eq(m.mk_eq(get_expr(v1), get_expr(v2)), m);
-        sat::literal neq = ctx.internalize(eq, true, false, m_is_redundant);
-        s().add_clause(1, &neq, sat::status::th(m_is_redundant, get_id()));
+        expr_ref eq(m.mk_eq(var2expr(v1), var2expr(v2)), m);
+        add_unit(~ctx.internalize(eq, false, false, m_is_redundant));
     }
 
     std::ostream& solver::display(std::ostream& out, theory_var v) const {
+        expr* e = var2expr(v);
         out << "v";
         out.width(4);
         out << std::left << v;
-        out << " #";
+        out << " ";
         out.width(4);
-        out << get_enode(v)->get_owner_id() << " -> #";
+        out << e->get_id() << " -> ";
         out.width(4);
-#if 0
-        out << get_enode(find(v))->get_owner_id();
-        out << std::right << ", bits:";
-        literal_vector const& bits = m_bits[v];
-        for (literal lit : bits) {
-            out << " " << lit << ":";
-            ctx.display_literal(out, lit);
+        out << var2enode(find(v))->get_owner_id();
+        out << std::right;
+        out.flush();
+        atom* a = nullptr;
+        if (is_bv(v)) {
+            numeral val;
+            if (get_fixed_value(v, val))
+                out << " (= " << val << ")";
+            for (literal lit : m_bits[v]) {
+                out << " " << lit << ":" << mk_bounded_pp(literal2expr(lit), m, 1);
+            }
         }
-        numeral val;
-        if (get_fixed_value(v, val))
-            out << ", value: " << val;
+        else if (m.is_bool(e) && (a = m_bool_var2atom.get(expr2literal(e).var(), nullptr))) {
+            if (a->is_bit()) {
+                for (var_pos vp : a->to_bit())
+                    out << " " << var2enode(vp.first)->get_owner_id() << "[" << vp.second << "]";
+            }
+            else
+                out << "def-atom";
+        }
+        else
+            out << " " << mk_bounded_pp(e, m, 1);
         out << "\n";
-#endif
         return out;
     }
 
+    void solver::new_eq_eh(euf::th_eq const& eq) {
+        TRACE("bv", tout << "new eq " << eq.m_v1 << " == " << eq.m_v2 << "\n";);
+        if (is_bv(eq.m_v1))
+            m_find.merge(eq.m_v1, eq.m_v2);
+    }
+
     double solver::get_reward(literal l, sat::ext_constraint_idx idx, sat::literal_occs_fun& occs) const { return 0; }
     bool solver::is_extended_binary(sat::ext_justification_idx idx, literal_vector& r) { return false; }
     bool solver::is_external(bool_var v) { return true; }
     bool solver::propagate(literal l, sat::ext_constraint_idx idx) { return false; }
-    void solver::get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector& r) {}
-    void solver::asserted(literal l) {}
-    sat::check_result solver::check() { return sat::check_result::CR_DONE; }
-    void solver::push() {}
-    void solver::pop(unsigned n) {}
+
+    void solver::get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector& r, bool probing) {
+        auto& c = bv_justification::from_index(idx);
+        TRACE("bv", display_constraint(tout, idx););
+        switch (c.m_kind) {
+        case bv_justification::kind_t::bv2bit:
+            r.push_back(c.m_antecedent);
+            ctx.add_antecedent(var2enode(c.m_v1), var2enode(c.m_v2));
+            break;
+        case bv_justification::kind_t::bit2bv:
+            SASSERT(m_bits[c.m_v1].size() == m_bits[c.m_v2].size());
+            for (unsigned i = m_bits[c.m_v1].size(); i-- > 0; ) {
+                sat::literal a = m_bits[c.m_v1][i];
+                sat::literal b = m_bits[c.m_v2][i];
+                SASSERT(a == b || s().value(a) != l_undef);
+                SASSERT(s().value(a) == s().value(b));
+                if (a == b)
+                    continue;
+                if (s().value(a) == l_false) {
+                    a.neg();
+                    b.neg();
+                }
+                r.push_back(a);
+                r.push_back(b);
+            }
+            break;
+        }
+        if (!probing && ctx.use_drat())
+            log_drat(c);
+    }
+
+    void solver::log_drat(bv_justification const& c) {
+        // this has a side-effect so changes provability:
+        expr_ref eq(m.mk_eq(var2expr(c.m_v1), var2expr(c.m_v2)), m);
+        sat::literal leq = ctx.internalize(eq, false, false, false);
+        sat::literal_vector lits;
+        auto add_bit = [&](sat::literal lit) {
+            if (s().value(lit) == l_true)
+                lit.neg();
+            lits.push_back(lit);
+        };
+        switch (c.m_kind) {
+        case bv_justification::kind_t::bv2bit:
+            lits.push_back(~leq);
+            lits.push_back(~c.m_antecedent);
+            lits.push_back(c.m_consequent);
+            break;
+        case bv_justification::kind_t::bit2bv:
+            lits.push_back(leq);
+            for (unsigned i = m_bits[c.m_v1].size(); i-- > 0; ) {
+                sat::literal a = m_bits[c.m_v1][i];
+                sat::literal b = m_bits[c.m_v2][i];
+                if (a != b) {
+                    add_bit(a);
+                    add_bit(b);
+                }
+            }
+            break;
+        }
+        ctx.get_drat().add(lits, status());
+    }
+
+    void solver::asserted(literal l) {
+        atom* a = get_bv2a(l.var());
+        TRACE("bv", tout << "asserted: " << l << "\n";);
+        if (a->is_bit())
+            for (auto vp : a->to_bit())
+                m_prop_queue.push_back(vp);
+    }
+
+    bool solver::unit_propagate() {
+        if (m_prop_queue_head == m_prop_queue.size())
+            return false;
+        ctx.push(value_trail<euf::solver, unsigned>(m_prop_queue_head));
+        for (; m_prop_queue_head < m_prop_queue.size() && !s().inconsistent(); ++m_prop_queue_head)
+            propagate_bits(m_prop_queue[m_prop_queue_head]);
+        return true;
+    }
+
+    void solver::propagate_bits(var_pos entry) {
+        theory_var v1 = entry.first;
+        unsigned idx = entry.second;
+        SASSERT(idx < m_bits[v1].size());
+        if (m_wpos[v1] == idx)
+            find_wpos(v1);
+
+        literal bit1 = m_bits[v1][idx];
+        lbool   val = s().value(bit1);
+        TRACE("bv", tout << "propagating v" << v1 << " #" << var2enode(v1)->get_owner_id() << "[" << idx << "] = " << val << "\n";);
+        if (val == l_undef)
+            return;
+
+        if (val == l_false)
+            bit1.neg();
+
+        for (theory_var v2 = m_find.next(v1); v2 != v1 && !s().inconsistent(); v2 = m_find.next(v2)) {
+            literal bit2 = m_bits[v2][idx];
+            SASSERT(m_bits[v1][idx] != ~m_bits[v2][idx]);
+            TRACE("bv", tout << "propagating #" << var2enode(v2)->get_owner_id() << "[" << idx << "] = " << s().value(bit2) << "\n";);
+
+            if (val == l_false)
+                bit2.neg();
+            if (l_true != s().value(bit2))
+                assign_bit(bit2, v1, v2, idx, bit1, false);
+        }
+    }
+
+    sat::check_result solver::check() {
+        SASSERT(m_prop_queue.size() == m_prop_queue_head);
+        return sat::check_result::CR_DONE;
+    }
+
+    void solver::push() {
+        th_euf_solver::lazy_push();
+        m_prop_queue_lim.push_back(m_prop_queue.size());
+    }
+
+    void solver::pop(unsigned n) {
+        unsigned old_sz = m_prop_queue_lim.size() - n;
+        m_prop_queue.shrink(m_prop_queue_lim[old_sz]);
+        m_prop_queue_lim.shrink(old_sz);
+        n = lazy_pop(n);
+        if (n > 0) {
+            old_sz = get_num_vars();
+            m_bits.shrink(old_sz);
+            m_wpos.shrink(old_sz);
+            m_zero_one_bits.shrink(old_sz);
+        }
+    }
+
     void solver::pre_simplify() {}
-    void solver::simplify() {}
+
+    void solver::simplify() {
+        m_ackerman.propagate();
+    }
+
+    bool solver::set_root(literal l, literal r) {
+        atom* a = get_bv2a(l.var());
+        if (!a || !a->is_bit())
+            return true;
+        for (auto vp : a->to_bit()) {
+            sat::literal l2 = m_bits[vp.first][vp.second];
+            sat::literal r2 = (l.sign() == l2.sign()) ? r : ~r;
+            SASSERT(l2.var() == l.var());
+            ctx.push(bit_trail(*this, vp));
+            m_bits[vp.first][vp.second] = r2;
+            set_bit_eh(vp.first, r2, vp.second);
+        }
+        return true;
+    }
+
+    /**
+    * Instantiate Ackerman axioms for bit-vectors that have become equal after roots have been added.
+    */
+    void solver::flush_roots() {
+        struct eq {
+            solver& s;
+            eq(solver& s) :s(s) {}
+            bool operator()(theory_var v1, theory_var v2) const {
+                return s.m_bits[v1] == s.m_bits[v2];
+            }
+        };
+        struct hash {
+            solver& s;
+            hash(solver& s) :s(s) {}
+            bool operator()(theory_var v) const {
+                literal_vector const& a = s.m_bits[v];
+                return string_hash(reinterpret_cast<char*>(a.c_ptr()), a.size() * sizeof(sat::literal), 3);
+            }
+        };
+        eq eq_proc(*this);
+        hash hash_proc(*this);
+        map<theory_var, theory_var, hash, eq> table(hash_proc, eq_proc);
+        for (unsigned v = 0; v < get_num_vars(); ++v) {
+            if (!m_bits[v].empty()) {
+                theory_var w = table.insert_if_not_there(v, v);
+                if (v != w && m_find.find(v) != m_find.find(w))
+                    assert_ackerman(v, w);
+            }
+        }    
+        TRACE("bv", tout << "infer new equations for bit-vectors that are now equal\n";);
+    }
+
     void solver::clauses_modifed() {}
     lbool solver::get_phase(bool_var v) { return l_undef; }
-    std::ostream& solver::display(std::ostream& out) const { return out; }
-    std::ostream& solver::display_justification(std::ostream& out, sat::ext_justification_idx idx) const { return out; }
-    std::ostream& solver::display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const { return out; }
-    void solver::collect_statistics(statistics& st) const {}
-    sat::extension* solver::copy(sat::solver* s) { return nullptr; }
+    std::ostream& solver::display(std::ostream& out) const {
+        unsigned num_vars = get_num_vars();
+        if (num_vars > 0)
+            out << "bv-solver:\n";
+        for (unsigned v = 0; v < num_vars; v++)
+            out << pp(v);
+        return out;
+    }
+
+    std::ostream& solver::display_justification(std::ostream& out, sat::ext_justification_idx idx) const {
+        return display_constraint(out, idx);
+    }
+
+    std::ostream& solver::display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const {
+        auto& c = bv_justification::from_index(idx);
+        switch (c.m_kind) {
+        case bv_justification::kind_t::bv2bit:
+            return out << c.m_consequent << " <= " << c.m_antecedent << " v" << c.m_v1 << " == v" << c.m_v2 << "\n";
+        case bv_justification::kind_t::bit2bv:
+            return out << m_bits[c.m_v1] << " == " << m_bits[c.m_v2] << " => v" << c.m_v1 << " == v" << c.m_v2 << "\n";
+        default:
+            UNREACHABLE();
+            break;
+        }
+        return out;
+    }
+
+    void solver::collect_statistics(statistics& st) const {
+        st.update("bv conflicts", m_stats.m_num_conflicts);
+        st.update("bv diseqs", m_stats.m_num_diseq_static);
+        st.update("bv dynamic diseqs", m_stats.m_num_diseq_dynamic);
+        st.update("bv bit2core", m_stats.m_num_bit2core);
+        st.update("bv->core eq", m_stats.m_num_th2core_eq);
+        st.update("bv ackerman", m_stats.m_ackerman);
+    }
+
+    sat::extension* solver::copy(sat::solver* s) { UNREACHABLE(); return nullptr; }
+
+    euf::th_solver* solver::fresh(sat::solver* s, euf::solver& ctx) {
+        bv::solver* result = alloc(bv::solver, ctx, get_id());
+        ast_translation tr(m, ctx.get_manager());
+        for (unsigned i = 0; i < get_num_vars(); ++i) {
+            expr* e1 = var2expr(i);
+            expr* e2 = tr(e1);
+            euf::enode* n2 = ctx.get_enode(e2);
+            SASSERT(n2);
+            result->mk_var(n2);
+            result->m_bits[i].append(m_bits[i]);
+            result->m_zero_one_bits[i].append(m_zero_one_bits[i]);
+        }
+        for (unsigned i = 0; i < get_num_vars(); ++i)
+            if (find(i) != i)
+                result->m_find.merge(i, find(i));
+        result->m_prop_queue.append(m_prop_queue);
+        for (unsigned i = 0; i < m_bool_var2atom.size(); ++i) {
+            atom* a = m_bool_var2atom[i];
+            if (!a)
+                continue;
+
+            if (a->is_bit()) {
+                bit_atom* new_a = new (result->get_region()) bit_atom();
+                m_bool_var2atom.setx(i, new_a, nullptr);
+                for (auto vp : a->to_bit())
+                    new_a->m_occs = new (result->get_region()) var_pos_occ(vp.first, vp.second, new_a->m_occs);
+            }
+            else {
+                def_atom* new_a = new (result->get_region()) def_atom(a->to_def().m_var, a->to_def().m_def);
+                m_bool_var2atom.setx(i, new_a, nullptr);
+            }
+        }
+        return result;
+    }
+
     void solver::pop_reinit() {}
     bool solver::validate() { return true; }
     void solver::init_use_list(sat::ext_use_list& ul) {}
     bool solver::is_blocked(literal l, sat::ext_constraint_idx) { return false; }
     bool solver::check_model(sat::model const& m) const { return true; }
-    unsigned solver::max_var(unsigned w) const { return 0;}
- 
+    unsigned solver::max_var(unsigned w) const { return w; }
+
+    void solver::add_value(euf::enode* n, expr_ref_vector& values) {
+        SASSERT(bv.is_bv(n->get_owner()));
+        theory_var v = n->get_th_var(get_id());
+        rational val;
+        unsigned i = 0;
+        for (auto l : m_bits[v]) {
+            switch (s().value(l)) {
+            case l_true:
+                val += power2(i);
+                break;
+            default:
+                break;
+            }
+            ++i;
+        }
+        values[n->get_root_id()] = bv.mk_numeral(val, m_bits[v].size());
+    }
+
+    trail_stack<euf::solver>& solver::get_trail_stack() {
+        return ctx.get_trail_stack();
+    }
+
+    void solver::merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) {
+        TRACE("bv", tout << "merging: v" << v1 << " #" << var2enode(v1)->get_owner_id() << " v" << v2 << " #" << var2enode(v2)->get_owner_id() << "\n";);
+        if (!merge_zero_one_bits(r1, r2)) {
+            TRACE("bv", tout << "conflict detected\n";);
+            return; // conflict was detected
+        }
+        SASSERT(m_bits[v1].size() == m_bits[v2].size());
+        unsigned sz = m_bits[v1].size();
+        for (unsigned idx = 0; !s().inconsistent() && idx < sz; idx++) {
+            literal bit1 = m_bits[v1][idx];
+            literal bit2 = m_bits[v2][idx];
+            CTRACE("bv", bit1 == ~bit2, tout << pp(v1) << pp(v2) << "idx: " << idx << "\n";);
+            SASSERT(bit1 != ~bit2);
+            lbool val1 = s().value(bit1);
+            lbool val2 = s().value(bit2);
+            TRACE("bv", tout << "merge v" << v1 << " " << bit1 << ":= " << val1 << " " << bit2 << ":= " << val2 << "\n";);
+            if (val1 == val2)
+                continue;
+            CTRACE("bv", (val1 != l_undef && val2 != l_undef), tout << "inconsistent "; tout << pp(v1) << pp(v2) << "idx: " << idx << "\n";);
+            if (val1 == l_false)
+                assign_bit(~bit2, v1, v2, idx, ~bit1, true);
+            else if (val1 == l_true)
+                assign_bit(bit2, v1, v2, idx, bit1, true);
+            else if (val2 == l_false)
+                assign_bit(~bit1, v2, v1, idx, ~bit2, true);
+            else if (val2 == l_true)
+                assign_bit(bit1, v2, v1, idx, bit2, true);
+        }
+    }
+
+    sat::justification solver::mk_bv2bit_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a) {
+        void* mem = get_region().allocate(bv_justification::get_obj_size());
+        sat::constraint_base::initialize(mem, this);
+        auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2, c, a);
+        return sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index());
+    }
+
+    sat::ext_justification_idx solver::mk_bit2bv_justification(theory_var v1, theory_var v2) {
+        void* mem = get_region().allocate(bv_justification::get_obj_size());
+        sat::constraint_base::initialize(mem, this);
+        auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2);
+        return constraint->to_index();
+    }
+
+    void solver::assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc) {
+        m_stats.m_num_bit2core++;
+        SASSERT(ctx.s().value(antecedent) == l_true);
+        SASSERT(m_bits[v2][idx].var() == consequent.var());
+        SASSERT(consequent.var() != antecedent.var());
+        s().assign(consequent, mk_bv2bit_justification(v1, v2, consequent, antecedent));
+        if (s().value(consequent) == l_false) {
+            m_stats.m_num_conflicts++;
+            SASSERT(s().inconsistent());
+        }
+        else {
+            if (get_config().m_bv_eq_axioms && false) {
+                // TODO - enable when pop_reinit is available
+                expr_ref eq(m.mk_eq(var2expr(v1), var2expr(v2)), m);
+                flet<bool> _is_redundant(m_is_redundant, true);
+                literal eq_lit = ctx.internalize(eq, false, false, m_is_redundant);
+                add_clause(~antecedent, ~eq_lit, consequent);
+                add_clause(antecedent, ~eq_lit, ~consequent);
+            }
+
+            if (m_wpos[v2] == idx)
+                find_wpos(v2);
+            bool_var cv = consequent.var();
+            atom* a = get_bv2a(cv);
+            if (a && a->is_bit())
+                for (auto curr : a->to_bit())
+                    if (propagate_eqc || find(curr.first) != find(v2) || curr.second != idx) 
+                        m_prop_queue.push_back(curr);                    
+        }
+    }
+
+    void solver::unmerge_eh(theory_var v1, theory_var v2) {
+        // v1 was the root of the equivalence class
+        // I must remove the zero_one_bits that are from v2.
+        zero_one_bits& bits = m_zero_one_bits[v1];
+        if (bits.empty())
+            return;
+        for (unsigned j = bits.size(); j-- > 0; ) {
+            zero_one_bit& bit = bits[j];
+            if (find(bit.m_owner) == v1) {
+                bits.shrink(j + 1);
+                return;
+            }
+        }
+        bits.shrink(0);
+    }
+
+    bool solver::merge_zero_one_bits(theory_var r1, theory_var r2) {
+        zero_one_bits& bits2 = m_zero_one_bits[r2];
+        if (bits2.empty())
+            return true;
+        zero_one_bits& bits1 = m_zero_one_bits[r1];
+        unsigned bv_size = get_bv_size(r1);
+        SASSERT(bv_size == get_bv_size(r2));
+        m_merge_aux[0].reserve(bv_size + 1, euf::null_theory_var);
+        m_merge_aux[1].reserve(bv_size + 1, euf::null_theory_var);
+
+        struct scoped_reset {
+            solver& s;
+            zero_one_bits& bits1;
+            scoped_reset(solver& s, zero_one_bits& bits1) :s(s), bits1(bits1) {}
+            ~scoped_reset() {
+                for (auto& zo : bits1)
+                    s.m_merge_aux[zo.m_is_true][zo.m_idx] = euf::null_theory_var;
+            }
+        };
+        scoped_reset _sr(*this, bits1);
+
+        DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) SASSERT(m_merge_aux[0][i] == euf::null_theory_var || m_merge_aux[1][i] == euf::null_theory_var););
+
+        // save info about bits1
+        for (auto& zo : bits1)
+            m_merge_aux[zo.m_is_true][zo.m_idx] = zo.m_owner;
+        // check if bits2 is consistent with bits1, and copy new bits to bits1
+        for (auto& zo : bits2) {
+            theory_var v2 = zo.m_owner;
+            theory_var v1 = m_merge_aux[!zo.m_is_true][zo.m_idx];
+            if (v1 != euf::null_theory_var) {
+                // conflict was detected ... v1 and v2 have complementary bits
+                SASSERT(m_bits[v1][zo.m_idx] == ~(m_bits[v2][zo.m_idx]));
+                SASSERT(m_bits[v1].size() == m_bits[v2].size());
+                mk_new_diseq_axiom(v1, v2, zo.m_idx);
+                return false;
+            }
+            // copy missing variable to bits1
+            if (m_merge_aux[zo.m_is_true][zo.m_idx] == euf::null_theory_var)
+                bits1.push_back(zo);
+        }
+        // reset m_merge_aux vector
+        DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) { SASSERT(m_merge_aux[0][i] == euf::null_theory_var || m_merge_aux[1][i] == euf::null_theory_var); });
+        return true;
+    }
+
+    rational const& solver::power2(unsigned i) const {
+        while (m_power2.size() <= i)
+            m_power2.push_back(m_bb.power(m_power2.size()));
+        return m_power2[i];
+    }
+
+    /**
+        \brief Check whether m_zero_one_bits is an accurate summary of the bits in the
+        equivalence class rooted by v.
+        \remark The method does nothing if v is not the root of the equivalence class.
+    */
+    bool solver::check_zero_one_bits(theory_var v) {
+        if (s().inconsistent())
+            return true; // property is only valid if the context is not in a conflict.
+        if (!is_root(v) || !is_bv(v))
+            return true;
+        bool_vector bits[2];
+        unsigned      num_bits = 0;
+        unsigned      bv_sz = get_bv_size(v);
+        bits[0].resize(bv_sz, false);
+        bits[1].resize(bv_sz, false);
+
+        theory_var curr = v;
+        do {
+            literal_vector const& lits = m_bits[curr];
+            for (unsigned i = 0; i < lits.size(); i++) {
+                literal l = lits[i];
+                if (s().value(l) != l_undef) {
+                    unsigned is_true = s().value(l) == l_true;
+                    if (bits[!is_true][i]) {
+                        // expect a conflict later on.
+                        return true;
+                    }
+                    if (!bits[is_true][i]) {
+                        bits[is_true][i] = true;
+                        num_bits++;
+                    }
+                }
+            }
+            curr = m_find.next(curr);
+        } while (curr != v);
+
+        zero_one_bits const& _bits = m_zero_one_bits[v];
+        SASSERT(_bits.size() == num_bits);
+        bool_vector already_found;
+        already_found.resize(bv_sz, false);
+        for (auto& zo : _bits) {
+            SASSERT(find(zo.m_owner) == v);
+            SASSERT(bits[zo.m_is_true][zo.m_idx]);
+            SASSERT(!already_found[zo.m_idx]);
+            already_found[zo.m_idx] = true;
+        }
+        return true;
+    }
 }
diff --git a/src/sat/smt/bv_solver.h b/src/sat/smt/bv_solver.h
index 3c2df99fe..179d96ba4 100644
--- a/src/sat/smt/bv_solver.h
+++ b/src/sat/smt/bv_solver.h
@@ -17,8 +17,13 @@ Author:
 #pragma once
 
 #include "sat/smt/sat_th.h"
+#include "sat/smt/bv_ackerman.h"
 #include "ast/rewriter/bit_blaster/bit_blaster.h"
 
+namespace euf {
+    class solver;
+}
+
 namespace bv {
 
     class solver : public euf::th_euf_solver {
@@ -32,15 +37,42 @@ namespace bv {
         typedef std::pair<numeral, unsigned> value_sort_pair;
         typedef pair_hash<obj_hash<numeral>, unsigned_hash> value_sort_pair_hash;
         typedef map<value_sort_pair, theory_var, value_sort_pair_hash, default_eq<value_sort_pair> > value2var;
-        typedef union_find<solver>  th_union_find;
+        typedef union_find<solver, euf::solver>  bv_union_find;
+        typedef std::pair<theory_var, unsigned> var_pos;
+
+        friend class ackerman;
 
         struct stats {
             unsigned   m_num_diseq_static, m_num_diseq_dynamic, m_num_bit2core, m_num_th2core_eq, m_num_conflicts;
-            unsigned   m_num_eq_dynamic;
+            unsigned   m_ackerman;
             void reset() { memset(this, 0, sizeof(stats)); }
             stats() { reset(); }
         };
 
+        struct bv_justification {
+            enum kind_t { bv2bit, bit2bv };
+            kind_t     m_kind;
+            theory_var m_v1;
+            theory_var m_v2;
+            sat::literal m_consequent;
+            sat::literal m_antecedent;
+            bv_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a) :
+                m_kind(kind_t::bv2bit), m_v1(v1), m_v2(v2), m_consequent(c), m_antecedent(a) {}
+            bv_justification(theory_var v1, theory_var v2):
+                m_kind(kind_t::bit2bv), m_v1(v1), m_v2(v2) {}
+            sat::ext_constraint_idx to_index() const { 
+                return sat::constraint_base::mem2base(this); 
+            }
+            static bv_justification& from_index(size_t idx) {
+                return *reinterpret_cast<bv_justification*>(sat::constraint_base::from_index(idx)->mem());
+            }
+            static size_t get_obj_size() { return sat::constraint_base::obj_size(sizeof(bv_justification)); }
+        };
+
+        sat::justification mk_bv2bit_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a);
+        sat::ext_justification_idx mk_bit2bv_justification(theory_var v1, theory_var v2);
+        void log_drat(bv_justification const& c);
+ 
 
         /**
            \brief Structure used to store the position of a bitvector variable that
@@ -66,17 +98,31 @@ namespace bv {
 
         typedef svector<zero_one_bit> zero_one_bits;
 
+        struct bit_atom;
+        struct def_atom;
         class atom {
         public:
             virtual ~atom() {}
             virtual bool is_bit() const = 0;
+            bit_atom& to_bit();
+            def_atom& to_def();
         };
 
         struct var_pos_occ {
-            theory_var    m_var;
-            unsigned      m_idx;
+            var_pos       m_vp;
             var_pos_occ * m_next;
-            var_pos_occ(theory_var v = euf::null_theory_var, unsigned idx = 0, var_pos_occ * next = nullptr):m_var(v), m_idx(idx), m_next(next) {}
+            var_pos_occ(theory_var v = euf::null_theory_var, unsigned idx = 0, var_pos_occ * next = nullptr):m_vp(v, idx), m_next(next) {}
+        };
+
+        class var_pos_it {
+            var_pos_occ* m_first;
+        public:
+            var_pos_it(var_pos_occ* c) : m_first(c) {}
+            var_pos operator*() { return m_first->m_vp; }
+            var_pos_it& operator++() { m_first = m_first->m_next; return *this; }
+            var_pos_it operator++(int) { var_pos_it tmp = *this; ++* this; return tmp; }
+            bool operator==(var_pos_it const& other) const { return m_first == other.m_first; }
+            bool operator!=(var_pos_it const& other) const { return !(*this == other); }
         };
 
         struct bit_atom : public atom {
@@ -84,44 +130,51 @@ namespace bv {
             bit_atom():m_occs(nullptr) {}
             ~bit_atom() override {}
             bool is_bit() const override { return true; }
+            var_pos_it begin() const { return var_pos_it(m_occs); }
+            var_pos_it end() const { return var_pos_it(nullptr); }
         };
 
-        struct le_atom : public atom {
+        struct def_atom : public atom {
             literal    m_var;
             literal    m_def;
-            le_atom(literal v, literal d):m_var(v), m_def(d) {}
-            ~le_atom() override {}
+            def_atom(literal v, literal d):m_var(v), m_def(d) {}
+            ~def_atom() override {}
             bool is_bit() const override { return false; }
         };
 
-        friend class add_var_pos_trail;
-        friend class mk_atom_trail;
+        class bit_trail;
+        class add_var_pos_trail;
+        class mk_atom_trail;
         typedef ptr_vector<atom> bool_var2atom;
 
         bv_util                  bv;
         arith_util               m_autil;
         stats                    m_stats;
+        ackerman                 m_ackerman;
         bit_blaster              m_bb;
-        th_union_find            m_find;
+        bv_union_find            m_find;
         vector<literal_vector>   m_bits;     // per var, the bits of a given variable.
-        ptr_vector<expr>         m_bits_expr;
-        svector<unsigned>        m_wpos;     // per var, watch position for fixed variable detection. 
+        unsigned_vector          m_wpos;     // per var, watch position for fixed variable detection. 
         vector<zero_one_bits>    m_zero_one_bits; // per var, see comment in the struct zero_one_bit
         bool_var2atom            m_bool_var2atom;
+        value2var                m_fixed_var_table;
+        mutable vector<rational> m_power2;
+        literal_vector           m_tmp_literals;
+        svector<var_pos>         m_prop_queue;
+        unsigned_vector          m_prop_queue_lim;
+        unsigned                 m_prop_queue_head { 0 };
+
+
         sat::solver* m_solver;
         sat::solver& s() { return *m_solver;  }
 
-        // internalize:
-
+        // internalize
         void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); }
         void erase_bv2a(bool_var bv) { m_bool_var2atom[bv] = 0; }
         atom * get_bv2a(bool_var bv) const { return m_bool_var2atom.get(bv, 0); }
-
-        sat::literal false_literal;
-        sat::literal true_literal;
         bool visit(expr* e) override;
         bool visited(expr* e) override;
-        bool post_visit(expr* e, bool sign, bool root) override { return true; }
+        bool post_visit(expr* e, bool sign, bool root) override;
         unsigned get_bv_size(euf::enode* n);
         unsigned get_bv_size(theory_var v);
         theory_var get_var(euf::enode* n);
@@ -129,66 +182,55 @@ namespace bv {
         inline theory_var get_arg_var(euf::enode* n, unsigned idx);
         void get_bits(theory_var v, expr_ref_vector& r);
         void get_bits(euf::enode* n, expr_ref_vector& r);
-        void get_arg_bits(euf::enode* n, unsigned idx, expr_ref_vector& r);
         void get_arg_bits(app* n, unsigned idx, expr_ref_vector& r);
-        euf::enode* mk_enode(expr* n, ptr_vector<euf::enode> const& args);
         void fixed_var_eh(theory_var v);
-
+        bool is_bv(theory_var v) const { return bv.is_bv(var2expr(v)); }
         sat::status status() const { return sat::status::th(m_is_redundant, get_id());  }
-        void add_unit(sat::literal lit);
         void register_true_false_bit(theory_var v, unsigned i);
         void add_bit(theory_var v, sat::literal lit);
-        void init_bits(euf::enode * n, expr_ref_vector const & bits);
+        void set_bit_eh(theory_var v, literal l, unsigned idx);
+        void init_bits(expr* e, expr_ref_vector const & bits);
         void mk_bits(theory_var v);
-        expr_ref mk_bit2bool(expr* b, unsigned idx);
-        void internalize_num(app * n, theory_var v);
-        void internalize_add(app * n);
-        void internalize_sub(app * n);
-        void internalize_mul(app * n);
-        void internalize_udiv(app * n);
-        void internalize_sdiv(app * n);
-        void internalize_urem(app * n);
-        void internalize_srem(app * n);
-        void internalize_smod(app * n);
-        void internalize_shl(app * n);
-        void internalize_lshr(app * n);
-        void internalize_ashr(app * n);
-        void internalize_ext_rotate_left(app * n);
-        void internalize_ext_rotate_right(app * n);
-        void internalize_and(app * n);
-        void internalize_or(app * n);
-        void internalize_not(app * n);
-        void internalize_nand(app * n);
-        void internalize_nor(app * n);
-        void internalize_xor(app * n);
-        void internalize_xnor(app * n);
-        void internalize_concat(app * n);
-        void internalize_sign_extend(app * n);
-        void internalize_zero_extend(app * n);
-        void internalize_extract(app * n);
-        void internalize_redand(app * n);
-        void internalize_redor(app * n);
-        void internalize_comp(app * n);
-        void internalize_rotate_left(app * n);
-        void internalize_rotate_right(app * n);
+        void add_def(sat::literal def, sat::literal l);
+        void internalize_unary(app* n, std::function<void(unsigned, expr* const*, expr_ref_vector&)>& fn);
+        void internalize_binary(app* n, std::function<void(unsigned, expr* const*, expr* const*, expr_ref_vector&)>& fn);
+        void internalize_ac_binary(app* n, std::function<void(unsigned, expr* const*, expr* const*, expr_ref_vector&)>& fn);
+        void internalize_par_unary(app* n, std::function<void(unsigned, expr* const*, unsigned p, expr_ref_vector&)>& fn);
+        void internalize_novfl(app* n, std::function<void(unsigned, expr* const*, expr* const*, expr_ref&)>& fn);
+        void internalize_num(app * n, theory_var v);       
+        void internalize_concat(app * n);        
         void internalize_bv2int(app* n);
         void internalize_int2bv(app* n);
         void internalize_mkbv(app* n);
-        void internalize_umul_no_overflow(app *n);
-        void internalize_smul_no_overflow(app *n);
-        void internalize_smul_no_underflow(app *n);
-
+        void internalize_xor3(app* n);
+        void internalize_carry(app* n);
+        void internalize_sub(app* n);
+        void internalize_extract(app* n);
+        void internalize_bit2bool(app* n);
+        template<bool Signed>
+        void internalize_le(app* n);
         void assert_bv2int_axiom(app * n);
         void assert_int2bv_axiom(app* n);
+        void assert_ackerman(theory_var v1, theory_var v2);
 
         // solving
+        theory_var find(theory_var v) const { return m_find.find(v); }
         void find_wpos(theory_var v);
-        void find_new_diseq_axioms(var_pos_occ* occs, theory_var v, unsigned idx);
+        void find_new_diseq_axioms(bit_atom& a, theory_var v, unsigned idx);
         void mk_new_diseq_axiom(theory_var v1, theory_var v2, unsigned idx);
+        bool get_fixed_value(theory_var v, numeral& result) const;
+        void add_fixed_eq(theory_var v1, theory_var v2);      
+        svector<theory_var>   m_merge_aux[2]; //!< auxiliary vector used in merge_zero_one_bits
+        bool merge_zero_one_bits(theory_var r1, theory_var r2);
+        void assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc);
+        void propagate_bits(var_pos entry);
+        numeral const& power2(unsigned i) const;
 
-
+        // invariants
+        bool check_zero_one_bits(theory_var v);
+       
     public:
-        solver(euf::solver& ctx);
+        solver(euf::solver& ctx, theory_id id);
         ~solver() override {}
         void set_solver(sat::solver* s) override { m_solver = s; }
         void set_lookahead(sat::lookahead* s) override { }
@@ -197,19 +239,22 @@ namespace bv {
         bool is_extended_binary(sat::ext_justification_idx idx, literal_vector& r) override;
         bool is_external(bool_var v) override;
         bool propagate(literal l, sat::ext_constraint_idx idx) override;
-        void get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector & r) override;
+        void get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector & r, bool probing) override;
         void asserted(literal l) override;
         sat::check_result check() override;
         void push() override;
         void pop(unsigned n) override;
         void pre_simplify() override;        
         void simplify() override;
+        bool set_root(literal l, literal r) override;
+        void flush_roots() override;
         void clauses_modifed() override;
         lbool get_phase(bool_var v) override;
         std::ostream& display(std::ostream& out) const override;
         std::ostream& display_justification(std::ostream& out, sat::ext_justification_idx idx) const override;
         std::ostream& display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const override;
         void collect_statistics(statistics& st) const override;
+        euf::th_solver* fresh(sat::solver* s, euf::solver& ctx) override;
         extension* copy(sat::solver* s) override;       
         void find_mutexes(literal_vector& lits, vector<literal_vector> & mutexes) override {}
         void gc() override {}
@@ -220,18 +265,31 @@ namespace bv {
         bool check_model(sat::model const& m) const override;
         unsigned max_var(unsigned w) const override;
 
+        void new_eq_eh(euf::th_eq const& eq) override;
+        bool unit_propagate() override;
+
+        void add_value(euf::enode* n, expr_ref_vector& values) override;
+
         bool extract_pb(std::function<void(unsigned sz, literal const* c, unsigned k)>& card,
                         std::function<void(unsigned sz, literal const* c, unsigned const* coeffs, unsigned k)>& pb) override { return false; }
 
         bool to_formulas(std::function<expr_ref(sat::literal)>& l2e, expr_ref_vector& fmls) override { return false; }
         sat::literal internalize(expr* e, bool sign, bool root, bool learned) override;
+        void internalize(expr* e, bool redundant) override;
         euf::theory_var mk_var(euf::enode* n) override;
+        void apply_sort_cnstr(euf::enode * n, sort * s) override;
 
+        
+        void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2);
+        void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { SASSERT(check_zero_one_bits(r1)); }
+        void unmerge_eh(theory_var v1, theory_var v2);
+        trail_stack<euf::solver>& get_trail_stack();
 
         // disagnostics
-        std::ostream& display(std::ostream& out, theory_var v) const;
+        std::ostream& display(std::ostream& out, theory_var v) const;        
         typedef std::pair<solver const*, theory_var> pp_var;
         pp_var pp(theory_var v) const { return pp_var(this, v); }
+
     };
 
     inline std::ostream& operator<<(std::ostream& out, solver::pp_var const& p) { return p.first->display(out, p.second); }
diff --git a/src/sat/smt/euf_ackerman.cpp b/src/sat/smt/euf_ackerman.cpp
index 77d586655..a551db4de 100644
--- a/src/sat/smt/euf_ackerman.cpp
+++ b/src/sat/smt/euf_ackerman.cpp
@@ -58,37 +58,10 @@ namespace euf {
         inf.b = b;
         inf.c = nullptr;
         inf.is_cc = true;
+        inf.m_count = 0;
         insert();
     }
 
-    void ackerman::remove_from_queue(inference* inf) {
-        if (m_queue->m_next == m_queue) {
-            SASSERT(inf == m_queue);
-            m_queue = nullptr;
-            return;
-        }            
-        if (m_queue == inf) 
-            m_queue = inf->m_next;
-        auto* next = inf->m_next;
-        auto* prev = inf->m_prev;
-        prev->m_next = next;
-        next->m_prev = prev;        
-    }
-
-    void ackerman::push_to_front(inference* inf) {
-        if (!m_queue) {
-            m_queue = inf;
-        }
-        else if (m_queue != inf) {
-            auto* next = inf->m_next;
-            auto* prev = inf->m_prev;
-            prev->m_next = next;
-            next->m_prev = prev;
-            inf->m_prev = m_queue->m_prev;
-            inf->m_next = m_queue;
-            m_queue->m_prev = inf;
-        }
-    }
 
     void ackerman::insert() {
         inference* inf = m_tmp_inference;
@@ -97,15 +70,16 @@ namespace euf {
             m.inc_ref(inf->a);
             m.inc_ref(inf->b);
             m.inc_ref(inf->c);
-        }
-        else 
             new_tmp();        
+        }
         other->m_count++;
-        push_to_front(other);
+        if (other->m_count > m_high_watermark) 
+            s.s().set_should_simplify();
+        inference::push_to_front(m_queue, other);
     }
 
     void ackerman::remove(inference* inf) {
-        remove_from_queue(inf);
+        inference::remove_from(m_queue, inf);
         m_table.erase(inf);
         m.dec_ref(inf->a);
         m.dec_ref(inf->b);
@@ -115,13 +89,10 @@ namespace euf {
 
     void ackerman::new_tmp() {
         m_tmp_inference = alloc(inference);
-        m_tmp_inference->m_next = m_tmp_inference->m_prev = m_tmp_inference;
-        m_tmp_inference->m_count = 0;
+        m_tmp_inference->init(m_tmp_inference);
     }
 
     void ackerman::cg_conflict_eh(expr * n1, expr * n2) {
-        if (s.m_config.m_dack != DACK_ROOT)
-            return;
         if (!is_app(n1) || !is_app(n2))
             return;
         app* a = to_app(n1);
@@ -133,8 +104,6 @@ namespace euf {
     }
 
     void ackerman::used_eq_eh(expr* a, expr* b, expr* c) {
-        if (!s.m_config.m_dack_eq)
-            return;
         if (a == b || a == c || b == c)
             return;
         insert(a, b, c);
@@ -142,8 +111,6 @@ namespace euf {
     }
         
     void ackerman::used_cc_eh(app* a, app* b) {
-        if (s.m_config.m_dack != DACK_CR)
-            return;
         SASSERT(a->get_decl() == b->get_decl());
         SASSERT(a->get_num_args() == b->get_num_args());        
         insert(a, b);
@@ -157,8 +124,8 @@ namespace euf {
         m_num_propagations_since_last_gc = 0;
         
         while (m_table.size() > m_gc_threshold) 
-            remove(m_queue->m_prev);     
-
+            remove(m_queue->prev());
+    
         m_gc_threshold *= 110;
         m_gc_threshold /= 100;
         m_gc_threshold++;
@@ -171,13 +138,16 @@ namespace euf {
         unsigned num_prop = static_cast<unsigned>(s.s().get_stats().m_conflict * s.m_config.m_dack_factor);
         num_prop = std::min(num_prop, m_table.size());
         for (unsigned i = 0; i < num_prop; ++i, n = k) {
-            k = n->m_next;
+            k = n->next();
             if (n->m_count < s.m_config.m_dack_threshold) 
                 continue;
+            if (n->m_count >= m_high_watermark && num_prop < m_table.size())
+                ++num_prop;
             if (n->is_cc) 
                 add_cc(n->a, n->b);
             else 
-                add_eq(n->a, n->b, n->c);           
+                add_eq(n->a, n->b, n->c);       
+            ++s.m_stats.m_ackerman;
             remove(n);
         }
     }
diff --git a/src/sat/smt/euf_ackerman.h b/src/sat/smt/euf_ackerman.h
index aa92d88ec..d794d0276 100644
--- a/src/sat/smt/euf_ackerman.h
+++ b/src/sat/smt/euf_ackerman.h
@@ -16,6 +16,7 @@ Author:
 --*/
 #pragma once
 
+#include "util/dlist.h"
 #include "ast/euf/euf_egraph.h"
 #include "sat/smt/atom2bool_var.h"
 #include "sat/smt/sat_th.h"
@@ -24,13 +25,12 @@ namespace euf {
 
     class solver;
 
+
     class ackerman {
 
-        struct inference {
+        struct inference : dll_base<inference>{
             bool is_cc;
             expr* a, *b, *c;
-            inference* m_next{ nullptr };
-            inference* m_prev{ nullptr };
             unsigned   m_count{ 0 };
             inference():is_cc(false), a(nullptr), b(nullptr), c(nullptr) {}
             inference(app* a, app* b):is_cc(true), a(a), b(b), c(nullptr) {}
@@ -58,6 +58,7 @@ namespace euf {
         inference*    m_queue { nullptr };
         inference*    m_tmp_inference { nullptr };
         unsigned      m_gc_threshold { 1 };
+        unsigned      m_high_watermark { 1000 };
         unsigned      m_num_propagations_since_last_gc { 0 };
  
         void reset();
@@ -69,8 +70,6 @@ namespace euf {
         void add_cc(expr* a, expr* b);
         void add_eq(expr* a, expr* b, expr* c);        
         void gc();
-        void push_to_front(inference* inf);        
-        void remove_from_queue(inference* inf);
 
     public:
         ackerman(solver& s, ast_manager& m);
diff --git a/src/sat/smt/euf_internalize.cpp b/src/sat/smt/euf_internalize.cpp
index 30d8abc9a..0898f8847 100644
--- a/src/sat/smt/euf_internalize.cpp
+++ b/src/sat/smt/euf_internalize.cpp
@@ -21,17 +21,27 @@ Author:
 
 namespace euf {
 
-    sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) {
+    void solver::internalize(expr* e, bool redundant) {
         if (si.is_bool_op(e))
-            return si.internalize(e, redundant);
-        auto* ext = get_solver(e);
-        if (ext)
-            return ext->internalize(e, sign, root, redundant);
+            attach_lit(si.internalize(e, redundant), e);        
+        else if (auto* ext = get_solver(e))
+            ext->internalize(e, redundant);
+        else
+            visit_rec(m, e, false, false, redundant);
+        SASSERT(m_egraph.find(e));
+    }
 
+    sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) {
+        if (si.is_bool_op(e)) 
+            return attach_lit(si.internalize(e, redundant), e);        
+        if (auto* ext = get_solver(e))
+            return ext->internalize(e, sign, root, redundant);
         if (!visit_rec(m, e, sign, root, redundant))
             return sat::null_literal;
         SASSERT(m_egraph.find(e));
-        return literal(m_expr2var.to_bool_var(e), sign);
+        if (m.is_bool(e))
+            return literal(si.to_bool_var(e), sign);
+        return sat::null_literal;
     }
 
     bool solver::visit(expr* e) {
@@ -39,15 +49,7 @@ namespace euf {
         if (n)
             return true;
         if (si.is_bool_op(e)) {
-            sat::literal lit = si.internalize(e, m_is_redundant);
-            n = m_var2node.get(lit.var(), nullptr);
-            if (n && !lit.sign())
-                return n;
-            
-            n = m_egraph.mk(e, 0, nullptr);
-            attach_lit(lit, n);
-            if (!m.is_true(e) && !m.is_false(e)) 
-                s().set_external(lit.var());
+            attach_lit(si.internalize(e, m_is_redundant), e);
             return true;
         }
         if (is_app(e) && to_app(e)->get_num_args() > 0) {
@@ -64,8 +66,12 @@ namespace euf {
         m_args.reset();
         for (unsigned i = 0; i < num; ++i)
             m_args.push_back(m_egraph.find(to_app(e)->get_arg(i)));
-        if (root && internalize_root(to_app(e), sign))
+        if (root && internalize_root(to_app(e), sign, m_args))
             return false;
+        if (auto* s = get_solver(e)) {
+            s->internalize(e, m_is_redundant);
+            return true;
+        }
         enode* n = m_egraph.mk(e, num, m_args.c_ptr());
         attach_node(n);
         return true;
@@ -77,33 +83,45 @@ namespace euf {
 
     void solver::attach_node(euf::enode* n) {
         expr* e = n->get_owner();
-        log_node(n);
-        if (m.is_bool(e)) {
-            sat::bool_var v = si.add_bool_var(e);
-            log_bool_var(v, n);
-            attach_lit(literal(v, false),  n);
+        if (!m.is_bool(e))
+            log_node(e);
+        else 
+            attach_lit(literal(si.add_bool_var(e), false),  e);        
+
+        if (!m.is_bool(e) && m.get_sort(e)->get_family_id() != null_family_id) {
+            auto* e_ext = get_solver(e);
+            auto* s_ext = get_solver(m.get_sort(e));
+            if (s_ext && s_ext != e_ext)
+                s_ext->apply_sort_cnstr(n, m.get_sort(e));
         }
         axiomatize_basic(n);
     }
 
-    void solver::attach_lit(literal lit, euf::enode* n) {
+    sat::literal solver::attach_lit(literal lit, expr* e) {
         if (lit.sign()) {
-            sat::bool_var v = si.add_bool_var(n->get_owner());
+            sat::bool_var v = si.add_bool_var(e);
+            s().set_external(v);
             sat::literal lit2 = literal(v, false);
-            s().mk_clause(~lit, lit2, sat::status::th(false, m.get_basic_family_id()));
-            s().mk_clause(lit, ~lit2, sat::status::th(false, m.get_basic_family_id()));
+            s().mk_clause(~lit, lit2, sat::status::asserted());
+            s().mk_clause(lit, ~lit2, sat::status::asserted());
             lit = lit2;
         }
         sat::bool_var v = lit.var();
-        m_var2node.reserve(v + 1, nullptr);
-        SASSERT(m_var2node[v] == nullptr);
-        m_var2node[v] = n;
+        m_var2expr.reserve(v + 1, nullptr);
+        SASSERT(m_var2expr[v] == nullptr);
+        m_var2expr[v] = e;
         m_var_trail.push_back(v);
+        s().set_external(v);
+        if (!m_egraph.find(e)) {
+            enode* n = m_egraph.mk(e, 0, nullptr);
+            m_egraph.set_merge_enabled(n, false);
+        }
+        return lit;
     }
 
-    bool solver::internalize_root(app* e, bool sign) {
+    bool solver::internalize_root(app* e, bool sign, enode_vector const& args) {
         if (m.is_distinct(e)) {
-            enode_vector _args(m_args);
+            enode_vector _args(args);
             if (sign)
                 add_not_distinct_axiom(e, _args.c_ptr());
             else
@@ -202,7 +220,7 @@ namespace euf {
             expr* c = a->get_arg(0);
             expr* th = a->get_arg(1);
             expr* el = a->get_arg(2);
-            sat::bool_var v = m_expr2var.to_bool_var(c);
+            sat::bool_var v = si.to_bool_var(c);
             SASSERT(v != sat::null_bool_var);
             SASSERT(!m.is_bool(e));
             expr_ref eq_th(m.mk_eq(a, th), m);
@@ -224,7 +242,7 @@ namespace euf {
                 }
             }
             expr_ref fml(m.mk_or(eqs), m);
-            sat::literal dist(m_expr2var.to_bool_var(e), false);
+            sat::literal dist(si.to_bool_var(e), false);
             sat::literal some_eq = si.internalize(fml, m_is_redundant);
             sat::literal lits1[2] = { ~dist, ~some_eq };
             sat::literal lits2[2] = { dist, some_eq };
diff --git a/src/sat/smt/euf_invariant.cpp b/src/sat/smt/euf_invariant.cpp
new file mode 100644
index 000000000..bb5e0c688
--- /dev/null
+++ b/src/sat/smt/euf_invariant.cpp
@@ -0,0 +1,48 @@
+/*++
+Copyright (c) 2020 Microsoft Corporation
+
+Module Name:
+
+    euf_invariant.cpp
+
+Abstract:
+
+    Check invariants for EUF solver.
+
+Author:
+
+    Nikolaj Bjorner (nbjorner) 2020-09-07
+
+--*/
+
+#include "sat/smt/euf_solver.h"
+
+namespace euf {
+
+    /**
+       \brief Check if expressions attached to bool_variables and enodes have a consistent assignment.
+       For all a, b.  root(a) == root(b) ==> get_assignment(a) == get_assignment(b)
+    */
+
+    void solver::check_eqc_bool_assignment() const {
+        for (enode* n : m_egraph.nodes()) {
+            VERIFY(!m.is_bool(n->get_owner()) || 
+                   s().value(get_literal(n)) == s().value(get_literal(n->get_root())));        
+        }
+    }
+
+    void solver::check_missing_bool_enode_propagation() const {
+        for (enode* n : m_egraph.nodes()) 
+            if (m.is_bool(n->get_owner()) && l_undef == s().value(get_literal(n))) {
+                if (!n->is_root()) {
+                    VERIFY(l_undef == s().value(get_literal(n->get_root())));
+                }
+                else
+                    for (enode* o : enode_class(n)) {
+                        VERIFY(l_undef == s().value(get_literal(o)));
+                    }
+            }
+    }
+
+
+}
diff --git a/src/sat/smt/euf_model.cpp b/src/sat/smt/euf_model.cpp
index a56d8f4cb..26f6106d9 100644
--- a/src/sat/smt/euf_model.cpp
+++ b/src/sat/smt/euf_model.cpp
@@ -16,6 +16,7 @@ Author:
 --*/
 
 #include "ast/ast_pp.h"
+#include "ast/ast_ll_pp.h"
 #include "sat/smt/euf_solver.h"
 #include "model/value_factory.h"
 
@@ -79,7 +80,7 @@ namespace euf {
                 }
                 if (is_app(e) && to_app(e)->get_family_id() == m.get_basic_family_id())
                     continue;
-                sat::bool_var v = m_expr2var.to_bool_var(e);
+                sat::bool_var v = si.to_bool_var(e);
                 SASSERT(v != sat::null_bool_var);
                 switch (s().value(v)) {
                 case l_true:
@@ -100,6 +101,8 @@ namespace euf {
                 expr* v = user_sort.get_fresh_value(m.get_sort(e));
                 values.set(id, v);
             }
+            else if ((mb = get_solver(m.get_sort(e))))
+                mb->add_value(n, values);
             else {
                 IF_VERBOSE(1, verbose_stream() << "no model values created for " << mk_pp(e, m) << "\n");
             }                
@@ -119,6 +122,9 @@ namespace euf {
             if (m.is_bool(e) && is_uninterp_const(e) && mdl->get_const_interp(f))
                 continue;
             expr* v = values.get(n->get_root_id());
+            CTRACE("euf", !v, tout << "no value for " << mk_pp(e, m) << "\n";);
+            if (!v)
+                continue;
             unsigned arity = f->get_arity();
             if (arity == 0) 
                 mdl->register_decl(f, v);
@@ -129,8 +135,11 @@ namespace euf {
                     mdl->register_decl(f, fi);
                 }
                 args.reset();
-                for (enode* arg : enode_args(n)) 
+                for (enode* arg : enode_args(n)) {
                     args.push_back(values.get(arg->get_root_id()));
+                    SASSERT(args.back());
+                }
+                SASSERT(args.size() == arity);
                 if (!fi->get_entry(args.c_ptr()))
                     fi->insert_new_entry(args.c_ptr(), v);
             }
diff --git a/src/sat/smt/euf_proof.cpp b/src/sat/smt/euf_proof.cpp
index 930da2c36..f591161d7 100644
--- a/src/sat/smt/euf_proof.cpp
+++ b/src/sat/smt/euf_proof.cpp
@@ -19,53 +19,60 @@ Author:
 
 namespace euf {
 
-    void solver::log_node(enode* n) {
-        if (m_drat) {
-            expr* e = n->get_owner();
-            if (is_app(e)) {
-                symbol const& name = to_app(e)->get_name();
-                s().get_drat().def_begin(n->get_owner_id(), name);
-                for (enode* arg : enode_args(n)) {
-                    s().get_drat().def_add_arg(arg->get_owner_id());
-                }
-                s().get_drat().def_end();
-            }
-            else {
-                IF_VERBOSE(0, verbose_stream() << "logging binders is TBD\n");
-            }
-        }
-    }
-
-    void solver::log_bool_var(sat::bool_var v, enode* n) {
-        if (m_drat) {
-            s().get_drat().bool_def(v, n->get_owner_id());
+    void solver::init_drat() {
+        if (!m_drat_initialized)
+            get_drat().add_theory(m.get_basic_family_id(), symbol("euf"));
+        m_drat_initialized = true;
+    }
+
+    void solver::log_node(expr* e) {
+        if (!use_drat())
+            return;
+        if (is_app(e)) {
+            std::stringstream strm;
+            strm << mk_ismt2_func(to_app(e)->get_decl(), m);
+            get_drat().def_begin(e->get_id(), strm.str());
+            for (expr* arg : *to_app(e))
+                get_drat().def_add_arg(arg->get_id());
+            get_drat().def_end();
+        }
+        else {
+            IF_VERBOSE(0, verbose_stream() << "logging binders is TBD\n");
         }
     }
 
+    /**
+     * \brief logs antecedents to a proof trail.
+     *
+     * NB with theories, this is not a pure EUF justification,
+     * It is true modulo EUF and previously logged certificates
+     * so it isn't necessarily an axiom over EUF,
+     * We will here leave it to the EUF checker to perform resolution steps.
+     */
     void solver::log_antecedents(literal l, literal_vector const& r) {
         TRACE("euf", log_antecedents(tout, l, r););
-        if (m_drat) {
-            literal_vector lits;
-            for (literal lit : r) lits.push_back(~lit);
-            if (l != sat::null_literal)
-                lits.push_back(l);
-            s().get_drat().add(lits, sat::status::th(true, m.get_basic_family_id()));
-        }
+        if (!use_drat())
+            return;
+        literal_vector lits;
+        for (literal lit : r) lits.push_back(~lit);
+        if (l != sat::null_literal)
+            lits.push_back(l);
+        get_drat().add(lits, sat::status::th(true, m.get_basic_family_id()));
     }
 
     void solver::log_antecedents(std::ostream& out, literal l, literal_vector const& r) {
         for (sat::literal l : r) {
-            enode* n = m_var2node[l.var()];
+            expr* n = m_var2expr[l.var()];
             out << ~l << ": ";
             if (!l.sign()) out << "! ";
-            out << mk_pp(n->get_owner(), m) << "\n";
+            out << mk_pp(n, m) << "\n";
             SASSERT(s().value(l) == l_true);
         }
         if (l != sat::null_literal) {
             out << l << ": ";
             if (l.sign()) out << "! ";
-            enode* n = m_var2node[l.var()];
-            out << mk_pp(n->get_owner(), m) << "\n";            
+            expr* n = m_var2expr[l.var()];
+            out << mk_pp(n, m) << "\n";
         }
     }
 
diff --git a/src/sat/smt/euf_solver.cpp b/src/sat/smt/euf_solver.cpp
index 4dfe1f152..171f329ca 100644
--- a/src/sat/smt/euf_solver.cpp
+++ b/src/sat/smt/euf_solver.cpp
@@ -20,27 +20,25 @@ Author:
 #include "sat/sat_solver.h"
 #include "sat/smt/sat_smt.h"
 #include "sat/smt/ba_solver.h"
+#include "sat/smt/bv_solver.h"
 #include "sat/smt/euf_solver.h"
 
 namespace euf {
 
     void solver::updt_params(params_ref const& p) {
         m_config.updt_params(p);
-        m_drat = m_solver && m_solver->get_config().m_drat;
-        if (m_drat) 
-            m_solver->get_drat().add_theory(m.get_basic_family_id(), symbol("euf"));
     }
 
     /**
     * retrieve extension that is associated with Boolean variable.
     */
     th_solver* solver::get_solver(sat::bool_var v) {
-        if (v >= m_var2node.size())
+        if (v >= m_var2expr.size())
             return nullptr;
-        euf::enode* n = m_var2node[v];
-        if (!n)
+        expr* e = m_var2expr[v];
+        if (!e)
             return nullptr;
-        return get_solver(n->get_owner());
+        return get_solver(e);
     }
 
     th_solver* solver::get_solver(expr* e) {
@@ -49,8 +47,7 @@ namespace euf {
         return nullptr;
     }
 
-    th_solver* solver::get_solver(func_decl* f) {
-        family_id fid = f->get_family_id();
+    th_solver* solver::get_solver(family_id fid, func_decl* f) {
         if (fid == null_family_id)
             return nullptr;
         auto* ext = m_id2solver.get(fid, nullptr);
@@ -59,19 +56,24 @@ namespace euf {
         if (fid == m.get_basic_family_id())
             return nullptr;
         pb_util pb(m);
+        bv_util bvu(m);
         if (pb.get_family_id() == fid) {
             ext = alloc(sat::ba_solver, *this, fid);
-            if (m_drat)
-                m_solver->get_drat().add_theory(fid, symbol("ba"));
+            if (use_drat())
+                s().get_drat().add_theory(fid, symbol("ba"));
+        }
+        else if (bvu.get_family_id() == fid) {
+            ext = alloc(bv::solver, *this, fid);
+            if (use_drat())
+                s().get_drat().add_theory(fid, symbol("bv"));            
         }
         if (ext) {
             ext->set_solver(m_solver);
             ext->push_scopes(s().num_scopes());
             add_solver(fid, ext);
         }
-        else {
+        else if (f) 
             unhandled_function(f);
-        }
         return ext;
     }
 
@@ -84,7 +86,7 @@ namespace euf {
         if (m_unhandled_functions.contains(f))
             return;
         m_unhandled_functions.push_back(f);
-        m_trail.push_back(new (m_region) push_back_vector<solver, func_decl_ref_vector>(m_unhandled_functions));
+        m_trail.push(push_back_vector<solver, func_decl_ref_vector>(m_unhandled_functions));
         IF_VERBOSE(0, verbose_stream() << mk_pp(f, m) << " not handled\n");
     }
 
@@ -93,7 +95,7 @@ namespace euf {
     }
 
     bool solver::is_external(bool_var v) {
-        if (nullptr != m_var2node.get(v, nullptr))
+        if (nullptr != m_var2expr.get(v, nullptr))
             return true;
         for (auto* s : m_solvers)
             if (s->is_external(v))
@@ -107,86 +109,125 @@ namespace euf {
         return ext->propagate(l, idx);
     }
 
-    void solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector& r) {
+    void solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector& r, bool probing) {
+        m_egraph.begin_explain();
+        m_explain.reset();
         auto* ext = sat::constraint_base::to_extension(idx);
         if (ext == this)
-            get_antecedents(l, constraint::from_idx(idx), r);
+            get_antecedents(l, constraint::from_idx(idx), r, probing);
         else
-            ext->get_antecedents(l, idx, r);
+            ext->get_antecedents(l, idx, r, probing);
+        for (unsigned qhead = 0; qhead < m_explain.size(); ++qhead) {
+            size_t* e = m_explain[qhead];
+            if (is_literal(e))
+                r.push_back(get_literal(e));
+            else {
+                size_t idx = get_justification(e);
+                auto* ext = sat::constraint_base::to_extension(idx);
+                SASSERT(ext != this);
+                sat::literal lit = sat::null_literal;
+                ext->get_antecedents(lit, idx, r, probing);
+            }
+        }
+        m_egraph.end_explain();
+        if (!probing)
+            log_antecedents(l, r);
     }
 
-    void solver::get_antecedents(literal l, constraint& j, literal_vector& r) {
-        m_explain.reset();
+    void solver::add_antecedent(enode* a, enode* b) {
+        m_egraph.explain_eq<size_t>(m_explain, a, b);
+    }
+
+    void solver::propagate(enode* a, enode* b, ext_justification_idx idx) {
+        m_egraph.merge(a, b, to_ptr(idx));
+    }
+
+
+    void solver::get_antecedents(literal l, constraint& j, literal_vector& r, bool probing) {
+        expr* e = nullptr;
         euf::enode* n = nullptr;
 
-        // init_ackerman();
+        init_ackerman();
 
         switch (j.kind()) {
         case constraint::kind_t::conflict:
             SASSERT(m_egraph.inconsistent());
-            m_egraph.explain<unsigned>(m_explain);
+            m_egraph.explain<size_t>(m_explain);
             break;
         case constraint::kind_t::eq:
-            n = m_var2node[l.var()];
+            e = m_var2expr[l.var()];
+            n = m_egraph.find(e);
             SASSERT(n);
             SASSERT(m_egraph.is_equality(n));
             SASSERT(!l.sign());
-            m_egraph.explain_eq<unsigned>(m_explain, n->get_arg(0), n->get_arg(1));
+            m_egraph.explain_eq<size_t>(m_explain, n->get_arg(0), n->get_arg(1));
             break;
         case constraint::kind_t::lit:
-            n = m_var2node[l.var()];
+            e = m_var2expr[l.var()];
+            n = m_egraph.find(e);
             SASSERT(n);
             SASSERT(m.is_bool(n->get_owner()));
-            m_egraph.explain_eq<unsigned>(m_explain, n, (l.sign() ? mk_false() : mk_true()));
+            m_egraph.explain_eq<size_t>(m_explain, n, (l.sign() ? mk_false() : mk_true()));
             break;
         default:
             IF_VERBOSE(0, verbose_stream() << (unsigned)j.kind() << "\n");
             UNREACHABLE();
         }
-        for (unsigned* idx : m_explain) 
-            r.push_back(sat::to_literal((unsigned)(idx - base_ptr())));
-
-        log_antecedents(l, r);
     }
 
     void solver::asserted(literal l) {
-        auto* ext = get_solver(l.var());
-        if (ext) {
-            ext->asserted(l);
+        expr* e = m_var2expr.get(l.var(), nullptr);
+        if (!e) {
             return;
         }
 
-        bool sign = l.sign();
-        auto n = m_var2node.get(l.var(), nullptr);
+        bool sign = l.sign();        
+        TRACE("euf", tout << "asserted: " << l << "@" << s().scope_lvl() << " " << (sign ? "not ": " ") << e->get_id()  << "\n";);
+        euf::enode* n = m_egraph.find(e);
         if (!n)
             return;
-        
-        expr* e = n->get_owner();
-        if (m.is_eq(e) && !sign) {
+        for (auto th : enode_th_vars(n))           
+            m_id2solver[th.get_id()]->asserted(l);
+        if (!n->merge_enabled())
+            return;
+        size_t* c = to_ptr(l);
+        SASSERT(is_literal(c));
+        SASSERT(l == get_literal(c));
+        if (m.is_eq(e) && !sign && n->num_args() == 2) {
             euf::enode* na = n->get_arg(0);
             euf::enode* nb = n->get_arg(1);
-            TRACE("euf", tout << mk_pp(e, m) << "\n";);
-            m_egraph.merge(na, nb, base_ptr() + l.index());
+            m_egraph.merge(na, nb, c);
         }
         else {            
             euf::enode* nb = sign ? mk_false() : mk_true();
-            TRACE("euf", tout << (sign ? "not ": " ") << mk_pp(n->get_owner(), m)  << "\n";);
-            m_egraph.merge(n, nb, base_ptr() + l.index());
+            m_egraph.merge(n, nb, c);
         }
-        // TBD: delay propagation?
-        propagate();
     }
 
-    void solver::propagate() {  
-        while (m_egraph.propagate() && !s().inconsistent()) {
+    bool solver::unit_propagate() {
+        bool propagated = false;
+        while (!s().inconsistent()) {
             if (m_egraph.inconsistent()) {  
                 unsigned lvl = s().scope_lvl();
                 s().set_conflict(sat::justification::mk_ext_justification(lvl, conflict_constraint().to_index()));
-                return;
+                return true;
             }
-            propagate_literals();
-            propagate_th_eqs();
+            bool propagated1 = false;
+            if (m_egraph.propagate()) {
+                propagate_literals();
+                propagate_th_eqs();
+                propagated1 = true;
+            }
+
+            for (auto* s : m_solvers) {
+                if (s->unit_propagate())
+                    propagated1 = true;
+            }
+            if (!propagated1)
+                break;
+            propagated = true;             
         }
+        return propagated;
     }
 
     void solver::propagate_literals() {
@@ -196,7 +237,7 @@ namespace euf {
             bool is_eq = p.second;
             expr* e = n->get_owner();
             expr* a = nullptr, *b = nullptr;
-            bool_var v = m_expr2var.to_bool_var(e);
+            bool_var v = si.to_bool_var(e);
             SASSERT(m.is_bool(e));
             size_t cnstr;
             literal lit;
@@ -207,7 +248,7 @@ namespace euf {
             }
             else {
                 a = e, b = n->get_root()->get_owner();
-                SASSERT(m.is_true(a) || m.is_false(b));
+                SASSERT(m.is_true(b) || m.is_false(b));
                 cnstr = lit_constraint().to_index();
                 lit = literal(v, m.is_false(b));
             }
@@ -222,7 +263,7 @@ namespace euf {
     void solver::propagate_th_eqs() {
         for (; m_egraph.has_th_eq() && !s().inconsistent() && !m_egraph.inconsistent(); m_egraph.next_th_eq()) {
             th_eq eq = m_egraph.get_th_eq();
-            m_id2solver[eq.m_id]->new_eq_eh(eq);
+            m_id2solver[eq.m_id]->new_eq_eh(eq);            
         }
     }
 
@@ -246,6 +287,7 @@ namespace euf {
     }
 
     sat::check_result solver::check() { 
+        TRACE("euf", s().display(tout););
         bool give_up = false;
         bool cont = false;
         for (auto* e : m_solvers)
@@ -262,37 +304,52 @@ namespace euf {
     }
 
     void solver::push() {
+        si.push();
         scope s;
         s.m_var_lim = m_var_trail.size();
-        s.m_trail_lim = m_trail.size();
         m_scopes.push_back(s);
-        m_region.push_scope();
+        m_trail.push_scope();
         for (auto* e : m_solvers)
             e->push();
         m_egraph.push();
     }
 
-    void solver::force_push() {
-        for (; m_num_scopes > 0; --m_num_scopes) {
-
-        }
-    }
-
     void solver::pop(unsigned n) {
         m_egraph.pop(n);
         for (auto* e : m_solvers)
             e->pop(n);
-
         scope const & s = m_scopes[m_scopes.size() - n];
-
         for (unsigned i = m_var_trail.size(); i-- > s.m_var_lim; )
-            m_var2node[m_var_trail[i]] = nullptr;
-        m_var_trail.shrink(s.m_var_lim);
-        
-        undo_trail_stack(*this, m_trail, s.m_trail_lim);
-
-        m_region.pop_scope(n);
+            m_var2expr[m_var_trail[i]] = nullptr;
+        m_var_trail.shrink(s.m_var_lim);        
+        m_trail.pop_scope(n);
         m_scopes.shrink(m_scopes.size() - n);
+        si.pop(n);
+    }
+
+    void solver::start_reinit(unsigned n) {
+        sat::literal_vector lits;
+        m_reinit_vars.reset();
+        m_reinit_exprs.reset();
+        s().get_reinit_literals(n, lits);
+        for (sat::literal lit : lits) {
+            sat::bool_var v = lit.var();
+            expr* e = bool_var2expr(v);
+            if (m_reinit_vars.contains(v) || !e)
+                continue;
+            m_reinit_vars.push_back(v);
+            m_reinit_exprs.push_back(e);
+        }
+    }
+
+    void solver::finish_reinit() {
+        unsigned sz = m_reinit_vars.size();
+        for (unsigned i = 0; i < sz; ++i) {
+            euf::enode* n = get_enode(m_reinit_exprs.get(i));
+            if (n)
+                continue;
+
+        }
     }
 
     void solver::pre_simplify() {
@@ -319,13 +376,33 @@ namespace euf {
         return l_undef; 
     }
 
+    bool solver::set_root(literal l, literal r) {
+        bool ok = true;
+        for (auto* s : m_solvers)
+            if (!s->set_root(l, r))
+                ok = false;
+        expr* e = bool_var2expr(l.var());
+        if (e) {
+            if (m.is_eq(e) && !m.is_iff(e))
+                ok = false;
+            euf::enode* n = get_enode(e);
+            if (n && n->merge_enabled())
+                ok = false;
+        }
+        return ok;
+    }
+
+    void solver::flush_roots() {
+        for (auto* s : m_solvers)
+            s->flush_roots();
+    }
+
     std::ostream& solver::display(std::ostream& out) const {
         m_egraph.display(out);
-
         out << "bool-vars\n";
         for (unsigned v : m_var_trail) {
-            euf::enode* n = m_var2node[v];
-            out << v << ": " << n->get_owner_id() << " " << mk_bounded_pp(n->get_owner(), m, 1) << "\n";        
+            expr* e = m_var2expr[v];
+            out << v << ": " << e->get_id() << " " << m_solver->value(v) << " " << mk_bounded_pp(e, m, 1) << "\n";        
         }
         for (auto* e : m_solvers)
             e->display(out);
@@ -350,13 +427,19 @@ namespace euf {
         m_egraph.collect_statistics(st);
         for (auto* e : m_solvers)
             e->collect_statistics(st);
-        st.update("euf dynack", m_stats.m_num_dynack);
+        st.update("euf ackerman", m_stats.m_ackerman);
     }
 
     sat::extension* solver::copy(sat::solver* s) { 
-        auto* r = alloc(solver, *m_to_m, *m_to_expr2var, *m_to_si);
+        auto* r = alloc(solver, *m_to_m, *m_to_si);
         r->m_config = m_config;
-        std::function<void* (void*)> copy_justification = [&](void* x) { return (void*)(r->base_ptr() + ((unsigned*)x - base_ptr())); };
+        sat::literal true_lit = sat::null_literal;
+        if (s->init_trail_size() > 0)
+            true_lit = s->trail_literal(0); 
+        std::function<void* (void*)> copy_justification = [&](void* x) { 
+            SASSERT(true_lit != sat::null_literal); 
+            return (void*)(r->to_ptr(true_lit)); 
+        };
         r->m_egraph.copy_from(m_egraph, copy_justification);        
         r->set_solver(s);
         for (unsigned i = 0; i < m_id2solver.size(); ++i) {
@@ -386,6 +469,9 @@ namespace euf {
         for (auto* e : m_solvers)
             if (!e->validate())
                 return false;
+        check_eqc_bool_assignment();
+        check_missing_bool_enode_propagation();
+        m_egraph.invariant();
         return true; 
     }
 
@@ -411,9 +497,9 @@ namespace euf {
     unsigned solver::max_var(unsigned w) const { 
         for (auto* e : m_solvers)
             w = e->max_var(w);
-        for (unsigned sz = m_var2node.size(); sz-- > 0; ) {
-            euf::enode* n = m_var2node[sz];
-            if (n && m.is_bool(n->get_owner())) {
+        for (unsigned sz = m_var2expr.size(); sz-- > 0; ) {
+            expr* n = m_var2expr[sz];
+            if (n && m.is_bool(n)) {
                 w = std::max(w, sz);
                 break;
             }           
@@ -422,21 +508,15 @@ namespace euf {
     }
 
     double solver::get_reward(literal l, ext_constraint_idx idx, sat::literal_occs_fun& occs) const {
-        double r = 0;
-        for (auto* e : m_solvers) {
-            r = e->get_reward(l, idx, occs);
-            if (r != 0)
-                return r;
-        }
-        return r;
+        auto* ext = sat::constraint_base::to_extension(idx);
+        SASSERT(ext);        
+        return (ext == this) ? 0 : ext->get_reward(l, idx, occs);        
     }
 
     bool solver::is_extended_binary(ext_justification_idx idx, literal_vector& r) {
-        for (auto* e : m_solvers) {
-            if (e->is_extended_binary(idx, r))
-                return true;
-        }
-        return false;
+        auto* ext = sat::constraint_base::to_extension(idx);
+        SASSERT(ext);        
+        return (ext != this) && ext->is_extended_binary(idx, r);
     }
 
     void solver::init_ackerman() {
diff --git a/src/sat/smt/euf_solver.h b/src/sat/smt/euf_solver.h
index d7ed6f81d..5480b6fad 100644
--- a/src/sat/smt/euf_solver.h
+++ b/src/sat/smt/euf_solver.h
@@ -48,41 +48,52 @@ namespace euf {
         size_t to_index() const { return sat::constraint_base::mem2base(this); }
     };
 
+
+
     class solver : public sat::extension, public th_internalizer, public th_decompile {
         typedef top_sort<euf::enode> deps_t;
         friend class ackerman;
         // friend class sat::ba_solver;
         struct stats {
-            unsigned m_num_dynack;
+            unsigned m_ackerman;
             stats() { reset(); }
             void reset() { memset(this, 0, sizeof(*this)); }
         };
         struct scope {
             unsigned m_var_lim;
-            unsigned m_trail_lim;
         };
-        typedef ptr_vector<trail<solver> > trail_stack;
+        typedef trail_stack<solver> euf_trail_stack;
+
+
+        size_t* to_ptr(sat::literal l) { return TAG(size_t*, reinterpret_cast<size_t*>((size_t)(l.index() << 4)), 1); }
+        size_t* to_ptr(size_t jst) { return TAG(size_t*, reinterpret_cast<size_t*>(jst), 2); }
+        bool is_literal(size_t* p) const { return GET_TAG(p) == 1; }
+        bool is_justification(size_t* p) const { return GET_TAG(p) == 2; }
+        sat::literal get_literal(size_t* p) {
+            unsigned idx = static_cast<unsigned>(reinterpret_cast<size_t>(UNTAG(size_t*, p)));
+            return sat::to_literal(idx >> 4);
+        }
+        size_t get_justification(size_t* p) const {
+            return reinterpret_cast<size_t>(UNTAG(size_t*, p));
+        }
 
         ast_manager&          m;
-        atom2bool_var&        m_expr2var;
         sat::sat_internalizer& si;
         smt_params            m_config;
-        bool                  m_drat { false };
         euf::egraph           m_egraph;
+        euf_trail_stack       m_trail;
         stats                 m_stats;
-        region                m_region;
         func_decl_ref_vector  m_unhandled_functions;
-        trail_stack           m_trail;
+        
 
         sat::solver*           m_solver { nullptr };
         sat::lookahead*        m_lookahead { nullptr };
         ast_manager*           m_to_m;
-        atom2bool_var*         m_to_expr2var;
         sat::sat_internalizer* m_to_si;
         scoped_ptr<euf::ackerman>   m_ackerman;
 
-        ptr_vector<euf::enode>                          m_var2node;
-        ptr_vector<unsigned>                            m_explain;
+        ptr_vector<expr>                                m_var2expr;
+        ptr_vector<size_t>                              m_explain;
         unsigned                                        m_num_scopes { 0 };
         unsigned_vector                                 m_var_trail;
         svector<scope>                                  m_scopes;
@@ -93,26 +104,29 @@ namespace euf {
         constraint* m_eq       { nullptr };
         constraint* m_lit      { nullptr };
 
-        sat::solver& s() { return *m_solver; }
-        unsigned * base_ptr() { return reinterpret_cast<unsigned*>(this); }
-
         // internalization
-
         bool visit(expr* e) override;
         bool visited(expr* e) override;
-        bool post_visit(expr* e, bool sign, bool root) override;
-        void attach_node(euf::enode* n);
-        void attach_lit(sat::literal lit, euf::enode* n);
+        bool post_visit(expr* e, bool sign, bool root) override;        
+        sat::literal attach_lit(sat::literal lit, expr* e);
         void add_distinct_axiom(app* e, euf::enode* const* args);
         void add_not_distinct_axiom(app* e, euf::enode* const* args);
         void axiomatize_basic(enode* n);
-        bool internalize_root(app* e, bool sign);
+        bool internalize_root(app* e, bool sign, ptr_vector<enode> const& args);
         euf::enode* mk_true();
         euf::enode* mk_false();
+
+        // replay
+        expr_ref_vector      m_reinit_exprs;
+        sat::bool_var_vector m_reinit_vars;
         
+        void start_reinit(unsigned num_scopes);
+        void finish_reinit();
 
         // extensions
-        th_solver* get_solver(func_decl* f);
+        th_solver* get_solver(family_id fid, func_decl* f);
+        th_solver* get_solver(sort* s) { return get_solver(s->get_family_id(), nullptr); }
+        th_solver* get_solver(func_decl* f) { return get_solver(f->get_family_id(), f); }
         th_solver* get_solver(expr* e);
         th_solver* get_solver(sat::bool_var v);
         void add_solver(family_id fid, th_solver* th);
@@ -127,15 +141,20 @@ namespace euf {
         void values2model(deps_t const& deps, expr_ref_vector const& values, model_ref& mdl);
 
         // solving
-        void propagate();
         void propagate_literals();
         void propagate_th_eqs();
-        void get_antecedents(literal l, constraint& j, literal_vector& r);
-        void force_push();
+        void get_antecedents(literal l, constraint& j, literal_vector& r, bool probing);
+
+        // proofs
         void log_antecedents(std::ostream& out, literal l, literal_vector const& r);
         void log_antecedents(literal l, literal_vector const& r);
-        void log_node(enode* n);
-        void log_bool_var(sat::bool_var v, enode* n);
+        void log_node(expr* n);
+        bool m_drat_initialized{ false };
+        void init_drat();
+
+        // invariant
+        void check_eqc_bool_assignment() const;
+        void check_missing_bool_enode_propagation() const;        
 
         constraint& mk_constraint(constraint*& c, constraint::kind_t k);
         constraint& conflict_constraint() { return mk_constraint(m_conflict, constraint::kind_t::conflict); }
@@ -143,17 +162,17 @@ namespace euf {
         constraint& lit_constraint() { return mk_constraint(m_lit, constraint::kind_t::lit); }
 
     public:
-       solver(ast_manager& m, atom2bool_var& expr2var, sat::sat_internalizer& si, params_ref const& p = params_ref()):
+       solver(ast_manager& m, sat::sat_internalizer& si, params_ref const& p = params_ref()):
             m(m),
-            m_expr2var(expr2var),
             si(si),
             m_egraph(m),
+            m_trail(*this),
             m_unhandled_functions(m),
             m_solver(nullptr),
             m_lookahead(nullptr),
             m_to_m(&m),
-            m_to_expr2var(&expr2var),
-            m_to_si(&si)
+            m_to_si(&si),
+            m_reinit_exprs(m)
         {
             updt_params(p);
         }
@@ -166,37 +185,45 @@ namespace euf {
 
        struct scoped_set_translate {
            solver& s;
-           scoped_set_translate(solver& s, ast_manager& m, atom2bool_var& a2b, sat::sat_internalizer& si) :
+           scoped_set_translate(solver& s, ast_manager& m, sat::sat_internalizer& si) :
                s(s) {
                s.m_to_m = &m;
-               s.m_to_expr2var = &a2b;
                s.m_to_si = &si;
            }
            ~scoped_set_translate() {
                s.m_to_m = &s.m;
-               s.m_to_expr2var = &s.m_expr2var;
                s.m_to_si = &s.si;
            }
        };
 
+        sat::solver& s() { return *m_solver; }
+        sat::solver const& s() const { return *m_solver; }
         sat::sat_internalizer& get_si() { return si; }
         ast_manager& get_manager() { return m; }
         enode* get_enode(expr* e) { return m_egraph.find(e); }
-        sat::literal get_literal(expr* e) { return literal(m_expr2var.to_bool_var(e), false); }
+        sat::literal get_literal(expr* e) const { return literal(si.to_bool_var(e), false); }
+        sat::literal get_literal(enode* e) const { return get_literal(e->get_owner()); }
         smt_params const& get_config() { return m_config; }
-        region& get_region() { return m_region; }
+        region& get_region() { return m_trail.get_region(); }
         template <typename C>
-        void push(C const& c) { m_trail.push_back(new (m_region) C(c));  }
+        void push(C const& c) { m_trail.push(c);  }
+        euf_trail_stack& get_trail_stack() { return m_trail; }
 
         void updt_params(params_ref const& p);
-        void set_solver(sat::solver* s) override { m_solver = s; m_drat = s->get_config().m_drat; }
+        void set_solver(sat::solver* s) override { m_solver = s; }
         void set_lookahead(sat::lookahead* s) override { m_lookahead = s; }
         void init_search() override;
         double get_reward(literal l, ext_constraint_idx idx, sat::literal_occs_fun& occs) const override;
         bool is_extended_binary(ext_justification_idx idx, literal_vector& r) override;
         bool is_external(bool_var v) override;
         bool propagate(literal l, ext_constraint_idx idx) override;
-        void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) override;
+        bool unit_propagate() override;
+        void propagate(enode* a, enode* b, ext_justification_idx);
+        bool set_root(literal l, literal r) override;
+        void flush_roots() override;
+
+        void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) override;
+        void add_antecedent(enode* a, enode* b);
         void asserted(literal l) override;
         sat::check_result check() override;
         void push() override;
@@ -220,6 +247,9 @@ namespace euf {
         bool check_model(sat::model const& m) const override;
         unsigned max_var(unsigned w) const override;
 
+        bool use_drat() { return s().get_config().m_drat && (init_drat(), true);  }
+        sat::drat& get_drat() { return s().get_drat(); }
+
         // decompile
         bool extract_pb(std::function<void(unsigned sz, literal const* c, unsigned k)>& card,
                         std::function<void(unsigned sz, literal const* c, unsigned const* coeffs, unsigned k)>& pb) override;
@@ -228,11 +258,18 @@ namespace euf {
 
         // internalize
         sat::literal internalize(expr* e, bool sign, bool root, bool learned) override;
+        void internalize(expr* e, bool learned) override;
         void attach_th_var(enode* n, th_solver* th, theory_var v) { m_egraph.add_th_var(n, v, th->get_id()); }
+        void attach_node(euf::enode* n);
         euf::enode* mk_enode(expr* e, unsigned n, enode* const* args) { return m_egraph.mk(e, n, args); }
+        expr* bool_var2expr(sat::bool_var v) { return m_var2expr.get(v, nullptr); }
 
         void update_model(model_ref& mdl);
 
         func_decl_ref_vector const& unhandled_functions() { return m_unhandled_functions; }       
-    };
+    };    
 };
+
+inline std::ostream& operator<<(std::ostream& out, euf::solver const& s) {
+    return s.display(out);
+}
diff --git a/src/sat/smt/sat_smt.h b/src/sat/smt/sat_smt.h
index 82b1ab16a..7b46f89b8 100644
--- a/src/sat/smt/sat_smt.h
+++ b/src/sat/smt/sat_smt.h
@@ -39,8 +39,11 @@ namespace sat {
         virtual ~sat_internalizer() {}
         virtual bool is_bool_op(expr* e) const = 0;
         virtual literal internalize(expr* e, bool learned) = 0;
+        virtual bool_var to_bool_var(expr* e) = 0;
         virtual bool_var add_bool_var(expr* e)  = 0;
         virtual void cache(app* t, literal l) = 0;
+        virtual void push() = 0;
+        virtual void pop(unsigned n) = 0;
     };
     
     class constraint_base {
diff --git a/src/sat/smt/sat_th.cpp b/src/sat/smt/sat_th.cpp
index 435d330f5..21b2a4751 100644
--- a/src/sat/smt/sat_th.cpp
+++ b/src/sat/smt/sat_th.cpp
@@ -65,15 +65,24 @@ namespace euf {
         return ctx.get_region();
     }
 
-    enode* th_euf_solver::get_enode(expr* e) const { 
+    trail_stack<euf::solver>& th_euf_solver::get_trail_stack() {
+        return ctx.get_trail_stack();
+    }
+
+    enode* th_euf_solver::expr2enode(expr* e) const { 
         return ctx.get_enode(e); 
     }
 
-    sat::literal th_euf_solver::get_literal(expr* e) const {
+    sat::literal th_euf_solver::expr2literal(expr* e) const {
         return ctx.get_literal(e);
     }
 
+    expr* th_euf_solver::bool_var2expr(sat::bool_var v) const {
+        return ctx.bool_var2expr(v);
+    }
+
     theory_var th_euf_solver::mk_var(enode * n) {
+        force_push();
         SASSERT(!is_attached_to_var(n));
         euf::theory_var v = m_var2enode.size();
         m_var2enode.push_back(n);
@@ -82,7 +91,7 @@ namespace euf {
 
     bool th_euf_solver::is_attached_to_var(enode* n) const {
         theory_var v = n->get_th_var(get_id());
-        return v != null_theory_var && get_enode(v) == n;
+        return v != null_theory_var && var2enode(v) == n;
     }
 
     theory_var th_euf_solver::get_th_var(expr* e) const {
@@ -99,4 +108,35 @@ namespace euf {
         m_var2enode_lim.shrink(new_lvl);
     }
 
+    unsigned th_euf_solver::lazy_pop(unsigned n) {
+        if (n <= m_num_scopes) {
+            m_num_scopes -= n;
+            return 0;
+        }
+        else {
+            n -= m_num_scopes;
+            pop(n);
+            return n;
+        }
+    }
+
+    void th_euf_solver::add_unit(sat::literal lit) {
+        ctx.s().add_clause(1, &lit, sat::status::th(m_is_redundant, get_id())); 
+    }
+
+    void th_euf_solver::add_clause(sat::literal a, sat::literal b) {
+        sat::literal lits[2] = { a, b };
+        ctx.s().add_clause(2, lits, sat::status::th(m_is_redundant, get_id()));
+    }
+
+    void th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c) {
+        sat::literal lits[3] = { a, b, c };
+        ctx.s().add_clause(3, lits, sat::status::th(m_is_redundant, get_id()));
+    }
+
+    void th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d) {
+        sat::literal lits[4] = { a, b, c, d };
+        ctx.s().add_clause(4, lits, sat::status::th(m_is_redundant, get_id()));
+    }
+
 }
diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h
index 987bc8dc6..55c0793d9 100644
--- a/src/sat/smt/sat_th.h
+++ b/src/sat/smt/sat_th.h
@@ -36,11 +36,14 @@ namespace euf {
         virtual bool visit(expr* e) { return false; }
         virtual bool visited(expr* e) { return false; }
         virtual bool post_visit(expr* e, bool sign, bool root) { return false; }
+
     public:
         virtual ~th_internalizer() {}
 
         virtual sat::literal internalize(expr* e, bool sign, bool root, bool redundant) = 0;
 
+        virtual void internalize(expr* e, bool redundant) = 0;
+
         /**
            \brief Apply (interpreted) sort constraints on the given enode.
         */
@@ -69,7 +72,7 @@ namespace euf {
         /**
            \brief compute dependencies for node n
          */
-        virtual void add_dep(euf::enode* n, top_sort<euf::enode>& dep) {}
+        virtual void add_dep(euf::enode* n, top_sort<euf::enode>& dep) { dep.insert(n, nullptr);  }
 
         /**
            \brief should function be included in model.
@@ -102,24 +105,39 @@ namespace euf {
         solver &            ctx;
         euf::enode_vector   m_var2enode;
         unsigned_vector     m_var2enode_lim;
+        unsigned            m_num_scopes{ 0 };
 
         smt_params const& get_config() const;
-        sat::literal get_literal(expr* e) const;
+        sat::literal expr2literal(expr* e) const;
         region& get_region();
+        
+
+        void add_unit(sat::literal lit);
+        void add_clause(sat::literal a, sat::literal b);
+        void add_clause(sat::literal a, sat::literal b, sat::literal c);
+        void add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d);
+
     public:
         th_euf_solver(euf::solver& ctx, euf::theory_id id);
         virtual ~th_euf_solver() {}
         virtual theory_var mk_var(enode * n);
         unsigned get_num_vars() const { return m_var2enode.size();}
-        enode* get_enode(expr* e) const; 
-        enode* get_enode(theory_var v) const { return m_var2enode[v]; }
-        expr* get_expr(theory_var v) const { return get_enode(v)->get_owner(); }
-        expr_ref get_expr(sat::literal lit) const { expr* e = get_expr(lit.var()); return lit.sign() ? expr_ref(m.mk_not(e), m) : expr_ref(e, m); }
+        enode* expr2enode(expr* e) const; 
+        enode* var2enode(theory_var v) const { return m_var2enode[v]; }
+        expr* var2expr(theory_var v) const { return var2enode(v)->get_owner(); }
+        expr* bool_var2expr(sat::bool_var v) const;
+        expr_ref literal2expr(sat::literal lit) const { expr* e = bool_var2expr(lit.var()); return lit.sign() ? expr_ref(m.mk_not(e), m) : expr_ref(e, m); }
         theory_var get_th_var(enode* n) const { return n->get_th_var(get_id()); }
         theory_var get_th_var(expr* e) const;
+        trail_stack<euf::solver> & get_trail_stack();
         bool is_attached_to_var(enode* n) const;
+        bool is_root(theory_var v) const { return var2enode(v)->is_root(); }
         void push() override;
         void pop(unsigned n) override;
+
+        void lazy_push() { ++m_num_scopes; }
+        void force_push() { for (; m_num_scopes > 0; --m_num_scopes) push(); }
+        unsigned lazy_pop(unsigned n);
     };
 
 
diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp
index 954933379..c1eb93ce9 100644
--- a/src/sat/tactic/goal2sat.cpp
+++ b/src/sat/tactic/goal2sat.cpp
@@ -29,6 +29,7 @@ Notes:
 #include "util/ref_util.h"
 #include "ast/ast_smt2_pp.h"
 #include "ast/ast_pp.h"
+#include "ast/ast_smt2_pp.h"
 #include "ast/ast_ll_pp.h"
 #include "ast/pb_decl_plugin.h"
 #include "ast/ast_util.h"
@@ -38,6 +39,7 @@ Notes:
 #include "tactic/tactic.h"
 #include "tactic/generic_model_converter.h"
 #include "sat/sat_cut_simplifier.h"
+#include "sat/sat_drat.h"
 #include "sat/tactic/goal2sat.h"
 #include "sat/smt/ba_solver.h"
 #include "sat/smt/euf_solver.h"
@@ -72,6 +74,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
     bool                        m_default_external;
     bool                        m_xor_solver;
     bool                        m_euf;
+    bool                        m_drat;
     bool                        m_is_redundant { false };
     sat::literal_vector         aig_lits;
     
@@ -98,6 +101,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
         m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX));
         m_xor_solver = p.get_bool("xor_solver", false);
         m_euf = sp.euf();
+        m_drat = sp.drat_file() != symbol();
     }
 
     void throw_op_not_handled(std::string const& s) {
@@ -125,19 +129,71 @@ struct goal2sat::imp : public sat::sat_internalizer {
         m_solver.add_clause(num, lits, m_is_redundant ? sat::status::redundant() : sat::status::asserted());
     }
 
+    void mk_root_clause(sat::literal l) {
+        TRACE("goal2sat", tout << "mk_clause: " << l << "\n";);
+        m_solver.add_clause(1, &l, m_is_redundant ? sat::status::redundant() : sat::status::input());
+    }
+
+    void mk_root_clause(sat::literal l1, sat::literal l2) {
+        TRACE("goal2sat", tout << "mk_clause: " << l1 << " " << l2 << "\n";);
+        m_solver.add_clause(l1, l2, m_is_redundant ? sat::status::redundant() : sat::status::input());
+    }
+
+    void mk_root_clause(sat::literal l1, sat::literal l2, sat::literal l3) {
+        TRACE("goal2sat", tout << "mk_clause: " << l1 << " " << l2 << " " << l3 << "\n";);
+        m_solver.add_clause(l1, l2, l3, m_is_redundant ? sat::status::redundant() : sat::status::input());
+    }
+
+    void mk_root_clause(unsigned num, sat::literal * lits) {
+        TRACE("goal2sat", tout << "mk_clause: "; for (unsigned i = 0; i < num; i++) tout << lits[i] << " "; tout << "\n";);
+        m_solver.add_clause(num, lits, m_is_redundant ? sat::status::redundant() : sat::status::input());
+    }
+
+    sat::bool_var add_var(bool is_ext, expr* n) {
+        auto v = m_solver.add_var(is_ext);
+        log_node(v, n);
+        return v;
+    }
+
+    void log_node(sat::bool_var v, expr* n) {
+        if (m_drat && m_solver.get_drat_ptr()) {
+            sat::drat* drat = m_solver.get_drat_ptr();
+            if (is_app(n)) {
+                app* a = to_app(n);
+                std::stringstream strm;
+                strm << mk_ismt2_func(a->get_decl(), m);
+                drat->def_begin(n->get_id(), strm.str());
+                for (expr* arg : *a)
+                    drat->def_add_arg(arg->get_id());
+                drat->def_end();
+            }
+            else {
+                IF_VERBOSE(0, verbose_stream() << "skipping DRAT of non-app\n");
+            }
+            drat->bool_def(v, n->get_id());
+        }
+    }
+
+
+
     sat::literal mk_true() {
         if (m_true == sat::null_literal) {
             // create fake variable to represent true;
-            m_true = sat::literal(m_solver.add_var(false), false);
+            m_true = sat::literal(add_var(false, m.mk_true()), false);
             mk_clause(m_true); // v is true
         }
         return m_true;
     }
 
+    sat::bool_var to_bool_var(expr* e) override {
+        return m_map.to_bool_var(e);
+    }
+
     sat::bool_var add_bool_var(expr* t) override {
         sat::bool_var v = m_map.to_bool_var(t);
         if (v == sat::null_bool_var) {
-            v = m_solver.add_var(true);
+            v = add_var(true, t);
+            force_push();
             m_map.insert(t, v);
         }
         else {
@@ -146,6 +202,28 @@ struct goal2sat::imp : public sat::sat_internalizer {
         return v;
     }
 
+    unsigned m_num_scopes{ 0 };
+
+    void force_push() {
+        for (; m_num_scopes > 0; --m_num_scopes)
+            m_map.push();
+    }
+
+    void push() override {
+        ++m_num_scopes;
+    }
+
+    void pop(unsigned n) override {
+        if (n <= m_num_scopes) {
+            m_num_scopes -= n;
+            return;
+        }
+        n -= m_num_scopes;
+        m_num_scopes = 0;
+        m_cache.reset();
+        m_map.pop(n);
+    }
+
     void cache(app* t, sat::literal l) override {
         m_cache.insert(t, l);
     }
@@ -166,12 +244,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
                 strm << mk_ismt2_pp(t, m);
                 throw_op_not_handled(strm.str());
             }
-            else {
-                bool ext = m_default_external || !is_uninterp_const(t) || m_interface_vars.contains(t);
-                v = m_solver.add_var(ext);
-                m_map.insert(t, v);
-                l = sat::literal(v, sign);
-                TRACE("sat", tout << "new_var: " << v << ": " << mk_bounded_pp(t, m, 2) << " " << is_uninterp_const(t) << "\n";);
+            else {                
                 if (!is_uninterp_const(t)) {
                     if (m_euf) {
                         convert_euf(t, root, sign);                        
@@ -180,6 +253,12 @@ struct goal2sat::imp : public sat::sat_internalizer {
                     else
                         m_unhandled_funs.push_back(to_app(t)->get_decl());
                 }
+                bool ext = m_default_external || !is_uninterp_const(t) || m_interface_vars.contains(t);
+                v = add_var(ext, t);
+                force_push();
+                m_map.insert(t, v);
+                l = sat::literal(v, sign);
+                TRACE("sat", tout << "new_var: " << v << ": " << mk_bounded_pp(t, m, 2) << " " << is_uninterp_const(t) << "\n";);
             }
         }
         else {
@@ -191,7 +270,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
             m_result_stack.reset();
         SASSERT(l != sat::null_literal);
         if (root)
-            mk_clause(l);
+            mk_root_clause(l);
         else
             m_result_stack.push_back(l);
     }
@@ -213,7 +292,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
             if (sign)
                 l.neg();
             if (root)
-                mk_clause(l);
+                mk_root_clause(l);
             else
                 m_result_stack.push_back(l);
             return true;
@@ -275,17 +354,17 @@ struct goal2sat::imp : public sat::sat_internalizer {
                 for (unsigned i = 0; i < num; i++) {
                     sat::literal l = m_result_stack[i];
                     l.neg();
-                    mk_clause(l);
+                    mk_root_clause(l);
                 }
             }
             else {
-                mk_clause(m_result_stack.size(), m_result_stack.c_ptr());
+                mk_root_clause(m_result_stack.size(), m_result_stack.c_ptr());
             }
             m_result_stack.reset();
         }
         else {
             SASSERT(num <= m_result_stack.size());
-            sat::bool_var k = m_solver.add_var(false);
+            sat::bool_var k = add_var(false, t);
             sat::literal  l(k, false);
             m_cache.insert(t, l);
             sat::literal * lits = m_result_stack.end() - num;           
@@ -321,18 +400,18 @@ struct goal2sat::imp : public sat::sat_internalizer {
                 for (unsigned i = 0; i < num; ++i) {
                     m_result_stack[i].neg();
                 }                
-                mk_clause(m_result_stack.size(), m_result_stack.c_ptr());
+                mk_root_clause(m_result_stack.size(), m_result_stack.c_ptr());
             }
             else {
                 for (unsigned i = 0; i < num; ++i) {
-                    mk_clause(m_result_stack[i]);
+                    mk_root_clause(m_result_stack[i]);
                 }
             }
             m_result_stack.reset();
         }
         else {
             SASSERT(num <= m_result_stack.size());
-            sat::bool_var k = m_solver.add_var(false);
+            sat::bool_var k = add_var(false, t);
             sat::literal  l(k, false);
             m_cache.insert(t, l);
             sat::literal * lits = m_result_stack.end() - num;
@@ -373,17 +452,17 @@ struct goal2sat::imp : public sat::sat_internalizer {
         if (root) {
             SASSERT(sz == 3);
             if (sign) {
-                mk_clause(~c, ~t);
-                mk_clause(c,  ~e);
+                mk_root_clause(~c, ~t);
+                mk_root_clause(c,  ~e);
             }
             else {
-                mk_clause(~c, t);
-                mk_clause(c, e);
+                mk_root_clause(~c, t);
+                mk_root_clause(c, e);
             }
             m_result_stack.reset();
         }
         else {
-            sat::bool_var k = m_solver.add_var(false);
+            sat::bool_var k = add_var(false, n);
             sat::literal  l(k, false);
             m_cache.insert(n, l);
             mk_clause(~l, ~c, t);
@@ -411,16 +490,16 @@ struct goal2sat::imp : public sat::sat_internalizer {
         if (root) {
             SASSERT(sz == 2);
             if (sign) {
-                mk_clause(l1);
-                mk_clause(~l2);
+                mk_root_clause(l1);
+                mk_root_clause(~l2);
             }
             else {
-                mk_clause(~l1, l2);
+                mk_root_clause(~l1, l2);
             }
             m_result_stack.reset();
         }
         else {
-            sat::bool_var k = m_solver.add_var(false);
+            sat::bool_var k = add_var(false, t);
             sat::literal  l(k, false);
             m_cache.insert(t, l);
             // l <=> (l1 => l2)
@@ -443,17 +522,17 @@ struct goal2sat::imp : public sat::sat_internalizer {
         if (root) {
             SASSERT(sz == 2);
             if (sign) {
-                mk_clause(l1, l2);
-                mk_clause(~l1, ~l2);
+                mk_root_clause(l1, l2);
+                mk_root_clause(~l1, ~l2);
             }
             else {
-                mk_clause(l1, ~l2);
-                mk_clause(~l1, l2);
+                mk_root_clause(l1, ~l2);
+                mk_root_clause(~l1, l2);
             }
             m_result_stack.reset();
         }
         else {
-            sat::bool_var k = m_solver.add_var(false);
+            sat::bool_var k = add_var(false, t);
             sat::literal  l(k, false);
             m_cache.insert(t, l);
             mk_clause(~l, l1, ~l2);
@@ -486,7 +565,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
         sat::extension* ext = m_solver.get_extension();
         euf::solver* euf = nullptr;
         if (!ext) {
-            euf = alloc(euf::solver, m, m_map, *this);
+            euf = alloc(euf::solver, m, *this);
             m_solver.set_extension(euf);
             for (unsigned i = m_solver.num_scopes(); i-- > 0; )
                 euf->push();
@@ -502,7 +581,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
         if (lit == sat::null_literal)
             return;
         if (root)
-            mk_clause(lit);
+            mk_root_clause(lit);
         else
             m_result_stack.push_back(lit);
     }
@@ -528,7 +607,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
         if (lit == sat::null_literal)
             return;
         if (root) 
-            mk_clause(lit);        
+            mk_root_clause(lit);        
         else 
             m_result_stack.push_back(lit);      
     }
@@ -643,6 +722,7 @@ struct goal2sat::imp : public sat::sat_internalizer {
 
     sat::literal internalize(expr* n, bool redundant) override {
         unsigned sz = m_result_stack.size();
+        (void)sz;
         process(n, false, redundant);
         SASSERT(m_result_stack.size() == sz + 1);
         sat::literal result = m_result_stack.back();
diff --git a/src/sat/tactic/sat_tactic.cpp b/src/sat/tactic/sat_tactic.cpp
index 8249bd865..c9afc4578 100644
--- a/src/sat/tactic/sat_tactic.cpp
+++ b/src/sat/tactic/sat_tactic.cpp
@@ -64,7 +64,8 @@ class sat_tactic : public tactic {
             TRACE("sat_dimacs", m_solver->display_dimacs(tout););
             dep2assumptions(dep2asm, assumptions);
             lbool r = m_solver->check(assumptions.size(), assumptions.c_ptr());
-            TRACE("sat", tout << "result of checking: " << r << " " << m_solver->get_reason_unknown() << "\n";);
+            TRACE("sat", tout << "result of checking: " << r << " "; 
+                  if (r == l_undef) tout << m_solver->get_reason_unknown(); tout << "\n";);
             if (r == l_false) {
                 expr_dependency * lcore = nullptr;
                 if (produce_core) {
@@ -88,13 +89,18 @@ class sat_tactic : public tactic {
                     for (auto const& kv : map) {
                         expr * n   = kv.m_key;
                         sat::bool_var v = kv.m_value;
+                        if (!is_app(n))
+                            continue;
+                        app* a = to_app(n);
+                        if (!is_uninterp_const(a))
+                            continue;
                         TRACE("sat_tactic", tout << "extracting value of " << mk_ismt2_pp(n, m) << "\nvar: " << v << "\n";);
                         switch (sat::value_at(v, ll_m)) {
                         case l_true: 
-                            md->register_decl(to_app(n)->get_decl(), m.mk_true()); 
+                            md->register_decl(a->get_decl(), m.mk_true()); 
                             break;
                         case l_false:
-                            md->register_decl(to_app(n)->get_decl(), m.mk_false());
+                            md->register_decl(a->get_decl(), m.mk_false());
                             break;
                         default:
                             break;
diff --git a/src/shell/CMakeLists.txt b/src/shell/CMakeLists.txt
index 278246341..d55626d05 100644
--- a/src/shell/CMakeLists.txt
+++ b/src/shell/CMakeLists.txt
@@ -20,6 +20,7 @@ endforeach()
 add_executable(shell
   datalog_frontend.cpp
   dimacs_frontend.cpp
+  drat_frontend.cpp
   "${CMAKE_CURRENT_BINARY_DIR}/gparams_register_modules.cpp"
   "${CMAKE_CURRENT_BINARY_DIR}/install_tactic.cpp"
   main.cpp
diff --git a/src/shell/drat_frontend.cpp b/src/shell/drat_frontend.cpp
new file mode 100644
index 000000000..561c185e5
--- /dev/null
+++ b/src/shell/drat_frontend.cpp
@@ -0,0 +1,197 @@
+/*++
+Copyright (c) 2020 Microsoft Corporation
+
+--*/
+
+#include<iostream>
+#include<fstream>
+#include "util/memory_manager.h"
+#include "util/statistics.h"
+#include "sat/dimacs.h"
+#include "sat/sat_solver.h"
+#include "sat/sat_drat.h"
+#include "smt/smt_solver.h"
+#include "shell/drat_frontend.h"
+#include "parsers/smt2/smt2parser.h"
+#include "cmd_context/cmd_context.h"
+
+class smt_checker {
+    ast_manager& m;
+    expr_ref_vector const& m_b2e;
+    expr_ref_vector m_fresh_exprs;
+    expr_ref_vector m_core;
+    params_ref m_params;
+    scoped_ptr<solver> m_solver;
+
+    expr* fresh(expr* e) {
+        unsigned i = e->get_id();
+        m_fresh_exprs.reserve(i + 1);
+        expr* r = m_fresh_exprs.get(i);
+        if (!r) {
+            r = m.mk_fresh_const("sk", m.get_sort(e));
+            m_fresh_exprs[i] = r;
+        }
+        return r;
+    }
+
+    expr_ref define(expr* e, unsigned depth) {
+        expr_ref r(fresh(e), m);
+        m_core.push_back(m.mk_eq(r, e));
+        if (depth == 0)
+            return r;
+        r = e;
+        if (is_app(e)) {
+            expr_ref_vector args(m);
+            for (expr* arg : *to_app(e)) 
+                args.push_back(define(arg, depth - 1));
+            r = m.mk_app(to_app(e)->get_decl(), args.size(), args.c_ptr());
+        }
+        return r;
+    }
+
+    void unfold1(sat::literal_vector const& lits) {
+        m_core.reset();
+        for (sat::literal lit : lits) {
+            expr* e = m_b2e[lit.var()];
+            expr_ref fml = define(e, 2);
+            if (!lit.sign())
+                fml = m.mk_not(fml);
+            m_core.push_back(fml);
+        }
+    }
+public:
+    smt_checker(expr_ref_vector const& b2e): 
+        m(b2e.m()), m_b2e(b2e), m_fresh_exprs(m), m_core(m) {
+        m_solver = mk_smt_solver(m, m_params, symbol());
+    }
+    
+    void check_shallow(sat::literal_vector const& lits) {
+        unfold1(lits);
+        m_solver->push();
+        for (auto* c : m_core)
+            m_solver->assert_expr(c);
+        lbool is_sat = m_solver->check_sat();
+        m_solver->pop(1);
+        if (is_sat == l_true) {
+            std::cout << "did not verify: " << lits << "\n" << m_core << "\n";
+            for (sat::literal lit : lits) {
+                expr_ref e(m_b2e[lit.var()], m);
+                if (lit.sign())
+                    e = m.mk_not(e);
+                std::cout << e << " ";                
+            }
+            std::cout << "\n";
+        }
+    }
+};
+
+static void verify_smt(char const* drat_file, char const* smt_file) {
+    cmd_context ctx;
+    ctx.set_ignore_check(true);
+    ctx.set_regular_stream(std::cerr);
+    ctx.set_solver_factory(mk_smt_strategic_solver_factory());    
+    if (smt_file) {
+        std::ifstream smt_in(smt_file);
+        if (!parse_smt2_commands(ctx, smt_in)) {            
+            std::cerr << "could not read file " << smt_file << "\n";
+            return;
+        }
+    }
+    
+    std::ifstream ins(drat_file);
+    dimacs::drat_parser drat(ins, std::cerr);
+    std::function<int(char const* read_theory)> read_theory = [&](char const* r) {
+        if (strcmp(r, "euf") == 0)
+            return 0;
+        return ctx.m().mk_family_id(symbol(r));
+    };
+    drat.set_read_theory(read_theory);
+    params_ref p;
+    reslimit lim;
+    p.set_bool("drat.check_unsat", true);
+    sat::solver solver(p, lim);
+    sat::drat drat_checker(solver);
+    drat_checker.updt_config();
+
+    expr_ref_vector bool_var2expr(ctx.m());
+    expr_ref_vector exprs(ctx.m()), args(ctx.m());
+    func_decl* f = nullptr;
+    ptr_vector<sort> sorts;
+
+    smt_checker checker(bool_var2expr);
+
+    auto check_smt = [&](dimacs::drat_record const& r) {
+        auto const& st = r.m_status;
+        if (st.is_input())
+            ;
+        else if (st.is_sat() && st.is_asserted()) {
+            std::cout << "Tseitin tautology " << r;
+            checker.check_shallow(r.m_lits);
+        }
+        else if (st.is_sat())
+            ;
+        else if (st.is_deleted())
+            ;
+        else {
+            std::cout << "check smt " << r;
+            checker.check_shallow(r.m_lits);
+            // TBD: shallow check may fail because it doesn't include
+            // all RUP units, whish are sometimes required.
+        }
+    };
+    
+    for (auto const& r : drat) {
+        std::cout << r;
+        std::cout.flush();
+        switch (r.m_tag) {
+        case dimacs::drat_record::tag_t::is_clause:
+            for (sat::literal lit : r.m_lits)
+                while (lit.var() >= solver.num_vars())
+                    solver.mk_var(true);
+            drat_checker.add(r.m_lits, r.m_status);
+            check_smt(r);
+            break;
+        case dimacs::drat_record::tag_t::is_node:
+            args.reset();
+            sorts.reset();
+            for (auto n : r.m_args) {
+                args.push_back(exprs.get(n));
+                sorts.push_back(ctx.m().get_sort(args.back()));
+            }
+            if (r.m_name[0] == '(') {
+                std::cout << "parsing sexprs is TBD\n";
+                exit(0);
+            }
+            f = ctx.find_func_decl(symbol(r.m_name.c_str()), 0, nullptr, args.size(), sorts.c_ptr(), nullptr);
+            if (!f) {
+                std::cout << "could not find function\n";
+                exit(0);
+            }
+            exprs.reserve(r.m_node_id+1);
+            exprs.set(r.m_node_id, ctx.m().mk_app(f, args.size(), args.c_ptr()));
+            break;
+        case dimacs::drat_record::is_bool_def:
+            bool_var2expr.reserve(r.m_node_id+1);
+            bool_var2expr.set(r.m_node_id, exprs.get(r.m_args[0]));
+            break;
+        default:
+            UNREACHABLE();
+            break;
+        }
+    }
+    statistics st;
+    drat_checker.collect_statistics(st);
+    std::cout << st << "\n";
+}
+
+
+unsigned read_drat(char const* drat_file, char const* problem_file) {
+    if (!problem_file) {
+        std::cerr << "No smt2 file provided to checker\n";
+        return -1;
+    }
+    verify_smt(drat_file, problem_file);
+    return 0;
+}
+
+
diff --git a/src/shell/drat_frontend.h b/src/shell/drat_frontend.h
new file mode 100644
index 000000000..ef37fcfaa
--- /dev/null
+++ b/src/shell/drat_frontend.h
@@ -0,0 +1,8 @@
+/*++
+Copyright (c) 2011 Microsoft Corporation
+--*/
+#pragma once
+
+unsigned read_drat(char const * drat_file, char const* problem_file);
+
+
diff --git a/src/shell/main.cpp b/src/shell/main.cpp
index bee73fb25..81c06d387 100644
--- a/src/shell/main.cpp
+++ b/src/shell/main.cpp
@@ -38,15 +38,17 @@ Revision History:
 #include "util/env_params.h"
 #include "util/file_path.h"
 #include "shell/lp_frontend.h"
+#include "shell/drat_frontend.h"
 
 #if defined( _WINDOWS ) && defined( __MINGW32__ ) && ( defined( __GNUG__ ) || defined( __clang__ ) )
 #include <crtdbg.h>
 #endif
 
-typedef enum { IN_UNSPECIFIED, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_LP, IN_Z3_LOG, IN_MPS } input_kind;
+typedef enum { IN_UNSPECIFIED, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_LP, IN_Z3_LOG, IN_MPS, IN_DRAT } input_kind;
 
 static std::string  g_aux_input_file;
 static char const * g_input_file          = nullptr;
+static char const * g_drat_input_file     = nullptr;
 static bool         g_standard_input      = false;
 static input_kind   g_input_kind          = IN_UNSPECIFIED;
 bool                g_display_statistics  = false;
@@ -301,12 +303,14 @@ static void parse_cmd_line_args(int argc, char ** argv) {
             gparams::set(key, value);
         }
         else {
-            if (g_input_file) {
+            if (get_extension(arg) && strcmp(get_extension(arg), "drat") == 0) {
+                g_input_kind = IN_DRAT;
+                g_drat_input_file = arg;
+            }
+            else if (g_input_file) 
                 warning_msg("input file was already specified.");
-            }
-            else {
+            else 
                 g_input_file = arg;
-            }
         }
         i++;
     }
@@ -361,7 +365,7 @@ int STD_CALL main(int argc, char ** argv) {
                     g_input_kind = IN_MPS;
                 }
             }
-    }
+        }
         switch (g_input_kind) {
         case IN_SMTLIB_2:
             memory::exit_when_out_of_memory(true, "(error \"out of memory\")");
@@ -388,6 +392,9 @@ int STD_CALL main(int argc, char ** argv) {
         case IN_MPS:
             return_value = read_mps_file(g_input_file);
             break;
+        case IN_DRAT:
+            return_value = read_drat(g_drat_input_file, g_input_file);
+            break;
         default:
             UNREACHABLE();
         }
diff --git a/src/shell/opt_frontend.cpp b/src/shell/opt_frontend.cpp
index bf59b8cea..897b411d1 100644
--- a/src/shell/opt_frontend.cpp
+++ b/src/shell/opt_frontend.cpp
@@ -1,4 +1,3 @@
-
 /*++
 Copyright (c) 2015 Microsoft Corporation
 
diff --git a/src/smt/CMakeLists.txt b/src/smt/CMakeLists.txt
index ba1c58cdb..f797a7f40 100644
--- a/src/smt/CMakeLists.txt
+++ b/src/smt/CMakeLists.txt
@@ -68,7 +68,6 @@ z3_add_component(smt
     theory_dl.cpp
     theory_dummy.cpp
     theory_fpa.cpp
-    theory_jobscheduler.cpp
     theory_lra.cpp
     theory_opt.cpp
     theory_pb.cpp
diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp
index c477dbeca..8cf2f244d 100644
--- a/src/smt/smt_setup.cpp
+++ b/src/smt/smt_setup.cpp
@@ -37,7 +37,6 @@ Revision History:
 #include "smt/theory_pb.h"
 #include "smt/theory_fpa.h"
 #include "smt/theory_str.h"
-#include "smt/theory_jobscheduler.h"
 
 namespace smt {
 
@@ -123,8 +122,6 @@ namespace smt {
             setup_UFLRA();
         else if (m_logic == "LRA")
             setup_LRA();
-        else if (m_logic == "CSP")
-            setup_CSP();
         else if (m_logic == "QF_FP")
             setup_QF_FP();
         else if (m_logic == "QF_FPBV" || m_logic == "QF_BVFP")
@@ -203,8 +200,6 @@ namespace smt {
                 setup_QF_DT();
             else if (m_logic == "LRA")
                 setup_LRA();
-            else if (m_logic == "CSP")
-                setup_CSP();
             else 
                 setup_unknown(st);
         }
@@ -932,11 +927,6 @@ namespace smt {
         m_context.register_plugin(alloc(smt::theory_seq, m_context));
     }
 
-    void setup::setup_CSP() {
-        setup_unknown();
-        m_context.register_plugin(alloc(smt::theory_jobscheduler, m_context));
-    }
-
     void setup::setup_special_relations() {
         m_context.register_plugin(alloc(smt::theory_special_relations, m_context, m_manager));
     }
diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp
deleted file mode 100644
index 8684e65d0..000000000
--- a/src/smt/theory_jobscheduler.cpp
+++ /dev/null
@@ -1,1183 +0,0 @@
-/*++
-Copyright (c) 2018 Microsoft Corporation
-
-Module Name:
-
-    theory_jobscheduler.cpp
-
-Abstract:
-
-
-Author:
-
-    Nikolaj Bjorner (nbjorner) 2018-09-08.
-
-Revision History:
-
-TODO:
-
-- arithmetic interface
-
-- propagation queue:
-  - register bounds on start times to propagate energy constraints
-  - more general registration mechanism for arithmetic theory.
-- interact with opt
-- jobs without resources
-  - complain or add dummy resource? At which level.
-
-Features:
-- properties
-- priority
-  - try mss based from outside
-- job-goal
-   - try optimization based on arithmetic solver.
-   - earliest start, latest start
-- constraint level
-   - add constraints gradually
-- resource groups
-  - resource groups like a resource
-  - resources bound to resource groups within time intervals
-  - job can require to use K resources from a resource group simultaneously.
-
---*/
-
-#include "ast/ast_pp.h"
-#include "smt/theory_jobscheduler.h"
-#include "smt/smt_context.h"
-#include "smt/smt_arith_value.h"
-#include "smt/smt_model_generator.h"
-
-namespace smt {
-
-    theory_var theory_jobscheduler::mk_var(enode * n) {
-        theory_var v = theory::mk_var(n);
-        return v;
-    }
-
-    bool theory_jobscheduler::internalize_term(app * term) {
-        if (ctx.e_internalized(term))
-            return true;
-        for (expr* arg : *term) {
-            if (!ctx.e_internalized(arg)) 
-                ctx.internalize(arg, false);
-        }        
-        enode* e = ctx.mk_enode(term, false, false, true);
-        theory_var v = mk_var(e);
-        ctx.attach_th_var(e, this, v);
-        ctx.mark_as_relevant(e);
-        return true;
-    }
-
-    bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) {
-        TRACE("csp", tout << mk_pp(atom, m) << "\n";);
-        SASSERT(u.is_model(atom));
-        for (expr* arg : *atom) {
-            if (!ctx.e_internalized(arg)) ctx.internalize(arg, false);
-            internalize_cmd(arg);
-        }
-        add_done();
-        bool_var bv = ctx.mk_bool_var(atom);
-        ctx.set_var_theory(bv, get_id());
-        return true;
-    }
-
-    struct symbol_cmp {
-        bool operator()(symbol const& s1, symbol const& s2) const {
-            return lt(s1, s2);
-        }
-    };
-
-    // TBD: stronger parameter validation
-    void theory_jobscheduler::internalize_cmd(expr* cmd) {
-        symbol key, val;
-        rational r;
-        expr* job, *resource;
-        unsigned j = 0, res = 0, cap = 0, loadpct = 100, level = 0;
-        time_t start = 0, end = std::numeric_limits<time_t>::max(), capacity = 0;
-        js_job_goal goal;
-        js_optimization_objective obj;
-        properties ps;
-        if (u.is_set_preemptable(cmd, job) && u.is_job(job, j)) {
-            set_preemptable(j, true);            
-        }
-        else if (u.is_add_resource_available(cmd, resource, loadpct, cap, start, end, ps) && u.is_resource(resource, res)) {
-            std::sort(ps.begin(), ps.end(), symbol_cmp());
-            (void) cap; // TBD
-            add_resource_available(res, loadpct, start, end, ps);
-        }
-        else if (u.is_add_job_resource(cmd, job, resource, loadpct, capacity, end, ps) && u.is_job(job, j) && u.is_resource(resource, res)) {
-            std::sort(ps.begin(), ps.end(), symbol_cmp());
-            add_job_resource(j, res, loadpct, capacity, end, ps);
-        }
-        else if (u.is_job_goal(cmd, goal, level, job) && u.is_job(job, j)) {
-            // skip
-        }
-        else if (u.is_objective(cmd, obj)) {
-            // skip
-        }
-        else {
-            invalid_argument("command not recognized ", cmd);
-        }
-    }
-
-    void theory_jobscheduler::invalid_argument(char const* msg, expr* arg) {
-        std::ostringstream strm;
-        strm << msg << mk_pp(arg, m);
-        throw default_exception(strm.str());
-    }
-
-    void theory_jobscheduler::new_eq_eh(theory_var v1, theory_var v2) {
-        enode* e1 = get_enode(v1);
-        enode* root = e1->get_root();
-        unsigned r;
-        if (u.is_resource(root->get_owner(), r)) {
-            enode* next = e1;
-            do {
-                unsigned j;
-                if (u.is_job2resource(next->get_owner(), j) && !m_jobs[j].m_is_bound) {
-                    m_bound_jobs.push_back(j);
-                    m_jobs[j].m_is_bound = true;
-                }
-                next = next->get_next();
-            }
-            while (e1 != next);
-        }
-    }
-
-
-    void theory_jobscheduler::new_diseq_eh(theory_var v1, theory_var v2) {
-
-    }
-
-    void theory_jobscheduler::push_scope_eh() {
-        scope s;
-        s.m_bound_jobs_lim = m_bound_jobs.size();
-        s.m_bound_qhead = m_bound_qhead;
-        m_scopes.push_back(s);
-    }
-
-    void theory_jobscheduler::pop_scope_eh(unsigned num_scopes) {
-        unsigned new_lvl = m_scopes.size() - num_scopes;
-        scope const& s = m_scopes[new_lvl];
-        for (unsigned i = s.m_bound_jobs_lim; i < m_bound_jobs.size(); ++i) {
-            unsigned j = m_bound_jobs[i];
-            m_jobs[j].m_is_bound = false;
-        }
-        m_bound_jobs.shrink(s.m_bound_jobs_lim);
-        m_bound_qhead = s.m_bound_qhead;
-        m_scopes.shrink(new_lvl);
-    }
-
-    final_check_status theory_jobscheduler::final_check_eh() {
-        TRACE("csp", tout << "\n";);
-        bool blocked = false;
-        for (unsigned j = 0; j < m_jobs.size(); ++j) {
-            if (split_job2resource(j)) {
-                return FC_CONTINUE;
-            }
-        }
-        for (unsigned r = 0; r < m_resources.size(); ++r) {
-            if (constrain_resource_energy(r)) {
-                blocked = true;
-            }
-        }
-        for (unsigned j = 0; j < m_jobs.size(); ++j) {
-            if (constrain_end_time_interval(j, resource(j))) {
-                blocked = true;
-            }
-        }
-        
-        if (blocked) return FC_CONTINUE;
-        return FC_DONE;
-    }
-
-    bool theory_jobscheduler::can_propagate() {
-        return m_bound_qhead < m_bound_jobs.size();
-    }
-
-    literal theory_jobscheduler::mk_literal(expr * e) {
-        expr_ref _e(e, m);
-        if (!ctx.e_internalized(e)) {
-            ctx.internalize(e, false);
-        }
-        ctx.mark_as_relevant(ctx.get_enode(e));
-        return ctx.get_literal(e);
-    }
-
-    literal theory_jobscheduler::mk_ge(expr* e, time_t t) {
-        return mk_literal(mk_ge_expr(e, t));
-    }
-
-    expr* theory_jobscheduler::mk_ge_expr(expr* e, time_t t) {
-        return a.mk_ge(e, a.mk_int(rational(t, rational::ui64())));
-    }
-
-    literal theory_jobscheduler::mk_ge(enode* e, time_t t) {
-        return mk_ge(e->get_owner(), t);
-    }
-
-    literal theory_jobscheduler::mk_le(expr* e, time_t t) {
-        return mk_literal(mk_le_expr(e, t));
-    }
-
-    expr* theory_jobscheduler::mk_le_expr(expr* e, time_t t) {
-        return a.mk_le(e, a.mk_int(rational(t, rational::ui64())));
-    }
-
-    literal theory_jobscheduler::mk_le(enode* l, enode* r) {
-        expr_ref le(a.mk_le(l->get_owner(), r->get_owner()), m);
-        ctx.get_rewriter()(le);
-        return mk_literal(le);
-    }
-
-    literal theory_jobscheduler::mk_le(enode* e, time_t t) {
-        return mk_le(e->get_owner(), t);
-    }
-
-    literal theory_jobscheduler::mk_eq_lit(expr * l, expr * r) {
-        literal lit = mk_eq(l, r, false);
-        ctx.mark_as_relevant(lit);
-        return lit;
-    }
-
-
-    /**
-     * iterator of job overlaps.
-     */
-    theory_jobscheduler::job_overlap::job_overlap(vector<job_time>& starts, vector<job_time>& ends):
-        m_start(0), m_starts(starts), m_ends(ends), s_idx(0), e_idx(0) {
-        job_time::compare cmp;
-        std::sort(starts.begin(), starts.end(), cmp);
-        std::sort(ends.begin(),   ends.end(),   cmp);        
-    }
-
-    bool theory_jobscheduler::job_overlap::next() {
-        if (s_idx == m_starts.size()) {
-            return false;
-        }
-        do {
-            m_start = std::max(m_start, m_starts[s_idx].m_time);
-                
-            // add jobs that start before or at m_start
-            while (s_idx < m_starts.size() && m_starts[s_idx].m_time <= m_start) {
-                m_jobs.insert(m_starts[s_idx].m_job);
-                ++s_idx;
-            }
-            // remove jobs that end before m_start.
-            while (e_idx < m_ends.size() && m_ends[e_idx].m_time < m_start) {
-                m_jobs.remove(m_ends[e_idx].m_job);
-                ++e_idx;
-            }   
-        }
-        // as long as set of jobs increments, add to m_start
-        while (s_idx < m_starts.size() && e_idx < m_ends.size() && 
-               m_starts[s_idx].m_time <= m_ends[e_idx].m_time);
-            
-        return true;
-    }
-
-
-    /**
-     *  r = resource(j) & start(j) >= slb => end(j) >= ect(j, r, slb)
-     *
-     * note: not used so far
-     * note: subsumed by constrain_end_time_interval used in final-check
-     */
-    void theory_jobscheduler::propagate_end_time(unsigned j, unsigned r) {
-        time_t slb = est(j);
-        time_t clb = ect(j, r, slb);
-
-        if (clb > end(j)) {
-            job_info const& ji = m_jobs[j];
-            literal start_ge_lo = mk_ge(ji.m_start, slb);
-            if (ctx.get_assignment(start_ge_lo) != l_true) {
-                return;
-            }
-            enode_pair eq(ji.m_job2resource, m_resources[r].m_resource);
-            if (eq.first->get_root() != eq.second->get_root()) {
-                return;
-            }
-
-            literal end_ge_lo = mk_ge(ji.m_end, clb);
-            // Initialization ensures that satisfiable states have completion time below end.
-            ast_manager& m = get_manager();
-            if (m.has_trace_stream()) {
-                app_ref body(m);
-                body = m.mk_implies(m.mk_and(m.mk_eq(eq.first->get_owner(), eq.second->get_owner()), ctx.bool_var2expr(start_ge_lo.var())), ctx.bool_var2expr(end_ge_lo.var()));
-                log_axiom_instantiation(body);
-                m.trace_stream() << "[end-of-instance]\n";
-            }
-            region& r = ctx.get_region();
-            ctx.assign(end_ge_lo, 
-                       ctx.mk_justification(
-                           ext_theory_propagation_justification(get_id(), r, 1, &start_ge_lo, 1, &eq, end_ge_lo, 0, nullptr)));
-        }
-    }
-
-    /**
-     *  For time interval [t0, t1] the end-time can be computed as a function 
-     *  of start time based on resource load availability.
-     * 
-     *  r = resource(j) & t1 >= start(j) >= t0 => end(j) = start(j) + ect(j, r, t0) - t0
-     */
-    bool theory_jobscheduler::constrain_end_time_interval(unsigned j, unsigned r) {
-        unsigned idx1 = 0, idx2 = 0;
-        if (!job_has_resource(j, r)) {
-            IF_VERBOSE(0, verbose_stream() << "job " << j << " assigned non-registered resource " << r << "\n");
-            return false;
-        }
-        time_t s = start(j);
-        job_resource const& jr = get_job_resource(j, r);
-        TRACE("csp", tout << "job: " << j << " resource: " << r << " start: " << s <<  "\n";);
-        vector<res_available>& available = m_resources[r].m_available;
-        if (!resource_available(r, s, idx1)) return false;
-        if (!resource_available(jr, available[idx1])) return false;
-        time_t e = ect(j, r, s);
-        TRACE("csp", tout << "job: " << j << " resource: " << r << " ect: " << e <<  "\n";);
-        if (!resource_available(r, e, idx2)) return false; // infeasible..
-        if (!resource_available(jr, available[idx2])) return false;
-        time_t start1 = available[idx1].m_start;
-        time_t end1   = available[idx1].m_end;
-        unsigned cap1 = available[idx1].m_loadpct;
-        time_t start2 = available[idx2].m_start;
-        time_t end2   = available[idx2].m_end;
-        unsigned cap2 = available[idx2].m_loadpct;
-        // calculate minimal start1 <= t0 <= s,    such that ect(j, r, t0) >= start2 
-        // calculate maximal s      <= t1 <= end1, such that ect(j, r, t1) <= end2
-        time_t delta1 = (s - start1)*cap1;
-        time_t delta2 = (e - start2)*cap2;
-        time_t t0, t1;
-        if (delta1 <= delta2) {
-            t0 = start1;
-        }
-        else {
-            // solve for t0:
-            // (s - t0)*cap1 = (e - start2)*cap2;
-            t0 = s - (delta2 / cap1); 
-        }
-        delta1 = (end1 - s)*cap1;
-        delta2 = (end2 - e)*cap2;
-        if (delta1 <= delta2) {
-            t1 = end1;
-        }
-        else {
-            // solve for t1:
-            // (t1 - s)*cap1 = (end2 - e)*cap2
-            t1 = s + (delta2 / cap1);
-        }
-
-        time_t delta = ect(j, r, t0) - t0;
-        if (end(j) == start(j) + delta) {
-            return false;
-        }
-        TRACE("csp", tout << "job: " << j << " resource " << r << " t0: " << t0 << " t1: " << t1 << " delta: " << delta << "\n";);
-        literal_vector lits;
-        enode* start_e = m_jobs[j].m_start;
-        enode* end_e = m_jobs[j].m_end;
-        lits.push_back(~mk_eq_lit(m_jobs[j].m_job2resource, m_resources[r].m_resource));
-        lits.push_back(~mk_ge(start_e, t0));
-        lits.push_back(~mk_le(start_e, t1));        
-        expr_ref rhs(a.mk_add(start_e->get_owner(), a.mk_int(rational(delta, rational::ui64()))), m);
-        lits.push_back(mk_eq_lit(end_e->get_owner(), rhs));
-        ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_TH_LEMMA, nullptr);
-        ast_manager& m = get_manager();
-        if (m.has_trace_stream()) {
-            app_ref body(m);
-            body = m.mk_implies(m.mk_and(ctx.bool_var2expr(lits[0].var()), ctx.bool_var2expr(lits[1].var()), ctx.bool_var2expr(lits[2].var())), ctx.bool_var2expr(lits[3].var()));
-            log_axiom_instantiation(body);
-            m.trace_stream() << "[end-of-instance]\n";
-        }
-        return true;
-    }
-
-
-    /**
-     *  Ensure that job overlaps don't exceed available energy
-     */ 
-    bool theory_jobscheduler::constrain_resource_energy(unsigned r) {
-        bool blocked = false;
-        vector<job_time> starts, ends;
-        res_info const& ri = m_resources[r];
-        for (unsigned j : ri.m_jobs) {
-            if (resource(j) == r) {
-                starts.push_back(job_time(j, start(j)));
-                ends.push_back(job_time(j, end(j)));
-            }
-        }
-        job_overlap overlap(starts, ends);
-        while (overlap.next()) {
-            unsigned cap = 0;
-            auto const& jobs = overlap.jobs();
-            for (auto j : jobs) {
-                cap += get_job_resource(j, r).m_loadpct;
-                if (cap > 100) {
-                    block_job_overlap(r, jobs, j);
-                    blocked = true;
-                    goto try_next_overlap;
-                }
-            }    
-        try_next_overlap:
-            ;
-        }
-        return blocked;
-    }
-
-    void theory_jobscheduler::block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job) {
-        //
-        // block the following case:
-        // each job is assigned to r.
-        // max { start(j) | j0..last_job } <= min { end(j) | j0..last_job }
-        // joint capacity of jobs exceeds availability of resource.
-        // 
-        time_t max_start = 0;
-        unsigned max_j = last_job;
-        for (auto j : jobs) {
-            if (max_start < start(j)) {
-                max_start = start(j);
-                max_j = j;
-            }
-            if (j == last_job) break;
-        }
-        literal_vector lits;
-        for (auto j : jobs) {
-            // create literals for:  
-            // resource(j) == r
-            // m_jobs[j].m_start <= m_jobs[max_j].m_start;
-            // m_jobs[max_j].m_start <= m_jobs[j].m_end;
-            lits.push_back(~mk_eq_lit(m_jobs[j].m_job2resource, m_resources[r].m_resource));
-            if (j != max_j) {
-                lits.push_back(~mk_le(m_jobs[j].m_start, m_jobs[max_j].m_start));
-                lits.push_back(~mk_le(m_jobs[max_j].m_start, m_jobs[j].m_end));
-            }
-            if (j == last_job) break;
-        }
-        ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_TH_LEMMA, nullptr);
-    }
-
-    void theory_jobscheduler::propagate() {
-        while (m_bound_qhead < m_bound_jobs.size()) {
-            unsigned j = m_bound_jobs[m_bound_qhead++];
-            unsigned r = 0;
-            job_info const& ji = m_jobs[j];
-            VERIFY(u.is_resource(ji.m_job2resource->get_root()->get_owner(), r));
-            TRACE("csp", tout << "job: " << j << " resource: " << r << "\n";);
-            std::cout << j << " -o " << r << "\n";
-            propagate_job2resource(j, r);
-        }
-    }
-
-    void theory_jobscheduler::propagate_job2resource(unsigned j, unsigned r) {
-        job_info const& ji = m_jobs[j];
-        res_info const& ri = m_resources[r];
-        literal eq = mk_eq_lit(ji.m_job2resource, ri.m_resource);
-        if (!job_has_resource(j, r)) {
-            IF_VERBOSE(0, verbose_stream() << "job " << j << " assigned non-registered resource " << r << "\n");
-            return;
-        }
-        return;
-        job_resource const& jr = get_job_resource(j, r);
-        assert_last_end_time(j, r, jr, eq);
-        assert_last_start_time(j, r, eq);
-        assert_first_start_time(j, r, eq);
-        vector<res_available> const& available = ri.m_available;
-        // TBD: needs to take properties into account
-        
-        unsigned i = 0;
-        if (!first_available(jr, ri, i)) return;
-        while (true) {
-            unsigned next = i + 1;
-            if (!first_available(jr, ri, next)) return;
-            SASSERT(available[i].m_end < available[next].m_start);
-            assert_job_not_in_gap(j, r, i, next, eq);            
-            if (!ji.m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) {
-                assert_job_non_preemptable(j, r, i, next, eq);
-            }
-            i = next;
-        }
-    }
-        
-    theory_jobscheduler::theory_jobscheduler(context& ctx): 
-        theory(ctx, ctx.get_manager().get_family_id("csp")), 
-        m(ctx.get_manager()), 
-        u(m), 
-        a(m), 
-        m_bound_qhead(0) {
-    }
-
-    std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const {
-        return out << "r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_finite_capacity_end;
-        for (auto const& s : jr.m_properties) {
-            out << " " << s;
-        }
-        out << "\n";
-    }
-
-    std::ostream& theory_jobscheduler::display(std::ostream & out, job_info const& j) const {
-        for (job_resource const& jr : j.m_resources) {
-            display(out << "  ", jr) << "\n";
-        }
-        return out;
-    }
-
-    std::ostream& theory_jobscheduler::display(std::ostream & out, res_available const& r) const {
-        return out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%";
-        for (auto const& s : r.m_properties) {
-            out << " " << s;
-        }
-        out << "\n";
-    }
-
-    std::ostream& theory_jobscheduler::display(std::ostream & out, res_info const& r) const {
-        for (res_available const& ra : r.m_available) {
-            display(out << "   ", ra);
-        }
-        return out;
-    }
-
-    void theory_jobscheduler::display(std::ostream & out) const {
-        out << "jobscheduler:\n";
-        for (unsigned j = 0; j < m_jobs.size(); ++j) {
-            display(out << "job " << j << ":\n", m_jobs[j]) << "\n";
-        }
-        for (unsigned r = 0; r < m_resources.size(); ++r) {
-            display(out << "resource " << r << ":\n", m_resources[r]) << "\n";
-        }
-    }
-        
-    void theory_jobscheduler::collect_statistics(::statistics & st) const {
-
-    }
-
-    bool theory_jobscheduler::include_func_interp(func_decl* f) {
-        return 
-            f->get_decl_kind() == OP_JS_START ||
-            f->get_decl_kind() == OP_JS_END ||
-            f->get_decl_kind() == OP_JS_JOB2RESOURCE;
-    }
-
-    void theory_jobscheduler::init_model(model_generator & m) {
-
-    }
-    
-    model_value_proc * theory_jobscheduler::mk_value(enode * n, model_generator & mg) {
-        return alloc(expr_wrapper_proc, n->get_root()->get_owner());
-    }
-
-    bool theory_jobscheduler::get_value(enode * n, expr_ref & r) {
-        std::cout << mk_pp(n->get_owner(), m) << "\n";
-        return false;
-    }
-
-    theory * theory_jobscheduler::mk_fresh(context * new_ctx) { 
-        return alloc(theory_jobscheduler, *new_ctx);
-    }
-
-    time_t theory_jobscheduler::get_lo(expr* e) {
-        arith_value av(m);
-        av.init(&get_context());
-        rational val;
-        bool is_strict;
-        if (av.get_lo(e, val, is_strict) && !is_strict && val.is_uint64()) {
-            return val.get_uint64();
-        }
-        return 0;
-    }
-
-    time_t theory_jobscheduler::get_up(expr* e) {
-        arith_value av(m);
-        av.init(&get_context());
-        rational val;
-        bool is_strict;
-        if (av.get_up(e, val, is_strict) && !is_strict && val.is_uint64()) {
-            return val.get_uint64();
-        }
-        return std::numeric_limits<time_t>::max();
-    }
-
-    time_t theory_jobscheduler::get_value(expr* e) {
-        arith_value av(get_manager());
-        av.init(&get_context());
-        rational val;
-        if (av.get_value_equiv(e, val) && val.is_uint64()) {
-            return val.get_uint64();
-        }
-        return 0;
-    }    
-
-    time_t theory_jobscheduler::est(unsigned j) {
-        return get_lo(m_jobs[j].m_start->get_owner());
-    }
-
-    time_t theory_jobscheduler::lst(unsigned j) {
-        return get_up(m_jobs[j].m_start->get_owner());
-    }
-
-    time_t theory_jobscheduler::ect(unsigned j) {
-        return get_lo(m_jobs[j].m_end->get_owner());
-    }
-
-    time_t theory_jobscheduler::lct(unsigned j) {
-        return get_up(m_jobs[j].m_end->get_owner());
-    }
-
-    time_t theory_jobscheduler::start(unsigned j) {
-        return get_value(m_jobs[j].m_start->get_owner());
-    }
-
-    time_t theory_jobscheduler::end(unsigned j) {
-        return get_value(m_jobs[j].m_end->get_owner());
-    }
-
-    unsigned theory_jobscheduler::resource(unsigned j) {
-        unsigned r;
-        enode* next = m_jobs[j].m_job2resource, *n = next;
-        do {
-            if (u.is_resource(next->get_owner(), r)) {
-                return r;
-            }
-            next = next->get_next();
-        }
-        while (next != n);
-        return 0;
-    }
-
-    void theory_jobscheduler::set_preemptable(unsigned j, bool is_preemptable) {
-        ensure_job(j).m_is_preemptable = is_preemptable;        
-    }
-
-    theory_jobscheduler::res_info& theory_jobscheduler::ensure_resource(unsigned last) {
-        while (m_resources.size() <= last) {
-            unsigned r = m_resources.size();
-            m_resources.push_back(res_info());
-            res_info& ri = m_resources.back();
-            app_ref res(u.mk_resource(r), m);
-            if (!ctx.e_internalized(res)) ctx.internalize(res, false);            
-            ri.m_resource = ctx.get_enode(res);
-            app_ref ms(u.mk_makespan(r), m);
-            if (!ctx.e_internalized(ms)) ctx.internalize(ms, false);            
-            ri.m_makespan = ctx.get_enode(ms);
-        }
-        return m_resources[last];
-    }
-
-    theory_jobscheduler::job_info& theory_jobscheduler::ensure_job(unsigned last) {
-        while (m_jobs.size() <= last) {
-            unsigned j = m_jobs.size();
-            m_jobs.push_back(job_info());
-            job_info& ji = m_jobs.back();
-            app_ref job(u.mk_job(j), m);
-            app_ref start(u.mk_start(j), m);
-            app_ref end(u.mk_end(j), m);
-            app_ref res(u.mk_job2resource(j), m);
-            if (!ctx.e_internalized(job))   ctx.internalize(job, false);
-            if (!ctx.e_internalized(start)) ctx.internalize(start, false);
-            if (!ctx.e_internalized(end))   ctx.internalize(end, false);
-            if (!ctx.e_internalized(res))   ctx.internalize(res, false);
-            ji.m_job      = ctx.get_enode(job);
-            ji.m_start    = ctx.get_enode(start);
-            ji.m_end      = ctx.get_enode(end);
-            ji.m_job2resource = ctx.get_enode(res);
-        }
-        return m_jobs[last];
-    }
-
-    void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned loadpct, uint64_t cap, time_t finite_capacity_end, properties const& ps) {
-        SASSERT(ctx.at_base_level());
-        SASSERT(0 <= loadpct && loadpct <= 100);
-        SASSERT(0 <= cap);
-        job_info& ji = ensure_job(j);
-        res_info& ri = ensure_resource(r);
-        if (ji.m_resource2index.contains(r)) {
-            throw default_exception("resource already bound to job");
-        }
-        ji.m_resource2index.insert(r, ji.m_resources.size());
-        ji.m_resources.push_back(job_resource(r, cap, loadpct, finite_capacity_end, ps));
-        SASSERT(!ri.m_jobs.contains(j));
-        ri.m_jobs.push_back(j);
-    }
-
-
-    void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps) {
-        SASSERT(ctx.at_base_level());
-        SASSERT(1 <= max_loadpct && max_loadpct <= 100);
-        SASSERT(start <= end);
-        res_info& ri = ensure_resource(r);
-        ri.m_available.push_back(res_available(max_loadpct, start, end, ps));
-    }
-
-    /*
-     * Initialize the state based on the set of jobs and resources added.
-     * Ensure that the availability slots for each resource is sorted by time.
-     *
-     * For each resource j:
-     *   est(j) <= start(j) <= end(j) <= lct(j)
-     * 
-     * possible strengthenings:
-     * - start(j) <= lst(j)
-     * - start(j) + min_completion_time(j) <= end(j)
-     * - start(j) + max_completion_time(j) >= end(j)
-     * 
-     * makespan constraints?
-     *
-     */
-    void theory_jobscheduler::add_done() {
-        TRACE("csp", tout << "add-done begin\n";);
-        
-        // sort availability intervals
-        for (res_info& ri : m_resources) {
-            vector<res_available>& available = ri.m_available;
-            res_available::compare cmp;
-            std::sort(available.begin(), available.end(), cmp);
-        }
-
-        literal lit;
-        unsigned job_id = 0;
-        
-        for (job_info const& ji : m_jobs) {
-            if (ji.m_resources.empty()) {
-                throw default_exception("every job should be associated with at least one resource");
-            }
-
-            // start(j) <= end(j)            
-            lit = mk_le(ji.m_start, ji.m_end);
-            if (m.has_trace_stream()) log_axiom_instantiation(ctx.bool_var2expr(lit.var()));
-            ctx.mk_th_axiom(get_id(), 1, &lit);
-            if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-
-            time_t start_lb = std::numeric_limits<time_t>::max();
-            time_t runtime_lb = std::numeric_limits<time_t>::max();
-            time_t end_ub = 0; // , runtime_ub = 0;
-            for (job_resource const& jr : ji.m_resources) {
-                unsigned r = jr.m_resource_id;
-                res_info const& ri = m_resources[r];
-                if (ri.m_available.empty()) {
-                    if (jr.m_capacity == 0) {
-                        start_lb = 0;
-                        end_ub = std::numeric_limits<time_t>::max();
-                        runtime_lb = 0;
-                    }
-                    continue;
-                }
-                unsigned idx = 0;
-                if (first_available(jr, ri, idx)) {
-                    start_lb = std::min(start_lb, ri.m_available[idx].m_start);
-                }
-                else {
-                    IF_VERBOSE(0, verbose_stream() << "not first-available\n";);
-                }
-                idx = ri.m_available.size();
-                if (last_available(jr, ri, idx)) {
-                    end_ub = std::max(end_ub, ri.m_available[idx].m_end);                    
-                }
-                else {
-                    IF_VERBOSE(0, verbose_stream() << "not last-available\n";);
-                }
-                runtime_lb = std::min(runtime_lb, jr.m_capacity);
-                // TBD: more accurate estimates for runtime_lb based on gaps
-                // TBD: correct estimate of runtime_ub taking gaps into account.
-            }
-            CTRACE("csp", (start_lb > end_ub), tout << "there is no associated resource working time\n";);
-            if (start_lb > end_ub) {
-                IF_VERBOSE(0, verbose_stream() << start_lb << " " << end_ub << "\n");
-                warning_msg("Job %d has no associated resource working time", job_id);
-                continue;
-            }
-
-            // start(j) >= start_lb
-            lit = mk_ge(ji.m_start, start_lb);
-            if (m.has_trace_stream()) log_axiom_instantiation(ctx.bool_var2expr(lit.var()));
-            ctx.mk_th_axiom(get_id(), 1, &lit);
-            if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-
-            // end(j) <= end_ub
-            lit = mk_le(ji.m_end, end_ub);
-            if (m.has_trace_stream()) log_axiom_instantiation(ctx.bool_var2expr(lit.var()));
-            ctx.mk_th_axiom(get_id(), 1, &lit);
-            if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-
-            // start(j) + runtime_lb <= end(j)
-            // end(j) <= start(j) + runtime_ub 
-
-            ++job_id;
-        }        
-        
-        TRACE("csp", tout << "add-done end\n";);
-    }
-
-    // resource(j) = r => end(j) <= end(j, r)
-    void theory_jobscheduler::assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq) {
-#if 0
-        job_info const& ji = m_jobs[j];
-        literal l2 = mk_le(ji.m_end, jr.m_finite_capacity_end);
-        if (m.has_trace_stream()) {
-            app_ref body(m);
-            body = m.mk_implies(ctx.bool_var2expr(eq.var()), ctx.bool_var2expr(l2.var()));
-            log_axiom_instantiation(body);
-        }
-        ctx.mk_th_axiom(get_id(), ~eq, l2);
-        if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-#endif
-    }
-
-    // resource(j) = r => start(j) <= lst(j, r, end(j, r))
-    void theory_jobscheduler::assert_last_start_time(unsigned j, unsigned r, literal eq) {
-        time_t t;
-        if (lst(j, r, t)) {
-            literal le = mk_le(m_jobs[j].m_start, t);
-            if (m.has_trace_stream()) {
-                app_ref body(m);
-                body = m.mk_implies(ctx.bool_var2expr(eq.var()), ctx.bool_var2expr(le.var()));
-                log_axiom_instantiation(body);
-            }
-            ctx.mk_th_axiom(get_id(), ~eq, le);
-            if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-        }
-        else {
-            eq.neg();
-            if (m.has_trace_stream()) {
-                app_ref body(m);
-                body = m.mk_not(ctx.bool_var2expr(eq.var()));
-                log_axiom_instantiation(body);
-            }
-            ctx.mk_th_axiom(get_id(), 1, &eq);
-            if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-        }
-    }
-
-    // resource(j) = r => start(j) >= available[0].m_start
-    void theory_jobscheduler::assert_first_start_time(unsigned j, unsigned r, literal eq) {
-        job_resource const& jr = get_job_resource(j, r);
-        unsigned idx = 0;
-        if (!first_available(jr, m_resources[r], idx)) return;
-        vector<res_available>& available = m_resources[r].m_available;
-        literal l2 = mk_ge(m_jobs[j].m_start, available[idx].m_start);
-        if (m.has_trace_stream()) {
-            app_ref body(m);
-            body = m.mk_implies(ctx.bool_var2expr(eq.var()), ctx.bool_var2expr(l2.var()));
-            log_axiom_instantiation(body);
-        }
-        ctx.mk_th_axiom(get_id(), ~eq, l2);
-        if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-    }
-
-    // resource(j) = r => start(j) <= end[idx]  || start[idx+1] <= start(j);
-    void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq) {
-        job_resource const& jr = get_job_resource(j, r);
-        (void) jr;
-        vector<res_available>& available = m_resources[r].m_available;        
-        SASSERT(resource_available(jr, available[idx]));
-        literal l2 = mk_ge(m_jobs[j].m_start, available[idx1].m_start);
-        literal l3 = mk_le(m_jobs[j].m_start, available[idx].m_end);
-        if (m.has_trace_stream()) {
-            app_ref body(m);
-            body = m.mk_implies(ctx.bool_var2expr(eq.var()), m.mk_or(ctx.bool_var2expr(l2.var()), ctx.bool_var2expr(l3.var())));
-            log_axiom_instantiation(body);
-        }
-        ctx.mk_th_axiom(get_id(), ~eq, l2, l3);        
-        if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-    }
-
-    // resource(j) = r => end(j) <= end[idx] || start[idx+1] <= start(j);
-    void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq) {
-        vector<res_available>& available = m_resources[r].m_available;        
-        job_resource const& jr = get_job_resource(j, r);
-        (void) jr;
-        SASSERT(resource_available(jr, available[idx]));
-        literal l2 = mk_le(m_jobs[j].m_end, available[idx].m_end);
-        literal l3 = mk_ge(m_jobs[j].m_start, available[idx1].m_start);
-        if (m.has_trace_stream()) {
-            app_ref body(m);
-            body = m.mk_implies(ctx.bool_var2expr(eq.var()), m.mk_or(ctx.bool_var2expr(l2.var()), ctx.bool_var2expr(l3.var())));
-            log_axiom_instantiation(body);
-        }
-        ctx.mk_th_axiom(get_id(), ~eq, l2, l3);
-        if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-    }    
-
-    /**
-     * bind a job to one of the resources it can run on.
-     */
-    bool theory_jobscheduler::split_job2resource(unsigned j) {
-        job_info const& ji = m_jobs[j];
-        if (ji.m_is_bound) return false;
-        auto const& jrs = ji.m_resources;
-        for (job_resource const& jr : jrs) {
-            unsigned r = jr.m_resource_id;
-            res_info const& ri = m_resources[r];
-            enode* e1 = ji.m_job2resource;
-            enode* e2 = ri.m_resource;
-            if (ctx.is_diseq(e1, e2))
-                continue;
-            literal eq = mk_eq_lit(e1, e2);
-            if (m.has_trace_stream()) {
-                app_ref body(m);
-                body = m.mk_or(ctx.bool_var2expr(eq.var()), m.mk_not(ctx.bool_var2expr(eq.var())));
-                log_axiom_instantiation(body);
-                m.trace_stream() << "[end-of-instance]\n";
-            }
-            if (ctx.get_assignment(eq) != l_false) {
-                ctx.mark_as_relevant(eq);
-                if (assume_eq(e1, e2)) {
-                    return true;
-                }
-            }
-        }
-        literal_vector lits;
-        expr_ref_vector exprs(m);
-        for (job_resource const& jr : jrs) {
-            unsigned r = jr.m_resource_id;
-            res_info const& ri = m_resources[r];
-            enode* e1 = ji.m_job2resource;
-            enode* e2 = ri.m_resource;
-            literal eq = mk_eq_lit(e1, e2);
-            lits.push_back(eq);
-            exprs.push_back(ctx.bool_var2expr(eq.var()));
-        }
-        if (m.has_trace_stream()) {
-            app_ref body(m);
-            body = m.mk_or(exprs.size(), exprs.c_ptr());
-            log_axiom_instantiation(body);
-        }
-        ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr());
-        if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
-        return true;
-    }
-
-    /**
-     * check that each job is run on some resource according to 
-     * requested capacity.
-     * 
-     * Check that the sum of jobs run in each time instance
-     * does not exceed capacity.
-     */
-    void theory_jobscheduler::validate_assignment() {
-        vector<vector<job_time>> start_times, end_times;
-        start_times.reserve(m_resources.size());
-        end_times.reserve(m_resources.size());
-        for (unsigned j = 0; j < m_jobs.size(); ++j) {
-            unsigned r = resource(j);
-            start_times[r].push_back(job_time(j, start(j))); 
-            end_times[r].push_back(job_time(j, end(j))); 
-            if (ect(j, r, start(j)) > end(j)) {
-                throw default_exception("job not assigned full capacity");
-            }
-            unsigned idx;
-            if (!resource_available(r, start(j), idx)) {
-                throw default_exception("resource is not available at job start time");
-            }
-        }
-        for (unsigned r = 0; r < m_resources.size(); ++r) {
-            // order jobs running on r by start, end-time intervals
-            // then consume ordered list to find jobs in scope.
-            job_overlap overlap(start_times[r], end_times[r]);
-            while (overlap.next()) {
-                // check that sum of job loads does not exceed 100%
-                unsigned cap = 0;
-                for (auto j : overlap.jobs()) {
-                    cap += get_job_resource(j, r).m_loadpct;
-                }
-                if (cap > 100) {
-                    throw default_exception("capacity on resource exceeded");
-                }
-            }
-        }
-    }
-
-    bool theory_jobscheduler::job_has_resource(unsigned j, unsigned r) const {
-        return m_jobs[j].m_resource2index.contains(r);
-    }
-
-    theory_jobscheduler::job_resource const& theory_jobscheduler::get_job_resource(unsigned j, unsigned r) const {
-        job_info const& ji = m_jobs[j];
-        return ji.m_resources[ji.m_resource2index[r]];
-    }
-
-    /**
-     * find idx, if any, such that t is within the time interval of available[idx]
-     */    
-    bool theory_jobscheduler::resource_available(unsigned r, time_t t, unsigned& idx) {
-        vector<res_available>& available = m_resources[r].m_available;
-        unsigned lo = 0, hi = available.size(), mid = hi / 2;
-        while (lo < hi) {
-            SASSERT(lo <= mid && mid < hi);
-            res_available const& ra = available[mid];
-            if (ra.m_start <= t && t <= ra.m_end) {
-                idx = mid;
-                return true;
-            }
-            else if (ra.m_start > t && mid > 0) {
-                hi = mid;
-                mid = lo + (mid - lo) / 2;
-            }
-            else if (ra.m_end < t) {
-                lo = mid + 1;
-                mid += (hi - mid) / 2;
-            }
-            else {
-                break;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * compute earliest completion time for job j on resource r starting at time start.
-     */    
-    time_t theory_jobscheduler::ect(unsigned j, unsigned r, time_t start) {
-        job_resource const& jr = get_job_resource(j, r);
-        vector<res_available>& available = m_resources[r].m_available;
-
-        unsigned j_load_pct = jr.m_loadpct;
-        time_t cap = jr.m_capacity;
-        unsigned idx = 0;
-        if (!resource_available(r, start, idx)) {
-            TRACE("csp", tout << "resource is not available for " << j << " " << r << "\n";);
-            return std::numeric_limits<time_t>::max();
-        }
-        SASSERT(cap > 0);
-        
-        for (; idx < available.size(); ++idx) {
-            if (!resource_available(jr, available[idx])) continue;
-            start             = std::max(start, available[idx].m_start);
-            time_t end        = available[idx].m_end;
-            unsigned load_pct = available[idx].m_loadpct;
-            time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end);
-            TRACE("csp", tout << "delta: " << delta << " capacity: " << cap << " load " 
-                  << load_pct << " jload: " << j_load_pct << " start: " << start << " end " << end << "\n";);
-            if (delta > cap) {
-                end = solve_for_end(load_pct, j_load_pct, start, cap);
-                cap = 0;
-            }
-            else {
-                cap -= delta;
-            }
-            if (cap == 0) {
-                return end;
-            }
-        }        
-        return std::numeric_limits<time_t>::max();        
-    }
-
-    /**
-     * find end, such that cap = (load / job_load_pct) * (end - start + 1)
-     */
-    time_t theory_jobscheduler::solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap) {
-        SASSERT(load_pct > 0);
-        SASSERT(job_load_pct > 0);
-        //      cap = (load / job_load_pct) * (end - start + 1)
-        // <=> 
-        //      end - start + 1 = (cap * job_load_pct) / load 
-        // <=>
-        //      end = start - 1 + (cap * job_load_pct) / load
-        // <=>
-        //      end = (load * (start - 1) + cap * job_load_pct) / load
-        unsigned load = std::min(load_pct, job_load_pct);
-        return (load * (start - 1) + cap * job_load_pct) / load;
-    }
-
-    /**
-     * find start, such that cap = (load / job_load_pct) * (end - start + 1)
-     */
-    time_t theory_jobscheduler::solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap) {
-        SASSERT(load_pct > 0);
-        SASSERT(job_load_pct > 0);
-        //      cap = (load / job_load_pct) * (end - start + 1)
-        // <=> 
-        //      end - start + 1 = (cap * job_load_pct) / load 
-        // <=>
-        //      start = end + 1 - (cap * job_load_pct) / load
-        // <=>
-        //      start = (load * (end + 1) - cap * job_load_pct) / load
-        unsigned load = std::min(load_pct, job_load_pct);
-        return (load * (end + 1) - cap * job_load_pct) / load;
-    }
-
-    /**
-     * find cap, such that cap = (load / job_load_pct) * (end - start + 1)
-     */
-    time_t theory_jobscheduler::solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end) {
-        SASSERT(job_load_pct > 0);
-        unsigned load = std::min(load_pct, job_load_pct);
-        return (load * (end - start + 1)) / job_load_pct;
-    }
-
-    /**
-     * Compute last start time for job on resource r.
-     */
-    bool theory_jobscheduler::lst(unsigned j, unsigned r, time_t& start) {
-        start = 0;
-        job_resource const& jr = get_job_resource(j, r);
-        vector<res_available>& available = m_resources[r].m_available;
-        unsigned j_load_pct = jr.m_loadpct;
-        time_t cap = jr.m_capacity;
-        for (unsigned idx = available.size(); idx-- > 0; ) {
-            if (!resource_available(jr, available[idx])) continue;
-            start    = available[idx].m_start;
-            time_t end      = available[idx].m_end;
-            unsigned load_pct = available[idx].m_loadpct;
-            time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end);
-            if (delta > cap) {
-                start = solve_for_start(load_pct, j_load_pct, end, cap);
-                cap = 0;
-            }
-            else {
-                cap -= delta;
-            }
-            if (cap == 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * \brief check that job properties is a subset of resource properties.
-     * It assumes that both vectors are sorted.
-     */
-
-    bool theory_jobscheduler::resource_available(job_resource const& jr, res_available const& ra) const {
-        auto const& jps = jr.m_properties;
-        auto const& rps = ra.m_properties;
-        if (jps.size() > rps.size()) {
-            return false;
-        }
-        unsigned j = 0, i = 0;
-        for (; i < jps.size() && j < rps.size(); ) {
-            if (jps[i] == rps[j]) {
-                ++i; ++j;
-            }
-            else if (lt(rps[j], jps[i])) {
-                ++j;
-            }
-            else {
-                break;
-            }            
-        }
-        return i == jps.size();
-    }
-
-    /**
-     * \brief minimal current resource available for job resource, includes idx.
-     */
-    bool theory_jobscheduler::first_available(job_resource const& jr, res_info const& ri, unsigned& idx) const {
-        for (; idx < ri.m_available.size(); ++idx) {
-            if (resource_available(jr, ri.m_available[idx])) 
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * \brief maximal previous resource available for job resource, excludes idx.
-     */
-    bool theory_jobscheduler::last_available(job_resource const& jr, res_info const& ri, unsigned& idx) const {
-        while (idx-- > 0) {
-            if (resource_available(jr, ri.m_available[idx])) 
-                return true;
-        }
-        return false;
-    }
-
-    
-};
-
diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h
deleted file mode 100644
index 9c0e73b30..000000000
--- a/src/smt/theory_jobscheduler.h
+++ /dev/null
@@ -1,247 +0,0 @@
-/*++
-Copyright (c) 2018 Microsoft Corporation
-
-Module Name:
-
-    theory_jobscheduling.h
-
-Abstract:
-
-    Propagation solver for jobscheduling problems.
-    It relies on an external module to tighten bounds of 
-    job variables.
-
-Author:
-
-    Nikolaj Bjorner (nbjorner) 2018-09-08.
-
-Revision History:
-
---*/
-#pragma once
-
-#include "util/uint_set.h"
-#include "ast/csp_decl_plugin.h"
-#include "ast/arith_decl_plugin.h"
-#include "smt/smt_theory.h"
-
-namespace smt {
-
-    typedef uint64_t time_t;
-    
-    class theory_jobscheduler : public theory {
-    public:
-        typedef svector<symbol> properties;
-    protected:
-
-        struct job_resource {
-            unsigned m_resource_id;   // id of resource
-            time_t   m_capacity;      // amount of resource to use
-            unsigned m_loadpct;       // assuming loadpct
-            time_t   m_finite_capacity_end;   
-            properties m_properties;
-            job_resource(unsigned r, time_t cap, unsigned loadpct, time_t end, properties const& ps):
-                m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_finite_capacity_end(end), m_properties(ps) {}
-        };
-
-        struct job_time {
-            unsigned m_job;
-            time_t   m_time;
-            job_time(unsigned j, time_t time): m_job(j), m_time(time) {}
-
-            struct compare {
-                bool operator()(job_time const& jt1, job_time const& jt2) const {
-                    return jt1.m_time < jt2.m_time;
-                }
-            };
-        };
-
-        struct job_info {
-            bool                 m_is_preemptable; // can job be pre-empted
-            vector<job_resource> m_resources; // resources allowed to run job.
-            u_map<unsigned>      m_resource2index; // resource to index into vector 
-            enode*               m_job;
-            enode*               m_start;
-            enode*               m_end;
-            enode*               m_job2resource;
-            bool                 m_is_bound;
-            job_info(): m_is_preemptable(false), m_job(nullptr), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr), m_is_bound(false)  {}
-        };
-
-        struct res_available {
-            unsigned   m_loadpct;
-            time_t     m_start;
-            time_t     m_end;
-            properties m_properties;
-            res_available(unsigned load_pct, time_t start, time_t end, properties const& ps):
-                m_loadpct(load_pct),
-                m_start(start),
-                m_end(end),
-                m_properties(ps)
-            {}
-            struct compare {
-                bool operator()(res_available const& ra1, res_available const& ra2) const {
-                    return ra1.m_start < ra2.m_start;
-                }
-            };
-        };
-
-        struct res_info {
-            unsigned_vector       m_jobs;      // jobs allocated to run on resource
-            vector<res_available> m_available; // time intervals where resource is available
-            enode*                m_resource;
-            enode*                m_makespan;
-            res_info(): m_resource(nullptr), m_makespan(nullptr) {}
-        };
-        
-        ast_manager&     m;
-        csp_util         u;
-        arith_util       a;
-        vector<job_info> m_jobs;
-        vector<res_info> m_resources;
-        unsigned_vector  m_bound_jobs;
-        unsigned         m_bound_qhead;
-        struct scope {
-            unsigned m_bound_jobs_lim;
-            unsigned m_bound_qhead;
-        };
-        svector<scope> m_scopes;
-        
-    protected:
-
-        theory_var mk_var(enode * n) override;        
-
-        bool internalize_atom(app * atom, bool gate_ctx) override;
-
-        bool internalize_term(app * term) override;
-
-        void assign_eh(bool_var v, bool is_true) override {}
-
-        void new_eq_eh(theory_var v1, theory_var v2) override;
-
-        void new_diseq_eh(theory_var v1, theory_var v2) override;
-
-        void push_scope_eh() override;
-
-        void pop_scope_eh(unsigned num_scopes) override;
-
-        final_check_status final_check_eh() override;
-
-        bool can_propagate() override;
-
-        void propagate() override;
-        
-    public:
-
-        theory_jobscheduler(context& ctx);
-
-        ~theory_jobscheduler() override {}
-        
-        void display(std::ostream & out) const override;
-        
-        void collect_statistics(::statistics & st) const override;
-
-        bool include_func_interp(func_decl* f) override;
-
-        void init_model(model_generator & m) override;
-
-        model_value_proc * mk_value(enode * n, model_generator & mg) override;
-
-        bool get_value(enode * n, expr_ref & r) override;
-
-        theory * mk_fresh(context * new_ctx) override; 
-
-        res_info& ensure_resource(unsigned r);
-        job_info& ensure_job(unsigned j);
-
-    public:
-        // set up job/resource global constraints
-        void set_preemptable(unsigned j, bool is_preemptable);
-        void add_job_resource(unsigned j, unsigned r, unsigned loadpct, time_t cap, time_t end, properties const& ps);
-        void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps);
-        void add_done();
-
-        // assignments
-        time_t est(unsigned j);      // earliest start time of job j
-        time_t lst(unsigned j);      // last start time
-        time_t ect(unsigned j);      // earliest completion time
-        time_t lct(unsigned j);      // last completion time
-        time_t start(unsigned j);    // start time of job j
-        time_t end(unsigned j);      // end time of job j
-        time_t get_lo(expr* e);
-        time_t get_up(expr* e);
-        time_t get_value(expr* e);
-        unsigned resource(unsigned j); // resource of job j
-
-        // derived bounds
-        time_t ect(unsigned j, unsigned r, time_t start);
-        bool lst(unsigned j, unsigned r, time_t& t);
-        
-        bool resource_available(job_resource const& jr, res_available const& ra) const;
-        bool first_available(job_resource const& jr, res_info const& ri, unsigned& idx) const;
-        bool last_available(job_resource const& jr, res_info const& ri, unsigned& idx) const;
-
-        time_t solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap);
-        time_t solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap);
-        time_t solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end);
-
-        // validate assignment
-        void validate_assignment();
-        bool resource_available(unsigned r, time_t t, unsigned& idx); // load available on resource r at time t.
-        time_t capacity_used(unsigned j, unsigned r, time_t start, time_t end);        // capacity used between start and end
-
-        job_resource const& get_job_resource(unsigned j, unsigned r) const;
-        bool job_has_resource(unsigned j, unsigned r) const;
-
-        // propagation
-        void propagate_end_time(unsigned j, unsigned r);
-        void propagate_job2resource(unsigned j, unsigned r);
-
-        // final check constraints
-        bool constrain_end_time_interval(unsigned j, unsigned r);
-        bool constrain_resource_energy(unsigned r);
-        bool split_job2resource(unsigned j);
-
-        void assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq);
-        void assert_last_start_time(unsigned j, unsigned r, literal eq);
-        void assert_first_start_time(unsigned j, unsigned r, literal eq);
-        void assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq);
-        void assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq);
-
-        void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job);
-
-        class job_overlap {
-            time_t m_start;
-            vector<job_time> & m_starts, &m_ends;
-            unsigned s_idx, e_idx; // index into starts/ends
-            uint_set m_jobs;
-        public:
-            job_overlap(vector<job_time>& starts, vector<job_time>& ends);
-            bool next();
-            uint_set const& jobs() const { return m_jobs; }
-        };
-
-        // term builders
-        literal mk_ge(expr* e, time_t t);
-        expr* mk_ge_expr(expr* e, time_t t);
-        literal mk_ge(enode* e, time_t t);
-        literal mk_le(expr* e, time_t t);
-        expr* mk_le_expr(expr* e, time_t t);
-        literal mk_le(enode* e, time_t t);
-        literal mk_le(enode* l, enode* r);
-        literal mk_literal(expr* e);
-        literal mk_eq_lit(enode * l, enode * r) { return mk_eq_lit(l->get_owner(), r->get_owner()); }
-        literal mk_eq_lit(expr * l, expr * r);
-
-        void internalize_cmd(expr* cmd);
-        void unrecognized_argument(expr* arg) { invalid_argument("unrecognized argument ", arg); }
-        void invalid_argument(char const* msg, expr* arg);
-
-        std::ostream& display(std::ostream & out, res_info const& r) const;
-        std::ostream& display(std::ostream & out, res_available const& r) const;
-        std::ostream& display(std::ostream & out, job_info const& r) const;
-        std::ostream& display(std::ostream & out, job_resource const& r) const;
-
-    };    
-};
-
diff --git a/src/test/egraph.cpp b/src/test/egraph.cpp
index 40dc63c4d..e03340bbb 100644
--- a/src/test/egraph.cpp
+++ b/src/test/egraph.cpp
@@ -120,7 +120,9 @@ static void test3() {
     std::cout << g << "\n";
     SASSERT(g.inconsistent());
     ptr_vector<int> js;
+    g.begin_explain();
     g.explain<int>(js);
+    g.end_explain();
     for (int* j : js) {
         std::cout << "conflict: " << *j << "\n";
     }
diff --git a/src/util/dlist.h b/src/util/dlist.h
index 4dcaa205a..9b6c87066 100644
--- a/src/util/dlist.h
+++ b/src/util/dlist.h
@@ -18,6 +18,9 @@ Revision History:
 --*/
 #pragma once
 
+#if 0
+-- unused
+
 /**
    Add element \c elem to the list headed by \c head.
    NextProc and PrevProc must have the methods:
@@ -131,5 +134,52 @@ bool dlist_check_invariant(T * head, NextProc const & next = NextProc(), PrevPro
     return true;
 }
 
+#endif
+
+template<typename T>
+class dll_base {
+    T* m_next { nullptr };
+    T* m_prev { nullptr };
+public:
+
+    T* prev() { return m_prev; }
+    T* next() { return m_next; }
+
+    void init(T* t) {
+        m_next = t;
+        m_prev = t;
+    }
+    
+    static void remove_from(T*& list, T* elem) {
+        if (list->m_next == list) {
+            SASSERT(elem == list);
+            list = nullptr;
+            return;
+        }            
+        if (list == elem)
+            list = elem->m_next;
+        auto* next = elem->m_next;
+        auto* prev = elem->m_prev;
+        prev->m_next = next;
+        next->m_prev = prev;        
+    }
+
+    static void push_to_front(T*& list, T* elem) {
+        if (!list) {
+            list = elem;
+            elem->m_next = elem;
+            elem->m_prev = elem;            
+        }
+        else if (list != elem) {
+            remove_from(list, elem);
+            list->m_prev->m_next = elem;
+            elem->m_prev = list->m_prev;
+            elem->m_next = list;
+            list->m_prev = elem;
+            list = elem;
+        }
+    }
+};
+
 
 
diff --git a/src/util/scoped_limit_trail.h b/src/util/scoped_limit_trail.h
new file mode 100644
index 000000000..6ea82ab82
--- /dev/null
+++ b/src/util/scoped_limit_trail.h
@@ -0,0 +1,36 @@
+
+#include "util/vector.h"
+
+#pragma once
+
+class scoped_limit_trail {
+    unsigned_vector m_lim;
+    unsigned m_scopes{ 0 };
+    unsigned m_last{ 0 };
+ public:
+    
+    void push(unsigned n) { 
+        if (m_last == n)
+            m_scopes++; 
+        else {
+            for (; m_scopes > 0; --m_scopes)
+                m_lim.push_back(m_last);
+            m_last = n;
+        }
+    }
+    unsigned pop(unsigned n) {
+        SASSERT(n > 0);
+        SASSERT(m_scopes + m_lim.size() >= n);
+        if (n <= m_scopes) {
+            m_scopes -= n;
+            return m_last;
+        }
+        else {
+            n -= m_scopes;
+            m_scopes = 0;
+            m_last = m_lim[m_lim.size() - n];
+            m_lim.shrink(m_lim.size() - n);
+            return m_last;
+        }
+    }
+};
diff --git a/src/util/statistics.cpp b/src/util/statistics.cpp
index 9c8d1fd99..148dec78b 100644
--- a/src/util/statistics.cpp
+++ b/src/util/statistics.cpp
@@ -99,7 +99,7 @@ unsigned get_max_len(ptr_buffer<char> & keys) {
     return max;
 }
 
-void statistics::display_smt2(std::ostream & out) const {   
+std::ostream& statistics::display_smt2(std::ostream & out) const {   
 #define INIT_DISPLAY()                                  \
     key2val m_u;                                        \
     key2dval m_d;                                       \
@@ -140,9 +140,10 @@ void statistics::display_smt2(std::ostream & out) const {
         }
     }
     out << ")\n";
+    return out;
 }
 
-void statistics::display(std::ostream & out) const {
+std::ostream& statistics::display(std::ostream & out) const {
     INIT_DISPLAY();
 
 #undef DISPLAY_KEY
@@ -169,6 +170,7 @@ void statistics::display(std::ostream & out) const {
             out << " " << std::fixed << std::setprecision(2) << d_val << "\n";
         }
     }
+    return out;
 }
 
 template<typename M>
diff --git a/src/util/statistics.h b/src/util/statistics.h
index be0463ca7..cea9099d1 100644
--- a/src/util/statistics.h
+++ b/src/util/statistics.h
@@ -32,8 +32,8 @@ public:
     void reset();
     void update(char const * key, unsigned inc);
     void update(char const * key, double inc);
-    void display(std::ostream & out) const;
-    void display_smt2(std::ostream & out) const;
+    std::ostream& display(std::ostream & out) const;
+    std::ostream& display_smt2(std::ostream & out) const;
     void display_internal(std::ostream & out) const;
     unsigned size() const;
     bool is_uint(unsigned idx) const;
@@ -42,6 +42,8 @@ public:
     double get_double_value(unsigned idx) const;
 };
 
+inline std::ostream& operator<<(std::ostream& out, statistics const& st) { return st.display(out); }
+
 void get_memory_statistics(statistics& st);
 void get_rlimit_statistics(reslimit& l, statistics& st);
 
diff --git a/src/util/union_find.h b/src/util/union_find.h
index ef81c9d56..f706cd162 100644
--- a/src/util/union_find.h
+++ b/src/util/union_find.h
@@ -36,10 +36,10 @@ private:
     _trail_stack m_stack;
 };
 
-template<typename Ctx = union_find_default_ctx>
+template<typename Ctx = union_find_default_ctx, typename StackCtx = Ctx>
 class union_find {
     Ctx &                         m_ctx;
-    trail_stack<Ctx> &            m_trail_stack;
+    trail_stack<StackCtx> &       m_trail_stack;
     svector<unsigned>             m_find;
     svector<unsigned>             m_size;
     svector<unsigned>             m_next;
@@ -47,12 +47,12 @@ class union_find {
     class mk_var_trail;
     friend class mk_var_trail;
 
-    class mk_var_trail : public trail<Ctx> {
+    class mk_var_trail : public trail<StackCtx> {
         union_find & m_owner;
     public:
         mk_var_trail(union_find & o):m_owner(o) {}
         ~mk_var_trail() override {}
-        void undo(Ctx & ctx) override {
+        void undo(StackCtx& ctx) override {
             m_owner.m_find.pop_back();
             m_owner.m_size.pop_back();
             m_owner.m_next.pop_back();
@@ -64,13 +64,13 @@ class union_find {
     class merge_trail;
     friend class merge_trail;
 
-    class merge_trail : public trail<Ctx> {
+    class merge_trail : public trail<StackCtx> {
         union_find & m_owner;
         unsigned     m_r1;
     public:
         merge_trail(union_find & o, unsigned r1):m_owner(o), m_r1(r1) {}
         ~merge_trail() override {}
-        void undo(Ctx & ctx) override { m_owner.unmerge(m_r1); }
+        void undo(StackCtx& ctx) override { m_owner.unmerge(m_r1); }
     };
 
     void unmerge(unsigned r1) {