人工智能——基于谓词逻辑的归结原理

实验二 归结原理实验

一、实验目的

加深学生对归结原理进行定理证明过程的理解,掌握基于谓词逻辑的归结过程中子句变换过程、替换与合一算法和归结策略等重要环节,进一步了解实现机器自动定理证明的步骤。

二、实验内容

对于任意一阶谓词逻辑描述的定理,给出基于归结原理的证明过程。如输入:

A1 : ("x)(P(x)®(Q(x) ÙR(x)))

 A2 : ("x)(P(x) Ù S(x))

        G :  ($x)(S(x)ÙR(x))

要证明:G是A1和A2的逻辑结果。

三、实验环境

推荐使用C++语言,编译器推荐使用Microsoft Visual Studio Professional 2019(version=16.3.8)。

四、实验原理

归结原理是一种推理规则。从谓词公式转化为子句集的过程中看出,在子句集中子句之间是合取关系,其中只要有一个子句不可满足,则子句集就不可满足。若一个子句集中包含空子句,则这个子句集一定是不可满足的。归结原理就是基于这一认识提出来的。

应用归结原理进行定理证明的过程:

设要被证明的定义表示为:

A1∧A2∧…∧An→B

(1)首先否定结论B,并将否定后的公式¬B与前提公式集组成如下形式的谓词公式:G= A1∧A2∧…∧An∧¬B。

(2)求谓词公式G的子句集S。

(3)应用归结原理,证明子句集S的不可满足性。

五、实验步骤

步骤一 设计谓词公式的存储结构,即内部表示,注意对全称量词"x和存在量词$x可采用其他符号代替。

原符号

替换符号

全称量词∀

@

存在量词∃

#

析取符号∨

|

合取符号∧

&

取反符号¬

~

蕴含符号→

>

例如A1 : ("x)(P(x)®(Q(x) ÙR(x))),输入的时候要替换为A1:(@x)(P(x)>(Q(x)&R(x)))。

核心代码如下所示:

typedef string Formula; //谓词公式
typedef vector<string> SubsentenceSet;
typedef map<string,int> SubsentenceMap;
typedef string::iterator FormulaIter;  //迭代器
typedef string::reverse_iterator FormulaRevIter;  //反向迭代器
typedef vector<string>::iterator VectorIter;
typedef vector<string>::reverse_iterator VectorReIter;
map<string, string> m1;
int clause[20]; // 用这个来记录被删除的子句
/*("x)(P(x)®(Q(x) ÙR(x))),输入的时候要替换为A1:(@x)(P(x)>(Q(x)&R(x)))。*/
// 公式符号定义
const char EQ = '#';          // 存在量词符号
const char UQ = '@';          // 全称量词符号
const char IMPLICATION = '>'; // 蕴含符号
const char NEGATION = '~';    // 否定符号
const char CONJUNCTION = '&'; // 合取符号
const char DISJUNCTION = '|'; // 析取符号
const char CONSTANT_ALPHA[] = {'a', 'b', 'c', 'd', 'e',
                               'i', 'j', 'k'};

步骤二 变换子句集,可按以下过程变换,变换过程的核心代码如下所示:

1、消去蕴含连接词。

FormulaRevIter GetBeginOfFormula(FormulaRevIter position,FormulaRevIter rend){
	//考虑用栈来找到蕴含前件
	int location;
	bool isFormula=true;//判断是不是蕴含前件是一个整体还是一个公式,公式是true,整体是false
	stack<char> seek; //创建一个栈
	++position; //逆向回退1
	while(position!=rend){ //循环没有结束
		if(*position.base()==')') //如果是‘)’入栈
			seek.push(*position.base());
		if(seek.size()>1)
			isFormula=false;    //说明括号不止一个,是个整体
		if(*position.base()=='(') //如果是'('出栈
			seek.pop();
		if(seek.empty()==1){ //如果栈为空,说明已经找完了蕴含前件了
			if(isFormula)
				return position; //这里需要注意一下 正向迭代器与反向迭代器之间的关系
			else
				return position-1;
		}
		position++;
	
	}
	return rend;//要么返回第一个字符的位置
}

// 1、消去蕴含连接词。
Formula& RemoveImplication(Formula& f)
{
	FormulaIter iter;
	while((iter = find(f.begin(), f.end(), IMPLICATION))   //找蕴含符号
					!= f.end()) {
		*iter = DISJUNCTION; // 将蕴含符号替换为析取符号
		FormulaRevIter revIter(iter);
        revIter =
			GetBeginOfFormula(revIter, f.rend()); // 查找蕴含前件   //发f,rend()正数第一个字符
        iter = revIter.base()-1;// 反向迭代器到正向迭代器转换需要减1
		f.insert(iter, NEGATION);	// 在前件前面插入否定
	}
	return f;
}

2、将否定符号移到紧靠谓词的位置。

Formula& MoveNegation(Formula& f)
{
	FormulaIter iter = find(f.begin(), f.end(), NEGATION);
	while(iter != f.end()) {
		if(*(iter+1) == '(') {// 否定不是直接修饰谓词公式,需要内移
			// 否定符号修饰着带量词的谓词公式
			if(*(iter+2) == EQ || *(iter+2) == UQ) {
				// 量词取反
				*(iter+2) == EQ ? *(iter+2) = UQ : *(iter+2) = EQ;

				string leftDonePart(f.begin(), iter+5);
				// cout<<leftDonePart<<endl;
				// 移除否定符号
				leftDonePart.erase(find(leftDonePart.begin(),
										leftDonePart.end(), NEGATION));

				string rightPart(iter + 5, f.end());
				// 否定内移
				rightPart.insert(rightPart.begin(), NEGATION);
				// 递归处理右部分
				MoveNegation(rightPart);
				string(leftDonePart + rightPart).swap(f);
				return f;
			}else {	// 修饰着多个公式,形如~(P(x)|Q(x))
				iter = f.insert(iter+2, NEGATION);	// 内移否定符号
				while(1) {
					iter = FindFormula(iter, f.end());         //用来找是否有谓词公式?
					assert(iter != f.end() && "No Predicate Formula!");  //断言
					FormulaIter iter2 = FindPairChar(                   //找到整个谓词公式
										iter, f.end(), '(', ')');
					++iter2;
					if(IsConnector(*iter2)) {  //判断是不是合取符号和析取符号
						*iter2 == DISJUNCTION ? *iter2 = CONJUNCTION
											  : *iter2 = DISJUNCTION;
						iter = f.insert(iter2+1, NEGATION);   //插上否定
					}else
						break;
				}
				f.erase(find(f.begin(), f.end(),
								NEGATION));// 清除原否定符号
				return MoveNegation(f);
			}
		}else if(*(iter+1) == NEGATION) {// 两个否定,直接相消
			f.erase(iter, iter + 2);
			return MoveNegation(f);	// 重新处理
		}else {
			// iter = find(iter + 1, f.end(), NEGATION);   //这行代码是错误的
			//这里应该还要加上递归,不然就会消除掉第一个否定产生错误
			string leftDonePart(f.begin(), iter + 1);
            string rightPart(iter + 1, f.end());
            MoveNegation(rightPart);
            string(leftDonePart + rightPart).swap(f);
            return f;
		}
	}
	return f;
}

3、适当改名使量词间不含同名指导变元,对变元标准化。

Formula& StandardizeValues(Formula& f)
{
	set<char> checkedAlpha;
	FormulaIter iter = FindQuantifier(f.begin(), f.end());
	while(iter != f.end()) {
		char varName = *++iter;	// 获取变量名
		if(checkedAlpha.find(varName) == checkedAlpha.end()) {  //如果set里面没有该变量名字,就加入
			checkedAlpha.insert(varName);
		}else {	// 变量名冲突了,需要改名
			// 获取新名子
			char newName = FindNewLowerAlpha(checkedAlpha);
			checkedAlpha.insert(newName); //这里在源代码的基础上加入一个 要加入新生成的变量名字
			// 查找替换右边界
			FormulaIter rightBorder = FindPairChar(
									iter + 2, f.end(), '(', ')');
			// 将冲突变量名替换为新的名子
			*iter = newName;
			replace(iter, rightBorder, varName, newName);
			iter = rightBorder;	// 移动到新的开始
		}
		iter = FindQuantifier(iter, f.end());
	}
	// 调试
	// for (set<char>::iterator it = checkedAlpha.begin(); it != checkedAlpha.end(); it++)
	// 	cout << *it << " ";
	return f;
}

4、化为前束范式。

Formula& TransformToPNF(Formula& f)

{

     FormulaIter iter = FindQuantifier(f.begin(), f.end());

     if(iter == f.end())

            return f;

     else if(iter-1 == f.begin()) { // 量词已经在最前面

            iter += 3;

            string leftPart(f.begin(), iter);

            string rightPart(iter, f.end());

            TransformToPNF(rightPart);  // 递归处理右部分

            (leftPart + rightPart).swap(f);

     }else {    // 量词在内部,需要提到前面

            string quantf(iter-1, iter+3);   // 保存量词

            f.erase(iter-1, iter+3);      // 移除量词

            f.insert(f.begin(), quantf.begin(), quantf.end());

            return TransformToPNF(f);     // 继续处理

     }

     return f;

}

5、消去存在量词。

Formula& RemoveEQ(Formula& f)
{
	set<char> checkedAlpha;
	FormulaIter eqIter = find(f.begin(), f.end(), EQ);  //找到存在量词的位置
	if(eqIter == f.end()) return f;
	FormulaRevIter uqIter = find(FormulaRevIter(eqIter), f.rend(), UQ);  //反向迭代器还要减一
	// cout<<"我在测试此时的是啥"<<*uqIter<<endl;//debug
	if(uqIter == f.rend()) { // 该存在量词前没有任意量词
		char varName = *(eqIter + 1);
		char newName = GetNewConstantAlha(f);
		auto rightBound = FindPairChar(eqIter + 3, f.end(), '(', ')');
		assert(rightBound != f.end());
		replace(eqIter + 3, rightBound, varName, newName); // 常量化
		f.erase(eqIter - 1, eqIter + 3);	// 移除存在量词
	}else {
		// 记录公式中已经存在的字母,maybe
		copy_if(f.begin(), f.end(),
			inserter(checkedAlpha, checkedAlpha.begin()),
			ptr_fun<int, int>(isalpha));
		const char oldName = *(eqIter+1);  //eqIter是存在量词的位置,这个得到函数值
		// 准备任意量词的函数来替换该存在量词
		const char funcName = FindNewLowerAlpha(checkedAlpha);
		string funcFormula;
		funcFormula = funcFormula + funcName
						+ '(' + *(uqIter-1) + ')';
		f.erase(eqIter - 1, eqIter + 3);	// 移除存在量词
		ReplaceAlphaWithString(f, oldName, funcFormula);  //在这里产生替换
	}
	// RemoveOuterBracket(f); //这个函数不知道要干啥,感觉没有必要要了,不知道啥含义,我看网上人家也没要
	return RemoveEQ(f);	// 递归处理
}

6、消去全称量词。

Formula& RemoveUQ(Formula& f)
{
	FormulaIter uqIter = find(f.begin(), f.end(), UQ);
	while(uqIter != f.end()) {
		uqIter = f.erase(uqIter-1, uqIter+3); // 直接移除全称量词
		uqIter = find(uqIter, f.end(), UQ); // 继续扫描
	}
	// RemoveOuterBracket(f); //这个函数不知道要干啥,感觉没有必要要了,不知道啥含义,我看网上人家也没要
	return f;
}

7、化为Skolem标准型。

Formula& TransformToSkolem(Formula& f)
{
	RemoveEQ(f);
	cout<<"6.1消去存在量词:"<<endl;
	cout<<f<<endl; //测试
	RemoveUQ(f);
	cout<<"6.2消去全称量词:"<<endl;
	cout<<f<<endl; //测试
	return f;
}

8、消去合取词,以子句为元素组成一个集合S

需要考虑子句、子句集的存储结构的设计。

步骤三 选择并设计归结策略,常用的归结策略有:

删除策略、支持集策略、线性归结策略、输入归结策略、单元归结策略、锁归结策略、祖先过滤型策略等。

步骤四 实现归结算法,并在其中实现合一算法,使用归结原理进行定理证明,要求采用归结反演过程,即:

1、先求出要证明的命题公式的否定式的子句集S;

2、然后对子句集S(一次或者多次)使用归结原理;

3、若在某一步推出了空子句,即推出了矛盾,则说明子句集S是不可满足的,从而原否定式也是不可满足的,进而说明原公式是永真的。

合一算法及核心代码如下:

1、置k=0,Sk=S, σk =ε;

2、若Sk只含有一个谓词公式,则算法停止, σk就是最一般合一;

3、求Sk的差异集Dk;

4、若中存在元素xk和tk ,其中xk是变元, tk是项且xk不在tk中出现,则置Sk +1=Sk{tk/ xk} σk =ε然后转Step2;

5、算法停止,S的最一般合一不存在。

Sustitutes MGU(const FormulaNamepace::Subsentence& subsent1,

                          const FormulaNamepace::Subsentence& subsent2)

{

     pair<Subsentence, Subsentence> w = { subsent1, subsent2 };

     Sustitutes mgu;

     while(w.first != w.second) { // w未合一

            // 找不一致集

            auto iter1 = FindPredicate(w.first.begin(), w.first.end());

            auto iter2 = FindPredicate(w.second.begin(), w.second.end());

            while(iter1 != w.first.end() && iter2 != w.second.end()) {

                   if(*iter1 != *iter2)

                          break;

                   iter1 = FindPredicate(iter1 + 1, w.first.end());

                   iter2 = FindPredicate(iter2 + 1, w.second.end());

            }

            // 找到不一致集合

            if(iter1 != w.first.end() && iter2 != w.second.end()) {

                   string item1 = GetPredicate(iter1,  w.first.end());

                   string item2 = GetPredicate(iter2,  w.second.end());

                   // 不允许置换有嵌套关系

                   if(StrStr(item1, item2) != item1.end() ||

                          StrStr(item2, item1) != item2.end()) {

                                 throw ResolutionException("cannot unifier");

                   }

                   // 只允许常量替换变量

                   if(!IsConstantAlpha(*iter1))

                          item1.swap(item2);

                   // 更新置换,然后置换子句集

                   Sustitutes sustiSet = { make_pair(item1, item2) };

                   mgu = ComposeSustitutes(mgu, sustiSet);

                   Substitution(w.first, mgu);

                   Substitution(w.second, mgu);

            }

            else {     // 两子句不可合一

                   throw ResolutionException("cannot unifier");

            }

     }

     return mgu;

}

步骤五 编写代码,调试程序。

六、实验要求

根据归结原理编写程序(编程语言不限),要求给出如下过程:

1  求子句集:

         (1) ¬P(x) ÚQ(x)

         (2) ¬P(y) ÚR(y)

         (3)P(a)

         (4)S(a)

         (5) ¬S(z) Ú ¬ R(z)    (¬G)

2  归结:

        (6)R(a)         [(2),(3), σ1={a/y}]

        (7) ¬ R(a)       [(4),(5), σ2 ={a/z}]

        (8)Nil          [(6),(7)]

代码运行结果1
代码运行结果1
水平浸透法+删除策略
                                

 

 

 

代码运行结果2
代码运行结果2
支持集策略

 

        该代码是参考了老师给的部分函数以及网上的大佬写的,尽管数据结构等都是仿写,但是自己也将参考的代码都理解了一遍。网上有大佬写的支持集策略,但是有一点点的小bug,比如说在寻找合一的时候不能把本次子句全部遍历完,我在此基础上改善了一下,同时,我也写了水平浸透法+删除策略的归结方法,这个算法也是小缺陷,比如说在删除的时候,目前算法只考虑了删除被别的子句类含的子句,而永真等也应该删除的子句没有去实现,希望接下来会有时间去实现。

        由于很多代码都是参考别的大佬的,所以这里就不粘贴源码出来了。本次实验收获良多。

 

 

 

  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值