SAT Solving
This section explains the satisfiability solving capabilities of SymbolicSMT.jl.
Satisfiability vs Provability
SymbolicSMT.jl provides two main types of queries:
Satisfiability (issatisfiable
)
Question: "Can this expression be true given the constraints?"
using Symbolics, SymbolicSMT
@variables x::Real y::Real
constraints = Constraints([x > 0, y > 0])
# Can x be greater than 10?
issatisfiable(x > 10, constraints) # true - yes, it's possible
Provability (isprovable
)
Question: "Is this expression always true given the constraints?"
# Is x always positive?
isprovable(x > 0, constraints) # true - yes, always true
# Is x always greater than y?
isprovable(x > y, constraints) # false - not necessarily true
Understanding Return Values
Three-Valued Logic
Both functions can return three possible values:
true
: Yes (satisfiable/provable)false
: No (unsatisfiable/not provable)nothing
: Unknown (solver timeout or undecidable)
result = issatisfiable(complex_expression, constraints)
if result === true
println("Satisfiable!")
elseif result === false
println("Unsatisfiable!")
else # result === nothing
println("Solver couldn't determine")
end
Practical Interpretation
For issatisfiable
:
true
: There exists at least one solutionfalse
: No solution existsnothing
: Solver cannot determine (rare)
For isprovable
:
true
: Statement is always true (tautology under constraints)false
: Statement can be false (not a tautology)nothing
: Solver cannot determine (rare)
Resolution with resolve
The resolve
function combines both operations to simplify expressions:
@variables t::Real
time_constraints = Constraints([t >= 0, t <= 24])
# Expressions that resolve to constants
resolve(t >= 0, time_constraints) # true (always true)
resolve(t < 0, time_constraints) # false (never true)
resolve(t > 30, time_constraints) # false (impossible)
# Expressions that cannot be resolved
resolve(t > 12, time_constraints) # t > 12 (unchanged)
Practical Examples
Example 1: Geometric Reasoning
@variables x::Real y::Real
# Points inside unit circle
unit_circle = Constraints([x^2 + y^2 <= 1])
# Can a point be outside the circle?
issatisfiable(x^2 + y^2 > 1, unit_circle) # false
# Are all points within distance 1 of origin?
isprovable(x^2 + y^2 <= 1, unit_circle) # true
Example 2: Logical Reasoning
@variables p::Bool q::Bool r::Bool
# Logical constraints: if p then q, if q then r
implications = Constraints([
!p | q, # p => q
!q | r # q => r
])
# If p is true, is r necessarily true?
isprovable(p => r, implications) # true (transitivity)
# Can all three be false?
issatisfiable(!p & !q & !r, implications) # true
Example 3: Optimization Bounds
@variables x::Real y::Real
# Feasible region for optimization
feasible = Constraints([
x >= 0, y >= 0, # Non-negativity
x + y <= 10, # Resource constraint
2*x + y <= 15 # Additional constraint
])
# Is the origin feasible?
issatisfiable(x == 0 & y == 0, feasible) # true
# Is there a solution with both variables > 5?
issatisfiable(x > 5 & y > 5, feasible) # false
Advanced SAT Solving Concepts
Contradiction Detection
Detect when constraint sets are unsatisfiable:
contradictory = Constraints([x > 5, x < 3])
# Any expression is unsatisfiable with contradictory constraints
issatisfiable(true, contradictory) # false
issatisfiable(x == 4, contradictory) # false
Implication Checking
Check if one expression implies another:
@variables a::Real b::Real
constraints = Constraints([a > 0, b > 0])
# Does a > 5 imply a + b > 5?
# This is equivalent to: isprovable(a + b > 5, constraints * {a > 5})
extended = Constraints([a > 0, b > 0, a > 5])
isprovable(a + b > 5, extended) # true
Equivalence Checking
Check if two expressions are equivalent under constraints:
expr1 = x + y
expr2 = y + x
# Are they equivalent? (Check both directions)
left_to_right = isprovable(expr1 == expr2, constraints) # true
right_to_left = isprovable(expr2 == expr1, constraints) # true
Solver Limitations
Current Limitations
- Real arithmetic: Currently mapped to integer arithmetic in Z3
- Division: Limited support for division operations
- Nonlinear expressions: Complex nonlinear constraints may be slow
- Quantifiers: No direct support for universal/existential quantifiers
When Z3 Cannot Decide
Some problems are undecidable or too complex:
# Very complex nonlinear constraint
complex = Constraints([sin(x*y) + cos(x^2 + y^2) > exp(x - y)])
result = issatisfiable(x > 0, complex)
# May return `nothing` if too complex
Debugging Tips
Check Constraint Satisfiability First
Always verify your constraints have solutions:
cs = Constraints([...])
if issatisfiable(true, cs) == false
println("Warning: Constraint set is unsatisfiable!")
end
Simplify Complex Expressions
Break down complex expressions:
# Instead of one complex constraint
complex_expr = (x > 0) & (y > 0) & (x^2 + y^2 < 1) & (x + y > 0.5)
# Use multiple simpler constraints
simple_constraints = Constraints([
x > 0,
y > 0,
x^2 + y^2 < 1,
x + y > 0.5
])
Test with Known Solutions
Verify your constraint logic with known solutions:
cs = Constraints([x >= 0, y >= 0, x + y <= 1])
# Test a known feasible point
issatisfiable(x == 0.3 & y == 0.3, cs) # Should be true
# Test a known infeasible point
issatisfiable(x == 0.8 & y == 0.8, cs) # Should be false