3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-26 04:56:03 +00:00
z3/src/solver/combined_solver.cpp
Ilana Shapiro 6044389446
Parallel solving (#7771)
* very basic setup

* ensure solve_eqs is fully disabled when smt.solve_eqs=false, #7743

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* respect smt configuration parameter in elim_unconstrained simplifier

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* indentation

* add bash files for test runs

* add option to selectively disable variable solving for only ground expressions

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* remove verbose output

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* fix #7745

axioms for len(substr(...)) escaped due to nested rewriting

* ensure atomic constraints are processed by arithmetic solver

* #7739 optimization

add simplification rule for at(x, offset) = ""

Introducing j just postpones some rewrites that prevent useful simplifications. Z3 already uses common sub-expressions.
The example highlights some opportunities for simplification, noteworthy at(..) = "".
The example is solved in both versions after adding this simplification.

* fix unsound len(substr) axiom

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* FreshConst is_sort (#7748)

* #7750

add pre-processing simplification

* Add parameter validation for selected API functions

* updates to ac-plugin

fix incrementality bugs by allowing destructive updates during saturation at the cost of redoing saturation after a pop.

* enable passive, add check for bloom up-to-date

* add top-k fixed-sized min-heap priority queue for top scoring literals

* set up worker thread batch manager for multithreaded batch cubes paradigm, need to debug as I am getting segfault still

* fix bug in parallel solving batch setup

* fix bug

* allow for internalize implies

* disable pre-processing during cubing

* debugging

* remove default constructor

* remove a bunch of string copies

* Update euf_ac_plugin.cpp

include reduction rules in forward simplification

* Update euf_completion.cpp

try out restricting scope of equalities added by instantation

* Update smt_parallel.cpp

Drop non-relevant units from shared structures.

* process cubes as lists of individual lits

* merge

* Add support for Algebraic Datatypes in JavaScript/TypeScript bindings (#7734)

* Initial plan

* Add datatype type definitions to types.ts (work in progress)

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

* Complete datatype type definitions with working TypeScript compilation

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

* Implement core datatype functionality with TypeScript compilation success

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

* Complete datatype implementation with full Context integration and tests

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

* chipping away at the new code structure

* comments

* debug infinite recursion and split cubes on existing split atoms that aren't in the cube

* share lemmas, learn from unsat core, try to debug a couple of things, there was a subtle bug that i have a hard time repro'ing

* merge

* fix #7603: race condition in Ctrl-C handling (#7755)

* fix #7603: race condition in Ctrl-C handling

* fix race in cancel_eh

* fix build

* add arithemtic saturation

* add an option to register callback on quantifier instantiation

Suppose a user propagator encodes axioms using quantifiers and uses E-matching for instantiation. If it wants to implement a custom priority scheme or drop some instances based on internal checks it can register a callback with quantifier instantiation

* missing new closure

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* add Z3_solver_propagate_on_binding to ml callback declarations

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* add python file

Signed-off-by: Lev Nachmanson <levnach@Levs-MacBook-Pro.local>

* debug under defined calls

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* more untangle params

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* precalc parameters to define the eval order

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* remove a printout

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* rename a Python file

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* add on_binding callbacks across APIs

update release notes,
add to Java, .Net, C++

* use jboolean in Native interface

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* register on_binding attribute

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* fix java build for java bindings

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* avoid interferring side-effects in function calls

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* remove theory_str and classes that are only used by it

* remove automata from python build

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* remove ref to theory_str

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* get the finest factorizations before project

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* rename add_lcs to add_lc

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* resolve bad bug about l2g and g2l translators using wrong global context. add some debug prints

* initial attempt at dynamically switching from greedy to frugal splitting strategy in return_cubes. need to test. also there is some bug where the threads take forever to cancel?

* Update RELEASE_NOTES.md

* resolve bug about not translating managers correctly for the second phase of the greedy cubing, and the frugal fallback

---------

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Signed-off-by: Lev Nachmanson <levnach@Levs-MacBook-Pro.local>
Signed-off-by: Lev Nachmanson <levnach@hotmail.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Co-authored-by: humnrdble <83878671+humnrdble@users.noreply.github.com>
Co-authored-by: Nuno Lopes <nuno.lopes@tecnico.ulisboa.pt>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
Co-authored-by: Lev Nachmanson <levnach@hotmail.com>
2025-08-11 09:14:20 -07:00

431 lines
14 KiB
C++

/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
combined_solver.cpp
Abstract:
Implements the solver API by combining two solvers.
This is a replacement for the strategic_solver class.
Author:
Leonardo (leonardo) 2012-12-11
Notes:
--*/
#include "util/scoped_timer.h"
#include "util/common_msgs.h"
#include "ast/ast_pp.h"
#include "solver/solver.h"
#include "solver/combined_solver_params.hpp"
#include <atomic>
#define PS_VB_LVL 15
/**
\brief Implementation of the solver API that combines two given solvers.
The combined solver has two modes:
- non-incremental
- incremental
In non-incremental mode, the first solver is used.
In incremental mode, the second one is used.
A timeout for the second solver can be specified.
If the timeout is reached, then the first solver is executed.
The object switches to incremental when:
- push is used
- assertions are performed after a check_sat
- parameter ignore_solver1==false
*/
class combined_solver : public solver {
public:
// Behavior when the incremental solver returns unknown.
enum inc_unknown_behavior {
IUB_RETURN_UNDEF, // just return unknown
IUB_USE_TACTIC_IF_QF, // invoke tactic if problem is quantifier free
IUB_USE_TACTIC // invoke tactic
};
private:
bool m_inc_mode;
bool m_check_sat_executed;
bool m_use_solver1_results;
ref<solver> m_solver1;
ref<solver> m_solver2;
// We delay sending assertions to solver 2
// This is relevant for big benchmarks that are meant to be solved
// by a non-incremental solver. );
bool m_ignore_solver1;
inc_unknown_behavior m_inc_unknown_behavior;
unsigned m_inc_timeout;
void switch_inc_mode() {
m_inc_mode = true;
}
struct aux_timeout_eh : public event_handler {
solver * m_solver;
std::atomic<bool> m_canceled;
aux_timeout_eh(solver * s):m_solver(s), m_canceled(false) {}
~aux_timeout_eh() override {
if (m_canceled) {
m_solver->get_manager().limit().dec_cancel();
}
}
void operator()(event_handler_caller_t caller_id) override {
m_canceled = true;
m_solver->get_manager().limit().inc_cancel();
}
};
void updt_local_params(params_ref const & _p) {
combined_solver_params p(_p);
m_inc_timeout = p.solver2_timeout();
m_ignore_solver1 = p.ignore_solver1();
m_inc_unknown_behavior = static_cast<inc_unknown_behavior>(p.solver2_unknown());
}
ast_manager& get_manager() const override { return m_solver1->get_manager(); }
bool has_quantifiers() const {
unsigned sz = get_num_assertions();
for (unsigned i = 0; i < sz; i++) {
if (::has_quantifiers(get_assertion(i)))
return true;
}
return false;
}
bool use_solver1_when_undef() const {
switch (m_inc_unknown_behavior) {
case IUB_RETURN_UNDEF: return false;
case IUB_USE_TACTIC_IF_QF: return !has_quantifiers();
case IUB_USE_TACTIC: return true;
default:
UNREACHABLE();
return false;
}
}
public:
combined_solver(solver * s1, solver * s2, params_ref const & p):
solver(s1->get_manager()) {
m_solver1 = s1;
m_solver2 = s2;
updt_local_params(p);
m_inc_mode = false;
m_check_sat_executed = false;
m_use_solver1_results = true;
}
solver* translate(ast_manager& m, params_ref const& p) override {
TRACE(solver, tout << "translate\n";);
solver* s1 = m_solver1->translate(m, p);
solver* s2 = m_solver2->translate(m, p);
combined_solver* r = alloc(combined_solver, s1, s2, p);
r->m_inc_mode = m_inc_mode;
r->m_check_sat_executed = m_check_sat_executed;
r->m_use_solver1_results = m_use_solver1_results;
return r;
}
void set_phase(expr* e) override { m_solver1->set_phase(e); m_solver2->set_phase(e); }
solver::phase* get_phase() override { auto* p = m_solver1->get_phase(); if (!p) p = m_solver2->get_phase(); return p; }
void set_phase(solver::phase* p) override { m_solver1->set_phase(p); m_solver2->set_phase(p); }
void move_to_front(expr* e) override { m_solver1->move_to_front(e); m_solver2->move_to_front(e); }
void updt_params(params_ref const & p) override {
solver::updt_params(p);
m_solver1->updt_params(p);
m_solver2->updt_params(p);
updt_local_params(p);
}
void collect_param_descrs(param_descrs & r) override {
m_solver1->collect_param_descrs(r);
m_solver2->collect_param_descrs(r);
combined_solver_params::collect_param_descrs(r);
}
void set_produce_models(bool f) override {
m_solver1->set_produce_models(f);
m_solver2->set_produce_models(f);
}
void assert_expr_core(expr * t) override {
if (m_check_sat_executed)
switch_inc_mode();
m_solver1->assert_expr(t);
m_solver2->assert_expr(t);
}
void assert_expr_core2(expr * t, expr * a) override {
if (m_check_sat_executed)
switch_inc_mode();
m_solver1->assert_expr(t, a);
m_solver2->assert_expr(t, a);
}
void push() override {
switch_inc_mode();
m_solver1->push();
m_solver2->push();
TRACE(pop, tout << "push\n";);
}
void pop(unsigned n) override {
TRACE(pop, tout << n << "\n";);
switch_inc_mode();
m_solver1->pop(n);
m_solver2->pop(n);
}
unsigned get_scope_level() const override {
return m_solver1->get_scope_level();
}
lbool get_consequences(expr_ref_vector const& asms, expr_ref_vector const& vars, expr_ref_vector& consequences) override {
switch_inc_mode();
m_use_solver1_results = false;
try {
return m_solver2->get_consequences(asms, vars, consequences);
}
catch (z3_exception& ex) {
if (!get_manager().inc()) {
throw;
}
else {
set_reason_unknown(ex.what());
}
}
return l_undef;
}
lbool check_sat_core(unsigned num_assumptions, expr * const * assumptions) override {
m_check_sat_executed = true;
m_use_solver1_results = false;
if (get_num_assumptions() != 0 ||
num_assumptions > 0 || // assumptions were provided
m_ignore_solver1) {
// must use incremental solver
switch_inc_mode();
return m_solver2->check_sat_core(num_assumptions, assumptions);
}
if (m_inc_mode) {
if (m_inc_timeout == UINT_MAX) {
IF_VERBOSE(PS_VB_LVL, verbose_stream() << "(combined-solver \"using solver 2 (without a timeout)\")\n";);
lbool r = m_solver2->check_sat_core(num_assumptions, assumptions);
if (r != l_undef || !use_solver1_when_undef() || !get_manager().inc()) {
return r;
}
}
else {
IF_VERBOSE(PS_VB_LVL, verbose_stream() << "(combined-solver \"using solver 2 (with timeout)\")\n";);
aux_timeout_eh eh(m_solver2.get());
lbool r = l_undef;
try {
scoped_timer timer(m_inc_timeout, &eh);
r = m_solver2->check_sat_core(num_assumptions, assumptions);
}
catch (z3_exception&) {
if (!eh.m_canceled) {
throw;
}
}
if ((r != l_undef || !use_solver1_when_undef()) && !eh.m_canceled) {
return r;
}
}
IF_VERBOSE(PS_VB_LVL, verbose_stream() << "(combined-solver \"solver 2 failed, trying solver1\")\n";);
}
IF_VERBOSE(PS_VB_LVL, verbose_stream() << "(combined-solver \"using solver 1\")\n";);
m_use_solver1_results = true;
return m_solver1->check_sat_core(num_assumptions, assumptions);
}
void set_progress_callback(progress_callback * callback) override {
m_solver1->set_progress_callback(callback);
m_solver2->set_progress_callback(callback);
}
unsigned get_num_assertions() const override {
return m_solver1->get_num_assertions();
}
expr * get_assertion(unsigned idx) const override {
return m_solver1->get_assertion(idx);
}
unsigned get_num_assumptions() const override {
return m_solver1->get_num_assumptions() + m_solver2->get_num_assumptions();
}
expr_ref_vector cube(expr_ref_vector& vars, unsigned backtrack_level) override {
switch_inc_mode();
return m_solver2->cube(vars, backtrack_level);
}
expr* congruence_next(expr* e) override { switch_inc_mode(); return m_solver2->congruence_next(e); }
expr* congruence_root(expr* e) override { switch_inc_mode(); return m_solver2->congruence_root(e); }
expr_ref congruence_explain(expr* a, expr* b) override { switch_inc_mode(); return m_solver2->congruence_explain(a, b); }
expr * get_assumption(unsigned idx) const override {
unsigned c1 = m_solver1->get_num_assumptions();
if (idx < c1) return m_solver1->get_assumption(idx);
return m_solver2->get_assumption(idx - c1);
}
std::ostream& display(std::ostream & out, unsigned n, expr* const* es) const override {
return m_solver1->display(out, n, es);
}
void collect_statistics(statistics & st) const override {
m_solver2->collect_statistics(st);
if (m_use_solver1_results)
m_solver1->collect_statistics(st);
}
void get_unsat_core(expr_ref_vector & r) override {
if (m_use_solver1_results)
m_solver1->get_unsat_core(r);
else
m_solver2->get_unsat_core(r);
}
void get_model_core(model_ref & m) override {
if (m_use_solver1_results)
m_solver1->get_model(m);
else
m_solver2->get_model(m);
}
void get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) override {
if (m_use_solver1_results)
m_solver1->get_levels(vars, depth);
else
m_solver2->get_levels(vars, depth);
}
expr_ref_vector get_trail(unsigned max_level) override {
if (m_use_solver1_results)
return m_solver1->get_trail(max_level);
else
return m_solver2->get_trail(max_level);
}
proof * get_proof_core() override {
if (m_use_solver1_results)
return m_solver1->get_proof_core();
else
return m_solver2->get_proof_core();
}
std::string reason_unknown() const override {
if (m_use_solver1_results)
return m_solver1->reason_unknown();
else
return m_solver2->reason_unknown();
}
void set_reason_unknown(char const* msg) override {
m_solver1->set_reason_unknown(msg);
m_solver2->set_reason_unknown(msg);
}
void get_labels(svector<symbol> & r) override {
if (m_use_solver1_results)
return m_solver1->get_labels(r);
else
return m_solver2->get_labels(r);
}
void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause) override {
switch_inc_mode();
m_solver2->register_on_clause(ctx, on_clause);
}
void user_propagate_init(
void* ctx,
user_propagator::push_eh_t& push_eh,
user_propagator::pop_eh_t& pop_eh,
user_propagator::fresh_eh_t& fresh_eh) override {
switch_inc_mode();
m_solver2->user_propagate_init(ctx, push_eh, pop_eh, fresh_eh);
}
void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override {
m_solver2->user_propagate_register_fixed(fixed_eh);
}
void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override {
m_solver2->user_propagate_register_final(final_eh);
}
void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override {
m_solver2->user_propagate_register_eq(eq_eh);
}
void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override {
m_solver2->user_propagate_register_diseq(diseq_eh);
}
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
m_solver2->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override {
m_solver2->user_propagate_register_expr(e);
}
void user_propagate_register_created(user_propagator::created_eh_t& r) override {
m_solver2->user_propagate_register_created(r);
}
void user_propagate_register_decide(user_propagator::decide_eh_t& r) override {
m_solver2->user_propagate_register_decide(r);
}
void user_propagate_clear() override {
m_solver2->user_propagate_clear();
}
void user_propagate_initialize_value(expr* var, expr* value) override {
m_solver1->user_propagate_initialize_value(var, value);
m_solver2->user_propagate_initialize_value(var, value);
}
};
solver * mk_combined_solver(solver * s1, solver * s2, params_ref const & p) {
return alloc(combined_solver, s1, s2, p);
}
class combined_solver_factory : public solver_factory {
scoped_ptr<solver_factory> m_f1;
scoped_ptr<solver_factory> m_f2;
public:
combined_solver_factory(solver_factory * f1, solver_factory * f2):m_f1(f1), m_f2(f2) {}
solver * operator()(ast_manager & m, params_ref const & p, bool proofs_enabled, bool models_enabled, bool unsat_core_enabled, symbol const & logic) override {
return mk_combined_solver((*m_f1)(m, p, proofs_enabled, models_enabled, unsat_core_enabled, logic),
(*m_f2)(m, p, proofs_enabled, models_enabled, unsat_core_enabled, logic),
p);
}
};
solver_factory * mk_combined_solver_factory(solver_factory * f1, solver_factory * f2) {
return alloc(combined_solver_factory, f1, f2);
}