The general idea is to begin with a characterization of what a solution looks like, and try to push this well-formedness test on candidate solutions as far as possible into the generator:
find(X) :- generate(X), test(X).
permutation(Xs,[Z|Zs]) :- range(M,N,[M|Ns]) :-
select(Z,Xs,Ys), M < N,
permutation(Ys,Zs). M1 is M + 1,
permutation([],[]). range(M1,N,Ns).
range(N,N,[N]).
select(X,[X|Xs],Xs).
select(X,[Y|Ys],[Y|Zs]) :-
select(X,Ys,Zs).
queens(N,Qs) :- qqueens(N,Qs) :-
range(1,N,Ns), range(1,N,Ns),
permutation(Ns,Qs), qqueens(Ns,[],Qs),
nl,nl, nl,write('Found a solution!'),
write('Generated a potential solution: '), nl.
write(Qs),
nl, qqueens(UnplacedQs,SafeQs,Qs) :-
write('Testing if actual solution.'), nl,
safe(Qs), nl,write('Unplaced queens: '),
nl,write('Found a solution!'),nl. write(UnplacedQs),
nl,write(' Safe queens: '),
safe([Q|Qs]) :- write(SafeQs),
safe(Qs), select(Q,UnplacedQs,UnplacedQs1),
\+ attack(Q,Qs). nl,nl,
safe([]). write(' Trying queen '),
write(Q),
attack(X,Xs) :- \+ attack(Q,SafeQs),
attack(X,1,Xs). nl,nl,write(' Queen '),
write(Q),write(' is safe.'),
attack(X,N,[Y|Ys]) :- qqueens(UnplacedQs1,[Q|SafeQs],Qs).
X is Y+N,
nl,write(' Not safe: '), qqueens([],Qs,Qs).
write(X),write(' attacks '),write(Y).
attack(X,N,[Y|Ys]) :-
X is Y-N,
nl,write(' Not safe: '),
write(X),write(' attacks '),write(Y).
attack(X,N,[Y|Ys]) :-
N1 is N+1,
attack(X,N1,Ys).