You are right that 1\/5
would be optimal pruning in this case.
However, for efficiency reasons, CLP(FD) systems typically maintain only so-called bounds consistency for arithmetic constraints, and in general do not remove interior elements from domains even when some of them cannot participate in solutions.
Bounds consistency, in the finite case, means that there are solutions where the variable assumes the lower and upper boundary of the domain. In this case, there are solutions for A=1
and A=5
.
Notice that these are the only solutions in this concrete case, but in general, there are also solutions with interior points in analogous larger instances, for example:
?- [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.
The good news though is that the number of such solutions only grows logarithmically in the size of the domain:
?- 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.
This is in contrast to other cases, like X mod 2 #= 0
, where the number of solutions grows linearly in the size of the domain of X
(and thus exponentially in the length of its decimal representation), and it is thus not feasible to explicitly prune the domain.
Thus, as a feasible workaround, you can use label/1
to obtain concrete solutions, and then use in/2
constraints to restrict the operands to their concretely admissible domains:
:- 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).
Your example:
?- [A,B] ins 1..5, A*B#=5, stricter_domains([A,B]).
A in 1\/5,
A*B#=5,
B in 1\/5.
contracting/1
? – Balk