Z3 的数据类型:式、类型与函数声明的关系#
本章的内容#
Z3 的对象按 Python 的类整理得很清楚。理清这层关系之后,查阅 Z3 的 API 时就不必猜测某个对象属于哪一类。这些类名决定了对象能调用哪些方法。
以
AstRef为顶点,其下有SortRef即类型、FuncDeclRef即函数声明、ExprRef即式变量与式都是
ExprRef,x + y与x > y属于同一族用
sort()取类型,用decl()取函数声明从模型
ModelRef取值时,类型会随之变化
本章要看的是前面各章直接使用过的 Int、Bool、model[x] 的内部。
import z3
from z3 import *
Z3 的类关系#
└──Z3PPObject
├──AstRef
│ ├──SortRef
│ │ ├──BoolSortRef
│ │ ├──ArithSortRef
│ │ ├──...
│ │
│ ├──FuncDeclRef
│ └──ExprRef
│ ├──BoolRef
│ │ └──QuantifierRef
│ ├──ArithRef
│ │ ├──IntNumRef
│ │ ├──...
│ ├──...
├──ModelRef
├──Solver
└──...
AstRef 即抽象语法树的节点,它下面有三类子类。
类 |
表示的内容 |
例子 |
|---|---|---|
|
类型,即 sort |
|
|
函数与运算符的声明 |
|
|
式 |
|
三类都挂在同一个根下,是因为 Z3 内部的一切都是抽象语法树的节点,类型、声明与式都可以用同一套方式遍历与打印。
Int('x') 与 x > y 都是 ExprRef 的实例,下面查看它们的类型。
x, y = Ints('x y')
expr = x > y
print(f'type(x) = {type(x).__name__}')
print(f'type(expr) = {type(expr).__name__}')
type(x) = ArithRef
type(expr) = BoolRef
x 是 ArithRef,即算术式,expr 是 BoolRef,即真假式,两者都是 ExprRef 的子类。
ExprRef:看式的结构#
式是树结构,因此可以查看子节点。
num_args():参数的个数arg(i):第 i 个参数
于是查看式的结构只需要这两个方法,不必关心它是用哪种运算符构造出来的。
print(f'{x.num_args() = }')
print(f'{expr.num_args() = }')
x.num_args() = 0
expr.num_args() = 2
变量 x 没有参数,因此 num_args() 是 0,而 x > y 以 x 与 y 两个为参数,因此 num_args() 是 2。
print(f'{expr.arg(0) = }')
print(f'{expr.arg(1) = }')
expr.arg(0) = x
expr.arg(1) = y
expr = x > y 的参数就是 x 与 y。能把式拆开查看内部,正是 ExprRef 的特点。
SortRef:式的类型#
sort() 返回式的类型,即 SortRef。
x_sort = x.sort()
expr_sort = expr.sort()
print(f'{x_sort = }')
print(f'{expr_sort = }')
print(f'type(x_sort) = {type(x_sort).__name__}')
print(f'type(expr_sort) = {type(expr_sort).__name__}')
x_sort = Int
expr_sort = Bool
type(x_sort) = ArithSortRef
type(expr_sort) = BoolSortRef
x 是整数,因此类型是 Int,类型对象的类是 ArithSortRef;expr = x > y 是真假值,因此类型是 Bool,类是 BoolSortRef。
FuncDeclRef:函数声明#
decl() 返回式的函数声明,即 FuncDeclRef。在 Z3 的内部,变量与运算符都当作函数处理。因此常量的声明与运算符的声明是同一种对象,区别只在参数的个数。
x_decl = x.decl()
expr_decl = expr.decl()
print(f'{x_decl = }')
print(f'{expr_decl = }')
print(f'type(x_decl) = {type(x_decl).__name__}')
print(f'type(expr_decl) = {type(expr_decl).__name__}')
x_decl = x
expr_decl = >
type(x_decl) = FuncDeclRef
type(expr_decl) = FuncDeclRef
x是没有参数的函数x(),因此声明是FuncDeclRefexpr = x > y中的>也是函数>,因此声明是FuncDeclRef
从 FuncDeclRef 可以取出函数的各种信息。
方法 |
返回的内容 |
|---|---|
|
函数的名字 |
|
参数的个数 |
|
返回值的类型,即 |
|
第 i 个参数的类型,即 |
|
Z3 内部的种类,以整数编号表示 |
|
S 表达式,即 Lisp 风格的文本表示 |
name()、arity() 这类信息都来自声明,而不是某一次求值的结果。
print(f'{x_decl.name() = }')
print(f'{x_decl.arity() = }')
print(f'{x_decl.range() = }')
print(f'{expr_decl.name() = }')
print(f'{expr_decl.arity() = }')
print(f'{expr_decl.range() = }')
print(f'{expr_decl.domain(0) = }')
print(f'{expr_decl.domain(1) = }')
x_decl.name() = 'x'
x_decl.arity() = 0
x_decl.range() = Int
expr_decl.name() = '>'
expr_decl.arity() = 2
expr_decl.range() = Bool
expr_decl.domain(0) = Int
expr_decl.domain(1) = Int
变量 x 的名字是 x,参数 0 个,返回值是 Int;> 的名字是 >,参数 2 个,返回值是 Bool,两个参数都是 Int。
kind() 返回 Z3 内部的标识符,是一个整数,与 z3 模块中的常数比较就能看出含义。
print(f'{x_decl.kind() = } # 用户定义的名称')
print(f'{expr_decl.kind() = } # 比较运算符 >')
print(f'{z3.Z3_OP_UNINTERPRETED = }')
print(f'{z3.Z3_OP_GT = }')
x_decl.kind() = 49167 # 用户定义的名称
expr_decl.kind() = 517 # 比较运算符 >
z3.Z3_OP_UNINTERPRETED = 49167
z3.Z3_OP_GT = 517
x_decl.kind() 与 z3.Z3_OP_UNINTERPRETED 相同,因此 x 是用户定义的、不被解释的符号。expr_decl.kind() 是 z3.Z3_OP_GT,即大于运算符。kind() 返回的是整数编号,比较时要拿模块里的常数,不要直接写数字。
用 sexpr() 可以确认 Z3 内部持有的声明本身。
print(f'{x_decl.sexpr() = }')
print(f'{expr_decl.sexpr() = }')
x_decl.sexpr() = '(declare-fun x () Int)'
expr_decl.sexpr() = '(declare-fun > (Int Int) Bool)'
可以看出,x 被声明为 (declare-fun x () Int),> 被声明为 (declare-fun > (Int Int) Bool)。
画成关系图#
把上面用到的对应关系画成图,箭头标出哪一个方法返回哪一个对象。
graph
x["Int('x')\nArithRef"]
y["Int('y')\nArithRef"]
expr["x > y\nBoolRef"]
x_decl["x 的声明\nFuncDeclRef"]
expr_decl["> 的声明\nFuncDeclRef"]
int_sort["Int\nArithSortRef"]
bool_sort["Bool\nBoolSortRef"]
x --sort()--> int_sort
y --sort()--> int_sort
expr --sort()--> bool_sort
x --decl()--> x_decl
expr --decl()--> expr_decl
expr --arg(0)--> x
expr --arg(1)--> y
x_decl --range()--> int_sort
expr_decl --range()--> bool_sort
expr_decl --domain(0)--> int_sort
expr_decl --domain(1)--> int_sort
ModelRef:从模型取值#
Solver.model() 返回的是 ModelRef。ModelRef 的 [] 会因传入的内容不同而给出不同的结果。
x, y, z = Ints('x y z')
solver = Solver()
solver.add(x > y, y > z, x > 0, z < 5)
solver.check()
m = solver.model()
print(f'type(m) = {type(m).__name__}')
print(f'{m[x] = } type = {type(m[x]).__name__}')
print(f'{m[0] = } type = {type(m[0]).__name__}')
type(m) = ModelRef
m[x] = 1 type = IntNumRef
m[0] = z type = FuncDeclRef
传入变量
m[x],得到该变量的值,类型是IntNumRef传入整数
m[0],得到第 0 个变量的声明,类型是FuncDeclRef
IntNumRef 是 Z3 的整数值,但不是 Python 的 int,要参与计算必须用 as_long() 转换。转换是单向的:as_long() 得到的数已经与模型无关。
print(f'{m[x].as_long() = }')
print(f'type(m[x].as_long()) = {type(m[x].as_long()).__name__}')
# 传入表达式时,返回该表达式的值(BoolRef)
print(f'{m.eval(x > y) = } type = {type(m.eval(x > y)).__name__}')
m[x].as_long() = 1
type(m[x].as_long()) = int
m.eval(x > y) = True type = BoolRef
用 as_long() 就得到 Python 的整数,而把式传给 m.eval() 之后,返回的是该式的求值结果,这里是 x > y 的真假。
IntNumRef 的 decl() 得到的是表示 Int 这一值种类的声明,而不是变量的声明,与 x.decl() 即变量 x 的声明并不是一回事。
print(f'{m[x].decl().name() = }')
print(f'{x.decl().name() = }')
print(m[x].decl().eq(x.decl()))
m[x].decl().name() = 'Int'
x.decl().name() = 'x'
False
m[x].decl() 的名字是 'Int',x.decl() 的名字是 'x'。同样是 decl(),作用的对象不同,含义也就随之不同。
验证继承关系#
用 type(obj).mro() 可以确认这个类继承了哪些类。
print(type(x).mro())
print(type(x.decl()).mro())
print(type(m).mro())
[<class 'z3.z3.ArithRef'>, <class 'z3.z3.ExprRef'>, <class 'z3.z3.AstRef'>, <class 'z3.z3.Z3PPObject'>, <class 'object'>]
[<class 'z3.z3.FuncDeclRef'>, <class 'z3.z3.AstRef'>, <class 'z3.z3.Z3PPObject'>, <class 'object'>]
[<class 'z3.z3.ModelRef'>, <class 'z3.z3.Z3PPObject'>, <class 'object'>]
ArithRef 依次继承 ExprRef、AstRef、Z3PPObject,而 FuncDeclRef 直接位于 AstRef 之下,与类图一致。mro() 给出的顺序就是 Python 查找属性时实际走的顺序。
下面把 AstRef 的子类实际列出来。
print([cls.__name__ for cls in z3.AstRef.__subclasses__()])
def print_tree(cls, indent=0, depth=2):
print(" " * indent + cls.__name__)
if depth == 0:
return
for sub in cls.__subclasses__():
print_tree(sub, indent + 1, depth - 1)
print()
print_tree(z3.SortRef, depth=1)
['SortRef', 'FuncDeclRef', 'ExprRef']
SortRef
TypeVarRef
BoolSortRef
ArithSortRef
BitVecSortRef
ArraySortRef
FiniteSetSortRef
DatatypeSortRef
FiniteDomainSortRef
FPSortRef
FPRMSortRef
SeqSortRef
CharSortRef
ReSortRef
可以看到 SortRef 之下排着 BoolSortRef、ArithSortRef、BitVecSortRef 等按类型划分的类,而前面各章用过的 BitVecSort、RealSort、IntSort 都属于 SortRef 这一族。
小结#
想知道的内容 |
使用的写法 |
返回的类型 |
|---|---|---|
变量与式的类型 |
|
|
式子子节点的个数 |
|
|
式的第 i 个子节点 |
|
|
函数声明 |
|
|
函数的名字、参数与类型 |
|
字符串、数、 |
模型中的值 |
|
|
转成 Python 的整数 |
|
|
Int、Bool与x > y都是ExprRef,即式类型是
SortRef,函数声明是FuncDeclRef,式是ExprRef,三者都属于AstRef一族model[0]取的是声明,model[x]取的是值,同样是[],含义并不相同