Python ペル方程式の最小解を求める

# ペル方程式とは
$$x^2 - D y^2 = 1$$ はペル方程式と呼ばれており, $\sqrt{D}$ の連分数展開によって最小解を求めることができる。

$$\sqrt{D} = a_0 + \cfrac{1}
{a_1 + \cfrac{1}
  {a_2 + \cfrac{1}{\ddots}}
}$$

こちらが非常にわかりやすいです。

https://www.youtube.com/watch?v=sdsReCvz5RI

こちらの記事はわかりやすく解説されており、連分数を求めるアプリも開発されています。

https://tsujimotter.hatenablog.com/entry/pell-equation-and-continued-fraction



# 実装

こちらに置きました。

https://github.com/quryu-1/pell

```python
import math
from decimal import (
    Decimal,
    getcontext
)

def is_square(n):
    is_square = False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n == i ** 2:
            is_square = True
            break
    return is_square

def get_sqrt(D):
    getcontext().prec = 100
    x = int(D)
    for i in range(20):
        x = Decimal(str(x - (x ** 2 - D) / (2 * x)))
    return x

def get_continued_fraction(D):
    continued_fraction = []
    if not is_square(D):
        y = get_sqrt(D) - int(get_sqrt(D))
        continued_fraction.append(int(1 / y))
        z = 1 / y - int(1 / y)
        while True:
            if abs(y - z) < 1e-5:
                break
            else:
                continued_fraction.append(int(1 / z))
                z = 1 / z - int(1 / z)
    return continued_fraction

def get_minimal_solution(D):
    continued_fraction = get_continued_fraction(D)
    length = len(continued_fraction)
    num = length % 2 + 1
    continued_fraction = continued_fraction * num
    an2, bn2 = 0, 0
    an1, bn1 = 0, 0
    an, bn = 1, 0 
    
    for n in range (1, num * length + 1):
        if n == 1:
            an1, bn1 = int(math.sqrt(D)), 1
        else:
            an2, bn2 = (an + continued_fraction[n - 2] * an1,
                        bn + continued_fraction[n - 2] * bn1)
            an, bn = an1, bn1
            an1, bn1 = an2, bn2
    print_continued_fraction = continued_fraction[:int(len(continued_fraction) / num)]
    print_continued_fraction.insert(0, int(get_sqrt(D)))
    print('連分数展開: {}'.format(print_continued_fraction))
    return an2, bn2

if __name__ == '__main__':
    d = 2
    x, y = get_minimal_solution(d)
    print('最小解\n  x: {}\n  y: {}'.format(x, y))
```

# 出力例

$D=2$

```
連分数展開: [1, 2]
最小解
  x: 3
  y: 2
処理時間: 0.0020055770874023438
```

$D=7$

```
連分数展開: [2, 1, 1, 1, 4]
最小解
  x: 8
  y: 3
処理時間: 0.0009946823120117188
```

$D=14$

```
連分数展開: [3, 1, 2, 1, 6]
最小解
  x: 15
  y: 4
処理時間: 0.0010349750518798828
```

$D=61$

```
連分数展開: [7, 1, 4, 3, 1, 2, 2, 1, 3, 4, 1, 14]
最小解
  x: 1766319049
  y: 226153980
処理時間: 0.0010004043579101562
```

$D=751$

```
連分数展開: [27, 2, 2, 8, 1, 2, 1, 3, 5, 1, 4, 1, 1, 1, 3, 1, 1, 3, 10, 1, 2, 7, 2, 17, 1, 4, 27, 4, 1, 17, 2, 7, 2, 1, 10, 3, 1, 1, 3, 1, 1, 1, 4, 1, 5, 3, 1, 2, 1, 8, 2, 2, 54]
最小解
  x: 7293318466794882424418960
  y: 266136970677206024456793
処理時間: 0.0009648799896240234
```

$D=991$

```
連分数展開: [31, 2, 12, 10, 2, 2, 2, 1, 1, 2, 6, 1, 1, 1, 1, 3, 1, 8, 4, 1, 2, 1, 2, 3, 1, 4, 1, 20, 6, 4, 31, 4, 6, 20, 1, 4, 1, 3, 2, 1, 2, 1, 4, 8, 1, 3, 1, 1, 1, 1, 6, 2, 1, 1, 2, 2, 2, 10, 12, 2, 62]
最小解
  x: 379516400906811930638014896080
  y: 12055735790331359447442538767
処理時間: 0.002000093460083008
```

# 参考記事

https://ja.wikipedia.org/wiki/%E3%83%9A%E3%83%AB%E6%96%B9%E7%A8%8B%E5%BC%8F

https://www.youtube.com/watch?v=sdsReCvz5RI

https://tsujimotter.hatenablog.com/entry/pell-equation-and-continued-fraction