在Python中实现Prolog统一算法?回溯

时间:2018-03-04 22:48:39

标签: python algorithm prolog backtracking unification

我试图实施统一,但遇到问题......已经有十几个例子,但他们所做的只是浑水。我比开明更困惑:

http://www.cs.trincoll.edu/~ram/cpsc352/notes/unification.html

https://www.doc.ic.ac.uk/~sgc/teaching/pre2012/v231/lecture8.html [以下代码基于此介绍]

http://www.cs.bham.ac.uk/research/projects/poplog/paradigms_lectures/lecture20.html#representing

https://norvig.com/unify-bug.pdf

How can I implement the unification algorithm in a language like Java or C#?

Prolog的艺术......还有其他几个。 最大的问题是我无法清楚地说明问题所在。更多肮脏或狡猾的解释让我更加困惑。

作为一个良好的开端,遵循基于列表的表示似乎是一个好主意(例如在lispy案例中),即:

pred(Var, val)  =becomes=> [pred, Var, val] 
p1(val1, p2(val2, Var1)) ==> [p1, val1, [p2, val2, Var1]]
  

除了你如何表示自己的名单!即[H | T]

如果你能告诉我一个Python伪代码和/或更详细的算法描述或指向一个的指针,我很乐意。

我掌握的一些要点是需要在一般 - 统一和var-unification中分离代码,但后来我无法看到相互背叛的情况! ......等等。

作为旁注:我也很乐意提及你如何处理Backtracking的统一。我想我已经回归平方,但我知道在回溯时替换帧会发生一些事情。

添加了当前代码的答案。

http://www.igrok.site/bi/Bi_language.html

http://www.igrok.site/bi/TOC.html

https://github.com/vsraptor/bi/blob/master/lib/bi_engine.py

3 个答案:

答案 0 :(得分:10)

我将从Unification Theory快速总结Baader和Snyder关于Handbook of Automated Reasoning的章节:

条款是根据常量(以小写字母开头)和变量(以大写字母开头)构建的:

  • 一个没有参数的常量是一个术语:例如car
  • 一个带有术语作为参数的常量,一个所谓的函数应用程序,是一个术语。例如date(1,10,2000)
  • 变量是一个术语,例如Date(变量永远不会有参数)

替换是一个为变量分配术语的地图。在文献中,这通常写为{f(Y)/X, g(X)/Y}或带有箭头{X→f(Y), Y→g(X)}。将替换应用于术语会将每个变量替换为列表中的相应术语。例如。上面的替换应用于tuple(X,Y)会导致术语tuple(f(Y),g(X))

鉴于两个词st unifier 是一个使st相等的替换。例如。如果我们将替换{a/X, a/Y}应用于术语date(X,1,2000),我们会得到date(a,1,2000),如果我们将其应用于date(Y,1,2000),我们也会获得date(a,1,2000)。换句话说,(语法)相等date(X,1,2000) = date(Y,1,2000)可以通过应用统一符{a/X,a/Y}来解决。另一个更简单的统一者是X/Y。最简单的这种统一者称为最通用的统一者。出于我们的目的,我们可以将自己局限于搜索这样一个最通用的统一者,并且如果它存在,它就是唯一的(取决于某些变量的名称)。

Mortelli和Montanari(参见文章的第2.2节和那里的参考文献)给出了一组规则来计算这样一个最通用的统一者(如果存在的话)。输入是一组术语对(例如{f(X,b)= f(a,Y),X = Y}),并且输出是最通用的统一符,如果它存在或失败(如果它不存在)。在示例中,替换{a / X,b / Y}将使第一对相等(f(a,b) = f(a,b)),但第二对将是不同的(a = b不是真的)。​​

算法不确定地从集合中选择一个等式并对其应用以下规则之一:

  • 琐事:等式s = s(或X=X)已经相等,可以安全删除。
  • 分解:平等f(u,v) = f(s,t)被等号u=sv=t取代。
  • 符号冲突:等同a=bf(X) = g(X)会终止进程失败。
  • Orient:t=X形式的相等性t不是另一个变量,将其翻转为X=t,使变量位于左侧。
  • 发生检查:如果等式的格式为X=tt本身不是X,如果X出现在t内,我们就会失败。 [1]
  • 变量消除:我们有一个等式X=t,其中X t未出现t/X,我们可以将替换{X=s, Y=t, ...}应用于所有其他问题。

