From 0f22cd789dacc5d5af5ae2511db0e2ab7746ae83 Mon Sep 17 00:00:00 2001 From: Juni Date: Mon, 27 Nov 2023 15:43:16 -0500 Subject: [PATCH] first commit --- .dockerignore | 160 +++++++++++++++++++++++++++++ .gitignore | 161 +++++++++++++++++++++++++++++ Dockerfile | 11 ++ app.py | 32 ++++++ requirements.txt | 19 ++++ solver.py | 240 ++++++++++++++++++++++++++++++++++++++++++++ templates/page.html | 70 +++++++++++++ 7 files changed, 693 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 requirements.txt create mode 100644 solver.py create mode 100644 templates/page.html diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0933e32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,161 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +.DS_Store +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fa06c13 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:latest + +WORKDIR /app +COPY ./requirements.txt /app/ +EXPOSE 8000 + +RUN pip install -r requirements.txt + +COPY . /app + +CMD ["gunicorn", "app:app", "-w", "3", "-b", "0.0.0.0:8000"] diff --git a/app.py b/app.py new file mode 100644 index 0000000..8cf517a --- /dev/null +++ b/app.py @@ -0,0 +1,32 @@ +from flask import Flask, render_template, request +import solver + +app = Flask(__name__) + + +@app.route("/", methods=["GET", "POST"]) +def index(): + if request.method == "GET": + return render_template("page.html") + else: + try: + M = int(request.form["M"]) + N = int(request.form["N"]) + except ValueError: + return render_template("page.html", invalid=True) + if not (1 <= M <= 40 and 1 <= N <= 40): + return render_template("page.html", invalid=True) + + g = solver.construct(M, N) + if g is None: + return render_template("page.html", nosol=True, M=M, N=N) + else: + image, works = g.plot() + return render_template("page.html", image=image, works=works, M=M, N=N) + + +if __name__ == "__main__": + app.run( + host="0.0.0.0", + port=8000 + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3937b0e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,19 @@ +blinker==1.7.0 +click==8.1.7 +contourpy==1.2.0 +cycler==0.12.1 +Flask==3.0.0 +fonttools==4.45.1 +gunicorn==21.2.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +kiwisolver==1.4.5 +MarkupSafe==2.1.3 +matplotlib==3.8.2 +numpy==1.26.2 +packaging==23.2 +Pillow==10.1.0 +pyparsing==3.1.1 +python-dateutil==2.8.2 +six==1.16.0 +Werkzeug==3.0.1 diff --git a/solver.py b/solver.py new file mode 100644 index 0000000..283ecf0 --- /dev/null +++ b/solver.py @@ -0,0 +1,240 @@ +#!/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 diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..b0f5e22 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,70 @@ + + + + USAMTS 2/5/35 Solution + + + + + + + + +
+

Problem

+

+ Let \(m\) and \(n\) be positive integers. Let \(S\) be the set of all points + \((x, y)\) with integer coordinates such that \(1 \leq x, y \leq m + n − 1\) + and \(m + 1 \leq x + y \leq 2m + n − 1\). Let L be the set of the \(3m + 3n − + 3\) lines parallel to one of \(x = 0\), \(y = 0\), or \(x + y = 0\) and passing + through at least one point in \(S\). For which pairs \((m, n)\) does there + exist a subset \(T\) of \(S\) such that every line in \(L\) intersects an odd + number of elements of \(T\)? +

+

Proof

+

+ The answer is just all \((m,n)\) such that \( (m+n-1)(m-n) \) is divisible + by 4. The sum of all the x and y coordinates should clearly be even, but + the sum of all \(x+y \mod 2\) is \( \frac{(m+n-1)(3m+n)}{2} \). For a + construction to even be viable, this quantity clearly needs to be even. +

+

Constructor

+ {% if invalid %} +

Invalid Input!

+ {% endif %} + {% if nosol %} +

No Valid Solution is Possible!

+ {% endif %} + {% if image %} + Solution Image + {% endif %} + {% if works == False %} +

Claimed Construction Fails!!!!

+ {% endif %} +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ +