Z3求解器基础学习 (一) 从例子入门

Z3基础学习(一) 从例子入门

打了星号的标题表示没有掌握

一.解不等式

例1.

输入:

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

输出:

[y = 0, x = 7]

函数Int('x')创建了一个名为x的变量。函数solve解决了一个约束系统。上面的例子用到了两个变量x和y,以及三个约束条件(x>2)、(y<10)、(x+2y7)。Z3向python使用=进行赋值。运算符<,<=,>,>=,和!=用于比较。上面的例子中,表达式x+2y==7是一个Z3约束。Z3可以解决和紧缩公式。

二.简化器

使用simplify将表达式进行化简

例1.

输入:

from z3 import *
x=Int('x')
y=Int('y')
print (simplify(x + y + 2*x + 3))
print (simplify(x < y + x + 2))
print (simplify(And(x + 1 >= 3, x**2 + x**2 + y**2 + 2 >= 5)))

输出:

3 + 3*x + y
Not(y <= -2)
And(x >= 2, 2*x**2 + y**2 >= 3)

三.表达式分析

Z3提供遍历表达式的函数。

输入

from z3 import *
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())

输出

num args:  2
children:  [x + y, 3]
1st child: x + y
2nd child: 3
operator:  >=
op name:   >=

四.数学运算

Z3提供了所有基本的数学运算。 Z3使用Python语言的相同运算符优先级。 像Python一样,**是幂运算。 Z3可以求解非线性多项式约束。

通常,是逻辑与,是逻辑或

例1.

输入

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

输出

[y = 2, x = 1/8]

其中,Real('x')创建实际变量x。 Z3可以表示任意大的整数,有理数(如上例)和无理代数。 一个无理数代数是具有整数系数的多项式的根。 在内部,Z3精确地代表了所有这些数字。 无理数以十进制表示形式显示,以便读取结果。

五.精度设置

set_option用于配置Z3环境。 它用于设置全局配置选项,如结果如何显示。 选项set_option(precision = 30)设置显示结果时使用的小数位数。 这个 标记在1.2599210498? 中表示输出被截断。

Real类型的数据求解默认为分数形式,如果设置rational_to_decimal=True则结果位小数形式,默认显示小数点后10位,10位后用?表示后面还有数值

例1.

输入:

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

set_option(precision=30)
#precision=30即保留30位小数
print ("Solving, and displaying result with 30 decimal places")
solve(x**2 + y**2 == 3, x**3 == 2)

输出:

[y = -1.1885280594?, x = 1.2599210498?]
Solving, and displaying result with 30 decimal places
[y = -1.188528059421316533710369365015?,
 x = 1.259921049894873164767210607278?]

例2.

以下示例演示了一个常见的错误。?

表达式1/3是一个Python整数,而不是Z3有理数。 该示例还显示了在Z3Py中创建有理数的不同方法。 程序Q(num,den)创建一个Z3有理数,其中num是分子,den是分母。 RealVal(1)创建一个表示数字1的Z3实数。

注意观察对应的输入输出

输入:

from z3 import *
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)

输出:

0.3333333333333333
1/3
1/3
x + 3333333333333333/10000000000000000
x + 1/3
x + 1/3
x + 1/4

例3.

有理数也可以用十进制表示法显示。

输入:

from z3 import *
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 = 1/3]
[x = 0.3333333333?]
[x = 0.333333333333333333333333333333?]

六.不可满足/无解

约束系统也可能没有解决方案。 在这种情况下,我们说这个系统是不可满足的。

输入:

from z3 import *
x = Real('x')
solve(x > 4, x < 0)

输出:

no solution

七.注释

像在Python中一样,注释以开头,并在行尾结束。 Z3Py不支持跨越多行的评论

八.BOOL LOGIC 布尔逻辑*

Z3支持布尔运算符:And, Or, Not, Implies (implication), If (if-then-else)。双蕴含符号用==表示。 以下示例显示如何解决一组简单的布尔约束。

输入:

from z3 import *
p = Bool('p')
q = Bool('q')
r = Bool('r')
solve(Implies(p, q), r == Not(q), Or(Not(p), r))

输出:

[q = True, p = False, r = False]

九.True、False in Python*

Python布尔常量TrueFalse可用于构建Z3布尔表达式。

输入:

from z3 import *
p = Bool('p')
q = Bool('q')
print (And(p, q, True))
print (simplify(And(p, q, True)))
print (simplify(And(p, False)))

输出:

And(p, q, True)
And(p, q)
False

十.多项式与布尔组合

以下示例使用多项式和布尔约束的组合。

输入:

from z3 import *
p = Bool('p')
x = Real('x')
solve(Or(x < 5, x > 10), Or(p, x**2 == 2), Not(p))

输出:

[x = -1.4142135623?, p = False]

因为solve中的三个assert都要满足,所以Not(p)推出p = False, 所以x**2 == 2要成立,所以x = +- sqrt(2)。又因为x > 10不可能,所以就是x < 5,也就是正负根号2都可以,只输出一个解即可,所以输出负根号2.

十一.pop / push 断言堆栈*

输入:

from z3 import *
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 ()

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

输出:

[]
[x > 10, y == x + 2]
Solving constraints in the solver s ...
sat
Create a new scope...
[x > 10, y == x + 2, y < 11]
Solving updated set of constraints...
unsat
Restoring state...
[x > 10, y == x + 2]
Solving restored set of constraints...
sat

可以看到,一开始求解器为空,后来加上两个断言之后,求解器的context就有了那两个断言。check求解器得到结果。sat 意味着满足(satisfied)。接下来创建了一个新的范围,可以看到新增了一个断言,这时候check的结果就是unsat,意味着不可满足(unsatisfied). 再把新增的assert 弹出(pop)之后,可以看到又sat了。

Solver()命令创建一个通用求解器。约束可以使用方法add添加。方法check()解决了断言的约束。如果找到解决方案,结果是sat(满足)。如果不存在解决方案,结果unsat(不可满足)。我们也可以说,所声明的约束系统是不可行的(infeasible)。最后,求解器可能无法解决约束系统并返回unknown(未知)。

在一些应用中,我们想要探索几个共享几个约束的类似问题。我们可以使用pushpop命令来做到这一点。每个求解器维护一堆断言。命令push通过保存当前堆栈大小来创建一个新的作用域。命令pop删除它与匹配推送之间执行的任何断言。检查方法始终对求解器断言堆栈的内容进行操作。

十二.只能求解非线形多项式约束

以下示例显示了Z3无法解决的示例。求解器在这种情况下返回未知数。回想一下,Z3可以求解非线性多项式约束,但2 ** x不是一个多项式。

输入:

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

输出:

unknown

十三.遍历 / 性能统计*

以下示例显示如何遍历断言解析器中的约束,以及如何收集检查方法的性能统计信息。

输入:

from z3 import *
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))

输出:

asserted constraints...
x > 1
y > 1
Or(x + y > 3, x - y < 2)
sat
statistics for the last check method...
(:arith-lower         1
 :arith-make-feasible 3
 :arith-max-columns   8
 :arith-max-rows      2
 :arith-upper         3
 :decisions           2
 :final-checks        1
 :max-memory          19.63
 :memory              19.23
 :mk-bool-var         4
 :mk-clause-binary    1
 :num-allocs          433302
 :num-checks          1
 :rlimit-count        355
 :time                0.00)
decisions : 2
final checks : 1
mk clause binary : 1
num checks : 1
mk bool var : 4
arith-lower : 1
arith-upper : 3
arith-make-feasible : 3
arith-max-columns : 8
arith-max-rows : 2
num allocs : 433302
rlimit count : 355
max memory : 19.63
memory : 19.23
time : 0.004

当Z3找到一组已确定约束的解决方案时,check就会返回sat, 我们就可以说Z3满足了这些约束条件,这个解决方案是这组声明约束的modelmodel是使每个断言约束都为真的interpretation。(大致翻译:model 模型, interpretion:实例)

