我正在进行一项计划修正/综合项目。我的任务是获取错误跟踪(反例),在完整的状态空间中找到它,并修复该位置的模型。我想将其实现为NuSMV扩展。
我一直在调试NuSMV以了解和探索它的源代码。到目前为止,我已经找到了创建BDD FSM的方法(compile.c第520行)。我试图找到一种遍历bdd的方法,以获得对状态空间的编程访问,从而对模型执行我的纠正工作。我还没有理解NuSMV用于通过bdd fsm验证属性的递归探索函数。
我想知道如何遍历bdd结构,以便我可以通过dot等工具对其进行可视化。我还想知道是否已经进行了这样或者微观的可视化(我已经搜索过但是空洞的)。其次,我想验证我所采用的方向是否正确,或者是否有更好的方法来获得给定模型的完整状态空间,并进行探索,特别是关于获得的反例通过NuSMV。
答案 0 :(得分:0)
这是有关使用CUDD而不是通过NuSMV来处理二进制决策图(BDD)的方式的答案,因此请关注问题的第二部分。
关于研究状态空间的符号算法,Kesten,Pnueli和Raviv(ICALP '98,DOI:"Algorithmic verification of linear temporal logic specifications")发表的论文10.1007/BFb0055036是一个很好的介绍,涵盖了反例构造。 / p>
一种可视化BDD的可能性是在Python中使用Cython绑定到CUDD:
from dd import cudd
def dump_pdf_cudd():
bdd = cudd.BDD()
bdd.declare('x', 'y', 'z')
u = bdd.add_expr('(x /\ y) \/ ~ z')
bdd.dump('foo.pdf', [u])
if __name__ == '__main__':
dump_pdf_cudd()
此方法使用dd
,可以使用pip
和pip install dd
进行安装。可以找到文档here。查看(内部)模块dd._abc
可能也很有帮助(这是一种规范;名称“ abc”暗示Python中的abstract base classes)。
(纯Python可以解决较小的问题,而CUDD可以解决较大的问题)。
与该问题有关的遍历有两种:
这些将在下面分别讨论。
使用BDD时,深度优先遍历比呼吸优先遍历更为普遍。
对于dd.cudd
和dd.autoref
的接口,遍历为:
from dd import autoref as _bdd
def demo_bdd_traversal():
bdd = _bdd.BDD()
bdd.declare('x', 'y')
u = bdd.add_expr('x /\ y')
print_bdd_nodes(u)
def print_bdd_nodes(u):
visited = set()
_print_bdd_nodes(u, visited)
def _print_bdd_nodes(u, visited):
# terminal ?
if u.var is None:
print('terminal reached')
return
# non-terminal
# already visited ?
if u in visited:
return
# recurse
v = u.low
w = u.high
# DFS pre-order
print('found node {u}'.format(u=u))
_print_bdd_nodes(v, visited)
# DFS in-order
print('back to node {u}'.format(u=u))
_print_bdd_nodes(w, visited)
# DFS post-order
print('leaving node {u}'.format(u=u))
# memoize
visited.add(u)
if __name__ == '__main__':
demo_bdd_traversal()
在使用BDD(使用CUDD或类似的库)时,还需要考虑 Complemented edges。属性u.negated
提供了此信息。
函数dd.bdd.copy_bdd
是遍历BDD的纯Python示例。此功能直接通过dd.autoref
包装的“低级”界面来操作BDD,使其看起来与dd.cudd
相同。
脚本dd/examples/reachability.py
显示了如何以有限的步骤计算从哪些状态可以达到给定状态集。
与dd
相比,软件包omega
更方便开发基于BDD的与系统行为相关的算法。脚本omega/examples/reachability_solver
演示了使用omega
的可达性计算。
使用omega == 0.3.1
进行转发可达性的基本示例如下:
from omega.symbolic import temporal as trl
from omega.symbolic import prime as prm
def reachability_example():
"""How to find what states are reachable."""
aut = trl.Automaton()
vrs = dict(
x=(0, 10),
y=(3, 50))
aut.declare_variables(**vrs)
aut.varlist = dict(
sys=['x', 'y'])
aut.prime_varlists()
s = r'''
Init ==
/\ x = 0
/\ y = 45
Next ==
/\ (x' = IF x < 10 THEN x + 1 ELSE 0)
/\ (y' = IF y > 5 THEN y - 1 ELSE 45)
'''
aut.define(s)
init = aut.add_expr('Init', with_ops=True)
action = aut.add_expr('Next', with_ops=True)
reachable = reachable_states(init, action, vrs, aut)
n = aut.count(reachable, care_vars=['x', 'y'])
print('{n} states are reachable'.format(n=n))
def reachable_states(init, action, vrs, aut):
"""States reachable by `action` steps, starting from `init`."""
operator = lambda y: image(y, action, vrs, aut)
r = least_fixpoint(operator, init)
assert prm.is_state_predicate(r)
return r
def image(source, action, vrs, aut):
"""States reachable from `source` in one step that satisfies `action`."""
u = source & action
u = aut.exist(vrs, u)
return aut.replace_with_unprimed(vrs, u)
def least_fixpoint(operator, target):
"""Least fixpoint of `operator`, starting from `target`."""
y = target
yold = None
while y != yold:
yold = y
y |= operator(y)
return y
if __name__ == '__main__':
reachability_example()
将omega
与dd
进行比较:
omega
支持变量和常量,以及它们的整数值(相关模块为omega.symbolic.temporal
)。变量表示状态更改,例如x
和x'
。常量通过系统行为保持不变。
dd
仅支持布尔值变量(omega
使用布尔值变量代表整数值变量,因此通过dd
将谓词表示为BDD; bitblasting在模块omega.logic.bitvector
中完成)。
在omega.symbolic.fixpoint
中实现了几个定点运算符。这些运算符可用于模型检查或temporal synthesis。模块omega.logic.past
包含与符号模型检查(也称为temporal testers)相关的时间运算符的翻译。
可以找到here的omega
文档。
我在上面使用了术语“步骤”来表示一对连续状态,这些状态代表规范允许的状态变化。 Leslie Lamport的书Specifying Systems中介绍了TLA +语言和步骤,灵活灵活的变量和其他有用的概念。
https://github.com/johnyf/tool_lists/列出了一组正式的验证软件。
以我的经验,在Python级别上工作,仅在C级别上使用BDD管理器是一种有效的方法,可以使代码更可读,算法更清晰。