mirror of
https://github.com/Z3Prover/z3
synced 2026-06-27 19:08:49 +00:00
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:
parent
15f33f458d
commit
612fab1c9a
29 changed files with 2694 additions and 1796 deletions
|
|
@ -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'),
|
||||
))
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -118,6 +118,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();
|
||||
|
|
@ -3652,6 +3656,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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -292,6 +294,10 @@ namespace smt {
|
|||
return m_fparams;
|
||||
}
|
||||
|
||||
smt_params const& get_fparams() const {
|
||||
return m_fparams;
|
||||
}
|
||||
|
||||
params_ref const & get_params() {
|
||||
return m_params;
|
||||
}
|
||||
|
|
@ -452,6 +458,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; }
|
||||
|
||||
|
|
@ -538,6 +546,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;
|
||||
}
|
||||
|
|
@ -1697,6 +1707,8 @@ namespace smt {
|
|||
|
||||
lbool setup_and_check(bool reset_cancel = true);
|
||||
|
||||
void setup_for_parallel();
|
||||
|
||||
void reduce_assertions();
|
||||
|
||||
bool resource_limits_exceeded();
|
||||
|
|
@ -1913,5 +1925,3 @@ namespace smt {
|
|||
std::ostream& operator<<(std::ostream& out, enode_pp const& p);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -280,10 +280,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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,22 +763,25 @@ 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();
|
||||
|
|
@ -790,7 +793,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 +827,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 +855,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 +888,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 +1106,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 +1159,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 +1167,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 +1312,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 +1366,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 +1393,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 +1409,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 +1453,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) {
|
||||
|
|
@ -1745,7 +1810,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 +1843,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,19 +1859,14 @@ 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();
|
||||
parallel_params pp(ctx.m_params);
|
||||
unsigned num_global_bb_batch_threads = pp.num_bb_threads();
|
||||
if (num_global_bb_batch_threads > 2)
|
||||
throw default_exception("smt_parallel.num_global_bb_batch_threads must be 0, 1, or 2");
|
||||
throw default_exception("parallel.num_bb_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);
|
||||
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 num_global_bb_threads = num_global_bb_batch_threads;
|
||||
unsigned total_threads = num_workers + num_sls_threads + num_core_min_threads + num_global_bb_threads;
|
||||
|
||||
IF_VERBOSE(1, verbose_stream() << "Parallel SMT with " << total_threads << " threads\n";);
|
||||
|
|
@ -1856,18 +1916,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
|
||||
|
|
|
|||
|
|
@ -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()); }
|
||||
};
|
||||
|
||||
|
|
@ -145,7 +152,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 +182,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 +200,7 @@ namespace smt {
|
|||
|
||||
void set_unsat(ast_translation& l2g, expr_ref_vector const& unsat_core);
|
||||
void set_sat(ast_translation& l2g, model& m);
|
||||
void set_canceled();
|
||||
void set_exception(std::string const& msg);
|
||||
void set_exception(unsigned error_code);
|
||||
void collect_statistics(::statistics& st) const;
|
||||
|
|
@ -210,14 +224,14 @@ 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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,23 +1,25 @@
|
|||
/*++
|
||||
Copyright (c) 2017 Microsoft Corporation
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
parallel_tactic.h
|
||||
parallel_tactical.h
|
||||
|
||||
Abstract:
|
||||
|
||||
Parallel tactic in the style of Treengeling.
|
||||
Parallel portfolio solver using the solver API.
|
||||
Models the internals after smt/smt_parallel.cpp but operates
|
||||
on generic solver objects instead of smt::context.
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2017-10-9
|
||||
|
||||
(based on smt_parallel.cpp and parallel_tactical.cpp)
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
class tactic;
|
||||
class solver;
|
||||
class params_ref;
|
||||
|
||||
tactic * mk_parallel_tactic(solver* s, params_ref const& p);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,903 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
parallel_tactical2.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Parallel portfolio solver using the solver API.
|
||||
|
||||
Models the internals after smt/smt_parallel.cpp but operates on generic
|
||||
solver objects (smt_solver, inc_sat_solver, etc.) via the solver interface
|
||||
instead of accessing smt::context internals directly.
|
||||
|
||||
Key features compared to parallel_tactical.cpp:
|
||||
- Search tree for coordinated non-chronological backtracking (from smt_parallel).
|
||||
- Shared clause pool: learned conflict clauses are broadcast to all workers.
|
||||
- Shared backbone/unit pool: base-level units propagated by one worker are
|
||||
asserted as facts on every other worker's solver.
|
||||
- Workers reuse their solver state across multiple cube checks, accumulating
|
||||
learned clauses (same pattern as smt_parallel workers).
|
||||
|
||||
Key differences from smt_parallel:
|
||||
- Uses the solver API throughout (translate, check_sat, get_trail, cube,
|
||||
get_model, get_unsat_core, assert_expr, push, pop, updt_params, …)
|
||||
rather than accessing smt::context members directly.
|
||||
- Works with any conforming solver implementation.
|
||||
|
||||
Cube path management follows the assumption-based pattern from smt_parallel:
|
||||
- The worker's solver base assertion set is fixed at construction (the full
|
||||
problem is translated into the worker's own ast_manager once).
|
||||
- Shared clauses discovered by other workers are appended to the base set via
|
||||
assert_expr at any time.
|
||||
- The current cube path is passed as extra assumptions on every check_sat call,
|
||||
so the solver can reuse learned clauses across different cube checks.
|
||||
|
||||
Split atom selection is performed by temporarily pushing the cube path onto
|
||||
the solver, calling solver::cube(), retrieving the first proposed literal, and
|
||||
then popping, so that the base state is preserved.
|
||||
|
||||
Author:
|
||||
|
||||
(based on smt_parallel.cpp by nbjorner / Ilana Shapiro, and
|
||||
parallel_tactical.cpp by nbjorner / Miguel Neves)
|
||||
|
||||
--*/
|
||||
|
||||
#include "util/scoped_ptr_vector.h"
|
||||
#include "util/uint_set.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/ast_util.h"
|
||||
#include "ast/ast_translation.h"
|
||||
#include "solver/solver.h"
|
||||
#include "solver/parallel_tactical2.h"
|
||||
#include "solver/parallel_params.hpp"
|
||||
#include "solver/solver_preprocess.h"
|
||||
#include "util/search_tree.h"
|
||||
#include "tactic/tactic.h"
|
||||
#include "tactic/tactical.h"
|
||||
#include "solver/solver2tactic.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Single-threaded stub */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
class non_parallel_tactic2 : public tactic {
|
||||
public:
|
||||
non_parallel_tactic2(solver*, params_ref const&) {}
|
||||
char const* name() const override { return "parallel_tactic2"; }
|
||||
void operator()(const goal_ref&, goal_ref_buffer&) override {
|
||||
throw default_exception("parallel_tactic2 is disabled in single-threaded mode");
|
||||
}
|
||||
tactic* translate(ast_manager&) override { return nullptr; }
|
||||
void cleanup() override {}
|
||||
};
|
||||
|
||||
#ifdef SINGLE_THREAD
|
||||
|
||||
tactic* mk_parallel_tactic2(solver* s, params_ref const& p) {
|
||||
return alloc(non_parallel_tactic2, s, p);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Search-tree literal configuration */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
struct solver_cube_config {
|
||||
using literal = expr_ref;
|
||||
static bool literal_is_null(expr_ref const& l) { return l == nullptr; }
|
||||
static std::ostream& display_literal(std::ostream& out, expr_ref const& l) {
|
||||
if (l) return out << mk_bounded_pp(l, l.get_manager());
|
||||
return out << "(null)";
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* parallel_solver – the core portfolio engine */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
class parallel_solver {
|
||||
|
||||
/* ---- forward declarations ---- */
|
||||
class worker;
|
||||
|
||||
/* ---- node lease (mirrors smt_parallel) ---- */
|
||||
struct node_lease {
|
||||
search_tree::node<solver_cube_config>* leased_node = nullptr;
|
||||
unsigned cancel_epoch = 0;
|
||||
bool cancel_signaled = false;
|
||||
};
|
||||
|
||||
/* ---- shared clause entry ---- */
|
||||
struct shared_clause {
|
||||
unsigned source_worker_id;
|
||||
expr_ref clause;
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* batch_manager
|
||||
* Coordinates workers: distributes cubes, collects clauses/units,
|
||||
* stores the final result (sat model / unsat core / exception).
|
||||
* ================================================================ */
|
||||
class batch_manager {
|
||||
|
||||
enum state {
|
||||
is_running,
|
||||
is_sat,
|
||||
is_unsat,
|
||||
is_exception_msg,
|
||||
is_exception_code
|
||||
};
|
||||
|
||||
struct stats {
|
||||
unsigned m_num_cubes = 0;
|
||||
unsigned m_max_cube_depth = 0;
|
||||
unsigned m_backbones_found = 0;
|
||||
};
|
||||
|
||||
ast_manager& m;
|
||||
parallel_solver& p;
|
||||
std::mutex mux;
|
||||
state m_state = state::is_running;
|
||||
stats m_stats;
|
||||
|
||||
search_tree::tree<solver_cube_config> m_search_tree;
|
||||
vector<node_lease> m_worker_leases;
|
||||
|
||||
/* shared clause pool (guarded by mux) */
|
||||
vector<shared_clause> m_shared_clause_trail;
|
||||
obj_hashtable<expr> m_shared_clause_set;
|
||||
|
||||
/* shared backbone / unit pool (guarded by mux) */
|
||||
obj_hashtable<expr> m_global_backbones;
|
||||
|
||||
/* result storage (guarded by mux) */
|
||||
unsigned m_exception_code = 0;
|
||||
std::string m_exception_msg;
|
||||
model_ref m_model; /* sat model translated to m */
|
||||
expr_ref_vector m_unsat_core; /* unsat core translated to m */
|
||||
|
||||
/* ---- cancellation helpers (called under mux) ---- */
|
||||
void cancel_workers_unlocked() {
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: canceling workers\n");
|
||||
for (auto* w : p.m_workers)
|
||||
w->cancel();
|
||||
}
|
||||
|
||||
void release_lease_unlocked(unsigned worker_id,
|
||||
search_tree::node<solver_cube_config>* n) {
|
||||
if (worker_id >= m_worker_leases.size()) return;
|
||||
auto& lease = m_worker_leases[worker_id];
|
||||
if (!lease.leased_node || lease.leased_node != n) return;
|
||||
m_search_tree.dec_active_workers(lease.leased_node);
|
||||
lease = {};
|
||||
}
|
||||
|
||||
void cancel_closed_leases_unlocked(unsigned source_worker_id) {
|
||||
unsigned n = std::min(m_worker_leases.size(), p.m_workers.size());
|
||||
for (unsigned id = 0; id < n; ++id) {
|
||||
if (id == source_worker_id) continue;
|
||||
auto const& lease = m_worker_leases[id];
|
||||
if (lease.leased_node && !lease.cancel_signaled &&
|
||||
m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch)) {
|
||||
p.m_workers[id]->cancel_lease();
|
||||
m_worker_leases[id].cancel_signaled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collect_clause_unlocked(ast_translation& l2g,
|
||||
unsigned source_worker_id,
|
||||
expr* clause) {
|
||||
expr* g_clause = l2g(clause);
|
||||
if (!m_shared_clause_set.contains(g_clause)) {
|
||||
m_shared_clause_set.insert(g_clause);
|
||||
shared_clause sc{source_worker_id, expr_ref(g_clause, m)};
|
||||
m_shared_clause_trail.push_back(std::move(sc));
|
||||
}
|
||||
}
|
||||
|
||||
bool is_global_backbone_unlocked(ast_translation& l2g,
|
||||
expr* bb_cand) {
|
||||
expr_ref cand(l2g(bb_cand), m);
|
||||
return m_global_backbones.contains(cand.get());
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
batch_manager(ast_manager& m, parallel_solver& p)
|
||||
: m(m), p(p),
|
||||
m_search_tree(expr_ref(m)),
|
||||
m_unsat_core(m) {}
|
||||
|
||||
/* ---- initialisation ---- */
|
||||
void initialize(unsigned num_workers,
|
||||
unsigned initial_max_thread_conflicts = 1000) {
|
||||
m_state = state::is_running;
|
||||
m_search_tree.reset();
|
||||
m_search_tree.set_effort_unit(initial_max_thread_conflicts);
|
||||
m_worker_leases.reset();
|
||||
m_worker_leases.resize(num_workers);
|
||||
m_shared_clause_trail.reset();
|
||||
m_shared_clause_set.reset();
|
||||
m_global_backbones.reset();
|
||||
m_model = nullptr;
|
||||
m_unsat_core.reset();
|
||||
}
|
||||
|
||||
/* ---- result setters (called by workers, guarded by mux) ---- */
|
||||
void set_sat(ast_translation& l2g, model& mdl) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: batch_manager SAT\n");
|
||||
if (m_state != state::is_running) return;
|
||||
m_state = state::is_sat;
|
||||
m_model = mdl.translate(l2g);
|
||||
cancel_workers_unlocked();
|
||||
}
|
||||
|
||||
void set_unsat(ast_translation& l2g,
|
||||
expr_ref_vector const& core) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: batch_manager UNSAT\n");
|
||||
if (m_state != state::is_running) return;
|
||||
m_state = state::is_unsat;
|
||||
SASSERT(m_unsat_core.empty());
|
||||
for (expr* c : core)
|
||||
m_unsat_core.push_back(l2g(c));
|
||||
cancel_workers_unlocked();
|
||||
}
|
||||
|
||||
void set_exception(std::string const& msg) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: batch_manager exception: " << msg << "\n");
|
||||
if (m_state != state::is_running) return;
|
||||
m_state = state::is_exception_msg;
|
||||
m_exception_msg = msg;
|
||||
cancel_workers_unlocked();
|
||||
}
|
||||
|
||||
void set_exception(unsigned error_code) {
|
||||
std::scoped_lock lock(mux);
|
||||
if (m_state != state::is_running) return;
|
||||
m_state = state::is_exception_code;
|
||||
m_exception_code = error_code;
|
||||
cancel_workers_unlocked();
|
||||
}
|
||||
|
||||
/* ---- cube distribution (called by workers) ---- */
|
||||
bool get_cube(ast_translation& g2l, unsigned id,
|
||||
expr_ref_vector& cube, bool is_first_run,
|
||||
node_lease& lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
cube.reset();
|
||||
if (m_search_tree.is_closed()) return false;
|
||||
if (m_state != state::is_running) return false;
|
||||
|
||||
auto* t = is_first_run
|
||||
? m_search_tree.activate_root()
|
||||
: m_search_tree.activate_best_node();
|
||||
if (!t) return false;
|
||||
|
||||
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;
|
||||
|
||||
/* build cube from path root → t */
|
||||
for (auto* cur = t; cur; cur = cur->parent()) {
|
||||
if (solver_cube_config::literal_is_null(cur->get_literal()))
|
||||
break;
|
||||
cube.push_back(expr_ref(g2l(cur->get_literal().get()), g2l.to()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---- backtrack on conflict (called by workers) ---- */
|
||||
void backtrack(ast_translation& l2g, unsigned worker_id,
|
||||
expr_ref_vector const& core,
|
||||
node_lease const& lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
if (m_state != state::is_running) return;
|
||||
|
||||
vector<solver_cube_config::literal> g_core;
|
||||
for (auto c : core)
|
||||
g_core.push_back(expr_ref(l2g(c), m));
|
||||
|
||||
if (!m_search_tree.is_lease_canceled(
|
||||
lease.leased_node, lease.cancel_epoch)) {
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
m_search_tree.backtrack(lease.leased_node, g_core);
|
||||
}
|
||||
|
||||
cancel_closed_leases_unlocked(worker_id);
|
||||
|
||||
IF_VERBOSE(2, m_search_tree.display(verbose_stream() << "\n"););
|
||||
|
||||
if (m_search_tree.is_closed()) {
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: search tree closed → UNSAT\n");
|
||||
m_state = state::is_unsat;
|
||||
for (auto& e : m_search_tree.get_core_from_root())
|
||||
m_unsat_core.push_back(e.get());
|
||||
cancel_workers_unlocked();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- try to split (called on undef) ---- */
|
||||
void try_split(ast_translation& l2g, unsigned worker_id,
|
||||
node_lease const& 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)) 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);
|
||||
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
|
||||
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);
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: split on "
|
||||
<< mk_bounded_pp(lit, m, 3) << "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void release_lease(unsigned worker_id, node_lease const& lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
}
|
||||
|
||||
bool 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);
|
||||
}
|
||||
|
||||
/* ---- clause sharing ---- */
|
||||
void collect_clause(ast_translation& l2g,
|
||||
unsigned source_worker_id,
|
||||
expr* clause) {
|
||||
std::scoped_lock lock(mux);
|
||||
collect_clause_unlocked(l2g, source_worker_id, clause);
|
||||
}
|
||||
|
||||
expr_ref_vector return_shared_clauses(ast_translation& g2l,
|
||||
unsigned& worker_limit,
|
||||
unsigned worker_id) {
|
||||
std::scoped_lock lock(mux);
|
||||
expr_ref_vector result(g2l.to());
|
||||
for (unsigned i = worker_limit; i < m_shared_clause_trail.size(); ++i) {
|
||||
if (m_shared_clause_trail[i].source_worker_id != worker_id)
|
||||
result.push_back(g2l(m_shared_clause_trail[i].clause.get()));
|
||||
}
|
||||
worker_limit = m_shared_clause_trail.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ---- backbone / unit sharing ---- */
|
||||
bool collect_global_backbone(ast_translation& l2g,
|
||||
expr_ref const& backbone,
|
||||
unsigned source_worker_id = UINT_MAX) {
|
||||
std::scoped_lock lock(mux);
|
||||
if (is_global_backbone_unlocked(l2g, backbone.get()))
|
||||
return false;
|
||||
expr_ref g_bb(l2g(backbone.get()), m);
|
||||
m_global_backbones.insert(g_bb.get());
|
||||
++m_stats.m_backbones_found;
|
||||
IF_VERBOSE(2, verbose_stream() << "par2: new backbone "
|
||||
<< mk_bounded_pp(g_bb, m, 3) << "\n");
|
||||
/* share it as a unit clause so other workers pick it up */
|
||||
collect_clause_unlocked(l2g, source_worker_id, backbone.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---- result accessors ---- */
|
||||
lbool get_result() const {
|
||||
if (m.limit().is_canceled()) return l_undef;
|
||||
switch (m_state) {
|
||||
case state::is_running:
|
||||
throw default_exception("par2: inconsistent end state");
|
||||
case state::is_sat: return l_true;
|
||||
case state::is_unsat: return l_false;
|
||||
case state::is_exception_msg:
|
||||
throw default_exception(m_exception_msg.c_str());
|
||||
case state::is_exception_code:
|
||||
throw z3_error(m_exception_code);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return l_undef;
|
||||
}
|
||||
}
|
||||
|
||||
model_ref& get_model() { return m_model; }
|
||||
|
||||
expr_ref_vector const& get_unsat_core() const { return m_unsat_core; }
|
||||
|
||||
void collect_statistics(statistics& st) const {
|
||||
st.update("par2-cubes", m_stats.m_num_cubes);
|
||||
st.update("par2-cube-depth", m_stats.m_max_cube_depth);
|
||||
st.update("par2-backbones", m_stats.m_backbones_found);
|
||||
}
|
||||
}; // class batch_manager
|
||||
|
||||
/* ================================================================
|
||||
* worker
|
||||
* Each worker owns a translated copy of the original solver plus
|
||||
* its own ast_manager. Workers communicate only through the
|
||||
* batch_manager (mutex-protected).
|
||||
* ================================================================ */
|
||||
class worker {
|
||||
struct config {
|
||||
unsigned m_threads_max_conflicts = 1000;
|
||||
double m_max_conflict_mul = 1.5;
|
||||
unsigned m_max_conflicts = UINT_MAX;
|
||||
bool m_share_units = true;
|
||||
bool m_share_conflicts = true;
|
||||
unsigned m_max_cube_depth = 20;
|
||||
};
|
||||
|
||||
unsigned id;
|
||||
batch_manager& b;
|
||||
ast_manager m; /* worker-local manager */
|
||||
ref<solver> s; /* translated solver copy */
|
||||
expr_ref_vector asms; /* translated assumptions */
|
||||
ast_translation m_g2l, m_l2g; /* global↔local translations */
|
||||
config m_config;
|
||||
expr_mark m_known_units; /* units already shared by this worker */
|
||||
unsigned m_shared_clause_limit = 0;
|
||||
|
||||
void update_max_conflicts() {
|
||||
m_config.m_threads_max_conflicts = static_cast<unsigned>(
|
||||
m_config.m_max_conflict_mul * m_config.m_threads_max_conflicts);
|
||||
/* cap at the configured global maximum to prevent runaway cube checks */
|
||||
if (m_config.m_threads_max_conflicts > m_config.m_max_conflicts)
|
||||
m_config.m_threads_max_conflicts = m_config.m_max_conflicts;
|
||||
}
|
||||
|
||||
/* Check the current cube (passed as additional assumptions).
|
||||
* The solver's conflict budget is set via updt_params before
|
||||
* each call so that long-running cubes are interrupted. */
|
||||
lbool check_cube(expr_ref_vector const& cube) {
|
||||
params_ref p;
|
||||
p.set_uint("max_conflicts",
|
||||
std::min(m_config.m_threads_max_conflicts,
|
||||
m_config.m_max_conflicts));
|
||||
s->updt_params(p);
|
||||
|
||||
expr_ref_vector combined(m);
|
||||
combined.append(asms);
|
||||
combined.append(cube);
|
||||
|
||||
IF_VERBOSE(2, verbose_stream() << "par2 worker " << id
|
||||
<< ": checking cube of size " << cube.size() << "\n");
|
||||
lbool r = l_undef;
|
||||
try {
|
||||
r = s->check_sat(combined);
|
||||
}
|
||||
catch (z3_error& err) {
|
||||
if (!m.limit().is_canceled())
|
||||
b.set_exception(err.error_code());
|
||||
}
|
||||
catch (z3_exception& ex) {
|
||||
if (!m.limit().is_canceled())
|
||||
b.set_exception(ex.what());
|
||||
}
|
||||
IF_VERBOSE(2, verbose_stream() << "par2 worker " << id
|
||||
<< ": cube result " << r << "\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Assert shared clauses discovered by other workers into the
|
||||
* base assertion set of this worker's solver. The solver
|
||||
* automatically re-uses them on the next check_sat call. */
|
||||
void collect_shared_clauses() {
|
||||
expr_ref_vector nc = b.return_shared_clauses(
|
||||
m_g2l, m_shared_clause_limit, id);
|
||||
for (expr* e : nc) {
|
||||
IF_VERBOSE(4, verbose_stream() << "par2 worker " << id
|
||||
<< ": asserting shared clause "
|
||||
<< mk_bounded_pp(e, m, 3) << "\n");
|
||||
s->assert_expr(e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Propagate any new base-level units (backbone literals) this
|
||||
* worker has learned to the shared backbone pool.
|
||||
*
|
||||
* Uses solver::get_trail(0) which returns all literals
|
||||
* propagated at decision level 0. */
|
||||
void share_units() {
|
||||
if (!m_config.m_share_units) return;
|
||||
expr_ref_vector trail = s->get_trail(0);
|
||||
for (expr* e : trail) {
|
||||
/* get_trail may include ground terms; skip complex ones */
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
if (!is_uninterp_const(atom)) continue;
|
||||
if (m_known_units.is_marked(e)) continue;
|
||||
m_known_units.mark(e);
|
||||
expr_ref lit(e, m);
|
||||
b.collect_global_backbone(m_l2g, lit, id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Select a split atom using solver::cube() on a temporary
|
||||
* solver state that includes the current cube path.
|
||||
*
|
||||
* We push the cube literals, call cube(), take the first
|
||||
* literal, then pop to restore the base state. */
|
||||
expr_ref get_split_atom(expr_ref_vector const& cube) {
|
||||
if (cube.size() >= m_config.m_max_cube_depth)
|
||||
return expr_ref(nullptr, m);
|
||||
|
||||
s->push();
|
||||
for (expr* c : cube)
|
||||
s->assert_expr(c);
|
||||
|
||||
expr_ref_vector vars(m);
|
||||
expr_ref_vector c = s->cube(vars, UINT_MAX);
|
||||
|
||||
s->pop(1);
|
||||
|
||||
/* solver::cube() convention: an empty result means done; a result
|
||||
* whose last element is true means the problem is trivially sat;
|
||||
* a result whose last element is false means unsat was detected.
|
||||
* In all other cases every element (including index 0) is a
|
||||
* valid literal that can serve as a split atom. */
|
||||
if (c.empty() || m.is_true(c.back()) || m.is_false(c.back()))
|
||||
return expr_ref(nullptr, m);
|
||||
|
||||
return expr_ref(c.get(0), m);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
worker(unsigned id, parallel_solver& p,
|
||||
solver& src, params_ref const& params,
|
||||
expr_ref_vector const& src_asms)
|
||||
: id(id), b(p.m_batch_manager),
|
||||
asms(m), m_g2l(src.get_manager(), m), m_l2g(m, src.get_manager())
|
||||
{
|
||||
/* create translated solver copy */
|
||||
s = src.translate(m, params);
|
||||
|
||||
/* translate assumptions */
|
||||
for (expr* a : src_asms)
|
||||
asms.push_back(m_g2l(a));
|
||||
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: worker " << id
|
||||
<< " created (" << asms.size() << " assumptions)\n");
|
||||
}
|
||||
|
||||
void run() {
|
||||
bool is_first_run = true;
|
||||
node_lease lease;
|
||||
expr_ref_vector cube(m);
|
||||
|
||||
while (true) {
|
||||
if (!b.get_cube(m_g2l, id, cube, is_first_run, lease)) {
|
||||
IF_VERBOSE(1, verbose_stream() << "par2 worker " << id
|
||||
<< ": no more cubes\n");
|
||||
return;
|
||||
}
|
||||
is_first_run = false;
|
||||
|
||||
collect_shared_clauses();
|
||||
|
||||
lbool r = check_cube(cube);
|
||||
|
||||
if (b.lease_canceled(lease)) {
|
||||
IF_VERBOSE(1, verbose_stream() << "par2 worker " << id
|
||||
<< ": lease canceled\n");
|
||||
lease = {};
|
||||
m.limit().dec_cancel();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m.inc()) return;
|
||||
|
||||
switch (r) {
|
||||
|
||||
case l_undef: {
|
||||
update_max_conflicts();
|
||||
IF_VERBOSE(1, verbose_stream() << "par2 worker " << id
|
||||
<< ": undef – attempting split\n");
|
||||
expr_ref atom = get_split_atom(cube);
|
||||
if (atom) {
|
||||
b.try_split(m_l2g, id, lease, atom.get(),
|
||||
m_config.m_threads_max_conflicts);
|
||||
}
|
||||
else {
|
||||
b.release_lease(id, lease);
|
||||
}
|
||||
if (m_config.m_share_units) share_units();
|
||||
break;
|
||||
}
|
||||
|
||||
case l_true: {
|
||||
IF_VERBOSE(1, verbose_stream() << "par2 worker " << id
|
||||
<< ": SAT\n");
|
||||
model_ref mdl;
|
||||
s->get_model(mdl);
|
||||
if (mdl)
|
||||
b.set_sat(m_l2g, *mdl);
|
||||
return;
|
||||
}
|
||||
|
||||
case l_false: {
|
||||
IF_VERBOSE(1, verbose_stream() << "par2 worker " << id
|
||||
<< ": UNSAT cube\n");
|
||||
expr_ref_vector core(m);
|
||||
s->get_unsat_core(core);
|
||||
|
||||
/* Filter to only cube literals (exclude base assumptions). */
|
||||
expr_ref_vector cube_core(m);
|
||||
for (expr* c : core) {
|
||||
if (cube.contains(c))
|
||||
cube_core.push_back(c);
|
||||
}
|
||||
|
||||
/* If core contains none of the cube lits, the whole
|
||||
* problem is UNSAT independent of the cube path. */
|
||||
if (cube_core.empty()) {
|
||||
b.set_unsat(m_l2g, core);
|
||||
return;
|
||||
}
|
||||
|
||||
b.backtrack(m_l2g, id, cube_core, lease);
|
||||
|
||||
if (m_config.m_share_conflicts) {
|
||||
/* Share the negation of the cube-core conjunction
|
||||
* as a learned clause: ¬(c₁ ∧ … ∧ cₙ) ≡ ¬c₁ ∨ … ∨ ¬cₙ */
|
||||
expr_ref_vector neg_lits(m);
|
||||
for (expr* c : cube_core)
|
||||
neg_lits.push_back(mk_not(expr_ref(c, m)));
|
||||
expr_ref clause(mk_or(neg_lits), m);
|
||||
b.collect_clause(m_l2g, id, clause.get());
|
||||
}
|
||||
if (m_config.m_share_units) share_units();
|
||||
break;
|
||||
}
|
||||
|
||||
} // switch
|
||||
} // while
|
||||
} // run()
|
||||
|
||||
void cancel() {
|
||||
m.limit().cancel();
|
||||
}
|
||||
|
||||
void cancel_lease() {
|
||||
m.limit().inc_cancel();
|
||||
}
|
||||
|
||||
void collect_statistics(statistics& st) const {
|
||||
s->collect_statistics(st);
|
||||
}
|
||||
|
||||
reslimit& limit() { return m.limit(); }
|
||||
}; // class worker
|
||||
|
||||
/* ---- members ---- */
|
||||
ref<solver> m_solver;
|
||||
ast_manager& m_manager;
|
||||
params_ref m_params;
|
||||
scoped_ptr_vector<worker> m_workers;
|
||||
batch_manager m_batch_manager;
|
||||
statistics m_stats;
|
||||
|
||||
public:
|
||||
|
||||
parallel_solver(solver* s, params_ref const& p)
|
||||
: m_solver(s),
|
||||
m_manager(s->get_manager()),
|
||||
m_params(p),
|
||||
m_batch_manager(s->get_manager(), *this) {}
|
||||
|
||||
/* Run the portfolio. Returns sat/unsat/undef.
|
||||
*
|
||||
* On sat: *mdl is populated (translated into m_manager).
|
||||
* On unsat: *core is populated (translated into m_manager).
|
||||
* asms: original external assumptions (in m_manager). */
|
||||
lbool solve(expr_ref_vector const& asms,
|
||||
model_ref& mdl,
|
||||
expr_ref_vector& core) {
|
||||
|
||||
parallel_params pp(m_params);
|
||||
unsigned num_threads = std::min(
|
||||
static_cast<unsigned>(std::thread::hardware_concurrency()),
|
||||
pp.threads_max());
|
||||
if (num_threads < 2) num_threads = 2;
|
||||
|
||||
IF_VERBOSE(1, verbose_stream() << "par2: launching " << num_threads
|
||||
<< " threads\n");
|
||||
|
||||
if (m_manager.has_trace_stream())
|
||||
throw default_exception(
|
||||
"parallel_tactic2 does not work with trace streams");
|
||||
|
||||
/* Build workers – each gets a translated solver copy. */
|
||||
m_workers.reset();
|
||||
scoped_limits sl(m_manager.limit());
|
||||
params_ref worker_params(m_params);
|
||||
worker_params.set_bool("override_incremental", true);
|
||||
|
||||
for (unsigned i = 0; i < num_threads; ++i) {
|
||||
auto* w = alloc(worker, i, *this, *m_solver, worker_params, asms);
|
||||
m_workers.push_back(w);
|
||||
sl.push_child(&(w->limit()));
|
||||
}
|
||||
|
||||
m_batch_manager.initialize(num_threads);
|
||||
|
||||
/* Launch threads. */
|
||||
vector<std::thread> threads;
|
||||
for (auto* w : m_workers)
|
||||
threads.push_back(std::thread([w]() { w->run(); }));
|
||||
|
||||
for (auto& t : threads)
|
||||
t.join();
|
||||
|
||||
/* Collect per-worker statistics. */
|
||||
for (auto* w : m_workers)
|
||||
w->collect_statistics(m_stats);
|
||||
m_batch_manager.collect_statistics(m_stats);
|
||||
|
||||
m_manager.limit().reset_cancel();
|
||||
|
||||
lbool result = m_batch_manager.get_result();
|
||||
|
||||
if (result == l_true)
|
||||
mdl = m_batch_manager.get_model();
|
||||
|
||||
if (result == l_false) {
|
||||
for (expr* c : m_batch_manager.get_unsat_core())
|
||||
core.push_back(c);
|
||||
}
|
||||
|
||||
m_workers.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
void collect_statistics(statistics& st) const {
|
||||
st.copy(m_stats);
|
||||
}
|
||||
|
||||
void reset_statistics() {
|
||||
m_stats.reset();
|
||||
}
|
||||
}; // class parallel_solver
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* parallel_tactic2 – wraps parallel_solver as a tactic */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
class parallel_tactic2 : public tactic {
|
||||
|
||||
solver_ref m_solver;
|
||||
ast_manager& m_manager;
|
||||
params_ref m_params;
|
||||
statistics m_stats;
|
||||
|
||||
public:
|
||||
|
||||
parallel_tactic2(solver* s, params_ref const& p)
|
||||
: m_solver(s), m_manager(s->get_manager()), m_params(p) {}
|
||||
|
||||
char const* name() const override { return "parallel_tactic2"; }
|
||||
|
||||
void operator()(const goal_ref& g, goal_ref_buffer& result) override {
|
||||
fail_if_proof_generation("parallel_tactic2", g);
|
||||
ast_manager& m = g->m();
|
||||
|
||||
if (m.has_trace_stream())
|
||||
throw default_exception(
|
||||
"parallel_tactic2 does not work with trace streams");
|
||||
|
||||
/* Translate goal into a set of clauses + assumptions. */
|
||||
solver* s = m_solver->translate(m, m_params);
|
||||
expr_ref_vector clauses(m);
|
||||
ptr_vector<expr> assumptions_raw;
|
||||
obj_map<expr, expr*> bool2dep;
|
||||
ref<generic_model_converter> fmc;
|
||||
extract_clauses_and_dependencies(g, clauses, assumptions_raw,
|
||||
bool2dep, fmc);
|
||||
for (expr* cl : clauses)
|
||||
s->assert_expr(cl);
|
||||
|
||||
expr_ref_vector asms(m);
|
||||
asms.append(assumptions_raw.size(), assumptions_raw.data());
|
||||
|
||||
parallel_solver ps(s, m_params);
|
||||
|
||||
model_ref mdl;
|
||||
expr_ref_vector core(m);
|
||||
lbool is_sat = ps.solve(asms, mdl, core);
|
||||
|
||||
ps.collect_statistics(m_stats);
|
||||
|
||||
switch (is_sat) {
|
||||
case l_true:
|
||||
g->reset();
|
||||
if (g->models_enabled() && mdl) {
|
||||
if (fmc)
|
||||
g->add(concat(fmc.get(), model2model_converter(mdl.get())));
|
||||
else
|
||||
g->add(model2model_converter(mdl.get()));
|
||||
}
|
||||
break;
|
||||
|
||||
case l_false: {
|
||||
SASSERT(!g->proofs_enabled());
|
||||
expr_dependency* lcore = nullptr;
|
||||
proof* pr = nullptr;
|
||||
if (!core.empty()) {
|
||||
for (expr* c : core) {
|
||||
expr* dep = nullptr;
|
||||
if (bool2dep.find(c, dep))
|
||||
lcore = m.mk_join(lcore, m.mk_leaf(dep));
|
||||
}
|
||||
}
|
||||
g->assert_expr(m.mk_false(), pr, lcore);
|
||||
break;
|
||||
}
|
||||
|
||||
case l_undef:
|
||||
if (!m.inc())
|
||||
throw tactic_exception(Z3_CANCELED_MSG);
|
||||
break;
|
||||
}
|
||||
|
||||
result.push_back(g.get());
|
||||
}
|
||||
|
||||
void cleanup() override {
|
||||
m_stats.reset();
|
||||
}
|
||||
|
||||
tactic* translate(ast_manager& m) override {
|
||||
solver* s = m_solver->translate(m, m_params);
|
||||
return alloc(parallel_tactic2, s, m_params);
|
||||
}
|
||||
|
||||
void updt_params(params_ref const& p) override {
|
||||
m_params.copy(p);
|
||||
}
|
||||
|
||||
void collect_statistics(statistics& st) const override {
|
||||
st.copy(m_stats);
|
||||
}
|
||||
|
||||
void reset_statistics() override {
|
||||
m_stats.reset();
|
||||
}
|
||||
}; // class parallel_tactic2
|
||||
|
||||
tactic* mk_parallel_tactic2(solver* s, params_ref const& p) {
|
||||
return alloc(parallel_tactic2, s, p);
|
||||
}
|
||||
|
||||
#endif /* !SINGLE_THREAD */
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
parallel_tactical2.h
|
||||
|
||||
Abstract:
|
||||
|
||||
Parallel portfolio solver using the solver API.
|
||||
Models the internals after smt/smt_parallel.cpp but operates
|
||||
on generic solver objects instead of smt::context.
|
||||
|
||||
Author:
|
||||
|
||||
(based on smt_parallel.cpp and parallel_tactical.cpp)
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
class tactic;
|
||||
class solver;
|
||||
class params_ref;
|
||||
|
||||
tactic * mk_parallel_tactic2(solver* s, params_ref const& p);
|
||||
|
|
@ -22,6 +22,7 @@ Notes:
|
|||
#include "solver/check_sat_result.h"
|
||||
#include "solver/progress_callback.h"
|
||||
#include "util/params.h"
|
||||
#include "util/sat_literal.h"
|
||||
|
||||
class solver;
|
||||
class model_converter;
|
||||
|
|
@ -58,6 +59,13 @@ class solver : public check_sat_result, public user_propagator::core {
|
|||
params_ref m_params;
|
||||
symbol m_cancel_backup_file;
|
||||
public:
|
||||
struct scored_literal {
|
||||
expr_ref lit;
|
||||
double score = 0.0;
|
||||
scored_literal(ast_manager& m, expr* e, double s): lit(e, m), score(s) {}
|
||||
scored_literal(expr_ref const& e, double s): lit(e), score(s) {}
|
||||
};
|
||||
|
||||
solver(ast_manager& m): check_sat_result(m) {}
|
||||
|
||||
/**
|
||||
|
|
@ -247,7 +255,9 @@ public:
|
|||
\brief extract a lookahead candidates for branching.
|
||||
*/
|
||||
|
||||
virtual expr_ref_vector cube(expr_ref_vector& vars, unsigned backtrack_level) = 0;
|
||||
virtual expr_ref_vector cube(expr_ref_vector& vars, unsigned backtrack_level=0) = 0;
|
||||
|
||||
virtual expr_ref cube_vsids(expr_ref_vector const&) { return expr_ref(m); }
|
||||
|
||||
/**
|
||||
\brief retrieve congruence closure root.
|
||||
|
|
@ -298,9 +308,34 @@ public:
|
|||
expr_ref_vector get_non_units();
|
||||
|
||||
virtual expr_ref_vector get_trail(unsigned max_level) = 0; // { return expr_ref_vector(get_manager()); }
|
||||
virtual expr_ref_vector get_assigned_literals() { return get_trail(UINT_MAX); }
|
||||
virtual unsigned get_assign_level(expr* e) const { return UINT_MAX; }
|
||||
virtual bool is_relevant(expr* e) const { return true; }
|
||||
virtual unsigned get_num_bool_vars() const { return UINT_MAX; }
|
||||
virtual sat::bool_var get_bool_var(expr* e) const { return sat::null_bool_var; }
|
||||
virtual expr* bool_var2expr(sat::bool_var) const { return nullptr; }
|
||||
virtual lbool get_assignment(sat::bool_var) const { return l_undef; }
|
||||
virtual double get_activity(sat::bool_var) const { return 0.0; }
|
||||
virtual bool was_eliminated(sat::bool_var) const { return false; }
|
||||
|
||||
virtual void pop_to_base_level() {}
|
||||
|
||||
virtual void setup_for_parallel() {}
|
||||
|
||||
virtual void set_preprocess(bool) {}
|
||||
|
||||
virtual void set_max_conflicts(unsigned max_conflicts) {
|
||||
params_ref p;
|
||||
p.set_uint("max_conflicts", max_conflicts);
|
||||
updt_params(p);
|
||||
}
|
||||
|
||||
virtual unsigned get_max_conflicts() const { return UINT_MAX; }
|
||||
|
||||
virtual void get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) = 0;
|
||||
|
||||
virtual void get_backbone_candidates(vector<scored_literal>&, unsigned) {}
|
||||
|
||||
class scoped_push {
|
||||
solver& s;
|
||||
bool m_nopop;
|
||||
|
|
@ -328,4 +363,3 @@ typedef ref<solver> solver_ref;
|
|||
inline std::ostream& operator<<(std::ostream& out, solver const& s) {
|
||||
return s.display(out);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,21 @@ public:
|
|||
return m_solver->get_trail(max_level);
|
||||
}
|
||||
|
||||
void setup_for_parallel() override { m_solver->setup_for_parallel(); }
|
||||
void set_max_conflicts(unsigned c) override { m_solver->set_max_conflicts(c); }
|
||||
unsigned get_max_conflicts() const override { return m_solver->get_max_conflicts(); }
|
||||
expr_ref_vector get_assigned_literals() override { return m_solver->get_assigned_literals(); }
|
||||
unsigned get_assign_level(expr* e) const override { flush_assertions(); return m_solver->get_assign_level(e); }
|
||||
bool is_relevant(expr* e) const override { flush_assertions(); return m_solver->is_relevant(e); }
|
||||
unsigned get_num_bool_vars() const override { flush_assertions(); return m_solver->get_num_bool_vars(); }
|
||||
sat::bool_var get_bool_var(expr* e) const override { flush_assertions(); return m_solver->get_bool_var(e); }
|
||||
expr* bool_var2expr(sat::bool_var v) const override { return m_solver->bool_var2expr(v); }
|
||||
lbool get_assignment(sat::bool_var v) const override { return m_solver->get_assignment(v); }
|
||||
double get_activity(sat::bool_var v) const override { return m_solver->get_activity(v); }
|
||||
bool was_eliminated(sat::bool_var v) const override { return m_solver->was_eliminated(v); }
|
||||
expr_ref cube_vsids(expr_ref_vector const& invalid_split_atoms) override { flush_assertions(); return m_solver->cube_vsids(invalid_split_atoms); }
|
||||
void get_backbone_candidates(vector<scored_literal>& candidates, unsigned max_num) override { flush_assertions(); m_solver->get_backbone_candidates(candidates, max_num); }
|
||||
|
||||
model_converter* external_model_converter() const {
|
||||
return concat(mc0(), local_model_converter());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,21 @@ public:
|
|||
return m_solver->get_trail(max_level);
|
||||
}
|
||||
|
||||
void setup_for_parallel() override { m_solver->setup_for_parallel(); }
|
||||
void set_max_conflicts(unsigned c) override { m_solver->set_max_conflicts(c); }
|
||||
unsigned get_max_conflicts() const override { return m_solver->get_max_conflicts(); }
|
||||
expr_ref_vector get_assigned_literals() override { return m_solver->get_assigned_literals(); }
|
||||
unsigned get_assign_level(expr* e) const override { return m_solver->get_assign_level(e); }
|
||||
bool is_relevant(expr* e) const override { return m_solver->is_relevant(e); }
|
||||
unsigned get_num_bool_vars() const override { return m_solver->get_num_bool_vars(); }
|
||||
sat::bool_var get_bool_var(expr* e) const override { return m_solver->get_bool_var(e); }
|
||||
expr* bool_var2expr(sat::bool_var v) const override { return m_solver->bool_var2expr(v); }
|
||||
lbool get_assignment(sat::bool_var v) const override { return m_solver->get_assignment(v); }
|
||||
double get_activity(sat::bool_var v) const override { return m_solver->get_activity(v); }
|
||||
bool was_eliminated(sat::bool_var v) const override { return m_solver->was_eliminated(v); }
|
||||
expr_ref cube_vsids(expr_ref_vector const& invalid_split_atoms) override { return m_solver->cube_vsids(invalid_split_atoms); }
|
||||
void get_backbone_candidates(vector<scored_literal>& candidates, unsigned max_num) override { m_solver->get_backbone_candidates(candidates, max_num); }
|
||||
|
||||
unsigned get_num_assertions() const override {
|
||||
return m_solver->get_num_assertions();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,21 @@ public:
|
|||
return m_solver->get_trail(max_level);
|
||||
}
|
||||
|
||||
void setup_for_parallel() override { m_solver->setup_for_parallel(); }
|
||||
void set_max_conflicts(unsigned c) override { m_solver->set_max_conflicts(c); }
|
||||
unsigned get_max_conflicts() const override { return m_solver->get_max_conflicts(); }
|
||||
expr_ref_vector get_assigned_literals() override { return m_solver->get_assigned_literals(); }
|
||||
unsigned get_assign_level(expr* e) const override { flush_assertions(); return m_solver->get_assign_level(e); }
|
||||
bool is_relevant(expr* e) const override { flush_assertions(); return m_solver->is_relevant(e); }
|
||||
unsigned get_num_bool_vars() const override { flush_assertions(); return m_solver->get_num_bool_vars(); }
|
||||
sat::bool_var get_bool_var(expr* e) const override { flush_assertions(); return m_solver->get_bool_var(e); }
|
||||
expr* bool_var2expr(sat::bool_var v) const override { return m_solver->bool_var2expr(v); }
|
||||
lbool get_assignment(sat::bool_var v) const override { return m_solver->get_assignment(v); }
|
||||
double get_activity(sat::bool_var v) const override { return m_solver->get_activity(v); }
|
||||
bool was_eliminated(sat::bool_var v) const override { return m_solver->was_eliminated(v); }
|
||||
expr_ref cube_vsids(expr_ref_vector const& invalid_split_atoms) override { flush_assertions(); return m_solver->cube_vsids(invalid_split_atoms); }
|
||||
void get_backbone_candidates(vector<scored_literal>& candidates, unsigned max_num) override { flush_assertions(); m_solver->get_backbone_candidates(candidates, max_num); }
|
||||
|
||||
model_converter* external_model_converter() const{
|
||||
return concat(mc0(), local_model_converter());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ Notes:
|
|||
#include "sat/sat_solver/inc_sat_solver.h"
|
||||
#include "sat/sat_solver/sat_smt_solver.h"
|
||||
#include "ast/rewriter/bv_rewriter.h"
|
||||
#include "solver/solver2tactic.h"
|
||||
#include "solver/parallel_tactical.h"
|
||||
#include "solver/parallel_params.hpp"
|
||||
#include "params/tactic_params.hpp"
|
||||
#include "parsers/smt2/smt2parser.h"
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ namespace search_tree {
|
|||
unsigned m_effort_spent = 0;
|
||||
unsigned m_round_max_effort = 0;
|
||||
unsigned m_active_workers = 0;
|
||||
unsigned m_cancel_epoch = 0;
|
||||
|
||||
public:
|
||||
node(literal const &l, node *parent) : m_literal(l), m_parent(parent), m_status(status::open) {}
|
||||
|
|
@ -68,9 +67,17 @@ namespace search_tree {
|
|||
literal const &get_literal() const {
|
||||
return m_literal;
|
||||
}
|
||||
bool path_contains_atom(literal const& l) const {
|
||||
for (node const* n = this; n; n = n->parent())
|
||||
if (!Config::literal_is_null(n->get_literal()) && Config::same_atom(n->get_literal(), l))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
void split(literal const &a, literal const &b) {
|
||||
SASSERT(!Config::literal_is_null(a));
|
||||
SASSERT(!Config::literal_is_null(b));
|
||||
VERIFY(!path_contains_atom(a));
|
||||
VERIFY(!path_contains_atom(b));
|
||||
if (m_status != status::active)
|
||||
return;
|
||||
SASSERT(!m_left);
|
||||
|
|
@ -148,12 +155,6 @@ namespace search_tree {
|
|||
m_round_max_effort = effort;
|
||||
m_effort_spent += m_round_max_effort;
|
||||
}
|
||||
unsigned get_cancel_epoch() const {
|
||||
return m_cancel_epoch;
|
||||
}
|
||||
void inc_cancel_epoch() {
|
||||
++m_cancel_epoch;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Config> class tree {
|
||||
|
|
@ -340,7 +341,6 @@ namespace search_tree {
|
|||
void close(node<Config> *n, vector<literal> const &C) {
|
||||
if (!n || n->get_status() == status::closed)
|
||||
return;
|
||||
n->inc_cancel_epoch();
|
||||
n->set_status(status::closed);
|
||||
n->set_core(C);
|
||||
close(n->left(), C);
|
||||
|
|
@ -444,8 +444,8 @@ namespace search_tree {
|
|||
|
||||
// On timeout, either expand the current leaf or reopen the node for a
|
||||
// later revisit, depending on the tree-expansion heuristic.
|
||||
bool try_split(node<Config> *n, unsigned cancel_epoch, literal const &a, literal const &b, unsigned effort) {
|
||||
if (is_lease_canceled(n, cancel_epoch))
|
||||
bool try_split(node<Config> *n, literal const &a, literal const &b, unsigned effort) {
|
||||
if (is_lease_canceled(n))
|
||||
return false;
|
||||
|
||||
// Record at most one effort contribution per concurrent round on this node.
|
||||
|
|
@ -544,8 +544,8 @@ namespace search_tree {
|
|||
n->dec_active_workers();
|
||||
}
|
||||
|
||||
bool is_lease_canceled(node<Config>* n, unsigned cancel_epoch) const {
|
||||
return !n || n->get_status() == status::closed || n->get_cancel_epoch() != cancel_epoch;
|
||||
bool is_lease_canceled(node<Config>* n) const {
|
||||
return !n || n->get_status() == status::closed;
|
||||
}
|
||||
|
||||
vector<literal> const &get_core_from_root() const {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue