mirror of
https://github.com/Z3Prover/z3
synced 2025-04-16 13:58:45 +00:00
Merge branch 'master' of https://github.com/Z3Prover/z3 into jfleisher/nightlyversion
This commit is contained in:
commit
9c7a3e4f8d
|
@ -10,6 +10,11 @@ Version 4.next
|
|||
- native word level bit-vector solving.
|
||||
- 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
|
||||
==============
|
||||
- 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.
|
||||
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 from_string method to JavaScript solver object.
|
||||
- add fromString method to JavaScript solver object.
|
||||
|
||||
Version 4.10.2
|
||||
==============
|
||||
|
|
|
@ -35,7 +35,7 @@ def help(ous):
|
|||
ous.write("Z3 Options\n")
|
||||
z3_exe = BUILD_DIR + "/z3"
|
||||
out = subprocess.Popen([z3_exe, "-pm"],stdout=subprocess.PIPE).communicate()[0]
|
||||
modules = []
|
||||
modules = ["global"]
|
||||
if out != None:
|
||||
out = out.decode(sys.stdout.encoding)
|
||||
module_re = re.compile(r"\[module\] (.*)\,")
|
||||
|
|
|
@ -27,7 +27,7 @@ def init_project_def():
|
|||
add_lib('params', ['util'])
|
||||
add_lib('smt_params', ['params'], 'smt/params')
|
||||
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('lp', ['util', 'nlsat', 'grobner', 'interval', 'smt_params'], 'math/lp')
|
||||
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']
|
||||
add_lib('api', ['portfolio', 'realclosure', 'opt'],
|
||||
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('test', ['api', 'fuzzing', 'simplex', 'sat_smt'], exe_name='test-z3', install=False)
|
||||
_libz3Component = add_dll('api_dll', ['api', 'sat', 'extra_cmds'], 'api/dll',
|
||||
|
|
|
@ -242,6 +242,7 @@ extern "C" {
|
|||
std::istringstream is(s);
|
||||
ctx->set_regular_stream(ous);
|
||||
ctx->set_diagnostic_stream(ous);
|
||||
cmd_context::scoped_redirect _redirect(*ctx);
|
||||
try {
|
||||
if (!parse_smt2_commands(*ctx.get(), is)) {
|
||||
SET_ERROR_CODE(Z3_PARSER_ERROR, ous.str());
|
||||
|
|
|
@ -71,6 +71,7 @@ namespace Microsoft.Z3
|
|||
Solver solver;
|
||||
Context ctx;
|
||||
Z3_solver_callback callback = IntPtr.Zero;
|
||||
int callbackNesting = 0;
|
||||
FixedEh fixed_eh;
|
||||
Action final_eh;
|
||||
EqEh eq_eh;
|
||||
|
@ -91,6 +92,7 @@ namespace Microsoft.Z3
|
|||
|
||||
void Callback(Action fn, Z3_solver_callback cb)
|
||||
{
|
||||
this.callbackNesting++;
|
||||
this.callback = cb;
|
||||
try
|
||||
{
|
||||
|
@ -102,7 +104,9 @@ namespace Microsoft.Z3
|
|||
}
|
||||
finally
|
||||
{
|
||||
this.callback = IntPtr.Zero;
|
||||
callbackNesting--;
|
||||
if (callbackNesting == 0) // callbacks can be nested (e.g., internalizing new element in "created")
|
||||
this.callback = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
if (logic == symbol::null)
|
||||
if (logic == symbol::null) {
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,7 @@ void ast_pp_util::push() {
|
|||
m_rec_decls.push();
|
||||
m_decls.push();
|
||||
m_sorts.push();
|
||||
m_defined_lim.push_back(m_defined.size());
|
||||
}
|
||||
|
||||
void ast_pp_util::pop(unsigned n) {
|
||||
|
@ -136,4 +137,55 @@ void ast_pp_util::pop(unsigned n) {
|
|||
m_rec_decls.pop(n);
|
||||
m_decls.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;
|
||||
}
|
||||
|
|
|
@ -30,15 +30,18 @@ class ast_pp_util {
|
|||
stacked_value<unsigned> m_rec_decls;
|
||||
stacked_value<unsigned> m_decls;
|
||||
stacked_value<unsigned> m_sorts;
|
||||
expr_mark m_is_defined;
|
||||
expr_ref_vector m_defined;
|
||||
unsigned_vector m_defined_lim;
|
||||
|
||||
public:
|
||||
|
||||
decl_collector coll;
|
||||
|
||||
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) {}
|
||||
|
||||
ast_pp_util(ast_manager& m): m(m), m_env(m), m_rec_decls(0), m_decls(0), m_sorts(0), 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);
|
||||
|
||||
|
@ -60,6 +63,10 @@ class ast_pp_util {
|
|||
|
||||
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 pop(unsigned n);
|
||||
|
|
|
@ -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) {
|
||||
if (args[i]->get_sort() != r->get_domain(i)) {
|
||||
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());
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ Revision History:
|
|||
\brief Macros are universally quantified formulas of the form:
|
||||
(forall X (= (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.
|
||||
It has support for backtracking and tagging declarations in an expression as forbidded for being macros.
|
||||
|
|
|
@ -66,7 +66,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p)
|
|||
bool single_line = p.single_line();
|
||||
|
||||
unsigned pos = 0;
|
||||
unsigned ribbon_pos = 0;
|
||||
unsigned line = 0;
|
||||
unsigned len;
|
||||
unsigned i;
|
||||
|
@ -92,7 +91,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p)
|
|||
break;
|
||||
}
|
||||
pos += len;
|
||||
ribbon_pos += len;
|
||||
out << f->get_decl()->get_parameter(0).get_symbol();
|
||||
break;
|
||||
case OP_INDENT:
|
||||
|
@ -121,7 +119,6 @@ void pp(std::ostream & out, format * f, ast_manager & m, params_ref const & _p)
|
|||
break;
|
||||
}
|
||||
pos = indent;
|
||||
ribbon_pos = 0;
|
||||
line++;
|
||||
if (line < max_num_lines) {
|
||||
out << "\n";
|
||||
|
|
|
@ -291,7 +291,11 @@ namespace recfun {
|
|||
expr * e = stack.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
|
||||
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
|
||||
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));
|
||||
}
|
||||
|
||||
// yield new case
|
||||
bool is_imm = is_i(case_rhs);
|
||||
|
@ -471,9 +474,8 @@ namespace recfun {
|
|||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool plugin::has_defs() const {
|
||||
|
|
|
@ -29,8 +29,8 @@ public:
|
|||
m.toggle_proof_mode(mode);
|
||||
}
|
||||
~scoped_proof_mode() {
|
||||
m.toggle_proof_mode(m_mode);
|
||||
}
|
||||
m.toggle_proof_mode(m_mode);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ public:
|
|||
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];
|
||||
VERIFY(m_subst.find(var.first, var.second, r));
|
||||
}
|
||||
|
|
|
@ -561,6 +561,7 @@ cmd_context::~cmd_context() {
|
|||
finalize_cmds();
|
||||
finalize_tactic_cmds();
|
||||
finalize_probes();
|
||||
m_proof_cmds = nullptr;
|
||||
reset(true);
|
||||
m_mcs.reset();
|
||||
m_solver = nullptr;
|
||||
|
|
|
@ -90,6 +90,17 @@ public:
|
|||
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.
|
||||
*/
|
||||
|
@ -172,6 +183,7 @@ public:
|
|||
bool owns_manager() const { return m_manager != nullptr; }
|
||||
};
|
||||
|
||||
|
||||
class cmd_context : public progress_callback, public tactic_manager, public ast_printer_context {
|
||||
public:
|
||||
enum status {
|
||||
|
@ -191,6 +203,22 @@ public:
|
|||
~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:
|
||||
|
@ -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_exit_on_error = false;
|
||||
bool m_allow_duplicate_declarations = false;
|
||||
scoped_ptr<proof_cmds> m_proof_cmds;
|
||||
|
||||
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; }
|
||||
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_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(); }
|
||||
|
|
|
@ -3,6 +3,7 @@ z3_add_component(extra_cmds
|
|||
dbg_cmds.cpp
|
||||
polynomial_cmds.cpp
|
||||
subpaving_cmds.cpp
|
||||
proof_cmds.cpp
|
||||
COMPONENT_DEPENDENCIES
|
||||
arith_tactics
|
||||
cmd_context
|
||||
|
|
267
src/cmd_context/extra_cmds/proof_cmds.cpp
Normal file
267
src/cmd_context/extra_cmds/proof_cmds.cpp
Normal 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));
|
||||
}
|
36
src/cmd_context/extra_cmds/proof_cmds.h
Normal file
36
src/cmd_context/extra_cmds/proof_cmds.h
Normal 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);
|
||||
|
|
@ -546,6 +546,7 @@ bool emonics::invariant() const {
|
|||
}
|
||||
CTRACE("nla_solver_mons", !found, tout << "not found v" << v << ": " << m << "\n";);
|
||||
SASSERT(found);
|
||||
(void)found;
|
||||
c = c->m_next;
|
||||
}
|
||||
while (c != ht.m_head);
|
||||
|
|
|
@ -49,6 +49,7 @@ const char* lp_status_to_string(lp_status status) {
|
|||
case lp_status::TIME_EXHAUSTED: return "TIME_EXHAUSTED";
|
||||
case lp_status::EMPTY: return "EMPTY";
|
||||
case lp_status::UNSTABLE: return "UNSTABLE";
|
||||
case lp_status::CANCELLED: return "CANCELLED";
|
||||
default:
|
||||
lp_unreachable();
|
||||
}
|
||||
|
|
|
@ -380,45 +380,43 @@ namespace opt {
|
|||
m_below.reset();
|
||||
for (unsigned row_id : row_ids) {
|
||||
SASSERT(row_id != m_objective_id);
|
||||
if (visited.contains(row_id)) {
|
||||
if (visited.contains(row_id))
|
||||
continue;
|
||||
}
|
||||
visited.insert(row_id);
|
||||
row& r = m_rows[row_id];
|
||||
if (r.m_alive) {
|
||||
rational a = get_coefficient(row_id, x);
|
||||
if (a.is_zero()) {
|
||||
// skip
|
||||
}
|
||||
else if (a.is_pos() == is_pos || r.m_type == t_eq) {
|
||||
rational value = x_val - (r.m_value/a);
|
||||
if (bound_row_index == UINT_MAX) {
|
||||
lub_val = value;
|
||||
bound_row_index = row_id;
|
||||
bound_coeff = a;
|
||||
}
|
||||
else if ((value == lub_val && r.m_type == opt::t_lt) ||
|
||||
(is_pos && value < lub_val) ||
|
||||
|
||||
(!is_pos && value > lub_val)) {
|
||||
m_above.push_back(bound_row_index);
|
||||
lub_val = value;
|
||||
bound_row_index = row_id;
|
||||
bound_coeff = a;
|
||||
}
|
||||
else {
|
||||
m_above.push_back(row_id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_below.push_back(row_id);
|
||||
}
|
||||
if (!r.m_alive)
|
||||
continue;
|
||||
rational a = get_coefficient(row_id, x);
|
||||
if (a.is_zero()) {
|
||||
// skip
|
||||
}
|
||||
else if (a.is_pos() == is_pos || r.m_type == t_eq) {
|
||||
rational value = x_val - (r.m_value/a);
|
||||
if (bound_row_index == UINT_MAX) {
|
||||
lub_val = value;
|
||||
bound_row_index = row_id;
|
||||
bound_coeff = a;
|
||||
}
|
||||
else if ((value == lub_val && r.m_type == opt::t_lt) ||
|
||||
(is_pos && value < lub_val) ||
|
||||
|
||||
(!is_pos && value > lub_val)) {
|
||||
m_above.push_back(bound_row_index);
|
||||
lub_val = value;
|
||||
bound_row_index = row_id;
|
||||
bound_coeff = a;
|
||||
}
|
||||
else
|
||||
m_above.push_back(row_id);
|
||||
}
|
||||
else
|
||||
m_below.push_back(row_id);
|
||||
}
|
||||
return bound_row_index != UINT_MAX;
|
||||
}
|
||||
|
||||
void model_based_opt::retire_row(unsigned row_id) {
|
||||
SASSERT(!m_retired_rows.contains(row_id));
|
||||
m_rows[row_id].m_alive = false;
|
||||
m_retired_rows.push_back(row_id);
|
||||
}
|
||||
|
@ -736,6 +734,8 @@ namespace opt {
|
|||
|
||||
void model_based_opt::normalize(unsigned row_id) {
|
||||
row& r = m_rows[row_id];
|
||||
if (!r.m_alive)
|
||||
return;
|
||||
if (r.m_vars.empty()) {
|
||||
retire_row(row_id);
|
||||
return;
|
||||
|
@ -934,6 +934,7 @@ namespace opt {
|
|||
else {
|
||||
row_id = m_retired_rows.back();
|
||||
m_retired_rows.pop_back();
|
||||
SASSERT(!m_rows[row_id].m_alive);
|
||||
m_rows[row_id].reset();
|
||||
m_rows[row_id].m_alive = true;
|
||||
}
|
||||
|
@ -995,10 +996,10 @@ namespace opt {
|
|||
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();
|
||||
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();
|
||||
set_row(row_id, coeffs, c, m, rel);
|
||||
m_rows[row_id].m_id = id;
|
||||
|
@ -1006,6 +1007,7 @@ namespace opt {
|
|||
m_var2row_ids[coeff.m_id].push_back(row_id);
|
||||
SASSERT(invariant(row_id, m_rows[row_id]));
|
||||
normalize(row_id);
|
||||
return row_id;
|
||||
}
|
||||
|
||||
void model_based_opt::set_objective(vector<var> const& coeffs, rational const& c) {
|
||||
|
@ -1057,23 +1059,18 @@ namespace opt {
|
|||
unsigned eq_row = UINT_MAX;
|
||||
// select the lub and glb.
|
||||
for (unsigned row_id : row_ids) {
|
||||
if (visited.contains(row_id)) {
|
||||
if (visited.contains(row_id))
|
||||
continue;
|
||||
}
|
||||
visited.insert(row_id);
|
||||
row& r = m_rows[row_id];
|
||||
if (!r.m_alive) {
|
||||
if (!r.m_alive)
|
||||
continue;
|
||||
}
|
||||
rational a = get_coefficient(row_id, x);
|
||||
if (a.is_zero()) {
|
||||
if (a.is_zero())
|
||||
continue;
|
||||
}
|
||||
if (r.m_type == t_eq) {
|
||||
if (r.m_type == t_eq)
|
||||
eq_row = row_id;
|
||||
continue;
|
||||
}
|
||||
if (r.m_type == t_mod)
|
||||
else if (r.m_type == t_mod)
|
||||
mod_rows.push_back(row_id);
|
||||
else if (r.m_type == t_div)
|
||||
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())
|
||||
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)
|
||||
return solve_for(eq_row, x, compute_def);
|
||||
|
||||
|
@ -1218,89 +1212,7 @@ namespace opt {
|
|||
// - 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
|
||||
//
|
||||
|
||||
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
|
||||
// Replace x |-> K*y + z
|
||||
|
@ -1322,14 +1234,17 @@ namespace opt {
|
|||
// where k is between 0 and g
|
||||
// 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;
|
||||
unsigned_vector div_rows(_div_rows);
|
||||
SASSERT(!div_rows.empty());
|
||||
unsigned_vector div_rows(_div_rows), mod_rows(_mod_rows);
|
||||
SASSERT(!div_rows.empty() || !mod_rows.empty());
|
||||
TRACE("opt", display(tout << "solve_div " << x << "\n"));
|
||||
|
||||
rational K(1);
|
||||
for (unsigned ri : div_rows)
|
||||
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 z_value = mod(x_value, K);
|
||||
|
@ -1341,12 +1256,28 @@ namespace opt {
|
|||
unsigned y = add_var(y_value, true);
|
||||
|
||||
uint_set visited;
|
||||
unsigned j = 0;
|
||||
for (unsigned ri : div_rows) {
|
||||
if (visited.contains(ri))
|
||||
continue;
|
||||
row& r = m_rows[ri];
|
||||
mul(ri, K / r.m_mod);
|
||||
r.m_alive = false;
|
||||
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.
|
||||
for (unsigned ri : m_var2row_ids[x]) {
|
||||
|
@ -1361,9 +1292,10 @@ namespace opt {
|
|||
add_lower_bound(z, rational::zero());
|
||||
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) {
|
||||
|
||||
|
@ -1375,9 +1307,11 @@ namespace opt {
|
|||
rational coeff = m_rows[ri].m_coeff;
|
||||
unsigned w = UINT_MAX;
|
||||
rational offset(0);
|
||||
if (coeffs.empty())
|
||||
if (K == 1)
|
||||
offset = coeff;
|
||||
else if (coeffs.empty())
|
||||
offset = div(coeff, K);
|
||||
else
|
||||
else
|
||||
w = add_div(coeffs, coeff, K);
|
||||
|
||||
//
|
||||
|
@ -1412,20 +1346,27 @@ namespace opt {
|
|||
vector<var> div_coeffs;
|
||||
div_coeffs.push_back(var(v, rational::minus_one()));
|
||||
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);
|
||||
|
||||
unsigned u = UINT_MAX;
|
||||
offset = 0;
|
||||
if (coeffs.empty())
|
||||
if (K == 1)
|
||||
offset = 0;
|
||||
else if (coeffs.empty())
|
||||
offset = mod(coeff, K);
|
||||
else
|
||||
u = add_mod(coeffs, coeff, K);
|
||||
|
||||
|
||||
// add a*z + (b mod K) < (k + 1)*K
|
||||
vector<var> bound_coeffs;
|
||||
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 k*K <= az + (b mod K)
|
||||
|
@ -1433,11 +1374,49 @@ namespace opt {
|
|||
c.m_coeff.neg();
|
||||
add_constraint(bound_coeffs, k * K - offset, t_le);
|
||||
// allow to recycle row.
|
||||
m_retired_rows.push_back(ri);
|
||||
project(v, false);
|
||||
retire_row(ri);
|
||||
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.
|
||||
|
||||
def y_def = project(y, compute_def);
|
||||
|
@ -1501,12 +1480,12 @@ namespace opt {
|
|||
unsigned_vector const& row_ids = m_var2row_ids[x];
|
||||
uint_set visited;
|
||||
for (unsigned row_id : row_ids) {
|
||||
if (!visited.contains(row_id)) {
|
||||
// x |-> D*y + u
|
||||
replace_var(row_id, x, D, y, u);
|
||||
visited.insert(row_id);
|
||||
normalize(row_id);
|
||||
}
|
||||
if (visited.contains(row_id))
|
||||
continue;
|
||||
// x |-> D*y + u
|
||||
replace_var(row_id, x, D, y, u);
|
||||
visited.insert(row_id);
|
||||
normalize(row_id);
|
||||
}
|
||||
TRACE("opt1", display(tout << "tableau after replace x by y := v" << y << "\n"););
|
||||
def result = project(y, compute_def);
|
||||
|
@ -1611,25 +1590,28 @@ namespace opt {
|
|||
uint_set visited;
|
||||
visited.insert(row_id1);
|
||||
for (unsigned row_id2 : row_ids) {
|
||||
if (!visited.contains(row_id2)) {
|
||||
visited.insert(row_id2);
|
||||
b = get_coefficient(row_id2, x);
|
||||
if (b.is_zero())
|
||||
continue;
|
||||
row& dst = m_rows[row_id2];
|
||||
switch (dst.m_type) {
|
||||
case t_eq:
|
||||
case t_lt:
|
||||
case t_le:
|
||||
solve(row_id1, a, row_id2, x);
|
||||
break;
|
||||
case t_divides:
|
||||
case t_mod:
|
||||
case t_div:
|
||||
// mod reduction already done.
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
if (visited.contains(row_id2))
|
||||
continue;
|
||||
visited.insert(row_id2);
|
||||
row& r = m_rows[row_id2];
|
||||
if (!r.m_alive)
|
||||
continue;
|
||||
b = get_coefficient(row_id2, x);
|
||||
if (b.is_zero())
|
||||
continue;
|
||||
row& dst = m_rows[row_id2];
|
||||
switch (dst.m_type) {
|
||||
case t_eq:
|
||||
case t_lt:
|
||||
case t_le:
|
||||
solve(row_id1, a, row_id2, x);
|
||||
break;
|
||||
case t_divides:
|
||||
case t_mod:
|
||||
case t_div:
|
||||
// mod reduction already done.
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
def result;
|
||||
|
|
|
@ -60,14 +60,13 @@ namespace opt {
|
|||
}
|
||||
};
|
||||
struct row {
|
||||
row(): m_type(t_le), m_value(0), m_alive(false) {}
|
||||
vector<var> m_vars; // variables with coefficients
|
||||
rational m_coeff; // constant in inequality
|
||||
rational m_mod; // value the term divide
|
||||
ineq_type m_type; // inequality type
|
||||
rational m_value; // value of m_vars + m_coeff under interpretation of m_var2value.
|
||||
bool m_alive; // rows can be marked dead if they have been processed.
|
||||
unsigned m_id; // variable defined by row (used for mod_t and div_t)
|
||||
vector<var> m_vars; // variables with coefficients
|
||||
rational m_coeff = rational::zero(); // constant in inequality
|
||||
rational m_mod = rational::zero(); // value the term divide
|
||||
ineq_type m_type = t_le; // inequality type
|
||||
rational m_value = rational::zero(); // value of m_vars + m_coeff under interpretation of m_var2value.
|
||||
bool m_alive = false; // rows can be marked dead if they have been processed.
|
||||
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(); }
|
||||
|
||||
row& normalize();
|
||||
|
@ -139,7 +138,7 @@ namespace opt {
|
|||
|
||||
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);
|
||||
|
||||
|
@ -167,9 +166,7 @@ namespace opt {
|
|||
|
||||
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_div(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);
|
||||
|
||||
bool is_int(unsigned x) const { return m_var2is_int[x]; }
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ namespace simplex {
|
|||
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) {
|
||||
rational delta(1);
|
||||
for (unsigned i = 0; i < S.get_num_vars(); ++i) {
|
||||
|
|
|
@ -203,5 +203,6 @@ namespace simplex {
|
|||
void ensure_rational_solution(simplex<mpq_ext>& s);
|
||||
|
||||
void kernel(sparse_matrix<mpq_ext>& s, vector<vector<rational>>& K);
|
||||
void kernel_ffe(sparse_matrix<mpq_ext> &s, vector<vector<rational>> &K);
|
||||
};
|
||||
|
||||
|
|
|
@ -168,6 +168,7 @@ namespace simplex {
|
|||
void add_var(row r, numeral const& n, var_t var);
|
||||
void add(row r, numeral const& n, row src);
|
||||
void mul(row r, numeral const& n);
|
||||
void div(row r, numeral const& n);
|
||||
void neg(row r);
|
||||
void del(row r);
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -43,7 +43,7 @@ class sparse_matrix_ops {
|
|||
for (auto [row, row_entry] : M.get_rows(k)) {
|
||||
if (c[row.id()] != 0) continue;
|
||||
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;
|
||||
m.set(D, m_jk);
|
||||
|
@ -68,9 +68,10 @@ class sparse_matrix_ops {
|
|||
K.push_back(vector<rational>());
|
||||
for (unsigned i = 0; i < n_vars; ++i) {
|
||||
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)));
|
||||
} else if (i == k)
|
||||
}
|
||||
else if (i == k)
|
||||
K.back().push_back(rational(1));
|
||||
else
|
||||
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) {
|
||||
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
|
||||
|
|
|
@ -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) {
|
||||
SASSERT(m_ar.is_array(a));
|
||||
bool are_values = true;
|
||||
are_unique = true;
|
||||
TRACE("model_evaluator", tout << mk_pp(a, m) << "\n";);
|
||||
|
||||
while (m_ar.is_store(a)) {
|
||||
expr_ref_vector store(m);
|
||||
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);
|
||||
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)) {
|
||||
for (auto const& store : stores) {
|
||||
are_values &= args_are_values(store, are_unique);
|
||||
}
|
||||
for (auto const& store : stores)
|
||||
args_are_values(store, are_unique);
|
||||
return true;
|
||||
}
|
||||
if (!m_ar.is_as_array(a)) {
|
||||
|
|
|
@ -169,7 +169,6 @@ def_module_params('fp',
|
|||
('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'),
|
||||
('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"),
|
||||
('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.ctp', BOOL, True, 'Enable counterexample-to-pushing'),
|
||||
('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.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.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'),
|
||||
))
|
||||
|
|
8
src/muz/spacer/.clang-format
Normal file
8
src/muz/spacer/.clang-format
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
BasedOnStyle: LLVM
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
IndentWidth: '4'
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
...
|
|
@ -10,6 +10,7 @@ z3_add_component(spacer
|
|||
spacer_prop_solver.cpp
|
||||
spacer_sym_mux.cpp
|
||||
spacer_util.cpp
|
||||
spacer_cluster_util.cpp
|
||||
spacer_iuc_solver.cpp
|
||||
spacer_legacy_mbp.cpp
|
||||
spacer_proof_utils.cpp
|
||||
|
@ -22,12 +23,19 @@ z3_add_component(spacer
|
|||
spacer_sem_matcher.cpp
|
||||
spacer_quant_generalizer.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_json.cpp
|
||||
spacer_iuc_proof.cpp
|
||||
spacer_mbc.cpp
|
||||
spacer_pdr.cpp
|
||||
spacer_sat_answer.cpp
|
||||
spacer_concretize.cpp
|
||||
spacer_convex_closure.cpp
|
||||
spacer_conjecture.cpp
|
||||
spacer_arith_kernel.cpp
|
||||
COMPONENT_DEPENDENCIES
|
||||
arith_tactics
|
||||
core_tactics
|
||||
|
|
|
@ -155,6 +155,7 @@ void anti_unifier::operator()(expr *e1, expr *e2, expr_ref &res,
|
|||
m_pinned.push_back(u);
|
||||
m_cache.insert(n1, n2, u);
|
||||
}
|
||||
m_todo.pop_back();
|
||||
}
|
||||
|
||||
expr *r;
|
||||
|
|
108
src/muz/spacer/spacer_arith_kernel.cpp
Normal file
108
src/muz/spacer/spacer_arith_kernel.cpp
Normal 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
|
93
src/muz/spacer/spacer_arith_kernel.h
Normal file
93
src/muz/spacer/spacer_arith_kernel.h
Normal 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
|
397
src/muz/spacer/spacer_cluster.cpp
Normal file
397
src/muz/spacer/spacer_cluster.cpp
Normal 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
|
176
src/muz/spacer/spacer_cluster.h
Normal file
176
src/muz/spacer/spacer_cluster.h
Normal 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
|
240
src/muz/spacer/spacer_cluster_util.cpp
Normal file
240
src/muz/spacer/spacer_cluster_util.cpp
Normal 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>;
|
208
src/muz/spacer/spacer_concretize.cpp
Normal file
208
src/muz/spacer/spacer_concretize.cpp
Normal 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
|
||||
|
77
src/muz/spacer/spacer_concretize.h
Normal file
77
src/muz/spacer/spacer_concretize.h
Normal 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
|
98
src/muz/spacer/spacer_conjecture.cpp
Normal file
98
src/muz/spacer/spacer_conjecture.cpp
Normal 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
|
|
@ -20,6 +20,7 @@ Notes:
|
|||
|
||||
--*/
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
@ -51,30 +52,36 @@ Notes:
|
|||
#include "muz/transforms/dl_mk_rule_inliner.h"
|
||||
#include "muz/spacer/spacer_qe_project.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
|
||||
|
||||
namespace spacer {
|
||||
|
||||
/// pob -- proof obligation
|
||||
pob::pob (pob* parent, pred_transformer& pt,
|
||||
unsigned level, unsigned depth, bool add_to_parent):
|
||||
m_ref_count (0),
|
||||
m_parent (parent), m_pt (pt),
|
||||
m_post (m_pt.get_ast_manager ()),
|
||||
m_binding(m_pt.get_ast_manager()),
|
||||
m_new_post (m_pt.get_ast_manager ()),
|
||||
m_level (level), m_depth (depth),
|
||||
m_open (true), m_use_farkas (true), m_in_queue(false),
|
||||
m_weakness(0), m_blocked_lvl(0) {
|
||||
pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth,
|
||||
bool add_to_parent)
|
||||
: m_ref_count(0), m_parent(parent), m_pt(pt),
|
||||
m_post(m_pt.get_ast_manager()), m_binding(m_pt.get_ast_manager()),
|
||||
m_new_post(m_pt.get_ast_manager()), m_level(level), m_depth(depth),
|
||||
m_desired_level(0), m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false),
|
||||
m_enable_local_gen(true), m_enable_concretize(false), m_is_subsume(false),
|
||||
m_enable_expand_bnd_gen(false), m_weakness(0), m_blocked_lvl(0),
|
||||
m_concretize_pat(m_pt.get_ast_manager()),
|
||||
m_gas(0) {
|
||||
if (add_to_parent && m_parent) {
|
||||
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) {
|
||||
app_ref_vector empty_binding(get_ast_manager());
|
||||
set_post(post, empty_binding);
|
||||
set_post(post, {get_ast_manager()});
|
||||
}
|
||||
|
||||
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(m_parent == p.m_parent);
|
||||
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_new_post);
|
||||
|
||||
|
@ -99,11 +111,21 @@ void pob::inherit(pob const &p) {
|
|||
|
||||
m_level = p.m_level;
|
||||
m_depth = p.m_depth;
|
||||
m_desired_level = std::max(m_desired_level, p.m_desired_level);
|
||||
m_open = p.m_open;
|
||||
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_derivation = nullptr;
|
||||
|
||||
m_gas = p.m_gas;
|
||||
}
|
||||
|
||||
void pob::close () {
|
||||
|
@ -756,6 +778,8 @@ void pred_transformer::collect_statistics(statistics& st) const
|
|||
m_must_reachable_watch.get_seconds ());
|
||||
st.update("time.spacer.ctp", m_ctp_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()
|
||||
|
@ -1193,6 +1217,7 @@ expr_ref pred_transformer::get_origin_summary (model &mdl,
|
|||
for (auto* s : summary) {
|
||||
if (!is_quantifier(s) && !mdl.is_true(s)) {
|
||||
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
|
||||
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 ());
|
||||
prop_solver::scoped_level _sl (*m_solver, n.level ());
|
||||
m_solver->set_core (nullptr);
|
||||
m_solver->set_model (nullptr);
|
||||
m_solver->set_model(model);
|
||||
|
||||
expr_ref_vector post(m), _aux(m);
|
||||
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,
|
||||
bool& is_concrete, datalog::rule const*& r,
|
||||
bool_vector& reach_pred_used,
|
||||
unsigned& num_reuse_reach)
|
||||
unsigned& num_reuse_reach, bool use_iuc)
|
||||
{
|
||||
TRACE("spacer",
|
||||
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
|
||||
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,
|
||||
ctx.weak_abs() ? n.weakness() : UINT_MAX);
|
||||
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);
|
||||
TRACE("spacer",
|
||||
tout << "reachable is_sat: " << is_sat << " "
|
||||
<< r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n";
|
||||
ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout) << "\n";
|
||||
);
|
||||
<< r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\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";);
|
||||
}
|
||||
|
||||
|
@ -2272,7 +2298,8 @@ context::context(fp_params const& params, ast_manager& m) :
|
|||
m_last_result(l_undef),
|
||||
m_inductive_lvl(0),
|
||||
m_expanded_lvl(0),
|
||||
m_json_marshaller(this),
|
||||
m_global_gen(nullptr),
|
||||
m_expand_bnd_gen(nullptr),
|
||||
m_trace_stream(nullptr) {
|
||||
|
||||
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_pool2 = alloc(solver_pool, pool2_base.get(), max_num_contexts);
|
||||
|
||||
m_lmma_cluster = alloc(lemma_cluster_finder, m);
|
||||
updt_params();
|
||||
|
||||
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()
|
||||
{
|
||||
reset_lemma_generalizers();
|
||||
dealloc(m_lmma_cluster);
|
||||
reset();
|
||||
|
||||
if (m_trace_stream) {
|
||||
|
@ -2347,7 +2376,13 @@ void context::updt_params() {
|
|||
m_restart_initial_threshold = m_params.spacer_restart_initial_threshold();
|
||||
m_pdr_bfs = m_params.spacer_gpdr_bfs();
|
||||
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) {
|
||||
// set options to be compatible with GPDR
|
||||
m_weak_abs = false;
|
||||
|
@ -2665,7 +2700,8 @@ void context::init_lemma_generalizers()
|
|||
//m_lemma_generalizers.push_back (alloc (unsat_core_generalizer, *this));
|
||||
|
||||
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)
|
||||
|
@ -2678,6 +2714,15 @@ void context::init_lemma_generalizers()
|
|||
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) {
|
||||
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 (lvl > 0 && m_use_propagate)
|
||||
if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { dump_json(); return l_false; }
|
||||
|
||||
dump_json();
|
||||
if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { return l_false; }
|
||||
|
||||
if (is_inductive()){
|
||||
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());
|
||||
|
||||
*m_trace_stream << "** expand-pob: " << n.pt().head()->get_name()
|
||||
<< (n.is_conjecture() ? " CONJ" : "")
|
||||
<< (n.is_subsume() ? " SUBS" : "")
|
||||
<< " level: " << n.level()
|
||||
<< " depth: " << (n.depth() - m_pob_queue.min_depth())
|
||||
<< " 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()
|
||||
<< (n.is_conjecture() ? " CONJ" : "")
|
||||
<< (n.is_subsume() ? " SUBS" : "")
|
||||
<< " level: " << n.level()
|
||||
<< " 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";);
|
||||
|
||||
STRACE("spacer_progress",
|
||||
tout << "** expand-pob: " << n.pt().head()->get_name()
|
||||
<< (n.is_conjecture() ? " CONJ" : "")
|
||||
<< (n.is_subsume() ? " SUBS" : "")
|
||||
<< " level: " << n.level()
|
||||
<< " depth: " << (n.depth() - m_pob_queue.min_depth()) << "\n"
|
||||
<< mk_epp(n.post(), m) << "\n\n";);
|
||||
|
@ -3151,13 +3201,21 @@ bool context::check_reachability ()
|
|||
node = last_reachable;
|
||||
last_reachable = nullptr;
|
||||
if (m_pob_queue.is_root(*node)) { return true; }
|
||||
if (is_reachable (*node->parent())) {
|
||||
last_reachable = node->parent ();
|
||||
|
||||
// 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())) {
|
||||
last_reachable = node->parent();
|
||||
SASSERT(last_reachable->is_closed());
|
||||
last_reachable->close ();
|
||||
last_reachable->close();
|
||||
} else if (!node->parent()->is_closed()) {
|
||||
/* bump node->parent */
|
||||
node->parent ()->bump_weakness();
|
||||
node->parent()->bump_weakness();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3202,20 +3260,37 @@ bool context::check_reachability ()
|
|||
case l_true:
|
||||
SASSERT(m_pob_queue.size() == old_sz);
|
||||
SASSERT(new_pobs.empty());
|
||||
node->close();
|
||||
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;
|
||||
case l_false:
|
||||
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) {
|
||||
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;}
|
||||
break;
|
||||
case l_undef:
|
||||
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);}
|
||||
break;
|
||||
}
|
||||
|
@ -3228,7 +3303,10 @@ bool context::check_reachability ()
|
|||
|
||||
/// returns true if the given pob can be re-scheduled
|
||||
bool context::is_requeue(pob &n) {
|
||||
if (!m_push_pob) {return false;}
|
||||
// if have not reached desired level, then requeue
|
||||
if (n.level() <= n.desired_level()) { return true; }
|
||||
if (!m_push_pob) { return false; }
|
||||
|
||||
unsigned max_depth = m_push_pob_max_depth;
|
||||
return (n.level() >= m_pob_queue.max_level() ||
|
||||
m_pob_queue.max_level() - n.level() <= max_depth);
|
||||
|
@ -3270,9 +3348,9 @@ bool context::is_reachable(pob &n)
|
|||
unsigned saved = n.level ();
|
||||
// TBD: don't expose private field
|
||||
n.m_level = infty_level ();
|
||||
lbool res = n.pt().is_reachable(n, nullptr, &mdl,
|
||||
uses_level, is_concrete, r,
|
||||
reach_pred_used, num_reuse_reach);
|
||||
lbool res =
|
||||
n.pt().is_reachable(n, nullptr, &mdl, uses_level, is_concrete, r,
|
||||
reach_pred_used, num_reuse_reach, m_use_iuc);
|
||||
n.m_level = saved;
|
||||
|
||||
if (res != l_true || !is_concrete) {
|
||||
|
@ -3326,16 +3404,6 @@ bool context::is_reachable(pob &n)
|
|||
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()
|
||||
{
|
||||
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);
|
||||
|
||||
stopwatch watch;
|
||||
IF_VERBOSE (1, verbose_stream () << "expand: " << n.pt ().head ()->get_name ()
|
||||
<< " (" << n.level () << ", "
|
||||
IF_VERBOSE(1, verbose_stream()
|
||||
<< "expand: " << n.pt().head()->get_name() << " ("
|
||||
<< n.level() << ", "
|
||||
<< (n.depth () - m_pob_queue.min_depth ()) << ") "
|
||||
<< (n.use_farkas_generalizer () ? "FAR " : "SUB ")
|
||||
<< " w(" << n.weakness() << ") "
|
||||
<< n.post ()->get_id ();
|
||||
verbose_stream().flush ();
|
||||
watch.start (););
|
||||
<< (n.is_conjecture() ? "CONJ " : "")
|
||||
<< (n.is_subsume() ? " SUBS" : "") << " w("
|
||||
<< n.weakness() << ") " << n.post()->get_id();
|
||||
verbose_stream().flush(); watch.start(););
|
||||
|
||||
// used in case n is unreachable
|
||||
unsigned uses_level = infty_level ();
|
||||
|
@ -3468,25 +3537,59 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
|
|||
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_VERBOSE (1, verbose_stream () << " K "
|
||||
<< std::fixed << std::setprecision(2)
|
||||
<< watch.get_seconds () << "\n";);
|
||||
n.inc_level();
|
||||
out.push_back(&n);
|
||||
return l_false;
|
||||
}
|
||||
<< watch.get_seconds () << "\n";);
|
||||
n.inc_level();
|
||||
out.push_back(&n);
|
||||
return l_false;
|
||||
}
|
||||
|
||||
if (/* XXX noop */ n.pt().is_qblocked(n)) {
|
||||
STRACE("spacer_progress",
|
||||
tout << "This pob can be blocked by instantiation\n";);
|
||||
}
|
||||
if (/* XXX noop */ n.pt().is_qblocked(n)) {
|
||||
STRACE("spacer_progress",
|
||||
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();
|
||||
|
||||
lbool res = n.pt ().is_reachable (n, &cube, &model, uses_level, is_concrete, r,
|
||||
reach_pred_used, num_reuse_reach);
|
||||
lbool res =
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
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 ")
|
||||
<< std::fixed << std::setprecision(2)
|
||||
|
@ -3565,7 +3679,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
|
|||
throw unknown_exception();
|
||||
}
|
||||
case l_false: {
|
||||
// n is unreachable, create new summary facts
|
||||
// n is unreachable, create a new lemma
|
||||
timeit _timer (is_trace_enabled("spacer_timeit"),
|
||||
"spacer::expand_pob::false",
|
||||
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.
|
||||
if (n.level() < m_expanded_lvl) { m_expanded_lvl = n.level(); }
|
||||
|
||||
TRACE("spacer", tout << "cube:\n";
|
||||
for (unsigned j = 0; j < cube.size(); ++j)
|
||||
tout << mk_pp(cube[j].get(), m) << "\n";);
|
||||
TRACE("spacer", tout << "cube:\n" << cube << "\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);
|
||||
|
||||
// -- create lemma from a pob and last unsat core
|
||||
lemma_ref lemma = alloc(class lemma, pob_ref(&n), cube, uses_level);
|
||||
lemma_ref lemma_pob;
|
||||
if (n.is_local_gen_enabled()) {
|
||||
lemma_pob = alloc(class lemma, nref, cube, uses_level);
|
||||
// -- run all lemma generalizers
|
||||
for (unsigned i = 0;
|
||||
// -- only generalize if lemma was constructed using farkas
|
||||
n.use_farkas_generalizer() && !lemma_pob->is_false() &&
|
||||
i < m_lemma_generalizers.size();
|
||||
++i) {
|
||||
checkpoint ();
|
||||
(*m_lemma_generalizers[i])(lemma_pob);
|
||||
}
|
||||
} else if (m_global_gen || m_expand_bnd_gen) {
|
||||
m_stats.m_non_local_gen++;
|
||||
|
||||
// -- run all lemma generalizers
|
||||
for (unsigned i = 0;
|
||||
// -- only generalize if lemma was constructed using farkas
|
||||
n.use_farkas_generalizer () && !lemma->is_false() &&
|
||||
i < m_lemma_generalizers.size(); ++i) {
|
||||
checkpoint ();
|
||||
(*m_lemma_generalizers[i])(lemma);
|
||||
expr_ref_vector pob_cube(m);
|
||||
n.get_post_simplified(pob_cube);
|
||||
|
||||
lemma_pob = alloc(class lemma, nref, pob_cube, n.level());
|
||||
TRACE("global", tout << "Disabled local gen on pob (id: "
|
||||
<< 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);
|
||||
}
|
||||
DEBUG_CODE(
|
||||
lemma_sanity_checker sanity_checker(*this);
|
||||
sanity_checker(lemma);
|
||||
);
|
||||
|
||||
CTRACE("global", n.is_conjecture() || n.is_subsume(),
|
||||
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";);
|
||||
|
||||
TRACE("spacer", tout << "invariant state: "
|
||||
<< (is_infty_level(lemma->level())?"(inductive)":"")
|
||||
<< mk_pp(lemma->get_expr(), m) << "\n";);
|
||||
DEBUG_CODE(lemma_sanity_checker sanity_checker(*this);
|
||||
sanity_checker(lemma_pob););
|
||||
|
||||
bool v = n.pt().add_lemma (lemma.get());
|
||||
if (v) { m_stats.m_num_lemmas++; }
|
||||
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
|
||||
if (v && m_use_lemma_as_pob) {
|
||||
if (is_new && m_use_lemma_as_pob) {
|
||||
expr_ref c(m);
|
||||
c = mk_and(lemma->get_cube());
|
||||
c = mk_and(lemma_pob->get_cube());
|
||||
// check that the post condition is different
|
||||
if (c != n.post()) {
|
||||
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
|
||||
n.inc_level();
|
||||
out.push_back(&n);
|
||||
|
@ -3636,7 +3800,24 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out)
|
|||
return l_false;
|
||||
}
|
||||
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 */) {
|
||||
bool has_new_child = false;
|
||||
SASSERT(m_weak_abs);
|
||||
|
@ -3933,6 +4114,11 @@ bool context::create_children(pob& n, datalog::rule const& r,
|
|||
!mdl.is_true(n.post())))
|
||||
{ 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);
|
||||
m_stats.m_num_queries++;
|
||||
return true;
|
||||
|
@ -3971,6 +4157,17 @@ void context::collect_statistics(statistics& st) const
|
|||
st.update("SPACER num lemmas", m_stats.m_num_lemmas);
|
||||
// -- number of restarts taken
|
||||
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
|
||||
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) {
|
||||
m_lemma_generalizers[i]->collect_statistics(st);
|
||||
}
|
||||
m_lmma_cluster->collect_statistics(st);
|
||||
}
|
||||
|
||||
void context::reset_statistics()
|
||||
|
@ -4008,6 +4206,7 @@ void context::reset_statistics()
|
|||
m_lemma_generalizers[i]->reset_statistics();
|
||||
}
|
||||
|
||||
m_lmma_cluster->reset_statistics();
|
||||
m_init_rules_watch.reset ();
|
||||
m_solve_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) {
|
||||
if (m_params.spacer_print_json().is_non_empty_string())
|
||||
m_json_marshaller.register_lemma(lem);
|
||||
bool handle=false;
|
||||
for (unsigned i = 0; i < m_callbacks.size(); i++) {
|
||||
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) {
|
||||
if (m_params.spacer_print_json().is_non_empty_string())
|
||||
m_json_marshaller.register_pob(p);
|
||||
}
|
||||
void context::new_pob_eh(pob *p) { }
|
||||
|
||||
bool context::is_inductive() {
|
||||
// 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.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
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
389
src/muz/spacer/spacer_convex_closure.cpp
Normal file
389
src/muz/spacer/spacer_convex_closure.cpp
Normal 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
|
187
src/muz/spacer/spacer_convex_closure.h
Normal file
187
src/muz/spacer/spacer_convex_closure.h
Normal 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
|
229
src/muz/spacer/spacer_expand_bnd_generalizer.cpp
Normal file
229
src/muz/spacer/spacer_expand_bnd_generalizer.cpp
Normal 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
|
68
src/muz/spacer/spacer_expand_bnd_generalizer.h
Normal file
68
src/muz/spacer/spacer_expand_bnd_generalizer.h
Normal 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
|
|
@ -21,6 +21,7 @@ Revision History:
|
|||
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "muz/spacer/spacer_context.h"
|
||||
#include "muz/spacer/spacer_expand_bnd_generalizer.h"
|
||||
|
||||
namespace spacer {
|
||||
|
||||
|
@ -174,5 +175,10 @@ class limit_num_generalizer : public lemma_generalizer {
|
|||
void collect_statistics(statistics &st) const override;
|
||||
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
|
||||
|
|
796
src/muz/spacer/spacer_global_generalizer.cpp
Normal file
796
src/muz/spacer/spacer_global_generalizer.cpp
Normal 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
|
176
src/muz/spacer/spacer_global_generalizer.h
Normal file
176
src/muz/spacer/spacer_global_generalizer.h
Normal 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
|
303
src/muz/spacer/spacer_ind_lemma_generalizer.cpp
Normal file
303
src/muz/spacer/spacer_ind_lemma_generalizer.cpp
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -17,143 +17,169 @@ Revision History:
|
|||
--*/
|
||||
#include "muz/spacer/spacer_matrix.h"
|
||||
|
||||
namespace spacer
|
||||
{
|
||||
spacer_matrix::spacer_matrix(unsigned m, unsigned n) : m_num_rows(m), m_num_cols(n)
|
||||
{
|
||||
for (unsigned i=0; i < m; ++i)
|
||||
{
|
||||
vector<rational> v;
|
||||
for (unsigned j=0; j < n; ++j)
|
||||
{
|
||||
v.push_back(rational(0));
|
||||
namespace spacer {
|
||||
spacer_matrix::spacer_matrix(unsigned m, unsigned n)
|
||||
: m_num_rows(m), m_num_cols(n) {
|
||||
m_matrix.reserve(m_num_rows);
|
||||
for (unsigned i = 0; i < m_num_rows; ++i) {
|
||||
m_matrix[i].reserve(m_num_cols, rational(0));
|
||||
}
|
||||
}
|
||||
|
||||
void spacer_matrix::get_col(unsigned i, vector<rational> &row) const {
|
||||
SASSERT(i < m_num_cols);
|
||||
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);
|
||||
}
|
||||
|
||||
void spacer_matrix::add_row(const vector<rational> &row) {
|
||||
SASSERT(row.size() == m_num_cols);
|
||||
m_matrix.push_back(row);
|
||||
m_num_rows = m_matrix.size();
|
||||
}
|
||||
|
||||
unsigned spacer_matrix::perform_gaussian_elimination() {
|
||||
unsigned i = 0;
|
||||
unsigned j = 0;
|
||||
while (i < m_matrix.size() && j < m_matrix[0].size()) {
|
||||
// find maximal element in column with row index bigger or equal i
|
||||
rational max = m_matrix[i][j];
|
||||
unsigned max_index = i;
|
||||
|
||||
for (unsigned k = i + 1; k < m_matrix.size(); ++k) {
|
||||
if (max < m_matrix[k][j]) {
|
||||
max = m_matrix[k][j];
|
||||
max_index = k;
|
||||
}
|
||||
m_matrix.push_back(v);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned spacer_matrix::num_rows()
|
||||
{
|
||||
return m_num_rows;
|
||||
}
|
||||
|
||||
unsigned spacer_matrix::num_cols()
|
||||
{
|
||||
return m_num_cols;
|
||||
}
|
||||
|
||||
const rational& spacer_matrix::get(unsigned int i, unsigned int j)
|
||||
{
|
||||
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 j=0;
|
||||
while(i < m_matrix.size() && j < m_matrix[0].size())
|
||||
if (max.is_zero()) // skip this column
|
||||
{
|
||||
// find maximal element in column with row index bigger or equal i
|
||||
rational max = m_matrix[i][j];
|
||||
unsigned max_index = i;
|
||||
++j;
|
||||
} else {
|
||||
// reorder rows if necessary
|
||||
vector<rational> tmp = m_matrix[i];
|
||||
m_matrix[i] = m_matrix[max_index];
|
||||
m_matrix[max_index] = m_matrix[i];
|
||||
|
||||
for (unsigned k=i+1; k < m_matrix.size(); ++k)
|
||||
{
|
||||
if (max < m_matrix[k][j])
|
||||
{
|
||||
max = m_matrix[k][j];
|
||||
max_index = k;
|
||||
// normalize row
|
||||
rational pivot = m_matrix[i][j];
|
||||
if (!pivot.is_one()) {
|
||||
for (unsigned k = 0; k < m_matrix[i].size(); ++k) {
|
||||
m_matrix[i][k] = m_matrix[i][k] / pivot;
|
||||
}
|
||||
}
|
||||
|
||||
if (max.is_zero()) // skip this column
|
||||
{
|
||||
++j;
|
||||
}
|
||||
else
|
||||
{
|
||||
// reorder rows if necessary
|
||||
vector<rational> tmp = m_matrix[i];
|
||||
m_matrix[i] = m_matrix[max_index];
|
||||
m_matrix[max_index] = m_matrix[i];
|
||||
|
||||
// normalize row
|
||||
rational pivot = m_matrix[i][j];
|
||||
if (!pivot.is_one())
|
||||
{
|
||||
for (unsigned k=0; k < m_matrix[i].size(); ++k)
|
||||
{
|
||||
m_matrix[i][k] = m_matrix[i][k] / pivot;
|
||||
// subtract row from all other rows
|
||||
for (unsigned k = 1; k < m_matrix.size(); ++k) {
|
||||
if (k != i) {
|
||||
rational factor = m_matrix[k][j];
|
||||
for (unsigned l = 0; l < m_matrix[k].size(); ++l) {
|
||||
m_matrix[k][l] =
|
||||
m_matrix[k][l] - (factor * m_matrix[i][l]);
|
||||
}
|
||||
}
|
||||
|
||||
// subtract row from all other rows
|
||||
for (unsigned k=1; k < m_matrix.size(); ++k)
|
||||
{
|
||||
if (k != i)
|
||||
{
|
||||
rational factor = m_matrix[k][j];
|
||||
for (unsigned l=0; l < m_matrix[k].size(); ++l)
|
||||
{
|
||||
m_matrix[k][l] = m_matrix[k][l] - (factor * m_matrix[i][l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_verbosity_level() >= 1)
|
||||
{
|
||||
SASSERT(m_matrix.size() > 0);
|
||||
++i;
|
||||
++j;
|
||||
}
|
||||
|
||||
return i; //i points to the row after the last row which is non-zero
|
||||
}
|
||||
|
||||
void spacer_matrix::print_matrix()
|
||||
{
|
||||
verbose_stream() << "\nMatrix\n";
|
||||
for (const auto& row : m_matrix)
|
||||
{
|
||||
for (const auto& element : row)
|
||||
{
|
||||
verbose_stream() << element << ", ";
|
||||
}
|
||||
verbose_stream() << "\n";
|
||||
}
|
||||
verbose_stream() << "\n";
|
||||
if (get_verbosity_level() >= 1) { SASSERT(m_matrix.size() > 0); }
|
||||
|
||||
return i; // i points to the row after the last row which is non-zero
|
||||
}
|
||||
|
||||
std::ostream &spacer_matrix::display(std::ostream &out) const {
|
||||
out << "Matrix\n";
|
||||
for (const auto &row : m_matrix) {
|
||||
for (const auto &element : row) { out << element << ", "; }
|
||||
out << "\n";
|
||||
}
|
||||
void spacer_matrix::normalize()
|
||||
{
|
||||
rational den = rational::one();
|
||||
for (unsigned i=0; i < m_num_rows; ++i)
|
||||
{
|
||||
for (unsigned j=0; j < m_num_cols; ++j)
|
||||
{
|
||||
den = lcm(den, denominator(m_matrix[i][j]));
|
||||
}
|
||||
out << "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
void spacer_matrix::normalize() {
|
||||
rational den = rational::one();
|
||||
for (unsigned i = 0; i < m_num_rows; ++i) {
|
||||
for (unsigned j = 0; j < m_num_cols; ++j) {
|
||||
den = lcm(den, denominator(m_matrix[i][j]));
|
||||
}
|
||||
for (unsigned i=0; i < m_num_rows; ++i)
|
||||
{
|
||||
for (unsigned j=0; j < m_num_cols; ++j)
|
||||
{
|
||||
m_matrix[i][j] = den * m_matrix[i][j];
|
||||
SASSERT(m_matrix[i][j].is_int());
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < m_num_rows; ++i) {
|
||||
for (unsigned j = 0; j < m_num_cols; ++j) {
|
||||
m_matrix[i][j] = den * m_matrix[i][j];
|
||||
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
|
||||
|
|
|
@ -22,24 +22,44 @@ Revision History:
|
|||
|
||||
namespace spacer {
|
||||
|
||||
class spacer_matrix {
|
||||
public:
|
||||
spacer_matrix(unsigned m, unsigned n); // m rows, n columns
|
||||
class spacer_matrix {
|
||||
private:
|
||||
unsigned m_num_rows;
|
||||
unsigned m_num_cols;
|
||||
vector<vector<rational>> m_matrix;
|
||||
|
||||
unsigned num_rows();
|
||||
unsigned num_cols();
|
||||
bool is_lin_reltd(unsigned i, unsigned j, rational &coeff1,
|
||||
rational &coeff2, rational &off) const;
|
||||
|
||||
const rational& get(unsigned i, unsigned j);
|
||||
void set(unsigned i, unsigned j, const rational& v);
|
||||
public:
|
||||
spacer_matrix(unsigned m, unsigned n); // m rows, n columns
|
||||
|
||||
unsigned perform_gaussian_elimination();
|
||||
unsigned num_rows() const { return m_num_rows; }
|
||||
unsigned num_cols() const { return m_num_cols; }
|
||||
|
||||
void print_matrix();
|
||||
void normalize();
|
||||
private:
|
||||
unsigned m_num_rows;
|
||||
unsigned m_num_cols;
|
||||
vector<vector<rational>> m_matrix;
|
||||
};
|
||||
}
|
||||
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
|
||||
|
|
|
@ -284,6 +284,11 @@ namespace spacer {
|
|||
ptr_buffer<proof> const &parents,
|
||||
unsigned num_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 */);
|
||||
arith_util a(m);
|
||||
th_rewriter rw(m);
|
||||
|
|
|
@ -84,7 +84,7 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos)
|
|||
top = false;
|
||||
|
||||
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;
|
||||
|
||||
// x<=y == !(x>y)
|
||||
|
@ -120,6 +120,26 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos)
|
|||
else {
|
||||
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();
|
||||
|
|
|
@ -38,11 +38,11 @@ class sem_matcher {
|
|||
substitution * m_subst;
|
||||
svector<expr_pair> m_todo;
|
||||
|
||||
void reset();
|
||||
|
||||
bool match_var(var *v, expr *e);
|
||||
public:
|
||||
sem_matcher(ast_manager &man);
|
||||
void reset();
|
||||
|
||||
/**
|
||||
\brief Return true if e2 is an instance of e1.
|
||||
|
|
|
@ -396,8 +396,8 @@ namespace spacer {
|
|||
matrix.set(i, map[pair.second], pair.first);
|
||||
}
|
||||
}
|
||||
matrix.print_matrix();
|
||||
|
||||
IF_VERBOSE(10, matrix.display(verbose_stream()););
|
||||
// 3. normalize matrix to integer values
|
||||
matrix.normalize();
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,126 +21,163 @@ Revision History:
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
#include "ast/ast.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/ref_vector.h"
|
||||
#include "util/trace.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 "util/stopwatch.h"
|
||||
|
||||
class model;
|
||||
class model_core;
|
||||
|
||||
namespace spacer {
|
||||
|
||||
inline unsigned infty_level () {
|
||||
return UINT_MAX;
|
||||
}
|
||||
inline unsigned infty_level() { return UINT_MAX; }
|
||||
|
||||
inline bool is_infty_level(unsigned lvl) {
|
||||
// XXX: level is 16 bits in class pob
|
||||
return lvl >= 65535;
|
||||
}
|
||||
|
||||
inline unsigned next_level(unsigned lvl) {
|
||||
return is_infty_level(lvl)?lvl:(lvl+1);
|
||||
}
|
||||
|
||||
inline unsigned prev_level (unsigned lvl) {
|
||||
if (is_infty_level(lvl)) return infty_level();
|
||||
if (lvl == 0) return 0;
|
||||
return lvl - 1;
|
||||
}
|
||||
|
||||
struct pp_level {
|
||||
unsigned m_level;
|
||||
pp_level(unsigned l): m_level(l) {}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, pp_level const& p) {
|
||||
if (is_infty_level(p.m_level)) {
|
||||
return out << "oo";
|
||||
} else {
|
||||
return out << p.m_level;
|
||||
}
|
||||
}
|
||||
|
||||
typedef ptr_vector<app> app_vector;
|
||||
typedef ptr_vector<func_decl> decl_vector;
|
||||
typedef obj_hashtable<func_decl> func_decl_set;
|
||||
|
||||
/**
|
||||
\brief hoist non-boolean if expressions.
|
||||
*/
|
||||
|
||||
void to_mbp_benchmark(std::ostream &out, const expr* fml, const app_ref_vector &vars);
|
||||
|
||||
|
||||
// TBD: deprecate by qe::mbp
|
||||
/**
|
||||
* do the following in sequence
|
||||
* 1. use qe_lite to cheaply eliminate vars
|
||||
* 2. for remaining boolean vars, substitute using M
|
||||
* 3. use MBP for remaining array and arith variables
|
||||
* 4. for any remaining arith variables, substitute using M
|
||||
*/
|
||||
void qe_project (ast_manager& m, app_ref_vector& vars,
|
||||
expr_ref& fml, model &mdl,
|
||||
bool reduce_all_selects=false,
|
||||
bool native_mbp=false,
|
||||
bool dont_sub=false);
|
||||
|
||||
// deprecate
|
||||
void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml,
|
||||
model_ref& M, expr_map& map);
|
||||
|
||||
// TBD: sort out
|
||||
void expand_literals(ast_manager &m, expr_ref_vector& conjs);
|
||||
expr_ref_vector compute_implicant_literals(model &mdl,
|
||||
expr_ref_vector &formula);
|
||||
void simplify_bounds (expr_ref_vector &lemmas);
|
||||
void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false);
|
||||
|
||||
/**
|
||||
* Ground expression by replacing all free variables by skolem
|
||||
* constants. On return, out is the resulting expression, and vars is
|
||||
* a map from variable ids to corresponding skolem constants.
|
||||
*/
|
||||
void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars);
|
||||
|
||||
void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml);
|
||||
|
||||
bool contains_selects (expr* fml, ast_manager& m);
|
||||
void get_select_indices (expr* fml, app_ref_vector& indices);
|
||||
|
||||
void find_decls (expr* fml, app_ref_vector& decls, std::string& prefix);
|
||||
|
||||
/**
|
||||
* extended pretty-printer
|
||||
* used for debugging
|
||||
* disables aliasing of common sub-expressions
|
||||
*/
|
||||
struct mk_epp : public mk_pp {
|
||||
params_ref m_epp_params;
|
||||
expr_ref m_epp_expr;
|
||||
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);
|
||||
};
|
||||
|
||||
bool is_clause(ast_manager &m, expr *n);
|
||||
bool is_literal(ast_manager &m, expr *n);
|
||||
bool is_atom(ast_manager &m, expr *n);
|
||||
|
||||
// set f to true in model
|
||||
void set_true_in_mdl(model &model, func_decl *f);
|
||||
inline bool is_infty_level(unsigned lvl) {
|
||||
// XXX: level is 16 bits in class pob
|
||||
return lvl >= 65535;
|
||||
}
|
||||
|
||||
inline unsigned next_level(unsigned lvl) {
|
||||
return is_infty_level(lvl) ? lvl : (lvl + 1);
|
||||
}
|
||||
|
||||
inline unsigned prev_level(unsigned lvl) {
|
||||
if (is_infty_level(lvl)) return infty_level();
|
||||
if (lvl == 0) return 0;
|
||||
return lvl - 1;
|
||||
}
|
||||
|
||||
struct pp_level {
|
||||
unsigned m_level;
|
||||
pp_level(unsigned l) : m_level(l) {}
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, pp_level const &p) {
|
||||
if (is_infty_level(p.m_level)) {
|
||||
return out << "oo";
|
||||
} else {
|
||||
return out << p.m_level;
|
||||
}
|
||||
}
|
||||
|
||||
typedef ptr_vector<app> app_vector;
|
||||
typedef ptr_vector<func_decl> decl_vector;
|
||||
typedef obj_hashtable<func_decl> func_decl_set;
|
||||
|
||||
/**
|
||||
\brief hoist non-boolean if expressions.
|
||||
*/
|
||||
|
||||
void to_mbp_benchmark(std::ostream &out, const expr *fml,
|
||||
const app_ref_vector &vars);
|
||||
|
||||
// TBD: deprecate by qe::mbp
|
||||
/**
|
||||
* do the following in sequence
|
||||
* 1. use qe_lite to cheaply eliminate vars
|
||||
* 2. for remaining boolean vars, substitute using M
|
||||
* 3. use MBP for remaining array and arith variables
|
||||
* 4. for any remaining arith variables, substitute using M
|
||||
*/
|
||||
void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, model &mdl,
|
||||
bool reduce_all_selects = false, bool native_mbp = false,
|
||||
bool dont_sub = false);
|
||||
|
||||
// deprecate
|
||||
void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml,
|
||||
model_ref &M, expr_map &map);
|
||||
|
||||
// TBD: sort out
|
||||
void expand_literals(ast_manager &m, expr_ref_vector &conjs);
|
||||
expr_ref_vector compute_implicant_literals(model &mdl,
|
||||
expr_ref_vector &formula);
|
||||
void simplify_bounds(expr_ref_vector &lemmas);
|
||||
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
|
||||
* constants. On return, out is the resulting expression, and vars is
|
||||
* a map from variable ids to corresponding skolem constants.
|
||||
*/
|
||||
void ground_expr(expr *e, expr_ref &out, app_ref_vector &vars);
|
||||
|
||||
void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml);
|
||||
|
||||
bool contains_selects(expr *fml, ast_manager &m);
|
||||
void get_select_indices(expr *fml, app_ref_vector &indices);
|
||||
|
||||
void find_decls(expr *fml, app_ref_vector &decls, std::string &prefix);
|
||||
|
||||
/**
|
||||
* extended pretty-printer
|
||||
* used for debugging
|
||||
* disables aliasing of common sub-expressions
|
||||
*/
|
||||
struct mk_epp : public mk_pp {
|
||||
params_ref m_epp_params;
|
||||
expr_ref m_epp_expr;
|
||||
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);
|
||||
};
|
||||
|
||||
bool is_clause(ast_manager &m, expr *n);
|
||||
bool is_literal(ast_manager &m, expr *n);
|
||||
bool is_atom(ast_manager &m, expr *n);
|
||||
|
||||
// set f to true in model
|
||||
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
|
||||
|
|
|
@ -286,22 +286,23 @@ namespace nlsat {
|
|||
}
|
||||
|
||||
bool check_invariant() const {
|
||||
SASSERT(m_sections.size() == m_sorted_sections.size());
|
||||
for (unsigned i = 0; i < m_sorted_sections.size(); i++) {
|
||||
SASSERT(m_sorted_sections[i] < m_sections.size());
|
||||
SASSERT(m_sections[m_sorted_sections[i]].m_pos == i);
|
||||
}
|
||||
unsigned total_num_sections = 0;
|
||||
unsigned total_num_signs = 0;
|
||||
for (unsigned i = 0; i < m_info.size(); i++) {
|
||||
SASSERT(m_info[i].m_first_section <= m_poly_sections.size());
|
||||
SASSERT(m_info[i].m_num_roots == 0 || m_info[i].m_first_section < m_poly_sections.size());
|
||||
SASSERT(m_info[i].m_first_sign < m_poly_signs.size());
|
||||
total_num_sections += m_info[i].m_num_roots;
|
||||
total_num_signs += m_info[i].m_num_roots + 1;
|
||||
}
|
||||
SASSERT(total_num_sections == m_poly_sections.size());
|
||||
SASSERT(total_num_signs == m_poly_signs.size());
|
||||
DEBUG_CODE(
|
||||
SASSERT(m_sections.size() == m_sorted_sections.size());
|
||||
for (unsigned i = 0; i < m_sorted_sections.size(); i++) {
|
||||
SASSERT(m_sorted_sections[i] < m_sections.size());
|
||||
SASSERT(m_sections[m_sorted_sections[i]].m_pos == i);
|
||||
}
|
||||
unsigned total_num_sections = 0;
|
||||
unsigned total_num_signs = 0;
|
||||
for (unsigned i = 0; i < m_info.size(); i++) {
|
||||
SASSERT(m_info[i].m_first_section <= m_poly_sections.size());
|
||||
SASSERT(m_info[i].m_num_roots == 0 || m_info[i].m_first_section < m_poly_sections.size());
|
||||
SASSERT(m_info[i].m_first_sign < m_poly_signs.size());
|
||||
total_num_sections += m_info[i].m_num_roots;
|
||||
total_num_signs += m_info[i].m_num_roots + 1;
|
||||
}
|
||||
SASSERT(total_num_sections == m_poly_sections.size());
|
||||
SASSERT(total_num_signs == m_poly_signs.size()););
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ def_module_params('solver',
|
|||
('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"),
|
||||
('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'),
|
||||
))
|
||||
|
||||
|
|
|
@ -376,7 +376,7 @@ namespace mbp {
|
|||
rhs = m_bv.mk_concat(2, args);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else {
|
||||
|
@ -384,6 +384,15 @@ namespace mbp {
|
|||
}
|
||||
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;
|
||||
}
|
||||
public:
|
||||
|
|
|
@ -269,8 +269,6 @@ public:
|
|||
}
|
||||
|
||||
bool validate_model(model& model, expr_ref_vector const& fmls) {
|
||||
expr_ref val(m);
|
||||
model_evaluator eval(model);
|
||||
for (expr* f : fmls) {
|
||||
VERIFY(!model.is_false(f));
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ z3_add_component(sat
|
|||
util
|
||||
dd
|
||||
grobner
|
||||
params
|
||||
PYG_FILES
|
||||
sat_asymm_branch_params.pyg
|
||||
sat_params.pyg
|
||||
|
|
|
@ -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>
|
||||
|
@ -177,25 +156,7 @@ namespace dimacs {
|
|||
std::ostream& operator<<(std::ostream& out, drat_pp const& p) {
|
||||
auto const& r = p.r;
|
||||
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";
|
||||
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;
|
||||
return out << pp << " " << r.m_lits << " 0\n";
|
||||
}
|
||||
|
||||
char const* drat_parser::parse_identifier() {
|
||||
|
@ -266,47 +227,10 @@ namespace dimacs {
|
|||
}
|
||||
|
||||
bool drat_parser::next() {
|
||||
int n, b, e, 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();
|
||||
};
|
||||
int theory_id;
|
||||
try {
|
||||
loop:
|
||||
skip_whitespace(in);
|
||||
m_record.m_pragma.clear();
|
||||
m_record.m_hint.reset();
|
||||
switch (*in) {
|
||||
case EOF:
|
||||
return false;
|
||||
|
@ -321,7 +245,6 @@ namespace dimacs {
|
|||
++in;
|
||||
skip_whitespace(in);
|
||||
read_clause(in, err, m_record.m_lits);
|
||||
m_record.m_tag = drat_record::tag_t::is_clause;
|
||||
m_record.m_status = sat::status::input();
|
||||
break;
|
||||
case 'a':
|
||||
|
@ -331,49 +254,13 @@ namespace dimacs {
|
|||
theory_id = read_theory_id();
|
||||
skip_whitespace(in);
|
||||
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);
|
||||
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':
|
||||
// parse clause deletion
|
||||
++in;
|
||||
skip_whitespace(in);
|
||||
read_clause(in, err, m_record.m_lits);
|
||||
m_record.m_tag = drat_record::tag_t::is_clause;
|
||||
m_record.m_status = sat::status::deleted();
|
||||
break;
|
||||
case 'r':
|
||||
|
@ -383,13 +270,11 @@ namespace dimacs {
|
|||
skip_whitespace(in);
|
||||
theory_id = read_theory_id();
|
||||
read_clause(in, err, m_record.m_lits);
|
||||
m_record.m_tag = drat_record::tag_t::is_clause;
|
||||
m_record.m_status = sat::status::th(true, theory_id);
|
||||
break;
|
||||
default:
|
||||
// parse clause redundant modulo DRAT (or mostly just DRUP)
|
||||
read_clause(in, err, m_record.m_lits);
|
||||
m_record.m_tag = drat_record::tag_t::is_clause;
|
||||
m_record.m_status = sat::status::redundant();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -53,18 +53,11 @@ namespace dimacs {
|
|||
};
|
||||
|
||||
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 node populates m_node_id, m_name, m_args
|
||||
// a bool def populates m_node_id and one element in m_args
|
||||
sat::literal_vector m_lits;
|
||||
sat::status m_status = 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 {
|
||||
|
|
|
@ -20,6 +20,7 @@ Revision History:
|
|||
#include "sat/sat_types.h"
|
||||
#include "sat/sat_params.hpp"
|
||||
#include "sat/sat_simplifier_params.hpp"
|
||||
#include "params/solver_params.hpp"
|
||||
|
||||
|
||||
namespace sat {
|
||||
|
@ -31,6 +32,8 @@ namespace sat {
|
|||
|
||||
void config::updt_params(params_ref const & _p) {
|
||||
sat_params p(_p);
|
||||
solver_params sp(_p);
|
||||
|
||||
m_max_memory = megabytes_to_bytes(p.max_memory());
|
||||
|
||||
symbol s = p.restart();
|
||||
|
@ -194,10 +197,10 @@ namespace sat {
|
|||
m_drat_check_unsat = p.drat_check_unsat();
|
||||
m_drat_check_sat = p.drat_check_sat();
|
||||
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_activity = p.drat_activity();
|
||||
m_drup_trim = p.drup_trim();
|
||||
m_dyn_sub_res = p.dyn_sub_res();
|
||||
|
||||
// Parameters used in Liang, Ganesh, Poupart, Czarnecki AAAI 2016.
|
||||
|
@ -248,13 +251,9 @@ namespace sat {
|
|||
m_card_solver = p.cardinality_solver();
|
||||
m_xor_solver = false; // prevent users from playing with this option
|
||||
|
||||
sat_simplifier_params sp(_p);
|
||||
m_elim_vars = sp.elim_vars();
|
||||
sat_simplifier_params ssp(_p);
|
||||
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) {
|
||||
|
|
|
@ -177,9 +177,9 @@ namespace sat {
|
|||
bool m_drat;
|
||||
bool m_drat_binary;
|
||||
symbol m_drat_file;
|
||||
symbol m_smt_proof;
|
||||
bool m_drat_check_unsat;
|
||||
bool m_drat_check_sat;
|
||||
bool m_drup_trim;
|
||||
bool m_drat_activity;
|
||||
|
||||
bool m_card_solver;
|
||||
|
|
|
@ -52,8 +52,7 @@ namespace sat {
|
|||
void drat::updt_config() {
|
||||
m_check_unsat = s.get_config().m_drat_check_unsat;
|
||||
m_check_sat = s.get_config().m_drat_check_sat;
|
||||
m_trim = s.get_config().m_drup_trim;
|
||||
m_check = m_check_unsat || m_check_sat || m_trim;
|
||||
m_check = m_check_unsat || m_check_sat;
|
||||
m_activity = s.get_config().m_drat_activity;
|
||||
}
|
||||
|
||||
|
@ -130,14 +129,6 @@ namespace sat {
|
|||
}
|
||||
}
|
||||
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';
|
||||
m_out->write(buffer, len);
|
||||
}
|
||||
|
@ -210,8 +201,6 @@ namespace sat {
|
|||
if (st.is_redundant() && st.is_sat())
|
||||
verify(1, &l);
|
||||
|
||||
if (m_trim)
|
||||
m_proof.push_back({mk_clause(1, &l, st.is_redundant()), st});
|
||||
|
||||
if (st.is_deleted())
|
||||
return;
|
||||
|
@ -230,8 +219,7 @@ namespace sat {
|
|||
|
||||
IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st););
|
||||
if (st.is_deleted()) {
|
||||
if (m_trim)
|
||||
m_proof.push_back({mk_clause(2, lits, true), st});
|
||||
;
|
||||
}
|
||||
else {
|
||||
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) {
|
||||
TRACE("sat_drat", pp(tout, st) << " " << c << "\n";);
|
||||
for (literal lit : c) declare(lit);
|
||||
|
@ -453,6 +416,8 @@ namespace sat {
|
|||
void drat::verify(unsigned n, literal const* c) {
|
||||
if (!m_check_unsat)
|
||||
return;
|
||||
if (m_inconsistent)
|
||||
return;
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
declare(c[i]);
|
||||
if (is_drup(n, c)) {
|
||||
|
@ -683,6 +648,7 @@ namespace sat {
|
|||
verify(0, nullptr);
|
||||
SASSERT(m_inconsistent);
|
||||
}
|
||||
if (m_clause_eh) m_clause_eh->on_clause(0, nullptr, status::redundant());
|
||||
}
|
||||
void drat::add(literal l, bool learned) {
|
||||
++m_stats.m_num_add;
|
||||
|
@ -690,6 +656,8 @@ namespace sat {
|
|||
if (m_out) dump(1, &l, st);
|
||||
if (m_bout) bdump(1, &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) {
|
||||
if (st.is_deleted())
|
||||
|
@ -700,6 +668,7 @@ namespace sat {
|
|||
if (m_out) dump(2, ls, st);
|
||||
if (m_bout) bdump(2, ls, 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) {
|
||||
if (st.is_deleted())
|
||||
|
@ -709,6 +678,7 @@ namespace sat {
|
|||
if (m_out) dump(c.size(), c.begin(), st);
|
||||
if (m_bout) bdump(c.size(), c.begin(), 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) {
|
||||
|
@ -722,13 +692,16 @@ namespace sat {
|
|||
++m_stats.m_num_add;
|
||||
if (m_check) {
|
||||
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;
|
||||
default: append(mk_clause(sz, lits, st.is_redundant()), st); break;
|
||||
}
|
||||
}
|
||||
if (m_out)
|
||||
dump(sz, lits, st);
|
||||
|
||||
if (m_clause_eh)
|
||||
m_clause_eh->on_clause(sz, lits, st);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -755,6 +730,7 @@ namespace sat {
|
|||
if (m_out) dump(1, &l, status::deleted());
|
||||
if (m_bout) bdump(1, &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) {
|
||||
|
@ -763,6 +739,7 @@ namespace sat {
|
|||
if (m_out) dump(2, ls, status::deleted());
|
||||
if (m_bout) bdump(2, ls, 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) {
|
||||
|
@ -780,7 +757,8 @@ namespace sat {
|
|||
++m_stats.m_num_del;
|
||||
if (m_out) dump(c.size(), c.begin(), status::deleted());
|
||||
if (m_bout) bdump(c.size(), c.begin(), status::deleted());
|
||||
if (m_check) 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) {
|
||||
|
@ -796,23 +774,9 @@ namespace sat {
|
|||
if (m_out) dump(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_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) {
|
||||
}
|
||||
|
||||
|
@ -844,196 +808,4 @@ namespace sat {
|
|||
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
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,11 @@ namespace sat {
|
|||
class justification;
|
||||
class clause;
|
||||
|
||||
struct clause_eh {
|
||||
virtual ~clause_eh() {}
|
||||
virtual void on_clause(unsigned, literal const*, status) = 0;
|
||||
};
|
||||
|
||||
class drat {
|
||||
struct stats {
|
||||
unsigned m_num_drup = 0;
|
||||
|
@ -73,6 +78,7 @@ namespace sat {
|
|||
watched_clause(clause* c, literal l1, literal l2):
|
||||
m_clause(c), m_l1(l1), m_l2(l2) {}
|
||||
};
|
||||
clause_eh* m_clause_eh = nullptr;
|
||||
svector<watched_clause> m_watched_clauses;
|
||||
typedef svector<unsigned> watch;
|
||||
solver& s;
|
||||
|
@ -89,9 +95,9 @@ namespace sat {
|
|||
bool m_check_sat = false;
|
||||
bool m_check = false;
|
||||
bool m_activity = false;
|
||||
bool m_trim = false;
|
||||
stats m_stats;
|
||||
|
||||
|
||||
void dump_activity();
|
||||
void dump(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(unsigned sz, literal const* lits, status st);
|
||||
|
||||
// support for SMT - connect Boolean variables with AST nodes
|
||||
// associate AST node id with Boolean variable v
|
||||
void bool_def(bool_var v, unsigned n);
|
||||
void set_clause_eh(clause_eh& clause_eh) { m_clause_eh = &clause_eh; }
|
||||
|
||||
// declare AST node n with 'name' and arguments arg
|
||||
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);
|
||||
std::ostream* out() { return m_out; }
|
||||
|
||||
bool is_cleaned(clause& c) const;
|
||||
void del(literal l);
|
||||
|
@ -175,8 +173,6 @@ namespace sat {
|
|||
svector<std::pair<literal, clause*>> const& units() { return m_units; }
|
||||
bool is_drup(unsigned n, literal const* c, literal_vector& units);
|
||||
solver& get_solver() { return s; }
|
||||
|
||||
svector<std::pair<clause&, status>> trim();
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -46,11 +46,12 @@ def_module_params('sat',
|
|||
('backtrack.conflicts', UINT, 4000, 'number of conflicts before enabling chronological backtracking'),
|
||||
('threads', UINT, 1, 'number of parallel threads to use'),
|
||||
('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.binary', BOOL, False, 'use Binary DRAT output format'),
|
||||
('drat.check_unsat', BOOL, False, 'build up internal proof and check'),
|
||||
('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'),
|
||||
('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)'),
|
||||
|
|
|
@ -402,6 +402,7 @@ namespace sat {
|
|||
extension::scoped_drating _sd(*m_ext.get());
|
||||
if (j.get_kind() == justification::EXT_JUSTIFICATION)
|
||||
fill_ext_antecedents(lit, j, false);
|
||||
TRACE("sat", tout << "drat-unit\n");
|
||||
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) {
|
||||
bool redundant = st.is_redundant();
|
||||
TRACE("sat", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";);
|
||||
bool logged = false;
|
||||
if (!redundant || !st.is_sat()) {
|
||||
unsigned old_sz = num_lits;
|
||||
bool keep = simplify_clause(num_lits, lits);
|
||||
|
@ -420,8 +422,10 @@ namespace sat {
|
|||
return nullptr; // clause is equivalent to true.
|
||||
}
|
||||
// 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);
|
||||
logged = true;
|
||||
}
|
||||
|
||||
++m_stats.m_non_learned_generation;
|
||||
if (!m_searching) {
|
||||
|
@ -435,7 +439,7 @@ namespace sat {
|
|||
set_conflict();
|
||||
return nullptr;
|
||||
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);
|
||||
assign_unit(lits[0]);
|
||||
return nullptr;
|
||||
|
@ -945,8 +949,7 @@ namespace sat {
|
|||
if (j.level() == 0) {
|
||||
if (m_config.m_drat)
|
||||
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 {
|
||||
VERIFY(!at_base_lvl());
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace sat {
|
|||
};
|
||||
|
||||
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 {
|
||||
|
|
|
@ -94,22 +94,9 @@ namespace sat {
|
|||
|
||||
};
|
||||
|
||||
enum class hint_type {
|
||||
null_h,
|
||||
farkas_h,
|
||||
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 proof_hint {
|
||||
public:
|
||||
virtual ~proof_hint() {}
|
||||
};
|
||||
|
||||
class status {
|
||||
|
|
|
@ -21,6 +21,7 @@ z3_add_component(sat_smt
|
|||
euf_invariant.cpp
|
||||
euf_model.cpp
|
||||
euf_proof.cpp
|
||||
euf_proof_checker.cpp
|
||||
euf_relevancy.cpp
|
||||
euf_solver.cpp
|
||||
fpa_solver.cpp
|
||||
|
|
|
@ -264,11 +264,12 @@ namespace arith {
|
|||
SASSERT(k1 != k2 || kind1 != kind2);
|
||||
|
||||
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()) {
|
||||
bound_params = &m_farkas2;
|
||||
m_farkas2.m_literals[0] = std::make_pair(rational(1), ~l1);
|
||||
m_farkas2.m_literals[1] = std::make_pair(rational(1), ~l2);
|
||||
m_arith_hint.set_type(ctx, hint_type::farkas_h);
|
||||
m_arith_hint.add_lit(rational(1), ~l1);
|
||||
m_arith_hint.add_lit(rational(1), ~l2);
|
||||
bound_params = m_arith_hint.mk(ctx);
|
||||
}
|
||||
add_clause(l1, l2, bound_params);
|
||||
};
|
||||
|
|
|
@ -15,6 +15,8 @@ Author:
|
|||
|
||||
--*/
|
||||
|
||||
#include "ast/ast_util.h"
|
||||
#include "ast/scoped_proof.h"
|
||||
#include "sat/smt/euf_solver.h"
|
||||
#include "sat/smt/arith_solver.h"
|
||||
|
||||
|
@ -81,7 +83,6 @@ namespace arith {
|
|||
}
|
||||
|
||||
void solver::explain_assumptions() {
|
||||
m_arith_hint.reset();
|
||||
unsigned i = 0;
|
||||
for (auto const & ev : m_explanation) {
|
||||
++i;
|
||||
|
@ -91,14 +92,12 @@ namespace arith {
|
|||
switch (m_constraint_sources[idx]) {
|
||||
case inequality_source: {
|
||||
literal lit = m_inequalities[idx];
|
||||
m_arith_hint.m_literals.push_back({ev.coeff(), lit});
|
||||
m_arith_hint.add_lit(ev.coeff(), lit);
|
||||
break;
|
||||
}
|
||||
case equality_source: {
|
||||
auto [u, v] = m_equalities[idx];
|
||||
ctx.drat_log_expr(u->get_expr());
|
||||
ctx.drat_log_expr(v->get_expr());
|
||||
m_arith_hint.m_eqs.push_back({u->get_expr_id(), v->get_expr_id()});
|
||||
m_arith_hint.add_eq(u, v);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -115,22 +114,65 @@ namespace arith {
|
|||
* such that there is a r >= 1
|
||||
* (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())
|
||||
return nullptr;
|
||||
m_arith_hint.m_ty = ty;
|
||||
m_arith_hint.set_type(ctx, ty);
|
||||
explain_assumptions();
|
||||
if (lit != sat::null_literal)
|
||||
m_arith_hint.m_literals.push_back({rational(1), ~lit});
|
||||
return &m_arith_hint;
|
||||
m_arith_hint.add_lit(rational(1), ~lit);
|
||||
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())
|
||||
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();
|
||||
m_arith_hint.m_diseqs.push_back({a->get_expr_id(), b->get_expr_id()});
|
||||
return &m_arith_hint;
|
||||
m_arith_hint.add_diseq(a, b);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
Copyright (c) 2022 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
|
@ -11,7 +11,15 @@ Abstract:
|
|||
|
||||
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
|
||||
|
@ -19,11 +27,12 @@ Author:
|
|||
#include "util/obj_pair_set.h"
|
||||
#include "ast/ast_trail.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "sat/smt/euf_proof_checker.h"
|
||||
|
||||
|
||||
namespace arith {
|
||||
|
||||
class proof_checker {
|
||||
class proof_checker : public euf::proof_checker_plugin {
|
||||
struct row {
|
||||
obj_map<expr, rational> m_coeffs;
|
||||
rational m_coeff;
|
||||
|
@ -131,11 +140,13 @@ namespace arith {
|
|||
SASSERT(m_todo.empty());
|
||||
m_todo.push_back({ mul, e });
|
||||
rational coeff1;
|
||||
expr* e1, *e2;
|
||||
expr* e1, *e2, *e3;
|
||||
for (unsigned i = 0; i < m_todo.size(); ++i) {
|
||||
auto [coeff, e] = m_todo[i];
|
||||
if (a.is_mul(e, e1, e2) && a.is_numeral(e1, coeff1))
|
||||
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))
|
||||
m_todo.push_back({coeff*coeff1, e1});
|
||||
else if (a.is_add(e))
|
||||
|
@ -149,6 +160,8 @@ namespace arith {
|
|||
}
|
||||
else if (a.is_numeral(e, coeff1))
|
||||
r.m_coeff += coeff*coeff1;
|
||||
else if (a.is_uminus(e, e1) && a.is_numeral(e1, coeff1))
|
||||
r.m_coeff -= coeff*coeff1;
|
||||
else
|
||||
add(r, e, coeff);
|
||||
}
|
||||
|
@ -300,6 +313,8 @@ namespace arith {
|
|||
|
||||
public:
|
||||
proof_checker(ast_manager& m): m(m), a(m) {}
|
||||
|
||||
~proof_checker() override {}
|
||||
|
||||
void reset() {
|
||||
m_ineq.reset();
|
||||
|
@ -350,6 +365,70 @@ namespace arith {
|
|||
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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -39,8 +39,6 @@ namespace arith {
|
|||
lp().settings().set_random_seed(get_config().m_random_seed);
|
||||
|
||||
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() {
|
||||
|
@ -197,11 +195,12 @@ namespace arith {
|
|||
reset_evidence();
|
||||
m_core.push_back(lit1);
|
||||
TRACE("arith", tout << lit2 << " <- " << m_core << "\n";);
|
||||
sat::proof_hint* ph = nullptr;
|
||||
arith_proof_hint* ph = nullptr;
|
||||
if (ctx.use_drat()) {
|
||||
ph = &m_farkas2;
|
||||
m_farkas2.m_literals[0] = std::make_pair(rational(1), lit1);
|
||||
m_farkas2.m_literals[1] = std::make_pair(rational(1), ~lit2);
|
||||
m_arith_hint.set_type(ctx, hint_type::farkas_h);
|
||||
m_arith_hint.add_lit(rational(1), lit1);
|
||||
m_arith_hint.add_lit(rational(1), ~lit2);
|
||||
ph = m_arith_hint.mk(ctx);
|
||||
}
|
||||
assign(lit2, m_core, m_eqs, ph);
|
||||
++m_stats.m_bounds_propagations;
|
||||
|
@ -262,7 +261,7 @@ namespace arith {
|
|||
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); });
|
||||
++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)
|
||||
|
@ -378,7 +377,7 @@ namespace arith {
|
|||
reset_evidence();
|
||||
m_explanation.clear();
|
||||
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());
|
||||
IF_VERBOSE(4, verbose_stream() << "cut " << b << "\n");
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
@ -1200,7 +1199,7 @@ namespace arith {
|
|||
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()) {
|
||||
m_core2.reset();
|
||||
for (auto const& c : core)
|
||||
|
@ -1247,7 +1246,7 @@ namespace arith {
|
|||
for (literal& c : m_core)
|
||||
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 {
|
||||
|
|
|
@ -48,8 +48,61 @@ namespace arith {
|
|||
typedef sat::literal_vector literal_vector;
|
||||
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 {
|
||||
|
||||
friend struct arith_proof_hint;
|
||||
|
||||
struct scope {
|
||||
unsigned m_bounds_lim;
|
||||
unsigned m_idiv_lim;
|
||||
|
@ -414,15 +467,15 @@ namespace arith {
|
|||
void set_conflict();
|
||||
void set_conflict_or_lemma(literal_vector const& core, bool is_conflict);
|
||||
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 dbg_finalize_model(model& mdl);
|
||||
|
||||
sat::proof_hint m_arith_hint;
|
||||
sat::proof_hint m_farkas2;
|
||||
sat::proof_hint const* explain(sat::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_builder m_arith_hint;
|
||||
|
||||
arith_proof_hint const* explain(hint_type ty, sat::literal lit = sat::null_literal);
|
||||
arith_proof_hint const* explain_implied_eq(euf::enode* a, euf::enode* b);
|
||||
void explain_assumptions();
|
||||
|
||||
|
||||
|
|
|
@ -49,16 +49,19 @@ namespace bv {
|
|||
update_glue(*other);
|
||||
|
||||
vv::push_to_front(m_queue, other);
|
||||
if (other == n) {
|
||||
bool do_gc = other == n;
|
||||
if (other == n)
|
||||
new_tmp();
|
||||
gc();
|
||||
}
|
||||
|
||||
if (other->m_glue == 0) {
|
||||
do_gc = false;
|
||||
remove(other);
|
||||
add_cc(v1, v2);
|
||||
}
|
||||
else if (other->m_count > 2*m_propagate_high_watermark)
|
||||
propagate();
|
||||
if (do_gc)
|
||||
gc();
|
||||
}
|
||||
|
||||
void ackerman::used_diseq_eh(euf::theory_var v1, euf::theory_var v2) {
|
||||
|
|
|
@ -429,6 +429,8 @@ namespace bv {
|
|||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -211,9 +211,8 @@ namespace bv {
|
|||
return;
|
||||
}
|
||||
euf::enode* n1 = var2enode(eq.v1());
|
||||
for (euf::enode* bv2int : euf::enode_class(n1)) {
|
||||
if (!bv.is_bv2int(bv2int->get_expr()))
|
||||
continue;
|
||||
|
||||
auto propagate_bv2int = [&](euf::enode* bv2int) {
|
||||
euf::enode* bv2int_arg = bv2int->get_arg(0);
|
||||
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()) {
|
||||
|
@ -224,6 +223,19 @@ namespace bv {
|
|||
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;
|
||||
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()) {
|
||||
force_push();
|
||||
assert_ackerman(v1, v2);
|
||||
|
@ -377,8 +391,8 @@ namespace bv {
|
|||
if (c.m_kind != bv_justification::kind_t::bit2ne) {
|
||||
expr* e1 = var2expr(c.m_v1);
|
||||
expr* e2 = var2expr(c.m_v2);
|
||||
eq = m.mk_eq(e1, e2);
|
||||
ctx.drat_eq_def(leq, eq);
|
||||
eq = m.mk_eq(e1, e2);
|
||||
ctx.set_tmp_bool_var(leq.var(), eq);
|
||||
}
|
||||
|
||||
sat::literal_vector lits;
|
||||
|
|
|
@ -207,8 +207,9 @@ namespace bv {
|
|||
literal_vector m_tmp_literals;
|
||||
svector<propagation_item> m_prop_queue;
|
||||
unsigned_vector m_prop_queue_lim;
|
||||
unsigned m_prop_queue_head { 0 };
|
||||
sat::literal m_true { sat::null_literal };
|
||||
unsigned m_prop_queue_head = 0;
|
||||
sat::literal m_true = sat::null_literal;
|
||||
euf::enode_vector m_bv2ints;
|
||||
|
||||
// internalize
|
||||
void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); }
|
||||
|
|
|
@ -16,107 +16,26 @@ Author:
|
|||
--*/
|
||||
|
||||
#include "sat/smt/euf_solver.h"
|
||||
#include "ast/ast_util.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace euf {
|
||||
|
||||
void solver::init_drat() {
|
||||
if (!m_drat_initialized) {
|
||||
void solver::init_proof() {
|
||||
if (!m_proof_initialized) {
|
||||
get_drat().add_theory(get_id(), symbol("euf"));
|
||||
get_drat().add_theory(m.get_basic_family_id(), symbol("bool"));
|
||||
}
|
||||
m_drat_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));
|
||||
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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
m_proof_initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
literal_vector lits;
|
||||
unsigned nv = s().num_vars();
|
||||
expr_ref_vector eqs(m);
|
||||
unsigned nv = s().num_vars();
|
||||
auto add_lit = [&](enode_pair const& eq) {
|
||||
++nv;
|
||||
literal lit(nv, false);
|
||||
eqs.push_back(m.mk_eq(eq.first->get_expr(), eq.second->get_expr()));
|
||||
drat_eq_def(lit, eqs.back());
|
||||
return lit;
|
||||
set_tmp_bool_var(nv, eqs.back());
|
||||
return literal(nv, false);
|
||||
};
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
void solver::drat_eq_def(literal lit, expr* eq) {
|
||||
expr *a = nullptr, *b = nullptr;
|
||||
VERIFY(m.is_eq(eq, a, b));
|
||||
drat_log_expr(a);
|
||||
drat_log_expr(b);
|
||||
get_drat().def_begin('e', eq->get_id(), std::string("="));
|
||||
get_drat().def_add_arg(a->get_id());
|
||||
get_drat().def_add_arg(b->get_id());
|
||||
get_drat().def_end();
|
||||
get_drat().bool_def(lit.var(), eq->get_id());
|
||||
void solver::on_clause(unsigned n, literal const* lits, sat::status st) {
|
||||
TRACE("euf", tout << "on-clause " << n << "\n");
|
||||
on_lemma(n, lits, st);
|
||||
on_proof(n, lits, st);
|
||||
}
|
||||
|
||||
void solver::on_proof(unsigned n, literal const* lits, sat::status st) {
|
||||
if (!m_proof_out)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
50
src/sat/smt/euf_proof_checker.cpp
Normal file
50
src/sat/smt/euf_proof_checker.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
46
src/sat/smt/euf_proof_checker.h
Normal file
46
src/sat/smt/euf_proof_checker.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -49,7 +49,8 @@ namespace euf {
|
|||
m_lookahead(nullptr),
|
||||
m_to_m(&m),
|
||||
m_to_si(&si),
|
||||
m_values(m)
|
||||
m_values(m),
|
||||
m_clause_visitor(m)
|
||||
{
|
||||
updt_params(p);
|
||||
m_relevancy.set_enabled(get_config().m_relevancy_lvl > 2);
|
||||
|
@ -347,8 +348,7 @@ namespace euf {
|
|||
if (m_relevancy.enabled())
|
||||
m_relevancy.propagate();
|
||||
if (m_egraph.inconsistent()) {
|
||||
unsigned lvl = s().scope_lvl();
|
||||
s().set_conflict(sat::justification::mk_ext_justification(lvl, conflict_constraint().to_index()));
|
||||
set_conflict(conflict_constraint().to_index());
|
||||
return true;
|
||||
}
|
||||
bool propagated1 = false;
|
||||
|
@ -519,7 +519,7 @@ namespace euf {
|
|||
bool merged = false;
|
||||
for (unsigned i = m_egraph.nodes().size(); i-- > 0; ) {
|
||||
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;
|
||||
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())));
|
||||
|
|
|
@ -60,11 +60,10 @@ namespace euf {
|
|||
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;
|
||||
friend class ackerman;
|
||||
class user_sort;
|
||||
// friend class sat::ba_solver;
|
||||
struct stats {
|
||||
unsigned m_ackerman;
|
||||
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(literal l, literal_vector const& r);
|
||||
void log_justification(literal l, th_explain const& jst);
|
||||
void drat_log_decl(func_decl* f);
|
||||
void drat_log_params(func_decl* f);
|
||||
void drat_log_expr1(expr* n);
|
||||
ptr_vector<expr> m_drat_todo;
|
||||
obj_hashtable<ast> m_drat_asts;
|
||||
bool m_drat_initialized{ false };
|
||||
void init_drat();
|
||||
|
||||
bool m_proof_initialized = false;
|
||||
void init_proof();
|
||||
ast_pp_util m_clause_visitor;
|
||||
bool m_display_all_decls = false;
|
||||
void on_clause(unsigned n, literal const* lits, sat::status st) override;
|
||||
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
|
||||
//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);
|
||||
// invariant
|
||||
void check_eqc_bool_assignment() const;
|
||||
|
@ -341,11 +342,16 @@ namespace euf {
|
|||
|
||||
|
||||
// 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(); }
|
||||
void drat_bool_def(sat::bool_var v, expr* n);
|
||||
void drat_eq_def(sat::literal lit, expr* eq);
|
||||
void drat_log_expr(expr* n);
|
||||
|
||||
void set_tmp_bool_var(sat::bool_var b, expr* e);
|
||||
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
|
||||
bool extract_pb(std::function<void(unsigned sz, literal const* c, unsigned k)>& card,
|
||||
|
|
|
@ -1430,10 +1430,9 @@ namespace pb {
|
|||
IF_VERBOSE(0, verbose_stream() << *c << "\n");
|
||||
VERIFY(c->well_formed());
|
||||
if (m_solver && m_solver->get_config().m_drat) {
|
||||
std::function<void(std::ostream& out)> fn = [&](std::ostream& out) {
|
||||
out << "c ba constraint " << *c << " 0\n";
|
||||
};
|
||||
m_solver->get_drat().log_adhoc(fn);
|
||||
auto * out = s().get_drat().out();
|
||||
if (out)
|
||||
*out << "c ba constraint " << *c << " 0\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -372,16 +372,18 @@ namespace q {
|
|||
}
|
||||
|
||||
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;
|
||||
ctx.set_conflict(j_idx);
|
||||
}
|
||||
else {
|
||||
else
|
||||
++m_stats.m_num_propagations;
|
||||
auto& j = justification::from_index(j_idx);
|
||||
auto lit = instantiate(j.m_clause, j.m_binding, j.m_clause[idx]);
|
||||
ctx.propagate(lit, j_idx);
|
||||
}
|
||||
|
||||
auto& j = justification::from_index(j_idx);
|
||||
sat::literal_vector lits;
|
||||
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() {
|
||||
|
@ -408,6 +410,7 @@ namespace q {
|
|||
void ematch::add_instantiation(clause& c, binding& b, sat::literal lit) {
|
||||
m_evidence.reset();
|
||||
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) {
|
||||
|
|
|
@ -47,21 +47,21 @@ namespace q {
|
|||
unsigned lim = m_indirect_nodes.size();
|
||||
lit l = c[i];
|
||||
lbool cmp = compare(n, binding, l.lhs, l.rhs, evidence);
|
||||
TRACE("q", tout << l.lhs << " ~~ " << l.rhs << " is " << cmp << "\n";);
|
||||
switch (cmp) {
|
||||
case l_false:
|
||||
case l_false:
|
||||
m_indirect_nodes.shrink(lim);
|
||||
if (!l.sign)
|
||||
break;
|
||||
c.m_watch = i;
|
||||
return l_true;
|
||||
case l_true:
|
||||
case l_true:
|
||||
m_indirect_nodes.shrink(lim);
|
||||
if (l.sign)
|
||||
break;
|
||||
break;
|
||||
c.m_watch = i;
|
||||
return l_true;
|
||||
case l_undef:
|
||||
TRACE("q", tout << l.lhs << " ~~ " << l.rhs << " is undef\n";);
|
||||
if (idx != UINT_MAX) {
|
||||
idx = UINT_MAX;
|
||||
return l_undef;
|
||||
|
|
|
@ -71,6 +71,7 @@ namespace q {
|
|||
for (auto const& [qlit, fml, generation] : m_instantiations) {
|
||||
euf::solver::scoped_generation sg(ctx, generation + 1);
|
||||
sat::literal lit = ctx.mk_literal(fml);
|
||||
m_qs.log_instantiation(~qlit, ~lit);
|
||||
m_qs.add_clause(~qlit, ~lit);
|
||||
}
|
||||
m_instantiations.reset();
|
||||
|
|
|
@ -24,6 +24,7 @@ Author:
|
|||
#include "sat/smt/euf_solver.h"
|
||||
#include "sat/smt/sat_th.h"
|
||||
#include "qe/lite/qe_lite.h"
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace q {
|
||||
|
@ -356,4 +357,10 @@ namespace q {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,5 +88,9 @@ namespace q {
|
|||
sat::literal_vector const& universal() const { return m_universal; }
|
||||
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);
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ namespace euf {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@ namespace euf {
|
|||
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 };
|
||||
return add_clause(2, lits, ps);
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ namespace euf {
|
|||
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;
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
was_true |= is_true(lits[i]);
|
||||
|
@ -226,13 +226,14 @@ namespace euf {
|
|||
return ctx.s().rand()();
|
||||
}
|
||||
|
||||
size_t th_explain::get_obj_size(unsigned num_lits, unsigned num_eqs, sat::proof_hint const* pma) {
|
||||
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));
|
||||
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);
|
||||
}
|
||||
|
||||
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_eq = p;
|
||||
m_proof_hint = pma;
|
||||
m_num_literals = n_lits;
|
||||
m_num_eqs = n_eqs;
|
||||
char * base_ptr = reinterpret_cast<char*>(this) + sizeof(th_explain);
|
||||
|
@ -244,33 +245,24 @@ namespace euf {
|
|||
m_eqs = reinterpret_cast<enode_pair*>(base_ptr);
|
||||
for (i = 0; i < n_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();
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -313,8 +305,8 @@ namespace euf {
|
|||
out << "--> " << m_consequent;
|
||||
if (m_eq.first != nullptr)
|
||||
out << "--> " << m_eq.first->get_expr_id() << " == " << m_eq.second->get_expr_id();
|
||||
if (m_pragma != nullptr)
|
||||
out << " p " << m_pragma;
|
||||
if (m_proof_hint != nullptr)
|
||||
out << " p ";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace euf {
|
|||
|
||||
};
|
||||
|
||||
|
||||
class th_decompile {
|
||||
public:
|
||||
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 {
|
||||
protected:
|
||||
solver& ctx;
|
||||
|
@ -150,16 +156,16 @@ namespace euf {
|
|||
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_units(sat::literal_vector const& lits);
|
||||
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, 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, 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(unsigned n, sat::literal* lits, sat::proof_hint const* ps = nullptr);
|
||||
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, th_proof_hint const* ps = nullptr);
|
||||
void add_equiv(sat::literal a, sat::literal b);
|
||||
void add_equiv_and(sat::literal a, sat::literal_vector const& bs);
|
||||
|
||||
|
@ -220,16 +226,16 @@ namespace euf {
|
|||
* that retrieve literals on demand.
|
||||
*/
|
||||
class th_explain {
|
||||
sat::literal m_consequent = sat::null_literal; // literal consequent for propagations
|
||||
enode_pair m_eq = enode_pair(); // equality consequent for propagations
|
||||
sat::literal m_consequent = sat::null_literal; // literal 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_eqs;
|
||||
sat::literal* m_literals;
|
||||
enode_pair* m_eqs;
|
||||
char* m_pragma = nullptr;
|
||||
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, 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, sat::proof_hint const* pma = nullptr);
|
||||
static size_t get_obj_size(unsigned num_lits, unsigned num_eqs);
|
||||
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);
|
||||
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);
|
||||
|
||||
public:
|
||||
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, 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, 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, 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, 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, th_proof_hint const* pma = nullptr);
|
||||
|
||||
sat::ext_constraint_idx to_index() const {
|
||||
return sat::constraint_base::mem2base(this);
|
||||
|
@ -277,7 +283,7 @@ namespace euf {
|
|||
|
||||
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; }
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -75,7 +75,6 @@ struct goal2sat::imp : public sat::sat_internalizer {
|
|||
func_decl_ref_vector m_unhandled_funs;
|
||||
bool m_default_external;
|
||||
bool m_euf { false };
|
||||
bool m_drat { false };
|
||||
bool m_is_redundant { false };
|
||||
bool m_top_level { false };
|
||||
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_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX));
|
||||
m_euf = sp.euf();
|
||||
m_drat = sp.drat_file().is_non_empty_string();
|
||||
}
|
||||
|
||||
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))
|
||||
return v;
|
||||
v = m_solver.add_var(is_ext);
|
||||
log_def(v, n);
|
||||
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::literal l;
|
||||
sat::bool_var v = m_map.to_bool_var(e);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue