问题定义
对于由 环境(可以看作是整个场景中不可控制的部分) 和 系统(一般来说就是你希望生成的程序)组成的双人博弈的场景,我们将其抽象为 8 元组:
- $\mathcal{X}$:由环境控制的变量集合
- $\mathcal{Y}$:由系统控制的变量集合
- $\mathcal{V} = \mathcal{X} \cup \mathcal{Y}$
- $\theta_e$:关于环境的,初始成立的断言,由 $\mathcal{X}$ 中的变量组成
- $\theta_s$:关于系统的,初始成立的断言,由 $\mathcal{Y}$ 中的变量组成
- $\rho_e: (\Sigma_V, \Sigma_X) \rightarrow \text{bool}$:用于选择 环境 的下一个动作的断言函数, 其中 第一个参数 输入当前 所有(环境与系统的)变量的取值, 第二个参数 输入环境可能采取的动作, 若 $\rho_e(s, x) = \text{true}$,说明 在所有当前变量的值为 $s$ 的情况下,环境可以采取动作 $x$
- $\rho_s: (\Sigma_V, \Sigma_X, \Sigma_Y) \rightarrow \text{bool}$:用于选择 系统 的下一个动作的断言函数, 其中 第一个参数 输入当前 所有(环境与系统的)变量的取值, 第二个参数 输入 环境这时采取的动作, 第三个参数 输入 系统可能采取的动作, 若 $\rho_s(s, x, y) = \text{true}$,说明 在所有当前变量的值为 $s$ 且 环境采取动作 $x$ 的情况下,系统可以采取动作 $y$
- $\varphi$:一个 LTL 公式,定义了(对于系统来说的)胜利条件
算法的目标是,找到一个函数 $f: (M, \Sigma_V, \Sigma_X) \rightarrow (M, \Sigma_X)$, 使得:
- 若 $\rho_e(s,s_X) = \text{true}$,那么 $\rho_s(s,s_X,\text{snd}(f(m,s,s_X))) = \text{true}$,
- 从某个状态 $s$ 出发时,所有符合 $f$ 的 对局(状态序列)都是系统胜利($(s_0, s_1, \cdots) \models \varphi$)的。
其中的 $M$ 是一个记忆空间,包含初始值 $m_0$。
这篇论文解决的问题是 Generalized Reactivity(1) 博弈,即 $\varphi$ 形如 $$ \varphi = \bigwedge_{i=1}^{m} \mathbf{G}(\mathbf{F}(J_i^e)) \rightarrow \bigwedge_{j=1}^{n} \mathbf{G}(\mathbf{F}(J_j^s)) $$ 的情况,其中 $J_i^e$ 和 $J_j^s$ 是布尔函数,分别表示 环境 和 系统 要保证/实现的 liveness 条件。
算法
必须的概念
μ-calculus
In theoretical computer science, the modal μ-calculus (Lμ, sometimes just μ-calculus, although this can have a more general meaning) is an extension of propositional modal logic (with many modalities) by adding the least fixed point operator μ and the greatest fixed point operator ν, thus a fixed-point logic. - Modal μ-calculus, Wikipedia
简单来说就是往一阶 propositional logic 中添加了 定点运算符 $\mu$ 和 $\nu$ 和 扩展运算符 $[-]$ 和 $\langle - \rangle$。
语法如下:
$$ \varphi ::= v \mid \neg \varphi \mid X \mid \varphi \vee \varphi \mid \varphi \wedge \varphi \mid [\varphi] \mid \langle \varphi \rangle \mid \mu X \varphi \mid \nu X \varphi $$
一个公式 $\phi$ 被解释为 $G$ 中状态集合 $Σ$ 中使 $\phi$ 成立的状态集合,记为 $[[\phi]]_G^E$,其中 $G$ 是游戏结构,$E: \text{Map}\langle\text{Var}, 2 ^ Σ\rangle$ 将每个关系变量1映射到一组状态。
除了常见 propositional logic 的符号以外几个运算符的(指称)语义:
-
$[[X]]_G^E = E[X]$
-
$[[ [\varphi] ]]_G^E$ 表示,系统可以强迫 $[[\varphi]]_G^E$ 成立的状态集合。
-
$[[ \langle \varphi \rangle ]]_G^E$ 表示,环境可以强迫 $[[\varphi]]_G^E$ 成立的状态集合。
-
$ [[ \mu X \varphi ]]_G^E $ 表示,在有限步之内能够使得 $\varphi$ 成立的状态集合
$[[ \mu X \varphi ]]_G^E = \bigcup_{i} S_i $ where $S_0 = \emptyset$ and $S_{i+1} = [[ \varphi ]]_G^{E[X \leftarrow S_i]}$
-
$ [[ \nu X \varphi ]]_G^E $ 表示,从现在开始能够一直使得 $\varphi$ 成立的状态集合。
$[[ \nu X \varphi ]]_G^E = \bigcap_{i} S_i $ where $S_0 = \Sigma$ and $S_{i+1} = [[ \varphi ]]_G^{E[X \leftarrow S_i]}$
要求解的公式
最终要求解的公式是:
$$ \varphi_{gr} = \nu \begin{bmatrix} Z_1 \\ Z_2 \\ \vdots \\ Z_n \end{bmatrix} \begin{bmatrix} \mu Y (\bigvee_{i=1}^{m} \nu X ((J_1^s \wedge [Z_2]) \vee [Y] \vee (\neg J_i^e \wedge [X]))) \\ \mu Y (\bigvee_{i=1}^{m} \nu X ((J_2^s \wedge [Z_3]) \vee [Y] \vee (\neg J_i^e \wedge [X]))) \\ \vdots \\ \mu Y (\bigvee_{i=1}^{m} \nu X ((J_n^s \wedge [Z_1]) \vee [Y] \vee (\neg J_i^e \wedge [X]))) \end{bmatrix} $$
最外层的 $\nu Z$ 和内部的 $Z_{i+1 \text{mod} n}$ 表示,如果系统满足了当前的 $J_i$,则可以转而去尝试满足下一个 $J_{i+1}$。
中间的 $\mu Y$ 保证了后面的 $\bigvee_{i=1}^{m} \nu X ((J_1^s \wedge [Z_2]) \vee [Y] \vee (\neg J_i^e \wedge [X]))$ 部分会在有限步内结束。
最里面的 $\nu X$ 和内部的 $\neg J_i^e \wedge [X]$ 表示,若环境没有满足某个 $J^e$,则系统可以不前进。
求解
论文中给出了利用 BDD 求解的代码:
BDD calculate_win() {
BDD Z = TRUE;
// 外层 νZ 的 fixpoint 迭代
for (Fix fZ = new Fix(); fZ.advance(Z);) {
mem.clear();
// for each system goal (J^s_j)
for (int j = 1; j <= sys.numJ(); j++) {
BDD Y = FALSE;
// 中层 μY 的 fixpoint 迭代
for (Fix fY = new Fix(); fY.advance(Y);) {
// 构造起始集合:要么已经满足 J^s_j 并进入下一个阶段 Z_{j⊕1},要么已在 Y 中
BDD start = sys.Ji(j).and(cox(Z)).or(cox(Y));
Y = FALSE;
for (int i = 1; i <= env.numJ(); i++) { // 遍历每个环境假设 J^e_i
BDD X = Z;
// 最内层 νX 的 fixpoint 迭代
for (Fix fX = new Fix(); fX.advance(X);) {
// 如果环境没有满足 J^e_i,系统可以“停在 X”;否则必须通过 start 推进
X = start.or(env.Ji(i).not().and(cox(X)));
}
// 存储第 j 个目标、第 i 个假设下的 X 集合
mem.addX(j, i, X);
// 把所有 X 累加进 Y,构建 μY
Y = Y.or(X);
}
// 存储 Y(表示目标 j 可在有限步内达成的状态集)
mem.addY(j, Y);
}
// 将最新的 Y 作为下一轮外层 Z 的输入(推进 νZ)
Z = Y;
}
}
return Z;
}
其中 cox(x)
是 $[x]$ 的实现。
提取策略
void build_symbolic_controller() {
ctrl = new FDS("symbolic_controller");
Zn = ctrl.newBDDDomain("Zn", 1, sys.numJ());
// 联合环境与系统的转移关系
BDD tr12 = sys.trans().and(env.trans());
// 当子目标 J^s_j 被满足,就将 Zn 轮换至下一个目标(j → j ⊕ 1)
for (int j = 1; j <= sys.numJ(); j++) {
BDD rho1 = (Zn.eq(j)) // 当前记忆状态是 j(正在处理第 j 个目标)
.and(Z) // 当前状态在 Z 中
.and(sys.Ji(j)) // 当前满足系统目标 J^s_j
.and(tr12) // 当前转移合法
.and(next(Z)) // 下一状态仍要在区域 Z 中
.and(next(Zn).eq((j % sys.numJ()) + 1)); // 下一状态将 Zn 切换到 j⊕1
// 将该转移加入控制器的转移关系
ctrl.disjunctTrans(rho1);
}
// 从当前状态 mY[j][r] 推进到更接近目标的状态 mY[j][r'],直到最终达成目标
for (int j = 1; j <= sys.numJ(); j++) {
BDD low = mem.Y(j, 1); // r=1 表示最接近达成目标的状态
for (int r = 2; r <= mem.maxr(j); r++) { // 从上层往下找
BDD rho2 = (Zn.eq(j)) // 当前是目标 j
.and(mem.Y(j, r)) // 当前距离目标还有 r 步
.and(low.not()) // 下一“层”不能是当前这一“层”(r′ < r)
.and(tr12) // 合法转移
.and(next(low)) // 下一状态进入更低“层”(r')
.and(next(Zn).eq(j)); // Zn 不变,仍处于当前目标
low = low.or(mem.Y(j, r)); // 更新“已知的低层”集合
ctrl.disjunctTrans(rho2); // 加入控制器
}
}
// 如果环境违背假设 J^e_i,系统可以不推进
for (int j = 1; j <= sys.numJ(); j++) {
BDD low = FALSE;
for (int r = 2; r <= mem.maxr(j); r++) {
for (int i = 1; i <= env.numJ(); i++) {
BDD rho3 = (Zn.eq(j)) // 当前子目标 j
.and(mem.X(j, r, i)) // 当前在环境假设 i 的推进层 X
.and(low.not()) // 下一层需下降
.and(env.Ji(i).not()) // 环境违反假设 J^e_i
.and(tr12) // 转移合法
.and(next(mem.X(j, r, i))) // 下一状态继续在 X(即维持)
.and(next(Zn).eq(j)); // Zn 不变
low = low.or(mem.X(j, r, i)); // 更新低层状态集合
ctrl.disjunctTrans(rho3); // 加入转移
}
}
}
}
关系变量一般代表了一个需要求解的状态集合。