pythonnumpymatrixmagic-square

NumPy equivalent of Matlab's magic()


In Ocatave / Matlab, I can use magic() to get a magic square, e.g.,

magic(4)

  16    2    3   13
   5   11   10    8
   9    7    6   12
   4   14   15    1

Definition: A magic square is an N×N grid of numbers in which the entries in each row, column and main diagonal sum to the same number (equal to N(N^2+1)/2).

How can I generate the same using NumPy?


Solution

  • This implementation follows Matlab's and should give exactly the same results with the following exception: it throws an error if n < 3 rather than return a non-magic square [[1, 3], [4, 2]] when n=2 like Matlab does.

    As usual, there are three cases: odd, divisible by 4, and even but not divisible by 4, the last one being the most complicated.

    def magic(n):
      n = int(n)
      if n < 3:
        raise ValueError("Size must be at least 3")
      if n % 2 == 1:
        p = np.arange(1, n+1)
        return n*np.mod(p[:, None] + p - (n+3)//2, n) + np.mod(p[:, None] + 2*p-2, n) + 1
      elif n % 4 == 0:
        J = np.mod(np.arange(1, n+1), 4) // 2
        K = J[:, None] == J
        M = np.arange(1, n*n+1, n)[:, None] + np.arange(n)
        M[K] = n*n + 1 - M[K]
      else:
        p = n//2
        M = magic(p)
        M = np.block([[M, M+2*p*p], [M+3*p*p, M+p*p]])
        i = np.arange(p)
        k = (n-2)//4
        j = np.concatenate((np.arange(k), np.arange(n-k+1, n)))
        M[np.ix_(np.concatenate((i, i+p)), j)] = M[np.ix_(np.concatenate((i+p, i)), j)]
        M[np.ix_([k, k+p], [0, k])] = M[np.ix_([k+p, k], [0, k])]
      return M 
    

    I also wrote a function to test this:

    def test_magic(ms):
      n = ms.shape[0]
      s = n*(n**2+1)//2 
      columns = np.all(ms.sum(axis=0) == s)
      rows = np.all(ms.sum(axis=1) == s)
      diag1 = np.diag(ms).sum() == s 
      diag2 = np.diag(ms[::-1, :]).sum() == s
      return columns and rows and diag1 and diag2 
    

    Try [test_magic(magic(n)) for n in range(3, 20)] to check the correctness.