3

I encountered this in a much larger codebase, but reduced it down to a minimal reproducible example. This is some code for an assembler:

:- use_module(library(clpfd)).

bigconst(X) :- X #=< 0x3FF, X #>= 0.

asm(instruction('ADD', [A]), R) :-
  bigconst(A),
  R #= 0b0000 + (A << 4).
asm(instruction('SUB', [A]), R) :-
  bigconst(A),
  R #= 0b0001 + (A << 4).

It seems to work when assembling:

?- asm(instruction('SUB', [5]), R).
R = 81.

But seems to fail when disassembling:

?- asm(I, 81).
I = instruction('ADD', [_42146]),
_42146 in 0..1023,
81#=_42146<<4 .

Is this a bug in my program or a bug in Prolog? How would I fix this?

false
  • 10,264
  • 13
  • 101
  • 209
redfast00
  • 1,363
  • 1
  • 12
  • 23

2 Answers2

2

When I found the answer it was LOL. I have used many odd patterns to solve a problem but this was one I have never used before. Once I saw it work I knew I had a shinny new tool for tool box.

With CLP(FD) problems they typically can work both ways and is what you wanted. The first problem you have is that you have bigconst(A) which is acting like guard statement. So just toss that out.

Then next thing is that R #= 0b0000 + (A << 4) works as expected but suffers from a problem, it doesn't work as desired both ways,

?- X #= 4 << 4.
X = 64.

?- 64 #= X << 4.
64#=X<<4.

Likewise the reverse

B #= A >> 4.

also works as expected and also suffers the same problem.

?- X #= 64 >> 4.
X = 4.

?- 4 #= X >> 4.
4#=X>>4.

So I tried adding a few constraints using in/2 and that didn't work, then realized I already had all the constraints needed and they worked.

asm(instruction('ADD', [A]), R) :-
    R #= 0b0000 + (A << 4),
    A #= (R - 0b0000) >> 4.

Example usage

?- asm(instruction('ADD', [5]), R).
R = 80.

?- asm(I,80).
I = instruction('ADD', [5]).

To show that it was not a one hit wonder.

asm(instruction('SUB', [A]), R) :-
    R #= 0b0001 + (A << 4),
    A #= (R - 0b0001) >> 4.

Example usage

?- asm(instruction('SUB', [5]), R).
R = 81.

?- asm(I,81).
I = instruction('SUB', [5]).
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
2

Is this a bug in my program or a bug in Prolog? How would I fix this?

This is a usability bug of the toplevel you are using. If you look very closely you might find a tiny hint:

81#=_42146<<4 .
             ^ SPACE

This tiny space means: Please note there is more than this answer. And in fact, there are two answers. The first answer (from 'ADD') is fake, it does not contain any solution. Only the second answer contains one solution.

Note that Prolog primarily produces answers and not solutions. That's why we talk about answer substitutions. The relation between answers and solutions is subtle. An answer may contain between zero and infinitely many solutions.

So why doesn't Prolog produce solutions directly?

First of all, for infinite solutions that would be very ineffective. Think of length(L,3) which has a single answer that contains all lists of length 3 with just any elements imaginable, thus infinitely many solutions. Like L = [1,2,3] or L = [bof, toto, machin-maschin] or L = [louis_XVI, bernie, bernie] and so on. With the help of the logical variable we can collapse this infinity of three-element lists into the following answer: L = [_A,_B,_C]. This is the power of the logical variable!

This power is also used in constraints. But with this power there comes a lot of responsibility and a lot of burden. After all, many problems are easy to compute in one direction and are difficult the other way round. You have uncovered such a problem. In this case it is thinkable to improve the clpfd to handle this case with ease, but in the general case it is undecidable. Thus, sometimes there is no algorithm whatsoever. And in such cases giving a fake answer (inconsistency, Scheinlösung, solution blanche) is the best a system can do. Alternatively it could produce an error and abort the entire computation. But that is gross. Often, we can live with such inconsistencies.

Take your case, if you really want to be sure that a solution exist, remove the constraints from the answer by labeling them out!

?- asm(I,81), I = instruction(_,[R]).
   I = instruction('ADD', [R]),
   R in 0..1023,
   81#=R<<4
;  I = instruction('SUB', [R]),
   R in 0..1023,
   80#=R<<4.

?- asm(I,81), I = instruction(_,[R]), labeling([],[R]).
   I = instruction('SUB', [5]),
   R = 5
;  false.

Another method would be to make the constraints stronger, as @Guy-Coder has shown. This may work (and then it is nice) but is more complex to understand. And might also be less efficient in certain situations. This is a real engineering problem. When do we accept an inconsistency as a price for faster solutions in certain cases?

false
  • 10,264
  • 13
  • 101
  • 209