当前位置: 主页 > 技术方案

归结原理 人工智能-VisualStudio《实验二归结原理实验》实验步骤步骤

发布时间:2023-06-19 09:07   浏览次数:次   作者:佚名

实验二归结原理实验

一、实验目的

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

二、实验内容

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

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 SubsentenceSet;
typedef map SubsentenceMap;
typedef string::iterator FormulaIter;  //迭代器
typedef string::reverse_iterator FormulaRevIter;  //反向迭代器
typedef vector::iterator VectorIter;
typedef vector::reverse_iterator VectorReIter;
map 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 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<

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

Formula& StandardizeValues(Formula& f)
{
	set 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::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 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<(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消去存在量词:"<

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 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,比如说在寻找合一的时候不能把本次子句全部遍历完,我在此基础上改善了一下,同时,我也写了水平浸透法+删除策略的归结方法,这个算法也是小缺陷,比如说在删除的时候,目前算法只考虑了删除被别的子句类含的子句归结原理 人工智能,而永真等也应该删除的子句没有去实现归结原理 人工智能,希望接下来会有时间去实现。

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