3

this is a three part question on the use of the Python API to Z3 (Z3Py).

  1. I thought I knew the difference between a constant and a variable but apparently not. I was thinking I could declare a sort and instantiate a variable of that sort as follows:

    Node, (a1,a2,a3) = EnumSort('Node', ['a1','a2','a3'])
    n1 = Node('n1') # c.f. x = Int('x')
    

    But python throws an exception saying that you can't "call Node". The only thing that seems to work is to declare n1 a constant

    Node, (a1,a2,a3) = EnumSort('Node', ['a1','a2','a3'])
    n1 = Const('n1',Node)
    

    but I'm baffled at this since I would think that a1,a2,a3 are the constants. Perhaps n1 is a symbolic constant, but how would I declare an actual variable?

  2. How to create a constant set? I tried starting with an empty set and adding to it but that doesn't work

    Node, (a1,a2,a3) = EnumSort('Node', ['a1','a2','a3'])
    n1 = Const('n1',Node)
    nodes = EmptySet(Node)
    SetAdd(nodes, a1) #<-- want to create a set {a1}
    solve([IsMember(n1,nodes)])
    

    But this doesn't work Z3 returns no solution. On the other hand replacing the 3rd line with

    nodes = Const('nodes',SetSort(Node))
    

    is now too permissive, allowing Z3 to interpret nodes as any set of nodes that's needed to satisfy the formula. How do I create just the set {a1}?

  3. Is there an easy way to create pairs, other than having to go through the datatype declaration which seems a bit cumbersome? eg

    Edge = Datatype('Edge')
    Edge.declare('pr', ('fst', Node), ('snd',Node))
    Edge.create()
    edge1 = Edge.pr(a1,a2)
    
Motorhead
  • 928
  • 6
  • 16
  • 2
    It's always best to ask one specific question per stack-overflow post; as it keeps the discussion pointed and easier to search for. – alias Jan 12 '19 at 08:02

1 Answers1

6

Declaring Enums

Const is the right way to declare as you found out. It's a bit misleading indeed, but it is actually how all symbolic variables are created. For instance, you can say:

 a = Const('a', IntSort())

and that would be equivalent to saying

a = Int('a')

It's just that the latter looks nicer, but in fact it's merely a function z3 folks defined that sort of does what the former does. If you like that syntax, you can do the following:

NodeSort, (a1,a2,a3) = EnumSort('Node', ['a1','a2','a3'])

def Node(nm):
    return Const(nm, NodeSort)

Now you can say:

 n1 = Node ('n1')

which is what you intended I suppose.

Inserting to sets

You're on the right track; but keep in mind that the function SetAdd does not modify the set argument. It just creates a new one. So, simply give it a name and use it like this:

emptyNodes = EmptySet(Node)
myNodes = SetAdd(emptyNodes, a1)
solve([IsMember(n1,myNodes)])

Or, you can simply substitute:

mySet = SetAdd(SetAdd(EmptySet(Node), a1), a2)

which would create the set {a1, a2}.

As a rule of thumb, the API tries to be always functional, i.e., no destructive updates to existing variables, but you instead create new values out of old.

Working with pairs

That's the only way. But nothing is stopping you from defining your own functions to simplify this task, just like we did with the Node function in the first part. After all, z3py is essentially Python library and z3 folks did a lot of work to make it nicer, but you also have the entire power of Python to simplify your life. In fact, many other interfaces to z3 from other languages (Scala, Haskell, O'Caml etc.) precisely do that to provide a much easier to work with API using the features of their respective host languages.

alias
  • 28,120
  • 2
  • 23
  • 40