mirror of
https://github.com/Z3Prover/z3
synced 2026-06-29 11:58:51 +00:00
Merge branch 'master' into derive-with-ranges
This commit is contained in:
commit
2703351f54
149 changed files with 6936 additions and 2049 deletions
|
|
@ -18,13 +18,13 @@
|
|||
|
||||
double ackr_helper::calculate_lemma_bound(fun2terms_map const& occs1, sel2terms_map const& occs2) {
|
||||
double total = 0;
|
||||
for (auto const& kv : occs1) {
|
||||
total += n_choose_2_chk(kv.m_value->var_args.size());
|
||||
total += kv.m_value->const_args.size() * kv.m_value->var_args.size();
|
||||
for (auto const &[k, v] : occs1) {
|
||||
total += n_choose_2_chk(v->var_args.size());
|
||||
total += v->const_args.size() * v->var_args.size();
|
||||
}
|
||||
for (auto const& kv : occs2) {
|
||||
total += n_choose_2_chk(kv.m_value->var_args.size());
|
||||
total += kv.m_value->const_args.size() * kv.m_value->var_args.size();
|
||||
for (auto const &[k, v] : occs2) {
|
||||
total += n_choose_2_chk(v->var_args.size());
|
||||
total += v->const_args.size() * v->var_args.size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,14 +52,26 @@ public:
|
|||
return m_autil.is_select(a) && is_uninterp_const(a->get_arg(0));
|
||||
}
|
||||
|
||||
void mark_non_select_rec(expr* t, expr_mark& visited, expr_mark& non_select) {
|
||||
if (visited.is_marked(t))
|
||||
return;
|
||||
visited.mark(t, true);
|
||||
non_select.mark(t, true);
|
||||
if (is_app(t)) {
|
||||
for (expr *arg : *to_app(t))
|
||||
mark_non_select_rec(arg, visited,non_select);
|
||||
}
|
||||
}
|
||||
|
||||
void mark_non_select(app* a, expr_mark& non_select) {
|
||||
if (m_autil.is_select(a)) {
|
||||
bool first = true;
|
||||
expr_mark visited;
|
||||
for (expr* arg : *a) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
non_select.mark(arg, true);
|
||||
mark_non_select_rec(arg, visited, non_select);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -70,10 +82,10 @@ public:
|
|||
|
||||
void prune_non_select(obj_map<app, app_set*> & sels, expr_mark& non_select) {
|
||||
ptr_vector<app> nons;
|
||||
for (auto& kv : sels) {
|
||||
if (non_select.is_marked(kv.m_key)) {
|
||||
nons.push_back(kv.m_key);
|
||||
dealloc(kv.m_value);
|
||||
for (auto &[k, v] : sels) {
|
||||
if (non_select.is_marked(k)) {
|
||||
nons.push_back(k);
|
||||
dealloc(v);
|
||||
}
|
||||
}
|
||||
for (app* s : nons) {
|
||||
|
|
|
|||
|
|
@ -149,9 +149,9 @@ void lackr::eager_enc() {
|
|||
checkpoint();
|
||||
ackr(v);
|
||||
}
|
||||
for (auto const& kv : m_sel2terms) {
|
||||
for (auto const &[k, v] : m_sel2terms) {
|
||||
checkpoint();
|
||||
ackr(kv.get_value());
|
||||
ackr(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,13 +190,13 @@ void lackr::abstract_fun(fun2terms_map const& apps) {
|
|||
}
|
||||
|
||||
void lackr::abstract_sel(sel2terms_map const& apps) {
|
||||
for (auto const& kv : apps) {
|
||||
func_decl * fd = kv.m_key->get_decl();
|
||||
for (app * t : kv.m_value->const_args) {
|
||||
for (auto const &[k, v] : apps) {
|
||||
func_decl * fd = k->get_decl();
|
||||
for (app * t : v->const_args) {
|
||||
app * fc = m.mk_fresh_const(fd->get_name(), t->get_sort());
|
||||
m_info->set_abstr(t, fc);
|
||||
}
|
||||
for (app * t : kv.m_value->var_args) {
|
||||
for (app * t : v->var_args) {
|
||||
app * fc = m.mk_fresh_const(fd->get_name(), t->get_sort());
|
||||
m_info->set_abstr(t, fc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
<Warn>4</Warn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<DocumentationFile>$(OutputPath)\Microsoft.Z3.xml</DocumentationFile>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Compilation items -->
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ static void decide_eh(void* _p, Z3_solver_callback cb, Z3_ast _val, unsigned bit
|
|||
info->jenv->CallVoidMethod(info->jobj, info->decide, (jlong)_val, bit, is_pos);
|
||||
}
|
||||
|
||||
static jboolean on_binding_eh(void* _p, Z3_solver_callback cb, Z3_ast _q, Z3_ast _inst) {
|
||||
[[maybe_unused]] static jboolean on_binding_eh(void* _p, Z3_solver_callback cb, Z3_ast _q, Z3_ast _inst) {
|
||||
JavaInfo *info = static_cast<JavaInfo*>(_p);
|
||||
ScopedCB scoped(info, cb);
|
||||
return info->jenv->CallBooleanMethod(info->jobj, info->on_binding, (jlong)_q, (jlong)_inst);
|
||||
|
|
|
|||
34
src/api/js/package-lock.json
generated
34
src/api/js/package-lock.json
generated
|
|
@ -5400,10 +5400,20 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz",
|
||||
"integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/markdown-it"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
|
|
@ -5501,15 +5511,25 @@
|
|||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz",
|
||||
"integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/markdown-it"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"linkify-it": "^5.0.1",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,29 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=70"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
# --- Pyodide / WebAssembly (PEP 783) build configuration ---------------------
|
||||
# Consumed by pyodide-build (invoked directly or via `cibuildwheel --platform
|
||||
# pyodide`). These flags are forwarded to the emscripten toolchain that compiles
|
||||
# libz3 to wasm32. setup.py's IS_PYODIDE branch appends the same -fexceptions
|
||||
# flags defensively, but declaring them here is what cibuildwheel relies on.
|
||||
# Pyodide 314 / emscripten 5 builds its main module with *native wasm*
|
||||
# exception handling and wasm longjmp (see Pyodide's Makefile.envs). Side
|
||||
# modules like libz3.so MUST match that ABI: building with the legacy
|
||||
# `-fexceptions` (JS-based EH) makes libz3 import `invoke_*` trampolines that the
|
||||
# Pyodide runtime no longer provides -> "cannot resolve symbol invoke_vi" at the
|
||||
# first Z3 call. WASM_BIGINT is required because the Z3 C API passes 64-bit ints
|
||||
# across the ctypes/JS boundary.
|
||||
[tool.pyodide.build]
|
||||
cflags = "-fwasm-exceptions -sSUPPORT_LONGJMP=wasm"
|
||||
cxxflags = "-fwasm-exceptions -sSUPPORT_LONGJMP=wasm"
|
||||
ldflags = "-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -sWASM_BIGINT -sSIDE_MODULE=1"
|
||||
|
||||
# --- cibuildwheel: produce a PyPI-publishable pyemscripten wheel -------------
|
||||
[tool.cibuildwheel]
|
||||
# Pyodide 314 ships CPython 3.14; match the single ABI it targets.
|
||||
build = "cp314-*"
|
||||
|
||||
[tool.cibuildwheel.pyodide]
|
||||
# z3test.py is the upstream smoke test; run it inside the Pyodide test venv.
|
||||
test-command = "python {project}/z3test.py z3"
|
||||
|
|
|
|||
|
|
@ -42,9 +42,16 @@ if RELEASE_DIR is None:
|
|||
BUILD_PLATFORM = "emscripten"
|
||||
BUILD_ARCH = "wasm32"
|
||||
BUILD_OS_VERSION = os.environ['_PYTHON_HOST_PLATFORM'].split('_')[1:-1]
|
||||
build_env['CFLAGS'] = build_env.get('CFLAGS', '') + " -fexceptions"
|
||||
build_env['CXXFLAGS'] = build_env.get('CXXFLAGS', '') + " -fexceptions"
|
||||
build_env['LDFLAGS'] = build_env.get('LDFLAGS', '') + " -fexceptions"
|
||||
# Match Pyodide's native-wasm exception/longjmp ABI (see Makefile.envs in
|
||||
# Pyodide). The legacy JS-based "-fexceptions" makes libz3.so import
|
||||
# invoke_* trampolines that the modern Pyodide runtime does not export,
|
||||
# which surfaces as "cannot resolve symbol invoke_vi" on the first Z3
|
||||
# call. These mirror [tool.pyodide.build] in pyproject.toml so direct
|
||||
# `pyodide build` invocations stay consistent with cibuildwheel.
|
||||
_wasm_eh = " -fwasm-exceptions -sSUPPORT_LONGJMP=wasm"
|
||||
build_env['CFLAGS'] = build_env.get('CFLAGS', '') + _wasm_eh
|
||||
build_env['CXXFLAGS'] = build_env.get('CXXFLAGS', '') + _wasm_eh
|
||||
build_env['LDFLAGS'] = build_env.get('LDFLAGS', '') + _wasm_eh + " -sWASM_BIGINT -sSIDE_MODULE=1"
|
||||
IS_SINGLE_THREADED = True
|
||||
ENABLE_LTO = False
|
||||
# build with pthread doesn't work. The WASM bindings are also single threaded.
|
||||
|
|
@ -305,6 +312,21 @@ class bdist_wheel(_bdist_wheel):
|
|||
|
||||
|
||||
def finalize_options(self):
|
||||
if BUILD_PLATFORM == "emscripten":
|
||||
# Under pyodide-build / `cibuildwheel --platform pyodide`, the
|
||||
# authoritative wheel platform tag is handed to us verbatim via
|
||||
# _PYTHON_HOST_PLATFORM. For PEP 783 (Pyodide >= 0.28 / "314") this
|
||||
# is e.g. "pyemscripten_2026_0_wasm32" -- a tag PyPI accepts. The
|
||||
# reconstruction below instead produced "emscripten_<ver>_wasm32",
|
||||
# which is locked to a Pyodide release and rejected by PyPI, so we
|
||||
# defer to pyodide-build's tag when it is available.
|
||||
host_platform = os.environ.get('_PYTHON_HOST_PLATFORM')
|
||||
if host_platform:
|
||||
self.plat_name = host_platform
|
||||
else:
|
||||
os_version_tag = '_'.join(BUILD_OS_VERSION) if BUILD_OS_VERSION else 'xxxxxx'
|
||||
self.plat_name = f"emscripten_{os_version_tag}_wasm32"
|
||||
return super().finalize_options()
|
||||
if BUILD_ARCH is not None and BUILD_PLATFORM is not None:
|
||||
os_version_tag = '_'.join(BUILD_OS_VERSION) if BUILD_OS_VERSION is not None else 'xxxxxx'
|
||||
os_version_tag = self.remove_build_machine_os_version(BUILD_PLATFORM, os_version_tag)
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ struct z3_replayer::imp {
|
|||
|
||||
Z3_symbol get_symbol(unsigned pos) const {
|
||||
check_arg(pos, SYMBOL);
|
||||
return (Z3_symbol)m_args[pos].m_sym;
|
||||
return (Z3_symbol)const_cast<void*>(m_args[pos].m_sym);
|
||||
}
|
||||
|
||||
void * get_obj(unsigned pos) const {
|
||||
|
|
|
|||
|
|
@ -2894,7 +2894,7 @@ proof * ast_manager::mk_transitivity(unsigned num_proofs, proof * const * proofs
|
|||
}
|
||||
});
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(mk_eq(n1,n2));
|
||||
return mk_app(basic_family_id, PR_TRANSITIVITY_STAR, args.size(), args.data());
|
||||
}
|
||||
|
|
@ -2903,7 +2903,7 @@ proof * ast_manager::mk_monotonicity(func_decl * R, app * f1, app * f2, unsigned
|
|||
SASSERT(f1->get_num_args() == f2->get_num_args());
|
||||
SASSERT(f1->get_decl() == f2->get_decl());
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(mk_app(R, f1, f2));
|
||||
proof* p = mk_app(basic_family_id, PR_MONOTONICITY, args.size(), args.data());
|
||||
return p;
|
||||
|
|
@ -2965,7 +2965,7 @@ proof * ast_manager::mk_rewrite_star(expr * s, expr * t, unsigned num_proofs, pr
|
|||
if (proofs_disabled())
|
||||
return nullptr;
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(mk_eq(s, t));
|
||||
return mk_app(basic_family_id, PR_REWRITE_STAR, args.size(), args.data());
|
||||
}
|
||||
|
|
@ -3055,7 +3055,7 @@ proof * ast_manager::mk_unit_resolution(unsigned num_proofs, proof * const * pro
|
|||
}
|
||||
|
||||
if (!found_complement) {
|
||||
args.append(num_proofs, (expr**)proofs);
|
||||
args.append(num_proofs, (expr* const *)proofs);
|
||||
CTRACE(mk_unit_resolution_bug, !is_or(f1), tout << mk_ll_pp(f1, *this) << "\n";
|
||||
for (unsigned i = 1; i < num_proofs; ++i)
|
||||
tout << mk_pp(proofs[i], *this) << "\n";
|
||||
|
|
@ -3125,7 +3125,7 @@ proof * ast_manager::mk_unit_resolution(unsigned num_proofs, proof * const * pro
|
|||
tout << mk_pp(new_fact, *this) << "\n";);
|
||||
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(new_fact);
|
||||
#ifdef Z3DEBUG
|
||||
expr * f1 = get_fact(proofs[0]);
|
||||
|
|
@ -3191,7 +3191,7 @@ proof * ast_manager::mk_apply_defs(expr * n, expr * def, unsigned num_proofs, pr
|
|||
if (proofs_disabled())
|
||||
return nullptr;
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(mk_oeq(n, def));
|
||||
return mk_app(basic_family_id, PR_APPLY_DEF, args.size(), args.data());
|
||||
}
|
||||
|
|
@ -3225,7 +3225,7 @@ proof * ast_manager::mk_nnf_pos(expr * s, expr * t, unsigned num_proofs, proof *
|
|||
return nullptr;
|
||||
check_nnf_proof_parents(num_proofs, proofs);
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(mk_oeq(s, t));
|
||||
return mk_app(basic_family_id, PR_NNF_POS, args.size(), args.data());
|
||||
}
|
||||
|
|
@ -3235,7 +3235,7 @@ proof * ast_manager::mk_nnf_neg(expr * s, expr * t, unsigned num_proofs, proof *
|
|||
return nullptr;
|
||||
check_nnf_proof_parents(num_proofs, proofs);
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(mk_oeq(mk_not(s), t));
|
||||
return mk_app(basic_family_id, PR_NNF_NEG, args.size(), args.data());
|
||||
}
|
||||
|
|
@ -3305,7 +3305,7 @@ proof * ast_manager::mk_redundant_del(expr* e) {
|
|||
|
||||
proof * ast_manager::mk_clause_trail(unsigned n, expr* const* ps) {
|
||||
ptr_buffer<expr> args;
|
||||
args.append(n, (expr**) ps);
|
||||
args.append(n, (expr* const *) ps);
|
||||
return mk_app(basic_family_id, PR_CLAUSE_TRAIL, 0, nullptr, args.size(), args.data());
|
||||
}
|
||||
|
||||
|
|
@ -3323,7 +3323,7 @@ proof * ast_manager::mk_th_lemma(
|
|||
for (unsigned i = 0; i < num_params; ++i)
|
||||
parameters.push_back(params[i]);
|
||||
ptr_buffer<expr> args;
|
||||
args.append(num_proofs, (expr**) proofs);
|
||||
args.append(num_proofs, (expr* const *) proofs);
|
||||
args.push_back(fact);
|
||||
return mk_app(basic_family_id, PR_TH_LEMMA, parameters.size(), parameters.data(), args.size(), args.data());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ private:
|
|||
inline ast_manager & m() const { return m_manager; }
|
||||
|
||||
// label for an expression
|
||||
std::string label_of_expr(const expr * e) const {
|
||||
expr_ref er((expr*)e, m());
|
||||
std::string label_of_expr(const expr *e) const {
|
||||
expr_ref er(const_cast<expr *>(e), m());
|
||||
std::ostringstream out;
|
||||
out << er << std::flush;
|
||||
return escape_dot(out.str());
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ z3_add_component(rewriter
|
|||
seq_rewriter.cpp
|
||||
seq_regex_bisim.cpp
|
||||
seq_skolem.cpp
|
||||
term_enumeration.cpp
|
||||
th_rewriter.cpp
|
||||
value_sweep.cpp
|
||||
var_subst.cpp
|
||||
|
|
|
|||
|
|
@ -768,9 +768,10 @@ void bit_blaster_tpl<Cfg>::mk_smod(unsigned sz, expr * const * a_bits, expr * co
|
|||
template<typename Cfg>
|
||||
void bit_blaster_tpl<Cfg>::mk_eq(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out) {
|
||||
expr_ref_vector out_bits(m());
|
||||
out_bits.resize(sz);
|
||||
for (unsigned i = 0; i < sz; ++i) {
|
||||
mk_iff(a_bits[i], b_bits[i], out);
|
||||
out_bits.push_back(out);
|
||||
out_bits[i] = out;
|
||||
}
|
||||
mk_and(out_bits.size(), out_bits.data(), out);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1060,7 +1060,7 @@ namespace seq {
|
|||
void axioms::replace_re_axiom(expr* e) {
|
||||
expr* s = nullptr, *r = nullptr, *t = nullptr;
|
||||
VERIFY(seq.str.is_replace_re(e, s, r, t));
|
||||
NOT_IMPLEMENTED_YET();
|
||||
throw default_exception("no support for replace-re");
|
||||
}
|
||||
|
||||
// A basic strategy for supporting replace_all and other
|
||||
|
|
@ -1105,7 +1105,7 @@ namespace seq {
|
|||
expr_ref branch1(m.mk_eq(len_r, vj), m);
|
||||
expr_ref test2(m.mk_and(a.mk_gt(len_s, vi), m.mk_eq(vi, a.mk_int(0)), seq.str.mk_is_empty(vp)), m);
|
||||
expr_ref branch2(m.mk_eq(vr, seq.str.mk_concat(vt, vs)), m);
|
||||
NOT_IMPLEMENTED_YET();
|
||||
throw default_exception("no support for replace-all");
|
||||
#if 0
|
||||
expr_ref test3(, m);
|
||||
expr_ref s1(m_sk.mk_prefix_inv(vp, vs), m);
|
||||
|
|
@ -1135,7 +1135,7 @@ namespace seq {
|
|||
void axioms::replace_re_all_axiom(expr* e) {
|
||||
expr* s = nullptr, *p = nullptr, *t = nullptr;
|
||||
VERIFY(seq.str.is_replace_re_all(e, s, p, t));
|
||||
NOT_IMPLEMENTED_YET();
|
||||
throw default_exception("no support for replace-re-all");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1856,6 +1856,127 @@ br_status seq_rewriter::mk_seq_replace_all(expr* a, expr* b, expr* c, expr_ref&
|
|||
return BR_FAILED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* replace_char("ab", "a", b") = empty
|
||||
* replace_char("bc", "a", b") = {"a", "b"}"c"
|
||||
* replace_char(R u R', "a", "b") = replace_char(R, "a", "b") u replace_char(R', "a", "b")
|
||||
* replace_char(R n R', "a", "b") = replace_char(R, "a", "b") n replace_char(R', "a", "b")
|
||||
* replace_char(R*, "a", "b") = replace_char(R, "a", "b")*
|
||||
* replace_char(R R', "a", "b") = replace_char(R, "a", "b") replace_char(R', "a", "b")
|
||||
*/
|
||||
expr_ref seq_rewriter::re_replace_char(expr *r, unsigned a_ch, unsigned b_ch, expr *a_str, expr *b_str) {
|
||||
expr *r1 = nullptr, *r2 = nullptr, *s = nullptr;
|
||||
zstring str_val;
|
||||
sort *seq_sort = nullptr;
|
||||
|
||||
if (re().is_to_re(r, s) && str().is_string(s, str_val)) {
|
||||
seq_sort = s->get_sort();
|
||||
expr_ref_vector parts(m());
|
||||
for (unsigned i = 0; i < str_val.length(); ++i) {
|
||||
if (str_val[i] == a_ch) {
|
||||
// replace_all never outputs a_ch, so this position is impossible
|
||||
return expr_ref(re().mk_empty(re().mk_re(seq_sort)), m());
|
||||
}
|
||||
else if (str_val[i] == b_ch) {
|
||||
// b in output came from either a or b in x
|
||||
auto a_re = re().mk_to_re(a_str);
|
||||
auto b_re = re().mk_to_re(b_str);
|
||||
parts.push_back(re().mk_union(a_re, b_re));
|
||||
}
|
||||
else {
|
||||
zstring ch(str_val[i]);
|
||||
parts.push_back(re().mk_to_re(str().mk_string(ch)));
|
||||
}
|
||||
}
|
||||
if (parts.empty())
|
||||
return expr_ref(re().mk_epsilon(seq_sort), m());
|
||||
expr_ref result(parts.back(), m());
|
||||
for (int i = parts.size() - 1; i-- > 0;)
|
||||
result = re().mk_concat(parts.get(i), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (re().is_range(r, r1, r2)) {
|
||||
zstring lo_s, hi_s;
|
||||
if (str().is_string(r1, lo_s) && str().is_string(r2, hi_s) && lo_s.length() == 1 && hi_s.length() == 1) {
|
||||
unsigned lo = lo_s[0], hi = hi_s[0];
|
||||
// Build the transformed range:
|
||||
// - Remove a_ch from the range (impossible in output)
|
||||
// - Replace b_ch with union(a_str, b_str)
|
||||
expr_ref_vector parts(m());
|
||||
// Characters in [lo, hi] excluding a_ch and b_ch
|
||||
if (lo <= hi) {
|
||||
// Sub-ranges excluding a_ch and b_ch
|
||||
unsigned prev = lo;
|
||||
for (unsigned ch = lo; ch <= hi; ++ch) {
|
||||
if (ch == a_ch || ch == b_ch) {
|
||||
if (prev < ch) {
|
||||
zstring prev_z(prev), pred_z(ch - 1);
|
||||
parts.push_back(re().mk_range(str().mk_string(prev_z), str().mk_string(pred_z)));
|
||||
}
|
||||
if (ch == b_ch) {
|
||||
parts.push_back(re().mk_union(re().mk_to_re(a_str), re().mk_to_re(b_str)));
|
||||
}
|
||||
// a_ch is simply excluded (not added)
|
||||
prev = ch + 1;
|
||||
}
|
||||
}
|
||||
if (prev <= hi) {
|
||||
zstring prev_z(prev), hi_z(hi);
|
||||
parts.push_back(re().mk_range(str().mk_string(prev_z), str().mk_string(hi_z)));
|
||||
}
|
||||
}
|
||||
if (parts.empty()) {
|
||||
sort *re_sort = r->get_sort();
|
||||
return expr_ref(re().mk_empty(re_sort), m());
|
||||
}
|
||||
expr_ref result(parts[0].get(), m());
|
||||
for (unsigned i = 1; i < parts.size(); ++i)
|
||||
result = re().mk_union(result, parts[i].get());
|
||||
return result;
|
||||
}
|
||||
return expr_ref(r, m());
|
||||
}
|
||||
|
||||
if (re().is_union(r, r1, r2)) {
|
||||
return expr_ref(
|
||||
re().mk_union(re_replace_char(r1, a_ch, b_ch, a_str, b_str), re_replace_char(r2, a_ch, b_ch, a_str, b_str)),
|
||||
m());
|
||||
}
|
||||
if (re().is_intersection(r, r1, r2)) {
|
||||
return expr_ref(
|
||||
re().mk_inter(re_replace_char(r1, a_ch, b_ch, a_str, b_str), re_replace_char(r2, a_ch, b_ch, a_str, b_str)),
|
||||
m());
|
||||
}
|
||||
if (re().is_concat(r, r1, r2)) {
|
||||
return expr_ref(re().mk_concat(re_replace_char(r1, a_ch, b_ch, a_str, b_str),
|
||||
re_replace_char(r2, a_ch, b_ch, a_str, b_str)),
|
||||
m());
|
||||
}
|
||||
if (re().is_star(r, r1)) {
|
||||
return expr_ref(re().mk_star(re_replace_char(r1, a_ch, b_ch, a_str, b_str)), m());
|
||||
}
|
||||
if (re().is_plus(r, r1)) {
|
||||
return expr_ref(re().mk_plus(re_replace_char(r1, a_ch, b_ch, a_str, b_str)), m());
|
||||
}
|
||||
if (re().is_opt(r, r1)) {
|
||||
return expr_ref(re().mk_opt(re_replace_char(r1, a_ch, b_ch, a_str, b_str)), m());
|
||||
}
|
||||
unsigned lo, hi;
|
||||
if (re().is_loop(r, r1, lo, hi)) {
|
||||
return expr_ref(re().mk_loop(re_replace_char(r1, a_ch, b_ch, a_str, b_str), lo, hi), m());
|
||||
}
|
||||
if (re().is_loop(r, r1, lo)) {
|
||||
return expr_ref(re().mk_loop(re_replace_char(r1, a_ch, b_ch, a_str, b_str), lo), m());
|
||||
}
|
||||
if (re().is_complement(r)) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
// For anything else (full_seq, empty, epsilon, of_pred, etc.), return unchanged
|
||||
return expr_ref(r, m());
|
||||
}
|
||||
|
||||
/**
|
||||
rewrites for map(f, s):
|
||||
|
||||
|
|
@ -2852,11 +2973,20 @@ expr_ref seq_rewriter::mk_derivative(expr* ele, expr* r) {
|
|||
|
||||
expr_ref seq_rewriter::mk_regex_union_normalize(expr* r1, expr* r2) {
|
||||
expr_ref _r1(r1, m()), _r2(r2, m());
|
||||
expr *a1, *b1, *a2, *b2;
|
||||
SASSERT(m_util.is_re(r1));
|
||||
SASSERT(m_util.is_re(r2));
|
||||
expr_ref result(m());
|
||||
std::function<bool(expr*, expr*&, expr*&)> test = [&](expr* t, expr*& a, expr*& b) { return re().is_union(t, a, b); };
|
||||
std::function<expr* (expr*, expr*)> compose = [&](expr* r1, expr* r2) { return (is_subset(r1, r2) ? r2 : (is_subset(r2, r1) ? r1 : re().mk_union(r1, r2))); };
|
||||
std::function<bool(expr *, expr *)> is_complement = [&](expr *a, expr *b) {
|
||||
expr *s;
|
||||
if (re().is_complement(a, s) && s == b)
|
||||
return true;
|
||||
if (re().is_complement(b, s) && s == a)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
if (r1 == r2 || re().is_empty(r2) || re().is_full_seq(r1))
|
||||
result = r1;
|
||||
else if (re().is_empty(r1) || re().is_full_seq(r2))
|
||||
|
|
@ -2865,8 +2995,28 @@ expr_ref seq_rewriter::mk_regex_union_normalize(expr* r1, expr* r2) {
|
|||
result = r1;
|
||||
else if (re().is_dot_plus(r2) && re().get_info(r1).min_length > 0)
|
||||
result = r2;
|
||||
else
|
||||
result = merge_regex_sets(r1, r2, re().mk_full_seq(r1->get_sort()), test, compose);
|
||||
// (R1 \ R2) U (R2 \ R1) = R1 xor R2
|
||||
else if (false && re().is_intersection(r1, a1, a2) && re().is_intersection(r2, b1, b2) &&
|
||||
is_complement(a1, b2) && is_complement(a2, b1)) {
|
||||
result = re().mk_xor(a1, re().mk_complement(a2));
|
||||
}
|
||||
else if (false && re().is_intersection(r1, a1, a2) && re().is_intersection(r2, b1, b2) &&
|
||||
is_complement(a1, b1) && is_complement(a2, b2)) {
|
||||
result = re().mk_xor(a1, re().mk_complement(a2));
|
||||
}
|
||||
else {
|
||||
// Range ∪ Range: [a,b] ∪ [c,d] = [min(a,c), max(b,d)] when overlapping or adjacent
|
||||
unsigned lo1_v = 0, hi1_v = 0, lo2_v = 0, hi2_v = 0;
|
||||
if (re().is_range(r1, lo1_v, hi1_v) && re().is_range(r2, lo2_v, hi2_v) &&
|
||||
lo2_v <= hi1_v + 1 && lo1_v <= hi2_v + 1) {
|
||||
unsigned new_lo = std::min(lo1_v, lo2_v);
|
||||
unsigned new_hi = std::max(hi1_v, hi2_v);
|
||||
result = re().mk_range(r1->get_sort(), new_lo, new_hi);
|
||||
}
|
||||
else
|
||||
result = merge_regex_sets(r1, r2, re().mk_full_seq(r1->get_sort()), test, compose);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -2895,8 +3045,17 @@ expr_ref seq_rewriter::mk_regex_inter_normalize(expr* r1, expr* r2) {
|
|||
result = r2;
|
||||
else if (re().is_dot_plus(r2) && re().get_info(r1).min_length > 0)
|
||||
result = r1;
|
||||
else
|
||||
result = merge_regex_sets(r1, r2, re().mk_empty(r1->get_sort()), test, compose);
|
||||
else {
|
||||
// Range ∩ Range: [a,b] ∩ [c,d] = [max(a,c), min(b,d)] or empty
|
||||
unsigned lo1_v = 0, hi1_v = 0, lo2_v = 0, hi2_v = 0;
|
||||
if (re().is_range(r1, lo1_v, hi1_v) && re().is_range(r2, lo2_v, hi2_v)) {
|
||||
unsigned new_lo = std::max(lo1_v, lo2_v);
|
||||
unsigned new_hi = std::min(hi1_v, hi2_v);
|
||||
result = re().mk_range(r1->get_sort(), new_lo, new_hi);
|
||||
}
|
||||
else
|
||||
result = merge_regex_sets(r1, r2, re().mk_empty(r1->get_sort()), test, compose);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -3631,6 +3790,23 @@ br_status seq_rewriter::mk_str_in_regexp(expr* a, expr* b, expr_ref& result) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// replace_all(x, a, b) in R where R is ground, a and b are unit-length strings
|
||||
// ==> x in R[b -> {a, b}, a -> empty]
|
||||
expr *ra_x = nullptr, *ra_a = nullptr, *ra_b = nullptr;
|
||||
zstring sa_val, sb_val;
|
||||
if (str().is_replace_all(a, ra_x, ra_a, ra_b) && ra_a == ra_b) {
|
||||
result = ra_x;
|
||||
return BR_DONE;
|
||||
}
|
||||
if (str().is_replace_all(a, ra_x, ra_a, ra_b) && str().is_string(ra_a, sa_val) && sa_val.length() == 1 &&
|
||||
str().is_string(ra_b, sb_val) && sb_val.length() == 1 && sa_val[0] != sb_val[0] && re().is_ground(b) &&
|
||||
re().get_info(b).classical) {
|
||||
expr_ref new_re = re_replace_char(b, sa_val[0], sb_val[0], ra_a, ra_b);
|
||||
result = re().mk_in_re(ra_x, new_re);
|
||||
return BR_REWRITE_FULL;
|
||||
}
|
||||
|
||||
expr_ref b_s(m());
|
||||
if (lift_str_from_to_re(b, b_s)) {
|
||||
result = m_br.mk_eq_rw(a, b_s);
|
||||
|
|
@ -4460,6 +4636,11 @@ br_status seq_rewriter::mk_re_range(expr* lo, expr* hi, expr_ref& result) {
|
|||
result = re().mk_empty(srt);
|
||||
return BR_DONE;
|
||||
}
|
||||
// Singleton: re.range "a" "a" → str.to_re "a"
|
||||
if (slo.length() == 1 && shi.length() == 1 && slo[0] == shi[0]) {
|
||||
result = re().mk_to_re(lo);
|
||||
return BR_DONE;
|
||||
}
|
||||
|
||||
return BR_FAILED;
|
||||
}
|
||||
|
|
@ -4771,6 +4952,25 @@ br_status seq_rewriter::mk_eq_core(expr * l, expr * r, expr_ref & result) {
|
|||
if (reduce_eq_empty(l, r, result))
|
||||
return BR_REWRITE_FULL;
|
||||
|
||||
// a, b are unit-length ground strings => replace_all(x, a, b) in re.to_re(s)
|
||||
{
|
||||
expr *ra_x = nullptr, *ra_a = nullptr, *ra_b = nullptr;
|
||||
zstring sa_val, sb_val, s_val;
|
||||
expr *str_side = nullptr, *ra_side = nullptr;
|
||||
if (str().is_replace_all(l))
|
||||
ra_side = l, str_side = r;
|
||||
else if (str().is_replace_all(r))
|
||||
ra_side = r, str_side = l;
|
||||
if (ra_side && str_side &&
|
||||
str().is_replace_all(ra_side, ra_x, ra_a, ra_b) && str().is_string(ra_a, sa_val) &&
|
||||
sa_val.length() == 1 &&
|
||||
str().is_string(ra_b, sb_val) && sb_val.length() == 1 &&
|
||||
str().is_string(str_side, s_val)) {
|
||||
result = re().mk_in_re(ra_side, re().mk_to_re(str_side));
|
||||
return BR_REWRITE_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (reduce_arith_eq(l, r, res) || reduce_arith_eq(r, l, res)) {
|
||||
result = mk_and(res);
|
||||
|
|
|
|||
|
|
@ -179,6 +179,11 @@ class seq_rewriter {
|
|||
//replace b in a by c into result
|
||||
void replace_all_subvectors(expr_ref_vector const& as, expr_ref_vector const& bs, expr* c, expr_ref_vector& result);
|
||||
|
||||
// For replace_all(x, a, b) in R: transform R so that
|
||||
// - occurrences of b_ch are replaced by union(to_re(a_str), to_re(b_str))
|
||||
// - occurrences of a_ch are replaced by empty (replace_all never outputs a)
|
||||
expr_ref re_replace_char(expr *r, unsigned a_ch, unsigned b_ch, expr *a_str, expr *b_str);
|
||||
|
||||
// Calculate derivative, memoized and enforcing a normal form
|
||||
expr_ref mk_der_op(decl_kind k, expr* a, expr* b);
|
||||
expr_ref mk_der_op_rec(decl_kind k, expr* a, expr* b);
|
||||
|
|
|
|||
672
src/ast/rewriter/term_enumeration.cpp
Normal file
672
src/ast/rewriter/term_enumeration.cpp
Normal file
|
|
@ -0,0 +1,672 @@
|
|||
/**
|
||||
* term_enumeration.cpp - Bottom-up term enumeration module for Z3
|
||||
*
|
||||
* Inspired by the Probe synthesizer (Barke et al., "Just-in-Time Learning
|
||||
* for Bottom-Up Enumerative Synthesis"). Adapted to use Z3's internal APIs.
|
||||
*
|
||||
* Key ideas:
|
||||
* - Terms are enumerated bottom-up by "cost" (calculated by tree size).
|
||||
* - A grammar describes which function symbols (operators) and leaves
|
||||
* (constants, variables) are available for enumeration.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "util/vector.h"
|
||||
#include "util/scoped_ptr_vector.h"
|
||||
#include "util/obj_hashtable.h"
|
||||
#include "util/uint_set.h"
|
||||
#include "ast/ast.h"
|
||||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "ast/rewriter/term_enumeration.h"
|
||||
|
||||
|
||||
namespace term_enum {
|
||||
|
||||
// ============================================================================
|
||||
// grammar production rule
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* A production describes how to construct a term from child terms.
|
||||
* - domain: the sort required for each child
|
||||
* - range: the sort of the produced term
|
||||
* - builder: given a vector of child exprs, produce the result expr
|
||||
*/
|
||||
struct production {
|
||||
std::string name;
|
||||
sort_ref range;
|
||||
sort_ref_vector domain;
|
||||
std::function<expr_ref(expr_ref_vector const&)> builder;
|
||||
|
||||
bool is_leaf() const { return domain.empty(); }
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// grammar
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* A grammar groups productions into leaves (arity 0) and operators (arity > 0).
|
||||
*/
|
||||
class grammar {
|
||||
public:
|
||||
grammar(ast_manager& m) : m(m), m_pinned(m) {}
|
||||
|
||||
void add_production(production* p) {
|
||||
if (p->is_leaf())
|
||||
m_leaves.push_back(p);
|
||||
else
|
||||
m_operators.push_back(p);
|
||||
}
|
||||
|
||||
scoped_ptr_vector<production> const& leaves() const { return m_leaves; }
|
||||
scoped_ptr_vector<production> const& operators() const { return m_operators; }
|
||||
ast_manager& mgr() const { return m; }
|
||||
|
||||
void add_func_decl(func_decl *f) {
|
||||
if (m_seen.contains(f))
|
||||
return;
|
||||
m_pinned.push_back(f);
|
||||
m_seen.insert(f);
|
||||
sort_ref range(f->get_range(), m);
|
||||
sort_ref_vector dom(m);
|
||||
for (unsigned i = 0; i < f->get_arity(); ++i)
|
||||
dom.push_back(sort_ref(f->get_domain(i), m));
|
||||
add_production(alloc(production, {f->get_name().str(), range, dom, [this, f](expr_ref_vector const &args) {
|
||||
return expr_ref(m.mk_app(f, args), m);
|
||||
}}));
|
||||
}
|
||||
|
||||
void add_expr(expr *e) {
|
||||
if (m_seen.contains(e))
|
||||
return;
|
||||
m_pinned.push_back(e);
|
||||
m_seen.insert(e);
|
||||
sort_ref range(e->get_sort(), m);
|
||||
sort_ref_vector dom(m);
|
||||
std::stringstream ss;
|
||||
ss << mk_bounded_pp(e, m);
|
||||
std::string name = ss.str();
|
||||
add_production(alloc(production, {name, range, dom, [this, e](expr_ref_vector const&) { return expr_ref(e, m); }}));
|
||||
}
|
||||
|
||||
std::ostream& display(std::ostream& out) const {
|
||||
out << "Leaves:\n";
|
||||
for (auto const *p : m_leaves) {
|
||||
out << " " << p->name << " : " << mk_pp(p->range, m) << "\n";
|
||||
}
|
||||
out << "Operators:\n";
|
||||
for (auto const *p : m_operators) {
|
||||
out << " " << p->name << " : (";
|
||||
for (unsigned i = 0; i < p->domain.size(); ++i) {
|
||||
if (i > 0)
|
||||
out << ", ";
|
||||
out << mk_pp(p->domain[i], m);
|
||||
}
|
||||
out << ") -> " << mk_pp(p->range, m) << "\n";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
ast_manager& m;
|
||||
ast_ref_vector m_pinned;
|
||||
scoped_ptr_vector<production> m_leaves;
|
||||
scoped_ptr_vector<production> m_operators;
|
||||
obj_hashtable<ast> m_seen;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Term Bank - stores enumerated terms by cost and sort
|
||||
// ============================================================================
|
||||
|
||||
using cost_terms = vector<std::pair<expr*, unsigned>>;
|
||||
|
||||
class term_bank {
|
||||
using sort_term_map = obj_map<sort, ptr_vector<expr>>;
|
||||
public:
|
||||
term_bank(ast_manager& m) : m(m), m_pinned(m) {}
|
||||
|
||||
~term_bank() {
|
||||
for (auto s : m_terms)
|
||||
dealloc(s);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_pinned.reset();
|
||||
m_terms.clear();
|
||||
}
|
||||
|
||||
void add(expr* term, unsigned cost) {
|
||||
sort* s = term->get_sort();
|
||||
m_pinned.push_back(term);
|
||||
if (cost >= m_terms.size())
|
||||
m_terms.resize(cost + 1);
|
||||
if (!m_terms[cost])
|
||||
m_terms[cost] = alloc(sort_term_map);
|
||||
m_terms[cost]->insert_if_not_there(s, ptr_vector<expr>()).push_back(term);
|
||||
}
|
||||
|
||||
/** Get all terms of a given sort up to (and including) max_cost */
|
||||
cost_terms get_by_sort(sort* s, unsigned max_cost) const {
|
||||
cost_terms result;
|
||||
for (unsigned c = 0; c <= max_cost; ++c) {
|
||||
if (c >= m_terms.size())
|
||||
break;
|
||||
if (!m_terms[c]->contains(s))
|
||||
continue;
|
||||
for (auto t : m_terms[c]->find(s))
|
||||
result.push_back({t, c});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Return true if there is at least one term at/above `cost` whose sort is
|
||||
// not in `sorts` (i.e., enumeration can still produce a new requested sort).
|
||||
bool is_productive(unsigned cost, uint_set const& sorts) {
|
||||
for (unsigned i = cost; i < m_terms.size(); ++i) {
|
||||
if (!m_terms[i])
|
||||
continue;
|
||||
for (auto const& entry : *m_terms[i]) {
|
||||
sort* term_sort = entry.m_key;
|
||||
if (!sorts.contains(term_sort->get_small_id()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr_vector<expr> null_ptr_vector;
|
||||
ptr_vector<expr> const &get_by_cost_and_sort(unsigned cost, sort *s) const {
|
||||
if (cost >= m_terms.size() || !m_terms[cost] || !m_terms[cost]->contains(s))
|
||||
return null_ptr_vector;
|
||||
return m_terms[cost]->find(s);
|
||||
}
|
||||
|
||||
std::ostream& display(std::ostream& out) const {
|
||||
for (unsigned cost = 0; cost < m_terms.size(); ++cost) {
|
||||
if (!m_terms[cost])
|
||||
continue;
|
||||
out << "cost " << cost << ":\n";
|
||||
for (auto& [s, terms] : *m_terms[cost]) {
|
||||
out << " sort " << mk_pp(s, m) << ":\n";
|
||||
for (expr* e : terms) {
|
||||
out << " #" << e->get_id() << " ";
|
||||
if (cost == 0) {
|
||||
out << mk_bounded_pp(e, m);
|
||||
}
|
||||
else if (is_app(e)) {
|
||||
app* a = to_app(e);
|
||||
out << a->get_decl()->get_name() << "(";
|
||||
bool first = true;
|
||||
for (expr* arg : *a) {
|
||||
if (!first) out << ", ";
|
||||
first = false;
|
||||
out << "#" << arg->get_id();
|
||||
}
|
||||
out << ")";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
ast_manager& m;
|
||||
expr_ref_vector m_pinned;
|
||||
// cost -> sort -> terms
|
||||
ptr_vector<sort_term_map> m_terms;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Children Iterator - generates all combinations of child terms
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Iterates over all tuples (c1, c2, ..., cn) where each ci has the required
|
||||
* sort, drawn from the term bank, with at least one child at the current
|
||||
* cost - 1 (to avoid regenerating previously seen terms).
|
||||
*/
|
||||
class children_iterator {
|
||||
public:
|
||||
children_iterator(ast_manager& m, production const& prod, term_bank const& bank, unsigned current_cost)
|
||||
: m(m), m_done(false)
|
||||
{
|
||||
m_arity = prod.domain.size();
|
||||
if (m_arity == 0) {
|
||||
m_done = true;
|
||||
return;
|
||||
}
|
||||
for (unsigned i = 0; i < m_arity; ++i) {
|
||||
m_candidates.push_back(bank.get_by_sort(prod.domain[i], current_cost - 1));
|
||||
if (m_candidates.back().empty()) {
|
||||
m_done = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_indices.resize(m_arity, 0);
|
||||
}
|
||||
|
||||
bool has_next(unsigned cost) {
|
||||
while (!m_done) {
|
||||
if (has_child_at_cost(cost))
|
||||
return true;
|
||||
advance();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
expr_ref_vector next(unsigned& cost) {
|
||||
expr_ref_vector result(m);
|
||||
cost = 1;
|
||||
for (unsigned i = 0; i < m_arity; ++i) {
|
||||
auto [e, c] = m_candidates[i].get(m_indices[i]);
|
||||
cost += c;
|
||||
result.push_back(e);
|
||||
}
|
||||
advance();
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
ast_manager& m;
|
||||
unsigned m_arity;
|
||||
bool m_done;
|
||||
vector<cost_terms> m_candidates;
|
||||
svector<unsigned> m_indices;
|
||||
|
||||
bool has_child_at_cost(unsigned cost) const {
|
||||
for (unsigned i = 0; i < m_arity; ++i) {
|
||||
auto [e, c] = m_candidates[i].get(m_indices[i]);
|
||||
if (c + 1 == cost)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void advance() {
|
||||
for (auto i = m_arity; i-- > 0;) {
|
||||
m_indices[i]++;
|
||||
if (m_indices[i] < m_candidates[i].size()) return;
|
||||
m_indices[i] = 0;
|
||||
}
|
||||
m_done = true;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// bottom_up_enumerator - the main bottom-up term enumeration engine
|
||||
// ============================================================================
|
||||
|
||||
|
||||
class bottom_up_enumerator {
|
||||
public:
|
||||
bottom_up_enumerator(grammar& grammar)
|
||||
: m_grammar(grammar), m(grammar.mgr()),
|
||||
m_bank(grammar.mgr()), m_pending(grammar.mgr()), m_rewriter(grammar.mgr())
|
||||
{}
|
||||
|
||||
void set_target_sort(sort *s) {
|
||||
m_target_sort = s;
|
||||
}
|
||||
bool has_next() {
|
||||
if (m_pending) return true;
|
||||
m_pending = find_next();
|
||||
return m_pending != nullptr;
|
||||
}
|
||||
|
||||
expr_ref next() {
|
||||
if (!m_pending)
|
||||
m_pending = find_next();
|
||||
expr_ref result(m_pending, m);
|
||||
m_pending = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
term_bank const& bank() const { return m_bank; }
|
||||
|
||||
std::ostream& display(std::ostream& out) const {
|
||||
m_grammar.display(out);
|
||||
return m_bank.display(out);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_cost = 0;
|
||||
m_leaf_idx = 0;
|
||||
m_op_idx = 0;
|
||||
m_state = State::Leaves;
|
||||
m_bank.reset();
|
||||
m_pending = nullptr;
|
||||
m_rewriter.reset();
|
||||
m_seen_terms.reset();
|
||||
m_children_iter.reset();
|
||||
}
|
||||
|
||||
expr* add_term(expr_ref const& term, unsigned cost) {
|
||||
expr_ref simplified(m);
|
||||
m_rewriter(term, simplified);
|
||||
if (m_seen_terms.contains(simplified))
|
||||
return nullptr;
|
||||
m_seen_terms.insert(simplified);
|
||||
m_bank.add(simplified, cost);
|
||||
return simplified;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State { Leaves, Operators, Done };
|
||||
|
||||
grammar& m_grammar;
|
||||
ast_manager& m;
|
||||
term_bank m_bank;
|
||||
unsigned m_cost = 0;
|
||||
unsigned m_leaf_idx = 0;
|
||||
unsigned m_op_idx = 0;
|
||||
unsigned m_bank_idx = 0;
|
||||
unsigned m_bank_size = 0;
|
||||
bool m_made_progress = false;
|
||||
uint_set m_sorts_produced;
|
||||
State m_state = State::Leaves;
|
||||
expr_ref m_pending;
|
||||
th_rewriter m_rewriter;
|
||||
obj_hashtable<expr> m_seen_terms;
|
||||
std::unique_ptr<children_iterator> m_children_iter;
|
||||
sort *m_target_sort = nullptr;
|
||||
|
||||
bool sort_matches(expr* e) const {
|
||||
return !m_target_sort || e->get_sort() == m_target_sort;
|
||||
}
|
||||
|
||||
expr* find_next() {
|
||||
while (true) {
|
||||
switch (m_state) {
|
||||
case State::Leaves:
|
||||
while (m_leaf_idx < m_grammar.leaves().size()) {
|
||||
production const &prod = *m_grammar.leaves()[m_leaf_idx];
|
||||
m_leaf_idx++;
|
||||
expr_ref_vector empty_args(m);
|
||||
expr_ref term = prod.builder(empty_args);
|
||||
expr* r = add_term(term, 0);
|
||||
if (r && sort_matches(r))
|
||||
return r;
|
||||
}
|
||||
m_state = State::Operators;
|
||||
m_cost = 1;
|
||||
m_op_idx = 0;
|
||||
m_bank_idx = 0;
|
||||
m_bank_size = get_bank_size();
|
||||
m_made_progress = false;
|
||||
m_sorts_produced.reset();
|
||||
m_children_iter.reset();
|
||||
break;
|
||||
|
||||
case State::Operators: {
|
||||
expr* result = enumerate_operators();
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
m_cost++;
|
||||
m_op_idx = 0;
|
||||
m_bank_idx = 0;
|
||||
m_bank_size = get_bank_size();
|
||||
m_children_iter.reset();
|
||||
if (!m_made_progress && !m_bank.is_productive(m_cost, m_sorts_produced)) {
|
||||
m_state = State::Done;
|
||||
return nullptr;
|
||||
}
|
||||
if (m_sorts_produced.contains(m_target_sort->get_small_id()))
|
||||
m_sorts_produced.reset();
|
||||
m_made_progress = false;
|
||||
break;
|
||||
}
|
||||
case State::Done:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned get_bank_size() const {
|
||||
auto const &terms = m_bank.get_by_cost_and_sort(m_cost, m_target_sort);
|
||||
return terms.size();
|
||||
}
|
||||
|
||||
expr *enumerate_operators() {
|
||||
auto const &ops = m_grammar.operators();
|
||||
while (true) {
|
||||
|
||||
// first find terms at m_cost that were already created
|
||||
if (m_bank_idx < m_bank_size) {
|
||||
auto const &terms = m_bank.get_by_cost_and_sort(m_cost, m_target_sort);
|
||||
auto t = terms.get(m_bank_idx);
|
||||
m_bank_idx++;
|
||||
SASSERT(sort_matches(t));
|
||||
return t;
|
||||
}
|
||||
|
||||
// then create new terms using children at cost below current m_cost.
|
||||
if (m_children_iter && m_children_iter->has_next(m_cost)) {
|
||||
unsigned new_cost = 0;
|
||||
expr_ref_vector children = m_children_iter->next(new_cost);
|
||||
production const &prod = *ops[m_op_idx - 1];
|
||||
expr_ref term = prod.builder(children);
|
||||
// IF_VERBOSE(0, verbose_stream() << term << "\n");
|
||||
SASSERT(new_cost >= m_cost);
|
||||
expr* r = add_term(term, new_cost);
|
||||
if (!r)
|
||||
continue;
|
||||
unsigned sort_id = r->get_sort()->get_small_id();
|
||||
if (!m_sorts_produced.contains(sort_id))
|
||||
m_made_progress = true;
|
||||
m_sorts_produced.insert(sort_id);
|
||||
if (sort_matches(r) && new_cost == m_cost) {
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_op_idx >= ops.size())
|
||||
return nullptr;
|
||||
|
||||
production const &prod = *ops[m_op_idx];
|
||||
m_op_idx++;
|
||||
m_children_iter = std::make_unique<children_iterator>(m, prod, m_bank, m_cost);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace term_enum
|
||||
|
||||
// ============================================================================
|
||||
// term_enumeration public interface implementation
|
||||
// ============================================================================
|
||||
|
||||
struct term_enumeration::imp {
|
||||
ast_manager& m;
|
||||
term_enum::grammar m_grammar;
|
||||
term_enum::bottom_up_enumerator m_bottom_up_enumerator;
|
||||
std::function<unsigned(expr*)> m_cost;
|
||||
|
||||
imp(ast_manager& m) :
|
||||
m(m), m_grammar(m), m_bottom_up_enumerator(m_grammar) {}
|
||||
|
||||
void add_production(func_decl* f) {
|
||||
m_grammar.add_func_decl(f);
|
||||
}
|
||||
|
||||
void add_production(expr* e) {
|
||||
m_grammar.add_expr(e);
|
||||
}
|
||||
|
||||
void set_cost(std::function<unsigned(expr*)> const& cost) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
std::ostream& display(std::ostream& out) const {
|
||||
return m_bottom_up_enumerator.display(out);
|
||||
}
|
||||
};
|
||||
|
||||
// -- iterator implementation --
|
||||
|
||||
struct term_enumeration::iterator::iter_imp {
|
||||
imp& m_imp;
|
||||
ast_manager & m;
|
||||
sort* m_sort;
|
||||
unsigned m_cost = 0;
|
||||
unsigned m_idx = 0;
|
||||
vector<expr_ref_vector> m_levels;
|
||||
expr_ref m_current;
|
||||
bool m_end;
|
||||
vector<expr_ref_vector> m_vars;
|
||||
vector<ptr_vector<sort>> m_decls;
|
||||
vector<vector<symbol>> m_names;
|
||||
|
||||
iter_imp(imp& i, sort* s) : m_imp(i), m(i.m), m_sort(s), m_current(i.m), m_end(false) {
|
||||
m_imp.m_bottom_up_enumerator.reset();
|
||||
init_sort();
|
||||
advance();
|
||||
}
|
||||
|
||||
// Sentinel constructor
|
||||
iter_imp(imp& i) :
|
||||
m_imp(i), m(i.m), m_sort(nullptr), m_current(i.m), m_end(true) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
|
||||
void init_sort() {
|
||||
array_util autil(m);
|
||||
sort *range = m_sort;
|
||||
|
||||
while (autil.is_array(range)) {
|
||||
m_vars.push_back(expr_ref_vector(m));
|
||||
m_decls.push_back(ptr_vector<sort>());
|
||||
m_names.push_back(vector<symbol>());
|
||||
for (unsigned i = 0; i < get_array_arity(range); ++i) {
|
||||
m_decls.back().push_back(get_array_domain(range, i));
|
||||
m_vars.back().push_back(nullptr);
|
||||
m_names.back().push_back(symbol());
|
||||
}
|
||||
|
||||
expr_ref_vector args(m);
|
||||
args.push_back(m.mk_const("a", range));
|
||||
for (unsigned i = 0; i < m_decls.back().size(); ++i) {
|
||||
args.push_back(m.mk_var(i, m_decls.back().get(i)));
|
||||
}
|
||||
app_ref sel(autil.mk_select(args), m);
|
||||
m_imp.m_grammar.add_func_decl(sel->get_decl());
|
||||
|
||||
range = get_array_range(range);
|
||||
}
|
||||
unsigned n = 0;
|
||||
for (unsigned i = m_decls.size(); i-- > 0;) {
|
||||
for (unsigned j = m_decls[i].size(); j-- > 0;) {
|
||||
m_vars[i][j] = m.mk_var(n, m_decls[i][j]);
|
||||
m_names[i][j] = symbol(n);
|
||||
m_imp.add_production(m_vars[i].get(j));
|
||||
n++;
|
||||
}
|
||||
}
|
||||
m_sort = range;
|
||||
m_imp.m_bottom_up_enumerator.set_target_sort(range);
|
||||
}
|
||||
|
||||
void mk_lambda() {
|
||||
if (!m_current)
|
||||
return;
|
||||
for (unsigned i = m_decls.size(); i-- > 0;)
|
||||
m_current = m.mk_lambda(m_decls[i].size(), m_decls[i].data(), m_names[i].data(), m_current);
|
||||
}
|
||||
|
||||
void advance() {
|
||||
if (m_end)
|
||||
return;
|
||||
m_current = m_imp.m_bottom_up_enumerator.next();
|
||||
SASSERT(!m_current || m_current->get_sort() == m_sort);
|
||||
mk_lambda();
|
||||
if (!m_current)
|
||||
m_end = true;
|
||||
}
|
||||
};
|
||||
|
||||
term_enumeration::iterator::iterator(imp& i, sort* s) {
|
||||
m_imp = alloc(iter_imp, i, s);
|
||||
}
|
||||
|
||||
term_enumeration::iterator::iterator(std::nullptr_t) {
|
||||
m_imp = nullptr;
|
||||
}
|
||||
|
||||
term_enumeration::iterator::~iterator() {
|
||||
dealloc(m_imp);
|
||||
}
|
||||
|
||||
expr* term_enumeration::iterator::operator*() {
|
||||
return m_imp ? m_imp->m_current.get() : nullptr;
|
||||
}
|
||||
|
||||
term_enumeration::iterator& term_enumeration::iterator::operator++() {
|
||||
if (m_imp) m_imp->advance();
|
||||
return *this;
|
||||
}
|
||||
|
||||
term_enumeration::iterator term_enumeration::iterator::operator++(int) {
|
||||
iterator tmp(*this);
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool term_enumeration::iterator::operator==(iterator const& other) const {
|
||||
if (!m_imp && !other.m_imp) return true;
|
||||
if (!m_imp) return other.m_imp->m_end;
|
||||
if (!other.m_imp) return m_imp->m_end;
|
||||
return m_imp->m_end == other.m_imp->m_end &&
|
||||
m_imp->m_current == other.m_imp->m_current;
|
||||
}
|
||||
|
||||
// -- terms implementation --
|
||||
|
||||
term_enumeration::terms::terms(imp* i, sort* s) : m_imp(i), m_sort(s) {}
|
||||
|
||||
term_enumeration::iterator term_enumeration::terms::begin() {
|
||||
return iterator(*m_imp, m_sort);
|
||||
}
|
||||
|
||||
term_enumeration::iterator term_enumeration::terms::end() {
|
||||
return iterator(nullptr);
|
||||
}
|
||||
|
||||
// -- term_enumeration implementation --
|
||||
|
||||
term_enumeration::term_enumeration(ast_manager& m) {
|
||||
m_imp = alloc(imp, m);
|
||||
}
|
||||
|
||||
term_enumeration::~term_enumeration() {
|
||||
dealloc(m_imp);
|
||||
}
|
||||
|
||||
void term_enumeration::add_production(func_decl* f) {
|
||||
m_imp->add_production(f);
|
||||
}
|
||||
|
||||
void term_enumeration::add_production(expr* e) {
|
||||
m_imp->add_production(e);
|
||||
}
|
||||
|
||||
void term_enumeration::set_cost(std::function<unsigned(expr*)> const& cost) {
|
||||
m_imp->set_cost(cost);
|
||||
}
|
||||
|
||||
term_enumeration::terms term_enumeration::enum_terms(sort* s) {
|
||||
return terms(m_imp, s);
|
||||
}
|
||||
|
||||
std::ostream& term_enumeration::display(std::ostream& out) const {
|
||||
return m_imp->display(out);
|
||||
}
|
||||
50
src/ast/rewriter/term_enumeration.h
Normal file
50
src/ast/rewriter/term_enumeration.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "ast/ast.h"
|
||||
#include <functional>
|
||||
|
||||
class term_enumeration {
|
||||
struct imp;
|
||||
imp* m_imp;
|
||||
public:
|
||||
term_enumeration(ast_manager& m);
|
||||
~term_enumeration();
|
||||
|
||||
void add_production(func_decl* f);
|
||||
void add_production(expr* e);
|
||||
// void add_production(sort *s, std::function<expr *()> g);
|
||||
|
||||
// cost function associated with expressions.
|
||||
// terms are enumerated with increasing cost.
|
||||
|
||||
void set_cost(std::function<unsigned(expr*)> const& cost);
|
||||
|
||||
class iterator {
|
||||
struct iter_imp;
|
||||
iter_imp* m_imp;
|
||||
public:
|
||||
iterator(imp& i, sort* s);
|
||||
iterator(std::nullptr_t);
|
||||
~iterator();
|
||||
expr* operator*();
|
||||
iterator operator++(int);
|
||||
iterator& operator++();
|
||||
bool operator!=(iterator const& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool operator==(iterator const &other) const;
|
||||
};
|
||||
|
||||
class terms {
|
||||
imp* m_imp;
|
||||
sort* m_sort;
|
||||
public:
|
||||
terms(imp* i, sort* s);
|
||||
iterator begin();
|
||||
iterator end();
|
||||
};
|
||||
|
||||
terms enum_terms(sort* s);
|
||||
|
||||
std::ostream& display(std::ostream& out) const;
|
||||
};
|
||||
|
|
@ -1208,6 +1208,15 @@ app* seq_util::rex::mk_of_pred(expr* p) {
|
|||
return m.mk_app(m_fid, OP_RE_OF_PRED, 0, nullptr, 1, &p);
|
||||
}
|
||||
|
||||
app* seq_util::rex::mk_range(sort* re_sort, unsigned lo, unsigned hi) {
|
||||
if (lo > hi)
|
||||
return mk_empty(re_sort);
|
||||
app* lo_str = u.str.mk_string(zstring(lo));
|
||||
if (lo == hi)
|
||||
return mk_to_re(lo_str);
|
||||
return mk_range(lo_str, u.str.mk_string(zstring(hi)));
|
||||
}
|
||||
|
||||
bool seq_util::rex::is_loop(expr const* n, expr*& body, unsigned& lo, unsigned& hi) const {
|
||||
if (is_loop(n)) {
|
||||
app const* a = to_app(n);
|
||||
|
|
@ -1671,11 +1680,19 @@ seq_util::rex::info seq_util::rex::mk_info_rec(app* e) const {
|
|||
case OP_RE_OPTION:
|
||||
i1 = get_info_rec(e->get_arg(0));
|
||||
return i1.opt();
|
||||
case OP_RE_RANGE:
|
||||
case OP_RE_RANGE: {
|
||||
// A concrete range [lo, hi] with lo <= hi is non-empty and classical.
|
||||
zstring slo, shi;
|
||||
if (u.str.is_string(e->get_arg(0), slo) && slo.length() == 1 &&
|
||||
u.str.is_string(e->get_arg(1), shi) && shi.length() == 1 &&
|
||||
slo[0] <= shi[0])
|
||||
return info(true, l_false, 1, true);
|
||||
// Symbolic or unknown: not classical
|
||||
return info(true, l_false, 1, false);
|
||||
}
|
||||
case OP_RE_FULL_CHAR_SET:
|
||||
case OP_RE_OF_PRED:
|
||||
//TBD: check if the character predicate contains uninterpreted symbols or is nonground or is unsat
|
||||
//TBD: check if the range is unsat
|
||||
return info(true, l_false, 1, false);
|
||||
case OP_RE_CONCAT:
|
||||
i1 = get_info_rec(e->get_arg(0));
|
||||
|
|
|
|||
|
|
@ -521,6 +521,8 @@ public:
|
|||
app* mk_to_re(expr* s) { return m.mk_app(m_fid, OP_SEQ_TO_RE, 1, &s); }
|
||||
app* mk_in_re(expr* s, expr* r) { return m.mk_app(m_fid, OP_SEQ_IN_RE, s, r); }
|
||||
app* mk_range(expr* s1, expr* s2) { return m.mk_app(m_fid, OP_RE_RANGE, s1, s2); }
|
||||
// Smart constructor: returns re.empty / str.to_re / re.range based on lo vs hi.
|
||||
app* mk_range(sort* re_sort, unsigned lo, unsigned hi);
|
||||
app* mk_concat(expr* r1, expr* r2) { return m.mk_app(m_fid, OP_RE_CONCAT, r1, r2); }
|
||||
app* mk_union(expr* r1, expr* r2) { return m.mk_app(m_fid, OP_RE_UNION, r1, r2); }
|
||||
app* mk_inter(expr* r1, expr* r2) { return m.mk_app(m_fid, OP_RE_INTERSECT, r1, r2); }
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ Author:
|
|||
class dependent_expr_state {
|
||||
unsigned m_qhead = 0;
|
||||
bool m_suffix_frozen = false;
|
||||
unsigned m_num_recfun = 0, m_num_lambdas = 0;
|
||||
unsigned m_num_recfun = 0;
|
||||
lbool m_has_quantifiers = l_undef;
|
||||
ast_mark m_frozen;
|
||||
func_decl_ref_vector m_frozen_trail;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Abstract:
|
|||
All variables x, y, z, .. can eventually be eliminated, but the tactic requires a global
|
||||
analysis between each elimination. We address this by using reference counts and maintaining
|
||||
a heap of reference counts.
|
||||
- it does not accomodate side constraints. The more general invertibility reduction methods, such
|
||||
- it does not accommodate side constraints. The more general invertibility reduction methods, such
|
||||
as those introduced for bit-vectors use side constraints.
|
||||
- it is not modular: we detach the expression invertion routines to self-contained code.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ It traverses the same sub-terms many times.
|
|||
|
||||
Outline of a presumably better scheme:
|
||||
|
||||
1. maintain map FV: term -> bit-set where bitset reprsents set of free variables. Assume the number of variables is bounded.
|
||||
1. maintain map FV: term -> bit-set where bitset represents set of free variables. Assume the number of variables is bounded.
|
||||
FV is built from initial terms.
|
||||
2. maintain parent: term -> term-list of parent occurrences.
|
||||
3. repeat
|
||||
|
|
|
|||
|
|
@ -17,45 +17,51 @@ Notes:
|
|||
--*/
|
||||
#pragma once
|
||||
|
||||
#define ATOMIC_CMD(CLS_NAME, NAME, DESCR, ACTION) \
|
||||
#define ATOMIC_CMD(CLS_NAME, NAME, DESCR, ACTION) \
|
||||
class CLS_NAME : public cmd { \
|
||||
public: \
|
||||
CLS_NAME():cmd(NAME) {} \
|
||||
char const * get_usage() const override { return 0; } \
|
||||
char const * get_descr(cmd_context & ctx) const override { \
|
||||
return DESCR; \
|
||||
} \
|
||||
unsigned get_arity() const override { return 0; } \
|
||||
void execute(cmd_context & ctx) override { ACTION } \
|
||||
}
|
||||
|
||||
#define UNARY_CMD(CLS_NAME, NAME, USAGE, DESCR, ARG_KIND, ARG_TYPE, ACTION) \
|
||||
class CLS_NAME : public cmd { \
|
||||
public: \
|
||||
CLS_NAME():cmd(NAME) {} \
|
||||
virtual char const * get_usage() const { return 0; } \
|
||||
virtual char const * get_descr(cmd_context & ctx) const { return DESCR; } \
|
||||
virtual unsigned get_arity() const { return 0; } \
|
||||
virtual void execute(cmd_context & ctx) { ACTION } \
|
||||
};
|
||||
|
||||
#define UNARY_CMD(CLS_NAME, NAME, USAGE, DESCR, ARG_KIND, ARG_TYPE, ACTION) \
|
||||
class CLS_NAME : public cmd { \
|
||||
public: \
|
||||
CLS_NAME():cmd(NAME) {} \
|
||||
virtual char const * get_usage() const { return USAGE; } \
|
||||
virtual char const * get_descr(cmd_context & ctx) const { return DESCR; } \
|
||||
virtual unsigned get_arity() const { return 1; } \
|
||||
virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return ARG_KIND; } \
|
||||
virtual void set_next_arg(cmd_context & ctx, ARG_TYPE arg) { ACTION } \
|
||||
char const * get_usage() const override { return USAGE; } \
|
||||
char const * get_descr(cmd_context & ctx) const override { \
|
||||
return DESCR; \
|
||||
} \
|
||||
unsigned get_arity() const override { return 1; } \
|
||||
cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { \
|
||||
return ARG_KIND; \
|
||||
} \
|
||||
void set_next_arg(cmd_context & ctx, ARG_TYPE arg) override { ACTION } \
|
||||
}
|
||||
|
||||
// Macro for creating commands where the first argument is a symbol
|
||||
// The second argument cannot be a symbol
|
||||
#define BINARY_SYM_CMD(CLS_NAME, NAME, USAGE, DESCR, ARG_KIND, ARG_TYPE, ACTION) \
|
||||
class CLS_NAME : public cmd { \
|
||||
symbol m_sym; \
|
||||
symbol m_sym; \
|
||||
public: \
|
||||
CLS_NAME():cmd(NAME) {} \
|
||||
virtual char const * get_usage() const { return USAGE; } \
|
||||
virtual char const * get_descr(cmd_context & ctx) const { return DESCR; } \
|
||||
virtual unsigned get_arity() const { return 2; } \
|
||||
virtual void prepare(cmd_context & ctx) { m_sym = symbol::null; } \
|
||||
virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { \
|
||||
return m_sym == symbol::null ? CPK_SYMBOL : ARG_KIND; \
|
||||
char const * get_usage() const override { return USAGE; } \
|
||||
char const * get_descr(cmd_context & ctx) const override { return DESCR; } \
|
||||
unsigned get_arity() const override { return 2; } \
|
||||
void prepare(cmd_context & ctx) override { m_sym = symbol::null; } \
|
||||
cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { \
|
||||
return m_sym == symbol::null ? CPK_SYMBOL : ARG_KIND; \
|
||||
} \
|
||||
virtual void set_next_arg(cmd_context & ctx, symbol const & s) { m_sym = s; } \
|
||||
virtual void set_next_arg(cmd_context & ctx, ARG_TYPE arg) { ACTION } \
|
||||
};
|
||||
|
||||
void set_next_arg(cmd_context & ctx, symbol const & s) override { m_sym = s; } \
|
||||
void set_next_arg(cmd_context & ctx, ARG_TYPE arg) override { ACTION } \
|
||||
}
|
||||
|
||||
|
||||
class ast;
|
||||
class expr;
|
||||
|
|
|
|||
|
|
@ -580,7 +580,7 @@ namespace lp {
|
|||
const lar_term* m_t;
|
||||
undo_add_term(imp& s, const lar_term* t) : m_s(s), m_t(t) {}
|
||||
|
||||
void undo() {
|
||||
void undo() override {
|
||||
m_s.undo_add_term_method(m_t);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ Author:
|
|||
Revision History:
|
||||
--*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "math/lp/int_solver.h"
|
||||
#include "math/lp/lar_solver.h"
|
||||
#include "math/lp/int_cube.h"
|
||||
|
|
@ -81,6 +84,252 @@ namespace lp {
|
|||
SASSERT(lp_status::OPTIMAL == lra.get_status() || lp_status::FEASIBLE == lra.get_status());
|
||||
}
|
||||
|
||||
// The largest cube test of Bromberger and Weidenbach:
|
||||
// maximize x_e subject to Ax + a'(x_e/2) <= b, x_e >= 0, where a'_i = ||a_i||_1,
|
||||
// with the 1-norm taken over the integer variables of the row.
|
||||
// The solution is the center z of a largest cube contained in the polyhedron.
|
||||
// If the maximal edge length is at least 1, then the rounding of z is
|
||||
// an integer solution; otherwise the rounding is checked, and possibly repaired,
|
||||
// against the original constraints.
|
||||
lia_move int_cube::find_largest_cube() {
|
||||
lia.settings().stats().m_lcube_calls++;
|
||||
TRACE(cube,
|
||||
for (unsigned j = 0; j < lra.number_of_vars(); ++j)
|
||||
lia.display_column(tout, j);
|
||||
tout << lra.constraints();
|
||||
);
|
||||
|
||||
lra.push();
|
||||
// The edge rows are ephemeral: suppress the add-term callback,
|
||||
// dioph_eq's reaction to it is not undone by pop().
|
||||
auto add_term_cb = lra.m_add_term_callback;
|
||||
lra.m_add_term_callback = nullptr;
|
||||
unsigned x_e = lra.add_var(UINT_MAX, false); // the edge length of the cube
|
||||
lra.add_var_bound(x_e, lconstraint_kind::GE, mpq(0));
|
||||
bool ok = add_cube_edge_rows(x_e);
|
||||
lra.m_add_term_callback = add_term_cb;
|
||||
if (!ok) {
|
||||
lra.pop();
|
||||
lra.set_status(lp_status::OPTIMAL);
|
||||
return lia_move::undef;
|
||||
}
|
||||
|
||||
lp_status st = lra.find_feasible_solution();
|
||||
if (st != lp_status::FEASIBLE && st != lp_status::OPTIMAL) {
|
||||
TRACE(cube, tout << "cannot find a feasible solution";);
|
||||
lra.pop();
|
||||
lra.move_non_basic_columns_to_bounds();
|
||||
// it can happen that we found an integer solution here
|
||||
return !lra.r_basis_has_inf_int()? lia_move::sat: lia_move::undef;
|
||||
}
|
||||
|
||||
impq e; // the maximal edge length
|
||||
st = lra.maximize_term(x_e, e, /*fix_int_cols*/ false);
|
||||
if (lia.settings().get_cancel_flag()) {
|
||||
lra.pop();
|
||||
return lia_move::undef;
|
||||
}
|
||||
if (st == lp_status::UNBOUNDED) {
|
||||
// infinite lattice width: the polyhedron contains cubes of arbitrary edge length
|
||||
lra.add_var_bound(x_e, lconstraint_kind::GE, mpq(1));
|
||||
st = lra.find_feasible_solution();
|
||||
if (st != lp_status::FEASIBLE && st != lp_status::OPTIMAL) {
|
||||
lra.pop();
|
||||
return lia_move::undef;
|
||||
}
|
||||
lra.pop();
|
||||
return sat_after_rounding();
|
||||
}
|
||||
TRACE(cube, tout << "max edge length = " << e << "\n";);
|
||||
if (e >= impq(mpq(1))) {
|
||||
lra.pop();
|
||||
return sat_after_rounding();
|
||||
}
|
||||
// the largest cube is smaller than the unit cube:
|
||||
// the rounded center is only a candidate
|
||||
lra.pop();
|
||||
return round_and_repair();
|
||||
}
|
||||
|
||||
bool int_cube::add_cube_edge_rows(unsigned x_e) {
|
||||
// snapshot the term columns: add_edge_rows_for_term appends to lra.terms()
|
||||
svector<unsigned> term_columns;
|
||||
for (const lar_term* t : lra.terms())
|
||||
term_columns.push_back(t->j());
|
||||
for (unsigned j : term_columns)
|
||||
if (!add_edge_rows_for_term(j, x_e)) {
|
||||
TRACE(cube, tout << "cannot add the edge rows";);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// i is the column index having the term
|
||||
bool int_cube::add_edge_rows_for_term(unsigned i, unsigned x_e) {
|
||||
if (!lra.column_associated_with_row(i))
|
||||
return true;
|
||||
const lar_term& t = lra.get_term(i);
|
||||
impq delta = get_cube_delta_for_term(t);
|
||||
TRACE(cube, lra.print_term_as_indices(t, tout); tout << ", delta = " << delta << "\n";);
|
||||
if (is_zero(delta))
|
||||
return true;
|
||||
if (!is_zero(delta.y))
|
||||
// the infinitesimal delta does not scale with x_e: tighten statically,
|
||||
// it is sound for any edge length
|
||||
return lra.tighten_term_bounds_by_delta(i, delta);
|
||||
if (lra.column_has_upper_bound(i)) {
|
||||
impq u = lra.get_upper_bound(i); // copy: add_term invalidates bound references
|
||||
vector<std::pair<mpq, unsigned>> coeffs;
|
||||
coeffs.push_back(std::make_pair(mpq(1), i));
|
||||
coeffs.push_back(std::make_pair(delta.x, x_e));
|
||||
unsigned s = lra.add_term(coeffs, UINT_MAX);
|
||||
lra.add_var_bound(s, is_zero(u.y) ? lconstraint_kind::LE : lconstraint_kind::LT, u.x);
|
||||
}
|
||||
if (lra.column_has_lower_bound(i)) {
|
||||
impq l = lra.get_lower_bound(i); // copy: add_term invalidates bound references
|
||||
vector<std::pair<mpq, unsigned>> coeffs;
|
||||
coeffs.push_back(std::make_pair(mpq(1), i));
|
||||
coeffs.push_back(std::make_pair(-delta.x, x_e));
|
||||
unsigned s = lra.add_term(coeffs, UINT_MAX);
|
||||
lra.add_var_bound(s, is_zero(l.y) ? lconstraint_kind::GE : lconstraint_kind::GT, l.x);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
lia_move int_cube::sat_after_rounding() {
|
||||
lra.round_to_integer_solution();
|
||||
lra.set_status(lp_status::FEASIBLE);
|
||||
SASSERT(lia.settings().get_cancel_flag() || lia.is_feasible());
|
||||
TRACE(cube, tout << "largest cube success";);
|
||||
lia.settings().stats().m_lcube_success++;
|
||||
return lia_move::sat;
|
||||
}
|
||||
|
||||
lia_move int_cube::round_and_repair() {
|
||||
lra.backup_x(); // remember the cube center
|
||||
vector<flip_candidate> flips;
|
||||
for (unsigned j = 0; j < lra.column_count(); ++j) {
|
||||
if (!lra.column_is_int(j) || lra.column_has_term(j))
|
||||
continue;
|
||||
const impq& v = lra.get_column_value(j);
|
||||
if (v.is_int())
|
||||
continue;
|
||||
flip_candidate f;
|
||||
f.m_j = j;
|
||||
f.m_lo = floor(v);
|
||||
flips.push_back(f);
|
||||
}
|
||||
lra.round_to_integer_solution();
|
||||
for (auto& f : flips)
|
||||
f.m_at_hi = lra.get_column_value(f.m_j).x > f.m_lo;
|
||||
if (repair_rounded_candidate(flips)) {
|
||||
lra.set_status(lp_status::FEASIBLE);
|
||||
SASSERT(lia.settings().get_cancel_flag() || lia.is_feasible());
|
||||
TRACE(cube, tout << "largest cube success";);
|
||||
lia.settings().stats().m_lcube_success++;
|
||||
return lia_move::sat;
|
||||
}
|
||||
// return to the cube center: an interior point of the polyhedron
|
||||
lra.restore_x();
|
||||
lra.set_status(lp_status::FEASIBLE);
|
||||
return lia_move::undef;
|
||||
}
|
||||
|
||||
// Checks the rounded center against the original constraints. On failure
|
||||
// searches the vertices of the lattice cell around the center greedily:
|
||||
// flip a coordinate between floor and ceiling to maximally decrease the
|
||||
// total bound violation, within a budget.
|
||||
bool int_cube::repair_rounded_candidate(vector<flip_candidate>& flips) {
|
||||
vector<bounded_row> rows;
|
||||
for (const lar_term* t : lra.terms()) {
|
||||
unsigned j = t->j();
|
||||
if (!lra.column_associated_with_row(j))
|
||||
continue;
|
||||
if (!lra.column_has_upper_bound(j) && !lra.column_has_lower_bound(j))
|
||||
continue;
|
||||
bounded_row r;
|
||||
r.m_j = j;
|
||||
r.m_val = t->apply(lra.r_x());
|
||||
rows.push_back(r);
|
||||
}
|
||||
auto row_violation = [&](unsigned ri, const impq& v) {
|
||||
impq w;
|
||||
unsigned j = rows[ri].m_j;
|
||||
if (lra.column_has_upper_bound(j) && v > lra.get_upper_bound(j))
|
||||
w += v - lra.get_upper_bound(j);
|
||||
if (lra.column_has_lower_bound(j) && v < lra.get_lower_bound(j))
|
||||
w += lra.get_lower_bound(j) - v;
|
||||
return w;
|
||||
};
|
||||
impq violation;
|
||||
for (unsigned ri = 0; ri < rows.size(); ++ri)
|
||||
violation += row_violation(ri, rows[ri].m_val);
|
||||
if (is_zero(violation))
|
||||
return true; // the rounded center fits as it is
|
||||
if (flips.empty())
|
||||
return false;
|
||||
|
||||
std::unordered_map<unsigned, unsigned> flip_of_var;
|
||||
for (unsigned fi = 0; fi < flips.size(); ++fi)
|
||||
flip_of_var[flips[fi].m_j] = fi;
|
||||
// occurrences of the flip candidates in the bounded rows
|
||||
vector<vector<std::pair<unsigned, mpq>>> occs;
|
||||
occs.resize(flips.size());
|
||||
for (unsigned ri = 0; ri < rows.size(); ++ri) {
|
||||
const lar_term& t = lra.get_term(rows[ri].m_j);
|
||||
for (lar_term::ival p : t) {
|
||||
auto it = flip_of_var.find(p.j());
|
||||
if (it != flip_of_var.end())
|
||||
occs[it->second].push_back(std::make_pair(ri, p.coeff()));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned budget = std::min(2 * flips.size(), lia.settings().lcube_flips());
|
||||
bool flipped = false;
|
||||
while (!is_zero(violation) && budget-- > 0) {
|
||||
unsigned best_fi = UINT_MAX;
|
||||
impq best_gain;
|
||||
for (unsigned fi = 0; fi < flips.size(); ++fi) {
|
||||
if (occs[fi].empty())
|
||||
continue;
|
||||
mpq step = flips[fi].m_at_hi ? mpq(-1) : mpq(1);
|
||||
impq gain;
|
||||
for (const auto& o : occs[fi]) {
|
||||
const impq& v = rows[o.first].m_val;
|
||||
gain += row_violation(o.first, v + impq(step * o.second)) - row_violation(o.first, v);
|
||||
}
|
||||
if (gain < best_gain) {
|
||||
best_gain = gain;
|
||||
best_fi = fi;
|
||||
}
|
||||
}
|
||||
if (best_fi == UINT_MAX)
|
||||
return false; // no flip decreases the violation
|
||||
mpq step = flips[best_fi].m_at_hi ? mpq(-1) : mpq(1);
|
||||
for (const auto& o : occs[best_fi])
|
||||
rows[o.first].m_val += impq(step * o.second);
|
||||
flips[best_fi].m_at_hi = !flips[best_fi].m_at_hi;
|
||||
violation += best_gain;
|
||||
flipped = true;
|
||||
TRACE(cube, tout << "flipped column " << flips[best_fi].m_j << ", violation = " << violation << "\n";);
|
||||
}
|
||||
if (!is_zero(violation))
|
||||
return false;
|
||||
|
||||
// apply the repaired candidate
|
||||
for (const auto& f : flips)
|
||||
lra.set_column_value(f.m_j, impq(f.m_at_hi ? f.m_lo + 1 : f.m_lo));
|
||||
for (const lar_term* t : lra.terms()) {
|
||||
unsigned j = t->j();
|
||||
if (!lra.column_associated_with_row(j))
|
||||
continue;
|
||||
lra.set_column_value(j, t->apply(lra.r_x()));
|
||||
}
|
||||
if (flipped)
|
||||
lia.settings().stats().m_lcube_flip_success++;
|
||||
return true;
|
||||
}
|
||||
|
||||
impq int_cube::get_cube_delta_for_term(const lar_term& t) const {
|
||||
if (t.size() == 2) {
|
||||
bool seen_minus = false;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,15 @@ Abstract:
|
|||
Cube finder
|
||||
|
||||
This routine attempts to find a feasible integer solution
|
||||
by tightnening bounds and running an LRA solver on the
|
||||
by tightnening bounds and running an LRA solver on the
|
||||
tighter system.
|
||||
|
||||
find_largest_cube() implements the largest cube test of
|
||||
Bromberger and Weidenbach (Fast Cube Tests for LIA Constraint
|
||||
Solving, IJCAR 2016): a fresh variable x_e for the cube edge
|
||||
length is introduced and maximized; the center of the largest
|
||||
cube is rounded to a candidate integer solution.
|
||||
|
||||
Author:
|
||||
Nikolaj Bjorner (nbjorner)
|
||||
Lev Nachmanson (levnach)
|
||||
|
|
@ -21,7 +27,10 @@ Revision History:
|
|||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "util/vector.h"
|
||||
#include "math/lp/lia_move.h"
|
||||
#include "math/lp/numeric_pair.h"
|
||||
#include "math/lp/lar_term.h"
|
||||
|
||||
namespace lp {
|
||||
class int_solver;
|
||||
|
|
@ -29,12 +38,30 @@ namespace lp {
|
|||
class int_cube {
|
||||
class int_solver& lia;
|
||||
class lar_solver& lra;
|
||||
// a fractional integer coordinate of the cube center:
|
||||
// the candidate value is m_lo or m_lo + 1
|
||||
struct flip_candidate {
|
||||
unsigned m_j = 0;
|
||||
mpq m_lo;
|
||||
bool m_at_hi = false;
|
||||
};
|
||||
// a term column with at least one bound, tracked during the repair
|
||||
struct bounded_row {
|
||||
unsigned m_j = 0;
|
||||
impq m_val;
|
||||
};
|
||||
bool tighten_term_for_cube(unsigned i);
|
||||
bool tighten_terms_for_cube();
|
||||
void find_feasible_solution();
|
||||
impq get_cube_delta_for_term(const lar_term& t) const;
|
||||
bool add_edge_rows_for_term(unsigned i, unsigned x_e);
|
||||
bool add_cube_edge_rows(unsigned x_e);
|
||||
lia_move sat_after_rounding();
|
||||
lia_move round_and_repair();
|
||||
bool repair_rounded_candidate(vector<flip_candidate>& flips);
|
||||
public:
|
||||
int_cube(int_solver& lia);
|
||||
lia_move operator()();
|
||||
lia_move find_largest_cube();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,11 @@ namespace lp {
|
|||
dioph_eq m_dio;
|
||||
int_gcd_test m_gcd;
|
||||
unsigned m_initial_dio_calls_period;
|
||||
|
||||
unsigned m_lcube_period;
|
||||
// The number of consecutive genuine dio calls that returned undef, reset on a dio
|
||||
// conflict. Drives the decision to start running Gomory with dio.
|
||||
unsigned m_dio_undef_in_a_row = 0;
|
||||
|
||||
bool column_is_int_inf(unsigned j) const {
|
||||
return lra.column_is_int(j) && (!lia.value_is_int(j));
|
||||
}
|
||||
|
|
@ -52,7 +56,8 @@ namespace lp {
|
|||
imp(int_solver& lia): lia(lia), lra(lia.lra), lrac(lia.lrac), m_hnf_cutter(lia), m_dio(lia), m_gcd(lia) {
|
||||
m_hnf_cut_period = settings().hnf_cut_period();
|
||||
m_initial_dio_calls_period = settings().dio_calls_period();
|
||||
}
|
||||
m_lcube_period = settings().m_int_find_cube_period;
|
||||
}
|
||||
|
||||
bool has_lower(unsigned j) const {
|
||||
switch (lrac.m_column_types()[j]) {
|
||||
|
|
@ -176,14 +181,16 @@ namespace lp {
|
|||
if (r == lia_move::conflict) {
|
||||
m_dio.explain(*this->m_ex);
|
||||
lia.settings().dio_calls_period() = m_initial_dio_calls_period;
|
||||
lia.settings().dio_enable_gomory_cuts() = false;
|
||||
m_dio_undef_in_a_row = 0;
|
||||
lia.settings().stop_running_gomory_with_dio(); // dio was productive: stop running Gomory
|
||||
lia.settings().set_run_gcd_test(false);
|
||||
return lia_move::conflict;
|
||||
}
|
||||
if (r == lia_move::undef) {
|
||||
lia.settings().dio_calls_period() *= 2;
|
||||
if (lra.settings().dio_calls_period() >= 16) {
|
||||
lia.settings().dio_enable_gomory_cuts() = true;
|
||||
lia.settings().dio_calls_period() *= 2; // throttle dio scheduling on failure
|
||||
++m_dio_undef_in_a_row;
|
||||
if (m_dio_undef_in_a_row >= lra.settings().dio_gomory_enable_period()) {
|
||||
lia.settings().start_running_gomory_with_dio(); // dio persistently unproductive: start running Gomory
|
||||
lia.settings().set_run_gcd_test(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -191,26 +198,66 @@ namespace lp {
|
|||
}
|
||||
|
||||
lp_settings& settings() { return lra.settings(); }
|
||||
|
||||
|
||||
// Decide whether a periodic heuristic fires on this call. When
|
||||
// random_hammers is enabled the gate is drawn at random with the same
|
||||
// 1/period expected rate instead of a deterministic "every k-th call"
|
||||
// modulus: a deterministic period can phase-lock with the search on
|
||||
// some families and drown the solver in conflicts while another handler
|
||||
// is starved; randomizing the gate breaks that resonance.
|
||||
bool hit_period(unsigned period) {
|
||||
if (period <= 1)
|
||||
return true;
|
||||
if (settings().random_hammers())
|
||||
return settings().random_next(period) == 0;
|
||||
return m_number_of_calls % period == 0;
|
||||
}
|
||||
|
||||
bool should_find_cube() {
|
||||
return m_number_of_calls % settings().m_int_find_cube_period == 0;
|
||||
return hit_period(settings().m_int_find_cube_period);
|
||||
}
|
||||
|
||||
// The largest cube test is throttled exponentially: when the polyhedron
|
||||
// does not contain a large enough cube it is unlikely to contain one
|
||||
// later, after more constraints are added, so each failure doubles the
|
||||
// period and a success resets it.
|
||||
bool should_find_lcube() {
|
||||
return settings().lcube() && hit_period(m_lcube_period);
|
||||
}
|
||||
|
||||
lia_move find_lcube() {
|
||||
lia_move r = int_cube(lia).find_largest_cube();
|
||||
if (r == lia_move::undef) {
|
||||
if (m_lcube_period < (1u << 30))
|
||||
m_lcube_period *= 2;
|
||||
}
|
||||
else
|
||||
m_lcube_period = settings().m_int_find_cube_period;
|
||||
return r;
|
||||
}
|
||||
|
||||
bool should_gomory_cut() {
|
||||
bool dio_allows_gomory = !settings().dio() || settings().dio_enable_gomory_cuts() ||
|
||||
m_dio.some_terms_are_ignored();
|
||||
return dio_allows_gomory && m_number_of_calls % settings().m_int_gomory_cut_period == 0;
|
||||
return dio_allows_gomory && hit_period(settings().m_int_gomory_cut_period);
|
||||
}
|
||||
|
||||
bool should_solve_dioph_eq() {
|
||||
return lia.settings().dio() && (m_number_of_calls % settings().dio_calls_period() == 0);
|
||||
bool ret = lia.settings().dio() && hit_period(settings().dio_calls_period());
|
||||
if (!ret && lia.settings().dio_calls_period() > m_initial_dio_calls_period) {
|
||||
unsigned dec = settings().dio_calls_period_decrease();
|
||||
unsigned& period = lia.settings().dio_calls_period();
|
||||
period = period > m_initial_dio_calls_period + dec ? period - dec : m_initial_dio_calls_period;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// HNF
|
||||
|
||||
bool should_hnf_cut() {
|
||||
return (!settings().dio() || settings().dio_enable_hnf_cuts())
|
||||
&& settings().enable_hnf() && m_number_of_calls % settings().hnf_cut_period() == 0;
|
||||
&& settings().enable_hnf() && hit_period(settings().hnf_cut_period());
|
||||
}
|
||||
|
||||
lia_move hnf_cut() {
|
||||
|
|
@ -245,11 +292,12 @@ namespace lp {
|
|||
|
||||
++m_number_of_calls;
|
||||
if (r == lia_move::undef) r = patch_basic_columns();
|
||||
if (r == lia_move::undef && should_find_cube()) r = int_cube(lia)();
|
||||
if (r == lia_move::undef && should_find_cube()) r = int_cube(lia)();
|
||||
if (r == lia_move::undef && should_find_lcube()) r = find_lcube();
|
||||
if (r == lia_move::undef) lra.move_non_basic_columns_to_bounds();
|
||||
if (r == lia_move::undef && should_hnf_cut()) r = hnf_cut();
|
||||
if (r == lia_move::undef && should_gomory_cut()) r = gomory(lia).get_gomory_cuts(2);
|
||||
if (r == lia_move::undef && should_solve_dioph_eq()) r = solve_dioph_eq();
|
||||
if (r == lia_move::undef && should_gomory_cut()) r = gomory(lia).get_gomory_cuts(2);
|
||||
if (r == lia_move::undef) r = int_branch(lia)();
|
||||
if (settings().get_cancel_flag()) r = lia_move::undef;
|
||||
return r;
|
||||
|
|
|
|||
|
|
@ -864,7 +864,7 @@ namespace lp {
|
|||
}
|
||||
|
||||
lp_status lar_solver::maximize_term(unsigned j,
|
||||
impq& term_max) {
|
||||
impq& term_max, bool fix_int_cols) {
|
||||
TRACE(lar_solver, print_values(tout););
|
||||
SASSERT(get_core_solver().m_r_solver.calc_current_x_is_feasible_include_non_basis());
|
||||
lar_term term = get_term_to_maximize(j);
|
||||
|
|
@ -879,6 +879,11 @@ namespace lp {
|
|||
return lp_status::UNBOUNDED;
|
||||
}
|
||||
|
||||
if (!fix_int_cols) {
|
||||
set_status(lp_status::OPTIMAL);
|
||||
return lp_status::OPTIMAL;
|
||||
}
|
||||
|
||||
impq opt_val = term_max;
|
||||
|
||||
bool change = false;
|
||||
|
|
|
|||
|
|
@ -205,7 +205,9 @@ public:
|
|||
set_column_value(j, v);
|
||||
}
|
||||
|
||||
lp_status maximize_term(unsigned j_or_term, impq& term_max);
|
||||
// fix_int_cols: after maximizing try to move the integer columns to integer values;
|
||||
// pass false to keep the optimal (possibly fractional) vertex intact, e.g., for the largest cube test
|
||||
lp_status maximize_term(unsigned j_or_term, impq& term_max, bool fix_int_cols);
|
||||
|
||||
core_solver_pretty_printer<lp::mpq, lp::impq> pp(std::ostream& out) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,15 @@ def_module_params(module_name='lp',
|
|||
params=(('dio', BOOL, True, 'use Diophantine equalities'),
|
||||
('dio_branching_period', UINT, 100, 'Period of calling branching on undef in Diophantine handler'),
|
||||
('dio_cuts_enable_gomory', BOOL, False, 'enable Gomory cuts together with Diophantine cuts, only relevant when dioph_eq is true'),
|
||||
('dio_gomory_enable_period', UINT, 16, 'number of consecutive unproductive (undef) Diophantine-handler calls after which the controller starts running Gomory cuts and the gcd test alongside dio; a dio conflict resets the count and stops them; set very large to never start them this way so Gomory follows dio_cuts_enable_gomory only'),
|
||||
('dio_cuts_enable_hnf', BOOL, True, 'enable hnf cuts together with Diophantine cuts, only relevant when dioph_eq is true'),
|
||||
('dio_ignore_big_nums', BOOL, True, 'Ignore the terms with big numbers in the Diophantine handler, only relevant when dioph_eq is true'),
|
||||
('dio_calls_period', UINT, 1, 'Period of calling the Diophantine handler in the final_check()'),
|
||||
('dio_run_gcd', BOOL, False, 'Run the GCD heuristic if dio is on, if dio is disabled the option is not used'),
|
||||
('dio_calls_period_decrease', UINT, 2, 'Amount by which dio_calls_period is decreased on each final_check() call where the Diophantine handler is not triggered, until it returns to its initial value'),
|
||||
('dio_run_gcd', BOOL, False, 'Run the GCD heuristic if dio is on, if dio is disabled the option is not used'),
|
||||
('lcube', BOOL, True, 'use the largest cube test for integer feasibility'),
|
||||
('lcube_flips', UINT, 16, 'maximal number of coordinate flips when repairing the rounded largest cube center, only relevant when lcube is true'),
|
||||
('int_hammer_period', UINT, 4, 'period (in final_check calls) for the integer cut/cube heuristics (find_cube, hnf, gomory); a smaller value calls them more often'),
|
||||
('random_hammers', BOOL, True, 'draw the periodic integer heuristic gates (find_cube, lcube, hnf, gomory, dio) at random with the same 1/period rate instead of a deterministic every-k-th-call modulus'),
|
||||
))
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,21 @@ void lp::lp_settings::updt_params(params_ref const& _p) {
|
|||
auto eps = p.arith_epsilon();
|
||||
m_epsilon = rational(std::max(1, (int)(100000*eps)), 100000);
|
||||
m_dio = lp_p.dio();
|
||||
m_dio_enable_gomory_cuts = lp_p.dio_cuts_enable_gomory();
|
||||
m_dio_cuts_enable_gomory = lp_p.dio_cuts_enable_gomory();
|
||||
m_dio_gomory_enable_period = lp_p.dio_gomory_enable_period();
|
||||
m_dio_enable_hnf_cuts = lp_p.dio_cuts_enable_hnf();
|
||||
m_dump_bound_lemmas = p.arith_dump_bound_lemmas();
|
||||
m_dio_ignore_big_nums = lp_p.dio_ignore_big_nums();
|
||||
m_dio_calls_period = lp_p.dio_calls_period();
|
||||
m_dio_calls_period_decrease = lp_p.dio_calls_period_decrease();
|
||||
m_dio_run_gcd = lp_p.dio_run_gcd();
|
||||
m_random_hammers = lp_p.random_hammers();
|
||||
m_lcube = lp_p.lcube();
|
||||
m_lcube_flips = lp_p.lcube_flips();
|
||||
unsigned hammer_period = lp_p.int_hammer_period();
|
||||
SASSERT(hammer_period != 0);
|
||||
m_int_find_cube_period = hammer_period;
|
||||
m_int_gomory_cut_period = hammer_period;
|
||||
m_hnf_cut_period = hammer_period;
|
||||
m_max_conflicts = p.max_conflicts();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,9 @@ struct statistics {
|
|||
unsigned m_gcd_conflicts = 0;
|
||||
unsigned m_cube_calls = 0;
|
||||
unsigned m_cube_success = 0;
|
||||
unsigned m_lcube_calls = 0;
|
||||
unsigned m_lcube_success = 0;
|
||||
unsigned m_lcube_flip_success = 0;
|
||||
unsigned m_patches = 0;
|
||||
unsigned m_patches_success = 0;
|
||||
unsigned m_hnf_cutter_calls = 0;
|
||||
|
|
@ -152,6 +155,9 @@ struct statistics {
|
|||
st.update("arith-gcd-conflict", m_gcd_conflicts);
|
||||
st.update("arith-cube-calls", m_cube_calls);
|
||||
st.update("arith-cube-success", m_cube_success);
|
||||
st.update("arith-lcube-calls", m_lcube_calls);
|
||||
st.update("arith-lcube-success", m_lcube_success);
|
||||
st.update("arith-lcube-flip-success", m_lcube_flip_success);
|
||||
st.update("arith-patches", m_patches);
|
||||
st.update("arith-patches-success", m_patches_success);
|
||||
st.update("arith-hnf-calls", m_hnf_cutter_calls);
|
||||
|
|
@ -252,15 +258,27 @@ private:
|
|||
bool m_print_external_var_name = false;
|
||||
bool m_propagate_eqs = false;
|
||||
bool m_dio = false;
|
||||
bool m_dio_enable_gomory_cuts = false;
|
||||
bool m_dio_cuts_enable_gomory = false;
|
||||
bool m_run_gomory_with_dio = false;
|
||||
unsigned m_dio_gomory_enable_period = 16;
|
||||
bool m_dio_enable_hnf_cuts = true;
|
||||
bool m_dump_bound_lemmas = false;
|
||||
bool m_dio_ignore_big_nums = false;
|
||||
unsigned m_dio_calls_period = 4;
|
||||
unsigned m_dio_calls_period_decrease = 2;
|
||||
bool m_dio_run_gcd = true;
|
||||
bool m_random_hammers = true;
|
||||
bool m_lcube = true;
|
||||
unsigned m_lcube_flips = 16;
|
||||
public:
|
||||
bool lcube() const { return m_lcube; }
|
||||
unsigned lcube_flips() const { return m_lcube_flips; }
|
||||
unsigned dio_calls_period() const { return m_dio_calls_period; }
|
||||
unsigned & dio_calls_period() { return m_dio_calls_period; }
|
||||
unsigned dio_calls_period_decrease() const { return m_dio_calls_period_decrease; }
|
||||
unsigned & dio_calls_period_decrease() { return m_dio_calls_period_decrease; }
|
||||
bool random_hammers() const { return m_random_hammers; }
|
||||
bool & random_hammers() { return m_random_hammers; }
|
||||
bool print_external_var_name() const { return m_print_external_var_name; }
|
||||
bool propagate_eqs() const { return m_propagate_eqs;}
|
||||
unsigned hnf_cut_period() const { return m_hnf_cut_period; }
|
||||
|
|
@ -268,8 +286,19 @@ public:
|
|||
unsigned random_next() { return m_rand(); }
|
||||
unsigned random_next(unsigned u ) { return m_rand(u); }
|
||||
bool dio() { return m_dio; }
|
||||
bool & dio_enable_gomory_cuts() { return m_dio_enable_gomory_cuts; }
|
||||
bool dio_enable_gomory_cuts() const { return m_dio && m_dio_enable_gomory_cuts; }
|
||||
// Static config: did the user request Gomory cuts up front? (lp.dio_cuts_enable_gomory)
|
||||
bool dio_cuts_enable_gomory() const { return m_dio_cuts_enable_gomory; }
|
||||
// dio_calls_period at which the Diophantine back-off starts running Gomory (lp.dio_gomory_enable_period)
|
||||
unsigned dio_gomory_enable_period() const { return m_dio_gomory_enable_period; }
|
||||
// Runtime flag owned by the Diophantine controller, kept separate from the static
|
||||
// config above so toggling it never clobbers the user's parameter: once dio has
|
||||
// backed off enough it starts running Gomory cuts alongside dio, and a productive
|
||||
// dio conflict stops them again.
|
||||
void start_running_gomory_with_dio() { m_run_gomory_with_dio = true; }
|
||||
void stop_running_gomory_with_dio() { m_run_gomory_with_dio = false; }
|
||||
// Effective state read by should_gomory_cut(): allowed if either the user enabled it
|
||||
// statically or the dio controller started running it, guarded by dio being active.
|
||||
bool dio_enable_gomory_cuts() const { return m_dio && (m_dio_cuts_enable_gomory || m_run_gomory_with_dio); }
|
||||
bool dio_run_gcd() const { return m_dio && m_dio_run_gcd; }
|
||||
bool dio_enable_hnf_cuts() const { return m_dio && m_dio_enable_hnf_cuts; }
|
||||
bool dio_ignore_big_nums() const { return m_dio_ignore_big_nums; }
|
||||
|
|
|
|||
|
|
@ -60,6 +60,14 @@ std::ostream& operator<<(std::ostream& out, const row_strip<T>& r) {
|
|||
return out << "\n";
|
||||
}
|
||||
|
||||
// Below, static_matrix has a superclass when Z3DEBUG is set, and some
|
||||
// methods are overrides in that case.
|
||||
#ifdef Z3DEBUG
|
||||
#define DEBUG_OVERRIDE override
|
||||
#else
|
||||
#define DEBUG_OVERRIDE
|
||||
#endif
|
||||
|
||||
// each assignment for this matrix should be issued only once!!!
|
||||
template <typename T, typename X>
|
||||
class static_matrix
|
||||
|
|
@ -119,9 +127,13 @@ public:
|
|||
|
||||
void init_empty_matrix(unsigned m, unsigned n);
|
||||
|
||||
unsigned row_count() const { return static_cast<unsigned>(m_rows.size()); }
|
||||
unsigned row_count() const DEBUG_OVERRIDE {
|
||||
return static_cast<unsigned>(m_rows.size());
|
||||
}
|
||||
|
||||
unsigned column_count() const { return static_cast<unsigned>(m_columns.size()); }
|
||||
unsigned column_count() const DEBUG_OVERRIDE {
|
||||
return static_cast<unsigned>(m_columns.size());
|
||||
}
|
||||
|
||||
unsigned lowest_row_in_column(unsigned col);
|
||||
|
||||
|
|
@ -197,7 +209,7 @@ public:
|
|||
|
||||
void cross_out_row_from_column(unsigned col, unsigned k);
|
||||
|
||||
T get_elem(unsigned i, unsigned j) const;
|
||||
T get_elem(unsigned i, unsigned j) const DEBUG_OVERRIDE;
|
||||
|
||||
|
||||
unsigned number_of_non_zeroes_in_column(unsigned j) const { return static_cast<unsigned>(m_columns[j].size()); }
|
||||
|
|
@ -218,8 +230,8 @@ public:
|
|||
#ifdef Z3DEBUG
|
||||
unsigned get_number_of_rows() const { return row_count(); }
|
||||
unsigned get_number_of_columns() const { return column_count(); }
|
||||
virtual void set_number_of_rows(unsigned /*m*/) { }
|
||||
virtual void set_number_of_columns(unsigned /*n*/) { }
|
||||
void set_number_of_rows(unsigned /*m*/) override { }
|
||||
void set_number_of_columns(unsigned /*n*/) override { }
|
||||
#endif
|
||||
|
||||
T get_balance() const;
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ expr * datatype_factory::get_almost_fresh_value(sort * s) {
|
|||
unsigned num = constructor->get_arity();
|
||||
for (unsigned i = 0; i < num; ++i) {
|
||||
sort * s_arg = constructor->get_domain(i);
|
||||
if (!found_fresh_arg && (!m_util.is_datatype(s_arg) || !m_util.are_siblings(s, s_arg))) {
|
||||
if (!found_fresh_arg && (!m_util.is_datatype(s_arg) || !m_util.are_siblings(s, s_arg) || !m_util.is_recursive(s_arg))) {
|
||||
expr * new_arg = m_model.get_fresh_value(s_arg);
|
||||
if (new_arg != nullptr) {
|
||||
found_fresh_arg = true;
|
||||
|
|
@ -105,7 +105,7 @@ expr * datatype_factory::get_almost_fresh_value(sort * s) {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (!found_fresh_arg && m_util.is_datatype(s_arg) && m_util.are_siblings(s, s_arg)) {
|
||||
if (!found_fresh_arg && m_util.is_datatype(s_arg) && m_util.are_siblings(s, s_arg) && m_util.is_recursive(s_arg)) {
|
||||
recursive = true;
|
||||
expr * last_fresh = get_last_fresh_value(s_arg);
|
||||
args.push_back(last_fresh);
|
||||
|
|
|
|||
|
|
@ -231,10 +231,8 @@ void func_interp::insert_new_entry(expr * const * args, expr * r) {
|
|||
m_args_are_values = false;
|
||||
m_entries.push_back(new_entry);
|
||||
if (!m_entry_table && m_entries.size() > 500) {
|
||||
m_entry_table = alloc(entry_table, 1024,
|
||||
func_entry_hash(m_arity), func_entry_eq(m_arity));
|
||||
for (func_entry* curr : m_entries)
|
||||
m_entry_table->insert(curr);
|
||||
init_table();
|
||||
|
||||
ptr_vector<expr> null_args;
|
||||
null_args.resize(m_arity, nullptr);
|
||||
m_key = func_entry::mk(m(), m_arity, null_args.data(), nullptr);
|
||||
|
|
@ -243,6 +241,12 @@ void func_interp::insert_new_entry(expr * const * args, expr * r) {
|
|||
m_entry_table->insert(new_entry);
|
||||
}
|
||||
|
||||
void func_interp::init_table() {
|
||||
m_entry_table = alloc(entry_table, 1024, func_entry_hash(m_arity), func_entry_eq(m_arity));
|
||||
for (func_entry *curr : m_entries)
|
||||
m_entry_table->insert(curr);
|
||||
}
|
||||
|
||||
void func_interp::del_entry(unsigned idx) {
|
||||
auto* e = m_entries[idx];
|
||||
if (m_entry_table)
|
||||
|
|
@ -307,6 +311,12 @@ void func_interp::compress() {
|
|||
if (j < m_entries.size()) {
|
||||
reset_interp_cache();
|
||||
m_entries.shrink(j);
|
||||
if (m_entry_table) {
|
||||
dealloc(m_entry_table);
|
||||
m_entry_table = nullptr;
|
||||
if (m_entries.size() > 500)
|
||||
init_table();
|
||||
}
|
||||
}
|
||||
// other compression, if else is a default branch.
|
||||
// or function encode identity.
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class func_entry {
|
|||
// m_result and m_args[i] must be ground terms.
|
||||
|
||||
expr * m_result;
|
||||
expr * m_args[];
|
||||
expr * m_args[0];
|
||||
|
||||
static unsigned get_obj_size(unsigned arity) { return sizeof(func_entry) + arity * sizeof(expr*); }
|
||||
func_entry(ast_manager & m, unsigned arity, expr * const * args, expr * result);
|
||||
|
|
@ -104,6 +104,8 @@ class func_interp {
|
|||
|
||||
void reset_interp_cache();
|
||||
|
||||
void init_table();
|
||||
|
||||
expr * get_interp_core() const;
|
||||
|
||||
expr_ref get_array_interp_core(func_decl * f) const;
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ void non_auf_macro_solver::collect_candidates(ptr_vector<quantifier> const& qs,
|
|||
TRACE(model_finder, tout << "considering macro for: " << f->get_name() << "\n";
|
||||
m->display(tout); tout << "\n";);
|
||||
if (m->is_unconditional() && (!qi->is_auf() || m->get_weight() >= m_mbqi_force_template)) {
|
||||
full_macros.insert(f, std::make_pair(m, q));
|
||||
full_macros.insert(f, {m, q});
|
||||
cond_macros.erase(f);
|
||||
}
|
||||
else if (!full_macros.contains(f) && !qi->is_auf())
|
||||
|
|
@ -524,10 +524,8 @@ void non_auf_macro_solver::collect_candidates(ptr_vector<quantifier> const& qs,
|
|||
}
|
||||
|
||||
void non_auf_macro_solver::process_full_macros(obj_map<func_decl, mq_pair> const& full_macros, obj_hashtable<quantifier>& removed) {
|
||||
for (auto const& kv : full_macros) {
|
||||
func_decl* f = kv.m_key;
|
||||
cond_macro* m = kv.m_value.first;
|
||||
quantifier* q = kv.m_value.second;
|
||||
for (auto const &[f, v] : full_macros) {
|
||||
auto [m, q] = v;
|
||||
SASSERT(m->is_unconditional());
|
||||
if (add_macro(f, m->get_def())) {
|
||||
get_qinfo(q)->set_the_one(f);
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ namespace nlsat {
|
|||
new_set->m_full = full;
|
||||
new_set->m_ref_count = 0;
|
||||
new_set->m_num_intervals = sz;
|
||||
memcpy(new_set->m_intervals, buf.data(), sizeof(interval)*sz);
|
||||
memcpy(static_cast<void*>(new_set->m_intervals), static_cast<const void*>(buf.data()), sizeof(interval)*sz);
|
||||
return new_set;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -203,6 +203,8 @@ namespace array {
|
|||
ctx.push_vec(d.m_parent_lambdas, lambda);
|
||||
if (should_prop_upward(d))
|
||||
propagate_select_axioms(d, lambda);
|
||||
if (d.m_has_default)
|
||||
push_axiom(default_axiom(lambda));
|
||||
}
|
||||
|
||||
void solver::add_parent_default(theory_var v) {
|
||||
|
|
|
|||
|
|
@ -4672,7 +4672,6 @@ namespace smt {
|
|||
theory_id th_id = l->get_id();
|
||||
|
||||
for (enode * parent : enode::parents(n)) {
|
||||
auto p = parent->get_expr();
|
||||
family_id fid = parent->get_family_id();
|
||||
if (fid != th_id && fid != m.get_basic_family_id()) {
|
||||
if (is_beta_redex(parent, n))
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ namespace smt {
|
|||
|
||||
if (use_inv) {
|
||||
unsigned sk_term_gen = 0;
|
||||
expr * sk_term = m_model_finder.get_inv(q, i, sk_value, sk_term_gen);
|
||||
expr * sk_term = m_model_finder.get_inv(q, i, sk_value, *cex, sk_term_gen);
|
||||
if (sk_term != nullptr) {
|
||||
TRACE(model_checker, tout << "Found inverse " << mk_pp(sk_term, m) << "\n";);
|
||||
SASSERT(!m.is_model_value(sk_term));
|
||||
|
|
@ -233,15 +233,10 @@ namespace smt {
|
|||
}
|
||||
else {
|
||||
expr * sk_term = get_term_from_ctx(sk_value);
|
||||
func_decl * f = nullptr;
|
||||
if (sk_term != nullptr) {
|
||||
TRACE(model_checker, tout << "sk term " << mk_pp(sk_term, m) << "\n");
|
||||
sk_value = sk_term;
|
||||
}
|
||||
// last ditch: am I an array?
|
||||
else if (false && autil.is_as_array(sk_value, f) && cex->get_func_interp(f) && cex->get_func_interp(f)->get_array_interp(f)) {
|
||||
sk_value = cex->get_func_interp(f)->get_array_interp(f);
|
||||
}
|
||||
|
||||
}
|
||||
if (contains_model_value(sk_value)) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Revision History:
|
|||
--*/
|
||||
#include "util/backtrackable_set.h"
|
||||
#include "ast/ast_util.h"
|
||||
#include "ast/has_free_vars.h"
|
||||
#include "ast/macros/macro_util.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/bv_decl_plugin.h"
|
||||
|
|
@ -31,6 +32,7 @@ Revision History:
|
|||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/well_sorted.h"
|
||||
#include "ast/ast_smt2_pp.h"
|
||||
#include "ast/rewriter/term_enumeration.h"
|
||||
#include "model/model_pp.h"
|
||||
#include "model/model_macro_solver.h"
|
||||
#include "smt/smt_model_finder.h"
|
||||
|
|
@ -107,9 +109,15 @@ namespace smt {
|
|||
}
|
||||
}
|
||||
|
||||
expr* get_inv(expr* v) const {
|
||||
expr* get_inv(expr* v, model& mdl) const {
|
||||
expr* t = nullptr;
|
||||
m_inv.find(v, t);
|
||||
if (!t) {
|
||||
for (auto [k, term] : m_inv) {
|
||||
if (mdl.are_equal(k, v))
|
||||
return term;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
|
|
@ -120,14 +128,11 @@ namespace smt {
|
|||
}
|
||||
|
||||
void mk_inverse(evaluator& ev) {
|
||||
for (auto const& kv : m_elems) {
|
||||
expr* t = kv.m_key;
|
||||
for (auto const &[t, gen] : m_elems) {
|
||||
SASSERT(!contains_model_value(t));
|
||||
unsigned gen = kv.m_value;
|
||||
expr* t_val = ev.eval(t, true);
|
||||
if (!t_val) break;
|
||||
TRACE(model_finder, tout << mk_pp(t, m) << " " << mk_pp(t_val, m) << "\n";);
|
||||
|
||||
expr* old_t = nullptr;
|
||||
if (m_inv.find(t_val, old_t)) {
|
||||
unsigned old_t_gen = 0;
|
||||
|
|
@ -187,14 +192,14 @@ namespace smt {
|
|||
\brief Base class used to solve model construction constraints.
|
||||
*/
|
||||
class node {
|
||||
unsigned m_id;
|
||||
node* m_find{ nullptr };
|
||||
unsigned m_eqc_size{ 1 };
|
||||
unsigned m_id = 0;
|
||||
node* m_find = nullptr;
|
||||
unsigned m_eqc_size = 1;
|
||||
|
||||
sort* m_sort; // sort of the elements in the instantiation set.
|
||||
sort* m_sort = nullptr; // sort of the elements in the instantiation set.
|
||||
|
||||
bool m_mono_proj{ false }; // relevant for integers & reals & bit-vectors
|
||||
bool m_signed_proj{ false }; // relevant for bit-vectors.
|
||||
bool m_mono_proj = false; // relevant for integers & reals & bit-vectors
|
||||
bool m_signed_proj = false; // relevant for bit-vectors.
|
||||
ptr_vector<node> m_avoid_set;
|
||||
ptr_vector<expr> m_exceptions;
|
||||
|
||||
|
|
@ -291,7 +296,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
void insert(expr* n, unsigned generation) {
|
||||
if (is_ground(n))
|
||||
if (is_ground(n) || (has_quantifiers(n) && !has_free_vars(n))) // this is a closed term
|
||||
get_root()->m_set->insert(n, generation);
|
||||
}
|
||||
|
||||
|
|
@ -599,7 +604,10 @@ namespace smt {
|
|||
}
|
||||
else {
|
||||
r = tmp;
|
||||
TRACE(model_finder, tout << "eval\n" << mk_pp(n, m) << "\n----->\n" << mk_pp(r, m) << "\n";);
|
||||
TRACE(model_finder, tout << "eval-failed\n" << mk_pp(n, m) << "\n----->\n" << mk_pp(r, m) << "\n";);
|
||||
if (is_lambda(tmp)) {
|
||||
r = m.mk_fresh_const("lambda", tmp->get_sort());
|
||||
}
|
||||
}
|
||||
m_eval_cache[model_completion].insert(n, r);
|
||||
m_eval_cache_range.push_back(r);
|
||||
|
|
@ -1235,8 +1243,8 @@ namespace smt {
|
|||
void populate_inst_sets(quantifier* q, func_decl* mhead, ptr_vector<instantiation_set>& uvar_inst_sets, context* ctx) override {
|
||||
if (m_f != mhead)
|
||||
return;
|
||||
uvar_inst_sets.reserve(m_var_j + 1, 0);
|
||||
if (uvar_inst_sets[m_var_j] == 0)
|
||||
uvar_inst_sets.reserve(m_var_j + 1, nullptr);
|
||||
if (uvar_inst_sets[m_var_j] == nullptr)
|
||||
uvar_inst_sets[m_var_j] = alloc(instantiation_set, ctx->get_manager());
|
||||
instantiation_set* s = uvar_inst_sets[m_var_j];
|
||||
SASSERT(s != nullptr);
|
||||
|
|
@ -1369,6 +1377,79 @@ namespace smt {
|
|||
|
||||
};
|
||||
|
||||
class ho_var : public qinfo {
|
||||
unsigned m_var_i;
|
||||
public:
|
||||
ho_var(ast_manager& m, unsigned i) : qinfo(m), m_var_i(i) {
|
||||
}
|
||||
|
||||
char const *get_kind() const override {
|
||||
return "ho_var";
|
||||
}
|
||||
|
||||
bool is_equal(qinfo const *qi) const override {
|
||||
if (qi->get_kind() != get_kind())
|
||||
return false;
|
||||
ho_var const *other = static_cast<ho_var const *>(qi);
|
||||
return m_var_i == other->m_var_i;
|
||||
}
|
||||
|
||||
void display(std::ostream &out) const override {
|
||||
out << "(" << "ho-var: " << m_var_i << ")";
|
||||
}
|
||||
|
||||
void process_auf(quantifier *q, auf_solver &s, context *ctx) override {
|
||||
/* node * S_i = */ s.get_uvar(q, m_var_i);
|
||||
}
|
||||
|
||||
void populate_inst_sets(quantifier *q, auf_solver &s, context *ctx) override {
|
||||
node *S = s.get_uvar(q, m_var_i);
|
||||
sort *srt = S->get_sort();
|
||||
|
||||
IF_VERBOSE(3, verbose_stream() << "ho_var::populate_inst_sets: " << q->get_id() << " " << mk_pp(srt, m) << "\n";);
|
||||
term_enumeration tn(m);
|
||||
// Add ground terms of type S.
|
||||
// Add productions for functions in E-graph
|
||||
// add other possible relevant functions such as equality over srt, Boolean operators
|
||||
|
||||
ast_mark visited;
|
||||
tn.add_production(m.mk_true());
|
||||
tn.add_production(m.mk_false());
|
||||
for (enode *n : ctx->enodes()) {
|
||||
if (!ctx->is_relevant(n))
|
||||
continue;
|
||||
auto e = n->get_expr();
|
||||
if (srt == n->get_sort()) {
|
||||
TRACE(model_finder, tout << "inserting " << mk_pp(e, m) << " into inst set\n");
|
||||
S->insert(e, n->get_generation());
|
||||
}
|
||||
else if (is_uninterp_const(e)) {
|
||||
TRACE(model_finder, tout << "add production " << mk_pp(e, m) << "\n");
|
||||
tn.add_production(e);
|
||||
}
|
||||
else if (is_uninterp(e)) {
|
||||
auto f = to_app(e)->get_decl();
|
||||
if (visited.is_marked(f))
|
||||
continue;
|
||||
visited.mark(f, true);
|
||||
TRACE(model_finder, tout << "add function " << mk_pp(f, m) << "\n");
|
||||
tn.add_production(f);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned max_count = 20;
|
||||
for (auto t : tn.enum_terms(srt)) {
|
||||
if (max_count == 0)
|
||||
break;
|
||||
--max_count;
|
||||
unsigned generation = 0; // todo - inherited from sub-term of t?
|
||||
TRACE(model_finder, tout << "ho_var: adding term " << mk_ismt2_pp(t, m)
|
||||
<< " to instantiation set of S" << std::endl;);
|
||||
S->insert(t, generation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
\brief auf_arr is a term (pattern) of the form:
|
||||
|
|
@ -2105,7 +2186,10 @@ namespace smt {
|
|||
process_app(to_app(curr));
|
||||
}
|
||||
else if (is_var(curr)) {
|
||||
m_info->m_is_auf = false; // unexpected occurrence of variable.
|
||||
if (m_array_util.is_array(curr)) {
|
||||
insert_qinfo(alloc(ho_var, m, to_var(curr)->get_idx()));
|
||||
}
|
||||
m_info->m_is_auf = false;
|
||||
}
|
||||
else {
|
||||
SASSERT(is_lambda(curr));
|
||||
|
|
@ -2163,7 +2247,6 @@ namespace smt {
|
|||
}
|
||||
|
||||
SASSERT(is_quantifier(atom));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void process_literal(expr* atom, polarity pol) {
|
||||
|
|
@ -2203,9 +2286,15 @@ namespace smt {
|
|||
if (is_app(curr)) {
|
||||
if (to_app(curr)->get_family_id() == m.get_basic_family_id() && m.is_bool(curr)) {
|
||||
switch (static_cast<basic_op_kind>(to_app(curr)->get_decl_kind())) {
|
||||
case OP_IMPLIES:
|
||||
case OP_IMPLIES:
|
||||
process_literal(to_app(curr)->get_arg(0), neg(pol));
|
||||
process_literal(to_app(curr)->get_arg(1), pol);
|
||||
break;
|
||||
case OP_XOR:
|
||||
UNREACHABLE(); // simplifier eliminated ANDs, IMPLIEs, and XORs
|
||||
for (expr *arg : *to_app(curr)) {
|
||||
visit_formula(arg, pol);
|
||||
visit_formula(arg, neg(pol));
|
||||
}
|
||||
break;
|
||||
case OP_OR:
|
||||
case OP_AND:
|
||||
|
|
@ -2515,11 +2604,12 @@ namespace smt {
|
|||
|
||||
Store in generation the generation of the result
|
||||
*/
|
||||
expr* model_finder::get_inv(quantifier* q, unsigned i, expr* val, unsigned& generation) {
|
||||
expr* model_finder::get_inv(quantifier* q, unsigned i, expr* val, model& mdl,unsigned& generation) {
|
||||
instantiation_set const* s = get_uvar_inst_set(q, i);
|
||||
if (s == nullptr)
|
||||
return nullptr;
|
||||
expr* t = s->get_inv(val);
|
||||
|
||||
expr* t = s->get_inv(val, mdl);
|
||||
if (m_auf_solver->is_default_representative(t))
|
||||
return val;
|
||||
if (t != nullptr) {
|
||||
|
|
@ -2555,16 +2645,27 @@ namespace smt {
|
|||
obj_map<expr, expr*> const& inv = s->get_inv_map();
|
||||
if (inv.empty())
|
||||
continue; // nothing to do
|
||||
ptr_buffer<expr> eqs;
|
||||
for (auto const& [val, _] : inv) {
|
||||
if (val->get_sort() == sk->get_sort())
|
||||
eqs.push_back(m.mk_eq(sk, val));
|
||||
expr_ref_vector eqs(m), defs(m);
|
||||
|
||||
for (auto const& [val, term] : inv) {
|
||||
if (val->get_sort() == sk->get_sort()) {
|
||||
if (is_lambda(term)) {
|
||||
eqs.push_back(m.mk_eq(sk, val));
|
||||
defs.push_back(m.mk_eq(val, term));
|
||||
}
|
||||
else
|
||||
eqs.push_back(m.mk_eq(sk, val));
|
||||
}
|
||||
}
|
||||
if (!eqs.empty()) {
|
||||
expr_ref new_cnstr(m);
|
||||
new_cnstr = m.mk_or(eqs);
|
||||
TRACE(model_finder, tout << "assert_restriction:\n" << mk_pp(new_cnstr, m) << "\n";);
|
||||
aux_ctx->assert_expr(new_cnstr);
|
||||
for (auto def : defs) {
|
||||
TRACE(model_finder, tout << "assert_def:\n" << mk_pp(def, m) << "\n";);
|
||||
aux_ctx->assert_expr(def);
|
||||
}
|
||||
asserted_something = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ namespace smt {
|
|||
void fix_model(proto_model * m);
|
||||
|
||||
quantifier * get_flat_quantifier(quantifier * q);
|
||||
expr * get_inv(quantifier * q, unsigned i, expr * val, unsigned & generation);
|
||||
expr * get_inv(quantifier * q, unsigned i, expr * val, model& m, unsigned & generation);
|
||||
bool restrict_sks_to_inst_set(context * aux_ctx, quantifier * q, expr_ref_vector const & sks);
|
||||
|
||||
void restart_eh();
|
||||
|
|
|
|||
|
|
@ -396,15 +396,15 @@ namespace smt {
|
|||
}
|
||||
|
||||
final_check_status theory_array::assert_delayed_axioms() {
|
||||
if (!m_params.m_array_delay_exp_axiom)
|
||||
return FC_DONE;
|
||||
final_check_status r = FC_DONE;
|
||||
unsigned num_vars = get_num_vars();
|
||||
for (unsigned v = 0; v < num_vars; ++v) {
|
||||
var_data * d = m_var_data[v];
|
||||
if (d->m_prop_upward && instantiate_axiom2b_for(v))
|
||||
r = FC_CONTINUE;
|
||||
}
|
||||
if (m_params.m_array_delay_exp_axiom) {
|
||||
unsigned num_vars = get_num_vars();
|
||||
for (unsigned v = 0; v < num_vars; ++v) {
|
||||
var_data *d = m_var_data[v];
|
||||
if (d->m_prop_upward && instantiate_axiom2b_for(v))
|
||||
r = FC_CONTINUE;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ namespace smt {
|
|||
return mk_select(num_args, args);
|
||||
}
|
||||
|
||||
|
||||
app * theory_array_base::mk_store(unsigned num_args, expr * const * args) {
|
||||
return m.mk_app(get_family_id(), OP_STORE, 0, nullptr, num_args, args);
|
||||
}
|
||||
|
|
@ -279,7 +278,7 @@ namespace smt {
|
|||
SASSERT(n1->get_num_args() == n2->get_num_args());
|
||||
unsigned n = n1->get_num_args();
|
||||
// skipping first argument of the select.
|
||||
for(unsigned i = 1; i < n; ++i) {
|
||||
for (unsigned i = 1; i < n; ++i) {
|
||||
if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -295,9 +294,8 @@ namespace smt {
|
|||
enode * r1 = v1->get_root();
|
||||
enode * r2 = v2->get_root();
|
||||
|
||||
if (r1->get_class_size() > r2->get_class_size()) {
|
||||
std::swap(r1, r2);
|
||||
}
|
||||
if (r1->get_class_size() > r2->get_class_size())
|
||||
std::swap(r1, r2);
|
||||
|
||||
m_array_value.reset();
|
||||
// populate m_array_value if the select(a, i) parent terms of r1
|
||||
|
|
@ -335,7 +333,7 @@ namespace smt {
|
|||
return false; // axiom was already instantiated
|
||||
if (already_diseq(n1, n2))
|
||||
return false;
|
||||
m_extensionality_todo.push_back(std::make_pair(n1, n2));
|
||||
m_extensionality_todo.push_back({n1, n2});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +346,7 @@ namespace smt {
|
|||
enode * nodes[2] = { a1, a2 };
|
||||
if (!ctx.add_fingerprint(this, 1, 2, nodes))
|
||||
return; // axiom was already instantiated
|
||||
m_congruent_todo.push_back(std::make_pair(a1, a2));
|
||||
m_congruent_todo.push_back({a1, a2});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -581,11 +579,11 @@ namespace smt {
|
|||
enode * n2 = get_enode(v2);
|
||||
sort * s2 = n2->get_sort();
|
||||
if (s1 == s2 && !ctx.is_diseq(n1, n2)) {
|
||||
app * eq = mk_eq_atom(n1->get_expr(), n2->get_expr());
|
||||
if (!ctx.b_internalized(eq) || !ctx.is_relevant(eq)) {
|
||||
app_ref eq = app_ref(mk_eq_atom(n1->get_expr(), n2->get_expr()), m);
|
||||
if (!ctx.b_internalized(eq.get()) || !ctx.is_relevant(eq.get())) {
|
||||
result++;
|
||||
ctx.internalize(eq, true);
|
||||
ctx.mark_as_relevant(eq);
|
||||
ctx.mark_as_relevant(eq.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -850,7 +848,7 @@ namespace smt {
|
|||
if (i < num_args) {
|
||||
SASSERT(!parent_sel_set->contains(sel) || (*(parent_sel_set->find(sel)))->get_root() == sel->get_root());
|
||||
parent_sel_set->insert(sel);
|
||||
todo.push_back(std::make_pair(parent_root, sel));
|
||||
todo.push_back({parent_root, sel});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@ namespace smt {
|
|||
return lit == arg;
|
||||
};
|
||||
auto lit1 = clause.get(0);
|
||||
auto lit2 = clause.get(1);
|
||||
[[maybe_unused]] auto lit2 = clause.get(1);
|
||||
auto position = 0;
|
||||
if (is_complement_to(is_true, lit1, e))
|
||||
position = 0;
|
||||
|
|
|
|||
|
|
@ -4029,7 +4029,7 @@ public:
|
|||
if (!lp().is_feasible() || lp().has_changed_columns())
|
||||
make_feasible();
|
||||
vi = get_lpvar(v);
|
||||
auto st = lp().maximize_term(vi, term_max);
|
||||
auto st = lp().maximize_term(vi, term_max, /*fix_int_cols*/ true);
|
||||
if (has_int() && lp().has_inf_int()) {
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
lp().restore_x();
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ namespace smt {
|
|||
unsigned m_final_check_ls_steps = 30000;
|
||||
unsigned m_final_check_ls_steps_delta = 10000;
|
||||
unsigned m_final_check_ls_steps_min = 10000;
|
||||
unsigned m_final_check_ls_steps_max = 30000;
|
||||
bool m_has_unassigned_clause_after_resolve = false;
|
||||
unsigned m_after_resolve_decide_gap = 4;
|
||||
unsigned m_after_resolve_decide_count = 0;
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ class asserted_formulas {
|
|||
public: \
|
||||
FUNCTOR m_functor; \
|
||||
NAME(asserted_formulas& af):simplify_fmls(af, MSG), m_functor ARG {} \
|
||||
virtual void simplify(justified_expr const& j, expr_ref& n, proof_ref& p) { \
|
||||
m_functor(j.fml(), n, p); \
|
||||
void simplify(justified_expr const& j, expr_ref& n, proof_ref& p) override { \
|
||||
m_functor(j.fml(), n, p); \
|
||||
} \
|
||||
virtual void post_op() { if (REDUCE) af.reduce_and_solve(); } \
|
||||
virtual bool should_apply() const { return APP; } \
|
||||
}; \
|
||||
void post_op() override { if (REDUCE) af.reduce_and_solve(); } \
|
||||
bool should_apply() const override { return APP; } \
|
||||
};
|
||||
|
||||
#define MK_SIMPLIFIERF(NAME, FUNCTOR, MSG, APP, REDUCE) MK_SIMPLIFIERA(NAME, FUNCTOR, MSG, APP, (af.m), REDUCE)
|
||||
|
||||
|
|
|
|||
|
|
@ -460,7 +460,6 @@ class parallel_solver {
|
|||
};
|
||||
|
||||
unsigned id;
|
||||
parallel_solver& p;
|
||||
batch_manager& b;
|
||||
ast_manager m; /* worker-local manager */
|
||||
ref<solver> s; /* translated solver copy */
|
||||
|
|
@ -579,7 +578,7 @@ class parallel_solver {
|
|||
worker(unsigned id, parallel_solver& p,
|
||||
solver& src, params_ref const& params,
|
||||
expr_ref_vector const& src_asms)
|
||||
: id(id), p(p), b(p.m_batch_manager),
|
||||
: id(id), b(p.m_batch_manager),
|
||||
asms(m), m_g2l(src.get_manager(), m), m_l2g(m, src.get_manager())
|
||||
{
|
||||
/* create translated solver copy */
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ Notes:
|
|||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/pb_decl_plugin.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/recfun_decl_plugin.h"
|
||||
#include "ast/rewriter/rewriter_def.h"
|
||||
#include "ast/rewriter/expr_safe_replace.h"
|
||||
#include "ast/ast_util.h"
|
||||
|
|
@ -185,6 +186,10 @@ public:
|
|||
expr_safe_replace rep(m);
|
||||
|
||||
tactic_report report("lia2card", *g);
|
||||
if (recfun::util(m).has_rec_defs()) {
|
||||
result.push_back(g.get());
|
||||
return;
|
||||
}
|
||||
|
||||
bound_manager bounds(m);
|
||||
for (unsigned i = 0; i < g->size(); ++i)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Notes:
|
|||
#include "tactic/tactical.h"
|
||||
#include "ast/converters/generic_model_converter.h"
|
||||
#include "ast/rewriter/rewriter_def.h"
|
||||
#include "ast/rewriter/seq_rewriter.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/bv_decl_plugin.h"
|
||||
#include "ast/recfun_decl_plugin.h"
|
||||
|
|
@ -803,10 +804,10 @@ class elim_uncnstr_tactic : public tactic {
|
|||
|
||||
// x ++ y -> z, x -> z, y -> eps
|
||||
app * process_seq_app(func_decl * f, unsigned num, expr * const * args) {
|
||||
app *r = nullptr;
|
||||
switch (f->get_decl_kind()) {
|
||||
case _OP_STRING_CONCAT:
|
||||
case OP_SEQ_CONCAT: {
|
||||
app * r = nullptr;
|
||||
expr* x, *y;
|
||||
if (uncnstr(args[0]) && num == 2 &&
|
||||
args[1]->get_ref_count() == 1 &&
|
||||
|
|
@ -833,6 +834,27 @@ class elim_uncnstr_tactic : public tactic {
|
|||
|
||||
return r;
|
||||
}
|
||||
case OP_SEQ_IN_RE:
|
||||
return nullptr;
|
||||
if (uncnstr(args[0]) && m_seq_util.re.is_ground(args[1]) && m_seq_util.is_string(args[0]->get_sort())) {
|
||||
zstring s1;
|
||||
expr *re = args[1];
|
||||
seq_rewriter rw(m());
|
||||
if (l_true != rw.some_string_in_re(re, s1))
|
||||
return nullptr;
|
||||
zstring s2;
|
||||
expr_ref not_re(m_seq_util.re.mk_complement(re), m());
|
||||
if (l_true != rw.some_string_in_re(not_re, s2))
|
||||
return nullptr;
|
||||
|
||||
mk_fresh_uncnstr_var_for(f, num, args, r);
|
||||
expr_ref witness1 = expr_ref(m_seq_util.str.mk_string(s1), m());
|
||||
expr_ref witness2 = expr_ref(m_seq_util.str.mk_string(s2), m());
|
||||
if (m_mc)
|
||||
add_def(args[0], m().mk_ite(r, witness1, witness2));
|
||||
return r;
|
||||
}
|
||||
return nullptr;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ add_executable(test-z3
|
|||
api_datalog.cpp
|
||||
parametric_datatype.cpp
|
||||
arith_rewriter.cpp
|
||||
seq_rewriter.cpp
|
||||
arith_simplifier_plugin.cpp
|
||||
ast.cpp
|
||||
bdd.cpp
|
||||
|
|
@ -79,6 +80,7 @@ add_executable(test-z3
|
|||
"${CMAKE_CURRENT_BINARY_DIR}/install_tactic.cpp"
|
||||
interval.cpp
|
||||
karr.cpp
|
||||
lcube.cpp
|
||||
list.cpp
|
||||
main.cpp
|
||||
map.cpp
|
||||
|
|
@ -146,6 +148,7 @@ add_executable(test-z3
|
|||
symbol.cpp
|
||||
symbol_table.cpp
|
||||
tbv.cpp
|
||||
term_enumeration.cpp
|
||||
theory_dl.cpp
|
||||
theory_pb.cpp
|
||||
timeout.cpp
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ static void test_ackr_bound_probe() {
|
|||
// by the BV solver back to a model for the original formula (with UF).
|
||||
// The two null-pointer guards in ackr_model_converter.cpp are exercised here.
|
||||
//
|
||||
static void test_ackermannize_bv_model() {
|
||||
[[maybe_unused]] static void test_ackermannize_bv_model() {
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
|
|
|||
|
|
@ -81,6 +81,29 @@ void test_bvneg() {
|
|||
std::cout << r << "\n";
|
||||
}
|
||||
|
||||
{
|
||||
Z3_sort bv1 = Z3_mk_bv_sort(ctx, 1);
|
||||
Z3_sort bv64 = Z3_mk_bv_sort(ctx, 64);
|
||||
Z3_ast x = Z3_mk_fresh_const(ctx, "x", bv1);
|
||||
Z3_ast sx = Z3_mk_sign_ext(ctx, 63, x);
|
||||
Z3_ast zero = Z3_mk_int64(ctx, 0, bv64);
|
||||
Z3_ast minus_one = Z3_mk_int64(ctx, -1, bv64);
|
||||
Z3_ast args[2] = { Z3_mk_eq(ctx, sx, zero), Z3_mk_eq(ctx, sx, minus_one) };
|
||||
Z3_ast claim = Z3_mk_or(ctx, 2, args);
|
||||
|
||||
Z3_solver_push(ctx, s);
|
||||
Z3_solver_assert(ctx, s, Z3_mk_not(ctx, claim));
|
||||
ENSURE(Z3_solver_check(ctx, s) == Z3_L_FALSE);
|
||||
Z3_solver_pop(ctx, s, 1);
|
||||
|
||||
Z3_solver_push(ctx, s);
|
||||
Z3_solver_assert(ctx, s, Z3_mk_eq(ctx, sx, minus_one));
|
||||
std::string smt2 = Z3_solver_to_string(ctx, s);
|
||||
// Bit-vector numerals must print in canonical unsigned SMT2 form.
|
||||
ENSURE(smt2.find("(_ bv-") == std::string::npos);
|
||||
Z3_solver_pop(ctx, s, 1);
|
||||
}
|
||||
|
||||
Z3_solver_dec_ref(ctx, s);
|
||||
Z3_del_config(cfg);
|
||||
Z3_del_context(ctx);
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ static void test_bug_translate_null_target() {
|
|||
// Z3_translate(ctx, x, nullptr) would crash - no null check on target
|
||||
// The function checks c == target (line 1517) but doesn't check target != nullptr first
|
||||
// So mk_c(target) on line 1522 dereferences nullptr
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
Z3_get_error_code(ctx);
|
||||
std::cout << " [BUG DOCUMENTED] Z3_translate(ctx, ast, nullptr) would crash\n";
|
||||
std::cout << " No null check on target before mk_c(target) at api_ast.cpp:1522\n";
|
||||
|
||||
|
|
@ -596,7 +596,6 @@ static void test_bug_array_sort_mismatch() {
|
|||
|
||||
// Create Array(Int, Int)
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_sort bool_sort = Z3_mk_bool_sort(ctx);
|
||||
Z3_sort arr_sort = Z3_mk_array_sort(ctx, int_sort, int_sort);
|
||||
|
||||
Z3_ast arr = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), arr_sort);
|
||||
|
|
@ -651,7 +650,7 @@ static void test_bug_substitute_null_arrays() {
|
|||
|
||||
// With num_exprs=0, null arrays should be fine
|
||||
Z3_ast r = Z3_substitute(ctx, x, 0, nullptr, nullptr);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
Z3_get_error_code(ctx);
|
||||
if (r != nullptr) {
|
||||
std::cout << " substitute(x, 0, null, null) OK: " << Z3_ast_to_string(ctx, r) << "\n";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ static void test_pop() {
|
|||
TestNode* list = nullptr;
|
||||
TestNode node1(1);
|
||||
TestNode::push_to_front(list, &node1);
|
||||
TestNode* popped = TestNode::pop(list);
|
||||
[[maybe_unused]] TestNode* popped = TestNode::pop(list);
|
||||
SASSERT(popped == &node1);
|
||||
SASSERT(list == nullptr);
|
||||
SASSERT(popped->next() == popped);
|
||||
|
|
|
|||
|
|
@ -303,14 +303,13 @@ class test_doc_cls {
|
|||
bool_vector to_merge(N, false);
|
||||
bit_vector discard_cols;
|
||||
discard_cols.resize(N, false);
|
||||
unsigned num_bits = 1;
|
||||
union_find_default_ctx union_ctx;
|
||||
subset_ints equalities(union_ctx);
|
||||
unsigned lo = N;
|
||||
equalities.mk_var();
|
||||
for (unsigned i = 1; i < N; ++i) {
|
||||
to_merge[i] = (m_ran(2) == 0);
|
||||
if (!to_merge[i]) ++num_bits; else lo = i;
|
||||
if (to_merge[i]) lo = i;
|
||||
equalities.mk_var();
|
||||
}
|
||||
if (lo == N) return;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ int vals[N];
|
|||
|
||||
static void tst1() {
|
||||
int_set h1;
|
||||
int size = 0;
|
||||
for (int i = 1; i < N; i ++) {
|
||||
int v = rand() % (N / 2);
|
||||
h1.insert(v);
|
||||
|
|
@ -92,7 +91,7 @@ static void tst2() {
|
|||
ENSURE(contains(h1, elem));
|
||||
n++;
|
||||
}
|
||||
ENSURE(n == h1.size());
|
||||
ENSURE(n == static_cast<int>(h1.size()));
|
||||
}
|
||||
ENSURE(h1.size() == h2.size());
|
||||
// std::cout << "size: " << h1.size() << ", capacity: " << h1.capacity() << "\n"; std::cout.flush();
|
||||
|
|
@ -194,7 +193,7 @@ void test_hashtable_iterators() {
|
|||
ht.insert(3);
|
||||
|
||||
int count = 0;
|
||||
for (const auto& elem : ht) {
|
||||
for ([[maybe_unused]] const auto& elem : ht) {
|
||||
++count;
|
||||
}
|
||||
VERIFY(count == 3);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ expr_ref hilbert_basis_validate::mk_validate(hilbert_basis& hb) {
|
|||
bool is_initial;
|
||||
hb.get_basis_solution(i, v, is_initial);
|
||||
|
||||
for (unsigned j = 0; xs.size() < v.size(); ++j) {
|
||||
for (; xs.size() < v.size(); ) {
|
||||
xs.push_back(m.mk_fresh_const("x", a.mk_int()));
|
||||
}
|
||||
|
||||
|
|
|
|||
261
src/test/lcube.cpp
Normal file
261
src/test/lcube.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
lcube.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Tests for the largest cube test of Bromberger and Weidenbach
|
||||
(Fast Cube Tests for LIA Constraint Solving, IJCAR 2016),
|
||||
implemented in int_cube::find_largest_cube().
|
||||
|
||||
This file lives directly under src/test (not src/test/lp) so that the
|
||||
scripts/mk_make.py build, which only compiles the top-level test
|
||||
directory, links tst_lcube().
|
||||
|
||||
Author:
|
||||
|
||||
Lev Nachmanson (levnach)
|
||||
|
||||
--*/
|
||||
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
#include "util/debug.h"
|
||||
#include "util/params.h"
|
||||
#include "math/lp/int_cube.h"
|
||||
#include "math/lp/int_solver.h"
|
||||
#include "math/lp/lar_solver.h"
|
||||
#include "math/lp/numeric_pair.h"
|
||||
|
||||
namespace lp {
|
||||
|
||||
// tests for the largest cube test of Bromberger and Weidenbach
|
||||
namespace lcube_test {
|
||||
|
||||
struct ineq {
|
||||
vector<std::pair<mpq, unsigned>> m_coeffs;
|
||||
lconstraint_kind m_kind;
|
||||
mpq m_rs;
|
||||
};
|
||||
|
||||
// builds for every inequality a term with the bound and solves
|
||||
static void setup(lar_solver& solver, const vector<ineq>& ineqs, svector<unsigned>* term_columns = nullptr) {
|
||||
unsigned term_ext = 1000;
|
||||
for (const auto& in : ineqs) {
|
||||
unsigned t = solver.add_term(in.m_coeffs, term_ext++);
|
||||
solver.add_var_bound(t, in.m_kind, in.m_rs);
|
||||
if (term_columns)
|
||||
term_columns->push_back(t);
|
||||
}
|
||||
auto st = solver.solve();
|
||||
VERIFY(st == lp_status::OPTIMAL || st == lp_status::FEASIBLE);
|
||||
}
|
||||
|
||||
static void verify_model(const lar_solver& solver, const vector<ineq>& ineqs) {
|
||||
for (const auto& in : ineqs) {
|
||||
impq v;
|
||||
for (const auto& p : in.m_coeffs)
|
||||
v += solver.get_column_value(p.second) * p.first;
|
||||
switch (in.m_kind) {
|
||||
case lconstraint_kind::LE: VERIFY(v <= impq(in.m_rs)); break;
|
||||
case lconstraint_kind::GE: VERIFY(v >= impq(in.m_rs)); break;
|
||||
default: VERIFY(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void verify_int_values(const lar_solver& solver, std::initializer_list<unsigned> vars) {
|
||||
for (unsigned j : vars)
|
||||
VERIFY(solver.get_column_value(j).is_int());
|
||||
}
|
||||
|
||||
// The example of Bromberger and Weidenbach: 3x1 - x2 <= 0, -2x1 - x2 <= -2, -2x1 + x2 <= 1.
|
||||
// The largest cube is smaller than the unit cube, the rounded center is not a solution,
|
||||
// no coordinate flip repairs it (the only integer solution lies outside the lattice cell
|
||||
// of the center): expect undef and an intact solver state.
|
||||
static void test_paper_example_undef() {
|
||||
std::cout << "lcube: paper example, expecting undef\n";
|
||||
lar_solver solver;
|
||||
unsigned x1 = solver.add_named_var(0, true, "x1");
|
||||
unsigned x2 = solver.add_named_var(1, true, "x2");
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(3), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(-2)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(1), x2}}, lconstraint_kind::LE, mpq(1)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::undef);
|
||||
VERIFY(solver.ax_is_correct());
|
||||
}
|
||||
|
||||
// 3x1 - x2 <= 0, -2x1 - x2 <= -1, -2x1 + x2 <= 1: the largest cube has
|
||||
// edge 4/17 with the center (3/17, 1) that rounds to the solution (0, 1),
|
||||
// while the unit cube test fails: the largest cube test is stronger here.
|
||||
static void test_beats_unit_cube() {
|
||||
std::cout << "lcube: beating the unit cube test\n";
|
||||
lar_solver solver;
|
||||
unsigned x1 = solver.add_named_var(0, true, "x1");
|
||||
unsigned x2 = solver.add_named_var(1, true, "x2");
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(3), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(-1), x2}}, lconstraint_kind::LE, mpq(-1)});
|
||||
ineqs.push_back(ineq{{{mpq(-2), x1}, {mpq(1), x2}}, lconstraint_kind::LE, mpq(1)});
|
||||
svector<unsigned> tcols;
|
||||
setup(solver, ineqs, &tcols);
|
||||
// move the solution to a fractional feasible point: the cube tests only
|
||||
// run when the current solution has fractional integer variables
|
||||
solver.set_column_value_test(x1, impq(mpq(1, 4)));
|
||||
solver.set_column_value_test(x2, impq(mpq(5, 4)));
|
||||
solver.set_column_value_test(tcols[0], impq(mpq(-1, 2)));
|
||||
solver.set_column_value_test(tcols[1], impq(mpq(-7, 4)));
|
||||
solver.set_column_value_test(tcols[2], impq(mpq(3, 4)));
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s)();
|
||||
std::cout << "unit cube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::undef);
|
||||
m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x1, x2});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// 9/10 <= x + y + r <= 11/10, -11/10 <= x - y + r <= 1/10, 0 <= r <= 1/10,
|
||||
// x, y integer, r real. The real variable keeps the terms and their bounds
|
||||
// non-integer. A fractional center, e.g. (1/2, 1/2), rounds to an infeasible
|
||||
// point that is repaired by flipping one coordinate: expect sat.
|
||||
static void test_flip_repair() {
|
||||
std::cout << "lcube: rounding repair\n";
|
||||
lar_solver solver;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
unsigned r = solver.add_named_var(2, false, "r");
|
||||
solver.add_var_bound(r, lconstraint_kind::GE, mpq(0));
|
||||
solver.add_var_bound(r, lconstraint_kind::LE, mpq(1, 10));
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(9, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(-11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(1, 10)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m)
|
||||
<< ", flip successes: " << solver.settings().stats().m_lcube_flip_success << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// 3x + 5y >= 7 alone has infinite lattice width: the edge length is
|
||||
// unbounded and any cube center of edge >= 1 rounds to a solution.
|
||||
static void test_infinite_lattice_width() {
|
||||
std::cout << "lcube: infinite lattice width\n";
|
||||
lar_solver solver;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(3), x}, {mpq(5), y}}, lconstraint_kind::GE, mpq(7)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// 0 <= x + 2y + r <= 8, 0 <= x - 2y + r <= 8: the maximal edge length is
|
||||
// 8/3 >= 1, so the rounded center is guaranteed to be a solution.
|
||||
static void test_edge_at_least_one() {
|
||||
std::cout << "lcube: edge length at least 1\n";
|
||||
lar_solver solver;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
unsigned r = solver.add_named_var(2, false, "r");
|
||||
solver.add_var_bound(r, lconstraint_kind::GE, mpq(0));
|
||||
solver.add_var_bound(r, lconstraint_kind::LE, mpq(1, 10));
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(2), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(2), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(8)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-2), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(0)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-2), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(8)});
|
||||
setup(solver, ineqs);
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
lia_move m = int_cube(i_s).find_largest_cube();
|
||||
std::cout << "lcube returned " << lia_move_to_string(m) << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
// runs the flip-repair instance through int_solver::check() with the
|
||||
// lp.lcube parameter set and the cube period lowered to 1: verifies the
|
||||
// dispatch and the parameter plumbing
|
||||
static void test_dispatch() {
|
||||
std::cout << "lcube: dispatch through int_solver::check\n";
|
||||
lar_solver solver;
|
||||
params_ref p;
|
||||
p.set_bool("lcube", true);
|
||||
solver.settings().updt_params(p);
|
||||
VERIFY(solver.settings().lcube());
|
||||
solver.settings().m_int_find_cube_period = 1;
|
||||
unsigned x = solver.add_named_var(0, true, "x");
|
||||
unsigned y = solver.add_named_var(1, true, "y");
|
||||
unsigned r = solver.add_named_var(2, false, "r");
|
||||
solver.add_var_bound(r, lconstraint_kind::GE, mpq(0));
|
||||
solver.add_var_bound(r, lconstraint_kind::LE, mpq(1, 10));
|
||||
vector<ineq> ineqs;
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(9, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::GE, mpq(-11, 10)});
|
||||
ineqs.push_back(ineq{{{mpq(1), x}, {mpq(-1), y}, {mpq(1), r}}, lconstraint_kind::LE, mpq(1, 10)});
|
||||
svector<unsigned> tcols;
|
||||
setup(solver, ineqs, &tcols);
|
||||
// a fractional feasible point, so that check() does not return sat right away
|
||||
solver.set_column_value_test(x, impq(mpq(1, 2)));
|
||||
solver.set_column_value_test(y, impq(mpq(1, 2)));
|
||||
solver.set_column_value_test(r, impq(mpq(1, 10)));
|
||||
solver.set_column_value_test(tcols[0], impq(mpq(11, 10)));
|
||||
solver.set_column_value_test(tcols[1], impq(mpq(11, 10)));
|
||||
solver.set_column_value_test(tcols[2], impq(mpq(1, 10)));
|
||||
solver.set_column_value_test(tcols[3], impq(mpq(1, 10)));
|
||||
int_solver i_s(solver);
|
||||
solver.set_int_solver(&i_s);
|
||||
explanation ex;
|
||||
lia_move m = i_s.check(&ex);
|
||||
std::cout << "check returned " << lia_move_to_string(m)
|
||||
<< ", lcube calls: " << solver.settings().stats().m_lcube_calls << "\n";
|
||||
VERIFY(m == lia_move::sat);
|
||||
VERIFY(solver.settings().stats().m_lcube_calls >= 1);
|
||||
verify_int_values(solver, {x, y});
|
||||
verify_model(solver, ineqs);
|
||||
}
|
||||
|
||||
static void run() {
|
||||
test_paper_example_undef();
|
||||
test_beats_unit_cube();
|
||||
test_flip_repair();
|
||||
test_infinite_lattice_width();
|
||||
test_edge_at_least_one();
|
||||
test_dispatch();
|
||||
std::cout << "lcube tests passed\n";
|
||||
}
|
||||
} // namespace lcube_test
|
||||
} // namespace lp
|
||||
|
||||
void tst_lcube() {
|
||||
lp::lcube_test::run();
|
||||
}
|
||||
|
|
@ -1645,7 +1645,7 @@ void test_maximize_term() {
|
|||
lia_move lm = i_solver.check(&ex);
|
||||
VERIFY(lm == lia_move::sat);
|
||||
impq term_max;
|
||||
lp_status st = solver.maximize_term(term_2x_pl_2y, term_max);
|
||||
lp_status st = solver.maximize_term(term_2x_pl_2y, term_max, /*fix_int_cols*/ true);
|
||||
|
||||
std::cout << "status = " << lp_status_to_string(st) << std::endl;
|
||||
std::cout << "term_max = " << term_max << std::endl;
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
X(arith_rewriter) \
|
||||
X(range_predicate) \
|
||||
X(regex_range_collapse) \
|
||||
X(seq_rewriter) \
|
||||
X(check_assumptions) \
|
||||
X(smt_context) \
|
||||
X(theory_dl) \
|
||||
|
|
@ -196,7 +197,9 @@
|
|||
X(finite_set) \
|
||||
X(finite_set_rewriter) \
|
||||
X(fpa) \
|
||||
X(seq_regex_bisim)
|
||||
X(seq_regex_bisim) \
|
||||
X(term_enumeration) \
|
||||
X(lcube)
|
||||
|
||||
#define FOR_EACH_TEST(X, X_ARGV) \
|
||||
FOR_EACH_ALL_TEST(X, X_ARGV) \
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ static void hit_me(char const* wm) {
|
|||
Z3_mk_bv_sort(ctx,i);
|
||||
|
||||
}
|
||||
catch (std::bad_alloc) {
|
||||
catch (const std::bad_alloc&) {
|
||||
std::cout << "caught\n";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "model/model.h"
|
||||
#include "model/model_evaluator.h"
|
||||
#include "model/func_interp.h"
|
||||
#include "model/model_pp.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
|
|
@ -65,5 +66,29 @@ void tst_model_evaluator() {
|
|||
eval(e, v);
|
||||
std::cout << e << " " << v << "\n";
|
||||
}
|
||||
|
||||
{
|
||||
func_interp fi2(m, 1);
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
fi2.set_else(zero);
|
||||
for (unsigned i = 0; i < 600; ++i) {
|
||||
expr_ref arg(a.mk_int(rational(i)), m);
|
||||
expr* args[1] = { arg.get() };
|
||||
fi2.insert_entry(args, i == 599 ? one.get() : zero.get());
|
||||
}
|
||||
fi2.compress();
|
||||
SASSERT(fi2.num_entries() == 1);
|
||||
|
||||
expr_ref removed_arg(a.mk_int(0), m);
|
||||
[[maybe_unused]] expr* removed_args[1] = { removed_arg.get() };
|
||||
SASSERT(fi2.get_entry(removed_args) == nullptr);
|
||||
|
||||
expr_ref kept_arg(a.mk_int(599), m);
|
||||
expr* kept_args[1] = { kept_arg.get() };
|
||||
[[maybe_unused]] func_entry* kept = fi2.get_entry(kept_args);
|
||||
SASSERT(kept != nullptr);
|
||||
SASSERT(kept->get_result() == one.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ static void project(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsigned
|
|||
std::cout << "\n";
|
||||
}
|
||||
|
||||
static void project_fa(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsigned num, nlsat::literal const* lits) {
|
||||
static nlsat::scoped_literal_vector project_fa(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsigned num, nlsat::literal const* lits) {
|
||||
std::cout << "Project ";
|
||||
nlsat::scoped_literal_vector result(s);
|
||||
ex.compute_conflict_explanation(num, lits, result);
|
||||
|
|
@ -464,6 +464,7 @@ static void project_fa(nlsat::solver& s, nlsat::explain& ex, nlsat::var x, unsig
|
|||
s.display(std::cout << " ", ~lits[i]);
|
||||
}
|
||||
std::cout << ")\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
static nlsat::literal mk_gt(nlsat::solver& s, nlsat::poly* p) {
|
||||
|
|
@ -490,6 +491,39 @@ static nlsat::literal mk_root_eq(nlsat::solver& s, nlsat::poly* p, nlsat::var x,
|
|||
return nlsat::literal(b, false);
|
||||
}
|
||||
|
||||
static bool contains_var(nlsat::var_vector const& vars, nlsat::var x) {
|
||||
for (auto v : vars) {
|
||||
if (v == x)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool clause_contains_root_dependency(
|
||||
nlsat::solver& s,
|
||||
nlsat::scoped_literal_vector const& result,
|
||||
nlsat::atom::kind kind,
|
||||
nlsat::var target,
|
||||
unsigned root_index,
|
||||
nlsat::var dep1,
|
||||
nlsat::var dep2,
|
||||
nlsat::var dep3) {
|
||||
nlsat::pmanager& pm = s.pm();
|
||||
nlsat::var_vector vars;
|
||||
for (auto l : result) {
|
||||
nlsat::atom* a = s.bool_var2atom(l.var());
|
||||
if (!a || !a->is_root_atom() || a->get_kind() != kind)
|
||||
continue;
|
||||
nlsat::root_atom* ra = nlsat::to_root_atom(a);
|
||||
if (ra->x() != target || ra->i() != root_index || pm.max_var(ra->p()) != target)
|
||||
continue;
|
||||
s.vars(l, vars);
|
||||
if (contains_var(vars, dep1) && contains_var(vars, dep2) && contains_var(vars, dep3))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void set_assignment_value(nlsat::assignment& as, anum_manager& am, nlsat::var v, rational const& val) {
|
||||
scoped_anum tmp(am);
|
||||
am.set(tmp, val.to_mpq());
|
||||
|
|
@ -1183,8 +1217,8 @@ static void tst_15() {
|
|||
auto cell = lws.single_cell();
|
||||
}
|
||||
|
||||
// Test case for unsound lemma lws2380 - comparing standard projection vs levelwise
|
||||
// The issue: x7 is unconstrained in levelwise output but affects the section polynomial
|
||||
// Historical lws2380 regression test: both projection paths should preserve
|
||||
// the x7-linked section/root constraints that witness the projected dependency.
|
||||
static void tst_16() {
|
||||
// enable_trace("nlsat_explain");
|
||||
|
||||
|
|
@ -1283,8 +1317,9 @@ static void tst_16() {
|
|||
lits.push_back(mk_gt(s, p0.get())); // x13 > 0
|
||||
lits.push_back(mk_gt(s, p1.get())); // p1 > 0
|
||||
|
||||
project_fa(s, ex, x13, lits.size(), lits.data());
|
||||
auto result = project_fa(s, ex, x13, lits.size(), lits.data());
|
||||
std::cout << "\n";
|
||||
ENSURE(clause_contains_root_dependency(s, result, nlsat::atom::ROOT_EQ, x11, 1, x7, x8, x10));
|
||||
};
|
||||
|
||||
run_test(false); // Standard projection
|
||||
|
|
@ -2144,11 +2179,11 @@ static void tst_22() {
|
|||
}
|
||||
}
|
||||
|
||||
if (all_false) {
|
||||
if (all_false)
|
||||
std::cout << "*** ALL literals FALSE at counterexample - LEMMA IS UNSOUND! ***\n";
|
||||
} else {
|
||||
else
|
||||
std::cout << "At least one literal is TRUE - lemma is sound at this point\n";
|
||||
}
|
||||
ENSURE(!all_false);
|
||||
};
|
||||
|
||||
run_test(false); // lws=false (buggy)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ static void tst2() {
|
|||
m.enable_concurrent(true);
|
||||
|
||||
vector<std::pair<cell *, int> > object_coeff_pairs;
|
||||
unsigned num_resets = 0;
|
||||
[[maybe_unused]] unsigned num_resets = 0;
|
||||
|
||||
for (unsigned i = 0; i < 100000; ++i) {
|
||||
unsigned idx = rand() % 6;
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ static void test_polymorphic_datatype_api() {
|
|||
std::cout << "triple_int_bool: " << Z3_sort_to_string(ctx, triple_int_bool) << "\n";
|
||||
|
||||
// Get constructors and accessors from the instantiated datatype
|
||||
Z3_func_decl mk_triple_int_bool = Z3_get_datatype_sort_constructor(ctx, triple_int_bool, 0);
|
||||
[[maybe_unused]] Z3_func_decl mk_triple_int_bool = Z3_get_datatype_sort_constructor(ctx, triple_int_bool, 0);
|
||||
Z3_func_decl first_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 0);
|
||||
Z3_func_decl second_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 1);
|
||||
[[maybe_unused]] Z3_func_decl second_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 1);
|
||||
Z3_func_decl third_int_bool = Z3_get_datatype_sort_constructor_accessor(ctx, triple_int_bool, 0, 2);
|
||||
|
||||
std::cout << "Got constructors and accessors from instantiated datatype\n";
|
||||
|
|
|
|||
192
src/test/seq_rewriter.cpp
Normal file
192
src/test/seq_rewriter.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Regression tests for seq_rewriter smart constructors for regex ranges.
|
||||
|
||||
Tests:
|
||||
1. Empty range (lo > hi) → re.none
|
||||
2. Singleton range (lo == hi) → str.to_re lo
|
||||
3. Range ∩ Range → reduced range or re.none
|
||||
4. Range ∪ Range → merged range for overlapping/adjacent
|
||||
5. Complement of range → one or two ranges
|
||||
6. Downstream operators absorb empty ranges correctly
|
||||
--*/
|
||||
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
#include <iostream>
|
||||
|
||||
// Build a single-char string literal expression.
|
||||
static expr_ref mk_str(ast_manager& m, seq_util& su, unsigned c) {
|
||||
return expr_ref(su.str.mk_string(zstring(c)), m);
|
||||
}
|
||||
|
||||
void tst_seq_rewriter() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
th_rewriter rw(m);
|
||||
seq_util su(m);
|
||||
|
||||
sort* str_sort = su.str.mk_string_sort();
|
||||
sort* re_sort = su.re.mk_re(str_sort);
|
||||
|
||||
auto range = [&](unsigned lo, unsigned hi) -> expr_ref {
|
||||
return expr_ref(su.re.mk_range(mk_str(m, su, lo), mk_str(m, su, hi)), m);
|
||||
};
|
||||
|
||||
// Arbitrary regex variable for downstream tests.
|
||||
app_ref R(m.mk_fresh_const("R", re_sort), m);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 1. Empty range (lo > hi) → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e = range('z', 'a');
|
||||
rw(e);
|
||||
std::cout << "empty range lo>hi: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 2. Singleton range (lo == hi) → str.to_re lo
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e = range('a', 'a');
|
||||
rw(e);
|
||||
std::cout << "singleton range: " << mk_pp(e, m) << "\n";
|
||||
expr* inner = nullptr;
|
||||
ENSURE(su.re.is_to_re(e, inner));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 3. Range intersection: overlapping → smaller range
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(range('a', 'z'), range('f', 'k')), m);
|
||||
rw(e);
|
||||
std::cout << "range inter overlapping: " << mk_pp(e, m) << "\n";
|
||||
unsigned lo = 0, hi = 0;
|
||||
ENSURE(su.re.is_range(e, lo, hi) && lo == 'f' && hi == 'k');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 4. Range intersection: disjoint → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(range('a', 'f'), range('k', 'z')), m);
|
||||
rw(e);
|
||||
std::cout << "range inter disjoint: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 5. Range intersection: touching at boundary → singleton (str.to_re "f")
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(range('a', 'f'), range('f', 'z')), m);
|
||||
rw(e);
|
||||
std::cout << "range inter touching: " << mk_pp(e, m) << "\n";
|
||||
expr* inner = nullptr;
|
||||
ENSURE(su.re.is_to_re(e, inner));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 6. Range union: overlapping → merged range
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(range('a', 'f'), range('e', 'k')), m);
|
||||
rw(e);
|
||||
std::cout << "range union overlapping: " << mk_pp(e, m) << "\n";
|
||||
unsigned lo = 0, hi = 0;
|
||||
ENSURE(su.re.is_range(e, lo, hi) && lo == 'a' && hi == 'k');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 7. Range union: adjacent → merged range
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(range('a', 'f'), range('g', 'k')), m);
|
||||
rw(e);
|
||||
std::cout << "range union adjacent: " << mk_pp(e, m) << "\n";
|
||||
unsigned lo = 0, hi = 0;
|
||||
ENSURE(su.re.is_range(e, lo, hi) && lo == 'a' && hi == 'k');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 8. Range union: disjoint → stays as union
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(range('a', 'c'), range('m', 'z')), m);
|
||||
rw(e);
|
||||
std::cout << "range union disjoint (stays as union): " << mk_pp(e, m) << "\n";
|
||||
ENSURE(!su.re.is_range(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 9. Range complement (general): no longer a complement node
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_complement(range('b', 'y')), m);
|
||||
rw(e);
|
||||
std::cout << "range comp general: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(!su.re.is_complement(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 10. Range complement (lo = 0): single range e union [hi+1, max].*
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref lo_str(su.str.mk_string(zstring(0u)), m);
|
||||
expr_ref hi_str(su.str.mk_string(zstring((unsigned)'f')), m);
|
||||
expr_ref e(su.re.mk_complement(su.re.mk_range(lo_str, hi_str)), m);
|
||||
rw(e);
|
||||
std::cout << "range comp lo=min: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(!su.re.is_complement(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 11. Downstream: (re.* (re.range "z" "a")) → str.to_re ""
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_star(range('z', 'a')), m);
|
||||
rw(e);
|
||||
std::cout << "star of empty range: " << mk_pp(e, m) << "\n";
|
||||
expr* inner = nullptr;
|
||||
// star of empty → epsilon (str.to_re "")
|
||||
ENSURE(su.re.is_to_re(e, inner) && su.str.is_empty(inner));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 12. Downstream: concat absorbs empty range → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_concat(R, su.re.mk_concat(range('z', 'a'), R)), m);
|
||||
rw(e);
|
||||
std::cout << "concat absorbs empty range: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 13. Downstream: union absorbs empty range → R
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_union(R, range('z', 'a')), m);
|
||||
rw(e);
|
||||
std::cout << "union absorbs empty range: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(e.get() == R.get());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 14. Downstream: inter absorbs empty range → re.none
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
expr_ref e(su.re.mk_inter(R, range('z', 'a')), m);
|
||||
rw(e);
|
||||
std::cout << "inter absorbs empty range: " << mk_pp(e, m) << "\n";
|
||||
ENSURE(su.re.is_empty(e));
|
||||
}
|
||||
|
||||
std::cout << "tst_seq_rewriter: all tests passed\n";
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ struct test_seq {
|
|||
ptr_vector<expr> const& lhs(expr* eq) {
|
||||
auto& ev = get_eval(eq);
|
||||
if (ev.lhs.empty()) {
|
||||
expr* x, * y;
|
||||
expr* x = nullptr, * y = nullptr;
|
||||
VERIFY(m.is_eq(eq, x, y));
|
||||
seq.str.get_concat(x, ev.lhs);
|
||||
seq.str.get_concat(y, ev.rhs);
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ namespace bv {
|
|||
}
|
||||
|
||||
|
||||
static void test_eval1() {
|
||||
[[maybe_unused]] static void test_eval1() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
bv_util bv(m);
|
||||
|
|
@ -262,7 +262,7 @@ static void test_eval1() {
|
|||
}
|
||||
}
|
||||
|
||||
static void test_repair1() {
|
||||
[[maybe_unused]] static void test_repair1() {
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
bv_util bv(m);
|
||||
|
|
|
|||
309
src/test/term_enumeration.cpp
Normal file
309
src/test/term_enumeration.cpp
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
tst_term_enumeration.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Test term enumeration module
|
||||
|
||||
--*/
|
||||
|
||||
|
||||
#include "ast/rewriter/term_enumeration.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/bv_decl_plugin.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "util/obj_hashtable.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
static void tst_basic_enumeration() {
|
||||
std::cout << "=== test basic enumeration ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add some leaf productions (constants)
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
// Enumerate terms of Int sort
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
std::cout << "Term: " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 5) break; // Limit output
|
||||
}
|
||||
|
||||
ENSURE(count >= 2); // At least 0 and 1
|
||||
std::cout << "Enumerated " << count << " terms\n";
|
||||
}
|
||||
|
||||
static void tst_enumeration_with_operators() {
|
||||
std::cout << "=== test enumeration with operators ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add leaf productions
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
// Add operator productions (+ and *)
|
||||
// Get func_decl by creating an app and extracting the decl
|
||||
app_ref tmp_add(a.mk_add(zero, one), m);
|
||||
app_ref tmp_mul(a.mk_mul(zero, one), m);
|
||||
func_decl* add_decl = tmp_add->get_decl();
|
||||
func_decl* mul_decl = tmp_mul->get_decl();
|
||||
te.add_production(add_decl);
|
||||
te.add_production(mul_decl);
|
||||
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
std::cout << "Term: " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 20) break; // Limit output
|
||||
}
|
||||
|
||||
ENSURE(count >= 2); // At least the leaves
|
||||
std::cout << "Enumerated " << count << " terms with operators\n";
|
||||
}
|
||||
|
||||
static void tst_observational_equivalence_filter() {
|
||||
std::cout << "=== test observational equivalence filter ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
th_rewriter rw(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
app_ref tmp_add(a.mk_add(zero, one), m);
|
||||
te.add_production(tmp_add->get_decl());
|
||||
|
||||
sort* int_sort = a.mk_int();
|
||||
obj_hashtable<expr> seen;
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
expr_ref r(m);
|
||||
rw(e, r);
|
||||
ENSURE(r == e);
|
||||
ENSURE(!seen.contains(r));
|
||||
seen.insert(r);
|
||||
count++;
|
||||
if (count >= 20) break;
|
||||
}
|
||||
|
||||
ENSURE(count >= 2);
|
||||
}
|
||||
|
||||
static void tst_display() {
|
||||
std::cout << "=== test display ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add leaf productions
|
||||
expr_ref zero(a.mk_int(0), m);
|
||||
expr_ref one(a.mk_int(1), m);
|
||||
te.add_production(zero);
|
||||
te.add_production(one);
|
||||
|
||||
// Add operator productions
|
||||
app_ref tmp_add(a.mk_add(zero, one), m);
|
||||
func_decl* add_decl = tmp_add->get_decl();
|
||||
te.add_production(add_decl);
|
||||
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
(void)e;
|
||||
count++;
|
||||
if (count >= 10) break;
|
||||
}
|
||||
|
||||
std::cout << "Internal state after enumeration:\n";
|
||||
std::ostringstream oss;
|
||||
te.display(oss);
|
||||
std::cout << oss.str();
|
||||
|
||||
// Verify display produced some output
|
||||
ENSURE(!oss.str().empty());
|
||||
}
|
||||
|
||||
static void tst_bitvector_enumeration() {
|
||||
std::cout << "=== test bitvector enumeration ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
bv_util bv(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add bitvector constants
|
||||
unsigned bv_size = 8;
|
||||
expr_ref bv_zero(bv.mk_numeral(0, bv_size), m);
|
||||
expr_ref bv_one(bv.mk_numeral(1, bv_size), m);
|
||||
te.add_production(bv_zero);
|
||||
te.add_production(bv_one);
|
||||
|
||||
// Add bvadd operator
|
||||
app_ref tmp_add(bv.mk_bv_add(bv_zero, bv_one), m);
|
||||
func_decl* bvadd = tmp_add->get_decl();
|
||||
te.add_production(bvadd);
|
||||
|
||||
sort* bv8 = bv.mk_sort(bv_size);
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(bv8)) {
|
||||
std::cout << "BV Term: " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 10) break;
|
||||
}
|
||||
|
||||
ENSURE(count >= 2);
|
||||
std::cout << "Enumerated " << count << " bitvector terms\n";
|
||||
}
|
||||
|
||||
static void tst_multiple_sorts() {
|
||||
std::cout << "=== test multiple sorts ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
arith_util a(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Add Int constants
|
||||
expr_ref i_zero(a.mk_int(0), m);
|
||||
expr_ref i_one(a.mk_int(1), m);
|
||||
te.add_production(i_zero);
|
||||
te.add_production(i_one);
|
||||
|
||||
// Add Real constants
|
||||
expr_ref r_zero(a.mk_real(0), m);
|
||||
expr_ref r_one(a.mk_real(1), m);
|
||||
te.add_production(r_zero);
|
||||
te.add_production(r_one);
|
||||
|
||||
// Enumerate Int terms
|
||||
sort* int_sort = a.mk_int();
|
||||
unsigned int_count = 0;
|
||||
for (expr* e : te.enum_terms(int_sort)) {
|
||||
std::cout << "Int Term: " << mk_pp(e, m) << "\n";
|
||||
int_count++;
|
||||
if (int_count >= 5) break;
|
||||
}
|
||||
|
||||
ENSURE(int_count >= 2);
|
||||
std::cout << "Enumerated " << int_count << " Int terms\n";
|
||||
}
|
||||
|
||||
static void tst_nested_array_enumeration() {
|
||||
std::cout << "=== test nested array enumeration (Array(A, Array(B, A))) ===\n";
|
||||
ast_manager m;
|
||||
reg_decl_plugins(m);
|
||||
array_util arr(m);
|
||||
|
||||
term_enumeration te(m);
|
||||
|
||||
// Create uninterpreted sorts A and B
|
||||
sort_ref sort_A(m.mk_uninterpreted_sort(symbol("A")), m);
|
||||
sort_ref sort_B(m.mk_uninterpreted_sort(symbol("B")), m);
|
||||
|
||||
// Create nested array sort: Array(B, A) - arrays indexed by B returning A
|
||||
sort_ref array_B_A(arr.mk_array_sort(sort_B, sort_A), m);
|
||||
|
||||
// Create outer array sort: Array(A, Array(B, A)) - arrays indexed by A returning Array(B,A)
|
||||
sort_ref array_A_arrayBA(arr.mk_array_sort(sort_A, array_B_A), m);
|
||||
|
||||
std::cout << "Sort A: " << mk_pp(sort_A.get(), m) << "\n";
|
||||
std::cout << "Sort B: " << mk_pp(sort_B.get(), m) << "\n";
|
||||
std::cout << "Sort Array(B, A): " << mk_pp(array_B_A.get(), m) << "\n";
|
||||
std::cout << "Sort Array(A, Array(B, A)): " << mk_pp(array_A_arrayBA.get(), m) << "\n";
|
||||
|
||||
// Add constants of sort A
|
||||
app_ref a0(m.mk_const(symbol("a0"), sort_A), m);
|
||||
app_ref a1(m.mk_const(symbol("a1"), sort_A), m);
|
||||
te.add_production(a0);
|
||||
te.add_production(a1);
|
||||
|
||||
// Add constants of sort B
|
||||
app_ref b0(m.mk_const(symbol("b0"), sort_B), m);
|
||||
app_ref b1(m.mk_const(symbol("b1"), sort_B), m);
|
||||
te.add_production(b0);
|
||||
te.add_production(b1);
|
||||
|
||||
// Add a constant array of inner type Array(B, A) - const_array(a0) : Array(B, A)
|
||||
app_ref const_inner(arr.mk_const_array(array_B_A, a0), m);
|
||||
te.add_production(const_inner);
|
||||
|
||||
// Add a constant array of outer type Array(A, Array(B, A))
|
||||
app_ref const_outer(arr.mk_const_array(array_A_arrayBA, const_inner), m);
|
||||
te.add_production(const_outer);
|
||||
|
||||
// Add store operator for the inner array type Array(B, A)
|
||||
// store(array, index, value) : store(Array(B,A), B, A) -> Array(B,A)
|
||||
expr* store_inner_args[3] = { const_inner.get(), b0.get(), a0.get() };
|
||||
app_ref tmp_store_inner(arr.mk_store(3, store_inner_args), m);
|
||||
func_decl* store_inner_decl = tmp_store_inner->get_decl();
|
||||
te.add_production(store_inner_decl);
|
||||
|
||||
// Add store operator for the outer array type Array(A, Array(B, A))
|
||||
// store(array, index, value) : store(Array(A, Array(B,A)), A, Array(B,A)) -> Array(A, Array(B,A))
|
||||
expr* store_outer_args[3] = { const_outer.get(), a0.get(), const_inner.get() };
|
||||
app_ref tmp_store_outer(arr.mk_store(3, store_outer_args), m);
|
||||
func_decl* store_outer_decl = tmp_store_outer->get_decl();
|
||||
te.add_production(store_outer_decl);
|
||||
|
||||
// Add select operator for the outer array (returns Array(B, A))
|
||||
// select(Array(A, Array(B,A)), A) -> Array(B, A)
|
||||
app_ref tmp_select_outer(arr.mk_select(const_outer.get(), a0.get()), m);
|
||||
func_decl* select_outer_decl = tmp_select_outer->get_decl();
|
||||
te.add_production(select_outer_decl);
|
||||
|
||||
// Enumerate terms of the nested array sort Array(A, Array(B, A))
|
||||
std::cout << "\nEnumerating terms of sort Array(A, Array(B, A)):\n";
|
||||
unsigned count = 0;
|
||||
for (expr* e : te.enum_terms(array_A_arrayBA)) {
|
||||
std::cout << " Term " << count << ": " << mk_pp(e, m) << "\n";
|
||||
count++;
|
||||
if (count >= 15) break; // Limit output
|
||||
}
|
||||
|
||||
ENSURE(count >= 1); // At least the constant array
|
||||
std::cout << "Enumerated " << count << " terms of sort Array(A, Array(B, A))\n";
|
||||
|
||||
te.display(std::cout);
|
||||
}
|
||||
|
||||
void tst_term_enumeration() {
|
||||
tst_basic_enumeration();
|
||||
tst_enumeration_with_operators();
|
||||
tst_observational_equivalence_filter();
|
||||
tst_display();
|
||||
tst_bitvector_enumeration();
|
||||
tst_multiple_sorts();
|
||||
tst_nested_array_enumeration();
|
||||
std::cout << "All term_enumeration tests passed!\n";
|
||||
}
|
||||
|
|
@ -700,7 +700,7 @@ mpz mpz_manager<SYNCH>::mod2k(mpz const & a, unsigned k) {
|
|||
ensure_mpz_t a1(a);
|
||||
mk_big(result);
|
||||
MPZ_BEGIN_CRITICAL();
|
||||
mpz_tdiv_r_2exp(*result.m_ptr, a1(), k);
|
||||
mpz_fdiv_r_2exp(*result.m_ptr, a1(), k);
|
||||
MPZ_END_CRITICAL();
|
||||
#endif
|
||||
return result;
|
||||
|
|
@ -1904,8 +1904,11 @@ std::string mpz_manager<SYNCH>::to_string(mpz const & a) const {
|
|||
|
||||
template<bool SYNCH>
|
||||
unsigned mpz_manager<SYNCH>::hash(mpz const & a) {
|
||||
if (is_small(a))
|
||||
return ::abs(a.m_val);
|
||||
if (is_small(a)) {
|
||||
// compute abs in unsigned arithmetic: ::abs(INT_MIN) is undefined
|
||||
unsigned u = static_cast<unsigned>(a.m_val);
|
||||
return a.m_val < 0 ? 0u - u : u;
|
||||
}
|
||||
#ifndef _MP_GMP
|
||||
unsigned sz = size(a);
|
||||
if (sz == 1)
|
||||
|
|
|
|||
|
|
@ -132,15 +132,15 @@ public:
|
|||
}
|
||||
|
||||
void insert(Key * const k, Value const & v) {
|
||||
m_table.insert(key_data(k, v));
|
||||
m_table.insert(key_data{k, v});
|
||||
}
|
||||
|
||||
void insert(Key * const k, Value && v) {
|
||||
m_table.insert(key_data(k, std::move(v)));
|
||||
m_table.insert(key_data{k, std::move(v)});
|
||||
}
|
||||
|
||||
Value& insert_if_not_there(Key * k, Value const & v) {
|
||||
return m_table.insert_if_not_there2(key_data(k, v))->get_data().m_value;
|
||||
return m_table.insert_if_not_there2(key_data{k, v})->get_data().m_value;
|
||||
}
|
||||
|
||||
Value& insert_if_not_there(Key * k, Value && v) {
|
||||
|
|
@ -190,7 +190,7 @@ public:
|
|||
}
|
||||
|
||||
iterator find_iterator(Key * k) const {
|
||||
return m_table.find(key_data(k));
|
||||
return m_table.find(key_data{k});
|
||||
}
|
||||
|
||||
bool contains(Key * k) const {
|
||||
|
|
@ -198,7 +198,7 @@ public:
|
|||
}
|
||||
|
||||
void remove(Key * k) {
|
||||
m_table.remove(key_data(k));
|
||||
m_table.remove(key_data{k});
|
||||
}
|
||||
|
||||
void erase(Key * k) {
|
||||
|
|
@ -209,7 +209,7 @@ public:
|
|||
|
||||
void get_collisions(Key * k, vector<Key*>& collisions) {
|
||||
vector<key_data> cs;
|
||||
m_table.get_collisions(key_data(k), cs);
|
||||
m_table.get_collisions(key_data{k}, cs);
|
||||
for (key_data const& kd : cs) {
|
||||
collisions.push_back(kd.m_key);
|
||||
}
|
||||
|
|
@ -239,5 +239,3 @@ void erase_dealloc_value(obj_map<Key, Value*> & m, Key * k) {
|
|||
dealloc(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,17 +59,13 @@ protected:
|
|||
class entry;
|
||||
public:
|
||||
class key_data {
|
||||
Key1 * m_key1;
|
||||
Key2 * m_key2;
|
||||
Key1 * m_key1 = nullptr;
|
||||
Key2 * m_key2 = nullptr;
|
||||
Value m_value;
|
||||
unsigned m_hash;
|
||||
unsigned m_hash = 0;
|
||||
friend class entry;
|
||||
public:
|
||||
key_data():
|
||||
m_key1(nullptr),
|
||||
m_key2(nullptr),
|
||||
m_hash(0) {
|
||||
}
|
||||
key_data() = default;
|
||||
key_data(Key1 * k1, Key2 * k2):
|
||||
m_key1(k1),
|
||||
m_key2(k2) {
|
||||
|
|
|
|||
|
|
@ -60,19 +60,14 @@ protected:
|
|||
class entry;
|
||||
public:
|
||||
class key_data {
|
||||
Key1 * m_key1;
|
||||
Key2 * m_key2;
|
||||
Key3 * m_key3;
|
||||
Key1 * m_key1 = nullptr;
|
||||
Key2 * m_key2 = nullptr;
|
||||
Key3 * m_key3 = nullptr;
|
||||
Value m_value;
|
||||
unsigned m_hash;
|
||||
unsigned m_hash = 0;
|
||||
friend class entry;
|
||||
public:
|
||||
key_data():
|
||||
m_key1(nullptr),
|
||||
m_key2(nullptr),
|
||||
m_key3(nullptr),
|
||||
m_hash(0) {
|
||||
}
|
||||
key_data() = default;
|
||||
key_data(Key1 * k1, Key2 * k2, Key3 * k3):
|
||||
m_key1(k1),
|
||||
m_key2(k2),
|
||||
|
|
|
|||
|
|
@ -30,17 +30,6 @@ class symbol_table {
|
|||
struct key_data {
|
||||
symbol m_key;
|
||||
T m_data;
|
||||
|
||||
key_data() = default;
|
||||
|
||||
explicit key_data(symbol k):
|
||||
m_key(k) {
|
||||
}
|
||||
|
||||
key_data(symbol k, const T & d):
|
||||
m_key(k),
|
||||
m_data(d) {
|
||||
}
|
||||
};
|
||||
|
||||
struct key_data_hash_proc {
|
||||
|
|
@ -129,7 +118,7 @@ public:
|
|||
}
|
||||
|
||||
bool contains(symbol key) const {
|
||||
return m_sym_table.contains(key_data(key));
|
||||
return m_sym_table.contains(key_data{key});
|
||||
}
|
||||
|
||||
unsigned get_scope_level() const {
|
||||
|
|
@ -148,11 +137,11 @@ public:
|
|||
m_trail_stack.push_back(dummy);
|
||||
key_data & new_entry = m_trail_stack.back();
|
||||
new_entry.m_key = symbol::mark(new_entry.m_key);
|
||||
m_sym_table.insert(key_data(key, data));
|
||||
m_sym_table.insert(key_data{key, data});
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_sym_table.insert(key_data(key, data));
|
||||
m_sym_table.insert(key_data{key, data});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue