Z3Py教程(翻譯)

原文:https://ericpony.github.io/z3py-tutorial/guide-examples.htm

練習:http://3xp10it.cc/auxilary/2017/11/14/z3-solver%E5%AD%A6%E4%B9%A0/

注:該翻譯用於自嗨


Python中使用Z3 API

z3是Microsoft Research開發的高性能的定理證明工具。z3常常被用在許多應用中,如:軟件/硬件的驗證和測試,約束求解問題,混合系統分析,安全領域,生物學和幾何問題。

本教程演示z3py的主要功能,即在python中使用z3 api,請務必跟着一起動手實驗。

 

開始

我們來看一個簡單的例子:

x = Int('x')
y = Int('y')
solve(x > 2, y < 10, x + 2*y == 7)

Int('x')函數創建了一個z3的整形變量名爲x,solve函數求解了一個約束系統,這個例子使用了兩個變量x和y,以及3個約束條件。z3py和python一樣使用"="來進行賦值,使用<, <=, >, >=, ==, !=來進行比較。在以上例子中,表達式"x + 2*y == 7"是一個z3約束條件。

默認的,z3py顯示公式或表達式時使用數學的標記法,通常用∧來表示邏輯符號與,用∨來表示邏輯符號或,等等。命令set_option(html_mode=True/False)可以用來設置表達式的格式,True使用數學標記法,而False使用z3py的標記法,這也是離線版的z3py默認的模式

x = Int('x')
y = Int('y')
print x**2 + y**2 >= 1
set_option(html_mode=False)
print x**2 + y**2 >= 1

z3提供了一些函數來遍歷表達式內容

x = Int('x')
y = Int('y')
n = x + y >= 3
print "num args: ", n.num_args()
print "children: ", n.children()
print "1st child:", n.arg(0)
print "2nd child:", n.arg(1)
print "operator: ", n.decl()
print "op name:  ", n.decl().name() #返回str

z3提供所有基本的數學運算符。z3py使用與python相同的運算優先級。和python一樣,**代表冪運算。z3可以求解非線性的多項式條件約束。

x = Real('x')
y = Real('y')
solve(x**2 + y**2 > 3, x**3 + y < 5)

過程Real('x')創建了一個實數變量x。z3py可以表示任意大的整數,有理數和無理數。一個無理數是整數係數多項式的根。在內部,z3py精確的表示這些數,無理數以十進制小數的方式來表示,這方便讀取結果。

 

x = Real('x')
y = Real('y')
solve(x**2 + y**2 == 3, x**3 == 2)

set_option(precision=30)
print "Solving, and displaying result with 30 decimal places"
solve(x**2 + y**2 == 3, x**3 == 2)

過程set_option用於設置z3的執行環境。它用於設置全局配置選項,如以何種方式來顯示結果,選項precision=30,設置十進制顯示結果中的小數位數。1.2599210498?中的?標誌輸出被截斷。

以下例子演示了一個常見的錯誤,表達式1/3是一個python整形數字而非一個z3有理數,這個例子也體現出了使用z3py創建有理數的不同方式。過程Q(num, den)創建了一個z3有理數,該有理數以num作爲分子,den作爲分母。過程RealVal(1)創建了一個z3實數來表示數字1。

print 1/3
print RealVal(1)/3
print Q(1,3)

x = Real('x')
print x + 1/3
print x + Q(1,3)
print x + "1/3"
print x + 0.25

有理數也可以表示爲十進制小數形式

x = Real('x')
solve(3*x == 1)

set_option(rational_to_decimal=True)
solve(3*x == 1)

set_option(precision=30)
solve(3*x == 1)

一個約束系統可能無解,這種情況下,我們稱該系統爲不可滿足的。

x = Real('x')
solve(x > 4, x < 0)

和python相同,註釋以#開頭,以換行符結束,z3py不支持多行註釋。

BooLean Logic

z3支持布爾運算符:And,Or,Not,Implies,If。等價使用==來表示。

以下例子顯示瞭如何求解簡單的布爾條件約束
p = Bool('p')
q = Bool('q')
r = Bool('r')
solve(Implies(p, q), r == Not(q), Or(Not(p), r))

python布爾常量True和False可以創建z3的布爾表達式
p = Bool('p')
q = Bool('q')
print And(p, q, True)
print simplify(And(p, q, True))
print simplify(And(p, False))


以下例子結合了多項式和布爾約束
p = Bool('p')
x = Real('x')
solve(Or(x < 5, x > 10), Or(p, x**2 == 2), Not(p))

求解器

z3提供了不同的求解器。命令solve,在之前已經使用過了,它使用z3求解器API來實現的。它的實現可以在z3的發行版中的z3.py文件中找到。下列例子演示了基本的Solver API。

x = Int('x')
y = Int('y')

s = Solver()
print s

