Prolog手册或自定义标签

时间:2016-03-28 10:28:15

标签: prolog clpfd labeling

我目前正在为Prolog的平面规划问题编写解决方案,并且在标签部分存在一些问题。

当前的问题是我的约束被发布但是当我启动标签时,需要永远找到解决方案。我想引入一些启发式方法。

我的问题是,如何手动标记我的变量?我担心在定义像这样的clpfd变量之后:

X in Xinf..Xsup

并限制它,如果我这样做:

    fd_sup(X, Xmax),
    X = Xmax,
...

在我的自定义标签中,我不会使用Prolog的回溯功能来测试X域的其他值。我错了吗 ?

另外,有没有比编写自定义标签程序更明智的标记变量的方法?我对启发式的想法包括尝试替换变量域的极值(如max(X),min(X),max(X-1),min(X-1)等...)

希望你能帮助我:)。

5 个答案:

答案 0 :(得分:6)

编写自定义标注程序并不困难,而且对于大多数实际问题,最终还是需要一个,以便结合特定问题的启发式方法。

标签程序的两个主要组成部分是

  1. 变量选择:从所有剩余的(即尚未实例化的)问题变量中选择一个以供下一步考虑。
  2. 价值选择分支:通过回溯,通过以(通常)互补的方式减少所选变量的域来探索两个或多个替代子问题。
  3. 使用此方案,默认标签程序可写为

    label(Xs) :-
        ( select_variable(X, Xs, Xs1) ->
             branch(X),
             label(Xs1)
        ;
             true    % done, no variables left
        ).
    
    select_variable(X, [X|Xs], Xs).     % 'leftmost' strategy
    
    branch(X) :- indomain(X).
    

    您现在可以重新定义select_variable/3以实施“首次失败”等技术,并重新定义branch/1以尝试不同顺序的域值。只要您确保branch/1在回溯时枚举所有X的域值,您的搜索就会完整。

    有时你想首先尝试一个域值(比如一个启发式建议的域值),但是,如果它不好,则不要立即提交另一个值。 让我们说,就像在您的示例中,您想首先尝试最大域值。你可以把它写成

    branch(X) :-
        fd_sup(X, Xmax),
        (
             X = Xmax           % try the maximum
        ;
             X #\= Xmax         % otherwise exclude the maximum
        ).
    

    由于这两种情况是互补的并涵盖了X的所有可能值,因此您的搜索仍然完整。但是,由于第二种选择,branch/1现在可以成功使用未实例化的X,这意味着您必须在标记过程中确保不会从列表中丢失此变量。一种可能性是:

    label(Xs) :-
        ( select_variable(X, Xs, Xs1) ->
             branch(X),
             ( var(X) -> append(Xs1, [X], Xs2) ; Xs2=Xs1 ),
             label(Xs2)
        ;
             true    % done, no variables left
        ).
    

答案 1 :(得分:4)

首先,始终尝试内置启发式方法。 ff通常是一个很好的策略。

对于自定义标记策略,通常最简单的方法是首先将域转换为列表,然后重新排序列表,然后使用member/2使用新订单分配域的值。

一个好的建筑物黑色是dom_integers/2,将有限 CLP(FD)域与整数列表相关联:

:- use_module(library(clpfd)).

dom_integers(D, Is) :- phrase(dom_integers_(D), Is).

dom_integers_(I)      --> { integer(I) }, [I].
dom_integers_(L..U)   --> { numlist(L, U, Is) }, Is.
dom_integers_(D1\/D2) --> dom_integers_(D1), dom_integers_(D2).

您的特定策略很容易在这样的有序整数列表中表达,将这些整数与第二个列表相关联,其中值按您描述的顺序出现:

outside_in([]) --> [].
outside_in([I]) --> [I].
outside_in([First|Rest0]) --> [First,Last],
        { append(Rest, [Last], Rest0) },
        outside_in(Rest).

示例查询和结果:

?- phrase(outside_in([1,2,3,4]), Is).
Is = [1, 4, 2, 3] ;
false.

将此与fd_dom/2dom_integers/2相结合,得到(省略X以外的变量的绑定):

?- X in 10..20,
   fd_dom(X, Dom),
   dom_integers(Dom, Is0),
   phrase(outside_in(Is0), Is),
   member(X, Is).
X = 10 ;
X = 20 ;
X = 11 ;
X = 19 ;
X = 12 ;
X = 18 ;
etc.

member/2保留了非确定性。

答案 2 :(得分:4)

确保将标签策略与其他传播区分开来。这两个方面目前在你的问题中有点混杂。

在SWI-Prolog中,有一个名为clpfd:contracting/1的谓词。它执行您所描述的内容:它尝试来自域边界的值,并删除可被视为不一致的值,即,已知不存在解决方案的值。

因此,如果您有变量列表Vs,则可以尝试:clpfd:contracting(Vs),看看是否有帮助。

请注意,这也会大大减慢搜索速度,但另一方面,在尝试任何标记之前,还可以显着减少搜索空间!

答案 3 :(得分:3)

为了补充其他答案(一个对比标记和传播,一个显示专用标签方法),我现在解决这个问题的另一个非常重要的方面:

很多时候,当初学者抱怨他们的代码速度时,事实证明他们的代码实际上甚至没有终止!在这种情况下,更高的效率无济于事。

因此,这个答案指向您首先确保您的关系的实际终止

确保终止CLP(FD)计划的最佳方法是将它们分成两部分:

  1. 第一个,称为核心关系,只是发布所有约束。
  2. 第二个使用labeling/2来执行实际的搜索
  3. 你在程序中完成了这个吗?如果没有,请做。完成此操作后,请确保核心关系,例如solution/2(其中参数为:表示任务实例的术语,以及要标记的变量列表)通过查询终止通用:

    ?- solution(Instance, Vs), false.

    如果终止,然后以下终止:

    ?- solution(Instance, Vs), label(Vs), false.

    当然,在较大的任务中,您没有机会真正见证后一个查询的终止,但很有可能见证第一个查询的终止,因为设置约束通常比实际获得更快更快一个单一的解决方案。

    因此,请测试您的核心关系是否终止!

答案 4 :(得分:1)

这会跟进this previous answer by @mat

如果您有更多的CPU周期需要刻录,请按照this previous answer中的定义尝试shave_zs/1

shave_zs/1类似辅助库谓词clpfd:contracting/1。然而,与contracting/1不同,所有值都是"用于抓取"而不仅仅是边界处的值。 YMMV!