HomeHome More SamplesMore Samples
///////////////////////////////////////////////////////////////////////////////
//
// This program uses brute force to find all solutions to 5x5 magic squares.
// It is very important to determine number combinations where there are no 
// solutions possible as early as possible. In order to do that, we specify 
// the order in which the numbers are placed on the square. After each number 
// is placed, we perform a position dependent sanity test. We do not want to 
// test more nor less then it is neccessary. 
// The placement order was chosen in such a way as to allow most efficient 
// sanity tests. The tests are basically these:
//
//   1. Any three numbers in a line (row,column or diagonals) add up to less then 65
//   2. Any four numbers in a line (row, column or diagonals) add up to less than 65
//   3. Any five numbers in a line (row, column or diagonals) add up to exactly 65.
//
// In addition to these tests, there are a few tests to eliminate solutions that are 
// symmetrical. Having a notation as follows:
//
//      a1 a2 a3 a4 a5
//      b1 b2 b3 b4 b5
//      c1 c2 c3 c4 c5
//      d1 d2 d3 d4 d5
//      e1 e2 e3 e4 e5
//
// we eliminate symmetrical solutions by requesting
//
//      a1 > a5 and a1 > e1 and a1 > e5 and a5 > e1
//
// Other requirements (see http://www.hbmeyer.de/backtrack/mq5/mag5the.htm) 
// which can speed up calculations are possible, such as
// 
//      26 <= a1 + a5 + e1 + e5 <= 78
//      52 <= a1 + a5 + e1 + e5 + 2*c3 <= 104
//
///////////////////////////////////////////////////////////////////////////////
//
// To generate all solutions, run query: 
//
//      all MagicSquares5x5All()
//
// You may want to generate only a subset of all magic squares, for example
// to generate only 100000 solutions, use the following query:
//
//      all MagicSquares5x5All() & RtlTrimSolutions(100000)
//
///////////////////////////////////////////////////////////////////////////////     
//
// Using Core2 @ 2.4GHz generates upon average about 800 solutions per second.
// At this rate it still takes about 95 hours to generate all 275305224 
// solutions:
//
// Number of solutions: 275305224   Number of backtracks: 10806920438401
// Elapsed time: 94:54:16
//                           
///////////////////////////////////////////////////////////////////////////////

local Test = None | A1gtA5 |A1gtE1_A5gtE1 | A1gtE5 |
        RowFull | ColFull|DiagsIncomplete|
        RowIncomplete | ColIncomplete | RowColIncomplete |
        ColFullRowIncomplete | ColFullRowFull | RowFullColIncomplete |
        DiagIncomplete | DiagFull | InvDiagIncomplete | InvDiagFull

local Square = [0..4]->[0..4]->I[0..25]  
local AvailableNumbers = [0..24]->>I[0..25]
local AllPosInfo = [0..24]->>(row:I[0..4],col:I[0..4],test:Test)

///////////////////////////////////////////////////////////////////////////////
//
pred MagicSquares5x5All() iff
    // Initialize the "square", i.e. the 5x5 rectangle which we want to fill with
    // the numbers 1..25. We intialize all position as 0 (no numbers placed yet).
    square:.Square & Dupl(5,Dupl(5,0),square) & 
    CalculateMagicSquare5x5(square) & PrintFormattedSolution(square,0)

