This is quite similar to a previous stack-overflow question: Using SMT-LIB to count the number of modules using a formula
Bottom line: If there is more than one variable involved, then this problem is really hard to solve in general; unless you use specific knowledge of the problem. If there's precisely one variable, then you can indeed use optimization and other tricks to give you "ranges", but in general such an algorithm will not necessarily be performant. (Though in practice it can handle most simple things easily.)
If you do not care about discontinuities and only want to find out max/min, you can use z3's optimizer. Note that this is not part of standard SMTLib but rather a z3 extension. Here's an example:
(declare-const x Int)
(declare-const y Int)
(declare-const z Int)
(assert (= (+ x y) 10))
(assert (and (>= y 20) (>= x -20)))
(assert (distinct x (- 15)))
(assert (distinct y 25))
(push) (minimize y) (check-sat) (get-objectives) (pop)
(push) (maximize y) (check-sat) (get-objectives) (pop)
(push) (minimize x) (check-sat) (get-objectives) (pop)
(push) (maximize x) (check-sat) (get-objectives) (pop)
This prints:
sat
(objectives
(y 20)
)
sat
(objectives
(y 30)
)
sat
(objectives
(x (- 20))
)
sat
(objectives
(x (- 10))
)
So, you can get the bounds but notice how the requirements I added that x != -15
and y != 25
are simply outside the ranges you get.
Note that if your type is unbounded (such as Int
) you can also get +/- infinity as a bound:
(declare-const x Int)
(assert (< x 10))
(push) (minimize x) (check-sat) (get-objectives) (pop)
(push) (maximize x) (check-sat) (get-objectives) (pop)
This prints:
sat
(objectives
(x (* (- 1) oo))
)
sat
(objectives
(x 9)
)
Optimization is a hard problem in general, and the optimizing solver in z3 is definitely slower compared to the regular one. But it might just do the trick for your problem domain!