Fast reverse sort in (SWI) Prolog - sorting

I'm looking for a fast way to sort a list in reverse order in Prolog. Algorithmically it should perform as quickly as standard sort, but the options that I've come up with are much slower, for obvious reasons.
Predicate rsort1/2 sorts and then reverses.
rsort1(L1, L2) :-
sort(L1, Tmp),
reverse(Tmp, L2).
Predicate rsort2/2 uses predsort/3 with a custom comparator.
rsort2(L1, L2) :-
predsort(reverse_compare, L1, L2).
reverse_compare(Delta, E1, E2) :-
compare(Delta, E2, E1).
To test their performance I've generated a huge random list like this:
?- Size = 1234567,
findall(N, (between(1, Size, _), N is random(Size)), Ns),
assert(test_list(Ns)).
Size = 1234567,
Ns = [183677, 351963, 737135, 246842, 22754, 1176800, 1036258|...].
These are the runtimes for the standard sort:
?- test_list(Ns), time(sort(Ns, NsS)).
% 2 inferences, 7.550 CPU in 8.534 seconds (88% CPU, 0 Lips)
Ns = [183677, 351963, 737135, 246842, 22754, 1176800, 1036258, 625628|...],
NsS = [0, 1, 3, 5, 8, 10, 12, 14, 16|...].
... for rsort1:
?- test_list(Ns), time(rsort1(Ns, NsS)).
% 779,895 inferences, 8.310 CPU in 9.011 seconds (92% CPU, 93850 Lips)
Ns = [183677, 351963, 737135, 246842, 22754, 1176800, 1036258, 625628|...],
NsS = [1234564, 1234563, 1234562, 1234558, 1234557, 1234556, 1234555|...].
... and for rsort2:
?- test_list(Ns), time(rsort2(Ns, NsS)).
% 92,768,484 inferences, 67.990 CPU in 97.666 seconds (70% CPU, 1364443 Lips)
Ns = [183677, 351963, 737135, 246842, 22754, 1176800, 1036258, 625628|...],
NsS = [1234564, 1234563, 1234562, 1234558, 1234557, 1234556, 1234555|...].
Can I do better than rsort1 and rsort2 speedwise?

If you're after a sorting routine that is portable (i.e., defined using PROLOG), then you probably won't be able to implement anything faster (or as fast, with a need to reverse the sort order) than those predicates that execute sorting routines natively in C/C++, such as sort/2 or msort/2.
If speed is of overriding concern here, you could of course write your own non-portable predicate with an external definition to do the reverse sorting. For example, the SWI-PL C++ interface could be used (see the examples there) to write a C++ definition of rsort/2, perhaps using the comparison predicates also implemented in C++.
Similarly, you could also write rsort/2 in C using the SWI-PL C interface. src/pl-list.c in the SWI-PROLOG source contains implementations of the sorting methods (namely nat_sort()) which is used for sort/2, msort/2 and keysort/2. To implement rsort/2, you might only need to follow their implementation and tweak/reverse the interpretation of calls to compare() which describes the standard order of terms.

define predicates:
1. get lowest number in a list.
2. deleting an item in a list.
3. merging two lists.
4. sorting a list by
a) getting the lowest
b) deleting the lowest in a list
c) sort the new list without the lowest (recursion)
with the base rule orderlist([X],[X]).

Related

What is a simple worst case for occurs check in Prolog?

