素数定理の証明について

素数定理の証明については、ザギエ(Zagier)による4ページの解説論文「Newman's Short Proof of the Prime Number Theorem」に示されている証明があるけど、説明が簡潔すぎることもあって証明を読み進んでいくのがかなり大変という印象がある。

むしろ、アダマールとド・ラ・ヴァレ・プーサンによる素数定理の最初の証明の方が、きちんと正しく証明するのは非常に難しいにしても、やりたい事は分かりやすい気がする。

またザギエが参照しているコレヴァ(Korevaar)の解説論文を見てみると、ザギエの提示している証明の背後には、ウィーナー・池原の定理による証明が存在していることが分かる。*1

なので、その辺りのことをまとめておく。

1. チェビシェフ関数の性質

1.1. 第1チェビシェフ関数θ(x)、第2チェビシェフ関数ψ(x)

アダマールやド・ラ・ヴァレ・プーサンによる証明も、ウィーナー・池原の定理を用いる証明も、ザギエの証明も、いずれもチェビシェフ関数を利用しているので、その説明からおこなう。(ちなみにニューマンの論文では素数定理の証明を2つ示しているけれど、どちらもチェビシェフ関数を使うのとは別の経路で素数定理を証明している。)

第1チェビシェフ関数θ(x)は、

θ(x) = 「x以下の全ての素数pに対する自然対数log p の和」

と定義される。これは、

\theta(x) = \sum\limits_{\substack{p\\p \le x }} \log p

のように書くこともできる。(和の添字にpを使ったときは、pは素数だけを渡るとする)。
例えばθ(10)を考える。10より小さい素数は、2、3、5、7、なので、

θ(10) = log2 + log3 + log5 + log7

となる。

一方、第2チェビシェフ関数ψ(x)は、x以下の素数だけでなく、x以下の数で「素数のべき乗になる数 pm」に対しても、log p を足す。これは、

\psi(x) = \sum\limits_{\substack{p ,m\\p^m \le x }} \log p

と書くこともできる。(ここでpは素数、mは正整数を動く。)
例えば ψ(10)を考えると、10より小さい数で、素数、または、素数のべき乗になっている数は、

2、3、4(=2^2)、5、7、8(=2^3)、9(=3^2)

なので、

ψ(10) = log2 + log3 + log2 + log5 + log7 + log 2 + log3

となり、θ(10)と比べると、4、8、9に対するlog2、 log2、log3のぶんだけだけ大きくなっている。

1.2. チェビシェフ関数と素数定理

さて、素数定理の証明の観点から重要なのは、

θ(x) ~ x

あるいは、

ψ(x) ~ x

が成り立てば、そこから素数定理

\pi(x) \sim \frac{x}{\log x}

が導かれる、ということ。(ここで f(x)~g(x) は、\lim\limits_{x\to\infty}\frac{f(x)}{g(x)}=1を表す。)
実際、アダマールとド・ラ・ヴァレ・プーサンは、第2チェビシェフ関数ψ(x)について ψ(x)~x を示すことで素数定理を証明した。

1.3. チェビシェフ関数とゼータ関数の関係

