Construct a magic square of any size

Magic squares are cool. Algorithms that create magic squares are even cooler.

You probably remember magic squares from your childhood: they are n x n matrices that contain the numbers 1,2,...,n2 and for which the row sum, column sum, and the sum of both diagonals are the same value. There are many algorithms to generate magic squares. I remember learning as a child how to construct a magic square for any odd number, n, by using the "Siamese method." I also remember being fascinated by Ben Franklin's construction of "semi-magic squares" in which he used the sum of "bent diagonals" instead of straight diagonals.

Last week I had email discussions with several readers who had the idea to generate magic squares in SAS. The readers noted that my recent blog post on "How to return multiple values from a SAS/IML function," which features a module that computes row sums and column sums of a matrix, could be extended to check whether a matrix is a magic square.

When I first came to SAS, I was disappointed because there is no function in the SAS/IML language that generates magic squares. Prior to arriving at SAS, I had used MATLAB for matrix computations, and the MATLAB documentation is full of calls to the magic function, which generates magic squares of any size. An internet search for "magic squares in SAS" did not yield any useful results. However, when I searched for "MATLAB magic squares," I was thrilled to discover that Cleve Moler—cofounder of The MathWorks and numerical analyst extraordinaire—has written an e-book (C. Moler, 2011, Experiments with MATLAB) that includes a chapter on the construction of magic squares!

The following program implements Moler's algorithm in the SAS/IML language. For the description of the algorithm, see Chapter 10 (p. 7–10) of Moler's e-book.

proc iml;
 
