When you write something like:
X, Y = Const('X Y', Hum)
It does not mean that you are declaring two constants named X
and Y
of sort Hum
. (Yes, this is indeed confusing! Especially if you're coming from a Prolog like background!)
Instead, all it means is that you are saying there are two objects X
and Y
, which belong to the sort Hum
. It does not even mean X
and Y
are different. They might very well be the same, unless you explicitly state it, like this:
s.assert(z3.Distinct([X, Y]))
This might also explain your confusion regarding constants and variables. In your model, everything is a variable; you haven't declared any constants at all.
Your question about how come whi
is not Samu
is a little trickier to explain, but it stems from the fact that all you have are variables and no constants at all. Furthermore, whi
when used as a quantified variable will never have a value in the model: If you want a value for a variable, it has to be a top-level declared variable with its own assertions. This usually trips people who are new to z3py: When you do quantification over a variable, the top-level declaration is a mere trick just to get a name in the scope, it does not actually relate to the quantified variable. If you find this to be confusing, you're not alone: It's a "hack" that perhaps ended up being more confusing than helpful to newcomers. If you're interested, this is explained in detail here: https://theory.stanford.edu/~nikolaj/programmingz3.html#sec-quantifiers-and-lambda-binding But I'd recommend just taking it on faith that the bound variable whi
and what you declared at the top level as whi
are just two different variables. Once you get more familiar with how z3py works, you can look into the details and reasons behind this hack.
Coming back to your modeling question: You really want these constants to be present in your model. In particular, you want to say these are the humans in my universe and nobody else, and they are all distinct. (Kind of like Prolog's closed world assumption.) This sort of thing is done with a so-called enumeration sort in z3py. Here's how I would go about modeling your problem:
from z3 import *
# Declare an enumerated sort. In this declaration we create 'Human' to be a sort with
# only the elements as we list them below. They are guaranteed to be distinct, and further
# any element of this sort is guaranteed to be equal to one of these.
Human, (Rob, Kev, Sama, Tho, Dor, Jim, Bor, Eli, Samu, Zel, Max) \
= EnumSort('Human', ('Rob', 'Kev', 'Sama', 'Tho', 'Dor', 'Jim', 'Bor', 'Eli', 'Samu', 'Zel', 'Max'))
# Uninterpreted functions for parent/grandParent relationship.
parent = Function('parent', Human, Human, BoolSort())
grandParent = Function('grandParent', Human, Human, BoolSort())
s = Solver()
# An axiom about the parent and grandParent functions. Note that the variables
# x, y, and z are merely for the quantification reasons. They don't "live" in the
# same space when you see them at the top level or within a ForAll/Exists call.
x, y, z = Consts('x y z', Human)
s.add(ForAll([x, y, z], Implies(And(parent(x, z), parent(z, y)), grandParent(x, y))))
# Express known parenting facts. Note that unlike Prolog, we have to tell z3 that
# these are the only pairs of "parent"s available.
parents = [ (Rob, Kev), (Rob, Sama), (Sama, Tho), (Dor, Jim) \
, (Bor, Jim), (Bor, Eli), (Jim, Tho), (Sama, Samu) \
, (Jim, Samu), (Zel, Max), (Samu, Max) \
]
s.add(ForAll([x, y], Implies(parent(x, y), Or([And(x==i, y == j) for (i, j) in parents]))))
# Find what makes Rob-Max belong to the grandParent relationship:
witness = Const('witness', Human)
s.add(grandParent(Rob, Max))
s.add(grandParent(Rob, witness))
s.add(parent(witness, Max))
# Let's see what witness we have:
print s.check()
m = s.model()
print m[witness]
For this, z3 says:
sat
Samu
which I believe is what you were trying to achieve.
Note that the Horn-logic of z3 can express such problems in a nicer way. For that see here: https://rise4fun.com/Z3/tutorialcontent/fixedpoints. It's an extension that z3 supports which isn't available in SMT solvers, making it more suitable for relational programming tasks.
Having said that, while it is indeed possible to express these sorts of relationships using an SMT solver, such problems are really not what SMT solvers are designed for. They are much more suitable for quantifier-free fragments of logics that involve arithmetic, bit-vectors, arrays, uninterpreted-functions, floating-point numbers, etc. It's always fun to try these sorts of problems as a learning exercise, but if this sort of problem is what you really care about, you should really stick to Prolog and its variants which are much more suited for this kind of modeling.