///////////////////////////////////////////////////////////////////////////////
//
// This program uses the following order for placing the numbers on the square:
//
//       1 10 12 11  2
//      20  6 18  8 21
//      22 14  5 17 23
//      24  9 19  7 25
//       3 13 16 15  4
//
//  a1 > a5 and a1 > e1 and a1 > e5 and a5 > e1
//
// For example, this will happen after the first few numbers are placed:
//
//
// Position 1 at (0,0): place 'a1' number: No test neccessary
//
//  a1  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//
// Position 2 at (0,4): place 'a1' number: Test for a1 > a5.
//
//  a1  .  .  . a5
//   .  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//
// Position 3 at (4,0): place 'e1' number: Test for a1 > e1 & a5 > e1
//
//  a1  .  .  . a5
//   .  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//  e1  .  .  .  .
//
// Position 4 at (4,4): place 'e5' number: Test for a1 > e5 & 26 <= a1+a5+e1+e5 <= 78  
//
//  a1  .  .  . a5
//   .  .  .  .  .
//   .  .  .  .  .
//   .  .  .  .  .
//  e1  .  .  . e5
//
//
// Position 5 at (2,2): place 'c3' number: Test for a1+c3+e5 < 65 and e1+c3+e5 < 65 &
//                                         52 <= a1+a5+e1+e5+2*c3 <= 104
//  a1  .  .  . a5
//   .  .  .  .  .
//   .  . c3  .  .
//   .  .  .  .  .
//  e1  .  .  . e5
//
//
// Position 6 at (1,1): place 'b2' number: Test for a1+b2+c3+e5 < 65 
//
//  a1  .  .  . a5
//   . b2  .  .  .
//   .  . c3  .  .
//   .  .  .  .  .
//  e1  .  .  . e5
//
//
// Position 7 at (3,3): place 'd4' number: Test for a1+b2+c3+d4+e5 = 65 
//
//  a1  .  .  . a5
//   . b2  .  .  .
//   .  . c3  .  .
//   .  .  . d4  .
//  e1  .  .  . e5
//
//
// Position 8 at (1,3): place 'b4' number: Test for e1+c3+b4+a5 < 65 
//
//  a1  .  .  . a5
//   . b2  . b4  .
//   .  . c3  .  .
//   .  .  . d4  .
//  e1  .  .  . e5
//
//
// Position 9 at (3,1): place 'd2' number: Test for e1+d2+c3+b4+a5 = 65 
//
//  a1  .  .  . a5
//   . b2  . b4  .
//   .  . c3  .  .
//   . d2  . d4  .
//  e1  .  .  . e5
//
//
// Position 10 at (0,1): place 'a2' number: Test for a1+a2+a5 < 65 & a2+b2+d2 < 65 
//
//  a1 a2  .  . a5
//   . b2  . b4  .
//   .  . c3  .  .
//   . d2  . d4  .
//  e1  .  .  . e5
//
// Position 11 at (0,3): place 'a4' number: Test for a1+a2+a4+a5 < 65 & a4+b4+d4 < 65
//
//  a1 a2  . a4 a5
//   . b2  . b4  .
//   .  . c3  .  .
//   . d2  . d4  .
//  e1  .  .  . e5
//
// Position 12 at (0,2): place 'a3' number: Test for a1+a2+a3+a4+a5 < 65 
//
//  a1 a2 a3 a4 a5
//   . b2  . b4  .
//   .  . c3  .  .
//   . d2  . d4  .
//  e1  .  .  . e5
//
//  ....
//  ....
//  ....
//  ....
//
//
// Position 24 at (3,0): place 'd1' number: Test for a1+b1+c1+d1+e1 = 65 & d1+d2+d3+d4 < 65 
//
//  a1 a2 a3 a4 a5
//  b1 b2 b3 b4 b5
//  c1 c2 c3 c4 c5
//  d1 d2 d3 d4  .
//  e1 e2 e3 e4 e5
//
// Position 25 at (3,4): place 'd5' number: Test for a1+b1+c1+d1+e1 = 65 & d1+d2+d3+d4+d5 = 65 
//
//  a1 a2 a3 a4 a5
//  b1 b2 b3 b4 b5
//  c1 c2 c3 c4 c5
//  d1 d2 d3 d4 d5
//  e1 e2 e3 e4 e5
//
local pred CalculateMagicSquare5x5(square:.Square) iff
   allpos :> AllPosInfo & allpos  =  [
      (0,0,None),                     //1
      (0,4,A1gtA5),                   //2
      (4,0,A1gtE1_A5gtE1),            //3   
      (4,4,A1gtE5),                   //4
      (2,2,DiagsIncomplete),          //5
      (1,1,DiagIncomplete),           //6
      (3,3,DiagFull),                 //7
      (1,3,InvDiagIncomplete),        //8
      (3,1,InvDiagFull),              //9
      (0,1,RowColIncomplete),         //10
      (0,3,RowColIncomplete),         //11
      (0,2,RowFull),                  //12
      (4,1,RowColIncomplete),         //13
      (2,1,ColFull),                  //14
      (4,3,RowColIncomplete),         //15
      (4,2,RowFullColIncomplete),     //16
      (2,3,ColFullRowIncomplete),     //17
      (1,2,RowColIncomplete),         //18
      (3,2,ColFullRowIncomplete),     //19
      (1,0,RowColIncomplete),         //20
      (1,4,RowFullColIncomplete),     //21
      (2,0,RowColIncomplete),         //22
      (2,4,RowFullColIncomplete),     //23
      (3,0,ColFullRowIncomplete),     //24
      (3,4,ColFullRowFull)            //25
      ] &

    // We use a set of numbers 1..25 to fill the square. The sequential order of 
    // the numbers was intentionally chosen to correspond to a a valid solution.
    // This is done in order to allow us to verify our filling sequence step-by-step
    // easily...
    numbers :. AvailableNumbers &
    numbers := [25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1] &

    // Start the actual computation
    CoverAllSquares(square,numbers,allpos,0) 


///////////////////////////////////////////////////////////////////////////////
//
// Cover all 25 cells
//
///////////////////////////////////////////////////////////////////////////////
local pred 
CoverAllSquares(square:.Square,numbers:.AvailableNumbers,apos:<AllPosInfo,cnt:<I) iff
    if cnt < 25 then 
        row = apos(cnt).row & col = apos(cnt).col &
        PlaceOnePiece(square,numbers,row,col,apos(cnt).test) &

        // Uncomment the following line to see the filling sequence in slow motion...