Many papers do note that an equational unification problem such as below, might run in exponential time, when occurs_check=true. There is no stipulation that this is a top-level query or a clause body, its just the equational unification problem:
X1 = f(X0, X0),
X2 = f(X1, X1),
..
Xn-1 = f(Xn-2, Xn-2),
Xn = f(Xn-1, Xn-1).
If true this could be a worst case for occurs check, since normal variable sharing unification is linear. Does every Prolog system
necessarely feature this equational unification problem as a worst case?
If the Prolog system does not have an occurs_check=true flag, one could try unify_with_occurs_check/2 in place of (=)/2.
Here is a comparison. I tested the equational unification problem inside a clause body. Link to source code of the test and the benchmark results is at the end of this answer:
test :-
B = f(A, A),
C = f(B, B),
D = f(C, C),
X = f(D, D).
Etc..
Jekejeke Prolog 1.4.6 and SWI-Prolog 8.3.17 is still linear. Jekejeke Prolog uses a static analysis, doesn't work always. SWI-Prolog does it dynamically, I guess side effect of dealing with cyclic terms. But GNU Prolog 1.4.5 is exponential. I was using n=4, 6, 8 and 10:
Open Source:
Linear or Exponential?
https://gist.github.com/jburse/2d5fd1d3dd8436acceca52fdfc537581#file-size-pl
Not yet completely verified hypothesis. There is some confirmation
that we can look at the VM code. There is the danger that I am still
looking, looking, looking, … and I don’t see anything.
Here is a suspicion of mine for SWI-Prolog. Concerning this
equational unification problem, now inside a clause body:
X1 = f(X0, X0),
X2 = f(X1, X1),
..
Xn-1 = f(Xn-2, Xn-2),
Xn = f(Xn-1, Xn-1).
Only one equation is optimized away when occurs_check=true? This would
explain the differing LIPS count and the differing performance:
/* (=)/2, occurs_check=false */
% % 2,000,000 inferences, 0.222 CPU in 0.226 seconds (98% CPU, 9007995 Lips)
/* unify_with_occurs_check/2 */
% % 12,000,000 inferences, 1.382 CPU in 1.411 seconds (98% CPU, 8680009 Lips)
/* (=)/2, occurs_check=true */
% 11,000,000 inferences, 1.264 CPU in 1.270 seconds (100% CPU, 8704963 Lips)
Oki, Doki.

How to count the predicates

