如何冻结变量列表的目标?

时间:2015-10-14 15:09:40

标签: prolog clpfd automaton

我的最终目标是制作automaton / 3的具体版本,如果传递给它的序列中有任何变量,它将冻结。即我不希望自动机实例化变量。

(fd_length / 3,if_ / 3等由其他人在此定义)。

首先,我对单个变量进行了验证测试:

var_t(X,T):-
  var(X) ->
  T=true;
  T=false.

这允许我实现:

if_var_freeze(X,Goal):-
  if_(var_t(X),freeze(X,Goal),Goal).

所以我可以这样做:

?-X=bob,Goal =format("hello ~w\n",[X]),if_var_freeze(X,Goal).

其行为与:

相同
?-Goal =format("hello ~w\n",[X]),if_var_freeze(X,Goal),X=bob.

如何扩展它以处理变量列表,以便在所有变量实例化后只调用一次目标?

在这种方法中,如果我有多个变量,我可以得到这种我不想要的行为:

?-List=[X,Y],Goal = format("hello, ~w and ~w\n",List),
if_var_freeze(X,Goal),
if_var_freeze(Y,Goal),X=bob.

hello, bob and _G3322
List = [bob, Y],
X = bob,
Goal = format("hello, ~w and ~w\n", [bob, Y]),
freeze(Y, format("hello, ~w and ~w\n", [bob, Y])).

我试过了:

freeze_list(List,Goal):-
  freeze_list_h(List,Goal,FrozenList),
  call(FrozenList).

freeze_list_h([X],Goal,freeze(X,Goal)).
freeze_list_h(List,Goal,freeze(H,Frozen)):-
  List=[H|T],
  freeze_list_h(T,Goal,Frozen).

其中的作用如下:

 ?- X=bob,freeze_list([X,Y,Z],format("Hello ~w, ~w and ~w\n",[X,Y,Z])),Y=fred.
 X = bob,
 Y = fred,
 freeze(Z, format("Hello ~w, ~w and ~w\n", [bob, fred, Z])) .

?- X=bob,freeze_list([X,Y,Z],format("Hello ~w, ~w and ~w\n",[X,Y,Z])),Y=fred,Z=sue.
Hello bob, fred and sue
X = bob,
Y = fred,
Z = sue .

这似乎没问题,但我无法将其应用于automaton / 3。 重申的目的是制作automaton / 3的具体版本,如果传递给它的序列中有任何变量,则会冻结。即我不希望自动机实例化变量。

这就是我所拥有的:

ga(Seq,G) :-
    G=automaton(Seq, [source(a),sink(c)],
                     [arc(a,0,a), arc(a,1,b),
                      arc(b,0,a), arc(b,1,c),
                      arc(c,0,c), arc(c,1,c)]).

max_seq_automaton_t(Max,Seq,A,T):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each member of seq
  maplist(=(false),Var_T_List),  %check that all are false i.e no  uninstaninated vars
  call(A),!,
  T=true.
max_seq_automaton_t(Max,Seq,A,T):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each member of seq
  maplist(=(false),Var_T_List),  %check that all are false i.e no uninstaninated vars
  \+call(A),!,
  T=false.
max_seq_automaton_t(Max,Seq,A,true):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each
  memberd_t(true,Var_T_List,true), %at least one var
    freeze_list_h(Seq,A,FrozenList),
  call(FrozenList),
  call(A).
max_seq_automaton_t(Max,Seq,A,false):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each
  memberd_t(true,Var_T_List,true), %at least one var
    freeze_list_h(Seq,A,FrozenList),
    call(FrozenList),
  \+call(A).

哪个不起作用,应该冻结以下目标,直到实例化X:

?- Seq=[X,1],ga(Seq,A),max_seq_automaton_t(3,Seq,A,T).
Seq = [1, 1],
X = 1,
A = automaton([1, 1], [source(a), sink(c)], [arc(a, 0, a), arc(a, 1, b), arc(b, 0, a), arc(b, 1, c), arc(c, 0, c), arc(c, 1, c)]),
T = true 

更新这就是我现在所拥有的,我认为它的工作方式与我最初的预期相同,但我正在消化@Mat所说的,如果这实际上是我想要的话。将在明天进一步更新。

goals_to_conj([G|Gs],Conj) :- 
  goals_to_conj_(Gs,G,Conj).

goals_to_conj_([],G,nonvar(G)).
goals_to_conj_([G|Gs],G0,(nonvar(G0),Conj)) :-
  goals_to_conj_(Gs,G,Conj).

max_seq_automaton_t(Max,Seq,A,T):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each member of seq
  maplist(=(false),Var_T_List),  %check that all are false i.e no uninstaninated vars
  call(A),!,
  T=true.
max_seq_automaton_t(Max,Seq,A,T):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each member of seq
  maplist(=(false),Var_T_List),  %check that all are false i.e no uninstaninated vars
  \+call(A),!,
  T=false.