//      PrintFormattedSolution(square,0)& Pause(2) &
        CoverAllSquares(square,numbers,apos,cnt+1)
    end

///////////////////////////////////////////////////////////////////////////////
// The position at row,col is unused. 
// Place there one number and perform a position dependent test.
///////////////////////////////////////////////////////////////////////////////
local pred 
PlaceOnePiece(square:.Square,numbers:.AvailableNumbers, row:<I[0..4],col:<I[0..4],test:<Test) iff
    p:>I[0..24] &                       // get an unused number index
    number = numbers(p) & number <> 0 & // get an unused available number
    square(row,col):= number &

    // Perform the position dependent test.
    case test of
                            None => true;
                          A1gtA5 => number < square(0,0);
                   A1gtE1_A5gtE1 => number < square(0,0) & number < square(0,4);
                          A1gtE5 => number < square(0,0) &
                                    num = square(0,0) + square(0,4) + square(4,0) + number & 
                                    num <= 78 & num >= 26;
                  ColFullRowFull => CheckRowColFull(square,row,col);
                         RowFull => CheckRowFull(square,row);
                         ColFull => CheckColFull(square,col);
                   RowIncomplete => CheckRowIncomplete(square,row);
                   ColIncomplete => CheckColIncomplete(square,col);
                RowColIncomplete => CheckRowColIncomplete(square,row,col);
                 DiagsIncomplete => CheckDiagIncomplete(square) & CheckInvDiagIncomplete(square) &
                                    num = square(0,0) + square(0,4) + square(4,0) + square(4,4) + number + number &
                                    num <= 104 & num >= 52;
            ColFullRowIncomplete => CheckColFull(square,col) & CheckRowIncomplete(square,row);
            RowFullColIncomplete => CheckRowFull(square,row) & CheckColIncomplete(square,col); 
                        DiagFull => CheckDiagFull(square);
                     InvDiagFull => CheckInvDiagFull(square);
                  DiagIncomplete => CheckDiagIncomplete(square);
               InvDiagIncomplete => CheckInvDiagIncomplete(square);
        end &

    // If we came here, we passed the test. 
    // mark the number we just placed as used so it will not be used again in this
    // magic square.
    numbers(p) := 0

///////////////////////////////////////////////////////////////////////////////
//
local pred CheckRowColIncomplete(square:.Square, row:<I[0..4], col:<I[0..4])  iff
    square(row,0) + square(row,1) + square(row,2) + square(row,3) + square(row,4) < 65 & 
    square(0,col) + square(1,col) + square(2,col) + square(3,col) + square(4,col) < 65
  
local pred CheckRowColFull(square:.Square, row:<I[0..4], col:<I[0..4])  iff
    square(row,0) + square(row,1) + square(row,2) + square(row,3) + square(row,4) = 65 & 
    square(0,col) + square(1,col) + square(2,col) + square(3,col) + square(4,col) = 65

local pred CheckRowFull(square:.Square, row:<I[0..4])  iff
    square(row,0) + square(row,1) + square(row,2) + square(row,3) + square(row,4) = 65  

local pred CheckColFull(square:.Square, col:<I[0..4])  iff
    square(0,col) + square(1,col) + square(2,col) + square(3,col) + square(4,col) = 65

local pred CheckRowIncomplete(square:.Square, row:<I[0..4])  iff
    square(row,0) + square(row,1) + square(row,2) + square(row,3) + square(row,4) < 65  
  
local pred CheckColIncomplete(square:.Square, col:<I[0..4])  iff
    square(0,col) + square(1,col) + square(2,col) + square(3,col) + square(4,col) < 65

local pred CheckDiagFull(square:.Square) iff
    square(0,0) + square(1,1) + square(2,2) + square(3,3) + square(4,4) = 65

local pred CheckDiagIncomplete(square:.Square ) iff
    square(0,0) + square(1,1) + square(2,2) + square(3,3) + square(4,4) < 65

local pred CheckInvDiagFull(square:.Square) iff
    square(0,4) + square(1,3) + square(2,2) + square(3,1) + square(4,0) = 65

local pred CheckInvDiagIncomplete(square:.Square) iff
    square(0,4) + square(1,3) + square(2,2) + square(3,1) + square(4,0) < 65

///////////////////////////////////////////////////////////////////////////////
// Print the solution in a more presentable way
local proc PrintFormattedSolution(square:<Square,row:<I) iff
    if row < Len(square) then 
        Print('\n') & PrintOneRow(square(row),0) & PrintFormattedSolution(square,row+1) 
    else
        Print('\n')
    end

local proc PrintOneRow(r:<[0..]->I,col:<I) iff
    if col < Len(r) then
        if r(col) < 10 then 
            Print(' ')
        end &
        Print(' ',r(col)) & PrintOneRow(r,col+1)
    end













This page was created by F1toHTML