素数定理の証明では、ゼータ関数の性質が重要な役割を果たす。
まず第2チェビシェフ関数ψ(x)とゼータ関数ζ(s)の間には、

 \displaystyle{-\frac{\zeta'(s)}{\zeta(s)} = s\int_{1}^{\infty}\frac{\psi(x)}{x^{s+1}}dx}

の関係がある。アダマールとド・ラ・ヴァレ・プーサンによる素数定理証明はフォン・マンゴルトの明示公式にもとづいたものだけど、その明示公式はこの関係式から出発して導かれる。またウィーナー・池原の定理を用いる証明では、この関係式にウィーナー・池原の定理を適用する。
一方、第1チェビシェフ関数θ(x)とゼータ関数の関係はもう少しだけ複雑で、同様の形の変換をほどこすと、-ζ'(s)/ζ(s)の代わりに \Psi(s) = \sum\limits_{p} \frac{\log p}{n^p}というディリクレ級数(ここで和の添字pは全ての素数を渡る)で定義される関数に変換され、

 \Psi(s) =\quad \displaystyle{-\frac{\zeta'(s)}{\zeta(s)} - \sum\limits_{p}\frac{\log p}{p^s(p^s-1)} \quad= s\int_{1}^{\infty}\frac{\theta(x)}{x^{s+1}}dx}

という関係になっている。ザギエの証明は、第2チェビシェフ関数ではなく、第1チェビシェフ関数を使っているので、そのぶん素数定理ゼータ関数との関係は少し分かりにくくなっているような感じがする。

2. フォン・マンゴルトの明示公式と、アダマールとド・ラ・ヴァレ・プーサンによる証明

アダマールとド・ラ・ヴァレ・プーサンによる素数定理の証明では、チェビシェフ関数に対するフォン・マンゴルトの明示公式が重要な役割を果たしている。(フォン・マンゴルトが明示公式を証明したのが1895年。アダマールとド・ラ・ヴァレ・プーサンがそれぞれ独立に素数定理を証明したのが1896年)
導出過程などは略して明示公式を示すと、第2チェビシェフ関数ψ(x)(を不連続点で少し修正した関数)が、フォン・マルゴントの明示公式では

 \psi(x) = x - \sum\limits_{\rho}\frac{x^{\rho}}{\rho} - \log(2\pi) -\frac{1}{2}\log\left(1-\frac{1}{x^2}\right)

のように表される。ここで和の「ρ」は ゼータ関数ζ(s)の0≦Re(s)≦1の範囲にある全ての零点を渡る。
素数定理を示すには「ψ(x)~x」すなわち \lim\limits_{x\to\infty}\frac{\psi(x)}{x} =1 を示せば良かったけど、明示公式のうちゼータ関数の零点についての和以外の部分を見ると、

\displaystyle{\lim\limits_{x\to\infty}\frac{x - \log(2\pi) -\frac{1}{2}\log\left(1-\frac{1}{x^2}\right)}{x} = 1}

となっている。なので、あとは

 \displaystyle{\lim\limits_{x\to\infty}\frac{\sum\limits_{\rho}\frac{x^{\rho}}{\rho} }{x}=0}

が言えれば、素数定理が証明できたことになる。
ここで、ゼータ関数の零点ρ=a+ibを適当に一つ取って、

\displaystyle{\lim\limits_{x\to\infty}\frac{\frac{x^{\rho}}{\rho}}{x} = \lim\limits_{x\to\infty}\frac{x^{\rho-1}}{\rho} = \lim\limits_{x\to\infty}\frac{x^{(a-1)+ib}}{\rho}}

の値を考える。xは実数で、実数の虚数乗は絶対値が1だから、xibの部分は絶対値が1。なので、もしもρの実部Re(ρ)=a<1 ならば、\lim\limits_{x\to\infty}\frac{x^{\rho}/\rho}{x} =0 となる。
ということは、あとは 0≦Re(s)≦1の範囲にある全ての零点ρがRe(ρ)<1であることが言えれば(そのためにはRe(s)=1でζ(s)≠0となることが言えればいい)素数定理が証明される?

残念ながらこれは成り立たない。0≦Re(s)≦1の範囲にあるゼータ関数の零点は無限個あり、しかも\sum\limits_{\rho}\frac{x^{\rho}}{\rho} は条件収束しかしていないので、各項をxで割ったものが0に収束することが言えても、和全体をxで割ったものが0に収束することは言えない。
とはいえ、アダマールもド・ラ・ヴァレ・プーサンも、和の極限に関するこの問題をうまく回避し、なおかつRe(s)=1でζ(s)≠0 となることも証明して、素数定理を証明することに成功した。

3. ウィーナー・池原の定理による証明

3.1. メリン変換

アダマールもド・ラ・ヴァレ・プーサンも、フォン・マルゴントの明示公式(を和の極限の困難をさけるために修正したもの)を使って素数定理を証明している。しかし、導出過程をきちんと正当化しつつ明示公式を導くのは非常にたいへん。
なので明示公式を使わない証明があるとうれしい。
そこでまず、明示公式に至る前の -\frac{\zeta'(s)}{\zeta(s)} = s\int_{1}^{\infty}\frac{\psi(x)}{x^{s+1}}dx 、あるいは   \Psi(s) = s\int_{1}^{\infty}\frac{\theta(x)}{x^{s+1}}dx の形の積分

 \displaystyle{ g(s) = s\int_{1}^{\infty}\frac{f(x)}{x^{s+1}}dx}

について調べてみる。これはメリン変換と呼ばれる形の積分。(1/xを新しい変数にして変数変換して積分範囲も変えると、「メリン変換」で通常提示される形になる。)
まず f(x) = cx として、メリン変換 g(s) を計算してみると、

\displaystyle{ g(s) = \frac{c}{s-1} + c} \quad(\mbox{Re}(s)>1)

となる。またここで積分の下端1を別の値にしてみると、

 \displaystyle{ s\int_{A}^{\infty}\frac{cx}{x^{s+1}}dx} = \frac{c}{s-1}+\mbox{定数}\quad(\mbox{Re}(s)>1)

となっている。積分の下端Aをどれだけ大きな数にしてもこうなるのて、xが大きい部分での積分結果が \frac{c}{s-1}の部分に集約されているように見える。
次にf(x)として、f(x)~cxであるような任意の関数f(x)を取ってみる。するとこの場合もRe(s)>1で積分できて、右側からs=1に近づいていくと、 g(s)=\frac{c}{s-1}+O(1) となる。(つまりs→1のとき g(s)≒\frac{c}{s-1} のように振舞う。)
例えば、f(x) = ⌊x⌋ (= x以下の最大整数)とすると、f(x)~x で、 s\int_{1}^{\infty}\frac{\lfloor x \rfloor}{x^{s+1}}dx=\zeta(s)で、ζ(s)はs=1に留数1の1位の極を持つのでs→1でζ(s)≒\frac{1}{s-1}が成り立っている。

では逆に「s→1のときに g(s)≒\frac{c}{s-1} のように振舞うなら f(x)~cx」が言えるか?
というとこれは成り立たない。例えば、

f(x) = 2^k \quad \left(2^{k-1}\le x \lt 2^{k}\right)

という関数を考える。これはy=xのグラフとy=2xのグラフの間を階段状に増加していく関数なので、特定のcxには漸近しない。しかし、このf(x)からg(s)を計算すると、

\displaystyle{g(s) = 2\frac{1-2^{-s}}{1-2^{1-s}}} \quad(\mbox{Re}(s)>1)

となり、s→1のとき、g(s) = \frac{1}{\log 2}\frac{1}{s-1} + O(1) のように振舞う(はず。計算ミスがなければ。)

とはいえ、 g(s) = s\int_{1}^{\infty}\frac{f(x)}{x^{s+1}}dx が、s→1で g(s)≒\frac{c}{s-1} のように振舞うことと、なんらかの補助的な条件から、f(x)~cx を導けないだろうか。

それを助けてくれるのが、ウィーナー・池原の定理。

3.2. ウィーナー・池原の定理

ウィーナー・池原の定理を、ここでの話に合わせた形で提示すると、次のようになる。

ウィーナー・池原の定理
f(x)は、x≧1で非負かつ非減少な関数であり、そのメリン変換

 \displaystyle{ g(s) = s\int_{1}^{\infty}\frac{f(x)}{x^{s+1}}dx}

は、Re(s)>1で存在するとする。このとき、関数g(s) - \frac{c}{s-1} を Re(s)≧1 の範囲に連続関数として拡張できるなら、

\lim\limits_{x\to \infty}\frac{f(x)}{x} = c

が成り立つ。(つまり f(x)~cx が成り立つ。)

例えば、f(x)として先に例として出したy=xとy=2xの間を階段状に増加する関数(cxに漸近しない関数)を取ると、g(s) = 2\frac{1-2^{-s}}{1-2^{1-s}} だったので、境界線Re(s)=1に右から近づいたとき、s→1だけでなく、s\to 1+\frac{2\pi n}{\log 2} iの時にも発散するため、ウィーナー・池原の定理の条件「g(s) - \frac{c}{s-1}を Re(s)≧1 の範囲に連続関数として拡張できる」を満たすことができない。

3.3. ウィーナー・池原の定理を用いた素数定理の証明

ウィーナー・池原の定理を素数定理の証明に適用するためには、メリン変換

 \displaystyle{-\frac{\zeta'(s)}{\zeta(s)} = s\int_{1}^{\infty}\frac{\psi(x)}{x^{s+1}}dx}

が必要な性質を満たしていることを見る必要がある。

まずRe(s)>1の範囲で g(s) = s\int_{1}^{\infty}\frac{\psi(x)}{x^{s+1}}dxが値を持つ必要がある。(例えばf(x)=x2だと、Re(s)>2でないと積分が発散する。)
これを言うには「ψ(x) = O(x)」(ψ(x)の増加が、xの定数倍より遅い)を証明すればいい。

あとは、 -\frac{\zeta'(s)}{\zeta(s)} - \frac{1}{s-1}の、Re(s)=1上での様子(連続かどうか)が問題となる。
ζ(s)が零点か極になるsでは、-ζ'(s)/ζ(s)は極を持ち、それ以外の点では-ζ'(s)/ζ(s)は正則になることに注意して考える。(ここで、素数定理の最初の証明で示された「Re(s)=1でζ(s)≠0」が使われる。)
まずゼータ関数ζ(s)の極を見る。ζ(s)はs=1に1位の極を持っていて、\zeta(s) = \frac{1}{s-1} + \cdotsローラン展開される。このことから、  -\frac{\zeta'(s)}{\zeta(s)} = \frac{1}{s-1} + \cdots となって -ζ'(s)/ζ(s)もs=1で留数1の1位の極を持つことが分かる。
これでs=1のことが分かったので、Re(s)=1の直線上のs=1以外の点での -ζ'(s)/ζ(s) の様子が問題となる。素数定理の最初の証明で示されたように、Re(s)=1ではζ(s)≠0だった(そしてζ(s)はs=1以外には極を持たない)。なので、-ζ'(s)/ζ(s)はRe(s)=1の直線上でs=1を除いて正則になる。よって-\frac{\zeta'(s)}{\zeta(s)} - \frac{1}{s-1}は、Re(s)=1の直線上全体で正則となるので、Re(s)=1上で連続。
こうして、ζ(s)がRe(s)=1でζ(s)≠0であること(とs=1で極を持つこと)から、ψ(x)と -ζ'(s)/ζ(s) がウィーナー・池原の定理の適用条件を満たしていることが導かれる。
したがってウィーナー・池原の定理を適用することで、ψ(x)~xが言え、素数定理が証明される。

4. ニューマンの証明

4.1. コレヴァの証明

ニューマンは、コーシーの積分定理をうまく用いて補助的な定理を証明し、それを使って素数定理の証明を大幅に簡略化した。
しかしニューマン自身の証明は、チェビシェフ関数を使うのとは異なる仕方の証明なので、ここではニューマンの証明を分析したコレヴァによる証明を見る。
まずコレヴァは、ニューマンが用いた手法によって次の補助定理が証明できることから始める。(この補助定理はザギエの証明でも使われる。コレヴァの論文では「Auxiliary Tauberian theorem」と呼ばれ、ザギエの論文では「Analytic Theorem」と呼ばれている。)

定理: 関数F(t) (t≧0) は有界で、任意の有界区間積分できるとする。このとき

 G(z) = \int_{0}^{\infty}F(t)e^{-zt}dt

は、Re(z)>0で正則。
このG(z)がRe(z)≧0まで解析接続できるとする。
このとき、 \int_{0}^{\infty}F(t)dtは収束し、

 \int\limits_{0}^{\infty}F(t)dt = G(0)

が成り立つ。

コレヴァは次に、この定理から、弱い形のウィーナー・池原の定理を導く。「弱い」というのは、定理の適用条件が元の定理より厳しくなっているからで、元の定理で「Re(s)≧1まで連続関数として拡張できる」という条件だったところが、「Re(s)=1上の各点の近傍まで解析接続できる」になる。
しかし、-ζ'(s)/ζ(s) はこちらの条件も満たしているので、弱いウィーナー・池原の定理を適用できて、それにより素数定理が証明される。
これがコレヴァによる証明の道筋。

4.2. ザギエの証明での変更点

ザギエが解説論文で示している証明は、コレヴァの証明をいくらか修正したものになっている。
まず一点目として、ザギエは第2チェビシェフ関数ψ(x)ではなく第1チェビシェフ関数θ(x)を使って証明をおこなっている。
これは、第1チェビシェフ関数のメリン変換

 \displaystyle{ \Psi(s) = s\int_{1}^{\infty}\frac{\theta(x)}{x^{s+1}}dx}

にもウィーナー・池原の定理が適用できるため。 \Psi(s) = -\frac{\zeta'(s)}{\zeta(s)} - \sum_{p}\frac{\log p}{p^s(p^s-1)} で、 \sum_{p}\frac{\log p}{p^s(p^s-1)}の部分は、Re(z)=1を含む適当な範囲で正則なため、定理の適用条件には影響を与えないので。

二点目として、コレヴァのように補助定理から弱いウィーナー・池原の定理を一般的に導くのではなく、それを導く過程に対して直接第1チェビシェフ関数θ(x)を適用して、θ(x)~x を示している。

コレヴァは一般的なF(t) = e^{-t}f(e^t)-cG(z)=\frac{g(z+1)}{z+1}-\frac{c}{z} に補助定理を適用して、そこから f(x)~cx が成り立つことを示し、   g(s) = s\int_{1}^{\infty}\frac{f(x)}{x^{s+1}}dxに対する弱いウィーナー・池原の定理を証明する。

ザギエは、一般的なf(x)ではなく、F(t) = e^{-t}\theta(e^t)-1G(z)=\frac{\Psi(z+1)}{z+1}-\frac{1}{z} に補助定理を直接適用し、その結果を使ってθ(x)~xを証明する。

4.3. ザギエの証明でやらないといけないこと

最終的に、ザギエによる証明では、何を行う必要があるのか?

  • [1] 補助定理の証明

ニューマンの手法によってコーシーの積分定理を使って補助定理を証明する、というのがコレヴァ・ザギエの証明のキーポイントになる。

次に、F(t) = e^{-t}\theta(e^t)-1G(z)=\frac{\Psi(z+1)}{z+1}-\frac{1}{z}が補助定理の条件を満たしている事を示さないといけない。(以下の[2]~[5])。
まず、F(t) = e^{-t}\theta(e^t)-1有界でないといけない。(この条件はウィーナー・池原の定理での「メリン変換がRe(s)>1で存在する」に対応する。) それを示すために「θ(x) = O(x)」の証明がいる。

  • [2] θ(x) = O(x) (ザギエ 定理III)

そして、G(z)=\frac{\Psi(z+1)}{z+1}-\frac{1}{z} がRe(z)≧0で正則であることを示さないといけない。 G(z) = \frac{1}{z+1}\left(\Psi(z+1)-\frac{1}{z}\right)+\frac{1}{z+1}となるので、\Psi(s)-\frac{1}{s-1}がRe(s)≧1で正則であること(これは弱いウィーナー・池原の定理の適用条件にほかならない)を証明すればよい。  \Psi(s) -\frac{1}{s-1}=\left(-\frac{\zeta'(s)}{\zeta(s)} -\frac{1}{s-1}\right) - \sum\limits_{p}\frac{\log p}{p^s(p^s-1)} なので、本質的にはゼータ関数ζ(s)の性質を証明することになる。
まずζ(s)がs=1で留数1の1位の極を持ち、その点を除けば、Re(s)≧1を含む適当な領域で、ζ(s)が正則になる、という性質の証明をする。

  • [3] Re(s)>1で、ζ(s)が定義できる。 (ザギエ 定理I)
  • [4] \zeta(s)-\frac{1}{s-1}を、Re(s)>0まで、正則関数として拡張できる。 (ザギエ 定理II)

あとは、Re(s)≧1でζ(s)≠0となることが言えれば、-\frac{\zeta'(s)}{\zeta(s)}-\frac{1}{s-1} がRe(s)≧1で正則であることが言え、そこから\Psi(s)-\frac{1}{s-1}もRe(s)≧1で正則であることが言える。

  • [5] Re(s)≧1で、ζ(s)≠0かつ\Psi(s)-\frac{1}{s-1}は正則である。 (ザギエ 定理IV)

これでG(z)がRe(s)≧0で正則になることが言え、補助定理が適用でき、
\displaystyle{\int\limits_{0}^{\infty}F(t)dt=\int\limits_{0}^{\infty}\left(e^{-t}\theta(e^t)-1\right)dt=\int\limits_{1}^{\infty} \frac{\theta(x)-x}{x^2}dx}
が有限値に収束することが証明される。

  • [6] \int_1^{\infty}\frac{\theta(x)-x}{x^2}dxは収束する。 (ザギエ 定理V)

θ(x)~x というのは \lim\limits_{x\to \infty}\frac{\theta(x)-x}{x}=0 と等しいけど、これが\int_1^{\infty}\frac{\theta(x)-x}{x^2}dxが収束することから導かれる。

  • [7] θ(x)~x (ザギエ 定理VI)

そしてθ(x)~xから素数定理が導かれる。
ザギエは、以上の補助定理と定理I~VIの証明、素数定理の解説を4ページにまとめて提示している。

5. 参考文献

  • D. J. Newman, Simple Analytic Proof of the Prime Number Theorem(1980)
  • J. Korevaar, On Newman's Quick Way to the Prime Number Theorem(1982)
  • D. Zagier, Newman's Short Proof of the Prime Number Theorem(1997)
  • 河田敬義『数論―古典数論から類体論へ』岩波書店(1992)
  • INTEGERS「素数定理の証明」

*1:ザギエの論文のタイトルは「Newman's Short Proof of the Prime Number Theorem」だけど、ニューマン自身の証明とはかなり違っていて、むしろコレヴァの解説論文「On Newman's Quick Way to the Prime Number Theorem」の証明を元にしている。

ルジャンドル変換についてのメモ

単調増加関数の双対性

ルジャンドル変換は凸関数に関する概念だけど、そこに至る準備として単調増加関数(つまり x≦x'ならf(x)≦f(x')となる関数)を考える。

単調増加関数の入力側をxで表し出力側をXで表すことにする。つまり、x ↦ X のように値が定まっている。
また関数自体も出力と同じXで表記し、X = f(x) のように書かず

X = X(x)

のように書くことにする。

さて関数 X(x) は単調増加関数なので、逆関数ほぼ定まる。
ここで「ほぼ」なのは、

  • 不連続点x*で関数値が X1からX2に不連続に増加したとすると、X1からX2の範囲は関数X(x)の値域にならないので、逆関数の側では X1からX2までの値について対応するxの値がない。
  • あるx1からx2まで関数値が一定値X*を取ったとすると、逆関数の側では、X*に対応するxの点が無数にある。

となるので。
しかし

  • X1からX2までの値には、x*を対応させる。
  • X*に対応する値はx1とx2のどちらかにして、関数x(X)の不連続点とする。

と決めてやれば、X(x)の「逆関数」x(X)が決まる。このx(X)も単調増加関数になる。

ここで増加関数X(x)と、その「逆関数」x(X)の間には次のような関係がある。

X(x) x(X)
単調増加関数 単調増加関数
不連続点x*で、左極限値X1から右極限値X2への不連続な増加。 X1からX2まで、一定値x*を取る。
x1からx2まで、一定値X*を取る。 不連続点X*で、左極限値x1から右極限値x2への不連続な増加。

凸関数の間の双対性

ここで、さらに関数X(x)と関数x(X)をそれぞれ積分して、
 f(x) = \int X(x)\,dx\qquad F(X) = \int x(X)\,dX
というふたつの関数f(x)とF(X)を考える。

まず、X(x)、x(X)が単調増加関数なので、f(x)、F(X)は下に凸な関数になる。
X(x)がx*で不連続のときf(x)はx*微分できない。(左微分係数と右微分係数が異なる。) またX(x)が一定値を取る区間では、f(x)は微分係数(傾き)が一定だからその部分では一次関数と一致する。
X(x)、x(X)とその積分f(x)、F(X)の関係をまとめると次のようになる。

f(x) X(x) x(X) F(X)
下凸関数 単調増加関数 単調増加関数 下凸関数
点x*微分できない。(左微分係数がX1で右微分係数がX2) 不連続点x*で、左極限値X1から右極限値X2への不連続な増加。 X1からX2まで、一定値x*を取る。 X1からX2まで、F(X)の微分係数が一定になる。
x1からx2まで、f(x)の微分係数が一定になる。 x1からx2まで、一定値X*を取る。 不連続点X*で、左極限値x1から右極限値x2への不連続な増加。 点X*微分できない。(左微分係数がx1で右微分係数がx2)

ここで、

が成り立っているから、

  • f(x)とF(X)は、定数分の不定性を除いて、互いを正確に再現することができる。( X(x)とx(X)の不連続点での不定性は積分には効いてこないので、f(x)、F(X)の値には影響しない。)

下凸関数f(x)から下凸関数F(X)を得る

ここまでの話を踏まえると、下凸関数f(x)に対して次の手順をおこなうことで下凸関数F(X)が得られる。

  1. f(x)は下凸関数なので、(1)連続関数になり、(2)ほとんどの点で微分可能になる。
  2. f(x)を微分して、増加関数X(x) = \frac{df}{dx}(x)を得る。
    • 微分不可能な点(X(x)は不連続な増加をする)での値は後の部分で効いてこないので特に決めなくていい。
  3. 増加関数X(x)の「ほぼ逆関数」x(X)を取る。(X(x)の不連続点はx(X)では一定値、X(x)の一定値はx(X)の不連続点とする。)
  4. x(X)を積分して、F(X) =\int x(X)\,dxを得る。

逆に、この手順をF(X)に対しておこなえば、F(X)からf(x)を(定数の不定さを除いて)再現することができる。

関数f(x)に「微分不可能な点」も「微分係数が一定の区間」も無い場合は、x↔Xがちょうど一対一で対応するので、f(x)の値とF(X)の値も対応する。
微分不可能な点」や「微分係数が一定の区間」がある場合は一対一では対応せず、

という対応関係になる。
一点と区間が対応しているので、一見すると変換の過程で必要な情報が足りなかったり削ってしまったりしそうだけど、微分係数一定の区間での関数値は区間の両端の値を直線的に結べば決まるので、問題なく変換が成立する。

この「下凸関数f(x) ↔ 下凸関数F(X)」の間の相互変換は、いわゆるルジャンドル変換と同じものになっている。

ルジャンドル変換

上でのf(x)からF(X)への変換を、普通のルジャンドル変換の形に近づけていく。

f(x)に微分不可能な点がなく微分係数が一定の区間もない場合

まず、f(x)に微分不可能な点がなくて、微分係数が一定になる区間もない場合を考える。
すると、部分積分により
 \begin{eqnarray} F(X) &= & \int^{X}_{X_0} x(X)\,dX = [x(X)\cdot X]^X_{X_0}  - \int^x_{x_0}X(x)\,dx \\ &=& [x(X)\cdot X]^X_{X_0} - [f(x)]^x_{x_0} \\ &=& [x(X)\cdot X   - f(x(X))]^X_{X_0} \\ &=& x(X)\cdot X - f(x(X)) +C \end{eqnarray}

となり、定数項を消すと、通常のルジャンドル変換の式、

F(X) = x(X)\cdot X - f(x(X))

と一致する式が得られる。

f(x)に微分不可能な点がある場合

x=x*でf(x)が微分不可能で、点x*区間[X1, X2]に対応しているとする。つまりx(X1) = x(X2) = x*となっている。
この場合のF(X)を計算する。
XがX1以下のときのF(X)は、さっきの計算と同じ。

X2以上のXについてのF(X)を計算すると、
\begin{eqnarray}  F(X) &=&  \int^{X_1}_{X_0} x(X)\,dX +  \int^{X_2}_{X_1} x(X)\,dX +  \int^{X}_{X_2} x(X)\,dX \\ &=&  [x(X)\cdot X   - f(x(X))]^{X_1}_{X_0} +(X_2-X_1)x_{*} + [x(X)\cdot X   - f(x(X))]^{X}_{X_2} \\ &=&  [x(X)\cdot X   - f(x(X))]^X_{X_0} \\ &=& x(X)\cdot X - f(x(X)) +C  \end{eqnarray}
となり、微分不可能な点がない場合と同じ式が得られる。(x(X1) = x(X2) = x*のため、式変形の途中で値が打ち消しあっている。)

X1 < X < X2となるXについて計算すると、

\begin{eqnarray}  F(X) &=&  \int^{X_1}_{X_0} x(X)\,dX +  \int^{X}_{X_1} x(X)\,dX \\ &=& x_{*} \cdot X - f(x_*) + C \end{eqnarray}

となり、ルジャンドル変換の式で「x(X)」のところを x* したものになっている。
また、この式は一次式になっているから、「f(x)が微分不可能になる点には、F(X)の微分係数が一定になる区間が対応する」と合致している。

f(x)に微分係数が一定の区間がある場合

今度は区間[x1, x2]でf(x)の微分係数が一定値X*を取る場合のF(X)を計算する。

左からX*に近づいた場合、x(X)→x1となり、右からX*に近づいた場合、x(X)→x2となることに注意する。

X > X*として、

\begin{eqnarray}  F(X) &=&  \int^{X_*}_{X_0} x(X)\,dX +  \int^{X}_{X_*} x(X)\,dX \\ &=&  [x(X)\cdot X   - f(x(X))]^{X_*}_{X_0} +  [x(X)\cdot X   - f(x(X))]^X_{X_*} \end{eqnarray}

ここで、

  • 第1項:  \Bigl(x_1 X_* - f(x_1) \Bigr) + C
  • 第2項:  \Bigl( x(X)\cdot X - f(x(X) \Bigr) - \Bigl( x_2 X_* - f(x_2) \Bigr)

となる。
区間[x1, x2]でf(x)の微分係数は常にX*なので、

 f(x_2) = f(x_1) + (x_2 - x_1)X_*

が成り立っているので、第1項の前半と第2項の後半が打ち消しあって、ふたたび

 F(X) = x(X)\cdot X - f(x(X)) +C

の式が得られた。

ルジャンドル変換の式

結局、f(x)に微分不可能な点や微分係数一定の区間がある場合も含めて

  •  F(X) = x(X)\cdot X - f(x(X)) \quad (Xは、f(x)の微分不可能点と対応しない)
  •  F(X) = x_* \cdot X - f(x_*)\quad (Xは、f(x)の微分不可能点x*に対応する領域にある)

で、F(X)を計算できる。(定数C=0とした。)

この結果を図で見ると次のようになる。
f:id:lemniscus:20210514223432p:plain:w480
関数 x(X) が右上象限に入る点を (Xs, xs) = (Xs, x(Xs)) としておく。
このとき、図の右下の領域の面積と左上の領域の面積は、それぞれ

 \begin{array}{ccc} \int^X_{X_s}x(X)\,dX  &=& F(X) - F(X_s) \\ \int^{x(X)}_{x_s} X(s)\,dx &=& f(x(X)) - f(x_s) \end{array}

となる。これらの和が長方形の面積 x(X)・X になるので、

 F(X) = x(X)\cdot X - f(x(X)) + \Bigl(f(x_s) - F(X_s) \Bigr)

が成り立ち、F(Xs) = f(xs)となるようにF(X)を決めると

 F(X) = x(X)\cdot X - f(x(X))

となる。ただし、f(x)の微分不可能な点や増加率一定の区間では、xとXが一対一に対応しないので、

  • f(x)の微分不可能な点(図のx*)に対応する区間 [X1, X2] の間にXがある場合は、 F(X) = x_* \cdot X - f(x_*) となる。
  • f(x)の増加率が一定の区間 [x1, x2] に対応する点 X* の場合、この区間内のどのxの値を使っても、 F(X) = x \cdot X - f(x)が成り立つ。

そして逆に

  •  f(x) = x\cdot X(x) - F(X(x)) \quad (xは、F(X)の微分不可能点と対応しない)
  •  f(x) = x \cdot X_* - F(X_*)\quad (xは、F(X)の微分不可能点X*に対応する領域にある)

によってF(X)からf(x)を求めることができ、もとの関数が再現される。

熱力学の場合

熱力学では、ルジャンドル変換を  F(X) = x(X)\cdot X - f(x(X)) とせず、符号を逆にして

 F(X) = f(x(X)) - x(X)\cdot X

と定義している。
このため、F(X)からf(x)への変換は

 f(x) = F(X(x)) + x\cdot X(x)

となり、f(x)からF(X)への変換式とは違う形になり、また凸関数としても「f(x)は下凸関数 ↔ F(X)は上凸関数」となる。

これは、熱力学では複数の変数についてルジャンドル変換をおこなっていて、その場合こちらの定義の方がわかりやすいから。

場の量子論への入門の一歩前

目次:「1.場の量子論 = 粒子数の変化を扱える量子論」「2.生成・消滅演算子」「3.古典論への移行と量子化」「4.量子力学から場の量子論への移行」「5.量子力学から場の量子論への移行(古典場を経由しない仕方)」「6.終わり」

1. 場の量子論 = 粒子数の変化を扱える量子論

てっとり早く場の量子論を特徴づけると、

  • 場の量子論 … 粒子の数の変化を扱える(粒子の生成・消滅を扱える)量子論

のように説明できる。
それと対比させると「量子力学」は粒子数が変化しない量子論ということなる。

(「量子力学」といったときに場の量子論も含める場合も多いけど、この文章では、場の量子論と対比させた意味で「量子力学」という言葉を使うことにする。)


ここで「粒子」と言っても、それは量子論的な「粒子」なので、粒子的な性質と波動的な性質の両方を合わせ持っており、日常で観察される何かの粒のように理解することはできない。それでも、

  • 「生成」「消滅」ごとにエネルギーや運動量が離散的に増減し、「個数」(個数演算子)も定義できる。

という点で、粒子(何かのかたまり)のような性質を持っている。

また場の量子論では、日常的な感覚では粒子っぽくないものも粒子として扱うことができる。
例えば結晶格子の振動(音の波)を量子論を考慮して扱うと、離散的なエネルギー励起が現れる。このときのエネルギーの離散的な増減を、場の量子論では「粒子(フォノン)の生成・消滅」として扱える。

また粒子の性質だけでなく波動の性質も持っているので、「粒子の生成・消滅」を波動の視点から見ると「波の強さや周波数成分を離散的に変化させている」と表現することができる。

2. 生成・消滅演算子

粒子の生成・消滅を扱うために、運動量{\bf p}の粒子を生成する演算子\hat{a}^{\dagger}({\bf p})、消滅させる演算子\hat{a}({\bf p})や、それらをフーリエ変換した\hat{a}^{\dagger}({\bf x}),\hat{a}({\bf x})といった演算子が導入される。

こうした演算子の持つ性質が粒子の性質を規定するので、これらの演算子の性質を特定することが場の量子論の第一歩になる。(生成・消滅演算子調和振動子の上昇・下降演算子と同じような性質を持っているので、場の量子論への導入として調和振動子の話から入ったりする。)

粒子の生成・消滅を扱うために導入された演算子は、空間の各点{\bf x}(またはフーリエ変換した運動量空間の各点{\bf p})ごとに定義されているので場の演算子と呼ばれる。

3. 古典論への移行と量子化

場の演算子\hat{a}^{\dagger}({\bf x}),\hat{a}({\bf x})の適用によって状態が離散的に変化するけれど、この離散的変化があたかも連続的に見えるような近似(古典的極限)を考えると、粒子性と波動性のうち粒子的な性質が見えなくなり、量子論的ではなく古典的に扱える場の量 a(x) に移行する。*1
ただしこうして現れた場は、「古典的」とはいっても、その場を古典力学的・電磁気学的な現象として観察できるとは限らない。単に演算子などが登場せず古典力学的に取り扱えるという意味で「古典的」と言っているにすぎない。

逆に古典的な場から出発し、何らかの手続きによって場の量子論に移行する操作を「場の量子化」という。

また、古典場ではなく質点の力学から出発してそれを「量子化」すると量子力学に移行する。
これらをまとめると次の表のような関係になっている。

有限自由度無限自由度
古典論質点古典場
古典極限↑ ↓量子化古典極限↑ ↓量子化
量子論量子力学
(粒子数固定)

粒子数固定
場の量子論
(粒子数可変)

  • 場の量子論で、粒子の数が固定している場合(「個数」が保存量の場合)だけに状況を限定すると量子力学で扱える。
  • 場の量子論で、粒子の生成・消滅による離散的変化が見えないくらい粒子数が多いと古典場として扱える(電磁場とか)。

このような話の脈絡だと「古典場を量子化して場の量子論を得る例」として、まずは電磁場が思い浮かぶ。
実際、歴史的にも光の輻射を量子論的に取り扱うための試行錯誤から場の量子論が生まれている。

ただし

  • マクスウェルの方程式自体が複雑なので、古典論の時点ですでに数式が面倒。
  • 量子化するときゲージ自由度のことを考える必要がある。

というように、場の量子論一般に比べて扱いが面倒なので、場の量子論の本ではいきなり電磁場を扱うのではなく他の場を扱ったあとに回されることも多いみたい。*2

4. 量子力学から場の量子論への移行

量子力学が「粒子数固定の量子論」で、場の量子論が「粒子数の変化を扱える量子論」だった。
ということは場の量子論の方が扱う範囲が広いのだから、量子力学で扱っている問題を場の量子論の問題として扱うこともできるだろう。

量子力学と場の量子論には次のような関係があった。

有限自由度無限自由度
古典論質点古典場
古典極限↑ ↓量子化古典極限↑ ↓量子化
量子論量子力学
(粒子数固定)

粒子数固定
場の量子論
(粒子数可変)
そこで、次のようにすれば量子力学から場の量子論へ移行できる。

  1. 何かちょうどいい古典場を取る。
  2. その古典場を量子化する。
  3. 量子化して得た場の量子論の式が、もとの量子力学系と同じ結果を与えるようになっていればよい。

しかし、そのような「ちょうどいい古典場」は非常に混乱を生む性質を持っている。
それは、

  • 粒子同士が相互作用しない場合、「ちょうどいい古典場」は、1粒子系の波動関数が満たす方程式と同じ方程式を満たす。(また場の演算子も同じ方程式を満たす。)

という性質。

波動関数量子論的な概念で、その一方、古典場のほうは古典論的な概念(たとえその場が古典力学電磁気学には登場しないような場だとしても)なので、そのどちらも同一の方程式を満たしてしまうというのは、両者の違いを曖昧にして混乱を生むことになる。
例えば、

  1. 質点の力学を出発点にして、それを量子化して波動関数の満たす方程式(シュレディンガー方程式とかクライン・ゴルドン方程式とか)を得る。
  2. その方程式を満たしている古典場(これは波動関数とは別のもの)を考えて、その古典場を量子化して場の演算子を得る。

という手順で場の量子論に到達した場合、二回の量子化が関わっている。

ここで同一の方程式に従うふたつのもの(波動関数と古典場)があることを曖昧にすると、量子化して得たもの(波動関数)をもう一度量子化しているように誤解してしまう。*3

高橋康『古典場から量子場への道』だと3章でシュレディンガー方程式やクライン・ゴルドン方程式を導入し4章で場の量子化を扱っているけれど、読んでいて古典論の対象を扱っているのか量子論の対象を扱っているのか分からなくなって混乱した。

朝永振一郎量子力学II』では、見かけは同一の方程式でも、波動関数が満たす式をSchrödinger方程式、古典場が満たす式をde Broglie方程式と呼んで区別している。

Schrödinger方程式とde Broglie方程式とが同じ形をとることから、量子力学の発見されたはじめのころは、この二つの方程式がまったく異なる概念に属するものであることが物理学者によくわからなかった。そこでこの差異に関する無知に基づいた混乱や無用の論争がしばしば起こったのであった。
(朝永振一郎量子力学II』§50)

また朝永『量子力学II』では、

  1. 「幾何光学と光の波動論」との類推から、電子の波動論としてde Broglie方程式を導く。(第6章)
    • 量子現象を扱うとき「de Broglie方程式 + 量子化」を使っている。(6章§47)
  2. マトリックス力学とde Broglie方程式との対応関係から、Schrödinger方程式を得る。(7章§49)

という説明をしている。つまり「古典場」から「量子力学」という説明の流れ。

5. 量子力学から場の量子論への移行(古典場を経由しない仕方)

前節では「波動関数と同一の方程式を満たす古典場を考えて、その量子化によって場の量子論にいたる」という流れで、量子力学から場の量子論への移行を説明した。
これとは別に、古典場は経由せずに、多粒子系の波動関数を生成演算子を用いた形に変形する、という説明の仕方もある。*4

同種粒子n個からなる系を量子力学で扱う場合、次のようにおこなわれる。

  1. 量子論では同種粒子は区別できないけれど、いったん区別できるかのように扱ってn個の粒子の座標をそれぞれx1、…、xnとして*5、n変数波動関数 φ(x1、…、xn) を考える。
  2. その上で、n個の変数を入れ換えても同じ状態を表すような波動関数だけを選別して用いる。ボース粒子なら対称関数、フェルミ粒子なら反対称関数を取る。

場の量子論への移行は、このような対称関数や反対称関数で表されている系を生成演算子を用いた形に変形することでおこなう。

  1. n粒子状態は、真空状態に生成演算子をn個適用したものとして表現される。
    • 扱っている粒子がボース粒子なのかフェルミ粒子なのかというのは、生成演算子の持つ代数的性質によって表される。
  2. 生成演算子の適用を「状態1の粒子を生成する演算子の適用がN1回、状態2の粒子を生成する演算子の適用がN2回、…」のように表現すれば、各状態1,2,…のそれぞれにいくつの粒子が入っているかの占有数N1、N2、N3、…によって系の状態を表していると見ることもできる。(占有数表示)

6. 終わり

ここまで来るとあとは数式を用いた話に入っていくことになるけれど、それは「入門の一歩前」ではなく「入門」の話になるだろう。

*1:「古典的極限」が正確にどういうことなのかよく理解できていないけれど、粒子の「数が非常に多く占有数を連続変数のように見なせる場合が古典的な極限にあたる」(サクライ『上級量子力学 上』2.3節。3.10節も参照)と考えておく。cf. 首藤啓『古典と量子の間』(岩波講座物理の世界)。

*2:一方、量子力学の本で、本の終わり辺りで電磁場の量子化を扱っていることもある。物理入門コースの中嶋貞雄『量子力学II』がマクスウェル方程式に踏み込まず電磁波の式をあらかじめ与えた上で量子化を論じているので比較的読みやすい? 他に本の目次をながめてみると小出昭一郎『量子力学II』、猪木慶治・川合光『量子力学II』、砂川重信『量子力学』など。

*3:ただし歴史的に見ると当初は「一度量子化したものをもう一度量子化する」という道筋で場の量子化が導入されたみたい。「ここでディラックは、彼独特のアクロバットをやりました。…そもそもシュレーディンガー方程式はすでに量子化の結果あらわれたものです。…それを、第2量子化の名が示すように、もう一度量子化するということにいったいどんな意味があるのだろうか。凡人たちはここで戸惑いを感ぜざるを得ない。」(朝永振一郎『スピンはめぐる』第6話)

*4:このアプローチが書かれている文献としては、一冊全体で80ページの新井朝雄『多体系と量子場』(岩波講座物理の世界) が分量的には最も手軽か? 他に北孝文『別冊数理科学 統計力学から理解する超伝導理論』2章、高橋康『物性研究者のための場の量子論1』2章、小出昭一郎『量子力学II』11章、猪木慶治・川合光『量子力学II』11.3節、砂川重信『量子力学』6章§6など。

*5:各粒子が区別されアイデンティティを持ち消滅もしないため量子力学では座標演算子が考えられる。場の量子論では座標演算子は登場しない。場の量子論の観点からは、位置演算子の性質(交換関係)はどのように生じるのだろう?

Rust入門を兼ねてプロジェクト・オイラーの問題を解く

とりあえず問100まで解くことに決めて始めたのだけど、問題の難易度やプログラムを書く面倒さが速いペースで増加していくので割と早い段階で問題を解くことが優先になって、Rustの機能やライブラリを理解することがおざなりになってしまった。
それでも言語の初歩的な部分についてはそれなりにRustの感覚が得られた気がする。

1. 整数とベクター

大部分の問題は、整数、整数のベクター、整数のベクターベクター等々だけを使って解ける。
そのため、プログラムを書くときに所有権などのせいで書き方に困ることはあっても、そうした所有権とか生存期間とかの規則が問題を解く上で本質的な障害になることはそんなに無い。

  • 「gotoしかない言語でプログラムを書いていた人がforやwhileだけでプログラムを書く」とか「破壊的変更が普通の言語を使っていた人がデータは基本的に不変な言語を使う」ような感じかも。
  • ベクターすら使う必要がなく整数を扱うだけで済む問題では、型の規則(不変可変、型の変換)にちょっとうるさいだけの普通の言語と思ってプログラムが書ける。

逆に、プロジェクト・オイラーの問題に取り組んでも、所有権や生存期間のことをよく考えないといけない場合についての理解はあまり深まらないとも言える。

よく使ったベクターの関数
  1. push
    • ベクターをあらかじめ大きなサイズを確保することはあまりなくてだんだん要素を追加していくことが多かったので、ベクターを使うときはだいたいpushを使っていた。
  2. sort
    • 重複するものを取り除く/重複しているかどうかの判定(dedupを併用)。
    • ソートしたもの同士に対して同値性を比較する。
    • 使わなくても済む場合(もっと計算が少なくて済む方法がある場合、ソートせずに操作しても結果は変わらない場合など)でも割と気にせず使っている。
    • sort_by、sort_by_keyはあまり使っていなかった。
  3. iter
    • はじめのうちはあまりイテレーターを使わなかった(イテレーターの関数をあまり知らなかった)ので、途中からはsortよりも頻繁に使っている。
  4. pop
    • pushと比較すると使用する機会はだいぶ少ない。
  5. dedup
    • だいたい「sortしたあとdedupして重複を消す」という使い方。
      • 「各桁がすべて異なるか」みたいな条件の判定にも。
      • きちんと判定すれば同じ計算を繰り返すのを避けられる場合でも、判定をサボって最後にsort + dedupで重複を消すみたいな使い方も。
  6. その他 (このあたりは、よく使ったというほとではない)
    • remove、insert
      • 組み合わせなどを列挙、探索するときにremove、insertを組みで使っていることが多かった。
    • append、reverse
      • 時々使っていたけど、ベクターでなくリストだったらもっと多用していたと思われる。
    • swap_remove
      • 使う機会はほとんどなかったけど、cloneできないデータのベクターを使う機会が多かったら結構使っていそう。
    • binary_search

2. イテレータ

ベクターへの操作をイテレーターの組み合わせて簡潔に書ける場合もある一方で、型や所有権に関するエラーが出て想定するように書けない場合もある。

  • たいていは型や所有権やライブラリ関数に対する理解不足が原因なので、動くように書き直していくことで型や所有権などへの理解が深まる。
  • エラーが消えないとかでどうにも困ったら(あるいは単に面倒になった場合も)添字でのアクセスに切り替えればよい。
    • もともとプロジェクトオイラーの問題の傾向からしても、添字経由でベクターを操作する方が自然な書き方になる場合も多いので、イテレーターに無理に固執しなくても良い。
    • 添字でのアクセスの方が所有権とか借用とかをあまり気にせず気楽に書ける。
よく使ったイテレーターの関数
  1. rev
    • forループで添字を降順で使いたい場合での使用が多い。
  2. step_by
    • ほとんど「step_by(2)」の形で使っていた。
  3. map
    • イテレーターの連鎖が分かりにくい感じになってmapを使うのをやめたことが何度かあった割には結構使っていた。
  4. collect
    • はじめのうちは結果を受け取る変数の型を指定して「let v: Vec<_> = 〜.collect()」みたいな書き方が多くて、だんだん「〜.collect::<Vec<_>>()」「〜.collect::<String>()」みたいに書くことが多くなっていた。どちらが見やすいのかはよく分からない。
  5. fold
    • ベクターの要素の和を取る」というのが一番多い使い方だった。sumがあるのに気づいてから使用する機会が減った。
  6. zip
    • zipをうまく使うと簡潔に書けることがある一方で、イテレーターの連鎖と参照がいろいろからんで分かりにくくなることも多い、という印象。
    • rev(だったと思う)と組み合わせて使おうとしたときに型エラーをなくすのにしばらくかかった。
  7. enumerate
    • enumerateを使って書けそうな場合でも、単に添字に対してforループを回して添字でアクセスする書き方をすることが多かったように思う。
  8. sum
    • はじめのうちsumの存在に気が付かなかったので、初めから使っていたらもっと使っていたと思われる。sumの積バージョンproductは使える機会はあったはずだけど一度も使っていなかった。
  9. max、min、max_by_key、min_by_key
    • これも途中まで存在に気付かず、使える機会に使っていなかった。
  10. その他 (よく使ったというほとではない)
    • chain、find、all、any、peekable、cycleなどは別に使わなくてもいいなと思いながら使った気がする。

3. 標準ライブラリに無いもの

多倍長整数

多倍長整数(bignum)は標準には入っていないので、大きな数を扱わないといけない問題では何とかする必要がある。

  • 多倍長整数が必要になるのは問100までのうち10問程度。(あと、64ビット整数だとオーバーフローするけど128ビット整数にするとオーバーフローしない問題が何問かあった。)
  • 多倍長整数が要る問題のほとんどは、多倍長整数に対する足し算と掛け算の組み合わせで解ける。実装の効率を気にする必要も特にない。
  • 多倍長整数の割り算まで必要な問題が1問あったけど、2で割る場合だけ実装するだけでも問題は解ける。(いくらか効率の悪い計算をすることになるけど。)
  • crates.ioにいくつか多倍長整数ライブラリが登録されているようなので、それを使った方が手っ取り早かったかも。
乱数

問100までのなかに、乱数が必要(乱数なしでも解けるけどたぶん行列計算が面倒)な問題が1問あるけど、乱数関数が標準では入っていない。

  • xorshiftは実装する手間があまりかからない。
  • crates.ioに乱数ライブラリがある。

4. テスト

Rustはコンパイル時に型のエラーをたくさん出すため、単純な間違いが早い段階で見つかる場合も多い。
一方で型エラーでは防げない単純ミスもたくさん存在する。数値や式の書き間違い、問題や仕様の捉え間違い、0番目から数えるか1番めから数えるかの間違い、「≦」と「<」との間違い、真偽の扱いを逆にする、等々。
型システムで防げるミスをたくさん入れるような人間は型システムで防げないミスもたくさん引き起こすから結局テストが必要になる。

テストは近くに書けば書くほど書く心理的ハードルが下がる

Rustではテスト用のクラスやモジュールを定義せずにテストを書くことができる。テストを別ファイルに分けることもスタイルとして強制されない。

このとき、テストしたい関数の直下や直上にテストを書くようにすると気分的に非常に気軽にテストが書ける。テストを書こうとしたときにまず別クラス別ファイル別ディレクトリを作るみたいな余分なステップが必要ない。(別モジュール、別ファイルを作るにしても必要になってから分ければいい。)

また新しく書かれたばかりの関数やプライベートな関数は、関数名や引数や動作が頻繁に変わったり関数自体がなくなったり分解されたり統合されたりすることが多いけど、そうした変更にテストコードを追従させるのも、テストコードがテストされるコードのすぐ近くの方が楽にできる。要らなくなったテストの破棄も気楽にできる。
テストのメンテナンスコストが大きいからテストを書かない、みたいなことを避けられる。

Rustではテスト中の出力はデフォルトではキャプチャされるけど--nocaptureオプションをつけると出力されるから、出力を見て動作確認する(REPLで確認したりprintを埋め込んで確認する)ようなコードをまずテスト関数内に書いてテスト実行して確認して、次にそれをテストコードの形に直すということもできる。最初からテストの形で書いた方が楽な場合も多いけど。
また失敗しているテストの詳細を出力させてコードを直すなど、テストするコード/されるコードの連携がやりやすいという印象。

プライベートな関数に対するテストとパブリックな関数に対するテスト

プライベートな関数(メソッド)に対するテストを書けない(書く手間のかかる)言語やテスティングフレームワークと異なり、Rustではプライベートに対して何の手間もなくテストが書ける。

  • プライベートな関数の方が一般的に変更が激しいからそれに合わせてテストの変更も楽におこないたいのに、多くの言語のテスティングフレームワークではプライベートに対するテストを書くのが難しかったり抜け道的な手法を取る必要があって、テストを書くコストがむやみに高くなる。
  • テストコードが同じモジュール(または内部モジュール)に置かれるので、外部モジュール(外部クラス、外部ファイル)からプライベートな要素を覗いてテストしているという気持ち悪さ(モジュールシステムの意図・制約に逆らっている)を感じなくてよい。
    • 概念的にはプライベートに対するテストは外部コードではなく内部コードの一部として見るべきなのだろう。
    • 一方、パブリックなものになっていくほど外部から見た振る舞いや引数などが固定される傾向があり、それに合わせてテストも安定化し、公開されている振る舞いや契約を確認するものになりやすい。こうしてだんだん外部コード的なテストが増えていくことになる。(もちろん内部コード的なテストと外部コード的なテストをきっちり区別できるわけではなく、パブリックな関数であっても複雑なアルゴリズムを使っていたりすると振る舞いより内部ロジックの正しさをテストしたい場合もあるだろう。)
プロジェクト・オイラーでのテスト

もちろんプログラム一般と同じようにテストすればよいけど。

プロジェクト・オイラーでは、問題の中で小さい値での答えが与えられている場合がよくある。
例えば問1は(要約すると)「10未満の時の合計は23になる。では1000未満の時は?」と聞いていて、問4は「2桁の数を使った場合は9009が最大になる。では3桁では?」と聞いている。
こういう問題に対しては、問題文で答えが提示されている値に対して正しく答えているかどうかをテストにすることができる。問1なら解答を出す関数が引数10に対して23を返すかをテストする。
こうしたテストがパスしたからといって解答が正しいことは保証されないけど、いくらかの安心は得られる。
ただしこのテストが使えるのは、一般のnに対するプログラムを書ける場合に限る。例えば問4の場合、2桁と3桁のどちらの場合も解くプログラムを書くのは面倒なので、このテストは適用しにくい。

また問題文の中で数列や式などの値が具体的に例示されていることも多い。
問2ではフィボナッチ数列の初めの部分「1,2,3,5,8,13,21,34,55,89、…」が示され、問12では三角数「1,3,6,10,15,21,28,36,45,55,…」が示されている。(こうした例示がされているのは、問題の曖昧さを避ける意図もあるのだと思う。例えば数列の初項を何にするかとか0番から数えるか1番から数えるかなどの違いで答えが違った値になってしまう。問12では三角数の第7項目は28だと書くことで曖昧さを消している。)
こうした例示されている値をそのままテストに流用できる。もちろん自分で計算してテストを作ればいいのだけど、こうしたテストの正解(期待される値)の計算で間違えることもよくある。問題文に出ている値をそのまま使う方がいくらか信頼度があがる。

簡単な数式でも思った以上に何度となく入力ミスしていた(そしてだいたい型エラーはでない)けど、こうしたテストでよくミスが見つかった。
また間違えようがないからとこうしたテストを書くのをサボると、だいたいあとでもっと手間がかかることになった。プログラムのサイズが大きくないからそんなにひどいことにはならなかったけど。

無職になる

2年前から給料未払いだったから実質無職だったのと変わらない気もするけど会社職場も消滅し正式に無職の状態になって3ヶ月ぐらいになった。貯金の残りからすると今年中にも破綻しそうだけど考えるのも面倒なのでとりあえず本を読んでいる。読んだ本のいくつか。

  • 今昔物語集 二』(新日本古典文学大系)
    • 去年手に取った本朝世俗部(新潮日本古典集成で読んだ)が意外と読み進められたので震旦部も読んでみた。いくらなんでも同じような展開の話が続きすぎるような。
  • 正史三国志英傑伝』
    • 内容以前に人名地名が覚えられず読む端から頭から落ちていく。
    • 漢文の載っている本は漢文に返り点が振ってあるものが多いけれど、徳間書店から出ている本の多くが原文、書き下し文、現代語訳の形で掲載されている。返り点なしで読めるかというとおおむね読めないのだけど。
    • 漢文法や語法について書いてある本はすでに返り点の打ち終わった文を提示していて、なぜ他の仕方ではなくそのように解析されるのかの説明がない。『漢文解釈教室』みたいな本はないものか。
  • 『プログラミングRust』
    • 「これ何だったかな」と確認しようとするとだいたい索引に載っていなかった。索引より記憶の問題のようにも思える。
  • 『英文読解講座』『越前敏弥の日本人なら必ず誤訳する英文』『英語リーディングの探究』『英文をいかに読むか』『実践 英語のセンスを磨く』『英語の読み方、味わい方』
    • 英文解釈系の本を続けて読んだけど、読めば読むほど、英語をまともに読めるようになるとは思えなくなっていく。
  • 枕草子』(新潮日本古典集成)
    • 枕草子のたくらみ』が非常に面白かったから読もうとは思っていたけどようやく。文章をおぼろげにしか読み取れない感じで読み進める。注に従来の解釈・説は間違いという指摘が多く出てくるけど、どのくらい妥当とされているのだろう。
  • 和泉式部日記』(新潮日本古典集成)
    • とりあえず文字を追っただけで内容は紗がかかったように不鮮明。和歌の贈答が入ると不鮮明さが跳ね上がる。
  • 『Murder in Mesopotamia』
    • 邦訳は読んでいて核になる部分は覚えているし中東が舞台でイギリス生活に密着した単語があまり出てこないはずから読みやすいだろうという推測で選んでいた本。実際読みやすかったけど読むにつれてクリスティーの本の中でそんなに好きでなかったことを思い出した。
  • 『The Tragedy of X』
    • クリスティーに比べると分からない単語が多すぎて辞書を引くのをサボりまくり全体的にぼんやりとしか読めていない。そのせいなのかドルリー・レーンがすごく魅力のない探偵役に思えた。「マヌケな自称名探偵」とは言わないけど。
  • 『Titan: The Fighiting Fantasy World』
    • 邦訳を読んだ時期は上の2冊と同時期のはずなのに読んでいて懐かしさを感じる。読み終わってから邦訳が再刊されていたことを知る。
  • 『短編ミステリの二百年』
    • 1をようやく読んで、出たばかりの2も続けて読む。1巻は150ページ、2巻は250ページほどの評論がついていて、そちらがメインの読書になった。海外ミステリは読んでも好みから外れることが多くてこのアンソロジーも例外ではなかったけど、評論とのセットで満足度は高かった。
  • ユリシーズIII』
    • 読み残っていた14章太陽神の牛、15章キルケをようやく読んで一応全章を読み終わった。14章の「何が書いてあるか分からない」度がかなり高い。
    • 柳瀬尚紀訳にも完結してほしかった。と思って確認したら柳瀬訳11章セイレーンが雑誌掲載されたのが2011年で時間感覚がおかしくなる。
  • 『日本探偵小説全集11 名作集1』
    • 北村薫による本の紹介や感想や評論はどれも面白いし編むアンソロジーも解説や対談は非常に面白い。のだけど選んでいる作品自体は好みでないものが多くて、多くのアンソロジーが読めていない。これも少しだけ読んでそのままにしていた。
    • それより年刊日本SF傑作選が2013年度(さよならの儀式)以降の全部を積み残しているのだった。こちらは次が出るのを楽しみにしていたはずが読み終わる前に次の巻が出て読むのが止まってしまった。
  • 平家物語』(講談社学術文庫)
    • 出たときに買って分量の多さにそのままにしていた。原文のあとに現代語訳、語釈、解説が付いていて読み進めやすい。その代わり文庫で700ページ×4冊で読んでも読んでも終わらないのはつらかった。
  • 宇治拾遺物語』(講談社学術文庫)
    • こちらは800ページ×上下巻だけど、平家物語よりサクサク読めた印象。『今昔物語集』と比べて話の扱い方や語り方の感じは好みのような気がする。
  • 古今和歌集』(新潮日本古典集成)
    • 物語や説話や日記に和歌が出てくるとだいたい何を言っているのか分からないことが多いのだけど、それと比較すると意外なほど内容を読み取れる歌が多い。物語等に出てくる和歌は状況文脈を踏まえて込められた意味を読み取らないといけないから理解の難易度が高くなっていたのかも。

数学と物理の本はつまみ読み。小説はあまり読めない。

スピンと群の表現

続きを読む

圏論における普遍性と普遍射

数学における普遍性の概念は、どういうものなのか一言で説明しにくいものだけど、圏論では普遍射を使って定義するのが普通。
と思ったら、『ベーシック圏論』は副題(訳書での追加?)が「普遍性からの速習コース」だけど、本文中に普遍射という言葉がそもそも出てこない。普遍元は出てくるけど、これも脇役的な扱いという感じがする。

続きを読む