diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index 812e65abf..2597967df 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -119,6 +119,7 @@ namespace sat { m_simplify_mult2 = _p.get_double("simplify_mult2", 1.5); m_simplify_max = _p.get_uint("simplify_max", 500000); // -------------------------------- + m_simplify_delay = p.simplify_delay(); s = p.gc(); if (s == symbol("dyn_psm")) diff --git a/src/sat/sat_config.h b/src/sat/sat_config.h index 6a704ab44..20d966dd1 100644 --- a/src/sat/sat_config.h +++ b/src/sat/sat_config.h @@ -116,6 +116,7 @@ namespace sat { unsigned m_simplify_mult1; double m_simplify_mult2; unsigned m_simplify_max; + unsigned m_simplify_delay; unsigned m_variable_decay; @@ -126,6 +127,7 @@ namespace sat { unsigned m_gc_k; bool m_gc_burst; + bool m_minimize_lemmas; bool m_dyn_sub_res; bool m_core_minimize; diff --git a/src/sat/sat_params.pyg b/src/sat/sat_params.pyg index 56a5b6e6c..320ddaf9e 100644 --- a/src/sat/sat_params.pyg +++ b/src/sat/sat_params.pyg @@ -24,6 +24,7 @@ def_module_params('sat', ('gc.small_lbd', UINT, 3, 'learned clauses with small LBD are never deleted (only used in dyn_psm)'), ('gc.k', UINT, 7, 'learned clauses that are inactive for k gc rounds are permanently deleted (only used in dyn_psm)'), ('gc.burst', BOOL, True, 'perform eager garbage collection during initialization'), + ('simplify.delay', UINT, 0, 'set initial delay of simplification by a conflict count'), ('minimize_lemmas', BOOL, True, 'minimize learned clauses'), ('dyn_sub_res', BOOL, True, 'dynamic subsumption resolution for minimizing learned clauses'), ('core.minimize', BOOL, False, 'minimize computed core'), @@ -42,9 +43,9 @@ def_module_params('sat', ('local_search_mode', SYMBOL, 'wsat', 'local search algorithm, either default wsat or qsat'), ('unit_walk', BOOL, False, 'use unit-walk search instead of CDCL'), ('unit_walk_threads', UINT, 0, 'number of unit-walk search threads to find satisfiable solution'), - ('lookahead.cube.cutoff', SYMBOL, 'adaptive_freevars', 'cutoff type used to create lookahead cubes: depth, freevars, psat, adaptive_freevars, adaptive_psat'), + ('lookahead.cube.cutoff', SYMBOL, 'depth', 'cutoff type used to create lookahead cubes: depth, freevars, psat, adaptive_freevars, adaptive_psat'), ('lookahead.cube.fraction', DOUBLE, 0.4, 'adaptive fraction to create lookahead cubes. Used when lookahead.cube.cutoff is adaptive_freevars or adaptive_psat'), - ('lookahead.cube.depth', UINT, 10, 'cut-off depth to create cubes. Used when lookahead.cube.cutoff is depth.'), + ('lookahead.cube.depth', UINT, 1, 'cut-off depth to create cubes. Used when lookahead.cube.cutoff is depth.'), ('lookahead.cube.freevars', DOUBLE, 0.8, 'cube free fariable fraction. Used when lookahead.cube.cutoff is freevars'), ('lookahead.cube.psat.var_exp', DOUBLE, 1, 'free variable exponent for PSAT cutoff'), ('lookahead.cube.psat.clause_base', DOUBLE, 2, 'clause base for PSAT cutoff'), diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index da11ed3a1..fb992d57e 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -1503,7 +1503,7 @@ namespace sat { m_restarts = 0; m_simplifications = 0; m_conflicts_since_init = 0; - m_next_simplify = 0; + m_next_simplify = m_config.m_simplify_delay; m_min_d_tk = 1.0; m_search_lvl = 0; m_conflicts_since_gc = 0; diff --git a/src/tactic/portfolio/parallel_tactic.cpp b/src/tactic/portfolio/parallel_tactic.cpp index 9b9e74629..988ab2ec4 100644 --- a/src/tactic/portfolio/parallel_tactic.cpp +++ b/src/tactic/portfolio/parallel_tactic.cpp @@ -107,6 +107,11 @@ class parallel_tactic : public tactic { } } + bool is_idle() { + std::lock_guard lock(m_mutex); + return m_tasks.empty() && m_num_waiters > 0; + } + solver_state* get_task() { while (!m_shutdown) { inc_wait(); @@ -153,19 +158,21 @@ class parallel_tactic : public tactic { class cube_var { expr_ref_vector m_vars; - expr_ref m_cube; + expr_ref_vector m_cube; public: - cube_var(expr* c, expr_ref_vector& vs): - m_vars(vs), m_cube(c, vs.get_manager()) {} + cube_var(expr_ref_vector& c, expr_ref_vector& vs): + m_vars(vs), m_cube(c) {} cube_var operator()(ast_translation& tr) { expr_ref_vector vars(tr.to()); + expr_ref_vector cube(tr.to()); for (expr* v : m_vars) vars.push_back(tr(v)); - return cube_var(tr(m_cube.get()), vars); + for (expr* c : m_cube) cube.push_back(tr(c)); + return cube_var(cube, vars); } - expr* cube() const { return m_cube; } - expr_ref_vector const& vars() { return m_vars; } + expr_ref_vector const& cube() const { return m_cube; } + expr_ref_vector const& vars() const { return m_vars; } }; class solver_state { @@ -179,17 +186,6 @@ class parallel_tactic : public tactic { double m_width; // estimate of fraction of problem handled by state unsigned m_restart_max; // saved configuration value - expr_ref_vector cube_literals(expr* cube) { - expr_ref_vector literals(m()); - if (m().is_and(cube)) { - literals.append(to_app(cube)->get_num_args(), to_app(cube)->get_args()); - } - else { - literals.push_back(cube); - } - return literals; - } - public: solver_state(ast_manager* m, solver* s, params_ref const& p, task_type t): m_type(t), @@ -212,12 +208,12 @@ class parallel_tactic : public tactic { solver const& get_solver() const { return *m_solver; } - solver_state* clone() { + solver_state* clone(solver* s0 = nullptr) { SASSERT(!m_cubes.empty()); ast_manager& m = m_solver->get_manager(); ast_manager* new_m = alloc(ast_manager, m, !m.proof_mode()); ast_translation tr(m, *new_m); - solver* s = m_solver->translate(*new_m, m_params); + solver* s = (s0 ? s0 : m_solver.get())->translate(*new_m, m_params); solver_state* st = alloc(solver_state, new_m, s, m_params, m_type); for (auto & c : m_cubes) st->m_cubes.push_back(c(tr)); for (expr* c : m_asserted_cubes) st->m_asserted_cubes.push_back(tr(c)); @@ -284,15 +280,14 @@ class parallel_tactic : public tactic { return r; } - void assert_cube(expr* cube) { + void assert_cube(expr_ref_vector const& cube) { get_solver().assert_expr(cube); - m_asserted_cubes.append(cube_literals(cube)); + m_asserted_cubes.append(cube); } - lbool solve(expr* cube) { + lbool solve(expr_ref_vector const& cube) { set_conquer_params(); - expr_ref_vector literals = cube_literals(cube); - return get_solver().check_sat(literals); + return get_solver().check_sat(cube); } void set_cube_params() { @@ -300,22 +295,33 @@ class parallel_tactic : public tactic { } void set_conquer_params() { - m_params.set_bool("gc.initial", true); - m_params.set_bool("lookahead_simplify", false); - m_params.set_uint("restart.max", m_restart_max); - get_solver().updt_params(m_params); + set_conquer_params(get_solver()); + } + + void set_conquer_params(solver& s) { + params_ref p; + p.copy(m_params); + p.set_bool("gc.burst", true); // apply eager gc + p.set_uint("simplify.delay", 1000); // delay simplification by 1000 conflicts + p.set_bool("lookahead_simplify", false); + p.set_uint("restart.max", m_restart_max); + p.set_uint("inprocess.max", UINT_MAX); // base bounds on restart.max + s.updt_params(p); } void set_simplify_params(bool pb_simp, bool retain_blocked) { parallel_params pp(m_params); - m_params.set_bool("cardinality.solver", pb_simp); - m_params.set_sym ("pb.solver", pb_simp ? symbol("solver") : symbol("circuit")); - if (m_params.get_uint("inprocess.max", UINT_MAX) == UINT_MAX) - m_params.set_uint("inprocess.max", pp.inprocess_max()); - m_params.set_bool("lookahead_simplify", true); - m_params.set_uint("restart.max", UINT_MAX); - m_params.set_bool("retain_blocked_clauses", retain_blocked); - get_solver().updt_params(m_params); + params_ref p; + p.copy(m_params); + + p.set_bool("cardinality.solver", pb_simp); + p.set_sym ("pb.solver", pb_simp ? symbol("solver") : symbol("circuit")); + if (p.get_uint("inprocess.max", UINT_MAX) == UINT_MAX) + p.set_uint("inprocess.max", pp.inprocess_max()); + p.set_bool("lookahead_simplify", true); + p.set_uint("restart.max", UINT_MAX); + p.set_bool("retain_blocked_clauses", retain_blocked); + get_solver().updt_params(p); } bool canceled() { @@ -340,6 +346,8 @@ private: std::mutex m_mutex; double m_progress; unsigned m_branches; + unsigned m_backtrack_frequency; + unsigned m_conquer_threshold; bool m_has_undef; bool m_allsat; unsigned m_num_unsat; @@ -351,15 +359,32 @@ private: m_progress = 0; m_has_undef = false; m_allsat = false; - m_num_unsat = 0; - m_branches = 0; + m_branches = 0; + m_num_unsat = 0; + m_backtrack_frequency = 10; + m_conquer_threshold = 10; m_exn_code = 0; m_params.set_bool("override_incremental", true); } + void log_branches(lbool status) { + IF_VERBOSE(0, verbose_stream() << "(tactic.parallel :progress " << m_progress << "% "; + if (status == l_true) verbose_stream() << ":status sat "; + if (status == l_undef) verbose_stream() << ":status unknown "; + verbose_stream() << ":closed " << m_num_unsat << " :open " << m_branches << ")\n";); + } + void add_branches(unsigned b) { + { + std::lock_guard lock(m_mutex); + m_branches += b; + } + log_branches(l_false); + } + + void dec_branch() { std::lock_guard lock(m_mutex); - m_branches += b; + --m_branches; } void close_branch(solver_state& s, lbool status) { @@ -369,10 +394,7 @@ private: m_progress += f; --m_branches; } - IF_VERBOSE(0, verbose_stream() << "(tactic.parallel :progress " << m_progress << "% "; - if (status == l_true) verbose_stream() << ":status sat "; - if (status == l_undef) verbose_stream() << ":status unknown "; - verbose_stream() << ":unsat " << m_num_unsat << " :open-branches " << m_branches << ")\n";); + log_branches(status); } void report_sat(solver_state& s) { @@ -392,11 +414,13 @@ private: } } + void inc_unsat() { + std::lock_guard lock(m_mutex); + ++m_num_unsat; + } + void report_unsat(solver_state& s) { - { - std::lock_guard lock(m_mutex); - ++m_num_unsat; - } + inc_unsat(); close_branch(s, l_false); } @@ -405,7 +429,7 @@ private: close_branch(s, l_undef); } - void cube_and_conquer(solver_state& s) { + void cube_and_conquer1(solver_state& s) { ast_manager& m = s.m(); vector cube, hard_cubes, cubes; expr_ref_vector vars(m); @@ -452,7 +476,7 @@ private: if (m.is_false(c.back())) { break; } - cubes.push_back(cube_var(mk_and(c), vars)); + cubes.push_back(cube_var(c, vars)); IF_VERBOSE(2, verbose_stream() << "(tactic.parallel :cube " << cubes.size() << " :vars " << vars.size() << ")\n"); } @@ -500,8 +524,9 @@ private: ast_manager& m = s.m(); vector cube, hard_cubes, cubes; expr_ref_vector vars(m); - SASSERT(s.type() == cube_task); + cube_again: + SASSERT(s.type() == cube_task); // extract up to one cube and add it. cube.reset(); cube.append(s.split_cubes(1)); @@ -515,7 +540,9 @@ private: } s.inc_depth(1); + simplify_again: // simplify + if (canceled(s)) return; switch (s.simplify()) { case l_undef: break; case l_true: report_sat(s); return; @@ -526,112 +553,124 @@ private: // extract cubes. cubes.reset(); s.set_cube_params(); - solver_ref conquer = s.copy_solver(); + solver_ref conquer; + unsigned cutoff = UINT_MAX; - while (true) { + bool first = true; + unsigned num_backtracks = 0, width = 0; + while (cutoff > 0 && !canceled(s)) { expr_ref_vector c = s.get_solver().cube(vars, cutoff); if (c.empty()) { - report_undef(s); - return; + goto simplify_again; } if (m.is_false(c.back())) { break; } - lbool is_sat = conquer->check_sat(c); - switch (is_sat) { - case l_false: { - // TBD: minimize core instead. - expr_ref_vector core(m); - conquer->get_unsat_core(core); - obj_hashtable hcore; - for (expr* e : core) hcore.insert(e); - for (unsigned i = c.size(); i-- > 0; ) { - if (hcore.contains(c[i].get())) { - cutoff = i; - break; - } - } - break; + lbool is_sat = l_undef; + if (width >= m_conquer_threshold && !conquer) { + conquer = s.copy_solver(); + s.set_conquer_params(*conquer.get()); } + if (conquer) { + is_sat = conquer->check_sat(c); + } + switch (is_sat) { + case l_false: + cutoff = c.size(); + backtrack(*conquer.get(), c, (num_backtracks++) % m_backtrack_frequency == 0); + if (cutoff != c.size()) { + IF_VERBOSE(0, verbose_stream() << "(tactic.parallel :backtrack " << cutoff << " -> " << c.size() << ")\n"); + cutoff = c.size(); + } + inc_unsat(); + log_branches(l_false); + break; + case l_true: report_sat(s); + if (conquer) { + collect_statistics(*conquer.get()); + } return; + case l_undef: - IF_VERBOSE(2, verbose_stream() << "(tactic.parallel :cube " << cubes.size() << " :vars " << vars.size() << ")\n"); - cubes.push_back(cube_var(mk_and(c), vars)); + ++width; + IF_VERBOSE(2, verbose_stream() << "(tactic.parallel :cube " << c.size() << " :vars " << vars.size() << ")\n"); + cubes.push_back(cube_var(c, vars)); cutoff = UINT_MAX; break; - } - // TBD flush cube task early - } - - IF_VERBOSE(1, verbose_stream() << "(tactic.parallel :cubes " << cubes.size() << ")\n";); - if (cubes.empty()) { + } + if (cubes.size() >= conquer_batch_size() || (!cubes.empty() && m_queue.is_idle())) { + spawn_cubes(s, std::max(2u, width), cubes); + first = false; + cubes.reset(); + } + } + + if (conquer) { + collect_statistics(*conquer.get()); + } + + if (cubes.empty() && first) { report_unsat(s); + } + else if (cubes.empty()) { + dec_branch(); return; } else { - s.inc_width(cubes.size()); - add_branches(cubes.size() - 1); - NOT_IMPLEMENTED_YET(); - // TBD add as a cube task. + s.inc_width(width); + add_branches(cubes.size()-1); + s.set_cubes(cubes); + goto cube_again; + } + } + + void spawn_cubes(solver_state& s, unsigned width, vector& cubes) { + if (cubes.empty()) return; + add_branches(cubes.size()); + s.set_cubes(cubes); + solver_state* s1 = s.clone(); + s1->inc_width(width); + m_queue.add_task(s1); + } + + /* + * \brief backtrack from unsatisfiable core + */ + void backtrack(solver& s, expr_ref_vector& asms, bool full) { + ast_manager& m = s.get_manager(); + expr_ref_vector core(m); + obj_hashtable hcore; + s.get_unsat_core(core); + while (!asms.empty() && !core.contains(asms.back())) asms.pop_back(); + if (!full) return; + + //s.assert_expr(m.mk_not(mk_and(core))); + if (!asms.empty()) { + expr* last = asms.back(); + expr_ref not_last(mk_not(m, last), m); + asms.pop_back(); + asms.push_back(not_last); + lbool r = s.check_sat(asms); + asms.pop_back(); + if (r != l_false) { + asms.push_back(last); + return; + } + core.reset(); + s.get_unsat_core(core); + if (core.contains(not_last)) { + //s.assert_expr(m.mk_not(mk_and(core))); + r = s.check_sat(asms); + } + if (r == l_false) { + backtrack(s, asms, full); + } } } - expr_ref_vector baktrack(expr_ref_vector const& c) { - - } - -#if 0 - public BoolExpr[] Backtrack(Statistics stats, BoolExpr[] _asms) - { - int count = _asms.Count(); - stats.Stopwatch.Start(); - var asms = new List(_asms); - _Backtrack(stats, asms); - stats.AddBacktrackStats((uint)(count - asms.Count()), stats.Stopwatch.Elapsed()); - return asms.ToArray(); - } - - public void _Backtrack(Statistics stats, List asms) - { - HashSet core = new HashSet(Solver.UnsatCore); - while (asms.Count > 0 && !core.Contains(asms.Last())) - { - asms.RemoveAt(asms.Count - 1); - } - stats.Cores++; - Solver.Add(!Context.MkAnd(core)); - if (asms.Count > 0) - { - BoolExpr last = asms.Last(); - BoolExpr not_last = last.IsNot ? (BoolExpr)last.Args[0] : Context.MkNot(last); - asms.RemoveAt(asms.Count - 1); - asms.Add(not_last); - Status status = CheckSat(null, asms); - asms.RemoveAt(asms.Count - 1); - if (status != Status.UNSATISFIABLE) - { - asms.Add(last); - return; - } - core = new HashSet(Solver.UnsatCore); - if (core.Contains(not_last)) - { - stats.Cores++; - Solver.Add(!Context.MkAnd(core)); - status = CheckSat(null, asms); - } - if (status == Status.UNSATISFIABLE) - { - _Backtrack(stats, asms); - } - } - } - -#endif - bool canceled(solver_state& s) { if (s.canceled()) { m_has_undef = true; @@ -644,9 +683,8 @@ private: void run_solver() { try { - add_branches(1); while (solver_state* st = m_queue.get_task()) { - cube_and_conquer(*st); + cube_and_conquer2(*st); collect_statistics(*st); m_queue.task_done(st); if (st->m().canceled()) m_queue.shutdown(); @@ -669,11 +707,16 @@ private: } void collect_statistics(solver_state& s) { + collect_statistics(s.get_solver()); + } + + void collect_statistics(solver& s) { std::lock_guard lock(m_mutex); - s.get_solver().collect_statistics(m_stats); + s.collect_statistics(m_stats); } lbool solve(model_ref& mdl) { + add_branches(1); vector threads; for (unsigned i = 0; i < m_num_threads; ++i) threads.push_back(std::thread([this]() { run_solver(); })); @@ -706,7 +749,7 @@ public: parallel_tactic(ast_manager& m, params_ref const& p) : m_manager(m), m_params(p) { - init(); + init(); } void operator ()(const goal_ref & g,goal_ref_buffer & result) { @@ -746,23 +789,13 @@ public: result.push_back(g.get()); } - virtual void collect_param_descrs(param_descrs & r) { - r.insert("conquer_batch_size", CPK_UINT, "(default: 1000) batch size of cubes to conquer"); - } - unsigned conquer_batch_size() const { parallel_params pp(m_params); return pp.conquer_batch_size(); } - bool filter_cubes() const { - parallel_params pp(m_params); - return pp.filter_cubes(); - } - void cleanup() { m_queue.reset(); - init(); } tactic* translate(ast_manager& m) { @@ -771,6 +804,8 @@ public: virtual void updt_params(params_ref const & p) { m_params.copy(p); + parallel_params pp(p); + m_conquer_threshold = pp.conquer_threshold(); } virtual void collect_statistics(statistics & st) const { diff --git a/src/tactic/smtlogics/parallel_params.pyg b/src/tactic/smtlogics/parallel_params.pyg index 58da305f4..84f4eb2c4 100644 --- a/src/tactic/smtlogics/parallel_params.pyg +++ b/src/tactic/smtlogics/parallel_params.pyg @@ -6,6 +6,6 @@ def_module_params('parallel', ('enable', BOOL, False, 'enable parallel solver by default on selected tactics (for QF_BV)'), ('conquer_batch_size', UINT, 1000, 'number of cubes to batch together for fast conquer'), ('inprocess.max', UINT, 2, 'maximal number of inprocessing steps during simplification'), - ('restart.max', UINT, 100, 'maximal number of restarts during conquer phase'), - ('filter_cubes', BOOL, False, 'filter cubes using a short running check'), + ('restart.max', UINT, 5, 'maximal number of restarts during conquer phase'), + ('conquer_threshold', UINT, 10, 'number of cubes generated before simple conquer solver is created'), ))