/*++ Copyright (c) 2006 Microsoft Corporation Module Name: proto_model.cpp Abstract: Author: Leonardo de Moura (leonardo) 2007-03-08. Revision History: --*/ #include"proto_model.h" #include"ast_pp.h" #include"ast_ll_pp.h" #include"var_subst.h" #include"front_end_params.h" #include"array_decl_plugin.h" #include"well_sorted.h" #include"used_symbols.h" #include"model_v2_pp.h" proto_model::proto_model(ast_manager & m, simplifier & s, model_params const & p): model_core(m), m_params(p), m_asts(m), m_simplifier(s), m_afid(m.get_family_id(symbol("array"))) { register_factory(alloc(basic_factory, m)); m_user_sort_factory = alloc(user_sort_factory, m); register_factory(m_user_sort_factory); } void proto_model::reset_finterp() { decl2finterp::iterator it = m_finterp.begin(); decl2finterp::iterator end = m_finterp.end(); for (; it != end; ++it) { dealloc(it->m_value); } } proto_model::~proto_model() { reset_finterp(); } void proto_model::register_decl(func_decl * d, expr * v) { SASSERT(d->get_arity() == 0); if (m_interp.contains(d)) { DEBUG_CODE({ expr * old_v = 0; m_interp.find(d, old_v); SASSERT(old_v == v); }); return; } SASSERT(!m_interp.contains(d)); m_decls.push_back(d); m_asts.push_back(d); m_asts.push_back(v); m_interp.insert(d, v); m_const_decls.push_back(d); } void proto_model::register_decl(func_decl * d, func_interp * fi, bool aux) { SASSERT(d->get_arity() > 0); SASSERT(!m_finterp.contains(d)); m_decls.push_back(d); m_asts.push_back(d); m_finterp.insert(d, fi); m_func_decls.push_back(d); if (aux) m_aux_decls.insert(d); } /** \brief Set new_fi as the new interpretation for f. If f_aux != 0, then assign the old interpretation of f to f_aux. If f_aux == 0, then delete the old interpretation of f. f_aux is marked as a auxiliary declaration. */ void proto_model::reregister_decl(func_decl * f, func_interp * new_fi, func_decl * f_aux) { func_interp * fi = get_func_interp(f); if (fi == 0) { register_decl(f, new_fi); } else { if (f_aux != 0) { register_decl(f_aux, fi); m_aux_decls.insert(f_aux); } else { dealloc(fi); } m_finterp.insert(f, new_fi); } } expr * proto_model::mk_some_interp_for(func_decl * d) { SASSERT(!has_interpretation(d)); expr * r = get_some_value(d->get_range()); // if t is a function, then it will be the constant function. if (d->get_arity() == 0) { register_decl(d, r); } else { func_interp * new_fi = alloc(func_interp, m_manager, d->get_arity()); new_fi->set_else(r); register_decl(d, new_fi); } return r; } /** \brief Evaluate the expression e in the current model, and store the result in \c result. It returns \c true if succeeded, and false otherwise. If the evaluation fails, then r contains a term that is simplified as much as possible using the interpretations available in the model. When model_completion == true, if the model does not assign an interpretation to a declaration it will build one for it. Moreover, partial functions will also be completed. So, if model_completion == true, the evaluator never fails if it doesn't contain quantifiers. */ bool proto_model::eval(expr * e, expr_ref & result, bool model_completion) { bool is_ok = true; SASSERT(is_well_sorted(m_manager, e)); TRACE("model_eval", tout << mk_pp(e, m_manager) << "\n"; tout << "sort: " << mk_pp(m_manager.get_sort(e), m_manager) << "\n";); obj_map eval_cache; expr_ref_vector trail(m_manager); sbuffer, 128> todo; ptr_buffer args; expr * null = static_cast(0); todo.push_back(std::make_pair(e, null)); expr * a; expr * expanded_a; while (!todo.empty()) { std::pair & p = todo.back(); a = p.first; expanded_a = p.second; if (expanded_a != 0) { expr * r = 0; eval_cache.find(expanded_a, r); SASSERT(r != 0); todo.pop_back(); eval_cache.insert(a, r); TRACE("model_eval", tout << "orig:\n" << mk_pp(a, m_manager) << "\n"; tout << "after beta reduction:\n" << mk_pp(expanded_a, m_manager) << "\n"; tout << "new:\n" << mk_pp(r, m_manager) << "\n";); } else { switch(a->get_kind()) { case AST_APP: { app * t = to_app(a); bool visited = true; args.reset(); unsigned num_args = t->get_num_args(); for (unsigned i = 0; i < num_args; ++i) { expr * arg = 0; if (!eval_cache.find(t->get_arg(i), arg)) { visited = false; todo.push_back(std::make_pair(t->get_arg(i), null)); } else { args.push_back(arg); } } if (!visited) { continue; } SASSERT(args.size() == t->get_num_args()); expr_ref new_t(m_manager); func_decl * f = t->get_decl(); if (!has_interpretation(f)) { // the model does not assign an interpretation to f. SASSERT(new_t.get() == 0); if (f->get_family_id() == null_family_id) { if (model_completion) { // create an interpretation for f. new_t = mk_some_interp_for(f); } else { is_ok = false; } } if (new_t.get() == 0) { // t is interpreted or model completion is disabled. m_simplifier.mk_app(f, num_args, args.c_ptr(), new_t); trail.push_back(new_t); if (!is_app(new_t) || to_app(new_t)->get_decl() != f) { // if the result is not of the form (f ...), then assume we must simplify it. expr * new_new_t = 0; if (!eval_cache.find(new_t.get(), new_new_t)) { todo.back().second = new_t; todo.push_back(std::make_pair(new_t, null)); continue; } else { new_t = new_new_t; } } } } else { // the model has an interpretaion for f. if (num_args == 0) { // t is a constant new_t = get_const_interp(f); } else { // t is a function application SASSERT(new_t.get() == 0); // try to use function graph first func_interp * fi = get_func_interp(f); SASSERT(fi->get_arity() == num_args); expr_ref r1(m_manager); // fi may be partial... if (!fi->eval(m_simplifier, args.c_ptr(), r1)) { SASSERT(fi->is_partial()); // fi->eval only fails when fi is partial. if (model_completion) { expr * r = get_some_value(f->get_range()); fi->set_else(r); SASSERT(!fi->is_partial()); new_t = r; } else { // f is an uninterpreted function, there is no need to use m_simplifier.mk_app new_t = m_manager.mk_app(f, num_args, args.c_ptr()); trail.push_back(new_t); is_ok = false; } } else { SASSERT(r1); trail.push_back(r1); expr * r2 = 0; if (!eval_cache.find(r1.get(), r2)) { todo.back().second = r1; todo.push_back(std::make_pair(r1, null)); continue; } else { new_t = r2; } } } } TRACE("model_eval", tout << "orig:\n" << mk_pp(t, m_manager) << "\n"; tout << "new:\n" << mk_pp(new_t, m_manager) << "\n";); todo.pop_back(); SASSERT(new_t.get() != 0); eval_cache.insert(t, new_t); break; } case AST_VAR: SASSERT(a != 0); eval_cache.insert(a, a); todo.pop_back(); break; case AST_QUANTIFIER: is_ok = false; // evaluator does not handle quantifiers. SASSERT(a != 0); eval_cache.insert(a, a); todo.pop_back(); break; default: UNREACHABLE(); break; } } } if (!eval_cache.find(e, a)) { TRACE("model_eval", tout << "FAILED e: " << mk_bounded_pp(e, m_manager) << "\n";); UNREACHABLE(); } result = a; TRACE("model_eval", ast_ll_pp(tout << "original: ", m_manager, e); ast_ll_pp(tout << "evaluated: ", m_manager, a); ast_ll_pp(tout << "reduced: ", m_manager, result.get()); tout << "sort: " << mk_pp(m_manager.get_sort(e), m_manager) << "\n"; ); SASSERT(is_well_sorted(m_manager, result.get())); return is_ok; } /** \brief Replace uninterpreted constants occurring in fi->get_else() by their interpretations. */ void proto_model::cleanup_func_interp(func_interp * fi, func_decl_set & found_aux_fs) { if (fi->is_partial()) return; expr * fi_else = fi->get_else(); TRACE("model_bug", tout << "cleaning up:\n" << mk_pp(fi_else, m_manager) << "\n";); obj_map cache; expr_ref_vector trail(m_manager); ptr_buffer todo; ptr_buffer args; todo.push_back(fi_else); expr * a; while (!todo.empty()) { a = todo.back(); if (is_uninterp_const(a)) { todo.pop_back(); func_decl * a_decl = to_app(a)->get_decl(); expr * ai = get_const_interp(a_decl); if (ai == 0) { ai = get_some_value(a_decl->get_range()); register_decl(a_decl, ai); } cache.insert(a, ai); } else { switch(a->get_kind()) { case AST_APP: { app * t = to_app(a); bool visited = true; args.reset(); unsigned num_args = t->get_num_args(); for (unsigned i = 0; i < num_args; ++i) { expr * arg = 0; if (!cache.find(t->get_arg(i), arg)) { visited = false; todo.push_back(t->get_arg(i)); } else { args.push_back(arg); } } if (!visited) { continue; } func_decl * f = t->get_decl(); if (m_aux_decls.contains(f)) found_aux_fs.insert(f); expr_ref new_t(m_manager); m_simplifier.mk_app(f, num_args, args.c_ptr(), new_t); if (t != new_t.get()) trail.push_back(new_t); todo.pop_back(); cache.insert(t, new_t); break; } default: SASSERT(a != 0); cache.insert(a, a); todo.pop_back(); break; } } } if (!cache.find(fi_else, a)) { UNREACHABLE(); } fi->set_else(a); } void proto_model::remove_aux_decls_not_in_set(ptr_vector & decls, func_decl_set const & s) { unsigned sz = decls.size(); unsigned i = 0; unsigned j = 0; for (; i < sz; i++) { func_decl * f = decls[i]; if (!m_aux_decls.contains(f) || s.contains(f)) { decls[j] = f; j++; } } decls.shrink(j); } /** \brief Replace uninterpreted constants occurring in the func_interp's get_else() by their interpretations. */ void proto_model::cleanup() { func_decl_set found_aux_fs; decl2finterp::iterator it = m_finterp.begin(); decl2finterp::iterator end = m_finterp.end(); for (; it != end; ++it) { func_interp * fi = (*it).m_value; cleanup_func_interp(fi, found_aux_fs); } // remove auxiliary declarations that are not used. if (found_aux_fs.size() != m_aux_decls.size()) { remove_aux_decls_not_in_set(m_decls, found_aux_fs); remove_aux_decls_not_in_set(m_func_decls, found_aux_fs); func_decl_set::iterator it2 = m_aux_decls.begin(); func_decl_set::iterator end2 = m_aux_decls.end(); for (; it2 != end2; ++it2) { func_decl * faux = *it2; if (!found_aux_fs.contains(faux)) { TRACE("cleanup_bug", tout << "eliminating " << faux->get_name() << "\n";); func_interp * fi = 0; m_finterp.find(faux, fi); SASSERT(fi != 0); m_finterp.erase(faux); dealloc(fi); } } m_aux_decls.swap(found_aux_fs); } } value_factory * proto_model::get_factory(family_id fid) { return m_factories.get_plugin(fid); } void proto_model::freeze_universe(sort * s) { SASSERT(m_manager.is_uninterp(s)); m_user_sort_factory->freeze_universe(s); } /** \brief Return the known universe of an uninterpreted sort. */ obj_hashtable const & proto_model::get_known_universe(sort * s) const { SASSERT(m_manager.is_uninterp(s)); return m_user_sort_factory->get_known_universe(s); } ptr_vector const & proto_model::get_universe(sort * s) const { ptr_vector & tmp = const_cast(this)->m_tmp; tmp.reset(); obj_hashtable const & u = get_known_universe(s); obj_hashtable::iterator it = u.begin(); obj_hashtable::iterator end = u.end(); for (; it != end; ++it) tmp.push_back(*it); return tmp; } unsigned proto_model::get_num_uninterpreted_sorts() const { return m_user_sort_factory->get_num_sorts(); } sort * proto_model::get_uninterpreted_sort(unsigned idx) const { SASSERT(idx < get_num_uninterpreted_sorts()); return m_user_sort_factory->get_sort(idx); } /** \brief Return true if the given sort is uninterpreted and has a finite interpretation in the model. */ bool proto_model::is_finite(sort * s) const { return m_manager.is_uninterp(s) && m_user_sort_factory->is_finite(s); } expr * proto_model::get_some_value(sort * s) { family_id fid = s->get_family_id(); if (fid == null_family_id) { return m_user_sort_factory->get_some_value(s); } value_factory * f = get_factory(fid); if (f) return f->get_some_value(s); // there is no factory for the family id, then assume s is uninterpreted. return m_user_sort_factory->get_some_value(s); } bool proto_model::get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { family_id fid = s->get_family_id(); if (fid == null_family_id) { return m_user_sort_factory->get_some_values(s, v1, v2); } value_factory * f = get_factory(fid); if (f) return f->get_some_values(s, v1, v2); else return false; } expr * proto_model::get_fresh_value(sort * s) { family_id fid = s->get_family_id(); if (fid == null_family_id) return m_user_sort_factory->get_fresh_value(s); value_factory * f = get_factory(fid); if (f) return f->get_fresh_value(s); else // Use user_sort_factory if the theory has no support for model construnction. // This is needed when dummy theories are used for arithmetic or arrays. return m_user_sort_factory->get_fresh_value(s); } void proto_model::register_value(expr * n) { sort * s = m_manager.get_sort(n); family_id fid = s->get_family_id(); if (fid == null_family_id) { m_user_sort_factory->register_value(n); } else { value_factory * f = get_factory(fid); if (f) f->register_value(n); } } bool proto_model::is_array_value(expr * v) const { return is_app_of(v, m_afid, OP_AS_ARRAY); } void proto_model::compress() { ptr_vector::iterator it = m_func_decls.begin(); ptr_vector::iterator end = m_func_decls.end(); for (; it != end; ++it) { func_decl * f = *it; func_interp * fi = get_func_interp(f); SASSERT(fi != 0); fi->compress(); } } /** \brief Complete the interpretation fi of f if it is partial. If f does not have an interpretation in the given model, then this is a noop. */ void proto_model::complete_partial_func(func_decl * f) { func_interp * fi = get_func_interp(f); if (fi && fi->is_partial()) { expr * else_value = 0; #if 0 // For UFBV benchmarks, setting the "else" to false is not a good idea. // TODO: find a permanent solution. A possibility is to add another option. if (m_manager.is_bool(f->get_range())) { else_value = m_manager.mk_false(); } else { else_value = fi->get_max_occ_result(); if (else_value == 0) else_value = get_some_value(f->get_range()); } #else else_value = fi->get_max_occ_result(); if (else_value == 0) else_value = get_some_value(f->get_range()); #endif fi->set_else(else_value); } } /** \brief Set the (else) field of function interpretations... */ void proto_model::complete_partial_funcs() { if (m_params.m_model_partial) return; ptr_vector::iterator it = m_func_decls.begin(); ptr_vector::iterator end = m_func_decls.end(); for (; it != end; ++it) complete_partial_func(*it); } model * proto_model::mk_model() { TRACE("proto_model", tout << "mk_model\n"; model_v2_pp(tout, *this);); model * m = alloc(model, m_manager); decl2expr::iterator it1 = m_interp.begin(); decl2expr::iterator end1 = m_interp.end(); for (; it1 != end1; ++it1) { m->register_decl(it1->m_key, it1->m_value); } decl2finterp::iterator it2 = m_finterp.begin(); decl2finterp::iterator end2 = m_finterp.end(); for (; it2 != end2; ++it2) { m->register_decl(it2->m_key, it2->m_value); } m_finterp.reset(); // m took the ownership of the func_interp's unsigned sz = get_num_uninterpreted_sorts(); for (unsigned i = 0; i < sz; i++) { sort * s = get_uninterpreted_sort(i); TRACE("proto_model", tout << "copying uninterpreted sorts...\n" << mk_pp(s, m_manager) << "\n";); ptr_buffer buf; obj_hashtable const & u = get_known_universe(s); obj_hashtable::iterator it = u.begin(); obj_hashtable::iterator end = u.end(); for (; it != end; ++it) { buf.push_back(*it); } m->register_usort(s, buf.size(), buf.c_ptr()); } return m; }