SWI-Prolog CLPFD

Я новичок в прологе для программирования ограничений. У меня проблема с CLPFD, которая не уменьшает домен, как я ожидаю. Это, вероятно, очень просто.

 [A,B] ins 1..5,A*B#=5.

Я ожидаю, что он уменьшит домен A и B до

1\/5

Но он просто дает

A in 1..5,
A*B#=5,
B in 1..5.

Любые предложения будут оценены.

Ответ 1

Хотя этот ответ настроен на как реализовано в , идея/метод переносится.

<Предварительно > : - use_module (библиотека (clpfd)).

Здесь мы можем уменьшить размеры домена до начала полного перечисления:

shave_zs(Zs) :-
   maplist(flag_zs_shave_z(F,Zs), Zs),
   once((var(F) ; ground(Zs) ; shave_zs(Zs))).

flag_zs_shave_z(Flag, Zs, Z) :-
   (  fd_size(Z, sup)
   -> true                                    % never shave the infinite
   ;  fd_dom(Z, Z_dom),
      phrase(dom_integers_(Z_dom), Z_vals),
      maplist(flag_zs_z_val(Flag,Zs,Z), Z_vals)
   ).

flag_zs_z_val(Flag, Zs, Z, Z_val) :-
   (  \+ call_with_inference_limit((Z #= Z_val,labeling([],Zs)), 1000, _)
   -> Z #\= Z_val,
      Flag = true
   ;  true
   ).

Мы используем грамматику dom_integers_//1, как определено на странице руководства SWI-Prolog clpfd:

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).

Примеры запросов:

?- [A,B] ins 1..5,  A*B #= 5,  (Shaved = false ; Shaved = true, shave_zs([A,B])).
Shaved = false, A*B #= 5, A in 1..5, B in 1..5 ;
Shaved =  true, A*B #= 5, A in 1\/5, B in 1\/5.

?- [A,B] ins 1..10, A*B #= 10, (Shaved = false ; Shaved = true, shave_zs([A,B])).
Shaved = false, A*B #= 10, A in 1..10      , B in 1..10 ;
Shaved =  true, A*B #= 10, A in 1..2\/5\/10, B in 1..2\/5\/10.

Ответ 2

Вы правы, что 1\/5 будет оптимальной обрезкой в ​​этом случае.

Однако по соображениям эффективности системы CLP (FD) обычно поддерживают только так называемую согласованность границ для арифметических ограничений и вообще не удаляют внутренние элементы из доменов, даже если некоторые из них не могут участвовать в решениях.

Согласованность границ в конечном случае означает, что существуют решения, в которых переменная принимает нижнюю и верхнюю границы области. В этом случае существуют решения для A=1 и A=5.

Обратите внимание, что это единственные решения в этом конкретном случае, но, как правило, существуют также решения с внутренними точками в аналогичных больших экземплярах, например:

?- [A,B] ins 1..10, A*B#=10, label([A,B]).
A = 1,
B = 10 ;
A = 2,
B = 5 ;
A = 5,
B = 2 ;
A = 10,
B = 1.

Хорошей новостью является то, что количество таких решений логарифмически растет только в размере домена:

?- length(_, Exp), N #= 2^Exp, [A,B] ins 1..N,A*B#=N,
   findall(., label([A,B]), Ls), length(Ls, L),
   writeln(Exp-L), false.
0-1
1-2
2-3
3-4
4-5
5-6
6-7
7-8
etc.

Это отличается от других случаев, таких как X mod 2 #= 0, где число решений растет линейно по размеру области X (и, следовательно, экспоненциально по длине десятичного представления), и, таким образом, нецелесообразно явно обрезать домен.

Таким образом, в качестве возможного обходного пути вы можете использовать label/1 для получения конкретных решений, а затем использовать ограничения in/2, чтобы ограничить операнды их конкретными допустимыми доменами:

:- use_module(library(clpfd)).

stricter_domains(Vs) :-
        findall(Vs, label(Vs), Sols0),
        transpose(Sols0, Sols),
        maplist(list_to_domain, Sols, Ds),
        maplist(in, Vs, Ds).

list_to_domain([L|Ls], Dom) :- foldl(dom_disj, Ls, L, Dom).

dom_disj(D0, I, D0\/I).

Ваш пример:

?- [A,B] ins 1..5, A*B#=5, stricter_domains([A,B]).
A in 1\/5,
A*B#=5,
B in 1\/5.