第二个例子DieHard
这是一个倒水的例子,初始一个3升容量的桶,一个5升容量的桶,水不限,如何倒出一个容量刚好是4升的水。在一部老电影DieHard中主角就面临这个问题,这里就将module名命名为DieHard。
如果要倒出一个4升容量的水,那这4升水最后一定在5升的桶里。具体实现方式不止一种,这里举一个例子说明倒水的过程:假设5升桶为big,3升桶为small
1、装满small桶,将small水倒入big桶中:此时small为0,big为3
2、装满small桶,将small水倒入big桶中:此时small为1,big为5
3、将big桶倒掉,big=0,将small桶中水倒入big桶,此时,small=0,big=1
4、装满small桶,将small桶中水倒入big桶,此时,small=0,big=4。 big桶中就有了4升水。
TLA+实现过程
假设small表示3升桶,big表示5升桶,初始化桶中无水,即都等于0。
VARIABLES big , small
Init == big = 0 /\ small = 0
TypeOK == small \in 0..3 /\ big \in 0..5 \* 这是一个检查项,用TypeOK检查small与big的值是否正确
将上述倒水的过程进行拆解,可能的操作为:
1、装满small桶,big桶不变
FillSmallJug == small' = 3 /\ big' = big
\* 这里用 small',big'表示变更后的状态,=左边不能使用small,big这样的原变量
2、装满big桶,small桶不变
FillBigJug == big' = 5 /\ small' = small \* 逻辑同上
3、将small桶中水倒掉
EmptySmallJug == small' = 0 /\ big' = big \* 逻辑同上
4、将big桶中水倒掉
EmptyBigJug == big' = 0 /\ small' = small \* 逻辑同上
5、将small桶中水倒入big桶中
Min(m,n) == IF m < n THEN m ELSE n \* 定义一个取小函数
SmallToBig == big' = Min(big + small, 5) /\ small' = small - (big' - big)
\* small桶倒入big桶时,有可能溢出,因此需要判断一下
\* 对于big桶,if small+big < 5,THEN big' = big + small ELSE 5
\* 对于small桶,剩余水为减去倒入到big桶的量(big'-big),即 small' = small - (big'-big)
6、将big桶中水倒入small桶中
BigToSmall == small' = Min(big + small, 3) /\ big' = big - (small' - small) \* 逻辑同上
上述6种操作覆盖了所有的可行操作,其他的如倒水的时候即没倒满也没倒完这样的情况是无法达到目标的,因此不需要考虑。因此基于初始化状态,可行的动作就是上述6中操作中的一种。
上述代码汇总如下:
------------------------------ MODULE DieHard -------------------------------
EXTENDS Naturals \* 这里使用Integers Module替代 Naturals Module也是可以的
VARIABLES big, small
Init == big = 0 /\ small = 0
TypeOK == small \in 0..3 /\ big \in 0..5 \* 这是一个检查项,用TypeOK检查small与big的值是否正确
\* 需要将TypeOK添加到model的Invariants中来实现TLC的自动检查
\* 删除TypeOK不会对此Module有影响
FillSmallJug == small' = 3 /\ big' = big
\* 这里用 small',big'表示变更后的状态,=左边不能使用small,big这样的原变量
FillBigJug == big' = 5 /\ small' = small \* 逻辑同上
EmptySmallJug == small' = 0 /\ big' = big \* 逻辑同上
EmptyBigJug == big' = 0 /\ small' = small \* 逻辑同上
Min(m,n) == IF m < n THEN m ELSE n \* 定义一个取小函数
SmallToBig == big' = Min(big + small, 5) /\ small' = small - (big' - big)
\* small桶倒入big桶时,有可能溢出,因此需要判断一下
\* 对于big桶,if small+big < 5,THEN big' = big + small ELSE 5
\* 对于small桶,剩余水为减去倒入到big桶的量(big'-big),即 small' = small - (big'-big)
BigToSmall == small' = Min(big + small, 3) /\ big' = big - (small' - small) \* 逻辑同上
Next == \/ FillSmallJug
\/ FillBigJug
\/ EmptySmallJug
\/ EmptyBigJug
\/ SmallToBig
\/ BigToSmall
==================================================================
检查与运行
新建一个model,在Init处输入Init,在Next处输入Next
在Invariants处增加TypeOK 和 big # 4。 如果上述TLA+代码中删除了TypeOK,这里就不用添加TypeOK的检查。
添加big # 4是为了将状态转换过程 通过 Error-trace打印出来:基于初始状态Init,经过多次Next的状态变化,当出现big == 4 时将会停机,并将从Init状态出发到big == 4时的过程打印出来。
点击运行,可以看到Error-trace的状态变化过程。
经过多次运行,会出现Error-trace跟上面图中不一样的情况,那是另外一种实现方法。