diff --git a/src/muz/base/fixedpoint_params.pyg b/src/muz/base/fixedpoint_params.pyg
index f0f4e0881..e91eb86fe 100644
--- a/src/muz/base/fixedpoint_params.pyg
+++ b/src/muz/base/fixedpoint_params.pyg
@@ -187,7 +187,8 @@ def_module_params('fixedpoint',
                           ('spacer.reuse_pobs', BOOL, True, 'reuse POBs'),
                           ('spacer.print_farkas_stats', BOOL, False, 'prints for each proof how many Farkas lemmas it contains and how many of these participate in the cut'),
                           ('spacer.iuc.debug_proof', BOOL, False, 'prints proof used by unsat-core-learner for debugging purposes'),
-                          ('spacer.simplify_pob', BOOL, False, 'simplify POBs by removing redundant constraints')
+                          ('spacer.simplify_pob', BOOL, False, 'simplify POBs by removing redundant constraints'),
+                          ('spacer.use_quant_generalizer', BOOL, False, 'use quantified lemma generalizer'),
                           ))
 
 
diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp
index 1151909f1..607bf2018 100644
--- a/src/muz/spacer/spacer_context.cpp
+++ b/src/muz/spacer/spacer_context.cpp
@@ -1894,7 +1894,6 @@ void pob::set_post(expr* post, app_ref_vector const &b) {
     expr_safe_replace sub(m);
     for (unsigned i = 0, sz = m_binding.size(); i < sz; ++i) {
         expr* e;
-
         e = m_binding.get(i);
         pinned.push_back (mk_zk_const (m, i, get_sort(e)));
         sub.insert (e, pinned.back());
@@ -1939,7 +1938,6 @@ void pob::close () {
 void pob::get_skolems(app_ref_vector &v) {
     for (unsigned i = 0, sz = m_binding.size(); i < sz; ++i) {
         expr* e;
-
         e = m_binding.get(i);
         v.push_back (mk_zk_const (get_ast_manager(), i, get_sort(e)));
     }
@@ -2273,6 +2271,11 @@ void context::init_lemma_generalizers(datalog::rule_set& rules)
         fparams.m_ng_lift_ite = LI_FULL;
     }
 
+    if (m_params.spacer_use_quant_generalizer()) {
+        m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0, false));
+        m_lemma_generalizers.push_back(alloc(lemma_quantifier_generalizer, *this));
+    }
+
     if (get_params().spacer_use_eqclass()) {
         m_lemma_generalizers.push_back (alloc(lemma_eq_generalizer, *this));
     }
diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h
index 494da06a7..e909cd9ad 100644
--- a/src/muz/spacer/spacer_context.h
+++ b/src/muz/spacer/spacer_context.h
@@ -527,7 +527,9 @@ public:
     unsigned get_free_vars_size() { return m_binding.size(); }
     app_ref_vector const &get_binding() const {return m_binding;}
     /*
-     * Return skolem variables that appear in post
+     * Returns a map from variable id to skolems that implicitly
+     * represent them in the pob. Note that only some (or none) of the
+     * skolems returned actually appear in the post of the pob.
      */
     void get_skolems(app_ref_vector& v);
 
diff --git a/src/muz/spacer/spacer_generalizers.cpp b/src/muz/spacer/spacer_generalizers.cpp
index c87bdc892..342e43ad3 100644
--- a/src/muz/spacer/spacer_generalizers.cpp
+++ b/src/muz/spacer/spacer_generalizers.cpp
@@ -326,4 +326,6 @@ void lemma_eq_generalizer::operator() (lemma_ref &lemma)
         lemma->update_cube(lemma->get_pob(), core);
     }
 }
+
+
 };
diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h
index 17298495c..46ad0c4a3 100644
--- a/src/muz/spacer/spacer_generalizers.h
+++ b/src/muz/spacer/spacer_generalizers.h
@@ -97,6 +97,54 @@ public:
     void operator()(lemma_ref &lemma) override;
 };
 
