重新排序目标后,Prolog不会终止

时间:2019-03-04 14:56:38

标签: prolog backtracking failure-slice non-termination

我目前正在通过“立即学习Prolog”示例进行研究,对于一个exercise,如果我对一条规则的更改很小,那么我的KB会耗尽本地堆栈。这是KB:

byCar(auckland,hamilton). 
byCar(hamilton,raglan). 
byCar(valmont,saarbruecken). 
byCar(valmont,metz). 

byTrain(metz,frankfurt). 
byTrain(saarbruecken,frankfurt). 
byTrain(metz,paris). 
byTrain(saarbruecken,paris). 

byPlane(frankfurt,bangkok). 
byPlane(frankfurt,singapore). 
byPlane(paris,losAngeles). 
byPlane(bangkok,auckland). 
byPlane(singapore,auckland). 
byPlane(losAngeles,auckland).

travel(X,Y) :- byCar(X,Y).
travel(X,Y) :- byTrain(X,Y).
travel(X,Y) :- byPlane(X,Y).

及相关规则:

travel(X,Y) :- travel(X,Z), travel(Z,Y).

这是有问题的查询,它用完了堆栈:

?- travel(valmont,losAngeles).

但是如果我将规则更改为

travel(X,Y) :- travel(Z,Y), travel(X,Z).

然后它起作用。

如果我跟踪查询,我会像这样被迅速卡住:

   Redo: (17) travel(raglan, _6896) ? creep
   Call: (18) byPlane(raglan, _6896) ? creep
   Fail: (18) byPlane(raglan, _6896) ? creep
   Redo: (17) travel(raglan, _6896) ? creep
   Call: (18) travel(raglan, _6896) ? creep
   Call: (19) byCar(raglan, _6896) ? creep
   Fail: (19) byCar(raglan, _6896) ? creep
   Redo: (18) travel(raglan, _6896) ? creep
   Call: (19) byTrain(raglan, _6896) ? creep
   Fail: (19) byTrain(raglan, _6896) ? creep
   Redo: (18) travel(raglan, _6896) ? creep
   Call: (19) byPlane(raglan, _6896) ? creep
   Fail: (19) byPlane(raglan, _6896) ? creep
   Redo: (18) travel(raglan, _6896) ? creep
...

但是我不明白为什么。难道它不只是了解raglan是一个终端站,因此必须回溯一个级别吗?

谢谢!

编辑:我使用SWI Prolog

编辑:逐步解决后,我发现了问题。 就拉格伦而言,根本没有任何规则可言。因此,在尝试byPlane, byTrain, byCar之后,它将再次尝试travel(raglan, X)(最后一条规则的第一个目标),从而循环播放。 但我看不出其他规则会更好。

2 个答案:

答案 0 :(得分:4)

您需要弄清楚“有效”的含义。实际上,谓词travel/2的两个版本都不会终止。但是碰巧找到了针对高度特定查询的解决方案。

现在问?- travel(hamilton, losAngeles).两者都循环。

因此,您的修复程序仅适用于某些查询,而不适用于其他查询。没有更可靠的出路吗?

通常,很难预测Prolog产生的答案替换的非常精确的顺序。您将必须模拟Prolog采取的每一个微小步骤。

另一方面,有一个非常相关的概念称为(通用)终止,因为它独立于程序中的许多细节(例如事实的显示顺序),因此更容易预测。查询通用终止的最简单方法是在查询结束时添加目标 false

但是您甚至可以在 1 任意位置进一步添加目标 false 。这样的修改程序称为。无论您如何插入 false ,以下条件都有效:

  

如果故障切片未终止,那么您的原始程序也不会终止。

现在考虑travel/2的两个变体的失败切片:

travel(X,Y) :- false, byCar(X,Y).
travel(X,Y) :- false, byTrain(X,Y).
travel(X,Y) :- false, byPlane(X,Y).
travel(X,Y) :- travel(X,Z), false, travel(Z,Y).

以及您的其他版本:

travel(X,Y) :- false, byCar(X,Y).
travel(X,Y) :- false, byTrain(X,Y).
travel(X,Y) :- false, byPlane(X,Y).
travel(X,Y) :- travel(Z,Y), false, travel(X,Z).

在这两者中,根本不考虑XY!因此,这两个参数不会影响终止。因此,这两个版本都不会终止。也就是说,它们从不终止。

现在将这个结论与更传统的观察痕迹的方法进行比较。尽管失败切片使我们能够做出一般性结论(“ ...永不终止”),但是特定的跟踪只能向您显示特定执行的详细信息。

要解决此问题,您需要更改可见部分的内容。我的建议是使用closure/3。那就是:

travel(X, Y) :-
   closure(connexion, X, Y).

connexion(X,Y) :- byCar(X,Y).
connexion(X,Y) :- byTrain(X,Y).
connexion(X,Y) :- byPlane(X,Y).

或者使用更通用的path/4


1实际上,这仅在纯单调程序中起作用。您的程序就是其中之一

答案 1 :(得分:1)

很显然,在这种情况下,目标排序非常重要。如您所知,您的第一个表述允许通过一个假设的另一个城市Z来找到从拉格兰到任何地方的另一个假设连接,该城市的不存在永远不会得到证明,因为您一直在寻找无限递归。的确,跟踪是您最好的朋友,但这对您而言并不容易。您还必须考虑所有一个或两个参数都没有约束的情况。

您的第二种说法根本不是更好,只是在不同情况下会失败:

travel(losAngeles, valmont).
ERROR: Out of local stack

我建议通过区分直接连接和多站旅程来使您的逻辑更安全:

connection(X,Y) :- byCar(X,Y).
connection(X,Y) :- byTrain(X,Y).
connection(X,Y) :- byPlane(X,Y).

travel(X,Y) :- connection(X,Y).
travel(X,Y) :- connection(X,Z), travel(Z,Y).

目标顺序现在无关紧要,因为travel始终需要存在一些物理连接(而不是递归)才能继续。

这也使您更容易记录您想要的旅程(对吗?):

connection(X,Y, car(X,Y))   :- byCar(X,Y).
connection(X,Y, train(X,Y)) :- byTrain(X,Y).
connection(X,Y, plane(X,Y)) :- byPlane(X,Y).

travel(X,Y,[Part])       :- connection(X,Y,Part).
travel(X,Y,[Part|Parts]) :- connection(X,Z,Part), travel(Z,Y,Parts).

?- travel(valmont, losAngeles, Journey).
Journey = [car(valmont, saarbruecken), train(saarbruecken, paris), plane(paris, losAngeles)] 

对于没有有效行程的情况:

travel(losAngeles, valmont, Journey).
false.