{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Diophantine equation (Euler Problem 66)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Consider quadratic Diophantine equations of the form:\n",
    "\n",
    "$x^2 – Dy^2 = 1$\n",
    "\n",
    "For example, when D=13, the minimal solution in x is 6492 – 13×1802 = 1.\n",
    "\n",
    "It can be assumed that there are no solutions in positive integers when D is square.\n",
    "\n",
    "By finding minimal solutions in x for D = {2, 3, 5, 6, 7}, we obtain the following:\n",
    "\n",
    "$3^2 – 2×2^2 = 1$\n",
    "\n",
    "$2^2 – 3×1^2 = 1$\n",
    "\n",
    "$9^2 – 5×4^2 = 1$\n",
    "\n",
    "$5^2 – 6×2^2 = 1$\n",
    "\n",
    "$8^2 – 7×3^2 = 1$\n",
    "\n",
    "Hence, by considering minimal solutions in x for D ≤ 7, the largest x is obtained when D=5.\n",
    "\n",
    "Find the value of D ≤ 1000 in minimal solutions of x for which the largest value of x is obtained.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "def get_floor_sqrt(n):\n",
    "    return math.floor(math.sqrt(n))\n",
    "\n",
    "assert(get_floor_sqrt(5) == 2)\n",
    "assert(get_floor_sqrt(23) == 4)\n",
    "\n",
    "def next_expansion_1(current_a, current_nominator, current_denominator, original_number):\n",
    "    cn = current_nominator\n",
    "    cd = current_denominator\n",
    "    cn, cd = cd, (original_number - cd * cd) // cn\n",
    "    next_a = math.floor((math.sqrt(original_number) + cn) // cd)\n",
    "    cn = cn - next_a * cd\n",
    "    cn *= -1\n",
    "    \n",
    "    return next_a, cn, cd\n",
    "\n",
    "def get_continued_fraction_sequence(n):\n",
    "    \n",
    "    # If number is a square number we return it.\n",
    "    floor_sqrt = get_floor_sqrt(n)\n",
    "    if n == floor_sqrt * floor_sqrt:\n",
    "        return ((floor_sqrt), [])\n",
    "    \n",
    "    # Otherwise, we calculate the next expansion till we\n",
    "    # encounter a step a second time.    \n",
    "    a = floor_sqrt\n",
    "    cn = a\n",
    "    cd = 1\n",
    "    sequence = []\n",
    "    previous_steps = []\n",
    "    \n",
    "    while not (a, cn, cd) in previous_steps :\n",
    "        #print(\"a: {} cn: {} cd: {}\".format(a, cn, cd))\n",
    "        previous_steps.append((a, cn, cd))\n",
    "        a, cn, cd = next_expansion_1(a, cd, cn, n)\n",
    "        sequence.append(a)\n",
    "    sequence.pop()\n",
    "    return ((floor_sqrt), sequence)\n",
    "\n",
    "def get_continued_fraction_sequence(n):\n",
    "    \n",
    "    # If number is a square number we return it.\n",
    "    floor_sqrt = get_floor_sqrt(n)\n",
    "    if n == floor_sqrt * floor_sqrt:\n",
    "        return ((floor_sqrt), [])\n",
    "    \n",
    "    # Otherwise, we calculate the next expansion till we\n",
    "    # encounter a step a second time.    \n",
    "    a = floor_sqrt\n",
    "    cn = a\n",
    "    cd = 1\n",
    "    sequence = []\n",
    "    previous_steps = []\n",
    "    \n",
    "    while not (a, cn, cd) in previous_steps :\n",
    "        #print(\"a: {} cn: {} cd: {}\".format(a, cn, cd))\n",
    "        previous_steps.append((a, cn, cd))\n",
    "        a, cn, cd = next_expansion_1(a, cd, cn, n)\n",
    "        sequence.append(a)\n",
    "    sequence.pop()\n",
    "    return ((floor_sqrt), sequence)\n",
    "\n",
    "assert(get_continued_fraction_sequence(1) == ((1), []))\n",
    "assert(get_continued_fraction_sequence(4) == ((2), []))\n",
    "assert(get_continued_fraction_sequence(25) == ((5), []))\n",
    "assert(get_continued_fraction_sequence(2) == ((1), [2]))\n",
    "assert(get_continued_fraction_sequence(3) == ((1), [1,2]))\n",
    "assert(get_continued_fraction_sequence(5) == ((2), [4]))\n",
    "assert(get_continued_fraction_sequence(13) == ((3), [1,1,1,1,6]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def gcd(a, b):\n",
    "    if b > a:\n",
    "        a, b = b, a\n",
    "    while a % b != 0:\n",
    "        a, b = b, a % b\n",
    "    return b\n",
    "\n",
    "def add_fractions(n1, d1, n2, d2):\n",
    "    d = d1 * d2\n",
    "    n1 = n1 * (d // d1)\n",
    "    n2 = n2 * (d // d2)\n",
    "    n = n1 + n2\n",
    "    p = gcd(n, d)\n",
    "    return (n // p, d // p)\n",
    "\n",
    "def next_expansion_2(previous_numerator, previous_denumerator, value):\n",
    "    if previous_numerator == 0:\n",
    "        return (value, 1)\n",
    "    return add_fractions(previous_denumerator, previous_numerator, value, 1)\n",
    "    \n",
    "def get_fractions(n, x):\n",
    "    #print(\"get_fractions(n={}, x={})\".format(n, x))\n",
    "    # Get sequence of a_x\n",
    "    first_value, sequence = get_continued_fraction_sequence(n)\n",
    "    sequence = [first_value] + math.ceil(x / len(sequence)) * sequence\n",
    "    sequence = sequence[:x + 1]\n",
    "    sequence = sequence[::-1]\n",
    "    \n",
    "    n, d = 0, 1\n",
    "    for s in sequence:\n",
    "        n, d = next_expansion_2(n, d, s)\n",
    "    return (n, d)\n",
    "\n",
    "assert(get_fractions(7, 0) == (2, 1))\n",
    "assert(get_fractions(7, 1) == (3, 1))\n",
    "assert(get_fractions(7, 2) == (5, 2))\n",
    "assert(get_fractions(7, 3) == (8, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def get_minimal_solution(d):\n",
    "    for i in range(0, 100):\n",
    "        x, y = get_fractions(d, i)\n",
    "        if x * x - d * y * y == 1:\n",
    "            return((x, y))\n",
    "\n",
    "assert(get_minimal_solution(2) == (3, 2))\n",
    "assert(get_minimal_solution(3) == (2, 1))\n",
    "assert(get_minimal_solution(85) == (285769, 30996))\n",
    "assert(get_minimal_solution(109) == (158070671986249, 15140424455100))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    }
   ],
   "source": [
    "from math import sqrt\n",
    "\n",
    "def is_square(n):\n",
    "    return sqrt(n).is_integer()\n",
    "\n",
    "print(is_square(4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "d: 661 x: 16421658242965910275055840472270471049\n"
     ]
    }
   ],
   "source": [
    "x_max = 0\n",
    "d_max = 0\n",
    "\n",
    "for d in range(2, 1001):\n",
    "    if is_square(d):\n",
    "        continue\n",
    "    \n",
    "    x, y = get_minimal_solution(d)\n",
    "    if x > x_max:\n",
    "        x_max = x\n",
    "        d_max = d\n",
    "\n",
    "print(\"d: {} x: {}\".format(d_max, x_max))\n",
    "s = d_max\n",
    "assert(s == 661)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "completion_date": "Mon, 28 Jan 2019, 21:08",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python3.6",
   "name": "python3"
  },
  "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.6.5"
  },
  "tags": [
   "diophantine",
   "equation",
   "pell's equation"
  ]
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
