3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-14 14:55:25 +00:00

add an option to register callback on quantifier instantiation

Suppose a user propagator encodes axioms using quantifiers and uses E-matching for instantiation. If it wants to implement a custom priority scheme or drop some instances based on internal checks it can register a callback with quantifier instantiation
This commit is contained in:
Nikolaj Bjorner 2025-08-06 21:11:38 -07:00
parent d4a4dd6cc7
commit b33f444545
24 changed files with 126 additions and 3 deletions

View file

@ -1944,6 +1944,7 @@ Z3_eq_eh = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_
Z3_created_eh = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) Z3_created_eh = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p)
Z3_decide_eh = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int) Z3_decide_eh = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int)
Z3_on_binding_eh = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p)
_lib.Z3_solver_register_on_clause.restype = None _lib.Z3_solver_register_on_clause.restype = None
_lib.Z3_solver_propagate_init.restype = None _lib.Z3_solver_propagate_init.restype = None

View file

@ -1160,6 +1160,14 @@ extern "C" {
Z3_CATCH; Z3_CATCH;
} }
void Z3_API Z3_solver_propagate_on_binding(Z3_context c, Z3_solver s, Z3_on_binding_eh binding_eh) {
Z3_TRY;
RESET_ERROR_CODE();
user_propagator::binding_eh_t c = (bool(*)(void*, user_propagator::callback*, expr*, expr*))binding_eh;
to_solver_ref(s)->user_propagate_register_on_binding(c);
Z3_CATCH;
}
bool Z3_API Z3_solver_next_split(Z3_context c, Z3_solver_callback cb, Z3_ast t, unsigned idx, Z3_lbool phase) { bool Z3_API Z3_solver_next_split(Z3_context c, Z3_solver_callback cb, Z3_ast t, unsigned idx, Z3_lbool phase) {
Z3_TRY; Z3_TRY;
LOG_Z3_solver_next_split(c, cb, t, idx, phase); LOG_Z3_solver_next_split(c, cb, t, idx, phase);

View file

@ -11814,6 +11814,16 @@ def user_prop_decide(ctx, cb, t_ref, idx, phase):
t = _to_expr_ref(to_Ast(t_ref), prop.ctx()) t = _to_expr_ref(to_Ast(t_ref), prop.ctx())
prop.decide(t, idx, phase) prop.decide(t, idx, phase)
prop.cb = old_cb prop.cb = old_cb
def user_prop_binding(ctx, cb, q_ref, inst_ref):
prop = _prop_closures.get(ctx)
old_cb = prop.cb
prop.cb = cb
q = _to_expr_ref(to_Ast(q_ref), prop.ctx())
inst = _to_expr_ref(to_Ast(inst_ref), prop.ctx())
r = prop.binding(q, inst)
prop.cb = old_cb
return r
_user_prop_push = Z3_push_eh(user_prop_push) _user_prop_push = Z3_push_eh(user_prop_push)
@ -11825,6 +11835,7 @@ _user_prop_final = Z3_final_eh(user_prop_final)
_user_prop_eq = Z3_eq_eh(user_prop_eq) _user_prop_eq = Z3_eq_eh(user_prop_eq)
_user_prop_diseq = Z3_eq_eh(user_prop_diseq) _user_prop_diseq = Z3_eq_eh(user_prop_diseq)
_user_prop_decide = Z3_decide_eh(user_prop_decide) _user_prop_decide = Z3_decide_eh(user_prop_decide)
_user_prop_binding = Z3_on_binding_eh(user_prop_binding)
def PropagateFunction(name, *sig): def PropagateFunction(name, *sig):
@ -11873,6 +11884,7 @@ class UserPropagateBase:
self.diseq = None self.diseq = None
self.decide = None self.decide = None
self.created = None self.created = None
self.binding = None
if ctx: if ctx:
self.fresh_ctx = ctx self.fresh_ctx = ctx
if s: if s:
@ -11936,7 +11948,14 @@ class UserPropagateBase:
assert not self._ctx assert not self._ctx
if self.solver: if self.solver:
Z3_solver_propagate_decide(self.ctx_ref(), self.solver.solver, _user_prop_decide) Z3_solver_propagate_decide(self.ctx_ref(), self.solver.solver, _user_prop_decide)
self.decide = decide self.decide = decide
def add_on_binding(self, binding):
assert not self.binding
assert not self._ctx
if self.solver:
Z3_solver_propagate_on_binding(self.ctx_ref(), self.solver.solver, _user_prop_binding)
self.binding = binding
def push(self): def push(self):
raise Z3Exception("push needs to be overwritten") raise Z3Exception("push needs to be overwritten")

View file

@ -1440,6 +1440,7 @@ Z3_DECLARE_CLOSURE(Z3_eq_eh, void, (void* ctx, Z3_solver_callback cb, Z3_as
Z3_DECLARE_CLOSURE(Z3_final_eh, void, (void* ctx, Z3_solver_callback cb)); Z3_DECLARE_CLOSURE(Z3_final_eh, void, (void* ctx, Z3_solver_callback cb));
Z3_DECLARE_CLOSURE(Z3_created_eh, void, (void* ctx, Z3_solver_callback cb, Z3_ast t)); Z3_DECLARE_CLOSURE(Z3_created_eh, void, (void* ctx, Z3_solver_callback cb, Z3_ast t));
Z3_DECLARE_CLOSURE(Z3_decide_eh, void, (void* ctx, Z3_solver_callback cb, Z3_ast t, unsigned idx, bool phase)); Z3_DECLARE_CLOSURE(Z3_decide_eh, void, (void* ctx, Z3_solver_callback cb, Z3_ast t, unsigned idx, bool phase));
Z3_DECLARE_CLOSURE(Z3_on_binding_eh, bool, (void* ctx, Z3_solver_callback cb, Z3_ast q, Z3_ast inst));
Z3_DECLARE_CLOSURE(Z3_on_clause_eh, void, (void* ctx, Z3_ast proof_hint, unsigned n, unsigned const* deps, Z3_ast_vector literals)); Z3_DECLARE_CLOSURE(Z3_on_clause_eh, void, (void* ctx, Z3_ast proof_hint, unsigned n, unsigned const* deps, Z3_ast_vector literals));
@ -7225,6 +7226,17 @@ extern "C" {
*/ */
void Z3_API Z3_solver_propagate_decide(Z3_context c, Z3_solver s, Z3_decide_eh decide_eh); void Z3_API Z3_solver_propagate_decide(Z3_context c, Z3_solver s, Z3_decide_eh decide_eh);
/**
\brief register a callback when the solver instantiates a quantifier.
If the callback returns false, the actual instantiation of the quantifier is blocked.
This allows the user propagator selectively prioritize instantiations without relying on default
or configured weights.
def_API('Z3_solver_propagate_on_binding', VOID, (_in(CONTEXT), _in(SOLVER), _fnptr(Z3_on_binding_eh)))
*/
void Z3_API Z3_solver_propagate_on_binding(Z3_context c, Z3_solver s, Z3_on_binding_eh on_binding_eh);
/** /**
Sets the next (registered) expression to split on. Sets the next (registered) expression to split on.
The function returns false and ignores the given expression in case the expression is already assigned internally The function returns false and ignores the given expression in case the expression is already assigned internally

View file

@ -565,6 +565,10 @@ public:
void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override {
ensure_euf()->user_propagate_register_diseq(diseq_eh); ensure_euf()->user_propagate_register_diseq(diseq_eh);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
ensure_euf()->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override { void user_propagate_register_expr(expr* e) override {
ensure_euf()->user_propagate_register_expr(e); ensure_euf()->user_propagate_register_expr(e);

View file

@ -554,6 +554,10 @@ namespace euf {
check_for_user_propagator(); check_for_user_propagator();
m_user_propagator->register_decide(ceh); m_user_propagator->register_decide(ceh);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& on_binding_eh) {
check_for_user_propagator();
NOT_IMPLEMENTED_YET();
}
void user_propagate_register_expr(expr* e) { void user_propagate_register_expr(expr* e) {
check_for_user_propagator(); check_for_user_propagator();
m_user_propagator->add_expr(e); m_user_propagator->add_expr(e);

View file

@ -263,6 +263,11 @@ namespace smt {
if (stat->get_num_instances() % m_params.m_qi_profile_freq == 0) { if (stat->get_num_instances() % m_params.m_qi_profile_freq == 0) {
m_qm.display_stats(verbose_stream(), q); m_qm.display_stats(verbose_stream(), q);
} }
if (m_on_binding && !m_on_binding(q, instance)) {
verbose_stream() << "qi_queue: on_binding returned false, skipping instance.\n";
return;
}
expr_ref lemma(m); expr_ref lemma(m);
if (m.is_or(s_instance)) { if (m.is_or(s_instance)) {
ptr_vector<expr> args; ptr_vector<expr> args;

View file

@ -28,6 +28,7 @@ Revision History:
#include "params/qi_params.h" #include "params/qi_params.h"
#include "ast/cost_evaluator.h" #include "ast/cost_evaluator.h"
#include "util/statistics.h" #include "util/statistics.h"
#include "tactic/user_propagator_base.h"
namespace smt { namespace smt {
class context; class context;
@ -52,6 +53,7 @@ namespace smt {
cached_var_subst m_subst; cached_var_subst m_subst;
svector<float> m_vals; svector<float> m_vals;
double m_eager_cost_threshold = 0; double m_eager_cost_threshold = 0;
std::function<bool(quantifier*,expr*)> m_on_binding;
struct entry { struct entry {
fingerprint * m_qb; fingerprint * m_qb;
float m_cost; float m_cost;
@ -95,6 +97,9 @@ namespace smt {
void reset(); void reset();
void display_delayed_instances_stats(std::ostream & out) const; void display_delayed_instances_stats(std::ostream & out) const;
void collect_statistics(::statistics & st) const; void collect_statistics(::statistics & st) const;
void register_on_binding(std::function<bool(quantifier* q, expr* e)> & on_binding) {
m_on_binding = on_binding;
}
}; };
}; };

View file

@ -1814,6 +1814,14 @@ namespace smt {
m_user_propagator->register_decide(r); m_user_propagator->register_decide(r);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& t) {
m_user_propagator->register_on_binding(t);
}
void register_on_binding(std::function<bool(quantifier* q, expr* inst)>& f) {
m_qmanager->register_on_binding(f);
}
void user_propagate_initialize_value(expr* var, expr* value); void user_propagate_initialize_value(expr* var, expr* value);
bool watches_fixed(enode* n) const; bool watches_fixed(enode* n) const;

View file

@ -307,6 +307,10 @@ namespace smt {
void kernel::user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) { void kernel::user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) {
m_imp->m_kernel.user_propagate_register_fixed(fixed_eh); m_imp->m_kernel.user_propagate_register_fixed(fixed_eh);
} }
void kernel::user_propagate_register_on_binding(user_propagator::binding_eh_t& on_binding) {
m_imp->m_kernel.user_propagate_register_on_binding(on_binding);
}
void kernel::user_propagate_register_final(user_propagator::final_eh_t& final_eh) { void kernel::user_propagate_register_final(user_propagator::final_eh_t& final_eh) {
m_imp->m_kernel.user_propagate_register_final(final_eh); m_imp->m_kernel.user_propagate_register_final(final_eh);

View file

@ -319,6 +319,8 @@ namespace smt {
void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh); void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh);
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh);
void user_propagate_register_expr(expr* e); void user_propagate_register_expr(expr* e);
void user_propagate_register_created(user_propagator::created_eh_t& r); void user_propagate_register_created(user_propagator::created_eh_t& r);

View file

@ -339,6 +339,10 @@ namespace smt {
m_plugin->add_eq_eh(n1, n2); m_plugin->add_eq_eh(n1, n2);
} }
void register_on_binding(std::function<bool(quantifier*, expr*)>& on_binding) {
m_qi_queue.register_on_binding(on_binding);
}
void relevant_eh(enode * n) { void relevant_eh(enode * n) {
m_plugin->relevant_eh(n); m_plugin->relevant_eh(n);
} }
@ -493,6 +497,10 @@ namespace smt {
m_imp->add_eq_eh(n1, n2); m_imp->add_eq_eh(n1, n2);
} }
void quantifier_manager::register_on_binding(std::function<bool(quantifier*, expr*)>& on_binding) {
m_imp->register_on_binding(on_binding);
}
void quantifier_manager::relevant_eh(enode * n) { void quantifier_manager::relevant_eh(enode * n) {
m_imp->relevant_eh(n); m_imp->relevant_eh(n);
} }

View file

@ -23,6 +23,7 @@ Revision History:
#include "util/statistics.h" #include "util/statistics.h"
#include "util/params.h" #include "util/params.h"
#include "smt/smt_types.h" #include "smt/smt_types.h"
#include "tactic/user_propagator_base.h"
#include <tuple> #include <tuple>
class proto_model; class proto_model;
@ -96,6 +97,8 @@ namespace smt {
void collect_statistics(::statistics & st) const; void collect_statistics(::statistics & st) const;
void reset_statistics(); void reset_statistics();
void register_on_binding(std::function<bool(quantifier*, expr*)> & f);
ptr_vector<quantifier>::const_iterator begin_quantifiers() const; ptr_vector<quantifier>::const_iterator begin_quantifiers() const;
ptr_vector<quantifier>::const_iterator end_quantifiers() const; ptr_vector<quantifier>::const_iterator end_quantifiers() const;
ptr_vector<quantifier>::const_iterator begin() const { return begin_quantifiers(); } ptr_vector<quantifier>::const_iterator begin() const { return begin_quantifiers(); }

View file

@ -244,6 +244,10 @@ namespace {
m_context.user_propagate_register_expr(e); m_context.user_propagate_register_expr(e);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
m_context.user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_created(user_propagator::created_eh_t& c) override { void user_propagate_register_created(user_propagator::created_eh_t& c) override {
m_context.user_propagate_register_created(c); m_context.user_propagate_register_created(c);
} }

View file

@ -402,6 +402,10 @@ public:
m_diseq_eh = diseq_eh; m_diseq_eh = diseq_eh;
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
m_ctx.load()->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override { void user_propagate_register_expr(expr* e) override {
m_vars.push_back(e); m_vars.push_back(e);
} }

View file

@ -110,6 +110,15 @@ void theory_user_propagator::register_cb(expr* e) {
add_expr(e, true); add_expr(e, true);
} }
void theory_user_propagator::register_on_binding(user_propagator::binding_eh_t& binding_eh) {
std::function<bool(quantifier* q, expr* inst)> on_binding =
[this, binding_eh](quantifier* q, expr* inst) {
return binding_eh(m_user_context, this, q, inst);
};
ctx.register_on_binding(on_binding);
}
bool theory_user_propagator::next_split_cb(expr* e, unsigned idx, lbool phase) { bool theory_user_propagator::next_split_cb(expr* e, unsigned idx, lbool phase) {
if (e == nullptr) { // clear if (e == nullptr) { // clear
m_next_split_var = nullptr; m_next_split_var = nullptr;

View file

@ -132,6 +132,7 @@ namespace smt {
void register_diseq(user_propagator::eq_eh_t& diseq_eh) { m_diseq_eh = diseq_eh; } void register_diseq(user_propagator::eq_eh_t& diseq_eh) { m_diseq_eh = diseq_eh; }
void register_created(user_propagator::created_eh_t& created_eh) { m_created_eh = created_eh; } void register_created(user_propagator::created_eh_t& created_eh) { m_created_eh = created_eh; }
void register_decide(user_propagator::decide_eh_t& decide_eh) { m_decide_eh = decide_eh; } void register_decide(user_propagator::decide_eh_t& decide_eh) { m_decide_eh = decide_eh; }
void register_on_binding(user_propagator::binding_eh_t& binding_eh);
bool has_fixed() const { return (bool)m_fixed_eh; } bool has_fixed() const { return (bool)m_fixed_eh; }

View file

@ -379,6 +379,10 @@ public:
void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override {
m_solver2->user_propagate_register_diseq(diseq_eh); m_solver2->user_propagate_register_diseq(diseq_eh);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
m_solver2->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override { void user_propagate_register_expr(expr* e) override {
m_solver2->user_propagate_register_expr(e); m_solver2->user_propagate_register_expr(e);

View file

@ -387,7 +387,10 @@ public:
void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override { s->user_propagate_register_fixed(fixed_eh); } void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override { s->user_propagate_register_fixed(fixed_eh); }
void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override { s->user_propagate_register_final(final_eh); } void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override { s->user_propagate_register_final(final_eh); }
void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override { s->user_propagate_register_eq(eq_eh); } void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override { s->user_propagate_register_eq(eq_eh); }
void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { s->user_propagate_register_diseq(diseq_eh); } void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { s->user_propagate_register_diseq(diseq_eh); }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
s->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override { m_preprocess_state.freeze(e); s->user_propagate_register_expr(e); } void user_propagate_register_expr(expr* e) override { m_preprocess_state.freeze(e); s->user_propagate_register_expr(e); }
void user_propagate_register_created(user_propagator::created_eh_t& r) override { s->user_propagate_register_created(r); } void user_propagate_register_created(user_propagator::created_eh_t& r) override { s->user_propagate_register_created(r); }
void user_propagate_register_decide(user_propagator::decide_eh_t& r) override { s->user_propagate_register_decide(r); } void user_propagate_register_decide(user_propagator::decide_eh_t& r) override { s->user_propagate_register_decide(r); }

View file

@ -415,7 +415,8 @@ public:
void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override { s->user_propagate_register_fixed(fixed_eh); } void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override { s->user_propagate_register_fixed(fixed_eh); }
void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override { s->user_propagate_register_final(final_eh); } void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override { s->user_propagate_register_final(final_eh); }
void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override { s->user_propagate_register_eq(eq_eh); } void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override { s->user_propagate_register_eq(eq_eh); }
void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { s->user_propagate_register_diseq(diseq_eh); } void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { s->user_propagate_register_diseq(diseq_eh); }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override { s->user_propagate_register_on_binding(binding_eh); }
void user_propagate_register_expr(expr* e) override { s->user_propagate_register_expr(e); } void user_propagate_register_expr(expr* e) override { s->user_propagate_register_expr(e); }
void user_propagate_register_created(user_propagator::created_eh_t& r) override { s->user_propagate_register_created(r); } void user_propagate_register_created(user_propagator::created_eh_t& r) override { s->user_propagate_register_created(r); }
void user_propagate_register_decide(user_propagator::decide_eh_t& r) override { s->user_propagate_register_decide(r); } void user_propagate_register_decide(user_propagator::decide_eh_t& r) override { s->user_propagate_register_decide(r); }

View file

@ -115,6 +115,10 @@ public:
m_tactic->user_propagate_register_diseq(diseq_eh); m_tactic->user_propagate_register_diseq(diseq_eh);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
m_tactic->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override { void user_propagate_register_expr(expr* e) override {
m_tactic->user_propagate_register_expr(e); m_tactic->user_propagate_register_expr(e);
} }

View file

@ -168,6 +168,7 @@ public:
m_frozen.push_back(e); m_frozen.push_back(e);
} }
void user_propagate_clear() override { void user_propagate_clear() override {
if (m_simp) { if (m_simp) {
pop(1); pop(1);

View file

@ -200,6 +200,10 @@ public:
m_t2->user_propagate_register_diseq(diseq_eh); m_t2->user_propagate_register_diseq(diseq_eh);
} }
void user_propagate_register_on_binding(user_propagator::binding_eh_t& binding_eh) override {
m_t2->user_propagate_register_on_binding(binding_eh);
}
void user_propagate_register_expr(expr* e) override { void user_propagate_register_expr(expr* e) override {
m_t1->user_propagate_register_expr(e); m_t1->user_propagate_register_expr(e);
m_t2->user_propagate_register_expr(e); m_t2->user_propagate_register_expr(e);

View file

@ -28,6 +28,7 @@ namespace user_propagator {
typedef std::function<void(void*, callback*, expr*)> created_eh_t; typedef std::function<void(void*, callback*, expr*)> created_eh_t;
typedef std::function<void(void*, callback*, expr*, unsigned, bool)> decide_eh_t; typedef std::function<void(void*, callback*, expr*, unsigned, bool)> decide_eh_t;
typedef std::function<void(void*, expr*, unsigned, unsigned const*, unsigned, expr* const*)> on_clause_eh_t; typedef std::function<void(void*, expr*, unsigned, unsigned const*, unsigned, expr* const*)> on_clause_eh_t;
typedef std::function<bool(void*, callback*, expr*, expr*)> binding_eh_t;
class plugin : public decl_plugin { class plugin : public decl_plugin {
public: public:
@ -92,6 +93,10 @@ namespace user_propagator {
throw default_exception("user-propagators are only supported on the SMT solver"); throw default_exception("user-propagators are only supported on the SMT solver");
} }
virtual void user_propagate_register_on_binding(binding_eh_t& r) {
throw default_exception("user-propagators are only supported on the SMT solver");
}
virtual void user_propagate_clear() { virtual void user_propagate_clear() {
} }