如何使用 call_with_depth_limit/3

问题描述

我试图在 SWI-Prolog 中使用 call_with_depth_limit/3 来实现迭代深化,但我不明白它是如何工作的,或者它行为不端。我有一个发生以下情况的示例:

?- call_with_depth_limit(mygoal,29,Result).
Result = 29 ;
Result = 25 ;
Result = 27 ;
Result = 27 ;
false.
?- call_with_depth_limit(mygoal,26,Result).
Result = depth_limit_exceeded ;
false.

根据文档,如果目标可以用极限最大递归或更少来证明,它应该会成功。在限制为 30 的第一次调用中,我们看到结果为 25,因此我希望以 26 的限制调用它会成功。 我在模块中使用了约束处理规则,以防那里可能有一些交互使其行为不端。

编辑:在玩弄伊莎贝尔的回答后,我想我明白它的行为了:

  • 它像往常一样运行深度优先搜索,但如果达到 Limit+1 深度,它就好像失败了一样。
  • 失败的分支计入结果。
  • 每次在成功回答后回溯时,它都会将 Result 重置为堆栈的当前深度。

看这个例子:

loop :- loop.

succeed(0).
succeed(N) :- N > 0,N1 is N - 1,succeed(N1).

fail(N) :- N > 0,fail(N1).
?- call_with_depth_limit(succeed(0),1000,Result).
Result = 1 ;
false.

?- call_with_depth_limit(fail(50);succeed(0),Result).
Result = 53 ;
false.

% It tries loop until Limit+1 and therefore this is the Result
?- call_with_depth_limit(loop;succeed(0),Result).
Result = 1001 ;
false.

% In the second result it has to unroll the stack from 100 before trying the next,so Result is 100.
?- call_with_depth_limit(loop;succeed(100);succeed(0),Result).
Result = 1001 ;
Result = 103 ;
false.

?- call_with_depth_limit(loop;succeed(0);succeed(0),Result).
Result = 1001 ;
Result = 3 ;
false.

% If it gets to the end,and it has reached Limit+1 since the last successful result it returns depth_limit_exceeded.
?- call_with_depth_limit(loop;succeed(100);succeed(0);loop,Result).
Result = 1001 ;
Result = 103 ;
Result = depth_limit_exceeded.

解决方法

我不相信 SWI-Prolog 甚至为 call_with_depth_limit/3 实现了自己的规范。至少我读过:

如果 Goal 可以在没有比 Limit 级别更深的递归的情况下被证明,则 call_with_depth_limit/3 成功,将 Result 绑定到证明期间使用的最深递归级别。否则,如果在证明过程中超过限制,则 Result 与 depth_limit_exceeded 统一...

暗示Result永远不会大于Limit。但是有了这个程序:

succeed_with_depth(3) :-
    succeed(3).
succeed_with_depth(1) :-
    succeed(1).

succeed(0).
succeed(N) :-
    N > 0,N1 is N - 1,succeed(N1).

我观察到(SWI-Prolog 7.6.4):

?- call_with_depth_limit(succeed_with_depth(N),5,Result).
N = 3,Result = 5 ;
N = 1,Result = 6 ;
false.

证明更浅的目标会导致更深的递归。超过限制但仍然成功的递归。

就我个人而言,阅读文档时,我希望在回溯时获得与直接调用 succeed_with_depth(1) 相同的深度:

?- call_with_depth_limit(succeed_with_depth(1),Result).
Result = 3 ;
false.

或者在 succeed_with_depth 的最外层添加 1 个深度用于回溯?那应该仍然给 Result = 4 而不是 6

编辑: 正如 rajashekar 在评论中指出的那样,在 succeed/1 的第一个子句中添加一个剪切会改变预期的意外行为。我认为这进一步表明 SWI-Prolog 的行为被破坏了:唯一的区别是切断了回溯将立即失败的选择。在任何后续计算中都没有实际更深的递归。

编辑 2:为了说明我为此设想的语义很简单,并且它实际上可以用于实现迭代深化,这里是一个小元- 足以从上面执行我的程序的解释器:

interpret_with_depth_limit(Goal,Limit,Result) :-
    interpret_with_depth_limit(Goal,Result).

interpret_with_depth_limit(_Goal,Current,depth_limit_exceeded) :-
    Current >= Limit,!.
interpret_with_depth_limit(Builtin,N,_Limit,N) :-
    builtin(Builtin),!,call(Builtin).
interpret_with_depth_limit((A,B),Result) :-
    !,interpret_with_depth_limit(A,ResultA),integer(ResultA),interpret_with_depth_limit(B,ResultB),integer(ResultB),Result is max(ResultA,ResultB).