+class lemma_quantifier_generalizer : public lemma_generalizer {
+    struct stats {
+        unsigned count;
+        unsigned num_failures;
+        stopwatch watch;
+        stats() {reset();}
+        void reset() {count = 0; num_failures = 0; watch.reset();}
+    };
 
+    ast_manager &m;
+    arith_util m_arith;
+    stats m_st;
+public:
+    lemma_quantifier_generalizer(context &ctx);
+    virtual ~lemma_quantifier_generalizer() {}
+    virtual void operator()(lemma_ref &lemma);
+
+    virtual void collect_statistics(statistics& st) const;
+    virtual void reset_statistics() {m_st.reset();}
+private:
+    bool match_sk_idx(expr *e, app_ref_vector const &zks, expr *&idx, app *&sk);
+    void cleanup(expr_ref_vector& cube, app_ref_vector const &zks, expr_ref &bind);
+    void generalize_pattern_lits(expr_ref_vector &pats);
+    void find_candidates(
+        expr *e,
+        app_ref_vector &candidate);
+    void generate_patterns(
+        expr_ref_vector const &cube,
+        app_ref_vector const &candidates,
+        var_ref_vector &subs,
+        expr_ref_vector &patterns,
+        unsigned offset);
+    void find_matching_expressions(
+        expr_ref_vector const &cube,
+        var_ref_vector const &subs,
+        expr_ref_vector &patterns,
+        vector<expr_ref_vector> &idx_instances,
+        vector<bool> &dirty);
+    void find_guards(
+        expr_ref_vector const &indices,
+        expr_ref &lower,
+        expr_ref &upper);
+    void add_lower_bounds(
+        var_ref_vector const &subs,
+        app_ref_vector const &zks,
+        vector<expr_ref_vector> const &idx_instances,
+        expr_ref_vector &cube);
 };
+}
+
 #endif