I'm looking for a way to count the numbers of predicates.
Example:
%facts
has_subclass(thing,animal).
has_subclass(thing,tree).
has_subclass(thing,object).
% and I ask
count_has_subclass(thing,X).
% result
X = 3.
For facts like your example:
count_has_subclass(What, Count):-
findall(1, call(has_subclass(What, _)), L),
length(L, Count).
We can use findall/3 for this, and then use length/2 to obtain the length of the list:
count_has_subclass(What, N):-
findall(X, has_subclass(What, X), L),
length(L, N).
If it is however possible that has_subclass/2 yields the same values multiple times for a given key (like thing), then we can use for example sort/2 as a duplicate filter, like:
count_has_subclass(What, N):-
findall(X, has_subclass(What, X), L),
sort(L, S), %% remove duplicates
length(S, N).
Note that if What is a free variable, then you will count all yeilds of has_subclass(_, _). (optionally with a uniqness filter on the second parameter).
Using the standard setof/3 is a better option as it allows easy definition of a more general predicate that can enumerate solutions when the class argument is not bound. For example, assume the following database:
has_subclass(thing,animal).
has_subclass(thing,tree).
has_subclass(thing,object).
has_subclass(animal,cat).
has_subclass(animal,dog).
has_subclass(tree,pine).
has_subclass(tree,oak).
And the definition:
subclass_count(Class, Count) :-
setof(Subclass, has_subclass(Class, Subclass), Subclasses),
length(Subclasses, Count).
Sample call:
| ?- subclass_count(Class, Count).
Class = animal
Count = 2 ? ;
Class = thing
Count = 3 ? ;
Class = tree
Count = 2
yes
If you try instead one of the findall/3 solutions in the other answers, we get instead:
| ?- count_has_subclass(What, Count).
Count = 7
But note that this solution also have a sensible interpretation as returning the number of all existing subclasses when the class is not specified.
To build a list just to count solutions seems 'old style', but in traditional Prolog there is only the DB (assert/retract) alternative to overcome the 'stateless' nature of computations. Indeed findall/3 and friends builtins could be (naively) rewritten by means of assert/retract. But something have shown up since the early 80's ages :). I illustrate with SWI-Prolog, but - more or less naively - all of them could be implemented by assert/retract. Of course, most Prolog implementations have 'non logical' (or 'imperative', or 'impure') facilities to implement such basic task, without resorting to the heavyweight DB interface - like setarg/3, nb_setval/2 and others...
I digress... library(aggregate) should be shown first:
?- aggregate(count, T^has_subclass(thing,T), C).
C = 3.
This lib is well worth to study, does a lot more than - efficiently - counting...
Another, recent, addition is library(solution_sequences). It's not more efficient than setof/3 + length/2, guess, but interesting in its own. Counting is a bit more involved, and uses call_nth/2:
?- order_by([desc(C)],call_nth(has_subclass(thing,T),C)).
C = 3,
T = object ;
...
Some far simpler code of mine, based on nb_setarg/3 (and help from #false):
?- [carlo(snippets/lag)].
true.
?- integrate(count,has_subclass(thing,T),C).
C = 3.

Assert Intermediate Result in Prolog

This is the question.
Define a predicate sigma(N,S) such that S = 1+2+...+N. And remember every new intermediate result in the query. For example, after query sigma(3,S), it will store some thing like sigma(2,3),sigma(3,6) to database such that we needn't do duplicate and useless work later.
I tried the following method to solve it.
sigmares(1,1).
mysigma(N,A,Sum) :-
sigmares(N,SN),
Sum is SN+A,
!.
mysigma(N1,Acc,Sum) :-
N is N1-1,
A is Acc + N1,
mysigma(N,A,Sum),
assertz(sigmares(N1,Sum)). % <<<<<<<<<< This line doesn't work properly.
sigma(N,X) :-
mysigma(N,0,X).
There is some problem with assertz line. Since sum can be only initialized once which is the value of sum from 1 to N, sigma(2,6),sigma(3,6) for query sigma(3,S) will be inserted. Is there any other way to store new intermediate sigmares?
First, it's good coding style to always declare the dynamic predicates that your code uses using the standard dynamic/1 directive. Simply add at the beginning of the file:
:- dynamic(sigmares/2).
An interesting aspect of your definition of the mysigma/3 predicate is that it is a non tail-recursive with the consequence that it requires space linear on its inputs. But that allows it to cache all intermediate results as you intend. A fixed version of your code will be:
:- dynamic(sigma_cache/2).
sigma_cache(1, 1).
sigma(N, S) :-
sigma_cache(N, S),
!.
sigma(N, S) :-
N > 1,
M is N - 1,
sigma(M, SM),
S is SM + N,
assertz(sigma_cache(N, S)).
Sample call:
?- sigma(5, S).
S = 15.
?- listing(sigma_cache/2).
:- dynamic sigma_cache/2.
sigma_cache(1, 1).
sigma_cache(2, 3).
sigma_cache(3, 6).
sigma_cache(4, 10).
sigma_cache(5, 15).
true.
This alternative answer provides a solution based on the tabling mechanism found in some Prolog systems, including B-Prolog, Ciao, SWI-Prolog, XSB, and YAP:
:- table(sigma/2).
sigma(1, 1).
sigma(N, S) :-
N > 1,
M is N - 1,
sigma(M, SM),
S is SM + N.
Let's test it with the help of SWI-Prolog handy time/1 library predicate that reports the time and number of inferences taken to prove a goal:
?- time(sigma(5, S)).
% 166 inferences, 0.000 CPU in 0.006 seconds (2% CPU, 1238806 Lips)
S = 15.
?- time(sigma(5, S)).
% 5 inferences, 0.000 CPU in 0.000 seconds (68% CPU, 208333 Lips)
S = 15.
Note that I used a non tail-recursive definition for the sigma/2 predicate on purpose so that all intermediate results are cached (as per the requirements in your question). For example:
?- time(sigma(4, S)).
% 5 inferences, 0.000 CPU in 0.000 seconds (70% CPU, 217391 Lips)
S = 10.
You can see that, after the first call, the result is cached by the tabling mechanism, resulting in a much lower number of inferences when we repeat the query.
?- time(sigma(6, S)).
% 32 inferences, 0.000 CPU in 0.000 seconds (86% CPU, 727273 Lips)
S = 21.
?- time(sigma(6, S)).
% 5 inferences, 0.000 CPU in 0.000 seconds (70% CPU, 217391 Lips)
S = 21.
Note again the number of inferences. The first query reuses the cached result for sigma(5, S) and caches the result for sigma(6, S), making the repeated query again faster as it just reuses the cached result.

Prolog - multiplying by addition

I need simple fuction in SWI-prolog which multiplying by addition. Something like m(X,Y,Z) where for example X=5, Z=3 <==> 5*3. Y is result: Y=5, Y=10, Y=15 [stop]. I was thinking about something like that:
m(X,Y,Z):- Z>0, /*when Z reaches 0 you stop */ I=X+X, W=Z-1, m(I,Y,W).
But it always return "false" and dunno why.
Let's start by thinking about what the predicate should describe: it's a relation between three numbers, where the third is the product of the first two. Since you want to describe multiplication by reducing the second argument to zero while adding up the first accordingly many times we are talking about natural numbers. So a nicely descriptive name would be nat_nat_prod/3. Next consider the possible cases:
The second argument can be zero. Then the product has to be zero as well since X*0=0. So this is the base case.
Otherwise the second argument is greater than zero. Then you want to decrement it by one and calculate the product of the first argument and this new number. Since the predicate can use itself to describe that, this is a recursive goal. Subsequently you add the first argument to the intermediary product described by the recursion.
This can be written in Prolog like so:
nat_nat_prod(_X,0,0). % case 1)
nat_nat_prod(X,Y1,P1) :- % case 2)
Y1 > 0,
Y0 is Y1-1,
nat_nat_prod(X,Y0,P0),
P1 is P0+X.
Now let's try some queries:
?- nat_nat_prod(5,3,P).
P = 15 ;
false.
?- nat_nat_prod(5,4,P).
P = 20 ;
false.
?- nat_nat_prod(5,0,P).
P = 0 ;
false.
?- nat_nat_prod(1,0,P).
P = 0 ;
false.
?- nat_nat_prod(1,1,P).
P = 1 ;
false.
However, when playing around with the predicate, you'll notice that the first two arguments have to be instantiated otherwise you'll get an error:
?- nat_nat_prod(1,Y,3).
ERROR: >/2: Arguments are not sufficiently instantiated
?- nat_nat_prod(X,1,3).
ERROR: is/2: Arguments are not sufficiently instantiated
This happens due to the use of >/2 and is/2. You could get around this problem by using CLP(FD) but I think that's beside the point. This way of defining multiplication is obviously very inefficient compared to using the standard arithmetic function */2, e.g.:
?- time(nat_nat_prod(2,1000000,P)).
% 3,000,000 inferences, 33.695 CPU in 33.708 seconds (100% CPU, 89035 Lips)
P = 2000000 ;
% 3 inferences, 0.031 CPU in 0.031 seconds (100% CPU, 97 Lips)
false.
?- time(P is 2*1000000).
% 1 inferences, 0.000 CPU in 0.000 seconds (86% CPU, 82325 Lips)
P = 2000000.
As already hinted by #false in the comments it is more common to introduce people to successor arithmetics first and then to define addition/multiplication of two numbers in s(X) notation this way. Since you can't use the standard arithmetic functions with s(X) numbers, you also don't run into the associated instantiation errors.

