mirror of
				https://github.com/Z3Prover/z3
				synced 2025-10-31 19:52:29 +00:00 
			
		
		
		
	revise axiom instantiation scheme for finite-sets
Instead of asserting theory axioms lazily we create them on the fly and allow propagation eagerly. The approach uses a waterfall model as follows: - terms are created: they are inserted into an index for (set.in x S) axiom creation. - two terms are merged by an equality. Loop over all new opportunities for axiom instantiation New axioms are added to a queue of recently created axioms. - an atomic formula was asserted by the SAT solver. Update the watch list to find new propagations. During propagation recently created axioms are either inserted into a propagation queue, or inserted into a watch list. They are inserted into a propagation queue all or all but one literal is assigned to false. They are inserted into a watch list if at least two literals are unassigned They are dropped if the axiom contains a literal that is assigned to true The propagation queue is processed by by asserting the theory axiom to the core. Also add some elementary statistics. A breaking change is to change the datatype for undo-trail in smt_context to not use a custom data-structure. This can likely cause regressions. For example, the region allocator now comes from the stack_trail instead of being owned within smt_context with a different declaration order. smt_context could crash during destruction or maybe even pop. We take the risk as the change is overdue. Add swap method to ref_vector.
This commit is contained in:
		
							parent
							
								
									aa1f1f56b6
								
							
						
					
					
						commit
						43d40ac142
					
				
					 7 changed files with 522 additions and 209 deletions
				
			
		|  | @ -66,7 +66,7 @@ namespace smt { | ||||||
|         m_progress_callback(nullptr), |         m_progress_callback(nullptr), | ||||||
|         m_next_progress_sample(0), |         m_next_progress_sample(0), | ||||||
|         m_clause_proof(*this), |         m_clause_proof(*this), | ||||||
|         m_fingerprints(m, m_region), |         m_fingerprints(m, get_region()), | ||||||
|         m_b_internalized_stack(m), |         m_b_internalized_stack(m), | ||||||
|         m_e_internalized_stack(m), |         m_e_internalized_stack(m), | ||||||
|         m_l_internalized_stack(m), |         m_l_internalized_stack(m), | ||||||
|  | @ -799,7 +799,7 @@ namespace smt { | ||||||
|                 } |                 } | ||||||
|                 else { |                 else { | ||||||
|                     // uncommon case: r2 will have two theory_vars attached to it.
 |                     // uncommon case: r2 will have two theory_vars attached to it.
 | ||||||
|                     r2->add_th_var(v1, t1, m_region); |                     r2->add_th_var(v1, t1, get_region()); | ||||||
|                     push_new_th_diseqs(r2, v1, get_theory(t1)); |                     push_new_th_diseqs(r2, v1, get_theory(t1)); | ||||||
|                     push_new_th_diseqs(r1, v2, get_theory(t2)); |                     push_new_th_diseqs(r1, v2, get_theory(t2)); | ||||||
|                 } |                 } | ||||||
|  | @ -850,7 +850,7 @@ namespace smt { | ||||||
|                 theory_var v2 = r2->get_th_var(t1); |                 theory_var v2 = r2->get_th_var(t1); | ||||||
|                 TRACE(merge_theory_vars, tout << get_theory(t1)->get_name() << ": " << v2 << " == " << v1 << "\n"); |                 TRACE(merge_theory_vars, tout << get_theory(t1)->get_name() << ": " << v2 << " == " << v1 << "\n"); | ||||||
|                 if (v2 == null_theory_var) { |                 if (v2 == null_theory_var) { | ||||||
|                     r2->add_th_var(v1, t1, m_region); |                     r2->add_th_var(v1, t1, get_region()); | ||||||
|                     push_new_th_diseqs(r2, v1, get_theory(t1)); |                     push_new_th_diseqs(r2, v1, get_theory(t1)); | ||||||
|                 } |                 } | ||||||
|                 l1 = l1->get_next(); |                 l1 = l1->get_next(); | ||||||
|  | @ -1939,14 +1939,13 @@ namespace smt { | ||||||
|             m.trace_stream() << "[push] " << m_scope_lvl << "\n"; |             m.trace_stream() << "[push] " << m_scope_lvl << "\n"; | ||||||
| 
 | 
 | ||||||
|         m_scope_lvl++; |         m_scope_lvl++; | ||||||
|         m_region.push_scope(); |         get_region().push_scope(); | ||||||
|         m_scopes.push_back(scope()); |         m_scopes.push_back(scope()); | ||||||
|         scope & s = m_scopes.back(); |         scope & s = m_scopes.back(); | ||||||
|         // TRACE(context, tout << "push " << m_scope_lvl << "\n";);
 |         // TRACE(context, tout << "push " << m_scope_lvl << "\n";);
 | ||||||
| 
 | 
 | ||||||
|         m_relevancy_propagator->push(); |         m_relevancy_propagator->push(); | ||||||
|         s.m_assigned_literals_lim    = m_assigned_literals.size(); |         s.m_assigned_literals_lim    = m_assigned_literals.size(); | ||||||
|         s.m_trail_stack_lim          = m_trail_stack.size(); |  | ||||||
|         s.m_aux_clauses_lim          = m_aux_clauses.size(); |         s.m_aux_clauses_lim          = m_aux_clauses.size(); | ||||||
|         s.m_justifications_lim       = m_justifications.size(); |         s.m_justifications_lim       = m_justifications.size(); | ||||||
|         s.m_units_to_reassert_lim    = m_units_to_reassert.size(); |         s.m_units_to_reassert_lim    = m_units_to_reassert.size(); | ||||||
|  | @ -1962,12 +1961,6 @@ namespace smt { | ||||||
|         CASSERT("context", check_invariant()); |         CASSERT("context", check_invariant()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |  | ||||||
|        \brief Execute generic undo-objects. |  | ||||||
|     */ |  | ||||||
|     void context::undo_trail_stack(unsigned old_size) { |  | ||||||
|         ::undo_trail_stack(m_trail_stack, old_size); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|        \brief Remove watch literal idx from the given clause. |        \brief Remove watch literal idx from the given clause. | ||||||
|  | @ -2455,7 +2448,7 @@ namespace smt { | ||||||
| 
 | 
 | ||||||
|             m_fingerprints.pop_scope(num_scopes); |             m_fingerprints.pop_scope(num_scopes); | ||||||
|             unassign_vars(s.m_assigned_literals_lim); |             unassign_vars(s.m_assigned_literals_lim); | ||||||
|             undo_trail_stack(s.m_trail_stack_lim); |             m_trail_stack.pop_scope(num_scopes); | ||||||
| 
 | 
 | ||||||
|             for (theory* th : m_theory_set)  |             for (theory* th : m_theory_set)  | ||||||
|                 th->pop_scope_eh(num_scopes); |                 th->pop_scope_eh(num_scopes); | ||||||
|  | @ -2470,7 +2463,6 @@ namespace smt { | ||||||
|             m_th_eq_propagation_queue.reset(); |             m_th_eq_propagation_queue.reset(); | ||||||
|             m_th_diseq_propagation_queue.reset(); |             m_th_diseq_propagation_queue.reset(); | ||||||
|             m_atom_propagation_queue.reset(); |             m_atom_propagation_queue.reset(); | ||||||
|             m_region.pop_scope(num_scopes); |  | ||||||
|             m_scopes.shrink(new_lvl); |             m_scopes.shrink(new_lvl); | ||||||
|             m_conflict_resolution->reset(); |             m_conflict_resolution->reset(); | ||||||
| 
 | 
 | ||||||
|  | @ -3058,7 +3050,7 @@ namespace smt { | ||||||
|         del_clauses(m_lemmas, 0); |         del_clauses(m_lemmas, 0); | ||||||
|         del_justifications(m_justifications, 0); |         del_justifications(m_justifications, 0); | ||||||
|         reset_tmp_clauses(); |         reset_tmp_clauses(); | ||||||
|         undo_trail_stack(0); |         m_trail_stack.reset(); | ||||||
|         m_qmanager = nullptr; |         m_qmanager = nullptr; | ||||||
|         if (m_is_diseq_tmp) { |         if (m_is_diseq_tmp) { | ||||||
|             m_is_diseq_tmp->del_eh(m, false); |             m_is_diseq_tmp->del_eh(m, false); | ||||||
|  |  | ||||||
|  | @ -113,7 +113,6 @@ namespace smt { | ||||||
|         progress_callback *         m_progress_callback; |         progress_callback *         m_progress_callback; | ||||||
|         unsigned                    m_next_progress_sample; |         unsigned                    m_next_progress_sample; | ||||||
|         clause_proof                m_clause_proof; |         clause_proof                m_clause_proof; | ||||||
|         region                      m_region; |  | ||||||
|         fingerprint_set             m_fingerprints; |         fingerprint_set             m_fingerprints; | ||||||
| 
 | 
 | ||||||
|         expr_ref_vector             m_b_internalized_stack; // stack of the boolean expressions already internalized.
 |         expr_ref_vector             m_b_internalized_stack; // stack of the boolean expressions already internalized.
 | ||||||
|  | @ -304,7 +303,7 @@ namespace smt { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         region & get_region() { |         region & get_region() { | ||||||
|             return m_region; |             return m_trail_stack.get_region(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         bool relevancy() const { |         bool relevancy() const { | ||||||
|  | @ -643,7 +642,6 @@ namespace smt { | ||||||
|         //
 |         //
 | ||||||
|         // -----------------------------------
 |         // -----------------------------------
 | ||||||
|     protected: |     protected: | ||||||
|         typedef ptr_vector<trail >   trail_stack; |  | ||||||
|         trail_stack                           m_trail_stack; |         trail_stack                           m_trail_stack; | ||||||
| #ifdef Z3DEBUG | #ifdef Z3DEBUG | ||||||
|         bool                                  m_trail_enabled { true }; |         bool                                  m_trail_enabled { true }; | ||||||
|  | @ -653,11 +651,15 @@ namespace smt { | ||||||
|         template<typename TrailObject> |         template<typename TrailObject> | ||||||
|         void push_trail(const TrailObject & obj) { |         void push_trail(const TrailObject & obj) { | ||||||
|             SASSERT(m_trail_enabled); |             SASSERT(m_trail_enabled); | ||||||
|             m_trail_stack.push_back(new (m_region) TrailObject(obj)); |             m_trail_stack.push(obj); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         void push_trail_ptr(trail * ptr) { |         void push_trail_ptr(trail * ptr) { | ||||||
|             m_trail_stack.push_back(ptr); |             m_trail_stack.push_ptr(ptr); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         trail_stack& get_trail_stack() { | ||||||
|  |             return m_trail_stack; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     protected: |     protected: | ||||||
|  | @ -667,7 +669,6 @@ namespace smt { | ||||||
|         unsigned                    m_search_lvl { 0 }; // It is greater than m_base_lvl when assumptions are used.  Otherwise, it is equals to m_base_lvl
 |         unsigned                    m_search_lvl { 0 }; // It is greater than m_base_lvl when assumptions are used.  Otherwise, it is equals to m_base_lvl
 | ||||||
|         struct scope { |         struct scope { | ||||||
|             unsigned                m_assigned_literals_lim; |             unsigned                m_assigned_literals_lim; | ||||||
|             unsigned                m_trail_stack_lim; |  | ||||||
|             unsigned                m_aux_clauses_lim; |             unsigned                m_aux_clauses_lim; | ||||||
|             unsigned                m_justifications_lim; |             unsigned                m_justifications_lim; | ||||||
|             unsigned                m_units_to_reassert_lim; |             unsigned                m_units_to_reassert_lim; | ||||||
|  | @ -687,8 +688,6 @@ namespace smt { | ||||||
| 
 | 
 | ||||||
|         void pop_scope(unsigned num_scopes); |         void pop_scope(unsigned num_scopes); | ||||||
| 
 | 
 | ||||||
|         void undo_trail_stack(unsigned old_size); |  | ||||||
| 
 |  | ||||||
|         void unassign_vars(unsigned old_lim); |         void unassign_vars(unsigned old_lim); | ||||||
| 
 | 
 | ||||||
|         void remove_watch_literal(clause * cls, unsigned idx); |         void remove_watch_literal(clause * cls, unsigned idx); | ||||||
|  | @ -1021,7 +1020,7 @@ namespace smt { | ||||||
| 
 | 
 | ||||||
|         template<typename Justification> |         template<typename Justification> | ||||||
|         justification * mk_justification(Justification const & j) { |         justification * mk_justification(Justification const & j) { | ||||||
|             justification * js = new (m_region) Justification(j); |             justification * js = new (get_region()) Justification(j); | ||||||
|             SASSERT(js->in_region()); |             SASSERT(js->in_region()); | ||||||
|             if (js->has_del_eh()) |             if (js->has_del_eh()) | ||||||
|                 m_justifications.push_back(js); |                 m_justifications.push_back(js); | ||||||
|  |  | ||||||
|  | @ -608,7 +608,7 @@ namespace smt { | ||||||
|         m_lambdas.insert(lam_node, q); |         m_lambdas.insert(lam_node, q); | ||||||
|         m_app2enode.setx(q->get_id(), lam_node, nullptr); |         m_app2enode.setx(q->get_id(), lam_node, nullptr); | ||||||
|         m_l_internalized_stack.push_back(q); |         m_l_internalized_stack.push_back(q); | ||||||
|         m_trail_stack.push_back(&m_mk_lambda_trail); |         m_trail_stack.push_ptr(&m_mk_lambda_trail); | ||||||
|         bool_var bv = get_bool_var(fa); |         bool_var bv = get_bool_var(fa); | ||||||
|         assign(literal(bv, false), nullptr); |         assign(literal(bv, false), nullptr); | ||||||
|         mark_as_relevant(bv); |         mark_as_relevant(bv); | ||||||
|  | @ -959,7 +959,7 @@ namespace smt { | ||||||
|             m_activity[v]      = 0.0; |             m_activity[v]      = 0.0; | ||||||
|         m_case_split_queue->mk_var_eh(v); |         m_case_split_queue->mk_var_eh(v); | ||||||
|         m_b_internalized_stack.push_back(n); |         m_b_internalized_stack.push_back(n); | ||||||
|         m_trail_stack.push_back(&m_mk_bool_var_trail); |         m_trail_stack.push_ptr(&m_mk_bool_var_trail); | ||||||
|         m_stats.m_num_mk_bool_var++; |         m_stats.m_num_mk_bool_var++; | ||||||
|         SASSERT(check_bool_var_vector_sizes()); |         SASSERT(check_bool_var_vector_sizes()); | ||||||
|         return v; |         return v; | ||||||
|  | @ -1010,7 +1010,8 @@ namespace smt { | ||||||
|             CTRACE(cached_generation, generation != m_generation, |             CTRACE(cached_generation, generation != m_generation, | ||||||
|                    tout << "cached_generation: #" << n->get_id() << " " << generation << " " << m_generation << "\n";); |                    tout << "cached_generation: #" << n->get_id() << " " << generation << " " << m_generation << "\n";); | ||||||
|         } |         } | ||||||
|         enode * e           = enode::mk(m, m_region, m_app2enode, n, generation, suppress_args, merge_tf, m_scope_lvl, cgc_enabled, true); |         enode *e = enode::mk(m, get_region(), m_app2enode, n, generation, suppress_args, merge_tf, m_scope_lvl, | ||||||
|  |                              cgc_enabled, true); | ||||||
|         TRACE(mk_enode_detail, tout << "e.get_num_args() = " << e->get_num_args() << "\n";); |         TRACE(mk_enode_detail, tout << "e.get_num_args() = " << e->get_num_args() << "\n";); | ||||||
|         if (m.is_unique_value(n)) |         if (m.is_unique_value(n)) | ||||||
|             e->mark_as_interpreted(); |             e->mark_as_interpreted(); | ||||||
|  | @ -1018,7 +1019,7 @@ namespace smt { | ||||||
|         TRACE(generation, tout << "mk_enode: " << id << " " << generation << "\n";); |         TRACE(generation, tout << "mk_enode: " << id << " " << generation << "\n";); | ||||||
|         m_app2enode.setx(id, e, nullptr); |         m_app2enode.setx(id, e, nullptr); | ||||||
|         m_e_internalized_stack.push_back(n); |         m_e_internalized_stack.push_back(n); | ||||||
|         m_trail_stack.push_back(&m_mk_enode_trail); |         m_trail_stack.push_ptr(&m_mk_enode_trail); | ||||||
|         m_enodes.push_back(e); |         m_enodes.push_back(e); | ||||||
|         if (e->get_num_args() > 0) { |         if (e->get_num_args() > 0) { | ||||||
|             if (e->is_true_eq()) { |             if (e->is_true_eq()) { | ||||||
|  | @ -1864,11 +1865,11 @@ namespace smt { | ||||||
|         if (old_v == null_theory_var) { |         if (old_v == null_theory_var) { | ||||||
|             enode * r     = n->get_root(); |             enode * r     = n->get_root(); | ||||||
|             theory_var v2 = r->get_th_var(th_id); |             theory_var v2 = r->get_th_var(th_id); | ||||||
|             n->add_th_var(v, th_id, m_region); |             n->add_th_var(v, th_id, get_region()); | ||||||
|             push_trail(add_th_var_trail(n, th_id)); |             push_trail(add_th_var_trail(n, th_id)); | ||||||
|             if (v2 == null_theory_var) { |             if (v2 == null_theory_var) { | ||||||
|                 if (r != n) |                 if (r != n) | ||||||
|                     r->add_th_var(v, th_id, m_region); |                     r->add_th_var(v, th_id, get_region()); | ||||||
|                 push_new_th_diseqs(r, v, th); |                 push_new_th_diseqs(r, v, th); | ||||||
|             } |             } | ||||||
|             else if (r != n) { |             else if (r != n) { | ||||||
|  |  | ||||||
|  | @ -20,13 +20,13 @@ Abstract: | ||||||
| namespace smt { | namespace smt { | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|     Constructor. |     * Constructor. | ||||||
|     Set up callback that adds axiom instantiations as clauses.  |     * Set up callback that adds axiom instantiations as clauses.  | ||||||
|     **/ |     **/ | ||||||
|     theory_finite_set::theory_finite_set(context& ctx): |     theory_finite_set::theory_finite_set(context& ctx): | ||||||
|         theory(ctx, ctx.get_manager().mk_family_id("finite_set")), |         theory(ctx, ctx.get_manager().mk_family_id("finite_set")), | ||||||
|         u(m), |         u(m), | ||||||
|         m_axioms(m) |         m_axioms(m), m_find(*this) | ||||||
|     { |     { | ||||||
|         // Setup the add_clause callback for axioms
 |         // Setup the add_clause callback for axioms
 | ||||||
|         std::function<void(expr_ref_vector const &)> add_clause_fn =  |         std::function<void(expr_ref_vector const &)> add_clause_fn =  | ||||||
|  | @ -46,40 +46,146 @@ namespace smt { | ||||||
|         m_set_members.reset(); |         m_set_members.reset(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /**
 | ||||||
|  |     * When creating a theory variable, we associate extra data structures with it. | ||||||
|  |     * if n := (set.in x S) | ||||||
|  |     * then for every T in the equivalence class of S (including S), assert theory axioms for x in T. | ||||||
|  |     *  | ||||||
|  |     * if n := (setop T U) | ||||||
|  |     * then for every (set.in x S) where either S ~ T, S ~ U, assert theory axioms for x in n. | ||||||
|  |     * Since n is fresh there are no other (set.in x S) with S ~ n in the state. | ||||||
|  |     *  | ||||||
|  |     * if n := (set.filter p S) | ||||||
|  |     * then for every (set.in x T) where S ~ T, assert theory axiom for x in (set.filter p S) | ||||||
|  |     *  | ||||||
|  |     * if n := (set.map f S) | ||||||
|  |     * then for every (set.in x T) where S ~ T, assert theory axiom for (set.in x S) and map. | ||||||
|  |     * In other words, assert  | ||||||
|  |     *         (set.in (f x) (set.map f S)) | ||||||
|  |     */ | ||||||
|  |     theory_var theory_finite_set::mk_var(enode *n) { | ||||||
|  |         theory_var r = theory::mk_var(n); | ||||||
|  |         VERIFY(r == static_cast<theory_var>(m_find.mk_var())); | ||||||
|  |         SASSERT(r == static_cast<int>(m_var_data.size())); | ||||||
|  |         m_var_data.push_back(alloc(var_data)); | ||||||
|  |         ctx.push_trail(push_back_vector<ptr_vector<var_data>>(m_var_data)); | ||||||
|  |         ctx.push_trail(new_obj_trail(m_var_data.back())); | ||||||
|  |         expr *e = n->get_expr(); | ||||||
|  |         if (u.is_in(e)) { | ||||||
|  |             auto set = n->get_arg(1); | ||||||
|  |             auto v = set->get_root()->get_th_var(get_id()); | ||||||
|  |             SASSERT(v != null_theory_var); | ||||||
|  |             m_var_data[v]->m_parent_in.push_back(n); | ||||||
|  |             ctx.push_trail(push_back_trail(m_var_data[v]->m_parent_in)); | ||||||
|  |             add_in_axioms(n, m_var_data[v]);  // add axioms x in S x S ~ T, T := setop, or T is arg of setop.
 | ||||||
|  |             auto f = to_app(e)->get_decl(); | ||||||
|  |             if (!m_set_in_decls.contains(f)) { | ||||||
|  |                 m_set_in_decls.push_back(f); | ||||||
|  |                 ctx.push_trail(push_back_vector(m_set_in_decls)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if (u.is_union(e) || u.is_intersect(e) ||  | ||||||
|  |             u.is_difference(e) || u.is_singleton(e) || | ||||||
|  |             u.is_empty(e) || u.is_range(e)) {             | ||||||
|  |             m_var_data[r]->m_setops.push_back(n); | ||||||
|  |             ctx.push_trail(push_back_trail(m_var_data[r]->m_setops)); | ||||||
|  |             for (auto arg : enode::args(n)) { | ||||||
|  |                 if (!u.is_finite_set(arg->get_expr())) | ||||||
|  |                     continue; | ||||||
|  |                 auto v = arg->get_root()->get_th_var(get_id()); | ||||||
|  |                 SASSERT(v != null_theory_var); | ||||||
|  |                 // add axioms for x in S, e := setop S T ...
 | ||||||
|  |                 for (auto in : m_var_data[v]->m_parent_in) | ||||||
|  |                     add_membership_axioms(in->get_arg(0)->get_expr(), e); | ||||||
|  |                 m_var_data[v]->m_parent_setops.push_back(n); | ||||||
|  |                 ctx.push_trail(push_back_trail(m_var_data[v]->m_parent_setops)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if (u.is_map(e) || u.is_select(e)) { | ||||||
|  |             NOT_IMPLEMENTED_YET(); | ||||||
|  |         } | ||||||
|  |         return r; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     trail_stack& theory_finite_set::get_trail_stack() { | ||||||
|  |         return ctx.get_trail_stack(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /*
 | ||||||
|  |     * Merge the equivalence classes of two variables. | ||||||
|  |      * parent_in := vector of (set.in x S) terms where S is in the equivalence class of r. | ||||||
|  |      * parent_setops := vector of (set.op S T) where S or T is in the equivalence class of r. | ||||||
|  |      * setops := vector of (set.op S T) where (set.op S T) is in the equivalence class of r. | ||||||
|  |      *  | ||||||
|  |     */ | ||||||
|  |     void theory_finite_set::merge_eh(theory_var root, theory_var other, theory_var, theory_var) { | ||||||
|  |         // r is the new root
 | ||||||
|  |         TRACE(finite_set, tout << "merging v" << root << " v" << other << "\n"; display_var(tout, root); | ||||||
|  |               tout << " <- " << mk_pp(get_enode(other)->get_expr(), m) << "\n";); | ||||||
|  |         SASSERT(root == find(root)); | ||||||
|  |         var_data *d_root= m_var_data[root]; | ||||||
|  |         var_data *d_other = m_var_data[other]; | ||||||
|  |         ctx.push_trail(restore_vector(d_root->m_setops)); | ||||||
|  |         ctx.push_trail(restore_vector(d_root->m_parent_setops)); | ||||||
|  |         ctx.push_trail(restore_vector(d_root->m_parent_in)); | ||||||
|  |         add_in_axioms(root, other); | ||||||
|  |         add_in_axioms(other, root); | ||||||
|  |         d_root->m_setops.append(d_other->m_setops); | ||||||
|  |         d_root->m_parent_setops.append(d_other->m_parent_setops); | ||||||
|  |         d_root->m_parent_in.append(d_other->m_parent_in); | ||||||
|  |         TRACE(finite_set, tout << "after merge\n"; display_var(tout, root);); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /*
 | ||||||
|  |      * for each (set.in x S) in d1->parent_in, | ||||||
|  |      *     add axioms for (set.in x S) | ||||||
|  |      */ | ||||||
|  |     void theory_finite_set::add_in_axioms(theory_var v1, theory_var v2) { | ||||||
|  |         auto d1 = m_var_data[v1]; | ||||||
|  |         auto d2 = m_var_data[v2]; | ||||||
|  |         for (enode *in : d1->m_parent_in)  | ||||||
|  |             add_in_axioms(in, d2);         | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /*
 | ||||||
|  |      *  let (set.in x S)  | ||||||
|  |      * | ||||||
|  |      *  for each T := (set.op U V) in d2->parent_setops | ||||||
|  |      *         then S ~ U or S ~ V by construction | ||||||
|  |      *              add axioms for (set.in x T) | ||||||
|  |      *         | ||||||
|  |      *  for each T := (set.op U V) in d2->setops | ||||||
|  |      *         then S ~ T by construction | ||||||
|  |      *              add axioms for (set.in x T) | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     void theory_finite_set::add_in_axioms(enode *in, var_data *d) { | ||||||
|  |         SASSERT(u.is_in(in->get_expr())); | ||||||
|  |         auto e = in->get_arg(0)->get_expr(); | ||||||
|  |         auto set1 = in->get_arg(1); | ||||||
|  |         for (enode *setop : d->m_parent_setops) { | ||||||
|  |             SASSERT( | ||||||
|  |                 any_of(enode::args(setop), [&](enode *arg) { return in->get_arg(1)->get_root() == arg->get_root(); })); | ||||||
|  |             add_membership_axioms(e, setop->get_expr()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for (enode *setop : d->m_setops) { | ||||||
|  |             SASSERT(in->get_arg(1)->get_root() == setop->get_root()); | ||||||
|  |             add_membership_axioms(e, setop->get_expr()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /**
 |     /**
 | ||||||
|     * Boolean atomic formulas for finite sets are one of: |     * Boolean atomic formulas for finite sets are one of: | ||||||
|     * (set.in x S) |     * (set.in x S) | ||||||
|     * (set.subset S T) |     * (set.subset S T) | ||||||
|     * When an atomic formula is first created it is to be registered with the solver. |     * When an atomic formula is first created it is to be registered with the solver. | ||||||
|     * The internalize_atom method takes care of this. |     * The internalize_atom method takes care of this. | ||||||
|     * Atomic formulas are special cases of terms (of non-Boolean type) so the first  |     * Atomic formulas are special cases of terms (of non-Boolean type) so they are registered as terms. | ||||||
|     * effect is to register the atom as a term. |  | ||||||
|     * The second effect is to set up tracking and assert axioms. |  | ||||||
|     * Tracking: |  | ||||||
|     *    For every occurrence (set.in x_i S_i) we track x_i.  |  | ||||||
|     * Axioms that can be added immediately. |  | ||||||
|     *     |     *     | ||||||
|     */ |     */ | ||||||
|     bool theory_finite_set::internalize_atom(app * atom, bool gate_ctx) { |     bool theory_finite_set::internalize_atom(app * atom, bool gate_ctx) { | ||||||
|         TRACE(finite_set, tout << "internalize_atom: " << mk_pp(atom, m) << "\n";); |         return internalize_term(atom); | ||||||
| 
 |  | ||||||
|         internalize_term(atom); |  | ||||||
|          |  | ||||||
|         // Track membership elements (set.in)
 |  | ||||||
|         expr* elem = nullptr, *set = nullptr; |  | ||||||
|         if (u.is_in(atom, elem, set)) { |  | ||||||
|             auto n = ctx.get_enode(elem); |  | ||||||
|             if (!m_elements.contains(n)) { |  | ||||||
|                 m_elements.insert(n); |  | ||||||
|                 ctx.push_trail(insert_obj_trail(m_elements, n)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Assert immediate axioms
 |  | ||||||
|         // if (!ctx.relevancy())
 |  | ||||||
|         add_immediate_axioms(atom); |  | ||||||
|          |  | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|  | @ -114,6 +220,10 @@ namespace smt { | ||||||
|         // Attach theory variable if this is a set
 |         // Attach theory variable if this is a set
 | ||||||
|         if (!is_attached_to_var(e))              |         if (!is_attached_to_var(e))              | ||||||
|             ctx.attach_th_var(e, this, mk_var(e)); |             ctx.attach_th_var(e, this, mk_var(e)); | ||||||
|  | 
 | ||||||
|  |         // Assert immediate axioms
 | ||||||
|  |         // if (!ctx.relevancy())
 | ||||||
|  |         add_immediate_axioms(term); | ||||||
|                  |                  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  | @ -126,16 +236,16 @@ namespace smt { | ||||||
| 
 | 
 | ||||||
|     void theory_finite_set::new_eq_eh(theory_var v1, theory_var v2) { |     void theory_finite_set::new_eq_eh(theory_var v1, theory_var v2) { | ||||||
|         TRACE(finite_set, tout << "new_eq_eh: v" << v1 << " = v" << v2 << "\n";); |         TRACE(finite_set, tout << "new_eq_eh: v" << v1 << " = v" << v2 << "\n";); | ||||||
|         //    x = y, y in S
 |         m_find.merge(v1, v2); // triggers merge_eh, which triggers incremental generation of theory axioms
 | ||||||
|         // -------------------
 |  | ||||||
|         //  axioms for x in S
 |  | ||||||
| 
 |  | ||||||
|         auto n1 = get_enode(v1); |  | ||||||
|         auto n2 = get_enode(v2); |  | ||||||
|         auto e1 = n1->get_expr(); |  | ||||||
|         auto e2 = n2->get_expr(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /**
 | ||||||
|  |     * Every dissequality has to be supported by at distinguishing element. | ||||||
|  |     *  | ||||||
|  |     * TODO: we can avoid instantiating the extensionality axiom if we know statically that e1, e2 | ||||||
|  |     * can never be equal (say, they have different cardinalities or they are finite sets by construction | ||||||
|  |     * with elements that can differentiate the sets) | ||||||
|  |     */ | ||||||
|     void theory_finite_set::new_diseq_eh(theory_var v1, theory_var v2) { |     void theory_finite_set::new_diseq_eh(theory_var v1, theory_var v2) { | ||||||
|         TRACE(finite_set, tout << "new_diseq_eh: v" << v1 << " != v" << v2 << "\n"); |         TRACE(finite_set, tout << "new_diseq_eh: v" << v1 << " != v" << v2 << "\n"); | ||||||
|         auto n1 = get_enode(v1); |         auto n1 = get_enode(v1); | ||||||
|  | @ -163,7 +273,7 @@ namespace smt { | ||||||
|     final_check_status theory_finite_set::final_check_eh() { |     final_check_status theory_finite_set::final_check_eh() { | ||||||
|         TRACE(finite_set, tout << "final_check_eh\n";); |         TRACE(finite_set, tout << "final_check_eh\n";); | ||||||
| 
 | 
 | ||||||
|         if (add_membership_axioms()) |         if (activate_unasserted_clause()) | ||||||
|             return FC_CONTINUE; |             return FC_CONTINUE; | ||||||
| 
 | 
 | ||||||
|         if (assume_eqs()) |         if (assume_eqs()) | ||||||
|  | @ -186,57 +296,172 @@ namespace smt { | ||||||
|      */ |      */ | ||||||
|     void theory_finite_set::add_immediate_axioms(app* term) { |     void theory_finite_set::add_immediate_axioms(app* term) { | ||||||
|         expr *elem = nullptr, *set = nullptr; |         expr *elem = nullptr, *set = nullptr; | ||||||
|         unsigned sz = m_theory_axioms.size(); |         unsigned sz = m_clauses.axioms.size(); | ||||||
|         if (u.is_in(term, elem, set) && u.is_empty(set)) |         if (u.is_in(term, elem, set) && u.is_empty(set)) | ||||||
|             add_membership_axioms(elem, set); |             add_membership_axioms(elem, set); | ||||||
|         else if (u.is_subset(term)) |         else if (u.is_subset(term)) | ||||||
|             m_axioms.subset_axiom(term); |             m_axioms.subset_axiom(term); | ||||||
|         else if (u.is_singleton(term, elem)) |         else if (u.is_singleton(term, elem)) | ||||||
|             m_axioms.in_singleton_axiom(elem, term); |             m_axioms.in_singleton_axiom(elem, term); | ||||||
| 
 |         | ||||||
|         // Assert all new lemmas as clauses
 |         // Assert all new lemmas as clauses
 | ||||||
|         for (unsigned i = sz; i < m_theory_axioms.size(); ++i)  |         for (unsigned i = sz; i < m_clauses.axioms.size(); ++i) | ||||||
|             assert_clause(m_theory_axioms[i]); |             m_clauses.squeue.push_back(i); | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         // TODO also add axioms for x in S u T, x in S n T, etc to the stack of m_theory_axioms.
 |  | ||||||
|         // The axioms are then instantiated if they are propagating.
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |     void theory_finite_set::assign_eh(bool_var v, bool is_true) { | ||||||
|      *   Set membership is saturated with respect to set operations. |         TRACE(finite_set, tout << "assign_eh: v" << v << " is_true: " << is_true << "\n";); | ||||||
|      *    For every (set.in x S) where S is a union, assert (or propagate) (set.in x S1) or (set.in x S2) |         expr *e = ctx.bool_var2expr(v); | ||||||
|      */ |         // retrieve the watch list for clauses where e appears with opposite polarity
 | ||||||
|     bool theory_finite_set::add_membership_axioms() { |         unsigned idx = 2 * e->get_id() + (is_true ? 1 : 0); | ||||||
|         expr *elem1 = nullptr, *set1 = nullptr; |         if (idx >= m_clauses.watch.size()) | ||||||
|  |             return; | ||||||
| 
 | 
 | ||||||
|         // walk all parents of elem in congruence table.
 |         // walk the watch list and try to find new watches or propagate
 | ||||||
|         // if a parent is of the form elem' in S u T, or similar.
 |         unsigned j = 0; | ||||||
|         // create clauses for elem in S u T.
 |         for (unsigned i = 0; i < m_clauses.watch[idx].size(); ++i) { | ||||||
|  |             TRACE(finite_set, tout << " watch[" << i << "] size: " << m_clauses.watch[i].size() << "\n";); | ||||||
|  |             auto clause_idx = m_clauses.watch[idx][i]; | ||||||
|  |             auto &clause = m_clauses.axioms[clause_idx]; | ||||||
|  |             if (any_of(clause, [&](expr *lit) { return ctx.find_assignment(lit) == l_true; })) { | ||||||
|  |                 TRACE(finite_set, tout << "  satisfied\n";); | ||||||
|  |                 m_clauses.watch[idx][j++] = clause_idx; | ||||||
|  |                 continue; // clause is already satisfied
 | ||||||
|  |             } | ||||||
|  |             auto is_complement_to = [&](bool is_true, expr* lit, expr* arg) { | ||||||
|  |                 if (is_true) | ||||||
|  |                     return m.is_not(lit) && to_app(lit)->get_arg(0) == arg; | ||||||
|  |                 else | ||||||
|  |                     return lit == arg; | ||||||
|  |             }; | ||||||
|  |             auto lit1 = clause.get(0); | ||||||
|  |             auto lit2 = clause.get(1); | ||||||
|  |             auto position = 0; | ||||||
|  |             if (is_complement_to(is_true, lit1, e)) | ||||||
|  |                 position = 0; | ||||||
|  |             else { | ||||||
|  |                 SASSERT(is_complement_to(is_true, lit2, e)); | ||||||
|  |                 position = 1; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             bool found_swap = false; | ||||||
|  |             for (unsigned k = 2; k < clause.size(); ++k) { | ||||||
|  |                 expr *lit = clause.get(k); | ||||||
|  |                 if (ctx.find_assignment(lit) == l_false) | ||||||
|  |                     continue; | ||||||
|  |                 // found a new watch     
 | ||||||
|  |                 clause.swap(position, k);           | ||||||
|  |                 // std::swap(clause[position], clause[k]);
 | ||||||
|  |                 bool litneg = m.is_not(lit, lit); | ||||||
|  |                 auto litid = 2 * lit->get_id() + litneg;               | ||||||
|  |                 m_clauses.watch.reserve(litid + 1); | ||||||
|  |                 m_clauses.watch[litid].push_back(clause_idx); | ||||||
|  |                 TRACE(finite_set, tout << "  new watch for " << mk_pp(lit, m) << "\n";); | ||||||
|  |                 found_swap = true; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if (found_swap)  | ||||||
|  |                 continue;  // the clause is removed from this watch list
 | ||||||
|  |             // either all literals are false, or the other watch literal is propagating.
 | ||||||
|  |             m_clauses.squeue.push_back(clause_idx); | ||||||
|  |             TRACE(finite_set, tout << "  propagate clause\n";); | ||||||
|  |             m_clauses.watch[idx][j++] = clause_idx; | ||||||
|  |             ++i; | ||||||
|  |             for (; i < m_clauses.watch[idx].size(); ++i) | ||||||
|  |                 m_clauses.watch[idx][j++] = m_clauses.watch[idx][i]; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         m_clauses.watch[idx].shrink(j); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         for (auto elem : m_elements) { |     bool theory_finite_set::can_propagate() { | ||||||
|             if (!ctx.is_relevant(elem)) |         return m_clauses.can_propagate(); | ||||||
|                 continue; |     } | ||||||
|             for (auto p : enode::parents(elem)) { | 
 | ||||||
|                 if (!u.is_in(p->get_expr(), elem1, set1)) |     void theory_finite_set::propagate() { | ||||||
|                     continue; |         TRACE(finite_set, tout << "propagate\n";); | ||||||
|                 if (elem->get_root() != p->get_arg(0)->get_root()) |         ctx.push_trail(value_trail(m_clauses.aqhead)); | ||||||
|                     continue;  // elem is then equal to set1 but not elem1. This is a different case.
 |         ctx.push_trail(value_trail(m_clauses.sqhead)); | ||||||
|                 if (!ctx.is_relevant(p)) |         while (can_propagate() && !ctx.inconsistent()) { | ||||||
|                     continue; |             // activate newly created clauses
 | ||||||
|                 for (auto sib : *p->get_arg(1)) |             while (m_clauses.aqhead < m_clauses.axioms.size())  | ||||||
|                     add_membership_axioms(elem->get_expr(), sib->get_expr()); |                 activate_clause(m_clauses.aqhead++); | ||||||
|  | 
 | ||||||
|  |             // empty the propagation queue of clauses to assert
 | ||||||
|  |             while (m_clauses.sqhead < m_clauses.squeue.size() && !ctx.inconsistent()) { | ||||||
|  |                 auto index = m_clauses.squeue[m_clauses.sqhead++]; | ||||||
|  |                 auto const &clause = m_clauses.axioms[index]; | ||||||
|  |                 assert_clause(clause); | ||||||
|  |             } | ||||||
|  |         }       | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void theory_finite_set::activate_clause(unsigned clause_idx) { | ||||||
|  |         TRACE(finite_set, tout << "activate_clause: " << clause_idx << "\n";); | ||||||
|  |         auto &clause = m_clauses.axioms[clause_idx]; | ||||||
|  |         if (any_of(clause, [&](expr *e) { return ctx.find_assignment(e) == l_true; })) | ||||||
|  |             return; | ||||||
|  |         if (clause.size() <= 1) { | ||||||
|  |             m_clauses.squeue.push_back(clause_idx); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         expr *w1 = nullptr, *w2 = nullptr; | ||||||
|  |         for (unsigned i = 0; i < clause.size(); ++i) { | ||||||
|  |             expr *lit = clause.get(i); | ||||||
|  |             switch (ctx.find_assignment(lit)) { | ||||||
|  |             case l_true: | ||||||
|  |                 UNREACHABLE(); | ||||||
|  |                  return;  | ||||||
|  |             case l_false:  | ||||||
|  |                 break; | ||||||
|  |             case l_undef: | ||||||
|  |                 if (!w1) { | ||||||
|  |                     w1 = lit; | ||||||
|  |                     clause.swap(0, i); | ||||||
|  |                 }                                     | ||||||
|  |                 else if (!w2) { | ||||||
|  |                     w2 = lit; | ||||||
|  |                     clause.swap(1, i); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (instantiate_false_lemma()) |         if (!w2) { | ||||||
|             return true; |             m_clauses.squeue.push_back(clause_idx); | ||||||
|         if (instantiate_unit_propagation()) |             return; | ||||||
|             return true; |         } | ||||||
|         if (instantiate_free_lemma()) |         bool w1neg = m.is_not(w1, w1); | ||||||
|             return true; |         bool w2neg = m.is_not(w2, w2);   | ||||||
|         return false; |         unsigned w1id = 2 * w1->get_id() + w1neg; | ||||||
|  |         unsigned w2id = 2 * w2->get_id() + w2neg; | ||||||
|  |         unsigned sz = std::max(w1id, w2id) + 1; | ||||||
|  |         m_clauses.watch.reserve(sz); | ||||||
|  |         m_clauses.watch[w1id].push_back(clause_idx); | ||||||
|  |         m_clauses.watch[w2id].push_back(clause_idx); | ||||||
|  | 
 | ||||||
|  |         struct unwatch_clause : public trail { | ||||||
|  |             theory_finite_set &th; | ||||||
|  |             unsigned index; | ||||||
|  |             unwatch_clause(theory_finite_set &th, unsigned index) : th(th), index(index) {} | ||||||
|  |             void undo() override { | ||||||
|  |                 auto &clause = th.m_clauses.axioms[index]; | ||||||
|  |                 expr *w1 = clause.get(0); | ||||||
|  |                 expr *w2 = clause.get(1); | ||||||
|  |                 bool w1neg = th.m.is_not(w1, w1); | ||||||
|  |                 bool w2neg = th.m.is_not(w2, w2); | ||||||
|  |                 unsigned w1id = 2 * w1->get_id() + w1neg; | ||||||
|  |                 unsigned w2id = 2 * w2->get_id() + w2neg; | ||||||
|  |                 auto &watch1 = th.m_clauses.watch[w1id]; | ||||||
|  |                 auto &watch2 = th.m_clauses.watch[w2id]; | ||||||
|  |                 watch1.erase(index); | ||||||
|  |                 watch2.erase(index); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         ctx.push_trail(unwatch_clause(*this, clause_idx)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      *  Saturate with respect to equality sharing: |      *  Saturate with respect to equality sharing: | ||||||
|      *  - Sets corresponding to shared variables having the same interpretation should also be congruent |      *  - Sets corresponding to shared variables having the same interpretation should also be congruent | ||||||
|  | @ -297,10 +522,10 @@ namespace smt { | ||||||
|                 table.erase({a, b}); |                 table.erase({a, b}); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         if (m_theory_axiom_exprs.contains({a, b})) |         if (m_clauses.members.contains({a, b})) | ||||||
|             return false; |             return false; | ||||||
|         m_theory_axiom_exprs.insert({a, b}); |         m_clauses.members.insert({a, b}); | ||||||
|         ctx.push_trail(insert_obj_pair_table(m_theory_axiom_exprs, a, b)); |         ctx.push_trail(insert_obj_pair_table(m_clauses.members, a, b)); | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -343,8 +568,9 @@ namespace smt { | ||||||
| 
 | 
 | ||||||
|     void theory_finite_set::add_clause(expr_ref_vector const& clause) { |     void theory_finite_set::add_clause(expr_ref_vector const& clause) { | ||||||
|         TRACE(finite_set, tout << "add_clause: " << clause << "\n"); |         TRACE(finite_set, tout << "add_clause: " << clause << "\n"); | ||||||
|         ctx.push_trail(push_back_vector(m_theory_axioms)); |         ctx.push_trail(push_back_vector(m_clauses.axioms)); | ||||||
|         m_theory_axioms.push_back(clause);         |         m_clauses.axioms.push_back(clause); | ||||||
|  |         m_stats.m_num_axioms_created++; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     theory * theory_finite_set::mk_fresh(context * new_ctx) { |     theory * theory_finite_set::mk_fresh(context * new_ctx) { | ||||||
|  | @ -353,6 +579,15 @@ namespace smt { | ||||||
| 
 | 
 | ||||||
|     void theory_finite_set::display(std::ostream & out) const { |     void theory_finite_set::display(std::ostream & out) const { | ||||||
|         out << "theory_finite_set:\n"; |         out << "theory_finite_set:\n"; | ||||||
|  |         for (unsigned i = 0; i < m_clauses.axioms.size(); ++i) | ||||||
|  |             out << "[" << i << "]: " << m_clauses.axioms[i] << "\n"; | ||||||
|  |         for (unsigned v = 0; v < get_num_vars(); ++v) | ||||||
|  |             display_var(out, v); | ||||||
|  |         for (unsigned i = 0; i < m_clauses.watch.size(); ++i) { | ||||||
|  |             if (m_clauses.watch[i].empty()) | ||||||
|  |                 continue; | ||||||
|  |             out << "watch[" << i << "] := " << m_clauses.watch[i] << "\n"; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void theory_finite_set::init_model(model_generator & mg) { |     void theory_finite_set::init_model(model_generator & mg) { | ||||||
|  | @ -369,35 +604,41 @@ namespace smt { | ||||||
|         // and ensure that the model factory has values for them.
 |         // and ensure that the model factory has values for them.
 | ||||||
|         // For now, we rely on the default model construction.
 |         // For now, we rely on the default model construction.
 | ||||||
|         reset_set_members(); |         reset_set_members(); | ||||||
|         for (auto x : m_elements) { |         for (auto f : m_set_in_decls) { | ||||||
|             if (!ctx.is_relevant(x)) |             for (auto n : ctx.enodes_of(f)) { | ||||||
|                 continue; |                 SASSERT(u.is_in(n->get_expr())); | ||||||
|             x = x->get_root(); |                 auto x = n->get_arg(0); | ||||||
|             if (x->is_marked()) |                 if (!ctx.is_relevant(x)) | ||||||
|                 continue; |  | ||||||
|             x->set_mark();  // make sure we only do this once per element
 |  | ||||||
|             // TODO: use marking of x to avoid duplicate work
 |  | ||||||
|             for (auto p : enode::parents(x)) { |  | ||||||
|                 if (!ctx.is_relevant(p)) |  | ||||||
|                     continue; |                     continue; | ||||||
|                 if (!u.is_in(p->get_expr())) |                 x = x->get_root(); | ||||||
|  |                 if (x->is_marked()) | ||||||
|                     continue; |                     continue; | ||||||
|                 if (ctx.get_assignment(p->get_expr()) != l_true) |                 x->set_mark();  // make sure we only do this once per element
 | ||||||
|                     continue; |                 for (auto p : enode::parents(x)) { | ||||||
|                 enode *elem = nullptr, *set = nullptr; |                     if (!ctx.is_relevant(p)) | ||||||
|                 set = p->get_arg(1)->get_root(); |                         continue; | ||||||
|                 elem = p->get_arg(0)->get_root(); |                     if (!u.is_in(p->get_expr())) | ||||||
|                 if (elem != x) |                         continue; | ||||||
|                     continue;  |                     if (ctx.get_assignment(p->get_expr()) != l_true) | ||||||
|                 if (!m_set_members.contains(set))  |                         continue; | ||||||
|                     m_set_members.insert(set, alloc(obj_hashtable<enode>));                 |                     auto set = p->get_arg(1)->get_root(); | ||||||
|                 m_set_members.find(set)->insert(x); |                     auto elem = p->get_arg(0)->get_root(); | ||||||
|  |                     if (elem != x) | ||||||
|  |                         continue; | ||||||
|  |                     if (!m_set_members.contains(set)) | ||||||
|  |                         m_set_members.insert(set, alloc(obj_hashtable<enode>)); | ||||||
|  |                     m_set_members.find(set)->insert(x); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         for (auto x : m_elements) { |         for (auto f : m_set_in_decls) { | ||||||
|             x = x->get_root(); |             for (auto n : ctx.enodes_of(f)) { | ||||||
|             if (x->is_marked()) |                 SASSERT(u.is_in(n->get_expr())); | ||||||
|                 x->unset_mark(); |                 auto x = n->get_arg(0); | ||||||
|  |                 x = x->get_root(); | ||||||
|  |                 if (x->is_marked()) | ||||||
|  |                     x->unset_mark(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -417,10 +658,12 @@ namespace smt { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         app *mk_value(model_generator &mg, expr_ref_vector const &values) override {             |         app *mk_value(model_generator &mg, expr_ref_vector const &values) override {             | ||||||
|             SASSERT(values.size() == m_elements->size()); |             SASSERT(values.empty() == !m_elements); | ||||||
|             if (values.empty())  |             if (values.empty())  | ||||||
|                 return th.u.mk_empty(s); |                 return th.u.mk_empty(s); | ||||||
|  | 
 | ||||||
|             SASSERT(m_elements); |             SASSERT(m_elements); | ||||||
|  |             SASSERT(values.size() == m_elements->size()); | ||||||
|             return th.mk_union(values.size(), values.data(), s); |             return th.mk_union(values.size(), values.data(), s); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  | @ -433,77 +676,84 @@ namespace smt { | ||||||
|         return alloc(finite_set_value_proc, *this, s, elements); |         return alloc(finite_set_value_proc, *this, s, elements); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     /**
 |     /**
 | ||||||
|     * Lemmas that are currently assinged to false are conflicts.  |     * a theory axiom can be unasserted if it contains two or more literals that have | ||||||
|     * They should be asserted as soon as possible. |     * not been internalized yet. | ||||||
|     * Only the first conflict needs to be asserted. |  | ||||||
|     *  |  | ||||||
|     */ |     */ | ||||||
|     bool theory_finite_set::instantiate_false_lemma() { |     bool theory_finite_set::activate_unasserted_clause() { | ||||||
|         for (auto const& clause : m_theory_axioms) { |         for (auto const &clause : m_clauses.axioms) { | ||||||
|             bool all_false = all_of(clause, [&](expr *e) { return ctx.find_assignment(e) == l_false; }); |             if (assert_clause(clause)) | ||||||
|             if (!all_false) |                 return true; | ||||||
|                 continue; |  | ||||||
|             assert_clause(clause); |  | ||||||
|             return true; |  | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |     bool theory_finite_set::assert_clause(expr_ref_vector const &clause) { | ||||||
|      * Lemmas that are unit propagating should be asserted as possible and can be asserted in a batch. |         expr *unit = nullptr; | ||||||
|      * It is possible to assert a unit propagating lemma as a clause. |         unsigned undef_count = 0; | ||||||
|      * A more efficient approach is to add a Theory propagation with the solver. |         for (auto e : clause) { | ||||||
|      * A theory propagation gets recorded on the assignment trail and the overhead of undoing it is baked in to backtracking. |             switch (ctx.find_assignment(e)) { | ||||||
|      * A theory axiom is also removed during backtracking. |             case l_true: | ||||||
|     */ |                 return false; // clause is already satisfied
 | ||||||
|     bool theory_finite_set::instantiate_unit_propagation() { |             case l_false: | ||||||
|         bool propagated = false; |                 break; | ||||||
|         for (auto const &clause : m_theory_axioms) { |             case l_undef: | ||||||
|             expr *undef = nullptr; |                 ++undef_count; | ||||||
|             bool is_unit_propagating = true; |                 unit = e; | ||||||
|             for (auto e : clause) { |                 break; | ||||||
|                 switch (ctx.find_assignment(e)) { |  | ||||||
|                 case l_false: continue; |  | ||||||
|                 case l_true: is_unit_propagating = false; break; |  | ||||||
|                 case l_undef: |  | ||||||
|                     if (undef != nullptr)  |  | ||||||
|                         is_unit_propagating = false;                     |  | ||||||
|                     undef = e; |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 if (!is_unit_propagating) |  | ||||||
|                     break; |  | ||||||
|             } |             } | ||||||
|             if (!is_unit_propagating || undef == nullptr) |  | ||||||
|                 continue;       |  | ||||||
|             assert_clause(clause); |  | ||||||
|             propagated = true; |  | ||||||
|         } |         } | ||||||
|         return propagated; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /**
 |         if (undef_count == 1) { | ||||||
|      * We assume the lemmas in the queue are necessary for completeness. |             TRACE(finite_set, tout << " propagate unit: " << mk_pp(unit, m) << "\n" << clause << "\n";); | ||||||
|      * So they all have to be enforced through case analysis. |             auto lit = mk_literal(unit); | ||||||
|      * Lemmas with more than one unassigned literal are asserted here. |             literal_vector core; | ||||||
|      * The solver will case split on the unassigned literals to satisfy the lemma. |             for (auto e : clause) { | ||||||
|     */ |                 if (e != unit) | ||||||
|     bool theory_finite_set::instantiate_free_lemma() { |                     core.push_back(~mk_literal(e)); | ||||||
|         for (auto const& clause : m_theory_axioms) { |             } | ||||||
|             if (any_of(clause, [&](expr *e) { return ctx.find_assignment(e) == l_true; })) |             m_stats.m_num_axioms_propagated++; | ||||||
|                 continue; |             ctx.assign(lit, ctx.mk_justification( | ||||||
|             assert_clause(clause); |                                 theory_propagation_justification(get_id(), ctx, core.size(), core.data(), lit))); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         return false; |         bool is_conflict = (undef_count == 0); | ||||||
|     } |         if (is_conflict) | ||||||
| 
 |             m_stats.m_num_axioms_conflicts++; | ||||||
|     void theory_finite_set::assert_clause(expr_ref_vector const &clause) { |         else | ||||||
|  |             m_stats.m_num_axioms_case_splits++; | ||||||
|  |         TRACE(finite_set, tout << " assert " << (is_conflict ? "conflict" : "case split") << clause << "\n";); | ||||||
|         literal_vector lclause; |         literal_vector lclause; | ||||||
|         for (auto e : clause) |         for (auto e : clause) | ||||||
|             lclause.push_back(mk_literal(e)); |             lclause.push_back(mk_literal(e)); | ||||||
|         ctx.mk_th_axiom(get_id(), lclause); |         ctx.mk_th_axiom(get_id(), lclause); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::ostream& theory_finite_set::display_var(std::ostream& out, theory_var v) const { | ||||||
|  |         out << "v" << v << " := " << enode_pp(get_enode(v), ctx) << "\n"; | ||||||
|  |         auto d = m_var_data[v]; | ||||||
|  |         if (!d->m_setops.empty()) { | ||||||
|  |             out << "  setops: "; | ||||||
|  |             for (auto n : d->m_setops) | ||||||
|  |                 out << enode_pp(n, ctx) << " "; | ||||||
|  |             out << "\n"; | ||||||
|  |         } | ||||||
|  |         if (!d->m_parent_setops.empty()) { | ||||||
|  |             out << "  parent_setops: "; | ||||||
|  |             for (auto n : d->m_parent_setops) | ||||||
|  |                 out << enode_pp(n, ctx) << " "; | ||||||
|  |             out << "\n"; | ||||||
|  |         } | ||||||
|  |         if (!d->m_parent_in.empty()) { | ||||||
|  |             out << "  parent_in: "; | ||||||
|  |             for (auto n : d->m_parent_in) | ||||||
|  |                 out << enode_pp(n, ctx) << " "; | ||||||
|  |             out << "\n"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return out; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| }  // namespace smt
 | }  // namespace smt
 | ||||||
|  |  | ||||||
|  | @ -20,40 +20,40 @@ Abstract: | ||||||
|     The set-based decision procedure relies on saturating with respect |     The set-based decision procedure relies on saturating with respect | ||||||
|     to rules of the form: |     to rules of the form: | ||||||
| 
 | 
 | ||||||
|        x in v1 == v2, v1 ~ set.empty |        x in v1 a term, v1 ~ set.empty | ||||||
|     ----------------------------------- |     ----------------------------------- | ||||||
|        v1 = set.empty => not (x in v1) |            not (x in set.empty) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|      x in v1 == v2, v1 ~ v3, v3 == (set.union v4 v5) |      x in v1 a term , v1 ~ v3, v3 := (set.union v4 v5) | ||||||
|      ----------------------------------------------- |      ----------------------------------------------- | ||||||
|            x in v3 <=> x in v4 or x in v5 |            x in v3 <=> x in v4 or x in v5 | ||||||
| 
 | 
 | ||||||
|      x in v1 == v2, v1 ~ v3, v3 == (set.intersect v4 v5) |      x in v1 a term, v1 ~ v3, v3 := (set.intersect v4 v5) | ||||||
|      --------------------------------------------------- |      --------------------------------------------------- | ||||||
|            x in v3 <=> x in v4 and x in v5 |            x in v3 <=> x in v4 and x in v5 | ||||||
| 
 | 
 | ||||||
|     x in v1 == v2, v1 ~ v3, v3 == (set.difference v4 v5) |     x in v1 a term, v1 ~ v3, v3 := (set.difference v4 v5) | ||||||
|     --------------------------------------------------- |     --------------------------------------------------- | ||||||
|          x in v3 <=> x in v4 and not (x in v5) |          x in v3 <=> x in v4 and not (x in v5) | ||||||
| 
 | 
 | ||||||
|     x in v1 == v2, v1 ~ v3, v3 == (set.singleton v4) |     x in v1 a term, v1 ~ v3, v3 := (set.singleton v4) | ||||||
|     ----------------------------------------------- |     ----------------------------------------------- | ||||||
|          x in v3 <=> x == v4 |          x in v3 <=> x == v4 | ||||||
| 
 | 
 | ||||||
|     x in v1 == v2, v1 ~ v3, v3 == (set.range lo hi) |     x in v1 a term, v1 ~ v3, v3 := (set.range lo hi) | ||||||
|     ----------------------------------------------- |     ----------------------------------------------- | ||||||
|          x in v3 <=> (lo <= x <= hi) |          x in v3 <=> (lo <= x <= hi) | ||||||
| 
 | 
 | ||||||
|     x in v1 == v2, v1 ~ v3, v3 == (set.map f v4) |     x in v1 a term, v1 ~ v3, v3 := (set.map f v4) | ||||||
|     ----------------------------------------------- |     ----------------------------------------------- | ||||||
|      x in v3 <=> set.map_inverse(f, x, v4) in v4 |      x in v3 <=> set.map_inverse(f, x, v4) in v4 | ||||||
| 
 | 
 | ||||||
|     x in v1 == v2, v1 ~ v3, v3 == (set.map f v4) |     x in v1 a term, v1 ~ v3, v3 := (set.map f v4) | ||||||
|    ----------------------------------------------- |    ----------------------------------------------- | ||||||
|         x in v4 => f(x) in v3 |         x in v4 => f(x) in v3 | ||||||
| 
 | 
 | ||||||
|    x in v1 == v2, v1 ~ v3, v3 == (set.select p v4) |    x in v1 a tern, v1 ~ v3, v3 := (set.select p v4) | ||||||
|    ----------------------------------------------- |    ----------------------------------------------- | ||||||
|         x in v3 <=> p(x) and x in v4 |         x in v3 <=> p(x) and x in v4 | ||||||
| 
 | 
 | ||||||
|  | @ -89,21 +89,60 @@ theory_finite_set.cpp. | ||||||
| #include "ast/finite_set_decl_plugin.h" | #include "ast/finite_set_decl_plugin.h" | ||||||
| #include "ast/rewriter/finite_set_axioms.h" | #include "ast/rewriter/finite_set_axioms.h" | ||||||
| #include "util/obj_pair_hashtable.h" | #include "util/obj_pair_hashtable.h" | ||||||
|  | #include "util/union_find.h" | ||||||
| #include "smt/smt_theory.h" | #include "smt/smt_theory.h" | ||||||
| #include "model/finite_set_factory.h" | #include "model/finite_set_factory.h" | ||||||
| 
 | 
 | ||||||
| namespace smt { | namespace smt { | ||||||
|     class theory_finite_set : public theory { |     class theory_finite_set : public theory { | ||||||
|  |         using th_union_find = union_find<theory_finite_set>; | ||||||
|         friend class theory_finite_set_test; |         friend class theory_finite_set_test; | ||||||
|         friend struct finite_set_value_proc; |         friend struct finite_set_value_proc; | ||||||
|  |         friend class th_union_find; | ||||||
|  | 
 | ||||||
|  |         struct var_data { | ||||||
|  |             ptr_vector<enode> m_setops; | ||||||
|  |             ptr_vector<enode> m_parent_in; | ||||||
|  |             ptr_vector<enode> m_parent_setops; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct theory_clauses { | ||||||
|  |             vector<expr_ref_vector> axioms;            // vector of created theory axioms
 | ||||||
|  |             unsigned                aqhead = 0;        // queue head of created axioms
 | ||||||
|  |             unsigned_vector         squeue;            // propagation queue of axioms to be added to the solver
 | ||||||
|  |             unsigned                sqhead = 0;        // head into propagation queue axioms to be added to solver
 | ||||||
|  |             obj_pair_hashtable<expr, expr> members;    // set of membership axioms that were instantiated
 | ||||||
|  |             vector<unsigned_vector>  watch;            // watch list from expression index to clause occurrence
 | ||||||
|  | 
 | ||||||
|  |             bool can_propagate() const { | ||||||
|  |                 return sqhead < squeue.size() || aqhead < axioms.size(); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         struct stats { | ||||||
|  |             unsigned m_num_axioms_created = 0; | ||||||
|  |             unsigned m_num_axioms_conflicts = 0; | ||||||
|  |             unsigned m_num_axioms_propagated = 0; | ||||||
|  |             unsigned m_num_axioms_case_splits = 0; | ||||||
|  | 
 | ||||||
|  |             void collect_statistics(::statistics & st) const { | ||||||
|  |                 st.update("finite-set-axioms-created", m_num_axioms_created); | ||||||
|  |                 st.update("finite-set-axioms-propagated", m_num_axioms_propagated); | ||||||
|  |                 st.update("finite-set-axioms-conflicts", m_num_axioms_conflicts); | ||||||
|  |                 st.update("finite-set-axioms-case-splits", m_num_axioms_case_splits); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|         finite_set_util           u; |         finite_set_util           u; | ||||||
|         finite_set_axioms         m_axioms; |         finite_set_axioms         m_axioms; | ||||||
|         obj_hashtable<enode>      m_elements;             // set of all 'x' where there is an 'x in S' atom
 |         th_union_find             m_find; | ||||||
|         vector<expr_ref_vector>   m_theory_axioms; |         theory_clauses            m_clauses; | ||||||
|         obj_pair_hashtable<expr, expr> m_theory_axiom_exprs; |  | ||||||
|         finite_set_factory *m_factory = nullptr; |         finite_set_factory *m_factory = nullptr; | ||||||
|         obj_map<enode, obj_hashtable<enode> *> m_set_members; |         obj_map<enode, obj_hashtable<enode> *> m_set_members; | ||||||
|          |         ptr_vector<func_decl> m_set_in_decls; | ||||||
|  |         ptr_vector<var_data> m_var_data; | ||||||
|  |         stats m_stats; | ||||||
|  |                 | ||||||
|     protected: |     protected: | ||||||
|         // Override relevant methods from smt::theory
 |         // Override relevant methods from smt::theory
 | ||||||
|         bool internalize_atom(app * atom, bool gate_ctx) override; |         bool internalize_atom(app * atom, bool gate_ctx) override; | ||||||
|  | @ -112,23 +151,32 @@ namespace smt { | ||||||
|         void new_diseq_eh(theory_var v1, theory_var v2) override; |         void new_diseq_eh(theory_var v1, theory_var v2) override; | ||||||
|         void apply_sort_cnstr(enode *n, sort *s) override;  |         void apply_sort_cnstr(enode *n, sort *s) override;  | ||||||
|         final_check_status final_check_eh() override; |         final_check_status final_check_eh() override; | ||||||
|  |         bool can_propagate() override; | ||||||
|  |         void propagate() override; | ||||||
|  |         void assign_eh(bool_var v, bool is_true) override; | ||||||
|          |          | ||||||
|         theory * mk_fresh(context * new_ctx) override; |         theory * mk_fresh(context * new_ctx) override; | ||||||
|         char const * get_name() const override { return "finite_set"; } |         char const * get_name() const override { return "finite_set"; } | ||||||
|         void display(std::ostream & out) const override; |         void display(std::ostream & out) const override; | ||||||
|         void init_model(model_generator & mg) override; |         void init_model(model_generator & mg) override; | ||||||
|         model_value_proc * mk_value(enode * n, model_generator & mg) override; |         model_value_proc * mk_value(enode * n, model_generator & mg) override; | ||||||
|  |         theory_var mk_var(enode *n) override; | ||||||
|  | 
 | ||||||
|  |         void collect_statistics(::statistics & st) const override { | ||||||
|  |             m_stats.collect_statistics(st); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         void add_in_axioms(theory_var v1, theory_var v2); | ||||||
|  |         void add_in_axioms(enode *in, var_data *d); | ||||||
| 
 | 
 | ||||||
|         // Helper methods for axiom instantiation
 |         // Helper methods for axiom instantiation
 | ||||||
|         void add_membership_axioms(expr* elem, expr* set); |         void add_membership_axioms(expr* elem, expr* set); | ||||||
|         void add_clause(expr_ref_vector const& clause); |         void add_clause(expr_ref_vector const& clause); | ||||||
|         void assert_clause(expr_ref_vector const &clause); |         bool assert_clause(expr_ref_vector const &clause); | ||||||
|         bool instantiate_false_lemma(); |         void activate_clause(unsigned index); | ||||||
|         bool instantiate_unit_propagation(); |         bool activate_unasserted_clause(); | ||||||
|         bool instantiate_free_lemma(); |  | ||||||
|         lbool truth_value(expr *e); |  | ||||||
|         void add_immediate_axioms(app *atom); |         void add_immediate_axioms(app *atom); | ||||||
|         bool add_membership_axioms(); |  | ||||||
|         bool assume_eqs(); |         bool assume_eqs(); | ||||||
|         bool is_new_axiom(expr *a, expr *b); |         bool is_new_axiom(expr *a, expr *b); | ||||||
|         app *mk_union(unsigned num_elems, expr *const *elems, sort* set_sort); |         app *mk_union(unsigned num_elems, expr *const *elems, sort* set_sort); | ||||||
|  | @ -136,6 +184,16 @@ namespace smt { | ||||||
|         // model construction
 |         // model construction
 | ||||||
|         void collect_members(); |         void collect_members(); | ||||||
|         void reset_set_members(); |         void reset_set_members(); | ||||||
|  | 
 | ||||||
|  |         // manage union-find of theory variables
 | ||||||
|  |         theory_var find(theory_var v) const { return m_find.find(v); }         | ||||||
|  |         bool is_root(theory_var v) const { return m_find.is_root(v); } | ||||||
|  |         trail_stack &get_trail_stack(); | ||||||
|  |         void merge_eh(theory_var v1, theory_var v2, theory_var, theory_var); | ||||||
|  |         void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) {} | ||||||
|  |         void unmerge_eh(theory_var v1, theory_var v2) {} | ||||||
|  | 
 | ||||||
|  |         std::ostream &display_var(std::ostream &out, theory_var v) const; | ||||||
|          |          | ||||||
|     public: |     public: | ||||||
|         theory_finite_set(context& ctx); |         theory_finite_set(context& ctx); | ||||||
|  |  | ||||||
|  | @ -266,6 +266,10 @@ public: | ||||||
|         SASSERT(&(this->m_manager) == &(other.m_manager)); |         SASSERT(&(this->m_manager) == &(other.m_manager)); | ||||||
|         this->m_nodes.swap(other.m_nodes); |         this->m_nodes.swap(other.m_nodes); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     void swap(unsigned idx1, unsigned idx2) noexcept { | ||||||
|  |         this->super::swap(idx1, idx2); | ||||||
|  |     } | ||||||
|      |      | ||||||
|     class element_ref { |     class element_ref { | ||||||
|         T * &       m_ref; |         T * &       m_ref; | ||||||
|  |  | ||||||
|  | @ -423,4 +423,13 @@ public: | ||||||
|         m_scopes.shrink(new_lvl); |         m_scopes.shrink(new_lvl); | ||||||
|         m_region.pop_scope(num_scopes); |         m_region.pop_scope(num_scopes); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     unsigned size() const { | ||||||
|  |         return m_trail_stack.size(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void shrink(unsigned new_size) { | ||||||
|  |         SASSERT(new_size <= m_trail_stack.size()); | ||||||
|  |         m_trail_stack.shrink(new_size); | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue