3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-14 23:05:26 +00:00

Merge branch 'master' of https://github.com/Z3Prover/z3 into jfleisher/nightlyversion

This commit is contained in:
jfleisher 2022-08-31 10:27:39 -04:00
commit 9c7a3e4f8d
116 changed files with 7435 additions and 3213 deletions

View file

@ -10,6 +10,11 @@ Version 4.next
- native word level bit-vector solving. - native word level bit-vector solving.
- introduction of simple induction lemmas to handle a limited repertoire of induction proofs. - introduction of simple induction lemmas to handle a limited repertoire of induction proofs.
Version 4.11.1
==============
- add error handling to fromString method in JavaScript
- fix regression in default parameters for CDCL (Nuno Lopes)
Version 4.11.0 Version 4.11.0
============== ==============
- remove `Z3_bool`, `Z3_TRUE`, `Z3_FALSE` from the API. Use `bool`, `true`, `false` instead. - remove `Z3_bool`, `Z3_TRUE`, `Z3_FALSE` from the API. Use `bool`, `true`, `false` instead.
@ -23,7 +28,7 @@ Version 4.11.0
- it allows to apply incremental pre-processing of bit-vectors by identifying ranges that are known to be constant. - it allows to apply incremental pre-processing of bit-vectors by identifying ranges that are known to be constant.
This rewrite is beneficial, for instance, when bit-vectors are constrained to have many high-level bits set to 0. This rewrite is beneficial, for instance, when bit-vectors are constrained to have many high-level bits set to 0.
- add feature to model-based projection for arithmetic to handle integer division. - add feature to model-based projection for arithmetic to handle integer division.
- add from_string method to JavaScript solver object. - add fromString method to JavaScript solver object.
Version 4.10.2 Version 4.10.2
============== ==============

View file

@ -35,7 +35,7 @@ def help(ous):
ous.write("Z3 Options\n") ous.write("Z3 Options\n")
z3_exe = BUILD_DIR + "/z3" z3_exe = BUILD_DIR + "/z3"
out = subprocess.Popen([z3_exe, "-pm"],stdout=subprocess.PIPE).communicate()[0] out = subprocess.Popen([z3_exe, "-pm"],stdout=subprocess.PIPE).communicate()[0]
modules = [] modules = ["global"]
if out != None: if out != None:
out = out.decode(sys.stdout.encoding) out = out.decode(sys.stdout.encoding)
module_re = re.compile(r"\[module\] (.*)\,") module_re = re.compile(r"\[module\] (.*)\,")

View file

@ -27,7 +27,7 @@ def init_project_def():
add_lib('params', ['util']) add_lib('params', ['util'])
add_lib('smt_params', ['params'], 'smt/params') add_lib('smt_params', ['params'], 'smt/params')
add_lib('grobner', ['ast', 'dd', 'simplex'], 'math/grobner') add_lib('grobner', ['ast', 'dd', 'simplex'], 'math/grobner')
add_lib('sat', ['util', 'dd', 'grobner']) add_lib('sat', ['params', 'util', 'dd', 'grobner'])
add_lib('nlsat', ['polynomial', 'sat']) add_lib('nlsat', ['polynomial', 'sat'])
add_lib('lp', ['util', 'nlsat', 'grobner', 'interval', 'smt_params'], 'math/lp') add_lib('lp', ['util', 'nlsat', 'grobner', 'interval', 'smt_params'], 'math/lp')
add_lib('rewriter', ['ast', 'polynomial', 'automata', 'params'], 'ast/rewriter') add_lib('rewriter', ['ast', 'polynomial', 'automata', 'params'], 'ast/rewriter')
@ -84,7 +84,7 @@ def init_project_def():
API_files = ['z3_api.h', 'z3_ast_containers.h', 'z3_algebraic.h', 'z3_polynomial.h', 'z3_rcf.h', 'z3_fixedpoint.h', 'z3_optimization.h', 'z3_fpa.h', 'z3_spacer.h'] API_files = ['z3_api.h', 'z3_ast_containers.h', 'z3_algebraic.h', 'z3_polynomial.h', 'z3_rcf.h', 'z3_fixedpoint.h', 'z3_optimization.h', 'z3_fpa.h', 'z3_spacer.h']
add_lib('api', ['portfolio', 'realclosure', 'opt'], add_lib('api', ['portfolio', 'realclosure', 'opt'],
includes2install=['z3.h', 'z3_v1.h', 'z3_macros.h'] + API_files) includes2install=['z3.h', 'z3_v1.h', 'z3_macros.h'] + API_files)
add_lib('extra_cmds', ['cmd_context', 'subpaving_tactic', 'qe', 'arith_tactics'], 'cmd_context/extra_cmds') add_lib('extra_cmds', ['cmd_context', 'subpaving_tactic', 'qe', 'euf', 'arith_tactics'], 'cmd_context/extra_cmds')
add_exe('shell', ['api', 'sat', 'extra_cmds', 'opt'], exe_name='z3') add_exe('shell', ['api', 'sat', 'extra_cmds', 'opt'], exe_name='z3')
add_exe('test', ['api', 'fuzzing', 'simplex', 'sat_smt'], exe_name='test-z3', install=False) add_exe('test', ['api', 'fuzzing', 'simplex', 'sat_smt'], exe_name='test-z3', install=False)
_libz3Component = add_dll('api_dll', ['api', 'sat', 'extra_cmds'], 'api/dll', _libz3Component = add_dll('api_dll', ['api', 'sat', 'extra_cmds'], 'api/dll',

View file

@ -242,6 +242,7 @@ extern "C" {
std::istringstream is(s); std::istringstream is(s);
ctx->set_regular_stream(ous); ctx->set_regular_stream(ous);
ctx->set_diagnostic_stream(ous); ctx->set_diagnostic_stream(ous);
cmd_context::scoped_redirect _redirect(*ctx);
try { try {
if (!parse_smt2_commands(*ctx.get(), is)) { if (!parse_smt2_commands(*ctx.get(), is)) {
SET_ERROR_CODE(Z3_PARSER_ERROR, ous.str()); SET_ERROR_CODE(Z3_PARSER_ERROR, ous.str());

View file

@ -71,6 +71,7 @@ namespace Microsoft.Z3
Solver solver; Solver solver;
Context ctx; Context ctx;
Z3_solver_callback callback = IntPtr.Zero; Z3_solver_callback callback = IntPtr.Zero;
int callbackNesting = 0;
FixedEh fixed_eh; FixedEh fixed_eh;
Action final_eh; Action final_eh;
EqEh eq_eh; EqEh eq_eh;
@ -91,6 +92,7 @@ namespace Microsoft.Z3
void Callback(Action fn, Z3_solver_callback cb) void Callback(Action fn, Z3_solver_callback cb)
{ {
this.callbackNesting++;
this.callback = cb; this.callback = cb;
try try
{ {
@ -102,6 +104,8 @@ namespace Microsoft.Z3
} }
finally finally
{ {
callbackNesting--;
if (callbackNesting == 0) // callbacks can be nested (e.g., internalizing new element in "created")
this.callback = IntPtr.Zero; this.callback = IntPtr.Zero;
} }
} }

View file

@ -889,8 +889,10 @@ void basic_decl_plugin::set_manager(ast_manager * m, family_id id) {
} }
void basic_decl_plugin::get_sort_names(svector<builtin_name> & sort_names, symbol const & logic) { void basic_decl_plugin::get_sort_names(svector<builtin_name> & sort_names, symbol const & logic) {
if (logic == symbol::null) if (logic == symbol::null) {
sort_names.push_back(builtin_name("bool", BOOL_SORT)); sort_names.push_back(builtin_name("bool", BOOL_SORT));
sort_names.push_back(builtin_name("Proof", PROOF_SORT)); // reserved name?
}
sort_names.push_back(builtin_name("Bool", BOOL_SORT)); sort_names.push_back(builtin_name("Bool", BOOL_SORT));
} }

View file

@ -129,6 +129,7 @@ void ast_pp_util::push() {
m_rec_decls.push(); m_rec_decls.push();
m_decls.push(); m_decls.push();
m_sorts.push(); m_sorts.push();
m_defined_lim.push_back(m_defined.size());
} }
void ast_pp_util::pop(unsigned n) { void ast_pp_util::pop(unsigned n) {
@ -136,4 +137,55 @@ void ast_pp_util::pop(unsigned n) {
m_rec_decls.pop(n); m_rec_decls.pop(n);
m_decls.pop(n); m_decls.pop(n);
m_sorts.pop(n); m_sorts.pop(n);
unsigned old_sz = m_defined_lim[m_defined_lim.size() - n];
for (unsigned i = m_defined.size(); i-- > old_sz; )
m_is_defined.mark(m_defined.get(i), false);
m_defined.shrink(old_sz);
m_defined_lim.shrink(m_defined_lim.size() - n);
}
std::ostream& ast_pp_util::display_expr_def(std::ostream& out, expr* n) {
if (is_app(n) && to_app(n)->get_num_args() == 0)
return out << mk_pp(n, m);
else
return out << "$" << n->get_id();
}
std::ostream& ast_pp_util::define_expr(std::ostream& out, expr* n) {
ptr_buffer<expr> visit;
visit.push_back(n);
while (!visit.empty()) {
n = visit.back();
if (m_is_defined.is_marked(n)) {
visit.pop_back();
continue;
}
if (is_app(n)) {
bool all_visit = true;
for (auto* e : *to_app(n)) {
if (m_is_defined.is_marked(e))
continue;
all_visit = false;
visit.push_back(e);
}
if (!all_visit)
continue;
m_defined.push_back(n);
m_is_defined.mark(n, true);
visit.pop_back();
if (to_app(n)->get_num_args() > 0) {
out << "(define-const $" << n->get_id() << " " << mk_pp(n->get_sort(), m) << " (";
out << mk_ismt2_func(to_app(n)->get_decl(), m);
for (auto* e : *to_app(n))
display_expr_def(out << " ", e);
out << "))\n";
}
continue;
}
out << "(define-const $" << n->get_id() << " " << mk_pp(n->get_sort(), m) << " " << mk_pp(n, m) << ")\n";
m_defined.push_back(n);
m_is_defined.mark(n, true);
visit.pop_back();
}
return out;
} }

View file

@ -30,15 +30,18 @@ class ast_pp_util {
stacked_value<unsigned> m_rec_decls; stacked_value<unsigned> m_rec_decls;
stacked_value<unsigned> m_decls; stacked_value<unsigned> m_decls;
stacked_value<unsigned> m_sorts; stacked_value<unsigned> m_sorts;
expr_mark m_is_defined;
expr_ref_vector m_defined;
unsigned_vector m_defined_lim;
public: public:
decl_collector coll; decl_collector coll;
ast_pp_util(ast_manager& m): m(m), m_env(m), m_rec_decls(0), m_decls(0), m_sorts(0), coll(m) {} ast_pp_util(ast_manager& m): m(m), m_env(m), m_rec_decls(0), m_decls(0), m_sorts(0), m_defined(m), coll(m) {}
void reset() { coll.reset(); m_removed.reset(); m_sorts.clear(0u); m_decls.clear(0u); m_rec_decls.clear(0u); }
void reset() { coll.reset(); m_removed.reset(); m_sorts.clear(0u); m_decls.clear(0u); m_rec_decls.clear(0u);
m_is_defined.reset(); m_defined.reset(); m_defined_lim.reset(); }
void collect(expr* e); void collect(expr* e);
@ -60,6 +63,10 @@ class ast_pp_util {
std::ostream& display_expr(std::ostream& out, expr* f, bool neat = true); std::ostream& display_expr(std::ostream& out, expr* f, bool neat = true);
std::ostream& define_expr(std::ostream& out, expr* f);
std::ostream& display_expr_def(std::ostream& out, expr* f);
void push(); void push();
void pop(unsigned n); void pop(unsigned n);

View file

@ -620,7 +620,7 @@ func_decl * bv_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p
for (unsigned i = 0; i < num_args; ++i) { for (unsigned i = 0; i < num_args; ++i) {
if (args[i]->get_sort() != r->get_domain(i)) { if (args[i]->get_sort() != r->get_domain(i)) {
std::ostringstream buffer; std::ostringstream buffer;
buffer << "Argument " << mk_pp(args[i], m) << " at position " << i << " does not match declaration " << mk_pp(r, m); buffer << "Argument " << mk_pp(args[i], m) << " at position " << i << " has sort " << mk_pp(args[i]->get_sort(), m) << " it does does not match declaration " << mk_pp(r, m);
m.raise_exception(buffer.str()); m.raise_exception(buffer.str());
return nullptr; return nullptr;
} }

View file

@ -29,7 +29,7 @@ Revision History:
\brief Macros are universally quantified formulas of the form: \brief Macros are universally quantified formulas of the form:
(forall X (= (f X) T[X])) (forall X (= (f X) T[X]))
(forall X (iff (f X) T[X])) (forall X (iff (f X) T[X]))
where T[X] does not contain X. where T[X] does not contain f.
This class is responsible for storing macros and expanding them. This class is responsible for storing macros and expanding them.
It has support for backtracking and tagging declarations in an expression as forbidded for being macros. It has support for backtracking and tagging declarations in an expression as forbidded for being macros.

View file

@ -66,7 +66,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p)
bool single_line = p.single_line(); bool single_line = p.single_line();
unsigned pos = 0; unsigned pos = 0;
unsigned ribbon_pos = 0;
unsigned line = 0; unsigned line = 0;
unsigned len; unsigned len;
unsigned i; unsigned i;
@ -92,7 +91,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p)
break; break;
} }
pos += len; pos += len;
ribbon_pos += len;
out << f->get_decl()->get_parameter(0).get_symbol(); out << f->get_decl()->get_parameter(0).get_symbol();
break; break;
case OP_INDENT: case OP_INDENT:
@ -121,7 +119,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p)
break; break;
} }
pos = indent; pos = indent;
ribbon_pos = 0;
line++; line++;
if (line < max_num_lines) { if (line < max_num_lines) {
out << "\n"; out << "\n";

View file

@ -291,7 +291,11 @@ namespace recfun {
expr * e = stack.back(); expr * e = stack.back();
stack.pop_back(); stack.pop_back();
if (m.is_ite(e)) { expr* cond = nullptr, *th = nullptr, *el = nullptr;
if (m.is_ite(e, cond, th, el) && contains_def(u, cond)) {
// skip
}
else if (m.is_ite(e)) {
// need to do a case split on `e`, forking the search space // need to do a case split on `e`, forking the search space
b.to_split = st.cons_ite(to_app(e), b.to_split); b.to_split = st.cons_ite(to_app(e), b.to_split);
} }
@ -338,9 +342,8 @@ namespace recfun {
// substitute, to get rid of `ite` terms // substitute, to get rid of `ite` terms
expr_ref case_rhs = subst(rhs); expr_ref case_rhs = subst(rhs);
for (unsigned i = 0; i < conditions.size(); ++i) { for (unsigned i = 0; i < conditions.size(); ++i)
conditions[i] = subst(conditions.get(i)); conditions[i] = subst(conditions.get(i));
}
// yield new case // yield new case
bool is_imm = is_i(case_rhs); bool is_imm = is_i(case_rhs);
@ -471,10 +474,9 @@ namespace recfun {
void plugin::set_definition(replace& r, promise_def & d, bool is_macro, unsigned n_vars, var * const * vars, expr * rhs) { void plugin::set_definition(replace& r, promise_def & d, bool is_macro, unsigned n_vars, var * const * vars, expr * rhs) {
u().set_definition(r, d, is_macro, n_vars, vars, rhs); u().set_definition(r, d, is_macro, n_vars, vars, rhs);
for (case_def & c : d.get_def()->get_cases()) { for (case_def & c : d.get_def()->get_cases())
m_case_defs.insert(c.get_decl(), &c); m_case_defs.insert(c.get_decl(), &c);
} }
}
bool plugin::has_defs() const { bool plugin::has_defs() const {
return !m_case_defs.empty(); return !m_case_defs.empty();

View file

@ -152,7 +152,7 @@ public:
return find(to_var(v.get_expr()), v.get_offset(), r); return find(to_var(v.get_expr()), v.get_offset(), r);
} }
void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) { void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) const {
var = m_vars[binding_num]; var = m_vars[binding_num];
VERIFY(m_subst.find(var.first, var.second, r)); VERIFY(m_subst.find(var.first, var.second, r));
} }

View file

@ -561,6 +561,7 @@ cmd_context::~cmd_context() {
finalize_cmds(); finalize_cmds();
finalize_tactic_cmds(); finalize_tactic_cmds();
finalize_probes(); finalize_probes();
m_proof_cmds = nullptr;
reset(true); reset(true);
m_mcs.reset(); m_mcs.reset();
m_solver = nullptr; m_solver = nullptr;

View file

@ -90,6 +90,17 @@ public:
vector<macro_decl>::iterator end() const { return m_decls->end(); } vector<macro_decl>::iterator end() const { return m_decls->end(); }
}; };
class proof_cmds {
public:
virtual ~proof_cmds() {}
virtual void add_literal(expr* e) = 0;
virtual void end_assumption() = 0;
virtual void end_learned() = 0;
virtual void end_deleted() = 0;
};
/** /**
\brief Generic wrapper. \brief Generic wrapper.
*/ */
@ -172,6 +183,7 @@ public:
bool owns_manager() const { return m_manager != nullptr; } bool owns_manager() const { return m_manager != nullptr; }
}; };
class cmd_context : public progress_callback, public tactic_manager, public ast_printer_context { class cmd_context : public progress_callback, public tactic_manager, public ast_printer_context {
public: public:
enum status { enum status {
@ -191,6 +203,22 @@ public:
~scoped_watch() { m_ctx.m_watch.stop(); } ~scoped_watch() { m_ctx.m_watch.stop(); }
}; };
struct scoped_redirect {
cmd_context& m_ctx;
std::ostream& m_verbose;
std::ostream* m_warning;
scoped_redirect(cmd_context& ctx): m_ctx(ctx), m_verbose(verbose_stream()), m_warning(warning_stream()) {
set_warning_stream(&(*m_ctx.m_diagnostic));
set_verbose_stream(m_ctx.diagnostic_stream());
}
~scoped_redirect() {
set_verbose_stream(m_verbose);
set_warning_stream(m_warning);
}
};
protected: protected:
@ -209,6 +237,7 @@ protected:
bool m_ignore_check = false; // used by the API to disable check-sat() commands when parsing SMT 2.0 files. bool m_ignore_check = false; // used by the API to disable check-sat() commands when parsing SMT 2.0 files.
bool m_exit_on_error = false; bool m_exit_on_error = false;
bool m_allow_duplicate_declarations = false; bool m_allow_duplicate_declarations = false;
scoped_ptr<proof_cmds> m_proof_cmds;
static std::ostringstream g_error_stream; static std::ostringstream g_error_stream;
@ -381,6 +410,9 @@ public:
pdecl_manager & pm() const { if (!m_pmanager) const_cast<cmd_context*>(this)->init_manager(); return *m_pmanager; } pdecl_manager & pm() const { if (!m_pmanager) const_cast<cmd_context*>(this)->init_manager(); return *m_pmanager; }
sexpr_manager & sm() const { if (!m_sexpr_manager) const_cast<cmd_context*>(this)->m_sexpr_manager = alloc(sexpr_manager); return *m_sexpr_manager; } sexpr_manager & sm() const { if (!m_sexpr_manager) const_cast<cmd_context*>(this)->m_sexpr_manager = alloc(sexpr_manager); return *m_sexpr_manager; }
proof_cmds* get_proof_cmds() { return m_proof_cmds.get(); }
void set_proof_cmds(proof_cmds* pc) { m_proof_cmds = pc; }
void set_solver_factory(solver_factory * s); void set_solver_factory(solver_factory * s);
void set_check_sat_result(check_sat_result * r) { m_check_sat_result = r; } void set_check_sat_result(check_sat_result * r) { m_check_sat_result = r; }
check_sat_result * get_check_sat_result() const { return m_check_sat_result.get(); } check_sat_result * get_check_sat_result() const { return m_check_sat_result.get(); }

View file

@ -3,6 +3,7 @@ z3_add_component(extra_cmds
dbg_cmds.cpp dbg_cmds.cpp
polynomial_cmds.cpp polynomial_cmds.cpp
subpaving_cmds.cpp subpaving_cmds.cpp
proof_cmds.cpp
COMPONENT_DEPENDENCIES COMPONENT_DEPENDENCIES
arith_tactics arith_tactics
cmd_context cmd_context

View file

@ -0,0 +1,267 @@
/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
proof_cmds.cpp
Abstract:
Commands for reading and checking proofs.
Author:
Nikolaj Bjorner (nbjorner) 2022-8-26
Notes:
Proof checker for clauses created during search.
1. Clauses annotated by RUP (reverse unit propagation)
are checked to be inferrable using reverse unit propagation
based on previous clauses.
2. Clauses annotated by supported proof rules (proof hints)
are checked by custom proof checkers. There is a proof checker
for each proof rule. Main proof checkers just have a single step
but the framework allows to compose proof rules, each inference
is checked for correctness by a plugin.
3. When there are no supported plugin to justify the derived
clause, or a custom check fails, the fallback is to check that the
derived clause is a consequence of the input clauses using SMT.
The last approach is a bail-out and offers a weaker notion of
self-validation. It is often (but not always) sufficient for using proof
checking for debugging, as the root-cause for an unsound inference in z3
does not necessarily manifest when checking the conclusion of the
inference. An external proof checker that uses such fallbacks could
use several solvers, or bootstrap from a solver that can generate certificates
when z3 does not.
--*/
#include "util/small_object_allocator.h"
#include "ast/ast_util.h"
#include "smt/smt_solver.h"
#include "sat/sat_solver.h"
#include "sat/sat_drat.h"
#include "sat/smt/euf_proof_checker.h"
#include "cmd_context/cmd_context.h"
#include <iostream>
class smt_checker {
ast_manager& m;
params_ref m_params;
// for checking proof rules (hints)
euf::proof_checker m_checker;
// for fallback SMT checker
scoped_ptr<solver> m_solver;
// for RUP
symbol m_rup;
sat::solver m_sat_solver;
sat::drat m_drat;
sat::literal_vector m_units;
sat::literal_vector m_clause;
void add_units() {
auto const& units = m_drat.units();
for (unsigned i = m_units.size(); i < units.size(); ++i)
m_units.push_back(units[i].first);
}
public:
smt_checker(ast_manager& m):
m(m),
m_checker(m),
m_sat_solver(m_params, m.limit()),
m_drat(m_sat_solver)
{
m_params.set_bool("drat.check_unsat", true);
m_sat_solver.updt_params(m_params);
m_drat.updt_config();
m_solver = mk_smt_solver(m, m_params, symbol());
m_rup = symbol("rup");
}
bool is_rup(app* proof_hint) {
return
proof_hint &&
proof_hint->get_name() == m_rup;
}
void mk_clause(expr_ref_vector const& clause) {
m_clause.reset();
for (expr* e : clause) {
bool sign = false;
while (m.is_not(e, e))
sign = !sign;
m_clause.push_back(sat::literal(e->get_id(), sign));
}
}
void mk_clause(expr* e) {
m_clause.reset();
bool sign = false;
while (m.is_not(e, e))
sign = !sign;
m_clause.push_back(sat::literal(e->get_id(), sign));
}
bool check_rup(expr_ref_vector const& clause) {
add_units();
mk_clause(clause);
return m_drat.is_drup(m_clause.size(), m_clause.data(), m_units);
}
bool check_rup(expr* u) {
add_units();
mk_clause(u);
return m_drat.is_drup(m_clause.size(), m_clause.data(), m_units);
}
void add_clause(expr_ref_vector const& clause) {
mk_clause(clause);
m_drat.add(m_clause, sat::status::input());
}
void check(expr_ref_vector& clause, app* proof_hint) {
if (is_rup(proof_hint) && check_rup(clause)) {
std::cout << "(verified-rup)\n";
return;
}
expr_ref_vector units(m);
if (m_checker.check(clause, proof_hint, units)) {
bool units_are_rup = true;
for (expr* u : units) {
if (!check_rup(u)) {
std::cout << "unit " << mk_pp(u, m) << " is not rup\n";
units_are_rup = false;
}
}
if (units_are_rup) {
std::cout << "(verified-" << proof_hint->get_name() << ")\n";
add_clause(clause);
return;
}
}
m_solver->push();
for (expr* lit : clause)
m_solver->assert_expr(m.mk_not(lit));
lbool is_sat = m_solver->check_sat();
if (is_sat != l_false) {
std::cout << "did not verify: " << is_sat << " " << clause << "\n\n";
m_solver->display(std::cout);
if (is_sat == l_true) {
model_ref mdl;
m_solver->get_model(mdl);
std::cout << *mdl << "\n";
}
exit(0);
}
m_solver->pop(1);
std::cout << "(verified-smt)\n";
add_clause(clause);
}
void assume(expr_ref_vector const& clause) {
add_clause(clause);
m_solver->assert_expr(mk_or(clause));
}
};
class proof_cmds_imp : public proof_cmds {
ast_manager& m;
expr_ref_vector m_lits;
app_ref m_proof_hint;
smt_checker m_checker;
public:
proof_cmds_imp(ast_manager& m): m(m), m_lits(m), m_proof_hint(m), m_checker(m) {}
void add_literal(expr* e) override {
if (m.is_proof(e))
m_proof_hint = to_app(e);
else
m_lits.push_back(e);
}
void end_assumption() override {
m_checker.assume(m_lits);
m_lits.reset();
m_proof_hint.reset();
}
void end_learned() {
m_checker.check(m_lits, m_proof_hint);
m_lits.reset();
m_proof_hint.reset();
}
void end_deleted() {
m_lits.reset();
m_proof_hint.reset();
}
};
static proof_cmds& get(cmd_context& ctx) {
if (!ctx.get_proof_cmds())
ctx.set_proof_cmds(alloc(proof_cmds_imp, ctx.m()));
return *ctx.get_proof_cmds();
}
// assumption
class assume_cmd : public cmd {
public:
assume_cmd():cmd("assume") {}
char const* get_usage() const override { return "<expr>+"; }
char const * get_descr(cmd_context& ctx) const override { return "proof command for adding assumption (input assertion)"; }
unsigned get_arity() const override { return VAR_ARITY; }
void prepare(cmd_context & ctx) override {}
void finalize(cmd_context & ctx) override {}
void failure_cleanup(cmd_context & ctx) override {}
cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; }
void set_next_arg(cmd_context & ctx, expr * arg) override { get(ctx).add_literal(arg); }
void execute(cmd_context& ctx) override { get(ctx).end_assumption(); }
};
// deleted clause
class del_cmd : public cmd {
public:
del_cmd():cmd("del") {}
char const* get_usage() const override { return "<expr>+"; }
char const * get_descr(cmd_context& ctx) const override { return "proof command for clause deletion"; }
unsigned get_arity() const override { return VAR_ARITY; }
void prepare(cmd_context & ctx) override {}
void finalize(cmd_context & ctx) override {}
void failure_cleanup(cmd_context & ctx) override {}
cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; }
void set_next_arg(cmd_context & ctx, expr * arg) override { get(ctx).add_literal(arg); }
void execute(cmd_context& ctx) override { get(ctx).end_deleted(); }
};
// learned/redundant clause
class learn_cmd : public cmd {
public:
learn_cmd():cmd("learn") {}
char const* get_usage() const override { return "<expr>+"; }
char const* get_descr(cmd_context& ctx) const override { return "proof command for learned (redundant) clauses"; }
unsigned get_arity() const override { return VAR_ARITY; }
void prepare(cmd_context & ctx) override {}
void finalize(cmd_context & ctx) override {}
void failure_cleanup(cmd_context & ctx) override {}
cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; }
void set_next_arg(cmd_context & ctx, expr * arg) override { get(ctx).add_literal(arg); }
void execute(cmd_context& ctx) override { get(ctx).end_learned(); }
};
void install_proof_cmds(cmd_context & ctx) {
ctx.insert(alloc(del_cmd));
ctx.insert(alloc(learn_cmd));
ctx.insert(alloc(assume_cmd));
}

View file

@ -0,0 +1,36 @@
/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
proof_cmds.h
Abstract:
Commands for reading proofs.
Author:
Nikolaj Bjorner (nbjorner) 2022-8-26
Notes:
--*/
#pragma once
/**
proof_cmds is a structure that tracks an evidence trail.
The main interface is to:
add literals one by one,
add proof hints
until receiving end-command: assumption, learned, deleted.
Evidence can be checked:
- By DRUP
- Theory lemmas
*/
class cmd_context;
void install_proof_cmds(cmd_context & ctx);

View file

@ -546,6 +546,7 @@ bool emonics::invariant() const {
} }
CTRACE("nla_solver_mons", !found, tout << "not found v" << v << ": " << m << "\n";); CTRACE("nla_solver_mons", !found, tout << "not found v" << v << ": " << m << "\n";);
SASSERT(found); SASSERT(found);
(void)found;
c = c->m_next; c = c->m_next;
} }
while (c != ht.m_head); while (c != ht.m_head);

View file

@ -49,6 +49,7 @@ const char* lp_status_to_string(lp_status status) {
case lp_status::TIME_EXHAUSTED: return "TIME_EXHAUSTED"; case lp_status::TIME_EXHAUSTED: return "TIME_EXHAUSTED";
case lp_status::EMPTY: return "EMPTY"; case lp_status::EMPTY: return "EMPTY";
case lp_status::UNSTABLE: return "UNSTABLE"; case lp_status::UNSTABLE: return "UNSTABLE";
case lp_status::CANCELLED: return "CANCELLED";
default: default:
lp_unreachable(); lp_unreachable();
} }

View file

@ -380,12 +380,12 @@ namespace opt {
m_below.reset(); m_below.reset();
for (unsigned row_id : row_ids) { for (unsigned row_id : row_ids) {
SASSERT(row_id != m_objective_id); SASSERT(row_id != m_objective_id);
if (visited.contains(row_id)) { if (visited.contains(row_id))
continue; continue;
}
visited.insert(row_id); visited.insert(row_id);
row& r = m_rows[row_id]; row& r = m_rows[row_id];
if (r.m_alive) { if (!r.m_alive)
continue;
rational a = get_coefficient(row_id, x); rational a = get_coefficient(row_id, x);
if (a.is_zero()) { if (a.is_zero()) {
// skip // skip
@ -406,19 +406,17 @@ namespace opt {
bound_row_index = row_id; bound_row_index = row_id;
bound_coeff = a; bound_coeff = a;
} }
else { else
m_above.push_back(row_id); m_above.push_back(row_id);
} }
} else
else {
m_below.push_back(row_id); m_below.push_back(row_id);
} }
}
}
return bound_row_index != UINT_MAX; return bound_row_index != UINT_MAX;
} }
void model_based_opt::retire_row(unsigned row_id) { void model_based_opt::retire_row(unsigned row_id) {
SASSERT(!m_retired_rows.contains(row_id));
m_rows[row_id].m_alive = false; m_rows[row_id].m_alive = false;
m_retired_rows.push_back(row_id); m_retired_rows.push_back(row_id);
} }
@ -736,6 +734,8 @@ namespace opt {
void model_based_opt::normalize(unsigned row_id) { void model_based_opt::normalize(unsigned row_id) {
row& r = m_rows[row_id]; row& r = m_rows[row_id];
if (!r.m_alive)
return;
if (r.m_vars.empty()) { if (r.m_vars.empty()) {
retire_row(row_id); retire_row(row_id);
return; return;
@ -934,6 +934,7 @@ namespace opt {
else { else {
row_id = m_retired_rows.back(); row_id = m_retired_rows.back();
m_retired_rows.pop_back(); m_retired_rows.pop_back();
SASSERT(!m_rows[row_id].m_alive);
m_rows[row_id].reset(); m_rows[row_id].reset();
m_rows[row_id].m_alive = true; m_rows[row_id].m_alive = true;
} }
@ -995,10 +996,10 @@ namespace opt {
return v; return v;
} }
void model_based_opt::add_constraint(vector<var> const& coeffs, rational const& c, rational const& m, ineq_type rel, unsigned id) { unsigned model_based_opt::add_constraint(vector<var> const& coeffs, rational const& c, rational const& m, ineq_type rel, unsigned id) {
auto const& r = m_rows.back(); auto const& r = m_rows.back();
if (r.m_vars == coeffs && r.m_coeff == c && r.m_mod == m && r.m_type == rel && r.m_id == id && r.m_alive) if (r.m_vars == coeffs && r.m_coeff == c && r.m_mod == m && r.m_type == rel && r.m_id == id && r.m_alive)
return; return m_rows.size() - 1;
unsigned row_id = new_row(); unsigned row_id = new_row();
set_row(row_id, coeffs, c, m, rel); set_row(row_id, coeffs, c, m, rel);
m_rows[row_id].m_id = id; m_rows[row_id].m_id = id;
@ -1006,6 +1007,7 @@ namespace opt {
m_var2row_ids[coeff.m_id].push_back(row_id); m_var2row_ids[coeff.m_id].push_back(row_id);
SASSERT(invariant(row_id, m_rows[row_id])); SASSERT(invariant(row_id, m_rows[row_id]));
normalize(row_id); normalize(row_id);
return row_id;
} }
void model_based_opt::set_objective(vector<var> const& coeffs, rational const& c) { void model_based_opt::set_objective(vector<var> const& coeffs, rational const& c) {
@ -1057,23 +1059,18 @@ namespace opt {
unsigned eq_row = UINT_MAX; unsigned eq_row = UINT_MAX;
// select the lub and glb. // select the lub and glb.
for (unsigned row_id : row_ids) { for (unsigned row_id : row_ids) {
if (visited.contains(row_id)) { if (visited.contains(row_id))
continue; continue;
}
visited.insert(row_id); visited.insert(row_id);
row& r = m_rows[row_id]; row& r = m_rows[row_id];
if (!r.m_alive) { if (!r.m_alive)
continue; continue;
}
rational a = get_coefficient(row_id, x); rational a = get_coefficient(row_id, x);
if (a.is_zero()) { if (a.is_zero())
continue; continue;
} if (r.m_type == t_eq)
if (r.m_type == t_eq) {
eq_row = row_id; eq_row = row_id;
continue; else if (r.m_type == t_mod)
}
if (r.m_type == t_mod)
mod_rows.push_back(row_id); mod_rows.push_back(row_id);
else if (r.m_type == t_div) else if (r.m_type == t_div)
div_rows.push_back(row_id); div_rows.push_back(row_id);
@ -1106,15 +1103,12 @@ namespace opt {
} }
} }
if (!mod_rows.empty())
return solve_mod(x, mod_rows, compute_def);
if (!div_rows.empty())
return solve_div(x, div_rows, compute_def);
if (!divide_rows.empty()) if (!divide_rows.empty())
return solve_divides(x, divide_rows, compute_def); return solve_divides(x, divide_rows, compute_def);
if (!div_rows.empty() || !mod_rows.empty())
return solve_mod_div(x, mod_rows, div_rows, compute_def);
if (eq_row != UINT_MAX) if (eq_row != UINT_MAX)
return solve_for(eq_row, x, compute_def); return solve_for(eq_row, x, compute_def);
@ -1218,89 +1212,7 @@ namespace opt {
// - 0 <= g*z.value + w.value < K*(g+1) // - 0 <= g*z.value + w.value < K*(g+1)
// - add g*z + w - v - k*K = 0 for suitable k from 0 .. g based on model // - add g*z + w - v - k*K = 0 for suitable k from 0 .. g based on model
// //
//
model_based_opt::def model_based_opt::solve_mod(unsigned x, unsigned_vector const& _mod_rows, bool compute_def) {
def result;
unsigned_vector mod_rows(_mod_rows);
rational K(1);
for (unsigned ri : mod_rows)
K = lcm(K, m_rows[ri].m_mod);
rational x_value = m_var2value[x];
rational y_value = div(x_value, K);
rational z_value = mod(x_value, K);
SASSERT(x_value == K * y_value + z_value);
SASSERT(0 <= z_value && z_value < K);
// add new variables
unsigned z = add_var(z_value, true);
unsigned y = add_var(y_value, true);
uint_set visited;
for (unsigned ri : mod_rows) {
m_rows[ri].m_alive = false;
visited.insert(ri);
}
// replace x by K*y + z in other rows.
for (unsigned ri : m_var2row_ids[x]) {
if (visited.contains(ri))
continue;
replace_var(ri, x, K, y, rational::one(), z);
visited.insert(ri);
normalize(ri);
}
// add bounds for z
add_lower_bound(z, rational::zero());
add_upper_bound(z, K - 1);
for (unsigned ri : mod_rows) {
rational a = get_coefficient(ri, x);
replace_var(ri, x, rational::zero());
// add w = b mod K
vector<var> coeffs = m_rows[ri].m_vars;
rational coeff = m_rows[ri].m_coeff;
unsigned v = m_rows[ri].m_id;
rational v_value = m_var2value[v];
unsigned w = UINT_MAX;
rational offset(0);
if (coeffs.empty())
offset = mod(coeff, K);
else
w = add_mod(coeffs, coeff, K);
rational w_value = w == UINT_MAX ? offset : m_var2value[w];
// add v = a*z + w - V, for k = (a*z_value + w_value) div K
// claim: (= (mod x K) (- x (* K (div x K)))))) is a theorem for every x, K != 0
rational V = v_value - a * z_value - w_value;
vector<var> mod_coeffs;
mod_coeffs.push_back(var(v, rational::minus_one()));
mod_coeffs.push_back(var(z, a));
if (w != UINT_MAX) mod_coeffs.push_back(var(w, rational::one()));
add_constraint(mod_coeffs, V + offset, t_eq);
add_lower_bound(v, rational::zero());
add_upper_bound(v, K - 1);
// allow to recycle row.
m_retired_rows.push_back(ri);
project(v, false);
}
def y_def = project(y, compute_def);
def z_def = project(z, compute_def);
if (compute_def) {
result = (y_def * K) + z_def;
m_var2value[x] = eval(result);
}
TRACE("opt", display(tout << "solve_mod\n"));
return result;
}
// //
// Given v = a*x + b div K // Given v = a*x + b div K
// Replace x |-> K*y + z // Replace x |-> K*y + z
@ -1322,14 +1234,17 @@ namespace opt {
// where k is between 0 and g // where k is between 0 and g
// when gcd(a, K) = 1, then there are only two cases. // when gcd(a, K) = 1, then there are only two cases.
// //
model_based_opt::def model_based_opt::solve_div(unsigned x, unsigned_vector const& _div_rows, bool compute_def) { model_based_opt::def model_based_opt::solve_mod_div(unsigned x, unsigned_vector const& _mod_rows, unsigned_vector const& _div_rows, bool compute_def) {
def result; def result;
unsigned_vector div_rows(_div_rows); unsigned_vector div_rows(_div_rows), mod_rows(_mod_rows);
SASSERT(!div_rows.empty()); SASSERT(!div_rows.empty() || !mod_rows.empty());
TRACE("opt", display(tout << "solve_div " << x << "\n"));
rational K(1); rational K(1);
for (unsigned ri : div_rows) for (unsigned ri : div_rows)
K = lcm(K, m_rows[ri].m_mod); K = lcm(K, m_rows[ri].m_mod);
for (unsigned ri : mod_rows)
K = lcm(K, m_rows[ri].m_mod);
rational x_value = m_var2value[x]; rational x_value = m_var2value[x];
rational z_value = mod(x_value, K); rational z_value = mod(x_value, K);
@ -1341,12 +1256,28 @@ namespace opt {
unsigned y = add_var(y_value, true); unsigned y = add_var(y_value, true);
uint_set visited; uint_set visited;
unsigned j = 0;
for (unsigned ri : div_rows) { for (unsigned ri : div_rows) {
if (visited.contains(ri))
continue;
row& r = m_rows[ri]; row& r = m_rows[ri];
mul(ri, K / r.m_mod); mul(ri, K / r.m_mod);
r.m_alive = false; r.m_alive = false;
visited.insert(ri); visited.insert(ri);
div_rows[j++] = ri;
} }
div_rows.shrink(j);
j = 0;
for (unsigned ri : mod_rows) {
if (visited.contains(ri))
continue;
m_rows[ri].m_alive = false;
visited.insert(ri);
mod_rows[j++] = ri;
}
mod_rows.shrink(j);
// replace x by K*y + z in other rows. // replace x by K*y + z in other rows.
for (unsigned ri : m_var2row_ids[x]) { for (unsigned ri : m_var2row_ids[x]) {
@ -1361,10 +1292,11 @@ namespace opt {
add_lower_bound(z, rational::zero()); add_lower_bound(z, rational::zero());
add_upper_bound(z, K - 1); add_upper_bound(z, K - 1);
TRACE("opt", display(tout));
// solve for x_value = K*y_value + z_value, 0 <= z_value < K. // solve for x_value = K*y_value + z_value, 0 <= z_value < K.
unsigned_vector vs;
for (unsigned ri : div_rows) { for (unsigned ri : div_rows) {
rational a = get_coefficient(ri, x); rational a = get_coefficient(ri, x);
@ -1375,7 +1307,9 @@ namespace opt {
rational coeff = m_rows[ri].m_coeff; rational coeff = m_rows[ri].m_coeff;
unsigned w = UINT_MAX; unsigned w = UINT_MAX;
rational offset(0); rational offset(0);
if (coeffs.empty()) if (K == 1)
offset = coeff;
else if (coeffs.empty())
offset = div(coeff, K); offset = div(coeff, K);
else else
w = add_div(coeffs, coeff, K); w = add_div(coeffs, coeff, K);
@ -1412,20 +1346,27 @@ namespace opt {
vector<var> div_coeffs; vector<var> div_coeffs;
div_coeffs.push_back(var(v, rational::minus_one())); div_coeffs.push_back(var(v, rational::minus_one()));
div_coeffs.push_back(var(y, a)); div_coeffs.push_back(var(y, a));
if (w != UINT_MAX) div_coeffs.push_back(var(w, rational::one())); if (w != UINT_MAX)
div_coeffs.push_back(var(w, rational::one()));
else if (K == 1)
div_coeffs.append(coeffs);
add_constraint(div_coeffs, k + offset, t_eq); add_constraint(div_coeffs, k + offset, t_eq);
unsigned u = UINT_MAX; unsigned u = UINT_MAX;
offset = 0; offset = 0;
if (coeffs.empty()) if (K == 1)
offset = 0;
else if (coeffs.empty())
offset = mod(coeff, K); offset = mod(coeff, K);
else else
u = add_mod(coeffs, coeff, K); u = add_mod(coeffs, coeff, K);
// add a*z + (b mod K) < (k + 1)*K // add a*z + (b mod K) < (k + 1)*K
vector<var> bound_coeffs; vector<var> bound_coeffs;
bound_coeffs.push_back(var(z, a)); bound_coeffs.push_back(var(z, a));
if (u != UINT_MAX) bound_coeffs.push_back(var(u, rational::one())); if (u != UINT_MAX)
bound_coeffs.push_back(var(u, rational::one()));
add_constraint(bound_coeffs, 1 - K * (k + 1) + offset, t_le); add_constraint(bound_coeffs, 1 - K * (k + 1) + offset, t_le);
// add k*K <= az + (b mod K) // add k*K <= az + (b mod K)
@ -1433,11 +1374,49 @@ namespace opt {
c.m_coeff.neg(); c.m_coeff.neg();
add_constraint(bound_coeffs, k * K - offset, t_le); add_constraint(bound_coeffs, k * K - offset, t_le);
// allow to recycle row. // allow to recycle row.
m_retired_rows.push_back(ri); retire_row(ri);
project(v, false); vs.push_back(v);
} }
TRACE("opt", display(tout << "solve_div reduced " << y << " " << z << "\n")); for (unsigned ri : mod_rows) {
rational a = get_coefficient(ri, x);
replace_var(ri, x, rational::zero());
// add w = b mod K
vector<var> coeffs = m_rows[ri].m_vars;
rational coeff = m_rows[ri].m_coeff;
unsigned v = m_rows[ri].m_id;
rational v_value = m_var2value[v];
unsigned w = UINT_MAX;
rational offset(0);
if (coeffs.empty() || K == 1)
offset = mod(coeff, K);
else
w = add_mod(coeffs, coeff, K);
rational w_value = w == UINT_MAX ? offset : m_var2value[w];
// add v = a*z + w - V, for k = (a*z_value + w_value) div K
// claim: (= (mod x K) (- x (* K (div x K)))))) is a theorem for every x, K != 0
rational V = v_value - a * z_value - w_value;
vector<var> mod_coeffs;
mod_coeffs.push_back(var(v, rational::minus_one()));
mod_coeffs.push_back(var(z, a));
if (w != UINT_MAX) mod_coeffs.push_back(var(w, rational::one()));
add_constraint(mod_coeffs, V + offset, t_eq);
add_lower_bound(v, rational::zero());
add_upper_bound(v, K - 1);
retire_row(ri);
vs.push_back(v);
}
for (unsigned v : vs)
project(v, false);
// project internal variables. // project internal variables.
def y_def = project(y, compute_def); def y_def = project(y, compute_def);
@ -1501,13 +1480,13 @@ namespace opt {
unsigned_vector const& row_ids = m_var2row_ids[x]; unsigned_vector const& row_ids = m_var2row_ids[x];
uint_set visited; uint_set visited;
for (unsigned row_id : row_ids) { for (unsigned row_id : row_ids) {
if (!visited.contains(row_id)) { if (visited.contains(row_id))
continue;
// x |-> D*y + u // x |-> D*y + u
replace_var(row_id, x, D, y, u); replace_var(row_id, x, D, y, u);
visited.insert(row_id); visited.insert(row_id);
normalize(row_id); normalize(row_id);
} }
}
TRACE("opt1", display(tout << "tableau after replace x by y := v" << y << "\n");); TRACE("opt1", display(tout << "tableau after replace x by y := v" << y << "\n"););
def result = project(y, compute_def); def result = project(y, compute_def);
if (compute_def) { if (compute_def) {
@ -1611,8 +1590,12 @@ namespace opt {
uint_set visited; uint_set visited;
visited.insert(row_id1); visited.insert(row_id1);
for (unsigned row_id2 : row_ids) { for (unsigned row_id2 : row_ids) {
if (!visited.contains(row_id2)) { if (visited.contains(row_id2))
continue;
visited.insert(row_id2); visited.insert(row_id2);
row& r = m_rows[row_id2];
if (!r.m_alive)
continue;
b = get_coefficient(row_id2, x); b = get_coefficient(row_id2, x);
if (b.is_zero()) if (b.is_zero())
continue; continue;
@ -1631,7 +1614,6 @@ namespace opt {
break; break;
} }
} }
}
def result; def result;
if (compute_def) { if (compute_def) {
result = def(m_rows[row_id1], x); result = def(m_rows[row_id1], x);

View file

@ -60,14 +60,13 @@ namespace opt {
} }
}; };
struct row { struct row {
row(): m_type(t_le), m_value(0), m_alive(false) {}
vector<var> m_vars; // variables with coefficients vector<var> m_vars; // variables with coefficients
rational m_coeff; // constant in inequality rational m_coeff = rational::zero(); // constant in inequality
rational m_mod; // value the term divide rational m_mod = rational::zero(); // value the term divide
ineq_type m_type; // inequality type ineq_type m_type = t_le; // inequality type
rational m_value; // value of m_vars + m_coeff under interpretation of m_var2value. rational m_value = rational::zero(); // value of m_vars + m_coeff under interpretation of m_var2value.
bool m_alive; // rows can be marked dead if they have been processed. bool m_alive = false; // rows can be marked dead if they have been processed.
unsigned m_id; // variable defined by row (used for mod_t and div_t) unsigned m_id = UINT_MAX; // variable defined by row (used for mod_t and div_t)
void reset() { m_vars.reset(); m_coeff.reset(); m_value.reset(); } void reset() { m_vars.reset(); m_coeff.reset(); m_value.reset(); }
row& normalize(); row& normalize();
@ -139,7 +138,7 @@ namespace opt {
void add_upper_bound(unsigned x, rational const& hi); void add_upper_bound(unsigned x, rational const& hi);
void add_constraint(vector<var> const& coeffs, rational const& c, rational const& m, ineq_type r, unsigned id); unsigned add_constraint(vector<var> const& coeffs, rational const& c, rational const& m, ineq_type r, unsigned id);
void replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B); void replace_var(unsigned row_id, unsigned x, rational const& A, unsigned y, rational const& B);
@ -167,9 +166,7 @@ namespace opt {
def solve_divides(unsigned x, unsigned_vector const& divide_rows, bool compute_def); def solve_divides(unsigned x, unsigned_vector const& divide_rows, bool compute_def);
def solve_mod(unsigned x, unsigned_vector const& divide_rows, bool compute_def); def solve_mod_div(unsigned x, unsigned_vector const& mod_rows, unsigned_vector const& divide_rows, bool compute_def);
def solve_div(unsigned x, unsigned_vector const& divide_rows, bool compute_def);
bool is_int(unsigned x) const { return m_var2is_int[x]; } bool is_int(unsigned x) const { return m_var2is_int[x]; }

View file

@ -41,6 +41,10 @@ namespace simplex {
sparse_matrix_ops::kernel(M, K); sparse_matrix_ops::kernel(M, K);
} }
void kernel_ffe(sparse_matrix<mpq_ext> &M, vector<vector<rational>> &K) {
sparse_matrix_ops::kernel_ffe(M, K);
}
void ensure_rational_solution(simplex<mpq_ext>& S) { void ensure_rational_solution(simplex<mpq_ext>& S) {
rational delta(1); rational delta(1);
for (unsigned i = 0; i < S.get_num_vars(); ++i) { for (unsigned i = 0; i < S.get_num_vars(); ++i) {

View file

@ -203,5 +203,6 @@ namespace simplex {
void ensure_rational_solution(simplex<mpq_ext>& s); void ensure_rational_solution(simplex<mpq_ext>& s);
void kernel(sparse_matrix<mpq_ext>& s, vector<vector<rational>>& K); void kernel(sparse_matrix<mpq_ext>& s, vector<vector<rational>>& K);
void kernel_ffe(sparse_matrix<mpq_ext> &s, vector<vector<rational>> &K);
}; };

View file

@ -168,6 +168,7 @@ namespace simplex {
void add_var(row r, numeral const& n, var_t var); void add_var(row r, numeral const& n, var_t var);
void add(row r, numeral const& n, row src); void add(row r, numeral const& n, row src);
void mul(row r, numeral const& n); void mul(row r, numeral const& n);
void div(row r, numeral const& n);
void neg(row r); void neg(row r);
void del(row r); void del(row r);

View file

@ -432,6 +432,25 @@ namespace simplex {
} }
} }
/**
\brief Set row <- n/row
*/
template <typename Ext>
void sparse_matrix<Ext>::div(row r, numeral const &n) {
SASSERT(!m.is_zero(n));
if (m.is_one(n)) {
// no op
} else if (m.is_minus_one(n)) {
neg(r);
} else {
row_iterator it = row_begin(r);
row_iterator end = row_end(r);
for (; it != end; ++it) {
m.div(it->m_coeff, n, it->m_coeff);
}
}
}
/** /**
\brief Delete row. \brief Delete row.
*/ */

View file

@ -43,7 +43,7 @@ class sparse_matrix_ops {
for (auto [row, row_entry] : M.get_rows(k)) { for (auto [row, row_entry] : M.get_rows(k)) {
if (c[row.id()] != 0) continue; if (c[row.id()] != 0) continue;
auto &m_jk = row_entry->m_coeff; auto &m_jk = row_entry->m_coeff;
if (mpq_manager<false>::is_zero(m_jk)) continue; if (m.is_zero(m_jk)) continue;
// D = rational(-1) / m_jk; // D = rational(-1) / m_jk;
m.set(D, m_jk); m.set(D, m_jk);
@ -68,9 +68,10 @@ class sparse_matrix_ops {
K.push_back(vector<rational>()); K.push_back(vector<rational>());
for (unsigned i = 0; i < n_vars; ++i) { for (unsigned i = 0; i < n_vars; ++i) {
if (d[i] > 0) { if (d[i] > 0) {
auto r = sparse_matrix<mpq_ext>::row(d[i] - 1); auto r = typename sparse_matrix<Ext>::row(d[i] - 1);
K.back().push_back(rational(M.get_coeff(r, k))); K.back().push_back(rational(M.get_coeff(r, k)));
} else if (i == k) }
else if (i == k)
K.back().push_back(rational(1)); K.back().push_back(rational(1));
else else
K.back().push_back(rational(0)); K.back().push_back(rational(0));
@ -81,5 +82,157 @@ class sparse_matrix_ops {
static void kernel(sparse_matrix<mpq_ext> &M, vector<vector<rational>> &K) { static void kernel(sparse_matrix<mpq_ext> &M, vector<vector<rational>> &K) {
kernel<mpq_ext>(M, K); kernel<mpq_ext>(M, K);
} }
/// \brief Kernel computation using fraction-free-elimination
///
template <typename Ext>
static void kernel_ffe(sparse_matrix<Ext> &M, vector<vector<rational>> &K) {
using scoped_numeral = typename Ext::scoped_numeral;
/// Based on George Nakos, Peter R. Turner, Robert M. Williams:
/// Fraction-free algorithms for linear and polynomial equations. SIGSAM
/// Bull. 31(3): 11-19 (1997)
vector<unsigned> d, c;
unsigned n_vars = M.num_vars(), n_rows = M.num_rows();
c.resize(n_rows, 0u);
d.resize(n_vars, 0u);
auto &m = M.get_manager();
scoped_numeral m_ik(m);
scoped_numeral m_jk(m);
scoped_numeral last_pv(m);
m.set(last_pv, 1);
for (unsigned k = 0; k < n_vars; ++k) {
d[k] = 0;
for (auto [row, row_entry] : M.get_rows(k)) {
if (c[row.id()] != 0) continue;
auto &m_jk_ref = row_entry->m_coeff;
if (m.is_zero(m_jk_ref))
// XXX: should not happen, the matrix is sparse
continue;
// this a pivot column
m.set(m_jk, m_jk_ref);
// ensure that pivot is negative
if (m.is_pos(m_jk_ref)) { M.neg(row); }
else { m.neg(m_jk); }
// m_jk is abs(M[j]][k])
for (auto row_i : M.get_rows()) {
if (row_i.id() == row.id()) continue;
m.set(m_ik, M.get_coeff(row_i, k));
// row_i *= m_jk
M.mul(row_i, m_jk);
if (!m.is_zero(m_ik)) {
// row_i += m_ik * row
M.add(row_i, m_ik, row);
}
M.div(row_i, last_pv);
}
c[row.id()] = k + 1;
d[k] = row.id() + 1;
m.set(last_pv, m_jk);
break;
}
}
for (unsigned k = 0; k < n_vars; ++k) {
if (d[k] != 0) continue;
K.push_back(vector<rational>());
for (unsigned i = 0; i < n_vars; ++i) {
if (d[i] > 0) {
auto r = typename sparse_matrix<Ext>::row(d[i] - 1);
K.back().push_back(rational(M.get_coeff(r, k)));
}
else if (i == k)
K.back().push_back(rational(last_pv));
else
K.back().push_back(rational(0));
}
}
}
static void kernel_ffe(sparse_matrix<mpq_ext> &M,
vector<vector<rational>> &K) {
kernel_ffe<mpq_ext>(M, K);
}
template <typename Ext>
static void kernel_ffe(sparse_matrix<Ext> &M, sparse_matrix<Ext> &K,
vector<unsigned> &basics) {
using scoped_numeral = typename Ext::scoped_numeral;
/// Based on George Nakos, Peter R. Turner, Robert M. Williams:
/// Fraction-free algorithms for linear and polynomial equations. SIGSAM
/// Bull. 31(3): 11-19 (1997)
vector<unsigned> d, c;
unsigned n_vars = M.num_vars(), n_rows = M.num_rows();
c.resize(n_rows, 0u);
d.resize(n_vars, 0u);
auto &m = M.get_manager();
scoped_numeral m_ik(m);
scoped_numeral m_jk(m);
scoped_numeral last_pv(m);
m.set(last_pv, 1);
for (unsigned k = 0; k < n_vars; ++k) {
d[k] = 0;
for (auto [row, row_entry] : M.get_rows(k)) {
if (c[row.id()] != 0) continue;
auto &m_jk_ref = row_entry->m_coeff;
if (m.is_zero(m_jk_ref))
// XXX: should not happen, the matrix is sparse
continue;
// this a pivot column
m.set(m_jk, m_jk_ref);
// ensure that pivot is negative
if (m.is_pos(m_jk_ref)) { M.neg(row); }
else { m.neg(m_jk); }
// m_jk is abs(M[j]][k])
for (auto row_i : M.get_rows()) {
if (row_i.id() == row.id()) continue;
m.set(m_ik, M.get_coeff(row_i, k));
// row_i *= m_jk
M.mul(row_i, m_jk);
if (!m.is_zero(m_ik)) {
// row_i += m_ik * row
M.add(row_i, m_ik, row);
}
M.div(row_i, last_pv);
}
c[row.id()] = k + 1;
d[k] = row.id() + 1;
m.set(last_pv, m_jk);
break;
}
}
K.ensure_var(n_vars - 1);
for (unsigned k = 0; k < n_vars; ++k) {
if (d[k] != 0) continue;
auto row = K.mk_row();
basics.push_back(k);
for (unsigned i = 0; i < n_vars; ++i) {
if (d[i] > 0) {
auto r = typename sparse_matrix<Ext>::row(d[i] - 1);
K.add_var(row, M.get_coeff(r, k), i);
}
else if (i == k)
K.add_var(row, last_pv, i);
}
}
}
}; };
} // namespace simplex } // namespace simplex

View file

@ -566,14 +566,13 @@ struct evaluator_cfg : public default_rewriter_cfg {
bool extract_array_func_interp(expr* a, vector<expr_ref_vector>& stores, expr_ref& else_case, bool& are_unique) { bool extract_array_func_interp(expr* a, vector<expr_ref_vector>& stores, expr_ref& else_case, bool& are_unique) {
SASSERT(m_ar.is_array(a)); SASSERT(m_ar.is_array(a));
bool are_values = true;
are_unique = true; are_unique = true;
TRACE("model_evaluator", tout << mk_pp(a, m) << "\n";); TRACE("model_evaluator", tout << mk_pp(a, m) << "\n";);
while (m_ar.is_store(a)) { while (m_ar.is_store(a)) {
expr_ref_vector store(m); expr_ref_vector store(m);
store.append(to_app(a)->get_num_args()-1, to_app(a)->get_args()+1); store.append(to_app(a)->get_num_args()-1, to_app(a)->get_args()+1);
are_values &= args_are_values(store, are_unique); args_are_values(store, are_unique);
stores.push_back(store); stores.push_back(store);
a = to_app(a)->get_arg(0); a = to_app(a)->get_arg(0);
} }
@ -584,9 +583,8 @@ struct evaluator_cfg : public default_rewriter_cfg {
} }
if (m_ar_rw.has_index_set(a, else_case, stores)) { if (m_ar_rw.has_index_set(a, else_case, stores)) {
for (auto const& store : stores) { for (auto const& store : stores)
are_values &= args_are_values(store, are_unique); args_are_values(store, are_unique);
}
return true; return true;
} }
if (!m_ar.is_as_array(a)) { if (!m_ar.is_as_array(a)) {

View file

@ -169,7 +169,6 @@ def_module_params('fp',
('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'), ('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'),
('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"), ('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"),
('spacer.min_level', UINT, 0, 'Minimal level to explore'), ('spacer.min_level', UINT, 0, 'Minimal level to explore'),
('spacer.print_json', SYMBOL, '', 'Print pobs tree in JSON format to a given file'),
('spacer.trace_file', SYMBOL, '', 'Log file for progress events'), ('spacer.trace_file', SYMBOL, '', 'Log file for progress events'),
('spacer.ctp', BOOL, True, 'Enable counterexample-to-pushing'), ('spacer.ctp', BOOL, True, 'Enable counterexample-to-pushing'),
('spacer.use_inc_clause', BOOL, True, 'Use incremental clause to represent trans'), ('spacer.use_inc_clause', BOOL, True, 'Use incremental clause to represent trans'),
@ -181,4 +180,10 @@ def_module_params('fp',
('spacer.use_lim_num_gen', BOOL, False, 'Enable limit numbers generalizer to get smaller numbers'), ('spacer.use_lim_num_gen', BOOL, False, 'Enable limit numbers generalizer to get smaller numbers'),
('spacer.logic', SYMBOL, '', 'SMT-LIB logic to configure internal SMT solvers'), ('spacer.logic', SYMBOL, '', 'SMT-LIB logic to configure internal SMT solvers'),
('spacer.arith.solver', UINT, 2, 'arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver'), ('spacer.arith.solver', UINT, 2, 'arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver'),
('spacer.global', BOOL, False, 'Enable global guidance'),
('spacer.gg.concretize', BOOL, True, 'Enable global guidance concretize'),
('spacer.gg.conjecture', BOOL, True, 'Enable global guidance conjecture'),
('spacer.gg.subsume', BOOL, True, 'Enable global guidance subsume'),
('spacer.use_iuc', BOOL, True, 'Enable Interpolating Unsat Core(IUC) for lemma generalization'),
('spacer.expand_bnd', BOOL, False, 'Enable expand-bound lemma generalization'),
)) ))

View file

@ -0,0 +1,8 @@
---
BasedOnStyle: LLVM
AllowShortFunctionsOnASingleLine: All
IndentWidth: '4'
AllowShortBlocksOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
...

View file

@ -10,6 +10,7 @@ z3_add_component(spacer
spacer_prop_solver.cpp spacer_prop_solver.cpp
spacer_sym_mux.cpp spacer_sym_mux.cpp
spacer_util.cpp spacer_util.cpp
spacer_cluster_util.cpp
spacer_iuc_solver.cpp spacer_iuc_solver.cpp
spacer_legacy_mbp.cpp spacer_legacy_mbp.cpp
spacer_proof_utils.cpp spacer_proof_utils.cpp
@ -22,12 +23,19 @@ z3_add_component(spacer
spacer_sem_matcher.cpp spacer_sem_matcher.cpp
spacer_quant_generalizer.cpp spacer_quant_generalizer.cpp
spacer_arith_generalizers.cpp spacer_arith_generalizers.cpp
spacer_global_generalizer.cpp
spacer_ind_lemma_generalizer.cpp
spacer_expand_bnd_generalizer.cpp
spacer_cluster.cpp
spacer_callback.cpp spacer_callback.cpp
spacer_json.cpp
spacer_iuc_proof.cpp spacer_iuc_proof.cpp
spacer_mbc.cpp spacer_mbc.cpp
spacer_pdr.cpp spacer_pdr.cpp
spacer_sat_answer.cpp spacer_sat_answer.cpp
spacer_concretize.cpp
spacer_convex_closure.cpp
spacer_conjecture.cpp
spacer_arith_kernel.cpp
COMPONENT_DEPENDENCIES COMPONENT_DEPENDENCIES
arith_tactics arith_tactics
core_tactics core_tactics

View file

@ -155,6 +155,7 @@ void anti_unifier::operator()(expr *e1, expr *e2, expr_ref &res,
m_pinned.push_back(u); m_pinned.push_back(u);
m_cache.insert(n1, n2, u); m_cache.insert(n1, n2, u);
} }
m_todo.pop_back();
} }
expr *r; expr *r;

View file

@ -0,0 +1,108 @@
/**++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_arith_kernel.cpp
Abstract:
Compute kernel of a matrix
Author:
Hari Govind
Arie Gurfinkel
Notes:
--*/
#include "muz/spacer/spacer_arith_kernel.h"
#include "math/simplex/sparse_matrix_def.h"
#include "math/simplex/sparse_matrix_ops.h"
using namespace spacer;
bool spacer_arith_kernel::compute_kernel() {
SASSERT(m_matrix.num_rows() > 1);
if (false && m_matrix.compute_linear_deps(m_kernel)) {
// the matrix cannot be reduced further
if (m_matrix.num_cols() - m_kernel.num_rows() <= 1) return true;
m_kernel.reset(m_kernel.num_cols());
SASSERT(m_matrix.num_cols() > 2);
}
if (m_matrix.num_cols() > 2) m_st.m_failed++;
if (m_plugin /* && m_matrix.num_cols() > 2 */) {
return m_plugin->compute_kernel(m_matrix, m_kernel, m_basic_vars);
}
return false;
}
namespace {
class simplex_arith_kernel_plugin : public spacer_arith_kernel::plugin {
public:
simplex_arith_kernel_plugin() {}
bool compute_kernel(const spacer_matrix &in, spacer_matrix &out,
vector<unsigned> &basics) override {
using qmatrix = simplex::sparse_matrix<simplex::mpq_ext>;
unsynch_mpq_manager m;
qmatrix qmat(m);
// extra column for column of 1
qmat.ensure_var(in.num_cols());
for (unsigned i = 0, n_rows = in.num_rows(); i < n_rows; ++i) {
auto row_id = qmat.mk_row();
unsigned j, n_cols;
for (j = 0, n_cols = in.num_cols(); j < n_cols; ++j) {
qmat.add_var(row_id, in.get(i, j).to_mpq(), j);
}
qmat.add_var(row_id, rational::one().to_mpq(), n_cols);
}
TRACE("gg", qmat.display(tout););
qmatrix kern(m);
simplex::sparse_matrix_ops::kernel_ffe<simplex::mpq_ext>(qmat, kern,
basics);
out.reset(kern.num_vars());
vector<rational> vec;
for (auto row : kern.get_rows()) {
vec.reset();
vec.reserve(kern.num_vars(), rational(0));
for (auto &[coeff, v] : kern.get_row(row)) {
vec[v] = rational(coeff);
}
out.add_row(vec);
}
TRACE("gg", {
tout << "Computed kernel\n";
qmat.display(tout);
tout << "\n";
kern.display(tout);
tout << "\n";
tout << "basics: " << basics << "\n";
out.display(tout);
});
return out.num_rows() > 0;
}
void collect_statistics(statistics &st) const override {}
void reset_statistics() override {}
void reset() override {}
};
} // namespace
namespace spacer {
spacer_arith_kernel::plugin *mk_simplex_kernel_plugin() {
return alloc(simplex_arith_kernel_plugin);
}
} // namespace spacer

View file

@ -0,0 +1,93 @@
#pragma once
/**++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_arith_kernel.cpp
Abstract:
Compute kernel of a matrix
Author:
Hari Govind
Arie Gurfinkel
Notes:
--*/
#include "spacer_matrix.h"
#include "util/statistics.h"
namespace spacer {
/**
Computes a kernel of a matrix.
*/
class spacer_arith_kernel {
public:
class plugin {
public:
virtual ~plugin() {}
virtual bool compute_kernel(const spacer_matrix &in_matrix,
spacer_matrix &out_kernel,
vector<unsigned> &basics) = 0;
virtual void collect_statistics(statistics &st) const = 0;
virtual void reset_statistics() = 0;
virtual void reset() = 0;
};
protected:
struct stats {
unsigned m_failed;
stats() { reset(); }
void reset() { m_failed = 0; }
};
stats m_st;
/// Input matrix for which kernel is to be computed
const spacer_matrix &m_matrix;
/// Output matrix representing the kernel
spacer_matrix m_kernel;
/// columns in the kernel that correspond to basic vars
vector<unsigned> m_basic_vars;
scoped_ptr<plugin> m_plugin;
public:
spacer_arith_kernel(spacer_matrix &matrix)
: m_matrix(matrix), m_kernel(0, 0) {}
virtual ~spacer_arith_kernel() = default;
void set_plugin(spacer_arith_kernel::plugin *plugin) { m_plugin = plugin; }
/// Computes kernel of a matrix
/// returns true if the computation was successful
/// use \p spacer_arith_kernel::get_kernel() to get the kernel
bool compute_kernel();
bool operator()() { return compute_kernel(); }
const spacer_matrix &get_kernel() const { return m_kernel; }
const vector<unsigned> &get_basic_vars() const { return m_basic_vars; }
void reset() {
m_kernel = spacer_matrix(0, 0);
if (m_plugin) m_plugin->reset();
}
virtual void collect_statistics(statistics &st) const {
st.update("SPACER arith kernel failed", m_st.m_failed);
if (m_plugin) { m_plugin->collect_statistics(st); }
}
virtual void reset_statistics() {
m_st.reset();
if (m_plugin) m_plugin->reset_statistics();
}
};
spacer_arith_kernel::plugin *mk_simplex_kernel_plugin();
} // namespace spacer

View file

@ -0,0 +1,397 @@
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_cluster.cpp
Abstract:
Discover and mark lemma clusters
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include <algorithm>
#include "ast/arith_decl_plugin.h"
#include "ast/ast_util.h"
#include "ast/rewriter/var_subst.h"
#include "ast/substitution/substitution.h"
#include "muz/spacer/spacer_antiunify.h"
#include "muz/spacer/spacer_cluster.h"
#include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_manager.h"
#include "muz/spacer/spacer_util.h"
#include "smt/tactic/unit_subsumption_tactic.h"
#include "util/mpq.h"
#include "util/vector.h"
#define MAX_CLUSTER_SIZE 5
#define MAX_CLUSTERS 5
#define GAS_INIT 10
namespace spacer {
using var_offset = std::pair<unsigned, unsigned>;
lemma_cluster::lemma_cluster(const expr_ref &pattern)
: m(pattern.get_manager()), m_arith(m), m_bv(m), m_ref_count(0),
m_pattern(pattern), m_matcher(m), m_gas(GAS_INIT) {
m_num_vars = get_num_vars(m_pattern);
}
lemma_cluster::lemma_cluster(const lemma_cluster &other)
: m(other.get_manager()), m_arith(m), m_bv(m), m_ref_count(0),
m_pattern(other.get_pattern()), m_num_vars(other.m_num_vars),
m_matcher(m), m_gas(other.get_gas()) {
for (const auto &li : other.get_lemmas()) { m_lemma_vec.push_back(li); }
}
/// Get a conjunction of all the lemmas in cluster
void lemma_cluster::get_conj_lemmas(expr_ref &e) const {
expr_ref_vector conj(m);
for (const auto &lem : get_lemmas()) {
conj.push_back(lem.get_lemma()->get_expr());
}
e = mk_and(conj);
}
bool lemma_cluster::contains(const lemma_ref &lemma) {
for (const auto &li : get_lemmas()) {
if (lemma->get_expr() == li.get_lemma()->get_expr()) return true;
}
return false;
}
unsigned lemma_cluster::get_min_lvl() {
if (m_lemma_vec.empty()) return 0;
unsigned lvl = m_lemma_vec[0].get_lemma()->level();
for (auto l : m_lemma_vec) { lvl = std::min(lvl, l.get_lemma()->level()); }
// if all lemmas are at infinity, use the level of the lowest pob
if (is_infty_level(lvl)) {
for (auto l : m_lemma_vec) {
if (l.get_lemma()->has_pob())
lvl = std::min(lvl, l.get_lemma()->get_pob()->level());
}
}
return lvl;
}
/// Checks whether \p e matches the pattern of the cluster
/// Returns true on success and set \p sub to the corresponding substitution
bool lemma_cluster::match(const expr_ref &e, substitution &sub) {
bool pos;
var_offset var;
expr_offset r;
m_matcher.reset();
bool is_match = m_matcher(m_pattern, e, sub, pos);
if (!(is_match && pos)) return false;
unsigned n_binds = sub.get_num_bindings();
auto is_numeral = [&](expr *e) {
return m_arith.is_numeral(e) || m_bv.is_numeral(e);
};
// All the matches should be numerals
for (unsigned i = 0; i < n_binds; i++) {
sub.get_binding(i, var, r);
if (!is_numeral(r.get_expr())) return false;
}
return true;
}
bool lemma_cluster::can_contain(const lemma_ref &lemma) {
substitution sub(m);
expr_ref cube(m);
sub.reserve(1, m_num_vars);
cube = mk_and(lemma->get_cube());
normalize_order(cube, cube);
return match(cube, sub);
}
lemma_cluster::lemma_info *
lemma_cluster::get_lemma_info(const lemma_ref &lemma) {
SASSERT(contains(lemma));
for (auto &li : m_lemma_vec) {
if (lemma == li.get_lemma()) { return &li; }
}
UNREACHABLE();
return nullptr;
}
/// Removes subsumed lemmas in the cluster
///
/// Removed lemmas are placed into \p removed_lemmas
void lemma_cluster::rm_subsumed(lemma_info_vector &removed_lemmas) {
removed_lemmas.reset();
if (m_lemma_vec.size() <= 1) return;
// set up and run the simplifier
tactic_ref simplifier = mk_unit_subsumption_tactic(m);
goal_ref g(alloc(goal, m, false, false, false));
goal_ref_buffer result;
for (auto l : m_lemma_vec) { g->assert_expr(l.get_lemma()->get_expr()); }
(*simplifier)(g, result);
SASSERT(result.size() == 1);
goal *r = result[0];
// nothing removed
if (r->size() == m_lemma_vec.size()) return;
// collect removed lemmas
lemma_info_vector keep;
for (auto lem : m_lemma_vec) {
bool found = false;
for (unsigned i = 0; i < r->size(); i++) {
if (lem.get_lemma()->get_expr() == r->form(i)) {
found = true;
keep.push_back(lem);
TRACE("cluster_stats_verb", tout << "Keeping lemma "
<< lem.get_lemma()->get_cube()
<< "\n";);
break;
}
}
if (!found) {
TRACE("cluster_stats_verb", tout << "Removing subsumed lemma "
<< lem.get_lemma()->get_cube()
<< "\n";);
removed_lemmas.push_back(lem);
}
}
m_lemma_vec.reset();
m_lemma_vec.append(keep);
}
/// A a lemma to a cluster
///
/// Removes subsumed lemmas if \p subs_check is true
///
/// Returns false if lemma does not match the pattern or if it is already in the
/// cluster. Repetition of lemmas is avoided by doing a linear scan over the
/// lemmas in the cluster. Adding a lemma can reduce the size of the cluster due
/// to subsumption reduction.
bool lemma_cluster::add_lemma(const lemma_ref &lemma, bool subsume) {
substitution sub(m);
expr_ref cube(m);
sub.reserve(1, m_num_vars);
cube = mk_and(lemma->get_cube());
normalize_order(cube, cube);
if (!match(cube, sub)) return false;
// cluster already contains the lemma
if (contains(lemma)) return false;
TRACE("cluster_stats_verb",
tout << "Trying to add lemma " << lemma->get_cube() << "\n";);
lemma_cluster::lemma_info li(lemma, sub);
m_lemma_vec.push_back(li);
if (subsume) {
lemma_info_vector removed_lemmas;
rm_subsumed(removed_lemmas);
for (auto rm : removed_lemmas) {
// There is going to at most one removed lemma that matches l_i
// if there is one, return false since the new lemma was not added
if (rm.get_lemma() == li.get_lemma()) return false;
}
}
TRACE("cluster_stats", tout << "Added lemma\n" << mk_and(lemma->get_cube()) << "\n"
<< "to existing cluster\n" << m_pattern << "\n";);
return true;
}
lemma_cluster_finder::lemma_cluster_finder(ast_manager &_m)
: m(_m), m_arith(m), m_bv(m) {}
/// Check whether \p cube and \p lcube differ only in interpreted constants
bool lemma_cluster_finder::are_neighbours(const expr_ref &cube1,
const expr_ref &cube2) {
SASSERT(is_ground(cube1));
SASSERT(is_ground(cube2));
anti_unifier antiunify(m);
expr_ref pat(m);
substitution sub1(m), sub2(m);
antiunify(cube1, cube2, pat, sub1, sub2);
SASSERT(sub1.get_num_bindings() == sub2.get_num_bindings());
return is_numeric_sub(sub1) && is_numeric_sub(sub2);
}
/// Compute antiunification of \p cube with all formulas in \p fmls.
///
/// Should return
/// \exist res (\forall f \in fmls (\exist i_sub res[i_sub] == f))
/// However, the algorithm is incomplete: it returns such a res iff
/// res \in {antiU(cube, e) | e \in fmls}
/// Returns true if res is found
/// TODO: do complete n-ary anti-unification. Not done now
/// because anti_unifier does not support free variables
bool lemma_cluster_finder::anti_unify_n_intrp(const expr_ref &cube,
expr_ref_vector &fmls,
expr_ref &res) {
expr_ref_vector patterns(m);
expr_ref pat(m);
anti_unifier antiunify(m);
substitution sub1(m), sub2(m);
TRACE("cluster_stats_verb",
tout << "Trying to generate a general pattern for " << cube
<< " neighbours are " << fmls << "\n";);
// collect candidates for res
for (expr *c : fmls) {
antiunify.reset();
sub1.reset();
sub2.reset();
SASSERT(are_neighbours(cube, {c, m}));
antiunify(cube, expr_ref(c, m), pat, sub1, sub2);
patterns.push_back(pat);
}
// go through all the patterns to see if there is a pattern which is general
// enough to include all lemmas.
bool is_general_pattern = false, pos = true, all_same = true;
sem_matcher matcher(m);
unsigned n_vars_pat = 0;
for (expr *e : patterns) {
TRACE("cluster_stats_verb",
tout << "Checking pattern " << mk_pp(e, m) << "\n";);
is_general_pattern = true;
n_vars_pat = get_num_vars(e);
all_same = all_same && n_vars_pat == 0;
for (auto *lcube : fmls) {
matcher.reset();
sub1.reset();
sub1.reserve(1, n_vars_pat);
if (!(matcher(e, lcube, sub1, pos) && pos)) {
// this pattern is no good
is_general_pattern = false;
break;
}
}
if (is_general_pattern) {
SASSERT(e != nullptr);
TRACE("cluster_stats",
tout << "Found a general pattern\n" << mk_pp(e, m) << "\n";);
// found a good pattern
res = expr_ref(e, m);
return true;
}
}
CTRACE("cluster_stats", !all_same,
tout << "Failed to find a general pattern for cluster. Cube is: "
<< cube << " Patterns are " << patterns << "\n";);
return false;
}
/// Add a new lemma \p lemma to a cluster
///
/// Creates a new cluster for the lemma if necessary
void lemma_cluster_finder::cluster(lemma_ref &lemma) {
scoped_watch _w_(m_st.watch);
pred_transformer &pt = (lemma->get_pob())->pt();
// check whether lemmas has already been added
if (pt.clstr_contains(lemma)) return;
/// Add the lemma to a cluster it is matched against
lemma_cluster *clstr = pt.clstr_match(lemma);
if (clstr && clstr->get_size() <= MAX_CLUSTER_SIZE) {
TRACE("cluster_stats_verb", {
tout << "Trying to add lemma\n" << lemma->get_cube()
<< " to an existing cluster\n";
for (auto lem : clstr->get_lemmas())
tout << lem.get_lemma()->get_cube() << "\n";
});
clstr->add_lemma(lemma);
return;
}
/// Dont create more than MAX_CLUSTERS number of clusters
if (clstr && pt.clstr_count(clstr->get_pattern()) > MAX_CLUSTERS) {
return;
}
// Try to create a new cluster with lemma even if it can belong to an
// oversized cluster. The new cluster will not contain any lemma that is
// already in another cluster.
lemma_ref_vector all_lemmas;
pt.get_all_lemmas(all_lemmas, false);
expr_ref lcube(m), cube(m);
lcube = mk_and(lemma->get_cube());
normalize_order(lcube, lcube);
expr_ref_vector lma_cubes(m);
lemma_ref_vector neighbours;
for (auto *l : all_lemmas) {
cube.reset();
cube = mk_and(l->get_cube());
normalize_order(cube, cube);
// make sure that l is not in any other clusters
if (are_neighbours(lcube, cube) && cube != lcube &&
!pt.clstr_contains(l)) {
neighbours.push_back(l);
lma_cubes.push_back(cube);
}
}
if (neighbours.empty()) return;
// compute the most general pattern to which lemmas fit
expr_ref pattern(m);
bool is_cluster = anti_unify_n_intrp(lcube, lma_cubes, pattern);
// no general pattern
if (!is_cluster || get_num_vars(pattern) == 0) return;
// When creating a cluster, its size can be more than MAX_CLUSTER_SIZE. The
// size limitation is only for adding new lemmas to the cluster. The size is
// just an arbitrary number.
// What matters is that we do not allow a cluster to grow indefinitely.
// for example, given a cluster in which one lemma subsumes all other
// lemmas. No matter how big the cluster is, GSpacer is going to produce the
// exact same pob on this cluster. This can lead to divergence. The
// subsumption check we do is based on unit propagation, it is not complete.
lemma_cluster *cluster = pt.mk_cluster(pattern);
TRACE("cluster_stats",
tout << "created new cluster with pattern:\n" << pattern << "\n"
<< " and lemma cube:\n" << lcube << "\n";);
IF_VERBOSE(2, verbose_stream() << "\ncreated new cluster with pattern: "
<< pattern << "\n"
<< " and lemma cube: " << lcube << "\n";);
for (const lemma_ref &l : neighbours) {
SASSERT(cluster->can_contain(l));
bool added = cluster->add_lemma(l, false);
CTRACE("cluster_stats", added,
tout << "Added neighbour lemma\n" << mk_and(l->get_cube()) << "\n";);
}
// finally add the lemma and do subsumption check
cluster->add_lemma(lemma, true);
SASSERT(cluster->get_size() >= 1);
}
void lemma_cluster_finder::collect_statistics(statistics &st) const {
st.update("time.spacer.solve.reach.cluster", m_st.watch.get_seconds());
}
} // namespace spacer

View file

@ -0,0 +1,176 @@
#pragma once
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_cluster.h
Abstract:
Discover and mark lemma clusters
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include <ast/ast_util.h>
#include <ast/substitution/substitution.h>
#include <muz/spacer/spacer_sem_matcher.h>
#include <util/ref_vector.h>
#include <util/statistics.h>
#include <util/stopwatch.h>
#define GAS_POB_COEFF 5
namespace spacer {
class lemma;
using lemma_ref = ref<lemma>;
/// Representation of a cluster of lemmas
///
/// A cluster of lemmas is a collection of lemma instances. A cluster is
/// defined by a \p pattern that is a qff formula with free variables, and
/// contains lemmas that are instances of the pattern (i.e., obtained from the
/// pattern by substitution of constants for variables). That is, each lemma
/// in the cluster matches the pattern.
class lemma_cluster {
/// Lemma in a cluster
///
/// A lemma and a substitution witnessing that lemma is an instance of a
/// pattern
class lemma_info {
// a lemma
lemma_ref m_lemma;
// a substitution such that for some pattern, \p m_lemma is an instance
// substitution is stored in std_order for quantifiers (i.e., reverse of
// expected)
substitution m_sub;
public:
lemma_info(const lemma_ref &body, const substitution &sub)
: m_lemma(body), m_sub(sub) {}
const lemma_ref &get_lemma() const { return m_lemma; }
const substitution &get_sub() const { return m_sub; }
};
public:
using lemma_info_vector = vector<lemma_cluster::lemma_info, true>;
private:
ast_manager &m;
arith_util m_arith;
bv_util m_bv;
// reference counter
unsigned m_ref_count;
// pattern defining the cluster
expr_ref m_pattern;
unsigned m_num_vars;
// vector of lemmas in the cluster
lemma_info_vector m_lemma_vec;
// shared matcher object to match lemmas against the pattern
sem_matcher m_matcher;
// The number of times CSM has to be tried using this cluster
unsigned m_gas;
/// Remove subsumed lemmas in the cluster.
///
/// Returns list of removed lemmas in \p removed_lemmas
void rm_subsumed(lemma_info_vector &removed_lemmas);
/// Checks whether \p e matches m_pattern.
///
/// Returns true on success and sets \p sub to the corresponding
/// substitution
bool match(const expr_ref &e, substitution &sub);
ast_manager &get_manager() const { return m; }
public:
lemma_cluster(const expr_ref &pattern);
lemma_cluster(const lemma_cluster &other);
const lemma_info_vector &get_lemmas() const { return m_lemma_vec; }
void dec_gas() {
if (m_gas > 0) m_gas--;
}
unsigned get_gas() const { return m_gas; }
unsigned get_pob_gas() const { return GAS_POB_COEFF * m_lemma_vec.size(); }
/// Get a conjunction of all the lemmas in cluster
void get_conj_lemmas(expr_ref &e) const;
/// Try to add \p lemma to cluster. Remove subsumed lemmas if \p subs_check
/// is true
///
/// Returns false if lemma does not match the pattern or if it is already in
/// the cluster Repetition of lemmas is avoided by doing a linear scan over
/// the lemmas in the cluster. Adding a lemma can reduce the size of the
/// cluster due to subs_check
bool add_lemma(const lemma_ref &lemma, bool subsume = false);
bool contains(const lemma_ref &lemma);
bool can_contain(const lemma_ref &lemma);
/// Return the minimum level of lemmas in he cluster
unsigned get_min_lvl();
lemma_cluster::lemma_info *get_lemma_info(const lemma_ref &lemma);
unsigned get_size() const { return m_lemma_vec.size(); }
const expr_ref &get_pattern() const { return m_pattern; }
void inc_ref() { ++m_ref_count; }
void dec_ref() {
--m_ref_count;
if (m_ref_count == 0) { dealloc(this); }
}
};
class lemma_cluster_finder {
struct stats {
unsigned max_group_size;
stopwatch watch;
stats() { reset(); }
void reset() {
max_group_size = 0;
watch.reset();
}
};
stats m_st;
ast_manager &m;
arith_util m_arith;
bv_util m_bv;
/// Check whether \p cube and \p lcube differ only in interpreted constants
bool are_neighbours(const expr_ref &cube, const expr_ref &lcube);
/// N-ary antiunify
///
/// Returns whether there is a substitution with only interpreted consts
bool anti_unify_n_intrp(const expr_ref &cube, expr_ref_vector &fmls,
expr_ref &res);
public:
lemma_cluster_finder(ast_manager &m);
/// Add a new lemma \p lemma to a cluster
///
/// Creates a new cluster for the lemma if necessary
void cluster(lemma_ref &lemma);
void collect_statistics(statistics &st) const;
void reset_statistics() { m_st.reset(); }
};
using lemma_info_vector = lemma_cluster::lemma_info_vector;
} // namespace spacer

View file

@ -0,0 +1,240 @@
/**++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_cluster_util.cpp
Abstract:
Utility methods for clustering
Author:
Hari Govind
Arie Gurfinkel
Notes:
--*/
#include "ast/arith_decl_plugin.h"
#include "ast/ast.h"
#include "ast/ast_pp.h"
#include "ast/for_each_expr.h"
#include "ast/rewriter/rewriter.h"
#include "ast/rewriter/rewriter_def.h"
#include "ast/rewriter/th_rewriter.h"
#include "muz/spacer/spacer_util.h"
namespace spacer {
/// Arithmetic term order
struct arith_add_less_proc {
const arith_util &m_arith;
arith_add_less_proc(const arith_util &arith) : m_arith(arith) {}
bool operator()(expr *e1, expr *e2) const {
if (e1 == e2) return false;
ast_lt_proc ast_lt;
expr *k1 = nullptr, *t1 = nullptr, *k2 = nullptr, *t2 = nullptr;
// k1*t1 < k2*t2 iff t1 < t2 or t1 == t2 && k1 < k2
// k1 and k2 can be null
if (!m_arith.is_mul(e1, k1, t1)) { t1 = e1; }
if (!m_arith.is_mul(e2, k2, t2)) { t2 = e2; }
SASSERT(t1 && t2);
if (t1 != t2) return ast_lt(t1, t2);
// here: t1 == t2 && k1 != k2
SASSERT(k1 != k2);
// check for null
if (!k1 || !k2) return !k1;
return ast_lt(k1, k2);
}
};
struct bool_and_less_proc {
ast_manager &m;
const arith_util &m_arith;
bool_and_less_proc(ast_manager &mgr, const arith_util &arith)
: m(mgr), m_arith(arith) {}
bool operator()(expr *e1, expr *e2) const {
expr *a1 = nullptr, *a2 = nullptr;
bool is_not1, is_not2;
if (e1 == e2) return false;
is_not1 = m.is_not(e1, a1);
a1 = is_not1 ? a1 : e1;
is_not2 = m.is_not(e2, a2);
a2 = is_not2 ? a2 : e2;
return a1 == a2 ? is_not1 < is_not2 : arith_lt(a1, a2);
}
bool arith_lt(expr *e1, expr *e2) const {
ast_lt_proc ast_lt;
expr *t1, *k1, *t2, *k2;
if (e1 == e2) return false;
if (e1->get_kind() != e2->get_kind()) return e1->get_kind() < e2->get_kind();
if (!is_app(e1)) return ast_lt(e1, e2);
app *a1 = to_app(e1), *a2 = to_app(e2);
if (a1->get_family_id() != a2->get_family_id())
return a1->get_family_id() < a2->get_family_id();
if (a1->get_decl_kind() != a2->get_decl_kind())
return a1->get_decl_kind() < a2->get_decl_kind();
if (!(m_arith.is_le(e1, t1, k1) || m_arith.is_lt(e1, t1, k1) ||
m_arith.is_ge(e1, t1, k1) || m_arith.is_gt(e1, t1, k1))) {
t1 = e1;
k1 = nullptr;
}
if (!(m_arith.is_le(e2, t2, k2) || m_arith.is_lt(e2, t2, k2) ||
m_arith.is_ge(e2, t2, k2) || m_arith.is_gt(e2, t2, k2))) {
t2 = e2;
k2 = nullptr;
}
if (!k1 || !k2) { return k1 == k2 ? ast_lt(t1, t2) : k1 < k2; }
if (t1 == t2) return ast_lt(k1, k2);
if (t1->get_kind() != t2->get_kind())
return t1->get_kind() < t2->get_kind();
if (!is_app(t1)) return ast_lt(t1, t2);
unsigned d1 = to_app(t1)->get_depth();
unsigned d2 = to_app(t2)->get_depth();
if (d1 != d2) return d1 < d2;
// AG: order by the leading uninterpreted constant
expr *u1 = nullptr, *u2 = nullptr;
u1 = get_first_uc(t1);
u2 = get_first_uc(t2);
if (!u1 || !u2) { return u1 == u2 ? ast_lt(t1, t2) : u1 < u2; }
return u1 == u2 ? ast_lt(t1, t2) : ast_lt(u1, u2);
}
/// Returns first in left-most traversal uninterpreted constant of \p e
///
/// Returns null when no uninterpreted constant is found.
/// Recursive, assumes that expression is shallow and recursion is bounded.
expr *get_first_uc(expr *e) const {
expr *t, *k;
if (is_uninterp_const(e))
return e;
else if (m_arith.is_add(e)) {
if (to_app(e)->get_num_args() == 0) return nullptr;
expr *a1 = to_app(e)->get_arg(0);
// HG: for 3 + a, returns nullptr
return get_first_uc(a1);
} else if (m_arith.is_mul(e, k, t)) {
return get_first_uc(t);
}
return nullptr;
}
};
// Rewriter for normalize_order()
struct term_ordered_rpp : public default_rewriter_cfg {
ast_manager &m;
arith_util m_arith;
arith_add_less_proc m_add_less;
bool_and_less_proc m_and_less;
term_ordered_rpp(ast_manager &man)
: m(man), m_arith(m), m_add_less(m_arith), m_and_less(m, m_arith) {}
bool is_add(func_decl const *n) const {
return is_decl_of(n, m_arith.get_family_id(), OP_ADD);
}
br_status reduce_app(func_decl *f, unsigned num, expr *const *args,
expr_ref &result, proof_ref &result_pr) {
br_status st = BR_FAILED;
if (is_add(f)) {
ptr_buffer<expr> kids;
kids.append(num, args);
std::stable_sort(kids.data(), kids.data() + kids.size(),
m_add_less);
result = m_arith.mk_add(num, kids.data());
return BR_DONE;
}
if (m.is_and(f)) {
ptr_buffer<expr> kids;
kids.append(num, args);
std::stable_sort(kids.data(), kids.data() + kids.size(),
m_and_less);
result = m.mk_and(num, kids.data());
return BR_DONE;
}
return st;
}
};
// Normalize an arithmetic expression using term order
void normalize_order(expr *e, expr_ref &out) {
params_ref params;
// -- arith_rewriter params
params.set_bool("sort_sums", true);
// params.set_bool("gcd_rounding", true);
// params.set_bool("arith_lhs", true);
// -- poly_rewriter params
// params.set_bool("som", true);
// params.set_bool("flat", true);
// apply theory rewriter
th_rewriter rw1(out.m(), params);
rw1(e, out);
STRACE("spacer_normalize_order'",
tout << "OUT Before:" << mk_pp(out, out.m()) << "\n";);
// apply term ordering
term_ordered_rpp t_ordered(out.m());
rewriter_tpl<term_ordered_rpp> rw2(out.m(), false, t_ordered);
rw2(out.get(), out);
STRACE("spacer_normalize_order'",
tout << "OUT After :" << mk_pp(out, out.m()) << "\n";);
}
/// Multiply an expression \p fml by a rational \p num
///
/// \p fml should be of sort Int, Real, or BitVec
/// multiplication is simplifying
void mul_by_rat(expr_ref &fml, rational num) {
if (num.is_one()) return;
ast_manager &m = fml.get_manager();
arith_util m_arith(m);
bv_util m_bv(m);
expr_ref e(m);
SASSERT(m_arith.is_int_real(fml) || m_bv.is_bv(fml));
if (m_arith.is_int_real(fml)) {
e = m_arith.mk_mul(m_arith.mk_numeral(num, m_arith.is_int(fml)), fml);
} else if (m_bv.is_bv(fml)) {
unsigned sz = m_bv.get_bv_size(fml);
e = m_bv.mk_bv_mul(m_bv.mk_numeral(num, sz), fml);
}
// use theory rewriter to simplify
params_ref params;
params.set_bool("som", true);
params.set_bool("flat", true);
th_rewriter rw(m, params);
rw(e, fml);
}
} // namespace spacer
template class rewriter_tpl<spacer::term_ordered_rpp>;

View file

@ -0,0 +1,208 @@
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_concretize.cpp
Abstract:
Concretize a pob
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include "spacer_concretize.h"
namespace pattern_var_marker_ns {
struct proc {
arith_util &m_arith;
expr_fast_mark2 &m_marks;
proc(arith_util &arith, expr_fast_mark2 &marks)
: m_arith(arith), m_marks(marks) {}
void operator()(var *n) const {}
void operator()(quantifier *q) const {}
void operator()(app const *n) const {
expr *e1, *e2;
if (m_arith.is_mul(n, e1, e2)) {
if (is_var(e1) && !is_var(e2))
m_marks.mark(e2);
else if (is_var(e2) && !is_var(e1))
m_marks.mark(e1);
}
}
};
}; // namespace pattern_var_marker_ns
namespace spacer {
void pob_concretizer::mark_pattern_vars() {
pattern_var_marker_ns::proc proc(m_arith, m_var_marks);
quick_for_each_expr(proc, const_cast<expr *>(m_pattern));
}
bool pob_concretizer::push_out(expr_ref_vector &out, const expr_ref &e) {
// using m_var_marks to mark both variables and expressions sent to out
// the two sets are distinct so we can reuse the same marks
if (!m_var_marks.is_marked(e)) {
m_var_marks.mark(e);
out.push_back(e);
return true;
}
return false;
}
bool pob_concretizer::apply(const expr_ref_vector &cube, expr_ref_vector &out) {
// mark variables that are being split out
mark_pattern_vars();
for (auto *lit : cube) {
if (!apply_lit(lit, out)) {
out.reset();
m_var_marks.reset();
return false;
}
}
m_var_marks.reset();
return true;
}
bool pob_concretizer::is_split_var(expr *e, expr *&var, bool &pos) {
expr *e1, *e2;
rational n;
if (m_var_marks.is_marked(e)) {
var = e;
pos = true;
return true;
} else if (m_arith.is_mul(e, e1, e2) && m_arith.is_numeral(e1, n) &&
m_var_marks.is_marked(e2)) {
var = e2;
pos = !n.is_neg();
return true;
}
return false;
}
void pob_concretizer::split_lit_le_lt(expr *_lit, expr_ref_vector &out) {
expr *e1, *e2;
expr *lit = _lit;
m.is_not(_lit, lit);
VERIFY(m_arith.is_le(lit, e1, e2) || m_arith.is_gt(lit, e1, e2) ||
m_arith.is_lt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2));
ptr_buffer<expr> kids;
expr *var;
bool pos;
expr_ref val(m);
for (auto *arg : *to_app(e1)) {
if (is_split_var(arg, var, pos)) {
val = m_model->operator()(var);
// reuse val to keep the new literal
val = pos ? m_arith.mk_le(var, val) : m_arith.mk_ge(var, val);
push_out(out, val);
} else {
kids.push_back(arg);
}
}
if (kids.empty()) return;
// -- nothing was changed in the literal, move it out as is
if (kids.size() == to_app(e1)->get_num_args()) {
push_out(out, {_lit, m});
return;
}
// create new leftover literal using remaining arguments
expr_ref lhs(m);
if (kids.size() == 1) {
lhs = kids.get(0);
} else
lhs = m_arith.mk_add(kids.size(), kids.data());
expr_ref rhs = m_model->operator()(lhs);
expr_ref new_lit(m_arith.mk_le(lhs, rhs), m);
push_out(out, new_lit);
}
void pob_concretizer::split_lit_ge_gt(expr *_lit, expr_ref_vector &out) {
expr *e1, *e2;
expr *lit = _lit;
m.is_not(_lit, lit);
VERIFY(m_arith.is_le(lit, e1, e2) || m_arith.is_gt(lit, e1, e2) ||
m_arith.is_lt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2));
ptr_buffer<expr> kids;
expr *var;
bool pos;
expr_ref val(m);
for (auto *arg : *to_app(e1)) {
if (is_split_var(arg, var, pos)) {
val = m_model->operator()(var);
// reuse val to keep the new literal
val = pos ? m_arith.mk_ge(var, val) : m_arith.mk_le(var, val);
push_out(out, val);
} else {
kids.push_back(arg);
}
}
if (kids.empty()) return;
// -- nothing was changed in the literal, move it out as is
if (kids.size() == to_app(e1)->get_num_args()) {
push_out(out, {_lit, m});
return;
}
// create new leftover literal using remaining arguments
expr_ref lhs(m);
if (kids.size() == 1) {
lhs = kids.get(0);
} else
lhs = m_arith.mk_add(kids.size(), kids.data());
expr_ref rhs = m_model->operator()(lhs);
expr_ref new_lit(m_arith.mk_ge(lhs, rhs), m);
push_out(out, new_lit);
}
bool pob_concretizer::apply_lit(expr *_lit, expr_ref_vector &out) {
expr *lit = _lit;
bool is_neg = m.is_not(_lit, lit);
// split literals of the form a1*x1 + ... + an*xn ~ c, where c is a
// constant, ~ is <, <=, >, or >=, and the top level operator of LHS is +
expr *e1, *e2;
if ((m_arith.is_lt(lit, e1, e2) || m_arith.is_le(lit, e1, e2)) &&
m_arith.is_add(e1)) {
SASSERT(m_arith.is_numeral(e2));
if (!is_neg) {
split_lit_le_lt(_lit, out);
} else {
split_lit_ge_gt(_lit, out);
}
} else if ((m_arith.is_gt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)) &&
m_arith.is_add(e1)) {
SASSERT(m_arith.is_numeral(e2));
if (!is_neg) {
split_lit_ge_gt(_lit, out);
} else {
split_lit_le_lt(_lit, out);
}
} else {
out.push_back(_lit);
}
return true;
}
} // namespace spacer

View file

@ -0,0 +1,77 @@
#pragma once
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_concretize.h
Abstract:
Concretize a pob
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include "ast/ast.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_util.h"
#include "tactic/core/ctx_simplify_tactic.h"
#include "util/obj_ref_hashtable.h"
#include "util/rational.h"
namespace spacer {
class pob_concretizer {
ast_manager &m;
arith_util m_arith;
model_ref &m_model;
const expr *m_pattern;
expr_fast_mark2 m_var_marks;
// Marks all constants to be split in the pattern
void mark_pattern_vars();
// Adds a literal to \p out (unless it is already there)
bool push_out(expr_ref_vector &out, const expr_ref &e);
// Determines whether \p e is a*var, for some variable in \p m_var_marks
// Sets \p pos to sign(a)
bool is_split_var(expr *e, expr *&var, bool &pos);
// Splits a < or <= literal using the model
void split_lit_le_lt(expr *lit, expr_ref_vector &out);
// See split_lit_le_lt
void split_lit_ge_gt(expr *lit, expr_ref_vector &out);
public:
pob_concretizer(ast_manager &_m, model_ref &model, const expr *pattern)
: m(_m), m_arith(m), m_model(model), m_pattern(pattern) {}
/// Concretize \p cube into conjunction of simpler literals
///
/// Returns true on success and adds new literals to out
/// ensures: mk_and(out) ==> cube
bool apply(expr *cube, expr_ref_vector &out) {
expr_ref_vector flat(m);
flatten_and(cube, flat);
return apply(flat, out);
}
/// Concretizes a vector of literals
bool apply(const expr_ref_vector &cube, expr_ref_vector &out);
/// Concretizes a single literal
///
/// Returns true on success, new literals are added to \p out
bool apply_lit(expr *lit, expr_ref_vector &out);
};
} // namespace spacer

View file

@ -0,0 +1,98 @@
/**
Copyright (c) 2019 Microsoft Corporation and Arie Gurfinkel
Module Name:
spacer_conjecture.cpp
Abstract:
Methods to implement conjecture rule in gspacer
Author:
Arie Gurfinkel
Hari Govind
Notes:
--*/
#include "ast/for_each_expr.h"
#include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_util.h"
namespace spacer {
/// Returns true if \p lit is LA/BV inequality with a single free variable
bool is_mono_var_lit(expr *lit, ast_manager &m) {
expr *e;
bv_util bv(m);
arith_util a_util(m);
if (m.is_not(lit, e)) return is_mono_var_lit(e, m);
if (a_util.is_arith_expr(lit) || bv.is_bv_ule(lit) ||
bv.is_bv_sle(lit)) {
return get_num_vars(lit) == 1 && !has_nonlinear_var_mul(lit, m);
}
return false;
}
/// Returns true if \p pattern contains a single mono-var literal
///
/// That is, \p pattern contains a single suitable literal.
/// The literal is returned in \p res
bool find_unique_mono_var_lit(const expr_ref &pattern, expr_ref &res) {
if (get_num_vars(pattern) != 1) return false;
ast_manager &m = res.m();
// if the pattern has multiple literals, check whether exactly one of them
// is leq
expr_ref_vector conj(m);
conj.push_back(pattern);
flatten_and(conj);
unsigned count = 0;
for (auto *lit : conj) {
if (is_mono_var_lit(lit, m)) {
if (count) return false;
res = lit;
count++;
}
}
SASSERT(count <= 1);
return count == 1;
}
/// Filter out a given literal \p lit from a list of literals
///
/// Returns true if at least one literal was filtered out
/// \p out contains all the remaining (not filtered) literals
/// \p out holds the result. Returns true if any literal has been dropped
bool filter_out_lit(const expr_ref_vector &vec, const expr_ref &lit, expr_ref_vector &out) {
ast_manager &m = vec.get_manager();
bool dirty = false, pos = false;
sem_matcher matcher(m);
substitution sub(m);
out.reset();
unsigned lit_num_vars = get_num_vars(lit.get());
SASSERT(!(m.is_not(lit) && m.is_eq(to_app(lit)->get_arg(0))));
for (auto &c : vec) {
sub.reset();
sub.reserve(1, lit_num_vars);
matcher.reset();
if (matcher(lit, c, sub, pos) && pos) {
if (is_numeric_sub(sub)) {
dirty = true;
continue;
}
}
out.push_back(c);
}
CTRACE("global", dirty,
tout << "Filtered " << lit << " from " << vec << "\n got " << out << "\n";);
return dirty;
}
} // namespace spacer

View file

@ -20,6 +20,7 @@ Notes:
--*/ --*/
// clang-format off
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
@ -51,30 +52,36 @@ Notes:
#include "muz/transforms/dl_mk_rule_inliner.h" #include "muz/transforms/dl_mk_rule_inliner.h"
#include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_qe_project.h"
#include "muz/spacer/spacer_sat_answer.h" #include "muz/spacer/spacer_sat_answer.h"
#include "muz/spacer/spacer_concretize.h"
#include "muz/spacer/spacer_global_generalizer.h"
#define WEAKNESS_MAX 65535 #define WEAKNESS_MAX 65535
namespace spacer { namespace spacer {
/// pob -- proof obligation /// pob -- proof obligation
pob::pob (pob* parent, pred_transformer& pt, pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth,
unsigned level, unsigned depth, bool add_to_parent): bool add_to_parent)
m_ref_count (0), : m_ref_count(0), m_parent(parent), m_pt(pt),
m_parent (parent), m_pt (pt), m_post(m_pt.get_ast_manager()), m_binding(m_pt.get_ast_manager()),
m_post (m_pt.get_ast_manager ()), m_new_post(m_pt.get_ast_manager()), m_level(level), m_depth(depth),
m_binding(m_pt.get_ast_manager()), m_desired_level(0), m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false),
m_new_post (m_pt.get_ast_manager ()), m_enable_local_gen(true), m_enable_concretize(false), m_is_subsume(false),
m_level (level), m_depth (depth), m_enable_expand_bnd_gen(false), m_weakness(0), m_blocked_lvl(0),
m_open (true), m_use_farkas (true), m_in_queue(false), m_concretize_pat(m_pt.get_ast_manager()),
m_weakness(0), m_blocked_lvl(0) { m_gas(0) {
if (add_to_parent && m_parent) { if (add_to_parent && m_parent) {
m_parent->add_child(*this); m_parent->add_child(*this);
} }
if (m_parent) {
m_is_conjecture = m_parent->is_conjecture();
// m_is_subsume = m_parent->is_subsume();
m_gas = m_parent->get_gas();
}
} }
void pob::set_post(expr* post) { void pob::set_post(expr* post) {
app_ref_vector empty_binding(get_ast_manager()); set_post(post, {get_ast_manager()});
set_post(post, empty_binding);
} }
void pob::set_post(expr* post, app_ref_vector const &binding) { void pob::set_post(expr* post, app_ref_vector const &binding) {
@ -91,6 +98,11 @@ void pob::inherit(pob const &p) {
SASSERT(!is_in_queue()); SASSERT(!is_in_queue());
SASSERT(m_parent == p.m_parent); SASSERT(m_parent == p.m_parent);
SASSERT(&m_pt == &p.m_pt); SASSERT(&m_pt == &p.m_pt);
// -- HACK: normalize second time because th_rewriter is not idempotent
if (m_post != p.m_post) {
normalize(m_post, m_post, false, false);
}
SASSERT(m_post == p.m_post); SASSERT(m_post == p.m_post);
SASSERT(!m_new_post); SASSERT(!m_new_post);
@ -99,11 +111,21 @@ void pob::inherit(pob const &p) {
m_level = p.m_level; m_level = p.m_level;
m_depth = p.m_depth; m_depth = p.m_depth;
m_desired_level = std::max(m_desired_level, p.m_desired_level);
m_open = p.m_open; m_open = p.m_open;
m_use_farkas = p.m_use_farkas; m_use_farkas = p.m_use_farkas;
m_is_conjecture = p.m_is_conjecture;
m_enable_local_gen = p.m_enable_local_gen;
m_enable_concretize = p.m_enable_concretize;
m_is_subsume = p.m_is_subsume;
m_enable_expand_bnd_gen = p.m_enable_expand_bnd_gen;
m_weakness = p.m_weakness; m_weakness = p.m_weakness;
m_derivation = nullptr; m_derivation = nullptr;
m_gas = p.m_gas;
} }
void pob::close () { void pob::close () {
@ -756,6 +778,8 @@ void pred_transformer::collect_statistics(statistics& st) const
m_must_reachable_watch.get_seconds ()); m_must_reachable_watch.get_seconds ());
st.update("time.spacer.ctp", m_ctp_watch.get_seconds()); st.update("time.spacer.ctp", m_ctp_watch.get_seconds());
st.update("time.spacer.mbp", m_mbp_watch.get_seconds()); st.update("time.spacer.mbp", m_mbp_watch.get_seconds());
// -- Max cluster size can decrease during run
st.update("SPACER max cluster size", m_cluster_db.get_max_cluster_size());
} }
void pred_transformer::reset_statistics() void pred_transformer::reset_statistics()
@ -1193,6 +1217,7 @@ expr_ref pred_transformer::get_origin_summary (model &mdl,
for (auto* s : summary) { for (auto* s : summary) {
if (!is_quantifier(s) && !mdl.is_true(s)) { if (!is_quantifier(s) && !mdl.is_true(s)) {
TRACE("spacer", tout << "Summary not true in the model: " << mk_pp(s, m) << "\n";); TRACE("spacer", tout << "Summary not true in the model: " << mk_pp(s, m) << "\n";);
return expr_ref(m);
} }
} }
@ -1258,12 +1283,11 @@ void pred_transformer::get_pred_bg_invs(expr_ref_vector& out) {
/// \brief Returns true if the obligation is already blocked by current lemmas /// \brief Returns true if the obligation is already blocked by current lemmas
bool pred_transformer::is_blocked (pob &n, unsigned &uses_level) bool pred_transformer::is_blocked(pob &n, unsigned &uses_level, model_ref *model) {
{
ensure_level (n.level ()); ensure_level (n.level ());
prop_solver::scoped_level _sl (*m_solver, n.level ()); prop_solver::scoped_level _sl (*m_solver, n.level ());
m_solver->set_core (nullptr); m_solver->set_core (nullptr);
m_solver->set_model (nullptr); m_solver->set_model(model);
expr_ref_vector post(m), _aux(m); expr_ref_vector post(m), _aux(m);
post.push_back (n.post ()); post.push_back (n.post ());
@ -1335,7 +1359,7 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core,
model_ref* model, unsigned& uses_level, model_ref* model, unsigned& uses_level,
bool& is_concrete, datalog::rule const*& r, bool& is_concrete, datalog::rule const*& r,
bool_vector& reach_pred_used, bool_vector& reach_pred_used,
unsigned& num_reuse_reach) unsigned& num_reuse_reach, bool use_iuc)
{ {
TRACE("spacer", TRACE("spacer",
tout << "is-reachable: " << head()->get_name() << " level: " tout << "is-reachable: " << head()->get_name() << " level: "
@ -1349,7 +1373,8 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core,
// prepare the solver // prepare the solver
prop_solver::scoped_level _sl(*m_solver, n.level()); prop_solver::scoped_level _sl(*m_solver, n.level());
prop_solver::scoped_subset_core _sc (*m_solver, !n.use_farkas_generalizer ()); prop_solver::scoped_subset_core _sc(
*m_solver, !(use_iuc && n.use_farkas_generalizer()));
prop_solver::scoped_weakness _sw(*m_solver, 0, prop_solver::scoped_weakness _sw(*m_solver, 0,
ctx.weak_abs() ? n.weakness() : UINT_MAX); ctx.weak_abs() ? n.weakness() : UINT_MAX);
m_solver->set_core(core); m_solver->set_core(core);
@ -1406,9 +1431,10 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core,
r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach); r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach);
TRACE("spacer", TRACE("spacer",
tout << "reachable is_sat: " << is_sat << " " tout << "reachable is_sat: " << is_sat << " "
<< r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n"; << r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n";);
ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout) << "\n"; CTRACE("spacer", r,
); ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout);
tout << "\n";);
TRACE("spacer_sat", tout << "model is:\n" << **model << "\n";); TRACE("spacer_sat", tout << "model is:\n" << **model << "\n";);
} }
@ -2272,7 +2298,8 @@ context::context(fp_params const& params, ast_manager& m) :
m_last_result(l_undef), m_last_result(l_undef),
m_inductive_lvl(0), m_inductive_lvl(0),
m_expanded_lvl(0), m_expanded_lvl(0),
m_json_marshaller(this), m_global_gen(nullptr),
m_expand_bnd_gen(nullptr),
m_trace_stream(nullptr) { m_trace_stream(nullptr) {
params_ref p; params_ref p;
@ -2290,6 +2317,7 @@ context::context(fp_params const& params, ast_manager& m) :
m_pool1 = alloc(solver_pool, pool1_base.get(), max_num_contexts); m_pool1 = alloc(solver_pool, pool1_base.get(), max_num_contexts);
m_pool2 = alloc(solver_pool, pool2_base.get(), max_num_contexts); m_pool2 = alloc(solver_pool, pool2_base.get(), max_num_contexts);
m_lmma_cluster = alloc(lemma_cluster_finder, m);
updt_params(); updt_params();
if (m_params.spacer_trace_file().is_non_empty_string()) { if (m_params.spacer_trace_file().is_non_empty_string()) {
@ -2302,6 +2330,7 @@ context::context(fp_params const& params, ast_manager& m) :
context::~context() context::~context()
{ {
reset_lemma_generalizers(); reset_lemma_generalizers();
dealloc(m_lmma_cluster);
reset(); reset();
if (m_trace_stream) { if (m_trace_stream) {
@ -2347,7 +2376,13 @@ void context::updt_params() {
m_restart_initial_threshold = m_params.spacer_restart_initial_threshold(); m_restart_initial_threshold = m_params.spacer_restart_initial_threshold();
m_pdr_bfs = m_params.spacer_gpdr_bfs(); m_pdr_bfs = m_params.spacer_gpdr_bfs();
m_use_bg_invs = m_params.spacer_use_bg_invs(); m_use_bg_invs = m_params.spacer_use_bg_invs();
m_global = m_params.spacer_global();
m_expand_bnd = m_params.spacer_expand_bnd();
m_gg_conjecture = m_params.spacer_gg_conjecture();
m_gg_subsume = m_params.spacer_gg_subsume();
m_gg_concretize = m_params.spacer_gg_concretize();
m_use_iuc = m_params.spacer_use_iuc();
if (m_use_gpdr) { if (m_use_gpdr) {
// set options to be compatible with GPDR // set options to be compatible with GPDR
m_weak_abs = false; m_weak_abs = false;
@ -2665,7 +2700,8 @@ void context::init_lemma_generalizers()
//m_lemma_generalizers.push_back (alloc (unsat_core_generalizer, *this)); //m_lemma_generalizers.push_back (alloc (unsat_core_generalizer, *this));
if (m_use_ind_gen) { if (m_use_ind_gen) {
m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); // m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0));
m_lemma_generalizers.push_back(alloc_lemma_inductive_generalizer(*this));
} }
// after the lemma is minimized (maybe should also do before) // after the lemma is minimized (maybe should also do before)
@ -2678,6 +2714,15 @@ void context::init_lemma_generalizers()
m_lemma_generalizers.push_back(alloc(lemma_array_eq_generalizer, *this)); m_lemma_generalizers.push_back(alloc(lemma_array_eq_generalizer, *this));
} }
if (m_global) {
m_global_gen = alloc(lemma_global_generalizer, *this);
m_lemma_generalizers.push_back(m_global_gen);
}
if (m_expand_bnd) {
m_expand_bnd_gen = alloc(lemma_expand_bnd_generalizer, *this);
m_lemma_generalizers.push_back(m_expand_bnd_gen);
}
if (m_validate_lemmas) { if (m_validate_lemmas) {
m_lemma_generalizers.push_back(alloc(lemma_sanity_checker, *this)); m_lemma_generalizers.push_back(alloc(lemma_sanity_checker, *this));
} }
@ -3020,9 +3065,7 @@ lbool context::solve_core (unsigned from_lvl)
if (check_reachability()) { return l_true; } if (check_reachability()) { return l_true; }
if (lvl > 0 && m_use_propagate) if (lvl > 0 && m_use_propagate)
if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { dump_json(); return l_false; } if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { return l_false; }
dump_json();
if (is_inductive()){ if (is_inductive()){
return l_false; return l_false;
@ -3070,6 +3113,8 @@ void context::log_expand_pob(pob &n) {
if (n.parent()) pob_id = std::to_string(n.parent()->post()->get_id()); if (n.parent()) pob_id = std::to_string(n.parent()->post()->get_id());
*m_trace_stream << "** expand-pob: " << n.pt().head()->get_name() *m_trace_stream << "** expand-pob: " << n.pt().head()->get_name()
<< (n.is_conjecture() ? " CONJ" : "")
<< (n.is_subsume() ? " SUBS" : "")
<< " level: " << n.level() << " level: " << n.level()
<< " depth: " << (n.depth() - m_pob_queue.min_depth()) << " depth: " << (n.depth() - m_pob_queue.min_depth())
<< " exprID: " << n.post()->get_id() << " pobID: " << pob_id << "\n" << " exprID: " << n.post()->get_id() << " pobID: " << pob_id << "\n"
@ -3077,13 +3122,18 @@ void context::log_expand_pob(pob &n) {
} }
TRACE("spacer", tout << "expand-pob: " << n.pt().head()->get_name() TRACE("spacer", tout << "expand-pob: " << n.pt().head()->get_name()
<< (n.is_conjecture() ? " CONJ" : "")
<< (n.is_subsume() ? " SUBS" : "")
<< " level: " << n.level() << " level: " << n.level()
<< " depth: " << (n.depth() - m_pob_queue.min_depth()) << " depth: " << (n.depth() - m_pob_queue.min_depth())
<< " fvsz: " << n.get_free_vars_size() << "\n" << " fvsz: " << n.get_free_vars_size()
<< " gas: " << n.get_gas() << "\n"
<< mk_pp(n.post(), m) << "\n";); << mk_pp(n.post(), m) << "\n";);
STRACE("spacer_progress", STRACE("spacer_progress",
tout << "** expand-pob: " << n.pt().head()->get_name() tout << "** expand-pob: " << n.pt().head()->get_name()
<< (n.is_conjecture() ? " CONJ" : "")
<< (n.is_subsume() ? " SUBS" : "")
<< " level: " << n.level() << " level: " << n.level()
<< " depth: " << (n.depth() - m_pob_queue.min_depth()) << "\n" << " depth: " << (n.depth() - m_pob_queue.min_depth()) << "\n"
<< mk_epp(n.post(), m) << "\n\n";); << mk_epp(n.post(), m) << "\n\n";);
@ -3151,6 +3201,14 @@ bool context::check_reachability ()
node = last_reachable; node = last_reachable;
last_reachable = nullptr; last_reachable = nullptr;
if (m_pob_queue.is_root(*node)) { return true; } if (m_pob_queue.is_root(*node)) { return true; }
// do not check the parent if its may pob status is different
if (node->parent()->is_may_pob() != node->is_may_pob())
{
last_reachable = nullptr;
break;
}
if (is_reachable(*node->parent())) { if (is_reachable(*node->parent())) {
last_reachable = node->parent(); last_reachable = node->parent();
SASSERT(last_reachable->is_closed()); SASSERT(last_reachable->is_closed());
@ -3202,20 +3260,37 @@ bool context::check_reachability ()
case l_true: case l_true:
SASSERT(m_pob_queue.size() == old_sz); SASSERT(m_pob_queue.size() == old_sz);
SASSERT(new_pobs.empty()); SASSERT(new_pobs.empty());
node->close();
last_reachable = node; last_reachable = node;
last_reachable->close ();
if (m_pob_queue.is_root(*node)) { return true; } if (m_pob_queue.is_root(*node)) { return true; }
break; break;
case l_false: case l_false:
SASSERT(m_pob_queue.size() == old_sz); SASSERT(m_pob_queue.size() == old_sz);
// re-queue all pobs introduced by global gen and any pobs that can be blocked at a higher level
for (auto pob : new_pobs) { for (auto pob : new_pobs) {
if (is_requeue(*pob)) {m_pob_queue.push(*pob);} TRACE("gg", tout << "pob: is_may_pob " << pob->is_may_pob()
<< " with post:\n"
<< mk_pp(pob->post(), m)
<< "\n";);
//if ((pob->is_may_pob() && pob->post() != node->post()) || is_requeue(*pob)) {
if (is_requeue(*pob)) {
TRACE("gg",
tout << "Adding back blocked pob at level "
<< pob->level()
<< " and depth " << pob->depth() << "\n");
m_pob_queue.push(*pob);
}
} }
if (m_pob_queue.is_root(*node)) {return false;} if (m_pob_queue.is_root(*node)) {return false;}
break; break;
case l_undef: case l_undef:
SASSERT(m_pob_queue.size() == old_sz); SASSERT(m_pob_queue.size() == old_sz);
// collapse may pobs if the reachability of one of them cannot
// be estimated
if ((node->is_may_pob()) && new_pobs.size() == 0) {
close_all_may_parents(node);
}
for (auto pob : new_pobs) {m_pob_queue.push(*pob);} for (auto pob : new_pobs) {m_pob_queue.push(*pob);}
break; break;
} }
@ -3228,7 +3303,10 @@ bool context::check_reachability ()
/// returns true if the given pob can be re-scheduled /// returns true if the given pob can be re-scheduled
bool context::is_requeue(pob &n) { bool context::is_requeue(pob &n) {
// if have not reached desired level, then requeue
if (n.level() <= n.desired_level()) { return true; }
if (!m_push_pob) { return false; } if (!m_push_pob) { return false; }
unsigned max_depth = m_push_pob_max_depth; unsigned max_depth = m_push_pob_max_depth;
return (n.level() >= m_pob_queue.max_level() || return (n.level() >= m_pob_queue.max_level() ||
m_pob_queue.max_level() - n.level() <= max_depth); m_pob_queue.max_level() - n.level() <= max_depth);
@ -3270,9 +3348,9 @@ bool context::is_reachable(pob &n)
unsigned saved = n.level (); unsigned saved = n.level ();
// TBD: don't expose private field // TBD: don't expose private field
n.m_level = infty_level (); n.m_level = infty_level ();
lbool res = n.pt().is_reachable(n, nullptr, &mdl, lbool res =
uses_level, is_concrete, r, n.pt().is_reachable(n, nullptr, &mdl, uses_level, is_concrete, r,
reach_pred_used, num_reuse_reach); reach_pred_used, num_reuse_reach, m_use_iuc);
n.m_level = saved; n.m_level = saved;
if (res != l_true || !is_concrete) { if (res != l_true || !is_concrete) {
@ -3326,16 +3404,6 @@ bool context::is_reachable(pob &n)
return next ? is_reachable(*next) : true; return next ? is_reachable(*next) : true;
} }
void context::dump_json()
{
if (m_params.spacer_print_json().is_non_empty_string()) {
std::ofstream of;
of.open(m_params.spacer_print_json().bare_str());
m_json_marshaller.marshal(of);
of.close();
}
}
void context::predecessor_eh() void context::predecessor_eh()
{ {
for (unsigned i = 0; i < m_callbacks.size(); i++) { for (unsigned i = 0; i < m_callbacks.size(); i++) {
@ -3446,14 +3514,15 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
log_expand_pob(n); log_expand_pob(n);
stopwatch watch; stopwatch watch;
IF_VERBOSE (1, verbose_stream () << "expand: " << n.pt ().head ()->get_name () IF_VERBOSE(1, verbose_stream()
<< " (" << n.level () << ", " << "expand: " << n.pt().head()->get_name() << " ("
<< n.level() << ", "
<< (n.depth () - m_pob_queue.min_depth ()) << ") " << (n.depth () - m_pob_queue.min_depth ()) << ") "
<< (n.use_farkas_generalizer () ? "FAR " : "SUB ") << (n.use_farkas_generalizer () ? "FAR " : "SUB ")
<< " w(" << n.weakness() << ") " << (n.is_conjecture() ? "CONJ " : "")
<< n.post ()->get_id (); << (n.is_subsume() ? " SUBS" : "") << " w("
verbose_stream().flush (); << n.weakness() << ") " << n.post()->get_id();
watch.start ();); verbose_stream().flush(); watch.start(););
// used in case n is unreachable // used in case n is unreachable
unsigned uses_level = infty_level (); unsigned uses_level = infty_level ();
@ -3468,7 +3537,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
unsigned num_reuse_reach = 0; unsigned num_reuse_reach = 0;
if (m_push_pob && n.pt().is_blocked(n, uses_level)) { if (!n.is_may_pob() && m_push_pob && n.pt().is_blocked(n, uses_level)) {
// if (!m_pob_queue.is_root (n)) n.close (); // if (!m_pob_queue.is_root (n)) n.close ();
IF_VERBOSE (1, verbose_stream () << " K " IF_VERBOSE (1, verbose_stream () << " K "
<< std::fixed << std::setprecision(2) << std::fixed << std::setprecision(2)
@ -3483,10 +3552,44 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
tout << "This pob can be blocked by instantiation\n";); tout << "This pob can be blocked by instantiation\n";);
} }
if ((n.is_may_pob()) && n.get_gas() == 0) {
TRACE("global", tout << "Cant prove may pob. Collapsing "
<< mk_pp(n.post(), m) << "\n";);
m_stats.m_num_pob_ofg++;
return l_undef;
}
// Decide whether to concretize pob
// get a model that satisfies the pob and the current set of lemmas
// TODO: if push_pob is enabled, avoid calling is_blocked twice
if (m_gg_concretize && n.is_concretize_enabled() &&
!n.pt().is_blocked(n, uses_level, &model)) {
TRACE("global",
tout << "Concretizing: " << mk_pp(n.post(), m) << "\n"
<< "\t" << n.get_gas() << " attempts left\n";);
SASSERT(m_global_gen);
if (pob *new_pob = m_global_gen->mk_concretize_pob(n, model)) {
m_stats.m_num_concretize++;
out.push_back(new_pob);
out.push_back(&n);
IF_VERBOSE(1, verbose_stream()
<< " C " << std::fixed << std::setprecision(2)
<< watch.get_seconds() << "\n";);
unsigned gas = n.get_gas();
SASSERT(gas > 0);
// dec gas for orig pob to limit number of concretizations
new_pob->set_gas(gas--);
n.set_gas(gas);
return l_undef;
}
}
model = nullptr;
predecessor_eh(); predecessor_eh();
lbool res = n.pt ().is_reachable (n, &cube, &model, uses_level, is_concrete, r, lbool res =
reach_pred_used, num_reuse_reach); n.pt().is_reachable(n, &cube, &model, uses_level, is_concrete, r,
reach_pred_used, num_reuse_reach, m_use_iuc);
if (model) model->set_model_completion(false); if (model) model->set_model_completion(false);
if (res == l_undef && model) res = handle_unknown(n, r, *model); if (res == l_undef && model) res = handle_unknown(n, r, *model);
@ -3534,7 +3637,18 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
out.push_back (next); out.push_back (next);
} }
} }
if(n.is_subsume())
m_stats.m_num_subsume_pob_reachable++;
if(n.is_conjecture())
m_stats.m_num_conj_failed++;
CTRACE("global", n.is_conjecture(),
tout << "Failed to block conjecture "
<< n.post()->get_id() << "\n";);
CTRACE("global", n.is_subsume(),
tout << "Failed to block subsume generalization "
<< mk_pp(n.post(), m) << "\n";);
IF_VERBOSE(1, verbose_stream () << (next ? " X " : " T ") IF_VERBOSE(1, verbose_stream () << (next ? " X " : " T ")
<< std::fixed << std::setprecision(2) << std::fixed << std::setprecision(2)
@ -3565,7 +3679,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
throw unknown_exception(); throw unknown_exception();
} }
case l_false: { case l_false: {
// n is unreachable, create new summary facts // n is unreachable, create a new lemma
timeit _timer (is_trace_enabled("spacer_timeit"), timeit _timer (is_trace_enabled("spacer_timeit"),
"spacer::expand_pob::false", "spacer::expand_pob::false",
verbose_stream ()); verbose_stream ());
@ -3573,40 +3687,68 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
// -- only update expanded level when new lemmas are generated at it. // -- only update expanded level when new lemmas are generated at it.
if (n.level() < m_expanded_lvl) { m_expanded_lvl = n.level(); } if (n.level() < m_expanded_lvl) { m_expanded_lvl = n.level(); }
TRACE("spacer", tout << "cube:\n"; TRACE("spacer", tout << "cube:\n" << cube << "\n";);
for (unsigned j = 0; j < cube.size(); ++j)
tout << mk_pp(cube[j].get(), m) << "\n";);
if(n.is_conjecture()) m_stats.m_num_conj_success++;
if(n.is_subsume()) m_stats.m_num_subsume_pob_blckd++;
pob_ref nref(&n); pob_ref nref(&n);
// -- create lemma from a pob and last unsat core
lemma_ref lemma = alloc(class lemma, pob_ref(&n), cube, uses_level);
// -- create lemma from a pob and last unsat core
lemma_ref lemma_pob;
if (n.is_local_gen_enabled()) {
lemma_pob = alloc(class lemma, nref, cube, uses_level);
// -- run all lemma generalizers // -- run all lemma generalizers
for (unsigned i = 0; for (unsigned i = 0;
// -- only generalize if lemma was constructed using farkas // -- only generalize if lemma was constructed using farkas
n.use_farkas_generalizer () && !lemma->is_false() && n.use_farkas_generalizer() && !lemma_pob->is_false() &&
i < m_lemma_generalizers.size(); ++i) { i < m_lemma_generalizers.size();
++i) {
checkpoint (); checkpoint ();
(*m_lemma_generalizers[i])(lemma); (*m_lemma_generalizers[i])(lemma_pob);
} }
DEBUG_CODE( } else if (m_global_gen || m_expand_bnd_gen) {
lemma_sanity_checker sanity_checker(*this); m_stats.m_non_local_gen++;
sanity_checker(lemma);
);
expr_ref_vector pob_cube(m);
n.get_post_simplified(pob_cube);
TRACE("spacer", tout << "invariant state: " lemma_pob = alloc(class lemma, nref, pob_cube, n.level());
<< (is_infty_level(lemma->level())?"(inductive)":"") TRACE("global", tout << "Disabled local gen on pob (id: "
<< mk_pp(lemma->get_expr(), m) << "\n";); << n.post()->get_id() << ")\n"
<< mk_pp(n.post(), m) << "\n"
<< "Lemma:\n"
<< mk_and(lemma_pob->get_cube()) << "\n";);
if (m_global_gen) (*m_global_gen)(lemma_pob);
if (m_expand_bnd_gen) (*m_expand_bnd_gen)(lemma_pob);
} else {
lemma_pob = alloc(class lemma, nref, cube, uses_level);
}
bool v = n.pt().add_lemma (lemma.get()); CTRACE("global", n.is_conjecture() || n.is_subsume(),
if (v) { m_stats.m_num_lemmas++; } tout << "Blocked "
<< (n.is_conjecture() ? "conjecture " : "subsume ") << n.post()->get_id()
<< " at level " << n.level()
<< " using lemma\n" << mk_pp(lemma_pob->get_expr(), m) << "\n";);
DEBUG_CODE(lemma_sanity_checker sanity_checker(*this);
sanity_checker(lemma_pob););
TRACE("spacer",
tout << "invariant state: "
<< (is_infty_level(lemma_pob->level()) ? "(inductive)" : "")
<< mk_pp(lemma_pob->get_expr(), m) << "\n";);
bool is_new = n.pt().add_lemma(lemma_pob.get());
if (is_new) {
if (m_global) m_lmma_cluster->cluster(lemma_pob);
m_stats.m_num_lemmas++;
}
// Optionally update the node to be the negation of the lemma // Optionally update the node to be the negation of the lemma
if (v && m_use_lemma_as_pob) { if (is_new && m_use_lemma_as_pob) {
expr_ref c(m); expr_ref c(m);
c = mk_and(lemma->get_cube()); c = mk_and(lemma_pob->get_cube());
// check that the post condition is different // check that the post condition is different
if (c != n.post()) { if (c != n.post()) {
pob *f = n.pt().find_pob(n.parent(), c); pob *f = n.pt().find_pob(n.parent(), c);
@ -3622,6 +3764,28 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
} }
} }
if (m_global_gen) {
// if global gen is enabled, post-process the pob to create new subsume or conjecture pob
if (pob* new_pob = m_global_gen->mk_subsume_pob(n)) {
new_pob->set_gas(n.get_gas() - 1);
n.set_gas(n.get_gas() - 1);
out.push_back(new_pob);
m_stats.m_num_subsume_pobs++;
TRACE("global_verbose",
tout << "New subsume pob\n" << mk_pp(new_pob->post(), m) << "\n"
<< "gas:" << new_pob->get_gas() << "\n";);
} else if (pob* new_pob = m_gg_conjecture ? m_global_gen->mk_conjecture_pob(n) : nullptr) {
new_pob->set_gas(n.get_gas() - 1);
n.set_gas(n.get_gas() - 1);
out.push_back(new_pob);
m_stats.m_num_conj++;
TRACE("global",
tout << "New conjecture pob\n" << mk_pp(new_pob->post(), m) << "\n";);
}
}
// schedule the node to be placed back in the queue // schedule the node to be placed back in the queue
n.inc_level(); n.inc_level();
out.push_back(&n); out.push_back(&n);
@ -3636,7 +3800,24 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
return l_false; return l_false;
} }
case l_undef: case l_undef:
// something went wrong // if the pob is a may pob, handle specially
if (n.is_may_pob()) {
// do not create children, but bump weakness
// bail out if this does not help
// AG: do not know why this is a good strategy
if (n.weakness() < 10) {
SASSERT(out.empty());
n.bump_weakness();
return expand_pob(n, out);
}
n.close();
m_stats.m_expand_pob_undef++;
IF_VERBOSE(1, verbose_stream() << " UNDEF "
<< std::fixed << std::setprecision(2)
<< watch.get_seconds () << "\n";);
return l_undef;
}
if (n.weakness() < 10 /* MAX_WEAKENSS */) { if (n.weakness() < 10 /* MAX_WEAKENSS */) {
bool has_new_child = false; bool has_new_child = false;
SASSERT(m_weak_abs); SASSERT(m_weak_abs);
@ -3933,6 +4114,11 @@ bool context::create_children(pob& n, datalog::rule const& r,
!mdl.is_true(n.post()))) !mdl.is_true(n.post())))
{ kid->reset_derivation(); } { kid->reset_derivation(); }
if (kid->is_may_pob()) {
SASSERT(n.get_gas() > 0);
n.set_gas(n.get_gas() - 1);
kid->set_gas(n.get_gas() - 1);
}
out.push_back(kid); out.push_back(kid);
m_stats.m_num_queries++; m_stats.m_num_queries++;
return true; return true;
@ -3971,6 +4157,17 @@ void context::collect_statistics(statistics& st) const
st.update("SPACER num lemmas", m_stats.m_num_lemmas); st.update("SPACER num lemmas", m_stats.m_num_lemmas);
// -- number of restarts taken // -- number of restarts taken
st.update("SPACER restarts", m_stats.m_num_restarts); st.update("SPACER restarts", m_stats.m_num_restarts);
// -- number of time pob abstraction was invoked
st.update("SPACER conj", m_stats.m_num_conj);
st.update("SPACER conj success", m_stats.m_num_conj_success);
st.update("SPACER conj failed",
m_stats.m_num_conj_failed);
st.update("SPACER pob out of gas", m_stats.m_num_pob_ofg);
st.update("SPACER subsume pob", m_stats.m_num_subsume_pobs);
st.update("SPACER subsume failed", m_stats.m_num_subsume_pob_reachable);
st.update("SPACER subsume success", m_stats.m_num_subsume_pob_blckd);
st.update("SPACER concretize", m_stats.m_num_concretize);
st.update("SPACER non local gen", m_stats.m_non_local_gen);
// -- time to initialize the rules // -- time to initialize the rules
st.update ("time.spacer.init_rules", m_init_rules_watch.get_seconds ()); st.update ("time.spacer.init_rules", m_init_rules_watch.get_seconds ());
@ -3991,6 +4188,7 @@ void context::collect_statistics(statistics& st) const
for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) { for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) {
m_lemma_generalizers[i]->collect_statistics(st); m_lemma_generalizers[i]->collect_statistics(st);
} }
m_lmma_cluster->collect_statistics(st);
} }
void context::reset_statistics() void context::reset_statistics()
@ -4008,6 +4206,7 @@ void context::reset_statistics()
m_lemma_generalizers[i]->reset_statistics(); m_lemma_generalizers[i]->reset_statistics();
} }
m_lmma_cluster->reset_statistics();
m_init_rules_watch.reset (); m_init_rules_watch.reset ();
m_solve_watch.reset (); m_solve_watch.reset ();
m_propagate_watch.reset (); m_propagate_watch.reset ();
@ -4093,8 +4292,6 @@ void context::add_constraint (expr *c, unsigned level)
} }
void context::new_lemma_eh(pred_transformer &pt, lemma *lem) { void context::new_lemma_eh(pred_transformer &pt, lemma *lem) {
if (m_params.spacer_print_json().is_non_empty_string())
m_json_marshaller.register_lemma(lem);
bool handle=false; bool handle=false;
for (unsigned i = 0; i < m_callbacks.size(); i++) { for (unsigned i = 0; i < m_callbacks.size(); i++) {
handle|=m_callbacks[i]->new_lemma(); handle|=m_callbacks[i]->new_lemma();
@ -4116,10 +4313,7 @@ void context::new_lemma_eh(pred_transformer &pt, lemma *lem) {
} }
} }
void context::new_pob_eh(pob *p) { void context::new_pob_eh(pob *p) { }
if (m_params.spacer_print_json().is_non_empty_string())
m_json_marshaller.register_pob(p);
}
bool context::is_inductive() { bool context::is_inductive() {
// check that inductive level (F infinity) of the query predicate // check that inductive level (F infinity) of the query predicate
@ -4140,6 +4334,10 @@ inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const
if (n1.depth() != n2.depth()) { return n1.depth() < n2.depth(); } if (n1.depth() != n2.depth()) { return n1.depth() < n2.depth(); }
if (n1.is_subsume() != n2.is_subsume()) { return n1.is_subsume(); }
if (n1.is_conjecture() != n2.is_conjecture()) { return n1.is_conjecture(); }
if (n1.get_gas() != n2.get_gas()) { return n1.get_gas() > n2.get_gas(); }
// -- a more deterministic order of proof obligations in a queue // -- a more deterministic order of proof obligations in a queue
// if (!n1.get_context ().get_params ().spacer_nondet_tie_break ()) // if (!n1.get_context ().get_params ().spacer_nondet_tie_break ())
{ {
@ -4193,5 +4391,27 @@ inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const
} }
// set gas of each may parent to 0
// TODO: close siblings as well. kids of a pob are not stored in the pob
void context::close_all_may_parents(pob_ref node) {
pob_ref_vector to_do;
to_do.push_back(node.get());
while (to_do.size() != 0) {
pob_ref t = to_do.back();
t->set_gas(0);
if (t->is_may_pob()) {
t->close();
} else
break;
to_do.pop_back();
to_do.push_back(t->parent());
}
}
// construct a simplified version of the post
void pob::get_post_simplified(expr_ref_vector &pob_cube) {
pob_cube.reset();
pob_cube.push_back(m_post);
flatten_and(pob_cube);
simplify_bounds(pob_cube);
}
} }

View file

@ -21,22 +21,24 @@ Notes:
--*/ --*/
#pragma once #pragma once
// clang-format off
#include <queue>
#include <fstream>
#include <algorithm> #include <algorithm>
#include <fstream>
#include <queue>
#include "util/scoped_ptr_vector.h" #include "muz/spacer/spacer_cluster.h"
#include "muz/spacer/spacer_manager.h" #include "muz/spacer/spacer_manager.h"
#include "muz/spacer/spacer_prop_solver.h" #include "muz/spacer/spacer_prop_solver.h"
#include "muz/spacer/spacer_json.h" #include "muz/spacer/spacer_sem_matcher.h"
#include "util/scoped_ptr_vector.h"
#include "muz/base/fp_params.hpp" #include "muz/base/fp_params.hpp"
namespace datalog { namespace datalog {
class rule_set; class rule_set;
class context; class context;
}; }; // namespace datalog
namespace spacer { namespace spacer {
@ -46,6 +48,9 @@ class pred_transformer;
class derivation; class derivation;
class pob_queue; class pob_queue;
class context; class context;
class lemma_cluster;
class lemma_cluster_finder;
class lemma_global_generalizer;
typedef obj_map<datalog::rule const, app_ref_vector *> rule2inst; typedef obj_map<datalog::rule const, app_ref_vector *> rule2inst;
typedef obj_map<func_decl, pred_transformer *> decl2rel; typedef obj_map<func_decl, pred_transformer *> decl2rel;
@ -59,6 +64,10 @@ class reach_fact;
typedef ref<reach_fact> reach_fact_ref; typedef ref<reach_fact> reach_fact_ref;
typedef sref_vector<reach_fact> reach_fact_ref_vector; typedef sref_vector<reach_fact> reach_fact_ref_vector;
class lemma;
using lemma_ref = ref<lemma>;
using lemma_ref_vector = sref_vector<lemma>;
class reach_fact { class reach_fact {
unsigned m_ref_count; unsigned m_ref_count;
@ -74,45 +83,43 @@ class reach_fact {
bool m_init; bool m_init;
public: public:
reach_fact (ast_manager &m, const datalog::rule &rule, reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact,
expr* fact, const ptr_vector<app> &aux_vars, const ptr_vector<app> &aux_vars, bool init = false)
bool init = false) : : m_ref_count(0), m_fact(fact, m), m_aux_vars(aux_vars), m_rule(rule),
m_ref_count (0), m_fact (fact, m), m_aux_vars (aux_vars), m_tag(m), m_init(init) {}
m_rule(rule), m_tag(m), m_init (init) {} reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact,
reach_fact (ast_manager &m, const datalog::rule &rule, bool init = false)
expr* fact, bool init = false) : : m_ref_count(0), m_fact(fact, m), m_rule(rule), m_tag(m),
m_ref_count (0), m_fact (fact, m), m_rule(rule), m_tag(m), m_init (init) {} m_init(init) {}
bool is_init() { return m_init; } bool is_init() { return m_init; }
const datalog::rule &get_rule() { return m_rule; } const datalog::rule &get_rule() { return m_rule; }
void add_justification(reach_fact *f) { m_justification.push_back(f); } void add_justification(reach_fact *f) { m_justification.push_back(f); }
const reach_fact_ref_vector& get_justifications () {return m_justification;} const reach_fact_ref_vector &get_justifications() {
return m_justification;
}
expr *get() { return m_fact.get(); } expr *get() { return m_fact.get(); }
const ptr_vector<app> &aux_vars() { return m_aux_vars; } const ptr_vector<app> &aux_vars() { return m_aux_vars; }
app* tag() const {SASSERT(m_tag); return m_tag;} app *tag() const {
SASSERT(m_tag);
return m_tag;
}
void set_tag(app *tag) { m_tag = tag; } void set_tag(app *tag) { m_tag = tag; }
void inc_ref() { ++m_ref_count; } void inc_ref() { ++m_ref_count; }
void dec_ref () void dec_ref() {
{
SASSERT(m_ref_count > 0); SASSERT(m_ref_count > 0);
--m_ref_count; --m_ref_count;
if (m_ref_count == 0) { dealloc(this); } if (m_ref_count == 0) { dealloc(this); }
} }
}; };
class lemma;
typedef ref<lemma> lemma_ref;
typedef sref_vector<lemma> lemma_ref_vector;
typedef pob pob;
// a lemma // a lemma
class lemma { class lemma {
// clang-format off
unsigned m_ref_count; unsigned m_ref_count;
ast_manager &m; ast_manager &m;
@ -129,9 +136,12 @@ class lemma {
unsigned m_external:1; // external lemma from another solver unsigned m_external:1; // external lemma from another solver
unsigned m_blocked:1; // blocked by CTP unsigned m_blocked:1; // blocked by CTP
unsigned m_background:1; // background assumed fact unsigned m_background:1; // background assumed fact
// clang-format on
// clang-format off
void mk_expr_core(); void mk_expr_core();
void mk_cube_core(); void mk_cube_core();
public: public:
lemma(ast_manager &manager, expr *fml, unsigned lvl); lemma(ast_manager &manager, expr *fml, unsigned lvl);
lemma(pob_ref const &p); lemma(pob_ref const &p);
@ -195,8 +205,6 @@ struct lemma_lt_proc {
} }
}; };
// //
// Predicate transformer state. // Predicate transformer state.
// A predicate transformer corresponds to the // A predicate transformer corresponds to the
@ -206,12 +214,16 @@ struct lemma_lt_proc {
class pred_transformer { class pred_transformer {
struct stats { struct stats {
// clang-format off
unsigned m_num_propagations; // num of times lemma is pushed higher unsigned m_num_propagations; // num of times lemma is pushed higher
unsigned m_num_invariants; // num of infty lemmas found unsigned m_num_invariants; // num of infty lemmas found
unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing
unsigned m_num_is_invariant; // num of times lemmas are pushed unsigned m_num_is_invariant; // num of times lemmas are pushed
unsigned m_num_lemma_level_jump; // lemma learned at higher level than expected unsigned m_num_lemma_level_jump; // lemma learned at higher level than
// expected
unsigned m_num_reach_queries; unsigned m_num_reach_queries;
// clang-format on
// clang-format off
stats() { reset(); } stats() { reset(); }
void reset() { memset(this, 0, sizeof(*this)); } void reset() { memset(this, 0, sizeof(*this)); }
@ -221,6 +233,7 @@ class pred_transformer {
#include "muz/spacer/spacer_legacy_frames.h" #include "muz/spacer/spacer_legacy_frames.h"
class frames { class frames {
private: private:
// clang-format off
pred_transformer &m_pt; // parent pred_transformer pred_transformer &m_pt; // parent pred_transformer
lemma_ref_vector m_pinned_lemmas; // all created lemmas lemma_ref_vector m_pinned_lemmas; // all created lemmas
lemma_ref_vector m_lemmas; // active lemmas lemma_ref_vector m_lemmas; // active lemmas
@ -229,18 +242,18 @@ class pred_transformer {
bool m_sorted; // true if m_lemmas is sorted by m_lt bool m_sorted; // true if m_lemmas is sorted by m_lt
lemma_lt_proc m_lt; // sort order for m_lemmas lemma_lt_proc m_lt; // sort order for m_lemmas
// clang-format on
// clang-format off
void sort(); void sort();
public: public:
frames (pred_transformer &pt) : m_pt (pt), frames(pred_transformer &pt) : m_pt(pt), m_size(0), m_sorted(true) {}
m_size(0), m_sorted (true) {}
void simplify_formulas(); void simplify_formulas();
pred_transformer &pt() const { return m_pt; } pred_transformer &pt() const { return m_pt; }
const lemma_ref_vector &lemmas() const { return m_lemmas; } const lemma_ref_vector &lemmas() const { return m_lemmas; }
void get_frame_lemmas(unsigned level, expr_ref_vector &out) const { void get_frame_lemmas(unsigned level, expr_ref_vector &out) const {
for (auto &lemma : m_lemmas) { for (auto &lemma : m_lemmas) {
if (lemma->level() == level) { if (lemma->level() == level) {
@ -256,11 +269,17 @@ class pred_transformer {
} }
} }
if (with_bg) { if (with_bg) {
for (auto &lemma : m_bg_invs) for (auto &lemma : m_bg_invs) out.push_back(lemma->get_expr());
out.push_back(lemma->get_expr());
} }
} }
void get_frame_all_lemmas(lemma_ref_vector &out,
bool with_bg = false) const {
for (auto &lemma : m_lemmas) { out.push_back(lemma); }
if (with_bg) {
for (auto &lemma : m_bg_invs) out.push_back(lemma);
}
}
const lemma_ref_vector &get_bg_invs() const { return m_bg_invs; } const lemma_ref_vector &get_bg_invs() const { return m_bg_invs; }
unsigned size() const { return m_size; } unsigned size() const { return m_size; }
unsigned lemma_size() const { return m_lemmas.size(); } unsigned lemma_size() const { return m_lemmas.size(); }
@ -269,9 +288,9 @@ class pred_transformer {
void add_frame() { m_size++; } void add_frame() { m_size++; }
void inherit_frames(frames &other) { void inherit_frames(frames &other) {
for (auto &other_lemma : other.m_lemmas) { for (auto &other_lemma : other.m_lemmas) {
lemma_ref new_lemma = alloc(lemma, m_pt.get_ast_manager(), lemma_ref new_lemma =
other_lemma->get_expr(), alloc(lemma, m_pt.get_ast_manager(),
other_lemma->level()); other_lemma->get_expr(), other_lemma->level());
new_lemma->add_binding(other_lemma->get_bindings()); new_lemma->add_binding(other_lemma->get_bindings());
add_lemma(new_lemma.get()); add_lemma(new_lemma.get());
} }
@ -308,13 +327,13 @@ class pred_transformer {
// a store // a store
pob_ref_vector m_pinned; pob_ref_vector m_pinned;
public: public:
pob_manager(pred_transformer &pt) : m_pt(pt) {} pob_manager(pred_transformer &pt) : m_pt(pt) {}
pob* mk_pob(pob *parent, unsigned level, unsigned depth, pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post,
expr *post, app_ref_vector const &b); app_ref_vector const &b);
pob* mk_pob(pob *parent, unsigned level, unsigned depth, pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) {
expr *post) {
app_ref_vector b(m_pt.get_ast_manager()); app_ref_vector b(m_pt.get_ast_manager());
return mk_pob(parent, level, depth, post, b); return mk_pob(parent, level, depth, post, b);
} }
@ -324,28 +343,42 @@ class pred_transformer {
}; };
class pt_rule { class pt_rule {
// clang-format off
const datalog::rule &m_rule; const datalog::rule &m_rule;
expr_ref m_trans; // ground version of m_rule expr_ref m_trans; // ground version of m_rule
ptr_vector<app> m_auxs; // auxiliary variables in m_trans ptr_vector<app> m_auxs; // auxiliary variables in m_trans
app_ref_vector m_reps; // map from fv in m_rule to ground constants app_ref_vector m_reps; // map from fv in m_rule to ground constants
app_ref m_tag; // a unique tag for the rule app_ref m_tag; // a unique tag for the rule
// clang-format on
// clang-format off
public: public:
pt_rule(ast_manager &m, const datalog::rule &r) : pt_rule(ast_manager &m, const datalog::rule &r)
m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} : m_rule(r), m_trans(m), m_reps(m), m_tag(m) {}
const datalog::rule &rule() const { return m_rule; } const datalog::rule &rule() const { return m_rule; }
void set_tag(expr *tag) {SASSERT(is_app(tag)); set_tag(to_app(tag));} void set_tag(expr *tag) {
SASSERT(is_app(tag));
set_tag(to_app(tag));
}
void set_tag(app *tag) { m_tag = tag; } void set_tag(app *tag) { m_tag = tag; }
app *tag() const { return m_tag; } app *tag() const { return m_tag; }
ptr_vector<app> &auxs() { return m_auxs; } ptr_vector<app> &auxs() { return m_auxs; }
void set_auxs(ptr_vector<app> &v) {m_auxs.reset(); m_auxs.append(v);} void set_auxs(ptr_vector<app> &v) {
void set_reps(app_ref_vector &v) {m_reps.reset(); m_reps.append(v);} m_auxs.reset();
m_auxs.append(v);
}
void set_reps(app_ref_vector &v) {
m_reps.reset();
m_reps.append(v);
}
void set_trans(expr_ref &v) { m_trans = v; } void set_trans(expr_ref &v) { m_trans = v; }
expr *trans() const { return m_trans; } expr *trans() const { return m_trans; }
bool is_init() const {return m_rule.get_uninterpreted_tail_size() == 0;} bool is_init() const {
return m_rule.get_uninterpreted_tail_size() == 0;
}
}; };
class pt_rules { class pt_rules {
@ -354,8 +387,11 @@ class pred_transformer {
typedef rule2ptrule::iterator iterator; typedef rule2ptrule::iterator iterator;
rule2ptrule m_rules; rule2ptrule m_rules;
tag2ptrule m_tags; tag2ptrule m_tags;
public: public:
~pt_rules() {for (auto &kv : m_rules) {dealloc(kv.m_value);}} ~pt_rules() {
for (auto &kv : m_rules) { dealloc(kv.m_value); }
}
bool find_by_rule(const datalog::rule &r, pt_rule *&ptr) { bool find_by_rule(const datalog::rule &r, pt_rule *&ptr) {
return m_rules.find(&r, ptr); return m_rules.find(&r, ptr);
@ -377,9 +413,73 @@ class pred_transformer {
bool empty() { return m_rules.empty(); } bool empty() { return m_rules.empty(); }
iterator begin() { return m_rules.begin(); } iterator begin() { return m_rules.begin(); }
iterator end() { return m_rules.end(); } iterator end() { return m_rules.end(); }
}; };
/// Clusters of lemmas
class cluster_db {
sref_vector<lemma_cluster> m_clusters;
unsigned m_max_cluster_size;
public:
cluster_db() : m_max_cluster_size(0) {}
unsigned get_max_cluster_size() const { return m_max_cluster_size; }
/// Return the smallest cluster than can contain \p lemma
lemma_cluster *can_contain(const lemma_ref &lemma) {
unsigned sz = UINT_MAX;
lemma_cluster *res = nullptr;
for (auto *c : m_clusters) {
if (c->get_gas() > 0 && c->get_size() < sz &&
c->can_contain(lemma)) {
res = c;
sz = res->get_size();
}
}
return res;
}
bool contains(const lemma_ref &lemma) {
for (auto *c : m_clusters) {
if (c->contains(lemma)) { return true; }
}
return false;
}
/// The number of clusters with pattern \p pattern
unsigned clstr_count(const expr_ref &pattern) {
unsigned count = 0;
for (auto c : m_clusters) {
if (c->get_pattern() == pattern) count++;
}
return count;
}
lemma_cluster *mk_cluster(const expr_ref &pattern) {
m_clusters.push_back(alloc(lemma_cluster, pattern));
return m_clusters.back();
}
/// Return the smallest cluster containing \p lemma
lemma_cluster *get_cluster(const lemma_ref &lemma) {
unsigned sz = UINT_MAX;
lemma_cluster *res = nullptr;
for (auto *c : m_clusters) {
if (c->get_size() < sz && c->contains(lemma)) {
res = c;
sz = res->get_size();
}
}
return res;
}
lemma_cluster *get_cluster(const expr *pattern) {
for (lemma_cluster *lc : m_clusters) {
if (lc->get_pattern().get() == pattern) return lc;
}
return nullptr;
}
};
// clang-format off
manager& pm; // spacer::manager manager& pm; // spacer::manager
ast_manager& m; // ast_manager ast_manager& m; // ast_manager
context& ctx; // spacer::context context& ctx; // spacer::context
@ -408,6 +508,9 @@ class pred_transformer {
stopwatch m_ctp_watch; stopwatch m_ctp_watch;
stopwatch m_mbp_watch; stopwatch m_mbp_watch;
bool m_has_quantified_frame; // True when a quantified lemma is in the frame bool m_has_quantified_frame; // True when a quantified lemma is in the frame
cluster_db m_cluster_db;
// clang-format on
// clang-format off
void init_sig(); void init_sig();
app_ref mk_extend_lit(); app_ref mk_extend_lit();
@ -426,14 +529,17 @@ class pred_transformer {
void simplify_formulas(tactic &tac, expr_ref_vector &fmls); void simplify_formulas(tactic &tac, expr_ref_vector &fmls);
void add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r); void add_premises(decl2rel const &pts, unsigned lvl, datalog::rule &rule,
expr_ref_vector &r);
app_ref mk_fresh_rf_tag(); app_ref mk_fresh_rf_tag();
// get tagged formulae of all of the background invariants for all of the // get tagged formulae of all of the background invariants for all of the
// predecessors of the current transformer // predecessors of the current transformer
void get_pred_bg_invs(expr_ref_vector &out); void get_pred_bg_invs(expr_ref_vector &out);
const lemma_ref_vector &get_bg_invs() const {return m_frames.get_bg_invs();} const lemma_ref_vector &get_bg_invs() const {
return m_frames.get_bg_invs();
}
public: public:
pred_transformer(context &ctx, manager &pm, func_decl *head); pred_transformer(context &ctx, manager &pm, func_decl *head);
@ -446,10 +552,13 @@ public:
} }
return nullptr; return nullptr;
} }
void find_predecessors(datalog::rule const& r, ptr_vector<func_decl>& predicates) const; void find_predecessors(datalog::rule const &r,
ptr_vector<func_decl> &predicates) const;
void add_rule(datalog::rule *r) { m_rules.push_back(r); } void add_rule(datalog::rule *r) { m_rules.push_back(r); }
void add_use(pred_transformer* pt) {if (!m_use.contains(pt)) {m_use.insert(pt);}} void add_use(pred_transformer *pt) {
if (!m_use.contains(pt)) { m_use.insert(pt); }
}
void initialize(decl2rel const &pts); void initialize(decl2rel const &pts);
func_decl *head() const { return m_head; } func_decl *head() const { return m_head; }
@ -482,9 +591,8 @@ public:
/// \brief Collects all the reachable facts used in mdl /// \brief Collects all the reachable facts used in mdl
void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector &res); void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector &res);
void get_all_used_rf(model &mdl, reach_fact_ref_vector &res); void get_all_used_rf(model &mdl, reach_fact_ref_vector &res);
expr_ref get_origin_summary(model &mdl, expr_ref get_origin_summary(model &mdl, unsigned level, unsigned oidx,
unsigned level, unsigned oidx, bool must, bool must, const ptr_vector<app> **aux);
const ptr_vector<app> **aux);
bool is_ctp_blocked(lemma *lem); bool is_ctp_blocked(lemma *lem);
const datalog::rule *find_rule(model &mdl); const datalog::rule *find_rule(model &mdl);
@ -516,32 +624,30 @@ public:
reach_fact *get_last_rf() const { return m_reach_facts.back(); } reach_fact *get_last_rf() const { return m_reach_facts.back(); }
expr *get_last_rf_tag() const; expr *get_last_rf_tag() const;
pob* mk_pob(pob *parent, unsigned level, unsigned depth, pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post,
expr *post, app_ref_vector const &b){ app_ref_vector const &b) {
return m_pobs.mk_pob(parent, level, depth, post, b); return m_pobs.mk_pob(parent, level, depth, post, b);
} }
pob *find_pob(pob *parent, expr *post) { pob *find_pob(pob *parent, expr *post) {
return m_pobs.find_pob(parent, post); return m_pobs.find_pob(parent, post);
} }
pob* mk_pob(pob *parent, unsigned level, unsigned depth, pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) {
expr *post) {
return m_pobs.mk_pob(parent, level, depth, post); return m_pobs.mk_pob(parent, level, depth, post);
} }
lbool is_reachable(pob& n, expr_ref_vector* core, model_ref *model, lbool is_reachable(pob& n, expr_ref_vector* core, model_ref *model,
unsigned& uses_level, bool& is_concrete, unsigned& uses_level, bool& is_concrete,
datalog::rule const*& r, datalog::rule const *&r, bool_vector &reach_pred_used,
bool_vector& reach_pred_used, unsigned& num_reuse_reach, bool use_iuc = true);
unsigned& num_reuse_reach); bool is_invariant(unsigned level, lemma *lem, unsigned &solver_level,
bool is_invariant(unsigned level, lemma* lem,
unsigned& solver_level,
expr_ref_vector* core = nullptr); expr_ref_vector* core = nullptr);
bool is_invariant(unsigned level, expr* lem, bool is_invariant(unsigned level, expr *lem, unsigned &solver_level,
unsigned& solver_level, expr_ref_vector* core = nullptr) { expr_ref_vector *core = nullptr) {
// XXX only needed for legacy_frames to compile // XXX only needed for legacy_frames to compile
UNREACHABLE(); return false; UNREACHABLE();
return false;
} }
bool check_inductive(unsigned level, expr_ref_vector &state, bool check_inductive(unsigned level, expr_ref_vector &state,
@ -559,15 +665,17 @@ public:
void inherit_lemmas(pred_transformer &other); void inherit_lemmas(pred_transformer &other);
void ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector<app>& aux_vars, void ground_free_vars(expr *e, app_ref_vector &vars,
bool is_init); ptr_vector<app> &aux_vars, bool is_init);
/// \brief Adds a given expression to the set of initial rules /// \brief Adds a given expression to the set of initial rules
app *extend_initial(expr *e); app *extend_initial(expr *e);
/// \brief Returns true if the obligation is already blocked by current lemmas /// \brief Returns true if the obligation is already blocked by current
bool is_blocked (pob &n, unsigned &uses_level); /// lemmas
/// \brief Returns true if the obligation is already blocked by current quantified lemmas bool is_blocked(pob &n, unsigned &uses_level, model_ref *model = nullptr);
/// \brief Returns true if the obligation is already blocked by current
/// quantified lemmas
bool is_qblocked(pob &n); bool is_qblocked(pob &n);
/// \brief interface to Model Based Projection /// \brief interface to Model Based Projection
@ -577,19 +685,48 @@ public:
void updt_solver(prop_solver *solver); void updt_solver(prop_solver *solver);
void updt_solver_with_lemmas(prop_solver *solver, void updt_solver_with_lemmas(prop_solver *solver,
const pred_transformer &pt, const pred_transformer &pt, app *rule_tag,
app *rule_tag, unsigned pos); unsigned pos);
void update_solver_with_rfs(prop_solver *solver, // exposing ACTIVE lemmas (alternatively, one can expose `m_pinned_lemmas`
const pred_transformer &pt, // for ALL lemmas)
void get_all_lemmas(lemma_ref_vector &out, bool with_bg = false) const {
m_frames.get_frame_all_lemmas(out, with_bg);
}
void update_solver_with_rfs(prop_solver *solver, const pred_transformer &pt,
app *rule_tag, unsigned pos); app *rule_tag, unsigned pos);
lemma_cluster *mk_cluster(const expr_ref &pattern) {
return m_cluster_db.mk_cluster(pattern);
}
// Checks whether \p lemma is in any existing cluster
bool clstr_contains(const lemma_ref &lemma) {
return m_cluster_db.contains(lemma);
}
/// The number of clusters with pattern \p pattern
unsigned clstr_count(const expr_ref &pattern) {
return m_cluster_db.clstr_count(pattern);
}
/// Checks whether \p lemma matches any cluster
lemma_cluster *clstr_match(const lemma_ref &lemma) {
lemma_cluster *res = m_cluster_db.get_cluster(lemma);
if (!res) res = m_cluster_db.can_contain(lemma);
return res;
}
/// Returns a cluster with pattern \p pattern
lemma_cluster *get_cluster(const expr *pattern) {
return m_cluster_db.get_cluster(pattern);
}
}; };
/** /**
* A proof obligation. * A proof obligation.
*/ */
class pob { class pob {
// clang-format off
// TBD: remove this // TBD: remove this
friend class context; friend class context;
unsigned m_ref_count; unsigned m_ref_count;
@ -608,12 +745,25 @@ class pob {
unsigned m_level:16; unsigned m_level:16;
unsigned m_depth:16; unsigned m_depth:16;
unsigned m_desired_level:16;
/// whether a concrete answer to the post is found /// whether a concrete answer to the post is found
unsigned m_open:1; unsigned m_open:1;
/// whether to use farkas generalizer to construct a lemma blocking this node /// whether to use farkas generalizer to construct a lemma blocking this
/// node
unsigned m_use_farkas:1; unsigned m_use_farkas:1;
/// true if this pob is in pob_queue /// true if this pob is in pob_queue
unsigned m_in_queue:1; unsigned m_in_queue:1;
// true if this pob is a conjecture
unsigned m_is_conjecture:1;
// should do local generalizations on pob
unsigned m_enable_local_gen:1;
// should concretize cube
unsigned m_enable_concretize:1;
// is a subsume pob
unsigned m_is_subsume:1;
// should apply expand bnd generalization on pob
unsigned m_enable_expand_bnd_gen:1;
unsigned m_weakness; unsigned m_weakness;
/// derivation representing the position of this node in the parent's rule /// derivation representing the position of this node in the parent's rule
@ -625,15 +775,35 @@ class pob {
// lemmas created to block this pob (at any time, not necessarily active) // lemmas created to block this pob (at any time, not necessarily active)
ptr_vector<lemma> m_lemmas; ptr_vector<lemma> m_lemmas;
// depth -> watch
std::map<unsigned, stopwatch> m_expand_watches;
unsigned m_blocked_lvl; unsigned m_blocked_lvl;
// clang-format on
// clang-format off
// pattern identified for one of its lemmas
expr_ref m_concretize_pat;
// gas decides how much time is spent in blocking this (may) pob
unsigned m_gas;
// additional data used by global (and other) generalizations
scoped_ptr<pob> m_data;
public: public:
pob (pob* parent, pred_transformer& pt, pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0,
unsigned level, unsigned depth=0, bool add_to_parent=true); bool add_to_parent = true);
~pob() {if (m_parent) { m_parent->erase_child(*this); }} // no copy constructor
pob(const pob&) = delete;
// no move constructor
pob(pob &&) = delete;
~pob() {
if (m_parent) { m_parent->erase_child(*this); }
}
void set_data(pob* v) { m_data = v; }
void reset_data() { set_data(nullptr); }
pob* get_data() { return m_data.get(); }
bool has_data() { return m_data; }
// TBD: move into constructor and make private // TBD: move into constructor and make private
void set_post(expr *post, app_ref_vector const &binding); void set_post(expr *post, app_ref_vector const &binding);
@ -645,7 +815,9 @@ public:
void inc_level() { void inc_level() {
SASSERT(!is_in_queue()); SASSERT(!is_in_queue());
m_level++; m_depth++;reset_weakness(); m_level++;
m_depth++;
reset_weakness();
} }
void inherit(pob const &p); void inherit(pob const &p);
@ -658,6 +830,25 @@ public:
pob* parent() const { return m_parent.get (); } pob* parent() const { return m_parent.get (); }
bool is_conjecture() const { return m_is_conjecture; }
void set_conjecture(bool v = true) { m_is_conjecture = v; }
void disable_expand_bnd_gen() { m_enable_expand_bnd_gen = false; }
bool is_expand_bnd_enabled() { return m_enable_expand_bnd_gen; }
void set_expand_bnd(bool v = true) { m_enable_expand_bnd_gen = v; }
void set_concretize_pattern(const expr_ref &pattern) { m_concretize_pat = pattern; }
const expr_ref &get_concretize_pattern() const { return m_concretize_pat; }
bool is_subsume() const { return m_is_subsume; }
void set_subsume(bool v = true) { m_is_subsume = v; }
bool is_may_pob() const { return is_subsume() || is_conjecture(); }
unsigned get_gas() const { return m_gas; }
void set_gas(unsigned n) { m_gas = n; }
bool is_local_gen_enabled() const { return m_enable_local_gen; }
void disable_local_gen() { m_enable_local_gen = false; }
void get_post_simplified(expr_ref_vector &res);
bool is_concretize_enabled() const { return m_enable_concretize && m_gas > 0; }
void set_concretize(bool v = true) { m_enable_concretize = v; }
pred_transformer& pt() const { return m_pt; } pred_transformer& pt() const { return m_pt; }
ast_manager& get_ast_manager() const { return m_pt.get_ast_manager(); } ast_manager& get_ast_manager() const { return m_pt.get_ast_manager(); }
manager& get_manager() const { return m_pt.get_manager(); } manager& get_manager() const { return m_pt.get_manager(); }
@ -665,6 +856,8 @@ public:
unsigned level() const { return m_level; } unsigned level() const { return m_level; }
unsigned depth() const { return m_depth; } unsigned depth() const { return m_depth; }
unsigned desired_level() const { return m_desired_level; }
void set_desired_level(unsigned v) { m_desired_level = v; }
unsigned width() const { return m_kids.size(); } unsigned width() const { return m_kids.size(); }
unsigned blocked_at(unsigned lvl = 0) { unsigned blocked_at(unsigned lvl = 0) {
return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); return (m_blocked_lvl = std::max(lvl, m_blocked_lvl));
@ -698,14 +891,11 @@ public:
void get_skolems(app_ref_vector &v); void get_skolems(app_ref_vector &v);
void on_expand() { void on_expand() {
m_expand_watches[m_depth].start();
if (m_parent.get()) { m_parent.get()->on_expand(); } if (m_parent.get()) { m_parent.get()->on_expand(); }
} }
void off_expand() { void off_expand() {
m_expand_watches[m_depth].stop();
if (m_parent.get()) { m_parent.get()->off_expand(); } if (m_parent.get()) { m_parent.get()->off_expand(); }
} }
double get_expand_time(unsigned depth) { return m_expand_watches[depth].get_seconds();}
void inc_ref() { ++m_ref_count; } void inc_ref() { ++m_ref_count; }
void dec_ref() { void dec_ref() {
@ -714,9 +904,9 @@ public:
} }
std::ostream &display(std::ostream &out, bool full = false) const; std::ostream &display(std::ostream &out, bool full = false) const;
class on_expand_event class on_expand_event {
{
pob &m_p; pob &m_p;
public: public:
on_expand_event(pob &p) : m_p(p) { m_p.on_expand(); } on_expand_event(pob &p) : m_p(p) { m_p.on_expand(); }
~on_expand_event() { m_p.off_expand(); } ~on_expand_event() { m_p.off_expand(); }
@ -767,7 +957,7 @@ class derivation {
const ptr_vector<app> *aux_vars = nullptr); const ptr_vector<app> *aux_vars = nullptr);
}; };
// clang-format off
/// parent model node /// parent model node
pob& m_parent; pob& m_parent;
@ -782,16 +972,20 @@ class derivation {
expr_ref m_trans; expr_ref m_trans;
// implicitly existentially quantified variables in m_trans // implicitly existentially quantified variables in m_trans
app_ref_vector m_evars; app_ref_vector m_evars;
// clang-format on
// clang-format off
/// -- create next child using given model as the guide /// -- create next child using given model as the guide
/// -- returns NULL if there is no next child /// -- returns NULL if there is no next child
pob *create_next_child(model &mdl); pob *create_next_child(model &mdl);
/// existentially quantify vars and skolemize the result /// existentially quantify vars and skolemize the result
void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res); void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res);
public: public:
derivation (pob& parent, datalog::rule const& rule, derivation(pob &parent, datalog::rule const &rule, expr *trans,
expr *trans, app_ref_vector const &evars); app_ref_vector const &evars);
void add_premise (pred_transformer &pt, unsigned oidx, void add_premise(pred_transformer &pt, unsigned oidx, expr *summary,
expr * summary, bool must, const ptr_vector<app> *aux_vars = nullptr); bool must, const ptr_vector<app> *aux_vars = nullptr);
/// creates the first child. Must be called after all the premises /// creates the first child. Must be called after all the premises
/// are added. The model must be valid for the premises /// are added. The model must be valid for the premises
@ -810,10 +1004,10 @@ public:
pred_transformer &pt() const { return m_parent.pt(); } pred_transformer &pt() const { return m_parent.pt(); }
}; };
class pob_queue { class pob_queue {
typedef std::priority_queue<pob*, std::vector<pob*>, pob_gt_proc> pob_queue_ty; typedef std::priority_queue<pob *, std::vector<pob *>, pob_gt_proc>
pob_queue_ty;
pob_ref m_root; pob_ref m_root;
unsigned m_max_level; unsigned m_max_level;
unsigned m_min_depth; unsigned m_min_depth;
@ -848,13 +1042,13 @@ public:
size_t size() const { return m_data.size(); } size_t size() const { return m_data.size(); }
}; };
/** /**
* Generalizers (strengthens) a lemma * Generalizers (strengthens) a lemma
*/ */
class lemma_generalizer { class lemma_generalizer {
protected: protected:
context &m_ctx; context &m_ctx;
public: public:
lemma_generalizer(context &ctx) : m_ctx(ctx) {} lemma_generalizer(context &ctx) : m_ctx(ctx) {}
virtual ~lemma_generalizer() = default; virtual ~lemma_generalizer() = default;
@ -863,7 +1057,6 @@ public:
virtual void reset_statistics() {} virtual void reset_statistics() {}
}; };
class spacer_callback { class spacer_callback {
protected: protected:
context &m_context; context &m_context;
@ -890,7 +1083,6 @@ public:
virtual inline bool propagate() { return false; } virtual inline bool propagate() { return false; }
virtual void propagate_eh() {} virtual void propagate_eh() {}
}; };
// order in which children are processed // order in which children are processed
@ -913,10 +1105,20 @@ class context {
unsigned m_num_restarts; unsigned m_num_restarts;
unsigned m_num_lemmas_imported; unsigned m_num_lemmas_imported;
unsigned m_num_lemmas_discarded; unsigned m_num_lemmas_discarded;
unsigned m_num_conj;
unsigned m_num_conj_success;
unsigned m_num_conj_failed;
unsigned m_num_subsume_pobs;
unsigned m_num_subsume_pob_reachable;
unsigned m_num_subsume_pob_blckd;
unsigned m_num_concretize;
unsigned m_num_pob_ofg;
unsigned m_non_local_gen;
stats() { reset(); } stats() { reset(); }
void reset() { memset(this, 0, sizeof(*this)); } void reset() { memset(this, 0, sizeof(*this)); }
}; };
// clang-format off
// stat watches // stat watches
stopwatch m_solve_watch; stopwatch m_solve_watch;
stopwatch m_propagate_watch; stopwatch m_propagate_watch;
@ -935,7 +1137,6 @@ class context {
scoped_ptr<solver_pool> m_pool1; scoped_ptr<solver_pool> m_pool1;
scoped_ptr<solver_pool> m_pool2; scoped_ptr<solver_pool> m_pool2;
random_gen m_random; random_gen m_random;
spacer_children_order m_children_order; spacer_children_order m_children_order;
decl2rel m_rels; // Map from relation predicate to fp-operator. decl2rel m_rels; // Map from relation predicate to fp-operator.
@ -946,6 +1147,9 @@ class context {
unsigned m_inductive_lvl; unsigned m_inductive_lvl;
unsigned m_expanded_lvl; unsigned m_expanded_lvl;
ptr_buffer<lemma_generalizer> m_lemma_generalizers; ptr_buffer<lemma_generalizer> m_lemma_generalizers;
lemma_global_generalizer *m_global_gen;
lemma_generalizer *m_expand_bnd_gen;
lemma_cluster_finder *m_lmma_cluster;
stats m_stats; stats m_stats;
model_converter_ref m_mc; model_converter_ref m_mc;
proof_converter_ref m_pc; proof_converter_ref m_pc;
@ -978,21 +1182,26 @@ class context {
bool m_simplify_formulas_post; bool m_simplify_formulas_post;
bool m_pdr_bfs; bool m_pdr_bfs;
bool m_use_bg_invs; bool m_use_bg_invs;
bool m_global;
bool m_expand_bnd;
bool m_gg_conjecture;
bool m_gg_subsume;
bool m_gg_concretize;
bool m_use_iuc;
unsigned m_push_pob_max_depth; unsigned m_push_pob_max_depth;
unsigned m_max_level; unsigned m_max_level;
unsigned m_restart_initial_threshold; unsigned m_restart_initial_threshold;
unsigned m_blast_term_ite_inflation; unsigned m_blast_term_ite_inflation;
scoped_ptr_vector<spacer_callback> m_callbacks; scoped_ptr_vector<spacer_callback> m_callbacks;
json_marshaller m_json_marshaller;
std::fstream* m_trace_stream; std::fstream* m_trace_stream;
// clang-format on
// clang-format off
// Solve using gpdr strategy // Solve using gpdr strategy
lbool gpdr_solve_core(); lbool gpdr_solve_core();
bool gpdr_check_reachability(unsigned lvl, model_search &ms); bool gpdr_check_reachability(unsigned lvl, model_search &ms);
bool gpdr_create_split_children(pob &n, const datalog::rule &r, bool gpdr_create_split_children(pob &n, const datalog::rule &r, expr *trans,
expr *trans, model &mdl, pob_ref_buffer &out);
model &mdl,
pob_ref_buffer &out);
// progress logging // progress logging
void log_enter_level(unsigned lvl); void log_enter_level(unsigned lvl);
@ -1008,8 +1217,7 @@ class context {
unsigned full_prop_lvl); unsigned full_prop_lvl);
bool is_reachable(pob &n); bool is_reachable(pob &n);
lbool expand_pob(pob &n, pob_ref_buffer &out); lbool expand_pob(pob &n, pob_ref_buffer &out);
bool create_children(pob& n, const datalog::rule &r, bool create_children(pob &n, const datalog::rule &r, model &mdl,
model &mdl,
const bool_vector &reach_pred_used, const bool_vector &reach_pred_used,
pob_ref_buffer &out); pob_ref_buffer &out);
@ -1028,7 +1236,6 @@ class context {
vector<relation_info> &rs, vector<relation_info> &rs,
bool with_bg = false) const; bool with_bg = false) const;
// Initialization // Initialization
void init_lemma_generalizers(); void init_lemma_generalizers();
void reset_lemma_generalizers(); void reset_lemma_generalizers();
@ -1046,8 +1253,6 @@ class context {
void simplify_formulas(); void simplify_formulas();
void dump_json();
void predecessor_eh(); void predecessor_eh();
void updt_params(); void updt_params();
@ -1056,13 +1261,13 @@ class context {
public: public:
/** /**
Initial values of predicates are stored in corresponding relations in dctx. Initial values of predicates are stored in corresponding relations in
We check whether there is some reachable state of the relation checked_relation. dctx. We check whether there is some reachable state of the relation
checked_relation.
*/ */
context(fp_params const &params, ast_manager &m); context(fp_params const &params, ast_manager &m);
~context(); ~context();
const fp_params &get_params() const { return m_params; } const fp_params &get_params() const { return m_params; }
bool use_eq_prop() const { return m_use_eq_prop; } bool use_eq_prop() const { return m_use_eq_prop; }
bool use_native_mbp() const { return m_use_native_mbp; } bool use_native_mbp() const { return m_use_native_mbp; }
@ -1075,26 +1280,31 @@ public:
bool simplify_pob() const { return m_simplify_pob; } bool simplify_pob() const { return m_simplify_pob; }
bool use_ctp() const { return m_use_ctp; } bool use_ctp() const { return m_use_ctp; }
bool use_inc_clause() const { return m_use_inc_clause; } bool use_inc_clause() const { return m_use_inc_clause; }
unsigned blast_term_ite_inflation() const {return m_blast_term_ite_inflation;} unsigned blast_term_ite_inflation() const {
return m_blast_term_ite_inflation;
}
bool elim_aux() const { return m_elim_aux; } bool elim_aux() const { return m_elim_aux; }
bool reach_dnf() const { return m_reach_dnf; } bool reach_dnf() const { return m_reach_dnf; }
bool use_bg_invs() const { return m_use_bg_invs; } bool use_bg_invs() const { return m_use_bg_invs; }
bool do_subsume() const { return m_gg_subsume; }
ast_manager &get_ast_manager() const { return m; } ast_manager &get_ast_manager() const { return m; }
manager &get_manager() { return m_pm; } manager &get_manager() { return m_pm; }
const manager &get_manager() const { return m_pm; } const manager &get_manager() const { return m_pm; }
decl2rel const &get_pred_transformers() const { return m_rels; } decl2rel const &get_pred_transformers() const { return m_rels; }
pred_transformer& get_pred_transformer(func_decl* p) const {return *m_rels.find(p);} pred_transformer &get_pred_transformer(func_decl *p) const {
return *m_rels.find(p);
}
datalog::context &get_datalog_context() const { datalog::context &get_datalog_context() const {
SASSERT(m_context); return *m_context; SASSERT(m_context);
return *m_context;
} }
void update_rules(datalog::rule_set &rules); void update_rules(datalog::rule_set &rules);
lbool solve(unsigned from_lvl = 0); lbool solve(unsigned from_lvl = 0);
lbool solve_from_lvl(unsigned from_lvl); lbool solve_from_lvl(unsigned from_lvl);
expr_ref get_answer(); expr_ref get_answer();
/** /**
* get bottom-up (from query) sequence of ground predicate instances * get bottom-up (from query) sequence of ground predicate instances
@ -1136,6 +1346,8 @@ public:
bool is_inductive(); bool is_inductive();
// close all parents of may pob when gas runs out
void close_all_may_parents(pob_ref node);
// three different solvers with three different sets of parameters // three different solvers with three different sets of parameters
// different solvers are used for different types of queries in spacer // different solvers are used for different types of queries in spacer
@ -1145,5 +1357,4 @@ public:
}; };
inline bool pred_transformer::use_native_mbp() { return ctx.use_native_mbp(); } inline bool pred_transformer::use_native_mbp() { return ctx.use_native_mbp(); }
} } // namespace spacer

View file

@ -0,0 +1,389 @@
/**++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_convex_closure.cpp
Abstract:
Compute convex closure of polyhedra
Author:
Hari Govind
Arie Gurfinkel
Notes:
--*/
#include "muz/spacer/spacer_convex_closure.h"
#include "ast/rewriter/th_rewriter.h"
namespace {
bool is_int_matrix(const spacer::spacer_matrix &matrix) {
rational val;
for (unsigned i = 0, rows = matrix.num_rows(); i < rows; i++) {
for (unsigned j = 0, cols = matrix.num_cols(); j < cols; j++)
if (!matrix.get(i, j).is_int()) return false;
}
return true;
}
bool is_sorted(const vector<rational> &data) {
for (unsigned i = 0; i < data.size() - 1; i++) {
if (!(data[i] >= data[i + 1])) return false;
}
return true;
}
/// Check whether all elements of \p data are congruent modulo \p m
bool is_congruent_mod(const vector<rational> &data, const rational &m) {
SASSERT(data.size() > 0);
rational p = data[0] % m;
for (auto k : data)
if (k % m != p) return false;
return true;
}
app *mk_bvadd(bv_util &bv, unsigned num, expr *const *args) {
if (num == 0) return nullptr;
if (num == 1) return is_app(args[0]) ? to_app(args[0]) : nullptr;
if (num == 2) { return bv.mk_bv_add(args[0], args[1]); }
/// XXX no mk_bv_add for n-ary bv_add
return bv.get_manager().mk_app(bv.get_fid(), OP_BADD, num, args);
}
} // namespace
namespace spacer {
convex_closure::convex_closure(ast_manager &_m)
: m(_m), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_implicit(true), m_dim(0),
m_data(0, 0), m_col_vars(m), m_kernel(m_data), m_alphas(m),
m_implicit_cc(m), m_explicit_cc(m) {
m_kernel.set_plugin(mk_simplex_kernel_plugin());
}
void convex_closure::reset(unsigned n_cols) {
m_dim = n_cols;
m_kernel.reset();
m_data.reset(m_dim);
m_col_vars.reset();
m_col_vars.reserve(m_dim);
m_dead_cols.reset();
m_dead_cols.reserve(m_dim, false);
m_alphas.reset();
m_bv_sz = 0;
m_enable_implicit = true;
}
void convex_closure::collect_statistics(statistics &st) const {
st.update("time.spacer.solve.reach.gen.global.cc",
m_st.watch.get_seconds());
st.update("SPACER cc num dim reduction success", m_st.m_num_reductions);
st.update("SPACER cc max reduced dim", m_st.m_max_dim);
m_kernel.collect_statistics(st);
}
// call m_kernel to reduce dimensions of m_data
// return the rank of m_data
unsigned convex_closure::reduce() {
if (m_dim <= 1) return m_dim;
bool has_kernel = m_kernel.compute_kernel();
if (!has_kernel) {
TRACE("cvx_dbg",
tout << "No linear dependencies between pattern vars\n";);
return m_dim;
}
const spacer_matrix &ker = m_kernel.get_kernel();
SASSERT(ker.num_rows() > 0);
SASSERT(ker.num_rows() <= m_dim);
SASSERT(ker.num_cols() == m_dim + 1);
// m_dim - ker.num_rows() is the number of variables that have no linear
// dependencies
for (auto v : m_kernel.get_basic_vars())
// XXX sometimes a constant can be basic, need to find a way to
// switch it to var
if (v < m_dead_cols.size()) m_dead_cols[v] = true;
return m_dim - ker.num_rows();
}
// For row \p row in m_kernel, construct the equality:
//
// row * m_col_vars = 0
//
// In the equality, exactly one variable from m_col_vars is on the lhs
void convex_closure::kernel_row2eq(const vector<rational> &row, expr_ref &out) {
expr_ref_buffer lhs(m);
expr_ref e1(m);
bool is_int = false;
for (unsigned i = 0, sz = row.size(); i < sz; ++i) {
rational val_i = row.get(i);
if (val_i.is_zero()) continue;
SASSERT(val_i.is_int());
if (i < sz - 1) {
e1 = m_col_vars.get(i);
is_int |= m_arith.is_int(e1);
mul_by_rat(e1, val_i);
} else {
e1 = mk_numeral(val_i, is_int);
}
lhs.push_back(e1);
}
e1 = !has_bv() ? mk_add(lhs) : mk_bvadd(m_bv, lhs.size(), lhs.data());
e1 = m.mk_eq(e1, mk_numeral(rational::zero(), is_int));
// revisit this simplification step, it is here only to prevent/simplify
// formula construction everywhere else
params_ref params;
params.set_bool("som", true);
params.set_bool("flat", true);
th_rewriter rw(m, params);
rw(e1, out);
}
/// Generates linear equalities implied by m_data
///
/// the linear equalities are m_kernel * m_col_vars = 0 (where * is matrix
/// multiplication) the new equalities are stored in m_col_vars for each row
/// [0, 1, 0, 1 , 1] in m_kernel, the equality v1 = -1*v3 + -1*1 is
/// constructed and stored at index 1 of m_col_vars
void convex_closure::kernel2fmls(expr_ref_vector &out) {
// assume kernel has been computed already
const spacer_matrix &kern = m_kernel.get_kernel();
SASSERT(kern.num_rows() > 0);
TRACE("cvx_dbg", kern.display(tout););
expr_ref eq(m);
for (unsigned i = kern.num_rows(); i > 0; i--) {
auto &row = kern.get_row(i - 1);
kernel_row2eq(row, eq);
out.push_back(eq);
}
}
expr *convex_closure::mk_add(const expr_ref_buffer &vec) {
SASSERT(!vec.empty());
expr_ref s(m);
if (vec.size() == 1) {
return vec[0];
} else if (vec.size() > 1) {
return m_arith.mk_add(vec.size(), vec.data());
}
UNREACHABLE();
return nullptr;
}
expr *convex_closure::mk_numeral(const rational &n, bool is_int) {
if (!has_bv())
return m_arith.mk_numeral(n, is_int);
else
return m_bv.mk_numeral(n, m_bv_sz);
}
/// Construct the equality ((m_alphas . m_data[*][i]) = m_col_vars[i])
///
/// Where . is the dot product, m_data[*][i] is
/// the ith column of m_data. Add the result to res_vec.
void convex_closure::cc_col2eq(unsigned col, expr_ref_vector &out) {
SASSERT(!has_bv());
expr_ref_buffer sum(m);
for (unsigned row = 0, sz = m_data.num_rows(); row < sz; row++) {
expr_ref alpha(m);
auto n = m_data.get(row, col);
if (n.is_zero()) {
; // noop
} else {
alpha = m_alphas.get(row);
if (!n.is_one()) {
alpha = m_arith.mk_mul(
m_arith.mk_numeral(n, false /* is_int */), alpha);
}
}
if (alpha) sum.push_back(alpha);
}
SASSERT(!sum.empty());
expr_ref s(m);
s = mk_add(sum);
expr_ref v(m);
expr *vi = m_col_vars.get(col);
v = m_arith.is_int(vi) ? m_arith.mk_to_real(vi) : vi;
out.push_back(m.mk_eq(s, v));
}
void convex_closure::cc2fmls(expr_ref_vector &out) {
sort_ref real_sort(m_arith.mk_real(), m);
expr_ref zero(m_arith.mk_real(rational::zero()), m);
for (unsigned row = 0, sz = m_data.num_rows(); row < sz; row++) {
if (row >= m_alphas.size()) {
m_alphas.push_back(m.mk_fresh_const("a!cc", real_sort));
}
SASSERT(row < m_alphas.size());
// forall j :: alpha_j >= 0
out.push_back(m_arith.mk_ge(m_alphas.get(row), zero));
}
for (unsigned k = 0, sz = m_col_vars.size(); k < sz; k++) {
if (m_col_vars.get(k) && !m_dead_cols[k]) cc_col2eq(k, out);
}
//(\Sum j . m_new_vars[j]) = 1
out.push_back(m.mk_eq(
m_arith.mk_add(m_data.num_rows(),
reinterpret_cast<expr *const *>(m_alphas.data())),
m_arith.mk_real(rational::one())));
}
#define MAX_DIV_BOUND 101
// check whether \exists m, d s.t data[i] mod m = d. Returns the largest m and
// corresponding d
// TODO: find the largest divisor, not the smallest.
// TODO: improve efficiency
bool convex_closure::infer_div_pred(const vector<rational> &data, rational &m,
rational &d) {
TRACE("cvx_dbg_verb", {
tout << "computing div constraints for ";
for (rational r : data) tout << r << " ";
tout << "\n";
});
SASSERT(data.size() > 1);
SASSERT(is_sorted(data));
m = rational(2);
// special handling for even/odd
if (is_congruent_mod(data, m)) {
mod(data.back(), m, d);
return true;
}
// hard cut off to save time
rational bnd(MAX_DIV_BOUND);
rational big = data.back();
// AG: why (m < big)? Note that 'big' is the smallest element of data
for (; m < big && m < bnd; m++) {
if (is_congruent_mod(data, m)) break;
}
if (m >= big) return false;
if (m == bnd) return false;
mod(data[0], m, d);
SASSERT(d >= rational::zero());
TRACE("cvx_dbg_verb", tout << "div constraint generated. cf " << m
<< " and off " << d << "\n";);
return true;
}
bool convex_closure::compute() {
scoped_watch _w_(m_st.watch);
SASSERT(is_int_matrix(m_data));
unsigned rank = reduce();
// store dim var before rewrite
expr_ref var(m_col_vars.get(0), m);
if (rank < dims()) {
m_st.m_num_reductions++;
kernel2fmls(m_explicit_cc);
TRACE("cvx_dbg", tout << "Linear equalities true of the matrix "
<< mk_and(m_explicit_cc) << "\n";);
}
m_st.m_max_dim = std::max(m_st.m_max_dim, rank);
if (rank == 0) {
// AG: Is this possible?
return false;
} else if (rank > 1) {
if (m_enable_implicit) {
TRACE("subsume", tout << "Computing syntactic convex closure\n";);
cc2fmls(m_implicit_cc);
} else {
return false;
}
return true;
}
SASSERT(rank == 1);
cc_1dim(var, m_explicit_cc);
return true;
}
// construct the formula result_var <= bnd or result_var >= bnd
expr *convex_closure::mk_le_ge(expr *v, rational n, bool is_le) {
if (m_arith.is_int_real(v)) {
expr *en = m_arith.mk_numeral(n, m_arith.is_int(v));
return is_le ? m_arith.mk_le(v, en) : m_arith.mk_ge(v, en);
} else if (m_bv.is_bv(v)) {
expr *en = m_bv.mk_numeral(n, m_bv.get_bv_size(v->get_sort()));
return is_le ? m_bv.mk_ule(v, en) : m_bv.mk_ule(en, v);
} else {
UNREACHABLE();
}
return nullptr;
}
void convex_closure::cc_1dim(const expr_ref &var, expr_ref_vector &out) {
// XXX assumes that var corresponds to col 0
// The convex closure over one dimension is just a bound
vector<rational> data;
m_data.get_col(0, data);
auto gt_proc = [](rational const &x, rational const &y) -> bool {
return x > y;
};
std::sort(data.begin(), data.end(), gt_proc);
// -- compute LB <= var <= UB
expr_ref res(m);
res = var;
// upper-bound
out.push_back(mk_le_ge(res, data[0], true));
// lower-bound
out.push_back(mk_le_ge(res, data.back(), false));
// -- compute divisibility constraints
rational cr, off;
// add div constraints for all variables.
for (unsigned j = 0; j < m_data.num_cols(); j++) {
auto *v = m_col_vars.get(j);
if (v && (m_arith.is_int(v) || m_bv.is_bv(v))) {
data.reset();
m_data.get_col(j, data);
std::sort(data.begin(), data.end(), gt_proc);
if (infer_div_pred(data, cr, off)) {
out.push_back(mk_eq_mod(v, cr, off));
}
}
}
}
expr *convex_closure::mk_eq_mod(expr *v, rational d, rational r) {
expr *res = nullptr;
if (m_arith.is_int(v)) {
res = m.mk_eq(m_arith.mk_mod(v, m_arith.mk_int(d)), m_arith.mk_int(r));
} else if (m_bv.is_bv(v)) {
res = m.mk_eq(m_bv.mk_bv_urem(v, m_bv.mk_numeral(d, m_bv_sz)),
m_bv.mk_numeral(r, m_bv_sz));
} else {
UNREACHABLE();
}
return res;
}
} // namespace spacer

View file

@ -0,0 +1,187 @@
#pragma once
/**++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_convex_closure.h
Abstract:
Compute convex closure of polyhedra
Author:
Hari Govind
Arie Gurfinkel
Notes:
--*/
#include "ast/arith_decl_plugin.h"
#include "ast/ast.h"
#include "ast/ast_util.h"
#include "muz/spacer/spacer_arith_kernel.h"
#include "muz/spacer/spacer_matrix.h"
#include "muz/spacer/spacer_util.h"
#include "util/statistics.h"
namespace spacer {
/// Computes a convex closure of a set of points
class convex_closure {
struct stats {
unsigned m_num_reductions;
unsigned m_max_dim;
stopwatch watch;
stats() { reset(); }
void reset() {
m_num_reductions = 0;
m_max_dim = 0;
watch.reset();
}
};
stats m_st;
ast_manager &m;
arith_util m_arith;
bv_util m_bv;
// size of all bit vectors in m_col_vars
unsigned m_bv_sz;
// Enable computation of implicit syntactic convex closure
bool m_enable_implicit;
// number of columns in \p m_data
unsigned m_dim;
// A vector of rational valued points
spacer_matrix m_data;
// Variables naming columns in `m_data`
// \p m_col_vars[k] is a var for column \p k
expr_ref_vector m_col_vars;
vector<bool> m_dead_cols;
// Kernel of \p m_data
// Set at the end of computation
spacer_arith_kernel m_kernel;
// Free variables introduced by syntactic convex closure
// These variables are always of sort Real
expr_ref_vector m_alphas;
expr_ref_vector m_implicit_cc;
expr_ref_vector m_explicit_cc;
/// Reduces dimension of \p m_data and returns its rank
unsigned reduce();
/// Constructs an equality corresponding to a given row in the kernel
///
/// The equality is conceptually corresponds to
/// row * m_col_vars = 0
/// where row is a row vector and m_col_vars is a column vector.
/// However, the equality is put in a form so that exactly one variable from
/// \p m_col_vars is on the LHS
void kernel_row2eq(const vector<rational> &row, expr_ref &out);
/// Construct all linear equations implied by points in \p m_data
/// This is defined by \p m_kernel * m_col_vars = 0
void kernel2fmls(expr_ref_vector &out);
/// Compute syntactic convex closure of \p m_data
void cc2fmls(expr_ref_vector &out);
/// Construct the equality ((m_alphas . m_data[*][k]) = m_col_vars[k])
///
/// \p m_data[*][k] is the kth column of m_data
/// The equality is added to \p out.
void cc_col2eq(unsigned k, expr_ref_vector &out);
/// Compute one dimensional convex closure over \p var
///
/// \p var is the dimension over which convex closure is computed
/// Result is stored in \p out
void cc_1dim(const expr_ref &var, expr_ref_vector &out);
/// Computes div constraint implied by a set of data points
///
/// Finds the largest numbers \p m, \p d such that \p m_data[i] mod m = d
/// Returns true if successful
bool infer_div_pred(const vector<rational> &data, rational &m, rational &d);
/// Constructs a formula \p var ~ n , where ~ = is_le ? <= : >=
expr *mk_le_ge(expr *var, rational n, bool is_le);
expr *mk_add(const expr_ref_buffer &vec);
expr *mk_numeral(const rational &n, bool is_int);
/// Returns equality (v = r mod d)
expr *mk_eq_mod(expr *v, rational d, rational r);
bool has_bv() { return m_bv_sz > 0; }
public:
convex_closure(ast_manager &_m);
/// Resets all data points
///
/// n_cols is the number of dimensions of new expected data points
void reset(unsigned n_cols);
/// Turn support for fixed sized bit-vectors of size \p sz
///
/// Disables syntactic convex closure as a side-effect
void set_bv(unsigned sz) {
SASSERT(sz > 0);
m_bv_sz = sz;
m_enable_implicit = false;
}
/// \brief Name dimension \p i with a variable \p v.
void set_col_var(unsigned i, expr *v) {
SASSERT(i < dims());
SASSERT(m_col_vars.get(i) == nullptr);
m_col_vars[i] = v;
}
/// \brief Return number of dimensions of each point
unsigned dims() const { return m_dim; }
/// \brief Add an n-dimensional point to convex closure
void add_row(const vector<rational> &point) {
SASSERT(point.size() == dims());
m_data.add_row(point);
};
bool operator()() { return this->compute(); }
bool compute();
bool has_implicit() { return !m_implicit_cc.empty(); }
bool has_explicit() { return !m_explicit_cc.empty(); }
/// Returns the implicit component of convex closure (if available)
///
/// Implicit component contains constants from get_alphas() that are
/// implicitly existentially quantified
const expr_ref_vector &get_implicit() { return m_implicit_cc; }
/// \brief Return implicit constants in implicit convex closure
const expr_ref_vector &get_alphas() const { return m_alphas; }
/// Returns the explicit component of convex closure (if available)
///
/// The explicit component is in term of column variables
const expr_ref_vector &get_explicit() { return m_explicit_cc; }
/// Returns constants used to name columns
///
/// Explicit convex closure is in terms of these variables
const expr_ref_vector &get_col_vars() { return m_col_vars; }
void collect_statistics(statistics &st) const;
void reset_statistics() { m_st.reset(); }
};
} // namespace spacer

View file

@ -0,0 +1,229 @@
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_expand_bnd_generalizer.cpp
Abstract:
Strengthen lemmas by changing numeral constants inside arithmetic literals
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include "muz/spacer/spacer_expand_bnd_generalizer.h"
#include "ast/for_each_expr.h"
#include "ast/rewriter/expr_safe_replace.h"
namespace {
/// Returns true if \p e is arithmetic comparison
///
/// Returns true if \p e is of the form \p lhs op rhs, where
/// op in {<=, <, >, >=}, and rhs is a numeric value
bool is_arith_comp(const expr *e, expr *&lhs, rational &rhs, bool &is_int,
ast_manager &m) {
arith_util arith(m);
expr *e1, *e2;
if (m.is_not(e, e1)) return is_arith_comp(e1, lhs, rhs, is_int, m);
if (arith.is_le(e, lhs, e2) || arith.is_lt(e, lhs, e2) ||
arith.is_ge(e, lhs, e2) || arith.is_gt(e, lhs, e2))
return arith.is_numeral(e2, rhs, is_int);
return false;
}
bool is_arith_comp(const expr *e, expr *&lhs, rational &rhs, ast_manager &m) {
bool is_int;
return is_arith_comp(e, lhs, rhs, is_int, m);
}
bool is_arith_comp(const expr *e, rational &rhs, ast_manager &m) {
expr *lhs;
return is_arith_comp(e, lhs, rhs, m);
}
/// If \p lit is of the form (x op v), replace v with num
///
/// Supports arithmetic literals where op is <, <=, >, >=, or negation
bool update_bound(const expr *lit, rational num, expr_ref &res,
bool negate = false) {
SASSERT(is_app(lit));
ast_manager &m = res.get_manager();
expr *e1;
if (m.is_not(lit, e1)) { return update_bound(e1, num, res, !negate); }
arith_util arith(m);
expr *lhs;
rational val;
bool is_int;
if (!is_arith_comp(lit, lhs, val, is_int, m)) return false;
res = m.mk_app(to_app(lit)->get_decl(), lhs, arith.mk_numeral(num, is_int));
if (negate) { m.mk_not(res); }
return true;
}
} // namespace
namespace spacer {
namespace collect_rationals_ns {
/// Finds rationals in an expression
struct proc {
ast_manager &m;
arith_util m_arith;
vector<rational> &m_res;
proc(ast_manager &a_m, vector<rational> &res)
: m(a_m), m_arith(m), m_res(res) {}
void operator()(expr *n) const {}
void operator()(app *n) {
rational val;
if (m_arith.is_numeral(n, val)) m_res.push_back(val);
}
};
} // namespace collect_rationals_ns
/// Extract all numerals from an expression
void collect_rationals(expr *e, vector<rational> &res, ast_manager &m) {
collect_rationals_ns::proc proc(m, res);
quick_for_each_expr(proc, e);
}
lemma_expand_bnd_generalizer::lemma_expand_bnd_generalizer(context &ctx)
: lemma_generalizer(ctx), m(ctx.get_ast_manager()), m_arith(m) {
// -- collect rationals from initial condition and transition relation
for (auto &kv : ctx.get_pred_transformers()) {
collect_rationals(kv.m_value->init(), m_values, m);
collect_rationals(kv.m_value->transition(), m_values, m);
}
// remove duplicates
std::sort(m_values.begin(), m_values.end());
auto last = std::unique(m_values.begin(), m_values.end());
for (unsigned i = 0, sz = std::distance(last, m_values.end()); i < sz; ++i)
m_values.pop_back();
}
void lemma_expand_bnd_generalizer::operator()(lemma_ref &lemma) {
scoped_watch _w_(m_st.watch);
if (!lemma->get_pob()->is_expand_bnd_enabled()) return;
expr_ref_vector cube(lemma->get_cube());
// -- temporary stores a core
expr_ref_vector core(m);
expr_ref lit(m), new_lit(m);
rational bnd;
// for every literal
for (unsigned i = 0, sz = cube.size(); i < sz; i++) {
lit = cube.get(i);
if (m.is_true(lit)) continue;
if (!is_arith_comp(lit, bnd, m)) continue;
TRACE("expand_bnd", tout << "Attempting to expand " << lit << " inside "
<< cube << "\n";);
// for every value
for (rational n : m_values) {
if (!is_interesting(lit, bnd, n)) continue;
m_st.atmpts++;
TRACE("expand_bnd", tout << "Attempting to expand " << lit
<< " with numeral " << n << "\n";);
// -- update bound on lit
VERIFY(update_bound(lit, n, new_lit));
// -- update lit to new_lit for a new candidate lemma
cube[i] = new_lit;
core.reset();
core.append(cube);
// -- check that candidate is inductive
if (check_inductive(lemma, core)) {
expr_fast_mark1 in_core;
for (auto *e : core) in_core.mark(e);
for (unsigned i = 0, sz = cube.size(); i < sz; ++i) {
if (!in_core.is_marked(cube.get(i))) cube[i] = m.mk_true();
}
// move to next literal if the current has been removed
if (!in_core.is_marked(new_lit)) break;
} else {
// -- candidate not inductive, restore original lit
cube[i] = lit;
}
}
}
// Currently, we allow for only one round of expand bound per lemma
// Mark lemma as already expanded so that it is not generalized in this way
// again
lemma->get_pob()->disable_expand_bnd_gen();
}
/// Check whether \p candidate is a possible generalization for \p lemma.
/// Side-effect: update \p lemma with the new candidate
bool lemma_expand_bnd_generalizer::check_inductive(lemma_ref &lemma,
expr_ref_vector &candidate) {
TRACE("expand_bnd_verb",
tout << "Attempting to update lemma with " << candidate << "\n";);
unsigned uses_level = 0;
auto &pt = lemma->get_pob()->pt();
bool res = pt.check_inductive(lemma->level(), candidate, uses_level,
lemma->weakness());
if (res) {
m_st.success++;
lemma->update_cube(lemma->get_pob(), candidate);
lemma->set_level(uses_level);
TRACE("expand_bnd", tout << "expand_bnd succeeded with "
<< mk_and(candidate) << " at level "
<< uses_level << "\n";);
}
return res;
}
/// Check whether lit ==> lit[val |--> n] (barring special cases). That is,
/// whether \p lit becomes weaker if \p val is replaced with \p n
///
/// \p lit has to be of the form t <= v where v is a numeral.
/// Special cases:
/// In the trivial case in which \p val == \p n, return false.
/// if lit is an equality or the negation of an equality, return true.
bool lemma_expand_bnd_generalizer::is_interesting(const expr *lit, rational val,
rational new_val) {
SASSERT(lit);
// the only case in which negation and non negation agree
if (val == new_val) return false;
if (m.is_eq(lit)) return true;
// negation is the actual negation modulo val == n
expr *e1;
if (m.is_not(lit, e1)) {
return m.is_eq(lit) || !is_interesting(e1, val, new_val);
}
SASSERT(val != new_val);
SASSERT(is_app(lit));
if (to_app(lit)->get_family_id() != m_arith.get_family_id()) return false;
switch (to_app(lit)->get_decl_kind()) {
case OP_LE:
case OP_LT:
return new_val > val;
case OP_GT:
case OP_GE:
return new_val < val;
default:
return false;
}
}
void lemma_expand_bnd_generalizer::collect_statistics(statistics &st) const {
st.update("time.spacer.solve.reach.gen.expand", m_st.watch.get_seconds());
st.update("SPACER expand_bnd attmpts", m_st.atmpts);
st.update("SPACER expand_bnd success", m_st.success);
}
} // namespace spacer

View file

@ -0,0 +1,68 @@
#pragma once
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_expand_bnd_generalizer.h
Abstract:
Strengthen lemmas by changing numeral constants inside arithmetic literals
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include "muz/spacer/spacer_context.h"
namespace spacer {
class lemma_expand_bnd_generalizer : public lemma_generalizer {
struct stats {
unsigned atmpts;
unsigned success;
stopwatch watch;
stats() { reset(); }
void reset() {
watch.reset();
atmpts = 0;
success = 0;
}
};
stats m_st;
ast_manager &m;
arith_util m_arith;
/// A set of numeral values that can be used to expand bound
vector<rational> m_values;
public:
lemma_expand_bnd_generalizer(context &ctx);
~lemma_expand_bnd_generalizer() override {}
void operator()(lemma_ref &lemma) override;
void collect_statistics(statistics &st) const override;
void reset_statistics() override { m_st.reset(); }
private:
/// Check whether lit ==> lit[val |--> n] (barring special cases). That is,
/// whether \p lit becomes weaker if \p val is replaced with \p n
///
/// \p lit has to be of the form t <= v where v is a numeral.
/// Special cases:
/// In the trivial case in which \p val == \p n, return false.
/// if lit is an equality or the negation of an equality, return true.
bool is_interesting(const expr *lit, rational val, rational n);
/// check whether \p conj is a possible generalization for \p lemma.
/// update \p lemma if it is.
bool check_inductive(lemma_ref &lemma, expr_ref_vector &candiate);
};
} // namespace spacer

View file

@ -21,6 +21,7 @@ Revision History:
#include "ast/arith_decl_plugin.h" #include "ast/arith_decl_plugin.h"
#include "muz/spacer/spacer_context.h" #include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_expand_bnd_generalizer.h"
namespace spacer { namespace spacer {
@ -174,5 +175,10 @@ class limit_num_generalizer : public lemma_generalizer {
void collect_statistics(statistics &st) const override; void collect_statistics(statistics &st) const override;
void reset_statistics() override { m_st.reset(); } void reset_statistics() override { m_st.reset(); }
}; };
} // namespace spacer
lemma_generalizer *
alloc_lemma_inductive_generalizer(spacer::context &ctx,
bool only_array_eligible = false,
bool enable_literal_weakening = true);
} // namespace spacer

View file

@ -0,0 +1,796 @@
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_global_generalizer.cpp
Abstract:
Global Guidance for Spacer
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include "muz/spacer/spacer_global_generalizer.h"
#include "ast/arith_decl_plugin.h"
#include "ast/ast_util.h"
#include "ast/for_each_expr.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "muz/spacer/spacer_cluster.h"
#include "muz/spacer/spacer_concretize.h"
#include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_manager.h"
#include "muz/spacer/spacer_matrix.h"
#include "muz/spacer/spacer_util.h"
#include "smt/smt_solver.h"
using namespace spacer;
namespace {
// LOCAL HELPER FUNCTIONS IN ANONYMOUS NAMESPACE
class to_real_stripper {
ast_manager &m;
arith_util m_arith;
public:
to_real_stripper(ast_manager &_m) : m(_m), m_arith(m) {}
bool operator()(expr_ref &e, unsigned depth = 8) {
rational num;
if (m_arith.is_int(e)) return true;
if (depth == 0) return false;
if (!is_app(e)) return false;
if (m_arith.is_to_real(e)) {
// strip to_real()
e = to_app(e)->get_arg(0);
return true;
} else if (m_arith.is_numeral(e, num)) {
// convert number to an integer
if (denominator(num).is_one()) {
e = m_arith.mk_int(num);
return true;
} else {
return false;
}
}
app *e_app = to_app(e);
expr_ref_buffer args(m);
expr_ref kid(m);
bool dirty = false;
for (unsigned i = 0, sz = e_app->get_num_args(); i < sz; ++i) {
auto *arg = e_app->get_arg(i);
kid = arg;
if (this->operator()(kid, depth - 1)) {
dirty |= (kid.get() != arg);
args.push_back(std::move(kid));
} else {
return false;
}
}
if (dirty)
e = m.mk_app(e_app->get_family_id(), e_app->get_decl_kind(),
args.size(), args.data());
return true;
}
bool operator()(expr_ref_vector &vec, unsigned depth = 8) {
bool res = true;
expr_ref e(m);
for (unsigned i = 0, sz = vec.size(); res && i < sz; ++i) {
e = vec.get(i);
res = this->operator()(e, depth);
if (res) { vec[i] = e; }
}
return res;
}
};
// Check whether \p sub contains a mapping to a bv_numeral.
// return bv_size of the bv_numeral in the first such mapping.
bool contains_bv(ast_manager &m, const substitution &sub, unsigned &sz) {
bv_util m_bv(m);
std::pair<unsigned, unsigned> v;
expr_offset r;
rational num;
for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) {
sub.get_binding(j, v, r);
if (m_bv.is_numeral(r.get_expr(), num, sz)) return true;
}
return false;
}
// Check whether 1) all expressions in the range of \p sub are bv_numerals 2)
// all bv_numerals in range are of size sz
bool all_same_sz(ast_manager &m, const substitution &sub, unsigned sz) {
bv_util m_bv(m);
std::pair<unsigned, unsigned> v;
expr_offset r;
rational num;
unsigned n_sz;
for (unsigned j = 0; j < sub.get_num_bindings(); j++) {
sub.get_binding(j, v, r);
if (!m_bv.is_numeral(r.get_expr(), num, n_sz) || n_sz != sz)
return false;
}
return true;
}
} // namespace
namespace spacer {
lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool ground_pob)
: m(a_m), m_arith(m), m_bv(m), m_tags(m), m_used_tags(0), m_col_names(m),
m_ground_pob(ground_pob) {
scoped_ptr<solver_factory> factory(
mk_smt_strategic_solver_factory(symbol::null));
m_solver = (*factory)(m, params_ref::get_empty(), false, true, false,
symbol::null);
}
app *lemma_global_generalizer::subsumer::mk_fresh_tag() {
if (m_used_tags == m_tags.size()) {
auto *bool_sort = m.mk_bool_sort();
// -- create 4 new tags
m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort));
m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort));
m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort));
m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort));
}
return m_tags.get(m_used_tags++);
}
lemma_global_generalizer::lemma_global_generalizer(context &ctx)
: lemma_generalizer(ctx), m(ctx.get_ast_manager()),
m_subsumer(m, ctx.use_ground_pob()), m_do_subsume(ctx.do_subsume()) {}
void lemma_global_generalizer::operator()(lemma_ref &lemma) {
scoped_watch _w_(m_st.watch);
generalize(lemma);
}
void lemma_global_generalizer::subsumer::mk_col_names(const lemma_cluster &lc) {
expr_offset r;
std::pair<unsigned, unsigned> v;
auto &lemmas = lc.get_lemmas();
SASSERT(!lemmas.empty());
const substitution &sub = lemmas.get(0).get_sub();
m_col_names.reserve(sub.get_num_bindings());
for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) {
// get var id (sub is in reverse order)
sub.get_binding(sz - 1 - j, v, r);
auto *sort = r.get_expr()->get_sort();
if (!m_col_names.get(j) || m_col_names.get(j)->get_sort() != sort) {
// create a fresh skolem constant for the jth variable
// reuse variables if they are already here and have matching sort
m_col_names[j] = m.mk_fresh_const("mrg_cvx!!", sort);
}
}
// -- lcm corresponds to a column, reset them since names have potentially
// changed
// -- this is a just-in-case
m_col_lcm.reset();
}
// Populate m_cvx_cls by 1) collecting all substitutions in the cluster \p lc
// 2) normalizing them to integer numerals
void lemma_global_generalizer::subsumer::setup_cvx_closure(
convex_closure &cc, const lemma_cluster &lc) {
expr_offset r;
std::pair<unsigned, unsigned> v;
mk_col_names(lc);
const lemma_info_vector &lemmas = lc.get_lemmas();
m_col_lcm.reset();
unsigned n_vars = 0;
rational num;
bool is_first = true;
for (const auto &lemma : lemmas) {
const substitution &sub = lemma.get_sub();
if (is_first) {
n_vars = sub.get_num_bindings();
m_col_lcm.reserve(n_vars, rational::one());
is_first = false;
}
for (unsigned j = 0; j < n_vars; j++) {
sub.get_binding(n_vars - 1 - j, v, r);
if (is_numeral(r.get_expr(), num)) {
m_col_lcm[j] = lcm(m_col_lcm.get(j), abs(denominator(num)));
}
}
}
cc.reset(n_vars);
unsigned bv_width;
if (contains_bv(m, lc.get_lemmas()[0].get_sub(), bv_width)) {
cc.set_bv(bv_width);
}
for (unsigned j = 0; j < n_vars; ++j)
cc.set_col_var(j, mk_rat_mul(m_col_lcm.get(j), m_col_names.get(j)));
vector<rational> row;
for (const auto &lemma : lemmas) {
row.reset();
const substitution &sub = lemma.get_sub();
for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) {
sub.get_binding(sz - 1 - j, v, r);
VERIFY(is_numeral(r.get_expr(), num));
row.push_back(m_col_lcm.get(j) * num);
}
// -- add normalized row to convex closure
cc.add_row(row);
}
}
/// Find a representative for \p c
// TODO: replace with a symbolic representative
expr *lemma_global_generalizer::subsumer::find_repr(const model_ref &mdl,
const app *c) {
return mdl->get_const_interp(c->get_decl());
}
/// Skolemize implicitly existentially quantified constants
///
/// Constants in \p m_dim_frsh_cnsts are existentially quantified in \p f. They
/// are replaced by specific skolem constants. The \p out vector is populated
/// with corresponding instantiations. Currently, instantiations are values
/// chosen from the model
void lemma_global_generalizer::subsumer::skolemize_for_quic3(
expr_ref &f, const model_ref &mdl, app_ref_vector &out) {
unsigned idx = out.size();
app_ref sk(m);
expr_ref eval(m);
expr_safe_replace sub(m);
expr_ref_vector f_cnsts(m);
spacer::collect_uninterp_consts(f, f_cnsts);
expr_fast_mark2 marks;
for (auto *c : f_cnsts) { marks.mark(c); }
for (unsigned i = 0, sz = m_col_names.size(); i < sz; i++) {
app *c = m_col_names.get(i);
if (!marks.is_marked(c)) continue;
SASSERT(m_arith.is_int(c));
// Make skolem constants for ground pob
sk = mk_zk_const(m, i + idx, c->get_sort());
eval = find_repr(mdl, c);
SASSERT(is_app(eval));
out.push_back(to_app(eval));
sub.insert(c, sk);
}
sub(f.get(), f);
TRACE("subsume", tout << "skolemized into " << f << "\n";);
m_col_names.reset();
}
bool lemma_global_generalizer::subsumer::find_model(
const expr_ref_vector &cc, const expr_ref_vector &alphas, expr *bg,
model_ref &out_model) {
// push because we re-use the solver
solver::scoped_push _sp(*m_solver);
if (bg) m_solver->assert_expr(bg);
// -- assert syntactic convex closure constraints
m_solver->assert_expr(cc);
// if there are alphas, we have syntactic convex closure
if (!alphas.empty()) {
SASSERT(alphas.size() >= 2);
// try to get an interior point in convex closure that also satisfies bg
{
// push because this might be unsat
solver::scoped_push _sp2(*m_solver);
expr_ref zero(m_arith.mk_real(0), m);
for (auto *alpha : alphas) {
m_solver->assert_expr(m_arith.mk_gt(alpha, zero));
}
auto res = m_solver->check_sat();
if (res == l_true) {
m_solver->get_model(out_model);
return true;
}
}
}
// failed, try to get any point in convex closure
auto res = m_solver->check_sat();
if (res == l_true) {
m_solver->get_model(out_model);
return true;
}
UNREACHABLE();
// something went wrong and there is no model, even though one was expected
return false;
}
/// Returns false if subsumption is not supported for \p lc
bool lemma_global_generalizer::subsumer::is_handled(const lemma_cluster &lc) {
// check whether all substitutions are to bv_numerals
unsigned sz = 0;
bool bv_clus = contains_bv(m, lc.get_lemmas()[0].get_sub(), sz);
// If there are no BV numerals, cases are handled.
// TODO: put restriction on Arrays, non linear arithmetic etc
if (!bv_clus) return true;
if (!all_same_sz(m, lc.get_lemmas()[0].get_sub(), sz)) {
TRACE("subsume",
tout << "cannot compute cvx cls of different size variables\n";);
return false;
}
return true;
}
void lemma_global_generalizer::subsumer::reset() {
m_used_tags = 0;
m_col_lcm.reset();
}
bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc,
expr_ref_vector &new_post,
app_ref_vector &bindings) {
if (!is_handled(lc)) return false;
convex_closure cvx_closure(m);
reset();
setup_cvx_closure(cvx_closure, lc);
// compute convex closure
if (!cvx_closure.compute()) { return false; }
bool is_syntactic = cvx_closure.has_implicit();
if (is_syntactic) { m_st.m_num_syn_cls++; }
CTRACE("subsume_verb", is_syntactic,
tout << "Convex closure introduced new variables. Implicit part of "
"closure is: "
<< mk_and(cvx_closure.get_implicit()) << "\n";);
expr_ref grounded(m);
ground_free_vars(lc.get_pattern(), grounded);
expr_ref_vector vec(m);
auto &implicit_cc = cvx_closure.get_implicit();
auto &explicit_cc = cvx_closure.get_explicit();
vec.append(implicit_cc.size(), implicit_cc.data());
vec.append(explicit_cc.size(), explicit_cc.data());
// get a model for mbp
model_ref mdl;
auto &alphas = cvx_closure.get_alphas();
find_model(vec, alphas, grounded, mdl);
app_ref_vector vars(m);
expr_ref conj(m);
vec.reset();
// eliminate real-valued alphas from syntactic convex closure
if (!implicit_cc.empty()) {
vec.append(implicit_cc.size(), implicit_cc.data());
conj = mk_and(vec);
vars.append(alphas.size(),
reinterpret_cast<app *const *>(alphas.data()));
qe_project(m, vars, conj, *mdl.get(), true, true, !m_ground_pob);
// mbp failed, not expected, bail out
if (!vars.empty()) return false;
}
// vec = [implicit_cc]
// store full cc, this is what we want to over-approximate explicitly
vec.append(explicit_cc.size(), explicit_cc.data());
flatten_and(grounded, vec);
// vec = [implicit_cc(alpha_j, v_i), explicit_cc(v_i), phi(v_i)]
expr_ref full_cc(mk_and(vec), m);
vec.reset();
if (conj) {
// if explicit version of implicit cc was successfully computed
// conj is it, but need to ensure it has no to_real()
to_real_stripper stripper(m);
flatten_and(conj, vec);
stripper(vec);
}
vec.append(explicit_cc.size(), explicit_cc.data());
flatten_and(grounded, vec);
// here vec is [cc(v_i), phi(v_i)], and we need to eliminate v_i from it
vars.reset();
vars.append(m_col_names.size(),
reinterpret_cast<app *const *>(m_col_names.data()));
conj = mk_and(vec);
qe_project(m, vars, conj, *mdl.get(), true, true, !m_ground_pob);
// failed
if (!vars.empty()) return false;
// at the end, new_post must over-approximate the implicit convex closure
flatten_and(conj, new_post);
return over_approximate(new_post, full_cc);
}
/// Find a weakening of \p a such that \p b ==> a
///
/// Returns true on success and sets \p a to the result
bool lemma_global_generalizer::subsumer::over_approximate(expr_ref_vector &a,
const expr_ref b) {
// B && !(A1 && A2 && A3) is encoded as
// B && ((tag1 && !A1) || (tag2 && !A2) || (tag3 && !A3))
// iterate and check tags
expr_ref_vector tags(m), tagged_a(m);
std::string tag_prefix = "o";
for (auto *lit : a) {
tags.push_back(mk_fresh_tag());
tagged_a.push_back(m.mk_implies(tags.back(), lit));
}
TRACE("subsume_verb", tout << "weakening " << mk_and(a)
<< " to over approximate " << b << "\n";);
solver::scoped_push _sp(*m_solver);
m_solver->assert_expr(b);
m_solver->assert_expr(push_not(mk_and(tagged_a)));
while (true) {
lbool res = m_solver->check_sat(tags.size(), tags.data());
if (res == l_false) {
break;
} else if (res == l_undef) {
break;
}
// flip tags for all satisfied literals of !A
model_ref mdl;
m_solver->get_model(mdl);
for (unsigned i = 0, sz = a.size(); i < sz; ++i) {
if (!m.is_not(tags.get(i)) && mdl->is_false(a.get(i))) {
tags[i] = m.mk_not(tags.get(i));
}
}
}
expr_ref_buffer res(m);
// remove all expressions whose tags are false
for (unsigned i = 0, sz = tags.size(); i < sz; i++) {
if (!m.is_not(tags.get(i))) { res.push_back(a.get(i)); }
}
a.reset();
a.append(res.size(), res.data());
if (a.empty()) {
// could not find an over approximation
TRACE("subsume",
tout << "mbp did not over-approximate convex closure\n";);
m_st.m_num_no_ovr_approx++;
return false;
}
TRACE("subsume",
tout << "over approximate produced " << mk_and(a) << "\n";);
return true;
}
/// Attempt to set a conjecture on pob \p n.
///
/// Done by dropping literal \p lit from
/// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for
/// the conjecture pob returns true if conjecture is set
bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma,
const expr_ref &lit, unsigned lvl,
unsigned gas) {
arith_util arith(m);
expr_ref_vector fml_vec(m);
expr_ref n_post(n->post(), m);
normalize(n_post, n_post, false, false);
// normalize_order(n_post, n_post);
fml_vec.push_back(n_post);
flatten_and(fml_vec);
expr_ref_vector conj(m);
bool is_filtered = filter_out_lit(fml_vec, lit, conj);
expr *e1 = nullptr, *e2 = nullptr;
if (!is_filtered &&
(arith.is_le(lit, e1, e2) || arith.is_ge(lit, e1, e2))) {
// if lit is '<=' or '>=', try matching '=='
is_filtered =
filter_out_lit(fml_vec, expr_ref(m.mk_eq(e1, e2), m), conj);
}
if (!is_filtered) {
// -- try using the corresponding lemma instead
conj.reset();
n_post = mk_and(lemma->get_cube());
normalize_order(n_post, n_post);
fml_vec.reset();
fml_vec.push_back(n_post);
flatten_and(fml_vec);
is_filtered = filter_out_lit(fml_vec, lit, conj);
}
SASSERT(0 < gas && gas < UINT_MAX);
if (conj.empty()) {
// If the pob cannot be abstracted, stop using generalization on
// it
TRACE("global", tout << "stop local generalization on pob " << n_post
<< " id is " << n_post->get_id() << "\n";);
n->disable_local_gen();
return false;
} else if (!is_filtered) {
// The literal to be abstracted is not in the pob
TRACE("global", tout << "Conjecture failed:\n"
<< lit << "\n"
<< n_post << "\n"
<< "conj:" << conj << "\n";);
n->disable_local_gen();
m_st.m_num_cant_abs++;
return false;
}
pob *root = n->parent();
while (root->parent()) root = root->parent();
scoped_ptr<pob> new_pob = alloc(pob, root, n->pt(), lvl, n->depth(), false);
if (!new_pob) return false;
new_pob->set_desired_level(n->level());
new_pob->set_post(mk_and(conj));
new_pob->set_conjecture();
// -- register with current pob
n->set_data(new_pob.detach());
// -- update properties of the current pob itself
n->set_expand_bnd();
n->set_gas(gas);
n->disable_local_gen();
TRACE("global", tout << "set conjecture " << mk_pp(n->get_data()->post(), m)
<< " at level " << n->get_data()->level() << "\n";);
return true;
}
// Decide global guidance based on lemma
void lemma_global_generalizer::generalize(lemma_ref &lemma) {
// -- pob that the lemma blocks
pob_ref &pob = lemma->get_pob();
// -- cluster that the lemma belongs to
lemma_cluster *cluster = pob->pt().clstr_match(lemma);
/// Lemma does not belong to any cluster. return
if (!cluster) return;
// if the cluster does not have enough gas, stop local generalization
// and return
if (cluster->get_gas() == 0) {
m_st.m_num_cls_ofg++;
pob->disable_local_gen();
TRACE("global", tout << "stop local generalization on pob "
<< mk_pp(pob->post(), m) << " id is "
<< pob->post()->get_id() << "\n";);
return;
}
// -- local cluster that includes the new lemma
lemma_cluster lc(*cluster);
// XXX most of the time lemma clustering happens before generalization
// XXX so `add_lemma` is likely to return false, but this does not mean
// XXX that the lemma is not new
bool is_new = lc.add_lemma(lemma, true);
(void)is_new;
const expr_ref &pat = lc.get_pattern();
TRACE("global", {
tout << "Global generalization of:\n"
<< mk_and(lemma->get_cube()) << "\n"
<< "at lvl: " << lemma->level() << "\n"
<< (is_new ? "new" : "old") << "\n"
<< "Using cluster:\n"
<< pat << "\n"
<< "Existing lemmas in the cluster:\n";
for (const auto &li : cluster->get_lemmas()) {
tout << mk_and(li.get_lemma()->get_cube())
<< " lvl:" << li.get_lemma()->level() << "\n";
}
});
// Concretize
if (has_nonlinear_var_mul(pat, m)) {
m_st.m_num_non_lin++;
TRACE("global",
tout << "Found non linear pattern. Marked to concretize \n";);
// not constructing the concrete pob here since we need a model for
// n->post()
pob->set_concretize_pattern(pat);
pob->set_concretize(true);
pob->set_gas(cluster->get_pob_gas());
cluster->dec_gas();
return;
}
// Conjecture
expr_ref lit(m);
if (find_unique_mono_var_lit(pat, lit)) {
// Create a conjecture by dropping literal from pob.
TRACE("global", tout << "Conjecture with pattern\n"
<< mk_pp(pat, m) << "\n"
<< "with gas " << cluster->get_gas() << "\n";);
unsigned gas = cluster->get_pob_gas();
unsigned lvl = lc.get_min_lvl();
if (pob) lvl = std::min(lvl, pob->level());
if (do_conjecture(pob, lemma, lit, lvl, gas)) {
// decrease the number of times this cluster is going to be used
// for conjecturing
cluster->dec_gas();
return;
} else {
// -- if conjecture failed, there is nothing else to do.
// -- the pob matched pre-condition for conjecture, so it should not
// be subsumed
return;
}
}
// if subsumption removed all the other lemmas, there is nothing to
// generalize
if (lc.get_size() < 2) return;
if (!m_do_subsume) return;
// -- new pob that is blocked by generalized lemma
expr_ref_vector new_post(m);
// -- bindings for free variables of new_pob
// -- subsumer might introduce extra free variables
app_ref_vector bindings(lemma->get_bindings());
if (m_subsumer.subsume(lc, new_post, bindings)) {
class pob *root = pob->parent();
while (root->parent()) root = root->parent();
unsigned new_lvl = lc.get_min_lvl();
if (pob) new_lvl = std::min(new_lvl, pob->level());
scoped_ptr<class pob> new_pob =
alloc(class pob, root, pob->pt(), new_lvl, pob->depth(), false);
if (!new_pob) return;
new_pob->set_desired_level(pob->level());
new_pob->set_post(mk_and(new_post), bindings);
new_pob->set_subsume();
pob->set_data(new_pob.detach());
// -- update properties of the pob itself
pob->set_gas(cluster->get_pob_gas() + 1);
pob->set_expand_bnd();
// Stop local generalization. Perhaps not the best choice in general.
// Helped with one instance on our benchmarks
pob->disable_local_gen();
cluster->dec_gas();
TRACE("global", tout << "Create subsume pob at level " << new_lvl
<< "\n"
<< mk_and(new_post) << "\n";);
}
}
/// Replace bound vars in \p fml with uninterpreted constants
void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat,
expr_ref &out) {
SASSERT(!is_ground(pat));
var_subst vs(m, false);
// m_col_names might be bigger since it contains previously used constants
// relying on the fact that m_col_lcm was just set. Better to compute free
// vars of pat
SASSERT(m_col_lcm.size() <= m_col_names.size());
out = vs(pat, m_col_lcm.size(),
reinterpret_cast<expr *const *>(m_col_names.data()));
SASSERT(is_ground(out));
}
pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) {
expr_ref_vector new_post(m);
spacer::pob_concretizer proc(m, model, n.get_concretize_pattern());
if (proc.apply(n.post(), new_post)) {
pob *new_pob = n.pt().mk_pob(n.parent(), n.level(), n.depth(),
mk_and(new_post), n.get_binding());
TRACE("concretize", tout << "pob:\n"
<< mk_pp(n.post(), m)
<< " is concretized into:\n"
<< mk_pp(new_pob->post(), m) << "\n";);
return new_pob;
}
return nullptr;
}
pob *lemma_global_generalizer::mk_subsume_pob(pob &n) {
if (!(n.get_gas() >= 0 && n.has_data() && n.get_data()->is_subsume()))
return nullptr;
pob *data = n.get_data();
pob *f = n.pt().find_pob(data->parent(), data->post());
if (f && (f->is_in_queue() || f->is_closed())) {
n.reset_data();
return nullptr;
}
TRACE("global", tout << "mk_subsume_pob at level " << data->level()
<< " with post state:\n"
<< mk_pp(data->post(), m) << "\n";);
f = n.pt().mk_pob(data->parent(), data->level(), data->depth(),
data->post(), n.get_binding());
f->set_subsume();
f->inherit(*data);
n.reset_data();
return f;
}
pob *lemma_global_generalizer::mk_conjecture_pob(pob &n) {
if (!(n.has_data() && n.get_data()->is_conjecture() && n.get_gas() > 0))
return nullptr;
pob *data = n.get_data();
pob *f = n.pt().find_pob(data->parent(), data->post());
if (f && (f->is_in_queue() || f->is_closed())) {
n.reset_data();
return nullptr;
}
f = n.pt().mk_pob(data->parent(), data->level(), data->depth(),
data->post(), {m});
// inherit all metadata from new_pob
f->inherit(*data);
n.reset_data();
return f;
}
void lemma_global_generalizer::subsumer::collect_statistics(
statistics &st) const {
st.update("SPACER num no over approximate", m_st.m_num_no_ovr_approx);
st.update("SPACER num sync cvx cls", m_st.m_num_syn_cls);
st.update("SPACER num mbp failed", m_st.m_num_mbp_failed);
// m_cvx_closure.collect_statistics(st);
}
void lemma_global_generalizer::collect_statistics(statistics &st) const {
st.update("time.spacer.solve.reach.gen.global", m_st.watch.get_seconds());
st.update("SPACER cluster out of gas", m_st.m_num_cls_ofg);
st.update("SPACER num non lin", m_st.m_num_non_lin);
st.update("SPACER num cant abstract", m_st.m_num_cant_abs);
}
} // namespace spacer

View file

@ -0,0 +1,176 @@
#pragma once
/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_global_generalizer.h
Abstract:
Global Guidance for Spacer
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_convex_closure.h"
namespace spacer {
/// Global guided generalization
///
/// See Hari Govind et al. Global Guidance for Local Generalization in Model
/// Checking. CAV 2020
class lemma_global_generalizer : public lemma_generalizer {
/// Subsumption strategy
class subsumer {
struct stats {
unsigned m_num_syn_cls;
unsigned m_num_mbp_failed;
unsigned m_num_no_ovr_approx;
stopwatch watch;
stats() { reset(); }
void reset() {
watch.reset();
m_num_syn_cls = 0;
m_num_mbp_failed = 0;
m_num_no_ovr_approx = 0;
}
};
stats m_st;
ast_manager &m;
arith_util m_arith;
bv_util m_bv;
// boolean variables used as local tags
app_ref_vector m_tags;
// number of tags currently used
unsigned m_used_tags;
// save fresh constants for mbp
app_ref_vector m_col_names;
vector<rational> m_col_lcm;
// create pob without free vars
bool m_ground_pob;
// Local solver to get model for computing mbp and to check whether
// cvx_cls ==> mbp
ref<solver> m_solver;
/// Return a fresh boolean variable
app *mk_fresh_tag();
void reset();
/// Returns false if subsumption is not supported for given cluster
bool is_handled(const lemma_cluster &lc);
/// Find a representative for \p c
expr *find_repr(const model_ref &mdl, const app *c);
/// Skolemize m_dim_frsh_cnsts in \p f
///
/// \p cnsts is appended with ground terms from \p mdl
void skolemize_for_quic3(expr_ref &f, const model_ref &mdl,
app_ref_vector &cnsts);
/// Create new vars to compute convex cls
void mk_col_names(const lemma_cluster &lc);
void setup_cvx_closure(convex_closure &cc, const lemma_cluster &lc);
/// Make \p fml ground using m_dim_frsh_cnsts. Store result in \p out
void ground_free_vars(expr *fml, expr_ref &out);
/// Weaken \p a such that (and a) overapproximates \p b
bool over_approximate(expr_ref_vector &a, const expr_ref b);
bool find_model(const expr_ref_vector &cc,
const expr_ref_vector &alphas, expr *bg,
model_ref &out_model);
bool is_numeral(const expr *e, rational &n) {
return m_arith.is_numeral(e, n) || m_bv.is_numeral(e, n);
}
expr *mk_rat_mul(rational n, expr *v) {
if (n.is_one()) return v;
return m_arith.mk_mul(m_arith.mk_numeral(n, m_arith.is_int(v)), v);
}
public:
subsumer(ast_manager &m, bool ground_pob);
void collect_statistics(statistics &st) const;
/// Compute a cube \p res such that \neg p subsumes all the lemmas in \p
/// lc
///
/// \p cnsts is a set of constants that can be used to make \p res
/// ground
bool subsume(const lemma_cluster &lc, expr_ref_vector &res,
app_ref_vector &cnsts);
};
struct stats {
unsigned m_num_cls_ofg;
unsigned m_num_syn_cls;
unsigned m_num_mbp_failed;
unsigned m_num_non_lin;
unsigned m_num_no_ovr_approx;
unsigned m_num_cant_abs;
stopwatch watch;
stats() { reset(); }
void reset() {
watch.reset();
m_num_cls_ofg = 0;
m_num_non_lin = 0;
m_num_syn_cls = 0;
m_num_mbp_failed = 0;
m_num_no_ovr_approx = 0;
m_num_cant_abs = 0;
}
};
stats m_st;
ast_manager &m;
subsumer m_subsumer;
/// Decide global guidance based on lemma
void generalize(lemma_ref &lemma);
/// Attempt to set a conjecture on pob \p n.
///
/// Done by dropping literal \p lit from
/// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for
/// the conjecture pob returns true if conjecture is set
bool do_conjecture(pob_ref &n, lemma_ref &lemma, const expr_ref &lit, unsigned lvl,
unsigned gas);
/// Enable/disable subsume rule
bool m_do_subsume;
public:
lemma_global_generalizer(context &ctx);
~lemma_global_generalizer() override {}
void operator()(lemma_ref &lemma) override;
void collect_statistics(statistics &st) const override;
void reset_statistics() override { m_st.reset(); }
// post-actions for pobs produced during generalization
pob *mk_concretize_pob(pob &n, model_ref &model);
pob *mk_subsume_pob(pob &n);
pob *mk_conjecture_pob(pob &n);
};
} // namespace spacer

View file

@ -0,0 +1,303 @@
#include "ast/expr_functors.h"
#include "muz/spacer/spacer_context.h"
using namespace spacer;
namespace {
class contains_array_op_proc : public i_expr_pred {
ast_manager &m;
family_id m_array_fid;
public:
contains_array_op_proc(ast_manager &manager)
: m(manager), m_array_fid(array_util(m).get_family_id()) {}
bool operator()(expr *e) override {
return is_app(e) && to_app(e)->get_family_id() == m_array_fid;
}
};
class lemma_inductive_generalizer : public lemma_generalizer {
struct stats {
unsigned count;
unsigned weaken_success;
unsigned weaken_fail;
stopwatch watch;
stats() { reset(); }
void reset() {
count = 0;
weaken_success = 0;
weaken_fail = 0;
watch.reset();
}
};
ast_manager &m;
expr_ref m_true;
stats m_st;
bool m_only_array_eligible;
bool m_enable_litweak;
contains_array_op_proc m_contains_array_op;
check_pred m_contains_array_pred;
expr_ref_vector m_pinned;
lemma *m_lemma = nullptr;
spacer::pred_transformer *m_pt = nullptr;
unsigned m_weakness = 0;
unsigned m_level = 0;
ptr_vector<expr> m_cube;
// temporary vector
expr_ref_vector m_core;
public:
lemma_inductive_generalizer(spacer::context &ctx,
bool only_array_eligible = false,
bool enable_literal_weakening = true)
: lemma_generalizer(ctx), m(ctx.get_ast_manager()),
m_true(m.mk_true(), m), m_only_array_eligible(only_array_eligible),
m_enable_litweak(enable_literal_weakening), m_contains_array_op(m),
m_contains_array_pred(m_contains_array_op, m),
m_pinned(m), m_core(m) {}
private:
// -- true if literal \p lit is eligible to be generalized
bool is_eligible(expr *lit) {
return !m_only_array_eligible || has_arrays(lit);
}
bool has_arrays(expr *lit) { return m_contains_array_op(lit); }
void reset() {
m_cube.reset();
m_weakness = 0;
m_level = 0;
m_pt = nullptr;
m_pinned.reset();
m_core.reset();
}
void setup(lemma_ref &lemma) {
// check that we start in uninitialized state
SASSERT(m_pt == nullptr);
m_lemma = lemma.get();
m_pt = &lemma->get_pob()->pt();
m_weakness = lemma->weakness();
m_level = lemma->level();
auto &cube = lemma->get_cube();
m_cube.reset();
for (auto *lit : cube) { m_cube.push_back(lit); }
}
// loads current generalization from m_cube to m_core
void load_cube_to_core() {
m_core.reset();
for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) {
auto *lit = m_cube.get(i);
if (lit == m_true) continue;
m_core.push_back(lit);
}
}
// returns true if m_cube is inductive
bool is_cube_inductive() {
load_cube_to_core();
if (m_core.empty()) return false;
unsigned used_level;
if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) {
m_level = std::max(m_level, used_level);
return true;
}
return false;
}
// intersect m_cube with m_core
unsigned update_cube_by_core(unsigned from = 0) {
// generalize away all literals in m_cube that are not in m_core
// do not assume anything about order of literals in m_core
unsigned success = 0;
// mark core
ast_fast_mark2 marked_core;
for (auto *v : m_core) { marked_core.mark(v); }
// replace unmarked literals by m_true in m_cube
for (unsigned i = from, sz = m_cube.size(); i < sz; ++i) {
auto *lit = m_cube.get(i);
if (lit == m_true) continue;
if (!marked_core.is_marked(lit)) {
m_cube[i] = m_true;
success++;
}
}
return success;
}
// generalizes m_core and removes from m_cube all generalized literals
unsigned generalize_core(unsigned from = 0) {
unsigned success = 0;
unsigned used_level;
// -- while it is possible that a single literal can be generalized to
// false,
// -- it is fairly unlikely. Thus, we give up generalizing in this case.
if (m_core.empty()) return 0;
// -- check whether candidate in m_core is inductive
if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) {
success += update_cube_by_core(from);
// update m_level to the largest level at which the the current
// candidate in m_cube is inductive
m_level = std::max(m_level, used_level);
}
return success;
}
// generalizes (i.e., drops) a specific literal of m_cube
unsigned generalize1(unsigned lit_idx) {
if (!is_eligible(m_cube.get(lit_idx))) return 0;
// -- populate m_core with all literals except the one being generalized
m_core.reset();
for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) {
auto *lit = m_cube.get(i);
if (lit == m_true || i == lit_idx) continue;
m_core.push_back(lit);
}
return generalize_core(lit_idx);
}
// generalizes all literals of m_cube in a given range
unsigned generalize_range(unsigned from, unsigned to) {
unsigned success = 0;
for (unsigned i = from; i < to; ++i) { success += generalize1(i); }
return success;
}
// weakens a given literal of m_cube
// weakening replaces a literal by a weaker literal(s)
// for example, x=y might get weakened into one of x<=y or y<=x
unsigned weaken1(unsigned lit_idx) {
if (!is_eligible(m_cube.get(lit_idx))) return 0;
if (m_cube.get(lit_idx) == m_true) return 0;
unsigned success = 0;
unsigned cube_sz = m_cube.size();
// -- save literal to be generalized, and replace it by true
expr *saved_lit = m_cube.get(lit_idx);
m_cube[lit_idx] = m_true;
// -- add new weaker literals to end of m_cube and attempt to generalize
expr_ref_vector weakening(m);
weakening.push_back(saved_lit);
expand_literals(m, weakening);
if (weakening.get(0) != saved_lit) {
for (auto *lit : weakening) {
m_cube.push_back(lit);
m_pinned.push_back(lit);
}
if (m_cube.size() - cube_sz >= 2) {
// normal case: generalize new weakening
success += generalize_range(cube_sz, m_cube.size());
} else {
// special case -- weaken literal by another literal, check that
// cube is still inductive
success += (is_cube_inductive() ? 1 : 0);
}
}
// -- failed to generalize, restore removed literal and m_cube
if (success == 0) {
m_cube[lit_idx] = saved_lit;
m_cube.shrink(cube_sz);
m_st.weaken_fail++;
} else {
m_st.weaken_success++;
}
return success;
}
// weakens literals of m_cube in a given range
unsigned weaken_range(unsigned from, unsigned to) {
unsigned success = 0;
for (unsigned i = from; i < to; ++i) { success += weaken1(i); }
return success;
}
public:
// entry point for generalization
void operator()(lemma_ref &lemma) override {
if (lemma->get_cube().empty()) return;
m_st.count++;
scoped_watch _w_(m_st.watch);
setup(lemma);
unsigned num_gens = 0;
// -- first round -- generalize by dropping literals
num_gens += generalize_range(0, m_cube.size());
// -- if weakening is enabled, start next round
if (m_enable_litweak) {
unsigned cube_sz = m_cube.size();
// -- second round -- weaken literals that cannot be dropped
num_gens += weaken_range(0, cube_sz);
// -- third round -- weaken literals produced in prev round
if (cube_sz < m_cube.size())
num_gens += weaken_range(cube_sz, m_cube.size());
}
// if there is at least one generalization, update lemma
if (num_gens > 0) {
TRACE("indgen",
tout << "Generalized " << num_gens << " literals\n";);
// reuse m_core since it is not needed for anything else
m_core.reset();
for (auto *lit : m_cube) {
if (lit != m_true) m_core.push_back(lit);
}
TRACE("indgen", tout << "Original: " << lemma->get_cube() << "\n"
<< "Generalized: " << m_core << "\n";);
lemma->update_cube(lemma->get_pob(), m_core);
lemma->set_level(m_level);
}
reset();
return;
}
void collect_statistics(statistics &st) const override {
st.update("time.spacer.solve.reach.gen.ind", m_st.watch.get_seconds());
st.update("SPACER inductive gen", m_st.count);
st.update("SPACER inductive gen weaken success", m_st.weaken_success);
st.update("SPACER inductive gen weaken fail", m_st.weaken_fail);
}
void reset_statistics() override { m_st.reset(); }
};
} // namespace
namespace spacer {
lemma_generalizer *
alloc_lemma_inductive_generalizer(spacer::context &ctx,
bool only_array_eligible,
bool enable_literal_weakening) {
return alloc(lemma_inductive_generalizer, ctx, only_array_eligible,
enable_literal_weakening);
}
} // namespace spacer

View file

@ -1,191 +0,0 @@
/**++
Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti
Module Name:
spacer_json.cpp
Abstract:
SPACER json marshalling support
Author:
Matteo Marescotti
Notes:
--*/
#include <iomanip>
#include "spacer_context.h"
#include "spacer_json.h"
#include "spacer_util.h"
namespace spacer {
static std::ostream &json_marshal(std::ostream &out, ast *t, ast_manager &m) {
mk_epp pp = mk_epp(t, m);
std::ostringstream ss;
ss << pp;
out << "\"";
for (auto &c:ss.str()) {
switch (c) {
case '"':
out << "\\\"";
break;
case '\\':
out << "\\\\";
break;
case '\b':
out << "\\b";
break;
case '\f':
out << "\\f";
break;
case '\n':
out << "\\n";
break;
case '\r':
out << "\\r";
break;
case '\t':
out << "\\t";
break;
default:
if ('\x00' <= c && c <= '\x1f') {
out << "\\u"
<< std::hex << std::setw(4) << std::setfill('0') << (int) c;
} else {
out << c;
}
}
}
out << "\"";
return out;
}
static std::ostream &json_marshal(std::ostream &out, lemma *l) {
out << "{"
<< R"("init_level":")" << l->init_level()
<< R"(", "level":")" << l->level()
<< R"(", "expr":)";
json_marshal(out, l->get_expr(), l->get_ast_manager());
out << "}";
return out;
}
static std::ostream &json_marshal(std::ostream &out, const lemma_ref_vector &lemmas) {
std::ostringstream ls;
for (auto l:lemmas) {
ls << ((unsigned)ls.tellp() == 0 ? "" : ",");
json_marshal(ls, l);
}
out << "[" << ls.str() << "]";
return out;
}
void json_marshaller::register_lemma(lemma *l) {
if (l->has_pob()) {
m_relations[&*l->get_pob()][l->get_pob()->depth()].push_back(l);
}
}
void json_marshaller::register_pob(pob *p) {
m_relations[p];
}
void json_marshaller::marshal_lemmas_old(std::ostream &out) const {
unsigned pob_id = 0;
for (auto &pob_map:m_relations) {
std::ostringstream pob_lemmas;
for (auto &depth_lemmas : pob_map.second) {
pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",")
<< "\"" << depth_lemmas.first << "\":";
json_marshal(pob_lemmas, depth_lemmas.second);
}
if (pob_lemmas.tellp()) {
out << ((unsigned)out.tellp() == 0 ? "" : ",\n");
out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}";
}
pob_id++;
}
}
void json_marshaller::marshal_lemmas_new(std::ostream &out) const {
unsigned pob_id = 0;
for (auto &pob_map:m_relations) {
std::ostringstream pob_lemmas;
pob *n = pob_map.first;
unsigned i = 0;
for (auto *l : n->lemmas()) {
pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",")
<< "\"" << i++ << "\":";
lemma_ref_vector lemmas_vec;
lemmas_vec.push_back(l);
json_marshal(pob_lemmas, lemmas_vec);
}
if (pob_lemmas.tellp()) {
out << ((unsigned)out.tellp() == 0 ? "" : ",\n");
out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}";
}
pob_id++;
}
}
std::ostream &json_marshaller::marshal(std::ostream &out) const {
std::ostringstream nodes;
std::ostringstream edges;
std::ostringstream lemmas;
if (m_old_style)
marshal_lemmas_old(lemmas);
else
marshal_lemmas_new(lemmas);
unsigned pob_id = 0;
unsigned depth = 0;
while (true) {
double root_expand_time = m_ctx->get_root().get_expand_time(depth);
bool a = false;
pob_id = 0;
for (auto &pob_map:m_relations) {
pob *n = pob_map.first;
double expand_time = n->get_expand_time(depth);
if (expand_time > 0) {
a = true;
std::ostringstream pob_expr;
json_marshal(pob_expr, n->post(), n->get_ast_manager());
nodes << ((unsigned)nodes.tellp() == 0 ? "" : ",\n") <<
"{\"id\":\"" << depth << n <<
"\",\"relative_time\":\"" << expand_time / root_expand_time <<
"\",\"absolute_time\":\"" << std::setprecision(2) << expand_time <<
"\",\"predicate\":\"" << n->pt().head()->get_name() <<
"\",\"expr_id\":\"" << n->post()->get_id() <<
"\",\"pob_id\":\"" << pob_id <<
"\",\"depth\":\"" << depth <<
"\",\"expr\":" << pob_expr.str() << "}";
if (n->parent()) {
edges << ((unsigned)edges.tellp() == 0 ? "" : ",\n") <<
"{\"from\":\"" << depth << n->parent() <<
"\",\"to\":\"" << depth << n << "\"}";
}
}
pob_id++;
}
if (!a) {
break;
}
depth++;
}
out << "{\n\"nodes\":[\n" << nodes.str() << "\n],\n";
out << "\"edges\":[\n" << edges.str() << "\n],\n";
out << "\"lemmas\":{\n" << lemmas.str() << "\n}\n}\n";
return out;
}
}

View file

@ -1,59 +0,0 @@
/**++
Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti
Module Name:
spacer_json.h
Abstract:
SPACER json marshalling support
Author:
Matteo Marescotti
Notes:
--*/
#pragma once
#include<ostream>
#include<map>
#include "util/ref.h"
#include "util/ref_vector.h"
class ast;
class ast_manager;
namespace spacer {
class lemma;
typedef sref_vector<lemma> lemma_ref_vector;
class context;
class pob;
class json_marshaller {
context *m_ctx;
bool m_old_style;
std::map<pob*, std::map<unsigned, lemma_ref_vector>> m_relations;
void marshal_lemmas_old(std::ostream &out) const;
void marshal_lemmas_new(std::ostream &out) const;
public:
json_marshaller(context *ctx, bool old_style = false) :
m_ctx(ctx), m_old_style(old_style) {}
void register_lemma(lemma *l);
void register_pob(pob *p);
std::ostream &marshal(std::ostream &out) const;
};
}

View file

@ -17,61 +17,40 @@ Revision History:
--*/ --*/
#include "muz/spacer/spacer_matrix.h" #include "muz/spacer/spacer_matrix.h"
namespace spacer namespace spacer {
{ spacer_matrix::spacer_matrix(unsigned m, unsigned n)
spacer_matrix::spacer_matrix(unsigned m, unsigned n) : m_num_rows(m), m_num_cols(n) : m_num_rows(m), m_num_cols(n) {
{ m_matrix.reserve(m_num_rows);
for (unsigned i=0; i < m; ++i) for (unsigned i = 0; i < m_num_rows; ++i) {
{ m_matrix[i].reserve(m_num_cols, rational(0));
vector<rational> v;
for (unsigned j=0; j < n; ++j)
{
v.push_back(rational(0));
}
m_matrix.push_back(v);
} }
} }
unsigned spacer_matrix::num_rows() void spacer_matrix::get_col(unsigned i, vector<rational> &row) const {
{ SASSERT(i < m_num_cols);
return m_num_rows; row.reset();
row.reserve(m_num_rows);
unsigned j = 0;
for (auto &v : m_matrix) { row[j++] = (v.get(i)); }
SASSERT(row.size() == m_num_rows);
} }
unsigned spacer_matrix::num_cols() void spacer_matrix::add_row(const vector<rational> &row) {
{ SASSERT(row.size() == m_num_cols);
return m_num_cols; m_matrix.push_back(row);
m_num_rows = m_matrix.size();
} }
const rational& spacer_matrix::get(unsigned int i, unsigned int j) unsigned spacer_matrix::perform_gaussian_elimination() {
{
SASSERT(i < m_num_rows);
SASSERT(j < m_num_cols);
return m_matrix[i][j];
}
void spacer_matrix::set(unsigned int i, unsigned int j, const rational& v)
{
SASSERT(i < m_num_rows);
SASSERT(j < m_num_cols);
m_matrix[i][j] = v;
}
unsigned spacer_matrix::perform_gaussian_elimination()
{
unsigned i = 0; unsigned i = 0;
unsigned j = 0; unsigned j = 0;
while(i < m_matrix.size() && j < m_matrix[0].size()) while (i < m_matrix.size() && j < m_matrix[0].size()) {
{
// find maximal element in column with row index bigger or equal i // find maximal element in column with row index bigger or equal i
rational max = m_matrix[i][j]; rational max = m_matrix[i][j];
unsigned max_index = i; unsigned max_index = i;
for (unsigned k=i+1; k < m_matrix.size(); ++k) for (unsigned k = i + 1; k < m_matrix.size(); ++k) {
{ if (max < m_matrix[k][j]) {
if (max < m_matrix[k][j])
{
max = m_matrix[k][j]; max = m_matrix[k][j];
max_index = k; max_index = k;
} }
@ -80,9 +59,7 @@ namespace spacer
if (max.is_zero()) // skip this column if (max.is_zero()) // skip this column
{ {
++j; ++j;
} } else {
else
{
// reorder rows if necessary // reorder rows if necessary
vector<rational> tmp = m_matrix[i]; vector<rational> tmp = m_matrix[i];
m_matrix[i] = m_matrix[max_index]; m_matrix[i] = m_matrix[max_index];
@ -90,23 +67,19 @@ namespace spacer
// normalize row // normalize row
rational pivot = m_matrix[i][j]; rational pivot = m_matrix[i][j];
if (!pivot.is_one()) if (!pivot.is_one()) {
{ for (unsigned k = 0; k < m_matrix[i].size(); ++k) {
for (unsigned k=0; k < m_matrix[i].size(); ++k)
{
m_matrix[i][k] = m_matrix[i][k] / pivot; m_matrix[i][k] = m_matrix[i][k] / pivot;
} }
} }
// subtract row from all other rows // subtract row from all other rows
for (unsigned k=1; k < m_matrix.size(); ++k) for (unsigned k = 1; k < m_matrix.size(); ++k) {
{ if (k != i) {
if (k != i)
{
rational factor = m_matrix[k][j]; rational factor = m_matrix[k][j];
for (unsigned l=0; l < m_matrix[k].size(); ++l) for (unsigned l = 0; l < m_matrix[k].size(); ++l) {
{ m_matrix[k][l] =
m_matrix[k][l] = m_matrix[k][l] - (factor * m_matrix[i][l]); m_matrix[k][l] - (factor * m_matrix[i][l]);
} }
} }
} }
@ -116,44 +89,97 @@ namespace spacer
} }
} }
if (get_verbosity_level() >= 1) if (get_verbosity_level() >= 1) { SASSERT(m_matrix.size() > 0); }
{
SASSERT(m_matrix.size() > 0);
}
return i; // i points to the row after the last row which is non-zero return i; // i points to the row after the last row which is non-zero
} }
void spacer_matrix::print_matrix() std::ostream &spacer_matrix::display(std::ostream &out) const {
{ out << "Matrix\n";
verbose_stream() << "\nMatrix\n"; for (const auto &row : m_matrix) {
for (const auto& row : m_matrix) for (const auto &element : row) { out << element << ", "; }
{ out << "\n";
for (const auto& element : row)
{
verbose_stream() << element << ", ";
} }
verbose_stream() << "\n"; out << "\n";
return out;
} }
verbose_stream() << "\n";
} void spacer_matrix::normalize() {
void spacer_matrix::normalize()
{
rational den = rational::one(); rational den = rational::one();
for (unsigned i=0; i < m_num_rows; ++i) for (unsigned i = 0; i < m_num_rows; ++i) {
{ for (unsigned j = 0; j < m_num_cols; ++j) {
for (unsigned j=0; j < m_num_cols; ++j)
{
den = lcm(den, denominator(m_matrix[i][j])); den = lcm(den, denominator(m_matrix[i][j]));
} }
} }
for (unsigned i=0; i < m_num_rows; ++i)
{ for (unsigned i = 0; i < m_num_rows; ++i) {
for (unsigned j=0; j < m_num_cols; ++j) for (unsigned j = 0; j < m_num_cols; ++j) {
{
m_matrix[i][j] = den * m_matrix[i][j]; m_matrix[i][j] = den * m_matrix[i][j];
SASSERT(m_matrix[i][j].is_int()); SASSERT(m_matrix[i][j].is_int());
} }
} }
} }
// attempt to guess that all rows of the matrix are linearly dependent
bool spacer_matrix::is_lin_reltd(unsigned i, unsigned j, rational &coeff1,
rational &coeff2, rational &off) const {
SASSERT(m_num_rows > 1);
coeff1 = m_matrix[0][j] - m_matrix[1][j];
coeff2 = m_matrix[1][i] - m_matrix[0][i];
off = (m_matrix[0][i] * m_matrix[1][j]) - (m_matrix[1][i] * m_matrix[0][j]);
for (unsigned k = 0; k < m_num_rows; k++) {
if (((coeff1 * m_matrix[k][i]) + (coeff2 * m_matrix[k][j]) + off) !=
rational::zero()) {
TRACE("cvx_dbg_verb",
tout << "Didn't work for " << m_matrix[k][i] << " and "
<< m_matrix[k][j] << " with coefficients " << coeff1
<< " , " << coeff2 << " and offset " << off << "\n";);
return false;
} }
}
rational div = gcd(coeff1, gcd(coeff2, off));
if (div == 0) return false;
coeff1 = coeff1 / div;
coeff2 = coeff2 / div;
off = off / div;
return true;
}
bool spacer_matrix::compute_linear_deps(spacer_matrix &eq) const {
SASSERT(m_num_rows > 1);
eq.reset(m_num_cols + 1);
rational coeff1, coeff2, off;
vector<rational> lin_dep;
lin_dep.reserve(m_num_cols + 1);
for (unsigned i = 0; i < m_num_cols; i++) {
for (unsigned j = i + 1; j < m_num_cols; j++) {
if (is_lin_reltd(i, j, coeff1, coeff2, off)) {
SASSERT(!(coeff1 == 0 && coeff2 == 0 && off == 0));
lin_dep[i] = coeff1;
lin_dep[j] = coeff2;
lin_dep[m_num_cols] = off;
eq.add_row(lin_dep);
TRACE("cvx_dbg_verb", {
tout << "Adding row ";
for (rational r : lin_dep) tout << r << " ";
tout << "\n";
});
// reset everything
lin_dep[i] = rational::zero();
lin_dep[j] = rational::zero();
lin_dep[m_num_cols] = 0;
// Found a dependency for this row, move on.
// sound because of transitivity of is_lin_reltd
break;
}
}
}
return eq.num_rows() > 0;
}
} // namespace spacer

View file

@ -23,23 +23,43 @@ Revision History:
namespace spacer { namespace spacer {
class spacer_matrix { class spacer_matrix {
public:
spacer_matrix(unsigned m, unsigned n); // m rows, n columns
unsigned num_rows();
unsigned num_cols();
const rational& get(unsigned i, unsigned j);
void set(unsigned i, unsigned j, const rational& v);
unsigned perform_gaussian_elimination();
void print_matrix();
void normalize();
private: private:
unsigned m_num_rows; unsigned m_num_rows;
unsigned m_num_cols; unsigned m_num_cols;
vector<vector<rational>> m_matrix; vector<vector<rational>> m_matrix;
};
bool is_lin_reltd(unsigned i, unsigned j, rational &coeff1,
rational &coeff2, rational &off) const;
public:
spacer_matrix(unsigned m, unsigned n); // m rows, n columns
unsigned num_rows() const { return m_num_rows; }
unsigned num_cols() const { return m_num_cols; }
const rational &get(unsigned i, unsigned j) const { return m_matrix[i][j]; }
void set(unsigned i, unsigned j, const rational &v) { m_matrix[i][j] = v; }
const vector<rational> &get_row(unsigned i) const {
SASSERT(i < num_rows());
return m_matrix.get(i);
} }
/// Returns a copy of row \p i
void get_col(unsigned i, vector<rational> &row) const;
void add_row(const vector<rational> &row);
void reset(unsigned n_cols) {
m_num_rows = 0;
m_num_cols = n_cols;
m_matrix.reset();
}
std::ostream &display(std::ostream &out) const;
void normalize();
unsigned perform_gaussian_elimination();
bool compute_linear_deps(spacer_matrix &eq) const;
};
} // namespace spacer

View file

@ -284,6 +284,11 @@ namespace spacer {
ptr_buffer<proof> const &parents, ptr_buffer<proof> const &parents,
unsigned num_params, unsigned num_params,
parameter const *params) { parameter const *params) {
if(num_params != parents.size() + 1) {
//TODO: fix bug
TRACE("spacer.fkab", tout << "UNEXPECTED INPUT TO FUNCTION. Bailing out\n";);
return proof_ref(m);
}
SASSERT(num_params == parents.size() + 1 /* one param is missing */); SASSERT(num_params == parents.size() + 1 /* one param is missing */);
arith_util a(m); arith_util a(m);
th_rewriter rw(m); th_rewriter rw(m);

View file

@ -84,7 +84,7 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos)
top = false; top = false;
if (n1->get_decl() != n2->get_decl()) { if (n1->get_decl() != n2->get_decl()) {
expr *e1 = nullptr, *e2 = nullptr; expr *e1 = nullptr, *e2 = nullptr, *e3 = nullptr, *e4 = nullptr, *e5 = nullptr;
rational val1, val2; rational val1, val2;
// x<=y == !(x>y) // x<=y == !(x>y)
@ -120,6 +120,26 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos)
else { else {
return false; return false;
} }
#if 0
// x >= var and !(y <= n)
// match (x, y) and (var, n+1)
if (m_arith.is_ge(n1, e1, e2) && is_var(e2) &&
m.is_not(n2, e3) && m_arith.is_le(e3, e4, e5) &&
m_arith.is_int(e5) &&
m_arith.is_numeral(e5, val2)) {
expr* num2 = m_arith.mk_numeral(val2 + 1, true);
m_pinned.push_back(num2);
if (!match_var(to_var(e2), num2)) return false;
m_todo.pop_back();
m_todo.push_back(expr_pair(e1, e4));
continue;
}
#endif
} }
unsigned num_args1 = n1->get_num_args(); unsigned num_args1 = n1->get_num_args();

View file

@ -38,11 +38,11 @@ class sem_matcher {
substitution * m_subst; substitution * m_subst;
svector<expr_pair> m_todo; svector<expr_pair> m_todo;
void reset();
bool match_var(var *v, expr *e); bool match_var(var *v, expr *e);
public: public:
sem_matcher(ast_manager &man); sem_matcher(ast_manager &man);
void reset();
/** /**
\brief Return true if e2 is an instance of e1. \brief Return true if e2 is an instance of e1.

View file

@ -396,8 +396,8 @@ namespace spacer {
matrix.set(i, map[pair.second], pair.first); matrix.set(i, map[pair.second], pair.first);
} }
} }
matrix.print_matrix();
IF_VERBOSE(10, matrix.display(verbose_stream()););
// 3. normalize matrix to integer values // 3. normalize matrix to integer values
matrix.normalize(); matrix.normalize();

File diff suppressed because it is too large Load diff

View file

@ -21,30 +21,28 @@ Revision History:
#pragma once #pragma once
#include "ast/arith_decl_plugin.h"
#include "ast/array_decl_plugin.h"
#include "ast/ast.h" #include "ast/ast.h"
#include "ast/ast_pp.h" #include "ast/ast_pp.h"
#include "ast/ast_util.h"
#include "ast/bv_decl_plugin.h"
#include "ast/expr_map.h"
#include "model/model.h"
#include "util/obj_hashtable.h" #include "util/obj_hashtable.h"
#include "util/ref_vector.h" #include "util/ref_vector.h"
#include "util/trace.h" #include "util/trace.h"
#include "util/vector.h" #include "util/vector.h"
#include "ast/arith_decl_plugin.h"
#include "ast/array_decl_plugin.h"
#include "ast/bv_decl_plugin.h"
#include "ast/ast_util.h"
#include "ast/expr_map.h"
#include "model/model.h"
#include "util/stopwatch.h"
#include "muz/spacer/spacer_antiunify.h" #include "muz/spacer/spacer_antiunify.h"
#include "util/stopwatch.h"
class model; class model;
class model_core; class model_core;
namespace spacer { namespace spacer {
inline unsigned infty_level () { inline unsigned infty_level() { return UINT_MAX; }
return UINT_MAX;
}
inline bool is_infty_level(unsigned lvl) { inline bool is_infty_level(unsigned lvl) {
// XXX: level is 16 bits in class pob // XXX: level is 16 bits in class pob
@ -82,8 +80,8 @@ namespace spacer {
\brief hoist non-boolean if expressions. \brief hoist non-boolean if expressions.
*/ */
void to_mbp_benchmark(std::ostream &out, const expr* fml, const app_ref_vector &vars); void to_mbp_benchmark(std::ostream &out, const expr *fml,
const app_ref_vector &vars);
// TBD: deprecate by qe::mbp // TBD: deprecate by qe::mbp
/** /**
@ -93,10 +91,8 @@ namespace spacer {
* 3. use MBP for remaining array and arith variables * 3. use MBP for remaining array and arith variables
* 4. for any remaining arith variables, substitute using M * 4. for any remaining arith variables, substitute using M
*/ */
void qe_project (ast_manager& m, app_ref_vector& vars, void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, model &mdl,
expr_ref& fml, model &mdl, bool reduce_all_selects = false, bool native_mbp = false,
bool reduce_all_selects=false,
bool native_mbp=false,
bool dont_sub = false); bool dont_sub = false);
// deprecate // deprecate
@ -108,8 +104,13 @@ namespace spacer {
expr_ref_vector compute_implicant_literals(model &mdl, expr_ref_vector compute_implicant_literals(model &mdl,
expr_ref_vector &formula); expr_ref_vector &formula);
void simplify_bounds(expr_ref_vector &lemmas); void simplify_bounds(expr_ref_vector &lemmas);
void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false); bool is_normalized(expr_ref e, bool use_simplify_bounds = true,
bool factor_eqs = false);
void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true,
bool factor_eqs = false);
void normalize_order(expr *e, expr_ref &out);
/** /**
* Ground expression by replacing all free variables by skolem * Ground expression by replacing all free variables by skolem
* constants. On return, out is the resulting expression, and vars is * constants. On return, out is the resulting expression, and vars is
@ -132,7 +133,8 @@ namespace spacer {
struct mk_epp : public mk_pp { struct mk_epp : public mk_pp {
params_ref m_epp_params; params_ref m_epp_params;
expr_ref m_epp_expr; expr_ref m_epp_expr;
mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = nullptr); mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0,
char const *var_prefix = nullptr);
void rw(expr *e, expr_ref &out); void rw(expr *e, expr_ref &out);
}; };
@ -142,5 +144,40 @@ namespace spacer {
// set f to true in model // set f to true in model
void set_true_in_mdl(model &model, func_decl *f); void set_true_in_mdl(model &model, func_decl *f);
} /// Returns number of free variables in \p e
unsigned get_num_vars(expr *e);
// Return all uninterpreted constants of \p q
void collect_uninterp_consts(expr *a, expr_ref_vector &out);
bool has_nonlinear_mul(expr *e, ast_manager &m);
// Returns true if \p e contains a multiplication a variable by not-a-number
bool has_nonlinear_var_mul(expr *e, ast_manager &m);
// check whether lit is an instance of mono_var_pattern
bool is_mono_var(expr *lit, ast_manager &m, arith_util &a_util);
// a mono_var_pattern has only one variable in the whole expression and is
// linear. lit is the literal with the variable
bool find_unique_mono_var_lit(const expr_ref &p, expr_ref &lit);
/// Drop all literals that numerically match \p lit, from \p fml_vec.
///
/// \p abs_fml holds the result. Returns true if any literal has been dropped
bool filter_out_lit(const expr_ref_vector &in, const expr_ref &lit,
expr_ref_vector &out);
/// Returns true if range of s is numeric
bool is_numeric_sub(const substitution &s);
// Returns true if \p e contains \p mod
bool contains_mod(const expr_ref &e);
// Returns true if \p e contains a real-valued sub-term
bool contains_real(const expr_ref &e);
// multiply fml with num and simplify rationals to ints
// fml should be in LIA/LRA/Arrays
// assumes that fml is a sum of products
void mul_by_rat(expr_ref &fml, rational num);
} // namespace spacer

View file

@ -286,6 +286,7 @@ namespace nlsat {
} }
bool check_invariant() const { bool check_invariant() const {
DEBUG_CODE(
SASSERT(m_sections.size() == m_sorted_sections.size()); SASSERT(m_sections.size() == m_sorted_sections.size());
for (unsigned i = 0; i < m_sorted_sections.size(); i++) { for (unsigned i = 0; i < m_sorted_sections.size(); i++) {
SASSERT(m_sorted_sections[i] < m_sections.size()); SASSERT(m_sorted_sections[i] < m_sections.size());
@ -301,7 +302,7 @@ namespace nlsat {
total_num_signs += m_info[i].m_num_roots + 1; total_num_signs += m_info[i].m_num_roots + 1;
} }
SASSERT(total_num_sections == m_poly_sections.size()); SASSERT(total_num_sections == m_poly_sections.size());
SASSERT(total_num_signs == m_poly_signs.size()); SASSERT(total_num_signs == m_poly_signs.size()););
return true; return true;
} }

View file

@ -6,6 +6,7 @@ def_module_params('solver',
('cancel_backup_file', SYMBOL, '', "file to save partial search state if search is canceled"), ('cancel_backup_file', SYMBOL, '', "file to save partial search state if search is canceled"),
('timeout', UINT, UINT_MAX, "timeout on the solver object; overwrites a global timeout"), ('timeout', UINT, UINT_MAX, "timeout on the solver object; overwrites a global timeout"),
('lemmas2console', BOOL, False, 'print lemmas during search'), ('lemmas2console', BOOL, False, 'print lemmas during search'),
('instantiations2console', BOOL, False, 'print quantifier instantiations to the console'),
('axioms2files', BOOL, False, 'print negated theory axioms to separate files during search'), ('axioms2files', BOOL, False, 'print negated theory axioms to separate files during search'),
)) ))

View file

@ -376,7 +376,7 @@ namespace mbp {
rhs = m_bv.mk_concat(2, args); rhs = m_bv.mk_concat(2, args);
} }
else if (lo > 0 && hi + 1 == sz) { else if (lo > 0 && hi + 1 == sz) {
expr* args[3] = { rhs, m_bv.mk_extract(lo - 1, 0, e) }; expr* args[2] = { rhs, m_bv.mk_extract(lo - 1, 0, e) };
rhs = m_bv.mk_concat(2, args); rhs = m_bv.mk_concat(2, args);
} }
else { else {
@ -384,6 +384,15 @@ namespace mbp {
} }
return true; return true;
} }
#if 0
expr *f = nullptr
// this one is for Nuno to find and generalize for invertible expressions
if (m_bv.is_bv_add(lhs, e, f) && is_variable(e)) {
lhs = e;
rhs = m_bv.mk_bv_sub(rhs, f);
return true;
}
#endif
return false; return false;
} }
public: public:

View file

@ -269,8 +269,6 @@ public:
} }
bool validate_model(model& model, expr_ref_vector const& fmls) { bool validate_model(model& model, expr_ref_vector const& fmls) {
expr_ref val(m);
model_evaluator eval(model);
for (expr* f : fmls) { for (expr* f : fmls) {
VERIFY(!model.is_false(f)); VERIFY(!model.is_false(f));
} }

View file

@ -39,6 +39,7 @@ z3_add_component(sat
util util
dd dd
grobner grobner
params
PYG_FILES PYG_FILES
sat_asymm_branch_params.pyg sat_asymm_branch_params.pyg
sat_params.pyg sat_params.pyg

View file

@ -112,27 +112,6 @@ static void read_clause(Buffer & in, std::ostream& err, sat::literal_vector & li
} }
} }
template<typename Buffer>
static void read_pragma(Buffer & in, std::ostream& err, std::string& p, sat::proof_hint& h) {
skip_whitespace(in);
if (*in != 'p')
return;
++in;
while (*in == ' ')
++in;
while (true) {
if (*in == EOF)
break;
if (*in == '\n') {
++in;
break;
}
p.push_back(*in);
++in;
}
if (!p.empty())
h.from_string(p);
}
template<typename Buffer> template<typename Buffer>
@ -177,25 +156,7 @@ namespace dimacs {
std::ostream& operator<<(std::ostream& out, drat_pp const& p) { std::ostream& operator<<(std::ostream& out, drat_pp const& p) {
auto const& r = p.r; auto const& r = p.r;
sat::status_pp pp(r.m_status, p.th); sat::status_pp pp(r.m_status, p.th);
switch (r.m_tag) {
case drat_record::tag_t::is_clause:
if (!r.m_pragma.empty())
return out << pp << " " << r.m_lits << " 0 p " << r.m_pragma << "\n";
return out << pp << " " << r.m_lits << " 0\n"; return out << pp << " " << r.m_lits << " 0\n";
case drat_record::tag_t::is_node:
return out << "e " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n";
case drat_record::tag_t::is_sort:
return out << "s " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n";
case drat_record::tag_t::is_decl:
return out << "f " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n";
case drat_record::tag_t::is_bool_def:
return out << "b " << r.m_node_id << " " << r.m_args << "0\n";
case drat_record::tag_t::is_var:
return out << "v " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n";
case drat_record::tag_t::is_quantifier:
return out << "q " << r.m_node_id << " " << r.m_name << " " << r.m_args << "0\n";
}
return out;
} }
char const* drat_parser::parse_identifier() { char const* drat_parser::parse_identifier() {
@ -266,47 +227,10 @@ namespace dimacs {
} }
bool drat_parser::next() { bool drat_parser::next() {
int n, b, e, theory_id; int theory_id;
auto parse_ast = [&](drat_record::tag_t tag) {
++in;
skip_whitespace(in);
n = parse_int(in, err);
skip_whitespace(in);
m_record.m_name = parse_sexpr();
m_record.m_tag = tag;
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);
}
};
auto parse_var = [&]() {
++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_var;
m_record.m_node_id = n;
m_record.m_args.reset();
n = parse_int(in, err);
if (n < 0)
throw lex_error();
m_record.m_args.push_back(n);
n = parse_int(in, err);
if (n != 0)
throw lex_error();
};
try { try {
loop: loop:
skip_whitespace(in); skip_whitespace(in);
m_record.m_pragma.clear();
m_record.m_hint.reset();
switch (*in) { switch (*in) {
case EOF: case EOF:
return false; return false;
@ -321,7 +245,6 @@ namespace dimacs {
++in; ++in;
skip_whitespace(in); skip_whitespace(in);
read_clause(in, err, m_record.m_lits); 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(); m_record.m_status = sat::status::input();
break; break;
case 'a': case 'a':
@ -331,49 +254,13 @@ namespace dimacs {
theory_id = read_theory_id(); theory_id = read_theory_id();
skip_whitespace(in); skip_whitespace(in);
read_clause(in, err, m_record.m_lits); read_clause(in, err, m_record.m_lits);
read_pragma(in, err, m_record.m_pragma, m_record.m_hint);
m_record.m_tag = drat_record::tag_t::is_clause;
m_record.m_status = sat::status::th(false, theory_id); m_record.m_status = sat::status::th(false, theory_id);
break; break;
case 'e':
// parse expression definition
parse_ast(drat_record::tag_t::is_node);
break;
case 'v':
parse_var();
break;
case 'q':
parse_ast(drat_record::tag_t::is_quantifier);
break;
case 'f':
// parse function declaration
parse_ast(drat_record::tag_t::is_decl);
break;
case 's':
// parse sort declaration (not used)
parse_ast(drat_record::tag_t::is_sort);
break;
case 'b':
// parse bridge between Boolean variable identifier b
// and expression identifier e, which is of type Bool
++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': case 'd':
// parse clause deletion // parse clause deletion
++in; ++in;
skip_whitespace(in); skip_whitespace(in);
read_clause(in, err, m_record.m_lits); 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(); m_record.m_status = sat::status::deleted();
break; break;
case 'r': case 'r':
@ -383,13 +270,11 @@ namespace dimacs {
skip_whitespace(in); skip_whitespace(in);
theory_id = read_theory_id(); theory_id = read_theory_id();
read_clause(in, err, m_record.m_lits); 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); m_record.m_status = sat::status::th(true, theory_id);
break; break;
default: default:
// parse clause redundant modulo DRAT (or mostly just DRUP) // parse clause redundant modulo DRAT (or mostly just DRUP)
read_clause(in, err, m_record.m_lits); 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(); m_record.m_status = sat::status::redundant();
break; break;
} }

View file

@ -53,18 +53,11 @@ namespace dimacs {
}; };
struct drat_record { struct drat_record {
enum class tag_t { is_clause, is_node, is_decl, is_sort, is_bool_def, is_var, is_quantifier };
tag_t m_tag{ tag_t::is_clause };
// a clause populates m_lits and m_status // a clause populates m_lits and m_status
// a node populates m_node_id, m_name, m_args // a node populates m_node_id, m_name, m_args
// a bool def populates m_node_id and one element in m_args // a bool def populates m_node_id and one element in m_args
sat::literal_vector m_lits; sat::literal_vector m_lits;
sat::status m_status = sat::status::redundant(); sat::status m_status = sat::status::redundant();
unsigned m_node_id = 0;
std::string m_name;
unsigned_vector m_args;
std::string m_pragma;
sat::proof_hint m_hint;
}; };
struct drat_pp { struct drat_pp {

View file

@ -20,6 +20,7 @@ Revision History:
#include "sat/sat_types.h" #include "sat/sat_types.h"
#include "sat/sat_params.hpp" #include "sat/sat_params.hpp"
#include "sat/sat_simplifier_params.hpp" #include "sat/sat_simplifier_params.hpp"
#include "params/solver_params.hpp"
namespace sat { namespace sat {
@ -31,6 +32,8 @@ namespace sat {
void config::updt_params(params_ref const & _p) { void config::updt_params(params_ref const & _p) {
sat_params p(_p); sat_params p(_p);
solver_params sp(_p);
m_max_memory = megabytes_to_bytes(p.max_memory()); m_max_memory = megabytes_to_bytes(p.max_memory());
symbol s = p.restart(); symbol s = p.restart();
@ -194,10 +197,10 @@ namespace sat {
m_drat_check_unsat = p.drat_check_unsat(); m_drat_check_unsat = p.drat_check_unsat();
m_drat_check_sat = p.drat_check_sat(); m_drat_check_sat = p.drat_check_sat();
m_drat_file = p.drat_file(); m_drat_file = p.drat_file();
m_drat = (m_drat_check_unsat || m_drat_file.is_non_empty_string() || m_drat_check_sat) && p.threads() == 1; m_smt_proof = p.smt_proof();
m_drat = !p.drat_disable() && (sp.lemmas2console() || m_drat_check_unsat || m_drat_file.is_non_empty_string() || m_smt_proof.is_non_empty_string() || m_drat_check_sat) && p.threads() == 1;
m_drat_binary = p.drat_binary(); m_drat_binary = p.drat_binary();
m_drat_activity = p.drat_activity(); m_drat_activity = p.drat_activity();
m_drup_trim = p.drup_trim();
m_dyn_sub_res = p.dyn_sub_res(); m_dyn_sub_res = p.dyn_sub_res();
// Parameters used in Liang, Ganesh, Poupart, Czarnecki AAAI 2016. // Parameters used in Liang, Ganesh, Poupart, Czarnecki AAAI 2016.
@ -248,13 +251,9 @@ namespace sat {
m_card_solver = p.cardinality_solver(); m_card_solver = p.cardinality_solver();
m_xor_solver = false; // prevent users from playing with this option m_xor_solver = false; // prevent users from playing with this option
sat_simplifier_params sp(_p); sat_simplifier_params ssp(_p);
m_elim_vars = sp.elim_vars(); m_elim_vars = ssp.elim_vars();
#if 0
if (m_drat && (m_xor_solver || m_card_solver))
throw sat_param_exception("DRAT checking only works for pure CNF");
#endif
} }
void config::collect_param_descrs(param_descrs & r) { void config::collect_param_descrs(param_descrs & r) {

View file

@ -177,9 +177,9 @@ namespace sat {
bool m_drat; bool m_drat;
bool m_drat_binary; bool m_drat_binary;
symbol m_drat_file; symbol m_drat_file;
symbol m_smt_proof;
bool m_drat_check_unsat; bool m_drat_check_unsat;
bool m_drat_check_sat; bool m_drat_check_sat;
bool m_drup_trim;
bool m_drat_activity; bool m_drat_activity;
bool m_card_solver; bool m_card_solver;

View file

@ -52,8 +52,7 @@ namespace sat {
void drat::updt_config() { void drat::updt_config() {
m_check_unsat = s.get_config().m_drat_check_unsat; m_check_unsat = s.get_config().m_drat_check_unsat;
m_check_sat = s.get_config().m_drat_check_sat; m_check_sat = s.get_config().m_drat_check_sat;
m_trim = s.get_config().m_drup_trim; m_check = m_check_unsat || m_check_sat;
m_check = m_check_unsat || m_check_sat || m_trim;
m_activity = s.get_config().m_drat_activity; m_activity = s.get_config().m_drat_activity;
} }
@ -130,14 +129,6 @@ namespace sat {
} }
} }
buffer[len++] = '0'; buffer[len++] = '0';
if (st.get_hint()) {
buffer[len++] = ' ';
buffer[len++] = 'p';
buffer[len++] = ' ';
auto* ps = st.get_hint();
for (auto ch : ps->to_string())
buffer[len++] = ch;
}
buffer[len++] = '\n'; buffer[len++] = '\n';
m_out->write(buffer, len); m_out->write(buffer, len);
} }
@ -210,8 +201,6 @@ namespace sat {
if (st.is_redundant() && st.is_sat()) if (st.is_redundant() && st.is_sat())
verify(1, &l); verify(1, &l);
if (m_trim)
m_proof.push_back({mk_clause(1, &l, st.is_redundant()), st});
if (st.is_deleted()) if (st.is_deleted())
return; return;
@ -230,8 +219,7 @@ namespace sat {
IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st);); IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st););
if (st.is_deleted()) { if (st.is_deleted()) {
if (m_trim) ;
m_proof.push_back({mk_clause(2, lits, true), st});
} }
else { else {
if (st.is_redundant() && st.is_sat()) if (st.is_redundant() && st.is_sat())
@ -255,31 +243,6 @@ namespace sat {
} }
} }
void drat::bool_def(bool_var v, unsigned n) {
if (m_out)
(*m_out) << "b " << v << " " << n << " 0\n";
}
void drat::def_begin(char id, unsigned n, std::string const& name) {
if (m_out)
(*m_out) << id << " " << n << " " << name;
}
void drat::def_add_arg(unsigned arg) {
if (m_out)
(*m_out) << " " << arg;
}
void drat::def_end() {
if (m_out)
(*m_out) << " 0\n";
}
void drat::log_adhoc(std::function<void(std::ostream&)>& fn) {
if (m_out)
fn(*m_out);
}
void drat::append(clause& c, status st) { void drat::append(clause& c, status st) {
TRACE("sat_drat", pp(tout, st) << " " << c << "\n";); TRACE("sat_drat", pp(tout, st) << " " << c << "\n";);
for (literal lit : c) declare(lit); for (literal lit : c) declare(lit);
@ -453,6 +416,8 @@ namespace sat {
void drat::verify(unsigned n, literal const* c) { void drat::verify(unsigned n, literal const* c) {
if (!m_check_unsat) if (!m_check_unsat)
return; return;
if (m_inconsistent)
return;
for (unsigned i = 0; i < n; ++i) for (unsigned i = 0; i < n; ++i)
declare(c[i]); declare(c[i]);
if (is_drup(n, c)) { if (is_drup(n, c)) {
@ -683,6 +648,7 @@ namespace sat {
verify(0, nullptr); verify(0, nullptr);
SASSERT(m_inconsistent); SASSERT(m_inconsistent);
} }
if (m_clause_eh) m_clause_eh->on_clause(0, nullptr, status::redundant());
} }
void drat::add(literal l, bool learned) { void drat::add(literal l, bool learned) {
++m_stats.m_num_add; ++m_stats.m_num_add;
@ -690,6 +656,8 @@ namespace sat {
if (m_out) dump(1, &l, st); if (m_out) dump(1, &l, st);
if (m_bout) bdump(1, &l, st); if (m_bout) bdump(1, &l, st);
if (m_check) append(l, st); if (m_check) append(l, st);
TRACE("sat", tout << "add " << m_clause_eh << "\n");
if (m_clause_eh) m_clause_eh->on_clause(1, &l, st);
} }
void drat::add(literal l1, literal l2, status st) { void drat::add(literal l1, literal l2, status st) {
if (st.is_deleted()) if (st.is_deleted())
@ -700,6 +668,7 @@ namespace sat {
if (m_out) dump(2, ls, st); if (m_out) dump(2, ls, st);
if (m_bout) bdump(2, ls, st); if (m_bout) bdump(2, ls, st);
if (m_check) append(l1, l2, st); if (m_check) append(l1, l2, st);
if (m_clause_eh) m_clause_eh->on_clause(2, ls, st);
} }
void drat::add(clause& c, status st) { void drat::add(clause& c, status st) {
if (st.is_deleted()) if (st.is_deleted())
@ -709,6 +678,7 @@ namespace sat {
if (m_out) dump(c.size(), c.begin(), st); if (m_out) dump(c.size(), c.begin(), st);
if (m_bout) bdump(c.size(), c.begin(), st); if (m_bout) bdump(c.size(), c.begin(), st);
if (m_check) append(mk_clause(c), st); if (m_check) append(mk_clause(c), st);
if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), st);
} }
void drat::add(literal_vector const& lits, status st) { void drat::add(literal_vector const& lits, status st) {
@ -722,13 +692,16 @@ namespace sat {
++m_stats.m_num_add; ++m_stats.m_num_add;
if (m_check) { if (m_check) {
switch (sz) { switch (sz) {
case 0: add(); break; case 0: if (st.is_input()) m_inconsistent = true; else add(); break;
case 1: append(lits[0], st); break; case 1: append(lits[0], st); break;
default: append(mk_clause(sz, lits, st.is_redundant()), st); break; default: append(mk_clause(sz, lits, st.is_redundant()), st); break;
} }
} }
if (m_out) if (m_out)
dump(sz, lits, st); dump(sz, lits, st);
if (m_clause_eh)
m_clause_eh->on_clause(sz, lits, st);
} }
void drat::add(literal_vector const& c) { void drat::add(literal_vector const& c) {
@ -748,6 +721,8 @@ namespace sat {
} }
} }
} }
if (m_clause_eh)
m_clause_eh->on_clause(c.size(), c.data(), status::redundant());
} }
void drat::del(literal l) { void drat::del(literal l) {
@ -755,6 +730,7 @@ namespace sat {
if (m_out) dump(1, &l, status::deleted()); if (m_out) dump(1, &l, status::deleted());
if (m_bout) bdump(1, &l, status::deleted()); if (m_bout) bdump(1, &l, status::deleted());
if (m_check) append(l, status::deleted()); if (m_check) append(l, status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(1, &l, status::deleted());
} }
void drat::del(literal l1, literal l2) { void drat::del(literal l1, literal l2) {
@ -763,6 +739,7 @@ namespace sat {
if (m_out) dump(2, ls, status::deleted()); if (m_out) dump(2, ls, status::deleted());
if (m_bout) bdump(2, ls, status::deleted()); if (m_bout) bdump(2, ls, status::deleted());
if (m_check) append(l1, l2, status::deleted()); if (m_check) append(l1, l2, status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(2, ls, status::deleted());
} }
void drat::del(clause& c) { void drat::del(clause& c) {
@ -781,6 +758,7 @@ namespace sat {
if (m_out) dump(c.size(), c.begin(), status::deleted()); if (m_out) dump(c.size(), c.begin(), status::deleted());
if (m_bout) bdump(c.size(), c.begin(), status::deleted()); if (m_bout) bdump(c.size(), c.begin(), status::deleted());
if (m_check) append(mk_clause(c), status::deleted()); if (m_check) append(mk_clause(c), status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted());
} }
clause& drat::mk_clause(clause& c) { clause& drat::mk_clause(clause& c) {
@ -796,23 +774,9 @@ namespace sat {
if (m_out) dump(c.size(), c.begin(), status::deleted()); if (m_out) dump(c.size(), c.begin(), status::deleted());
if (m_bout) bdump(c.size(), c.begin(), status::deleted()); if (m_bout) bdump(c.size(), c.begin(), status::deleted());
if (m_check) append(mk_clause(c.size(), c.begin(), true), status::deleted()); if (m_check) append(mk_clause(c.size(), c.begin(), true), status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted());
} }
//
// placeholder for trim function.
// 1. trail contains justification for the empty clause.
// 2. backward pass to prune.
//
svector<std::pair<clause&, status>> drat::trim() {
SASSERT(m_units.empty());
svector<std::pair<clause&, status>> proof;
for (auto const& [c, st] : m_proof)
if (!st.is_deleted())
proof.push_back({c,st});
return proof;
}
void drat::check_model(model const& m) { void drat::check_model(model const& m) {
} }
@ -844,196 +808,4 @@ namespace sat {
return out; return out;
} }
std::string proof_hint::to_string() const {
std::ostringstream ous;
switch (m_ty) {
case hint_type::null_h:
return std::string();
case hint_type::farkas_h:
ous << "farkas ";
break;
case hint_type::bound_h:
ous << "bound ";
break;
case hint_type::implied_eq_h:
ous << "implied_eq ";
break;
default:
UNREACHABLE();
break;
}
for (auto const& [q, l] : m_literals)
ous << rational(q) << " * " << l << " ";
for (auto const& [a, b] : m_eqs)
ous << " = " << a << " " << b << " ";
for (auto const& [a, b] : m_diseqs)
ous << " != " << a << " " << b << " ";
return ous.str();
}
void proof_hint::from_string(char const* s) {
proof_hint& h = *this;
h.reset();
h.m_ty = hint_type::null_h;
if (!s)
return;
auto ws = [&]() {
while (*s == ' ' || *s == '\n' || *s == '\t')
++s;
};
auto parse_type = [&]() {
if (0 == strncmp(s, "farkas", 6)) {
h.m_ty = hint_type::farkas_h;
s += 6;
return true;
}
if (0 == strncmp(s, "bound", 5)) {
h.m_ty = hint_type::bound_h;
s += 5;
return true;
}
if (0 == strncmp(s, "implied_eq", 10)) {
h.m_ty = hint_type::implied_eq_h;
s += 10;
return true;
}
return false;
};
sbuffer<char> buff;
auto parse_coeff = [&]() {
buff.reset();
while (*s && *s != ' ') {
buff.push_back(*s);
++s;
}
buff.push_back(0);
return rational(buff.data());
};
auto parse_literal = [&]() {
rational r = parse_coeff();
if (!r.is_int())
return sat::null_literal;
if (r < 0)
return sat::literal((-r).get_unsigned(), true);
return sat::literal(r.get_unsigned(), false);
};
auto parse_coeff_literal = [&]() {
if (*s == '=') {
++s;
ws();
unsigned a = parse_coeff().get_unsigned();
ws();
unsigned b = parse_coeff().get_unsigned();
h.m_eqs.push_back(std::make_pair(a, b));
return true;
}
if (*s == '!' && *(s + 1) == '=') {
s += 2;
ws();
unsigned a = parse_coeff().get_unsigned();
ws();
unsigned b = parse_coeff().get_unsigned();
h.m_diseqs.push_back(std::make_pair(a, b));
return true;
}
rational coeff = parse_coeff();
ws();
if (*s == '*') {
++s;
ws();
sat::literal lit = parse_literal();
h.m_literals.push_back(std::make_pair(coeff, lit));
return true;
}
return false;
};
ws();
if (!parse_type())
return;
ws();
while (*s) {
if (!parse_coeff_literal())
return;
ws();
}
}
#if 0
// debugging code
bool drat::is_clause(clause& c, literal l1, literal l2, literal l3, drat::status st1, drat::status st2) {
//if (st1 != st2) return false;
if (c.size() != 3) return false;
if (l1 == c[0]) {
if (l2 == c[1] && l3 == c[2]) return true;
if (l2 == c[2] && l3 == c[1]) return true;
}
if (l2 == c[0]) {
if (l1 == c[1] && l3 == c[2]) return true;
if (l1 == c[2] && l3 == c[1]) return true;
}
if (l3 == c[0]) {
if (l1 == c[1] && l2 == c[2]) return true;
if (l1 == c[2] && l2 == c[1]) return true;
}
return false;
}
#endif
#if 0
if (!m_inconsistent) {
literal_vector lits(n, c);
IF_VERBOSE(0, verbose_stream() << "not drup " << lits << "\n");
for (unsigned v = 0; v < m_assignment.size(); ++v) {
lbool val = m_assignment[v];
if (val != l_undef) {
IF_VERBOSE(0, verbose_stream() << literal(v, false) << " |-> " << val << "\n");
}
}
for (clause* cp : s.m_clauses) {
clause& cl = *cp;
bool found = false;
for (literal l : cl) {
if (m_assignment[l.var()] != (l.sign() ? l_true : l_false)) {
found = true;
break;
}
}
if (!found) {
IF_VERBOSE(0, verbose_stream() << "Clause is false under assignment: " << cl << "\n");
}
}
for (clause* cp : s.m_learned) {
clause& cl = *cp;
bool found = false;
for (literal l : cl) {
if (m_assignment[l.var()] != (l.sign() ? l_true : l_false)) {
found = true;
break;
}
}
if (!found) {
IF_VERBOSE(0, verbose_stream() << "Clause is false under assignment: " << cl << "\n");
}
}
svector<sat::solver::bin_clause> bin;
s.collect_bin_clauses(bin, true);
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;
if (!found) {
IF_VERBOSE(0, verbose_stream() << "Bin clause is false under assignment: " << b.first << " " << b.second << "\n");
}
}
IF_VERBOSE(0, s.display(verbose_stream()));
exit(0);
}
#endif
} }

View file

@ -60,6 +60,11 @@ namespace sat {
class justification; class justification;
class clause; class clause;
struct clause_eh {
virtual ~clause_eh() {}
virtual void on_clause(unsigned, literal const*, status) = 0;
};
class drat { class drat {
struct stats { struct stats {
unsigned m_num_drup = 0; unsigned m_num_drup = 0;
@ -73,6 +78,7 @@ namespace sat {
watched_clause(clause* c, literal l1, literal l2): watched_clause(clause* c, literal l1, literal l2):
m_clause(c), m_l1(l1), m_l2(l2) {} m_clause(c), m_l1(l1), m_l2(l2) {}
}; };
clause_eh* m_clause_eh = nullptr;
svector<watched_clause> m_watched_clauses; svector<watched_clause> m_watched_clauses;
typedef svector<unsigned> watch; typedef svector<unsigned> watch;
solver& s; solver& s;
@ -89,9 +95,9 @@ namespace sat {
bool m_check_sat = false; bool m_check_sat = false;
bool m_check = false; bool m_check = false;
bool m_activity = false; bool m_activity = false;
bool m_trim = false;
stats m_stats; stats m_stats;
void dump_activity(); void dump_activity();
void dump(unsigned n, literal const* c, status st); void dump(unsigned n, literal const* c, status st);
void bdump(unsigned n, literal const* c, status st); void bdump(unsigned n, literal const* c, status st);
@ -138,17 +144,9 @@ namespace sat {
void add(literal_vector const& c); // add learned clause void add(literal_vector const& c); // add learned clause
void add(unsigned sz, literal const* lits, status st); void add(unsigned sz, literal const* lits, status st);
// support for SMT - connect Boolean variables with AST nodes void set_clause_eh(clause_eh& clause_eh) { m_clause_eh = &clause_eh; }
// associate AST node id with Boolean variable v
void bool_def(bool_var v, unsigned n);
// declare AST node n with 'name' and arguments arg std::ostream* out() { return m_out; }
void def_begin(char id, unsigned n, std::string const& name);
void def_add_arg(unsigned arg);
void def_end();
// ad-hoc logging until a format is developed
void log_adhoc(std::function<void(std::ostream&)>& fn);
bool is_cleaned(clause& c) const; bool is_cleaned(clause& c) const;
void del(literal l); void del(literal l);
@ -176,8 +174,6 @@ namespace sat {
bool is_drup(unsigned n, literal const* c, literal_vector& units); bool is_drup(unsigned n, literal const* c, literal_vector& units);
solver& get_solver() { return s; } solver& get_solver() { return s; }
svector<std::pair<clause&, status>> trim();
}; };
} }

View file

@ -46,11 +46,12 @@ def_module_params('sat',
('backtrack.conflicts', UINT, 4000, 'number of conflicts before enabling chronological backtracking'), ('backtrack.conflicts', UINT, 4000, 'number of conflicts before enabling chronological backtracking'),
('threads', UINT, 1, 'number of parallel threads to use'), ('threads', UINT, 1, 'number of parallel threads to use'),
('dimacs.core', BOOL, False, 'extract core from DIMACS benchmarks'), ('dimacs.core', BOOL, False, 'extract core from DIMACS benchmarks'),
('drat.disable', BOOL, False, 'override anything that enables DRAT'),
('smt.proof', SYMBOL, '', 'add SMT proof to file'),
('drat.file', SYMBOL, '', 'file to dump DRAT proofs'), ('drat.file', SYMBOL, '', 'file to dump DRAT proofs'),
('drat.binary', BOOL, False, 'use Binary DRAT output format'), ('drat.binary', BOOL, False, 'use Binary DRAT output format'),
('drat.check_unsat', BOOL, False, 'build up internal proof and check'), ('drat.check_unsat', BOOL, False, 'build up internal proof and check'),
('drat.check_sat', BOOL, False, 'build up internal trace, check satisfying model'), ('drat.check_sat', BOOL, False, 'build up internal trace, check satisfying model'),
('drup.trim', BOOL, False, 'build and trim drup proof'),
('drat.activity', BOOL, False, 'dump variable activities'), ('drat.activity', BOOL, False, 'dump variable activities'),
('cardinality.solver', BOOL, True, 'use cardinality solver'), ('cardinality.solver', BOOL, True, 'use cardinality solver'),
('pb.solver', SYMBOL, 'solver', 'method for handling Pseudo-Boolean constraints: circuit (arithmetical circuit), sorting (sorting circuit), totalizer (use totalizer encoding), binary_merge, segmented, solver (use native solver)'), ('pb.solver', SYMBOL, 'solver', 'method for handling Pseudo-Boolean constraints: circuit (arithmetical circuit), sorting (sorting circuit), totalizer (use totalizer encoding), binary_merge, segmented, solver (use native solver)'),

View file

@ -402,6 +402,7 @@ namespace sat {
extension::scoped_drating _sd(*m_ext.get()); extension::scoped_drating _sd(*m_ext.get());
if (j.get_kind() == justification::EXT_JUSTIFICATION) if (j.get_kind() == justification::EXT_JUSTIFICATION)
fill_ext_antecedents(lit, j, false); fill_ext_antecedents(lit, j, false);
TRACE("sat", tout << "drat-unit\n");
m_drat.add(lit, m_searching); m_drat.add(lit, m_searching);
} }
@ -412,6 +413,7 @@ namespace sat {
clause * solver::mk_clause_core(unsigned num_lits, literal * lits, sat::status st) { clause * solver::mk_clause_core(unsigned num_lits, literal * lits, sat::status st) {
bool redundant = st.is_redundant(); bool redundant = st.is_redundant();
TRACE("sat", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";); TRACE("sat", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";);
bool logged = false;
if (!redundant || !st.is_sat()) { if (!redundant || !st.is_sat()) {
unsigned old_sz = num_lits; unsigned old_sz = num_lits;
bool keep = simplify_clause(num_lits, lits); bool keep = simplify_clause(num_lits, lits);
@ -420,8 +422,10 @@ namespace sat {
return nullptr; // clause is equivalent to true. return nullptr; // clause is equivalent to true.
} }
// if an input clause is simplified, then log the simplified version as learned // if an input clause is simplified, then log the simplified version as learned
if (m_config.m_drat && old_sz > num_lits) if (m_config.m_drat && old_sz > num_lits) {
drat_log_clause(num_lits, lits, st); drat_log_clause(num_lits, lits, st);
logged = true;
}
++m_stats.m_non_learned_generation; ++m_stats.m_non_learned_generation;
if (!m_searching) { if (!m_searching) {
@ -435,7 +439,7 @@ namespace sat {
set_conflict(); set_conflict();
return nullptr; return nullptr;
case 1: case 1:
if (m_config.m_drat && (!st.is_sat() || st.is_input())) if (!logged && m_config.m_drat && (!st.is_sat() || st.is_input()))
drat_log_clause(num_lits, lits, st); drat_log_clause(num_lits, lits, st);
assign_unit(lits[0]); assign_unit(lits[0]);
return nullptr; return nullptr;
@ -945,7 +949,6 @@ namespace sat {
if (j.level() == 0) { if (j.level() == 0) {
if (m_config.m_drat) if (m_config.m_drat)
drat_log_unit(l, j); drat_log_unit(l, j);
if (!m_config.m_drup_trim)
j = justification(0); // erase justification for level 0 j = justification(0); // erase justification for level 0
} }
else { else {

View file

@ -84,7 +84,7 @@ namespace sat {
}; };
struct no_drat_params : public params_ref { struct no_drat_params : public params_ref {
no_drat_params() { set_sym("drat.file", symbol()); } no_drat_params() { set_bool("drat.disable", true); }
}; };
class solver : public solver_core { class solver : public solver_core {

View file

@ -94,22 +94,9 @@ namespace sat {
}; };
enum class hint_type { class proof_hint {
null_h, public:
farkas_h, virtual ~proof_hint() {}
bound_h,
implied_eq_h,
};
struct proof_hint {
hint_type m_ty = hint_type::null_h;
vector<std::pair<rational, literal>> m_literals;
vector<std::pair<unsigned, unsigned>> m_eqs;
vector<std::pair<unsigned, unsigned>> m_diseqs;
void reset() { m_ty = hint_type::null_h; m_literals.reset(); m_eqs.reset(); m_diseqs.reset(); }
std::string to_string() const;
void from_string(char const* s);
void from_string(std::string const& s) { from_string(s.c_str()); }
}; };
class status { class status {

View file

@ -21,6 +21,7 @@ z3_add_component(sat_smt
euf_invariant.cpp euf_invariant.cpp
euf_model.cpp euf_model.cpp
euf_proof.cpp euf_proof.cpp
euf_proof_checker.cpp
euf_relevancy.cpp euf_relevancy.cpp
euf_solver.cpp euf_solver.cpp
fpa_solver.cpp fpa_solver.cpp

View file

@ -264,11 +264,12 @@ namespace arith {
SASSERT(k1 != k2 || kind1 != kind2); SASSERT(k1 != k2 || kind1 != kind2);
auto bin_clause = [&](sat::literal l1, sat::literal l2) { auto bin_clause = [&](sat::literal l1, sat::literal l2) {
sat::proof_hint* bound_params = nullptr; arith_proof_hint* bound_params = nullptr;
if (ctx.use_drat()) { if (ctx.use_drat()) {
bound_params = &m_farkas2; m_arith_hint.set_type(ctx, hint_type::farkas_h);
m_farkas2.m_literals[0] = std::make_pair(rational(1), ~l1); m_arith_hint.add_lit(rational(1), ~l1);
m_farkas2.m_literals[1] = std::make_pair(rational(1), ~l2); m_arith_hint.add_lit(rational(1), ~l2);
bound_params = m_arith_hint.mk(ctx);
} }
add_clause(l1, l2, bound_params); add_clause(l1, l2, bound_params);
}; };

View file

@ -15,6 +15,8 @@ Author:
--*/ --*/
#include "ast/ast_util.h"
#include "ast/scoped_proof.h"
#include "sat/smt/euf_solver.h" #include "sat/smt/euf_solver.h"
#include "sat/smt/arith_solver.h" #include "sat/smt/arith_solver.h"
@ -81,7 +83,6 @@ namespace arith {
} }
void solver::explain_assumptions() { void solver::explain_assumptions() {
m_arith_hint.reset();
unsigned i = 0; unsigned i = 0;
for (auto const & ev : m_explanation) { for (auto const & ev : m_explanation) {
++i; ++i;
@ -91,14 +92,12 @@ namespace arith {
switch (m_constraint_sources[idx]) { switch (m_constraint_sources[idx]) {
case inequality_source: { case inequality_source: {
literal lit = m_inequalities[idx]; literal lit = m_inequalities[idx];
m_arith_hint.m_literals.push_back({ev.coeff(), lit}); m_arith_hint.add_lit(ev.coeff(), lit);
break; break;
} }
case equality_source: { case equality_source: {
auto [u, v] = m_equalities[idx]; auto [u, v] = m_equalities[idx];
ctx.drat_log_expr(u->get_expr()); m_arith_hint.add_eq(u, v);
ctx.drat_log_expr(v->get_expr());
m_arith_hint.m_eqs.push_back({u->get_expr_id(), v->get_expr_id()});
break; break;
} }
default: default:
@ -115,22 +114,65 @@ namespace arith {
* such that there is a r >= 1 * such that there is a r >= 1
* (r1*a1+..+r_k*a_k) = r*a, (r1*b1+..+r_k*b_k) <= r*b * (r1*a1+..+r_k*a_k) = r*a, (r1*b1+..+r_k*b_k) <= r*b
*/ */
sat::proof_hint const* solver::explain(sat::hint_type ty, sat::literal lit) { arith_proof_hint const* solver::explain(hint_type ty, sat::literal lit) {
if (!ctx.use_drat()) if (!ctx.use_drat())
return nullptr; return nullptr;
m_arith_hint.m_ty = ty; m_arith_hint.set_type(ctx, ty);
explain_assumptions(); explain_assumptions();
if (lit != sat::null_literal) if (lit != sat::null_literal)
m_arith_hint.m_literals.push_back({rational(1), ~lit}); m_arith_hint.add_lit(rational(1), ~lit);
return &m_arith_hint; return m_arith_hint.mk(ctx);
} }
sat::proof_hint const* solver::explain_implied_eq(euf::enode* a, euf::enode* b) { arith_proof_hint const* solver::explain_implied_eq(euf::enode* a, euf::enode* b) {
if (!ctx.use_drat()) if (!ctx.use_drat())
return nullptr; return nullptr;
m_arith_hint.m_ty = sat::hint_type::implied_eq_h; m_arith_hint.set_type(ctx, hint_type::implied_eq_h);
explain_assumptions(); explain_assumptions();
m_arith_hint.m_diseqs.push_back({a->get_expr_id(), b->get_expr_id()}); m_arith_hint.add_diseq(a, b);
return &m_arith_hint; return m_arith_hint.mk(ctx);
}
expr* arith_proof_hint::get_hint(euf::solver& s) const {
ast_manager& m = s.get_manager();
family_id fid = m.get_family_id("arith");
arith_util arith(m);
solver& a = dynamic_cast<solver&>(*s.fid2solver(fid));
char const* name;
switch (m_ty) {
case hint_type::farkas_h:
name = "farkas";
break;
case hint_type::bound_h:
name = "bound";
break;
case hint_type::implied_eq_h:
name = "implied-eq";
break;
}
rational lc(1);
for (unsigned i = m_lit_head; i < m_lit_tail; ++i)
lc = lcm(lc, denominator(a.m_arith_hint.lit(i).first));
expr_ref_vector args(m);
sort_ref_vector sorts(m);
for (unsigned i = m_lit_head; i < m_lit_tail; ++i) {
auto const& [coeff, lit] = a.m_arith_hint.lit(i);
args.push_back(arith.mk_int(abs(coeff*lc)));
args.push_back(s.literal2expr(lit));
}
for (unsigned i = m_eq_head; i < m_eq_tail; ++i) {
auto const& [x, y, is_eq] = a.m_arith_hint.eq(i);
expr_ref eq(m.mk_eq(x->get_expr(), y->get_expr()), m);
if (!is_eq) eq = m.mk_not(eq);
args.push_back(arith.mk_int(1));
args.push_back(eq);
}
for (expr* arg : args)
sorts.push_back(arg->get_sort());
sort* range = m.mk_proof_sort();
func_decl* d = m.mk_func_decl(symbol(name), args.size(), sorts.data(), range);
expr* r = m.mk_app(d, args);
return r;
} }
} }

View file

@ -1,5 +1,5 @@
/*++ /*++
Copyright (c) 2020 Microsoft Corporation Copyright (c) 2022 Microsoft Corporation
Module Name: Module Name:
@ -11,7 +11,15 @@ Abstract:
Author: Author:
Nikolaj Bjorner (nbjorner) 2020-09-08 Nikolaj Bjorner (nbjorner) 2022-08-28
Notes:
The module assumes a limited repertoire of arithmetic proof rules.
- farkas - inequalities, equalities and disequalities with coefficients
- implied-eq - last literal is a disequality. The literals before imply the corresponding equality.
- bound - last literal is a bound. It is implied by prior literals.
--*/ --*/
#pragma once #pragma once
@ -19,11 +27,12 @@ Author:
#include "util/obj_pair_set.h" #include "util/obj_pair_set.h"
#include "ast/ast_trail.h" #include "ast/ast_trail.h"
#include "ast/arith_decl_plugin.h" #include "ast/arith_decl_plugin.h"
#include "sat/smt/euf_proof_checker.h"
namespace arith { namespace arith {
class proof_checker { class proof_checker : public euf::proof_checker_plugin {
struct row { struct row {
obj_map<expr, rational> m_coeffs; obj_map<expr, rational> m_coeffs;
rational m_coeff; rational m_coeff;
@ -131,11 +140,13 @@ namespace arith {
SASSERT(m_todo.empty()); SASSERT(m_todo.empty());
m_todo.push_back({ mul, e }); m_todo.push_back({ mul, e });
rational coeff1; rational coeff1;
expr* e1, *e2; expr* e1, *e2, *e3;
for (unsigned i = 0; i < m_todo.size(); ++i) { for (unsigned i = 0; i < m_todo.size(); ++i) {
auto [coeff, e] = m_todo[i]; auto [coeff, e] = m_todo[i];
if (a.is_mul(e, e1, e2) && a.is_numeral(e1, coeff1)) if (a.is_mul(e, e1, e2) && a.is_numeral(e1, coeff1))
m_todo.push_back({coeff*coeff1, e2}); m_todo.push_back({coeff*coeff1, e2});
else if (a.is_mul(e, e1, e2) && a.is_uminus(e1, e3) && a.is_numeral(e3, coeff1))
m_todo.push_back({-coeff*coeff1, e2});
else if (a.is_mul(e, e1, e2) && a.is_numeral(e2, coeff1)) else if (a.is_mul(e, e1, e2) && a.is_numeral(e2, coeff1))
m_todo.push_back({coeff*coeff1, e1}); m_todo.push_back({coeff*coeff1, e1});
else if (a.is_add(e)) else if (a.is_add(e))
@ -149,6 +160,8 @@ namespace arith {
} }
else if (a.is_numeral(e, coeff1)) else if (a.is_numeral(e, coeff1))
r.m_coeff += coeff*coeff1; r.m_coeff += coeff*coeff1;
else if (a.is_uminus(e, e1) && a.is_numeral(e1, coeff1))
r.m_coeff -= coeff*coeff1;
else else
add(r, e, coeff); add(r, e, coeff);
} }
@ -301,6 +314,8 @@ namespace arith {
public: public:
proof_checker(ast_manager& m): m(m), a(m) {} proof_checker(ast_manager& m): m(m), a(m) {}
~proof_checker() override {}
void reset() { void reset() {
m_ineq.reset(); m_ineq.reset();
m_conseq.reset(); m_conseq.reset();
@ -350,6 +365,70 @@ namespace arith {
return out; return out;
} }
bool check(expr_ref_vector const& clause, app* jst, expr_ref_vector& units) override {
reset();
expr_mark pos, neg;
for (expr* e : clause)
if (m.is_not(e, e))
neg.mark(e, true);
else
pos.mark(e, true);
if (jst->get_name() == symbol("farkas")) {
bool even = true;
rational coeff;
expr* x, *y;
for (expr* arg : *jst) {
if (even) {
if (!a.is_numeral(arg, coeff)) {
IF_VERBOSE(0, verbose_stream() << "not numeral " << mk_pp(jst, m) << "\n");
return false;
}
}
else {
bool sign = m.is_not(arg, arg);
if (a.is_le(arg) || a.is_lt(arg) || a.is_ge(arg) || a.is_gt(arg))
add_ineq(coeff, arg, sign);
else if (m.is_eq(arg, x, y)) {
if (sign)
add_diseq(x, y);
else
add_eq(x, y);
}
else
return false;
if (sign && !pos.is_marked(arg)) {
units.push_back(m.mk_not(arg));
pos.mark(arg, false);
}
else if (!sign && !neg.is_marked(arg)) {
units.push_back(arg);
neg.mark(arg, false);
}
}
even = !even;
}
if (check_farkas()) {
return true;
}
IF_VERBOSE(0, verbose_stream() << "did not check farkas\n" << mk_pp(jst, m) << "\n"; display(verbose_stream()); );
return false;
}
// todo: rules for bounds and implied-by
IF_VERBOSE(0, verbose_stream() << "did not check " << mk_pp(jst, m) << "\n");
return false;
}
void register_plugins(euf::proof_checker& pc) override {
pc.register_plugin(symbol("farkas"), this);
pc.register_plugin(symbol("bound"), this);
pc.register_plugin(symbol("implied-eq"), this);
}
}; };

View file

@ -39,8 +39,6 @@ namespace arith {
lp().settings().set_random_seed(get_config().m_random_seed); lp().settings().set_random_seed(get_config().m_random_seed);
m_lia = alloc(lp::int_solver, *m_solver.get()); m_lia = alloc(lp::int_solver, *m_solver.get());
m_farkas2.m_ty = sat::hint_type::farkas_h;
m_farkas2.m_literals.resize(2);
} }
solver::~solver() { solver::~solver() {
@ -197,11 +195,12 @@ namespace arith {
reset_evidence(); reset_evidence();
m_core.push_back(lit1); m_core.push_back(lit1);
TRACE("arith", tout << lit2 << " <- " << m_core << "\n";); TRACE("arith", tout << lit2 << " <- " << m_core << "\n";);
sat::proof_hint* ph = nullptr; arith_proof_hint* ph = nullptr;
if (ctx.use_drat()) { if (ctx.use_drat()) {
ph = &m_farkas2; m_arith_hint.set_type(ctx, hint_type::farkas_h);
m_farkas2.m_literals[0] = std::make_pair(rational(1), lit1); m_arith_hint.add_lit(rational(1), lit1);
m_farkas2.m_literals[1] = std::make_pair(rational(1), ~lit2); m_arith_hint.add_lit(rational(1), ~lit2);
ph = m_arith_hint.mk(ctx);
} }
assign(lit2, m_core, m_eqs, ph); assign(lit2, m_core, m_eqs, ph);
++m_stats.m_bounds_propagations; ++m_stats.m_bounds_propagations;
@ -262,7 +261,7 @@ namespace arith {
TRACE("arith", for (auto lit : m_core) tout << lit << ": " << s().value(lit) << "\n";); TRACE("arith", for (auto lit : m_core) tout << lit << ": " << s().value(lit) << "\n";);
DEBUG_CODE(for (auto lit : m_core) { VERIFY(s().value(lit) == l_true); }); DEBUG_CODE(for (auto lit : m_core) { VERIFY(s().value(lit) == l_true); });
++m_stats.m_bound_propagations1; ++m_stats.m_bound_propagations1;
assign(lit, m_core, m_eqs, explain(sat::hint_type::bound_h, lit)); assign(lit, m_core, m_eqs, explain(hint_type::bound_h, lit));
} }
if (should_refine_bounds() && first) if (should_refine_bounds() && first)
@ -378,7 +377,7 @@ namespace arith {
reset_evidence(); reset_evidence();
m_explanation.clear(); m_explanation.clear();
lp().explain_implied_bound(be, m_bp); lp().explain_implied_bound(be, m_bp);
assign(bound, m_core, m_eqs, explain(sat::hint_type::farkas_h, bound)); assign(bound, m_core, m_eqs, explain(hint_type::farkas_h, bound));
} }
@ -1178,7 +1177,7 @@ namespace arith {
app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper()); app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper());
IF_VERBOSE(4, verbose_stream() << "cut " << b << "\n"); IF_VERBOSE(4, verbose_stream() << "cut " << b << "\n");
literal lit = expr2literal(b); literal lit = expr2literal(b);
assign(lit, m_core, m_eqs, explain(sat::hint_type::bound_h, lit)); assign(lit, m_core, m_eqs, explain(hint_type::bound_h, lit));
lia_check = l_false; lia_check = l_false;
break; break;
} }
@ -1200,7 +1199,7 @@ namespace arith {
return lia_check; return lia_check;
} }
void solver::assign(literal lit, literal_vector const& core, svector<enode_pair> const& eqs, sat::proof_hint const* pma) { void solver::assign(literal lit, literal_vector const& core, svector<enode_pair> const& eqs, euf::th_proof_hint const* pma) {
if (core.size() < small_lemma_size() && eqs.empty()) { if (core.size() < small_lemma_size() && eqs.empty()) {
m_core2.reset(); m_core2.reset();
for (auto const& c : core) for (auto const& c : core)
@ -1247,7 +1246,7 @@ namespace arith {
for (literal& c : m_core) for (literal& c : m_core)
c.neg(); c.neg();
add_clause(m_core, explain(sat::hint_type::farkas_h)); add_clause(m_core, explain(hint_type::farkas_h));
} }
bool solver::is_infeasible() const { bool solver::is_infeasible() const {

View file

@ -48,8 +48,61 @@ namespace arith {
typedef sat::literal_vector literal_vector; typedef sat::literal_vector literal_vector;
typedef lp_api::bound<sat::literal> api_bound; typedef lp_api::bound<sat::literal> api_bound;
enum class hint_type {
farkas_h,
bound_h,
implied_eq_h
};
struct arith_proof_hint : public euf::th_proof_hint {
hint_type m_ty;
unsigned m_lit_head, m_lit_tail, m_eq_head, m_eq_tail;
arith_proof_hint(hint_type t, unsigned lh, unsigned lt, unsigned eh, unsigned et):
m_ty(t), m_lit_head(lh), m_lit_tail(lt), m_eq_head(eh), m_eq_tail(et) {}
expr* get_hint(euf::solver& s) const override;
};
class arith_proof_hint_builder {
vector<std::pair<rational, literal>> m_literals;
svector<std::tuple<euf::enode*,euf::enode*,bool>> m_eqs;
hint_type m_ty;
unsigned m_lit_head = 0, m_lit_tail = 0, m_eq_head = 0, m_eq_tail;
void reset() { m_lit_head = m_lit_tail; m_eq_head = m_eq_tail; }
void add(euf::enode* a, euf::enode* b, bool is_eq) {
if (m_eq_tail < m_eqs.size())
m_eqs[m_eq_tail] = std::tuple(a, b, is_eq);
else
m_eqs.push_back(std::tuple(a, b, is_eq));
m_eq_tail++;
}
public:
void set_type(euf::solver& ctx, hint_type ty) {
ctx.push(value_trail<unsigned>(m_eq_tail));
ctx.push(value_trail<unsigned>(m_lit_tail));
m_ty = ty;
reset();
}
void add_eq(euf::enode* a, euf::enode* b) { add(a, b, true); }
void add_diseq(euf::enode* a, euf::enode* b) { add(a, b, false); }
void add_lit(rational const& coeff, literal lit) {
if (m_lit_tail < m_literals.size())
m_literals[m_lit_tail] = {coeff, lit};
else
m_literals.push_back({coeff, lit});
m_lit_tail++;
}
std::pair<rational, literal> const& lit(unsigned i) const { return m_literals[i]; }
std::tuple<enode*, enode*, bool> const& eq(unsigned i) const { return m_eqs[i]; }
arith_proof_hint* mk(euf::solver& s) {
return new (s.get_region()) arith_proof_hint(m_ty, m_lit_head, m_lit_tail, m_eq_head, m_eq_tail);
}
};
class solver : public euf::th_euf_solver { class solver : public euf::th_euf_solver {
friend struct arith_proof_hint;
struct scope { struct scope {
unsigned m_bounds_lim; unsigned m_bounds_lim;
unsigned m_idiv_lim; unsigned m_idiv_lim;
@ -414,15 +467,15 @@ namespace arith {
void set_conflict(); void set_conflict();
void set_conflict_or_lemma(literal_vector const& core, bool is_conflict); void set_conflict_or_lemma(literal_vector const& core, bool is_conflict);
void set_evidence(lp::constraint_index idx); void set_evidence(lp::constraint_index idx);
void assign(literal lit, literal_vector const& core, svector<enode_pair> const& eqs, sat::proof_hint const* pma); void assign(literal lit, literal_vector const& core, svector<enode_pair> const& eqs, euf::th_proof_hint const* pma);
void false_case_of_check_nla(const nla::lemma& l); void false_case_of_check_nla(const nla::lemma& l);
void dbg_finalize_model(model& mdl); void dbg_finalize_model(model& mdl);
sat::proof_hint m_arith_hint; arith_proof_hint_builder m_arith_hint;
sat::proof_hint m_farkas2;
sat::proof_hint const* explain(sat::hint_type ty, sat::literal lit = sat::null_literal); arith_proof_hint const* explain(hint_type ty, sat::literal lit = sat::null_literal);
sat::proof_hint const* explain_implied_eq(euf::enode* a, euf::enode* b); arith_proof_hint const* explain_implied_eq(euf::enode* a, euf::enode* b);
void explain_assumptions(); void explain_assumptions();

View file

@ -49,16 +49,19 @@ namespace bv {
update_glue(*other); update_glue(*other);
vv::push_to_front(m_queue, other); vv::push_to_front(m_queue, other);
if (other == n) { bool do_gc = other == n;
if (other == n)
new_tmp(); new_tmp();
gc();
}
if (other->m_glue == 0) { if (other->m_glue == 0) {
do_gc = false;
remove(other); remove(other);
add_cc(v1, v2); add_cc(v1, v2);
} }
else if (other->m_count > 2*m_propagate_high_watermark) else if (other->m_count > 2*m_propagate_high_watermark)
propagate(); propagate();
if (do_gc)
gc();
} }
void ackerman::used_diseq_eh(euf::theory_var v1, euf::theory_var v2) { void ackerman::used_diseq_eh(euf::theory_var v1, euf::theory_var v2) {

View file

@ -429,6 +429,8 @@ namespace bv {
args.push_back(m.mk_ite(b, m_autil.mk_int(power2(i++)), zero)); args.push_back(m.mk_ite(b, m_autil.mk_int(power2(i++)), zero));
expr_ref sum(m_autil.mk_add(sz, args.data()), m); expr_ref sum(m_autil.mk_add(sz, args.data()), m);
sat::literal lit = eq_internalize(n, sum); sat::literal lit = eq_internalize(n, sum);
m_bv2ints.push_back(expr2enode(n));
ctx.push(push_back_vector<euf::enode_vector>(m_bv2ints));
add_unit(lit); add_unit(lit);
} }

View file

@ -211,9 +211,8 @@ namespace bv {
return; return;
} }
euf::enode* n1 = var2enode(eq.v1()); euf::enode* n1 = var2enode(eq.v1());
for (euf::enode* bv2int : euf::enode_class(n1)) {
if (!bv.is_bv2int(bv2int->get_expr())) auto propagate_bv2int = [&](euf::enode* bv2int) {
continue;
euf::enode* bv2int_arg = bv2int->get_arg(0); euf::enode* bv2int_arg = bv2int->get_arg(0);
for (euf::enode* p : euf::enode_parents(n1->get_root())) { for (euf::enode* p : euf::enode_parents(n1->get_root())) {
if (bv.is_int2bv(p->get_expr()) && p->get_sort() == bv2int_arg->get_sort() && p->get_root() != bv2int_arg->get_root()) { if (bv.is_int2bv(p->get_expr()) && p->get_sort() == bv2int_arg->get_sort() && p->get_root() != bv2int_arg->get_root()) {
@ -224,6 +223,19 @@ namespace bv {
break; break;
} }
} }
};
if (m_bv2ints.size() < n1->class_size()) {
for (auto* bv2int : m_bv2ints) {
if (bv2int->get_root() == n1->get_root())
propagate_bv2int(bv2int);
}
}
else {
for (euf::enode* bv2int : euf::enode_class(n1)) {
if (bv.is_bv2int(bv2int->get_expr()))
propagate_bv2int(bv2int);
}
} }
} }
@ -279,6 +291,8 @@ namespace bv {
++m_stats.m_num_ne2bit; ++m_stats.m_num_ne2bit;
s().assign(consequent, mk_ne2bit_justification(undef_idx, v1, v2, consequent, antecedent)); s().assign(consequent, mk_ne2bit_justification(undef_idx, v1, v2, consequent, antecedent));
} }
else if (!get_config().m_bv_eq_axioms)
;
else if (s().at_search_lvl()) { else if (s().at_search_lvl()) {
force_push(); force_push();
assert_ackerman(v1, v2); assert_ackerman(v1, v2);
@ -378,7 +392,7 @@ namespace bv {
expr* e1 = var2expr(c.m_v1); expr* e1 = var2expr(c.m_v1);
expr* e2 = var2expr(c.m_v2); expr* e2 = var2expr(c.m_v2);
eq = m.mk_eq(e1, e2); eq = m.mk_eq(e1, e2);
ctx.drat_eq_def(leq, eq); ctx.set_tmp_bool_var(leq.var(), eq);
} }
sat::literal_vector lits; sat::literal_vector lits;

View file

@ -207,8 +207,9 @@ namespace bv {
literal_vector m_tmp_literals; literal_vector m_tmp_literals;
svector<propagation_item> m_prop_queue; svector<propagation_item> m_prop_queue;
unsigned_vector m_prop_queue_lim; unsigned_vector m_prop_queue_lim;
unsigned m_prop_queue_head { 0 }; unsigned m_prop_queue_head = 0;
sat::literal m_true { sat::null_literal }; sat::literal m_true = sat::null_literal;
euf::enode_vector m_bv2ints;
// internalize // internalize
void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); } void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); }

View file

@ -16,107 +16,26 @@ Author:
--*/ --*/
#include "sat/smt/euf_solver.h" #include "sat/smt/euf_solver.h"
#include "ast/ast_util.h"
#include <iostream>
namespace euf { namespace euf {
void solver::init_drat() { void solver::init_proof() {
if (!m_drat_initialized) { if (!m_proof_initialized) {
get_drat().add_theory(get_id(), symbol("euf")); get_drat().add_theory(get_id(), symbol("euf"));
get_drat().add_theory(m.get_basic_family_id(), symbol("bool")); get_drat().add_theory(m.get_basic_family_id(), symbol("bool"));
} }
m_drat_initialized = true; if (!m_proof_out && s().get_config().m_drat &&
(get_config().m_lemmas2console || s().get_config().m_smt_proof.is_non_empty_string())) {
TRACE("euf", tout << "init-proof\n");
m_proof_out = alloc(std::ofstream, s().get_config().m_smt_proof.str(), std::ios_base::out);
if (get_config().m_lemmas2console)
get_drat().set_clause_eh(*this);
if (s().get_config().m_smt_proof.is_non_empty_string())
get_drat().set_clause_eh(*this);
} }
m_proof_initialized = true;
void solver::drat_log_params(func_decl* f) {
for (unsigned i = f->get_num_parameters(); i-- > 0; ) {
auto const& p = f->get_parameter(i);
if (!p.is_ast())
continue;
ast* a = p.get_ast();
if (is_func_decl(a))
drat_log_decl(to_func_decl(a));
}
}
void solver::drat_log_expr1(expr* e) {
if (is_app(e)) {
app* a = to_app(e);
drat_log_params(a->get_decl());
drat_log_decl(a->get_decl());
std::stringstream strm;
strm << mk_ismt2_func(a->get_decl(), m);
get_drat().def_begin('e', e->get_id(), strm.str());
for (expr* arg : *a)
get_drat().def_add_arg(arg->get_id());
get_drat().def_end();
}
else if (is_var(e)) {
var* v = to_var(e);
get_drat().def_begin('v', v->get_id(), "" + mk_pp(e->get_sort(), m));
get_drat().def_add_arg(v->get_idx());
get_drat().def_end();
}
else if (is_quantifier(e)) {
quantifier* q = to_quantifier(e);
std::stringstream strm;
strm << "(" << (is_forall(q) ? "forall" : (is_exists(q) ? "exists" : "lambda"));
for (unsigned i = 0; i < q->get_num_decls(); ++i)
strm << " (" << q->get_decl_name(i) << " " << mk_pp(q->get_decl_sort(i), m) << ")";
strm << ")";
get_drat().def_begin('q', q->get_id(), strm.str());
get_drat().def_add_arg(q->get_expr()->get_id());
get_drat().def_end();
}
else
UNREACHABLE();
m_drat_asts.insert(e);
push(insert_obj_trail<ast>(m_drat_asts, e));
}
void solver::drat_log_expr(expr* e) {
if (m_drat_asts.contains(e))
return;
ptr_vector<expr>::scoped_stack _sc(m_drat_todo);
m_drat_todo.push_back(e);
while (!m_drat_todo.empty()) {
e = m_drat_todo.back();
unsigned sz = m_drat_todo.size();
if (is_app(e))
for (expr* arg : *to_app(e))
if (!m_drat_asts.contains(arg))
m_drat_todo.push_back(arg);
if (is_quantifier(e)) {
expr* arg = to_quantifier(e)->get_expr();
if (!m_drat_asts.contains(arg))
m_drat_todo.push_back(arg);
}
if (m_drat_todo.size() != sz)
continue;
if (!m_drat_asts.contains(e))
drat_log_expr1(e);
m_drat_todo.pop_back();
}
}
void solver::drat_bool_def(sat::bool_var v, expr* e) {
if (!use_drat())
return;
drat_log_expr(e);
get_drat().bool_def(v, e->get_id());
}
void solver::drat_log_decl(func_decl* f) {
if (f->get_family_id() != null_family_id)
return;
if (m_drat_asts.contains(f))
return;
m_drat_asts.insert(f);
push(insert_obj_trail< ast>(m_drat_asts, f));
std::ostringstream strm;
smt2_pp_environment_dbg env(m);
ast_smt2_pp(strm, f, env);
get_drat().def_begin('f', f->get_small_id(), strm.str());
get_drat().def_end();
} }
/** /**
@ -155,16 +74,19 @@ namespace euf {
} }
} }
void solver::set_tmp_bool_var(bool_var b, expr* e) {
m_bool_var2expr.setx(b, e, nullptr);
}
void solver::log_justification(literal l, th_explain const& jst) { void solver::log_justification(literal l, th_explain const& jst) {
literal_vector lits; literal_vector lits;
unsigned nv = s().num_vars();
expr_ref_vector eqs(m); expr_ref_vector eqs(m);
unsigned nv = s().num_vars();
auto add_lit = [&](enode_pair const& eq) { auto add_lit = [&](enode_pair const& eq) {
++nv; ++nv;
literal lit(nv, false);
eqs.push_back(m.mk_eq(eq.first->get_expr(), eq.second->get_expr())); eqs.push_back(m.mk_eq(eq.first->get_expr(), eq.second->get_expr()));
drat_eq_def(lit, eqs.back()); set_tmp_bool_var(nv, eqs.back());
return lit; return literal(nv, false);
}; };
for (auto lit : euf::th_explain::lits(jst)) for (auto lit : euf::th_explain::lits(jst))
@ -180,16 +102,133 @@ namespace euf {
get_drat().add(lits, sat::status::th(m_is_redundant, jst.ext().get_id(), jst.get_pragma())); get_drat().add(lits, sat::status::th(m_is_redundant, jst.ext().get_id(), jst.get_pragma()));
} }
void solver::drat_eq_def(literal lit, expr* eq) { void solver::on_clause(unsigned n, literal const* lits, sat::status st) {
expr *a = nullptr, *b = nullptr; TRACE("euf", tout << "on-clause " << n << "\n");
VERIFY(m.is_eq(eq, a, b)); on_lemma(n, lits, st);
drat_log_expr(a); on_proof(n, lits, st);
drat_log_expr(b); }
get_drat().def_begin('e', eq->get_id(), std::string("="));
get_drat().def_add_arg(a->get_id()); void solver::on_proof(unsigned n, literal const* lits, sat::status st) {
get_drat().def_add_arg(b->get_id()); if (!m_proof_out)
get_drat().def_end(); return;
get_drat().bool_def(lit.var(), eq->get_id()); flet<bool> _display_all_decls(m_display_all_decls, true);
std::ostream& out = *m_proof_out;
if (!visit_clause(out, n, lits))
return;
if (st.is_asserted())
display_redundant(out, n, lits, status2proof_hint(st));
else if (st.is_deleted())
display_deleted(out, n, lits);
else if (st.is_redundant())
display_redundant(out, n, lits, status2proof_hint(st));
else if (st.is_input())
display_assume(out, n, lits);
else
UNREACHABLE();
out.flush();
}
void solver::on_lemma(unsigned n, literal const* lits, sat::status st) {
if (!get_config().m_lemmas2console)
return;
if (!st.is_redundant() && !st.is_asserted())
return;
std::ostream& out = std::cout;
if (!visit_clause(out, n, lits))
return;
std::function<symbol(int)> ppth = [&](int th) {
return m.get_family_name(th);
};
if (!st.is_sat())
out << "; " << sat::status_pp(st, ppth) << "\n";
display_assert(out, n, lits);
}
void solver::on_instantiation(unsigned n, sat::literal const* lits, unsigned k, euf::enode* const* bindings) {
std::ostream& out = std::cout;
for (unsigned i = 0; i < k; ++i)
visit_expr(out, bindings[i]->get_expr());
VERIFY(visit_clause(out, n, lits));
out << "(instantiate";
display_literals(out, n, lits);
for (unsigned i = 0; i < k; ++i)
display_expr(out << " :binding ", bindings[i]->get_expr());
out << ")\n";
}
bool solver::visit_clause(std::ostream& out, unsigned n, literal const* lits) {
for (unsigned i = 0; i < n; ++i) {
expr* e = bool_var2expr(lits[i].var());
if (!e)
return false;
visit_expr(out, e);
}
return true;
}
void solver::display_assert(std::ostream& out, unsigned n, literal const* lits) {
display_literals(out << "(assert (or", n, lits) << "))\n";
}
void solver::display_assume(std::ostream& out, unsigned n, literal const* lits) {
display_literals(out << "(assume", n, lits) << ")\n";
}
void solver::display_redundant(std::ostream& out, unsigned n, literal const* lits, expr* proof_hint) {
if (proof_hint)
visit_expr(out, proof_hint);
display_hint(display_literals(out << "(learn", n, lits), proof_hint) << ")\n";
}
void solver::display_deleted(std::ostream& out, unsigned n, literal const* lits) {
display_literals(out << "(del", n, lits) << ")\n";
}
std::ostream& solver::display_hint(std::ostream& out, expr* proof_hint) {
if (proof_hint)
return display_expr(out << " ", proof_hint);
else
return out;
}
expr_ref solver::status2proof_hint(sat::status st) {
if (st.is_sat())
return expr_ref(m.mk_const("rup", m.mk_proof_sort()), m); // provable by reverse unit propagation
auto* h = reinterpret_cast<euf::th_proof_hint const*>(st.get_hint());
if (!h)
return expr_ref(m);
expr* e = h->get_hint(*this);
if (e)
return expr_ref(e, m);
return expr_ref(m);
}
std::ostream& solver::display_literals(std::ostream& out, unsigned n, literal const* lits) {
for (unsigned i = 0; i < n; ++i) {
expr* e = bool_var2expr(lits[i].var());
if (lits[i].sign())
display_expr(out << " (not ", e) << ")";
else
display_expr(out << " ", e);
}
return out;
}
void solver::visit_expr(std::ostream& out, expr* e) {
m_clause_visitor.collect(e);
if (m_display_all_decls)
m_clause_visitor.display_decls(out);
else
m_clause_visitor.display_skolem_decls(out);
m_clause_visitor.define_expr(out, e);
}
std::ostream& solver::display_expr(std::ostream& out, expr* e) {
return m_clause_visitor.display_expr_def(out, e);
} }
} }

View file

@ -0,0 +1,50 @@
/*++
Copyright (c) 2020 Microsoft Corporation
Module Name:
euf_proof_checker.cpp
Abstract:
Plugin manager for checking EUF proofs
Author:
Nikolaj Bjorner (nbjorner) 2020-08-25
--*/
#include "ast/ast_pp.h"
#include "sat/smt/euf_proof_checker.h"
#include "sat/smt/arith_proof_checker.h"
namespace euf {
proof_checker::proof_checker(ast_manager& m):
m(m) {
arith::proof_checker* apc = alloc(arith::proof_checker, m);
m_plugins.push_back(apc);
apc->register_plugins(*this);
(void)m;
}
proof_checker::~proof_checker() {}
void proof_checker::register_plugin(symbol const& rule, proof_checker_plugin* p) {
m_map.insert(rule, p);
}
bool proof_checker::check(expr_ref_vector const& clause, expr* e, expr_ref_vector& units) {
if (!e || !is_app(e))
return false;
units.reset();
app* a = to_app(e);
proof_checker_plugin* p = nullptr;
if (m_map.find(a->get_decl()->get_name(), p))
return p->check(clause, a, units);
return false;
}
}

View file

@ -0,0 +1,46 @@
/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
euf_proof_checker.h
Abstract:
Plugin manager for checking EUF proofs
Author:
Nikolaj Bjorner (nbjorner) 2022-08-25
--*/
#pragma once
#include "util/map.h"
#include "util/scoped_ptr_vector.h"
#include "ast/ast.h"
namespace euf {
class proof_checker;
class proof_checker_plugin {
public:
virtual ~proof_checker_plugin() {}
virtual bool check(expr_ref_vector const& clause, app* jst, expr_ref_vector& units) = 0;
virtual void register_plugins(proof_checker& pc) = 0;
};
class proof_checker {
ast_manager& m;
scoped_ptr_vector<proof_checker_plugin> m_plugins;
map<symbol, proof_checker_plugin*, symbol_hash_proc, symbol_eq_proc> m_map;
public:
proof_checker(ast_manager& m);
~proof_checker();
void register_plugin(symbol const& rule, proof_checker_plugin*);
bool check(expr_ref_vector const& clause, expr* e, expr_ref_vector& units);
};
}

View file

@ -49,7 +49,8 @@ namespace euf {
m_lookahead(nullptr), m_lookahead(nullptr),
m_to_m(&m), m_to_m(&m),
m_to_si(&si), m_to_si(&si),
m_values(m) m_values(m),
m_clause_visitor(m)
{ {
updt_params(p); updt_params(p);
m_relevancy.set_enabled(get_config().m_relevancy_lvl > 2); m_relevancy.set_enabled(get_config().m_relevancy_lvl > 2);
@ -347,8 +348,7 @@ namespace euf {
if (m_relevancy.enabled()) if (m_relevancy.enabled())
m_relevancy.propagate(); m_relevancy.propagate();
if (m_egraph.inconsistent()) { if (m_egraph.inconsistent()) {
unsigned lvl = s().scope_lvl(); set_conflict(conflict_constraint().to_index());
s().set_conflict(sat::justification::mk_ext_justification(lvl, conflict_constraint().to_index()));
return true; return true;
} }
bool propagated1 = false; bool propagated1 = false;
@ -519,7 +519,7 @@ namespace euf {
bool merged = false; bool merged = false;
for (unsigned i = m_egraph.nodes().size(); i-- > 0; ) { for (unsigned i = m_egraph.nodes().size(); i-- > 0; ) {
euf::enode* n = m_egraph.nodes()[i]; euf::enode* n = m_egraph.nodes()[i];
if (!is_shared(n) || !m.is_bool(n->get_expr())) if (!m.is_bool(n->get_expr()) || !is_shared(n))
continue; continue;
if (n->value() == l_true && !m.is_true(n->get_root()->get_expr())) { if (n->value() == l_true && !m.is_true(n->get_root()->get_expr())) {
m_egraph.merge(n, mk_true(), to_ptr(sat::literal(n->bool_var()))); m_egraph.merge(n, mk_true(), to_ptr(sat::literal(n->bool_var())));

View file

@ -60,11 +60,10 @@ namespace euf {
std::ostream& display(std::ostream& out) const; std::ostream& display(std::ostream& out) const;
}; };
class solver : public sat::extension, public th_internalizer, public th_decompile { class solver : public sat::extension, public th_internalizer, public th_decompile, public sat::clause_eh {
typedef top_sort<euf::enode> deps_t; typedef top_sort<euf::enode> deps_t;
friend class ackerman; friend class ackerman;
class user_sort; class user_sort;
// friend class sat::ba_solver;
struct stats { struct stats {
unsigned m_ackerman; unsigned m_ackerman;
unsigned m_final_checks; unsigned m_final_checks;
@ -175,20 +174,22 @@ namespace euf {
void log_antecedents(std::ostream& out, literal l, literal_vector const& r); void log_antecedents(std::ostream& out, literal l, literal_vector const& r);
void log_antecedents(literal l, literal_vector const& r); void log_antecedents(literal l, literal_vector const& r);
void log_justification(literal l, th_explain const& jst); void log_justification(literal l, th_explain const& jst);
void drat_log_decl(func_decl* f);
void drat_log_params(func_decl* f); bool m_proof_initialized = false;
void drat_log_expr1(expr* n); void init_proof();
ptr_vector<expr> m_drat_todo; ast_pp_util m_clause_visitor;
obj_hashtable<ast> m_drat_asts; bool m_display_all_decls = false;
bool m_drat_initialized{ false }; void on_clause(unsigned n, literal const* lits, sat::status st) override;
void init_drat(); void on_lemma(unsigned n, literal const* lits, sat::status st);
void on_proof(unsigned n, literal const* lits, sat::status st);
std::ostream& display_literals(std::ostream& out, unsigned n, sat::literal const* lits);
void display_assume(std::ostream& out, unsigned n, literal const* lits);
void display_redundant(std::ostream& out, unsigned n, literal const* lits, expr* proof_hint);
void display_deleted(std::ostream& out, unsigned n, literal const* lits);
std::ostream& display_hint(std::ostream& out, expr* proof_hint);
expr_ref status2proof_hint(sat::status st);
// relevancy // relevancy
//bool_vector m_relevant_expr_ids;
//bool_vector m_relevant_visited;
//ptr_vector<expr> m_relevant_todo;
//void init_relevant_expr_ids();
//void push_relevant(sat::bool_var v);
bool is_propagated(sat::literal lit); bool is_propagated(sat::literal lit);
// invariant // invariant
void check_eqc_bool_assignment() const; void check_eqc_bool_assignment() const;
@ -341,11 +342,16 @@ namespace euf {
// proof // proof
bool use_drat() { return s().get_config().m_drat && (init_drat(), true); } bool use_drat() { return s().get_config().m_drat && (init_proof(), true); }
sat::drat& get_drat() { return s().get_drat(); } sat::drat& get_drat() { return s().get_drat(); }
void drat_bool_def(sat::bool_var v, expr* n);
void drat_eq_def(sat::literal lit, expr* eq); void set_tmp_bool_var(sat::bool_var b, expr* e);
void drat_log_expr(expr* n); bool visit_clause(std::ostream& out, unsigned n, literal const* lits);
void display_assert(std::ostream& out, unsigned n, literal const* lits);
void visit_expr(std::ostream& out, expr* e);
std::ostream& display_expr(std::ostream& out, expr* e);
void on_instantiation(unsigned n, sat::literal const* lits, unsigned k, euf::enode* const* bindings);
scoped_ptr<std::ostream> m_proof_out;
// decompile // decompile
bool extract_pb(std::function<void(unsigned sz, literal const* c, unsigned k)>& card, bool extract_pb(std::function<void(unsigned sz, literal const* c, unsigned k)>& card,

View file

@ -1430,10 +1430,9 @@ namespace pb {
IF_VERBOSE(0, verbose_stream() << *c << "\n"); IF_VERBOSE(0, verbose_stream() << *c << "\n");
VERIFY(c->well_formed()); VERIFY(c->well_formed());
if (m_solver && m_solver->get_config().m_drat) { if (m_solver && m_solver->get_config().m_drat) {
std::function<void(std::ostream& out)> fn = [&](std::ostream& out) { auto * out = s().get_drat().out();
out << "c ba constraint " << *c << " 0\n"; if (out)
}; *out << "c ba constraint " << *c << " 0\n";
m_solver->get_drat().log_adhoc(fn);
} }
} }

View file

@ -372,16 +372,18 @@ namespace q {
} }
void ematch::propagate(bool is_conflict, unsigned idx, sat::ext_justification_idx j_idx) { void ematch::propagate(bool is_conflict, unsigned idx, sat::ext_justification_idx j_idx) {
if (is_conflict) { if (is_conflict)
++m_stats.m_num_conflicts; ++m_stats.m_num_conflicts;
ctx.set_conflict(j_idx); else
}
else {
++m_stats.m_num_propagations; ++m_stats.m_num_propagations;
auto& j = justification::from_index(j_idx); auto& j = justification::from_index(j_idx);
auto lit = instantiate(j.m_clause, j.m_binding, j.m_clause[idx]); sat::literal_vector lits;
ctx.propagate(lit, j_idx); lits.push_back(~j.m_clause.m_literal);
} for (unsigned i = 0; i < j.m_clause.size(); ++i)
lits.push_back(instantiate(j.m_clause, j.m_binding, j.m_clause[i]));
m_qs.log_instantiation(lits, &j);
m_qs.add_clause(lits);
} }
bool ematch::flush_prop_queue() { bool ematch::flush_prop_queue() {
@ -408,6 +410,7 @@ namespace q {
void ematch::add_instantiation(clause& c, binding& b, sat::literal lit) { void ematch::add_instantiation(clause& c, binding& b, sat::literal lit) {
m_evidence.reset(); m_evidence.reset();
ctx.propagate(lit, mk_justification(UINT_MAX, c, b.nodes())); ctx.propagate(lit, mk_justification(UINT_MAX, c, b.nodes()));
m_qs.log_instantiation(~c.m_literal, lit);
} }
sat::literal ematch::instantiate(clause& c, euf::enode* const* binding, lit const& l) { sat::literal ematch::instantiate(clause& c, euf::enode* const* binding, lit const& l) {

View file

@ -47,6 +47,7 @@ namespace q {
unsigned lim = m_indirect_nodes.size(); unsigned lim = m_indirect_nodes.size();
lit l = c[i]; lit l = c[i];
lbool cmp = compare(n, binding, l.lhs, l.rhs, evidence); lbool cmp = compare(n, binding, l.lhs, l.rhs, evidence);
TRACE("q", tout << l.lhs << " ~~ " << l.rhs << " is " << cmp << "\n";);
switch (cmp) { switch (cmp) {
case l_false: case l_false:
m_indirect_nodes.shrink(lim); m_indirect_nodes.shrink(lim);
@ -61,7 +62,6 @@ namespace q {
c.m_watch = i; c.m_watch = i;
return l_true; return l_true;
case l_undef: case l_undef:
TRACE("q", tout << l.lhs << " ~~ " << l.rhs << " is undef\n";);
if (idx != UINT_MAX) { if (idx != UINT_MAX) {
idx = UINT_MAX; idx = UINT_MAX;
return l_undef; return l_undef;

View file

@ -71,6 +71,7 @@ namespace q {
for (auto const& [qlit, fml, generation] : m_instantiations) { for (auto const& [qlit, fml, generation] : m_instantiations) {
euf::solver::scoped_generation sg(ctx, generation + 1); euf::solver::scoped_generation sg(ctx, generation + 1);
sat::literal lit = ctx.mk_literal(fml); sat::literal lit = ctx.mk_literal(fml);
m_qs.log_instantiation(~qlit, ~lit);
m_qs.add_clause(~qlit, ~lit); m_qs.add_clause(~qlit, ~lit);
} }
m_instantiations.reset(); m_instantiations.reset();

View file

@ -24,6 +24,7 @@ Author:
#include "sat/smt/euf_solver.h" #include "sat/smt/euf_solver.h"
#include "sat/smt/sat_th.h" #include "sat/smt/sat_th.h"
#include "qe/lite/qe_lite.h" #include "qe/lite/qe_lite.h"
#include <iostream>
namespace q { namespace q {
@ -356,4 +357,10 @@ namespace q {
m_ematch.get_antecedents(l, idx, r, probing); m_ematch.get_antecedents(l, idx, r, probing);
} }
void solver::log_instantiation(unsigned n, sat::literal const* lits, justification* j) {
TRACE("q", for (unsigned i = 0; i < n; ++i) tout << literal2expr(lits[i]) << "\n";);
if (get_config().m_instantiations2console) {
ctx.on_instantiation(n, lits, j ? j->m_clause.num_decls() : 0, j ? j->m_binding : nullptr);
}
}
} }

View file

@ -88,5 +88,9 @@ namespace q {
sat::literal_vector const& universal() const { return m_universal; } sat::literal_vector const& universal() const { return m_universal; }
quantifier* flatten(quantifier* q); quantifier* flatten(quantifier* q);
void log_instantiation(sat::literal q, sat::literal i, justification* j = nullptr) { sat::literal lits[2] = { q, i }; log_instantiation(2, lits, j); }
void log_instantiation(sat::literal_vector const& lits, justification* j) { log_instantiation(lits.size(), lits.data(), j); }
void log_instantiation(unsigned n, sat::literal const* lits, justification* j);
}; };
} }

View file

@ -125,7 +125,7 @@ namespace euf {
pop_core(n); pop_core(n);
} }
sat::status th_euf_solver::mk_status(sat::proof_hint const* ps) { sat::status th_euf_solver::mk_status(th_proof_hint const* ps) {
return sat::status::th(m_is_redundant, get_id(), ps); return sat::status::th(m_is_redundant, get_id(), ps);
} }
@ -149,7 +149,7 @@ namespace euf {
return add_clause(2, lits); return add_clause(2, lits);
} }
bool th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::proof_hint const* ps) { bool th_euf_solver::add_clause(sat::literal a, sat::literal b, th_proof_hint const* ps) {
sat::literal lits[2] = { a, b }; sat::literal lits[2] = { a, b };
return add_clause(2, lits, ps); return add_clause(2, lits, ps);
} }
@ -164,7 +164,7 @@ namespace euf {
return add_clause(4, lits); return add_clause(4, lits);
} }
bool th_euf_solver::add_clause(unsigned n, sat::literal* lits, sat::proof_hint const* ps) { bool th_euf_solver::add_clause(unsigned n, sat::literal* lits, th_proof_hint const* ps) {
bool was_true = false; bool was_true = false;
for (unsigned i = 0; i < n; ++i) for (unsigned i = 0; i < n; ++i)
was_true |= is_true(lits[i]); was_true |= is_true(lits[i]);
@ -226,13 +226,14 @@ namespace euf {
return ctx.s().rand()(); return ctx.s().rand()();
} }
size_t th_explain::get_obj_size(unsigned num_lits, unsigned num_eqs, sat::proof_hint const* pma) { size_t th_explain::get_obj_size(unsigned num_lits, unsigned num_eqs) {
return sat::constraint_base::obj_size(sizeof(th_explain) + sizeof(sat::literal) * num_lits + sizeof(enode_pair) * num_eqs + (pma?pma->to_string().length()+1:1)); return sat::constraint_base::obj_size(sizeof(th_explain) + sizeof(sat::literal) * num_lits + sizeof(enode_pair) * num_eqs);
} }
th_explain::th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& p, sat::proof_hint const* pma) { th_explain::th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& p, th_proof_hint const* pma) {
m_consequent = c; m_consequent = c;
m_eq = p; m_eq = p;
m_proof_hint = pma;
m_num_literals = n_lits; m_num_literals = n_lits;
m_num_eqs = n_eqs; m_num_eqs = n_eqs;
char * base_ptr = reinterpret_cast<char*>(this) + sizeof(th_explain); char * base_ptr = reinterpret_cast<char*>(this) + sizeof(th_explain);
@ -244,33 +245,24 @@ namespace euf {
m_eqs = reinterpret_cast<enode_pair*>(base_ptr); m_eqs = reinterpret_cast<enode_pair*>(base_ptr);
for (i = 0; i < n_eqs; ++i) for (i = 0; i < n_eqs; ++i)
m_eqs[i] = eqs[i]; m_eqs[i] = eqs[i];
base_ptr += sizeof(enode_pair) * n_eqs;
m_pragma = reinterpret_cast<char*>(base_ptr);
i = 0;
if (pma) {
std::string s = pma->to_string();
for (i = 0; s[i]; ++i)
m_pragma[i] = s[i];
}
m_pragma[i] = 0;
} }
th_explain* th_explain::mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, sat::proof_hint const* pma) { th_explain* th_explain::mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, th_proof_hint const* pma) {
region& r = th.ctx.get_region(); region& r = th.ctx.get_region();
void* mem = r.allocate(get_obj_size(n_lits, n_eqs, pma)); void* mem = r.allocate(get_obj_size(n_lits, n_eqs));
sat::constraint_base::initialize(mem, &th); sat::constraint_base::initialize(mem, &th);
return new (sat::constraint_base::ptr2mem(mem)) th_explain(n_lits, lits, n_eqs, eqs, c, enode_pair(x, y)); return new (sat::constraint_base::ptr2mem(mem)) th_explain(n_lits, lits, n_eqs, eqs, c, enode_pair(x, y), pma);
} }
th_explain* th_explain::propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, sat::proof_hint const* pma) { th_explain* th_explain::propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, th_proof_hint const* pma) {
return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), consequent, nullptr, nullptr, pma); return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), consequent, nullptr, nullptr, pma);
} }
th_explain* th_explain::propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma) { th_explain* th_explain::propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_proof_hint const* pma) {
return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), sat::null_literal, x, y, pma); return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), sat::null_literal, x, y, pma);
} }
th_explain* th_explain::propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma) { th_explain* th_explain::propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_proof_hint const* pma) {
return mk(th, 0, nullptr, eqs.size(), eqs.data(), sat::null_literal, x, y, pma); return mk(th, 0, nullptr, eqs.size(), eqs.data(), sat::null_literal, x, y, pma);
} }
@ -313,8 +305,8 @@ namespace euf {
out << "--> " << m_consequent; out << "--> " << m_consequent;
if (m_eq.first != nullptr) if (m_eq.first != nullptr)
out << "--> " << m_eq.first->get_expr_id() << " == " << m_eq.second->get_expr_id(); out << "--> " << m_eq.first->get_expr_id() << " == " << m_eq.second->get_expr_id();
if (m_pragma != nullptr) if (m_proof_hint != nullptr)
out << " p " << m_pragma; out << " p ";
return out; return out;
} }

View file

@ -58,6 +58,7 @@ namespace euf {
}; };
class th_decompile { class th_decompile {
public: public:
virtual ~th_decompile() = default; virtual ~th_decompile() = default;
@ -138,6 +139,11 @@ namespace euf {
}; };
class th_proof_hint : public sat::proof_hint {
public:
virtual expr* get_hint(euf::solver& s) const = 0;
};
class th_euf_solver : public th_solver { class th_euf_solver : public th_solver {
protected: protected:
solver& ctx; solver& ctx;
@ -150,16 +156,16 @@ namespace euf {
region& get_region(); region& get_region();
sat::status mk_status(sat::proof_hint const* ps = nullptr); sat::status mk_status(th_proof_hint const* ps = nullptr);
bool add_unit(sat::literal lit); bool add_unit(sat::literal lit);
bool add_units(sat::literal_vector const& lits); bool add_units(sat::literal_vector const& lits);
bool add_clause(sat::literal lit) { return add_unit(lit); } bool add_clause(sat::literal lit) { return add_unit(lit); }
bool add_clause(sat::literal a, sat::literal b); bool add_clause(sat::literal a, sat::literal b);
bool add_clause(sat::literal a, sat::literal b, sat::proof_hint const* ps); bool add_clause(sat::literal a, sat::literal b, th_proof_hint const* ps);
bool add_clause(sat::literal a, sat::literal b, sat::literal c); bool add_clause(sat::literal a, sat::literal b, sat::literal c);
bool add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d); bool add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d);
bool add_clause(sat::literal_vector const& lits, sat::proof_hint const* ps = nullptr) { return add_clause(lits.size(), lits.data(), ps); } bool add_clause(sat::literal_vector const& lits, th_proof_hint const* ps = nullptr) { return add_clause(lits.size(), lits.data(), ps); }
bool add_clause(unsigned n, sat::literal* lits, sat::proof_hint const* ps = nullptr); bool add_clause(unsigned n, sat::literal* lits, th_proof_hint const* ps = nullptr);
void add_equiv(sat::literal a, sat::literal b); void add_equiv(sat::literal a, sat::literal b);
void add_equiv_and(sat::literal a, sat::literal_vector const& bs); void add_equiv_and(sat::literal a, sat::literal_vector const& bs);
@ -222,14 +228,14 @@ namespace euf {
class th_explain { class th_explain {
sat::literal m_consequent = sat::null_literal; // literal consequent for propagations sat::literal m_consequent = sat::null_literal; // literal consequent for propagations
enode_pair m_eq = enode_pair(); // equality consequent for propagations enode_pair m_eq = enode_pair(); // equality consequent for propagations
th_proof_hint const* m_proof_hint;
unsigned m_num_literals; unsigned m_num_literals;
unsigned m_num_eqs; unsigned m_num_eqs;
sat::literal* m_literals; sat::literal* m_literals;
enode_pair* m_eqs; enode_pair* m_eqs;
char* m_pragma = nullptr; static size_t get_obj_size(unsigned num_lits, unsigned num_eqs);
static size_t get_obj_size(unsigned num_lits, unsigned num_eqs, sat::proof_hint const* pma); th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& eq, th_proof_hint const* pma = nullptr);
th_explain(unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode_pair const& eq, sat::proof_hint const* pma = nullptr); static th_explain* mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, th_proof_hint const* pma = nullptr);
static th_explain* mk(th_euf_solver& th, unsigned n_lits, sat::literal const* lits, unsigned n_eqs, enode_pair const* eqs, sat::literal c, enode* x, enode* y, sat::proof_hint const* pma = nullptr);
public: public:
static th_explain* conflict(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs); static th_explain* conflict(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs);
@ -240,9 +246,9 @@ namespace euf {
static th_explain* conflict(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y); static th_explain* conflict(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y);
static th_explain* conflict(th_euf_solver& th, euf::enode* x, euf::enode* y); static th_explain* conflict(th_euf_solver& th, euf::enode* x, euf::enode* y);
static th_explain* propagate(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y); static th_explain* propagate(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y);
static th_explain* propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_proof_hint const* pma = nullptr);
static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, th_proof_hint const* pma = nullptr);
static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, th_proof_hint const* pma = nullptr);
sat::ext_constraint_idx to_index() const { sat::ext_constraint_idx to_index() const {
return sat::constraint_base::mem2base(this); return sat::constraint_base::mem2base(this);
@ -277,7 +283,7 @@ namespace euf {
enode_pair eq_consequent() const { return m_eq; } enode_pair eq_consequent() const { return m_eq; }
sat::proof_hint const* get_pragma() const { return nullptr; } //*m_pragma ? m_pragma : nullptr; } th_proof_hint const* get_pragma() const { return m_proof_hint; }
}; };

View file

@ -75,7 +75,6 @@ struct goal2sat::imp : public sat::sat_internalizer {
func_decl_ref_vector m_unhandled_funs; func_decl_ref_vector m_unhandled_funs;
bool m_default_external; bool m_default_external;
bool m_euf { false }; bool m_euf { false };
bool m_drat { false };
bool m_is_redundant { false }; bool m_is_redundant { false };
bool m_top_level { false }; bool m_top_level { false };
sat::literal_vector aig_lits; sat::literal_vector aig_lits;
@ -102,7 +101,6 @@ struct goal2sat::imp : public sat::sat_internalizer {
m_ite_extra = p.get_bool("ite_extra", true); m_ite_extra = p.get_bool("ite_extra", true);
m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX));
m_euf = sp.euf(); m_euf = sp.euf();
m_drat = sp.drat_file().is_non_empty_string();
} }
void throw_op_not_handled(std::string const& s) { void throw_op_not_handled(std::string const& s) {
@ -169,15 +167,9 @@ struct goal2sat::imp : public sat::sat_internalizer {
if (m_expr2var_replay && m_expr2var_replay->find(n, v)) if (m_expr2var_replay && m_expr2var_replay->find(n, v))
return v; return v;
v = m_solver.add_var(is_ext); v = m_solver.add_var(is_ext);
log_def(v, n);
return v; return v;
} }
void log_def(sat::bool_var v, expr* n) {
if (m_drat && m_euf)
ensure_euf()->drat_bool_def(v, n);
}
sat::bool_var to_bool_var(expr* e) override { sat::bool_var to_bool_var(expr* e) override {
sat::literal l; sat::literal l;
sat::bool_var v = m_map.to_bool_var(e); sat::bool_var v = m_map.to_bool_var(e);

View file

@ -17,90 +17,22 @@ Copyright (c) 2020 Microsoft Corporation
#include "cmd_context/cmd_context.h" #include "cmd_context/cmd_context.h"
#include "ast/proofs/proof_checker.h" #include "ast/proofs/proof_checker.h"
#include "ast/rewriter/th_rewriter.h" #include "ast/rewriter/th_rewriter.h"
#include "ast/reg_decl_plugins.h"
#include "sat/smt/arith_proof_checker.h" #include "sat/smt/arith_proof_checker.h"
class smt_checker { class drup_checker {
ast_manager& m;
sat::drat& m_drat; sat::drat& m_drat;
expr_ref_vector const& m_b2e;
expr_ref_vector m_fresh_exprs;
expr_ref_vector m_core;
expr_ref_vector m_inputs;
params_ref m_params;
scoped_ptr<solver> m_lemma_solver, m_input_solver;
sat::literal_vector m_units; sat::literal_vector m_units;
bool m_check_inputs { false }; bool m_check_inputs = false;
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", e->get_sort());
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.data());
}
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);
}
}
expr_ref lit2expr(sat::literal lit) {
return expr_ref(lit.sign() ? m.mk_not(m_b2e[lit.var()]) : m_b2e[lit.var()], m);
}
void add_units() { void add_units() {
auto const& units = m_drat.units(); auto const& units = m_drat.units();
#if 0
for (unsigned i = m_units.size(); i < units.size(); ++i) {
sat::literal lit = units[i].first;
m_lemma_solver->assert_expr(lit2expr(lit));
}
#endif
for (unsigned i = m_units.size(); i < units.size(); ++i) for (unsigned i = m_units.size(); i < units.size(); ++i)
m_units.push_back(units[i].first); m_units.push_back(units[i].first);
} }
void check_assertion_redundant(sat::literal_vector const& input) { void check_assertion_redundant(sat::literal_vector const& input) {
expr_ref_vector args(m);
for (auto lit : input)
args.push_back(lit2expr(lit));
m_inputs.push_back(args.size() == 1 ? args.back() : m.mk_or(args));
m_input_solver->push();
for (auto lit : input) {
m_input_solver->assert_expr(lit2expr(~lit));
}
lbool is_sat = m_input_solver->check_sat();
if (is_sat != l_false) {
std::cout << "Failed to verify input\n";
exit(0);
}
m_input_solver->pop(1);
} }
@ -113,58 +45,70 @@ class smt_checker {
sat::literal_vector drup_units; sat::literal_vector drup_units;
void check_clause(sat::literal_vector const& lits) { void check_clause(sat::literal_vector const& lits) {
}
void check_drup(sat::literal_vector const& lits) {
add_units(); add_units();
drup_units.reset(); drup_units.reset();
if (m_drat.is_drup(lits.size(), lits.data(), drup_units)) { if (m_drat.is_drup(lits.size(), lits.data(), drup_units)) {
std::cout << "drup\n"; std::cout << "drup\n";
return; return;
} }
m_input_solver->push(); std::cout << "did not verify " << lits << "\n";
// for (auto lit : drup_units)
// m_input_solver->assert_expr(lit2expr(lit));
for (auto lit : lits)
m_input_solver->assert_expr(lit2expr(~lit));
lbool is_sat = m_input_solver->check_sat();
if (is_sat != l_false) {
std::cout << "did not verify: " << is_sat << " " << lits << "\n";
for (sat::literal lit : lits)
std::cout << lit2expr(lit) << "\n";
std::cout << "\n";
m_input_solver->display(std::cout);
if (is_sat == l_true) {
model_ref mdl;
m_input_solver->get_model(mdl);
std::cout << *mdl << "\n";
}
exit(0); exit(0);
} }
m_input_solver->pop(1);
std::cout << "smt\n";
// check_assertion_redundant(lits);
}
public: public:
smt_checker(sat::drat& drat, expr_ref_vector const& b2e): drup_checker(sat::drat& drat): m_drat(drat) {}
m(b2e.m()), m_drat(drat), m_b2e(b2e), m_fresh_exprs(m), m_core(m), m_inputs(m) {
m_lemma_solver = mk_smt_solver(m, m_params, symbol());
m_input_solver = mk_smt_solver(m, m_params, symbol());
}
void add(sat::literal_vector const& lits, sat::status const& st, bool validated) { void add(sat::literal_vector const& lits, sat::status const& st) {
for (sat::literal lit : lits) for (sat::literal lit : lits)
while (lit.var() >= m_drat.get_solver().num_vars()) while (lit.var() >= m_drat.get_solver().num_vars())
m_drat.get_solver().mk_var(true); m_drat.get_solver().mk_var(true);
if (st.is_input() && m_check_inputs) if (st.is_sat())
check_assertion_redundant(lits); check_drup(lits);
else if (!st.is_sat() && !st.is_deleted() && !validated) m_drat.add(lits, st);
check_clause(lits); }
// m_drat.add(lits, st); };
unsigned read_drat(char const* drat_file) {
ast_manager m;
reg_decl_plugins(m);
std::ifstream ins(drat_file);
dimacs::drat_parser drat(ins, std::cerr);
std::function<int(char const* r)> read_theory = [&](char const* r) {
return m.mk_family_id(symbol(r));
};
std::function<symbol(int)> write_theory = [&](int th) {
return m.get_family_name(th);
};
drat.set_read_theory(read_theory);
params_ref p;
reslimit lim;
sat::solver solver(p, lim);
sat::drat drat_checker(solver);
drup_checker checker(drat_checker);
for (auto const& r : drat) {
std::cout << dimacs::drat_pp(r, write_theory);
std::cout.flush();
checker.add(r.m_lits, r.m_status);
if (drat_checker.inconsistent()) {
std::cout << "inconsistent\n";
return 0;
}
statistics st;
drat_checker.collect_statistics(st);
std::cout << st << "\n";
}
return 0;
} }
#if 0
bool validate_hint(expr_ref_vector const& exprs, sat::literal_vector const& lits, sat::proof_hint const& hint) { bool validate_hint(expr_ref_vector const& exprs, sat::literal_vector const& lits, sat::proof_hint const& hint) {
// return; // remove when testing this
arith_util autil(m); arith_util autil(m);
arith::proof_checker achecker(m); arith::proof_checker achecker(m);
proof_checker pc(m); proof_checker pc(m);
@ -248,302 +192,15 @@ public:
return false; return false;
} }
std::cout << "p hint verified\n"; std::cout << "p hint verified\n";
break;
}
default:
UNREACHABLE();
break;
}
return true; return true;
}
/**
* Add an assertion from the source file
*/
void add_assertion(expr* a) {
m_input_solver->assert_expr(a);
}
void display_input() {
scoped_ptr<solver> s = mk_smt_solver(m, m_params, symbol());
for (auto* e : m_inputs)
s->assert_expr(e);
s->display(std::cout);
}
symbol name;
unsigned_vector params;
ptr_vector<sort> sorts;
void parse_quantifier(sexpr_ref const& sexpr, cmd_context& ctx, quantifier_kind& k, sort_ref_vector& domain, svector<symbol>& names) {
k = quantifier_kind::forall_k;
symbol q;
unsigned sz;
if (sexpr->get_kind() != sexpr::kind_t::COMPOSITE)
goto bail;
sz = sexpr->get_num_children();
if (sz == 0)
goto bail;
q = sexpr->get_child(0)->get_symbol();
if (q == "forall")
k = quantifier_kind::forall_k;
else if (q == "exists")
k = quantifier_kind::exists_k;
else if (q == "lambda")
k = quantifier_kind::lambda_k;
else
goto bail;
for (unsigned i = 1; i < sz; ++i) {
auto* e = sexpr->get_child(i);
if (e->get_kind() != sexpr::kind_t::COMPOSITE)
goto bail;
if (2 != e->get_num_children())
goto bail;
symbol name = e->get_child(0)->get_symbol();
std::ostringstream ostrm;
e->get_child(1)->display(ostrm);
std::istringstream istrm(ostrm.str());
params_ref p;
auto srt = parse_smt2_sort(ctx, istrm, false, p, "quantifier");
if (!srt)
goto bail;
names.push_back(name);
domain.push_back(srt);
}
return;
bail:
std::cout << "Could not parse expression\n";
sexpr->display(std::cout);
std::cout << "\n";
exit(0);
}
void parse_sexpr(sexpr_ref const& sexpr, cmd_context& ctx, expr_ref_vector const& args, expr_ref& result) {
params.reset();
sorts.reset();
for (expr* arg : args)
sorts.push_back(arg->get_sort());
sort_ref rng(m);
func_decl* f = nullptr;
switch (sexpr->get_kind()) {
case sexpr::kind_t::COMPOSITE: {
unsigned sz = sexpr->get_num_children();
if (sz == 0)
goto bail;
if (sexpr->get_child(0)->get_symbol() == symbol("_")) {
name = sexpr->get_child(1)->get_symbol();
if (name == "bv" && sz == 4) {
bv_util bvu(m);
auto val = sexpr->get_child(2)->get_numeral();
auto n = sexpr->get_child(3)->get_numeral().get_unsigned();
result = bvu.mk_numeral(val, n);
return;
}
if (name == "is" && sz == 3) {
name = sexpr->get_child(2)->get_child(0)->get_symbol();
f = ctx.find_func_decl(name, params.size(), params.data(), args.size(), sorts.data(), rng.get());
if (!f)
goto bail;
datatype_util dtu(m);
result = dtu.mk_is(f, args[0]);
return;
}
if (name == "Real" && sz == 4) {
arith_util au(m);
rational r = sexpr->get_child(2)->get_numeral();
// rational den = sexpr->get_child(3)->get_numeral();
result = au.mk_numeral(r, false);
return;
}
if (name == "Int" && sz == 4) {
arith_util au(m);
rational num = sexpr->get_child(2)->get_numeral();
result = au.mk_numeral(num, true);
return;
}
if (name == "as-array" && sz == 3) {
array_util au(m);
auto const* ch2 = sexpr->get_child(2);
switch (ch2->get_kind()) {
case sexpr::kind_t::COMPOSITE:
break;
default:
name = sexpr->get_child(2)->get_symbol();
f = ctx.find_func_decl(name);
if (f) {
result = au.mk_as_array(f);
return;
}
}
}
for (unsigned i = 2; i < sz; ++i) {
auto* child = sexpr->get_child(i);
if (child->is_numeral() && child->get_numeral().is_unsigned())
params.push_back(child->get_numeral().get_unsigned());
else
goto bail;
}
break; break;
} }
goto bail;
}
case sexpr::kind_t::SYMBOL:
name = sexpr->get_symbol();
break;
case sexpr::kind_t::BV_NUMERAL: {
goto bail;
}
case sexpr::kind_t::STRING:
case sexpr::kind_t::KEYWORD:
case sexpr::kind_t::NUMERAL:
default:
goto bail;
}
f = ctx.find_func_decl(name, params.size(), params.data(), args.size(), sorts.data(), rng.get());
if (!f)
goto bail;
result = ctx.m().mk_app(f, args);
return;
bail:
std::cout << "Could not parse expression\n";
sexpr->display(std::cout);
std::cout << "\n";
exit(0);
}
};
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);
ast_manager& m = ctx.m();
std::function<int(char const* read_theory)> read_theory = [&](char const* r) {
return m.mk_family_id(symbol(r));
};
std::function<symbol(int)> write_theory = [&](int th) {
return m.get_family_name(th);
};
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(m);
expr_ref_vector exprs(m), args(m), inputs(m);
sort_ref_vector sargs(m), sorts(m);
func_decl_ref_vector decls(m);
smt_checker checker(drat_checker, bool_var2expr);
for (expr* a : ctx.assertions())
checker.add_assertion(a);
for (auto const& r : drat) {
std::cout << dimacs::drat_pp(r, write_theory);
std::cout.flush();
switch (r.m_tag) {
case dimacs::drat_record::tag_t::is_clause: {
bool validated = checker.validate_hint(exprs, r.m_lits, r.m_hint);
checker.add(r.m_lits, r.m_status, validated);
if (drat_checker.inconsistent()) {
std::cout << "inconsistent\n";
return;
}
break;
}
case dimacs::drat_record::tag_t::is_node: {
expr_ref e(m);
args.reset();
for (auto n : r.m_args)
args.push_back(exprs.get(n));
std::istringstream strm(r.m_name);
auto sexpr = parse_sexpr(ctx, strm, p, drat_file);
checker.parse_sexpr(sexpr, ctx, args, e);
exprs.reserve(r.m_node_id + 1);
exprs.set(r.m_node_id, e);
break;
}
case dimacs::drat_record::tag_t::is_var: {
var_ref e(m);
SASSERT(r.m_args.size() == 1);
std::istringstream strm(r.m_name);
auto srt = parse_smt2_sort(ctx, strm, false, p, drat_file);
e = m.mk_var(r.m_args[0], srt);
exprs.reserve(r.m_node_id + 1);
exprs.set(r.m_node_id, e);
break;
}
case dimacs::drat_record::tag_t::is_decl: {
std::istringstream strm(r.m_name);
ctx.set_allow_duplicate_declarations();
parse_smt2_commands(ctx, strm);
break;
}
case dimacs::drat_record::tag_t::is_sort: {
sort_ref srt(m);
symbol name = symbol(r.m_name.c_str());
sargs.reset();
for (auto n : r.m_args)
sargs.push_back(sorts.get(n));
psort_decl* pd = ctx.find_psort_decl(name);
if (pd)
srt = pd->instantiate(ctx.pm(), sargs.size(), sargs.data());
else
srt = m.mk_uninterpreted_sort(name);
sorts.reserve(r.m_node_id + 1);
sorts.set(r.m_node_id, srt);
break;
}
case dimacs::drat_record::tag_t::is_quantifier: {
VERIFY(r.m_args.size() == 1);
quantifier_ref q(m);
std::istringstream strm(r.m_name);
auto sexpr = parse_sexpr(ctx, strm, p, drat_file);
sort_ref_vector domain(m);
svector<symbol> names;
quantifier_kind k;
checker.parse_quantifier(sexpr, ctx, k, domain, names);
q = m.mk_quantifier(k, domain.size(), domain.data(), names.data(), exprs.get(r.m_args[0]));
exprs.reserve(r.m_node_id + 1);
exprs.set(r.m_node_id, q);
break;
}
case dimacs::drat_record::tag_t::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: default:
UNREACHABLE(); UNREACHABLE();
break; break;
} }
} return false;
statistics st;
drat_checker.collect_statistics(st);
std::cout << st << "\n";
} }
#endif
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;
}

Some files were not shown because too many files have changed in this diff Show more