This sort of "tracking" constraints comes up often. While there might be multiple solutions, I think the best solution is to explicitly add tracking variables, and iterate until you have all the constraints satisfied. You should use the optimizing solver for this purpose, maximizing the number of constraints that get discharged in every call. This way you'll ensure the number of solutions remains minimal.
Based on this strategy, I'd create a tracker variable pN for each constraint, and pair them up with implication. Then, I'd maximize for the number of pN that should be satisfied. Here's a possible encoding:
from z3 import *
x = Int('x')
p1, p2, p3 = Bools('p1 p2 p3')
constraints = [(p1, x < 2), (p2, x > 3), (p3, x > 5)]
solutions = []
while constraints:
o = Optimize()
s = 0
for (tracker, condition) in constraints:
o.add(Implies(tracker, condition))
s = s + If(tracker, 1, 0)
o.maximize(s)
remaining = []
r = o.check()
if r == sat:
m = o.model()
solutions.append(m[x])
print("Candidate: %s" % m[x])
for (tracker, condition) in constraints:
if(m[tracker]):
print(" Constraint: %s satisfied, skipping." % condition.sexpr())
else:
print(" Constraint: %s is not yet satisfied, adding." % condition.sexpr())
remaining.append((tracker, condition))
else:
print("Call returned: %s" % r)
exit(-1)
constraints = remaining
print("Solution: %s" % solutions)
When run, this prints:
Candidate: 6
Constraint: (< x 2) is not yet satisfied, adding.
Constraint: (> x 3) satisfied, skipping.
Constraint: (> x 5) satisfied, skipping.
Candidate: 0
Constraint: (< x 2) satisfied, skipping.
Solution: [6, 0]
So, this computes the solution [6, 0]. You originally wanted [1, 6], but I believe this is a valid solution as well.
If you want to get tighter bounds as well, you can also add conditions to the optimizing solver (via minimize/maximize calls). However, this is likely to run into problems: For instance, for your original set of constraints there's no minimum/maximum value of x that satisfy them uniquely, so you can run into situations where the optimizer will fail. If you know all your variables are bounded then you can go via that route. Otherwise, you can try calling the optimizer and if it doesn't return a fixed result, go with the result of the sat call as I did above. Of course, all of this depends on the particular constraints you have, but is otherwise irrelevant to z3.