如果没有要申请的规则,我们最终会得到一组方程{f(a,X) = f(Y,b)},代表要应用的替代。

以下是一些例子:

  • {f(a,X,X) = f(a,a,b)}是统一的: 分解得到{a = Y,X = b}并翻转得到{Y = a,X = b}
  • a=a不统一: 分解以获得{a = a,X = a,X = b},消除X的琐碎,然后消除变量{a=b}以获取{f(X,X) = f(Y,g(Y))}并使用符号冲突失败
  • {X=Y, X=g(Y)}不统一: 分解得到X,消除变量{Y=g(Y)}得到X = f(X),失败并发生检查

即使算法是非确定性的(因为我们需要选择相同的工作),顺序无关紧要。因为您可以承诺任何订单,所以无需撤消您的工作并尝试使用其他等式。这种技术通常称为回溯,对于Prolog中的证明搜索是必需的,但不适用于统一本身。

现在,您只需为术语和替换选择合适的数据结构,并实施将替换应用于术语的算法以及基于规则的统一算法。

[1]如果我们尝试解决f(Y),我们会看到X需要采用f(Y) = f(f(Y))形式才能应用分解。这导致解决问题Y = f(Y)并随后解决问题f。由于左侧总是有一个 List<String> urlList = new ArrayList<>(); Document doc = Jsoup.connect("https://moz.com/blog-sitemap.xml").get(); Elements urls = doc.getElementsByTag("loc"); for (Element url : urls) { urlList.add(url.text()); } 小于右侧的应用,只要我们将一个术语看作有限结构,它们就不能相等。

答案 1 :(得分:5)

  

我比开明更困惑

去过那里,做到了。

注意:对于引用的任何源代码,我没有测试代码并且不能说它是有效的,它们是作为示例提供的,并且看起来足够正确我将加载它们并针对它们运行测试用例以确定它们的有效性

首先:如果您使用正确的术语,您将获得更好的搜索结果,使用backward chaining而不是回溯。例如backward-chaining/inference.py

第二:了解您的问题已列出三个单独的阶段。
1.统一算法
2.使用统一的后向链接
3.列表的数据结构。您不会将其实现为Python源代码,而是将其作为要传递给函数的文本。请参阅:cons

在进行反向链接之前,您应首先开发并完全测试统一。然后在创建列表数据结构之前完全开发并测试反向链接。然后完全测试您的列表数据结构。

第三:实施统一算法的方法不止一种   一个。您注意到使用转换规则的那个,或者在Baader和Snyder的Unification Theory中记为基于规则的方法,例如: 删除 分解等 湾我更喜欢在这个OCaml Unification Theory或Python example中给出的Baader和Snyder在example中以通过递归下降统一的算法 C。我已经看到一些使用排列,但目前找不到好的参考。

第四:根据个人经验,首先使用笔和纸了解每个阶段的工作原理,然后在代码中实现。

第五:再次从个人经验来看,有很多关于如何做到这一点的信息,但是数学和技术论文可能会让人感到困惑,因为许多人对自学者或太密集了。我建议您专注于查找源代码/数据结构的实现并使用它来学习。

第六次:将您的结果与实际工作代码进行比较,例如SWI-Prolog

在进入下一阶段之前,我不能强调你需要多少测试每个阶段,并确保你拥有一套完整的测试用例。

当我想学习如何用函数式语言编写这本书时,人工智能1 2 3The Programming Languages Zoo的书籍非常宝贵。必须为LispOCaml安装环境,但值得付出努力。

答案 2 :(得分:0)

到目前为止,这对我提出的所有情况都有效(除了一个需要进行检查的情况,我还没有完成):

def unify_var(self, var, val, subst):
#   print "var> ", var, val, subst

    if var in subst :   
        return self.unify(subst[var], val, subst)
    elif isinstance(val, str) and val in subst : 
        return self.unify(var, subst[val], subst)
    #elif (var occurs anywhere in x) then return failure
    else :
        #print "%s := %s" % (var, val)
        subst[var] = val ; return subst

