mirror of
https://github.com/Z3Prover/z3
synced 2025-09-12 12:41:27 +00:00
Add comprehensive documentation and examples for UserPropagator quantifier instantiation callbacks
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
This commit is contained in:
parent
f3f0171f35
commit
2e031bc7fc
6 changed files with 1809 additions and 0 deletions
306
examples/python/quantifier_instantiation_callback.py
Normal file
306
examples/python/quantifier_instantiation_callback.py
Normal file
|
@ -0,0 +1,306 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example demonstrating the UserPropagator callback for quantifier instantiations in Z3.
|
||||
|
||||
This feature was added in Z3 version 4.15.3 and allows user propagators to intercept
|
||||
and control quantifier instantiations. The callback receives the quantifier and its
|
||||
proposed instantiation, and can return False to discard the instantiation.
|
||||
|
||||
This provides fine-grained control over the quantifier instantiation process,
|
||||
allowing users to:
|
||||
1. Inspect and log instantiations
|
||||
2. Filter out undesired instantiations
|
||||
3. Implement custom instantiation strategies
|
||||
4. Delay certain instantiations for performance reasons
|
||||
"""
|
||||
|
||||
from z3 import *
|
||||
|
||||
class QuantifierInstantiationController(UserPropagateBase):
|
||||
"""
|
||||
A user propagator that controls quantifier instantiations.
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Register a quantifier instantiation callback
|
||||
2. Inspect proposed instantiations
|
||||
3. Selectively allow or discard instantiations
|
||||
"""
|
||||
|
||||
def __init__(self, s=None, ctx=None):
|
||||
UserPropagateBase.__init__(self, s, ctx)
|
||||
self.instantiation_count = 0
|
||||
self.allowed_instantiations = 0
|
||||
self.blocked_instantiations = 0
|
||||
self.instantiation_log = []
|
||||
|
||||
# Register the quantifier instantiation callback
|
||||
self.add_on_binding(self.on_quantifier_instantiation)
|
||||
|
||||
def on_quantifier_instantiation(self, quantifier, instantiation):
|
||||
"""
|
||||
Callback invoked when Z3 wants to instantiate a quantifier.
|
||||
|
||||
Args:
|
||||
quantifier: The quantifier being instantiated (Z3 AST)
|
||||
instantiation: The proposed instantiation (Z3 AST)
|
||||
|
||||
Returns:
|
||||
bool: True to allow the instantiation, False to discard it
|
||||
"""
|
||||
self.instantiation_count += 1
|
||||
|
||||
# Log the instantiation for inspection
|
||||
q_str = str(quantifier)
|
||||
inst_str = str(instantiation)
|
||||
self.instantiation_log.append((q_str, inst_str))
|
||||
|
||||
print(f"Instantiation #{self.instantiation_count}:")
|
||||
print(f" Quantifier: {q_str}")
|
||||
print(f" Instantiation: {inst_str}")
|
||||
|
||||
# Example filtering logic: allow only the first 3 instantiations
|
||||
# In practice, you might filter based on the structure of the instantiation,
|
||||
# performance considerations, or other criteria
|
||||
allow = self.instantiation_count <= 3
|
||||
|
||||
if allow:
|
||||
self.allowed_instantiations += 1
|
||||
print(f" -> ALLOWED (#{self.allowed_instantiations})")
|
||||
else:
|
||||
self.blocked_instantiations += 1
|
||||
print(f" -> BLOCKED (#{self.blocked_instantiations})")
|
||||
|
||||
print()
|
||||
return allow
|
||||
|
||||
def push(self):
|
||||
# Required method for user propagators
|
||||
pass
|
||||
|
||||
def pop(self, num_scopes):
|
||||
# Required method for user propagators
|
||||
pass
|
||||
|
||||
def fresh(self, new_ctx):
|
||||
# Required method for user propagators
|
||||
return QuantifierInstantiationController(ctx=new_ctx)
|
||||
|
||||
def get_statistics(self):
|
||||
"""Return statistics about instantiation control."""
|
||||
return {
|
||||
'total_instantiation_attempts': self.instantiation_count,
|
||||
'allowed_instantiations': self.allowed_instantiations,
|
||||
'blocked_instantiations': self.blocked_instantiations,
|
||||
'instantiation_log': self.instantiation_log
|
||||
}
|
||||
|
||||
|
||||
def example_basic_quantifier_control():
|
||||
"""
|
||||
Basic example showing how to control quantifier instantiations.
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("BASIC QUANTIFIER INSTANTIATION CONTROL EXAMPLE")
|
||||
print("=" * 60)
|
||||
|
||||
# Create solver with user propagator
|
||||
s = Solver()
|
||||
controller = QuantifierInstantiationController(s)
|
||||
|
||||
# Create a simple quantified formula
|
||||
x = Int('x')
|
||||
f = Function('f', IntSort(), IntSort())
|
||||
|
||||
# Add a quantified axiom: forall x. f(x) >= 0
|
||||
axiom = ForAll([x], f(x) >= 0)
|
||||
s.add(axiom)
|
||||
|
||||
# Add constraints that might trigger instantiations
|
||||
a, b, c = Ints('a b c')
|
||||
s.add(f(a) < 10)
|
||||
s.add(f(b) < 20)
|
||||
s.add(f(c) < 30)
|
||||
|
||||
# Also add constraints that should conflict if all instantiations are allowed
|
||||
s.add(a == 1)
|
||||
s.add(b == 2)
|
||||
s.add(c == 3)
|
||||
|
||||
print("Checking satisfiability with instantiation control...")
|
||||
result = s.check()
|
||||
print(f"Result: {result}")
|
||||
|
||||
if result == sat:
|
||||
print(f"Model: {s.model()}")
|
||||
|
||||
# Print statistics
|
||||
stats = controller.get_statistics()
|
||||
print(f"\nInstantiation Statistics:")
|
||||
print(f" Total attempts: {stats['total_instantiation_attempts']}")
|
||||
print(f" Allowed: {stats['allowed_instantiations']}")
|
||||
print(f" Blocked: {stats['blocked_instantiations']}")
|
||||
|
||||
|
||||
def example_advanced_filtering():
|
||||
"""
|
||||
Advanced example showing more sophisticated instantiation filtering.
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("ADVANCED INSTANTIATION FILTERING EXAMPLE")
|
||||
print("=" * 60)
|
||||
|
||||
class AdvancedController(UserPropagateBase):
|
||||
def __init__(self, s=None, ctx=None):
|
||||
UserPropagateBase.__init__(self, s, ctx)
|
||||
self.instantiations_by_term = {}
|
||||
self.add_on_binding(self.smart_filter)
|
||||
|
||||
def smart_filter(self, quantifier, instantiation):
|
||||
"""
|
||||
Smart filtering based on instantiation content.
|
||||
"""
|
||||
# Extract information about the instantiation
|
||||
q_str = str(quantifier)
|
||||
inst_str = str(instantiation)
|
||||
|
||||
# Count instantiations per term pattern
|
||||
if inst_str not in self.instantiations_by_term:
|
||||
self.instantiations_by_term[inst_str] = 0
|
||||
self.instantiations_by_term[inst_str] += 1
|
||||
|
||||
# Allow only up to 2 instantiations of the same pattern
|
||||
allow = self.instantiations_by_term[inst_str] <= 2
|
||||
|
||||
print(f"Instantiation: {inst_str} (attempt #{self.instantiations_by_term[inst_str]})")
|
||||
print(f" -> {'ALLOWED' if allow else 'BLOCKED'}")
|
||||
|
||||
return allow
|
||||
|
||||
def push(self):
|
||||
pass
|
||||
|
||||
def pop(self, num_scopes):
|
||||
pass
|
||||
|
||||
def fresh(self, new_ctx):
|
||||
return AdvancedController(ctx=new_ctx)
|
||||
|
||||
# Create solver with advanced controller
|
||||
s = Solver()
|
||||
controller = AdvancedController(s)
|
||||
|
||||
# Create a more complex scenario
|
||||
x, y = Ints('x y')
|
||||
P = Function('P', IntSort(), IntSort(), BoolSort())
|
||||
|
||||
# Add quantified formula: forall x, y. P(x, y) => P(y, x)
|
||||
axiom = ForAll([x, y], Implies(P(x, y), P(y, x)))
|
||||
s.add(axiom)
|
||||
|
||||
# Add facts that will trigger instantiations
|
||||
a, b, c = Ints('a b c')
|
||||
s.add(P(a, b))
|
||||
s.add(P(b, c))
|
||||
s.add(P(c, a))
|
||||
|
||||
print("Checking satisfiability with advanced filtering...")
|
||||
result = s.check()
|
||||
print(f"Result: {result}")
|
||||
|
||||
if result == sat:
|
||||
print(f"Model: {s.model()}")
|
||||
|
||||
|
||||
def example_instantiation_logging():
|
||||
"""
|
||||
Example focused on logging and analyzing instantiation patterns.
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("INSTANTIATION LOGGING AND ANALYSIS EXAMPLE")
|
||||
print("=" * 60)
|
||||
|
||||
class LoggingController(UserPropagateBase):
|
||||
def __init__(self, s=None, ctx=None):
|
||||
UserPropagateBase.__init__(self, s, ctx)
|
||||
self.log = []
|
||||
self.add_on_binding(self.log_instantiation)
|
||||
|
||||
def log_instantiation(self, quantifier, instantiation):
|
||||
"""
|
||||
Log all instantiation attempts for later analysis.
|
||||
"""
|
||||
entry = {
|
||||
'quantifier': str(quantifier),
|
||||
'instantiation': str(instantiation),
|
||||
'timestamp': len(self.log)
|
||||
}
|
||||
self.log.append(entry)
|
||||
|
||||
# For this example, allow all instantiations
|
||||
return True
|
||||
|
||||
def push(self):
|
||||
pass
|
||||
|
||||
def pop(self, num_scopes):
|
||||
pass
|
||||
|
||||
def fresh(self, new_ctx):
|
||||
return LoggingController(ctx=new_ctx)
|
||||
|
||||
def analyze_patterns(self):
|
||||
"""Analyze logged instantiation patterns."""
|
||||
print(f"Total instantiations logged: {len(self.log)}")
|
||||
|
||||
# Group by quantifier
|
||||
by_quantifier = {}
|
||||
for entry in self.log:
|
||||
q = entry['quantifier']
|
||||
if q not in by_quantifier:
|
||||
by_quantifier[q] = []
|
||||
by_quantifier[q].append(entry['instantiation'])
|
||||
|
||||
for q, instantiations in by_quantifier.items():
|
||||
print(f"\nQuantifier: {q}")
|
||||
print(f" Instantiations ({len(instantiations)}):")
|
||||
for i, inst in enumerate(instantiations, 1):
|
||||
print(f" {i}. {inst}")
|
||||
|
||||
# Create solver with logging controller
|
||||
s = Solver()
|
||||
controller = LoggingController(s)
|
||||
|
||||
# Create scenario with multiple quantifiers
|
||||
x = Int('x')
|
||||
f = Function('f', IntSort(), IntSort())
|
||||
g = Function('g', IntSort(), IntSort())
|
||||
|
||||
# Add multiple quantified axioms
|
||||
s.add(ForAll([x], f(x) >= x)) # f(x) is at least x
|
||||
s.add(ForAll([x], g(x) <= x)) # g(x) is at most x
|
||||
|
||||
# Add constraints to trigger instantiations
|
||||
a, b = Ints('a b')
|
||||
s.add(f(a) < 5)
|
||||
s.add(g(b) > 10)
|
||||
s.add(a == 2)
|
||||
s.add(b == 8)
|
||||
|
||||
print("Solving with full logging enabled...")
|
||||
result = s.check()
|
||||
print(f"Result: {result}")
|
||||
|
||||
# Analyze the logged instantiation patterns
|
||||
print("\nInstantiation Analysis:")
|
||||
controller.analyze_patterns()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run all examples
|
||||
example_basic_quantifier_control()
|
||||
example_advanced_filtering()
|
||||
example_instantiation_logging()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All examples completed successfully!")
|
||||
print("=" * 60)
|
Loading…
Add table
Add a link
Reference in a new issue