简介
z3是微软最新的可满足模理论(SMT)求解器。它可以使用一个或者多个理论来检查逻辑公式的可满足性。(以下脚本不是python脚本)
https://github.com/Z3prover/z3.git
z3 script
基础命令
使用echo
回显信息
1 | (echo "starting z3...") |
声明指定类型的常量,这里声明了Int类型的a
1 | (declare-const a Int) |
声明函数,这里声明了一个接收整数和布尔变量并且返回整数的函数
1 | (declare-fun f (Int Bool) Int) |
assert
可以添加公式到z3的内部栈中。假如有一个令所有声明公式都为真的解,那么我们成这些在z3栈中的公式是具有可满足性的。
1 | (declare-const a Int) |
第一个声明公式表明a > 10
。第二个声明公式表示函数f
必须接收a
和true
作为传入参数,并且返回值要小于100
。check-sat
会说明当前在z3栈中的公式是否具有可满足性,假如具有会返回sat
,否则返回unsat
,有时候也会返回unknown
,这说明z3不能确定公式是否是可满足的。
get-model
能够获取一个解,上述脚本的返回值如下
1 | sat |
这个模型的意思是,a的值是11,对于f,传入参数有两个变量,一个是x!1
为整数,另一个是x!2
为布尔值。ite
为if-else语句,当x!1
等于11并且x!2
为真时返回前一个0,否则返回后一个0。
使用视图
我们可以使用push
和pop
来将全局栈重点声明和断言传入当前栈中。push和pop是相匹配的。
1 | (declare-const x Int) |
返回值
1 | sat |
配置
set-option
被用来配置z3。有一些选项只能在一开始被设置。我们可以使用reset
命令去消除所有的声明和断言。在使用reset
之后所有的配置选项都能被设置了。
1 | (set-option :print-success true) |
选项print-success
通常在z3被另一个使用pipe的应用程序控制时特别有用。在这个模式下,那些不输出任何值的命令将打印success
其他命令
display t
可以打印表达式。simplify t
会打印一个可能是与t
等价但是更简单的表达式。help simplify
可以看到所有的可用选项。
1 | (declare-const a (Array Int Int)) |
sort命令定义了一个新的排序符号,该符号是排序表达式的缩写。可以参数化新的排序符号,在这种情况下,参数的名称在命令中指定,排序表达式使用排序参数。命令的形式是这样的。(sort:the arrangement of data in a prescribed sequence)
1 | (define-sort [symbol] ([symbol]+) [sort]) |
1 | (define-sort Set (T) (Array T Bool)) |
运算
z3有对整数和实数的内建支持。这两种类型与机器整数(32位或64位)和浮点数不同。这两种类型(sorts)表示数学整数和实数。declare-const
用于声明整型和实数常量。
1 | (declare-const a Int) |
在声明常量之后,用户可以使用断言,包含这些常数的smt公式。公式包含算术运算符 。check-sat
将找到一个解。该解可以通过get-model
来获得。
1 | (declare-const a Int) |
实数常量需要包含一个小数点。和大多数的编程语言不同,z3不会自动的将整数转化为实数,反之亦然。to-real
可以用于将整数表达式转换为实数。
1 | (declare-const a Int) |
非线性运算
1 | (declare-const a Int) |
除法
z3也支持除法、整数除法、模和余数运算符。在内部,它们都会映射到乘法。
1 | (declare-const a Int) |
在z3中,允许0作为除数,但是该运算没有特殊的结果。除法不是一个部分函数。实际上,z3中所有的函数都是总和,尽管在某些情况下,比如除0,结果可能没有被指定。rem与mod的区别在负数的时候体现。
Bitvectors
现代cpu和主流的编程语言会在固定大小的向量位上使用算术。
z3支持任意大小的位向量。
z3支持任意大小的位向量。(_ BitVec n)是长度为n的一种位向量。位向量文字可以使用二进制、十进制和十六进制表示法来定义。在二进制和十六进制情况下,位向量的大小是从字符数推断出来的。例如,二进制格式的位向量文字#b010是一个大小为3的位向量,而十六进制格式的位向量文字#x0a0是一个大小为12的位向量。对于十进制格式的位向量文字,必须指定其大小。例如,(_ bv10 32)是一个大小为32的位向量,它表示数字10。默认情况下,如果位向量大小是4的倍数,z3以十六进制格式显示位向量,否则以二进制格式显示。命令(设置选项:pp。可用于强制z3以十进制格式显示位向量字面值。
1 | (display #b0100) |
基础Bitvector运算
1 | (simplify (bvadd #x07 #x03)) ; addition |
Bitwise Operations(位操作)
1 | (simplify (bvor #x6 #x3)) ; bitwise or |
有一种快速的方法可以检验固定大小的数是否为2的幂。当且仅当x&(x-1)为0时,位向量x是2的幂或0,其中&表示位与。
Bitvectors的比较
1 | (simplify (bvule #x0a #xf0)) ; unsigned less or equal |
有符号比较,例如bvsle,将位向量的符号位考虑到比较中,而无符号比较将位向量视为无符号比较(将位向量视为自然数)。
Arrays
作为数学计算理论程序的一部分,McCarthy提出了阵列的基本理论,其特征是选择-存储公理。表达式select a i
返回在a
中i
这个位置上的值,store a i v
返回一个在i
的位置上值为v
的新array
让我们先检查数组的一个基本属性。假设a1
是一个整数数组,那么约束(and (= (select a1 x) x) (= (store a1 x y) a1))
是可满足的,因为当x=y
时,第一个等式迫使位置x
上的值为x
。
1 | (declare-const x Int) |
常量数组,可以在z3中使用const构造所有位置上均为同一个值的数组。下面的示例定义了一个仅包含1的常量数组。
1 | (declare-const all1 (Array Int Int)) |
model如下
1 | sat |
模型提供了可满足公式中出现的自由常数和函数的解释。数组的解释与函数的解释非常相似。z3使用结构(_ as-array f)对arrays进行解释。假如数组a等于(_ as-array f),那么对于每个索引i,(select a i)都等于(f i)。对数组常量all1进行解释。
数组上的映射函数
下面,我们将使用z3中的数组理论扩展来模拟基本的布尔代数(集合理论)。z3提供了数组上的参数化映射函数。它允许在数组范围内应用任意函数。下面的示例演示如何使用map函数。
1 | (define-sort Set (T) (Array T Bool)) |
python
示例
1 | from z3 import * |
让我们从下面这个简单的例子开始
1 | x = Int('x') |
函数Int('x')
创建了一个整型变量x。solve
函数用来求解这些限制。
1 | x = Int('x') |
simplify
用来化简公式
1 | x = Int('x') |
html_mode为假时将不会用html语言的形式来呈现结果,这也是默认模式。
Z3提供了遍历表达式的函数。
1 | x = Int('x') |
1 | num args: 2 |
Z3Py可以解非线性约束。
1 | x = Real('x') |
可以使用set_option(precision=30)
来决定精度
1 | x = Real('x') |
1 | [x = 1.2599210498?, y = -1.1885280594?] |
输出中的?表示数字被截断
可以使用set_option(rational_to_decimal=True)
来设置输出的格式
可以使用布尔逻辑运算,z3支持与(and),或(or),非(not),蕴含(implies),if(if-then-else),与等于
a implies b 就是如果a,那么b,即if a == True, then b == True
1 | p = Bool('p') |
Solvers
该类可以使用原始的z3方法,例如前文提到的check, push, pop
1 | x = Int('x') |
下面的例子展示了检视模型使用基本方法
1 | x, y, z = Reals('x y z') |
1 | sat |
在上面的例子中,Reals('x y z')
同时创建了变量x
,y
,z
m[x]
返回模型m
中x
的值。
算术运算
z3支持实数与整数变量。他们能够在同一个问题中混用。和大多数编程语言一样,z3能够在需要的时候将整数表达式自动转成实数。下面的例子证明了声明整数和实数变量的不同方式。
1 | x = Real('x') |
ToReal
方法可以将整数表达式编程实数表达式。z3Py支持所有的基础算术运算符。
例如:
1 | a, b, c = Ints('a b c') |
simplify
可以化简表达式
1 | x, y = Reals('x y') |
化简式有两种展示风格。z3内部的选项名称用-
分割各个单词。这些选项也能够在z3Py中使用。z3Py也支持Python风格的选项名称。这种风格里使用_
来代替-
,下面的例子展示这两种不同的风格。
1 | x, y = Reals('x y') |
大数运算
Z3Py支持大数运算。下面的例子说明了如何使用大整数、有理数、无理数进行基本算术运算。Z3Py只支持代数无理数运算。它将会用小数形式去展示无理数。内建表达可以用sexpr()
方法去提取。它将会展示Lisp风格的表达式。
1 | x, y = Reals('x y') |
1 | [y = 20000000000000001, x = -9999979999999999999999] |
机器运算
现代CPU和主流编程语言使用定长比特向量来进行算术运算。机器运算在Z3Py中可以以Bit-Vetors的方式呈现。他们实现了无符号和有符号二补算术的精确语义。下面的示例演示展示了如何创建向量变量和常量。函数 BitVec('x', 16)
创建了一个名字为x
的16位的位向量 变量。也可以创建常量,BitVecVal(10,32)
创建一个大小为32的位向量,值为10
1 | x = BitVec('x', 16) |
1 | x + 2 |
在C、C++、C#、Java中,作为数字的有符号位向量和无符号位向量之间没有区别。相反,z3提供了特殊的有符号版本的算术运算,它决定位向量是被有符号的方式处理还是被无符号的方式处理。在Z3Py中,运算符 <, <=, >, >=, /, %,>>对应于带符号的版本。对应的无符号算子是ULT、ULE、UGT、UGE、UDiv、URem、LShR
1 | # Create to bit-vectors of size 32 |
函数
不像编程语言,有副作用(side-effects)会抛出异常或者从不返回,z3中的函数没有副作用。它们定义了所有的输入值。这包括函数,比如除法。z3基于第一顺序的逻辑。
对于约束x+y>3
我们说x、y
是变量。在很多教材上,x、y
被称作未解释的常数。也就是这个约束允许对于任意解释x+y>3
恒成立。
更准确的说,函数和在第一顺序逻辑的常量符号是未解释的或自由的,也就是说没有一个被附加的先验解释。这与属于理论签名的函数相反,比如算术,函数+
有一个固定的标准解释(它是两个数字的相加)。未解释函数和常数具有最大的灵活性,它们允许任何与函数或常数上的约束一致的解释。
为了说明未解释的函数和常量,让我们使用未解释的整型常量(也就是变量)x,y,最后让f
是一个未解释的函数,它接收一个类型为整型(也就是sort)的参数并得到 一个整型值。这个例子说明了如何解释f
对x
应用一次与x
不同。
1 | x = Int('x') |
这个解读作读作f(0)=1,f(1)=0
,f(a)
在0和1之外都是1。
在z3中,我们也可以计算模型中约束表达式。下面的例子中演示了如何使用evaluate
方法。
1 | x = Int('x') |
可满足性和有效性
如果公式/约束F总是对未解释的符号赋值为真,则该公式/约束F是有效的。一个公式/约束F是可满足的,如果有适当的赋值给它的未解释的符号,在这个符号下F的值为真。有效性是指找到一个陈述的证据;可满足性是指找到一组约束的解决方案。考虑一个包含a和b公式F。我们可以问F是否有效,是否总是这样任意组合为a和b的值。如果F总是正确的,那么不是(F)永远是假的,然后不是(F)不会有任何令人满意的作业(例如,解决方案);也就是说,Not(F)是不能满足的。也就是说,当非(F)是不可满足的(是不可满足的),F是有效的。或者,当且仅当非(F)是无效的(is invalid)时,F是可满足的。下面的例子证明了deMorgan定律。
下面的示例重新定义了Z3Py函数,证明它接收一个公式作为参数。此函数创建一个求解器,添加/断言公式的否定,并检查否定是否不可满足。这个函数的实现是Z3Py命令prove的一个更简单的版本。
1 | p, q = Bools('p q') |
List comprehension
Python支持
1 | # Create list [1, ..., 5] |
在上面的示例中,表达式"x%s"% i返回一个字符串,其中%s被替换为i的值。
命令pp与print类似,但是它对列表和元组使用Z3Py的格式化器,而不是Python的格式化器。
Z3Py还提供了创建布尔值、整数和实变量向量的函数。这些函数是使用列表理解实现的。
1 | X = IntVector('x', 5) |
运动学方程
在高中,学生学习运动学方程。这些方程描述了位移(d)、时间(t)、加速度(a)、初速度(v_i)和末速度(v_f)之间的数学关系。用Z3Py表示,我们可以把这些方程写成:
1 | a, t ,v_i, v_f = Reals('a t v__i v__f') #missing in HTML? |
问题1
Ima Hurryin正接近一个红绿灯,以每秒30.0米的速度行驶。灯变成黄色,Ima开始刹车并打滑停了下来。如果Ima的加速度是-8.00 m/s2,则确定汽车在打滑过程中的位移。
1 | d, a, t, v_i, v_f = Reals('d a t v__i v__f') |
问题2 Ben Rushin正在等红灯。当它最终变成绿色时,本从静止开始以6.00米/s2的速度加速,持续了4.10秒。确定本的车在这段时间内的位移。
1 | d, a, t, v_i, v_f = Reals('d a t v__i v__f') |
Bit Tricks
一些底层的hacks在C程序员中非常流行。我们在z3的实现中使用了其中的一些技巧。
二的阶乘
这个hack经常在C程序中使用(包括z3)来测试一个机器整数是否为2的幂。我们可以用z3来证明它是有效的。当且仅当x是2的幂时,x!=0&&!(x&(x-1))
是正确的。
1 | x = BitVec('x', 32) |
相反数
下面的一个简单hack可以被用来检测两个机器整数是否是相反数。
1 | x = BitVec('x', 32) |
Puzzles
狗、猫和老鼠(鸡兔同笼问题)
思考下面的智力游戏(Puzzle)。花100美元买100个动物。狗要花15美元,猫要花1美元,老鼠要花25美分。你每种至少要买一个。你需要买多少?
1 | # Create 3 integer variables |
数独
下面的例子将数度问题在z3中进行编码。不同的数独示例能够通过修改矩阵示例的方式解决。这个例子使用了很多list comprehensions。
1 | # 9x9 matrix of integer variables |
这里的If是lisp风格的写法
八皇后问题
八皇后难题是将8个国际象棋皇后放在8x8的棋盘上,这样就不会有两个皇后互相攻击。因此,解决方案要求两个皇后不能共享同一行、列或对角线。
1 | # We know each queen must be in a different row. |
应用:安装问题
安装问题包括确定是否可以在系统中安装一组新的软件包。这个应用程序时基于OPIUM:最优的包安装/卸载管理器。许多包依赖于其他包来提供一些功能。每个发行版都包含一个元数据文件,解释发行版的每个包的需求。元数据包含诸如名称、版本等细节。更重要的是,它包含了依赖和冲突条款,规定了哪些包应该安装在系统上。
使用z3可以很容易解决安装问题。它的思想是为每个包定义一个布尔变量。如果包必须在系统中,则此变量为真。如果a包依赖于b、c和z包,我们按照如下的方式写:
1 | a, b, c, d, e, f, g, z = Bools('a b c d e f g z') |
1 | def DependsOn(pack, deps): |
1 | def install_check(*problem): |
对于同余方程
https://www.dcode.fr/modular-equation-solver
https://github.com/55-AA/mod_equations
参考资料
https://github.com/Z3Prover/z3/wiki/Documentation
https://rise4fun.com/z3/tutorial/guide
https://github.com/Z3Prover/z3/tree/master/examples/python/tutorial/jupyter