fork(1) download
  1. from math import gcd
  2. import random
  3.  
  4. def _coprime(a, n):
  5. return gcd(a, n) == 1
  6.  
  7. def _find_linear_coeffs(N, max_tries=5000, rng_seed=None):
  8. """
  9. Find (a1,b1,c1,a2,b2,c2) so that:
  10. - a1,b1,a2,b2 are coprime to N
  11. - det = a1*b2 - a2*b1 is nonzero mod N (orthogonality)
  12. - a1±b1 and a2±b2 are coprime to N (diagonals are permutations)
  13. c1=c2=0 is fine; we keep them 0.
  14. """
  15. if rng_seed is not None:
  16. random.seed(rng_seed)
  17.  
  18. # search space: units modulo N (coprime to N)
  19. units = [x for x in range(1, N) if _coprime(x, N)]
  20. for _ in range(max_tries):
  21. a1, b1 = random.choice(units), random.choice(units)
  22. a2, b2 = random.choice(units), random.choice(units)
  23. if (a1 * b2 - a2 * b1) % N == 0:
  24. continue
  25. # diagonals: need slopes (a±b) to be units too
  26. if not (_coprime((a1 + b1) % N or N, N) and _coprime((a1 - b1) % N or N, N)):
  27. continue
  28. if not (_coprime((a2 + b2) % N or N, N) and _coprime((a2 - b2) % N or N, N)):
  29. continue
  30. return a1, b1, 0, a2, b2, 0
  31.  
  32. raise RuntimeError(f"Could not find suitable coefficients for N={N} (try increasing max_tries).")
  33.  
  34. def build_magic_square(N, base=None, rng_seed=None):
  35. """
  36. Build an N×N 'digit-balanced' magic square for odd N.
  37. Values are B*L1 + L2 where L1,L2 are orthogonal Latin squares
  38. with diagonal-permutation property.
  39. Returns the matrix and the base B.
  40. """
  41. if N <= 0 or N % 2 == 0:
  42. raise ValueError("N must be a positive odd integer.")
  43. B = max(10, N) if base is None else base
  44. a1, b1, c1, a2, b2, c2 = _find_linear_coeffs(N, rng_seed=rng_seed)
  45.  
  46. A = [[0]*N for _ in range(N)]
  47. for i in range(N):
  48. for j in range(N):
  49. l1 = (a1*i + b1*j + c1) % N
  50. l2 = (a2*i + b2*j + c2) % N
  51. A[i][j] = B*l1 + l2
  52. return A, B, (a1, b1, a2, b2)
  53.  
  54. def verify_properties(A, B):
  55. """
  56. Verify that all rows, columns, main & anti-diagonals have the same sum.
  57. Also checks 'inverse' (swap tens/ones -> swap L1 and L2) keeps the same sum.
  58. """
  59. N = len(A)
  60. # row/col sums
  61. row_sums = [sum(r) for r in A]
  62. col_sums = [sum(A[i][j] for i in range(N)) for j in range(N)]
  63. target = row_sums[0]
  64. ok_rows = all(s == target for s in row_sums)
  65. ok_cols = all(s == target for s in col_sums)
  66. main = sum(A[i][i] for i in range(N))
  67. anti = sum(A[i][N-1-i] for i in range(N))
  68. ok_diags = (main == target) and (anti == target)
  69.  
  70. # inverse: swap digits (tens<->ones), i.e., A' = (A % B)*B + (A // B)
  71. inv = [[(x % B)*B + (x // B) for x in row] for row in A]
  72. inv_row = [sum(r) for r in inv]
  73. ok_inverse = all(s == inv_row[0] == target for s in inv_row) and \
  74. all(sum(inv[i][j] for i in range(N)) == target for j in range(N)) and \
  75. sum(inv[i][i] for i in range(N)) == target and \
  76. sum(inv[i][N-1-i] for i in range(N)) == target
  77.  
  78. return {
  79. "target_sum": target,
  80. "rows_ok": ok_rows,
  81. "cols_ok": ok_cols,
  82. "diagonals_ok": ok_diags,
  83. "inverse_ok": ok_inverse
  84. }
  85.  
  86. if __name__ == "__main__":
  87. # demo
  88. N = 5 # <- change this to any odd N (3,5,7,9,...)
  89. A, B, coeffs = build_magic_square(N, rng_seed=42)
  90. props = verify_properties(A, B)
  91.  
  92. print(f"N={N}, base B={B}, coefficients (a1,b1,a2,b2)={coeffs}")
  93. print("Magic sum:", props["target_sum"])
  94. print("Rows/Cols/Diagonals OK? ", props["rows_ok"], props["cols_ok"], props["diagonals_ok"])
  95. print("Inverse (swap digits) also OK? ", props["inverse_ok"])
  96. print("\nSquare:")
  97. for row in A:
  98. print(" ".join(f"{x:>3d}" for x in row))
Success #stdin #stdout 0.14s 14296KB
stdin
4
stdout
N=5, base B=10, coefficients (a1,b1,a2,b2)=(4, 2, 4, 3)
Magic sum: 110
Rows/Cols/Diagonals OK?  True True True
Inverse (swap digits) also OK?  True

Square:
  0  23  41  14  32
 44  12  30   3  21
 33   1  24  42  10
 22  40  13  31   4
 11  34   2  20  43