diff --git a/src/muz/spacer/spacer_manager.cpp b/src/muz/spacer/spacer_manager.cpp
index d583813a8..c46c31924 100644
--- a/src/muz/spacer/spacer_manager.cpp
+++ b/src/muz/spacer/spacer_manager.cpp
@@ -340,22 +340,27 @@ app* mk_zk_const(ast_manager &m, unsigned idx, sort *s) {
 
 namespace find_zk_const_ns {
 struct proc {
+    int m_max;
     app_ref_vector &m_out;
-    proc (app_ref_vector &out) : m_out(out) {}
+    proc (app_ref_vector &out) : m_max(-1), m_out(out) {}
     void operator() (var const * n) const {}
-    void operator() (app *n) const {
-        if (is_uninterp_const(n) &&
-            n->get_decl()->get_name().str().compare (0, 3, "sk!") == 0) {
-            m_out.push_back (n);
+    void operator() (app *n) {
+        int idx;
+        if (is_zk_const(n, idx)) {
+            m_out.push_back(n);
+            if (idx > m_max) {
+                m_max = idx;
+            }
         }
     }
     void operator() (quantifier const *n) const {}
 };
 }
 
-void find_zk_const(expr *e, app_ref_vector &res) {
+int find_zk_const(expr *e, app_ref_vector &res) {
     find_zk_const_ns::proc p(res);
     for_each_expr (p, e);
+    return p.m_max;
 }
 
 namespace has_zk_const_ns {
@@ -363,8 +368,8 @@ struct found {};
 struct proc {
     void operator() (var const *n) const {}
     void operator() (app const *n) const {
-        if (is_uninterp_const(n) &&
-            n->get_decl()->get_name().str().compare(0, 3, "sk!") == 0) {
+        int idx;
+        if (is_zk_const(n, idx)) {
             throw found();
         }
     }
diff --git a/src/muz/spacer/spacer_manager.h b/src/muz/spacer/spacer_manager.h
index f49aa63fc..32cf61d57 100644
--- a/src/muz/spacer/spacer_manager.h
+++ b/src/muz/spacer/spacer_manager.h
@@ -339,8 +339,12 @@ public:
 };
 
 app* mk_zk_const (ast_manager &m, unsigned idx, sort *s);
-void find_zk_const(expr* e, app_ref_vector &out);
+int find_zk_const(expr* e, app_ref_vector &out);
+inline int find_zk_const(expr_ref_vector const &v, app_ref_vector &out) {
+    return find_zk_const (mk_and(v), out);
+}
 bool has_zk_const(expr* e);
+bool is_zk_const (const app *a, int &n);
 
 struct sk_lt_proc {
     bool operator()(const app* a1, const app* a2);
diff --git a/src/muz/spacer/spacer_quant_generalizer.cpp b/src/muz/spacer/spacer_quant_generalizer.cpp
new file mode 100644
index 000000000..8be97122c
--- /dev/null
+++ b/src/muz/spacer/spacer_quant_generalizer.cpp
@@ -0,0 +1,499 @@
+/*++
+Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel
+
+Module Name:
+
+    spacer_quant_generalizer.cpp
+
+Abstract:
+
+    Quantified lemma generalizer.
+
+Author:
+
+
+    Yakir Vizel
+    Arie Gurfinkel
+
+Revision History:
+
+--*/
+
+
+#include "muz/spacer/spacer_context.h"
+#include "muz/spacer/spacer_generalizers.h"
+#include "muz/spacer/spacer_manager.h"
+#include "ast/expr_abstract.h"
+#include "ast/rewriter/var_subst.h"
+#include "ast/for_each_expr.h"
+#include "ast/factor_equivs.h"
+#include "muz/spacer/spacer_term_graph.h"
+#include "ast/rewriter/expr_safe_replace.h"
+#include "ast/substitution/matcher.h"
+#include "ast/expr_functors.h"
+
+#include "muz/spacer/spacer_sem_matcher.h"
+
+using namespace spacer;
+
+namespace {
+struct index_lt_proc : public std::binary_function<app*, app *, bool> {
+    arith_util m_arith;
+    index_lt_proc(ast_manager &m) : m_arith(m) {}
+    bool operator() (app *a, app *b) {
+        // XXX This order is a bit strange.
+        // XXX It does the job in our current application, but only because
+        // XXX we assume that we only compare expressions of the form (B + k),
+        // XXX where B is fixed and k is a number.
+        // XXX Might be better to specialize just for that specific use case.
+        rational val1, val2;
+        bool is_num1 = m_arith.is_numeral(a, val1);
+        bool is_num2 = m_arith.is_numeral(b, val2);
+
+        if (is_num1 && is_num2) {
+            return val1 < val2;
+        }
+        else if (is_num1 != is_num2) {
+            return is_num1;
+        }
+
+        is_num1 = false;
+        is_num2 = false;
+        // compare the first numeric argument of a to first numeric argument of b
+        // if available
+        for (unsigned i = 0, sz = a->get_num_args(); !is_num1 && i < sz; ++i) {
+            is_num1 = m_arith.is_numeral (a->get_arg(i), val1);
+        }
+        for (unsigned i = 0, sz = b->get_num_args(); !is_num2 && i < sz; ++i) {
+            is_num2 = m_arith.is_numeral(b->get_arg(i), val2);
+        }
+
+        if (is_num1 && is_num2) {
+            return val1 < val2;
+        }
+        else if (is_num1 != is_num2) {
+            return is_num1;
+        }
+        else {
+            return a->get_id() < b->get_id();
+        }
+
+    }
+};
+
+}
+
+namespace spacer {
+
+lemma_quantifier_generalizer::lemma_quantifier_generalizer(context &ctx) :
+    lemma_generalizer(ctx), m(ctx.get_ast_manager()), m_arith(m) {}
+
+void lemma_quantifier_generalizer::collect_statistics(statistics &st) const
+{
+    st.update("time.spacer.solve.reach.gen.quant", m_st.watch.get_seconds());
+    st.update("quantifier gen", m_st.count);
+    st.update("quantifier gen failures", m_st.num_failures);
+}
+
+// XXX What does this do?
+void lemma_quantifier_generalizer::find_candidates(
+    expr *e, app_ref_vector &candidates)
+{
+    if (!contains_selects(e, m)) return;
+
+    app_ref_vector indices(m);
+    get_select_indices(e, indices, m);
+
+    app_ref_vector extra(m);
+    expr_sparse_mark marked_args;
+
+    // Make sure not to try and quantify already-quantified indices
+    for (unsigned idx=0, sz = indices.size(); idx < sz; idx++) {
+        // skip expressions that already contain a quantified variable
+        if (has_zk_const(indices.get(idx))) {
+            continue;
+        }
+
+        app *index = indices.get(idx);
+        TRACE ("spacer_qgen",
+               tout << "Candidate: "<< mk_pp(index, m)
+               << " in " << mk_pp(e, m) << "\n";);
+        extra.push_back(index);
+        // XXX expand one level of arguments. Might want to go deeper.
+        for (unsigned j = 0, asz = index->get_num_args(); j < asz; j++) {
+            expr *arg = index->get_arg(j);
+            if (!is_app(arg) || marked_args.is_marked(arg)) {continue;}
+            marked_args.mark(arg);
+            candidates.push_back (to_app(arg));
+        }
+    }
+
+    std::sort(candidates.c_ptr(), candidates.c_ptr() + candidates.size(),
+              index_lt_proc(m));
+    // keep actual select indecies in the order found at the back of
+    // candidate list
+
+    // XXX this corresponds to current implementation. Probably should
+    // XXX be sorted together with the rest of candidates
+    candidates.append(extra);
+}
+
+/* subs are the variables that might appear in the patterns */
+void lemma_quantifier_generalizer::generate_patterns(
+    expr_ref_vector const &cube, app_ref_vector const &candidates,
+    var_ref_vector &subs, expr_ref_vector &patterns, unsigned offset)
+{
+    if (candidates.empty()) return;
+
+    expr_safe_replace ses(m);
+
+    // Setup substitution
+    for (unsigned i=0; i < candidates.size(); i++) {
+        expr *cand = candidates.get(i);
+        var *var = m.mk_var(i+offset, get_sort(cand));
+        ses.insert(cand, var);
+        rational val;
+        if (m_arith.is_numeral(cand, val)) {
+            bool is_int = val.is_int();
+            ses.insert(
+                m_arith.mk_numeral(rational(val+1), is_int),
+                m_arith.mk_add(var, m_arith.mk_numeral(rational(1), is_int)));
+            ses.insert(
+                m_arith.mk_numeral(rational(-1*(val+1)), is_int),
+                m_arith.mk_add(m_arith.mk_sub(m_arith.mk_numeral(rational(0), is_int),var), m_arith.mk_numeral(rational(-1), is_int)));
+        }
+        subs.push_back(var);
+    }
+
+    // Generate patterns
+
+    // for every literal
+    for (unsigned j=0; j < cube.size(); j++) {
+        // abstract terms by variables
+        expr_ref pat(m);
+        ses(cube.get(j), pat);
+
+        if (pat.get() != cube.get(j)) {
+            // if abstraction is not identity
+            TRACE("spacer_qgen",
+                  tout << "Pattern is: " << mk_pp(pat, m) << "\n";);
+
+            // store the pattern
+            patterns.push_back(pat);
+        }
+    }
+}
+
+void lemma_quantifier_generalizer::find_matching_expressions(
+    expr_ref_vector const &cube,
+    var_ref_vector const &subs, expr_ref_vector &patterns,
+    vector<expr_ref_vector> &idx_instances,
+    vector<bool> &dirty)
+{
+    idx_instances.resize(subs.size(), expr_ref_vector(m));
+    // -- for every pattern
+    for (unsigned p = 0; p < patterns.size(); p++) {
+        expr *pattern = patterns.get(p);
+        // -- for every literal
+        for (unsigned j = 0; j < cube.size(); j++) {
+            if (dirty[j] || !is_app(cube.get(j))) continue;
+            app *lit = to_app(cube.get(j));
+
+            sem_matcher match(m);
+            bool pos;
+            substitution v_sub(m);
+            v_sub.reserve(2, subs.size()+1);
+
+            if (!match(pattern, lit, v_sub, pos)) {
+                continue;
+            }
+            // expect positive matches only
+            if (!pos) {continue;}
+
+            dirty[j] = true;
+            for (unsigned v = 0; v < subs.size(); v++) {
+                expr_offset idx;
+                if (v_sub.find(subs.get(v), 0, idx)) {
+                    TRACE ("spacer_qgen", tout << "INSTANCE IS: " << mk_pp(idx.get_expr(), m) << "\n";);
+                    idx_instances[v].push_back(idx.get_expr());
+                }
+            }
+        }
+    }
+}
+
+
+void lemma_quantifier_generalizer::find_guards(
+    expr_ref_vector const &indices,
+    expr_ref &lower, expr_ref &upper)
+{
+    if (indices.empty()) return;
+
+    auto minmax = std::minmax_element((app * *) indices.c_ptr(),
+                                      (app * *) indices.c_ptr() + indices.size(),
+                                      index_lt_proc(m));
+    lower = *minmax.first;
+    upper = *minmax.second;
+}
+
+void lemma_quantifier_generalizer::add_lower_bounds(
+    var_ref_vector const &subs,
+    app_ref_vector const &zks,
+    vector<expr_ref_vector> const &idx_instances,
+    expr_ref_vector &cube)
+{
+    for (unsigned v = 0; v < subs.size(); v++) {
+        var *var = subs.get(v);
+        if (idx_instances[v].empty()) continue;
+        TRACE("spacer_qgen",
+              tout << "Finding lower bounds for " << mk_pp(var, m) << "\n";);
+        expr_ref low(m);
+        expr_ref up(m);
+        find_guards(idx_instances[v], low, up);
+        cube.push_back(m_arith.mk_ge(zks.get(var->get_idx()), low));
+    }
+}
+
+/// returns true if expression e contains a sub-expression of the form (select A idx) where
+/// idx contains exactly one skolem from zks. Returns idx and the skolem
+bool lemma_quantifier_generalizer::match_sk_idx(expr *e, app_ref_vector const &zks, expr *&idx, app *&sk) {
+    if (zks.size() != 1) return false;
+    contains_app has_zk(m, zks.get(0));
+
+    if (!contains_selects(e, m)) return false;
+    app_ref_vector indicies(m);
+    get_select_indices(e, indicies, m);
+    if (indicies.size() > 2) return false;
+
+    unsigned i=0;
+    if (indicies.size() == 1) {
+        if (!has_zk(indicies.get(0))) return false;
+    }
+    else {
+        if (has_zk(indicies.get(0)) && !has_zk(indicies.get(1)))
+            i = 0;
+        else if (!has_zk(indicies.get(0)) && has_zk(indicies.get(1)))
+            i = 1;
+        else if (!has_zk(indicies.get(0)) && !has_zk(indicies.get(1)))
+            return false;
+    }
+
+    idx = indicies.get(i);
+    sk = zks.get(0);
+    return true;
+}
+
+expr* times_minus_one(expr *e, arith_util &arith) {
+    expr *r;
+    if (arith.is_times_minus_one (e, r)) { return r; }
+
+    return arith.mk_mul(arith.mk_numeral(rational(-1), arith.is_int(get_sort(e))), e);
+}
+
+/** Attempts to rewrite a cube so that quantified variable appears as
+    a top level argument of select-term
+
+    Find sub-expression of the form (select A (+ sk!0 t)) and replaces
+    (+ sk!0 t) --> sk!0 and sk!0 --> (+ sk!0 (* (- 1) t))
+
+    Current implementation is an ugly hack for one special case
+*/
+void lemma_quantifier_generalizer::cleanup(expr_ref_vector &cube, app_ref_vector const &zks, expr_ref &bind) {
+    if (zks.size() != 1) return;
+
+    arith_util arith(m);
+    expr *idx = nullptr;
+    app *sk = nullptr;
+    expr_ref rep(m);
+
+    for (expr *e : cube) {
+        if (match_sk_idx(e, zks, idx, sk)) {
+            CTRACE("spacer_qgen", idx != sk,
+                   tout << "Possible cleanup of " << mk_pp(idx, m) << " in "
+                   << mk_pp(e, m) << " on " << mk_pp(sk, m) << "\n";);
+
+            if (!arith.is_add(idx)) continue;
+            app *a = to_app(idx);
+            bool found = false;
+            expr_ref_vector kids(m);
+            expr_ref_vector kids_bind(m);
+            for (unsigned i = 0, sz = a->get_num_args(); i < sz; ++i) {
+                if (a->get_arg(i) == sk) {
+                    found = true;
+                    kids.push_back(a->get_arg(i));
+                    kids_bind.push_back(bind);
+                }
+                else {
+                    kids.push_back (times_minus_one(a->get_arg(i), arith));
+                    kids_bind.push_back (times_minus_one(a->get_arg(i), arith));
+                }
+            }
+            if (!found) continue;
+
+            rep = arith.mk_add(kids.size(), kids.c_ptr());
+            bind = arith.mk_add(kids_bind.size(), kids_bind.c_ptr());
+            TRACE("spacer_qgen",
+                  tout << "replace " << mk_pp(idx, m) << " with " << mk_pp(rep, m) << "\n";);
+            break;
+        }
+    }
+
+    if (rep) {
+        expr_safe_replace rw(m);
+        rw.insert(sk, rep);
+        rw.insert(idx, sk);
+        rw(cube);
+        TRACE("spacer_qgen",
+              tout << "Cleaned cube to: " << mk_and(cube) << "\n";);
+    }
+}
+
+void lemma_quantifier_generalizer::generalize_pattern_lits(expr_ref_vector &pats) {
+    // generalize pattern literals using 'x=t' --> 'x>=t'
+    for (unsigned i = 0, sz = pats.size(); i < sz; ++i) {
+        expr *e1, *e2;
+        expr *p = pats.get(i);
+        if (m.is_eq(p, e1, e2) && (is_var(e1) || is_var(e2))) {
+            if (m_arith.is_numeral(e1)) {
+                p = m_arith.mk_ge(e2, e1);
+            }
+            else if (m_arith.is_numeral(e2)) {
+                p = m_arith.mk_ge(e1, e2);
+            }
+        }
+        if (p != pats.get(i)) {
+            pats.set(i, p);
+        }
+    }
+}
+void lemma_quantifier_generalizer::operator()(lemma_ref &lemma) {
+    if (lemma->get_cube().empty()) return;
+    if (!lemma->has_pob()) return;
+
+    m_st.count++;
+    scoped_watch _w_(m_st.watch);
+
+    expr_ref_vector cube(m);
+    cube.append(lemma->get_cube());
+
+    TRACE("spacer_qgen",
+          tout << "initial cube: " << mk_and(lemma->get_cube()) << "\n";);
+    if (false) {
+        // -- re-normalize the cube
+        expr_ref c(m);
+        c = mk_and(cube);
+        normalize(c, c, true, true);
+        cube.reset();
+        flatten_and(c, cube);
+    }
+
+    app_ref_vector skolems(m);
+    lemma->get_pob()->get_skolems(skolems);
+    int offset = skolems.size();
+
+    // for every literal
+    for (unsigned i=0; i < cube.size(); i++) {
+        expr *r = cube.get(i);
+
+        // generate candidates
+        app_ref_vector candidates(m);
+        find_candidates(r, candidates);
+
+        // XXX Can candidates be empty and arguments not empty?
+        // XXX Why separate candidates and arguments?
+        // XXX Why are arguments processed before candidates?
+        if (candidates.empty()) continue;
+
+
+        // for every candidate
+        for (unsigned arg=0, sz = candidates.size(); arg < sz; arg++) {
+            app_ref_vector cand(m);
+            cand.push_back(to_app(candidates.get(arg)));
+            var_ref_vector subs(m);
+            expr_ref_vector patterns(m);
+            // generate patterns for a candidate
+            generate_patterns(cube, cand, subs, patterns, offset);
+
+            // Currently, only support one variable per pattern
+            SASSERT(subs.size() == cand.size());
+            SASSERT(cand.size() == 1);
+
+            // Find matching expressions
+            vector<bool> dirty;
+            dirty.resize(cube.size(), false);
+            vector<expr_ref_vector> idx_instances;
+            find_matching_expressions(cube, subs, patterns, idx_instances, dirty);
+
+            expr_ref_vector new_cube(m);
+
+            // move all unmatched expressions to the new cube
+            for (unsigned c=0; c < cube.size(); c++) {
+                if (dirty[c] == false) {
+                    new_cube.push_back(cube.get(c));
+                }
+            }
+
+            // everything moved, nothing left
+            if (new_cube.size() == cube.size()) continue;
+
+            // -- generalize equality in patterns
+            generalize_pattern_lits(patterns);
+
+            // ground quantified patterns in skolems
+            expr_ref gnd(m);
+            app_ref_vector zks(m);
+            ground_expr(mk_and(patterns), gnd, zks);
+            flatten_and(gnd, new_cube);
+
+            // compute lower bounds for quantified variables
+            add_lower_bounds(subs, zks, idx_instances, new_cube);
+
+            TRACE("spacer_qgen",
+                  tout << "New CUBE is: " << mk_pp(mk_and(new_cube),m) << "\n";);
+
+            // check if the result is a lemma
+            unsigned uses_level = 0;
+            unsigned weakness = lemma->weakness();
+            pred_transformer &pt = lemma->get_pob()->pt();
+            if (pt.check_inductive(lemma->level(), new_cube, uses_level, weakness)) {
+                TRACE("spacer",
+                      tout << "Quantifier Generalization Succeeded!\n"
+                      << "New CUBE is: " << mk_pp(mk_and(new_cube),m) << "\n";);
+                SASSERT(zks.size() >= offset);
+                SASSERT(cand.size() == 1);
+
+                // lift quantified variables to top of select
+                expr_ref bind(m);
+                bind = cand.get(0);
+                cleanup(new_cube, zks, bind);
+
+                // XXX better do that check before changing bind in cleanup()
+                // XXX Or not because substitution might introduce _n variable into bind
+                if (m_ctx.get_manager().is_n_formula(bind))
+                    // XXX this creates an instance, but not necessarily the needed one
+
+                    // XXX This is sound because any instance of
+                    // XXX universal quantifier is sound
+
+                    // XXX needs better long term solution. leave
+                    // comment here for the future
+                    m_ctx.get_manager().formula_n2o(bind, bind, 0);
+
+                lemma->update_cube(lemma->get_pob(), new_cube);
+                lemma->set_level(uses_level);
+
+                // XXX Assumes that offset + 1 == zks.size();
+                for (unsigned sk=offset; sk < zks.size(); sk++)
+                    lemma->add_skolem(zks.get(sk), to_app(bind));
+                return;
+            }
+            else {
+                ++m_st.num_failures;
+            }
+        }
+    }
+}
+
+
+
+}
diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp
index 9a0148784..cb11afc9f 100644
--- a/src/muz/spacer/spacer_util.cpp
+++ b/src/muz/spacer/spacer_util.cpp
@@ -1317,9 +1317,9 @@ bool contains_selects(expr* fml, ast_manager& m)
         return false;
     }
 
-void get_select_indices(expr* fml, app_ref_vector& indices, ast_manager& m)
+void get_select_indices(expr* fml, app_ref_vector &indices, ast_manager& m)
 {
-        array_util a_util(m);
+    array_util a_util(m);
     if (!is_app(fml)) { return; }
         ast_mark done;
         ptr_vector<app> todo;
diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h
index 7be2ee4b3..dabfa494a 100644
--- a/src/muz/spacer/spacer_util.h
+++ b/src/muz/spacer/spacer_util.h
@@ -144,7 +144,10 @@ void compute_implicant_literals (model_evaluator_util &mev,
 void simplify_bounds (expr_ref_vector &lemmas);
 void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false);
 
-/** ground expression by replacing all free variables by skolem constants */
+/** Ground expression by replacing all free variables by skolem
+ ** constants. On return, out is the resulting expression, and vars is
+ ** a map from variable ids to corresponding skolem constants.
+ */
 void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars);