mirror of
				https://github.com/Z3Prover/z3
				synced 2025-10-30 19:22:28 +00:00 
			
		
		
		
	Merge branch 'parallel' into param-tuning
This commit is contained in:
		
						commit
						39ec6764b6
					
				
					 2 changed files with 122 additions and 189 deletions
				
			
		|  | @ -412,17 +412,7 @@ namespace smt { | ||||||
|         switch (m_state) { |         switch (m_state) { | ||||||
|         case state::is_running:  // batch manager is still running, but all threads have processed their cubes, which
 |         case state::is_running:  // batch manager is still running, but all threads have processed their cubes, which
 | ||||||
|                                  // means all cubes were unsat
 |                                  // means all cubes were unsat
 | ||||||
|             if (!m_search_tree.is_closed()) |             throw default_exception("inconsistent end state"); | ||||||
|                 throw default_exception("inconsistent end state"); |  | ||||||
| 
 |  | ||||||
|             // case when all cubes were unsat, but depend on nonempty asms, so we need to add these asms to final unsat core
 |  | ||||||
|             // these asms are stored in the cube tree, at the root node
 |  | ||||||
|             if (p.ctx.m_unsat_core.empty()) { |  | ||||||
|                 SASSERT(root && root->is_closed()); |  | ||||||
|                 for (auto a : m_search_tree.get_core_from_root()) |  | ||||||
|                     p.ctx.m_unsat_core.push_back(a); |  | ||||||
|             } |  | ||||||
|             return l_false; |  | ||||||
|         case state::is_unsat: |         case state::is_unsat: | ||||||
|             return l_false; |             return l_false; | ||||||
|         case state::is_sat: |         case state::is_sat: | ||||||
|  |  | ||||||
|  | @ -35,26 +35,33 @@ namespace search_tree { | ||||||
| 
 | 
 | ||||||
|     enum class status { open, closed, active }; |     enum class status { open, closed, active }; | ||||||
| 
 | 
 | ||||||
|     template<typename Config> |     template <typename Config> class node { | ||||||
|     class node { |  | ||||||
|         typedef typename Config::literal literal; |         typedef typename Config::literal literal; | ||||||
|         literal m_literal; |         literal m_literal; | ||||||
|         node* m_left = nullptr, * m_right = nullptr, * m_parent = nullptr; |         node *m_left = nullptr, *m_right = nullptr, *m_parent = nullptr; | ||||||
|         status m_status; |         status m_status; | ||||||
|         vector<literal> m_core; |         vector<literal> m_core; | ||||||
|  | 
 | ||||||
|     public: |     public: | ||||||
|         node(literal const& l, node* parent) : |         node(literal const &l, node *parent) : m_literal(l), m_parent(parent), m_status(status::open) {} | ||||||
|             m_literal(l), m_parent(parent), m_status(status::open) {} |  | ||||||
|         ~node() { |         ~node() { | ||||||
|             dealloc(m_left); |             dealloc(m_left); | ||||||
|             dealloc(m_right); |             dealloc(m_right); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         status get_status() const { return m_status; } |         status get_status() const { | ||||||
|         void set_status(status s) { m_status = s; } |             return m_status; | ||||||
|         literal const& get_literal() const { return m_literal; } |         } | ||||||
|         bool literal_is_null() const { return Config::is_null(m_literal); } |         void set_status(status s) { | ||||||
|         void split(literal const& a, literal const& b) { |             m_status = s; | ||||||
|  |         } | ||||||
|  |         literal const &get_literal() const { | ||||||
|  |             return m_literal; | ||||||
|  |         } | ||||||
|  |         bool literal_is_null() const { | ||||||
|  |             return Config::is_null(m_literal); | ||||||
|  |         } | ||||||
|  |         void split(literal const &a, literal const &b) { | ||||||
|             SASSERT(!Config::literal_is_null(a)); |             SASSERT(!Config::literal_is_null(a)); | ||||||
|             SASSERT(!Config::literal_is_null(b)); |             SASSERT(!Config::literal_is_null(b)); | ||||||
|             if (m_status != status::active) |             if (m_status != status::active) | ||||||
|  | @ -66,16 +73,22 @@ namespace search_tree { | ||||||
|             m_status = status::open; |             m_status = status::open; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         node* left() const { return m_left; } |         node *left() const { | ||||||
|         node* right() const { return m_right; } |             return m_left; | ||||||
|         node* parent() const { return m_parent; } |         } | ||||||
|  |         node *right() const { | ||||||
|  |             return m_right; | ||||||
|  |         } | ||||||
|  |         node *parent() const { | ||||||
|  |             return m_parent; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         node* find_active_node() { |         node *find_active_node() { | ||||||
|             if (m_status == status::active) |             if (m_status == status::active) | ||||||
|                 return this; |                 return this; | ||||||
|             if (m_status != status::open) |             if (m_status != status::open) | ||||||
|                 return nullptr; |                 return nullptr; | ||||||
|             node* nodes[2] = { m_left, m_right }; |             node *nodes[2] = {m_left, m_right}; | ||||||
|             for (unsigned i = 0; i < 2; ++i) { |             for (unsigned i = 0; i < 2; ++i) { | ||||||
|                 auto res = nodes[i] ? nodes[i]->find_active_node() : nullptr; |                 auto res = nodes[i] ? nodes[i]->find_active_node() : nullptr; | ||||||
|                 if (res) |                 if (res) | ||||||
|  | @ -86,7 +99,7 @@ namespace search_tree { | ||||||
|             return nullptr; |             return nullptr; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         void display(std::ostream& out, unsigned indent) const { |         void display(std::ostream &out, unsigned indent) const { | ||||||
|             for (unsigned i = 0; i < indent; ++i) |             for (unsigned i = 0; i < indent; ++i) | ||||||
|                 out << " "; |                 out << " "; | ||||||
|             Config::display_literal(out, m_literal); |             Config::display_literal(out, m_literal); | ||||||
|  | @ -98,16 +111,18 @@ namespace search_tree { | ||||||
|                 m_right->display(out, indent + 2); |                 m_right->display(out, indent + 2); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         bool has_core() const { return !m_core.empty(); } |  | ||||||
|         void set_core(vector<literal> const &core) { |         void set_core(vector<literal> const &core) { | ||||||
|             m_core = core; |             m_core = core; | ||||||
|         } |         } | ||||||
|         vector<literal> const & get_core() const { return m_core; } |         vector<literal> const &get_core() const { | ||||||
|         void clear_core() { m_core.clear(); } |             return m_core; | ||||||
|  |         } | ||||||
|  |         void clear_core() { | ||||||
|  |             m_core.clear(); | ||||||
|  |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     template<typename Config> |     template <typename Config> class tree { | ||||||
|     class tree { |  | ||||||
|         typedef typename Config::literal literal; |         typedef typename Config::literal literal; | ||||||
|         scoped_ptr<node<Config>> m_root = nullptr; |         scoped_ptr<node<Config>> m_root = nullptr; | ||||||
|         literal m_null_literal; |         literal m_null_literal; | ||||||
|  | @ -115,7 +130,7 @@ namespace search_tree { | ||||||
| 
 | 
 | ||||||
|         // return an active node in the subtree rooted at n, or nullptr if there is none
 |         // return an active node in the subtree rooted at n, or nullptr if there is none
 | ||||||
|         // close nodes that are fully explored (whose children are all closed)
 |         // close nodes that are fully explored (whose children are all closed)
 | ||||||
|         node<Config>* activate_from_root(node<Config>* n) { |         node<Config> *activate_from_root(node<Config> *n) { | ||||||
|             if (!n) |             if (!n) | ||||||
|                 return nullptr; |                 return nullptr; | ||||||
|             if (n->get_status() != status::open) |             if (n->get_status() != status::open) | ||||||
|  | @ -126,7 +141,7 @@ namespace search_tree { | ||||||
|                 n->set_status(status::active); |                 n->set_status(status::active); | ||||||
|                 return n; |                 return n; | ||||||
|             } |             } | ||||||
|             node<Config>* nodes[2] = { left, right }; |             node<Config> *nodes[2] = {left, right}; | ||||||
|             unsigned index = m_rand(2); |             unsigned index = m_rand(2); | ||||||
|             auto child = activate_from_root(nodes[index]); |             auto child = activate_from_root(nodes[index]); | ||||||
|             if (child) |             if (child) | ||||||
|  | @ -139,140 +154,70 @@ namespace search_tree { | ||||||
|             return nullptr; |             return nullptr; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|       // Invariants: 
 |         void close(node<Config> *n) { | ||||||
|       // Cores labeling nodes are subsets of the literals on the path to the node and the (external) assumption literals.
 |             if (!n || n->get_status() == status::closed) | ||||||
|       // If a parent is open, then the one of the children is open.
 |                 return; | ||||||
|       void close_with_core(node<Config>* n, vector<literal> const &C, bool allow_resolve = true) { |             n->set_status(status::closed); | ||||||
|           if (!n || n->get_status() == status::closed) |             close(n->left()); | ||||||
|               return; |             close(n->right()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|           n->set_core(C); |         // Invariants:
 | ||||||
|           n->set_status(status::closed); |         // Cores labeling nodes are subsets of the literals on the path to the node and the (external) assumption
 | ||||||
|  |         // literals. If a parent is open, then the one of the children is open.
 | ||||||
|  |         void close_with_core(node<Config> *n, vector<literal> const &C) { | ||||||
|  |             if (!n || n->get_status() == status::closed) | ||||||
|  |                 return; | ||||||
|  |             node<Config> *p = n->parent(); | ||||||
|  |             if (p && all_of(C, [n](auto const &l) { return l != n->get_literal(); })) { | ||||||
|  |                 close_with_core(p, C); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             close(n->left()); | ||||||
|  |             close(n->right()); | ||||||
|  |             n->set_core(C); | ||||||
|  |             n->set_status(status::closed); | ||||||
| 
 | 
 | ||||||
|           close_with_core(n->left(), C, false); |             if (!p) | ||||||
|           close_with_core(n->right(), C, false); |                 return; | ||||||
|  |             auto left = p->left(); | ||||||
|  |             auto right = p->right(); | ||||||
|  |             if (!left || !right) | ||||||
|  |                 return; | ||||||
| 
 | 
 | ||||||
|           // stop at root
 |             // only attempt when both children are closed and each has a core
 | ||||||
|           if (!n->parent()) return; |             if (left->get_status() != status::closed || right->get_status() != status::closed) | ||||||
|  |                 return; | ||||||
| 
 | 
 | ||||||
|           node<Config>* p = n->parent(); |             auto resolvent = compute_sibling_resolvent(left, right); | ||||||
|           if (!p) return; // root reached
 |             close_with_core(p, resolvent); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|           auto is_literal_in_core = [](literal const& l, vector<literal> const& C) { |         // Given complementary sibling nodes for literals x and ¬x, sibling resolvent = (core_left ∪ core_right) \ {x,
 | ||||||
|               for (unsigned i = 0; i < C.size(); ++i) |         // ¬x}
 | ||||||
|                   if (C[i] == l) return true; |         vector<literal> compute_sibling_resolvent(node<Config> *left, node<Config> *right) { | ||||||
|               return false; |             vector<literal> res; | ||||||
|           }; |  | ||||||
| 
 | 
 | ||||||
|           // case 1: current splitting literal not in the conflict core
 |             auto &core_l = left->get_core(); | ||||||
|           if (!is_literal_in_core(n->get_literal(), C)) { |             auto &core_r = right->get_core(); | ||||||
|               close_with_core(p, C); |  | ||||||
|           // case 2: both siblings closed -> resolve
 |  | ||||||
|           } else if (allow_resolve && p->left()->get_status() == status::closed && p->right()->get_status() == status::closed) { |  | ||||||
|               try_resolve_upwards(p); |  | ||||||
|           } |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       // Given complementary sibling nodes for literals x and ¬x, sibling resolvent = (core_left ∪ core_right) \ {x, ¬x}
 |             if (core_l.empty() || core_r.empty() || left->parent() != right->parent()) | ||||||
|         vector<literal> compute_sibling_resolvent(node<Config>* left, node<Config>* right) { |                 return res; | ||||||
|           vector<literal> res; |  | ||||||
| 
 | 
 | ||||||
|           if (!left->has_core() || !right->has_core()) return res; |             auto lit_l = left->get_literal(); | ||||||
|  |             auto lit_r = right->get_literal(); | ||||||
| 
 | 
 | ||||||
|           bool are_sibling_complements = left->parent() == right->parent(); |             for (auto const &lit : core_l) | ||||||
|           if (!are_sibling_complements) |                 if (lit != lit_l && !res.contains(lit)) | ||||||
|               return res; |                     res.push_back(lit); | ||||||
| 
 |             for (auto const &lit : core_r) | ||||||
|           auto &core_l = left->get_core(); |                 if (lit != lit_l && !res.contains(lit)) | ||||||
|           auto &core_r = right->get_core(); |                     res.push_back(lit); | ||||||
| 
 |             return res; | ||||||
|           auto contains = [](vector<literal> const &v, literal const &l) { |         } | ||||||
|               for (unsigned i = 0; i < v.size(); ++i) |  | ||||||
|                   if (v[i] == l) return true; |  | ||||||
|               return false; |  | ||||||
|           }; |  | ||||||
| 
 |  | ||||||
|           auto lit_l = left->get_literal(); |  | ||||||
|           auto lit_r = right->get_literal(); |  | ||||||
| 
 |  | ||||||
|           // Add literals from left core, skipping lit_l
 |  | ||||||
|           for (unsigned i = 0; i < core_l.size(); ++i) { |  | ||||||
|               if (core_l[i] != lit_l && !contains(res, core_l[i])) |  | ||||||
|                   res.push_back(core_l[i]); |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           // Add literals from right core, skipping lit_r
 |  | ||||||
|           for (unsigned i = 0; i < core_r.size(); ++i) { |  | ||||||
|               if (core_r[i] != lit_r && !contains(res, core_r[i])) |  | ||||||
|                   res.push_back(core_r[i]); |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           return res; |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       void try_resolve_upwards(node<Config>* p) { |  | ||||||
|           while (p) { |  | ||||||
|               auto left = p->left(); |  | ||||||
|               auto right = p->right(); |  | ||||||
|               if (!left || !right) return; |  | ||||||
| 
 |  | ||||||
|               // only attempt when both children are closed and each has a core
 |  | ||||||
|               if (left->get_status() != status::closed || right->get_status() != status::closed) return; |  | ||||||
|               if (!left->has_core() || !right->has_core()) return; |  | ||||||
| 
 |  | ||||||
|               auto resolvent = compute_sibling_resolvent(left, right); |  | ||||||
| 
 |  | ||||||
|               // empty resolvent of sibling complement (i.e. tautology) -> global UNSAT
 |  | ||||||
|               if (resolvent.empty()) { |  | ||||||
|                   close_with_core(m_root.get(), resolvent, false); |  | ||||||
|                   return; |  | ||||||
|               } |  | ||||||
| 
 |  | ||||||
|               // if p already has the same core, nothing more to do
 |  | ||||||
|               if (p->has_core() && resolvent == p->get_core()) |  | ||||||
|                   return; |  | ||||||
| 
 |  | ||||||
|               // Bubble to the highest ancestor where ALL literals in the resolvent
 |  | ||||||
|               // are present somewhere on the path from that ancestor to root
 |  | ||||||
|               node<Config>* candidate = p; |  | ||||||
|               node<Config>* attach_here = p; // fallback
 |  | ||||||
| 
 |  | ||||||
|               while (candidate) { |  | ||||||
|                   bool all_found = true; |  | ||||||
| 
 |  | ||||||
|                   for (auto const& r : resolvent) { |  | ||||||
|                       bool found = false; |  | ||||||
|                       for (node<Config>* q = candidate; q; q = q->parent()) { |  | ||||||
|                           if (q->get_literal() == r) { |  | ||||||
|                               found = true; |  | ||||||
|                               break; |  | ||||||
|                           } |  | ||||||
|                       } |  | ||||||
|                       if (!found) { |  | ||||||
|                           all_found = false; |  | ||||||
|                           break; |  | ||||||
|                       } |  | ||||||
|                   } |  | ||||||
| 
 |  | ||||||
|                   if (all_found) { |  | ||||||
|                       attach_here = candidate;  // bubble up to this node
 |  | ||||||
|                   } |  | ||||||
| 
 |  | ||||||
|                   candidate = candidate->parent(); |  | ||||||
|               } |  | ||||||
| 
 |  | ||||||
|               // attach the resolvent and close the subtree at attach_here
 |  | ||||||
|               if (!attach_here->has_core() || attach_here->get_core() != resolvent) { |  | ||||||
|                   close_with_core(attach_here, resolvent, false); |  | ||||||
|               } |  | ||||||
| 
 |  | ||||||
|               // continue upward from parent of attach_here
 |  | ||||||
|               p = attach_here->parent(); |  | ||||||
|           } |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|     public: |     public: | ||||||
|         tree(literal const& null_literal) : m_null_literal(null_literal) { |         tree(literal const &null_literal) : m_null_literal(null_literal) { | ||||||
|             reset(); |             reset(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -287,13 +232,13 @@ namespace search_tree { | ||||||
| 
 | 
 | ||||||
|         // Split current node if it is active.
 |         // Split current node if it is active.
 | ||||||
|         // After the call, n is open and has two children.
 |         // After the call, n is open and has two children.
 | ||||||
|         void split(node<Config>* n, literal const& a, literal const& b) {            |         void split(node<Config> *n, literal const &a, literal const &b) { | ||||||
|             n->split(a, b); |             n->split(a, b); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // conflict is given by a set of literals.
 |         // conflict is given by a set of literals.
 | ||||||
|         // they are subsets of the literals on the path from root to n AND the external assumption literals
 |         // they are subsets of the literals on the path from root to n AND the external assumption literals
 | ||||||
|         void backtrack(node<Config>* n, vector<literal> const& conflict) { |         void backtrack(node<Config> *n, vector<literal> const &conflict) { | ||||||
|             if (conflict.empty()) { |             if (conflict.empty()) { | ||||||
|                 close_with_core(m_root.get(), conflict); |                 close_with_core(m_root.get(), conflict); | ||||||
|                 return; |                 return; | ||||||
|  | @ -301,21 +246,20 @@ namespace search_tree { | ||||||
|             SASSERT(n != m_root.get()); |             SASSERT(n != m_root.get()); | ||||||
|             // all literals in conflict are on the path from root to n
 |             // all literals in conflict are on the path from root to n
 | ||||||
|             // remove assumptions from conflict to ensure this.
 |             // remove assumptions from conflict to ensure this.
 | ||||||
|             DEBUG_CODE( |             DEBUG_CODE(auto on_path = | ||||||
|                 auto on_path = [&](literal const& a) { |                            [&](literal const &a) { | ||||||
|                     node<Config>* p = n; |                                node<Config> *p = n; | ||||||
|                     while (p) { |                                while (p) { | ||||||
|                         if (p->get_literal() == a) |                                    if (p->get_literal() == a) | ||||||
|                             return true; |                                        return true; | ||||||
|                         p = p->parent(); |                                    p = p->parent(); | ||||||
|                     } |                                } | ||||||
|                     return false; |                                return false; | ||||||
|                 }; |                            }; | ||||||
|                 SASSERT(all_of(conflict, [&](auto const& a) { return on_path(a); })); |                        SASSERT(all_of(conflict, [&](auto const &a) { return on_path(a); }));); | ||||||
|             ); |  | ||||||
| 
 | 
 | ||||||
|             while (n) { |             while (n) { | ||||||
|                 if (any_of(conflict, [&](auto const& a) { return a == n->get_literal(); })) { |                 if (any_of(conflict, [&](auto const &a) { return a == n->get_literal(); })) { | ||||||
|                     // close the subtree under n (preserves core attached to n), and attempt to resolve upwards
 |                     // close the subtree under n (preserves core attached to n), and attempt to resolve upwards
 | ||||||
|                     close_with_core(n, conflict); |                     close_with_core(n, conflict); | ||||||
|                     return; |                     return; | ||||||
|  | @ -329,7 +273,7 @@ namespace search_tree { | ||||||
|         // return an active node in the tree, or nullptr if there is none
 |         // return an active node in the tree, or nullptr if there is none
 | ||||||
|         // first check if there is a node to activate under n,
 |         // first check if there is a node to activate under n,
 | ||||||
|         // if not, go up the tree and try to activate a sibling subtree
 |         // if not, go up the tree and try to activate a sibling subtree
 | ||||||
|         node<Config>* activate_node(node<Config>* n) { |         node<Config> *activate_node(node<Config> *n) { | ||||||
|             if (!n) { |             if (!n) { | ||||||
|                 if (m_root->get_status() == status::active) |                 if (m_root->get_status() == status::active) | ||||||
|                     return m_root.get(); |                     return m_root.get(); | ||||||
|  | @ -341,8 +285,8 @@ namespace search_tree { | ||||||
| 
 | 
 | ||||||
|             auto p = n->parent(); |             auto p = n->parent(); | ||||||
|             while (p) { |             while (p) { | ||||||
|                 if (p->left() && p->left()->get_status() == status::closed && |                 if (p->left() && p->left()->get_status() == status::closed && p->right() && | ||||||
|                     p->right() && p->right()->get_status() == status::closed) { |                     p->right()->get_status() == status::closed) { | ||||||
|                     p->set_status(status::closed); |                     p->set_status(status::closed); | ||||||
|                     n = p; |                     n = p; | ||||||
|                     p = n->parent(); |                     p = n->parent(); | ||||||
|  | @ -365,11 +309,11 @@ namespace search_tree { | ||||||
|             return nullptr; |             return nullptr; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         node<Config>* find_active_node() { |         node<Config> *find_active_node() { | ||||||
|             return m_root->find_active_node(); |             return m_root->find_active_node(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         vector<literal> const& get_core_from_root() const { |         vector<literal> const &get_core_from_root() const { | ||||||
|             return m_root->get_core(); |             return m_root->get_core(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -377,10 +321,9 @@ namespace search_tree { | ||||||
|             return m_root->get_status() == status::closed; |             return m_root->get_status() == status::closed; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         std::ostream& display(std::ostream& out) const { |         std::ostream &display(std::ostream &out) const { | ||||||
|             m_root->display(out, 0); |             m_root->display(out, 0); | ||||||
|             return out; |             return out; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|     }; |     }; | ||||||
| } | }  // namespace search_tree
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue