Diophantine equation (Euler Problem 66)

Back to overview.

Consider quadratic Diophantine equations of the form:

$x^2 – Dy^2 = 1$

For example, when D=13, the minimal solution in x is 6492 – 13×1802 = 1.

It can be assumed that there are no solutions in positive integers when D is square.

By finding minimal solutions in x for D = {2, 3, 5, 6, 7}, we obtain the following:

$3^2 – 2×2^2 = 1$

$2^2 – 3×1^2 = 1$

$9^2 – 5×4^2 = 1$

$5^2 – 6×2^2 = 1$

$8^2 – 7×3^2 = 1$

Hence, by considering minimal solutions in x for D ≤ 7, the largest x is obtained when D=5.

Find the value of D ≤ 1000 in minimal solutions of x for which the largest value of x is obtained.

In [1]:
import math

def get_floor_sqrt(n):
    return math.floor(math.sqrt(n))

assert(get_floor_sqrt(5) == 2)
assert(get_floor_sqrt(23) == 4)

def next_expansion_1(current_a, current_nominator, current_denominator, original_number):
    cn = current_nominator
    cd = current_denominator
    cn, cd = cd, (original_number - cd * cd) // cn
    next_a = math.floor((math.sqrt(original_number) + cn) // cd)
    cn = cn - next_a * cd
    cn *= -1
    
    return next_a, cn, cd

def get_continued_fraction_sequence(n):
    
    # If number is a square number we return it.
    floor_sqrt = get_floor_sqrt(n)
    if n == floor_sqrt * floor_sqrt:
        return ((floor_sqrt), [])
    
    # Otherwise, we calculate the next expansion till we
    # encounter a step a second time.    
    a = floor_sqrt
    cn = a
    cd = 1
    sequence = []
    previous_steps = []
    
    while not (a, cn, cd) in previous_steps :
        #print("a: {} cn: {} cd: {}".format(a, cn, cd))
        previous_steps.append((a, cn, cd))
        a, cn, cd = next_expansion_1(a, cd, cn, n)
        sequence.append(a)
    sequence.pop()
    return ((floor_sqrt), sequence)

def get_continued_fraction_sequence(n):
    
    # If number is a square number we return it.
    floor_sqrt = get_floor_sqrt(n)
    if n == floor_sqrt * floor_sqrt:
        return ((floor_sqrt), [])
    
    # Otherwise, we calculate the next expansion till we
    # encounter a step a second time.    
    a = floor_sqrt
    cn = a
    cd = 1
    sequence = []
    previous_steps = []
    
    while not (a, cn, cd) in previous_steps :
        #print("a: {} cn: {} cd: {}".format(a, cn, cd))
        previous_steps.append((a, cn, cd))
        a, cn, cd = next_expansion_1(a, cd, cn, n)
        sequence.append(a)
    sequence.pop()
    return ((floor_sqrt), sequence)

assert(get_continued_fraction_sequence(1) == ((1), []))
assert(get_continued_fraction_sequence(4) == ((2), []))
assert(get_continued_fraction_sequence(25) == ((5), []))
assert(get_continued_fraction_sequence(2) == ((1), [2]))
assert(get_continued_fraction_sequence(3) == ((1), [1,2]))
assert(get_continued_fraction_sequence(5) == ((2), [4]))
assert(get_continued_fraction_sequence(13) == ((3), [1,1,1,1,6]))
In [43]:
def gcd(a, b):
    if b > a:
        a, b = b, a
    while a % b != 0:
        a, b = b, a % b
    return b

def add_fractions(n1, d1, n2, d2):
    d = d1 * d2
    n1 = n1 * (d // d1)
    n2 = n2 * (d // d2)
    n = n1 + n2
    p = gcd(n, d)
    return (n // p, d // p)

def next_expansion_2(previous_numerator, previous_denumerator, value):
    if previous_numerator == 0:
        return (value, 1)
    return add_fractions(previous_denumerator, previous_numerator, value, 1)
    
def get_fractions(n, x):
    #print("get_fractions(n={}, x={})".format(n, x))
    # Get sequence of a_x
    first_value, sequence = get_continued_fraction_sequence(n)
    sequence = [first_value] + math.ceil(x / len(sequence)) * sequence
    sequence = sequence[:x + 1]
    sequence = sequence[::-1]
    
    n, d = 0, 1
    for s in sequence:
        n, d = next_expansion_2(n, d, s)
    return (n, d)

assert(get_fractions(7, 0) == (2, 1))
assert(get_fractions(7, 1) == (3, 1))
assert(get_fractions(7, 2) == (5, 2))
assert(get_fractions(7, 3) == (8, 3))
In [48]:
def get_minimal_solution(d):
    for i in range(0, 100):
        x, y = get_fractions(d, i)
        if x * x - d * y * y == 1:
            return((x, y))

assert(get_minimal_solution(2) == (3, 2))
assert(get_minimal_solution(3) == (2, 1))
assert(get_minimal_solution(85) == (285769, 30996))
assert(get_minimal_solution(109) == (158070671986249, 15140424455100))
In [52]:
from math import sqrt

def is_square(n):
    return sqrt(n).is_integer()

print(is_square(4))
True
In [55]:
x_max = 0
d_max = 0

for d in range(2, 1001):
    if is_square(d):
        continue
    
    x, y = get_minimal_solution(d)
    if x > x_max:
        x_max = x
        d_max = d

print("d: {} x: {}".format(d_max, x_max))
s = d_max
assert(s == 661)
d: 661 x: 16421658242965910275055840472270471049
In [ ]: