satz

1. 相关数据结构

变量名 注释
NB_VAR 算例变元的数量
NB_CLAUSE 算例子句的数量
sat[x][y] 二维数组存放算例,例如:sat[x]就是第x个子句,子句末尾用NONE(-1)标识。注意sat的一维下标从0开始,即第一个子句是sat[0];假如sat[x][y]存放某个变元z,如果z为正变元就存放它本身,如果为负变元,实际存放的是abs(z) + NB_VAR - 1
clause_length[i] 第i个子句的长度
clause_state[i] 第i个子句的状态
var_state[i] 变元i的状态
var_rest_value[tested_var] 变元tested_var剩余的赋值
node_neg_in[i] 负变元i存在于那些子句当中
node_pos_in[i] 正变元i存在于那些子句当中
INIT_NB_CLAUSE 初始变元的数量
nb_neg_clause_of_length2 编号为var的负变元所在子句中二元子句的数量
nb_neg_clause_of_length3 编号为var的负变元所在子句中三元子句的数量
nb_pos_clause_of_length2 编号为var的正变元所在子句中二元子句的数量
nb_pos_clause_of_length3 编号为var的正变元所在子句中三元子句的数量
reduce_if_positive_nb[tested_var] 给变元tested_var赋值True时,及单子句传播之后长度变短子句的数量但不包括变短为单子句的数量,即只统计变短后长度>=2的子句数量
reduce_if_negative_nb[tested_var] 给变元tested_var赋值False时,及单子句传播之后长度变短子句的数量但不包括变短为单子句的数量,即只统计变短后长度>=2的子句数量
IMPLIED_LITS 存储up探测过程中没有出现unsat时通过单子句传播已经赋值过的变元
TESTED_VAR_STACK 存储两分支探测都正常的变元
CLAUSE_STACK 存储被移除的子句,即某个变元赋值导致成为真子句那些子句
MY_UNITCLAUSE_STACK 只在examine、examine1函数中使用,与CLAUSE_STACK作用一致
MANAGEDCLAUSE_STACK 存储因变元赋值导致子句缩短的子句的编号
MY_MANAGEDCLAUSE_STACK 只在examine、examine1函数中使用,与MANAGEDCLAUSE_STACK作用一致
saved_clause_stack[chosen_var] 存储up探测出来变元后当前CLAUSE_STACK中的数量,用于产生冲突后回溯
saved_managedclause_stack[chosen_var] 存储up探测出来变元后当前MANAGEDCLAUSE_STACK中的数量,用于产生冲突后回溯

2. 问题

  1. search_redundence函数中,旧子句包含新子句时,移除旧子句,假如说旧子句是1 2 3 4,新子句是1 2 3时不会有问题吗?
    不会,因为新子句1 2 3满足时,旧子句一定会满足,新子句1 2 3不满足时,旧子句也一定不会满足,解的一致性并没有发生改变。
  2. 对于PROP0探测的变元计算W(x) W(-x)值时跟论文上不一样,在代码中计算的方式为:
int get_resolvant_nb(int clause) {
	int *lits;
	int lit, var, i, resolvant_nb = 0;

	lits = sat[clause];
	for (lit = *lits; lit != NONE; lit = *(++lits)) {
		if (positive(lit)) {
			var = lit;
			if (var_state[var] == ACTIVE)
				resolvant_nb += (nb_neg_clause_of_length2[var] * WEIGTH) +
				nb_neg_clause_of_length3[var];
		}
		else {
			var = get_var_from_lit(lit);
			if (var_state[var] == ACTIVE)
				resolvant_nb += (nb_pos_clause_of_length2[var] * WEIGTH) +
				nb_pos_clause_of_length3[var];
		}
	}
	return resolvant_nb;
}
//给tested_var赋值True
for clause in MY_MANAGEDCLAUSE_STACK:
	if (clause_length[clause] == 2){
		reduce_if_positive_nb[tested_var] += get_resolvant_nb(clause);		
	}	
//给tested_var赋值False
for clause in MY_MANAGEDCLAUSE_STACK:
	if (clause_length[clause] == 2){
		reduce_if_negative_nb[tested_var] += get_resolvant_nb(clause);	
	}	
/*
该代码中的计算方式是论文中的改进方式,论文中的那种计算方式会涉及到小数的运算,其原理都差不多。
*/

^412822
3. build_sat_instance函数中的下面这个地方的代码还是不明白其意图

for (i = 0; i<NB_CLAUSE; i++) {
	plit = sat[i];
	length = clause_length[i];
	if (length == 1) push(i, UNITCLAUSE_STACK);
	if (search_redundence(plit) != NEW_CLAUSE_REDUNDANT) {
		if (add_resolvant(plit) == NONE) return NONE;
		else set_link(i);

	}
	else clause_state[i] = PASSIVE;//该分支是plit为冗余子句时的分支
}
/*
该代码块的作用是清除冗余子句,把归结之后长度<=3的两个子句进行归结。
*/

