mirror of
https://github.com/Z3Prover/z3
synced 2026-01-21 01:24:43 +00:00
* Introduce X-macro-based trace tag definition - Created trace_tags.def to centralize TRACE tag definitions - Each tag includes a symbolic name and description - Set up enum class TraceTag for type-safe usage in TRACE macros * Add script to generate Markdown documentation from trace_tags.def - Python script parses trace_tags.def and outputs trace_tags.md * Refactor TRACE_NEW to prepend TraceTag and pass enum to is_trace_enabled * trace: improve trace tag handling system with hierarchical tagging - Introduce hierarchical tag-class structure: enabling a tag class activates all child tags - Unify TRACE, STRACE, SCTRACE, and CTRACE under enum TraceTag - Implement initial version of trace_tag.def using X(tag, tag_class, description) (class names and descriptions to be refined in a future update) * trace: replace all string-based TRACE tags with enum TraceTag - Migrated all TRACE, STRACE, SCTRACE, and CTRACE macros to use enum TraceTag values instead of raw string literals * trace : add cstring header * trace : Add Markdown documentation generation from trace_tags.def via mk_api_doc.py * trace : rename macro parameter 'class' to 'tag_class' and remove Unicode comment in trace_tags.h. * trace : Add TODO comment for future implementation of tag_class activation * trace : Disable code related to tag_class until implementation is ready (#7663).
447 lines
16 KiB
C++
447 lines
16 KiB
C++
/*++
|
|
Copyright (c) 2025 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
sls_arith_clausal
|
|
|
|
Abstract:
|
|
|
|
Theory plugin for arithmetic local search
|
|
based on clausal search as used in HybridSMT (nia_ls)
|
|
|
|
In contrast to HybridSMT/nia_ls we reuse ddfw
|
|
for everything Boolean. It requiers exposing the following:
|
|
|
|
- unsat_vars - Boolean variables that are in unsat clauses.
|
|
- num_external_vars_in_unsat - External variables in unsat clauses
|
|
- shift_weights - allow plugin to invoke shift-weights
|
|
|
|
|
|
Author:
|
|
|
|
Nikolaj Bjorner (nbjorner) 2025-01-16
|
|
|
|
--*/
|
|
|
|
#include "ast/sls/sls_arith_clausal.h"
|
|
#include "ast/sls/sls_arith_base.h"
|
|
|
|
namespace sls {
|
|
template<typename num_t>
|
|
arith_clausal<num_t>::arith_clausal(arith_base<num_t>& a) :
|
|
ctx(a.ctx),
|
|
a(a) {
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::search() {
|
|
|
|
initialize();
|
|
|
|
TRACE(arith, ctx.display_all(tout));
|
|
|
|
a.m_config.max_moves = a.m_stats.m_steps + a.m_config.max_moves_base;
|
|
|
|
while (ctx.rlimit().inc() && a.m_stats.m_steps < a.m_config.max_moves && !ctx.unsat().empty()) {
|
|
a.m_stats.m_steps++;
|
|
|
|
check_restart();
|
|
|
|
unsigned vars_in_unsat = ctx.unsat_vars().size();
|
|
unsigned ext_in_unsat = ctx.num_external_in_unsat_vars();
|
|
unsigned bool_in_unsat = vars_in_unsat - ext_in_unsat;
|
|
sat::bool_var bv = sat::null_bool_var;
|
|
var_t v = null_arith_var;
|
|
bool time_up_bool = m_no_improve_bool * vars_in_unsat > 5 * bool_in_unsat;
|
|
bool time_up_arith = m_no_improve_arith * vars_in_unsat > 20 * ext_in_unsat;
|
|
if ((m_bool_mode && bool_in_unsat < vars_in_unsat && time_up_bool) || bool_in_unsat == 0)
|
|
enter_arith_mode();
|
|
else if ((!m_bool_mode && bool_in_unsat > 0 && time_up_arith) || vars_in_unsat == bool_in_unsat)
|
|
enter_bool_mode();
|
|
if (m_bool_mode) {
|
|
bv = ctx.bool_flip();
|
|
|
|
m_no_improve_bool = update_outer_best_solution() ? 0 : m_no_improve_bool + 1;
|
|
}
|
|
else {
|
|
v = move_arith_variable();
|
|
m_no_improve_arith = update_inner_best_solution() ? 0 : m_no_improve_arith + 1;
|
|
}
|
|
m_no_improve = update_best_solution() ? 0 : m_no_improve + 1;
|
|
|
|
(void)bv;
|
|
(void)v;
|
|
TRACE(arith,
|
|
if (bv != sat::null_bool_var) tout << "bool flip " << bv << "\n";
|
|
else if (v != null_arith_var) tout << "arith flip v" << v << "\n";
|
|
else tout << "no flip\n";
|
|
tout << "unsat-vars " << ctx.unsat_vars().size() << "\n";
|
|
tout << "bools: " << (ctx.unsat_vars().size() - ctx.num_external_in_unsat_vars()) << " timeup-bool " << time_up_bool << "\n";
|
|
tout << "no-improve bool: " << m_no_improve_bool << "\n";
|
|
tout << "no-improve arith: " << m_no_improve_arith << "\n";
|
|
tout << "ext: " << ext_in_unsat << " timeup-arith " << time_up_arith << "\n";
|
|
);
|
|
}
|
|
|
|
if (a.m_stats.m_steps >= a.m_config.max_moves)
|
|
a.m_config.max_moves_base += 100;
|
|
}
|
|
|
|
template<typename num_t>
|
|
var_t arith_clausal<num_t>::move_arith_variable() {
|
|
|
|
var_t v = null_arith_var;
|
|
|
|
{
|
|
m_best_score = 1;
|
|
flet<bool> _use_tabu(a.m_use_tabu, true);
|
|
if (v == null_arith_var) {
|
|
add_lookahead_on_unsat_vars();
|
|
v = critical_move_on_updates(unsat_var_move);
|
|
}
|
|
if (v == null_arith_var) {
|
|
add_lookahead_on_false_literals();
|
|
v = critical_move_on_updates(false_literal_move);
|
|
}
|
|
}
|
|
|
|
// tabu flips were not possible
|
|
|
|
if (v == null_arith_var)
|
|
ctx.shift_weights();
|
|
|
|
if (v == null_arith_var) {
|
|
m_best_score = -1;
|
|
flet<bool> _use_tabu(a.m_use_tabu, false);
|
|
add_lookahead_on_unsat_vars();
|
|
v = random_move_on_updates();
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::add_lookahead_on_unsat_vars() {
|
|
a.m_updates.reset();
|
|
TRACE(arith_verbose, tout << "unsat-vars ";
|
|
for (auto v : ctx.unsat_vars())
|
|
if (a.get_ineq(v)) tout << mk_bounded_pp(ctx.atom(v), a.m) << " ";
|
|
tout << "\n";);
|
|
|
|
for (auto v : ctx.unsat_vars())
|
|
add_lookahead(v);
|
|
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::add_lookahead(sat::bool_var bv) {
|
|
auto* ineq = a.get_ineq(bv);
|
|
if (!ineq)
|
|
return;
|
|
num_t na, nb;
|
|
flet<bool> _allow_recursive_delta(a.m_allow_recursive_delta, true);
|
|
for (auto const& [x, nl] : ineq->m_nonlinear) {
|
|
if (a.is_fixed(x))
|
|
continue;
|
|
if (a.is_linear(x, nl, nb))
|
|
a.find_linear_moves(*ineq, x, nb);
|
|
else if (a.is_quadratic(x, nl, na, nb))
|
|
a.find_quadratic_moves(*ineq, x, na, nb, ineq->m_args_value);
|
|
else
|
|
;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief walk over literals that are false in some clause.
|
|
* Try to determine if flipping them to true improves the overall score.
|
|
*/
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::add_lookahead_on_false_literals() {
|
|
a.m_updates.reset();
|
|
|
|
unsigned sz = a.m_bool_var_atoms.size();
|
|
bool is_big = sz > 45u;
|
|
sat::bool_var bv;
|
|
|
|
auto occurs_negative = [&](sat::bool_var bv) {
|
|
if (ctx.unsat_vars().contains(bv))
|
|
return false;
|
|
auto* ineq = a.get_ineq(bv);
|
|
if (!ineq)
|
|
return false;
|
|
sat::literal lit(bv, !ineq->is_true());
|
|
auto const& ul = ctx.get_use_list(~lit);
|
|
return ul.begin() != ul.end();
|
|
};
|
|
|
|
unsigned idx = 0;
|
|
if (is_big) {
|
|
for (unsigned i = 45, j = 90; j-- > 0 && i-- > 0 && sz > 0;) {
|
|
idx = ctx.rand(sz);
|
|
bv = a.m_bool_var_atoms[idx];
|
|
--sz;
|
|
a.m_bool_var_atoms.swap_elems(idx, sz);
|
|
if (occurs_negative(bv))
|
|
add_lookahead(bv);
|
|
else
|
|
++i;
|
|
}
|
|
}
|
|
else {
|
|
for (unsigned i = 0; i < sz; ++i) {
|
|
bv = a.m_bool_var_atoms[i];
|
|
if (occurs_negative(bv))
|
|
add_lookahead(bv);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename num_t>
|
|
var_t arith_clausal<num_t>::critical_move_on_updates(move_t mt) {
|
|
if (a.m_updates.empty())
|
|
return null_arith_var;
|
|
std::stable_sort(a.m_updates.begin(), a.m_updates.end(), [](auto const& a, auto const& b) { return a.m_var < b.m_var || (a.m_var == b.m_var && a.m_delta < b.m_delta); });
|
|
m_last_var = null_arith_var;
|
|
m_last_delta = 0;
|
|
m_best_var = null_arith_var;
|
|
m_best_delta = 0;
|
|
m_best_abs_value = num_t(-1);
|
|
m_best_last_step = UINT_MAX;
|
|
m_num_lookaheads = 0;
|
|
for (auto const& u : a.m_updates)
|
|
lookahead(u.m_var, u.m_delta);
|
|
|
|
// verbose_stream() << a.m_updates.size() << " " << m_num_lookaheads << " lookaheads\n";
|
|
ctx.rlimit().inc(1 + m_num_lookaheads);
|
|
critical_move(m_best_var, m_best_delta, mt);
|
|
return m_best_var;
|
|
}
|
|
|
|
template<typename num_t>
|
|
var_t arith_clausal<num_t>::random_move_on_updates() {
|
|
if (a.m_updates.empty())
|
|
return null_arith_var;
|
|
unsigned idx = ctx.rand(a.m_updates.size());
|
|
auto& [v, delta, score] = a.m_updates[idx];
|
|
if (!a.can_update_num(v, delta))
|
|
return null_arith_var;
|
|
critical_move(v, delta, random_move);
|
|
return v;
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::lookahead(var_t v, num_t const& delta) {
|
|
if (v == m_last_var && delta == m_last_delta)
|
|
return;
|
|
if (delta == 0)
|
|
return;
|
|
|
|
m_last_var = v;
|
|
m_last_delta = delta;
|
|
if (!a.can_update_num(v, delta))
|
|
return;
|
|
auto score = get_score(v, delta);
|
|
auto& vi = a.m_vars[v];
|
|
num_t abs_value = abs(vi.value() + delta);
|
|
unsigned last_step = vi.last_step(delta);
|
|
++m_num_lookaheads;
|
|
if (score < m_best_score)
|
|
return;
|
|
if (score > m_best_score ||
|
|
(m_best_abs_value == -1) ||
|
|
(abs_value < m_best_abs_value) ||
|
|
(abs_value == m_best_abs_value && last_step < m_best_last_step)) {
|
|
m_best_score = score;
|
|
m_best_var = v;
|
|
m_best_delta = delta;
|
|
m_best_last_step = last_step;
|
|
m_best_abs_value = abs_value;
|
|
}
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::critical_move(var_t v, num_t const& delta, move_t mt) {
|
|
if (v == null_arith_var)
|
|
return;
|
|
a.m_last_delta = delta;
|
|
a.m_last_var = v;
|
|
TRACE(arith, tout << mt << " v" << v << " " << mk_bounded_pp(a.m_vars[v].m_expr, a.m)
|
|
<< " += " << delta << " score:" << m_best_score << "\n");
|
|
a.m_vars[v].set_step(a.m_stats.m_steps, a.m_stats.m_steps + 3 + ctx.rand(10), delta);
|
|
VERIFY(a.update_num(v, delta));
|
|
for (auto bv : a.m_vars[v].m_bool_vars_of)
|
|
if (a.get_ineq(bv) && a.get_ineq(bv)->is_true() != ctx.is_true(bv))
|
|
ctx.flip(bv);
|
|
|
|
DEBUG_CODE(
|
|
for (sat::bool_var bv = 0; bv < ctx.num_bool_vars(); ++bv) {
|
|
if (a.get_ineq(bv) && a.get_ineq(bv)->is_true() != ctx.is_true(bv)) {
|
|
TRACE(arith, tout << "bv:" << bv << " " << *a.get_ineq(bv) << " " << (ctx.is_true(bv)?"T":"F") << "\n";
|
|
tout << "v" << v << " bool vars: " << a.m_vars[v].m_bool_vars_of << "\n";
|
|
tout << mk_bounded_pp(a.m_vars[v].m_expr, a.m) << "\n";
|
|
tout << mk_bounded_pp(ctx.atom(bv), a.m) << "\n";
|
|
ctx.display(tout););
|
|
}
|
|
// VERIFY(!a.get_ineq(bv) || a.get_ineq(bv)->is_true() == ctx.is_true(bv));
|
|
});
|
|
}
|
|
|
|
template<typename num_t>
|
|
double arith_clausal<num_t>::get_score(var_t v, num_t const& delta) {
|
|
auto& vi = a.m_vars[v];
|
|
TRACE(arith, tout << "get-score v" << v << " += " << delta << "\n";);
|
|
if (!a.update_num(v, delta))
|
|
return -1;
|
|
double score = 0;
|
|
for (auto ci : vi.m_clauses_of) {
|
|
auto const& c = ctx.get_clause(ci);
|
|
unsigned num_true = 0;
|
|
for (auto lit : c) {
|
|
auto bv = lit.var();
|
|
auto ineq = a.get_ineq(bv);
|
|
if (ineq) {
|
|
if (ineq->is_true() != lit.sign())
|
|
++num_true;
|
|
}
|
|
else if (ctx.is_true(lit))
|
|
++num_true;
|
|
}
|
|
|
|
CTRACE(arith_verbose, c.m_num_trues != num_true && (c.m_num_trues == 0 || num_true == 0),
|
|
tout << "clause: " << c
|
|
<< " v" << v << " += " << delta
|
|
<< " new-true lits: " << num_true
|
|
<< " old-true lits: " << c.m_num_trues
|
|
<< " w: " << c.m_weight << "\n";
|
|
for (auto lit : c)
|
|
if (a.get_ineq(lit.var()))
|
|
tout << lit << " " << *a.get_ineq(lit.var()) << "\n";);
|
|
if (c.m_num_trues > 0 && num_true == 0)
|
|
score -= c.m_weight;
|
|
else if (c.m_num_trues == 0 && num_true > 0)
|
|
score += c.m_weight;
|
|
}
|
|
|
|
// verbose_stream() << num_clauses << " " << num_dup << "\n";
|
|
// revert the update
|
|
TRACE(arith, tout << "revert update v" << v << " -= " << delta << "\n";);
|
|
a.update_unchecked(v, vi.value() - delta);
|
|
return score;
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::check_restart() {
|
|
|
|
if (m_no_improve <= 500000)
|
|
return;
|
|
#if 0
|
|
if (a.m_stats.m_steps < a.m_config.restart_next)
|
|
return;
|
|
|
|
|
|
++a.m_stats.m_restarts;
|
|
a.m_config.restart_next = std::max(a.m_config.restart_next, a.m_stats.m_steps);
|
|
|
|
a.m_config.restart_next += (2 * a.m_stats.m_restarts * a.m_config.restart_base)/3;
|
|
#endif
|
|
|
|
IF_VERBOSE(2, verbose_stream() << "restart sls-arith " << a.m_config.restart_next << "\n");
|
|
TRACE(arith, tout << "restart\n";);
|
|
// reset values of (arithmetical) variables at bounds.
|
|
for (auto& vi : a.m_vars) {
|
|
if (vi.m_lo && !vi.m_lo->is_strict && vi.m_lo->value > 0)
|
|
vi.set_value(vi.m_lo->value);
|
|
else if (vi.m_hi && !vi.m_hi->is_strict && vi.m_hi->value < 0)
|
|
vi.set_value(vi.m_hi->value);
|
|
else
|
|
vi.set_value(num_t(0));
|
|
}
|
|
initialize();
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::initialize() {
|
|
for (sat::bool_var v = 0; v < ctx.num_bool_vars(); ++v)
|
|
a.init_bool_var_assignment(v);
|
|
|
|
DEBUG_CODE(
|
|
for (sat::bool_var bv = 0; bv < ctx.num_bool_vars(); ++bv) {
|
|
if (a.get_ineq(bv) && a.get_ineq(bv)->is_true() != ctx.is_true(bv)) {
|
|
TRACE(arith, tout << "bv:" << bv << " " << *a.get_ineq(bv) << ctx.is_true(bv) << "\n";
|
|
tout << mk_bounded_pp(ctx.atom(bv), a.m) << "\n";
|
|
ctx.display(tout););
|
|
}
|
|
VERIFY(!a.get_ineq(bv) || a.get_ineq(bv)->is_true() == ctx.is_true(bv));
|
|
});
|
|
|
|
m_best_found_cost_bool = ctx.unsat().size();
|
|
m_best_found_cost_arith = ctx.unsat().size();
|
|
m_best_found_cost_restart = ctx.unsat().size();
|
|
m_no_improve = 0;
|
|
m_no_improve_bool = 0;
|
|
m_no_improve_arith = 0;
|
|
for (; m_num_clauses < ctx.clauses().size(); ++m_num_clauses) {
|
|
auto const& c = ctx.get_clause(m_num_clauses);
|
|
for (auto lit : c) {
|
|
auto bv = lit.var();
|
|
if (a.get_ineq(bv))
|
|
a.initialize_clauses_of(bv, m_num_clauses);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template<typename num_t>
|
|
bool arith_clausal<num_t>::update_outer_best_solution() {
|
|
if (ctx.unsat().size() >= m_best_found_cost_bool)
|
|
return false;
|
|
m_best_found_cost_bool = ctx.unsat().size();
|
|
return true;
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::enter_bool_mode() {
|
|
CTRACE(arith, !m_bool_mode, tout << "enter bool mode\n";);
|
|
m_best_found_cost_bool = ctx.unsat().size();
|
|
if (!m_bool_mode)
|
|
m_no_improve_bool = 0;
|
|
m_bool_mode = true;
|
|
}
|
|
|
|
template<typename num_t>
|
|
bool arith_clausal<num_t>::update_inner_best_solution() {
|
|
if (ctx.unsat().size() >= m_best_found_cost_arith)
|
|
return false;
|
|
m_best_found_cost_arith = ctx.unsat().size();
|
|
return true;
|
|
}
|
|
|
|
template<typename num_t>
|
|
void arith_clausal<num_t>::enter_arith_mode() {
|
|
CTRACE(arith, m_bool_mode, tout << "enter arith mode\n";);
|
|
m_best_found_cost_arith = ctx.unsat().size();
|
|
if (m_bool_mode)
|
|
m_no_improve_arith = 0;
|
|
m_bool_mode = false;
|
|
}
|
|
|
|
template<typename num_t>
|
|
bool arith_clausal<num_t>::update_best_solution() {
|
|
bool improved = false;
|
|
if (ctx.unsat().size() < m_best_found_cost_restart) {
|
|
improved = true;
|
|
m_best_found_cost_restart = ctx.unsat().size();
|
|
}
|
|
if (ctx.unsat().size() < m_best_found_cost) {
|
|
improved = true;
|
|
m_best_found_cost = ctx.unsat().size();
|
|
}
|
|
return improved;
|
|
}
|
|
}
|
|
|
|
template class sls::arith_clausal<checked_int64<true>>;
|
|
template class sls::arith_clausal<rational>;
|
|
|