前回の『プログラミング初心者でも素数公式を組んでみたい』
aryuaryuaryuryu.hatenablog.com
の続きです.
今回はプログラムの話メインになると思います.
基本的にC++とDxlibを用いてます.
関数実装
前回得られた公式
からプログラムを組んでみたい.
li以外の積分の実装
最初は簡単なものにチャレンジしたい.
以前作成した積分関数
aryuaryuaryuryu.hatenablog.com
を用いて
部分を計算したい.
これは被積分関数部分を組めば良いので
double RP(double t, std::vector<double> v) { return 1. / (t * (t * t - 1.) * log(t)); }
という感じでよいだろう.
グラフの形は
このようになっており,1で無限大に発散,10ではもう殆ど0に近い形になってる.
どうせは1超過でないといけないし,積分は20終了で十分な気がする.
メビウス関数の実装
次に組みやすそうな関数というとメビウス関数.
4で割れたら0を返す,2で割れるか調べて,3からひたすら奇数で小さい順に割っていくだけ.
平方数で割れたら0を返し割れた数で1を返すか-1を返すかを変えるだけ.
int MobiusFunc(int Num) {
int n = Num;
int count = 0;
if (n % 4 == 0)return 0;
if (n % 2 == 0) {
n /= 2;
count++;
}
for (int i = 3; i <= n; i+=2) {
if (n % (i * i) == 0)return 0;
if (n % i == 0) {
n /= i;
count++;
}
if (n == 1)break;
}
return (count % 2 == 0) ? 1 : -1;
}
対数積分の実装
積分関数実装してるとはいえ対数積分は結構面倒そうだなと思ったからいい方法無いかなと探したら指数積分を使う方法があるみたい.
正直勉強不足のためまだ理解が少々及ばないので殆どアテにしないように.
指数積分は
と定義されるようで,これは補正指数積分
を定義すると,のテイラー展開がと表記できるので全平面で正則になる.
また
という関数を定義するとになり,更に
という関係が得られ,
になるそう.
で,は無限和表示にすると
になる.
のテイラー展開は
std::complex<double> Ein(std::complex<double> z) {
std::complex<double> result = 0;
for (int k = 1; k <= 300; k++) {
std::complex<double> tmp = 1. / (double)k;
for (int i = 1; i <= k; i++)tmp *= -z / (double)i;
result += tmp;
}
return result;
}
と実装ができ,ここからは
std::complex<double> Ei(std::complex<double> z) {
return M_GAMMA + log(z) - Ein(-z);
}
と実装ができる.こうしてようやく対数積分を
std::complex<double> li(std::complex<double> z) { return Ei(log(z)); }
と実装できるようになった.
ちなみに今回は使ってないけど
はという関係を用いて
という感じにバラすことができる.
途中の関係式は
によるもの.
から
ここで
から得られる
k及び,より
になる.
ゼータ関数の非自明な零点
ゼータ関数の非自明な零点はプログラムで計算させようとはしたけどあまり精度がよくならなかったし計算時間もバカにならないので予めできあがっているものを用意させてもらった.
3分クッキングばりの省略の仕方で申し訳ない.
非自明な零点部分の対数積分
正直ここがすごく苦労した.対数積分も指数積分も理解がままなっていないので,実装が非常につらく無事に死亡したので,先駆者のtujimotterさんはどうやってるのかなととりあえずざっくり調べてみた.
H. Riesel and G. Göhl, "Some Calculations Related to Riemann's Prime Number Formula", Mathematics of Computation, Vol.24, No.112, pp. 969-983 (1970).
という論文に書いてある式で実数のまま計算ができるようになるということで,よく見つけたなぁという感じでありがたく参考にさせていただく.
式の導出は
なので,を代入してと置換すると,もしくはのとき
になる.
これは部分積分すると
になって,
との比の絶対値を考えると,と吹き飛ばすと
になるので,積分部分はほぼ無視をして
と近似ができるそう.
そこで
として,もしもリーマン予想が成り立つ,つまりと表せられるなら
と書くことができる.
これを有理化(?)して
展開する
になる.
一方になるので
になり,
になる.
最後に三角関数の合成
を使うと
になるから
と近似ができる.
申し訳程度のプログラムだと,Zerosという配列に零点を入れておいて
std::complex<double> RPSum = 0;
for (int i = 0; i < Zeros.size(); i++) {
RPSum += 2. * sqrt(x) / (abs(Zeros[i])*log(x)) * cos(Zeros[i].imag() * log(x) - arg(Zeros[i]));
}
となるよと一応.
これまでの結果を1~40まで配列に0.1刻みで保存して描画した.
グリッドは縦横それぞれ1刻みで引いてると思って見てください.
とりあえず組んでみて動いてちょっとうれしいので動画をあげた.
参考文献
リーマンの素数公式を可視化する - tsujimotterのノートブック
Some Calculations Related to Riemann's Prime Number Formula