mirror of
https://github.com/Z3Prover/z3
synced 2026-07-12 01:56:22 +00:00
Merge remote-tracking branch 'origin/master' into c3
This commit is contained in:
commit
706f62286e
199 changed files with 11004 additions and 4584 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 -->
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ go run basic_example.go
|
|||
|
||||
## Memory Management
|
||||
|
||||
The Go bindings use `runtime.SetFinalizer` to automatically manage Z3 reference counts. You don't need to manually call inc_ref/dec_ref. However, be aware that finalizers run during garbage collection, so resources may not be freed immediately.
|
||||
The Go bindings use `runtime.SetFinalizer` to automatically manage Z3 reference counts. You don't need to manually call inc_ref/dec_ref. However, be aware that finalizers run during garbage collection, so resources may not be freed immediately. The bindings enable `Z3_enable_concurrent_dec_ref` when creating contexts so finalizer-driven decref operations are safe with concurrent GC.
|
||||
|
||||
## Thread Safety
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ type Context struct {
|
|||
// NewContext creates a new Z3 context with default configuration.
|
||||
func NewContext() *Context {
|
||||
ctx := &Context{ptr: C.Z3_mk_context_rc(C.Z3_mk_config())}
|
||||
C.Z3_enable_concurrent_dec_ref(ctx.ptr)
|
||||
runtime.SetFinalizer(ctx, func(c *Context) {
|
||||
C.Z3_del_context(c.ptr)
|
||||
})
|
||||
|
|
@ -98,6 +99,7 @@ func NewContext() *Context {
|
|||
// NewContextWithConfig creates a new Z3 context with the given configuration.
|
||||
func NewContextWithConfig(cfg *Config) *Context {
|
||||
ctx := &Context{ptr: C.Z3_mk_context_rc(cfg.ptr)}
|
||||
C.Z3_enable_concurrent_dec_ref(ctx.ptr)
|
||||
runtime.SetFinalizer(ctx, func(c *Context) {
|
||||
C.Z3_del_context(c.ptr)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -39,11 +39,16 @@ z3_add_component(rewriter
|
|||
rewriter.cpp
|
||||
seq_axioms.cpp
|
||||
seq_eq_solver.cpp
|
||||
seq_derive.cpp
|
||||
seq_subset.cpp
|
||||
seq_split.cpp
|
||||
seq_derive.cpp
|
||||
seq_range_collapse.cpp
|
||||
seq_range_predicate.cpp
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1196,15 +1196,15 @@ bool bool_rewriter::decompose_ite(expr *r, expr_ref &c, expr_ref &th, expr_ref &
|
|||
}
|
||||
for (expr *e : subterms::ground(expr_ref(r, m()))) {
|
||||
if (m().is_ite(e, cond, r1, r2)) {
|
||||
expr_safe_replace rep1(m());
|
||||
expr_safe_replace rep2(m());
|
||||
rep1.insert(e, r1);
|
||||
rep2.insert(e, r2);
|
||||
m_rep1.reset();
|
||||
m_rep2.reset();
|
||||
m_rep1.insert(e, r1);
|
||||
m_rep2.insert(e, r2);
|
||||
c = cond;
|
||||
th = r;
|
||||
el = r;
|
||||
rep1(th);
|
||||
rep2(el);
|
||||
m_rep1(th);
|
||||
m_rep2(el);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1212,6 +1212,4 @@ bool bool_rewriter::decompose_ite(expr *r, expr_ref &c, expr_ref &th, expr_ref &
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template class rewriter_tpl<bool_rewriter_cfg>;
|
||||
template class rewriter_tpl<bool_rewriter_cfg>;
|
||||
|
|
@ -20,6 +20,7 @@ Notes:
|
|||
|
||||
#include "ast/ast.h"
|
||||
#include "ast/rewriter/rewriter.h"
|
||||
#include "ast/rewriter/expr_safe_replace.h"
|
||||
#include "util/params.h"
|
||||
|
||||
/**
|
||||
|
|
@ -64,6 +65,7 @@ class bool_rewriter {
|
|||
ptr_vector<expr> m_todo1, m_todo2;
|
||||
unsigned_vector m_counts1, m_counts2;
|
||||
expr_mark m_marked;
|
||||
expr_safe_replace m_rep1, m_rep2;
|
||||
|
||||
br_status mk_flat_and_core(unsigned num_args, expr * const * args, expr_ref & result);
|
||||
br_status mk_flat_or_core(unsigned num_args, expr * const * args, expr_ref & result);
|
||||
|
|
@ -87,7 +89,7 @@ class bool_rewriter {
|
|||
expr_ref simplify_eq_ite(expr* value, expr* ite);
|
||||
|
||||
public:
|
||||
bool_rewriter(ast_manager & m, params_ref const & p = params_ref()):m_manager(m), m_local_ctx_cost(0) {
|
||||
bool_rewriter(ast_manager & m, params_ref const & p = params_ref()):m_manager(m), m_local_ctx_cost(0), m_rep1(m), m_rep2(m) {
|
||||
updt_params(p);
|
||||
}
|
||||
ast_manager & m() const { return m_manager; }
|
||||
|
|
@ -243,7 +245,9 @@ public:
|
|||
void mk_nor(expr * arg1, expr * arg2, expr_ref & result);
|
||||
void mk_ge2(expr* a, expr* b, expr* c, expr_ref& result);
|
||||
|
||||
|
||||
// If r is, or contains, an if-then-else, decompose it into a top-level
|
||||
// ite by hoisting the (first) inner ite condition: returns c, th, el such
|
||||
// that r is equivalent to (ite c th el). Returns false if r has no ite.
|
||||
bool decompose_ite(expr *r, expr_ref &c, expr_ref &th, expr_ref &el);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -452,6 +452,8 @@ namespace seq {
|
|||
// |t| = 0 => |s| = 0 or indexof(t,s,offset) = -1
|
||||
// ~contains(t,s) => indexof(t,s,offset) = -1
|
||||
|
||||
add_clause(mk_ge(i, -1));
|
||||
|
||||
add_clause(cnt, i_eq_m1);
|
||||
add_clause(~t_eq_empty, s_eq_empty, i_eq_m1);
|
||||
|
||||
|
|
@ -638,8 +640,8 @@ namespace seq {
|
|||
add_clause(~i_ge_0, i_ge_len_s, mk_eq(i, len_x));
|
||||
}
|
||||
|
||||
add_clause(i_ge_0, mk_seq_eq(e, emp));
|
||||
add_clause(~i_ge_len_s, mk_seq_eq(e, emp));
|
||||
add_clause(i_ge_0, mk_eq(e, emp));
|
||||
add_clause(~i_ge_len_s, mk_eq(e, emp));
|
||||
add_clause(~i_ge_0, i_ge_len_s, mk_eq(one, len_e));
|
||||
add_clause(mk_le(len_e, 1));
|
||||
}
|
||||
|
|
@ -1066,7 +1068,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));
|
||||
throw default_exception("replace-re is not supported");
|
||||
throw default_exception("no support for replace-re");
|
||||
}
|
||||
|
||||
// A basic strategy for supporting replace_all and other
|
||||
|
|
@ -1075,34 +1077,22 @@ namespace seq {
|
|||
// using iterative deepening can be re-used.
|
||||
//
|
||||
// create recursive relation 'ra' with properties:
|
||||
// ra(i, j, s, p, t, r) =
|
||||
// if len(s) = i && len(r) = j then
|
||||
// true
|
||||
// else if len(s) > i = 0 && p = "" && r = t + s then
|
||||
// true
|
||||
// else if len(s) > i && p != "" &&
|
||||
// s = extract(s, 0, i) + p + extract(s, i + len(p), len(s)) &&
|
||||
// r = extract(r, 0, i) + t + extract(r, i + len(p), len(r)) && ra(i + len(p), j + len(t), s, p, t, r)
|
||||
// else if ~prefix(p, extract(s, i, len(s)) && at(s,i) = at(r,j) then
|
||||
// ra(i + 1, j + 1, s, p, t, r)
|
||||
// else false
|
||||
// ra(i, j, s, p, t, r) <- len(s) = i && len(r) = j
|
||||
// ra(i, j, s, p, t, r) <- len(s) > i = 0 && p = "" && r = t + s
|
||||
// ra(i, j, s, p, t, r) <- len(s) > i && p != "" && s = extract(s, 0, i) + p + extract(s, i + len(p), len(s)) && r = extract(r, 0, i) + t + extract(r, i + len(p), len(r)) && ra(i + len(p), j + len(t), s, p, t, r)
|
||||
// ra(i, s, p, t, r) <- ~prefix(p, extract(s, i, len(s)) && at(s,i) = at(r,j) && ra(i + 1, j + 1, s, p, t, r)
|
||||
// which amounts to:
|
||||
//
|
||||
//
|
||||
// Then assert
|
||||
// ra(s, p, t, replace_all(s, p, t))
|
||||
//
|
||||
// ra(s, p, t, r) is a recursive predicate:
|
||||
// ra(s, p, t, r) iff replace_all(s, p, t) = r
|
||||
//
|
||||
// Base case, empty s or p: r = s
|
||||
// Match case, prefix(p, s): s = p ++ s', r = t ++ r', ra(s', p, t, r')
|
||||
// No-match case: r[0] = s[0], ra(s[1:], p, t, r[1:])
|
||||
//
|
||||
// Assert: ra(s, p, t, replace_all(s, p, t))
|
||||
//
|
||||
void axioms::replace_all_axiom(expr* r) {
|
||||
expr* s = nullptr, *p = nullptr, *t = nullptr;
|
||||
VERIFY(seq.str.is_replace_all(r, s, p, t));
|
||||
recfun::util rec(m);
|
||||
recfun::decl::plugin& plugin = rec.get_plugin();
|
||||
recfun_replace replace(m);
|
||||
sort* srt = s->get_sort();
|
||||
sort* domain[4] = { srt, srt, srt, srt };
|
||||
auto ra = rec.find_def_decl(symbol("ra"), 4, domain, m.mk_bool_sort(), true);
|
||||
|
|
@ -1146,7 +1136,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));
|
||||
throw default_exception("replace_re_all is not supported");
|
||||
throw default_exception("no support for replace-re-all");
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1345,7 +1335,7 @@ namespace seq {
|
|||
|
||||
/**
|
||||
* Consider the recursive definition of negated contains:
|
||||
~contains(a, b) =
|
||||
~contains(a, b) =
|
||||
if |b| > |a| then true
|
||||
else if |b| = |a| then a != b
|
||||
else ~prefix(b, a) and ~contains(a[1:], b)
|
||||
|
|
@ -1420,9 +1410,9 @@ namespace seq {
|
|||
return bound_tracker;
|
||||
}
|
||||
|
||||
// |u| != |v| OR
|
||||
// |u| != |v| OR
|
||||
// (u = w[a]u' AND v = w[b]v' AND a != b AND |u'| = |v'|)
|
||||
void axioms::diseq_axiom(expr *u, expr *v) {
|
||||
void axioms::diseq_axiom(expr *u, expr *v) {
|
||||
expr_ref u_len(mk_len(u), m);
|
||||
expr_ref v_len(mk_len(v), m);
|
||||
expr_ref len_eq(mk_eq(u_len, v_len), m);
|
||||
|
|
|
|||
1520
src/ast/rewriter/seq_derive.cpp
Normal file
1520
src/ast/rewriter/seq_derive.cpp
Normal file
File diff suppressed because it is too large
Load diff
266
src/ast/rewriter/seq_derive.h
Normal file
266
src/ast/rewriter/seq_derive.h
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
seq_derive.h
|
||||
|
||||
Abstract:
|
||||
|
||||
Symbolic derivative computation for regular expressions.
|
||||
Produces an ITE-tree (transition regex) representation where
|
||||
the free variable is de Bruijn index 0 representing the input character.
|
||||
|
||||
Based on the theory of symbolic derivatives and transition regexes:
|
||||
- Veanes et al., "On Symbolic Derivatives and Transition Regexes" (LPAR 2024)
|
||||
- Varatalu, Veanes, Ernits, "RE#" (POPL 2025)
|
||||
- Stanford, Veanes, Bjørner, "Symbolic Boolean Derivatives" (PLDI 2021)
|
||||
|
||||
Authors:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2025-06-03
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
#include "ast/rewriter/bool_rewriter.h"
|
||||
#include "util/obj_pair_hashtable.h"
|
||||
#include "util/obj_triple_hashtable.h"
|
||||
#include <functional>
|
||||
|
||||
class seq_rewriter;
|
||||
|
||||
namespace seq {
|
||||
|
||||
enum class derivative_kind { antimirov_t, brzozowski_t };
|
||||
/**
|
||||
* Symbolic derivative engine for regular expressions.
|
||||
*
|
||||
* Given a regex r, operator()(r) computes a symbolic derivative δ(r)
|
||||
* represented as an ITE-tree over character predicates (using de Bruijn
|
||||
* variable 0 for the character). Evaluating the ITE-tree for a concrete
|
||||
* character 'a' yields the classical Brzozowski derivative δ_a(r).
|
||||
*
|
||||
* The ITE-tree structure implicitly defines minterms (equivalence classes
|
||||
* of characters indistinguishable by the regex).
|
||||
*
|
||||
* Key properties:
|
||||
* - Results are memoized for termination on cyclic derivative graphs
|
||||
* - Union/intersection operands are sorted for ACI canonicalization
|
||||
* - Depth-bounded to prevent stack overflow
|
||||
*/
|
||||
class derive {
|
||||
ast_manager& m;
|
||||
seq_util m_util;
|
||||
arith_util m_autil;
|
||||
bool_rewriter m_br;
|
||||
seq_rewriter& m_re;
|
||||
|
||||
// Cache: maps (ele, regex) pair to its derivative
|
||||
obj_pair_map<expr, expr, expr*> m_acache, m_bcache;
|
||||
obj_pair_map<expr, expr, expr*> m_atop_cache, m_btop_cache; // post-simplify cache
|
||||
expr_ref_vector m_trail; // pin cached results
|
||||
|
||||
// Op cache for ITE-hoisting operations (union, inter, concat, complement)
|
||||
// Path-aware caches: key is (a, b, path_expr) for binary ops, (a, path_expr) for complement
|
||||
obj_triple_map<expr, expr, expr, expr *> m_aunion_cache, m_bunion_cache, m_ainter_cache, m_binter_cache, m_axor_cache, m_bxor_cache;
|
||||
obj_pair_map<expr, expr, expr*> m_aconcat_cache, m_bconcat_cache;
|
||||
obj_pair_map<expr, expr, expr*> m_acomplement_cache, m_bcomplement_cache;
|
||||
|
||||
// Depth limiting
|
||||
unsigned m_depth { 0 };
|
||||
static const unsigned m_max_depth = 512;
|
||||
|
||||
seq_util::rex& re() { return m_util.re; }
|
||||
seq_util& u() { return m_util; }
|
||||
|
||||
derivative_kind m_derivative_kind = derivative_kind::antimirov_t;
|
||||
|
||||
// The element (character) for the current derivative computation
|
||||
expr_ref m_ele;
|
||||
|
||||
// Path state for inline pruning during mk_inter/mk_union/mk_complement
|
||||
using intervals_t = svector<std::pair<unsigned, unsigned>>;
|
||||
|
||||
// Path: vector of signed atoms
|
||||
svector<std::pair<expr*, bool>> m_path;
|
||||
// Intervals: feasible character ranges under current path (append-only)
|
||||
intervals_t m_intervals;
|
||||
unsigned m_intervals_start { 0 };
|
||||
// Stack of saved states for push/pop
|
||||
struct path_save { unsigned path_sz; unsigned intervals_sz; unsigned intervals_start; expr* path_expr; };
|
||||
svector<path_save> m_path_stack;
|
||||
// Boolean expression encoding of current path (for cache keys)
|
||||
expr_ref m_path_expr;
|
||||
|
||||
// Path interface
|
||||
lbool push(expr* c, bool sign); // l_true: implied, l_undef: pushed (must pop), l_false: contradicts
|
||||
void pop(); // restore state to matching push
|
||||
expr* get_path_expr() { return m_path_expr; }
|
||||
|
||||
obj_pair_map<expr, expr, expr *> &cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_acache : m_bcache;
|
||||
}
|
||||
|
||||
obj_pair_map<expr, expr, expr *> &top_cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_atop_cache : m_btop_cache;
|
||||
}
|
||||
|
||||
obj_triple_map<expr, expr, expr, expr *> &union_cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_aunion_cache : m_bunion_cache;
|
||||
}
|
||||
|
||||
obj_triple_map<expr, expr, expr, expr *> &inter_cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_ainter_cache : m_binter_cache;
|
||||
}
|
||||
|
||||
obj_triple_map<expr, expr, expr, expr *> &xor_cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_axor_cache : m_bxor_cache;
|
||||
}
|
||||
|
||||
obj_pair_map<expr, expr, expr *> &concat_cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_aconcat_cache : m_bconcat_cache;
|
||||
}
|
||||
|
||||
obj_pair_map<expr, expr, expr *> &complement_cache() {
|
||||
return m_derivative_kind == derivative_kind::antimirov_t ? m_acomplement_cache : m_bcomplement_cache;
|
||||
}
|
||||
|
||||
// Hoist ITE: apply_op through ite(c, t, e) with path pruning
|
||||
expr_ref apply_ite(expr* c, expr* t, expr* e, expr* r, std::function<expr_ref(expr*, expr*)> apply_op);
|
||||
expr_ref apply_ite(expr* c, expr* t1, expr* e1, expr* t2, expr* e2, std::function<expr_ref(expr*, expr*)> apply_op);
|
||||
expr_ref apply_ite(expr* c, expr* t, expr* e, std::function<expr_ref(expr*)> apply_op);
|
||||
// Common ITE dispatch for binary ops (union/inter)
|
||||
expr_ref hoist_ite(expr* a, expr* b, std::function<expr_ref(expr*, expr*)> apply_op);
|
||||
|
||||
// Evaluate a condition against the current path/intervals
|
||||
lbool eval_path_cond(expr* c);
|
||||
|
||||
// Internal helpers for push
|
||||
lbool push_path_atoms(expr* c, bool sign);
|
||||
lbool push_intervals_impl(expr* c, bool sign);
|
||||
|
||||
// Core derivative computation
|
||||
expr_ref derive_rec(expr* r);
|
||||
expr_ref derive_core(expr* r);
|
||||
|
||||
// Helpers for specific regex constructs
|
||||
expr_ref derive_to_re(expr* s, sort* seq_sort);
|
||||
expr_ref derive_range(expr* lo, expr* hi, sort* seq_sort);
|
||||
expr_ref derive_of_pred(expr* pred, sort* seq_sort);
|
||||
|
||||
// Nullable check: returns a Boolean expression
|
||||
expr_ref is_nullable(expr* r);
|
||||
expr_ref is_nullable_symbolic_regex(expr* r, sort* seq_sort);
|
||||
|
||||
// Smart constructors with path-aware simplification and ACI canonicalization
|
||||
expr_ref mk_union(expr* a, expr* b);
|
||||
bool are_complements(expr* a, expr* b);
|
||||
unsigned union_id(expr* e); // complement-aware ID for sorting
|
||||
bool is_subset(expr* a, expr* b);
|
||||
expr_ref mk_union_core(expr* a, expr* b);
|
||||
void add_union_elem(expr_ref_vector& set, expr* e);
|
||||
expr_ref mk_inter(expr* a, expr* b);
|
||||
expr_ref mk_inter_core(expr* a, expr* b);
|
||||
expr_ref mk_concat(expr* a, expr* b);
|
||||
expr_ref mk_complement(expr* a);
|
||||
expr_ref mk_complement_core(expr* a);
|
||||
expr_ref mk_xor(expr *a, expr *b);
|
||||
expr_ref mk_xor_core(expr *a, expr *b);
|
||||
expr_ref mk_core(decl_kind k, expr* a, expr* b);
|
||||
expr_ref mk_ite(expr* c, expr* t, expr* e);
|
||||
|
||||
// Distribute concatenation through ITE/union in derivative
|
||||
expr_ref mk_deriv_concat(expr* d, expr* tail);
|
||||
expr_ref mk_deriv_concat_core(expr* d, expr* tail);
|
||||
|
||||
// Extract head character and tail from a sequence expression
|
||||
bool get_head_tail(expr* s1, expr* s2, expr_ref& hd, expr_ref& tl);
|
||||
|
||||
// Predicate implication for character range conditions.
|
||||
bool pred_implies(bool sign_a, expr* a, bool sign_b, expr* b);
|
||||
bool pred_implies(expr* a, expr* b);
|
||||
|
||||
// Normalize reverse(r)
|
||||
expr_ref mk_regex_reverse(expr* r);
|
||||
|
||||
// Condition evaluation helpers
|
||||
lbool eval_cond(expr* cond);
|
||||
lbool eval_range_cond(expr* c);
|
||||
void intersect_intervals(unsigned lo, unsigned hi);
|
||||
void exclude_interval(unsigned lo, unsigned hi);
|
||||
|
||||
// Cofactor enumeration over a transition regex (ITE-tree).
|
||||
void get_cofactors_rec(expr* r, expr_ref_pair_vector& result);
|
||||
|
||||
// Re-apply union/intersection simplifications bottom-up to a cofactor
|
||||
// leaf. decompose_ite substitutes ITE branch values structurally
|
||||
// (no simplification), so leaves can contain un-normalized nodes such
|
||||
// as union(R, none) or inter(R, none); this rebuilds them through
|
||||
// mk_union/mk_inter so equal states share a canonical form.
|
||||
expr_ref clean_leaf(expr* r);
|
||||
|
||||
sort* re_sort(expr* r) { return r->get_sort(); }
|
||||
sort* seq_sort(expr* r) { sort* s = nullptr; m_util.is_re(r, s); return s; }
|
||||
sort* ele_sort(expr* r) { sort* s = seq_sort(r); sort* e = nullptr; m_util.is_seq(s, e); return e; }
|
||||
|
||||
void reset();
|
||||
void reset_op_caches();
|
||||
|
||||
public:
|
||||
derive(ast_manager& m, seq_rewriter& re);
|
||||
|
||||
/**
|
||||
* Compute the derivative of regex r with respect to element ele.
|
||||
* When ele is a de Bruijn variable, produces a symbolic ITE-tree.
|
||||
* When ele is a concrete character, produces the concrete derivative.
|
||||
*/
|
||||
expr_ref operator()(derivative_kind k, expr* ele, expr* r);
|
||||
|
||||
/**
|
||||
* Convenience: symbolic derivative using de Bruijn var 0.
|
||||
*/
|
||||
expr_ref operator()(derivative_kind k, expr* r);
|
||||
|
||||
/**
|
||||
* Nullable check: returns a Boolean expression that is true iff r accepts the empty string.
|
||||
*/
|
||||
expr_ref nullable(expr* r) { return is_nullable(r); }
|
||||
|
||||
/**
|
||||
* Enumerate the cofactors (min-terms) of a transition regex r taken with
|
||||
* respect to element ele. r is an ITE-tree over character predicates on
|
||||
* ele; for every feasible path through the tree this produces a pair
|
||||
* (path_condition, leaf_regex). Infeasible character-interval
|
||||
* combinations are pruned using the same path/interval context that the
|
||||
* derivative engine uses while hoisting ITEs.
|
||||
*/
|
||||
void get_cofactors(expr* ele, expr* r, expr_ref_pair_vector& result);
|
||||
|
||||
/**
|
||||
* Compute the symbolic derivative of r and enumerate its reachable
|
||||
* leaves in fully ITE-hoisted normal form.
|
||||
*
|
||||
* Concretely this returns, for every feasible minterm (character
|
||||
* class) of δ(r), a pair (path_condition, target_regex). Every
|
||||
* if-then-else over the input character (including ones that would
|
||||
* otherwise be buried under a concat/union) is hoisted to the top
|
||||
* via the same path/interval pruning used by the derivative engine,
|
||||
* so each target_regex is free of (:var 0) and its nullability is
|
||||
* always decidable. Unions are kept intact as single leaves (a
|
||||
* union leaf denotes a single bisimulation state). Infeasible
|
||||
* minterms are pruned, so all returned leaves are reachable.
|
||||
*
|
||||
* This is the entry point the regex_bisim equivalence procedure
|
||||
* uses: it consumes the target_regex of each pair and ignores the
|
||||
* (redundant) path condition.
|
||||
*/
|
||||
void derivative_cofactors(expr* r, expr_ref_pair_vector& result);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
160
src/ast/rewriter/seq_range_collapse.cpp
Normal file
160
src/ast/rewriter/seq_range_collapse.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
seq_range_collapse.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Implementation of regex <-> range_predicate translation for the
|
||||
boolean-combination-of-ranges fragment. See header for the recognized
|
||||
grammar and the canonical regex AST emitted by materialization.
|
||||
|
||||
Authors:
|
||||
|
||||
Margus Veanes (veanes) 2026
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/rewriter/seq_range_collapse.h"
|
||||
|
||||
namespace seq {
|
||||
|
||||
bool regex_to_range_predicate(seq_util& u, expr* r, range_predicate& out) {
|
||||
// The range algebra only models sets of single characters over the
|
||||
// unsigned character domain [0, max_char]. Guard against any regex
|
||||
// whose element type is not a sequence of characters (e.g. a regex
|
||||
// over (Seq Int) or (Seq (Seq Char))): for such regexes the
|
||||
// re.range/re.union/... matchers below would silently fabricate a
|
||||
// character-class predicate and change semantics. Reject them up
|
||||
// front so callers fall back to the generic regex path.
|
||||
sort* seq_sort = nullptr;
|
||||
if (!u.is_re(r, seq_sort) || !u.is_string(seq_sort))
|
||||
return false;
|
||||
|
||||
unsigned const max_char = u.max_char();
|
||||
auto& re = u.re;
|
||||
|
||||
if (re.is_empty(r)) {
|
||||
out = range_predicate::empty(max_char);
|
||||
return true;
|
||||
}
|
||||
if (re.is_full_char(r)) {
|
||||
out = range_predicate::top(max_char);
|
||||
return true;
|
||||
}
|
||||
unsigned lo = 0, hi = 0;
|
||||
expr* lo_e = nullptr;
|
||||
expr* hi_e = nullptr;
|
||||
if (re.is_range(r, lo_e, hi_e)) {
|
||||
auto extract_char = [&](expr* e, unsigned& c) -> bool {
|
||||
if (u.is_const_char(e, c)) return true;
|
||||
expr* inner = nullptr;
|
||||
if (u.str.is_unit(e, inner) && u.is_const_char(inner, c)) return true;
|
||||
zstring s;
|
||||
if (u.str.is_string(e, s) && s.length() == 1) {
|
||||
c = s[0];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!extract_char(lo_e, lo) || !extract_char(hi_e, hi))
|
||||
return false;
|
||||
// Empty/inverted range [lo > hi] is the empty regex.
|
||||
if (lo > hi) {
|
||||
out = range_predicate::empty(max_char);
|
||||
return true;
|
||||
}
|
||||
out = range_predicate::range(lo, hi, max_char);
|
||||
return true;
|
||||
}
|
||||
expr *a = nullptr, *b = nullptr, *c = nullptr;
|
||||
if (re.is_union(r, a, b)) {
|
||||
range_predicate pa(max_char), pb(max_char);
|
||||
if (!regex_to_range_predicate(u, a, pa)) return false;
|
||||
if (!regex_to_range_predicate(u, b, pb)) return false;
|
||||
out = pa | pb;
|
||||
return true;
|
||||
}
|
||||
auto mk_diff = [&](expr *a, expr *b) -> bool {
|
||||
range_predicate pa(max_char), pb(max_char);
|
||||
if (!regex_to_range_predicate(u, a, pa))
|
||||
return false;
|
||||
if (!regex_to_range_predicate(u, b, pb))
|
||||
return false;
|
||||
out = pa - pb;
|
||||
return true;
|
||||
};
|
||||
if (re.is_diff(r, a, b))
|
||||
return mk_diff(a, b);
|
||||
|
||||
if (re.is_intersection(r, a, b) && re.is_complement(b, c))
|
||||
return mk_diff(a, c);
|
||||
|
||||
if (re.is_intersection(r, a, b) && re.is_complement(a, c))
|
||||
return mk_diff(b, c);
|
||||
|
||||
if (re.is_intersection(r, a, b)) {
|
||||
range_predicate pa(max_char), pb(max_char);
|
||||
if (!regex_to_range_predicate(u, a, pa)) return false;
|
||||
if (!regex_to_range_predicate(u, b, pb)) return false;
|
||||
out = pa & pb;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: re.complement is intentionally NOT handled here.
|
||||
// re.complement is the SEQUENCE-level complement: its language
|
||||
// includes the empty string, strings of length >= 2, and any
|
||||
// length-1 string outside the operand. A character-class
|
||||
// range_predicate can only describe a set of length-1 strings,
|
||||
// so collapsing re.complement(R) to ~R (character-level
|
||||
// complement) would change semantics whenever R is wrapped in
|
||||
// any sequence-level context (e.g. re.diff at the top level,
|
||||
// or membership tests). De-Morgan equivalences and the
|
||||
// special cases re.complement(re.empty) / re.complement(re.full)
|
||||
// are already handled directly in seq_rewriter::mk_re_complement.
|
||||
return false;
|
||||
}
|
||||
|
||||
static expr_ref mk_unit_string_from_char(seq_util& u, unsigned c) {
|
||||
return expr_ref(u.str.mk_string(zstring(c)), u.get_manager());
|
||||
}
|
||||
|
||||
static expr_ref mk_single_range_regex(seq_util& u, unsigned lo, unsigned hi, sort* re_sort) {
|
||||
ast_manager& m = u.get_manager();
|
||||
return expr_ref(u.re.mk_range(re_sort, lo, hi), m);
|
||||
}
|
||||
|
||||
expr_ref range_predicate_to_regex(seq_util& u, range_predicate const& p, sort* seq_sort) {
|
||||
ast_manager& m = u.get_manager();
|
||||
sort* re_sort = u.re.mk_re(seq_sort);
|
||||
if (p.is_empty())
|
||||
return expr_ref(u.re.mk_empty(re_sort), m);
|
||||
unsigned const n = p.num_ranges();
|
||||
SASSERT(n > 0);
|
||||
if (n == 1) {
|
||||
auto [lo, hi] = p[0];
|
||||
return mk_single_range_regex(u, lo, hi, re_sort);
|
||||
}
|
||||
// Build single-range AST nodes first, then sort by expression id
|
||||
// so the resulting right-associated union matches the canonical
|
||||
// id-sorted shape that seq_rewriter::merge_regex_sets expects.
|
||||
// Without this the merge algorithm produces incorrect unions
|
||||
// when it has to combine our materialized output with another
|
||||
// (id-sorted) regex set.
|
||||
expr_ref_vector ranges(m);
|
||||
for (unsigned i = 0; i < n; ++i) {
|
||||
auto [lo, hi] = p[i];
|
||||
ranges.push_back(mk_single_range_regex(u, lo, hi, re_sort));
|
||||
}
|
||||
std::sort(ranges.data(), ranges.data() + ranges.size(),
|
||||
[](expr* a, expr* b) { return a->get_id() < b->get_id(); });
|
||||
expr_ref acc(ranges.get(n - 1), m);
|
||||
for (unsigned i = n - 1; i-- > 0; )
|
||||
acc = expr_ref(u.re.mk_union(ranges.get(i), acc), m);
|
||||
return acc;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/ast/rewriter/seq_range_collapse.h
Normal file
71
src/ast/rewriter/seq_range_collapse.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
seq_range_collapse.h
|
||||
|
||||
Abstract:
|
||||
|
||||
Recognize regexes that are boolean combinations of character-class
|
||||
primitives (re.empty, re.full_char, re.range with concrete chars,
|
||||
and re.union/inter/comp/diff over translatable arguments), and
|
||||
materialize a seq::range_predicate back into a canonical regex AST.
|
||||
|
||||
Together with seq_rewriter integration, this lets any boolean
|
||||
combination of character-class regexes collapse to a canonical
|
||||
multi-range form, so that equivalent character classes share AST
|
||||
identity, and downstream consumers (derivative, OneStep, caching)
|
||||
can short-circuit them as pure range predicates.
|
||||
|
||||
Authors:
|
||||
|
||||
Margus Veanes (veanes) 2026
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "ast/rewriter/seq_range_predicate.h"
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
|
||||
namespace seq {
|
||||
|
||||
/**
|
||||
* If r is a boolean combination of character-class regex primitives
|
||||
* over the unsigned character domain [0, max_char], compute the
|
||||
* equivalent range_predicate and return true. Otherwise return false
|
||||
* with out untouched.
|
||||
*
|
||||
* Recognized fragment (all character-class-preserving operations):
|
||||
* re.empty -> empty
|
||||
* re.full_char_set -> top
|
||||
* re.range "c_lo" "c_hi" (concrete) -> [c_lo, c_hi]
|
||||
* re.union r1 r2 -> p1 | p2
|
||||
* re.intersection r1 r2 -> p1 & p2
|
||||
* re.diff r1 r2 -> p1 - p2
|
||||
*
|
||||
* Notably re.complement is NOT recognized: it is a SEQUENCE-level
|
||||
* complement (over all of Σ*), not a character-class complement, so
|
||||
* collapsing it would change semantics whenever the result is used
|
||||
* in any non-character-class context. Sequence-level rewrites for
|
||||
* re.complement (double-comp, deMorgan, etc.) are handled directly
|
||||
* in seq_rewriter::mk_re_complement.
|
||||
*/
|
||||
bool regex_to_range_predicate(seq_util& u, expr* r, range_predicate& out);
|
||||
|
||||
/**
|
||||
* Canonical materialization of p as a regex AST over the given
|
||||
* sequence sort. Two range_predicates with equal canonical
|
||||
* representations produce structurally identical regex ASTs:
|
||||
*
|
||||
* empty -> re.empty
|
||||
* top -> re.full_char_set
|
||||
* single range [lo, hi] -> re.range "lo" "hi"
|
||||
* multiple ranges -> right-associated re.union of single
|
||||
* ranges, in increasing order of lo
|
||||
* (matching the canonical range order
|
||||
* held by range_predicate).
|
||||
*/
|
||||
expr_ref range_predicate_to_regex(seq_util& u, range_predicate const& p, sort* seq_sort);
|
||||
|
||||
}
|
||||
292
src/ast/rewriter/seq_range_predicate.cpp
Normal file
292
src/ast/rewriter/seq_range_predicate.cpp
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
seq_range_predicate.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Implementation of the specialized range-algebra used by symbolic
|
||||
derivative computation and regex rewriting. See seq_range_predicate.h
|
||||
for the algebraic specification.
|
||||
|
||||
All Boolean operations are implemented as single linear sweeps over
|
||||
the canonical sorted range vectors and produce canonical output
|
||||
(sorted, disjoint, non-adjacent).
|
||||
|
||||
Authors:
|
||||
|
||||
Margus Veanes (veanes) 2026
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/rewriter/seq_range_predicate.h"
|
||||
#include "util/debug.h"
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
|
||||
namespace seq {
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Factories
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
range_predicate range_predicate::empty(unsigned max_char) {
|
||||
return range_predicate(max_char);
|
||||
}
|
||||
|
||||
range_predicate range_predicate::top(unsigned max_char) {
|
||||
range_predicate r(max_char);
|
||||
r.m_ranges.push_back({0u, max_char});
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
range_predicate range_predicate::singleton(unsigned c, unsigned max_char) {
|
||||
SASSERT(c <= max_char);
|
||||
range_predicate r(max_char);
|
||||
r.m_ranges.push_back({c, c});
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
range_predicate range_predicate::range(unsigned lo, unsigned hi, unsigned max_char) {
|
||||
range_predicate r(max_char);
|
||||
if (lo <= hi && lo <= max_char) {
|
||||
unsigned clipped_hi = hi <= max_char ? hi : max_char;
|
||||
r.m_ranges.push_back({lo, clipped_hi});
|
||||
}
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Invariants and observers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool range_predicate::well_formed() const {
|
||||
for (unsigned i = 0; i < m_ranges.size(); ++i) {
|
||||
auto [lo, hi] = m_ranges[i];
|
||||
if (lo > hi) return false;
|
||||
if (hi > m_max_char) return false;
|
||||
if (i > 0) {
|
||||
unsigned prev_hi = m_ranges[i - 1].second;
|
||||
// Non-adjacent and sorted: prev_hi + 1 < lo, with care
|
||||
// around prev_hi == UINT_MAX which we never expect because
|
||||
// hi <= m_max_char.
|
||||
if (prev_hi + 1 >= lo) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool range_predicate::contains(unsigned c) const {
|
||||
// Binary search on first element of pairs.
|
||||
unsigned lo = 0, hi = m_ranges.size();
|
||||
while (lo < hi) {
|
||||
unsigned mid = lo + (hi - lo) / 2;
|
||||
auto [a, b] = m_ranges[mid];
|
||||
if (c < a) hi = mid;
|
||||
else if (c > b) lo = mid + 1;
|
||||
else return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t range_predicate::cardinality() const {
|
||||
uint64_t n = 0;
|
||||
for (auto [lo, hi] : m_ranges)
|
||||
n += static_cast<uint64_t>(hi) - static_cast<uint64_t>(lo) + 1u;
|
||||
return n;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Equality, ordering, hashing
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool range_predicate::equals(range_predicate const& o) const {
|
||||
if (m_max_char != o.m_max_char) return false;
|
||||
if (m_ranges.size() != o.m_ranges.size()) return false;
|
||||
for (unsigned i = 0; i < m_ranges.size(); ++i)
|
||||
if (m_ranges[i] != o.m_ranges[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool range_predicate::operator<(range_predicate const& o) const {
|
||||
if (m_max_char != o.m_max_char)
|
||||
return m_max_char < o.m_max_char;
|
||||
unsigned n = std::min(m_ranges.size(), o.m_ranges.size());
|
||||
for (unsigned i = 0; i < n; ++i) {
|
||||
auto a = m_ranges[i];
|
||||
auto b = o.m_ranges[i];
|
||||
if (a.first != b.first) return a.first < b.first;
|
||||
if (a.second != b.second) return a.second < b.second;
|
||||
}
|
||||
return m_ranges.size() < o.m_ranges.size();
|
||||
}
|
||||
|
||||
unsigned range_predicate::hash() const {
|
||||
// FNV-1a 32-bit over (max_char, then each (lo, hi)).
|
||||
uint32_t h = 2166136261u;
|
||||
auto step = [&](uint32_t x) {
|
||||
h ^= x;
|
||||
h *= 16777619u;
|
||||
};
|
||||
step(m_max_char);
|
||||
for (auto [lo, hi] : m_ranges) {
|
||||
step(lo);
|
||||
step(hi);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Boolean operations
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
// Append (lo, hi) to result, merging with the previous range if
|
||||
// adjacent or overlapping. Maintains canonical form.
|
||||
inline void append_merged(svector<std::pair<unsigned, unsigned>>& result,
|
||||
unsigned lo, unsigned hi) {
|
||||
SASSERT(lo <= hi);
|
||||
if (!result.empty() && result.back().second + 1 >= lo) {
|
||||
if (result.back().second < hi)
|
||||
result.back().second = hi;
|
||||
} else {
|
||||
result.push_back({lo, hi});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
range_predicate range_predicate::operator|(range_predicate const& o) const {
|
||||
SASSERT(m_max_char == o.m_max_char);
|
||||
range_predicate r(m_max_char);
|
||||
unsigned i = 0, j = 0;
|
||||
const unsigned n = m_ranges.size();
|
||||
const unsigned m = o.m_ranges.size();
|
||||
while (i < n && j < m) {
|
||||
auto a = m_ranges[i];
|
||||
auto b = o.m_ranges[j];
|
||||
if (a.first <= b.first) {
|
||||
append_merged(r.m_ranges, a.first, a.second);
|
||||
++i;
|
||||
} else {
|
||||
append_merged(r.m_ranges, b.first, b.second);
|
||||
++j;
|
||||
}
|
||||
}
|
||||
while (i < n) {
|
||||
auto a = m_ranges[i++];
|
||||
append_merged(r.m_ranges, a.first, a.second);
|
||||
}
|
||||
while (j < m) {
|
||||
auto b = o.m_ranges[j++];
|
||||
append_merged(r.m_ranges, b.first, b.second);
|
||||
}
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
range_predicate range_predicate::operator&(range_predicate const& o) const {
|
||||
SASSERT(m_max_char == o.m_max_char);
|
||||
range_predicate r(m_max_char);
|
||||
unsigned i = 0, j = 0;
|
||||
const unsigned n = m_ranges.size();
|
||||
const unsigned m = o.m_ranges.size();
|
||||
while (i < n && j < m) {
|
||||
auto [a_lo, a_hi] = m_ranges[i];
|
||||
auto [b_lo, b_hi] = o.m_ranges[j];
|
||||
unsigned lo = std::max(a_lo, b_lo);
|
||||
unsigned hi = std::min(a_hi, b_hi);
|
||||
if (lo <= hi)
|
||||
r.m_ranges.push_back({lo, hi});
|
||||
// Advance the range that ends first.
|
||||
if (a_hi < b_hi) ++i;
|
||||
else if (b_hi < a_hi) ++j;
|
||||
else { ++i; ++j; }
|
||||
}
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
range_predicate range_predicate::operator~() const {
|
||||
range_predicate r(m_max_char);
|
||||
unsigned cursor = 0;
|
||||
for (auto [lo, hi] : m_ranges) {
|
||||
if (cursor < lo)
|
||||
r.m_ranges.push_back({cursor, lo - 1});
|
||||
// Step past hi without overflow: hi <= m_max_char and we
|
||||
// only step when more characters remain.
|
||||
if (hi >= m_max_char) {
|
||||
cursor = m_max_char + 1; // sentinel: no more characters
|
||||
break;
|
||||
}
|
||||
cursor = hi + 1;
|
||||
}
|
||||
if (cursor <= m_max_char)
|
||||
r.m_ranges.push_back({cursor, m_max_char});
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
range_predicate range_predicate::operator-(range_predicate const& o) const {
|
||||
SASSERT(m_max_char == o.m_max_char);
|
||||
// A - B by linear sweep: for each range of A, subtract overlapping
|
||||
// ranges of B. Both inputs are sorted so we advance j monotonically.
|
||||
range_predicate r(m_max_char);
|
||||
unsigned j = 0;
|
||||
const unsigned m = o.m_ranges.size();
|
||||
for (auto [a_lo, a_hi] : m_ranges) {
|
||||
unsigned cursor = a_lo;
|
||||
while (j < m && o.m_ranges[j].second < cursor)
|
||||
++j;
|
||||
unsigned k = j;
|
||||
while (k < m && o.m_ranges[k].first <= a_hi) {
|
||||
auto [b_lo, b_hi] = o.m_ranges[k];
|
||||
if (cursor < b_lo)
|
||||
r.m_ranges.push_back({cursor, std::min(a_hi, b_lo - 1)});
|
||||
if (b_hi >= a_hi) {
|
||||
cursor = a_hi + 1;
|
||||
break;
|
||||
}
|
||||
cursor = b_hi + 1;
|
||||
++k;
|
||||
}
|
||||
if (cursor <= a_hi)
|
||||
r.m_ranges.push_back({cursor, a_hi});
|
||||
}
|
||||
SASSERT(r.well_formed());
|
||||
return r;
|
||||
}
|
||||
|
||||
range_predicate range_predicate::operator^(range_predicate const& o) const {
|
||||
SASSERT(m_max_char == o.m_max_char);
|
||||
// (A | B) - (A & B), but implemented directly with one linear sweep
|
||||
// over the union of breakpoints.
|
||||
return (*this | o) - (*this & o);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Display
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
std::ostream& range_predicate::display(std::ostream& out) const {
|
||||
if (m_ranges.empty()) {
|
||||
return out << "[]";
|
||||
}
|
||||
out << "[";
|
||||
bool first = true;
|
||||
for (auto [lo, hi] : m_ranges) {
|
||||
if (!first) out << ",";
|
||||
first = false;
|
||||
if (lo == hi)
|
||||
out << lo;
|
||||
else
|
||||
out << lo << "-" << hi;
|
||||
}
|
||||
return out << "]";
|
||||
}
|
||||
|
||||
}
|
||||
127
src/ast/rewriter/seq_range_predicate.h
Normal file
127
src/ast/rewriter/seq_range_predicate.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
seq_range_predicate.h
|
||||
|
||||
Abstract:
|
||||
|
||||
Specialized range-algebra over an unsigned character domain [0, max_char].
|
||||
|
||||
A range_predicate represents a subset of the character domain as a
|
||||
sorted sequence of non-overlapping, non-adjacent, non-empty ranges:
|
||||
|
||||
[(lo_0, hi_0), (lo_1, hi_1), ...] with hi_i + 1 < lo_{i+1}.
|
||||
|
||||
The representation is canonical, so two range_predicates over the same
|
||||
domain are extensionally equivalent iff their internal vectors are
|
||||
elementwise equal.
|
||||
|
||||
All Boolean operations (union, intersection, complement, difference)
|
||||
are linear in the total number of ranges and produce the canonical
|
||||
representation.
|
||||
|
||||
Intended use:
|
||||
* path conditions for symbolic derivative computation,
|
||||
* OneStep predicates capturing length-1 acceptance,
|
||||
* smart-constructor side conditions for regex rewrites such as
|
||||
R & psi --> toregex(OneStep(R) & psi).
|
||||
|
||||
The type is a pure value: no ast_manager allocation occurs in its
|
||||
construction or its Boolean operations. Conversion to and from
|
||||
expr* is the responsibility of a separate translator (see callers
|
||||
in seq_derive / seq_rewriter).
|
||||
|
||||
Authors:
|
||||
|
||||
Margus Veanes (veanes) 2026
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "util/vector.h"
|
||||
#include <iosfwd>
|
||||
#include <utility>
|
||||
|
||||
namespace seq {
|
||||
|
||||
class range_predicate {
|
||||
using range_t = std::pair<unsigned, unsigned>;
|
||||
using ranges_t = svector<range_t>;
|
||||
|
||||
// Sorted by first; ranges are disjoint and non-adjacent;
|
||||
// every range satisfies lo <= hi <= m_max_char.
|
||||
ranges_t m_ranges;
|
||||
unsigned m_max_char { 0 };
|
||||
|
||||
// Invariant check used in debug builds.
|
||||
bool well_formed() const;
|
||||
|
||||
public:
|
||||
range_predicate() = default;
|
||||
explicit range_predicate(unsigned max_char) : m_max_char(max_char) {}
|
||||
|
||||
// ---------------- Factory functions ----------------
|
||||
|
||||
static range_predicate empty(unsigned max_char);
|
||||
static range_predicate top(unsigned max_char);
|
||||
static range_predicate singleton(unsigned c, unsigned max_char);
|
||||
static range_predicate range(unsigned lo, unsigned hi, unsigned max_char);
|
||||
|
||||
// ---------------- Observers ----------------
|
||||
|
||||
unsigned max_char() const { return m_max_char; }
|
||||
unsigned num_ranges() const { return m_ranges.size(); }
|
||||
range_t operator[](unsigned i) const { return m_ranges[i]; }
|
||||
ranges_t const& ranges() const { return m_ranges; }
|
||||
|
||||
bool is_empty() const { return m_ranges.empty(); }
|
||||
bool is_top() const {
|
||||
return m_ranges.size() == 1
|
||||
&& m_ranges[0].first == 0
|
||||
&& m_ranges[0].second == m_max_char;
|
||||
}
|
||||
bool is_singleton(unsigned& c) const {
|
||||
if (m_ranges.size() != 1) return false;
|
||||
if (m_ranges[0].first != m_ranges[0].second) return false;
|
||||
c = m_ranges[0].first;
|
||||
return true;
|
||||
}
|
||||
bool contains(unsigned c) const;
|
||||
|
||||
// Number of characters in the predicate (well-defined for any domain).
|
||||
uint64_t cardinality() const;
|
||||
|
||||
// ---------------- Equality, ordering, hashing ----------------
|
||||
|
||||
bool equals(range_predicate const& o) const;
|
||||
bool operator==(range_predicate const& o) const { return equals(o); }
|
||||
bool operator!=(range_predicate const& o) const { return !equals(o); }
|
||||
|
||||
// Total order: lexicographic on the canonical range sequence,
|
||||
// with shorter sequences ordered before longer prefixes.
|
||||
// Predicates over different domains compare by max_char first.
|
||||
bool operator<(range_predicate const& o) const;
|
||||
bool less_than(range_predicate const& o) const { return *this < o; }
|
||||
|
||||
unsigned hash() const;
|
||||
|
||||
// ---------------- Boolean operations ----------------
|
||||
|
||||
range_predicate operator|(range_predicate const& o) const; // union
|
||||
range_predicate operator&(range_predicate const& o) const; // intersection
|
||||
range_predicate operator-(range_predicate const& o) const; // difference
|
||||
range_predicate operator^(range_predicate const& o) const; // symmetric diff
|
||||
range_predicate operator~() const; // complement
|
||||
|
||||
// ---------------- Display ----------------
|
||||
|
||||
std::ostream& display(std::ostream& out) const;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, range_predicate const& p) {
|
||||
return p.display(out);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -85,41 +85,6 @@ namespace seq {
|
|||
return is_ground(r);
|
||||
}
|
||||
|
||||
/*
|
||||
Collect the leaves of a t-regex der (an ITE / antimirov union /
|
||||
union-tree with regex leaves) into the output vector. Empty
|
||||
(re.empty) leaves are dropped.
|
||||
|
||||
Returns false if we encountered an unexpected node (e.g. a free
|
||||
variable creeping in) — in that case the caller should bail out.
|
||||
*/
|
||||
bool regex_bisim::collect_leaves(expr* der, expr_ref_vector& leaves) {
|
||||
ptr_vector<expr> work;
|
||||
obj_hashtable<expr> seen;
|
||||
work.push_back(der);
|
||||
seen.insert(der);
|
||||
while (!work.empty()) {
|
||||
expr* e = work.back();
|
||||
work.pop_back();
|
||||
expr* c = nullptr, * t = nullptr, * f = nullptr;
|
||||
if (m.is_ite(e, c, t, f) ||
|
||||
m_util.re.is_union(e, t, f) ||
|
||||
m_util.re.is_antimirov_union(e, t, f)) {
|
||||
if (seen.insert_if_not_there(t))
|
||||
work.push_back(t);
|
||||
if (seen.insert_if_not_there(f))
|
||||
work.push_back(f);
|
||||
continue;
|
||||
}
|
||||
if (m_util.re.is_empty(e))
|
||||
continue;
|
||||
if (!m_util.is_re(e))
|
||||
return false;
|
||||
leaves.push_back(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Fast inequivalence check based on the get_info().classical flag.
|
||||
|
||||
|
|
@ -228,15 +193,19 @@ namespace seq {
|
|||
m_worklist.pop_back();
|
||||
|
||||
// Compute the symbolic derivative wrt the canonical variable
|
||||
// (:var 0). The result is a transition regex (ITE tree) whose
|
||||
// leaves are regex expressions. We use the classical Brzozowski
|
||||
// entry point so the derivative stays as a single TRegex and
|
||||
// does not lift unions to the top via antimirov nodes — this
|
||||
// preserves the XOR-pair invariant the bisimulation relies on.
|
||||
expr_ref d(m_rw.mk_brz_derivative(r), m);
|
||||
// (:var 0) and enumerate its reachable leaves in fully
|
||||
// ITE-hoisted normal form. Every if-then-else over the input
|
||||
// character — even one that would otherwise be buried under a
|
||||
// concat or union — is hoisted to the top and infeasible
|
||||
// minterms are pruned, so each leaf is a ground regex free of
|
||||
// (:var 0) whose nullability is always decidable. Unions are
|
||||
// kept intact as single leaves (a union leaf denotes a single
|
||||
// bisimulation state, never a split into separate states).
|
||||
expr_ref_pair_vector cofs(m);
|
||||
m_rw.brz_derivative_cofactors(r, cofs);
|
||||
expr_ref_vector leaves(m);
|
||||
if (!collect_leaves(d, leaves))
|
||||
return l_undef;
|
||||
for (auto const& p : cofs)
|
||||
leaves.push_back(p.second);
|
||||
|
||||
// First pass: check for any nullable leaf (definitive
|
||||
// distinguishing empty-continuation word) or any classically
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ namespace seq {
|
|||
|
||||
unsigned node_of(expr* r);
|
||||
bool merge_leaf(expr* xor_pair);
|
||||
bool collect_leaves(expr* der, expr_ref_vector& leaves);
|
||||
lbool nullability(expr* r);
|
||||
bool is_supported(expr* r);
|
||||
// Returns true if the leaf l proves that the original pair is
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,9 @@ Notes:
|
|||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "seq_split.h"
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
#include "ast/rewriter/seq_derive.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/rewriter/rewriter_types.h"
|
||||
|
|
@ -129,16 +131,21 @@ class seq_rewriter {
|
|||
void insert(decl_kind op, expr* a, expr* b, expr* c, expr* r);
|
||||
};
|
||||
|
||||
friend class seq::derive;
|
||||
|
||||
seq_util m_util;
|
||||
seq_subset m_subset;
|
||||
seq_split m_split;
|
||||
arith_util m_autil;
|
||||
bool_rewriter m_br;
|
||||
seq::derive m_derive;
|
||||
// re2automaton m_re2aut;
|
||||
op_cache m_op_cache;
|
||||
expr_ref_vector m_es, m_lhs, m_rhs;
|
||||
bool m_coalesce_chars;
|
||||
bool m_in_bisim { false };
|
||||
bool m_coalesce_chars = true;
|
||||
bool m_in_bisim { false };
|
||||
unsigned m_re_deriv_depth { 0 };
|
||||
static const unsigned m_max_re_deriv_depth = 512;
|
||||
|
||||
enum length_comparison {
|
||||
shorter_c,
|
||||
|
|
@ -175,50 +182,22 @@ 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);
|
||||
|
||||
// Calculate derivative, memoized and enforcing a normal form
|
||||
expr_ref is_nullable_rec(expr* r);
|
||||
expr_ref mk_derivative_rec(expr* ele, expr* r);
|
||||
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);
|
||||
expr_ref mk_der_concat(expr* a, expr* b);
|
||||
expr_ref mk_der_union(expr* a, expr* b);
|
||||
expr_ref mk_der_inter(expr* a, expr* b);
|
||||
expr_ref mk_der_xor(expr* a, expr* b);
|
||||
expr_ref mk_der_compl(expr* a);
|
||||
expr_ref mk_der_cond(expr* cond, expr* ele, sort* seq_sort);
|
||||
expr_ref mk_der_antimirov_union(expr* r1, expr* r2);
|
||||
bool ite_bdds_compatible(expr* a, expr* b);
|
||||
/* if r has the form deriv(en..deriv(e1,to_re(s))..) returns 's = [e1..en]' else returns '() in r'*/
|
||||
expr_ref is_nullable_symbolic_regex(expr* r, sort* seq_sort);
|
||||
#ifdef Z3DEBUG
|
||||
bool check_deriv_normal_form(expr* r, int level = 3);
|
||||
#endif
|
||||
// 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);
|
||||
|
||||
void mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref& result);
|
||||
|
||||
expr_ref mk_antimirov_deriv(expr* e, expr* r, expr* path);
|
||||
expr_ref mk_in_antimirov_rec(expr* s, expr* d);
|
||||
expr_ref mk_in_antimirov(expr* s, expr* d);
|
||||
|
||||
expr_ref mk_antimirov_deriv_intersection(expr* elem, expr* d1, expr* d2, expr* path);
|
||||
expr_ref mk_antimirov_deriv_concat(expr* d, expr* r);
|
||||
expr_ref mk_antimirov_deriv_negate(expr* elem, expr* d);
|
||||
expr_ref mk_antimirov_deriv_union(expr* d1, expr* d2);
|
||||
expr_ref mk_antimirov_deriv_restrict(expr* elem, expr* d1, expr* cond);
|
||||
expr_ref mk_regex_reverse(expr* r);
|
||||
expr_ref mk_regex_concat(expr* r1, expr* r2);
|
||||
|
||||
expr_ref merge_regex_sets(expr* r1, expr* r2, expr* unit, std::function<bool(expr*, expr*&, expr*&)>& decompose, std::function<expr* (expr*, expr*)>& compose);
|
||||
|
||||
// elem is (:var 0) and path a condition that may have (:var 0) as a free variable
|
||||
// simplify path, e.g., (:var 0) = 'a' & (:var 0) = 'b' is simplified to false
|
||||
expr_ref simplify_path(expr* elem, expr* path);
|
||||
// expr_ref simplify_path(expr* elem, expr* path);
|
||||
|
||||
bool lt_char(expr* ch1, expr* ch2);
|
||||
bool eq_char(expr* ch1, expr* ch2);
|
||||
bool neq_char(expr* ch1, expr* ch2);
|
||||
bool le_char(expr* ch1, expr* ch2);
|
||||
bool pred_implies(expr* a, expr* b);
|
||||
bool are_complements(expr* r1, expr* r2) const;
|
||||
bool is_subset(expr* r1, expr* r2) const;
|
||||
|
||||
|
|
@ -264,6 +243,14 @@ class seq_rewriter {
|
|||
br_status mk_re_union0(expr* a, expr* b, expr_ref& result);
|
||||
br_status mk_re_inter0(expr* a, expr* b, expr_ref& result);
|
||||
br_status mk_re_complement(expr* a, expr_ref& result);
|
||||
// Range-set collapse helpers: if the operands form a boolean
|
||||
// combination of character-class regexes, materialize the result as a
|
||||
// canonical regex over a single range_predicate. See
|
||||
// ast/rewriter/seq_range_collapse.h for the recognized fragment.
|
||||
// NOTE: re.complement is intentionally not in this set because it
|
||||
// operates at the sequence level, not the character-class level.
|
||||
bool try_collapse_re_union(expr* a, expr* b, expr_ref& result);
|
||||
bool try_collapse_re_inter(expr* a, expr* b, expr_ref& result);
|
||||
br_status mk_re_star(expr* a, expr_ref& result);
|
||||
br_status mk_re_diff(expr* a, expr* b, expr_ref& result);
|
||||
br_status mk_re_xor(expr* a, expr* b, expr_ref& result);
|
||||
|
|
@ -347,10 +334,10 @@ class seq_rewriter {
|
|||
lbool some_string_in_re(expr_mark& visited, expr* r, unsigned_vector& str);
|
||||
|
||||
public:
|
||||
seq_rewriter(ast_manager & m, params_ref const & p = params_ref()):
|
||||
m_util(m), m_subset(m_util.re), m_split(*this), m_autil(m), m_br(m, p), // m_re2aut(m),
|
||||
seq_rewriter(ast_manager & m, params_ref const & p = params_ref()) :
|
||||
m_util(m), m_subset(m_util.re), m_split(*this), m_autil(m), m_br(m, p), m_derive(m, *this), // m_re2aut(m),
|
||||
m_op_cache(m), m_es(m),
|
||||
m_lhs(m), m_rhs(m), m_coalesce_chars(true) {
|
||||
m_lhs(m), m_rhs(m) {
|
||||
}
|
||||
ast_manager & m() const { return m_util.get_manager(); }
|
||||
family_id get_fid() const { return m_util.get_family_id(); }
|
||||
|
|
@ -361,7 +348,7 @@ public:
|
|||
static void get_param_descrs(param_descrs & r);
|
||||
|
||||
|
||||
bool coalesce_chars() const { return m_coalesce_chars; }
|
||||
// bool coalesce_chars() const { return m_coalesce_chars; }
|
||||
|
||||
br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result);
|
||||
br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result);
|
||||
|
|
@ -377,6 +364,34 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
expr_ref mk_xor0(expr *a, expr *b) {
|
||||
expr_ref result(m());
|
||||
if (mk_re_xor0(a, b, result) == BR_FAILED)
|
||||
result = re().mk_xor(a, b);
|
||||
return result;
|
||||
}
|
||||
|
||||
expr_ref mk_union(expr *a, expr *b) {
|
||||
expr_ref result(m());
|
||||
if (mk_re_union(a, b, result) == BR_FAILED)
|
||||
result = re().mk_union(a, b);
|
||||
return result;
|
||||
}
|
||||
|
||||
expr_ref mk_inter(expr *a, expr *b) {
|
||||
expr_ref result(m());
|
||||
if (mk_re_inter(a, b, result) == BR_FAILED)
|
||||
result = re().mk_inter(a, b);
|
||||
return result;
|
||||
}
|
||||
|
||||
expr_ref mk_complement(expr *a) {
|
||||
expr_ref result(m());
|
||||
if (mk_re_complement(a, result) == BR_FAILED)
|
||||
result = re().mk_complement(a);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* makes concat and simplifies
|
||||
*/
|
||||
|
|
@ -460,11 +475,35 @@ public:
|
|||
variable v0 = (:var 0). Unlike `mk_derivative` this entry point keeps
|
||||
the symbolic derivative as a single transition regex (TRegex): boolean
|
||||
operators are pushed into the ITE leaves rather than lifted to the top
|
||||
via _OP_RE_ANTIMIROV_UNION. Used by the regex_bisim equivalence
|
||||
as a union. Used by the regex_bisim equivalence
|
||||
procedure which relies on each leaf of D(p XOR q) being a coherent
|
||||
XOR pair (D_v p) XOR (D_v q).
|
||||
*/
|
||||
expr_ref mk_brz_derivative(expr* r);
|
||||
expr_ref mk_brz_derivative(expr *r) {
|
||||
return mk_derivative(r);
|
||||
}
|
||||
|
||||
/*
|
||||
Enumerate the cofactors (min-terms) of a transition regex r taken with
|
||||
respect to ele. Produces (path_condition, leaf_regex) pairs for every
|
||||
feasible path through the ITE-tree, pruning infeasible character ranges.
|
||||
Delegates to the derivative engine so the same path/interval context used
|
||||
while hoisting ITEs is reused for the leaf simplification.
|
||||
*/
|
||||
void get_cofactors(expr* ele, expr* r, expr_ref_pair_vector& result) {
|
||||
m_derive.get_cofactors(ele, r, result);
|
||||
}
|
||||
|
||||
/*
|
||||
Compute the symbolic derivative of r and enumerate its reachable leaves
|
||||
in fully ITE-hoisted normal form: a list of (path_condition, target)
|
||||
pairs where every target is free of (:var 0) (so nullability is always
|
||||
decidable) and unions are kept intact as single states. Used by
|
||||
regex_bisim, which consumes the targets and ignores the path conditions.
|
||||
*/
|
||||
void brz_derivative_cofactors(expr* r, expr_ref_pair_vector& result) {
|
||||
m_derive.derivative_cofactors(r, result);
|
||||
}
|
||||
|
||||
// heuristic elimination of element from condition that comes form a derivative.
|
||||
// special case optimization for conjunctions of equalities, disequalities and ranges.
|
||||
|
|
@ -475,15 +514,7 @@ public:
|
|||
/* Apply simplifications to the intersection to keep it normalized (r1 and r2 are not normalized)*/
|
||||
expr_ref mk_regex_inter_normalize(expr* r1, expr* r2);
|
||||
|
||||
/*
|
||||
* Extract some sequence that is a member of r.
|
||||
* result is set to a concrete sequence expression if l_true is returned.
|
||||
* For string-typed regexes, delegates to some_string_in_re.
|
||||
* For other sequence types, checks nullability and returns the empty
|
||||
* sequence if the regex accepts it; otherwise returns l_undef.
|
||||
* Returns l_false if the regex is known to be empty.
|
||||
*/
|
||||
lbool some_seq_in_re(expr* r, expr_ref& result);
|
||||
expr_ref mk_regex_concat(expr *r1, expr *r2);
|
||||
|
||||
/*
|
||||
* Extract some string that is a member of r.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Author:
|
|||
|
||||
bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const {
|
||||
while (true) {
|
||||
|
||||
|
||||
if (a == b)
|
||||
return true;
|
||||
if (m_re.is_empty(a))
|
||||
|
|
@ -30,7 +30,7 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const {
|
|||
return true;
|
||||
|
||||
if (depth >= m_max_depth)
|
||||
return false;
|
||||
return false;
|
||||
|
||||
expr* a1 = nullptr, * a2 = nullptr, * b1 = nullptr, * b2 = nullptr;
|
||||
unsigned la, ua, lb, ub;
|
||||
|
|
@ -39,16 +39,12 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const {
|
|||
if (m_re.is_dot_plus(b) && m_re.get_info(a).nullable == l_false)
|
||||
return true;
|
||||
|
||||
// a ⊆ a*
|
||||
if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth))
|
||||
return true;
|
||||
|
||||
// e ⊆ a*
|
||||
if (m_re.is_epsilon(a) && m_re.is_star(b, b1))
|
||||
return true;
|
||||
|
||||
// R ⊆ R*
|
||||
if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth + 1))
|
||||
// a ⊆ a*: if b = b1* and a ⊆ b1, then a ⊆ b1*
|
||||
if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth))
|
||||
return true;
|
||||
|
||||
// R1* ⊆ R2* if R1 ⊆ R2
|
||||
|
|
@ -112,6 +108,12 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const {
|
|||
if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b1) && is_subset_rec(a, b2, depth))
|
||||
return true;
|
||||
|
||||
// prefix absorption: P·R' ⊆ Σ*·R' for any prefix P (since P ⊆ Σ*).
|
||||
// Detect that a has R' (= b2) as a concatenation suffix, where b = Σ*·R'.
|
||||
// Covers contains-patterns, e.g. Σ*·a·Σ*·b·Σ* ⊆ Σ*·b·Σ*.
|
||||
if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b1) && ends_with(a, b2))
|
||||
return true;
|
||||
|
||||
// R ⊆ R'·Σ* if R ⊆ R'
|
||||
if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b2) && is_subset_rec(a, b1, depth))
|
||||
return true;
|
||||
|
|
@ -144,3 +146,30 @@ bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const {
|
|||
bool seq_subset::is_subset(expr* a, expr* b) const {
|
||||
return is_subset_rec(a, b, 0);
|
||||
}
|
||||
|
||||
bool seq_subset::ends_with(expr* a, expr* suf) const {
|
||||
if (a == suf)
|
||||
return true;
|
||||
// Flatten both regexes into their sequence of concatenation factors
|
||||
// (independent of left/right associativity) and test list-suffix equality.
|
||||
ptr_vector<expr> af, sf;
|
||||
flatten_concat(a, af);
|
||||
flatten_concat(suf, sf);
|
||||
if (sf.size() > af.size())
|
||||
return false;
|
||||
unsigned off = af.size() - sf.size();
|
||||
for (unsigned i = 0; i < sf.size(); ++i)
|
||||
if (af[off + i] != sf[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void seq_subset::flatten_concat(expr* a, ptr_vector<expr>& out) const {
|
||||
expr* a1 = nullptr, * a2 = nullptr;
|
||||
if (m_re.is_concat(a, a1, a2)) {
|
||||
flatten_concat(a1, out);
|
||||
flatten_concat(a2, out);
|
||||
}
|
||||
else
|
||||
out.push_back(a);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ class seq_subset {
|
|||
|
||||
bool is_subset_rec(expr* a, expr* b, unsigned depth) const;
|
||||
|
||||
// true if regex a, viewed as a flattened concatenation, has suf as a
|
||||
// structural (concatenation) suffix.
|
||||
bool ends_with(expr* a, expr* suf) const;
|
||||
|
||||
void flatten_concat(expr* a, ptr_vector<expr>& out) const;
|
||||
|
||||
public:
|
||||
explicit seq_subset(seq_util::rex& re) : m_re(re) {}
|
||||
bool is_subset(expr* a, expr* b) const;
|
||||
|
|
|
|||
681
src/ast/rewriter/term_enumeration.cpp
Normal file
681
src/ast/rewriter/term_enumeration.cpp
Normal file
|
|
@ -0,0 +1,681 @@
|
|||
/**
|
||||
* 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 (m.limit().is_canceled())
|
||||
return false;
|
||||
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;
|
||||
IF_VERBOSE(10, verbose_stream() << "add " << simplified << "\n");
|
||||
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) {
|
||||
if (m.limit().is_canceled()) {
|
||||
m_state = State::Done;
|
||||
return nullptr;
|
||||
}
|
||||
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) {
|
||||
if (m.limit().is_canceled())
|
||||
return nullptr;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
|
@ -241,7 +241,6 @@ void seq_decl_plugin::init() {
|
|||
m_sigs[OP_RE_OF_PRED] = alloc(psig, m, "re.of.pred", 1, 1, &predA, reA);
|
||||
m_sigs[OP_RE_REVERSE] = alloc(psig, m, "re.reverse", 1, 1, &reA, reA);
|
||||
m_sigs[OP_RE_DERIVATIVE] = alloc(psig, m, "re.derivative", 1, 2, AreA, reA);
|
||||
m_sigs[_OP_RE_ANTIMIROV_UNION] = alloc(psig, m, "re.union", 1, 2, reAreA, reA);
|
||||
m_sigs[OP_SEQ_TO_RE] = alloc(psig, m, "seq.to.re", 1, 1, &seqA, reA);
|
||||
m_sigs[OP_SEQ_IN_RE] = alloc(psig, m, "seq.in.re", 1, 2, seqAreA, boolT);
|
||||
m_sigs[OP_SEQ_REPLACE_RE_ALL] = alloc(psig, m, "str.replace_re_all", 1, 3, seqAreAseqA, seqA);
|
||||
|
|
@ -413,7 +412,6 @@ func_decl* seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p
|
|||
case OP_RE_COMPLEMENT:
|
||||
case OP_RE_REVERSE:
|
||||
case OP_RE_DERIVATIVE:
|
||||
case _OP_RE_ANTIMIROV_UNION:
|
||||
m_has_re = true;
|
||||
Z3_fallthrough;
|
||||
case OP_SEQ_UNIT:
|
||||
|
|
@ -423,7 +421,7 @@ func_decl* seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p
|
|||
case OP_STRING_LE:
|
||||
case OP_STRING_IS_DIGIT:
|
||||
case OP_STRING_TO_CODE:
|
||||
case OP_STRING_FROM_CODE:
|
||||
case OP_STRING_FROM_CODE:
|
||||
match(*m_sigs[k], arity, domain, range, rng);
|
||||
return m.mk_func_decl(m_sigs[k]->m_name, arity, domain, rng, func_decl_info(m_family_id, k));
|
||||
|
||||
|
|
@ -1213,6 +1211,17 @@ 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);
|
||||
if (lo == 0 && hi == u.max_char())
|
||||
return mk_full_char(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);
|
||||
|
|
@ -1441,7 +1450,7 @@ std::ostream& seq_util::rex::pp::print(std::ostream& out, expr* e) const {
|
|||
print(out, r1);
|
||||
print(out, r2);
|
||||
}
|
||||
else if (re.is_antimirov_union(e, r1, r2) || re.is_union(e, r1, r2)) {
|
||||
else if (re.is_union(e, r1, r2)) {
|
||||
out << "(";
|
||||
print(out, r1);
|
||||
out << (html_encode ? "⋃" : "|");
|
||||
|
|
@ -1674,11 +1683,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));
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ enum seq_op_kind {
|
|||
_OP_REGEXP_EMPTY,
|
||||
_OP_REGEXP_FULL_CHAR,
|
||||
_OP_RE_IS_NULLABLE,
|
||||
_OP_RE_ANTIMIROV_UNION, // Lifted union for antimirov-style derivatives
|
||||
_OP_SEQ_SKOLEM,
|
||||
LAST_SEQ_OP
|
||||
};
|
||||
|
|
@ -525,6 +524,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); }
|
||||
|
|
@ -546,7 +547,6 @@ public:
|
|||
app* mk_of_pred(expr* p);
|
||||
app* mk_reverse(expr* r) { return m.mk_app(m_fid, OP_RE_REVERSE, r); }
|
||||
app* mk_derivative(expr* ele, expr* r) { return m.mk_app(m_fid, OP_RE_DERIVATIVE, ele, r); }
|
||||
app* mk_antimirov_union(expr* r1, expr* r2) { return m.mk_app(m_fid, _OP_RE_ANTIMIROV_UNION, r1, r2); }
|
||||
|
||||
bool is_to_re(expr const* n) const { return is_app_of(n, m_fid, OP_SEQ_TO_RE); }
|
||||
bool is_concat(expr const* n) const { return is_app_of(n, m_fid, OP_RE_CONCAT); }
|
||||
|
|
@ -580,7 +580,6 @@ public:
|
|||
bool is_of_pred(expr const* n) const { return is_app_of(n, m_fid, OP_RE_OF_PRED); }
|
||||
bool is_reverse(expr const* n) const { return is_app_of(n, m_fid, OP_RE_REVERSE); }
|
||||
bool is_derivative(expr const* n) const { return is_app_of(n, m_fid, OP_RE_DERIVATIVE); }
|
||||
bool is_antimirov_union(expr const* n) const { return is_app_of(n, m_fid, _OP_RE_ANTIMIROV_UNION); }
|
||||
MATCH_UNARY(is_to_re);
|
||||
MATCH_BINARY(is_concat);
|
||||
MATCH_BINARY(is_union);
|
||||
|
|
@ -595,7 +594,6 @@ public:
|
|||
MATCH_UNARY(is_of_pred);
|
||||
MATCH_UNARY(is_reverse);
|
||||
MATCH_BINARY(is_derivative);
|
||||
MATCH_BINARY(is_antimirov_union);
|
||||
bool is_loop(expr const* n, expr*& body, unsigned& lo, unsigned& hi) const;
|
||||
bool is_loop(expr const* n, expr*& body, unsigned& lo) const;
|
||||
bool is_loop(expr const* n, expr*& body, expr*& lo, expr*& hi) const;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
|
|
@ -65,7 +64,8 @@ enum class token_kind {
|
|||
slash_tok,
|
||||
minus_tok,
|
||||
at_tok,
|
||||
lambda_tok
|
||||
lambda_tok,
|
||||
modal_tok
|
||||
};
|
||||
|
||||
struct parse_error : public std::exception {
|
||||
|
|
@ -87,6 +87,7 @@ struct token {
|
|||
std::string text;
|
||||
unsigned line = 1;
|
||||
unsigned col = 1;
|
||||
bool dquote = false; // true for double-quoted strings ("..."): TPTP distinct objects
|
||||
};
|
||||
|
||||
class lexer {
|
||||
|
|
@ -163,6 +164,7 @@ public:
|
|||
if (peek() == '\'' || peek() == '"') {
|
||||
char q = get();
|
||||
t.kind = token_kind::str;
|
||||
t.dquote = (q == '"');
|
||||
while (!eof()) {
|
||||
char c = get();
|
||||
if (c == '\\' && !eof()) {
|
||||
|
|
@ -252,7 +254,7 @@ public:
|
|||
case '^': t.kind = token_kind::lambda_tok; return t;
|
||||
case '{':
|
||||
// Modal operators: {$box}, {$dia}, etc. — lex as identifier including braces
|
||||
t.kind = token_kind::id;
|
||||
t.kind = token_kind::modal_tok;
|
||||
t.text.push_back(c);
|
||||
while (!eof() && peek() != '}')
|
||||
t.text.push_back(get());
|
||||
|
|
@ -277,10 +279,10 @@ public:
|
|||
};
|
||||
|
||||
struct parsed_type {
|
||||
std::vector<sort*> domain;
|
||||
ptr_vector<sort> domain;
|
||||
sort* range = nullptr;
|
||||
parsed_type(sort* s): range(s) {}
|
||||
parsed_type(std::vector<sort*> const& d, sort* r): domain(d), range(r) {}
|
||||
parsed_type(ptr_vector<sort> const& d, sort* r): domain(d), range(r) {}
|
||||
};
|
||||
|
||||
class tptp_parser {
|
||||
|
|
@ -290,13 +292,20 @@ class tptp_parser {
|
|||
array_util m_array;
|
||||
sort* m_univ;
|
||||
bool m_has_conjecture = false;
|
||||
unsigned m_dropped_formulas = 0; // axioms/definitions skipped due to encoding errors
|
||||
bool m_last_name_quoted = false;
|
||||
bool m_last_name_dquoted = false; // last parsed name was a double-quoted distinct object
|
||||
// Distinct objects: TPTP double-quoted strings ("...") denote pairwise distinct
|
||||
// domain elements. Collected here (deduplicated by name) so a single global
|
||||
// (distinct ...) constraint can be asserted before solving.
|
||||
std::unordered_map<std::string, expr*> m_distinct_objects;
|
||||
std::string m_expected_status; // SZS status from the input annotation, if any
|
||||
std::unordered_map<std::string, sort*> m_sorts;
|
||||
sort_ref_vector m_pinned_sorts; // prevents cached sorts from being freed
|
||||
std::unordered_map<std::string, func_decl*> m_decls;
|
||||
func_decl_ref_vector m_pinned_decls; // prevents cached func_decls from being freed
|
||||
expr_ref_vector m_pinned_exprs; // prevents bound variable apps from being freed
|
||||
std::unordered_map<std::string, std::pair<std::vector<sort*>, sort*>> m_typed_decls;
|
||||
std::unordered_map<std::string, std::pair<ptr_vector<sort>, sort*>> m_typed_decls;
|
||||
std::vector<std::unordered_map<std::string, app*>> m_bound;
|
||||
bool m_in_at_arg = false; // true when parsing inside @ argument (lambda body stops consuming @)
|
||||
struct implicit_var_scope {
|
||||
|
|
@ -424,6 +433,7 @@ class tptp_parser {
|
|||
std::string parse_name() {
|
||||
if (is(token_kind::id) || is(token_kind::str)) {
|
||||
m_last_name_quoted = is(token_kind::str);
|
||||
m_last_name_dquoted = is(token_kind::str) && m_curr.dquote;
|
||||
std::string r = m_curr.text;
|
||||
next();
|
||||
return r;
|
||||
|
|
@ -449,7 +459,7 @@ class tptp_parser {
|
|||
// For higher-order types like ($i > $o), create an uninterpreted sort
|
||||
// Function type A > B is represented as Array(A, B).
|
||||
// Multi-argument A * B > C is represented as Array(A, Array(B, C)) (curried).
|
||||
sort* get_ho_sort(std::vector<sort*> const& domain, sort* range) {
|
||||
sort* get_ho_sort(ptr_vector<sort> const& domain, sort* range) {
|
||||
sort* s = range;
|
||||
for (int i = (int)domain.size() - 1; i >= 0; --i)
|
||||
s = m_array.mk_array_sort(domain[i], s);
|
||||
|
|
@ -513,7 +523,8 @@ class tptp_parser {
|
|||
if (itt != m_typed_decls.end()) {
|
||||
std::string typed_decl_key = mk_decl_key(name, arity, 'd');
|
||||
auto itd = m_decls.find(typed_decl_key);
|
||||
if (itd != m_decls.end()) return itd->second;
|
||||
if (itd != m_decls.end())
|
||||
return itd->second;
|
||||
auto const& sig = itt->second;
|
||||
func_decl* f = m.mk_func_decl(symbol(name), sig.first.size(), sig.first.data(), sig.second);
|
||||
m_pinned_decls.push_back(f);
|
||||
|
|
@ -525,7 +536,7 @@ class tptp_parser {
|
|||
auto itd = m_decls.find(key);
|
||||
if (itd != m_decls.end()) return itd->second;
|
||||
|
||||
std::vector<sort*> dom(arity, m_univ);
|
||||
ptr_vector<sort> dom(arity, m_univ);
|
||||
func_decl* f = m.mk_func_decl(symbol(name), arity, dom.data(), pred ? m.mk_bool_sort() : m_univ);
|
||||
m_pinned_decls.push_back(f);
|
||||
m_decls.emplace(key, f);
|
||||
|
|
@ -571,6 +582,14 @@ class tptp_parser {
|
|||
expr_ref coerce_arg(expr_ref const& e, sort* target) {
|
||||
sort* actual = e->get_sort();
|
||||
if (actual == target) return e;
|
||||
// Int <-> Real conversions must use the arithmetic semantics (to_real /
|
||||
// to_int), never an uninterpreted boxing function: an uninterpreted box
|
||||
// severs the numeric link between the two sides and lets the solver build
|
||||
// spurious models (e.g. it would make floor/ceiling identities sat).
|
||||
if (m_arith.is_int(actual) && m_arith.is_real(target))
|
||||
return expr_ref(m_arith.mk_to_real(e), m);
|
||||
if (m_arith.is_real(actual) && m_arith.is_int(target))
|
||||
return expr_ref(m_arith.mk_to_int(e), m);
|
||||
// Create a boxing function from actual sort to target sort
|
||||
std::string box_name = std::string("$box_") + actual->get_name().str() + "_to_" + target->get_name().str();
|
||||
std::string key = mk_decl_key(box_name, 1, 'f');
|
||||
|
|
@ -697,14 +716,14 @@ class tptp_parser {
|
|||
// <defined_type> ::= $oType | $o | $iType | $i | $tType | $real | $rat | $int
|
||||
parsed_type parse_type_atom() {
|
||||
if (accept(token_kind::lparen)) {
|
||||
std::vector<sort*> prod = parse_type_product_raw();
|
||||
ptr_vector<sort> prod = parse_type_product_raw();
|
||||
if (accept(token_kind::gt_tok)) {
|
||||
// Full function type inside parens: (A * B > C) or (A > B > C)
|
||||
parsed_type rhs = parse_type_expr();
|
||||
std::vector<sort*> full_domain = prod;
|
||||
ptr_vector<sort> full_domain = prod;
|
||||
if (!rhs.domain.empty()) {
|
||||
// Nested higher-order: (A > B > C) → flatten
|
||||
full_domain.insert(full_domain.end(), rhs.domain.begin(), rhs.domain.end());
|
||||
full_domain.append(rhs.domain);
|
||||
}
|
||||
expect(token_kind::rparen, "')'");
|
||||
// Return with domain/range preserved for proper flattening
|
||||
|
|
@ -743,15 +762,15 @@ class tptp_parser {
|
|||
// Grammar: <thf_xprod_type> ::= <thf_unitary_type> * <thf_unitary_type>
|
||||
// | <thf_xprod_type> * <thf_unitary_type>
|
||||
// Product types form the domain in mapping types: (A * B) > C
|
||||
std::vector<sort*> parse_type_product_raw() {
|
||||
ptr_vector<sort> parse_type_product_raw() {
|
||||
parsed_type first = parse_type_atom();
|
||||
if (!first.domain.empty() && first.range == nullptr) {
|
||||
// Already a parenthesized product from nested parens
|
||||
std::vector<sort*> args = first.domain;
|
||||
ptr_vector<sort> args = first.domain;
|
||||
while (accept(token_kind::star_tok)) {
|
||||
parsed_type t = parse_type_atom();
|
||||
if (!t.domain.empty()) {
|
||||
args.insert(args.end(), t.domain.begin(), t.domain.end());
|
||||
args.append(t.domain);
|
||||
} else {
|
||||
args.push_back(t.range);
|
||||
}
|
||||
|
|
@ -761,28 +780,28 @@ class tptp_parser {
|
|||
if (!first.domain.empty()) {
|
||||
// Function type as first element of product — use ho_sort
|
||||
sort* ho = get_ho_sort(first.domain, first.range);
|
||||
std::vector<sort*> args;
|
||||
ptr_vector<sort> args;
|
||||
args.push_back(ho);
|
||||
while (accept(token_kind::star_tok)) {
|
||||
parsed_type t = parse_type_atom();
|
||||
if (!t.domain.empty() && t.range != nullptr) {
|
||||
args.push_back(get_ho_sort(t.domain, t.range));
|
||||
} else if (!t.domain.empty()) {
|
||||
args.insert(args.end(), t.domain.begin(), t.domain.end());
|
||||
args.append(t.domain);
|
||||
} else {
|
||||
args.push_back(t.range);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
std::vector<sort*> args;
|
||||
ptr_vector<sort> args;
|
||||
args.push_back(first.range);
|
||||
while (accept(token_kind::star_tok)) {
|
||||
parsed_type t = parse_type_atom();
|
||||
if (!t.domain.empty() && t.range != nullptr) {
|
||||
args.push_back(get_ho_sort(t.domain, t.range));
|
||||
} else if (!t.domain.empty()) {
|
||||
args.insert(args.end(), t.domain.begin(), t.domain.end());
|
||||
args.append(t.domain);
|
||||
} else {
|
||||
args.push_back(t.range);
|
||||
}
|
||||
|
|
@ -801,7 +820,7 @@ class tptp_parser {
|
|||
return first;
|
||||
}
|
||||
// Build product vector
|
||||
std::vector<sort*> args;
|
||||
ptr_vector<sort> args;
|
||||
if (!first.domain.empty() && first.range != nullptr) {
|
||||
// Function type used as element in a product
|
||||
args.push_back(get_ho_sort(first.domain, first.range));
|
||||
|
|
@ -816,7 +835,7 @@ class tptp_parser {
|
|||
if (!t.domain.empty() && t.range != nullptr) {
|
||||
args.push_back(get_ho_sort(t.domain, t.range));
|
||||
} else if (!t.domain.empty()) {
|
||||
args.insert(args.end(), t.domain.begin(), t.domain.end());
|
||||
args.append(t.domain);
|
||||
} else {
|
||||
args.push_back(t.range);
|
||||
}
|
||||
|
|
@ -833,7 +852,7 @@ class tptp_parser {
|
|||
if (is(token_kind::type_forall_tok) || is(token_kind::type_exists_tok)) {
|
||||
next();
|
||||
expect(token_kind::lbrack, "'['");
|
||||
std::vector<sort*> type_params;
|
||||
ptr_vector<sort> type_params;
|
||||
if (!accept(token_kind::rbrack)) {
|
||||
do {
|
||||
std::string tv = parse_name();
|
||||
|
|
@ -848,8 +867,8 @@ class tptp_parser {
|
|||
parsed_type inner = parse_type_expr();
|
||||
// Prepend type params to domain
|
||||
if (!type_params.empty()) {
|
||||
std::vector<sort*> full_domain = type_params;
|
||||
full_domain.insert(full_domain.end(), inner.domain.begin(), inner.domain.end());
|
||||
ptr_vector<sort> full_domain = type_params;
|
||||
full_domain.append(inner.domain);
|
||||
return parsed_type(full_domain, inner.range);
|
||||
}
|
||||
return inner;
|
||||
|
|
@ -858,7 +877,7 @@ class tptp_parser {
|
|||
if (accept(token_kind::gt_tok)) {
|
||||
parsed_type rhs = parse_type_expr();
|
||||
// prod is either a product (domain non-empty, range==nullptr) or a single sort (domain empty)
|
||||
std::vector<sort*> domain;
|
||||
ptr_vector<sort> domain;
|
||||
if (!prod.domain.empty() && prod.range == nullptr) {
|
||||
domain = prod.domain;
|
||||
} else if (!prod.domain.empty() && prod.range != nullptr) {
|
||||
|
|
@ -869,7 +888,7 @@ class tptp_parser {
|
|||
}
|
||||
if (!rhs.domain.empty()) {
|
||||
// Higher-order result type: A > (B > C) flattened to (A, B) > C
|
||||
domain.insert(domain.end(), rhs.domain.begin(), rhs.domain.end());
|
||||
domain.append(rhs.domain);
|
||||
return parsed_type(domain, rhs.range);
|
||||
}
|
||||
return parsed_type(domain, rhs.range);
|
||||
|
|
@ -925,7 +944,7 @@ class tptp_parser {
|
|||
// Grammar: (same as parse_term, primary productions)
|
||||
expr_ref parse_term_primary() {
|
||||
if (accept(token_kind::lparen)) {
|
||||
expr_ref e = parse_formula();
|
||||
expr_ref e = parse_formula(false);
|
||||
expect(token_kind::rparen, "')'");
|
||||
return e;
|
||||
}
|
||||
|
|
@ -946,6 +965,7 @@ class tptp_parser {
|
|||
return parse_numeral_from_name(n);
|
||||
}
|
||||
|
||||
bool dq_name = m_last_name_dquoted;
|
||||
expr_ref b(m);
|
||||
// Check bound variables: uppercase (quantifier vars) AND lowercase (let-bound names)
|
||||
if (!m_last_name_quoted && find_bound(n, b)) {
|
||||
|
|
@ -971,11 +991,11 @@ class tptp_parser {
|
|||
// $ite needs special parsing: first arg is formula, rest are formulas (branches can be equalities)
|
||||
if (n == "$ite") {
|
||||
expect(token_kind::lparen, "'('");
|
||||
args.push_back(parse_formula());
|
||||
args.push_back(parse_formula(true));
|
||||
expect(token_kind::comma, "','");
|
||||
args.push_back(parse_formula());
|
||||
args.push_back(parse_formula(false));
|
||||
expect(token_kind::comma, "','");
|
||||
args.push_back(parse_formula());
|
||||
args.push_back(parse_formula(false));
|
||||
expect(token_kind::rparen, "')'");
|
||||
}
|
||||
else if (n == "$let") {
|
||||
|
|
@ -995,14 +1015,17 @@ class tptp_parser {
|
|||
}
|
||||
|
||||
func_decl* f = mk_decl_or_ho_const(n, args.size(), false);
|
||||
if (!args.empty()) coerce_args(f, args);
|
||||
return expr_ref(args.empty() ? m.mk_const(f) : m.mk_app(f, args.size(), args.data()), m);
|
||||
coerce_args(f, args);
|
||||
expr_ref term(args.empty() ? m.mk_const(f) : m.mk_app(f, args.size(), args.data()), m);
|
||||
if (dq_name && args.empty())
|
||||
register_distinct_object(n, term);
|
||||
return term;
|
||||
}
|
||||
|
||||
// Grammar: <tff_logic_formula> ::= <tff_unitary_formula> | <tff_binary_formula>
|
||||
// <thf_logic_formula> ::= <thf_unitary_formula> | <thf_binary_formula>
|
||||
// Entry point for formula parsing (wraps parse_expr with default precedence).
|
||||
expr_ref parse_formula();
|
||||
expr_ref parse_formula(bool is_boolean);
|
||||
|
||||
// Grammar: <thf_apply_formula> ::= <thf_unitary_formula> @ <thf_unitary_formula>
|
||||
// | <thf_apply_formula> @ <thf_unitary_formula>
|
||||
|
|
@ -1042,7 +1065,7 @@ class tptp_parser {
|
|||
return parse_lambda_expr();
|
||||
}
|
||||
if (accept(token_kind::lparen)) {
|
||||
expr_ref e = parse_formula();
|
||||
expr_ref e = parse_formula(false);
|
||||
expect(token_kind::rparen, "')'");
|
||||
// Do NOT call apply_at here — outer apply_at owns the remaining @ tokens
|
||||
return e;
|
||||
|
|
@ -1074,7 +1097,7 @@ class tptp_parser {
|
|||
// Quantifier body in @-arg should NOT consume @ — those belong to enclosing application
|
||||
bool save_in_at_arg = m_in_at_arg;
|
||||
m_in_at_arg = true;
|
||||
expr_ref body = parse_formula();
|
||||
expr_ref body = parse_formula(false);
|
||||
m_in_at_arg = save_in_at_arg;
|
||||
m_bound.pop_back();
|
||||
return mk_quantifier(is_forall, vars, body);
|
||||
|
|
@ -1092,7 +1115,7 @@ class tptp_parser {
|
|||
std::string key = mk_decl_key(name_str, 0, 'c') + "\x1f" + std::to_string(range->get_id());
|
||||
auto it = m_decls.find(key);
|
||||
if (it != m_decls.end()) return it->second;
|
||||
func_decl* f = m.mk_func_decl(name, 0, static_cast<sort**>(nullptr), range);
|
||||
func_decl* f = m.mk_const_decl(name, range);
|
||||
m_pinned_decls.push_back(f);
|
||||
m_decls.emplace(key, f);
|
||||
return f;
|
||||
|
|
@ -1121,31 +1144,29 @@ class tptp_parser {
|
|||
|
||||
// Coerce two expressions to have the same sort for equality.
|
||||
// In TPTP, = is term equality and m_univ is the default sort.
|
||||
// If one side has Bool sort (parsed as predicate), coerce it to m_univ.
|
||||
// If sorts already match and are not Bool, returns lhs unchanged.
|
||||
// If sorts already match, returns lhs unchanged; otherwise applies minimal
|
||||
// arithmetic/box coercions so both sides of an equality share a sort.
|
||||
expr_ref coerce_eq(expr_ref lhs, expr_ref& rhs) {
|
||||
// Coerce Bool-sorted operands to m_univ since = is term equality in TPTP
|
||||
if (m.is_bool(lhs->get_sort()) && is_app(lhs) && !m.is_true(lhs) && !m.is_false(lhs))
|
||||
lhs = coerce_to_univ(lhs);
|
||||
if (m.is_bool(rhs->get_sort()) && is_app(rhs) && !m.is_true(rhs) && !m.is_false(rhs))
|
||||
rhs = coerce_to_univ(rhs);
|
||||
|
||||
if (lhs->get_sort() == rhs->get_sort()) return lhs;
|
||||
|
||||
// No coercion is needed when both sides already share a sort. In particular
|
||||
// `=` between two Boolean ($o) operands is logical equivalence (iff): keep
|
||||
// them Boolean instead of forcing one into the term universe U.
|
||||
if (lhs->get_sort() == rhs->get_sort())
|
||||
return lhs;
|
||||
if (m_arith.is_int_real(lhs) && m_arith.is_int_real(rhs))
|
||||
return lhs;
|
||||
// Coerce 0-arity constants to match the other side's sort
|
||||
if (is_app(lhs) && to_app(lhs)->get_num_args() == 0 && lhs->get_sort() != rhs->get_sort()) {
|
||||
if (is_app(lhs) && to_app(lhs)->get_num_args() == 0) {
|
||||
return coerce_zero_arity(to_app(lhs), rhs->get_sort());
|
||||
}
|
||||
if (is_app(rhs) && to_app(rhs)->get_num_args() == 0 && lhs->get_sort() != rhs->get_sort()) {
|
||||
if (is_app(rhs) && to_app(rhs)->get_num_args() == 0) {
|
||||
rhs = coerce_zero_arity(to_app(rhs), lhs->get_sort());
|
||||
return lhs;
|
||||
}
|
||||
// Last resort: coerce both sides to have the same sort
|
||||
if (lhs->get_sort() != rhs->get_sort()) {
|
||||
// Prefer coercing to rhs sort, falling back to m_univ
|
||||
sort* target = rhs->get_sort();
|
||||
lhs = coerce_arg(lhs, target);
|
||||
}
|
||||
// Prefer coercing to rhs sort, falling back to m_univ
|
||||
sort* target = rhs->get_sort();
|
||||
lhs = coerce_arg(lhs, target);
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
|
|
@ -1176,7 +1197,7 @@ class tptp_parser {
|
|||
// Bind parameter variables for parsing the RHS
|
||||
if (!param_scope.empty())
|
||||
m_bound.push_back(param_scope);
|
||||
expr_ref value = parse_formula();
|
||||
expr_ref value = parse_formula(false);
|
||||
if (!param_scope.empty())
|
||||
m_bound.pop_back();
|
||||
// For function-style definitions, wrap value in lambdas
|
||||
|
|
@ -1206,7 +1227,7 @@ class tptp_parser {
|
|||
|
||||
// --- Part 1: Parse type declarations ---
|
||||
std::vector<std::string> let_names;
|
||||
std::vector<sort*> let_sorts;
|
||||
ptr_vector<sort> let_sorts;
|
||||
|
||||
auto parse_one_typing = [&]() {
|
||||
std::string name = parse_name();
|
||||
|
|
@ -1264,7 +1285,7 @@ class tptp_parser {
|
|||
|
||||
// --- Part 3: Parse body with let-bound names in scope ---
|
||||
m_bound.push_back(scope);
|
||||
expr_ref body = parse_formula();
|
||||
expr_ref body = parse_formula(false);
|
||||
m_bound.pop_back();
|
||||
expect(token_kind::rparen, "')'");
|
||||
|
||||
|
|
@ -1288,7 +1309,7 @@ class tptp_parser {
|
|||
// <defined_pred> ::= $less | $lesseq | $greater | $greatereq | $is_int | $is_rat | ...
|
||||
// <defined_infix_pred> ::= = | !=
|
||||
// Also handles: let-bound name resolution, implicit variable creation.
|
||||
expr_ref parse_atomic_formula() {
|
||||
expr_ref parse_atomic_formula(bool is_boolean) {
|
||||
if (accept(token_kind::lparen)) {
|
||||
// Check for parenthesized connective used as higher-order term: (~), (&), (|), etc.
|
||||
if (is(token_kind::not_tok) || is(token_kind::and_tok) || is(token_kind::or_tok) ||
|
||||
|
|
@ -1307,31 +1328,50 @@ class tptp_parser {
|
|||
token saved = m_curr;
|
||||
next();
|
||||
if (accept(token_kind::rparen)) {
|
||||
// Parenthesized connective: treat as HO constant with array sort
|
||||
// A parenthesized connective used as a higher-order term, e.g.
|
||||
// "(~) @ p" or "(|) @ p @ q". Encode it as a genuine lambda over Bool
|
||||
// carrying the real logical semantics, so that application beta-reduces
|
||||
// to the actual connective (e.g. "(~) @ p" ==> "not p"). Encoding it as
|
||||
// an uninterpreted array constant instead would sever it from Boolean
|
||||
// logic and make valid higher-order theorems spuriously
|
||||
// CounterSatisfiable (the (~)/(|) applications would be unrelated to the
|
||||
// truth values of their operands).
|
||||
(void)op_text;
|
||||
sort* bool_sort = m.mk_bool_sort();
|
||||
sort* ho_sort;
|
||||
if (arity == 1)
|
||||
ho_sort = m_array.mk_array_sort(bool_sort, bool_sort);
|
||||
else
|
||||
ho_sort = m_array.mk_array_sort(bool_sort, m_array.mk_array_sort(bool_sort, bool_sort));
|
||||
std::string key = mk_decl_key(op_text, 0, 'h');
|
||||
auto it = m_decls.find(key);
|
||||
func_decl* f;
|
||||
if (it != m_decls.end()) {
|
||||
f = it->second;
|
||||
} else {
|
||||
f = m.mk_func_decl(symbol(op_text), 0, static_cast<sort**>(nullptr), ho_sort);
|
||||
m_pinned_decls.push_back(f);
|
||||
m_decls.emplace(key, f);
|
||||
symbol xn("X"), yn("Y");
|
||||
if (arity == 1) {
|
||||
// (~) ==> ^[X:$o] : ~X
|
||||
expr_ref body(m.mk_not(m.mk_var(0, bool_sort)), m);
|
||||
return expr_ref(m.mk_lambda(1, &bool_sort, &xn, body), m);
|
||||
}
|
||||
return expr_ref(m.mk_const(f), m);
|
||||
// binary connective ==> ^[X:$o] : ^[Y:$o] : (X <op> Y)
|
||||
// de Bruijn: X is var(1) (outer binder), Y is var(0) (inner binder).
|
||||
expr* vx = m.mk_var(1, bool_sort);
|
||||
expr* vy = m.mk_var(0, bool_sort);
|
||||
expr_ref opbody(m);
|
||||
switch (saved.kind) {
|
||||
case token_kind::and_tok: opbody = m.mk_and(vx, vy); break;
|
||||
case token_kind::or_tok: opbody = m.mk_or(vx, vy); break;
|
||||
case token_kind::implies_tok: opbody = m.mk_implies(vx, vy); break;
|
||||
case token_kind::iff_tok: opbody = m.mk_eq(vx, vy); break;
|
||||
case token_kind::xor_tok: opbody = m.mk_xor(vx, vy); break;
|
||||
default: opbody = m.mk_eq(vx, vy); break;
|
||||
}
|
||||
expr_ref inner(m.mk_lambda(1, &bool_sort, &yn, opbody), m);
|
||||
return expr_ref(m.mk_lambda(1, &bool_sort, &xn, inner), m);
|
||||
}
|
||||
// Not a parenthesized connective — lparen was consumed and connective was consumed
|
||||
// but ')' didn't follow. Parse as formula with the connective already consumed.
|
||||
expr_ref inner(m);
|
||||
if (saved.kind == token_kind::not_tok) {
|
||||
expr_ref e = parse_formula();
|
||||
inner = expr_ref(m.mk_not(e), m);
|
||||
// "( ~ <formula> )": the '~' is a unary connective binding only the
|
||||
// next unary unit; ordinary binary connectives then apply at their
|
||||
// own precedence (e.g. "( ~ p | q )" is "(~p) | q", NOT "~(p | q)").
|
||||
// We have already consumed '(' and '~', so negate the next unit and
|
||||
// resume precedence-climbing parsing from that negated left operand.
|
||||
expr_ref operand = parse_unary_formula(true);
|
||||
expr_ref neg(m.mk_not(ensure_bool(operand)), m);
|
||||
inner = parse_binary_rest(neg, PREC_IFF, true);
|
||||
} else {
|
||||
// Binary connective at start of parens — shouldn't happen in valid TPTP
|
||||
throw parse_error("unexpected connective after '(' at " + loc());
|
||||
|
|
@ -1342,12 +1382,15 @@ class tptp_parser {
|
|||
// Parentheses create a new scope for @ consumption
|
||||
bool save_in_at_arg = m_in_at_arg;
|
||||
m_in_at_arg = false;
|
||||
expr_ref e = parse_formula();
|
||||
expr_ref e = parse_formula(is_boolean);
|
||||
expect(token_kind::rparen, "')'");
|
||||
m_in_at_arg = save_in_at_arg;
|
||||
return e;
|
||||
}
|
||||
|
||||
if (accept(token_kind::modal_tok))
|
||||
throw parse_error("modal operators not supported in TPTP input at " + loc());
|
||||
|
||||
// Handle negative numerals in formula position: -2 = $uminus(2)
|
||||
if (accept(token_kind::minus_tok)) {
|
||||
expr_ref t = parse_term();
|
||||
|
|
@ -1358,9 +1401,9 @@ class tptp_parser {
|
|||
if (accept(token_kind::lbrack)) {
|
||||
if (accept(token_kind::rbrack))
|
||||
return expr_ref(m.mk_const(symbol("$nil"), m_univ), m);
|
||||
expr_ref first = parse_formula();
|
||||
expr_ref first = parse_formula(is_boolean);
|
||||
while (accept(token_kind::comma))
|
||||
parse_formula(); // consume remaining elements
|
||||
parse_formula(is_boolean); // consume remaining elements
|
||||
expect(token_kind::rbrack, "']'");
|
||||
return first;
|
||||
}
|
||||
|
|
@ -1373,6 +1416,7 @@ class tptp_parser {
|
|||
return parse_numeral_from_name(n);
|
||||
}
|
||||
|
||||
bool dq_name = m_last_name_dquoted;
|
||||
// Check if name is let-bound (works for both uppercase vars and lowercase let-bound names)
|
||||
{
|
||||
expr_ref b(m);
|
||||
|
|
@ -1417,7 +1461,7 @@ class tptp_parser {
|
|||
}
|
||||
expect(token_kind::colon, "':'");
|
||||
m_bound.push_back(scope);
|
||||
expr_ref body = parse_formula();
|
||||
expr_ref body = parse_formula(is_boolean);
|
||||
m_bound.pop_back();
|
||||
// Approximate choice as existential quantification
|
||||
return mk_quantifier(false, vars, body);
|
||||
|
|
@ -1427,11 +1471,11 @@ class tptp_parser {
|
|||
// $ite needs special parsing: first arg is formula, rest are formulas (branches can be equalities)
|
||||
if (n == "$ite") {
|
||||
expect(token_kind::lparen, "'('");
|
||||
args.push_back(parse_formula());
|
||||
args.push_back(parse_formula(true));
|
||||
expect(token_kind::comma, "','");
|
||||
args.push_back(parse_formula());
|
||||
args.push_back(parse_formula(is_boolean));
|
||||
expect(token_kind::comma, "','");
|
||||
args.push_back(parse_formula());
|
||||
args.push_back(parse_formula(is_boolean));
|
||||
expect(token_kind::rparen, "')'");
|
||||
}
|
||||
else if (n == "$let") {
|
||||
|
|
@ -1465,18 +1509,19 @@ class tptp_parser {
|
|||
auto typed = m_typed_decls.find(mk_typed_key(n, args.size()));
|
||||
if (typed != m_typed_decls.end()) {
|
||||
func_decl* f = args.empty() ? mk_decl_or_ho_const(n, 0, false) : mk_decl(n, args.size(), false);
|
||||
if (!args.empty()) coerce_args(f, args);
|
||||
return expr_ref(args.empty() ? m.mk_const(f) : m.mk_app(f, args.size(), args.data()), m);
|
||||
coerce_args(f, args);
|
||||
return expr_ref(m.mk_app(f, args.size(), args.data()), m);
|
||||
}
|
||||
|
||||
if (args.empty() && (is(token_kind::equal_tok) || is(token_kind::neq_tok))) {
|
||||
func_decl* f = mk_decl_or_ho_const(n, 0, false);
|
||||
return expr_ref(m.mk_const(f), m);
|
||||
}
|
||||
is_boolean = is_boolean && !is(token_kind::equal_tok) && !is(token_kind::neq_tok);
|
||||
|
||||
func_decl* pred = mk_decl_or_ho_const(n, args.size(), true);
|
||||
if (!args.empty()) coerce_args(pred, args);
|
||||
return expr_ref(args.empty() ? m.mk_const(pred) : m.mk_app(pred, args.size(), args.data()), m);
|
||||
|
||||
func_decl* pred = mk_decl_or_ho_const(n, args.size(), is_boolean);
|
||||
coerce_args(pred, args);
|
||||
expr_ref atom(m.mk_app(pred, args.size(), args.data()), m);
|
||||
if (dq_name && args.empty() && !m.is_bool(atom))
|
||||
register_distinct_object(n, atom);
|
||||
return atom;
|
||||
}
|
||||
|
||||
// Grammar: <thf_abstraction> ::= ^ [<thf_variable_list>] : <thf_unitary_formula>
|
||||
|
|
@ -1513,7 +1558,7 @@ class tptp_parser {
|
|||
// Lambda body does NOT consume @ — @ belongs to the enclosing application
|
||||
bool save_in_at_arg = m_in_at_arg;
|
||||
m_in_at_arg = true;
|
||||
expr_ref body = parse_formula();
|
||||
expr_ref body = parse_formula(false);
|
||||
m_in_at_arg = save_in_at_arg;
|
||||
m_bound.pop_back();
|
||||
if (vars.empty())
|
||||
|
|
@ -1538,9 +1583,9 @@ class tptp_parser {
|
|||
// <thf_quantified_formula> ::= <thf_quantification> <thf_unitary_formula>
|
||||
// <fof_quantifier> ::= ! | ?
|
||||
// Also handles: $ite, $let, lambda (^), parenthesized formulas, and atomic formulas.
|
||||
expr_ref parse_unary_formula() {
|
||||
expr_ref parse_unary_formula(bool is_boolean) {
|
||||
if (accept(token_kind::not_tok)) {
|
||||
expr_ref e = parse_unary_formula();
|
||||
expr_ref e = parse_unary_formula(true);
|
||||
return expr_ref(m.mk_not(ensure_bool(e)), m);
|
||||
}
|
||||
|
||||
|
|
@ -1552,7 +1597,7 @@ class tptp_parser {
|
|||
next(); // consume '['
|
||||
if (accept(token_kind::dot)) {
|
||||
expect(token_kind::rbrack, "']'");
|
||||
expr_ref sub = parse_unary_formula();
|
||||
expr_ref sub = parse_unary_formula(is_boolean);
|
||||
func_decl* f = mk_modal_op("box");
|
||||
return expr_ref(m.mk_app(f, sub.get()), m);
|
||||
}
|
||||
|
|
@ -1561,7 +1606,7 @@ class tptp_parser {
|
|||
std::string first_name = m_curr.text;
|
||||
next();
|
||||
if (accept(token_kind::rbrack)) {
|
||||
expr_ref sub = parse_unary_formula();
|
||||
expr_ref sub = parse_unary_formula(is_boolean);
|
||||
func_decl* f = mk_modal_op(mod_name);
|
||||
return expr_ref(m.mk_app(f, sub.get()), m);
|
||||
}
|
||||
|
|
@ -1575,11 +1620,11 @@ class tptp_parser {
|
|||
else if (should_create_implicit_var(first_name))
|
||||
first = expr_ref(get_or_create_implicit_var(first_name), m);
|
||||
else {
|
||||
func_decl* f = mk_decl_or_ho_const(first_name, 0, false);
|
||||
func_decl* f = mk_decl_or_ho_const(first_name, 0, is_boolean);
|
||||
first = expr_ref(m.mk_const(f), m);
|
||||
}
|
||||
while (accept(token_kind::comma))
|
||||
parse_formula(); // consume remaining elements
|
||||
parse_formula(is_boolean); // consume remaining elements
|
||||
expect(token_kind::rbrack, "']'");
|
||||
return first;
|
||||
}
|
||||
|
|
@ -1587,9 +1632,9 @@ class tptp_parser {
|
|||
// We already consumed '[', so parse as tuple inline
|
||||
if (accept(token_kind::rbrack))
|
||||
return expr_ref(m.mk_const(symbol("$nil"), m_univ), m);
|
||||
expr_ref first = parse_formula();
|
||||
expr_ref first = parse_formula(is_boolean);
|
||||
while (accept(token_kind::comma))
|
||||
parse_formula(); // consume remaining elements
|
||||
parse_formula(is_boolean); // consume remaining elements
|
||||
expect(token_kind::rbrack, "']'");
|
||||
return first;
|
||||
}
|
||||
|
|
@ -1605,7 +1650,7 @@ class tptp_parser {
|
|||
next();
|
||||
}
|
||||
expect(token_kind::gt_tok, "'>'");
|
||||
expr_ref sub = parse_unary_formula();
|
||||
expr_ref sub = parse_unary_formula(is_boolean);
|
||||
func_decl* f = mk_modal_op(mod_name);
|
||||
return expr_ref(m.mk_app(f, sub.get()), m);
|
||||
}
|
||||
|
|
@ -1652,7 +1697,13 @@ class tptp_parser {
|
|||
}
|
||||
expect(token_kind::colon, "':'");
|
||||
m_bound.push_back(scope);
|
||||
expr_ref body = parse_formula();
|
||||
// A TPTP quantifier body is a <unit_formula>: the quantifier binds
|
||||
// tighter than the binary connectives & | => <= <=> <~> ~| ~&. Parse
|
||||
// at equality precedence so the body absorbs an infix =/!= but stops
|
||||
// at any lower-precedence connective, which stays in the enclosing
|
||||
// expression. E.g. "! [X] : p(X) & q(X)" is "(! [X] : p(X)) & q(X)",
|
||||
// and "! [X] : (...) => g" keeps "=> g" outside the quantifier scope.
|
||||
expr_ref body = parse_expr(PREC_EQ, true, is_boolean);
|
||||
m_bound.pop_back();
|
||||
return mk_quantifier(is_forall, vars, body);
|
||||
}
|
||||
|
|
@ -1672,10 +1723,10 @@ class tptp_parser {
|
|||
expect(token_kind::rbrack, "']'");
|
||||
}
|
||||
expect(token_kind::colon, "':'");
|
||||
return parse_formula();
|
||||
return parse_formula(is_boolean);
|
||||
}
|
||||
|
||||
return parse_atomic_formula();
|
||||
return parse_atomic_formula(is_boolean);
|
||||
}
|
||||
|
||||
// Grammar: <tff_binary_formula> ::= <tff_binary_nonassoc> | <tff_binary_assoc>
|
||||
|
|
@ -1687,8 +1738,15 @@ class tptp_parser {
|
|||
// <tff_and_formula> ::= <tff_unit_formula> & <tff_unit_formula>
|
||||
// | <tff_and_formula> & <tff_unit_formula>
|
||||
// Implements a Pratt-style (precedence climbing) parser for binary connectives.
|
||||
expr_ref parse_expr(unsigned min_prec, bool consume_at = true) {
|
||||
expr_ref e = parse_unary_formula();
|
||||
expr_ref parse_expr(unsigned min_prec, bool consume_at, bool is_boolean) {
|
||||
expr_ref e = parse_unary_formula(is_boolean);
|
||||
return parse_binary_rest(e, min_prec, consume_at);
|
||||
}
|
||||
|
||||
// Precedence-climbing loop continued from an already-parsed left operand `e`.
|
||||
// Split out from parse_expr so callers that have consumed a leading unary unit
|
||||
// (e.g. a '~' immediately after '(') can resume binary-connective parsing.
|
||||
expr_ref parse_binary_rest(expr_ref e, unsigned min_prec, bool consume_at = true) {
|
||||
for (;;) {
|
||||
// Handle @ (function application) with highest precedence
|
||||
// But NOT when we're inside a lambda body that's an @ argument
|
||||
|
|
@ -1719,7 +1777,15 @@ class tptp_parser {
|
|||
if (it->second.precedence < min_prec) break;
|
||||
next(); // consume the operator token
|
||||
unsigned next_prec = it->second.right_assoc ? it->second.precedence : it->second.precedence + 1;
|
||||
expr_ref rhs = parse_expr(next_prec, consume_at);
|
||||
// Operands of every connective except '='/'!=' are Boolean; only equality
|
||||
// takes term operands. Derive the operand context from the operator rather
|
||||
// than inheriting is_boolean from the left operand. Otherwise, once a
|
||||
// term-valued equality literal (e.g. 'a = b') sets is_boolean to false, a
|
||||
// predicate atom later in the same clause ('a = b | q') would be parsed as a
|
||||
// term and boxed into '$box_U_to_Bool(q)', severing it from the Boolean
|
||||
// predicate 'q' used elsewhere and making refutable problems satisfiable.
|
||||
bool rhs_is_boolean = it->second.precedence != PREC_EQ;
|
||||
expr_ref rhs = parse_expr(next_prec, consume_at, rhs_is_boolean);
|
||||
expr_ref_vector args(m);
|
||||
args.push_back(e);
|
||||
args.push_back(rhs);
|
||||
|
|
@ -1786,12 +1852,27 @@ class tptp_parser {
|
|||
// Try relative to current file's directory
|
||||
std::string local = normalize_path(dirname(curr_file) + "/" + name);
|
||||
if (file_exists(local)) return local;
|
||||
// Try TPTP environment variable (standard TPTP convention)
|
||||
// Try TPTP environment variable (standard TPTP convention): includes such as
|
||||
// "Axioms/MAT001^0.ax" are resolved relative to the TPTP root directory named
|
||||
// by $TPTP. This is required when a problem is run from a directory that does
|
||||
// not contain the Axioms/ tree (e.g. an isolated benchmark harness workspace).
|
||||
char const* root = std::getenv("TPTP");
|
||||
if (root) {
|
||||
std::string env = normalize_path(std::string(root) + "/" + name);
|
||||
if (file_exists(env)) return env;
|
||||
}
|
||||
// Walk up ancestor directories of the current file. TPTP include paths are
|
||||
// relative to the TPTP root directory (e.g. "Axioms/BOO001-0.ax"), while the
|
||||
// problem file typically lives in a subdirectory such as "Problems/BOO/".
|
||||
std::string dir = dirname(curr_file);
|
||||
for (;;) {
|
||||
size_t idx = dir.find_last_of("/\\");
|
||||
if (idx == std::string::npos) break;
|
||||
dir = dir.substr(0, idx);
|
||||
if (dir.empty()) break;
|
||||
std::string candidate = normalize_path(dir + "/" + name);
|
||||
if (file_exists(candidate)) return candidate;
|
||||
}
|
||||
// Try relative to current working directory (common when running from TPTP root)
|
||||
std::string cwd_relative = normalize_path(name);
|
||||
if (file_exists(cwd_relative)) return cwd_relative;
|
||||
|
|
@ -1825,7 +1906,7 @@ class tptp_parser {
|
|||
// <annotations> ::= ,<source><optional_info> | <null>
|
||||
void parse_annotated() {
|
||||
expect(token_kind::lparen, "'('");
|
||||
parse_name();
|
||||
std::string formula_name = parse_name();
|
||||
expect(token_kind::comma, "','");
|
||||
std::string role = to_lower(parse_name());
|
||||
expect(token_kind::comma, "','");
|
||||
|
|
@ -1836,12 +1917,14 @@ class tptp_parser {
|
|||
else if (role == "logic") {
|
||||
// Modal logic declarations ($modal == [...]) — skip the formula body
|
||||
skip_annotations_until_rparen();
|
||||
warning_msg("non-classical logics are not supported");
|
||||
++m_dropped_formulas;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
implicit_var_scope implicit_scope;
|
||||
scoped_implicit_vars scoped(*this, implicit_scope);
|
||||
expr_ref f = parse_formula();
|
||||
expr_ref f = parse_formula(true);
|
||||
if (!implicit_scope.order.empty()) {
|
||||
f = mk_quantifier(true, implicit_scope.order, f);
|
||||
}
|
||||
|
|
@ -1854,13 +1937,30 @@ class tptp_parser {
|
|||
}
|
||||
m_cmd.assert_expr(f);
|
||||
} catch (z3_exception const& ex) {
|
||||
// Sort mismatch or other semantic error in this formula — skip it
|
||||
IF_VERBOSE(2, verbose_stream() << "skipping formula due to: " << ex.what() << "\n");
|
||||
// Sort mismatch or other semantic error in this formula — skip it.
|
||||
// A dropped axiom/definition removes constraints from the problem, so a
|
||||
// subsequent "sat" verdict is unsound: it may only hold because the
|
||||
// dropped formula was missing. The count is used to downgrade a sat
|
||||
// result to GaveUp rather than report a spurious CounterSatisfiable.
|
||||
++m_dropped_formulas;
|
||||
std::ostringstream oss;
|
||||
oss << "skipping formula '" << formula_name << "' due to: " << ex.what();
|
||||
warning_msg(oss.str().c_str());
|
||||
// Skip to '.' to resync the parser for the next annotated formula
|
||||
while (!is(token_kind::eof_tok) && !is(token_kind::dot))
|
||||
next();
|
||||
if (is(token_kind::dot)) next();
|
||||
return;
|
||||
} catch (std::exception const& ex) {
|
||||
++m_dropped_formulas;
|
||||
std::ostringstream oss;
|
||||
oss << "skipping formula '" << formula_name << "' (role " << role << ") due to: " << ex.what() << "\n";
|
||||
warning_msg(oss.str().c_str());
|
||||
while (!is(token_kind::eof_tok) && !is(token_kind::dot))
|
||||
next();
|
||||
if (is(token_kind::dot))
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2114,6 +2214,12 @@ public:
|
|||
}};
|
||||
}
|
||||
|
||||
// Record a double-quoted string constant as a TPTP distinct object (deduplicated by name).
|
||||
void register_distinct_object(std::string const& name, expr* c) {
|
||||
if (m_distinct_objects.emplace(name, c).second)
|
||||
m_pinned_exprs.push_back(c);
|
||||
}
|
||||
|
||||
void parse_input(std::istream& in, std::string const& current_file) {
|
||||
// Save parser state so that included files don't clobber the caller's lexer.
|
||||
std::string saved_input = std::move(m_input);
|
||||
|
|
@ -2123,6 +2229,7 @@ public:
|
|||
std::ostringstream buf;
|
||||
buf << in.rdbuf();
|
||||
m_input = buf.str();
|
||||
extract_expected_status(m_input);
|
||||
m_lex = std::make_unique<lexer>(m_input);
|
||||
next();
|
||||
parse_toplevel(current_file);
|
||||
|
|
@ -2149,6 +2256,78 @@ public:
|
|||
}
|
||||
|
||||
bool has_conjecture() const { return m_has_conjecture; }
|
||||
|
||||
// TPTP double-quoted strings ("...") denote pairwise distinct domain elements.
|
||||
// Assert a single global distinctness constraint over all collected distinct objects
|
||||
// so that e.g. "Apple" != "Microsoft" is recognized as a theorem.
|
||||
void assert_distinct_objects() {
|
||||
if (m_distinct_objects.size() < 2) return;
|
||||
expr_ref_vector objs(m);
|
||||
for (auto const& kv : m_distinct_objects)
|
||||
objs.push_back(kv.second);
|
||||
m_cmd.assert_expr(expr_ref(m.mk_distinct(objs.size(), objs.data()), m));
|
||||
}
|
||||
|
||||
// Number of axioms/definitions that were dropped during parsing because the
|
||||
// higher-order encoding could not type-check them. When non-zero, a "sat"
|
||||
// verdict cannot be trusted (the missing constraints may be exactly what
|
||||
// makes the problem unsatisfiable).
|
||||
unsigned dropped_formulas() const { return m_dropped_formulas; }
|
||||
|
||||
std::string const& expected_status() const { return m_expected_status; }
|
||||
|
||||
// Scan TPTP comments for an SZS/Status annotation, e.g.
|
||||
// % Status : Unsatisfiable
|
||||
// % SZS status Theorem
|
||||
// Only the first annotation found (the top-level file's) is recorded.
|
||||
void extract_expected_status(std::string const& text) {
|
||||
if (!m_expected_status.empty())
|
||||
return;
|
||||
std::istringstream in(text);
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
// TPTP comment lines start with '%'.
|
||||
size_t i = line.find_first_not_of(" \t");
|
||||
if (i == std::string::npos || line[i] != '%')
|
||||
continue;
|
||||
++i; // skip '%'
|
||||
// Skip leading '%' and spaces.
|
||||
i = line.find_first_not_of("% \t", i);
|
||||
if (i == std::string::npos)
|
||||
continue;
|
||||
std::string rest = line.substr(i);
|
||||
std::string status;
|
||||
// Form 1: "SZS status <Word>"
|
||||
if (rest.compare(0, 4, "SZS ") == 0) {
|
||||
size_t p = rest.find("status");
|
||||
if (p == std::string::npos)
|
||||
continue;
|
||||
p += 6; // length of "status"
|
||||
status = next_status_word(rest, p);
|
||||
}
|
||||
// Form 2: "Status : <Word>" / "Status : <Word>"
|
||||
else if (rest.compare(0, 6, "Status") == 0) {
|
||||
size_t p = rest.find(':', 6);
|
||||
if (p == std::string::npos)
|
||||
continue;
|
||||
status = next_status_word(rest, p + 1);
|
||||
}
|
||||
if (!status.empty()) {
|
||||
m_expected_status = status;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string next_status_word(std::string const& s, size_t p) {
|
||||
size_t a = s.find_first_not_of(" \t", p);
|
||||
if (a == std::string::npos)
|
||||
return "";
|
||||
size_t b = a;
|
||||
while (b < s.size() && (isalnum((unsigned char)s[b]) || s[b] == '_'))
|
||||
++b;
|
||||
return s.substr(a, b - a);
|
||||
}
|
||||
};
|
||||
|
||||
expr_ref tptp_parser::parse_term() {
|
||||
|
|
@ -2172,12 +2351,41 @@ expr_ref tptp_parser::parse_term() {
|
|||
return e;
|
||||
}
|
||||
|
||||
expr_ref tptp_parser::parse_formula() {
|
||||
return parse_expr(PREC_IFF);
|
||||
expr_ref tptp_parser::parse_formula(bool is_boolean) {
|
||||
return parse_expr(PREC_IFF, true, is_boolean);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Classify an SZS status into the coarse verdict used for cross-checking.
|
||||
// unsat: a refutation/proof exists (Theorem, Unsatisfiable, ContradictoryAxioms, ...)
|
||||
// sat: a model exists (Satisfiable, CounterSatisfiable, ...)
|
||||
// other: no comparable verdict (Open, Unknown, Timeout, GaveUp, empty, ...)
|
||||
enum class szs_verdict { unsat, sat, other };
|
||||
|
||||
static szs_verdict classify_szs(std::string const& s) {
|
||||
if (s == "Theorem" || s == "Unsatisfiable" || s == "ContradictoryAxioms" || s == "Unsat")
|
||||
return szs_verdict::unsat;
|
||||
if (s == "Satisfiable" || s == "CounterSatisfiable" || s == "CounterTheorem" || s == "Sat")
|
||||
return szs_verdict::sat;
|
||||
return szs_verdict::other;
|
||||
}
|
||||
|
||||
// Emit the SZS status produced by z3. If the input carries an annotated status
|
||||
// that contradicts the produced verdict, prepend "BUG" to flag the mismatch.
|
||||
static void report_szs_status(char const* produced, std::string const& expected) {
|
||||
szs_verdict pv = classify_szs(produced);
|
||||
szs_verdict ev = classify_szs(expected);
|
||||
bool is_bug = !expected.empty() &&
|
||||
(pv == szs_verdict::unsat || pv == szs_verdict::sat) &&
|
||||
(ev == szs_verdict::unsat || ev == szs_verdict::sat) &&
|
||||
pv != ev;
|
||||
if (is_bug)
|
||||
std::cout << "% SZS status BUG " << produced << " (expected " << expected << ")\n";
|
||||
else
|
||||
std::cout << "% SZS status " << produced << "\n";
|
||||
}
|
||||
|
||||
static unsigned read_tptp_stream(std::istream& in, char const* current_file) {
|
||||
register_on_timeout_proc(on_timeout);
|
||||
try {
|
||||
|
|
@ -2186,6 +2394,7 @@ static unsigned read_tptp_stream(std::istream& in, char const* current_file) {
|
|||
|
||||
tptp_parser p(ctx);
|
||||
p.parse_input(in, current_file ? current_file : ".");
|
||||
p.assert_distinct_objects();
|
||||
|
||||
// Suppress default check-sat output; TPTP frontend reports SZS status explicitly.
|
||||
std::ostringstream sink;
|
||||
|
|
@ -2194,12 +2403,24 @@ static unsigned read_tptp_stream(std::istream& in, char const* current_file) {
|
|||
ctx.check_sat(0, nullptr);
|
||||
switch (ctx.cs_state()) {
|
||||
case cmd_context::css_unsat:
|
||||
if (p.has_conjecture()) std::cout << "% SZS status Theorem\n";
|
||||
else std::cout << "% SZS status Unsatisfiable\n";
|
||||
if (p.has_conjecture()) report_szs_status("Theorem", p.expected_status());
|
||||
else report_szs_status("Unsatisfiable", p.expected_status());
|
||||
break;
|
||||
case cmd_context::css_sat:
|
||||
if (p.has_conjecture()) std::cout << "% SZS status CounterSatisfiable\n";
|
||||
else std::cout << "% SZS status Satisfiable\n";
|
||||
// A "sat" verdict is only sound if the whole problem was encoded. If any
|
||||
// axiom/definition was dropped during parsing (e.g. an unsupported
|
||||
// higher-order construct), the model may be spurious — the dropped
|
||||
// constraints could rule it out. Report GaveUp instead of a misleading
|
||||
// CounterSatisfiable/Satisfiable (which would otherwise be flagged BUG
|
||||
// against an annotated Theorem/Unsatisfiable status).
|
||||
if (p.dropped_formulas() > 0) {
|
||||
std::cout << "% SZS status GaveUp\n";
|
||||
std::cout << "% SZS reason " << p.dropped_formulas()
|
||||
<< " formula(s) dropped during encoding; model is not certified\n";
|
||||
break;
|
||||
}
|
||||
if (p.has_conjecture()) report_szs_status("CounterSatisfiable", p.expected_status());
|
||||
else report_szs_status("Satisfiable", p.expected_status());
|
||||
if (g_display_model) {
|
||||
model_ref mdl;
|
||||
if (ctx.is_model_available(mdl))
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ namespace lp {
|
|||
|
||||
void limit_j(unsigned bound_j, const mpq& u, bool coeff_before_j_is_pos, bool is_lower_bound, bool strict) {
|
||||
auto* lar = &m_bp.lp();
|
||||
const auto& row = this->m_row;
|
||||
auto* row = &this->m_row;
|
||||
auto explain = [row, bound_j, coeff_before_j_is_pos, is_lower_bound, strict, lar]() {
|
||||
(void) strict;
|
||||
TRACE(bound_analyzer, tout << "explain_bound_on_var_on_coeff, bound_j = " << bound_j << ", coeff_before_j_is_pos = " << coeff_before_j_is_pos << ", is_lower_bound = " << is_lower_bound << ", strict = " << strict << "\n";);
|
||||
|
|
@ -305,7 +305,7 @@ namespace lp {
|
|||
int j_sign = (coeff_before_j_is_pos ? 1 : -1) * bound_sign;
|
||||
|
||||
u_dependency* ret = nullptr;
|
||||
for (auto const& r : row) {
|
||||
for (auto const& r : *row) {
|
||||
unsigned j = r.var();
|
||||
if (j == bound_j)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -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,32 +84,264 @@ namespace lp {
|
|||
SASSERT(lp_status::OPTIMAL == lra.get_status() || lp_status::FEASIBLE == lra.get_status());
|
||||
}
|
||||
|
||||
impq int_cube::get_cube_delta_for_term(const lar_term& t) const {
|
||||
if (t.size() == 2) {
|
||||
bool seen_minus = false;
|
||||
bool seen_plus = false;
|
||||
for(lar_term::ival p : t) {
|
||||
if (!lia.column_is_int(p.j()))
|
||||
goto usual_delta;
|
||||
const mpq & c = p.coeff();
|
||||
if (c == one_of_type<mpq>()) {
|
||||
seen_plus = true;
|
||||
} else if (c == -one_of_type<mpq>()) {
|
||||
seen_minus = true;
|
||||
} else {
|
||||
goto usual_delta;
|
||||
// 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 = {{mpq(1), i}, {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 = {{mpq(1), i}, {-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;
|
||||
flips.push_back({j, floor(v), false});
|
||||
}
|
||||
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(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({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 (seen_minus && seen_plus)
|
||||
return zero_of_type<impq>();
|
||||
return impq(0, 1);
|
||||
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, seen_plus = false, all_ok = true;
|
||||
for (lar_term::ival p : t) {
|
||||
if (!lia.column_is_int(p.j())) { all_ok = false; break; }
|
||||
const mpq& c = p.coeff();
|
||||
if (c == one_of_type<mpq>()) seen_plus = true;
|
||||
else if (c == -one_of_type<mpq>()) seen_minus = true;
|
||||
else { all_ok = false; break; }
|
||||
}
|
||||
if (all_ok) {
|
||||
if (seen_minus && seen_plus)
|
||||
return zero_of_type<impq>();
|
||||
return impq(0, 1);
|
||||
}
|
||||
}
|
||||
usual_delta:
|
||||
mpq delta = zero_of_type<mpq>();
|
||||
for (lar_term::ival p : t)
|
||||
if (lia.column_is_int(p.j()))
|
||||
delta += abs(p.coeff());
|
||||
|
||||
delta *= mpq(1, 2);
|
||||
return impq(delta);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1120,8 +1125,10 @@ namespace lp {
|
|||
|
||||
void lar_solver::explain_fixed_column(unsigned j, explanation& ex) {
|
||||
SASSERT(column_is_fixed(j));
|
||||
auto* deps = get_bound_constraint_witnesses_for_column(j);
|
||||
for (auto ci : flatten(deps))
|
||||
const column& ul = m_imp->m_columns[j];
|
||||
m_imp->m_tmp_dependencies.reset();
|
||||
m_imp->m_dependencies.linearize(ul.lower_bound_witness(), ul.upper_bound_witness(), m_imp->m_tmp_dependencies);
|
||||
for (auto ci : m_imp->m_tmp_dependencies)
|
||||
ex.push_back(ci);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -969,7 +969,6 @@ lbool solver::check_assignment() {
|
|||
catch (z3_exception &) {
|
||||
statistics &st = m_imp->m_nla_core.lp_settings().stats().m_st;
|
||||
m_imp->m_nlsat->collect_statistics(st);
|
||||
IF_VERBOSE(0, verbose_stream() << "check-assignment\n");
|
||||
if (m_imp->m_limit.is_canceled()) {
|
||||
return l_undef;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Notes:
|
|||
#include "util/mpbqi.h"
|
||||
#include "util/timeit.h"
|
||||
#include "util/common_msgs.h"
|
||||
#include "util/index_sort_with_mutations.h"
|
||||
#include "math/polynomial/algebraic_numbers.h"
|
||||
#include "math/polynomial/upolynomial.h"
|
||||
#include "math/polynomial/sexpr2upolynomial.h"
|
||||
|
|
@ -593,10 +594,57 @@ namespace algebraic_numbers {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort an index permutation with a bounds-safe, mutation-aware merge
|
||||
// sort. The comparator (compare/lt) is NOT pure: it MUTATES the
|
||||
// algebraic numbers it compares (refining their isolating intervals) and
|
||||
// may throw on the resource limit, so std::sort would be undefined
|
||||
// behavior here. See util/index_sort_with_mutations.h for the rationale.
|
||||
void merge_sort_roots_perm(numeral_vector & r, unsigned_vector & perm) {
|
||||
unsigned n = perm.size();
|
||||
if (n < 2)
|
||||
return;
|
||||
unsigned_vector scratch;
|
||||
scratch.resize(n, 0);
|
||||
// Strict, total, stable index comparator: decided sign first, then index
|
||||
// tiebreak (covers the equal/limit case so the order stays deterministic).
|
||||
auto idx_lt = [&](unsigned x, unsigned y) {
|
||||
::sign s = compare(r[x], r[y]);
|
||||
return s != sign_zero ? s == sign_neg : x < y;
|
||||
};
|
||||
stable_index_merge_sort(perm.data(), scratch.data(), n, idx_lt);
|
||||
}
|
||||
|
||||
void sort_roots(numeral_vector & r) {
|
||||
if (m_limit.inc()) {
|
||||
// DEBUG_CODE(check_transitivity(r););
|
||||
std::sort(r.begin(), r.end(), lt_proc(m_wrapper));
|
||||
if (!m_limit.inc())
|
||||
return;
|
||||
// DEBUG_CODE(check_transitivity(r););
|
||||
unsigned n = r.size();
|
||||
if (n < 2)
|
||||
return;
|
||||
unsigned_vector perm;
|
||||
perm.resize(n, 0);
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
perm[i] = i;
|
||||
merge_sort_roots_perm(r, perm);
|
||||
// Apply the permutation in place via swap cycles. anum swap is a cheap
|
||||
// pointer swap (move nulls the source), so this is O(n) cheap moves.
|
||||
unsigned_vector pos; // pos[v] = current position of element v
|
||||
pos.resize(n, 0);
|
||||
unsigned_vector at; // at[p] = element currently at position p
|
||||
at.resize(n, 0);
|
||||
for (unsigned i = 0; i < n; ++i) {
|
||||
pos[i] = i;
|
||||
at[i] = i;
|
||||
}
|
||||
for (unsigned target = 0; target < n; ++target) {
|
||||
unsigned want = perm[target]; // element that should end up at target
|
||||
unsigned cur = pos[want]; // where it currently is
|
||||
if (cur == target)
|
||||
continue;
|
||||
unsigned other = at[target]; // element currently at target
|
||||
std::swap(r[target], r[cur]);
|
||||
at[target] = want; at[cur] = other;
|
||||
pos[want] = target; pos[other] = cur;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "math/polynomial/polynomial.h"
|
||||
#include "nlsat_common.h"
|
||||
#include "util/vector.h"
|
||||
#include "util/index_sort_with_mutations.h"
|
||||
#include "util/trace.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -956,6 +957,22 @@ namespace nlsat {
|
|||
return m_pm.id(a.ire.p) < m_pm.id(b.ire.p);
|
||||
}
|
||||
|
||||
// Sort an index permutation with a bounds-safe, mutation-aware merge
|
||||
// sort. The comparator (root_function_lt / anum_manager::lt -> compare)
|
||||
// is NOT pure: it MUTATES the algebraic numbers it compares by refining
|
||||
// their isolating intervals, and may throw on the resource limit, so a
|
||||
// single std::sort would be undefined behavior and can crash via an
|
||||
// out-of-bounds read on timeout. See util/index_sort_with_mutations.h
|
||||
// for the full rationale.
|
||||
template<typename Less>
|
||||
void merge_sort_perm(std_vector<unsigned>& perm, Less less) {
|
||||
unsigned n = static_cast<unsigned>(perm.size());
|
||||
if (n < 2)
|
||||
return;
|
||||
std_vector<unsigned> scratch(n);
|
||||
stable_index_merge_sort(perm.data(), scratch.data(), n, less);
|
||||
}
|
||||
|
||||
// Apply a permutation to a range of root_functions using swap cycles,
|
||||
// avoiding the bulk anum allocations that std::sort's move operations cause.
|
||||
void apply_permutation(std_vector<root_function>& rfs, unsigned offset, std_vector<unsigned> const& perm) {
|
||||
|
|
@ -982,7 +999,7 @@ namespace nlsat {
|
|||
if (mid_pos > 1) {
|
||||
std_vector<unsigned> perm(mid_pos);
|
||||
std::iota(perm.begin(), perm.end(), 0u);
|
||||
std::sort(perm.begin(), perm.end(), [&](unsigned a, unsigned b) {
|
||||
merge_sort_perm(perm, [&](unsigned a, unsigned b) {
|
||||
return root_function_lt(rfs[a], rfs[b], true);
|
||||
});
|
||||
apply_permutation(rfs, 0, perm);
|
||||
|
|
@ -993,7 +1010,7 @@ namespace nlsat {
|
|||
if (upper_sz > 1) {
|
||||
std_vector<unsigned> perm(upper_sz);
|
||||
std::iota(perm.begin(), perm.end(), 0u);
|
||||
std::sort(perm.begin(), perm.end(), [&](unsigned a, unsigned b) {
|
||||
merge_sort_perm(perm, [&](unsigned a, unsigned b) {
|
||||
return root_function_lt(rfs[mid_pos + a], rfs[mid_pos + b], false);
|
||||
});
|
||||
apply_permutation(rfs, mid_pos, perm);
|
||||
|
|
@ -1192,20 +1209,29 @@ namespace nlsat {
|
|||
if (root_vals.size() < 2)
|
||||
return;
|
||||
|
||||
std::sort(root_vals.begin(), root_vals.end(), [&](auto const& a, auto const& b) {
|
||||
return m_am.lt(a.first, b.first);
|
||||
// Sort root values by an index permutation with the bounds-safe,
|
||||
// mutation-aware merge sort (see merge_sort_perm). As in
|
||||
// sort_root_function_partitions, the comparator (anum_manager::lt ->
|
||||
// compare) MUTATES the algebraic numbers it compares (it refines their
|
||||
// isolating intervals and may hit the resource limit and throw), so it is
|
||||
// not a fixed strict weak ordering over a single sort; std::sort here would
|
||||
// be undefined behavior and crash via an out-of-bounds read on timeout.
|
||||
std_vector<unsigned> perm(root_vals.size());
|
||||
std::iota(perm.begin(), perm.end(), 0u);
|
||||
merge_sort_perm(perm, [&](unsigned a, unsigned b) {
|
||||
return m_am.lt(root_vals[a].first, root_vals[b].first);
|
||||
});
|
||||
|
||||
|
||||
TRACE(lws,
|
||||
tout << " Sorted roots:\n";
|
||||
for (unsigned j = 0; j < root_vals.size(); ++j)
|
||||
m_pm.display(m_am.display_decimal(tout << " [" << j << "] val=", root_vals[j].first, 5) << " poly=", root_vals[j].second) << "\n";
|
||||
for (unsigned j = 0; j < perm.size(); ++j)
|
||||
m_pm.display(m_am.display_decimal(tout << " [" << j << "] val=", root_vals[perm[j]].first, 5) << " poly=", root_vals[perm[j]].second) << "\n";
|
||||
);
|
||||
|
||||
std::set<std::pair<poly*, poly*>> added_pairs;
|
||||
for (unsigned j = 0; j + 1 < root_vals.size(); ++j) {
|
||||
poly* p1 = root_vals[j].second;
|
||||
poly* p2 = root_vals[j + 1].second;
|
||||
for (unsigned j = 0; j + 1 < perm.size(); ++j) {
|
||||
poly* p1 = root_vals[perm[j]].second;
|
||||
poly* p2 = root_vals[perm[j + 1]].second;
|
||||
if (!p1 || !p2 || p1 == p2)
|
||||
continue;
|
||||
if (p1 > p2) std::swap(p1, p2);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ z3_add_component(params
|
|||
seq_rewriter_params.pyg
|
||||
sls_params.pyg
|
||||
smt_params_helper.pyg
|
||||
smt_parallel_params.pyg
|
||||
solver_params.pyg
|
||||
tactic_params.pyg
|
||||
EXTRA_REGISTER_MODULE_HEADERS
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
def_module_params('smt_parallel',
|
||||
export=True,
|
||||
description='Experimental parameters for parallel solving',
|
||||
params=(
|
||||
('inprocessing', BOOL, False, 'integrate in-processing as a heuristic simplification'),
|
||||
('sls', BOOL, False, 'add sls-tactic as a separate worker thread outside the search tree parallelism'),
|
||||
('num_global_bb_fl_threads', UINT, 0, 'run failed-literal backbone worker threads; default is 0 (off), supported values are 1 (negative mode only) or 2 (negative and positive mode)'),
|
||||
('num_global_bb_batch_threads', UINT, 0, 'run Janota-style chunking backbone worker threads; default is 0 (off), supported values are 1 (negative mode only) or 2 (negative and positive mode)'),
|
||||
('local_backbones', BOOL, False, 'enable local backbones experiment within the search tree parallelism'),
|
||||
('core_minimize', BOOL, True, 'minimize unsat cores used for parallel cube backtracking'),
|
||||
('ablate_backtracking', BOOL, False, 'ablation: pass entire cube as core instead of unsat core during backtracking'),
|
||||
))
|
||||
|
|
@ -2467,6 +2467,7 @@ private:
|
|||
cur_vals[i] = var_lbs[i];
|
||||
|
||||
var_subst vs(m, false);
|
||||
inv_var_shifter shift(m);
|
||||
expr_ref_vector disjuncts(m);
|
||||
|
||||
while (true) {
|
||||
|
|
@ -2479,6 +2480,7 @@ private:
|
|||
for (expr* p : payload) {
|
||||
expr_ref inst(m);
|
||||
inst = vs(p, subst_map.size(), subst_map.data());
|
||||
shift(inst, num_decls, inst);
|
||||
inst_conjs.push_back(inst);
|
||||
}
|
||||
expr_ref inst_body(m);
|
||||
|
|
|
|||
|
|
@ -1290,6 +1290,15 @@ namespace qe {
|
|||
|
||||
TRACE(qe, tout << fml << "\n";);
|
||||
|
||||
// qe/qe2 over a quantifier-free formula has nothing to eliminate.
|
||||
// Under check-sat semantics the free variables are implicitly
|
||||
// existentially quantified, so decide satisfiability directly
|
||||
// instead of leaving an undecided residual goal (which would be
|
||||
// reported as 'unknown').
|
||||
flet<qsat_mode> _mode(m_mode,
|
||||
(m_mode == qsat_qe || m_mode == qsat_qe_rec) && !has_quantifiers(fml)
|
||||
? qsat_sat : m_mode);
|
||||
|
||||
if (m_mode == qsat_qe_rec) {
|
||||
fml = elim_rec(fml);
|
||||
in->reset();
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ namespace sat {
|
|||
m_best_phase.reset();
|
||||
m_phase.reset();
|
||||
m_prev_phase.reset();
|
||||
m_phase_birthdate.reset();
|
||||
m_best_phase_birthdate.reset();
|
||||
m_assigned_since_gc.reset();
|
||||
m_last_conflict.reset();
|
||||
m_last_propagation.reset();
|
||||
|
|
@ -161,6 +163,8 @@ namespace sat {
|
|||
m_phase[v] = src.m_phase[v];
|
||||
m_best_phase[v] = src.m_best_phase[v];
|
||||
m_prev_phase[v] = src.m_prev_phase[v];
|
||||
m_phase_birthdate[v] = src.m_phase_birthdate[v];
|
||||
m_best_phase_birthdate[v] = src.m_best_phase_birthdate[v];
|
||||
|
||||
// inherit activity:
|
||||
m_activity[v] = src.m_activity[v];
|
||||
|
|
@ -267,6 +271,8 @@ namespace sat {
|
|||
m_phase[v] = false;
|
||||
m_best_phase[v] = false;
|
||||
m_prev_phase[v] = false;
|
||||
m_phase_birthdate[v] = 0;
|
||||
m_best_phase_birthdate[v] = 0;
|
||||
m_assigned_since_gc[v] = false;
|
||||
m_last_conflict[v] = 0;
|
||||
m_last_propagation[v] = 0;
|
||||
|
|
@ -308,6 +314,8 @@ namespace sat {
|
|||
m_phase.push_back(false);
|
||||
m_best_phase.push_back(false);
|
||||
m_prev_phase.push_back(false);
|
||||
m_phase_birthdate.push_back(0);
|
||||
m_best_phase_birthdate.push_back(0);
|
||||
m_assigned_since_gc.push_back(false);
|
||||
m_last_conflict.push_back(0);
|
||||
m_last_propagation.push_back(0);
|
||||
|
|
@ -645,6 +653,26 @@ namespace sat {
|
|||
return 3*cls_allocator().get_allocation_size()/2 + memory::get_allocation_size() > memory::get_max_memory_size();
|
||||
}
|
||||
|
||||
void solver::set_phase(literal l) {
|
||||
if (l.var() >= num_vars())
|
||||
return;
|
||||
bool value = !l.sign();
|
||||
set_phase(l.var(), value);
|
||||
set_best_phase(l.var(), value);
|
||||
}
|
||||
|
||||
void solver::set_phase(bool_var v, bool value) {
|
||||
if (m_phase[v] != value)
|
||||
m_phase_birthdate[v] = m_stats.m_conflicts;
|
||||
m_phase[v] = value;
|
||||
}
|
||||
|
||||
void solver::set_best_phase(bool_var v, bool value) {
|
||||
if (m_best_phase[v] != value)
|
||||
m_best_phase_birthdate[v] = m_stats.m_conflicts;
|
||||
m_best_phase[v] = value;
|
||||
}
|
||||
|
||||
struct solver::cmp_activity {
|
||||
solver& s;
|
||||
cmp_activity(solver& s):s(s) {}
|
||||
|
|
@ -896,7 +924,7 @@ namespace sat {
|
|||
m_assignment[(~l).index()] = l_false;
|
||||
bool_var v = l.var();
|
||||
m_justification[v] = j;
|
||||
m_phase[v] = !l.sign();
|
||||
set_phase(v, !l.sign());
|
||||
m_assigned_since_gc[v] = true;
|
||||
m_trail.push_back(l);
|
||||
|
||||
|
|
@ -904,17 +932,17 @@ namespace sat {
|
|||
case BH_VSIDS:
|
||||
break;
|
||||
case BH_CHB:
|
||||
m_last_propagation[v] = m_stats.m_conflict;
|
||||
m_last_propagation[v] = m_stats.m_conflicts;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_config.m_anti_exploration) {
|
||||
uint64_t age = m_stats.m_conflict - m_canceled[v];
|
||||
uint64_t age = m_stats.m_conflicts - m_canceled[v];
|
||||
if (age > 0) {
|
||||
double decay = pow(0.95, static_cast<double>(age));
|
||||
set_activity(v, static_cast<unsigned>(m_activity[v] * decay));
|
||||
// NB. MapleSAT does not update canceled.
|
||||
m_canceled[v] = m_stats.m_conflict;
|
||||
m_canceled[v] = m_stats.m_conflicts;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1378,8 +1406,10 @@ namespace sat {
|
|||
lbool r = m_local_search->check(_lits.size(), _lits.data(), nullptr);
|
||||
auto const& mdl = m_local_search->get_model();
|
||||
if (mdl.size() == m_best_phase.size()) {
|
||||
for (unsigned i = 0; i < m_best_phase.size(); ++i)
|
||||
m_best_phase[i] = l_true == mdl[i];
|
||||
for (unsigned i = 0; i < m_best_phase.size(); ++i) {
|
||||
bool is_true = l_true == mdl[i];
|
||||
set_best_phase(i, is_true);
|
||||
}
|
||||
|
||||
if (r == l_true) {
|
||||
m_conflicts_since_restart = 0;
|
||||
|
|
@ -1671,12 +1701,12 @@ namespace sat {
|
|||
while (!m_case_split_queue.empty()) {
|
||||
if (m_config.m_anti_exploration) {
|
||||
next = m_case_split_queue.min_var();
|
||||
auto age = m_stats.m_conflict - m_canceled[next];
|
||||
auto age = m_stats.m_conflicts - m_canceled[next];
|
||||
while (age > 0) {
|
||||
set_activity(next, static_cast<unsigned>(m_activity[next] * pow(0.95, static_cast<double>(age))));
|
||||
m_canceled[next] = m_stats.m_conflict;
|
||||
m_canceled[next] = m_stats.m_conflicts;
|
||||
next = m_case_split_queue.min_var();
|
||||
age = m_stats.m_conflict - m_canceled[next];
|
||||
age = m_stats.m_conflicts - m_canceled[next];
|
||||
}
|
||||
}
|
||||
next = m_case_split_queue.next_var();
|
||||
|
|
@ -1714,6 +1744,25 @@ namespace sat {
|
|||
}
|
||||
}
|
||||
|
||||
void solver::get_backbone_candidates(literal_vector& lits, unsigned max_num) {
|
||||
struct candidate {
|
||||
literal lit;
|
||||
uint64_t age;
|
||||
};
|
||||
svector<candidate> cands;
|
||||
uint64_t now = m_stats.m_conflicts;
|
||||
for (bool_var v = 0; v < num_vars(); ++v) {
|
||||
if (value(v) != l_undef || was_eliminated(v))
|
||||
continue;
|
||||
bool is_pos = guess(v);
|
||||
cands.push_back({ literal(v, !is_pos), now - get_phase_birthdate(v) });
|
||||
}
|
||||
std::stable_sort(cands.begin(), cands.end(),
|
||||
[](candidate const& a, candidate const& b) { return a.age > b.age; });
|
||||
for (unsigned i = 0; i < cands.size() && i < max_num; ++i)
|
||||
lits.push_back(cands[i].lit);
|
||||
}
|
||||
|
||||
bool solver::decide() {
|
||||
bool_var next;
|
||||
lbool phase = l_undef;
|
||||
|
|
@ -2145,8 +2194,9 @@ namespace sat {
|
|||
for (bool_var v = 0; v < num; ++v) {
|
||||
if (!was_eliminated(v)) {
|
||||
m_model[v] = value(v);
|
||||
m_phase[v] = value(v) == l_true;
|
||||
m_best_phase[v] = value(v) == l_true;
|
||||
bool is_true = value(v) == l_true;
|
||||
set_phase(v, is_true);
|
||||
set_best_phase(v, is_true);
|
||||
}
|
||||
}
|
||||
TRACE(sat_mc_bug, m_mc.display(tout););
|
||||
|
|
@ -2274,7 +2324,7 @@ namespace sat {
|
|||
m_restart_logs++;
|
||||
|
||||
std::stringstream strm;
|
||||
strm << "(sat.stats " << std::setw(6) << m_stats.m_conflict << " "
|
||||
strm << "(sat.stats " << std::setw(6) << m_stats.m_conflicts << " "
|
||||
<< std::setw(6) << m_stats.m_decision << " "
|
||||
<< std::setw(4) << m_stats.m_restart
|
||||
<< mk_stat(*this)
|
||||
|
|
@ -2432,7 +2482,7 @@ namespace sat {
|
|||
m_conflicts_since_init++;
|
||||
m_conflicts_since_restart++;
|
||||
m_conflicts_since_gc++;
|
||||
m_stats.m_conflict++;
|
||||
m_stats.m_conflicts++;
|
||||
if (m_step_size > m_config.m_step_size_min)
|
||||
m_step_size -= m_config.m_step_size_dec;
|
||||
|
||||
|
|
@ -2564,7 +2614,7 @@ namespace sat {
|
|||
tout << "missed " << lit << "@" << lvl(lit) << "\n";);
|
||||
CTRACE(sat, idx == 0, display(tout););
|
||||
if (idx == 0)
|
||||
IF_VERBOSE(0, verbose_stream() << "num-conflicts: " << m_stats.m_conflict << "\n");
|
||||
IF_VERBOSE(0, verbose_stream() << "num-conflicts: " << m_stats.m_conflicts << "\n");
|
||||
VERIFY(idx > 0);
|
||||
idx--;
|
||||
}
|
||||
|
|
@ -2874,7 +2924,7 @@ namespace sat {
|
|||
inc_activity(var);
|
||||
break;
|
||||
case BH_CHB:
|
||||
m_last_conflict[var] = m_stats.m_conflict;
|
||||
m_last_conflict[var] = m_stats.m_conflicts;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -2915,14 +2965,15 @@ namespace sat {
|
|||
for (unsigned i = head; i < sz; ++i) {
|
||||
bool_var v = m_trail[i].var();
|
||||
TRACE(forget_phase, tout << "forgetting phase of v" << v << "\n";);
|
||||
m_phase[v] = m_rand() % 2 == 0;
|
||||
bool value = m_rand() % 2 == 0;
|
||||
set_phase(v, value);
|
||||
}
|
||||
if (is_sat_phase() && head >= m_best_phase_size) {
|
||||
m_best_phase_size = head;
|
||||
IF_VERBOSE(12, verbose_stream() << "sticky trail: " << head << "\n");
|
||||
for (unsigned i = 0; i < head; ++i) {
|
||||
bool_var v = m_trail[i].var();
|
||||
m_best_phase[v] = m_phase[v];
|
||||
set_best_phase(v, m_phase[v]);
|
||||
}
|
||||
set_has_new_best_phase(true);
|
||||
}
|
||||
|
|
@ -2971,23 +3022,30 @@ namespace sat {
|
|||
void solver::do_rephase() {
|
||||
switch (m_config.m_phase) {
|
||||
case PS_ALWAYS_TRUE:
|
||||
for (auto& p : m_phase) p = true;
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
set_phase(i, true);
|
||||
break;
|
||||
case PS_ALWAYS_FALSE:
|
||||
for (auto& p : m_phase) p = false;
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
set_phase(i, false);
|
||||
break;
|
||||
case PS_FROZEN:
|
||||
break;
|
||||
case PS_BASIC_CACHING:
|
||||
switch (m_rephase.count % 4) {
|
||||
case 0:
|
||||
for (auto& p : m_phase) p = (m_rand() % 2) == 0;
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i) {
|
||||
bool value = (m_rand() % 2) == 0;
|
||||
set_phase(i, value);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
for (auto& p : m_phase) p = false;
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
set_phase(i, false);
|
||||
break;
|
||||
case 2:
|
||||
for (auto& p : m_phase) p = !p;
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
set_phase(i, !m_phase[i]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -2995,18 +3053,21 @@ namespace sat {
|
|||
break;
|
||||
case PS_SAT_CACHING:
|
||||
if (m_search_state == s_sat)
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
m_phase[i] = m_best_phase[i];
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
set_phase(i, m_best_phase[i]);
|
||||
break;
|
||||
case PS_RANDOM:
|
||||
for (auto& p : m_phase) p = (m_rand() % 2) == 0;
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i) {
|
||||
bool value = (m_rand() % 2) == 0;
|
||||
set_phase(i, value);
|
||||
}
|
||||
break;
|
||||
case PS_LOCAL_SEARCH:
|
||||
if (m_search_state == s_sat) {
|
||||
if (m_rand() % 2 == 0)
|
||||
bounded_local_search();
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
m_phase[i] = m_best_phase[i];
|
||||
for (unsigned i = 0; i < m_phase.size(); ++i)
|
||||
set_phase(i, m_best_phase[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -3601,6 +3662,8 @@ namespace sat {
|
|||
m_phase.shrink(v);
|
||||
m_best_phase.shrink(v);
|
||||
m_prev_phase.shrink(v);
|
||||
m_phase_birthdate.shrink(v);
|
||||
m_best_phase_birthdate.shrink(v);
|
||||
m_assigned_since_gc.shrink(v);
|
||||
m_simplifier.reset_todos();
|
||||
}
|
||||
|
|
@ -3644,7 +3707,7 @@ namespace sat {
|
|||
SASSERT(value(v) == l_undef);
|
||||
m_case_split_queue.unassign_var_eh(v);
|
||||
if (m_config.m_anti_exploration) {
|
||||
m_canceled[v] = m_stats.m_conflict;
|
||||
m_canceled[v] = m_stats.m_conflicts;
|
||||
}
|
||||
}
|
||||
m_trail.shrink(old_sz);
|
||||
|
|
@ -3812,7 +3875,7 @@ namespace sat {
|
|||
double multiplier = m_config.m_reward_offset * (is_sat ? m_config.m_reward_multiplier : 1.0);
|
||||
for (unsigned i = qhead; i < m_trail.size(); ++i) {
|
||||
auto v = m_trail[i].var();
|
||||
auto d = m_stats.m_conflict - m_last_conflict[v] + 1;
|
||||
auto d = m_stats.m_conflicts - m_last_conflict[v] + 1;
|
||||
if (d == 0) d = 1;
|
||||
auto reward = multiplier / d;
|
||||
auto activity = m_activity[v];
|
||||
|
|
@ -4745,7 +4808,7 @@ namespace sat {
|
|||
st.update("sat mk var", m_mk_var);
|
||||
st.update("sat gc clause", m_gc_clause);
|
||||
st.update("sat del clause", m_del_clause);
|
||||
st.update("sat conflicts", m_conflict);
|
||||
st.update("sat conflicts", m_conflicts);
|
||||
st.update("sat decisions", m_decision);
|
||||
st.update("sat propagations 2ary", m_bin_propagate);
|
||||
st.update("sat propagations 3ary", m_ter_propagate);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace sat {
|
|||
unsigned m_mk_bin_clause;
|
||||
unsigned m_mk_ter_clause;
|
||||
unsigned m_mk_clause;
|
||||
unsigned m_conflict;
|
||||
unsigned m_conflicts;
|
||||
unsigned m_propagate;
|
||||
unsigned m_bin_propagate;
|
||||
unsigned m_ter_propagate;
|
||||
|
|
@ -148,6 +148,8 @@ namespace sat {
|
|||
bool_vector m_phase;
|
||||
bool_vector m_best_phase;
|
||||
bool_vector m_prev_phase;
|
||||
svector<uint64_t> m_phase_birthdate;
|
||||
svector<uint64_t> m_best_phase_birthdate;
|
||||
bool m_new_best_phase = false;
|
||||
svector<char> m_assigned_since_gc;
|
||||
search_state m_search_state;
|
||||
|
|
@ -373,12 +375,18 @@ namespace sat {
|
|||
bool was_eliminated(bool_var v) const { return m_eliminated[v]; }
|
||||
void set_eliminated(bool_var v, bool f) override;
|
||||
bool was_eliminated(literal l) const { return was_eliminated(l.var()); }
|
||||
void set_phase(literal l) override { if (l.var() < num_vars()) m_best_phase[l.var()] = m_phase[l.var()] = !l.sign(); }
|
||||
void set_phase(literal l) override;
|
||||
void set_phase(bool_var v, bool value);
|
||||
void set_best_phase(bool_var v, bool value);
|
||||
bool get_phase(bool_var b) { return m_phase.get(b, false); }
|
||||
bool get_best_phase(bool_var b) { return m_best_phase.get(b, false); }
|
||||
uint64_t get_phase_birthdate(bool_var b) const { return m_phase_birthdate.get(b, 0); }
|
||||
uint64_t get_best_phase_birthdate(bool_var b) const { return m_best_phase_birthdate.get(b, 0); }
|
||||
void set_has_new_best_phase(bool b) { m_new_best_phase = b; }
|
||||
bool has_new_best_phase() const { return m_new_best_phase; }
|
||||
void move_to_front(bool_var b);
|
||||
unsigned get_activity(bool_var v) const { return m_activity[v]; }
|
||||
void get_backbone_candidates(literal_vector& lits, unsigned max_num);
|
||||
unsigned scope_lvl() const { return m_scope_lvl; }
|
||||
unsigned search_lvl() const { return m_search_lvl; }
|
||||
bool at_search_lvl() const { return m_scope_lvl == m_search_lvl; }
|
||||
|
|
@ -440,6 +448,8 @@ namespace sat {
|
|||
void set_par(parallel* p, unsigned id);
|
||||
bool canceled() { return !m_rlimit.inc(); }
|
||||
config const& get_config() const { return m_config; }
|
||||
void set_max_conflicts(unsigned n) { m_config.m_max_conflicts = n; }
|
||||
unsigned get_max_conflicts() const { return m_config.m_max_conflicts; }
|
||||
void set_drat(bool d) { m_config.m_drat = d; }
|
||||
drat& get_drat() { return m_drat; }
|
||||
drat* get_drat_ptr() { return &m_drat; }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ Notes:
|
|||
#include "solver/tactic2solver.h"
|
||||
#include "solver/parallel_params.hpp"
|
||||
#include "solver/parallel_tactical.h"
|
||||
#include "solver/parallel_tactical2.h"
|
||||
#include "tactic/tactical.h"
|
||||
#include "tactic/aig/aig_tactic.h"
|
||||
#include "tactic/core/propagate_values_tactic.h"
|
||||
|
|
@ -391,6 +390,15 @@ public:
|
|||
if (m_preprocess) m_preprocess->collect_statistics(st);
|
||||
m_solver.collect_statistics(st);
|
||||
}
|
||||
|
||||
void set_max_conflicts(unsigned max_conflicts) override {
|
||||
m_solver.set_max_conflicts(max_conflicts);
|
||||
}
|
||||
|
||||
unsigned get_max_conflicts() const override {
|
||||
return m_solver.get_max_conflicts();
|
||||
}
|
||||
|
||||
void get_unsat_core(expr_ref_vector & r) override {
|
||||
r.reset();
|
||||
r.append(m_core.size(), m_core.data());
|
||||
|
|
@ -405,6 +413,46 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
unsigned get_assign_level(expr* e) const override {
|
||||
m.is_not(e, e);
|
||||
sat::bool_var bv = m_map.to_bool_var(e);
|
||||
return bv == sat::null_bool_var ? UINT_MAX : m_solver.lvl(bv);
|
||||
}
|
||||
|
||||
bool is_relevant(expr* e) const override {
|
||||
m.is_not(e, e);
|
||||
sat::bool_var bv = m_map.to_bool_var(e);
|
||||
if (bv == sat::null_bool_var)
|
||||
return true;
|
||||
auto* ext = dynamic_cast<euf::solver*>(m_solver.get_extension());
|
||||
return !ext || ext->is_relevant(bv);
|
||||
}
|
||||
|
||||
unsigned get_num_bool_vars() const override {
|
||||
return m_solver.num_vars();
|
||||
}
|
||||
|
||||
sat::bool_var get_bool_var(expr* e) const override {
|
||||
m.is_not(e, e);
|
||||
return m_map.to_bool_var(e);
|
||||
}
|
||||
|
||||
expr* bool_var2expr(sat::bool_var v) const override {
|
||||
return v < m_solver.num_vars() ? m_map.bool_var2expr(v) : nullptr;
|
||||
}
|
||||
|
||||
lbool get_assignment(sat::bool_var v) const override {
|
||||
return v < m_solver.num_vars() ? m_solver.value(v) : l_undef;
|
||||
}
|
||||
|
||||
double get_activity(sat::bool_var v) const override {
|
||||
return v < m_solver.num_vars() ? static_cast<double>(m_solver.get_activity(v)) : 0.0;
|
||||
}
|
||||
|
||||
bool was_eliminated(sat::bool_var v) const override {
|
||||
return v < m_solver.num_vars() && m_solver.was_eliminated(v);
|
||||
}
|
||||
|
||||
expr_ref_vector get_trail(unsigned max_level) override {
|
||||
expr_ref_vector result(m);
|
||||
unsigned sz = m_solver.trail_size();
|
||||
|
|
@ -482,6 +530,70 @@ public:
|
|||
return fmls;
|
||||
}
|
||||
|
||||
expr_ref cube_vsids(expr_ref_vector const& invalid_split_atoms) override {
|
||||
if (!is_internalized()) {
|
||||
lbool r = internalize_formulas();
|
||||
if (r != l_true)
|
||||
return expr_ref(m);
|
||||
}
|
||||
convert_internalized();
|
||||
if (m_solver.inconsistent())
|
||||
return expr_ref(m);
|
||||
|
||||
obj_hashtable<expr> invalid_split_atoms_set;
|
||||
for (expr* e : invalid_split_atoms) {
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
invalid_split_atoms_set.insert(atom);
|
||||
}
|
||||
|
||||
expr_ref result(m);
|
||||
double score = 0.0;
|
||||
unsigned n = 0;
|
||||
unsigned search_lvl = m_solver.search_lvl();
|
||||
for (auto& kv : m_map) {
|
||||
sat::bool_var v = kv.m_value;
|
||||
if (was_eliminated(v))
|
||||
continue;
|
||||
if (get_assignment(v) != l_undef && m_solver.lvl(v) <= search_lvl)
|
||||
continue;
|
||||
expr* e = kv.m_key;
|
||||
if (!e)
|
||||
continue;
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
if (invalid_split_atoms_set.contains(atom))
|
||||
continue;
|
||||
double new_score = get_activity(v);
|
||||
if (new_score > score || !result || (new_score == score && m_solver.rand()(++n) == 0)) {
|
||||
score = new_score;
|
||||
result = e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void get_backbone_candidates(vector<solver::scored_literal>& candidates, unsigned max_num) override {
|
||||
if (!is_internalized()) {
|
||||
lbool r = internalize_formulas();
|
||||
if (r != l_true)
|
||||
return;
|
||||
}
|
||||
convert_internalized();
|
||||
sat::literal_vector lits;
|
||||
m_solver.get_backbone_candidates(lits, max_num);
|
||||
expr_ref_vector lit2expr(m);
|
||||
lit2expr.resize(m_solver.num_vars() * 2);
|
||||
m_map.mk_inv(lit2expr);
|
||||
uint64_t now = m_solver.get_stats().m_conflicts;
|
||||
for (sat::literal lit : lits) {
|
||||
expr* e = lit2expr.get(lit.index());
|
||||
if (!e)
|
||||
continue;
|
||||
candidates.push_back(scored_literal(m, e, static_cast<double>(now - m_solver.get_phase_birthdate(lit.var()))));
|
||||
}
|
||||
}
|
||||
|
||||
expr* congruence_next(expr* e) override { return e; }
|
||||
expr* congruence_root(expr* e) override { return e; }
|
||||
expr_ref congruence_explain(expr* a, expr* b) override { return expr_ref(m.mk_eq(a, b), m); }
|
||||
|
|
@ -1186,7 +1298,5 @@ tactic * mk_psat_tactic(ast_manager& m, params_ref const& p) {
|
|||
parallel_params pp(p);
|
||||
if (pp.enable())
|
||||
return mk_parallel_tactic(mk_inc_sat_solver(m, p, false), p);
|
||||
if (pp.enable2())
|
||||
return mk_parallel_tactic2(mk_inc_sat_solver(m, p, false), p);
|
||||
return mk_sat_tactic(m);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ sat::bool_var atom2bool_var::to_bool_var(expr * n) const {
|
|||
return m_mapping[idx].m_value;
|
||||
}
|
||||
|
||||
expr* atom2bool_var::bool_var2expr(sat::bool_var v) const {
|
||||
for (auto const& kv : m_mapping)
|
||||
if (kv.m_value == v)
|
||||
return kv.m_key;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct collect_boolean_interface_proc {
|
||||
struct visitor {
|
||||
obj_hashtable<expr> & m_r;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public:
|
|||
atom2bool_var(ast_manager & m):expr2var(m) {}
|
||||
void insert(expr * n, sat::bool_var v) { expr2var::insert(n, v); }
|
||||
sat::bool_var to_bool_var(expr * n) const;
|
||||
expr* bool_var2expr(sat::bool_var v) const;
|
||||
void mk_inv(expr_ref_vector & lit2expr) const;
|
||||
void mk_var_inv(expr_ref_vector & var2expr) const;
|
||||
// return true if the mapping contains uninterpreted atoms.
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ namespace bv {
|
|||
void ackerman::propagate() {
|
||||
auto* n = m_queue;
|
||||
vv* k = nullptr;
|
||||
unsigned num_prop = static_cast<unsigned>(s.s().get_stats().m_conflict * s.get_config().m_dack_factor);
|
||||
unsigned num_prop = static_cast<unsigned>(s.s().get_stats().m_conflicts * s.get_config().m_dack_factor);
|
||||
num_prop = std::min(num_prop, m_table.size());
|
||||
for (unsigned i = 0; i < num_prop; ++i, n = k) {
|
||||
k = n->next();
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ namespace euf {
|
|||
SASSERT(ctx.s().at_base_lvl());
|
||||
auto* n = m_queue;
|
||||
inference* k = nullptr;
|
||||
unsigned num_prop = static_cast<unsigned>(ctx.s().get_stats().m_conflict * ctx.m_config.m_dack_factor);
|
||||
unsigned num_prop = static_cast<unsigned>(ctx.s().get_stats().m_conflicts * ctx.m_config.m_dack_factor);
|
||||
num_prop = std::min(num_prop, m_table.size());
|
||||
for (unsigned i = 0; i < num_prop; ++i, n = k) {
|
||||
k = n->next();
|
||||
|
|
|
|||
|
|
@ -5097,7 +5097,7 @@ namespace seq {
|
|||
svector<sat::literal>& mem_literals) const {
|
||||
SASSERT(m_root);
|
||||
const auto deps = collect_conflict_deps();
|
||||
vector<dep_source> vs;
|
||||
vector<dep_source, false> vs;
|
||||
m_dep_mgr.linearize(deps, vs);
|
||||
for (dep_source const& d : vs) {
|
||||
if (std::holds_alternative<enode_pair>(d))
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ bool theory_seq::len_based_split(depeq const& e) {
|
|||
expr_ref_vector const& rs = e.rs;
|
||||
|
||||
int offset = 0;
|
||||
if (!has_len_offset(ls, rs, offset))
|
||||
if (!has_len_offset(ls, rs, offset) || offset == 0)
|
||||
return false;
|
||||
|
||||
TRACE(seq, tout << "split based on length\n";);
|
||||
|
|
|
|||
|
|
@ -470,9 +470,10 @@ namespace smt {
|
|||
re_expr = m_seq.re.mk_inter(re_expr, loop);
|
||||
}
|
||||
|
||||
zstring str;
|
||||
expr_ref witness(m);
|
||||
// We checked non-emptiness during Nielsen already
|
||||
lbool wr = m_rewriter.some_seq_in_re(re_expr, witness);
|
||||
lbool wr = m_rewriter.some_string_in_re(re_expr, str);
|
||||
if (wr != l_true) {
|
||||
// some_seq_in_re can fail (l_undef / l_false) on regexes it does
|
||||
// not fully support — notably projection operators (re.proj),
|
||||
|
|
@ -482,7 +483,7 @@ namespace smt {
|
|||
wr = derivative_witness(m_sg.mk(re_expr), witness);
|
||||
}
|
||||
if (wr == l_true) {
|
||||
SASSERT(witness);
|
||||
witness = m_seq.str.mk_string(str);
|
||||
m_trail.push_back(witness);
|
||||
m_factory->register_value(witness);
|
||||
return witness;
|
||||
|
|
|
|||
|
|
@ -248,6 +248,17 @@ namespace smt {
|
|||
th.add_axiom(~lit);
|
||||
return true;
|
||||
}
|
||||
// Fall back to antimirov NFA reachability. The lazy state graph
|
||||
// keys states by AST identity and cannot close on intersections /
|
||||
// complements whose derivative product states do not canonicalize,
|
||||
// so it never detects their emptiness. re_is_empty decides
|
||||
// emptiness directly (the same procedure propagate_eq already uses
|
||||
// for re.none equalities).
|
||||
if (re_is_empty(r) == l_true) {
|
||||
STRACE(seq_regex_brief, tout << "(empty:re) ";);
|
||||
th.add_axiom(~lit);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -413,8 +424,15 @@ namespace smt {
|
|||
}
|
||||
|
||||
expr_ref seq_regex::symmetric_diff(expr* r1, expr* r2) {
|
||||
seq_rewriter rw(m);
|
||||
auto r = rw.mk_symmetric_diff(r1, r2);
|
||||
expr_ref r(m);
|
||||
if (r1 == r2)
|
||||
r = re().mk_empty(r1->get_sort());
|
||||
else if (re().is_empty(r1))
|
||||
r = r2;
|
||||
else if (re().is_empty(r2))
|
||||
r = r1;
|
||||
else
|
||||
r = re().mk_union(re().mk_diff(r1, r2), re().mk_diff(r2, r1));
|
||||
rewrite(r);
|
||||
return r;
|
||||
}
|
||||
|
|
@ -478,6 +496,24 @@ namespace smt {
|
|||
if (re().is_empty(r))
|
||||
//trivially true
|
||||
return;
|
||||
// When one side is re.none the equation is a pure emptiness check on
|
||||
// the other regex (symmetric_diff already returned it as r). Decide
|
||||
// it directly by antimirov NFA reachability instead of running the
|
||||
// bisimulation/XOR closure, which would build large un-canonicalized
|
||||
// product states for intersections of contains-patterns.
|
||||
if ((re().is_empty(r1) || re().is_empty(r2)) && is_ground(r)) {
|
||||
switch (re_is_empty(r)) {
|
||||
case l_true:
|
||||
STRACE(seq_regex_brief, tout << "empty:eq ";);
|
||||
return; // languages equal (both empty): trivially true
|
||||
case l_false:
|
||||
STRACE(seq_regex_brief, tout << "empty:neq ";);
|
||||
th.add_axiom(~th.mk_eq(r1, r2, false), false_literal);
|
||||
return;
|
||||
case l_undef:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Try the bisimulation procedure on ground regexes first. If it
|
||||
// returns a definite answer, dispatch the corresponding axiom and
|
||||
// bypass the symbolic emptiness/derivative closure.
|
||||
|
|
@ -579,16 +615,16 @@ namespace smt {
|
|||
lits.push_back(null_lit);
|
||||
|
||||
expr_ref_pair_vector cofactors(m);
|
||||
get_cofactors(d, cofactors);
|
||||
for (auto const& p : cofactors) {
|
||||
if (is_member(p.second, u))
|
||||
seq_rw().get_cofactors(hd, d, cofactors);
|
||||
for (auto const& [c, r] : cofactors) {
|
||||
if (is_member(r, u))
|
||||
continue;
|
||||
expr_ref cond(p.first, m);
|
||||
expr_ref cond(c, m);
|
||||
seq_rw().elim_condition(hd, cond);
|
||||
rewrite(cond);
|
||||
if (m.is_false(cond))
|
||||
continue;
|
||||
expr_ref next_non_empty = sk().mk_is_non_empty(p.second, re().mk_union(u, p.second), n);
|
||||
expr_ref next_non_empty = sk().mk_is_non_empty(r, re().mk_union(u, r), n);
|
||||
if (!m.is_true(cond))
|
||||
next_non_empty = m.mk_and(cond, next_non_empty);
|
||||
lits.push_back(th.mk_literal(next_non_empty));
|
||||
|
|
@ -689,40 +725,113 @@ namespace smt {
|
|||
}
|
||||
|
||||
/*
|
||||
Return a list of all target regexes in the derivative of a regex r,
|
||||
ignoring the conditions along each path.
|
||||
Decide emptiness of a ground regex r via antimirov-mode NFA
|
||||
reachability.
|
||||
|
||||
The derivative construction uses (:var 0) and tries
|
||||
to eliminate unsat condition paths but it does not perform
|
||||
full satisfiability checks and it is not guaranteed
|
||||
that all targets are actually reachable
|
||||
The symbolic derivative engine runs in antimirov mode, so the
|
||||
derivative of an intersection distributes into a *set* of individual
|
||||
product states inter(A_i, B_j) (each a small, ground regex) rather
|
||||
than one giant union-of-intersections term. get_derivative_targets
|
||||
enumerates these NFA successor states.
|
||||
|
||||
We short-circuit to l_false (non-empty) as soon as a reachable state
|
||||
is nullable (accepts the empty word) or classical (a regex built only
|
||||
from to_re/all/union/concat/star/plus/opt/loop, hence non-empty). An
|
||||
intersection itself is never classical, but once one operand reduces
|
||||
to Σ* the intersection collapses (via the derivative's subset
|
||||
simplification) to the other, classical, operand.
|
||||
|
||||
If the worklist is exhausted with no such state, r is empty (l_true).
|
||||
Returns l_undef if a step bound is hit, so callers can fall back to
|
||||
the general procedure.
|
||||
*/
|
||||
void seq_regex::get_derivative_targets(expr* r, expr_ref_vector& targets) {
|
||||
// constructs the derivative wrt (:var 0)
|
||||
expr_ref d(seq_rw().mk_derivative(r), m);
|
||||
|
||||
// use DFS to collect all the targets (leaf regexes) in d.
|
||||
expr* _1 = nullptr, * e1 = nullptr, * e2 = nullptr;
|
||||
obj_hashtable<expr>::entry* _2 = nullptr;
|
||||
vector<expr*> workset;
|
||||
workset.push_back(d);
|
||||
obj_hashtable<expr> done;
|
||||
done.insert(d);
|
||||
while (workset.size() > 0) {
|
||||
expr* e = workset.back();
|
||||
workset.pop_back();
|
||||
if (m.is_ite(e, _1, e1, e2) || re().is_union(e, e1, e2)) {
|
||||
if (done.insert_if_not_there_core(e1, _2))
|
||||
workset.push_back(e1);
|
||||
if (done.insert_if_not_there_core(e2, _2))
|
||||
workset.push_back(e2);
|
||||
lbool seq_regex::re_is_empty(expr* r) {
|
||||
if (re().is_empty(r))
|
||||
return l_true;
|
||||
expr_ref_vector pinned(m);
|
||||
obj_hashtable<expr> visited;
|
||||
ptr_vector<expr> work;
|
||||
work.push_back(r);
|
||||
visited.insert(r);
|
||||
pinned.push_back(r);
|
||||
unsigned const bound = 100000;
|
||||
unsigned steps = 0;
|
||||
while (!work.empty()) {
|
||||
if (++steps > bound)
|
||||
return l_undef;
|
||||
expr* s = work.back();
|
||||
work.pop_back();
|
||||
auto info = re().get_info(s);
|
||||
if (!info.is_known())
|
||||
return l_undef;
|
||||
// ε ∈ L(s) or s is a non-empty classical regex ⇒ L(r) non-empty.
|
||||
if (info.nullable == l_true || info.classical)
|
||||
return l_false;
|
||||
// Dead state: prune (min_length == UINT_MAX means no word is
|
||||
// accepted from here).
|
||||
if (info.min_length == UINT_MAX)
|
||||
continue;
|
||||
expr_ref_vector targets(m);
|
||||
get_derivative_targets(s, targets);
|
||||
for (expr* t : targets) {
|
||||
if (visited.contains(t))
|
||||
continue;
|
||||
visited.insert(t);
|
||||
pinned.push_back(t);
|
||||
work.push_back(t);
|
||||
}
|
||||
else if (!re().is_empty(e))
|
||||
targets.push_back(e);
|
||||
}
|
||||
return l_true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Return a list of all reachable target regexes in the derivative of a
|
||||
regex r.
|
||||
|
||||
The derivative is taken wrt (:var 0) and its reachable leaves are
|
||||
enumerated with the path-aware cofactor engine, which conjoins the
|
||||
ITE-path conditions and prunes infeasible character-range combinations
|
||||
(e.g. a nested branch requiring elem = 'a' and elem = 'B'). Each leaf
|
||||
is re-normalized with the path-aware smart constructors so that
|
||||
semantically equal states stay syntactically identical (essential for
|
||||
state dedup in the emptiness closure).
|
||||
|
||||
Without this pruning the naive ITE-tree DFS would reach infeasible
|
||||
leaves; an infeasible classical (intersection/complement-free) leaf
|
||||
would then be misjudged as a non-empty residual.
|
||||
*/
|
||||
void seq_regex::get_derivative_targets(expr* r, expr_ref_vector& targets) {
|
||||
expr_ref_pair_vector cofactors(m);
|
||||
seq_rw().brz_derivative_cofactors(r, cofactors);
|
||||
for (auto const& [c, t] : cofactors) {
|
||||
if (!re().is_empty(t))
|
||||
targets.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Return a list of all (cond, leaf) pairs in a given derivative
|
||||
expression r, where elem is the character symbol the derivative was
|
||||
taken with respect to.
|
||||
|
||||
The transition regexes produced by the symbolic derivative engine are
|
||||
ITE-trees over character predicates ci on elem (equalities such as
|
||||
elem = 'A', and ranges such as 'a' <= elem <= 'z'). These predicates
|
||||
are typically mutually exclusive, so the number of feasible truth
|
||||
assignments to {c1,..,ck} ("minterms") is small.
|
||||
|
||||
The enumeration is delegated to seq::derive (via seq_rw().get_cofactors)
|
||||
so it reuses the very same path/interval context that the derivative
|
||||
engine uses while hoisting ITEs: each feasible path through the ITE-tree
|
||||
yields one (path_condition, leaf) cofactor, infeasible character-range
|
||||
combinations are pruned, and the leaf is simplified with the path-aware
|
||||
smart constructors.
|
||||
|
||||
This is used by:
|
||||
propagate_is_empty
|
||||
propagate_is_non_empty
|
||||
*/
|
||||
|
||||
/*
|
||||
is_empty(r, u) => ~is_nullable(r)
|
||||
|
|
@ -750,11 +859,11 @@ namespace smt {
|
|||
d = mk_derivative_wrapper(hd, r);
|
||||
literal_vector lits;
|
||||
expr_ref_pair_vector cofactors(m);
|
||||
get_cofactors(d, cofactors);
|
||||
for (auto const& p : cofactors) {
|
||||
if (is_member(p.second, u))
|
||||
seq_rw().get_cofactors(hd, d, cofactors);
|
||||
for (auto const& [c, r] : cofactors) {
|
||||
if (is_member(r, u))
|
||||
continue;
|
||||
expr_ref cond(p.first, m);
|
||||
expr_ref cond(c, m);
|
||||
seq_rw().elim_condition(hd, cond);
|
||||
rewrite(cond);
|
||||
if (m.is_false(cond))
|
||||
|
|
@ -765,7 +874,7 @@ namespace smt {
|
|||
expr_ref ncond(mk_not(m, cond), m);
|
||||
lits.push_back(th.mk_literal(mk_forall(m, hd, ncond)));
|
||||
}
|
||||
expr_ref is_empty1 = sk().mk_is_empty(p.second, re().mk_union(u, p.second), n);
|
||||
expr_ref is_empty1 = sk().mk_is_empty(r, re().mk_union(u, r), n);
|
||||
lits.push_back(th.mk_literal(is_empty1));
|
||||
th.add_axiom(lits);
|
||||
}
|
||||
|
|
@ -878,50 +987,4 @@ namespace smt {
|
|||
return std::string("id") + std::to_string(e->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
Return a list of all (cond, leaf) pairs in a given
|
||||
expression r.
|
||||
|
||||
Note: this implementation is inefficient: it simply collects all expressions under an if and
|
||||
iterates over all combinations.
|
||||
*/
|
||||
void seq_regex::get_cofactors(expr *r, expr_ref_pair_vector &result) {
|
||||
obj_hashtable<expr> ifs;
|
||||
expr *cond = nullptr, *r1 = nullptr, *r2 = nullptr;
|
||||
for (expr *e : subterms::ground(expr_ref(r, m)))
|
||||
if (m.is_ite(e, cond, r1, r2))
|
||||
ifs.insert(cond);
|
||||
|
||||
expr_ref_vector rs(m);
|
||||
vector<expr_ref_vector> conds;
|
||||
conds.push_back(expr_ref_vector(m));
|
||||
rs.push_back(r);
|
||||
for (expr *c : ifs) {
|
||||
unsigned sz = conds.size();
|
||||
expr_safe_replace rep1(m);
|
||||
expr_safe_replace rep2(m);
|
||||
rep1.insert(c, m.mk_true());
|
||||
rep2.insert(c, m.mk_false());
|
||||
expr_ref r2(m);
|
||||
for (unsigned i = 0; i < sz; ++i) {
|
||||
expr_ref_vector cs = conds[i];
|
||||
cs.push_back(m.mk_not(c));
|
||||
conds.push_back(cs);
|
||||
conds[i].push_back(c);
|
||||
expr_ref r1(rs.get(i), m);
|
||||
rep1(r1, r2);
|
||||
rs[i] = r2;
|
||||
rep2(r1, r2);
|
||||
rs.push_back(r2);
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i < conds.size(); ++i) {
|
||||
expr_ref conj = mk_and(conds[i]);
|
||||
expr_ref r(rs.get(i), m);
|
||||
ctx.get_rewriter()(r);
|
||||
if (!m.is_false(conj) && !re().is_empty(r))
|
||||
result.push_back(conj, r);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,12 @@ namespace smt {
|
|||
expr_ref mk_deriv_accept(expr* s, unsigned i, expr* r);
|
||||
void get_derivative_targets(expr* r, expr_ref_vector& targets);
|
||||
|
||||
// Decide emptiness of a ground regex by antimirov-mode NFA
|
||||
// reachability: explore derivative target states, short-circuiting to
|
||||
// "non-empty" on the first reachable nullable or classical state.
|
||||
// Returns l_true (empty), l_false (non-empty), l_undef (gave up).
|
||||
lbool re_is_empty(expr* r);
|
||||
|
||||
/*
|
||||
Pretty print the regex of the state id to the out stream,
|
||||
seq_regex_ptr must be a pointer to seq_regex and the
|
||||
|
|
@ -183,9 +189,6 @@ namespace smt {
|
|||
|
||||
bool block_if_empty(expr* r, literal lit);
|
||||
|
||||
void get_cofactors(expr *r, expr_ref_pair_vector &result);
|
||||
|
||||
|
||||
public:
|
||||
|
||||
seq_regex(theory_seq& th);
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ namespace smt {
|
|||
if (!m_setup.already_configured()) {
|
||||
m_fparams.updt_params(p);
|
||||
}
|
||||
else {
|
||||
// selected parameters are safe to update after initialization
|
||||
m_fparams.m_max_conflicts = p.get_uint("max_conflicts", m_fparams.m_max_conflicts);
|
||||
}
|
||||
for (auto th : m_theory_set)
|
||||
if (th)
|
||||
th->updt_params();
|
||||
|
|
@ -3673,6 +3677,13 @@ namespace smt {
|
|||
}
|
||||
}
|
||||
|
||||
void context::setup_for_parallel() {
|
||||
// Native SMT parallel configures the parent context before cloning workers.
|
||||
// context::copy then configures/internalizes each worker copy while
|
||||
// preprocessing is still enabled.
|
||||
setup_context(m_fparams.m_auto_config);
|
||||
}
|
||||
|
||||
config_mode context::get_config_mode(bool use_static_features) const {
|
||||
if (!m_fparams.m_auto_config)
|
||||
return CFG_BASIC;
|
||||
|
|
@ -4693,7 +4704,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))
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ namespace smt {
|
|||
|
||||
class model_generator;
|
||||
class context;
|
||||
class kernel;
|
||||
|
||||
struct oom_exception : public z3_error {
|
||||
oom_exception() : z3_error(ERR_MEMOUT) {}
|
||||
|
|
@ -85,6 +86,7 @@ namespace smt {
|
|||
friend class model_generator;
|
||||
friend class lookahead;
|
||||
friend class parallel;
|
||||
friend class kernel;
|
||||
public:
|
||||
statistics m_stats;
|
||||
|
||||
|
|
@ -294,6 +296,10 @@ namespace smt {
|
|||
return m_fparams;
|
||||
}
|
||||
|
||||
smt_params const& get_fparams() const {
|
||||
return m_fparams;
|
||||
}
|
||||
|
||||
params_ref const & get_params() {
|
||||
return m_params;
|
||||
}
|
||||
|
|
@ -454,6 +460,8 @@ namespace smt {
|
|||
svector<double> const & get_activity_vector() const { return m_activity; }
|
||||
|
||||
double get_activity(bool_var v) const { return m_activity[v]; }
|
||||
unsigned get_num_assignments() const { return m_stats.m_num_assignments; }
|
||||
unsigned get_birthdate(bool_var v) const { return m_birthdate[v]; }
|
||||
|
||||
void set_activity(bool_var v, double act) { m_activity[v] = act; }
|
||||
|
||||
|
|
@ -540,6 +548,8 @@ namespace smt {
|
|||
return m_scope_lvl == m_search_lvl;
|
||||
}
|
||||
|
||||
void pop_to_search_level() { pop_to_search_lvl(); }
|
||||
|
||||
bool tracking_assumptions() const {
|
||||
return !m_assumptions.empty() && m_search_lvl > m_base_lvl;
|
||||
}
|
||||
|
|
@ -872,7 +882,7 @@ namespace smt {
|
|||
void undo_mk_enode();
|
||||
|
||||
|
||||
void apply_sort_cnstr(app * term, enode * e);
|
||||
void apply_sort_cnstr(expr * term, enode * e);
|
||||
|
||||
bool simplify_aux_clause_literals(unsigned & num_lits, literal * lits, literal_buffer & simp_lits);
|
||||
|
||||
|
|
@ -1699,6 +1709,8 @@ namespace smt {
|
|||
|
||||
lbool setup_and_check(bool reset_cancel = true);
|
||||
|
||||
void setup_for_parallel();
|
||||
|
||||
void reduce_assertions();
|
||||
|
||||
bool resource_limits_exceeded();
|
||||
|
|
@ -1917,5 +1929,3 @@ namespace smt {
|
|||
std::ostream& operator<<(std::ostream& out, enode_pp const& p);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -597,9 +597,10 @@ namespace smt {
|
|||
SASSERT(is_lambda(q));
|
||||
if (e_internalized(q))
|
||||
return;
|
||||
mk_enode(q, true, /* do suppress args */
|
||||
auto e = mk_enode(q, true, /* do suppress args */
|
||||
false, /* it is a term, so it should not be merged with true/false */
|
||||
true);
|
||||
apply_sort_cnstr(q, e);
|
||||
}
|
||||
|
||||
bool context::has_lambda() {
|
||||
|
|
@ -1084,8 +1085,8 @@ namespace smt {
|
|||
/**
|
||||
\brief Apply sort constraints on e.
|
||||
*/
|
||||
void context::apply_sort_cnstr(app * term, enode * e) {
|
||||
sort * s = term->get_decl()->get_range();
|
||||
void context::apply_sort_cnstr(expr * term, enode * e) {
|
||||
sort * s = term->get_sort();
|
||||
theory * th = m_theories.get_plugin(s->get_family_id());
|
||||
if (th) {
|
||||
th->apply_sort_cnstr(e, s);
|
||||
|
|
|
|||
|
|
@ -284,10 +284,22 @@ namespace smt {
|
|||
smt_params_helper::collect_param_descrs(d);
|
||||
}
|
||||
|
||||
void kernel::pop_to_base_level() {
|
||||
m_imp->m_kernel.pop_to_base_lvl();
|
||||
}
|
||||
|
||||
void kernel::set_preprocess(bool f) {
|
||||
m_imp->m_kernel.get_fparams().m_preprocess = f;
|
||||
}
|
||||
|
||||
context & kernel::get_context() {
|
||||
return m_imp->m_kernel;
|
||||
}
|
||||
|
||||
context const& kernel::get_context() const {
|
||||
return m_imp->m_kernel;
|
||||
}
|
||||
|
||||
void kernel::get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) {
|
||||
m_imp->m_kernel.get_levels(vars, depth);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,6 +300,10 @@ namespace smt {
|
|||
*/
|
||||
static void collect_param_descrs(param_descrs & d);
|
||||
|
||||
void pop_to_base_level();
|
||||
|
||||
void set_preprocess(bool f);
|
||||
|
||||
void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause);
|
||||
|
||||
/**
|
||||
|
|
@ -340,6 +344,6 @@ namespace smt {
|
|||
\warning This method should not be used in new code.
|
||||
*/
|
||||
context & get_context();
|
||||
context const& get_context() const;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,81 @@ 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_app(e) && to_app(e)->get_decl()->is_skolem())
|
||||
;
|
||||
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 +2188,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 +2249,6 @@ namespace smt {
|
|||
}
|
||||
|
||||
SASSERT(is_quantifier(atom));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void process_literal(expr* atom, polarity pol) {
|
||||
|
|
@ -2203,9 +2288,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 +2606,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 +2647,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();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Author:
|
|||
#include "smt/smt_parallel.h"
|
||||
#include "smt/smt_lookahead.h"
|
||||
#include "solver/solver_preprocess.h"
|
||||
#include "params/smt_parallel_params.hpp"
|
||||
#include "solver/parallel_params.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
|
|
@ -550,7 +550,7 @@ namespace smt {
|
|||
if (m_ablate_backtracking) {
|
||||
// Ablation: for each target, pass the entire path from root to that node
|
||||
for (auto const& target : targets) {
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node, target.cancel_epoch))
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node))
|
||||
continue;
|
||||
|
||||
// Reconstruct the full path from root to this target node
|
||||
|
|
@ -626,7 +626,7 @@ namespace smt {
|
|||
ctx->set_logic(p.ctx.m_setup.get_logic());
|
||||
context::copy(p.ctx, *ctx, true);
|
||||
ctx->pop_to_base_lvl();
|
||||
ctx->get_fparams().m_preprocess = false;
|
||||
ctx->get_fparams().m_preprocess = false; // avoid preprocessing lemmas that are exchanged
|
||||
}
|
||||
|
||||
void parallel::core_minimizer_worker::cancel() {
|
||||
|
|
@ -763,26 +763,42 @@ namespace smt {
|
|||
if (m_config.m_global_backbones) {
|
||||
bb_candidates local_candidates = find_backbone_candidates();
|
||||
b.collect_backbone_candidates(m_l2g, local_candidates);
|
||||
if (!m.inc())
|
||||
bool lease_canceled = false;
|
||||
if (!b.checkpoint_worker(id, lease, lease_canceled))
|
||||
return;
|
||||
if (lease_canceled) {
|
||||
LOG_WORKER(1, " abandoning canceled lease\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
lbool r = check_cube(cube);
|
||||
|
||||
if (b.lease_canceled(lease)) {
|
||||
bool lease_canceled = false;
|
||||
if (!b.checkpoint_worker(id, lease, lease_canceled))
|
||||
return;
|
||||
if (lease_canceled) {
|
||||
LOG_WORKER(1, " abandoning canceled lease\n");
|
||||
lease = {};
|
||||
m.limit().dec_cancel();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m.inc())
|
||||
return;
|
||||
|
||||
switch (r) {
|
||||
case l_undef: {
|
||||
update_max_thread_conflicts();
|
||||
LOG_WORKER(1, " found undef cube\n");
|
||||
// Escalating the per-thread conflict budget and re-splitting the
|
||||
// cube only helps when the cube was abandoned because the per-cube
|
||||
// conflict limit was reached. For any other source of incompleteness
|
||||
// (an incomplete theory, quantifiers, lambdas, resource limits, ...)
|
||||
// the verdict cannot change, so re-checking the same cube would spin
|
||||
// forever and the run hangs to a wall-clock timeout. Record a sound
|
||||
// 'unknown' verdict and stop working this branch instead.
|
||||
std::string reason = ctx->last_failure_as_string();
|
||||
if (reason != "max-conflicts-reached") {
|
||||
LOG_WORKER(1, " undef cube not conflict-limited (" << reason << "); reporting unknown\n");
|
||||
b.set_unknown(reason);
|
||||
return;
|
||||
}
|
||||
update_max_thread_conflicts();
|
||||
if (m_config.m_max_cube_depth <= cube.size())
|
||||
goto check_cube_start;
|
||||
|
||||
|
|
@ -790,7 +806,6 @@ namespace smt {
|
|||
if (!atom)
|
||||
goto check_cube_start;
|
||||
b.try_split(m_l2g, id, lease, atom, m_config.m_threads_max_conflicts);
|
||||
lease = {};
|
||||
simplify();
|
||||
break;
|
||||
}
|
||||
|
|
@ -825,7 +840,6 @@ namespace smt {
|
|||
b.backtrack(m_l2g, id, core_to_use, lease);
|
||||
if (m_config.m_core_minimize)
|
||||
b.enqueue_core_minimization(m_l2g, source, unsat_core);
|
||||
lease = {};
|
||||
|
||||
if (m_config.m_share_conflicts)
|
||||
b.collect_clause(m_l2g, id, mk_not(mk_and(unsat_core)));
|
||||
|
|
@ -854,10 +868,10 @@ namespace smt {
|
|||
m_num_initial_atoms = ctx->get_num_bool_vars();
|
||||
ctx->get_fparams().m_preprocess = false; // avoid preprocessing lemmas that are exchanged
|
||||
|
||||
smt_parallel_params pp(p.ctx.m_params);
|
||||
m_config.m_inprocessing = pp.inprocessing();
|
||||
m_config.m_global_backbones = pp.num_global_bb_batch_threads() > 0 || pp.num_global_bb_fl_threads() > 0;
|
||||
m_config.m_local_backbones = pp.local_backbones();
|
||||
parallel_params pp(p.ctx.m_params);
|
||||
m_config.m_inprocessing = false;
|
||||
m_config.m_global_backbones = pp.num_bb_threads() > 0;
|
||||
m_config.m_local_backbones = false;
|
||||
m_config.m_core_minimize = pp.core_minimize();
|
||||
m_config.m_ablate_backtracking = pp.ablate_backtracking();
|
||||
|
||||
|
|
@ -887,9 +901,9 @@ namespace smt {
|
|||
ctx->pop_to_base_lvl();
|
||||
m_shared_units_prefix = ctx->assigned_literals().size();
|
||||
m_num_initial_atoms = ctx->get_num_bool_vars();
|
||||
ctx->get_fparams().m_preprocess = false; // avoid preprocessing lemmas that are exchanged
|
||||
|
||||
smt_parallel_params pp(p.ctx.m_params);
|
||||
m_use_failed_literal_test = pp.num_global_bb_fl_threads() > 0;
|
||||
m_use_failed_literal_test = false;
|
||||
}
|
||||
|
||||
parallel::bb_candidates parallel::worker::find_backbone_candidates(unsigned k) {
|
||||
|
|
@ -1105,14 +1119,48 @@ namespace smt {
|
|||
return r;
|
||||
}
|
||||
|
||||
void parallel::batch_manager::release_lease_unlocked(unsigned worker_id, node* n) {
|
||||
if (worker_id >= m_worker_leases.size())
|
||||
void parallel::batch_manager::set_canceled_unlocked() {
|
||||
if (m_state != state::is_running)
|
||||
return;
|
||||
auto &lease = m_worker_leases[worker_id];
|
||||
if (!lease.leased_node || lease.leased_node != n)
|
||||
cancel_background_threads();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::set_canceled() {
|
||||
std::scoped_lock lock(mux);
|
||||
set_canceled_unlocked();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::release_worker_lease_unlocked(unsigned worker_id, node_lease& lease) {
|
||||
if (worker_id >= m_worker_leases.size()) {
|
||||
lease = {};
|
||||
return;
|
||||
m_search_tree.dec_active_workers(lease.leased_node);
|
||||
}
|
||||
auto& stored_lease = m_worker_leases[worker_id];
|
||||
if (!stored_lease.leased_node || stored_lease.leased_node != lease.leased_node) {
|
||||
lease = {};
|
||||
return;
|
||||
}
|
||||
bool cancel_signaled = stored_lease.cancel_signaled;
|
||||
m_search_tree.dec_active_workers(stored_lease.leased_node);
|
||||
stored_lease = {};
|
||||
lease = {};
|
||||
if (cancel_signaled)
|
||||
p.m_workers[worker_id]->limit().dec_cancel();
|
||||
}
|
||||
|
||||
bool parallel::batch_manager::attempt_release_canceled_lease_unlocked(unsigned worker_id, node_lease& lease) {
|
||||
if (m_state != state::is_running || !lease.leased_node || worker_id >= m_worker_leases.size())
|
||||
return false;
|
||||
|
||||
auto& stored_lease = m_worker_leases[worker_id];
|
||||
if (stored_lease.leased_node != lease.leased_node)
|
||||
return false;
|
||||
|
||||
if (!m_search_tree.is_lease_canceled(stored_lease.leased_node))
|
||||
return false;
|
||||
|
||||
release_worker_lease_unlocked(worker_id, lease);
|
||||
return true;
|
||||
}
|
||||
|
||||
void parallel::batch_manager::cancel_closed_leases_unlocked(unsigned source_worker_id) {
|
||||
|
|
@ -1124,7 +1172,7 @@ namespace smt {
|
|||
|
||||
// only cancel workers that currently hold a lease, whose lease is canceled,
|
||||
// and haven't already been signaled (prevents multiple inc_cancel() for same lease)
|
||||
if (lease.leased_node && !lease.cancel_signaled && m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch)) {
|
||||
if (lease.leased_node && !lease.cancel_signaled && m_search_tree.is_lease_canceled(lease.leased_node)) {
|
||||
p.m_workers[worker_id]->cancel_lease();
|
||||
m_worker_leases[worker_id].cancel_signaled = true;
|
||||
}
|
||||
|
|
@ -1132,7 +1180,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
void parallel::batch_manager::backtrack(ast_translation &l2g, unsigned worker_id, expr_ref_vector const &core,
|
||||
node_lease const &lease) {
|
||||
node_lease& lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
vector<cube_config::literal> g_core;
|
||||
for (auto c : core)
|
||||
|
|
@ -1277,7 +1325,7 @@ namespace smt {
|
|||
if (!g_core.empty()) {
|
||||
collect_matching_targets_unlocked(source, g_core[0].get(), g_core, targets);
|
||||
for (auto const& target : targets) {
|
||||
if (!m_search_tree.is_lease_canceled(target.leased_node, target.cancel_epoch))
|
||||
if (!m_search_tree.is_lease_canceled(target.leased_node))
|
||||
m_search_tree.backtrack(target.leased_node, g_core);
|
||||
}
|
||||
}
|
||||
|
|
@ -1331,7 +1379,7 @@ namespace smt {
|
|||
for (node* t : matches) {
|
||||
if (!t || t == source)
|
||||
continue;
|
||||
if (m_search_tree.is_lease_canceled(t, t->get_cancel_epoch()))
|
||||
if (m_search_tree.is_lease_canceled(t))
|
||||
continue;
|
||||
|
||||
// When source is provided, keep only external matches. Nodes in the
|
||||
|
|
@ -1358,12 +1406,12 @@ namespace smt {
|
|||
if (!is_highest_ancestor)
|
||||
continue;
|
||||
|
||||
targets.push_back({ t, t->get_cancel_epoch() });
|
||||
targets.push_back({t});
|
||||
}
|
||||
}
|
||||
|
||||
void parallel::batch_manager::backtrack_unlocked(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core,
|
||||
node_lease const* lease, vector<node_lease> const* targets) {
|
||||
node_lease* lease, vector<node_lease> const* targets) {
|
||||
if (m_state != state::is_running)
|
||||
return;
|
||||
|
||||
|
|
@ -1374,17 +1422,25 @@ namespace smt {
|
|||
SASSERT(lease != nullptr || targets != nullptr);
|
||||
bool did_backtrack = false;
|
||||
|
||||
if (lease && !m_search_tree.is_lease_canceled(lease->leased_node, lease->cancel_epoch)) {
|
||||
// we close/backtrack regardless of whether this lease is stale or not, as long as the lease isn't canceled
|
||||
// i.e. worker 1 splits this node, but then worker 2 determines UNSAT --> worker 2 is stale but we still close this node and backtrack
|
||||
did_backtrack = true;
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager backtracking.\n");
|
||||
release_lease_unlocked(worker_id, lease->leased_node);
|
||||
m_search_tree.backtrack(lease->leased_node, g_core);
|
||||
if (lease) {
|
||||
if (!m_search_tree.is_lease_canceled(lease->leased_node)) {
|
||||
// we close/backtrack regardless of whether this lease is stale or not, as long as the lease isn't canceled
|
||||
// i.e. worker 1 splits this node, but then worker 2 determines UNSAT --> worker 2 is stale but we still close this node and backtrack
|
||||
did_backtrack = true;
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager backtracking.\n");
|
||||
node* leased_node = lease->leased_node;
|
||||
release_worker_lease_unlocked(worker_id, *lease);
|
||||
m_search_tree.backtrack(leased_node, g_core);
|
||||
}
|
||||
else {
|
||||
// the lease was canceled by another worker. don't backtrack on this node with whatever new core we just found with this thread
|
||||
// however, we do proceed to external targets, since the new code may have exposed new external targets we can close/backtrack
|
||||
attempt_release_canceled_lease_unlocked(worker_id, *lease);
|
||||
}
|
||||
}
|
||||
if (targets) {
|
||||
for (auto const& target : *targets) {
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node, target.cancel_epoch))
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node))
|
||||
continue;
|
||||
|
||||
did_backtrack = true;
|
||||
|
|
@ -1410,37 +1466,59 @@ namespace smt {
|
|||
}
|
||||
|
||||
void parallel::batch_manager::try_split(ast_translation &l2g, unsigned worker_id,
|
||||
node_lease const &lease, expr *atom, unsigned effort) {
|
||||
node_lease& lease, expr *atom, unsigned effort) {
|
||||
std::scoped_lock lock(mux);
|
||||
|
||||
if (m_state != state::is_running)
|
||||
return;
|
||||
|
||||
if (m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch))
|
||||
if (m_search_tree.is_lease_canceled(lease.leased_node)) {
|
||||
attempt_release_canceled_lease_unlocked(worker_id, lease);
|
||||
return;
|
||||
}
|
||||
|
||||
expr_ref lit(m), nlit(m);
|
||||
lit = l2g(atom);
|
||||
nlit = mk_not(m, lit);
|
||||
bool did_split = m_search_tree.try_split(lease.leased_node, lease.cancel_epoch, lit, nlit, effort);
|
||||
node* leased_node = lease.leased_node;
|
||||
VERIFY(!leased_node->path_contains_atom(lit));
|
||||
VERIFY(!leased_node->path_contains_atom(nlit));
|
||||
bool did_split = m_search_tree.try_split(leased_node, lit, nlit, effort);
|
||||
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
release_worker_lease_unlocked(worker_id, lease);
|
||||
|
||||
if (did_split) {
|
||||
++m_stats.m_num_cubes;
|
||||
m_stats.m_max_cube_depth = std::max(m_stats.m_max_cube_depth, lease.leased_node->depth() + 1);
|
||||
m_stats.m_max_cube_depth = std::max(m_stats.m_max_cube_depth, leased_node->depth() + 1);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager splitting on literal: " << mk_bounded_pp(lit, m, 3) << "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void parallel::batch_manager::release_lease(unsigned worker_id, node_lease const &lease) {
|
||||
bool parallel::batch_manager::checkpoint_worker(unsigned worker_id, node_lease& lease, bool& lease_canceled) {
|
||||
std::scoped_lock lock(mux);
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
lease_canceled = false;
|
||||
SASSERT(worker_id < p.m_workers.size());
|
||||
|
||||
if (attempt_release_canceled_lease_unlocked(worker_id, lease)) {
|
||||
lease_canceled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.m_workers[worker_id]->limit().inc())
|
||||
return true;
|
||||
|
||||
if (attempt_release_canceled_lease_unlocked(worker_id, lease)) {
|
||||
lease_canceled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
set_canceled_unlocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parallel::batch_manager::lease_canceled(node_lease const &lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
return m_state == state::is_running && m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch);
|
||||
return m_state == state::is_running && m_search_tree.is_lease_canceled(lease.leased_node);
|
||||
}
|
||||
|
||||
void parallel::batch_manager::collect_clause(ast_translation &l2g, unsigned source_worker_id, expr *clause) {
|
||||
|
|
@ -1662,7 +1740,7 @@ namespace smt {
|
|||
void parallel::batch_manager::set_sat(ast_translation &l2g, model &m) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting SAT.\n");
|
||||
if (m_state != state::is_running)
|
||||
if (m_state != state::is_running && m_state != state::is_unknown)
|
||||
return;
|
||||
m_state = state::is_sat;
|
||||
p.ctx.set_model(m.translate(l2g));
|
||||
|
|
@ -1672,7 +1750,7 @@ namespace smt {
|
|||
void parallel::batch_manager::set_unsat(ast_translation &l2g, expr_ref_vector const &unsat_core) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting UNSAT.\n");
|
||||
if (m_state != state::is_running)
|
||||
if (m_state != state::is_running && m_state != state::is_unknown)
|
||||
return;
|
||||
m_state = state::is_unsat;
|
||||
|
||||
|
|
@ -1683,6 +1761,16 @@ namespace smt {
|
|||
cancel_background_threads();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::set_unknown(std::string const &reason) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting UNKNOWN: " << reason << ".\n");
|
||||
if (m_state != state::is_running)
|
||||
return; // a definitive sat/unsat verdict or exception already won.
|
||||
m_state = state::is_unknown;
|
||||
m_reason_unknown = reason;
|
||||
cancel_background_threads();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::set_exception(unsigned error_code) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting exception code: " << error_code << ".\n");
|
||||
|
|
@ -1714,6 +1802,8 @@ namespace smt {
|
|||
return l_false;
|
||||
case state::is_sat:
|
||||
return l_true;
|
||||
case state::is_unknown:
|
||||
return l_undef;
|
||||
case state::is_exception_msg:
|
||||
throw default_exception(m_exception_msg.c_str());
|
||||
case state::is_exception_code:
|
||||
|
|
@ -1745,7 +1835,6 @@ namespace smt {
|
|||
IF_VERBOSE(2, m_search_tree.display(verbose_stream()); verbose_stream() << "\n";);
|
||||
|
||||
lease.leased_node = t;
|
||||
lease.cancel_epoch = t->get_cancel_epoch();
|
||||
if (id >= m_worker_leases.size())
|
||||
m_worker_leases.resize(id + 1);
|
||||
m_worker_leases[id] = lease;
|
||||
|
|
@ -1779,8 +1868,9 @@ namespace smt {
|
|||
m_worker_leases.reset();
|
||||
m_worker_leases.resize(p.m_workers.size());
|
||||
|
||||
smt_parallel_params pp(p.ctx.m_params);
|
||||
parallel_params pp(p.ctx.m_params);
|
||||
m_ablate_backtracking = pp.ablate_backtracking();
|
||||
m_canceled = false;
|
||||
}
|
||||
|
||||
void parallel::batch_manager::collect_statistics(::statistics &st) const {
|
||||
|
|
@ -1794,20 +1884,26 @@ namespace smt {
|
|||
}
|
||||
|
||||
lbool parallel::operator()(expr_ref_vector const &asms) {
|
||||
smt_parallel_params pp(ctx.m_params);
|
||||
unsigned num_global_bb_batch_threads = pp.num_global_bb_batch_threads();
|
||||
if (num_global_bb_batch_threads > 2)
|
||||
throw default_exception("smt_parallel.num_global_bb_batch_threads must be 0, 1, or 2");
|
||||
unsigned num_workers = std::min((unsigned)std::thread::hardware_concurrency(), ctx.get_fparams().m_threads);
|
||||
unsigned num_sls_threads = (pp.sls() ? 1 : 0);
|
||||
parallel_params pp(ctx.m_params);
|
||||
unsigned num_global_bb_threads = pp.num_bb_threads();
|
||||
if (num_global_bb_threads > 2)
|
||||
throw default_exception("parallel.num_bb_threads must be 0, 1, or 2");
|
||||
unsigned total_threads = std::min((unsigned)std::thread::hardware_concurrency(), ctx.get_fparams().m_threads);
|
||||
unsigned num_workers = total_threads;
|
||||
unsigned num_sls_threads = 0;
|
||||
unsigned num_core_min_threads = (pp.core_minimize() ? 1 : 0);
|
||||
unsigned num_global_bb_fl_threads = pp.num_global_bb_fl_threads();
|
||||
if (num_global_bb_fl_threads > 2)
|
||||
throw default_exception("smt_parallel.num_global_bb_fl_threads must be 0, 1, or 2");
|
||||
if (num_global_bb_fl_threads > 0 && num_global_bb_batch_threads > 0)
|
||||
throw default_exception("smt_parallel.num_global_bb_fl_threads and smt_parallel.num_global_bb_batch_threads cannot both be enabled");
|
||||
unsigned num_global_bb_threads = num_global_bb_fl_threads > 0 ? num_global_bb_fl_threads : num_global_bb_batch_threads;
|
||||
unsigned total_threads = num_workers + num_sls_threads + num_core_min_threads + num_global_bb_threads;
|
||||
if (num_workers > 2 + num_core_min_threads)
|
||||
num_workers -= num_core_min_threads;
|
||||
else
|
||||
num_core_min_threads = 0;
|
||||
if (num_workers > 2 + num_global_bb_threads)
|
||||
num_workers -= num_global_bb_threads;
|
||||
else
|
||||
num_global_bb_threads = 0;
|
||||
if (num_workers > 2 + num_sls_threads)
|
||||
num_workers -= num_sls_threads;
|
||||
else
|
||||
num_sls_threads = 0;
|
||||
|
||||
IF_VERBOSE(1, verbose_stream() << "Parallel SMT with " << total_threads << " threads\n";);
|
||||
ast_manager &m = ctx.m;
|
||||
|
|
@ -1841,7 +1937,7 @@ namespace smt {
|
|||
m_sls_worker = alloc(sls_worker, *this);
|
||||
sl.push_child(&(m_sls_worker->limit()));
|
||||
}
|
||||
if (pp.core_minimize()) {
|
||||
if (num_core_min_threads == 1) {
|
||||
m_core_minimizer_worker = alloc(core_minimizer_worker, *this, asms);
|
||||
sl.push_child(&(m_core_minimizer_worker->limit()));
|
||||
}
|
||||
|
|
@ -1856,18 +1952,52 @@ namespace smt {
|
|||
<< m_global_backbones_workers.size() << " global backbone threads.\n";);
|
||||
|
||||
m_batch_manager.initialize(num_global_bb_threads);
|
||||
|
||||
auto safe_run = [&](auto&& run_fn, reslimit& lim) {
|
||||
try {
|
||||
run_fn();
|
||||
if (lim.is_canceled())
|
||||
m_batch_manager.set_canceled();
|
||||
} catch (z3_error &err) {
|
||||
IF_VERBOSE(0, verbose_stream() << "Exception in parallel solver: " << err.what() << "\n");
|
||||
if (!lim.is_canceled())
|
||||
m_batch_manager.set_exception(err.error_code());
|
||||
else
|
||||
m_batch_manager.set_canceled();
|
||||
} catch (z3_exception &ex) {
|
||||
IF_VERBOSE(0, verbose_stream() << "Exception in parallel solver: " << ex.what() << "\n");
|
||||
if (!lim.is_canceled() && !is_cancellation_exception(ex.what()))
|
||||
m_batch_manager.set_exception(ex.what());
|
||||
else
|
||||
m_batch_manager.set_canceled();
|
||||
} catch (...) {
|
||||
IF_VERBOSE(0, verbose_stream() << "Unknown exception in parallel solver\n");
|
||||
if (!lim.is_canceled())
|
||||
m_batch_manager.set_exception("unknown exception");
|
||||
else
|
||||
m_batch_manager.set_canceled();
|
||||
}
|
||||
};
|
||||
|
||||
// Launch threads
|
||||
vector<std::thread> threads(total_threads);
|
||||
unsigned thread_idx = 0;
|
||||
for (auto* w : m_workers)
|
||||
threads[thread_idx++] = std::thread([&, w]() { w->run(); });
|
||||
threads[thread_idx++] = std::thread([w, &safe_run]() {
|
||||
safe_run([w]() { w->run(); }, w->limit());
|
||||
});
|
||||
if (m_sls_worker)
|
||||
threads[thread_idx++] = std::thread([&]() { m_sls_worker->run(); });
|
||||
threads[thread_idx++] = std::thread([this, &safe_run]() {
|
||||
safe_run([this]() { m_sls_worker->run(); }, m_sls_worker->limit());
|
||||
});
|
||||
if (m_core_minimizer_worker)
|
||||
threads[thread_idx++] = std::thread([&]() { m_core_minimizer_worker->run(); });
|
||||
threads[thread_idx++] = std::thread([this, &safe_run]() {
|
||||
safe_run([this]() { m_core_minimizer_worker->run(); }, m_core_minimizer_worker->limit());
|
||||
});
|
||||
for (auto* w : m_global_backbones_workers)
|
||||
threads[thread_idx++] = std::thread([&, w]() { w->run(); });
|
||||
threads[thread_idx++] = std::thread([w, &safe_run]() {
|
||||
safe_run([w]() { w->run(); }, w->limit());
|
||||
});
|
||||
|
||||
|
||||
// Wait for all threads to finish
|
||||
|
|
@ -1884,7 +2014,10 @@ namespace smt {
|
|||
for (auto* bb_w : m_global_backbones_workers)
|
||||
bb_w->collect_statistics(ctx.m_aux_stats);
|
||||
|
||||
return m_batch_manager.get_result();
|
||||
lbool result = m_batch_manager.get_result();
|
||||
if (result == l_undef && !m_batch_manager.get_reason_unknown().empty())
|
||||
ctx.set_reason_unknown(m_batch_manager.get_reason_unknown().c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace smt
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ namespace smt {
|
|||
struct cube_config {
|
||||
using literal = expr_ref;
|
||||
static bool literal_is_null(expr_ref const& l) { return l == nullptr; }
|
||||
static bool same_atom(expr_ref const& a, expr_ref const& b) {
|
||||
expr* atom_a = a.get();
|
||||
expr* atom_b = b.get();
|
||||
a.get_manager().is_not(atom_a, atom_a);
|
||||
b.get_manager().is_not(atom_b, atom_b);
|
||||
return atom_a == atom_b;
|
||||
}
|
||||
static std::ostream& display_literal(std::ostream& out, expr_ref const& l) { return out << mk_bounded_pp(l, l.get_manager()); }
|
||||
};
|
||||
|
||||
|
|
@ -74,6 +81,7 @@ namespace smt {
|
|||
is_running,
|
||||
is_sat,
|
||||
is_unsat,
|
||||
is_unknown,
|
||||
is_exception_msg,
|
||||
is_exception_code
|
||||
};
|
||||
|
|
@ -102,6 +110,7 @@ namespace smt {
|
|||
|
||||
unsigned m_exception_code = 0;
|
||||
std::string m_exception_msg;
|
||||
std::string m_reason_unknown;
|
||||
vector<shared_clause> shared_clause_trail; // store all shared clauses with worker IDs
|
||||
obj_hashtable<expr> shared_clause_set; // for duplicate filtering on per-thread clause expressions
|
||||
|
||||
|
|
@ -145,7 +154,11 @@ namespace smt {
|
|||
w->cancel();
|
||||
}
|
||||
|
||||
std::atomic<bool> m_canceled = false;
|
||||
|
||||
void cancel_background_threads() {
|
||||
if (m_canceled.exchange(true))
|
||||
return; // already canceled
|
||||
cancel_workers();
|
||||
cancel_sls_worker();
|
||||
if (!p.m_global_backbones_workers.empty()) {
|
||||
|
|
@ -171,9 +184,11 @@ namespace smt {
|
|||
}
|
||||
|
||||
void backtrack_unlocked(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core,
|
||||
node_lease const* lease = nullptr, vector<node_lease> const* targets = nullptr);
|
||||
node_lease* lease = nullptr, vector<node_lease> const* targets = nullptr);
|
||||
void collect_clause_unlocked(ast_translation &l2g, unsigned source_worker_id, expr *clause);
|
||||
void release_lease_unlocked(unsigned worker_id, node* n);
|
||||
void set_canceled_unlocked();
|
||||
void release_worker_lease_unlocked(unsigned worker_id, node_lease& lease);
|
||||
bool attempt_release_canceled_lease_unlocked(unsigned worker_id, node_lease& lease);
|
||||
void cancel_closed_leases_unlocked(unsigned source_worker_id);
|
||||
void collect_matching_targets_unlocked(node* source, expr* lit, vector<cube_config::literal> const& core,
|
||||
vector<node_lease>& targets);
|
||||
|
|
@ -187,6 +202,8 @@ namespace smt {
|
|||
|
||||
void set_unsat(ast_translation& l2g, expr_ref_vector const& unsat_core);
|
||||
void set_sat(ast_translation& l2g, model& m);
|
||||
void set_unknown(std::string const& reason);
|
||||
void set_canceled();
|
||||
void set_exception(std::string const& msg);
|
||||
void set_exception(unsigned error_code);
|
||||
void collect_statistics(::statistics& st) const;
|
||||
|
|
@ -210,20 +227,21 @@ namespace smt {
|
|||
}
|
||||
|
||||
bool get_cube(ast_translation& g2l, unsigned id, expr_ref_vector& cube, bool is_first_run, node_lease& lease);
|
||||
void backtrack(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core, node_lease const& lease);
|
||||
void backtrack(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core, node_lease& lease);
|
||||
void enqueue_core_minimization(ast_translation& l2g, node* source, expr_ref_vector const& core);
|
||||
bool wait_for_core_min_job(ast_translation& g2l, node*& source,
|
||||
expr_ref_vector& core, reslimit& lim);
|
||||
void publish_minimized_core(ast_translation& l2g, expr_ref_vector const& asms, node* source,
|
||||
unsigned original_core_size, expr_ref_vector const& minimized_core);
|
||||
void try_split(ast_translation& l2g, unsigned worker_id, node_lease const& lease, expr* atom, unsigned effort);
|
||||
void release_lease(unsigned worker_id, node_lease const& lease);
|
||||
void try_split(ast_translation& l2g, unsigned worker_id, node_lease& lease, expr* atom, unsigned effort);
|
||||
bool checkpoint_worker(unsigned worker_id, node_lease& lease, bool& lease_canceled);
|
||||
bool lease_canceled(node_lease const& lease);
|
||||
|
||||
void collect_clause(ast_translation& l2g, unsigned source_worker_id, expr* clause);
|
||||
expr_ref_vector return_shared_clauses(ast_translation& g2l, unsigned& worker_limit, unsigned worker_id);
|
||||
|
||||
lbool get_result() const;
|
||||
std::string const& get_reason_unknown() const { return m_reason_unknown; }
|
||||
|
||||
bool is_global_backbone_or_negation(ast_translation& l2g, expr* bb_cand) {
|
||||
std::scoped_lock lock(mux);
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@ Notes:
|
|||
#include "ast/for_each_expr.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/func_decl_dependencies.h"
|
||||
#include "smt/smt_context.h"
|
||||
#include "smt/smt_kernel.h"
|
||||
#include "params/smt_params.h"
|
||||
#include "params/smt_params_helper.hpp"
|
||||
#include "solver/solver_na2as.h"
|
||||
#include "solver/mus.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
class smt_solver : public solver_na2as {
|
||||
|
|
@ -61,6 +64,7 @@ namespace {
|
|||
smt_params m_smt_params;
|
||||
smt::kernel m_context;
|
||||
cuber* m_cuber;
|
||||
random_gen m_rand;
|
||||
symbol m_logic;
|
||||
bool m_minimizing_core;
|
||||
bool m_core_extend_patterns;
|
||||
|
|
@ -84,16 +88,19 @@ namespace {
|
|||
updt_params(p);
|
||||
}
|
||||
|
||||
solver * translate(ast_manager & m, params_ref const & p) override {
|
||||
ast_translation translator(get_manager(), m);
|
||||
solver * translate(ast_manager & target, params_ref const & p) override {
|
||||
ast_translation translator(get_manager(), target);
|
||||
params_ref init;
|
||||
init.copy(get_params());
|
||||
init.copy(p);
|
||||
|
||||
smt_solver * result = alloc(smt_solver, m, p, m_logic);
|
||||
smt_solver* result = alloc(smt_solver, target, init, m_logic);
|
||||
smt::kernel::copy(m_context, result->m_context, true);
|
||||
|
||||
if (mc0())
|
||||
if (mc0())
|
||||
result->set_model_converter(mc0()->translate(translator));
|
||||
|
||||
for (auto & [k, v] : m_name2assertion) {
|
||||
for (auto& [k, v] : m_name2assertion) {
|
||||
expr* val = translator(k);
|
||||
expr* key = translator(v);
|
||||
result->assert_expr(val, key);
|
||||
|
|
@ -212,6 +219,97 @@ namespace {
|
|||
return m_context.get_trail(max_level);
|
||||
}
|
||||
|
||||
expr_ref_vector get_assigned_literals() override {
|
||||
expr_ref_vector result(m);
|
||||
auto const& ctx = m_context.get_context();
|
||||
for (auto lit : ctx.assigned_literals()) {
|
||||
expr* atom = ctx.bool_var2expr(lit.var());
|
||||
if (!atom)
|
||||
continue;
|
||||
result.push_back(lit.sign() ? m.mk_not(atom) : atom);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned get_assign_level(expr* e) const override {
|
||||
auto const& ctx = m_context.get_context();
|
||||
get_manager().is_not(e, e);
|
||||
if (!ctx.b_internalized(e))
|
||||
return UINT_MAX;
|
||||
return ctx.get_assign_level(ctx.get_bool_var(e));
|
||||
}
|
||||
|
||||
bool is_relevant(expr* e) const override {
|
||||
auto const& ctx = m_context.get_context();
|
||||
get_manager().is_not(e, e);
|
||||
return ctx.b_internalized(e) && ctx.is_relevant(e);
|
||||
}
|
||||
|
||||
unsigned get_num_bool_vars() const override {
|
||||
return m_context.get_context().get_num_bool_vars();
|
||||
}
|
||||
|
||||
sat::bool_var get_bool_var(expr* e) const override {
|
||||
auto const& ctx = m_context.get_context();
|
||||
get_manager().is_not(e, e);
|
||||
return ctx.b_internalized(e) ? ctx.get_bool_var(e) : sat::null_bool_var;
|
||||
}
|
||||
|
||||
void pop_to_base_level() override {
|
||||
m_context.pop_to_base_level();
|
||||
}
|
||||
|
||||
void setup_for_parallel() override {
|
||||
m_context.get_context().setup_for_parallel();
|
||||
}
|
||||
|
||||
void set_preprocess(bool f) override {
|
||||
m_context.set_preprocess(f);
|
||||
}
|
||||
|
||||
void set_max_conflicts(unsigned max_conflicts) override {
|
||||
auto& ctx = m_context.get_context();
|
||||
ctx.get_fparams().m_max_conflicts = max_conflicts;
|
||||
}
|
||||
|
||||
unsigned get_max_conflicts() const override {
|
||||
return m_context.get_context().get_fparams().m_max_conflicts;
|
||||
}
|
||||
|
||||
void get_backbone_candidates(vector<solver::scored_literal>& candidates, unsigned max_num) override {
|
||||
ast_manager& m = get_manager();
|
||||
auto& ctx = m_context.get_context();
|
||||
unsigned curr_time = ctx.get_num_assignments();
|
||||
vector<solver::scored_literal> all;
|
||||
|
||||
for (unsigned v = 0; v < ctx.get_num_bool_vars(); ++v) {
|
||||
if (ctx.get_assignment(v) != l_undef && ctx.get_assign_level(v) == ctx.get_base_level())
|
||||
continue;
|
||||
|
||||
expr* candidate = ctx.bool_var2expr(v);
|
||||
if (!candidate)
|
||||
continue;
|
||||
|
||||
auto const& d = ctx.get_bdata(v);
|
||||
if (d.m_phase_available && !d.m_phase)
|
||||
candidate = m.mk_not(candidate);
|
||||
|
||||
double age = static_cast<double>(curr_time - ctx.get_birthdate(v));
|
||||
all.push_back(solver::scored_literal(m, candidate, age));
|
||||
}
|
||||
|
||||
std::stable_sort(
|
||||
all.begin(),
|
||||
all.end(),
|
||||
[](solver::scored_literal const& a, solver::scored_literal const& b) {
|
||||
return a.score > b.score;
|
||||
});
|
||||
|
||||
unsigned n = std::min<unsigned>(max_num, all.size());
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
candidates.push_back(all[i]);
|
||||
}
|
||||
|
||||
void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause) override {
|
||||
m_context.register_on_clause(ctx, on_clause);
|
||||
}
|
||||
|
|
@ -368,6 +466,39 @@ namespace {
|
|||
return lits;
|
||||
}
|
||||
|
||||
expr_ref cube_vsids(expr_ref_vector const& invalid_split_atoms) override {
|
||||
ast_manager& m = get_manager();
|
||||
auto& ctx = m_context.get_context();
|
||||
obj_hashtable<expr> invalid_split_atoms_set;
|
||||
for (expr* e : invalid_split_atoms) {
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
invalid_split_atoms_set.insert(atom);
|
||||
}
|
||||
expr_ref result(m);
|
||||
double score = 0.0;
|
||||
unsigned n = 0;
|
||||
|
||||
ctx.pop_to_search_level();
|
||||
for (unsigned v = 0; v < ctx.get_num_bool_vars(); ++v) {
|
||||
if (ctx.get_assignment(v) != l_undef)
|
||||
continue;
|
||||
expr* e = ctx.bool_var2expr(v);
|
||||
if (!e)
|
||||
continue;
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
if (invalid_split_atoms_set.contains(atom))
|
||||
continue;
|
||||
double new_score = ctx.get_activity(v);
|
||||
if (new_score > score || !result || (new_score == score && m_rand(++n) == 0)) {
|
||||
score = new_score;
|
||||
result = e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct collect_fds_proc {
|
||||
ast_manager & m;
|
||||
func_decl_set & m_fds;
|
||||
|
|
@ -537,4 +668,3 @@ public:
|
|||
solver_factory * mk_smt_solver_factory() {
|
||||
return alloc(smt_solver_factory);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ Notes:
|
|||
#include "solver/solver.h"
|
||||
#include "solver/mus.h"
|
||||
#include "solver/parallel_tactical.h"
|
||||
#include "solver/parallel_tactical2.h"
|
||||
#include "solver/parallel_params.hpp"
|
||||
#include <mutex>
|
||||
|
||||
|
|
@ -431,8 +430,6 @@ static tactic * mk_seq_smt_tactic(ast_manager& m, params_ref const & p) {
|
|||
|
||||
tactic * mk_parallel_smt_tactic(ast_manager& m, params_ref const& p) {
|
||||
parallel_params pp(p);
|
||||
if (pp.enable2())
|
||||
return mk_parallel_tactic2(mk_smt_solver(m, p, symbol::null), p);
|
||||
return mk_parallel_tactic(mk_smt_solver(m, p, symbol::null), p);
|
||||
}
|
||||
|
||||
|
|
@ -440,8 +437,6 @@ tactic * mk_smt_tactic_core(ast_manager& m, params_ref const& p, symbol const& l
|
|||
parallel_params pp(p);
|
||||
if (pp.enable())
|
||||
return mk_parallel_tactic(mk_smt_solver(m, p, logic), p);
|
||||
if (pp.enable2())
|
||||
return mk_parallel_tactic2(mk_smt_solver(m, p, logic), p);
|
||||
return mk_seq_smt_tactic(m, p);
|
||||
}
|
||||
|
||||
|
|
@ -450,7 +445,7 @@ tactic * mk_smt_tactic_core_using(ast_manager& m, bool auto_config, params_ref c
|
|||
params_ref p = _p;
|
||||
p.set_bool("auto_config", auto_config);
|
||||
tactic *t = nullptr;
|
||||
if (pp.enable() || pp.enable2())
|
||||
if (pp.enable())
|
||||
t = mk_parallel_smt_tactic(m, p);
|
||||
else
|
||||
t = mk_seq_smt_tactic(m, p);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace smt {
|
|||
unsigned m_num_map_axiom, m_num_default_map_axiom;
|
||||
unsigned m_num_select_const_axiom, m_num_default_store_axiom, m_num_default_const_axiom, m_num_default_as_array_axiom;
|
||||
unsigned m_num_select_as_array_axiom, m_num_default_lambda_axiom, m_num_choice_axiom;
|
||||
unsigned m_num_select_lambda_axiom;
|
||||
void reset() { memset(this, 0, sizeof(theory_array_stats)); }
|
||||
theory_array_stats() { reset(); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -536,6 +534,7 @@ namespace smt {
|
|||
unsigned num_vars = get_num_vars();
|
||||
for (unsigned i = 0; i < num_vars; ++i) {
|
||||
enode * n = get_enode(i);
|
||||
TRACE(array, tout << enode_pp(n, ctx) << " is_relevant: " << ctx.is_relevant(n) << " is_array: " << is_array_sort(n) << "\n";);
|
||||
if (!ctx.is_relevant(n) || !is_array_sort(n)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -581,11 +580,12 @@ 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);
|
||||
TRACE(array_bug, tout << "mk_interface_eqs: adding: " << eq << "\n";);
|
||||
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 +850,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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,6 +393,10 @@ namespace smt {
|
|||
SASSERT(is_map(map));
|
||||
instantiate_select_map_axiom(s, map);
|
||||
}
|
||||
for (enode *lam : d_full->m_lambdas) {
|
||||
SASSERT(is_lambda(lam->get_expr()));
|
||||
instantiate_select_lambda_axiom(s, lam);
|
||||
}
|
||||
if (!m_params.m_array_delay_exp_axiom && d->m_prop_upward) {
|
||||
for (enode * map : d_full->m_parent_maps) {
|
||||
SASSERT(is_map(map));
|
||||
|
|
@ -468,7 +472,6 @@ namespace smt {
|
|||
SASSERT(map->get_num_args() > 0);
|
||||
func_decl* f = to_func_decl(map->get_decl()->get_parameter(0).get_ast());
|
||||
|
||||
|
||||
TRACE(array_map_bug, tout << "invoked instantiate_select_map_axiom\n";
|
||||
tout << sl->get_owner_id() << " " << mp->get_owner_id() << "\n";
|
||||
tout << mk_ismt2_pp(sl->get_expr(), m) << "\n" << mk_ismt2_pp(mp->get_expr(), m) << "\n";);
|
||||
|
|
@ -518,6 +521,34 @@ namespace smt {
|
|||
return try_assign_eq(sel1, sel2);
|
||||
}
|
||||
|
||||
bool theory_array_full::instantiate_select_lambda_axiom(enode* sl, enode* lambda) {
|
||||
app* select = sl->get_app();
|
||||
SASSERT(is_select(select));
|
||||
SASSERT(is_lambda(lambda->get_expr()));
|
||||
SASSERT(lambda->get_sort() == sl->get_arg(0)->get_sort());
|
||||
|
||||
if (!ctx.add_fingerprint(lambda, lambda->get_owner_id(), sl->get_num_args() - 1, sl->get_args() + 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stats.m_num_select_lambda_axiom++;
|
||||
|
||||
unsigned num_args = select->get_num_args();
|
||||
ptr_buffer<expr> args;
|
||||
args.push_back(lambda->get_expr());
|
||||
for (unsigned i = 1; i < num_args; ++i)
|
||||
args.push_back(select->get_arg(i));
|
||||
|
||||
expr_ref sel1(m), sel2(m);
|
||||
sel1 = mk_select(args.size(), args.data());
|
||||
sel2 = sel1;
|
||||
ctx.get_rewriter()(sel2);
|
||||
ctx.internalize(sel1, false);
|
||||
ctx.internalize(sel2, false);
|
||||
TRACE(array, tout << mk_bounded_pp(sel1, m) << "\n==\n" << mk_bounded_pp(sel2, m) << "\n";);
|
||||
return try_assign_eq(sel1, sel2);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
|
|
@ -881,6 +912,7 @@ namespace smt {
|
|||
st.update("array def as-array", m_stats.m_num_default_as_array_axiom);
|
||||
st.update("array sel as-array", m_stats.m_num_select_as_array_axiom);
|
||||
st.update("array def lambda", m_stats.m_num_default_lambda_axiom);
|
||||
st.update("array sel lambda", m_stats.m_num_select_lambda_axiom);
|
||||
st.update("array choice ax", m_stats.m_num_choice_axiom);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ namespace smt {
|
|||
bool instantiate_default_map_axiom(enode* map);
|
||||
bool instantiate_default_as_array_axiom(enode* arr);
|
||||
bool instantiate_default_lambda_def_axiom(enode* arr);
|
||||
bool instantiate_select_lambda_axiom(enode *lambda);
|
||||
|
||||
bool instantiate_choice_axiom(enode* ch);
|
||||
bool instantiate_parent_stores_default(theory_var v);
|
||||
|
||||
|
|
@ -96,6 +96,7 @@ namespace smt {
|
|||
bool instantiate_select_const_axiom(enode* select, enode* cnst);
|
||||
bool instantiate_select_as_array_axiom(enode* select, enode* arr);
|
||||
bool instantiate_select_map_axiom(enode* select, enode* map);
|
||||
bool instantiate_select_lambda_axiom(enode *select, enode *lambda);
|
||||
|
||||
bool instantiate_axiom_map_for(theory_var v);
|
||||
|
||||
|
|
|
|||
|
|
@ -437,11 +437,12 @@ namespace smt {
|
|||
return lit == arg;
|
||||
};
|
||||
auto lit1 = clause.get(0);
|
||||
[[maybe_unused]] auto lit2 = clause.get(1);
|
||||
auto position = 0;
|
||||
if (is_complement_to(is_true, lit1, e))
|
||||
position = 0;
|
||||
else {
|
||||
SASSERT(is_complement_to(is_true, clause.get(1), e));
|
||||
SASSERT(is_complement_to(is_true, lit2, e));
|
||||
position = 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4044,7 +4044,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();
|
||||
|
|
|
|||
|
|
@ -193,16 +193,16 @@ namespace smt {
|
|||
|
||||
void theory_nseq::new_eq_eh(theory_var v1, theory_var v2) {
|
||||
try {
|
||||
auto n1 = get_enode(v1);
|
||||
auto n2 = get_enode(v2);
|
||||
auto e1 = n1->get_expr();
|
||||
auto e2 = n2->get_expr();
|
||||
const auto n1 = get_enode(v1);
|
||||
const auto n2 = get_enode(v2);
|
||||
const auto e1 = n1->get_expr();
|
||||
const auto e2 = n2->get_expr();
|
||||
TRACE(seq, tout << mk_pp(e1, m) << " == " << mk_pp(e2, m) << "\n");
|
||||
//std::cout << mk_pp(e1, m) << " == " << mk_pp(e2, m) << std::endl;
|
||||
if (m_seq.is_re(e1)) {
|
||||
expr_ref s(m);
|
||||
auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_seq_in_re(r, s)) {
|
||||
zstring s;
|
||||
const auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_string_in_re(r, s)) {
|
||||
case l_false:
|
||||
// regexes are equivalent: nothing to do
|
||||
return;
|
||||
|
|
@ -237,15 +237,15 @@ namespace smt {
|
|||
}
|
||||
|
||||
void theory_nseq::new_diseq_eh(theory_var v1, theory_var v2) {
|
||||
auto n1 = get_enode(v1);
|
||||
auto n2 = get_enode(v2);
|
||||
auto e1 = n1->get_expr();
|
||||
auto e2 = n2->get_expr();
|
||||
const auto n1 = get_enode(v1);
|
||||
const auto n2 = get_enode(v2);
|
||||
const auto e1 = n1->get_expr();
|
||||
const auto e2 = n2->get_expr();
|
||||
TRACE(seq, tout << mk_pp(e1, m) << " != " << mk_pp(e2, m) << "\n");
|
||||
if (m_seq.is_re(e1)) {
|
||||
expr_ref s(m);
|
||||
auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_seq_in_re(r, s)) {
|
||||
zstring s;
|
||||
auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_string_in_re(r, s)) {
|
||||
case l_false: {
|
||||
enode_pair_vector eqs;
|
||||
const auto lit = mk_eq(e1, e2, false);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ z3_add_component(solver
|
|||
combined_solver.cpp
|
||||
mus.cpp
|
||||
parallel_tactical.cpp
|
||||
parallel_tactical2.cpp
|
||||
simplifier_solver.cpp
|
||||
slice_solver.cpp
|
||||
smt_logics.cpp
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ def_module_params('parallel',
|
|||
export=True,
|
||||
params=(
|
||||
('enable', BOOL, False, 'enable parallel solver by default on selected tactics (for QF_BV)'),
|
||||
('enable2', BOOL, False, 'enable (experimental) parallel solver by default on selected tactics (for QF_BV)'),
|
||||
('threads.max', UINT, 10000, 'caps maximal number of threads below the number of processors'),
|
||||
('num_bb_threads', UINT, 2, 'run Janota-style chunking backbone worker threads; default is 2 (negative and positive mode), supported values are 0 (off), 1 (negative mode only) or 2 (negative and positive mode)'),
|
||||
('core_minimize', BOOL, True, 'minimize unsat cores used for parallel cube backtracking'),
|
||||
('ablate_backtracking', BOOL, False, 'ablation: pass entire cube as core instead of unsat core during backtracking'),
|
||||
('cube.lookahead', BOOL, False, 'use lookahead cubing in the parallel solver; when false, use VSIDS activity to select one split literal'),
|
||||
('conquer.batch_size', UINT, 100, 'number of cubes to batch together for fast conquer'),
|
||||
('conquer.restart.max', UINT, 5, 'maximal number of restarts during conquer phase'),
|
||||
('conquer.delay', UINT, 10, 'delay of cubes until applying conquer'),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue