1

It looks like z3 expression has a hash() method but not __hash__(). Is there a reason why not using __hash__() ? This allows the expression to be hashable.

Leonardo de Moura
  • 21,065
  • 2
  • 47
  • 53
Vu Nguyen
  • 987
  • 1
  • 9
  • 20

1 Answers1

2

There is no reason for not calling it __hash__(). I called it hash() because I'm new to Python. I will add __hash__() in the next release (Z3 4.2).

EDIT: as pointed out in the comments, we also need __eq__ or __cmp__ to be able to use a Z3 object as a key in a Python dictionary. Unfortunately, the __eq__ method (defined at ExprRef) is used to build Z3 expressions. That is, if a and b are referencing Z3 expressions, then a == b returns the Z3 expression object representing the expression a = b. This "feature" is convenient for writing Z3 examples in Python, but it has a nasty side-effect: the Python dictionary class will assume that all Z3 expressions are equal to each other. Actually, it is even worse, since the Python dictionary only invokes the method __eq__ for objects that have the same hashcode. Thus, if we define __hash__() we may have the illusion that is safe to use Z3 expression objects as keys in Python dictionaries. For this reason, I will not include __hash__() in the class AstRef. Users that want to use Z3 expressions as keys in dictionaries may use the following trick:

from z3 import *

class AstRefKey:
    def __init__(self, n):
        self.n = n
    def __hash__(self):
        return self.n.hash()
    def __eq__(self, other):
        return self.n.eq(other.n)

def askey(n):
    assert isinstance(n, AstRef)
    return AstRefKey(n)


x = Int('x')
y = Int('y')
d = {}
d[askey(x)] = 10
d[askey(y)] = 20
d[askey(x + y)] = 30
print d[askey(x)]
print d[askey(y)]
print d[askey(x + y)]
n = simplify(x + 1 + y - 1)
print d[askey(n)]
Leonardo de Moura
  • 21,065
  • 2
  • 47
  • 53
  • Great, thanks -- I think __cmp__ and __eq__ are also required to hash an immutable object. See http://docs.python.org/glossary.html#term-hashable – Vu Nguyen Sep 18 '12 at 14:25
  • The `__eq__` method is problematic. I defined it in the class `ExprRef`. If `a` and `b` are Z3 expressions, then the Python code fragment `a == b` returns a Z3 expressions that represents `a = b`. The Python code `a == b` is essentially syntax sugar for `a.__eq__(b)`, right? If the Python hashtable class invokes `__eq__`, it will not get the expected results. – Leonardo de Moura Sep 18 '12 at 14:33
  • I don't know how Python hastable class uses __eq__ internally. But I think what you have, a.__eq__(b) returns a Z3 expression a == b, is just fine. The Sage software (sagehmath.org) does the same: if a,b are (Sage) expressions then a==b invokes a.__eq__(b) which returns an expression representing a = b. And the Sage expressions are hashable. In short, I think you just need to add in a function __hash__() that is exactly like your current hash() implementation. As long as an object instance is never changed throughout its life, then its hash value is valid. – Vu Nguyen Sep 18 '12 at 15:12
  • The problem is that two different Z3 expressions may have the same hash code. The code will misbehave in this case. That is, suppose `a` and `b` are different Z3 expressions, but they have the same hashcode. Assume also `d` is a Python dictionary, and we executed `d[a] = 10`. Then, if we execute `d[b] = 20`. The entry `a -> 10` will be overwritten in the dictionary `d`. – Leonardo de Moura Sep 18 '12 at 15:19
  • BTW, I will take a look at Sage, and check how they deal with this issue. – Leonardo de Moura Sep 18 '12 at 15:20
  • Actually, I don't think Python dict will use __eq__ to check if objects are equal for hashing them. I believe it uses the __hash__ functions to determine this. Consider this example at http://rise4fun.com/Z3Py/79a . EDIT: I just saw your comment on 2 different Z3 expressions may have the same hash code. I am wondering how you compute the hash code for these expressions ? If 2 expressions having same hash code then doe that mean those expressions are semantically equivalent ? like a < b having the same hash code as b > a ? – Vu Nguyen Sep 18 '12 at 15:32
  • Actually, I think the examples at rise4fun.com/Z3Py/79a demonstrates the problem I'm described above. The dictionary (at the last statement) contains `[('hi', None), ('hello', None)]` instead of `[('hi', None), ('hello', None), ('world', None)]` – Leonardo de Moura Sep 18 '12 at 15:48
  • Regarding hash code computation in Z3, it is just based on the structure of the expression. The only thing we should assume is that if `a` and `b` are syntactically identical, then `a.hash() == b.hash()`. – Leonardo de Moura Sep 18 '12 at 15:51
  • Maybe tangential, but here's how the Z3 Haskell bindings in SBV deal with this: If two objects hash equal (called StableNames in Haskell), then they are guaranteed equal. If they *do not* hash equal, then we don't know; we keep a separate data-structure to do an extra check. This scheme overcomes issues regarding garbage collection and movement of objects in the heap, and the type-system keeps everything sane by putting it in the IO monad. I'm not sure this extends to Python in any way. Details: http://hackage.haskell.org/packages/archive/base/4.6.0.0/doc/html/System-Mem-StableName.html – alias Sep 18 '12 at 18:02