OK, so I know that I may be jumping the gun here since there is another week of CrossProduct yet to come, so I am sorry. SPOILER ALERT!!!! So, you know avert your eyes, if you want to keep solving CrossProduct the old fashioned way with pen and paper, which I definitely did for the first two weeks and even for a first stab at this week's. However, as fun as this logic puzzle has been, I figured that I could outsource the heavy lifting to a much better logician with more brain power ... so I built a generic solver.
The general gist of the solver is not too dissimilar from the way that I typically attacked the problem with pen and paper: use the prime factorizations of the inputs to recast the puzzle as an integer constraint satisfaction problem. Assume we have a CrossProduct puzzle with I rows and J columns.
For each element ni in the input column, we can write the unique prime factorization as ni=2Ai3Bi5Ci7Di,i=1,…,I.
Let uij,vij,wij,xij be nonnegative integers for i∈{1,2,…,I},j∈{1,2,…,J}. We want to model the entry in the ith row and jth column as fij=2uij3vij5wij7xij,
The condition that fij is a single digit number can be translated to the linear condition ln(2)uij+ln(3)vij+ln(5)wij+ln(7)xij≤ln(9),i=1,…,I,j=1,…,J.
Using the mip solver for Python, I set up in a small Jupyter notebook, which is pasted below. The solver is very quick, at least for the type of problem sizes that one could hope to solve by pen and paper. I haven't fully tested the bounds of its perfomance with either I or J get big, but presumably they would still be better than solving by hand... Again, last chance to avert your eyes!!!!
import numpy as np
from mip import Model, xsum, INTEGER
def get_prime_factors(number, primes=[2, 3, 5, 7]):
factors = ()
for p in primes:
k = 0
while number % p == 0:
k += 1
number /= p
factors += (k,)
return factors
def solve_cross_product(column, row):
n_rows = len(column)
n_cols = len(row)
crossProduct = Model()
u = [[crossProduct.add_var('u({},{})'.format(i,j), var_type=INTEGER) for j in range(n_cols)] for i in range(n_rows)]
v = [[crossProduct.add_var('v({},{})'.format(i,j), var_type=INTEGER) for j in range(n_cols)] for i in range(n_rows)]
w = [[crossProduct.add_var('w({},{})'.format(i,j), var_type=INTEGER) for j in range(n_cols)] for i in range(n_rows)]
x = [[crossProduct.add_var('x({},{})'.format(i,j), var_type=INTEGER) for j in range(n_cols)] for i in range(n_rows)]
for i in range(n_rows):
for j in range(n_cols):
## all values must be nonnegative integers
crossProduct += u[i][j] >= 0
crossProduct += v[i][j] >= 0
crossProduct += w[i][j] >= 0
crossProduct += x[i][j] >= 0
## all entries must yield single digit entries
crossProduct += np.log(2.0) * u[i][j] + np.log(3.0) * v[i][j] + np.log(5.0) * w[i][j] + np.log(7.0) * x[i][j] <= np.log(9.0)
## each row must have the appropriate factors in order to multiply to the values in the input column
for i, c_ in enumerate(column):
factors = get_prime_factors(c_)
crossProduct += xsum(u[i][j] for j in range(n_cols)) == factors[0]
crossProduct += xsum(v[i][j] for j in range(n_cols)) == factors[1]
crossProduct += xsum(w[i][j] for j in range(n_cols)) == factors[2]
crossProduct += xsum(x[i][j] for j in range(n_cols)) == factors[3]
## each column must have the appropriate factors in order to multiply to the values in the input row
for j, r_ in enumerate(row):
factors = get_prime_factors(r_)
crossProduct += xsum(u[i][j] for i in range(n_rows)) == factors[0]
crossProduct += xsum(v[i][j] for i in range(n_rows)) == factors[1]
crossProduct += xsum(w[i][j] for i in range(n_rows)) == factors[2]
crossProduct += xsum(x[i][j] for i in range(n_rows)) == factors[3]
status = crossProduct.optimize()
if crossProduct.num_solutions:
y = np.ones((n_rows, n_cols))
for v in crossProduct.vars:
name = v.name
row_, col_ = v.name.split('(')[-1].split(')')[0].split(',')
val = v.x
y[int(row_),int(col_)] *= 2**val if name[0] == 'u' else 3**val if name[0] == 'v' else 5**val if name[0] == 'w' else 7**val
print(y)
column = np.array([280, 168, 162, 360, 60, 256, 126])
row = np.array([183708, 245760, 117600])
solve_cross_product(column, row)
No comments:
Post a Comment