prolog prolog-toplevel transitive-closure meta-predicate

prolog - Definición de cierre transitivo reflexivo



prolog-toplevel transitive-closure (1)

Es útil, pero en mi opinión aún no es ideal porque no puedo cortar caminos duplicados en el momento de su creación.

Considere, con el gráfico completo K_n :

n_complete(N, Es) :- numlist(1, N, Ns), phrase(pairs(Ns), Es). adjacent(Edges, X, Y) :- member(edge(X, Y), Edges). pairs([]) --> []. pairs([N|Ns]) --> edges(Ns, N), pairs(Ns). edges([], _) --> []. edges([N|Ns], X) --> [edge(X,N),edge(N,X)], edges(Ns, X).

La siguiente consulta ahora tiene un tiempo de ejecución superexponencial, aunque el cierre realmente se puede encontrar en tiempo polinomial:

?- length(_, N), n_complete(N, Es), portray_clause(N), time(findall(Y, closure0(adjacent(Es), 1, Y), Ys)), false. 1. 16 inferences, 0.000 CPU in 0.000 seconds (97% CPU, 1982161 Lips) 2. 54 inferences, 0.000 CPU in 0.000 seconds (98% CPU, 4548901 Lips) 3. 259 inferences, 0.000 CPU in 0.000 seconds (97% CPU, 14499244 Lips) 4. 1,479 inferences, 0.000 CPU in 0.000 seconds (100% CPU, 16219595 Lips) 5. 9,599 inferences, 0.000 CPU in 0.000 seconds (100% CPU, 27691393 Lips) 6. 70,465 inferences, 0.002 CPU in 0.002 seconds (100% CPU, 28911161 Lips) 7. 581,283 inferences, 0.020 CPU in 0.020 seconds (100% CPU, 29397339 Lips) 8. 5,343,059 inferences, 0.181 CPU in 0.181 seconds (100% CPU, 29488001 Lips) 9. 54,252,559 inferences, 1.809 CPU in 1.808 seconds (100% CPU, 29994536 Lips) 10. 603,682,989 inferences, 19.870 CPU in 19.865 seconds (100% CPU, 30381451 Lips)

Sería fantástico si una forma más eficiente de determinar el cierre también pudiera expresarse con este meta-predicado.

Por ejemplo, normalmente uno simplemente usaría el algoritmo de Warshall para calcular el cierre en tiempo cúbico, con un código similar a:

node_edges_closure(Node, Edges, Closure) :- warshall_fixpoint(Edges, [Node], Closure). warshall_fixpoint(Edges, Nodes0, Closure) :- findall(Y, (member(X, Nodes0), adjacent(Edges, X, Y)), Nodes1, Nodes0), sort(Nodes1, Nodes), ( Nodes == Nodes0 -> Closure = Nodes0 ; warshall_fixpoint(Edges, Nodes, Closure) ).

Rendimiento (con todos los inconvenientes en comparación con el closure0/3 declarativo closure0/3 ):

?- length(_, N), n_complete(N, Es), portray_clause(N), time(node_edges_closure(1, Es, Ys)), false. 1. % 16 inferences, 0.000 CPU in 0.000 seconds (75% CPU, 533333 Lips) 2. % 43 inferences, 0.000 CPU in 0.000 seconds (85% CPU, 1228571 Lips) 3. % 69 inferences, 0.000 CPU in 0.000 seconds (85% CPU, 1769231 Lips) 4. % 115 inferences, 0.000 CPU in 0.000 seconds (89% CPU, 2346939 Lips) 5. % 187 inferences, 0.000 CPU in 0.000 seconds (91% CPU, 2968254 Lips) 6. % 291 inferences, 0.000 CPU in 0.000 seconds (92% CPU, 3548780 Lips) 7. % 433 inferences, 0.000 CPU in 0.000 seconds (95% CPU, 3866071 Lips) 8. % 619 inferences, 0.000 CPU in 0.000 seconds (96% CPU, 4268966 Lips) 9. % 855 inferences, 0.000 CPU in 0.000 seconds (97% CPU, 4500000 Lips) 10. % 1,147 inferences, 0.000 CPU in 0.000 seconds (98% CPU, 4720165 Lips) etc.

Muchos predicados utilizan esencialmente alguna forma de cierre transitivo, solo para descubrir que la terminación también debe abordarse. ¿Por qué no resolver esto de una vez y para siempre con el closure0/3 :

:- meta_predicate closure0(2,?,?). :- meta_predicate closure(2,?,?). :- meta_predicate closure0(2,?,?,+). % internal closure0(R_2, X0,X) :- closure0(R_2, X0,X, [X0]). closure(R_2, X0,X) :- call(R_2, X0,X1), closure0(R_2, X1,X, [X1,X0]). closure0(_R_2, X,X, _). closure0(R_2, X0,X, Xs) :- call(R_2, X0,X1), non_member(X1, Xs), closure0(R_2, X1,X, [X1|Xs]). non_member(_E, []). non_member(E, [X|Xs]) :- dif(E,X), non_member(E, Xs).

¿Hay casos en que esta definición no se puede utilizar para implementar el cierre transitivo?

¿Por qué dif / 2?

Para responder al comentario de @WouterBeek en detalle: dif/2 o iso_dif/2 son ideales, ya que pueden mostrar o señalar problemas potenciales. Sin embargo, en las implementaciones actuales, el ciclo de nivel superior a menudo oculta los problemas reales. Considere el closure0(/_^_^true,a,b) la closure0(/_^_^true,a,b) que ciertamente es bastante problemático en sí mismo. Al usar los siguientes sistemas, el problema real no es visible directamente.

| ?- closure0(/_^_^true,a,b). % SICStus yes ?- closure0(/_^_^true,a,b). % SWI true ; true ; true ...

Ambos bucles de nivel superior no muestran lo que realmente queremos ver: las limitaciones que cuelgan. En SICStus necesitamos una pseudovariable para producir alguna sustitución, en SWI, la consulta debe ser envuelta con call_residue_vars/2 . De esta forma, se muestran todas las variables que tienen restricciones adjuntas.

| ?- closure0(/_^_^true,a,b), Alt=t. % SICStus Alt = t ? ; Alt = t, prolog:dif(_A,a), prolog:dif(b,_A) ? ; Alt = t, prolog:dif(_A,a), prolog:dif(_B,_A), prolog:dif(_B,a), prolog:dif(b,_B), prolog:dif(b,_A) ... ?- call_residue_vars(closure0(/_^_^true,a,b),Vs). % SWI Vs = [] ; Vs = [_G1744, _G1747, _G1750], dif(_G1744, a), dif(b, _G1744) ; Vs = [_G1915, _G1918, _G1921, _G1924, _G1927, _G1930, _G1933], dif(_G1915, a), dif(b, _G1915), dif(_G1921, _G1915), dif(_G1921, a), dif(b, _G1921) ...