start Magic(n);
/* Moler's algorithm for constructing a magic square for any n.
   See Moler, C. (2011) "Magic Square," Chapter 10 in 
   Experiments with MATLAB, p. 7-10
   E-Book: http://www.mathworks.com/moler/exm/chapters/magic.pdf
*/
 
   /* define helper functions */
   start ModPos(a, q); /* always return positive value for mod(a,q) */
      return( a - q*floor(a/q) );
   finish;
 
   /* Magic square when n is odd */
   start MagicOdd(n);
      I = repeat( T(1:n), 1, n );
      J = repeat( 1:n, n );  /* or T(I) */
      A = ModPos(I+J-(n+3)/2, n); /* modify formula to match MATLAB output */
      B = mod(I+2*J-2, n);
      return( n*A + B + 1 );
   finish;
 
   /* Magic square when n is a multiple of 4 */
   start Magic4(n);
      M = shape(1:n##2, n, n);
      I = repeat( T(1:n), 1, n );
      J = repeat( 1:n, n );  /* or T(I) */
      idx = loc(floor(mod(I,4)/2) = floor(mod(J,4)/2));
      M[idx] = n##2 + 1 - M[idx];
      return( M );
   finish;
 
   /* Magic square when n>4 is even but not a multiple of 4 */
   start Magic2(n);
      s = n/2;           /* s is odd */
      A = MagicOdd(s);
      M = (A         || (A + 2*s##2)) // /* 2x2 blocks of magic squares */
         ((A+3*s##2) || (A +   s##2));
      /* col sums are correct; permute within cols to adjust row sums */
      i = 1:s;
      r = (n-2)/4;
      h = do(n-r+2,n,1); /* might be empty */
      j = (1:r) || h;
      M[i || i+s, j] = M[i+s || i, j]; /* swap rows for cols in j */
      i = r+1;
      j = 1 || i;
      M[i || i+s, j] = M[i+s || i, j]; /* swap rows for cols in j */
      return( M );
   finish;
 
   /* main algorithm */
   if n<=1 then return(1);
   if n=2 then return( {. ., . .} ); /* no magic square exists */
   if mod(n,2)=1 then return( MagicOdd(n) ); /* n is odd */
   if mod(n,4)=0 then return( Magic4(n) );   /* n is divisible by 4 */
   return( Magic2(n) );        /* n is divisible by 2, but not by 4 */
finish;

The implementation uses two interesting features. First, I define a helper function that always returns a positive value for the expression mod(a,q), because the MOD function in SAS can sometimes return a negative value. Secondly, the computation for the Magic2 function constructs a block matrix of size 2n from a magic square of size n. Ian Wakeling told me that this is an application of a general technique to construct a large magic square from smaller components.

To test the implementation, you can construct and print a few magic squares, as follows:

 
do n = 3 to 6;
   M = Magic(n);
   L = cat("Magic Square, n=", char(n,1)); /* form label */
   print M[label=L];
end;

At long last, I have my wish: a function in PROC IML that generates magic squares!

Update: As of SAS/IML 12.3, the MAGIC function is built into SAS.

Do you have a favorite memory regarding magic squares? Do you have a favorite algorithm that constructs a magic square? Post a comment!

tags: Just for Fun, Matrix Computations

10 Comments

  1. Ian Wakeling
    Posted August 29, 2012 at 6:36 am | Permalink

    Very nice indeed Rick, thank you for sharing your code with us. It does bring back old memories.

    I would like to mention another method for constructing magic squares which is based on obtaining Latin squares from PROC FACTEX (SAS/QC) with additional code in IML. For those who would like to try, I have posted my program to the IML Discussion Forum here.

    https://communities.sas.com/thread/37262

    The IML part of the code is based on constructing larger squares from smaller ones, the first and coolest, is a simple multiplication of two squares of size m and n, to give a square of size m x n. The direct product operator '@' available in IML makes this trivially easy:

    start MagicProduct(X,Y);
    m=nrow(X);
    n=nrow(Y);
    return( j(n,n,1)@X + (Y-1)@j(m,m,m#m) );
    finish;

    There is also a doubling method which I believe is essentially equivalent to your module called Magic2(n) above (for the method see this web page http://www.grogono.com/magic/10x10.php). I have not done extensive tests, but you could try using the following code instead:

    start Magic2(n);
    m=n/2;
    v=0 || j(1, m, 1) || j(1, m-1, 0);
    v=repeat( v//(1-v), m, 1); /* basic pattern */
    v[(n-1):n, ]=v[(n-1):n, n:1]; /* reverse last two rows */
    return( (MagicOdd(m)-1)@j(2,2,4) + 2#v + t(v)[,n:1] +1);
    finish;

    In my program this module is called Magic2Q, and the last line looks slightly different as my program makes squares where the smallest element is a zero rather than one.

  2. Ian Wakeling
    Posted August 31, 2012 at 5:21 am | Permalink

    I have found a rearrangement of the formula for the MagicOdd construction that will guarantee that the first argument of the two mod functions can never go negative, so in this instance the helper function is not necessary.

    start MagicOdd(n);
    I = repeat( T(1:n), 1, n );
    J = repeat( ((n-1)/2) : (3#(n-1)/2), n );
    A = mod(I + J, n);
    B = mod(I + 2#J + 1, n);
    return( n*A + B + 1);
    finish;

    • Posted August 31, 2012 at 5:53 am | Permalink

      Thanks, Ian! I programmed it the way I did because that's the way that it was constructed in Moler's book. In MATLAB mod(a,q) is always positive when q>0, so I wrote the helper funtion to mimic the MATLAB mod() function. But you're right: in practice there is no need for the helper function.

  3. Laura Cappelletti
    Posted August 31, 2012 at 11:38 am | Permalink

    Very cool Rick....I bet you can solve my "evil" level Sudoku's with your magic too!

  4. Peter Borysov
    Posted September 4, 2012 at 8:40 am | Permalink

    This post is on the sas.com main page! It should really increase the ranking of you page in google:) I think every language that uses operations with matrices should have this example. I thought that magic square was really cool when I started learning matlab.

  5. Craig Knecht
    Posted October 14, 2012 at 8:47 pm | Permalink

    The wiki on Associative Magic Squares provides two references for recent programs that create magic squares. These programs utilize Local Search and Tabu search. The program on Sourceforge searches for the maximum water retention on magic squares. The Complete utility on the download section of Harry White's website allows one to specify certain values in a magic square and it will complete the skeleton and make it magic. Check out the 14 x 14 magic square with water writing in the square at the end of the wiki noted above.

    Thanks for all the coments above !

    Craig

  6. Ian Wakeling
    Posted March 27, 2014 at 7:49 am | Permalink

    Hi Rick,

    Here's a rather unexpected way of making new magic squares from old.

    A ring of magic powers!

    x = magic(4);
    print x [f=2.0];
    do cycle = 1 to 8;
    x = mod( x**5, 17);
    print x [f=2.0];
    end;

    The original square, and the one from cycle 1, are rotated anticlockwise in steps of 90 degrees.

    Can I make an animated heatmap to visualise something like this?

    • Posted March 27, 2014 at 9:28 am | Permalink

      Amazing. You'll have to explain why multiplying the matrix by itself 5 times (why 5?) mod 17 results in another magic square. Very cool.

      Speaking of rotating matrices, check out this blog post: http://blogs.sas.com/content/iml/2013/10/18/rotating-matrices/

      In SAS/IML 13.1 you can create heat maps to visualize matrices with the HEATMAPCONT and HEATMAPDISC subroutines. I need to think about how to animate the results.

      • Ian Wakeling
        Posted March 27, 2014 at 2:26 pm | Permalink

        I am not totally sure, other than to say that is to do with the pattern of eigenvalues. I read "Odd Magic Powers", A. C. Thompson, The American Mathematical Monthly, 101(1994), pp. 339-342, and then started experimenting in IML. Only odd powers will work and it seems that squares of order 4n produced by your function are specially symmetrical (but not pan-diagonal).
        .
        I have an even more fantastic example. This produces a set of 6 magic squares!
        .
        x = magic(8);
        print x [f=3.0];
        do cycle=1 to 6;
        x = rank( mod( x**5, 346) );
        print x [f=3.0];
        end;
        .
        If you try larger squares or larger powers, be careful not to exceed the largest integer that can be exactly represented.

  7. Bay Ima
    Posted April 2, 2014 at 10:09 am | Permalink

    Dr Rick,

    In your program that implements Moler's algorithm in the SAS/IML language, the computation for the Magic2 function constructs a block matrix of size 2n from a magic square of size n, as you said it. Don’t you think that the amount of time needed when n is big (n > 100) can be reduced if a well-defined function f(i, j) is stored for any variable n? Don’t you think that this renders the generation of magic squares more straight?

    Contact me if necessary, I mean if you are in search of such a function.

    Best wishes
    Bay Ima

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>