max_seq_automaton_t(Max,Seq,A,T):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each
  memberd_t(true,Var_T_List,true), %at least one var
  goals_to_conj(Seq,GoalForWhen),
  when(GoalForWhen,(A,T=true)).
max_seq_automaton_t(Max,Seq,A,T):-
  Max #>=L,
  fd_length(Seq,L),
  maplist(var_t,Seq,Var_T_List), %find var_t for each
  memberd_t(true,Var_T_List,true), %at least one var
  goals_to_conj(Seq,GoalForWhen),
  when(GoalForWhen,(\+A,T=false)).

3 个答案:

答案 0 :(得分:4)

在我看来,你在Prolog方面取得了很大的进步。在这一点上,谨慎地继续前进是有道理的。原则上,您要求的所有东西都可以轻松解决。您只需要freeze/2的一般化,可以when/2

但是,让我们退后一步,更深入地考虑这里的实际情况。

声明地说,当我们陈述约束时,我们的意思是它拥有。我们并不是指"它仅在所有内容都被实例化时才成立",因为这会将约束减少到纯粹的检查器,从而导致生成和测试"做法。只要有可能,约束点就是 prune ,在很多情况下会导致搜索空间大大减少。

具体化约束完全相同。当我们发布一个确定的约束时,我们声明具体化成立。不仅在实例化所有内容的情况下,总是。关键在于(已确定的)约束可以在所有方向上使用。如果已经实现了具体化的约束,我们就会知道它。同样,如果它不能成立,我们就会知道它。如果可能出现这种情况,我们需要明确搜索解决方案,或确定不存在解决方案。如果我们想要坚持正在实施的约束,那很容易实现;等

然而,在所有情况下,我们都可以专注于约束的声明性语义,而不受诸如实例化和何时进行的逻辑外过程考虑。如果我回答了你的字面问题,它会让你更接近操作考虑因素,比实际需要或想要的更接近。

因此,我不会回答你的字面问题。但我会给你一个解决你的实际,潜在问题的方法。

重点是 reifiy automaton/3。约束条件本身不会修剪任何东西,只要它是开放的,无论正在实现的约束是否实际成立。只有当我们坚持正在被强化的约束时才会发生传播。

通过重新组合构成其分解的约束,可以很容易地验证automaton/3。这是一种方法,基于SWI-Prolog中免费提供的代码:

:- use_module(library(clpfd)).

automaton(Vs, Ns, As, T) :-
        must_be(list(list), [Vs,Ns,As]),
        include_args1(source, Ns, Sources),
        include_args1(sink, Ns, Sinks),
        phrase((arcs_relation(As, Relation),
                nodes_nums(Sinks, SinkNums0),
                nodes_nums(Sources, SourceNums0)), [[]-0], _),
        phrase(transitions(Vs, Start, End), Tuples),
        list_to_drep(SinkNums0, SinkDrep),
        list_to_drep(SourceNums0, SourceDrep),
        (   Start in SourceDrep #/\
            End in SinkDrep #/\
            tuples_in(Tuples, Relation)) #<==> T.


include_args1(Goal, Ls0, As) :-
        include(Goal, Ls0, Ls),
        maplist(arg(1), Ls, As).

list_to_drep([L|Ls], Drep) :-
        foldl(drep_, Ls, L, Drep).

drep_(L, D0, D0\/L).

transitions([], S, S) --> [].
transitions([Sig|Sigs], S0, S) --> [[S0,Sig,S1]],
        transitions(Sigs, S1, S).

nodes_nums([], []) --> [].
nodes_nums([Node|Nodes], [Num|Nums]) -->
        node_num(Node, Num),
        nodes_nums(Nodes, Nums).

arcs_relation([], []) --> [].
arcs_relation([arc(S0,L,S1)|As], [[From,L,To]|Rs]) -->
        node_num(S0, From),
        node_num(S1, To),
        arcs_relation(As, Rs).

node_num(Node, Num), [Nodes-C] --> [Nodes0-C0],
        { (   member(N-I, Nodes0), N == Node ->
              Num = I, C = C0, Nodes = Nodes0
          ;   Num = C0, C is C0 + 1, Nodes = [Node-C0|Nodes0]
          ) }.

sink(sink(_)).

source(source(_)).

请注意,只要T未知,就无法传播

我现在使用以下定义进行一些示例查询:

seq(Seq, T) :-
        automaton(Seq, [source(a),sink(c)],
                       [arc(a,0,a), arc(a,1,b),
                        arc(b,0,a), arc(b,1,c),
                        arc(c,0,c), arc(c,1,c)], T).

示例:

?- seq([X,1], T).

结果(省略):发布约束,不传播任何内容。

下一个例子:

?- seq([X,1], T), X = 3.
X = 3,
T = 0.

显然,在这种情况下,具体化的automaton/3约束。但是, reifying 约束当然一如既往地保留,这就是T=0在这种情况下的原因。

