2

Firstly, the clp(fd) documentation mentions:

In modern Prolog systems, arithmetic constraints subsume and supersede low-level predicates over integers. The main advantage of arithmetic constraints is that they are true relations and can be used in all directions. For most programs, arithmetic constraints are the only predicates you will ever need from this library.

Secondly, on a previously asked question, it was mentioned that include/3 is incompatible with clp(fd).

Does that mean that only clp(fd) operators and clp(fd) predicates can be used when writing prolog with the clp(fd) library?

Furthermore, for example, why is include/3 incompatible with clp(fd)? Is it because it does not use clp(fd) operators? To use include/3 in clp(fd) code, would one need to rewrite a version that uses clp(fd) operators and constraints?

false
  • 10,264
  • 13
  • 101
  • 209
nich
  • 108
  • 3
  • 9
  • Have you seen [Understanding CLP(FD) Prolog code of N-queens problem](https://stackoverflow.com/a/53412988/1243762), in particular the statement near the very end, `those predicates generate constraints that are maintained internally` ? It should give you some more needed knowledge. – Guy Coder Jan 11 '23 at 13:25

2 Answers2

1

why is include/3 incompatible with clp(fd)?

?- X = 1, include(#\=(1),[0,X,2],Xs), X = 1.
   X = 1,
   Xs = [0,2].     % looks good
?-        include(#\=(1),[0,X,2],Xs), X = 1.
   false, unexpected.
?-        include(#\=(1),[0,X,2],Xs).   % generalization
   Xs = [0,X,2],
   X in inf..0\/2..sup
;  unexpected. % missing second answer

So, (#\=)/2 works in this case only if it is sufficiently instantiated. How can you be sure it is? Well, there is no direct safe test. And thus you will get incorrect results in certain cases. As long as these examples fit on a single line, it is rather easy to spot the error. But with a larger program, this is practically impossible. Because of this, constraints and include/3 are incompatible.

A way out would be to produce instantiation errors in cases with insufficient instantiation, but this is pretty hairy in the context of clpfd. Other built-in predicates like (=\=)/2 do this, and are limited in their applicability.

?- X = 1, include(=\=(1),[0,X,2],Xs), X = 1.
   X = 1,
   Xs = [0,2].
?-        include(=\=(1),[0,X,2],Xs), X = 1.
   instantiation_error.    % much better than an incorrect answer

A safe variant of include/3 is tfilter/3 of library(reif). But before using it, I recommend you read this.

false
  • 10,264
  • 13
  • 101
  • 209
-1

include/3 doesn't have to be incompatible with clpfd (or any other use of attributed variables) - depends on the safe (for the purposes of the program, rather than necessarily "logically pure") usage of the attributed variables. E.g.:

:- use_module(library(clpfd)).

test_include(F) :-
    L = [N1, N2, N3, N4],
    N1 #< 2,
    N2 #> 5,
    N3 #< 3,
    N4 #> 8,
    include(gt_3_fd, L, F).

gt_3_fd(I) :-
    I #> 3.

Result in swi-prolog:

?- test_include(F).
F = [_A, _B],
_A in 6..sup,
_B in 9..sup.

The above code is safe, because the variables being used with clpfd are being used consistently with clpfd, and the specified constraints result in reification of gt_3_fd being unnecessary.

Once the variables are nonvar/ground depending on the use-case (clpfd deals with integers, rather than e.g. compound terms, so nonvar is good enough), then the variables can also be used outside of clpfd. Example:

?- I = 5,  I > 4, I @> 4, I #> 4.
I = 5.

An operator such as > uses the actual value of the variable, and ignores any attributes which might have been added to the variable by e.g. clpfd.

Logical purity, e.g. adding constraints to the elements in list L after the include/3 rather than before, is a separate issue, and applies to non-attributed variables also.

In summary: A program may be using some integers with clpfd, and some integers outside of clpfd, i.e. a mixture. That is OK, as long as the inside-or-outside distinction is consistently applied, while it is relevant (because e.g. labeling will produce actual values).

brebs
  • 3,462
  • 2
  • 3
  • 12
  • Ok I see. If I understand correctly, I can use any predicate, as long as I'm consistent in the variables and operators I use with them. Just to clarify, say I use a predicate from a library `p(X) :- X > 5.`, would I be able to call it from my clp(fd) code with `X in 1..5, p(X).` or would that not work because the predicate `p` does not use the `#>` comparison operator? – nich Jan 11 '23 at 01:44
  • `X > 5` produces `ERROR: Arguments are not sufficiently instantiated` if X is *var* at that point - https://www.swi-prolog.org/pldoc/doc_for?object=var/1 – brebs Jan 11 '23 at 07:49
  • 1
    How can you distinguish above case from a 'minor' variation, say `N4 #< 8` where you get an incorrect result? – false Jan 11 '23 at 09:52
  • @false I would blame the programmer for not using reification, if reification (e.g. `tfilter` instead of `include`, as you mentioned) is *required* at that point for the program to run correctly :-) Or perhaps rewrite the program to use `include` *after* the variables involved have been instantiated. – brebs Jan 11 '23 at 10:58
  • 1
    That's the point of stating that they are incompatible. – false Jan 11 '23 at 13:07
  • @false I think our difference is that I'm interested in a program which works (i.e. meets the stated requirement), whereas you're pushing the *logically pure* style - which has its uses, but for the majority of Prolog questions on this site, is overkill and would show Prolog to beginners as overly complex and slow. – brebs Jan 11 '23 at 13:32
  • 1
    The problem behind is: If you get some response from Prolog, can you rely on the result or do you have to replay and verify every tiny step that Prolog performed. – false Jan 11 '23 at 13:34
  • 1
    ... so this has nothing to do with pure style as such. Think of `(=\=)/2` which is just safe but extremely restricted. – false Jan 11 '23 at 13:35
  • When we speak of "compatible" we always mean "compatible in the general case", we never mean "compatible sometimes" or "compatible under favourable conditions". Thus, `include/3` is compatible with clpfd **if and only if** it is *never* incompatible with it. – repeat Jan 14 '23 at 09:39
  • At first glance, the code you are presenting appears to make some sense. But if you look deeper it falls apart. Why? It depends on internal details of the implementation of the clpfd library you are using. Consider that `(#>)/2` *can* leave behind residual goals. In your contrived example it does not, but in general it can! – repeat Jan 14 '23 at 09:47
  • @repeat Notice that I'm certainly *not* saying that `include` is always safe to use. But saying that it is always *unsafe* is wrong. – brebs Jan 14 '23 at 10:36
  • Granted, but OTOH no one is saying that "it is always unsafe" either. The central question is: **Which properties can a user of `include/3` *rely* on?** So, in this context, "unsafe" does not mean "never safe", but "not always safe". A user of cannot rely on `include/3` being safe with clpfd (without knowing internal implementation details which are BTW subject to change), so we say "(in general) `include/3` is unsafe to use with clpfd". – repeat Jan 14 '23 at 10:51