この記事はアドベントカレンダー2025 17日目の記事です。
みなさまこんにちは。Nzt3です。
SageMathはPythonベースの言語でさまざまな数学ソフトウェアを使用できるオープンソースのソフトウェアです。計算に必要な機能がPythonに似た記述で利用できます。
こちらからダウンロードできます。https://www.sagemath.org/
SageMath Cell を使えばオンラインで実行することもできます。
対話型インタプリタが使えますが、jupyter notebookでの利用もできます。
今回はそのSageMathのさまざまな機能を使ってフィボナッチ数列のn項目を計算します。
定義 フィボナッチ数列
次の漸化式で定義します。
今回の目的はとなる関数を実装することです。
愚直実装
漸化式を素直に実装してみましょう。
def fib(n):
ans=[0]*(n+2)
ans[0],ans[1]=1,1
for i in range(2,n+1):
ans[i]=ans[i-1]+ans[i-2]
return ans[n]
実行してみると
という列が実際に計算できていることがわかります。
SageMathではPythonの記述は大体はそのまま動きます。上記のコードもSageMathで動作します。
途中の計算結果はいらないので変数を使い回すこともできます。
def fib(n):
a,b=1,1
for _ in range(n):
a,b=b,a+b
return a
これもSageMathで問題なく動作します。
一般項の利用
フィボナッチ数列は隣接3項間漸化式で定義されます。これの一般項の導出は高校数学の定番問題です。
の2つの解を とすれば定数を用いて
と表すことができます。
ところで、を計算するのが面倒ですね。
SageMathに計算してもらいましょう。
var('x')
solve([x^2==x+1],x)
出力: [x == -1/2*sqrt(5) + 1/2, x == 1/2*sqrt(5) + 1/2]
変数を宣言して方程式をについて解いてもらいました。
解が得られたので、としましょう。
あとは最初の3項に合うようにを計算してもらいます。
alpha = -1/2*sqrt(5) + 1/2
beta = 1/2*sqrt(5) + 1/2
var('A','B')
solve([A*alpha^0+B*beta^0==1,A*alpha^1+B*beta^1==1,A*alpha^2+B*beta^2==2],A,B)
出力: [[A == -1/10*sqrt(5) + 1/2, B == 1/10*sqrt(5) + 1/2]]
解が得られました。
しれっとalpha^2みたいに書いていますが、SageMathではこれが累乗の記法になります。xor演算子は^^となるのでPythonのプログラムを移植する際には注意しましょう。
def fib(n):
alpha = -1/2*sqrt(5) + 1/2
beta = 1/2*sqrt(5) + 1/2
A = -1/10*sqrt(5) + 1/2
B = 1/10*sqrt(5) + 1/2
return A*alpha^n+B*beta^n
これで良さそうなので試しにfib(10)を計算してみましょう。
fib(10)
出力: 1/10240*(sqrt(5) + 5)*(sqrt(5) + 1)^10 - 1/10240*(sqrt(5) - 1)^10*(sqrt(5) - 5)
数式がそのまま出てきてしまいました。これでは使えないので項を展開して計算してもらいましょう。
expandを使うと展開してくれます。
fib(10).expand()
出力:89
計算できましたね。
mod P での計算
大きなについてfib(n)を計算したいです。
しかし、を大きくするとfib(n)も巨大になって計算に時間がかかります。
値は非常に大きくなる可能性があるのでで割ったあまりを計算することにしましょう。
一般項を手に入れましたが、これをで割ったあまりにするにはどうしたら良いでしょうか。
整数の計算は問題ありませんが、という数が厄介です。ですが、最終的な計算結果ではは出てこないので何かしらを満たすであれば何を使っても計算結果は正しくなるはずです。
の解を調べましょう。といってもどうやって解けばいいのかわかりません。SageMathに調べてもらいましょう。
の根を調べてもらいます。
var('x')
R.<x>=PolynomialRing(Zmod(998244353))
(x^2-5).roots()
出力: []
根はないらしいです。終わりだ……
そう言えばって体になりますね。それにを添加して体を拡大してしまいましょう。
一般項を 上の計算にします。
def fib(n):
K.<s>=GF(998244353^2,modulus=x^2 -5)
alpha = -K(1/2)*s + K(1/2)
beta = K(1/2)*s + K(1/2)
A = -K(1/10)*s + K(1/2)
B = K(1/10)*s + K(1/2)
return A*alpha^n+B*beta^n
fib(100000000000000)
出力: 273740723
計算できていそうです!よかった〜
mod m での計算
素数以外のについてもfib(n)をで割ったあまりを知りたいです。
一般項には と が出てくるのでこれをどうにかしましょう。
とすればは存在します。これにを追加して計算してみます。
def fib(n):
R.<x> = PolynomialRing(Zmod(3^10))
K.<s> = R.quotient(x^2 - 5)
alpha = -K(1/2)*s + K(1/2)
beta = K(1/2)*s + K(1/2)
A = -K(1/10)*s + K(1/2)
B = K(1/10)*s + K(1/2)
return A*alpha^n+B*beta^n
fib(30)
出力:47191
です。
実際にをで割ったあまりが計算できました!
が存在すれば問題なく一般項を計算できます。
行列の利用
線形漸化式は行列積で表現できます。
フィボナッチ数列の場合は次のようになります。
これを初項まで繰り返していくことで行列の累乗で一般項を表記できます。
という謎の要素が出ていますが、は正しく計算できているので問題ありません。
これをSageMathで実装しましょう。
def fib(n):
M = matrix([[1,1],[1,0]])
A = matrix([[1],[0]])
return (M^n*A)[0,0]
としての第一成分を取り出します。
fib(10)
出力: 89
計算できていますね!
同じ方法でをで割ったあまりを求めることにします。
今回は全部整数の計算なので困る要素がありません。
Zmodを使って割ったあまりの計算にします。
def fib(n):
R = Zmod(998244353)
M = matrix(R,[[1,1],[1,0]])
A = matrix(R,[[1],[0]])
return (M^n*A)[0,0]
fib(100000000000000)
出力: 273740723
簡単に変更することができました。
を計算しているので非常に長い時間がかかりそうなものですが、実際には回くらいの計算で済むように賢い計算をしてくれるので変な数を入れてもちゃんと計算できます。
母関数の利用
数列について とした関数を考えることができます。これがその数列の母関数です。
この関数が値を持つかは知りませんが、係数列だけ考えます。
がフィボナッチ数列のとき母関数はどうなるでしょうか。答えはです。
この母関数の係数列を求められればフィボナッチ数列が計算できます。
SageMathで冪級数環を使って計算してみましょう。
def fib(n):
R.<x> = PowerSeriesRing(ZZ,n+1)
return (1/(1-x-x^2))[n]
fib(10)
出力: 89
PowerSeriesRing(ZZ,n+1)は整数係数の冪級数の係数を最初の項()計算することを意味します。
あとは係数列を計算させての項の係数を見ればフィボナッチ数が計算できます。
記述が随分とシンプルになりましたね!
おまけに も計算してみましょう。
def fibsum(n):
R.<x> = PowerSeriesRing(ZZ,n+1)
return (1/(1-x-x^2)/(1-x))[n]
fibsum(10)
出力: 232
で割るだけで総和になるので変更もシンプルです。
おわり
SageMathを使うとだったりだったりといったちょっと面倒なものを手軽に使うことができます。
適当に計算をするとき用のPythonに加えて、適当に数学的操作をするとき用にSageMathも使えると便利です。
みなさまも急に適当な計算をして家族や友達を驚かせましょう!
アドベントカレンダーの明日の担当は @mumumu です!
明日もお楽しみに!