【完结】毕设全记录——JavaBDD的应用(布尔表达式的运算、CTL公式解析、模型检验)

这是一个鸽了很久的系列,本来是要记录完成本科毕设《状态机自动生成与图形化仿真系统研究与实现》的过程,因为使用的JavaBDD算是一个很老的包了,文档很少,而且这个领域的资料也很少,所以想着整理一下,方便后来人使用。但写了前三篇《开端》《探索 Graphviz》《探索 JavaBDD》之后,因为各种事情搁置了。
时光飞逝,到现在已经过了一年半的时间,从当时的本科毕设,到现在都开始做研究生毕设了😂本来是打算弃坑的,因为觉得它的价值随着时代变化已经越来越少了,但仍然偶尔能看到有朋友私信问我相关的问题。我意识到,虽然关注这个领域的人并不多,但可能一直都会有人怀着相同的疑问而无处寻得解答。因此,我决定用这一篇作为毕设记录的完结。

这里假设读者对JavaBDD、Graphviz已经有基本的了解,也已经看过前文了。本篇文章继续讲解JavaBDD的应用,布尔表达式的运算、CTL公式解析和模型检验。当然,还有不少其他的应用,但这里主要关注这三个。

ps:想要更好的阅读体验,请到简书->地址


1. 布尔表达式的运算

与其说是运算,不如说是让布尔表达式为真时,取值的情况。比如布尔表达式:A or B or C,计算的输出是<0:0, 1:0, 2:1><0:0, 1:1><0:1>,这里冒号前的 0、1 和 2 分别对应着 A、B 和 C,冒号后的 0 和 1 代表着真和假,每对尖括号意味着一种取值情况。因此,输出的意思是有三种情况使该布尔表达式成立:一是 A 和 B 为假,C 为真;二是 A 为假,B 为真,C真或假都可以;三是 A 为真,B 和 C 为真或假都可以。

如果不使用JavaBDD,怎么得到结果?可能有人说我肉眼看就能看出来。这样的话稍微复杂点,眼就要看花,比如(A or C)and ((B or not A) and (D or C)),估计得看几分钟才能得到结果。还有人说我让每个值取 0 和 1 两种情况,遍历所有结果。这比第一种方法好,但这样的复杂度是 O(2^n),太慢了,而且输出也比较复杂。

以布尔函数result = (A or C)and(B or A)为例,它包含 A、B 和 C 三个布尔变量输入,result 一个布尔变量输出。当使用电路验证模块处理这个例子时,会得到下表中的结果:

序号 A B C result
1 0 0 0 0
2 0 0 1 0
3 0 1 0 0
4 0 1 1 1
5 1 0 0 1
6 1 0 1 1
7 1 1 0 1
8 1 1 1 1

但是当使用布尔运算模块处理时,就会得到下图的结果了。仔细观察,会发现它们并不冲突,图中描述的情况与表中相吻合。前者将 result 和布尔变量输入的取值变化详细地展现出来了,而后者则以一种更简洁的情况描述了 result 取真值的情况,且情况 2 只需要 A 为真即可,对其他的变量取值没有要求。通过检查布尔函数可知,这也是符合逻辑的。

布尔运算模块的输出

1.1 逆波兰表达式

接下来说明对于布尔函数的运算是如何实现的。首先需要明确的是,其本质还是需要将结果布尔变量转化成BDD变量,然后通过JavaBDD来求取结果。但是,由于输入的布尔表达式可能会很复杂,比如包含一些空格和括号,无法被很容易地转化前文中的JavaBDD的形式。因此,首先需要考虑如何对原始的布尔函数进行处理,使其变成计算机能够理解的形式。

在这一点上,我从算术表达式求解的过程中得到了启发。日常生活中出现的算术表达式,如1+2-3*4/5,属于中序表达式的范畴,其中的运算符是在第一个和第二个操作数中间的。有一种求解它的思路是,先将其转化为逆波兰表达式(也称为后序表达式),然后再进行求解。

例如中序表达式(a+b)*c转化成逆波兰表达式后就是ab+c*,转化过程需要两个栈分别存放着变量和运算符,从左往右读入中序表达式,并将元素压入对应的栈。但运算符栈顶元素的优先级必须小于要压栈的运算符的优先级,否则在压栈前需要将运算符出栈并压入变量栈,直到满足要求。(需要注意的是,括号需要压入运算符栈,当遇到右括号时,重复将运算符栈的元素出栈并压入变量栈,直到遇到左括号,此时将左括号出栈即可。)当结束读入表达式时,再将运算符栈的元素依次出栈并压入变量栈。最终变量栈从栈底到栈顶的内容即为逆波兰表达式。

可能你读完上面这段话已经晕了,那么就看下面的动图吧😄

逆波兰表达式的转换

逆波兰表达式求值的步骤也很简单。准备一个空栈,从左至右读入表达式,遇到变量则压栈,遇到运算符则依次从栈中弹出两个变量,运算后继续入栈,直到最后栈中只剩下唯一的值,即为表达式的结果。 这部分实现的代码在网上有很多了。

像这样对表达式进行处理,算法的实现并不难,而且还能取出括号,更易于计算机理解。那么布尔表达式是否也能像这样被处理呢?答案是肯定的。每个布尔变量就像是算术表达式中的一个数字,每个逻辑运算符则像是加减乘除之类的运算符,使用JavaBDD对出栈的BDD变量进行运算,并将结果BDD变量压栈,就相当于对表达式进行计算的过程。流程图如下所示。

算法流程示意图

1.2 布尔表达式求值算法的设计与实现

布尔函数求值的算法主要分为三部分:第一部分对输入的字符串预处理,如去除多余的空格等,最终转化成List<String>类型的中序表达式;第二部分是对中序表达式处理,得到List<String>类型的逆波兰表达式;第三部分则是由逆波兰表达式得到布尔函数对应的 BDD 变量。第二部分的处理算法如下所示:

public static List<String> toReversePolishExpression
(List<String> middleOrderExpression) {
	Stack<String> symbolStack = new Stack<>(); // 存放符号的栈
// 使用List来作为存放变量和符号的栈,更加灵活
	List<String> num_sym_list = new ArrayList<>();
	for (String element : middleOrderExpression) {
		if (element.equals("(")) { symbolStack.push(element); }// 若为左括号则入栈
		else if (element.equals(")")) {
  	       while (!symbolStack.peek().equals("(")) {
            num_sym_list.add(symbolStack.pop());}
  	        symbolStack.pop();}// 将符号出栈直到栈顶是左括号,也将其出栈
		else if (isBooleanSymbol(element)) {
         	while (symbolStack.size() != 0 && Operation.getValue(symbolStack.peek()) >= Operation.getValue(element)) {
            	num_sym_list.add(symbolStack.pop());}
         	symbolStack.push(element);}
		else {num_sym_list.add(element); // 如果是变量的情况
     		//注意var_list是专门存放变量的,之后会对变量出现的次数进行统计 		
			var_list.add(element);}}
	// 处理完中序表达式后,若符号栈还有元素则将其插入变量栈
	while (symbolStack.size() != 0) { num_sym_list.add(symbolStack.pop());}
	return num_sym_list;
}

其中用到了isBooleanSymbol方法,该方法的作用是判断 String 类型的变量是否属于布尔运算符之一,使用正则表达式进行判断。具体代码如下:

public static Bo
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值