def unify(self, sym1, sym2, subst):
    #print 'unify>', sym1, sym2, subst

    if subst is False : return False
    #when both symbols match
    elif isinstance(sym1, str) and isinstance(sym2, str) and sym1 == sym2 : return subst
    #variable cases
    elif isinstance(sym1, str) and is_var(sym1) : return self.unify_var(sym1, sym2, subst)
    elif isinstance(sym2, str) and is_var(sym2) : return self.unify_var(sym2, sym1, subst)
    elif isinstance(sym1, tuple) and isinstance(sym2, tuple) : #predicate case
        if len(sym1) == 0 and len(sym2) == 0 : return subst
        #Functors of structures have to match
        if isinstance(sym1[0], str) and  isinstance(sym2[0],str) and not (is_var(sym1[0]) or is_var(sym2[0])) and sym1[0] != sym2[0] : return False
        return self.unify(sym1[1:],sym2[1:], self.unify(sym1[0], sym2[0], subst))
    elif isinstance(sym1, list) and isinstance(sym2, list) : #list-case
        if len(sym1) == 0 and len(sym2) == 0 : return subst
        return self.unify(sym1[1:],sym2[1:], self.unify(sym1[0], sym2[0], subst))

    else: return False

失败案件应该失败:

OK: a <=> a : {}
OK: X <=> a : {'X': 'a'}
OK: ['a'] <=> ['a'] : {}
OK: ['X'] <=> ['a'] : {'X': 'a'}
OK: ['a'] <=> ['X'] : {'X': 'a'}
OK: ['X'] <=> ['X'] : {}
OK: ['X'] <=> ['Z'] : {'X': 'Z'}
OK: ['p', 'a'] <=> ['p', 'a'] : {}
OK: ['p', 'X'] <=> ['p', 'a'] : {'X': 'a'}
OK: ['p', 'X'] <=> ['p', 'X'] : {}
OK: ['p', 'X'] <=> ['p', 'Z'] : {'X': 'Z'}
OK: ['X', 'X'] <=> ['p', 'X'] : {'X': 'p'}
OK: ['p', 'X', 'Y'] <=> ['p', 'Y', 'X'] : {'X': 'Y'}
OK: ['p', 'X', 'Y', 'a'] <=> ['p', 'Y', 'X', 'X'] : {'Y': 'a', 'X': 'Y'}
 ================= STRUCT cases ===================
OK: ['e', 'X', ('p', 'a')] <=> ['e', 'Y', ('p', 'a')] : {'X': 'Y'}
OK: ['e', 'X', ('p', 'a')] <=> ['e', 'Y', ('p', 'Z')] : {'X': 'Y', 'Z': 'a'}
OK: ['e', 'X', ('p', 'a')] <=> ['e', 'Y', ('P', 'Z')] : {'X': 'Y', 'Z': 'a', 'P': 'p'}
OK: [('p', 'a', 'X')] <=> [('p', 'Y', 'b')] : {'Y': 'a', 'X': 'b'}
OK: ['X', 'Y'] <=> [('p', 'a'), 'X'] : {'Y': ('p', 'a'), 'X': ('p', 'a')}
OK: [('p', 'a')] <=> ['X'] : {'X': ('p', 'a')}
-----
FAIL: ['e', 'X', ('p1', 'a')] <=> ['e', 'Y', ('p2', 'Z')] : False
FAIL: ['e', 'X', ('p1', 'a')] <=> ['e', 'Y', ('p1', 'b')] : False
FAIL: [('p', 'a', 'X', 'X')] <=> [('p', 'a', 'a', 'b')] : False
(should fail, occurs) OK: [('p1', 'X', 'X')] <=> [('p1', 'Y', ('p2', 'Y'))] : {'Y': ('p2', 'Y'), 'X': 'Y'}
================= LIST cases ===================
OK: ['e', 'X', ['e', 'a']] <=> ['e', 'Y', ['e', 'a']] : {'X': 'Y'}
OK: ['e', 'X', ['a', 'a']] <=> ['e', 'Y', ['a', 'Z']] : {'X': 'Y', 'Z': 'a'}
OK: ['e', 'X', ['e', 'a']] <=> ['e', 'Y', ['E', 'Z']] : {'X': 'Y', 'Z': 'a', 'E': 'e'}
OK: ['e', 'X', ['e1', 'a']] <=> ['e', 'Y', ['e1', 'a']] : {'X': 'Y'}
OK: [['e', 'a']] <=> ['X'] : {'X': ['e', 'a']}
OK: ['X'] <=> [['e', 'a']] : {'X': ['e', 'a']}
================= FAIL cases ===================
FAIL: ['a'] <=> ['b'] : False
FAIL: ['p', 'a'] <=> ['p', 'b'] : False
FAIL: ['X', 'X'] <=> ['p', 'b'] : False