Tuesday, December 31, 2024

Monkey Dog Problem - Grid Multiplication

The following problems was recently posted in the IIM-Cal puzzle group (and eventually in my puzzle group as well): Can we fill an $n \times n$ grid with the first $n^2$ integers such that the product of the numbers in the $k$th row equals that of the $k$th column for $1 \leq k \leq n$?

The fact that this is impossible for $n=2$ is easy to show by inspection. Vamshi Jandhyala in our group posted grids with the required property for $3 \times 3$ and $4 \times 4$ (and eventually for $10 \times 10$) using a Python non-linear solver. However, this approach ended up time-expensive for larger grids.

He also showed such grids are impossible whenever $n=9$ or $n>10$ using a nice argument involving the number of primes between $n^2/2$ and $n^2$.

Not wanting to get into the realm of non-linearity, I log'ed the entire grid so that multiplication becomes addition and the problem becomes linear. Note that this made an Integer Non-Linear problem to a Non-Integer Linear problem. Despite precision errors, this approach worked for $n=3,4$ but proved elusive for larger $n$.

Running out of options, I eventually realised that each grid that satisfies the required property can be seen only for a particular prime (their exponents in all the $n^2$ numbers to be specific) while ignoring others.


The top half of the above snapshot shows a solution to a $3 \times 3$ grid for a particular prime. The bottom half shows the same but in terms of the exponents of the prime.

The prime exponents form makes it clear that the problem can indeed be modelled as an Integer Linear Problem which can be solved extremely efficiently using free solvers in Python. In fact, doing so enables us to solve the problem for all the cases in a matter of seconds.

A Solution for $5 \times 5$ grid

A Solution for $6 \times 6$ grid

A Solution for $7 \times 7$ grid

A Solution for $8 \times 8$ grid

Python code used for solving this problem.

from ortools.linear_solver import pywraplp
import numpy as np


def main():
    # Data
    n = 3
    vals = []
    # primes = [2, 3, 5, 7, 11, 13, 17, 19, 23]
    # primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
    # primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61]
    primes = [2, 3, 5, 7]
    primepi = len(primes)
    
    def vals(i0, k):
        i, cnt, p = i0, 0, primes[k]
        while i % p == 0:
            i //= p
            cnt += 1
        return cnt

    # Solver
    # Create the mip solver with the SCIP backend.
    solver = pywraplp.Solver.CreateSolver("SCIP")

    x = {}
    for i in range(n * n):
        for j in range(n * n):
            x[i, j] = solver.IntVar(0, 1, "")
    
    for i in range(n * n):
        solver.Add(solver.Sum([x[i, j] for j in range(n * n)]) == 1)
    
    for j in range(n * n):
        solver.Add(solver.Sum([x[i, j] for i in range(n * n)]) == 1)
    
    y = {}
    for i in range(n * n):
        for k in range(primepi):
            y[i, k] = solver.Sum([x[i, j] * vals(j + 1, k) for j in range(n * n)])

    
    rowsum = {}
    for i in range(n):
        for k in range(primepi):
            rowsum[i, k] = solver.Sum([y[j, k] for j in range(n * i, n * i + n)])
    
    colsum = {}
    for i in range(n):
        for k in range(primepi):
            colsum[i, k] = solver.Sum([y[j, k] for j in range(i, n * n, n)])
    
    for i in range(n):
        for k in range(primepi):
            solver.Add(rowsum[i, k] == colsum[i, k])
    
    solver.Minimize(1)
    status = solver.Solve()

    # Print solution.
    if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
        res = []
        for i in range(n * n):
            for j in range(n * n):
                if x[i, j].solution_value() > 0:
                    res += [j + 1]
        print(res)
    else:
        print("No solution found.")


if __name__ == "__main__":
    main()

That eventually settles the Monkey Dog Problem of Grid multiplication. There is also an interesting question of finding $n$ which has the lowest number of solutions if rotations and reflections are identified. That could be left as an exercise to the reader.

Hope you enjoyed this. See ya later.

Until then
Yours Aye
Me