下一个例子:

?- seq([1,1], T), indomain(T).
T = 0 ;
T = 1.

哦,哦!这里发生了什么?如何约束既真实又错误?这是因为我们没有看到在此示例中实际发布的所有约束。使用call_residue_vars/2查看全部真相。

事实上,尝试更简单的例子:

?- call_residue_vars(seq([1,1],0), Vs).

在这种情况下仍需要满足的未决残留约束是:

_G1496 in 0..1,
_G1502#/\_G1496#<==>_G1511,
tuples_in([[_G1505,1,_G1514]], [[0,0,0],[0,1,1],[1,0,0],[1,1,2],[2,0,2], [2,1,2]])#<==>_G825,
tuples_in([[_G831,1,_G827]], [[0,0,0],[0,1,1],[1,0,0],[1,1,2],[2,0,2],[2,1,2]])#<==>_G826,
_G829 in 0#<==>_G830,
_G830 in 0..1,
_G830#/\_G828#<==>_G831,
_G828 in 0..1,
_G827 in 2#<==>_G828,
_G829 in 0..1,
_G829#/\_G826#<==>0,
_G826 in 0..1,
_G825 in 0..1

所以,上面只有 if 这些约束,据说仍然是 flounder ,也是如此。

这是一个辅助定义,可帮助您标记剩余的有限域变量。这个例子足够了:

finite(V) :-
        fd_dom(V, L..U),
        dif(L, inf),
        dif(U, sup).

我们现在可以粘贴剩余程序(由CLP(FD)约束组成),并使用label_fixpoint/1标记域有限的变量:

?- Vs0 = [_G1496, _G1499, _G1502, _G1505, _G1508, _G1511, _G1514, _G1517, _G1520, _G1523, _G1526],
  _G1496 in 0..1,
  _G1502#/\_G1496#<==>_G1511,
  tuples_in([[_G1505,1,_G1514]], [[0,0,0],[0,1,1],[1,0,0],[1,1,2],[2,0,2], [2,1,2]])#<==>_G825,
  tuples_in([[_G831,1,_G827]], [[0,0,0],[0,1,1],[1,0,0],[1,1,2],[2,0,2],[2,1,2]])#<==>_G826,
  _G829 in 0#<==>_G830, _G830 in 0..1,
  _G830#/\_G828#<==>_G831, _G828 in 0..1,
  _G827 in 2#<==>_G828, _G829 in 0..1,
  _G829#/\_G826#<==>0, _G826 in 0..1, _G825 in 0..1,
  include(finite, Vs0, Vs),
  label(Vs).

请注意,我们无法在原始程序中直接使用标记,即无法执行:

?- call_residue_vars(seq([1,1],0), Vs), <label subset of Vs>.

因为call_residue_vars/2也会将内部变量带到表面,虽然它们分配了一个域并且看起来像普通的CLP(FD)变量,但并不意味着直接参与任何标记。

相比之下,残差程序可以毫无问题地用于进一步推理,实际上意味着以这种方式使用。

在这个具体的例子中,在上面的情况下标记其域仍然是有限的变量之后,仍然存在一些约束 。它们的形式如下:

tuples_in([[_G1487,1,_G1496]], [[0,0,0],[0,1,1],[1,0,0],[1,1,2],[2,0,2],[2,1,2]])#<==>_G1518

练习:是否会间接地将原始查询(即seq([1,1],0)无法容纳?)

所以,总结一下:

  1. 约束条件本身并不会导致正在实施的约束的传播。
  2. 约束条件通常会让您检测到无法保留的约束。
  3. 一般来说,CLP(FD)传播必然不完整,即我们无法确定是否因为我们的查询成功而存在解决方案。
  4. 如果域名是有限的,
  5. labeling/2可以让您了解是否有具体的解决方案。
  6. 要查看所有待处理约束,请将查询包装在call_residue_vars/2
  7. 只要待处理的约束仍然存在,它只是一个条件答案。
  8. 推荐:为了确保不存在任何挣扎的约束,请将您的查询包装在call_residue_vars/2中,并在顶层查找任何残留约束。

答案 1 :(得分:2)

考虑使用广泛使用的谓词when/2(有关详细信息,请考虑阅读SICStus Prolog manual page on when/2)。

请注意,原则上您可以像这样实施freeze/2

freeze(V,Goal) :-
   when(nonvar(V),Goal).

答案 2 :(得分:1)

您正在实施的内容在我看来是以下变体:

delayed_until_ground_t(Goal,T) :-
   (  ground(Goal)
   -> (  call(Goal)
      -> T = true
      ;  T = false
      )
   ;  T = true,  when(ground(Goal),once(Goal))
   ;  T = false, when(ground(Goal),  \+(Goal))
   ).

延迟目标可以是一个非常好的功能,但要注意永远拖延的危险。

请务必阅读并理解@mat关于call_residue_vars/2的上述答案!