I want to solve the Most Perfect Magic Square with a Prolog program.
Wiki page: https://en.wikipedia.org/wiki/Most-perfect_magic_square
When I input the query "magic_square(4, [[7, 12, 1, 14], [2, 13, 8, 11], [16, 3, 10, 5], [9, 6, 15, 4]])." (which is a valid Magic Square) my program returns true. So I assume my rule basis is correct.
Unfortunately, if more than 9 Values are unknown, it takes forever to find a solution.
I need help to restrict my search, so the Program finds a solution in a reasonable time. Ideally, it should also work with a 12 x 12 grid(and also every other multiple of 4) and no Values given: magic_square(12, Matrix).
Thanks a lot!
Here my Code:
:- use_module(library(clpfd)).
diag2_sum(0, _, _, _).
diag2_sum(I0, C1, Row1, Row3) :-
I0 > 0,
nth1(I0,Row1,A),
(I0 =:= 2 -> I2 = 4 ; I2 is mod(I0 + 2,4)),
nth1(I2,Row3,B),
C1 =:= A + B,
I1 is I0 - 1,
diag2_sum(I1, C1, Row1, Row3).
diag_sum([_,_], _, _).
diag_sum([Row1|Tail], C1, N) :-
nth1(2,Tail,Row3),
diag2_sum(N, C1, Row1,Row3),
diag_sum(Tail, C1, N).
square_sum_x(_, _, _, 0).
square_sum_x(Row1, Row2, C2, I0) :-
(I0 =:= 3 -> I2 = 4 ; I2 is mod(I0 + 1,4)),
nth1(I0,Row1,Elem1),
nth1(I2,Row1,Elem2),
nth1(I0,Row2,Elem3),
nth1(I2,Row2,Elem4),
C2 =:= Elem1 + Elem2 + Elem3 + Elem4,
I1 is I0 - 1,
square_sum_x(Row1, Row2, C2, I1).
square_sum_y(_, _, 0, _).
square_sum_y(Matrix, C2, I0, N) :-
(I0 =:= 3 -> I2 = 4 ; I2 is mod(I0 + 1,4)),
nth1(I0,Matrix,Row1),
nth1(I2,Matrix,Row2),
square_sum_x(Row1,Row2, C2, N),
I1 is I0 - 1,
square_sum_y(Matrix, C2, I1, N).
magic_square(N, Matrix) :-
Nmax is N * N,
C1 is Nmax + 1,
C2 is C1 * 2,
write(C1),nl,write(C2),nl,
length(Matrix, N),
maplist(same_length(Matrix), Matrix),
append(Matrix, Vs),
Vs ins 1..Nmax, all_different(Vs),
maplist(label, Matrix),
%write(Matrix),nl,
diag_sum(Matrix, C1, N),
square_sum_y(Matrix, C2, N, N).
% some queries i tryed out:
% magic_square(4, [[7, 12, 1, 14], [2, 13, 8, 11], [16, 3, 10, 5], [9, 6, 15, 4]]).
% magic_square(4, [[7, 12, 1, 14], [2, 13, 8, B4], [C1, C2, C3, C4], [D1, D2, D3, D4]]).
% magic_square(4, [[7, A2, A3, 14], [2, B2, 8, B4], [C1, C2, 10, C4], [D1, D2, D3, 4]]).
I confirm that a query with 10 holes seems to take very long, and even with 9 holes it's not very fast in SWI-Prolog:
So where does that time go? Let's use SWI-Prolog's profiler:
This opens a GUI window. If we click on
magic_square/2in the list of predicates, we see:Note the line for
__aux_maplist/2_label+0/1: 94.7% of your execution time is spent there! This is yourmaplist(label, Matrix)line, where you get stuck before ever getting to the interesting part with thediag_sumandsquare_sum_ypredicates that actually define what a magic square is.Basically, by posting
all_different(Vs)followed immediately by labeling it, you are asking Prolog to enumerate all permutations of different numbers that could fill the holes in the input term. The number of such permutations, and therefore your execution time, grows exponentially.You should not use labeling in this way, before all constraints are known. Labeling should be the last thing you do in your predicate, or even better, it should be done by a different predicate from the one where you define your constaints.
So you could set up things like this:
Now
diag_sumandsquare_sum_xwill be called with unbound (but constrained) variablesC1andC2, respectively. This is fine! This is what you want when you use CLP(FD)! But you need to change the numerical equality goals for these variables from=:=(which expects ground values) to#=(which actually works with constraint variables).And now you're fast with 9 variables:
And with 10:
And with even more: