Cheating Linguists: Difference between revisions

From This Prolog Life
Jump to navigation Jump to search
(15 intermediate revisions by the same user not shown)
Line 1: Line 1:
;Constrained Permutations in Prolog
;Constrained Permutations in Prolog
__NOTOC__
__NOTOC__
 
Problem posted to [https://groups.google.com/forum/#!msg/comp.lang.prolog/e_-sEr4tuF4/gEEKXjLqhKkJ comp.lang.prolog] by Daniel Dudley
Problem posted to [http://groups.google.com/group/comp.lang.prolog/msg/a984ea325e0a4180 comp.lang.prolog] by Daniel Dudley


===Problem Statement===
===Problem Statement===
Line 8: Line 7:
<blockquote>A university was to hold an examination in 5 subjects: Norwegian, German, English, French and Spanish. In each of these languages there were 4 candidates. The examination room was therefore divided into 20 cells, as shown in the figure below (view this in a fixed font):
<blockquote>A university was to hold an examination in 5 subjects: Norwegian, German, English, French and Spanish. In each of these languages there were 4 candidates. The examination room was therefore divided into 20 cells, as shown in the figure below (view this in a fixed font):


<pre>       X
<pre class="prolog">figure( [
        X X X X X
" X   ",
        X X X X
" XXXXX",
        X X X X
" XXXX ",
      X X X X X
" XXXX ",
              X</pre>
"XXXXX ",
"    X "
] ).</pre>
 
The university's administration wanted to secure themselves against cheating. Candidates in the same language were to be completely isolated from each other - so much so that their cells were not to coincide even at the corners.
The university's administration wanted to secure themselves against cheating. Candidates in the same language were to be completely isolated from each other - so much so that their cells were not to coincide even at the corners.


Line 20: Line 22:
Now it just so happens that the dean is an ardent prolog programmer in his spare time (how else could he make dean?) and, realizing that there could be several solutions to the problem, used his skills to find all solutions. Can you do the same?
Now it just so happens that the dean is an ardent prolog programmer in his spare time (how else could he make dean?) and, realizing that there could be several solutions to the problem, used his skills to find all solutions. Can you do the same?
</blockquote>
</blockquote>
===Note===
===Note===


&quot;Several solutions&quot; doesn't really cover it! Assuming that by 'a solution' we mean finding a mapping between candidates and cells, then, having found one such solution, we can find 955,514,879 other members of its solution &quot;family&quot; quite easily, through:
&ldquo;Several solutions&rdquo; doesn't really cover it! Assuming that by &lsquo;a solution&rsquo; we mean finding a mapping between candidates and cells, then, having found one such solution, we can easily find 955,514,879 other members of its solution &ldquo;family&rdquo; through:


* ''candidate permutation'': There are (4!)<sup>5</sup> permutations of the candidates within the cells allocated to their subjects;
* ''candidate permutation'': There are (4!)<sup>5</sup> permutations of the candidates within the cells allocated to their subjects;
* ''subject permutation'': we can multiply the 'candidate permutations' by the 5! permutations of the subjects allocated to particular sets of cells.
* ''subject permutation'': we can multiply the candidate permutations by the 5! permutations of the subjects allocated to particular sets of cells.


This means that the total number of solutions is 955514880 &times; D where D is the number of solution families i.e. solutions that cannot be derived from each other by candidate or subject permutation.
This means that the total number of solutions is 955514880 &times; D where D is the number of solution families i.e. solutions that cannot be derived from each other by candidate or subject permutation.
Line 36: Line 39:
subject/cell allocation problem for any five different subjects.
subject/cell allocation problem for any five different subjects.


<syntaxhighlight lang="prolog">nut1( Solutions ) :-
<pre class="prolog">nut1( Solutions ) :-
     Norwegian = 1,
     Norwegian = 1,
     German    = 2,
     German    = 2,
Line 43: Line 46:
     Spanish  = 5,
     Spanish  = 5,
     candidates( [Norwegian,German,English,French,Spanish], Candidates ),
     candidates( [Norwegian,German,English,French,Spanish], Candidates ),
     matrix( Cells ),
     cells( Cells ),
     count_solutions( allocate(Cells,Candidates), Solutions ).</syntaxhighlight>
     count_solutions( allocate(Cells,Candidates), Solutions ).</pre>


====allocate( +Cells, +Candidates )====
====allocate( +Cells, +Candidates )====
Line 51: Line 54:
from <var>Candidates</var>.
from <var>Candidates</var>.


<syntaxhighlight lang="prolog">allocate( Cells, Candidates ) :-
<pre class="prolog">allocate( Cells, Candidates ) :-
     allocation( Cells, 1, Candidates ).</syntaxhighlight>
     allocation( Cells, 1, Candidates ).</pre>


====allocation( +Cells, +NextSubject, +Subjects )====
====allocation( +Cells, +NextSubject, +Subjects )====
Line 67: Line 70:
on the following basis:
on the following basis:


* For each subject: the location of the N+1th candidate must be a successor of the location of the Nth candidate - to eliminate 'candidate permutations'.
* For each subject: the location of the N+1th candidate must be a successor of the location of the Nth candidate - to eliminate candidate permutations.
* The location of the first candidate of the N+1th subject must be a successor of the location of the first candidate of the Nth subject - to eliminate 'subject permutations'.
* The location of the first candidate of the N+1th subject must be a successor of the location of the first candidate of the Nth subject &ndash; to eliminate subject permutations.


Operationally, at each step we select a candidate for a cell and constrain all the cell's adjacent successors to have different subject(s) from it.
Operationally, at each step we select a candidate for a cell and constrain each of the cell's adjacent successors to have a different subject from it.


<syntaxhighlight lang="prolog">allocation( [], _Next, [] ).
<pre class="prolog">allocation( [], _Next, [] ).
allocation( [Cell|Cells], Next, Subjects ) :-
allocation( [Cell|Cells], Next, Subjects ) :-
     allocate_candidate( Subjects, Next, Candidate, Subjects1, Next1 ),
     allocate_candidate( Subjects, Next, Candidate, Subjects1, Next1 ),
Line 78: Line 81:
     adjacent_successors( Cell, AdjacentSuccessors ),
     adjacent_successors( Cell, AdjacentSuccessors ),
     blocked( AdjacentSuccessors, Candidate ),
     blocked( AdjacentSuccessors, Candidate ),
     allocation( Cells, Next1, Subjects1 ).</syntaxhighlight>
     allocation( Cells, Next1, Subjects1 ).</pre>


====allocate_candidate( +Subjects, +Next, ?Candidate, ?Subjects1, ?Next1 )====
====allocate_candidate( +Subjects, +Next, ?Candidate, ?Subjects1, ?Next1 )====


holds when <var>Candidate</var> is taken from <var>Subjects</var> leaving <var>Subjects1</var>. <var>Candidate</var> is represented by a subject number <code>=<</code> <var>Next</var>. <var>Next1</var> is the highest subject that can be allocated to the next cell, ensuring that the first candidate for each subject is allocated in order.
holds when <var>Candidate</var> is taken from <var>Subjects</var> leaving <var>Subjects1</var>. <var>Candidate</var> is represented by a subject number &le; <var>Next</var>. <var>Next1</var> is the highest subject that can be allocated to the next cell, ensuring that the first candidate for each subject is allocated in order.


<syntaxhighlight lang="prolog">allocate_candidate( [Subject|Subjects], Next, Candidate, Subjects1, Next1 ) :-
<pre class="prolog">allocate_candidate( [Subject|Subjects], Next, Candidate, Subjects1, Next1 ) :-
     Subject = [Candidate|Candidates],
     Subject = [Candidate|Candidates],
     Candidate =< Next,
     Candidate =< Next,
Line 96: Line 99:
residual_candidates( [], Subjects, Subjects ).
residual_candidates( [], Subjects, Subjects ).
residual_candidates( Candidates, Subjects, [Candidates|Subjects] ) :-
residual_candidates( Candidates, Subjects, [Candidates|Subjects] ) :-
     Candidates = [_|_].</syntaxhighlight>
     Candidates = [_|_].</pre>


====matrix( ?Matrix )====
====cells( ?Layout )====


holds when <var>Matrix</var> is a list of 'cells' ordered by their (x,y)
holds when <var>Layout</var> is a list of cells ordered by their row &times; column coordinates.
coordinates.


Each 'cell' is described by 5 binary digits, which indicate the subject
Each cell is described by 5 binary digits, which indicate the subject
assigned to it, and the set of successors of the cell that are adjacent
assigned to it, and the set of successors of the cell that are adjacent
to it.
to it.


<syntaxhighlight lang="prolog">matrix( Matrix ) :-
<pre class="prolog">cells( Layout ) :-
     layout( Layout ),
    findall( location_cell(Row,Column,_Cell), location(Row, Column), Cells ),
     matrix1( Layout, _Index, Matrix ).
    sort( Cells, Ordered ),
    cells1( Ordered, Layout ).
 
cells1( [], [] ).
cells1( [location_cell(Row,Column,Cell)|Successors], [Cell|Matrix] ) :-
     adjacent_successors( Cell, AdjacentSuccessors ),
     cells2( Successors, Row, Column, AdjacentSuccessors ),
    cells1( Successors, Matrix ).


matrix1( [], _Index, [] ).
cells2( [], _Row, _Column, [] ).
matrix1( [(Row, Column)|Layout], Index, [Cell|Matrix] ) :-
cells2( [location_cell(Row1,Column1,Cell)|Layout], Row, Column, Adjacent ) :-
    location_cell( Row, Column, Index, Cell ),
     ( adjacent( Row, Row1 ),
     findall(
      adjacent( Column, Column1 ) ->
        (Row1, Column1),
         Adjacent = [Cell|Adjacent1]
        (  successor( (Row, Column), (Row1, Column1) ),
    ; otherwise ->
            adjacent(Row, Row1),
         Adjacent = Adjacent1
            adjacent(Column, Column1)
         ),
         AdjacentSuccessorLocations
     ),
     ),
     location_cells( AdjacentSuccessorLocations, Index, AdjacentSuccessors ),
     cells2( Layout, Row, Column, Adjacent1 ).</pre>
     adjacent_successors( Cell, AdjacentSuccessors ),
 
     matrix1( Layout, Index, Matrix ).</syntaxhighlight>
====location( ?Row, ?Column )====
 
holds when <var>Row</var> and <var>Column</var> are the (unary) row and column offsets of
an "X" in <code>figure/1</code>.
 
<pre class="prolog">location( Row, Column ) :-
    X is "X",
    figure( Drawing ),
     offset( Cells, Drawing, Row ),
     offset( X, Cells, Column ).</pre>


====cell( ?Subject, ?Cell )====
====cell( ?Subject, ?Cell )====
holds when <var>Cell</var> is the cell representation for <var>Subject</var>.
holds when <var>Cell</var> is the cell representation for <var>Subject</var>.


<syntaxhighlight lang="prolog">cell( 1, cell(1,0,0,0,0,_) ).
<pre class="prolog">cell( 1, cell(1,0,0,0,0,_) ).
cell( 2, cell(0,1,0,0,0,_) ).
cell( 2, cell(0,1,0,0,0,_) ).
cell( 3, cell(0,0,1,0,0,_) ).
cell( 3, cell(0,0,1,0,0,_) ).
cell( 4, cell(0,0,0,1,0,_) ).
cell( 4, cell(0,0,0,1,0,_) ).
cell( 5, cell(0,0,0,0,1,_) ).</syntaxhighlight>
cell( 5, cell(0,0,0,0,1,_) ).</pre>


====block( ?Subject, ?Block )====
====block( ?Subject, ?Block )====
Line 139: Line 154:
incompatible with <var>Subject</var>.
incompatible with <var>Subject</var>.


<syntaxhighlight lang="prolog">block( 1, cell(0,_,_,_,_,_) ).
<pre class="prolog">block( 1, cell(0,_,_,_,_,_) ).
block( 2, cell(_,0,_,_,_,_) ).
block( 2, cell(_,0,_,_,_,_) ).
block( 3, cell(_,_,0,_,_,_) ).
block( 3, cell(_,_,0,_,_,_) ).
block( 4, cell(_,_,_,0,_,_) ).
block( 4, cell(_,_,_,0,_,_) ).
block( 5, cell(_,_,_,_,0,_) ).</syntaxhighlight>
block( 5, cell(_,_,_,_,0,_) ).</pre>


====adjacent_successors( ?Cell, ?AdjacentSuccesors )====
====adjacent_successors( ?Cell, ?AdjacentSuccesors )====
Line 149: Line 164:
<var>Cell</var> that are adjacent to it.
<var>Cell</var> that are adjacent to it.


<syntaxhighlight lang="prolog">adjacent_successors( cell(_,_,_,_,_,AdjacentSuccesors), AdjacentSuccesors ).</syntaxhighlight>
<pre class="prolog">adjacent_successors( cell(_,_,_,_,_,AdjacentSuccesors), AdjacentSuccesors ).</pre>


====blocked( +Blocked, ?Subject )====
====blocked( +Blocked, ?Subject )====
Line 155: Line 170:
<var>Subject</var>.
<var>Subject</var>.


<syntaxhighlight lang="prolog">blocked( [], _Subject ).
<pre class="prolog">blocked( [], _Subject ).
blocked( [Cell|Blocked], Subject ) :-
blocked( [Cell|Blocked], Subject ) :-
     block( Subject, Cell ),
     block( Subject, Cell ),
     blocked( Blocked, Subject ).</syntaxhighlight>
     blocked( Blocked, Subject ).</pre>


====adjacent( +Coordinate0, ?Coordinate1 )====
====candidates( ?Subjects, ?Candidates )====
holds when <var>Coordinate0</var> and <var>Coordinate1</var> are the
holds when there are 4 <var>Candidates</var> for each subject in <var>Subjects</var>.
same or differ by 1.


<syntaxhighlight lang="prolog">adjacent( N, N ).
<pre class="prolog">candidates( [], [] ).
adjacent( N0, N1 ) :-
candidates( [Subj|Subjects], [[Subj,Subj,Subj,Subj]|Candidates] ) :-
     N1 is N0 + 1.
     candidates( Subjects, Candidates ).</pre>
adjacent( N0, N1 ) :-
    N1 is N0 - 1.</syntaxhighlight>


====location_cells( ?Locations, ?Index, ?Cells )====
====offset( +Element, +List, ?Offset )====
holds when <var>Index</var> is a 6 &times; 6 array, <var>Locations</var>
When <var>Element</var> has unary <var>Offset</var> from the head of <var>List</var>.
is a list of (Row,Column) pairs and <var>Cells</var> is the list of
matching cells dereferenced from <var>Index</var>.


<syntaxhighlight lang="prolog">location_cells( [], _Index, [] ).
<pre class="prolog">offset( Element, List, Position ) :-
location_cells( [(Row, Column)|Locs], Index, [Cell|Cells] ) :-
     offset1( List, Element, 0, Position ).
     location_cell( Row, Column, Index, Cell ),
    location_cells( Locs, Index, Cells ).</syntaxhighlight>


====location_cell( ?Row, ?Column, ?Index, ?Cell )====
offset1( [Element|_Rest], Element, N, N ).
holds when <var>Index</var> is a 6 &times; 6 array with <var>Cell</var>
offset1( [_Head|List], Element, N0, N ):-
at location (<var>Row</var>,<var>Column</var>).
    offset1( List, Element, s(N0), N ).</pre>


<syntaxhighlight lang="prolog">location_cell( Row, Column, Index, Cell ) :-
====adjacent( +Coordinate0, ?Coordinate1 )====
    select_nth( Row, Index, IndexRow ),
holds when <var>Coordinate0</var> and <var>Coordinate1</var> are the
    select_nth( Column, IndexRow, Cell ).</syntaxhighlight>
same or differ by 1.
 
====select_nth( ?N, ?Array, ?Element )====
 
holds when <var>Element</var> is the <var>N</var>th element of <var>Array</var>.
 
<syntaxhighlight lang="prolog">select_nth( 1, array_6(A,_B,_C,_D,_E,_F), A ).
select_nth( 2, array_6(_A,B,_C,_D,_E,_F), B ).
select_nth( 3, array_6(_A,_B,C,_D,_E,_F), C ).
select_nth( 4, array_6(_A,_B,_C,D,_E,_F), D ).
select_nth( 5, array_6(_A,_B,_C,_D,E,_F), E ).
select_nth( 6, array_6(_A,_B,_C,_D,_E,F), F ).</syntaxhighlight>
 
====successor( ?Coordinates0, ?Coordinates1 )====
holds when <var>Coordinates0</var> and <var>Coordinates1</var> are valid
cell positions and <var>Coordinates0</var> &lt;<var>Coordinates1</var>.
 
<syntaxhighlight lang="prolog">successor( Coordinates0, Coordinates1 ) :-
    layout( Layout ),
    append( _Prefix, [Coordinates0|Successors], Layout ),
    member( Coordinates1, Successors ).</syntaxhighlight>
 
====candidates( ?Subjects, ?Candidates )====
holds when there are 4 <var>Candidates</var> for each subject in <var>Subjects</var>.
 
<syntaxhighlight lang="prolog">candidates( [], [] ).
candidates( [Subj|Subjects], [[Subj,Subj,Subj,Subj]|Candidates] ) :-
    candidates( Subjects, Candidates ).</syntaxhighlight>
 
====layout( ?Layout )====
holds when <var>Layout</var> is the sequence of all (x,y) co-ordinates of valid
'cells'.


<syntaxhighlight lang="prolog">layout( [
<pre class="prolog">adjacent( N, N ).
      (1,2),
adjacent( N, s(N) ).
      (2,2),(2,3),(2,4),(2,5),(2,6),
adjacent( s(N), N ).</pre>
      (3,2),(3,3),(3,4),(3,5),
      (4,2),(4,3),(4,4),(4,5),
(5,1),(5,2),(5,3),(5,4),(5,5),
                        (6,5)
        ] ).</syntaxhighlight>


Load a small library of [[Puzzle Utilities]].
Load a small library of [[Puzzle Utilities]].


<syntaxhighlight lang="prolog">:- ensure_loaded( misc ).</syntaxhighlight>
<pre class="prolog">:- ensure_loaded( misc ).</pre>


The code is available as plain text [http://www.binding-time.co.uk/download/nut1.txt here].
The code is available as plain text [https://binding-time.co.uk/download/nut1.txt here].


=== Result ===
=== Result ===


This program reports '''29870''' solutions.
This program reports '''29870''' solutions.

Revision as of 16:39, 13 January 2018

Constrained Permutations in Prolog

Problem posted to comp.lang.prolog by Daniel Dudley

Problem Statement

A university was to hold an examination in 5 subjects: Norwegian, German, English, French and Spanish. In each of these languages there were 4 candidates. The examination room was therefore divided into 20 cells, as shown in the figure below (view this in a fixed font):

figure( [
" X    ",
" XXXXX",
" XXXX ",
" XXXX ",
"XXXXX ",
"    X "
] ).

The university's administration wanted to secure themselves against cheating. Candidates in the same language were to be completely isolated from each other - so much so that their cells were not to coincide even at the corners.

A young lecturer was given the job of finding a solution to the problem, which he did, and justly received a pat on the back from the dean.

Now it just so happens that the dean is an ardent prolog programmer in his spare time (how else could he make dean?) and, realizing that there could be several solutions to the problem, used his skills to find all solutions. Can you do the same?

Note

“Several solutions” doesn't really cover it! Assuming that by ‘a solution’ we mean finding a mapping between candidates and cells, then, having found one such solution, we can easily find 955,514,879 other members of its solution “family” through:

  • candidate permutation: There are (4!)5 permutations of the candidates within the cells allocated to their subjects;
  • subject permutation: we can multiply the candidate permutations by the 5! permutations of the subjects allocated to particular sets of cells.

This means that the total number of solutions is 955514880 × D where D is the number of solution families i.e. solutions that cannot be derived from each other by candidate or subject permutation.

Finding D is the more interesting problem solved by this program.

nut1( ?Solutions )

Solutions is the number of distinct solutions to the subject/cell allocation problem for any five different subjects.

nut1( Solutions ) :-
    Norwegian = 1,
    German    = 2,
    English   = 3,
    French    = 4,
    Spanish   = 5,
    candidates( [Norwegian,German,English,French,Spanish], Candidates ),
    cells( Cells ),
    count_solutions( allocate(Cells,Candidates), Solutions ).

allocate( +Cells, +Candidates )

holds when each cell in Cells holds a candidate from Candidates.

allocate( Cells, Candidates ) :-
    allocation( Cells, 1, Candidates ).

allocation( +Cells, +NextSubject, +Subjects )

holds when Cells is a representation of a distinct solution to the subject/cell allocation problem. NextSubject is the highest subject that can be allocated next, while Subjects is the list of subjects needing allocation to Cells.

Each subject is represented by a list, in which each occurrence of the subject number represents a candidate.

We guarantee distinct solutions by ensuring that the allocation is made on the following basis:

  • For each subject: the location of the N+1th candidate must be a successor of the location of the Nth candidate - to eliminate candidate permutations.
  • The location of the first candidate of the N+1th subject must be a successor of the location of the first candidate of the Nth subject – to eliminate subject permutations.

Operationally, at each step we select a candidate for a cell and constrain each of the cell's adjacent successors to have a different subject from it.

allocation( [], _Next, [] ).
allocation( [Cell|Cells], Next, Subjects ) :-
    allocate_candidate( Subjects, Next, Candidate, Subjects1, Next1 ),
    cell( Candidate, Cell ),
    adjacent_successors( Cell, AdjacentSuccessors ),
    blocked( AdjacentSuccessors, Candidate ),
    allocation( Cells, Next1, Subjects1 ).

allocate_candidate( +Subjects, +Next, ?Candidate, ?Subjects1, ?Next1 )

holds when Candidate is taken from Subjects leaving Subjects1. Candidate is represented by a subject number ≤ Next. Next1 is the highest subject that can be allocated to the next cell, ensuring that the first candidate for each subject is allocated in order.

allocate_candidate( [Subject|Subjects], Next, Candidate, Subjects1, Next1 ) :-
    Subject = [Candidate|Candidates],
    Candidate =< Next,
    Next1 is max(Candidate+1,Next),
    residual_candidates( Candidates, Subjects, Subjects1 ).
allocate_candidate( [Subject|Subjects], Next, Candidate, [Subject|Subjects1], Next1 ) :-
    Subject = [Candidate0|_Candidates],
    Candidate0 < Next,
    allocate_candidate( Subjects, Next, Candidate, Subjects1, Next1 ).

residual_candidates( [], Subjects, Subjects ).
residual_candidates( Candidates, Subjects, [Candidates|Subjects] ) :-
    Candidates = [_|_].

cells( ?Layout )

holds when Layout is a list of cells ordered by their row × column coordinates.

Each cell is described by 5 binary digits, which indicate the subject assigned to it, and the set of successors of the cell that are adjacent to it.

cells( Layout ) :-
    findall( location_cell(Row,Column,_Cell), location(Row, Column), Cells ),
    sort( Cells, Ordered ),
    cells1( Ordered, Layout ).

cells1( [], [] ).
cells1( [location_cell(Row,Column,Cell)|Successors], [Cell|Matrix] ) :-
    adjacent_successors( Cell, AdjacentSuccessors ),
    cells2( Successors, Row, Column, AdjacentSuccessors ),
    cells1( Successors, Matrix ).

cells2( [], _Row, _Column, [] ).
cells2( [location_cell(Row1,Column1,Cell)|Layout], Row, Column, Adjacent ) :-
    ( adjacent( Row, Row1 ),
      adjacent( Column, Column1 ) ->
        Adjacent = [Cell|Adjacent1]
    ; otherwise ->
        Adjacent = Adjacent1
    ),
    cells2( Layout, Row, Column, Adjacent1 ).

location( ?Row, ?Column )

holds when Row and Column are the (unary) row and column offsets of an "X" in figure/1.

location( Row, Column ) :-
    X is "X",
    figure( Drawing ),
    offset( Cells, Drawing, Row ),
    offset( X, Cells, Column ).

cell( ?Subject, ?Cell )

holds when Cell is the cell representation for Subject.

cell( 1, cell(1,0,0,0,0,_) ).
cell( 2, cell(0,1,0,0,0,_) ).
cell( 3, cell(0,0,1,0,0,_) ).
cell( 4, cell(0,0,0,1,0,_) ).
cell( 5, cell(0,0,0,0,1,_) ).

block( ?Subject, ?Block )

holds when Block is a cell representation that is incompatible with Subject.

block( 1, cell(0,_,_,_,_,_) ).
block( 2, cell(_,0,_,_,_,_) ).
block( 3, cell(_,_,0,_,_,_) ).
block( 4, cell(_,_,_,0,_,_) ).
block( 5, cell(_,_,_,_,0,_) ).

adjacent_successors( ?Cell, ?AdjacentSuccesors )

holds when AdjacentSuccesors is the set of successors of Cell that are adjacent to it.

adjacent_successors( cell(_,_,_,_,_,AdjacentSuccesors), AdjacentSuccesors ).

blocked( +Blocked, ?Subject )

holds when all the cells in Blocked are incompatible with Subject.

blocked( [], _Subject ).
blocked( [Cell|Blocked], Subject ) :-
    block( Subject, Cell ),
    blocked( Blocked, Subject ).

candidates( ?Subjects, ?Candidates )

holds when there are 4 Candidates for each subject in Subjects.

candidates( [], [] ).
candidates( [Subj|Subjects], [[Subj,Subj,Subj,Subj]|Candidates] ) :-
    candidates( Subjects, Candidates ).

offset( +Element, +List, ?Offset )

When Element has unary Offset from the head of List.

offset( Element, List, Position ) :-
    offset1( List, Element, 0, Position ).

offset1( [Element|_Rest], Element, N, N ).
offset1( [_Head|List], Element, N0, N ):-
    offset1( List, Element, s(N0), N ).

adjacent( +Coordinate0, ?Coordinate1 )

holds when Coordinate0 and Coordinate1 are the same or differ by 1.

adjacent( N, N ).
adjacent( N, s(N) ).
adjacent( s(N), N ).

Load a small library of Puzzle Utilities.

:- ensure_loaded( misc ).

The code is available as plain text here.

Result

This program reports 29870 solutions.