s.add(x > 10, y == x + 2)
print s
print "Solving constraints in the solver s ..."
print s.check()

print "Create a new scope..."
s.push()
s.add(y < 11)
print s
print "Solving updated set of constraints..."
print s.check()

print "Restoring state..."
s.pop()
print s
print "Solving restored set of constraints..."
print s.check()

命令Solver()創建了一個通用的求解器。約束條件可以用方法add來添加。我們稱該約束條件已經在求解器中被斷言了。check()方法來斷言這些約束條件,返回結果sat表示有解,而unsat則表示無解。我們也可以說該系統斷言的約束條件是不可行的。最後,求解器也可能無法求解某約束系統而返回一個未知結果。

在一些應用當中,我們想要探究一些共享約束條件的類似問題,我們可以使用命令push和POP來完成這件事。每一個求解器都維護一個斷言棧,命令push創建一個新的空間來保存當前棧大小,命令pop刪除與匹配push命令之間的所有斷言,check方法操作的對象總是對當前的斷言棧。

以下展示了一個z3無法求解的例子,s.check()在這種情況下返回unknown。回想一下,Z3可以解決非線性的多項式約束,但2 ** x不是多項式

x = Real('x')
s = Solver()
s.add(2**x == 3)
print s.check()

以下示例顯示如何遍歷求解器的約束條件,以及如何收集check方法的性能統計信息

x = Real('x')
y = Real('y')
s = Solver()
s.add(x > 1, y > 1, Or(x + y > 3, x - y < 2))
print "asserted constraints..."
for c in s.assertions():
    print c

print s.check()
print "statistics for the last check method..."
print s.statistics()
# Traversing statistics
for k, v in s.statistics():
    print "%s : %s" % (k, v)

當z3找到斷言的約束條件的解時,check命令返回sat。我們說z3滿足了一系列約束條件,並稱其解爲一個模型(model),該模型滿足斷言的約束條件。模型是使每個斷言約束成立的解釋。 以下示例顯示了檢查模型的基本方法

x, y, z = Reals('x y z')
s = Solver()
s.add(x > 1, y > 1, x + y > 3, z - x < 10)
print s.check()

m = s.model()
print "x = %s" % m[x]

print "traversing model..."
for d in m.decls():
    print "%s = %s" % (d.name(), m[d])
在上述例子中,函數Reals('x y, z')創建了x,y,z,3個變量。它是以下指令的簡化版
x = Real('x')
y = Real('y')
z = Real('z')

表達式m[x]返回x在模型m中的解釋,表達式"%s = %s" % (d.name(), m[d])返回一個字符串,其中第一個%s由d.name()代替,而第二個%s由m[d]來代替,在需要的時候,z3py自動將z3對象轉換爲文本表現形式。

 

算數

z3支持實數和整數變量,它們可以在某個問題中混合使用。和大多數程序語言一樣,z3py在需要的時候會自動將整形表達式轉換爲實數表達式。以下例子展示了用不同方法聲明整形變量和實數變量。

a, b, c = Ints('a b c')
d, e = Reals('d e')
solve(a > b + 2,
      a == 2*c + 10,
      c + b <= 1000,
      d >= e)
命令simplify適用於z3表達式的簡單轉化。
x, y = Reals('x y')
# Put expression in sum-of-monomials form
t = simplify((x + y)**3, som=True)
print t
# Use power operator
t = simplify(t, mul_to_power=True)
print t

命令help_simplify()打印出所有可選操作。z3py允許用戶使用兩種風格來編寫選項。z3內部選項名以":"開頭,單詞之間以"-"分開。這些選項也可用於z3py之中。z3py也支持類python的名字,這時"-"被刪除,"-"被替換爲"_",下面例子展示了這兩種風格

x, y = Reals('x y')
# Using Z3 native option names
print simplify(x == y + 2, ':arith-lhs', True)
# Using Z3Py option names
print simplify(x == y + 2, arith_lhs=True)

print "\nAll available options:"
help_simplify()

z3py支持任意大的數字。下方的例子展示瞭如何用大數進行基本的算數運算。z3py只支持代數上的無理數,代數上的無理數足以表示多項式約束條件的解。z3py會將無理數轉換爲十進制小數的可讀性強的形式,無理數的內部表現形式可以用sexpr()方法提取出來,它用數學公式的形式或類似lisp語言的表達式來展示z3的內部表現形式。

x, y = Reals('x y')
solve(x + 10000000000000000000000 == y, y > 20000000000000000)

print Sqrt(2) + Sqrt(3)
print simplify(Sqrt(2) + Sqrt(3))
print simplify(Sqrt(2) + Sqrt(3)).sexpr()
# The sexpr() method is available for any Z3 expression
print (x + Sqrt(y) * 2).sexpr()

機器運算

現代CPU和主流程序語言使用固定大小的位向量進行運算。在z3py中可以使用Bit-Vector來實現機器運算。它實現了無符號運算和有符號運算的精確定義。