十四.检查模型

以下示例显示了检查模型的基本方法。

输入:

from z3 import *
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]))

输出:

sat
x = 3/2
traversing model...
y = 2
x = 3/2
z = 0

在上面的例子中,函数Reals('x y z')创建变量 x,y和z。 它是以下三句话的缩写:

x = Real('x')
y = Real('y')
z = Real('z')

注意Reals 加了s

表达式m [x]返回模型m中对x的解释。 表达式“%s=%s”%(d.name(),m [d])返回一个字符串,其中第一个%s被替换为d的名字(即d.name()),第二个 %s用文本表示d的解释(即m [d])。 Z3Py在需要时自动将Z3对象转换为文本表示。(这是python的写法)

十五.整数/实数变量

输入:

from z3 import *
x = Real('x')
y = Int('y')
a, b, c = Reals('a b c')
s, r = Ints('s r')
print (x + y + 1 + (a + s))
print (ToReal(y) + c)

输出:

x + ToReal(y) + 1 + a + ToReal(s)
ToReal(y) + c

函数ToReal将整型表达式转换为实型表达式。

Z3Py支持所有基本的算术运算。

十六.算数运算

输入:

from z3 import *
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)

输出:

[b = 0, c = 0, e = 0, d = 0, a = 10]

十七.Simplify

simplify命令对Z3表达式应用简单的转换。

输入

from z3 import *
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)

输出:

x*x*x + 3*x*x*y + 3*x*y*y + y*y*y
x**3 + 3*x**2*y + 3*x*y**2 + y**3

十八.help_simplify*

help_simplify() 命令打印所有可用的选项。 Z3Py允许用户以两种风格书写选项。 Z3内部选项名称以:开头,单词之间用 - 分隔。 这些选项可以在Z3Py中使用。 Z3Py还支持类似Python的名称,其中:被压缩并且被_替换。

以下示例演示如何使用这两种样式。

输入:

from z3 import *
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()

输出:

x + -1*y == 2
x + -1*y == 2

All available options:
algebraic_number_evaluator (bool) simplify/evaluate expressions containing (algebraic) irrational numbers. (default: true)
arith_ineq_lhs (bool) rewrite inequalities so that right-hand-side is a constant. (default: false)
arith_lhs (bool) all monomials are moved to the left-hand-side, and the right-hand-side is just a constant. (default: false)
bit2bool (bool) try to convert bit-vector terms of size 1 into Boolean terms (default: true)
blast_distinct (bool) expand a distinct predicate into a quadratic number of disequalities (default: false)
blast_distinct_threshold (unsigned int) when blast_distinct is true, only distinct expressions with less than this number of arguments are blasted (default: 4294967295)
blast_eq_value (bool) blast (some) Bit-vector equalities into bits (default: false)
blast_select_store (bool) eagerly replace all (select (store ..) ..) term by an if-then-else term (default: false)
bv_extract_prop (bool) attempt to partially propagate extraction inwards (default: false)
bv_ineq_consistency_test_max (unsigned int) max size of conjunctions on which to perform consistency test based on inequalities on bitvectors. (default: 0)
bv_ite2id (bool) rewrite ite that can be simplified to identity (default: false)
bv_le2extract (bool) disassemble bvule to extract (default: true)
bv_le_extra (bool) additional bu_(u/s)le simplifications (default: false)   
bv_not_simpl (bool) apply simplifications for bvnot (default: false)        
bv_sort_ac (bool) sort the arguments of all AC operators (default: false)   
cache_all (bool) cache all intermediate results. (default: false)
elim_and (bool) conjunctions are rewritten using negation and disjunctions (default: false)
elim_ite (bool) eliminate ite in favor of and/or (default: true)
elim_rem (bool) replace (rem x y) with (ite (>= y 0) (mod x y) (- (mod x y))). (default: false)
elim_sign_ext (bool) expand sign-ext operator using concat and extract (default: true)
elim_to_real (bool) eliminate to_real from arithmetic predicates that contain only integers. (default: false)
eq2ineq (bool) expand equalities into two inequalities (default: false)     
expand_nested_stores (bool) replace nested stores by a lambda expression (default: false)
expand_power (bool) expand (^ t k) into (* t ... t) if  1 < k <= max_degree. (default: false)
expand_select_ite (bool) expand select over ite expressions (default: false)
expand_select_store (bool) conservatively replace a (select (store ...) ...) term by an if-then-else term (default: false)
expand_store_eq (bool) reduce (store ...) = (store ...) with a common base into selects (default: false)
expand_tan (bool) replace (tan x) with (/ (sin x) (cos x)). (default: false)
flat (bool) create nary applications for and,or,+,*,bvadd,bvmul,bvand,bvor,bvxor (default: true)
gcd_rounding (bool) use gcd rounding on integer arithmetic atoms. (default: false)
hi_div0 (bool) use the 'hardware interpretation' for division by zero (for bit-vector terms) (default: true)
hoist_ite (bool) hoist shared summands under ite expressions (default: false)
hoist_mul (bool) hoist multiplication over summation to minimize number of multiplications (default: false)
ignore_patterns_on_ground_qbody (bool) ignores patterns on quantifiers that don't mention their bound variables. (default: true)
ite_extra_rules (bool) extra ite simplifications, these additional simplifications may reduce size locally but increase globally (default: false)       
local_ctx (bool) perform local (i.e., cheap) context simplifications (default: false)
local_ctx_limit (unsigned int) limit for applying local context simplifier (default: 4294967295)
max_degree (unsigned int) max degree of algebraic numbers (and power operators) processed by simplifier. (default: 64)
max_memory (unsigned int) maximum amount of memory in megabytes (default: 4294967295)
max_steps (unsigned int) maximum number of steps (default: 4294967295)      
mul2concat (bool) replace multiplication by a power of two into a concatenation (default: false)
mul_to_power (bool) collpase (* t ... t) into (^ t k), it is ignored if expand_power is true. (default: false)
pull_cheap_ite (bool) pull if-then-else terms when cheap. (default: false)  
push_ite_arith (bool) push if-then-else over arithmetic terms. (default: false)
push_ite_bv (bool) push if-then-else over bit-vector terms. (default: false)
push_to_real (bool) distribute to_real over * and +. (default: true)        
rewrite_patterns (bool) rewrite patterns. (default: false)
som (bool) put polynomials in sum-of-monomials form (default: false)        
som_blowup (unsigned int) maximum increase of monomials generated when putting a polynomial in sum-of-monomials normal form (default: 10)
sort_store (bool) sort nested stores when the indices are known to be different (default: false)
sort_sums (bool) sort the arguments of + application. (default: false)      
split_concat_eq (bool) split equalities of the form (= (concat t1 t2) t3) (default: false)

rith-lhs参数被设置成True(default:False),意味着所有参数都放在左手边,右手边只留下常数constant。

十九.无理数、任意大

Z3Py支持任意大的数字。 以下示例演示如何使用较大的整数,有理数和无理数执行基本算术。 Z3Py仅支持代数无理数。 代数无理数对于呈现多项式约束系统的解是足够的。 Z3Py将始终以十进制符号显示无理数,因为它更便于阅读。 内部表示可以使用sexpr()方法提取。 它以s表达式(Lisp-like)的形式显示数学公式和表达式的Z3内部表示。

输入:

from z3 import *
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())
# sexpr() 方法可用于任何 Z3 表达式
print ((x + Sqrt(y) * 2).sexpr())

输出:

[y = 20000000000000001, x = -9999979999999999999999]
2**(1/2) + 3**(1/2)
3.1462643699?
(root-obj (+ (^ x 4) (* (- 10) (^ x 2)) 1) 4)
(+ x (* (^ y (/ 1.0 2.0)) 2.0))

