You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
6.2 KiB
240 lines
6.2 KiB
#!/usr/bin/env python3
|
|
|
|
import io
|
|
import base64
|
|
|
|
from matplotlib import pyplot as plt
|
|
import numpy
|
|
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
|
from matplotlib.figure import Figure
|
|
|
|
|
|
class Grid:
|
|
def __init__(self, M, N):
|
|
self.N = N
|
|
self.M = M
|
|
self.grid = [[0 for _ in range(M+N-1)] for _ in range(M+N-1)]
|
|
|
|
def check_bounds(self, x, y):
|
|
return 1 <= x <= self.M+self.N-1 and\
|
|
1 <= y <= self.M+self.N-1 and\
|
|
self.M+1 <= x+y <= 2*self.M + self.N - 1
|
|
|
|
def get(self, x, y):
|
|
if not self.check_bounds(x, y):
|
|
return 0
|
|
return self.grid[x-1][y-1]
|
|
|
|
def select(self, x, y):
|
|
assert self.check_bounds(x, y)
|
|
self.grid[x-1][y-1] = 1
|
|
|
|
def grid_parity(self):
|
|
for x in range(1, self.M+self.N):
|
|
nums = [self.get(x, y) for y in range(1, self.N + self.M)]
|
|
if sum(nums) % 2 != 1:
|
|
print(f"Found a contradiction at x={x}! {nums}")
|
|
return False
|
|
for y in range(1, self.M+self.N):
|
|
nums = [self.get(x, y) for x in range(1, self.N + self.M)]
|
|
if sum(nums) % 2 != 1:
|
|
print(f"Found a contradiction at y={y}! {nums}")
|
|
return False
|
|
for s in range(self.M+1, 2*self.M+self.N):
|
|
nums = [self.get(x, s-x) for x in range(1, s)]
|
|
if sum(nums) % 2 != 1:
|
|
print(f"Found a contradiction at x+y={s}! {nums}")
|
|
return False
|
|
return True
|
|
|
|
def reflect(self):
|
|
res = [[0 for _ in range(len(self.grid[i]))]
|
|
for i in range(len(self.grid))]
|
|
for x in range(self.M+self.N-1):
|
|
# reflect across x + y = m + n - 2
|
|
for y in range(self.M+self.N-1):
|
|
# sm = 2 * (self.M+self.N-2) - (x+y)
|
|
# diff = x - y
|
|
res[self.M+self.N-2-x][self.M+self.N-2-y] = self.grid[x][y]
|
|
self.grid = res
|
|
tmp = self.M
|
|
self.M = self.N
|
|
self.N = tmp
|
|
|
|
def plot(self) -> tuple[str, bool]:
|
|
fig = Figure()
|
|
ax = fig.add_subplot(1, 1, 1)
|
|
ax.set_xticks(numpy.arange(1, self.M+self.N, 1))
|
|
ax.set_yticks(numpy.arange(1, self.M+self.N, 1))
|
|
ax.set_aspect("equal")
|
|
|
|
ax.set_xbound(0, self.M+self.N)
|
|
ax.set_ybound(0, self.M+self.N)
|
|
ax.autoscale(enable=False)
|
|
x = []
|
|
y = []
|
|
for i in range(1, self.M+self.N):
|
|
for j in range(1, self.M+self.N):
|
|
if self.get(i, j):
|
|
x.append(i)
|
|
y.append(j)
|
|
ax.scatter(x, y, color='b')
|
|
# plt.title(f"Construction for M={self.M}, N={self.N}")
|
|
ax.grid()
|
|
|
|
ax.plot([1, self.M], [self.M, 1], color='r')
|
|
ax.plot([self.M, self.N+self.M-1],
|
|
[self.N+self.M-1, self.M], color='r')
|
|
ax.plot([1, 1, self.M], [self.M, self.M +
|
|
self.N-1, self.M+self.N-1], color='r')
|
|
ax.plot([self.M, self.M+self.N-1, self.M+self.N-1],
|
|
[1, 1, self.M], color='r')
|
|
pngImage = io.BytesIO()
|
|
FigureCanvas(fig).print_png(pngImage)
|
|
pngImageB64String = "data:image/png;base64,"
|
|
pngImageB64String += base64.b64encode(
|
|
pngImage.getvalue()).decode('utf8')
|
|
return pngImageB64String, self.grid_parity()
|
|
|
|
|
|
def one(M, N) -> Grid:
|
|
assert (N-M) % 4 == 0
|
|
assert M == 1 or N == 1
|
|
reflect = M == 1
|
|
if reflect:
|
|
tmp = M
|
|
M = N
|
|
N = tmp
|
|
g = Grid(M, N)
|
|
for k in range(1, M+1):
|
|
g.select(k, M+1-k)
|
|
|
|
for k in range(1, M//2+1):
|
|
g.select((M+1)//2, (M+1)//2+k)
|
|
g.select(M, (M+1)//2+k)
|
|
|
|
if reflect:
|
|
g.reflect()
|
|
return g
|
|
|
|
|
|
def cong_mod4(M, N) -> Grid:
|
|
assert (N-M) % 4 == 0 and N != 1 and M != 1
|
|
reflect = M > N
|
|
if reflect:
|
|
tmp = M
|
|
M = N
|
|
N = tmp
|
|
g = Grid(M, N)
|
|
|
|
if M % 2 == 1:
|
|
g.select(M, M)
|
|
|
|
# Main Axis points
|
|
for k in range(1, N//2+1):
|
|
g.select(M, M+2*k-1)
|
|
for k in range(1, (M-1)//2+1):
|
|
g.select(M, M-2*k)
|
|
|
|
for k in range(1, (N-1)//2+1):
|
|
g.select(M+2*k, M)
|
|
for k in range(1, M//2+1):
|
|
g.select(M-2*k+1, M)
|
|
|
|
# Points on the diagonal
|
|
for k in range(1, M//2 + 1):
|
|
g.select(M+2*k-1, M-2*k+1) # going down
|
|
for k in range(1, (M-1)//2+1):
|
|
g.select(M-2*k, M+2*k) # going up
|
|
|
|
for k in range(1, (N-M)//2+1):
|
|
g.select(
|
|
M+2*(M//2) - 1 + 2*k,
|
|
M-2*(M//2) + 2,
|
|
)
|
|
g.select(
|
|
M - 2*((M-1)//2) + 1,
|
|
M + 2*((M-1)//2) + 2*k,
|
|
)
|
|
if reflect:
|
|
g.reflect()
|
|
return g
|
|
|
|
|
|
def mod_0_1(M, N):
|
|
assert (M+N) % 4 == 1 and M % 4 in [0, 1]
|
|
reflect = (M % 4) == 1
|
|
if reflect:
|
|
tmp = M
|
|
M = N
|
|
N = tmp
|
|
g = Grid(M, N)
|
|
|
|
g.select(M, M)
|
|
# Main Axis Points
|
|
for k in range(1, N//2+1):
|
|
g.select(M, M+2*k-1)
|
|
for k in range(1, (M-1)//2+1):
|
|
g.select(M, M-2*k)
|
|
|
|
for k in range(1, (N-1)//2+1):
|
|
g.select(M+2*k, M)
|
|
for k in range(1, M//2+1):
|
|
g.select(M-2*k+1, M)
|
|
# Step 3
|
|
for k in range(0, M//2):
|
|
g.select(M-2*k, 1+2*k)
|
|
|
|
# Tail
|
|
for k in range(1, (N-1)//2+1):
|
|
g.select(2, M+2*k)
|
|
g.select(M+2*k-1, 3)
|
|
|
|
if reflect:
|
|
g.reflect()
|
|
return g
|
|
|
|
|
|
def mod_2_3(M, N):
|
|
assert (M+N) % 4 == 1 and M % 4 in [2, 3]
|
|
reflect = (M % 4) == 2
|
|
if reflect:
|
|
tmp = M
|
|
M = N
|
|
N = tmp
|
|
g = Grid(M, N)
|
|
|
|
for k in range(1, M+1):
|
|
g.select(k, M+1-k)
|
|
|
|
for k in range((M+1)//2+1, M+1):
|
|
g.select((M+1)//2, k)
|
|
g.select(M+1, k)
|
|
g.select((M+1)//2, M+1)
|
|
|
|
for k in range(1, (N-2)//2 + 1):
|
|
g.select(M+1, M+2*k)
|
|
g.select(M+2*k, M)
|
|
|
|
g.select(1, M+1+2*k)
|
|
g.select(M+1+2*k, 1)
|
|
|
|
if reflect:
|
|
g.reflect()
|
|
return g
|
|
|
|
|
|
def construct(M, N) -> 'Grid | None':
|
|
if (M-N) % 4 == 0:
|
|
if M != 1 and N != 1:
|
|
return cong_mod4(M, N)
|
|
else:
|
|
return one(M, N)
|
|
elif (M+N) % 4 == 1:
|
|
if (M % 4) in [0, 1]:
|
|
return mod_0_1(M, N)
|
|
else:
|
|
return mod_2_3(M, N)
|
|
else:
|
|
assert ((N+M-1)*(N-M)) % 4 == 2
|
|
return None
|