下方例子展示瞭如何創建一個位向量變量和常量。函數BitVec('x', 16)創建了一個z3的位向量名爲x,它的大小爲16比特。爲了方便,在z3py中整數常量可以用於創建位向量表達式。函數BitVecVal(10, 32)創建了一個大小爲32比特的位向量,其數值爲10。

x = BitVec('x', 16)
y = BitVec('y', 16)
print x + 2
# Internal representation
print (x + 2).sexpr()

# -1 is equal to 65535 for 16-bit integers 
print simplify(x + y - 1)

# Creating bit-vector constants
a = BitVecVal(-1, 16)
b = BitVecVal(65535, 16)
print simplify(a == b)

a = BitVecVal(-1, 32)
b = BitVecVal(65535, 32)
# -1 is not equal to 65535 for 32-bit integers 
print simplify(a == b)

在一些程序語言,比如C,C++,C#,Java,有符號整數和無符號整數在位向量的層面上沒有任何區別。然而,z3爲有符號數與無符號數提供了不同的運算操作版本。在z3py中,運算符<, <=, >, >=, /, %和>>用於有符號數,而無符號數對應的操作符是ULT, ULE, UGT, UGE, UDiv, URem 和 LShR。

# Create to bit-vectors of size 32
x, y = BitVecs('x y', 32)

solve(x + y == 2, x > 0, y > 0)

# Bit-wise operators
# & bit-wise and
# | bit-wise or
# ~ bit-wise not
solve(x & y == ~y)

solve(x < 0)

# using unsigned version of < 
solve(ULT(x, 0))

運算符>>是算數右移操作,而<<是左移操作,而邏輯右移要使用LShR操作

 

函數

(有點譯不動)

程序語言的函數中會有意外情況發生,比如異常拋出或從不返回,和這些程序語言不同,z3的函數中完全不會出現這種情況,這是因爲所有的輸入值都是已被定義的。z3是建立在一階邏輯之上的。

對於一個約束條件,如x + y > 3,我們可以說x,y都是變量,在一些教材上,x和y被稱作未有解釋的量,也就是說,任何保證這個約束條件正確的解釋都是允許的。

更精確的來說,函數和常量符號在一階邏輯中都是未有解釋或者說是自由的,即沒有一種先天的解釋賦予它們。這和屬於簽名理論的函數形成對比,比如+運算擁有固定且標準的解釋,就是將兩個數相加。這種自由的函數和常量擁有最大的靈活性。它們允許任何滿足約束條件的解釋。

爲了說明未解釋的函數和常量,讓我們使用未解釋的整數x,y,最後讓f作爲一個未被解釋的函數,它接受一個整形參數並返回一個整形值。下方例子說明了如何強行出現一個解釋

x = Int('x')
y = Int('y')
f = Function('f', IntSort(), IntSort())
solve(f(f(x)) == x, f(x) == y, x != y)

對f的求解爲:f(0)爲1,f(1)爲0,f(c)爲1,這裏c爲除0和1外任意數
在z3中,我們還可以在模型中爲約束系統估計表達式。 以下示例顯示如何使用evaluate方法
x = Int('x')
y = Int('y')
f = Function('f', IntSort(), IntSort())
s = Solver()
s.add(f(f(x)) == x, f(x) == y, x != y)
print s.check()
m = s.model()
print "f(f(x)) =", m.evaluate(f(f(x)))
print "f(x)    =", m.evaluate(f(x))

 

Satisfiability and Validity

對於未解釋符號進行適當的賦值能使得方程或約束條件結果總是爲true,則稱方程或約束條件F有效。以約束條件的結果始終爲true作爲條件,若存在適當的對未解釋符號的賦值,則成約束條件可滿足。有效性是關於找到一個命題的證明,而可滿足性是找到約束條件的解(這裏就很清楚了)。設想一個方程F包含a和b,我們在問F是否有效時,即在問對任意值的a和b來說,它是否總是爲true。若F總是爲true,則Not(F)則總是false,這時a和b不會有任何可滿足的賦值組合,就稱Not(F)是不可滿足的,所以也可以這麼說,當Not(F)是不可滿足時,F是有效的。類似的,當Not(F)是無效的時,則F是可滿足的。(注:有效性是通過驗證來判斷的,即給定數值進行判斷,若任意判斷都是無效的,則稱不可滿足。命題形式:對任意驗證,若結果都是無效的,則稱條件約束不可滿足。逆否:若條件約束可以滿足,則存在驗證,使得結果有效)

p, q = Bools('p q')
demorgan = And(p, q) == Not(Or(Not(p), Not(q)))
print demorgan

def prove(f):
    s = Solver()
    s.add(Not(f))
    if s.check() == unsat:
        print "proved"
    else:
        print "failed to prove"

print "Proving demorgan..."
prove(demorgan)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章