3. 与自己代码的区别

  1. 李老师代码读入算例的时候就对子句中出现重复变元,相反变元的情况都做了处理,并且对出现冗余子句情况也做了处理。
  2. 自己代码的大框架与李老师代码差不多,主要区别在于变元的探测与选择上,即代码中的choose_and_instantiate_variable_in_clause()函数
    1. 进行PROP41、PROP31、PROP0探测之前,李老师会对对编号为var的负变元(其所在的子句不包含二元子句和三元子句)进行赋true值,对编号为var的正变元(其所在的子句不包含二元子句和三元子句)进行赋false值
    2. 李老师的代码并不是一开始就是将属于PROP41、PROP31、PROP0三个集合的变元筛出来,然后再用三个循环对其中变元做up探测,而是每轮都从第一个变元往后遍历,前两个循环分别满足PROP41、PROP31条件的变元就会进行up探测,如果是双分支变元就放入TESTED_VAR_STACK中。如果PROP41、PROP31探测结束,TESTED_VAR_STACK中的变元数目还是<\T(代码中是常量10),就继续进行PROP0探测,即最后一个循环,但进入循环之前会把TESTED_VAR_STACK清空
    3. 对于PROP41、PROP31条件探测出来的双分支变元,李老师代码中计算其W(x) W(-x)值就是reduce_if_positive_nb[tested_var]、reduce_if_negative_nb[tested_var]的值,即MY_MANAGEDCLAUSE_STACK中分别所存的子句的数量
    4. 对于PROP0探测出来的双分支变元,代码中计算其W(x) W(-x)值的方法与论文上并不一样,[[李老师satz代码阅读笔记#^412822|是对MY_MANAGEDCLAUSE_STACK中所有长度为2的子句中的变元的相反变元所在的二元子句的数量乘以5加上所在的三元子句的数量进行求和]]
  3. 产生冲突后回溯的方式,李老师代码中回溯的效率要比自己的高

3. 伪代码

读取cnf文件:
	将读取进来的每个子句做如下操作:
		1、变元按变元的编号升序排列
		2、去除重复的变元
		3、如果该子句是个重言式,则丢弃该子句
	记录变元存在于那些子句中
	找出算例中的单子句
	对算例进行删除冗余子句、归结子句操作

back:
	回溯到上一个决策的变元的位置
	并反转决策变元的赋值
	
choose_and_instantiate_variable_in_clause:
	 遍历所有自由变元(var):
		 统计编号为var的负变元所在子句中二元子句的数量和三元子句的数量
		 if 编号为var的负变元(其所在的子句不包含二元子句和三元子句):
			 对编号为var的变元赋true值
			 删除所有满足的子句
			 continue
		 统计编号为var的正变元所在子句中二元子句的数量和三元子句的数量
		 if 编号为var的正变元(其所在的子句不包含二元子句和三元子句):
			 对编号为var的变元赋true值
			 删除所有满足的子句
			 continue
		对变元var进行PROP41探测:
			如果是单分支则直接进行赋值
			如果是双分支都没有产生冲突,则将该变元记录在TESTED_VAR_STACK中,并记录下对该变元赋值时导致子句长度减少的个数,即计算w(var)、w(-var)
			如果两个分支都产生了冲突,则执行back操作,并返回函数
	如果len(TESTED_VAR_STACK)<T:
		遍历所有自由变元(var):
			对变元var进行PROP31探测:
				如果是单分支则直接进行赋值
				如果是双分支都没有产生冲突,则将该变元记录在TESTED_VAR_STACK中,并记录下对该变元赋值时导致子句长度减少的个数,即w(var)、w(-var)
				如果两个分支都产生了冲突,则执行back操作,并返回函数
	如果len(TESTED_VAR_STACK)<T:
		清除TESTED_VAR_STACK中存储的变元
		遍历所有自由变元(var):
			对变元var进行PROP0探测:
				如果是单分支则直接进行赋值
				如果是双分支都没有产生冲突,则将该变元记录在TESTED_VAR_STACK中,并计算w(var)、w(-var)(注意这里的计算方法跟PROP41、PROP31不一样,见3.2.4)
				如果两个分支都产生了冲突,则执行back操作,并返回函数
	遍历TESTED_VAR_STACK中的变元,并挑选出H(var)值最大的变元choose_var
	对变元choose_var(决策变元)进行赋值操作

unitclause_process:
	进行单子句传播:
		如果产生冲突,执行back操作
		算例中不存在单子句,返回函数

main:
	读取cnf文件
	do {
		if (当前算例中不存在单子句)
			choose_and_instantiate_variable_in_clause();//挑选决策变元并赋值
		unitclause_process();//单子句传播
	} while ((是否还存在未翻转的变元) && (!(满足的子句的数目==算例中子句的数目)));
posted @ 2025-03-12 12:18  seonwee  阅读(34)  评论(0)    收藏  举报