素数定理
今日は自分が趣味で作ったpdfを以下に紹介したいと思います。内容は素数分布の近似を与える素数定理についてで、その証明は解析的整数論の一つの金字塔と言えると思います。まだ作成途中であったり、ミスはあるのですがとりあえずというところまで来たので先に公開してしまおうと思いましたww。英語ではありますが読みたい方はぜひリンクからダウンロードしていただければと思います。
https://drive.google.com/file/d/1Lxx0YvlhBJ1NXbhEnZpEp7ajdlQnpEnK/view?usp=sharing
Euler-Mascheroni 定数
本日は級数の分野よりハーモニックシリーズとオイラーの定数について、一つご紹介です。初めに参考文献としては
Euler–Mascheroni constant - Wikipedia
です。
定理の主張としては、ハーモニックシリーズはのオーダーで収束しその差はオイラーの定数分しかないということになっております。Wikipediaによると、このオイラーの定数はであり、この記事ではこの極限の値を数値的に計算をする方法を見ていきたいというものです。誤差分を初めになどとして置いて、極限値がこの誤差分分より小さくなる大きなを探していくということでもいいのですが(論法的なこと)、オイラーの定数は積分計算でも得られるので、まずはその準備としてディガンマ関数というものを用意しましょう。ここではガンマ関数はWeierstrassの無限積表示をされているものと仮定しましょう(https://okenta.hatenablog.jp/entry/2019/07/31/160242)。すると、
のようにオイラーの定数が得られます。他方、ここでガンマ関数の微分を積分表示の微分と捉えてみると
であり、なのでこれらからオイラーの定数は次のように表されます。
変数変換すると
なので、この積分を数値計算してみましょう。数値計算はガウスの求積法により精度の良い計算が出来ます。他にも有名な定積分の数値計算としては trapezoidal rule や simpson' s rule、Euler-Maclaurinの公式などがあります。ガウスの求積法はscipyに内蔵される積分計算にも使われているのでかなり良い精度を保証してくれるようです。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline def Legendre(N,x): P = np.zeros((N+1,len(x))) Q = np.zeros((N+1,len(x))) P[0], P[1] = 1 + 0*x, x Q[1] = 1 + 0*x for n in range(1,N): P[n+1] = ((2*n+1)*x*P[n] - n*P[n-1])/(n+1) Q[n+1] = (2*n+1)*P[n] + Q[n-1] return P, Q def quadrature(N,imax=10): i = np.arange(N) x = np.cos(np.pi*(4*i+3)/(4*N+2)) for i in range(imax): P, Q = Legendre(N,x) x -= P[N]/Q[N] w = 2/((1-x**2)*(Q[N]**2)) return x, w def integrate(y,w): return np.sum(y*w) def scale(a,b,x,w): t = x*(b-a)/2+(b+a)/2 dt = (b-a)*w/2 return t, dt def f(t): return np.log(np.log(1/t)) def DYI_I(a,b,N): x,w = quadrature(N) t,dt = scale(a,b,x,w) return integrate(f(t),dt) -DYI_I(0,1,10000)
するとDYI_I
は次の値を返してくれます。
これは文字通りDIYですのでargumentの3個目の値を大きなものにすると精度が高くなります。僕のパソコンでは時間がかかってそれは面倒なので、scipyにお願いしましょう。
import scipy.integrate as integrate import scipy.special as special def integrand(n): return np.log(np.log(1/n)) eulerconst = - integrate.quad(integrand, 0, 1)[0] print("gamma = ",eulerconst)
するとこれは
を返します。こちらの方が初めのウィキの値と近くDYI_I
よりは精度がいいですね。本日はオイラーの定数に注目してガウスの求積法を実装してみましたがいかがだったでしょうか。上でfの部分を別の関数に置き換えても定積分の数値計算は簡単にできると思うのでそちらもやってみたい方はぜひ。ガウスの求積法についての参考文献も以下に挙げておきます。
Fibonacci数列についての考察
複素関数と解析接続を学ぶ中で以下の動画を見て、pythonを使ってもタマキさんが動画内で描いていた図と似たようなものを描けそうだなと思ったので、今回はそれをトライしてみました。まずは以下の動画を見て頂けると内容が分かるかと思います。
Fibonacci数列とは。
このとき上の漸化式はに対して、以下の関係式を満たす。
Fibonacci数列を係数に持つ級数と解析接続
この級数は次の収束半径を持つ。
この時、この収束半径内で級数は収束することが言えます。さて、他方
なので
を満たす。はを除く任意の点で正則で、先の級数表示の解析接続したものとなっています。
pythonでグラフを作る
さて、この関数の実部と虚部を複素平面上からの写像で3Dのグラフに表す。これはミックスされた形でタマキさんの動画でも流れているものかと思います。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from mpmath import * from mpl_toolkits.mplot3d import Axes3D def three_D_plots(func,picname): x, y = np.meshgrid(np.linspace( -5, 5, 500), np.linspace( -5, 5, 500)) z = x + y*1j w = func(z) fig1 = plt.figure() ax = Axes3D(fig1) ax.plot_surface(x, y, w.real, color="blue") ax.set_zlim3d(0, 15.) ax.autoscale(False) plt.axis([-6., 6., -6., 6.]) plt.title("real_part") fig2 = plt.figure() ax = Axes3D(fig2) ax.plot_surface(x, y, w.imag, color="orange") ax.set_zlim3d(0, 15.) ax.autoscale(False) plt.axis([-6., 6., -6., 6.]) plt.title("imag_part") plt.savefig(picname) plt.show()
def fibonacci(s): return 1/(1-s-s**2) three_D_plots(fibonacci,"fibonacci_real.png","fibonacci_img.png")
さて、今までやってきた通りこれを今度は絶対値付きのグラフにしたり、2Dの図を作ったりすると
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from mpl_toolkits.mplot3d import Axes3D from scipy.special import gamma def three_D_plot(func,picname): x, y = np.meshgrid(np.linspace( -5, 5, 500), np.linspace( -5, 5, 500)) z = x + y*1j w = func(z) fig = plt.figure() ax = Axes3D(fig) ax.plot_surface(x, y, np.abs(w)) ax.set_zlim3d(0, 15.) ax.autoscale(False) plt.axis([-6., 6., -6., 6.]) plt.savefig(picname) plt.show() def plot_complex(complex_func,picname1,picname2): x, y = np.meshgrid(np.linspace(-4, 4, 400), np.linspace(-4, 4, 400)) z = x + y*1j angles = (np.angle(complex_func(z)) + 2*np.pi) % (2*np.pi) plt.figure(figsize=(6,5)) im_1 = plt.pcolor(x, y, angles, cmap="hsv", vmin=0, vmax=6) plt.colorbar(im_1) plt.title('argument') plt.savefig(picname1) plt.figure(figsize=(6,5)) im_2 = plt.pcolor(x, y, np.abs(complex_func(z)), vmin=0, vmax=4) plt.title('absolute value') plt.colorbar(im_2) plt.savefig(picname2) plt.show() three_D_plot(fibonacci,"fibonacci3D.png") plot_complex(fibonacci,"fibonacci_arg.png","fibonacci_abs.png")
のようになります。タマキさんの今回の動画の面白いところはサムネにもある式がどういう意味をもつのかということだと思うので解析接続についてや無限級数の収束半径などは飛ばしてもとても楽しめます!その他Riemann Zeta関数についても似た内容の動画がアップされているかと思うので、ぜひ興味のある方はそちらもチェックなさってみてはいかがでしょう。
複素関数のグラフについて、様々な例
今まで作ってきた複素関数のプロット例をいくつか紹介したいと思います。一応、グラフのプロット関数をもう一度書いておきます。
複素関数のグラフを描く関数。
まず与えられた複素関数の絶対値付きの値を返す、3Dのグラフを描くプロット関数を下に記します。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from mpl_toolkits.mplot3d import Axes3D from scipy.special import gamma def three_D_plot(func,picname): x, y = np.meshgrid(np.linspace( -5, 5, 500), np.linspace( -5, 5, 500)) z = x + y*1j w = func(z) fig = plt.figure() ax = Axes3D(fig) ax.plot_surface(x, y, np.abs(w)) ax.set_zlim3d(0, 15.) ax.autoscale(False) plt.axis([-6., 6., -6., 6.]) plt.savefig(picname) plt.show()
次にこの複素関数を偏角ごとに2Dに描く関数と、絶対値付きの値をレベルカーブごとに2Dにプロットする関数を下に書いておきます。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from scipy.special import gamma def plot_complex(complex_func,picname1,picname2): x, y = np.meshgrid(np.linspace(-4, 4, 400), np.linspace(-4, 4, 400)) z = x + y*1j angles = (np.angle(complex_func(z)) + 2*np.pi) % (2*np.pi) plt.figure(figsize=(6,5)) im_1 = plt.pcolor(x, y, angles, cmap="hsv", vmin=0, vmax=6) plt.colorbar(im_1) plt.title('argument') plt.savefig(picname1) plt.figure(figsize=(6,5)) im_2 = plt.pcolor(x, y, np.abs(complex_func(z)), vmin=0, vmax=4) plt.title('absolute value') plt.colorbar(im_2) plt.savefig(picname2) plt.show()
いくつかの具体例。
例①
このプロット関数達を用いてまずガンマ関数を前回のようにプロットしてみる。ガンマ関数自体については一つ前のの記事を読んでみて下さい。(Gamma関数をお絵描き - 努力で数論)
three_D_plot(gamma,picname="gamma3D.png")
するとグラフは以下のようになる。
plot_complex(gamma,"gamma_arg.png","gamma_abs.png")
するとグラフは以下のようになる。
例②
さて別の複素関数でトライしてみよう。
def identity(z): return z three_D_plot(identity,picname="identity3D.png")
するとグラフは以下のようになる。
plot_complex(identity,"identity_arg.png","identity_abs.png")
するとグラフは以下のようになる。
例③
次の例は逆数を返す関数。ちなみにこの関数は上の例2とは違ってで極を持つのでそこを除けば正則な関数となっている。こういったビジュアルの要素であるグラフを通じ、極の理解が深まるはずである。
まず絶対値付きの場合は
def inverse(z): return 1/z three_D_plot(inverse,picname="inverse3D.png")
となり、次に2Dの図は
plot_complex(inverse,"inverse_arg.png","inverse_abs.png")
となる。
例④
複素指数関数は次のように定義でき、任意のに対して正則である。
three_D_plot(np.exp,picname="exp3D.png")
plot_complex(np.exp,"exp_arg.png","exp_abs.png")
グラフは上のようになる。
例⑤
複素三角関数の一例としてのグラフを描く。これも任意のに対して正則である。
three_D_plot(np.sin,picname="sin3D.png")
するとグラフは以下のようになる。
plot_complex(np.sin,"sin_arg.png","sin_abs.png")
するとグラフは以下のようになる。
Gamma関数をお絵描き
本日もpythonで描いたグラフを乗っけていこうと思います。第二回はガンマ関数のグラフについてです。
Gamma関数とは。
まずガンマ関数の定義ですが、これもWikipedia(ガンマ関数 - Wikipedia)を参照されるのがベターでしょうが、一応一般的によくある定義と解析接続したものであるWeierstrassの表示を紹介したいと思います。
これも定義域が複素平面全てではないのでグラフを描くにあたっては都合がよくありません。そこで次の解析接続後の、Weierstrassの無限積表示の定義も書いておきます。
この表示の逆数、即ちは任意のにおいて正則(複素微分可能で導関数が連続)です。他にもガンマ関数に関する定理、命題は次のページによくまとまっています。
Gamma Function -- from Wolfram MathWorld
pythonを使って描く。
まずに任意の実数を代入してガンマ関数を返す時のグラフが次のコードである。
from scipy.special import gamma, zeta from matplotlib import pyplot as plt %matplotlib inline import numpy as np x = np.linspace( -5., 5., 500) y = gamma(x) plt.axis([-5., 5., -5., 5.]) plt.plot(x, y,"o") plt.savefig("gamma_real.png") plt.show()
すると得られる画像が次の通り。
さて次に絶対値付きガンマ関数の3Dのグラフのコードが次の通りです。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from mpl_toolkits.mplot3d import Axes3D from scipy.special import gamma def three_D_plot(func,picname): x, y = np.meshgrid(np.linspace( -5, 5, 500), np.linspace( -5, 5, 500)) z = x + y*1j w = func(z) fig = plt.figure() ax = Axes3D(fig) ax.plot_surface(x, y, np.abs(w)) ax.set_zlim3d(0, 15.) ax.autoscale(False) plt.axis([-6., 6., -6., 6.]) plt.savefig(picname) plt.show() three_D_plot(gamma,picname="gamma3D.png")
するとグラフは次のようになります。
この図からも分かるように虚部がゼロの部分の断面図が先の図で実軸対象に折り返したものに一致することが確認できます。次にこのグラフの偏角の様子とレベルカーブを複素平面にプロットした図が次のコードが与える2つのグラフです。1つ目のカラフルな図が偏角によって色が変化していて、2つ目がレベルカーブです。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from scipy.special import gamma def plot_complex(complex_func,picname1,picname2): x, y = np.meshgrid(np.linspace(-4, 4, 400), np.linspace(-4, 4, 400)) z = x + y*1j angles = (np.angle(complex_func(z)) + 2*np.pi) % (2*np.pi) plt.figure(figsize=(6,5)) im_1 = plt.pcolor(x, y, angles, cmap="hsv", vmin=0, vmax=6) plt.colorbar(im_1) plt.title('argument') plt.savefig(picname1) plt.figure(figsize=(6,5)) im_2 = plt.pcolor(x, y, np.abs(complex_func(z)), vmin=0, vmax=4) plt.title('absolute value') plt.colorbar(im_2) plt.savefig(picname2) plt.show() plot_complex(gamma,"gamma_arg.png","gamma_abs.png")
上の図から実部が負の整数で虚部がゼロの時、黄色っぽくなっていることからここで極を取っていることが分かりますね。さて今まで書いてきたガンマ関数ですが今回はpythonのscipy
というライブラリにもともとあるガンマ関数を使っています。もちろんmpmath
にもガンマ関数はあって前回も書いた通り、cplot
という便利なプロット用の関数も用意されてるのでそちらを使っても同じものが描けます。最後にそれを一応乗せておきましょう。
from mpmath import * cplot(gamma, points=50000, file="gamma2D.png")
グラフとしては上の自前のものをミックスしたような形になっているのが得られます。ちなみに黒の濃淡が絶対値の大小を表しています。
Riemann Zeta関数をお絵描き
これから暇を持て余している時を見つけて、今までpythonを使って書いたグラフとかをとりあえずは乗っけていこうかなと思います。第一回はRiemann Zeta関数の零点についてです。
1.Riemann Zeta関数とは。
いろんなところで定義や説明がかかれてるはずなので、例えばWikipediaや(リーマンゼータ関数 - Wikipedia)次の記事(リーマンのゼータ関数で遊び倒そう (Ruby編) - tsujimotterのノートブック)を参照された方がより多くの内容を見れると思います(実際僕はこの2つの記事で結構勉強させて頂きました、ありがとうございます)が、一応素人なりに書いておきます。
これがRiemann Zeta関数と言われるものですが、今日のテーマはとなるようなを今はちょっと考えてみたいということです。本当は解析接続が何かということや解析接続を施す過程も書いておかなければ零点を考える上では十分ではないとも思ったのですが、とりあえずは上の定義域よりも広いところで(複素平面全体で)定義される同じような関数として以下の形もあげられます。
詳しくはRiemann Zeta Function -- from Wolfram MathWorldをお読みください。すべてには目を通してないですがいろいろ書かれてて面白いです。一致の定理により解析接続した後のRiemann Zeta関数はユニークに決まる関数となっているので、この関数に対して以下零点をプロットしていきます。
またに負の偶数(に対して)を代入するとということが分かりますが、これは自明な零点と言われています。理由としてはRiemann Zeta関数がBernoulli数を使って表現できるところにあり(これはWikipediaの上の記事で確認できる)、Bernoulli数()は が3以上の奇数の時にゼロになること(ベルヌーイ数 - Wikipedia)からある程度すぐ分かるということだと思います。
2.pythonを使って描く。
まず僕がどの環境でpythonを使っているかというと、jupyter notebookの中で使用しています。jupyterを使うとpythonの入出力とlatexを用いた文書作成の両方が一気にでき、出来上がったものが「データ+文」ということで結構見やすくなります。僕は、参考として挙げる次の記事にもあるようにpythonとAnacondaをダウンロードし、そこからjupyter notebookを使っています。
データ分析で欠かせない!Jupyter Notebookの使い方【初心者向け】 | TechAcademyマガジン
これらは基本的にはすべて無料なので学生には非常に有難いですね。
さてようやくグラフを描くのですが、今回はRiemann Zeta関数についてはすでにあるものを使用しました。mpmath
にはzeta
という関数が用意されてますし、なんならcplot
という僕が作ったものより性能がいいコードも準備されてるのでわざわざ自前でレベルカーブを描くほどでもないんですが、とりあえず今はRiemann Zeta関数についてはあるものを使ってグラフの書き方は自分で行うこととしました。またmpmath
に慣れておらず、途中無理やり新しい配列に置き換えてるのでコードはかなり汚いかもしれません(汗)。
始めは絶対値付きのRiemann Zeta関数を3Dで見たときのグラフのコードです。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from mpmath import * from mpl_toolkits.mplot3d import Axes3D def three_D_plot(func,picname): x, y = np.meshgrid(np.linspace( -5, 5, 100), np.linspace( -11, 11, 220)) z = x + y*1j w = np.abs(np.vectorize(func)(z)) new = np.zeros((x.shape[0],x.shape[1])) for i in range(x.shape[0]): for j in range(x.shape[1]): new[i][j]=w[i][j] fig = plt.figure() ax = Axes3D(fig) ax.plot_surface(x, y, np.abs(new)) ax.set_zlim3d(0, 2.) ax.autoscale(False) plt.axis([-6., 6., -12., 12.]) plt.savefig(picname) plt.show() three_D_plot(zeta,picname="zeta3D.png")
するとこんな絵が描けます。
はっきり言ってこの絵はあまり出来は良くなくて、例えばこの方(リーマンのゼータ関数の数値計算コード(複素平面) | シキノート)のように描ければベストですが今は描けていません。
さてこの上のグラフのレベルカーブを複素平面にプロットしたものが以下のコードです。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from mpmath import * def contour_coloring(func, picname): x, y = np.meshgrid(np.linspace( -20, 10, 300), np.linspace( -40, 40, 400)) z = x + y*1j w = np.abs(np.vectorize(func)(z)) new = np.zeros((x.shape[0],x.shape[1])) for i in range(x.shape[0]): for j in range(x.shape[1]): new[i][j]=w[i][j] plt.figure(figsize=(6,8)) cs = plt.pcolor(x, y, new, vmin=0, vmax=3) plt.colorbar(cs) plt.savefig(picname) plt.show() contour_coloring(zeta,"RH.png")
すると次のような図が描けます。
なかなかきれいですよね。リーマン予想というのは「任意の非自明な零点(上のグラフだと濃い青の点)が、実部がの線上に乗っている」というもので、この範囲ではその主張は信じることができそうだなといえます。数学者によってもこの予想が正しいと信ずるかどうかはいろいろ議論があるようですが、少なくともこの狭い範囲ではそれが成り立ちそうだなと予想できますね。どちらのコードもグラフを描くようの関数を先に定義しているので、これをちょっと変えれば別の関数に対しても同じようなグラフが描けます。