3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-05 14:56:11 +00:00

Parallel tactic (#9824) (#9825)

Add new parallel algorithm as a tactic (parallel_tactical2.cpp)
Don't port over old experiments from smt_parallel that we aren't using
(sls, inprocessing, failed_literal_mode for bb detection)
Fix bugs: lease cancellation/reslimit race condition, involves changing
lease epoch to simple boolean flag
Also, now there is a single shared set of params for the tactic and
smt_parallel

**Test runs for the parallel_tactical2 vs old smt_parallel version:**
run-2747-Z3-threads-4-qflia-30s-stats.md
run-2746-Z3-threads-4-qflia-30s-parallel_tactic-stats.md
run-2745-Z3-threads-1-qfbv-30s-stats.md
run-3013-Z3-threads-4-qfbv-30s-parallel_tactic-stats.md --> note this is
indeed run-3013, I reran after a bugfix in inc_sat_solver
run-2743-Z3-threads-4-qfnia-30s-stats.md
run-2742-Z3-threads-4-qfnia-30s-parallel_tactic-stats.md

**Test runs for the new smt_parallel with bugfixes:**
run-2801-Z3-threads-4-qflia-30s-smtparallel-bugfixes-stats.md,
run-2800-Z3-threads-4-qflia-30s-smtparallel-bugfixes-stats.md
run-2797-Z3-threads-4-qfnia-30s-smtparallel-bugfixes-stats.md
compare to old smt_parallel:
run-2747-Z3-threads-4-qflia-30s-stats.md
run-2743-Z3-threads-4-qfnia-30s-stats.md

Note that there is a slight regression on lia in run-2800. The source of
this appears to be the new new LP largest-cube LIA heuristic param,
which is enabled by default. disabling this param in run-2801 restored
performance (I didn't change this in this PR though, just something to
note)

http://mtzguido.tplinkdns.com:8081/z3/compare_stats.html

---------

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Co-authored-by: Ilana Shapiro <ilanashapiro@Ilanas-MacBook-Pro.local>
Co-authored-by: Ilana Shapiro <ilanashapiro@Ilanas-MBP.localdomain>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Nikolaj Bjorner 2026-06-26 09:36:15 -07:00 committed by GitHub
parent 15f33f458d
commit 612fab1c9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 2694 additions and 1796 deletions

View file

@ -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);

View file

@ -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; }

View file

@ -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);
}

View file

@ -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;

View file

@ -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.

View file

@ -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();

View file

@ -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();