From 7d733d80036b2ce45d161c3e1328d1ac21046f19 Mon Sep 17 00:00:00 2001 From: filifa Date: Thu, 29 May 2025 22:12:53 -0400 Subject: [PATCH] add problem 44 --- notebooks/problem0044.ipynb | 271 ++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 notebooks/problem0044.ipynb diff --git a/notebooks/problem0044.ipynb b/notebooks/problem0044.ipynb new file mode 100644 index 0000000..7b235a6 --- /dev/null +++ b/notebooks/problem0044.ipynb @@ -0,0 +1,271 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e0ff1edf", + "metadata": {}, + "source": [ + "# [Pentagon Numbers](https://projecteuler.net/problem=44)\n", + "\n", + "It's not difficult to test if a number is [pentagonal](https://en.wikipedia.org/wiki/Pentagonal_number)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "76d738b8", + "metadata": {}, + "outputs": [], + "source": [ + "from math import sqrt\n", + "\n", + "def is_pentagonal(x):\n", + " n = (sqrt(24*x+1) + 1) / 6\n", + " return n == int(n)" + ] + }, + { + "cell_type": "markdown", + "id": "04ec4508", + "metadata": {}, + "source": [ + "With this function, and given a value $j$, we can attempt to find a value $k$ such that $P_j + P_k$ and $P_j - P_k$ are both pentagonal by just iterating over all $k < j$." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3b7bfb29", + "metadata": {}, + "outputs": [], + "source": [ + "def pentagonal_pair(j):\n", + " a = polygonal_number(5, j)\n", + " for k in reversed(range(1, j)):\n", + " b = polygonal_number(5, k)\n", + " if is_pentagonal(a + b) and is_pentagonal(a - b):\n", + " return k\n", + " \n", + " return None" + ] + }, + { + "cell_type": "markdown", + "id": "5b9d6594", + "metadata": {}, + "source": [ + "A naive approach that happens to work is to just iterate over values of $j$ until this function returns a solution." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "182f53fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(2167, 1020)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from itertools import count\n", + "\n", + "for j in count(2):\n", + " k = pentagonal_pair(j)\n", + " if k is not None:\n", + " break\n", + "\n", + "j, k" + ] + }, + { + "cell_type": "markdown", + "id": "ac17792a", + "metadata": {}, + "source": [ + "We can then recover the pentagonal numbers from these indices." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1ec74c04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(7042750, 1560090)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a, b = polygonal_number(5, j), polygonal_number(5, k)\n", + "a, b" + ] + }, + { + "cell_type": "markdown", + "id": "a8756e09", + "metadata": {}, + "source": [ + "And calculate their difference." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "42e1eb38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5482660" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a - b" + ] + }, + { + "cell_type": "markdown", + "id": "54b24ef2", + "metadata": {}, + "source": [ + "This turns out to be the answer! The thing is... we sort of just got lucky that the first $j,k$ pair we found happened to minimize $|P_j - P_k|$. If you care to find out how we can *verify* that this is the minimal solution, read on.\n", + "\n", + "## Diophantine approach\n", + "Let's restate the problem. We are looking for positive integers $j, k, s, d$ such that\n", + "$$P_j + P_k = P_s$$\n", + "$$P_j - P_k = P_d$$\n", + "\n", + "Suppose $d$ is some constant. Then we can solve for $j$ and $k$ in the second equation using Diophantine methods. SageMath can do this for us with [solve_diophantine](https://doc.sagemath.org/html/en/reference/calculus/sage/symbolic/expression.html), but we can do it more efficiently ourselves.\n", + "\n", + "$P_j - P_k = P_d$ expands to\n", + "$$3j^2 - j - 3k^2 + k = 3d^2 - d$$\n", + "We can factor the left hand side into\n", + "$$(j-k)(3j + 3k - 1) = 3d^2 - d$$\n", + "This means that any solution $j,k$ corresponds to a factorization of $3d^2 - d$. Importantly, this means there's a finite number of solutions for a fixed $d$.\n", + "\n", + "So if we factor $3d^2 - d = ab$, and set $a=j-k$ and $b=3j+3k-1$, we can solve for $j$ and $k$:\n", + "$$k = \\frac{b+1-3a}{6}$$\n", + "$$j = a + k$$\n", + "If we iterate over all possible factorizations $ab$ of $3d^2 - d$, this will let us find all $j,k$ that solve $P_j - P_k = P_d$.\n", + "\n", + "Once we have found candidate pairs for $j$ and $k$, we can just check if $P_j + P_k$ is also a pentagonal number using the function from above - if so, $j$ and $k$ also satisfy $P_j + P_k = P_s$, and we've found a solution for our fixed value of $d$.\n", + "\n", + "Here's a function that applies all of this to find solutions (if they exist) for a given $d$." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "21b7a57d", + "metadata": {}, + "outputs": [], + "source": [ + "def pentagonal_sums(d):\n", + " psums = set()\n", + " N = 3*d^2 - d\n", + " for a in divisors(N):\n", + " b = N / a\n", + " \n", + " k = (b + 1 - 3*a) / 6\n", + " if k != int(k) or k <= 0:\n", + " continue\n", + " \n", + " j = a + k\n", + " \n", + " p = polygonal_number(5, j) + polygonal_number(5, k)\n", + " if is_pentagonal(p):\n", + " psums.add((j, k))\n", + " \n", + " return psums" + ] + }, + { + "cell_type": "markdown", + "id": "8bb82c01", + "metadata": {}, + "source": [ + "Since we want to minimize $P_d$, we can just iterate over increasing values of $d$ and stop once this function finds a solution. This way, we know our solution is minimal since we didn't find solutions for any smaller $d$." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "07d2dcc4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{(2167, 1020)}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "for d in count(1):\n", + " sols = pentagonal_sums(d)\n", + " if sols != set():\n", + " break\n", + " \n", + "sols" + ] + }, + { + "cell_type": "markdown", + "id": "1af2fcc4", + "metadata": {}, + "source": [ + "This confirms that the solution we found earlier is in fact the solution that minimizes $P_d$.\n", + "\n", + "## Relevant sequences\n", + "* Pentagonal numbers: [A000326](https://oeis.org/A000326)\n", + "* Pentagonal numbers which are the sum of two other positive pentagonal numbers: [A136117](https://oeis.org/A136117)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 9.5", + "language": "sage", + "name": "sagemath" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}