算法算法,离不开各种公式。我们坚信:只要解出这些公式的唯一解,就可以得到 flag。
但计算机中的 “公式” 往往不会是简单的 之类,而会是掺杂了算术、位向量、数据结构等。我们(判断能否)解出这些公式的过程,就是在判断这些公式的可满足性 (Satisfiability) 如何。
这里我们就提出了可满足性模理论 (Satisfiability Modulo Theories, SMT):
- 给定一组理论 (Theory),根据给定逻辑,求在该组理论解释下公式的可满足性
- 现有理论通常针对一阶理论,即公理都是一阶的
从逻辑学角度来看, 或者 都是逻辑系统中不包含的符号,计算机需要知道他们的意思,这就引出了理论的概念:
- 理论用于对这类符号谓词 / 函数(比如 ,,,…)赋予含义;
- 理论包含一组公理和这组公理能推导出的结论;
因此,如果想解决 SMT 问题,求解器就需要内置基本的理论。
首先,Z3 内置的理论都是在一阶逻辑 (FOL, first-order logic) 下定义的,需要推的公理只使用这些一阶逻辑理论定义。
一阶逻辑的显著特点是只对对象变量(如 , 等)进行量化(全称量化 ∀ 和存在量化 ∃),没有对函数或谓词本身做量化,它们在这些理论中应当是固定的。一阶逻辑相对好求,因为对象集合离散且数量级较小,可以用 SMT 技术穷举。
二阶逻辑量化的是谓词/函数本身,这些谓词和函数在现有理论中是不固定的。二阶逻辑难求,因为它需要遍历所有可能的谓词/函数,数量级非常大,Z3 不支持这一类问题。
换句话说,一阶逻辑相当于数学考试中的 “求符合条件的 的值”,二阶逻辑相当于 “求符合条件的函数 ”
Z3 是一个高效的 SMT 求解器,具有专门用于解决一阶逻辑理论的算法。
Z3Py 入门
要使用 Python 的 Z3,请通过 pip 安装:
接下来我们解一个简单的二元一次方程:
1 | from z3 import * |
得到输出:
1 | [y = 0, x = 7] |
函数 Int('x')
用于在 Z3 中创建一个名为 x
的整数变量。solve
函数求解一个约束系统 (System of Constraints)。上面的例子使用了两个变量 x
和 y
,以及三个约束 (Constraints):
-
x
必须大于 2 -
y
必须小于 10 -
当
x
加上y
的两倍时,它必须等于 7
Z3Py 和 Python 一样使用 =
进行赋值。使用运算符 <
、<=
、>
、>=
、==
和 !=
进行比较。在上面的示例中,表达式 x + 2 * y == 7
是一个 Z3 约束。因此,Z3 可以求解和处理公式。
接下来,我们将介绍 Z3 Solver 的一些特性。
表达式属性
1 | x = Int('x') |
输出:
1 | num args: 2 |
n
注册了一个表达式类,通过类 n
提供的方法,我们可以得到表达式的参数、子式(左右两边的式子)和比较运算符信息。
基本数学运算
Z3 提供所有基本的数学运算。Z3Py 使用与 Python 语言相同的运算符优先级。[1]
Z3 内置的整数算术是 Presburger 算术,只完备支持加减法、乘常数和比较运算。
Z3 基本数学运算支持与完备性速查表
本表由 GPT-5 生成
本表基于 Z3Py 与 SMT - LIB2 标准,标明完备性(✅ 完备 / ⚠️ 部分完备 / ❌ 不完备)。
- ✅ 完备:Z3 在相应理论(如 LIA/LRA)下可保证可判定且一定返回 SAT/UNSAT。
- ⚠️ 部分完备:Z3 尝试求解,但可能返回
unknown
。 - ❌ 不完备 / 不支持:Z3 无内置语义,需人工编码或近似化。
参考
- 通用 (LIA / LRA)
Z3Py | SMT-LIB2 | 含义 | 完备性 |
---|---|---|---|
+ |
+ |
加法 | ✅ |
- |
- |
减法 / 取负 | ✅ |
* |
* |
乘法(线性,有一个是常量) | ✅ |
/ |
/ |
实数除法 | ✅ |
< , <= |
< , <= |
小于 / 小于等于 | ✅ |
> , >= |
> , >= |
大于 / 大于等于 | ✅ |
= |
= |
等式 | ✅ |
Distinct(a,b,c) |
distinct |
两两不等 | ✅ |
- 整数专用 (LIA / LIA + mod)
Z3Py | SMT-LIB2 | 含义 | 完备性 |
---|---|---|---|
IntDiv(x, y) |
div |
整数除法(向零取整) | ✅ |
x % y / Mod(x, y) |
mod |
取模(符号与被除数相同) | ✅ |
Abs(x) |
abs |
绝对值 | ✅ |
ToReal(x) |
to_real |
Int → Real 转换 | ✅ |
ToInt(x) |
to_int |
Real → Int 转换(向零取整) | ✅ |
is_int(x) |
is_int |
检查实数是否是整数值 | ✅ |
- 常量与类型装换
Z3Py | SMT-LIB2 | 含义 |
---|---|---|
IntVal(n) |
numeral | 整数常量 |
RealVal(r) |
numeral | 实数常量 |
- 不属于基本运算的运算(非线性 / 超越)
运算 | 例子 | 完备性 | 备注 |
---|---|---|---|
非线性乘法 | x*y |
⚠️ | 两个算子都属于变量 |
幂运算(常数指数) | x**2 |
⚠️ | 属于非线性算术,可求但不完备 |
幂运算(变量指数) | x**y |
❌ | 一般不可判定 |
平方根 / 开方 | sqrt(x) |
⚠️/❌ | 需编码为幂运算 |
三角函数 | sin(x) |
❌ | 不支持内置语义 |
对数 / 指数函数 | log(x) / exp(x) |
❌ | 不支持内置语义 |
其他超越函数 | — | ❌ | 不支持 |
布尔逻辑运算
Z3 支持布尔运算符:与 (And
)、或 (Or
)、非 (Not
)、蕴含 / 充分条件 (Implies
)、如果 (If
)。双蕴含(即充要条件)使用等号 (==
) 表示。
常 / 变量表示
一阶逻辑公式由项(变量或常量)与扩展布尔结构组成。变量在 Z3 中可表示为:
1 | x = Int(name = 'x') # x is an integer |
整型与实数类型变量之间可以互相进行转换:
1 | ToReal(x) |
除了 Python 原有的数据类型外,Z3 也有自己的数据类型:
1 | 114514) # integer IntVal(val = |
Z3 中的布尔常量用 True
和 False
表示。
求解器
如果只有公式(约束)较少,那我们调用 solve
方法就好,但有时我们会有大量约束,单行命令易读性就很差了。此时需要创建一个求解器类 Solver
。求解器是一类约束的集合。
1 | s = Solver() |
现在我们创建了一个求解器实例 s
,需要用 add
方法向这个求解器添加约束:
1 | s.add(x * 5 == 10) |
添加约束之后,我们可以调用 check
方法检查约束是否可满足 (satisfiable)。如果返回 sat
即为 “可以满足”,unsat
即为 “不可满足”。
如果约束可以满足,则我们可以调用 model
方法得到一组解。
如果需要输出结果(包括满足性情况),注意使用 print
函数。
之前示例中使用的 solve
方法是通过 Z3 求解器 API 实现的。当你调用 solve
时,Z3 会帮你创建一个临时的求解器,将约束传入,检查是否满足并输出结果。可以将 solve
方法简单理解为 check
、model
和 print
的一次性封装。