二十.位向量

以下示例演示如何创建位向量变量和常量。

函数BitVec('x',16)在Z3中创建一个位向量变量,名称为x,具有16位。 为了方便起见,可以使用整型常量在Z3Py中创建位向量表达式。 函数BitVecVal(10,32)创建一个大小为32的位向量,其值为10。

输入:

from z3 import *
x = BitVec('x', 16)
y = BitVec('y', 16)
print (x + 2)
# 内部
print ((x + 2).sexpr())

# -1 等于 65535 对应 16-bit 整数 
print (simplify(x + y - 1))

# 创建位向量常量
a = BitVecVal(-1, 16)
b = BitVecVal(65535, 16)
print (simplify(a == b))

a = BitVecVal(-1, 32)
b = BitVecVal(65535, 32)
# -1 对于 32 位整数不等于 65535 
print (simplify(a == b))

输出:

x + 2
(bvadd x #x0002)
65535 + x + y
True
False

与诸如C,C ++,C#,Java等编程语言相比,有符号和无符号的位向量之间没有区别。 相反,Z3提供了算术运算的特殊符号版本,无论位向量是有符号还是无符号都是有区别的。 在Z3Py中,运算符<<=>> =/>>对应于有符号的版本。 相应的,无符号运算符是ULTULEUGTUGEUDivURemLShR

输入:

from z3 import *
# 创建大小为 32 的位向量
x, y = BitVecs('x y', 32)

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

#  位运算符
# &  按位与
# | 按位异或
# ~ 按位取反
solve(x & y == ~y)

solve(x < 0)

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

输出:

[y = 1, x = 1]
[y = 4294967295, x = 0]
[x = 4294967295]
no solution

二十一.位移运算

算子>>是算术右移,而<<是左移。 位移符号是左结合的。

输入:

from z3 import *
# 创建大小为 32 的位向量
x, y = BitVecs('x y', 32)

solve(x >> 2 == 3)

solve(x << 2 == 3)

solve(x << 2 == 24)

输出:

[x = 12]
no solution
[x = 6]

二十二.FUNCTIONS 函数

在常见的编程语言中,函数具有副作用,可以抛出异常或永不返回。但在Z3中,函数是没有副作用的,并且是完全的。也就是说,它们是在所有输入值上定义的。比如除法函数。 Z3是基于一阶逻辑的。

给定一个约束,如x + y > 3,我们说x和y是变量。在许多教科书中,x和y被称为未解释的常量。也就是说,它们允许任何与约束x + y> 3一致的解释。

更确切地说,纯粹的一阶逻辑中的函数和常量符号是不解释的(uninterprete)、或自由的(free),这意味着没有附加先验解释。这与属于理论特征的函数形成对比,例如函数+具有固定的标准解释(它将两个数字相加)。不解释的函数和常量能够达到最大程度地灵活;它们允许任何与函数或常数约束相一致的解释。

为了说明未解释的函数和常量,让我们用一个例子来说明。

输入:

from z3 import *
x = Int('x')
y = Int('y')
f = Function('f', IntSort(), IntSort())
solve(f(f(x)) == x, f(x) == y, x != y)

输出:

[x = 0, y = 1, f = [1 -> 0, else -> 1]]

设不解释的整数常量(又名变量)x,y;设f是一个不解释函数,它接受一个类型(又名 sort)整数的参数并生成一个整数值。这个例子说明了如何强制一个解释,其中f再次应用于x导致x,但是f应用于x不同于x。对于 f 的解(Interpretation)应该被解读为:

f(0)= 1,f(1)= 0,并且对于全部不同于0和1的a, f(a)=1。

在Z3中,我们也可以评估模型中的约束系统表达式。 以下示例显示如何使用评估方法。

输入:

from z3 import *
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)))

输出:

sat
f(f(x)) = 0
f(x)    = 1
posted @ 2022-12-28 02:49  说芬兰语的雪  阅读(2458)  评论(2编辑  收藏  举报