interpret_with_depth_limit(Goal,Result) :-
    N1 is N + 1,N1 < Limit,clause(Goal,Body),interpret_with_depth_limit(Body,N1,Result).

builtin(true).
builtin(_ > _).
builtin(_ is _).

这不会在回溯过程中保留深度信息,因此回溯的行为与调用目标的更具体实例时相同:

?- interpret_with_depth_limit(succeed_with_depth(N),6,Result = 3 ;
false.

?- interpret_with_depth_limit(succeed_with_depth(3),Result).
Result = 5 ;
false.

?- interpret_with_depth_limit(succeed_with_depth(1),Result).
Result = 3 ;
false.

迭代深化,以正确的顺序(即首先找到最浅的证明)找到每个答案,然后:

call_succeedingdepth(Goal,Depth) :-
    between(1,infinite,Limit),Depth is Limit - 1,interpret_with_depth_limit(Goal,Depth).

测试:

?- call_succeedingdepth(succeed_with_depth(N),Depth).
N = 1,Depth = 3 ;
N = 3,Depth = 5 ;
% nontermination

我认为对于非终止无能为力;您通常会将其用于具有无限多个答案的目标。

,

我试图通过使用这个程序来了解 call_with_depth_limit/3 的工作原理:

%           a        <- 1st call
%         /   \
%        b     g     <- 2nd call
%       /
%      c             <- 3rd call
%     / \
%    g   d           <- 4th call
%        |
%        e           <- 5th call

arc(a,b).
arc(a,g).
arc(b,c).
arc(c,g).
arc(c,d).
arc(d,e).

path(X,X,[X]).
path(X,Z,[X|R]) :- arc(X,Y),path(Y,R).

获得的结果:

?- call_with_depth_limit(path(a,g,P),4,D).
P = [a,b,c,g],D = 4 ;
P = [a,D = 5 ;
false.

似乎答案是:

  • P = [a,D = 4 表示 4 次调用以获得第一个解。
  • P = [a,D = 5 表示 5 次调用以获得第二个解决方案(注意,在获得第二个解决方案之前,必须探索失败路径 [a,d,e] 并且第 5 个调用导致失败 - 这个事实证明了答案D = 5).

另一个查询:

?- call_with_depth_limit(path(a,3,D = 4 ;
false.

我们可以看到搜索在 4 次调用 (D = 4) 后回溯,但是获得解 [a,g] 所需的第四次调用导致失败(因为 depth_limit 为 3)并且确实不产生结果。

[编辑] 另一个场景:在这个新场景中,在谓词 path/3 的第一个子句中添加了一个剪切,以避免扩展已经是解决方案的路径(否则,搜索将在同一路径上再向下尝试一步,并在找到第二个解决方案之前以深度 5 失败)。

%           a        <- 1st call
%         /   \
%        b     g     <- 2nd call
%       /
%      c             <- 3rd call
%     /
%    g               <- 4th call
%
%                    <- 5th call

arc(a,g).

path(X,[X]) :- !.
path(X,R).

在这个新场景中,我们有:

?- call_with_depth_limit(path(a,D = 2.

我们观察到:

  • 在第二个场景中,找到第二个解决方案所达到的最深递归级别是 2。

  • 然而,在第一种情况下,找到第二个解决方案的最深递归级别是 5,因为在找到第二个解决方案之前,搜索必须尝试扩展路径 [a,g] 并探索路径 { {1}}(因此,解决方案证明过程中使用的最深递归级别 [a,e] 为 5)。

请务必注意,如 SWI-Prolog 文档中所述,[a,g] 绑定到证明过程中使用的最深递归级别特定强> 解决方案,不是找到此解决方案的级别

Result 如果 Goal 可以在没有比 Limit 级别更深的递归的情况下被证明, call_with_depth_limit/3 成功,将 Result 绑定到使用的最深递归级别 在证明过程中。否则,Result 与 depth_limit_exceeded 统一,如果 证明过程中超出限制,或者如果目标失败则整个谓词失败 不超过限制。

迭代深化深度优先搜索

call_with_depth_limit(:Goal,+Limit,-Result)找到一个最浅的解,不超过Goal

Limit

以下是一些示例(两种情况的答案相同):

ids(Goal,Limit) :-
    between(1,Depth),call_with_depth_limit(Goal,Depth,Result),Result \= depth_limit_exceeded,!.
,

难道不是第一个成功的结果需要深度为29,这显然过分了,然后就炸了?那么下一个需要深度为 25 的结果将永远不会被尝试。

,

根据the SWI documentation

深度限制由内部机械保护。这可能与基于理论模型计算的深度不同。 [...] 因此, call_with_depth_limit/3 可能仍然在理论上应该在有限时间内完成的程序上无限循环。

所以它的“深度”不同于基于理论模型计算的深度。