3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-13 20:38:43 +00:00

fixes and tests for arith-sls

This commit is contained in:
Nikolaj Bjorner 2023-02-28 17:40:00 -08:00
parent e87fa1c299
commit 25d45a3500
7 changed files with 182 additions and 136 deletions

View file

@ -62,13 +62,16 @@ namespace sat {
void ddfw::check_with_plugin() {
m_plugin->init_search();
m_steps_since_progress = 0;
while (m_min_sz > 0 && m_steps_since_progress++ <= 150000) {
unsigned steps = 0;
while (m_min_sz > 0 && m_steps_since_progress++ <= 1500000) {
if (should_reinit_weights()) do_reinit_weights();
else if (steps % 5000 == 0) shift_weights(), m_plugin->on_rescale();
else if (should_restart()) do_restart(), m_plugin->on_restart();
else if (do_flip<true>());
else if (do_literal_flip<true>());
else if (should_restart()) do_restart(), m_plugin->on_restart();
else if (should_parallel_sync()) do_parallel_sync();
else shift_weights(), m_plugin->on_rescale();
++steps;
}
m_plugin->finish_search();
}
@ -135,7 +138,7 @@ namespace sat {
if (sum_pos > 0) {
double lim_pos = ((double) m_rand() / (1.0 + m_rand.max_value())) * sum_pos;
for (bool_var v : m_unsat_vars) {
r = uses_plugin ? plugin_reward(v) : reward(v);
r = uses_plugin && is_external(v) ? m_vars[v].m_last_reward : reward(v);
if (r > 0) {
lim_pos -= score(r);
if (lim_pos <= 0)
@ -472,9 +475,7 @@ namespace sat {
void ddfw::save_best_values() {
if (m_unsat.empty())
save_model();
else if (m_unsat.size() < m_min_sz) {
if (m_unsat.size() < m_min_sz) {
m_steps_since_progress = 0;
if (m_unsat.size() < 50 || m_min_sz * 10 > m_unsat.size() * 11)
save_model();
@ -489,13 +490,20 @@ namespace sat {
}
}
}
unsigned h = value_hash();
unsigned occs = 0;
bool contains = m_models.find(h, occs);
if (!m_models.contains(h)) {
for (unsigned v = 0; v < num_vars(); ++v)
for (unsigned v = 0; v < num_vars(); ++v)
bias(v) += value(v) ? 1 : -1;
m_models.insert(h);
if (m_models.size() > m_config.m_max_num_models)
m_models.erase(*m_models.begin());
if (m_models.size() > m_config.m_max_num_models)
m_models.erase(m_models.begin()->m_key);
}
m_models.insert(h, occs + 1);
if (occs > 100) {
m_restart_next = m_flips;
m_models.erase(h);
}
m_min_sz = m_unsat.size();
}

View file

@ -98,6 +98,7 @@ namespace sat {
var_info() {}
bool m_value = false;
double m_reward = 0;
double m_last_reward = 0;
unsigned m_make_count = 0;
int m_bias = 0;
bool m_external = false;
@ -127,7 +128,7 @@ namespace sat {
uint64_t m_restart_next = 0, m_reinit_next = 0, m_parsync_next = 0;
uint64_t m_flips = 0, m_last_flips = 0, m_shifts = 0;
unsigned m_min_sz = 0, m_steps_since_progress = 0;
hashtable<unsigned, unsigned_hash, default_eq<unsigned>> m_models;
u_map<unsigned> m_models;
stopwatch m_stopwatch;
parallel* m_par;
@ -153,7 +154,7 @@ namespace sat {
inline double reward(bool_var v) const { return m_vars[v].m_reward; }
inline double plugin_reward(bool_var v) const { return is_external(v) ? m_plugin->reward(v) : reward(v); }
inline double plugin_reward(bool_var v) { return is_external(v) ? (m_vars[v].m_last_reward = m_plugin->reward(v)) : reward(v); }
void set_external(bool_var v) { m_vars[v].m_external = true; }

View file

@ -40,26 +40,6 @@ Revision History:
namespace sat {
/**
* Special cases of kissat style general backoff calculation.
* The version here calculates
* limit := value*log(C)^2*n*log(n)
* (effort calculation in kissat is based on ticks not clauses)
*
* respectively
* limit := conflicts + value*log(C)^2*n*log(n)
*/
void backoff::delta_effort(solver& s) {
count++;
unsigned d = value * count * log2(count + 1);
unsigned cl = log2(s.num_clauses() + 2);
limit = cl * cl * d;
}
void backoff::delta_conflicts(solver& s) {
delta_effort(s);
limit += s.m_conflicts_since_init;
}
solver::solver(params_ref const & p, reslimit& l):
solver_core(l),
@ -1302,10 +1282,9 @@ namespace sat {
return l_undef;
}
if (false && m_config.m_phase == PS_LOCAL_SEARCH && m_ext) {
IF_VERBOSE(0, verbose_stream() << "WARNING: local search with theories is in testing mode\n");
if (m_config.m_phase == PS_LOCAL_SEARCH && m_ext) {
bounded_local_search();
exit(0);
// exit(0);
}
log_stats();
@ -1367,7 +1346,7 @@ namespace sat {
void solver::bounded_local_search() {
if (m_ext) {
verbose_stream() << "bounded local search\n";
IF_VERBOSE(0, verbose_stream() << "WARNING: local search with theories is in testing mode\n");
do_restart(true);
lbool r = m_ext->local_search(m_best_phase);
verbose_stream() << r << "\n";
@ -1388,8 +1367,8 @@ namespace sat {
m_local_search->set_seed(m_rand());
scoped_rl.push_child(&(m_local_search->rlimit()));
m_backoffs.m_local_search.delta_effort(*this);
m_local_search->rlimit().push(m_backoffs.m_local_search.limit);
m_local_search_lim.inc(num_clauses());
m_local_search->rlimit().push(m_local_search_lim.limit);
m_local_search->reinit(*this, m_best_phase);
lbool r = m_local_search->check(_lits.size(), _lits.data(), nullptr);
@ -1977,11 +1956,13 @@ namespace sat {
m_search_sat_conflicts = m_config.m_search_sat_conflicts;
m_search_next_toggle = m_search_unsat_conflicts;
m_best_phase_size = 0;
m_reorder.lo = m_config.m_reorder_base;
m_rephase.base = m_config.m_rephase_base;
m_rephase_lim = 0;
m_rephase_inc = 0;
m_reorder_lim = m_config.m_reorder_base;
m_backoffs.m_local_search.value = 500;
m_reorder_inc = 0;
m_local_search_lim.base = 500;
m_conflicts_since_restart = 0;
m_force_conflict_analysis = false;
m_restart_threshold = m_config.m_restart_initial;
@ -2981,6 +2962,7 @@ namespace sat {
bool solver::should_rephase() {
return m_conflicts_since_init > m_rephase_lim;
// return m_rephase.should_apply(m_conflicts_since_init);
}
void solver::do_rephase() {
@ -2994,7 +2976,7 @@ namespace sat {
case PS_FROZEN:
break;
case PS_BASIC_CACHING:
switch (m_rephase_lim % 4) {
switch (m_rephase.count % 4) {
case 0:
for (auto& p : m_phase) p = (m_rand() % 2) == 0;
break;
@ -3031,10 +3013,11 @@ namespace sat {
}
m_rephase_inc += m_config.m_rephase_base;
m_rephase_lim += m_rephase_inc;
m_rephase.inc(m_conflicts_since_init, num_clauses());
}
bool solver::should_reorder() {
return m_conflicts_since_init > m_reorder_lim;
return m_reorder.should_apply(m_conflicts_since_init);
}
void solver::do_reorder() {
@ -3078,8 +3061,7 @@ namespace sat {
update_activity(v, m_rand(10)/10.0);
}
#endif
m_reorder_inc += m_config.m_reorder_base;
m_reorder_lim += m_reorder_inc;
m_reorder.inc(m_conflicts_since_init, num_clauses());
}
void solver::updt_phase_counters() {

View file

@ -87,23 +87,10 @@ namespace sat {
struct no_drat_params : public params_ref {
no_drat_params() { set_bool("drat.disable", true); }
};
struct backoff {
unsigned value = 1;
unsigned lo = 0;
unsigned hi = 0;
unsigned limit = 0;
unsigned count = 0;
void delta_effort(solver& s);
void delta_conflicts(solver& s);
};
class solver : public solver_core {
public:
struct abort_solver {};
struct backoffs {
backoff m_local_search;
};
protected:
enum search_state { s_sat, s_unsat };
@ -172,11 +159,11 @@ namespace sat {
unsigned m_search_next_toggle;
unsigned m_phase_counter;
unsigned m_best_phase_size;
backoffs m_backoffs;
backoff m_local_search_lim;
unsigned m_rephase_lim;
unsigned m_rephase_inc;
unsigned m_reorder_lim;
unsigned m_reorder_inc;
backoff m_rephase;
backoff m_reorder;
var_queue m_case_split_queue;
unsigned m_qhead;
unsigned m_scope_lvl;

View file

@ -136,6 +136,40 @@ namespace sat {
std::ostream& operator<<(std::ostream& out, sat::status const& st);
std::ostream& operator<<(std::ostream& out, sat::status_pp const& p);
/**
* Special cases of kissat style general backoff calculation.
* The version here calculates
* limit := value*log(C)^2*n*log(n)
* (effort calculation in kissat is based on ticks not clauses)
*
* respectively
* limit := conflicts + value*log(C)^2*n*log(n)
*/
struct backoff {
unsigned base = 1;
unsigned lo = 0;
unsigned hi = UINT_MAX;
unsigned limit = 0;
unsigned count = 0;
bool should_apply(unsigned n) const {
return limit <= n && lo <= n && n <= hi;
}
void inc(unsigned num_clauses) {
count++;
unsigned d = base * count * log2(count + 1);
unsigned cl = log2(num_clauses + 2);
limit = cl * cl * d;
}
void inc(unsigned num_conflicts, unsigned num_clauses) {
inc(num_clauses);
limit += num_conflicts;
}
};
};

View file

@ -29,42 +29,49 @@ namespace arith {
m_terms.reset();
}
void sls::log() {
IF_VERBOSE(2, verbose_stream() << "(sls :flips " << m_stats.m_num_flips << " :unsat " << unsat().size() << ")\n");
}
void sls::save_best_values() {
for (unsigned v = 0; v < s.get_num_vars(); ++v)
m_vars[v].m_best_value = m_vars[v].m_value;
auto check_bool_var = [&](sat::bool_var bv) {
auto const* ineq = atom(bv);
if (!ineq)
return;
sat::literal lit(bv, !m_bool_search->get_value(bv));
int64_t d = dtt(lit.sign(), *ineq);
// verbose_stream() << "check " << lit << " " << *ineq << "\n";
if (is_true(lit) != (d == 0)) {
verbose_stream() << lit << " " << *ineq << "\n";
check_ineqs();
if (unsat().size() == 1) {
auto idx = *unsat().begin();
verbose_stream() << idx << "\n";
auto const& c = *m_bool_search->m_clauses[idx].m_clause;
verbose_stream() << c << "\n";
for (auto lit : c) {
bool_var bv = lit.var();
ineq* i = atom(bv);
if (i)
verbose_stream() << lit << ": " << *i << "\n";
}
VERIFY(is_true(lit) == (d == 0));
};
for (unsigned v = 0; v < s.get_num_vars(); ++v)
check_bool_var(v);
verbose_stream() << "\n";
}
}
void sls::store_best_values() {
// first compute assignment to terms
// then update non-basic variables in tableau.
for (auto const& [t, v] : m_terms) {
if (!unsat().empty())
return;
for (auto const& [t,v] : m_terms) {
int64_t val = 0;
lp::lar_term const& term = s.lp().get_term(t);
for (lp::lar_term::ival arg : term) {
for (lp::lar_term::ival const& arg : term) {
auto t2 = s.lp().column2tv(arg.column());
auto w = s.lp().local_to_external(t2.id());
val += to_numeral(arg.coeff()) * m_vars[w].m_best_value;
}
update(v, val);
if (v == 52) {
verbose_stream() << "update v" << v << " := " << val << "\n";
for (lp::lar_term::ival const& arg : term) {
auto t2 = s.lp().column2tv(arg.column());
auto w = s.lp().local_to_external(t2.id());
verbose_stream() << "v" << w << " := " << m_vars[w].m_best_value << " * " << to_numeral(arg.coeff()) << "\n";
}
}
m_vars[v].m_best_value = val;
}
for (unsigned v = 0; v < s.get_num_vars(); ++v) {
@ -80,16 +87,15 @@ namespace arith {
rational new_value_(new_value, rational::i64());
lp::impq val(new_value_, rational::zero());
s.lp().set_value_for_nbasic_column(vj.index(), val);
// TODO - figure out why this leads to unsound (unsat).
}
}
lbool r = s.make_feasible();
VERIFY (!unsat().empty() || r == l_true);
if (unsat().empty()) {
#if 0
if (unsat().empty())
s.m_num_conflicts = s.get_config().m_arith_propagation_threshold;
}
verbose_stream() << "has changed " << s.m_solver->has_changed_columns() << "\n";
#endif
auto check_bool_var = [&](sat::bool_var bv) {
auto* ineq = m_bool_vars.get(bv, nullptr);
@ -105,10 +111,10 @@ namespace arith {
return;
switch (b->get_bound_kind()) {
case lp_api::lower_t:
verbose_stream() << bv << " " << bound << " <= " << s.get_value(v) << "\n";
verbose_stream() << "v" << v << " " << bound << " <= " << s.get_value(v) << " " << m_vars[v].m_best_value << "\n";
break;
case lp_api::upper_t:
verbose_stream() << bv << " " << bound << " >= " << s.get_value(v) << "\n";
verbose_stream() << "v" << v << " " << bound << " >= " << s.get_value(v) << " " << m_vars[v].m_best_value << "\n";
break;
}
int64_t value = 0;
@ -117,6 +123,12 @@ namespace arith {
}
ineq->m_args_value = value;
verbose_stream() << *ineq << " dtt " << dtt(false, *ineq) << " phase " << s.get_phase(bv) << " model " << m_bool_search->get_model()[bv] << "\n";
for (auto const& [coeff, v] : ineq->m_args)
verbose_stream() << "v" << v << " := " << m_vars[v].m_best_value << "\n";
s.display(verbose_stream());
display(verbose_stream());
UNREACHABLE();
exit(0);
};
if (unsat().empty()) {
@ -200,16 +212,16 @@ namespace arith {
return dtt(sign, ineq.m_args_value + coeff * (new_value - old_value), ineq);
}
bool sls::cm(bool sign, ineq const& ineq, var_t v, int64_t& new_value) {
bool sls::cm(bool old_sign, ineq const& ineq, var_t v, int64_t& new_value) {
for (auto const& [coeff, w] : ineq.m_args)
if (w == v)
return cm(sign, ineq, v, coeff, new_value);
return cm(old_sign, ineq, v, coeff, new_value);
return false;
}
bool sls::cm(bool new_sign, ineq const& ineq, var_t v, int64_t coeff, int64_t& new_value) {
SASSERT(ineq.is_true() == new_sign);
VERIFY(ineq.is_true() == new_sign);
bool sls::cm(bool old_sign, ineq const& ineq, var_t v, int64_t coeff, int64_t& new_value) {
SASSERT(ineq.is_true() != old_sign);
VERIFY(ineq.is_true() != old_sign);
auto bound = ineq.m_bound;
auto argsv = ineq.m_args_value;
bool solved = false;
@ -239,7 +251,7 @@ namespace arith {
return true;
};
if (new_sign) {
if (!old_sign) {
switch (ineq.m_op) {
case ineq_kind::LE:
// args <= bound -> args > bound
@ -300,10 +312,10 @@ namespace arith {
int64_t new_value;
auto v = ineq.m_var_to_flip;
if (v == UINT_MAX) {
// verbose_stream() << "no var to flip\n";
IF_VERBOSE(1, verbose_stream() << "no var to flip\n");
return false;
}
if (!cm(!sign, ineq, v, new_value)) {
if (!cm(sign, ineq, v, new_value)) {
verbose_stream() << "no critical move for " << v << "\n";
return false;
}
@ -316,16 +328,16 @@ namespace arith {
// TODO - use cached dts instead of computed dts
// cached dts has to be updated when the score of literals are updated.
//
double sls::dscore(var_t v, int64_t new_value) const {
verbose_stream() << "dscore\n";
double sls::dscore(var_t v, int64_t new_value) const {
double score = 0;
#if 0
auto const& vi = m_vars[v];
verbose_stream() << "dscore " << v << "\n";
for (auto const& [coeff, lit] : vi.m_literals)
for (auto cl : m_bool_search->get_use_list(lit))
score += (compute_dts(cl) - dts(cl, v, new_value)) * m_bool_search->get_weight(cl);
#endif
for (auto const& [coeff, bv] : vi.m_bool_vars) {
sat::literal lit(bv, false);
for (auto cl : m_bool_search->get_use_list(lit))
score += (compute_dts(cl) - dts(cl, v, new_value)) * m_bool_search->get_weight(cl);
for (auto cl : m_bool_search->get_use_list(~lit))
score += (compute_dts(cl) - dts(cl, v, new_value)) * m_bool_search->get_weight(cl);
}
return score;
}
@ -341,12 +353,12 @@ namespace arith {
int64_t old_value = vi.m_value;
for (auto const& [coeff, bv] : vi.m_bool_vars) {
auto const& ineq = *atom(bv);
bool sign = !m_bool_search->value(bv);
int64_t dtt_old = dtt(sign, ineq);
int64_t dtt_new = dtt(sign, ineq, coeff, old_value, new_value);
bool old_sign = sign(bv);
int64_t dtt_old = dtt(old_sign, ineq);
int64_t dtt_new = dtt(old_sign, ineq, coeff, old_value, new_value);
if ((dtt_old == 0) == (dtt_new == 0))
continue;
sat::literal lit(bv, sign);
sat::literal lit(bv, old_sign);
if (dtt_old == 0)
// flip from true to false
lit.neg();
@ -408,14 +420,14 @@ namespace arith {
auto old_value = vi.m_value;
for (auto const& [coeff, bv] : vi.m_bool_vars) {
auto& ineq = *atom(bv);
bool sign = !m_bool_search->value(bv);
sat::literal lit(bv, sign);
bool old_sign = sign(bv);
sat::literal lit(bv, old_sign);
SASSERT(is_true(lit));
ineq.m_args_value += coeff * (new_value - old_value);
int64_t dtt_new = dtt(sign, ineq);
int64_t dtt_new = dtt(old_sign, ineq);
if (dtt_new != 0)
m_bool_search->flip(bv);
SASSERT(dtt(!m_bool_search->value(bv), ineq) == 0);
SASSERT(dtt(sign(bv), ineq) == 0);
}
vi.m_value = new_value;
}
@ -451,7 +463,7 @@ namespace arith {
void sls::add_args(sat::bool_var bv, ineq& ineq, lp::tv t, theory_var v, int64_t sign) {
if (t.is_term()) {
lp::lar_term const& term = s.lp().get_term(t);
m_terms.push_back({t,v});
for (lp::lar_term::ival arg : term) {
auto t2 = s.lp().column2tv(arg.column());
auto w = s.lp().local_to_external(t2.id());
@ -479,6 +491,7 @@ namespace arith {
auto& ineq = new_ineq(op, to_numeral(bound));
add_args(bv, ineq, t, b->get_var(), should_minus ? -1 : 1);
m_bool_vars.set(bv, &ineq);
m_bool_search->set_external(bv);
@ -516,7 +529,7 @@ namespace arith {
}
void sls::flip(sat::bool_var v) {
sat::literal lit(v, m_bool_search->get_value(v));
sat::literal lit(v, !sign(v));
SASSERT(!is_true(lit));
auto const* ineq = atom(v);
if (!ineq)
@ -524,7 +537,7 @@ namespace arith {
if (!ineq)
return;
SASSERT(ineq->is_true() == lit.sign());
flip(!lit.sign(), *ineq);
flip(sign(v), *ineq);
}
double sls::reward(sat::bool_var v) {
@ -535,21 +548,23 @@ namespace arith {
}
double sls::dtt_reward(sat::bool_var bv0) {
bool sign0 = !m_bool_search->get_value(bv0);
bool sign0 = sign(bv0);
auto* ineq = atom(bv0);
if (!ineq)
return -1;
int64_t new_value;
double max_result = -1;
for (auto const & [coeff, x] : ineq->m_args) {
if (!cm(!sign0, *ineq, x, coeff, new_value))
if (!cm(sign0, *ineq, x, coeff, new_value))
continue;
double result = 0;
auto old_value = m_vars[x].m_value;
for (auto const& [coeff, bv] : m_vars[x].m_bool_vars) {
bool sign = !m_bool_search->value(bv);
auto dtt_old = dtt(sign, *atom(bv));
auto dtt_new = dtt(sign, *atom(bv), coeff, old_value, new_value);
result += m_bool_search->reward(bv);
continue;
bool old_sign = sign(bv);
auto dtt_old = dtt(old_sign, *atom(bv));
auto dtt_new = dtt(old_sign, *atom(bv), coeff, old_value, new_value);
if ((dtt_new == 0) != (dtt_old == 0))
result += m_bool_search->reward(bv);
}
@ -563,17 +578,17 @@ namespace arith {
double sls::dscore_reward(sat::bool_var bv) {
m_dscore_mode = false;
bool sign = !m_bool_search->get_value(bv);
sat::literal litv(bv, sign);
bool old_sign = sign(bv);
sat::literal litv(bv, old_sign);
auto* ineq = atom(bv);
if (!ineq)
return 0;
SASSERT(ineq->is_true() != sign);
SASSERT(ineq->is_true() != old_sign);
int64_t new_value;
for (auto const& [coeff, v] : ineq->m_args) {
double result = 0;
if (cm(!sign, *ineq, v, coeff, new_value))
if (cm(old_sign, *ineq, v, coeff, new_value))
result = dscore(v, new_value);
// just pick first positive, or pick a max?
if (result > 0) {
@ -586,7 +601,7 @@ namespace arith {
// switch to dscore mode
void sls::on_rescale() {
// m_dscore_mode = true;
m_dscore_mode = true;
}
void sls::on_save_model() {
@ -597,23 +612,39 @@ namespace arith {
for (unsigned v = 0; v < s.s().num_vars(); ++v)
init_bool_var_assignment(v);
verbose_stream() << "on-restart\n";
check_ineqs();
}
void sls::check_ineqs() {
auto check_bool_var = [&](sat::bool_var bv) {
auto const* ineq = atom(bv);
if (!ineq)
return;
bool sign = !m_bool_search->get_value(bv);
int64_t d = dtt(sign, *ineq);
sat::literal lit(bv, sign);
// verbose_stream() << "check " << lit << " " << *ineq << "\n";
int64_t d = dtt(sign(bv), *ineq);
sat::literal lit(bv, sign(bv));
if (is_true(lit) != (d == 0)) {
verbose_stream() << "restart " << bv << " " << *ineq << "\n";
verbose_stream() << "invalid assignment " << bv << " " << *ineq << "\n";
}
VERIFY(is_true(lit) == (d == 0));
};
for (unsigned v = 0; v < s.get_num_vars(); ++v)
for (unsigned v = 0; v < s.get_num_vars(); ++v)
check_bool_var(v);
verbose_stream() << "on-restart-done\n";
}
std::ostream& sls::display(std::ostream& out) const {
for (bool_var bv = 0; bv < s.s().num_vars(); ++bv) {
auto const* ineq = atom(bv);
if (!ineq)
continue;
out << bv << " " << *ineq << "\n";
}
for (unsigned v = 0; v < s.get_num_vars(); ++v) {
if (s.is_bool(v))
continue;
out << "v" << v << " := " << m_vars[v].m_value << " " << m_vars[v].m_best_value << "\n";
}
return out;
}
}

View file

@ -119,12 +119,11 @@ namespace arith {
sat::ddfw::clause_info& get_clause_info(unsigned idx) { return m_bool_search->get_clause_info(idx); }
sat::ddfw::clause_info const& get_clause_info(unsigned idx) const { return m_bool_search->get_clause_info(idx); }
bool is_true(sat::literal lit) { return lit.sign() != m_bool_search->get_value(lit.var()); }
bool sign(sat::bool_var v) const { return !m_bool_search->get_value(v); }
void reset();
ineq* atom(sat::bool_var bv) const { return m_bool_vars[bv]; }
void log();
bool flip(bool sign, ineq const& ineq);
int64_t dtt(bool sign, ineq const& ineq) const { return dtt(sign, ineq.m_args_value, ineq); }
int64_t dtt(bool sign, int64_t args_value, ineq const& ineq) const;
@ -151,6 +150,10 @@ namespace arith {
int64_t value(var_t v) const { return m_vars[v].m_value; }
int64_t to_numeral(rational const& r);
void check_ineqs();
std::ostream& display(std::ostream& out) const;
public:
sls(solver& s);
~sls() override {}