STRIPS Planner loops indefinitely

I defined in Prolog a STRIPS Planner to solve logic problems. After a few tryouts with other simpler problems I set out to see if it could solve a more complex one. I gave him a STRIPS definition of the peg solitaire, the english version and considering we cant do diagonal moves and the last ball will end up in the center of the board and tried it, to which the program broke into a loop. Here's the problem: https://en.wikipedia.org/wiki/Peg_solitaire
Here's my solution:
%%%%%%%%%%%%%%%%%%%%%% PLAN %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
accao(nome : move(Xi,Yi,Xf,Yf),
condicoes : [empty(Xf,Yf),ball(Xi,Yi), ball(Xm,Ym)],
efeitos : [ball(Xf,Yf), -ball(Xm,Ym),-ball(Xi,Yi), empty(Xi,Yi), empty(Xm,Ym), -empty(Xf,Yf)],
restricoes : [abs(Xf-Xi)+abs(Yf-Yi)=:=2, abs(Xf-Xi)*abs(Yf-Yi)=:=0, Xi=<Xm, Xm=<Xf, Yi=<Ym, Ym=<Yf]).
inicial([empty(5,5), ball(1,4), ball(1,5), ball(1,6),
ball(2,4), ball(2,5), ball(2,6),
ball(3,4), ball(3,5), ball(3,6),
ball(4,1), ball(4,2), ball(4,3),ball(4,4), ball(4,5), ball(4,6),ball(4,7), ball(4,8), ball(4,9),
ball(5,1), ball(5,2), ball(5,3),ball(5,4), ball(5,6),ball(5,7), ball(5,8), ball(5,9),
ball(6,1), ball(6,2), ball(6,3),ball(6,4), ball(6,5), ball(6,6),ball(6,7), ball(6,8), ball(6,9),
ball(7,4), ball(7,5), ball(7,6),
ball(8,4), ball(8,5), ball(8,6),
ball(9,4), ball(9,5), ball(9,6)]).
objectivos([ball(5,5), empty(1,4), empty(1,5), empty(1,6),
empty(2,4), empty(2,5), empty(2,6),
empty(3,4), empty(3,5), empty(3,6),
empty(4,1), empty(4,2), empty(4,3),empty(4,4), empty(4,5), empty(4,6),empty(4,7), empty(4,8), empty(4,9),
empty(5,1), empty(5,2), empty(5,3),empty(5,4), empty(5,6),empty(5,7), empty(5,8), empty(5,9),
empty(6,1), empty(6,2), empty(6,3),empty(6,4), empty(6,5), empty(6,6),empty(6,7), empty(6,8), empty(6,9),
empty(7,4), empty(7,5), empty(7,6),
empty(8,4), empty(8,5), empty(8,6),
empty(9,4), empty(9,5), empty(9,6)]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%% PRINT FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%
printExec([]).
printExec([A,E|T]) :- write("Action performed: "),
write(A),nl,
write("Situation: "),
write(E),nl,
printExec(T).
writeExec([I|T]):- write("Initial Situation"),
write(I),nl,
printExec(T),
write("Goal: "),
objectivos(G),
write(G),
write(" satisfied."),nl.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%% AUXILIAR FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%
member(E,[E|_]).
member(E,[_|T]):-member(E,T).
sub([],_).
sub([H|T],L):- member(H,L),
sub(T,L).
remove(_,[],[]):-!.
remove(E1, [E2|T], T):- E1 == E2, !.
remove(E,[H|T1],[H|T2]):- remove(E,T1,T2).
add(E,[],[E]):-!.
add(E1,[E2|T],[E1,E2|T]):- E1 \== E2, !.
add(E,[H|T1],[H|T2]):-add(E,T1,T2).
effects([],S,S).
effects([-H|Fx],S,N) :-!,
remove(H,S,NS),
effects(Fx,NS,N).
effects([H|Fx],S,N) :- !,
add(H,S,NS),
effects(Fx,NS,N).
restriction([]).
restriction([R|T]) :- R,
restriction(T).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%% PLAN EXECUTE %%%%%%%%%%%%%%%%%%%%%%%%%%%
planExecute(P):-testPlan(P,E),writeExec(E),!.
satisfiedGoal(E):- objectivos(Fn),!,
sub(Fn,E).
testPlan(Plan,[I|Exec]) :- inicial(I),
testPlan(Plan,I,Exec,Fn),
satisfiedGoal(Fn).
testPlan([],Fn,[],Fn).
testPlan([H|T],S,[H,N|Exec],Fn) :- accao(nome:H, condicoes:C,efeitos:E, restricoes:R),
sub(C,S),
effects(E,S,N),
restriction(R),
testPlan(T,N,Exec,Fn).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%% FIND PLAN %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
plano(P) :- progressivePlan(P, 0).
progressivePlan(P, N) :- createPlan(P,_,0,N).
progressivePlan(P, N) :- \+ createPlan(P,_,0,N),
NewN is N + 1,
progressivePlan(P, NewN).
createPlan(Plan,[I|Exec],N,Max) :- inicial(I),
createPlan(Plan,I,Exec,Fn,N,Max),
satisfiedGoal(Fn).
createPlan([],Fn,[],Fn,Max,Max):- !.
createPlan([H|T],S,[H,N|Exec],Fn,Acc, Max) :- accao(nome:H, condicoes:C, efeitos:E, restricoes:R),
sub(C,S),
effects(E,S,N),
restriction(R),
NewAcc is Acc+1,
createPlan(T,N,Exec,Fn,NewAcc, Max).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%`
I've tried simplifying the goal by just doing one or two moves, which works for the one and when the two moves don't contradict each other, like moving a marble on through one that was already moved, entering the loop with two moves when they do, like with said objective:
objectivos([ball(4,5), empty(3,5), empty(5,5), empty(6,5)]).
I've tried tracing and debugging but I cant seem to find the issue, although I believe it to be located in the formulation of the problem as opposed to the Planner itself. Any Ideas?
There is at least one logical error in your code, and some simple performance tweaks are possible. This gives a partial solution to your problem.
First, for the logical error: The intended solution for the goal objectivos([ball(4,5), empty(3,5), empty(5,5), empty(6,5)]) seems to be the plan P = [move(3, 5, 5, 5), move(6, 5, 4, 5)]. But the second of these moves is not legal with your definition of restricoes: For this move you have Xi = 6, Xf = 4, and conditions requiring that 6 =< Xm and Xm <= 4, but this is impossible. The idea of these constraints is to ensure that ball(Xm,Ym) is between the other two balls in the move. Here is an alternative formulation that ensures this:
restricoes : [abs(Xf-Xi)+abs(Yf-Yi) =:= 2,
abs(Xf-Xi)*abs(Yf-Yi) =:= 0,
abs(Xf-Xm)+abs(Yf-Ym) =:= 1,
abs(Xi-Xm)+abs(Yi-Ym) =:= 1]
This also excludes a case that confused me before, when tracing the code: Previously it was legal to have ball(Xi,Yi) = ball(Xm,Ym).
Second, to improve performance, exchange the goals effects(E,S,N) and restriction(R) in the definition of createPlan/6. Previously you computed the effects of moves before checking their legality! Because most moves proposed by the planner are illegal, this wastes a lot of time.
Then, to make the whole thing nicer to use, you can change the definitions of plano/1 and createPlan/4 to:
plano(P) :-
length(P, PlanLength),
createPlan(P, _, 0, PlanLength).
createPlan(Plan,[I|Exec],N,Max) :- inicial(I),
N =< Max,
createPlan(Plan,I,Exec,Fn,N,Max),
satisfiedGoal(Fn).
This is simpler than the definition you had before, and it also behaves more nicely. We can pass in a complete plan to check whether it is legal, or just pass in a list of fixed length to ask what plans of that length exist:
?- P = [_,_], plano(P).
P = [move(3, 5, 5, 5), move(6, 5, 4, 5)] ;
false. % no more solutions
With your definition, this would go on looping and counting up the Max counter, searching for further solutions that cannot exist.
With this formulation we can switch to your big goal and try to search for a solution (this is partly specific to SWI-Prolog):
?- length(P, N), format('searching for solutions of length ~w~n', [N]), time(plano(P)).
searching for solutions of length 0
% 58 inferences, 0.000 CPU in 0.000 seconds (71% CPU, 2171959 Lips)
searching for solutions of length 1
% 9,709 inferences, 0.001 CPU in 0.001 seconds (100% CPU, 9123980 Lips)
searching for solutions of length 2
% 79,789 inferences, 0.009 CPU in 0.009 seconds (100% CPU, 8778416 Lips)
searching for solutions of length 3
% 477,230 inferences, 0.051 CPU in 0.051 seconds (100% CPU, 9409315 Lips)
searching for solutions of length 4
% 3,412,088 inferences, 0.361 CPU in 0.361 seconds (100% CPU, 9453315 Lips)
searching for solutions of length 5
% 30,967,699 inferences, 3.503 CPU in 3.503 seconds (100% CPU, 8840598 Lips)
searching for solutions of length 6
I had to interrupt the search at this point, it becomes too slow. More tweaks are certainly possible, and I might keep looking at this.

Resources