# 13 Euler-Maclaurinの和公式

黒木玄

2018-07-04

* Copyright 2018 Gen Kuroki
* License: MIT https://opensource.org/licenses/MIT
* Repository: https://github.com/genkuroki/Calculus

このファイルは次の場所できれいに閲覧できる:

* http://nbviewer.jupyter.org/github/genkuroki/Calculus/blob/master/13%20Euler-Maclaurin%20summation%20formula.ipynb

* https://genkuroki.github.io/documents/Calculus/13%20Euler-Maclaurin%20summation%20formula.pdf

このファイルは Julia Box で利用できる.

自分のパソコンにJulia言語をインストールしたい場合には

* WindowsへのJulia言語のインストール

を参照せよ.

論理的に完璧な説明をするつもりはない. 細部のいい加減な部分は自分で訂正・修正せよ.

$
\newcommand\eps{\varepsilon}
\newcommand\ds{\displaystyle}
\newcommand\Z{{\mathbb Z}}
\newcommand\R{{\mathbb R}}
\newcommand\C{{\mathbb C}}
\newcommand\QED{\text{□}}
\newcommand\root{\sqrt}
\newcommand\bra{\langle}
\newcommand\ket{\rangle}
\newcommand\d{\partial}
\newcommand\sech{\operatorname{sech}}
\newcommand\cosec{\operatorname{cosec}}
\newcommand\sign{\operatorname{sign}}
\newcommand\real{\operatorname{Re}}
\newcommand\imag{\operatorname{Im}}
$

Table of Contents

1  Bernoulli多項式

1.1  Bernoulli多項式の定義

1.2  Bernoulli多項式の基本性質

1.3  べき乗和

1.4  Bernoulli数の計算法

1.5  周期的Bernoulli多項式のFourier級数展開

2  Euler-Maclaurinの和公式

2.1  Euler-Maclaurinの和公式の導出

2.2  Euler-Maclaurinの和公式の2つの解釈

2.3  Euler-Maclaurinの和公式の形式的導出

2.4  べき乗和(再)

2.5  Stirlingの近似公式

2.6  Poissonの和公式とEuler-Maclaurinの和公式の関係

2.7  台形公式とPoissonの和公式の関係

3  ゼータ函数への応用

3.1  解析接続

3.2  ζ(2)の近似計算

3.3  s = 1でのζ(s)の定数項がEuler定数になること

3.4  負の整数におけるゼータ函数の特殊値の計算

3.5  発散級数の有限部分と ζ(-r) の関係

```julia
using Plots
gr(); ENV["PLOTS_TEST"] = "true"
#clibrary(:colorcet)
clibrary(:misc)

function pngplot(P...; kwargs...)
    sleep(0.1)
    pngfile = tempname() * ".png"
    savefig(plot(P...; kwargs...), pngfile)
    showimg("image/png", pngfile)
end
pngplot(; kwargs...) = pngplot(plot!(; kwargs...))

showimg(mime, fn) = open(fn) do f
    base64 = base64encode(f)
    display("text/html", """""")
end

using SymPy
#sympy[:init_printing](order="lex") # default
#sympy[:init_printing](order="rev-lex")

using SpecialFunctions
using QuadGK
```

## Bernoulli多項式

### Bernoulli多項式の定義

**定義(Bernoulli多項式):** Bernoulli多項式** $B_n(x)$ ($n=0,1,2,\ldots$)を

$$
\frac{ze^{zx}}{e^z-1} = \sum_{n=0}^\infty \frac{B_n(x)}{n!}z^n
$$

によって定義する. $\QED$

### Bernoulli多項式の基本性質

**一般化Bernoulli多項式の基本性質:** Bernoulli多項式 $B_n(x)$ は以下の性質を満たしている:

(1) $B_0(x)=1$.

(2) $\ds\int_0^1 B_n(x)\,dx = \delta_{n,0}$.

(3) $\ds B_n(x+h) = \sum_{k=0}^n\binom{n}{k}B_{n-k}(x)h^k =
\sum_{k=0}^n \binom{n}{k} B_k(x) h^{n-k}$.

(4) $B_n'(x)=nB_{n-1}(x)$.

(5) $\ds B_n(x+1)=B_n(x)+nx^{n-1}$.

(6) $B_n(1-x)=(-1)^n B_n(x)$.

(7) $B_n(1)=B_n(0)+\delta_{n,1}$ となる.

(8) $B_n(0)=1$, $\ds B_n(0)=-\frac{1}{2}$ とな, $n$ が3以上の奇数ならば $B_n(0)=0$ となる.

**証明:** (1) $e^{zx}=1+O(z)$, $\ds\frac{e^z-1}{z}=1+O(z)$ より, $\ds\frac{ze^{zx}}{e^z-1}=1+O(z)$ なので $B_0(x) = 1$.

(2)を示そう.

$$
\begin{aligned}
&
\int_0^1 \frac{ze^{zx}}{e^z-1}\,dx = \frac{z}{e^z-1}\int_0^1 e^{zx}\,dx =
\frac{z}{e^z-1}\frac{e^z-1}{z} = 1,
\\ &
\int_0^1\frac{ze^{zx}}{e^z-1}\,dx = \sum_{n=0}^\infty\frac{z^n}{n!}\int_0^1 B_n(x)\,dx
\end{aligned}
$$

なので, これらを比較して $\ds\int_0^1 B_n(x)\,dx = \delta_{n,0}$.

(3) 二項定理より,

$$
\int_0^1 (x+y)^n\,dy =
\sum_{k=0}^n \binom{n}{k} x^{n-k} \int_0^1 y^k\,dy.
$$

ゆえに, $x$ の函数を $x$ の函数に移す線形写像(前方移動平均)

$$
f(x)\mapsto \int_0^1 f(x+y)\,dy
$$

は多項式を多項式に移し, 最高次の係数が1の多項式を最高次の係数が1の同次の多項式に移す. これより, 線形写像 $\ds f(x)\mapsto \int_0^1 f(x+y)\,dy$ は多項式どうしの一対一対応を与える線形写像になっていることがわかる. そして,

$$
\begin{aligned}
&
\int_0^1\frac{ze^{z(x+y)}}{e^z-1}\,dx =
\sum_{n=0}^\infty\frac{\int_0^1 B_n(x+y)\,dy}{n!}z^n,
\\ &
\int_0^1\frac{ze^{z(x+y)}}{e^z-1}\,dx =
\frac{ze^{zx}}{e^z-1}\int_0^1 e^{zy}\,dy =
\frac{ze^{zx}}{e^z-1}\frac{e^z-1}{z} =
e^{zx} =
\sum_{n=0}^\infty \frac{x^n}{n!}z^n
\end{aligned}
$$

なので, これらを比較して,

$$
\int_0^1 B_n(x+y)\,dy = x^n
$$

が成立することがわかる. ゆえに,

$$
\int_0^1 B_n(x+h+y)\,dy = (x+h)^n = \sum_{k=0}^n \binom{n}{k}x^{n-k}h^k =
\int_0^1 \sum_{k=0}^n \binom{n}{k}B_{n-k}(x+y)h^k \,dy
$$

より

$$
B_n(x+h) = \sum_{k=0}^n \binom{n}{k}B_{n-k}(x)h^k.
$$

(4) すぐ上の等式の右辺の $h$ の係数を見ることによって,

$$
B_n'(x) = n B_{n-1}(x).
$$

(5) Bernoulli多項式の母函数の $x$ に $x+1$ を代入すると,

$$
\frac{ze^{z(x+1)}}{e^z-1} = \frac{ze^z e^{zx}}{e^z-1} =
\frac{z(1+(e^z-1))e^{zx}}{e^z-1} = \frac{ze^{zx}}{e^z-1} + ze{zx}
$$

なので両辺を $z$ について展開して比較すれば(5)が得られる.

(6) Bernoulli多項式の母函数の $x$ に $1-x$ を代入すると,

$$
\frac{ze^{z(1-x)}}{e^z-1} = \frac{ze^z e^{-zx}}{e^z-1} =
\frac{ze^{-zx}}{1-e^{-z}} = \frac{-ze^{-zx}}{e^{-z}-1}
$$

とBernoulli多項式の母函数の $z$ に $-z$ を代入したものになるので, 両辺を $z$ について展開して比較すれば(5)が得られる.

(7) 上の(2)と(4)より, $n$ が2以上のとき,

$$
B_n(1)-B_n(0) = \int_0^1 B_n'(x)\,dx = n\int_0^1 B_{n-1}(x)\,dx = n\delta_{n-1,0} = \delta_{n,1}
$$

ゆえに $n$ が2以上のとき $B_n(1)=B_n(0)+\delta_{n,1}$.

(8) 次の函数が $z$ の偶函数で $z\to 0$ で $1$ になることから, (6)が得られる:

$$
\frac{z}{e^z-1} + \frac{z}{2} = \frac{z}{2}\frac{e^{z/2}+e^{-z/2}}{e^{z/2}-e^{-z/2}}.
\qquad \QED
$$

**注意:** $B_n=B_n(0)$ は**Bernoulli数**と呼ばれている. (3)で $(x,h)$ を $(0,x)$ で置き換えると, Bernoulli多項式がBernoulli数で表わされることがわかる:

$$
B_n(x) = \sum_{k=0}^n \binom{n}{k}B_k x^{n-k}.
$$

上の定理の条件(1),(2),(4)によってBernoulli多項式 $B_n(x)$ が $n$ について帰納的に一意的に決まる. $\QED$

**例:**
$$
B_0 = 1, \quad B_1 = -\frac{1}{2}, \quad
B_2 = \frac{1}{6}, \quad B_3=0, \quad B_4 = -\frac{1}{30}
$$

なので

$$
\begin{aligned}
&
B_0(x)=1, \quad
B_1(x)=x-\frac{1}{2}, \quad
B_2(x)=x^2-x+\frac{1}{6},
\\ &
B_3(x)=x^3-\frac{3}{2}x^2+\frac{1}{2}x, \quad
B_4(x)=x^4-2x^3+x^2-\frac{1}{30}.
\qquad\QED
\end{aligned}
$$

```julia
BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
x = symbols("x", real=true)
[BernoulliPolynomial(n,x) for n in 0:10]
```

\begin{bmatrix}1\\x - \frac{1}{2}\\x^{2} - x + \frac{1}{6}\\x^{3} - \frac{3 x^{2}}{2} + \frac{x}{2}\\x^{4} - 2 x^{3} + x^{2} - \frac{1}{30}\\x^{5} - \frac{5 x^{4}}{2} + \frac{5 x^{3}}{3} - \frac{x}{6}\\x^{6} - 3 x^{5} + \frac{5 x^{4}}{2} - \frac{x^{2}}{2} + \frac{1}{42}\\x^{7} - \frac{7 x^{6}}{2} + \frac{7 x^{5}}{2} - \frac{7 x^{3}}{6} + \frac{x}{6}\\x^{8} - 4 x^{7} + \frac{14 x^{6}}{3} - \frac{7 x^{4}}{3} + \frac{2 x^{2}}{3} - \frac{1}{30}\\x^{9} - \frac{9 x^{8}}{2} + 6 x^{7} - \frac{21 x^{5}}{5} + 2 x^{3} - \frac{3 x}{10}\\x^{10} - 5 x^{9} + \frac{15 x^{8}}{2} - 7 x^{6} + 5 x^{4} - \frac{3 x^{2}}{2} + \frac{5}{66}\end{bmatrix}

```julia
# (2)

BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
x = symbols("x", real=true)
[integrate(BernoulliPolynomial(n,x), (x,0,1)) for n = 0:10]'
```

    1×11 RowVector{Any,ConjArray{Any,1,Array{SymPy.Sym,1}}}:
     1  0  0  0  0  0  0  0  0  0  0

```julia
# (3)

BernoulliNumber(n) = sympy[:bernoulli](n)
BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
BinomCoeff(n,k) = sympy[:binomial_coefficients_list](n)[k+1]
x, h = symbols("x h", real=true)
[BernoulliPolynomial(n,x) == sum(k->BinomCoeff(n,k)*BernoulliNumber(k)*x^(n-k), 0:n) for n in 0:10]'
```

    1×11 RowVector{Bool,Array{Bool,1}}:
     true  true  true  true  true  true  true  true  true  true  true

```julia
# (4)

BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
x = symbols("x", real=true)
[diff(BernoulliPolynomial(n,x), x) == n*BernoulliPolynomial(n-1,x) for n = 1:10]'
```

    1×10 RowVector{Bool,Array{Bool,1}}:
     true  true  true  true  true  true  true  true  true  true

```julia
# (5)

BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
x = symbols("x", real=true)
[simplify(BernoulliPolynomial(n,x+1) - BernoulliPolynomial(n,x)) for n in 0:10]
```

\begin{bmatrix}0\\1\\2 x\\3 x^{2}\\4 x^{3}\\5 x^{4}\\6 x^{5}\\7 x^{6}\\8 x^{7}\\9 x^{8}\\10 x^{9}\end{bmatrix}

```julia
# (6)

BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
x = symbols("x", real=true)
[expand(BernoulliPolynomial(n,1-x)) == (-1)^n*BernoulliPolynomial(n,x) for n in 0:10]'
```

    1×11 RowVector{Bool,Array{Bool,1}}:
     true  true  true  true  true  true  true  true  true  true  true

```julia
# (7)

BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
x = symbols("x", real=true)
[expand(BernoulliPolynomial(n,1)) - BernoulliPolynomial(n,0) for n in 0:10]'
```

    1×11 RowVector{Any,ConjArray{Any,1,Array{SymPy.Sym,1}}}:
     0  1  0  0  0  0  0  0  0  0  0

```julia
# (8)

BernoulliNumber(n) = sympy[:bernoulli](n)
[(n, BernoulliNumber(n)) for n in 0:10]
```

    11-element Array{Tuple{Int64,SymPy.Sym},1}:
     (0, 1)
     (1, -1/2)
     (2, 1/6)
     (3, 0)
     (4, -1/30)
     (5, 0)
     (6, 1/42)
     (7, 0)
     (8, -1/30)
     (9, 0)
     (10, 5/66)

### べき乗和

$m$ は正の整数であるする. Bernoulli多項式について,

$$
B_{m+1}(x+1)-B_{m+1}(x) = (m+1)x^m,
\quad\text{i.e.}\quad
x^m = \frac{B_{m+1}(x+1)-B_{m+1}(x)}{m+1}
$$

が成立しているので, これを $x=0,1,\ldots,n$ について足し上げると,

$$
\sum_{j=1}^n j^m = \frac{B_{m+1}(n+1)-B_{m+1}}{m+1}.
\qquad \QED
$$

```julia
PowerSum(m, n) = sum(j->j^m, 1:n)
BernoulliNumber(n) = sympy[:bernoulli](n)
BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)
PowerSumFormula(m, n) = (BernoulliPolynomial(m+1,n+1)-BernoulliNumber(m+1))/(m+1)
[(m, PowerSum(m,10), PowerSumFormula(m, 10)) for m in 1:10]
```

    10-element Array{Tuple{Int64,Int64,SymPy.Sym},1}:
     (1, 55, 55)
     (2, 385, 385)
     (3, 3025, 3025)
     (4, 25333, 25333)
     (5, 220825, 220825)
     (6, 1978405, 1978405)
     (7, 18080425, 18080425)
     (8, 167731333, 167731333)
     (9, 1574304985, 1574304985)
     (10, 14914341925, 14914341925)

### Bernoulli数の計算法

Bernoulli数 $B_n$ は

$$\displaystyle
\frac{z}{e^z-1}=\sum_{n=1}^\infty B_n\frac{z^n}{n!}
$$

で定義される. しかし, この展開を直接計算することによって Bernoulli 数を求めるのは効率が悪い.

まず, 左辺の $z\to 0$ の極限を取ることによって $B_0=1$ であることはすぐにわかる.

次に, $n$ が $3$ 以上の奇数のとき $B_n=0$ となることを(再び)示そう.

$$\displaystyle
\frac{z}{e^z-1} + \frac z2
=\frac z2\frac{e^z+1}{e^z-1}
=\frac z2\frac{e^{z/2}+e^{-z/2}}{e^{z/2}-e^{-z/2}}
$$

より, 左辺は偶函数になるので, その展開の奇数次の項は消える. このことから, $B_1=-1/2$ でかつ, $0=B_3=B_5=B_7=\cdots$ であることもわかる.

$$\displaystyle
\frac{ze^z}{e^z-1}
=\sum_{j,k=0}^\infty \frac{z^j}{j!}\frac{B_k z^k}{k!}
=\sum_{n=0}^\infty\left(\sum_{k=0}^n \binom{n}{k} B_k\right)\frac{z^n}{n!}
$$

でかつ

$$\displaystyle
\frac{ze^z}{e^z-1}
=\frac{z}{e^z-1}+z
=\sum_{n=0}^\infty(B_n+\delta_{n1})\frac{z^n}{n!}
$$

なので, これらを比較すると

$$\displaystyle
\sum_{k=0}^{n-1} \binom{n}{k} B_k = \delta_{n1}.
$$

ゆえに, $n$ を $n+1$ で置き換え, $n\geqq 1$ とし, $B_n$ を他で表わす式に書き直すと

$$\displaystyle
B_n = -\frac{1}{n+1}\sum_{k=0}^{n-1}\binom{n+1}{k}B_k
\qquad (n\geqq 1).
$$

これを使えば帰納的に $B_n$ を求めることができる. $B_0=1$, $B_1=-1/2$, $0=B_3=B_5=B_7=\cdots$ であることを使うと,

$$\displaystyle
B_{2m} = -\frac{1}{2m+1}\left(
1 -\frac{2m+1}{2}
+\sum_{k=1}^{m-1}\binom{2m+1}{2k}B_{2k}
\right).
$$

**問題:** 上の方ではSymPyにおけるBernoulli数の函数を利用した. Bernoulli数を計算するためのプログラムを自分で書け. $\QED$

**解答例:** 次のセルの通り. $\QED$

```julia
# binomial coefficient: binom(n,k) = n(n-1)・(n-k+1)/k!
#
mydiv(a, b) = a / b
mydiv(a::Integer, b::Integer) = a ÷ b
function binom(n, k)
    k < 0 && return zero(n)
    k == 0 && return one(n)
    b = one(n)
    for j in 1:k
        b = mydiv(b*(n-k+j), j)
    end
    b
end

@show binom(Rational(big"100")/3, 30)

# Bernoulli numbers: B(n) = Bernoulli[n+1] = B_n
#
struct Bernoulli{T}
    B::Array{T,1}
end
function Bernoulli(; maxn=200)
    B = zeros(Rational{BigInt},maxn+1)
    B[1] = 1      # B_0
    B[2] = -1//2  # B_1
    for n in big"2":2:maxn+1
        B[n+1] = -(1//(n+1))*sum(j->binom(n+1,j)*B[j+1], 0:n-1)
        # B_n = -(1/(n+1)) Σ_{j=0}^{n-1} binom(n+1,j)*B_j
    end
    Bernoulli(B)
end
(B::Bernoulli)(n) = B.B[n+1]

maxn = 200
@time B = Bernoulli(maxn=maxn) # B_n を B_{maxn} まで計算
BB(n) = float(B(n)) # B(n) = B_n である.  BB(n)はその浮動小数点版

# SymPyのBernoulli数と比較して正しく計算できているかどうかを確認
#
BernoulliNumber(n) = sympy[:bernoulli](n)
@show B_eq_B = [B(n) == BernoulliNumber(n) for n in 0:maxn]
println()
@show all(B_eq_B)

maxnprint = 30
println()
for n in [0; 1; 2:2:maxnprint]
    println("B($n) = ", B(n))
end
println()
for n in [0; 1; 2:2:maxnprint]
    println("BB($n) = ", BB(n))
end
```

    binom(Rational(@big_str("100")) / 3, 30) = 11240781188817808072725280//984770902183611232881
      1.215169 seconds (16.07 M allocations: 363.244 MiB, 19.05% gc time)
    B_eq_B = [B(n) == BernoulliNumber(n) for n = 0:maxn] = Bool[true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]

    all(B_eq_B) = true

    B(0) = 1//1
    B(1) = -1//2
    B(2) = 1//6
    B(4) = -1//30
    B(6) = 1//42
    B(8) = -1//30
    B(10) = 5//66
    B(12) = -691//2730
    B(14) = 7//6
    B(16) = -3617//510
    B(18) = 43867//798
    B(20) = -174611//330
    B(22) = 854513//138
    B(24) = -236364091//2730
    B(26) = 8553103//6
    B(28) = -23749461029//870
    B(30) = 8615841276005//14322

    BB(0) = 1.000000000000000000000000000000000000000000000000000000000000000000000000000000
    BB(1) = -5.000000000000000000000000000000000000000000000000000000000000000000000000000000e-01
    BB(2) = 1.666666666666666666666666666666666666666666666666666666666666666666666666666674e-01
    BB(4) = -3.333333333333333333333333333333333333333333333333333333333333333333333333333359e-02
    BB(6) = 2.380952380952380952380952380952380952380952380952380952380952380952380952380947e-02
    BB(8) = -3.333333333333333333333333333333333333333333333333333333333333333333333333333359e-02
    BB(10) = 7.57575757575757575757575757575757575757575757575757575757575757575757575757578e-02
    BB(12) = -2.531135531135531135531135531135531135531135531135531135531135531135531135531131e-01
    BB(14) = 1.166666666666666666666666666666666666666666666666666666666666666666666666666661
    BB(16) = -7.092156862745098039215686274509803921568627450980392156862745098039215686274513
    BB(18) = 5.497117794486215538847117794486215538847117794486215538847117794486215538847111e+01
    BB(20) = -5.291242424242424242424242424242424242424242424242424242424242424242424242424247e+02
    BB(22) = 6.192123188405797101449275362318840579710144927536231884057971014492753623188388e+03
    BB(24) = -8.658025311355311355311355311355311355311355311355311355311355311355311355311313e+04
    BB(26) = 1.425517166666666666666666666666666666666666666666666666666666666666666666666661e+06
    BB(28) = -2.729823106781609195402298850574712643678160919540229885057471264367816091954032e+07
    BB(30) = 6.015808739006423683843038681748359167714006423683843038681748359167714006423638e+08

### 周期的Bernoulli多項式のFourier級数展開

$\widetilde{B}_k(x) = B_k(x-\lfloor x\rfloor)$ を**周期的Bernoulli多項式**と呼ぶことにする. 周期的Bernoulli多項式は $\widetilde{B}_k(x+1)=\widetilde{B}_k(x)$ を満たしている.

周期的Bernoulli多項式の母函数 $\ds\frac{z e^{z(x-\lfloor x\rfloor)}}{e^z-1}$ の $x$ の函数としての Fourier係数 $a_n(z)$ は次のように求まる:

$$
\frac{e^z-1}{z}a_n(z) = \int_0^1 e^{zx}e^{-2\pi inx}\,dx =
\left[\frac{e^{(z-2\pi in)x}}{z-2\pi in}\right]_{x=0}^{x=1} =
\frac{e^z-1}{z-2\pi in},
\qquad
a_n(z) = \frac{z}{z-2\pi in}.
$$

ゆえに $a_0(z)=1$ であり, $n\ne 0$ のとき

$$
a_n(z) = -\sum_{k=1}^\infty \frac{z^k}{(2\pi in)^k}
$$

これより, $\widetilde{B}_k(x)$ のFourier係数 $a_{k,n}$ は, $a_{0,n}=\delta_{n,0}$, $a_{k,0}=\delta_{k,0}$ を満たし, $k\ne 0$, $n\geqq 1$ のとき

$$
a_{k,n} = -\frac{k!}{(2\pi in)^k}
$$

となることがわかる. したがって, Fourier級数論より, $k=1$ のときは整数ではない実数 $x$ について, $k\geqq 2$ の場合にはすべての実数 $x$ について次が成立することがわかる:

$$
\widetilde{B}_k(x) = B_k(x-\lfloor x\rfloor) =
-k!\sum_{n\ne 0} \frac{e^{2\pi inx}}{(2\pi in)^k}.
$$

すなわち, $k=1,2,3,\ldots$ について

$$
\widetilde{B}_{2k-1}(x) =
(-1)^k 2(2k-1)!\sum_{n=1}^\infty\frac{\sin(2\pi nx)}{(2\pi n)^{2k-1}},
\qquad
\widetilde{B}_{2k}(x) =
(-1)^{k-1} 2(2k)!\sum_{n=1}^\infty \frac{\cos(2\pi nx)}{(2\pi n)^{2k}}.
$$

このことから, $k$ が大きいとき(実際には $k=5,6$ 程度ですでに), 周期的Bernoulli多項式は $n=1$ の項だけで

$$
\widetilde{B}_{2k-1}(x) \approx
(-1)^k 2(2k-1)!\frac{\sin(2\pi x)}{(2\pi)^{2k-1}},
\qquad
\widetilde{B}_{2k}(x) \approx
(-1)^{k-1} 2(2k)!\frac{\cos(2\pi x)}{(2\pi)^{2k}}
$$

と近似できることがわかる. 適当にスケールすれば周期的Bernoulli多項式は $k\to\infty$ で三角函数に収束する.

```julia
BBB = Bernoulli(Float64.(B.B)) # Float64 Bernoulli numbers
BP(k,x) = sum(j->binom(k,j)*BBB(k-j)*x^j, 0:k) # Float64 Bernoulli polynomial
PBP(k,x) = BP(k, x - floor(x)) # periodic Bernoulli polynomial

# partial sum of Fourier series of periodic Bernoulli polynomial
function PSFS(k, N, x)
    k == 0 && return zero(x)
    if isodd(k)
        return (-1)^((k+1)÷2)*2*factorial(k)*sum(n->sin(2π*n*x)/(2π*n)^k, 1:N)
    else
        return (-1)^(k÷2-1)*2*factorial(k)*sum(n->cos(2π*n*x)/(2π*n)^k, 1:N)
    end
end

PP = []
x = -1.0:0.001:0.999
for (k,N) in [(1,20), (2,10), (3,3), (4,2), (5,1), (6,1)]
    y = PBP.(k,x)
    z = PSFS.(k, N, x)
    ymin = 1.2*minimum(y)
    ymax = 2.7*maximum(y)
    P = plot(legend=:topleft, size=(400, 250), ylim=(ymin, ymax))
    plot!(x, y, label="B_$k(x-[x])")
    plot!(x, z, label="partial sum of Fourier series (N=$N)")
    push!(PP, P)
end

plot(PP[1:2]..., size=(750, 280))
```

```julia
plot(PP[3:4]..., size=(750, 280))
```

```julia
plot(PP[5:6]..., size=(750, 280))
```

## Euler-Maclaurinの和公式

### Euler-Maclaurinの和公式の導出

Bernoulli多項式 $B_n(x)$ とBernoulli数 $B_n$ について

$$
\begin{aligned}
&
B_0(x) = 1, \quad \frac{d}{dx}\frac{B_n(x)}{n!} = \frac{B_{n-1}(x)}{(n-1)!},
\\ &
B_1(0)=-\frac{1}{2}, \quad B_1(1)=\frac{1}{2},
\\ &
B_n(1)=B_n(0)=B_n \quad (n=0,2,3,4,5,\ldots)
\\ &
B_{2j+1} = 0 \quad (j=1,2,3,\ldots)
\end{aligned}
$$

が成立している. 以下ではしばらくのあいだこれらの条件しか使わない.

部分積分を繰り返すことによって,

$$
\begin{aligned}
\int_0^1 f(x)\,dx &= \int_0^1 B_0(x)f(x)\,dx
\\ &=
[B_1(x)f(x)]_0^1 - \int_0^1 B_1(x)f'(x)\,dx
\\ &=
[B_1(x)f(x)]_0^1 - \frac{1}{2}[B_2(x)f'(x)]_0^1 + \int_0^1 \frac{B_2(x)}{2}f''(x)\,dx
\\ &=
[B_1(x)f(x)]_0^1 - \frac{1}{2}[B_2(x)f'(x)]_0^1 + \frac{1}{3!}[B_3(x)f''(x)]_0^1 - \int_0^1 \frac{B_3(x)}{3!}f'''(x)\,dx
\\ &=
\cdots\cdots\cdots\cdots\cdots
\\ &=
\sum_{k=1}^n \frac{(-1)^{k-1}}{k!}\left[B_k(x)f^{(k-1)}(x)\right]_0^1 +
(-1)^n\int_0^1 \frac{B_n(x)}{n!}f^{(n)}(x)\,dx
\\ &=
\frac{f(0)+f(1)}{2} + \sum_{k=2}^n(-1)^{k-1}\frac{B_k}{k!} (f^{(k-1)}(1)-f^{(k-1)}(0)) +
(-1)^n\int_0^1 \frac{B_n(x)}{n!}f^{(n)}(x)\,dx.
\end{aligned}
$$

実数 $x$ に対して, $x$ 以下の最大の整数を $\lfloor x\rfloor$ と書く. このとき, $x-\lfloor x\rfloor$ は $x$ の「小数部分」になる. このように記号を準備しておくと, 整数 $j$ に対して,

$$
\begin{aligned}
\int_j^{j+1} f(x)\,dx &= \int_0^1 f(x+j)\,dx
\\ &=
\frac{f(j)+f(j+1)}{2} + \sum_{k=2}^n (-1)^{k-1} \frac{B_k}{k!} (f^{(k-1)}(j+1)-f^{(k-1)}(j)) +
(-1)^n\int_0^1 \frac{B_n(x)}{n!}f^{(n)}(x+j)\,dx
\\ &=
\frac{f(j)+f(j+1)}{2} + \sum_{k=2}^n (-1)^{k-1}\frac{B_k}{k!} (f^{(k-1)}(j+1)-f^{(k-1)}(j)) +
(-1)^n\int_j^{j+1} \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx.
\end{aligned}
$$

$a<b$ を満たす整数 $a,b$ に対して, 上の式を $j=a$ から $j=b-1$ まで足し上げると,

$$
\begin{aligned}
\int_a^b f(x)\,dx &=
\sum_{j=a}^{b-1}\frac{f(j)+f(j+1)}{2} +
\sum_{k=2}^n (-1)^{k-1} \frac{B_k}{k!} (f^{(k-1)}(b)-f^{(k-1)}(a)) +
(-1)^n\int_a^b \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx
\\ &=
\frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j) +
\sum_{k=2}^n (-1)^{k-1}\frac{B_k}{k!} (f^{(k-1)}(b)-f^{(k-1)}(a)) - R_n.
\end{aligned}
$$

ここで

$$
R_n = (-1)^{n-1}\int_a^b \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx
$$

とおいた. さらい, $n$ が3以上の奇数のとき $B_n=0$ となることを使うと,

$$
\int_a^b f(x)\,dx =
\frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j) -
\sum_{1\leqq i\leqq n/2} \frac{B_{2i}}{(2i)!} (f^{(2i-1)}(b)-f^{(2i-1)}(a)) - R_n.
$$

この公式を**Euler-Maclaurinの和公式**と呼ぶ.  これは $n$ が3以上の奇数のとき $B_n=0$ となることを使うと次のように書き直される:

$$
\int_a^b f(x)\,dx =
\frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j) -
\sum_{k=2}^n \frac{B_k}{k!} (f^{(k-1)}(b)-f^{(k-1)}(a)) - R_n.
$$

**注意:** 上の公式は $n$ が2以上の整数の場合の結果である. $n=1$ の場合の結果は次のようにして得られる.

$$
\int_0^1 f(x)\,dx = \int_0^1 x' f(x)\,dx =
[xf(x)]_0^1 - \int_0^1 xf'(x)\,dx =
f(1) - \int_0^1 x f'(x)\,dx
$$

より, $a<b$ を満たす整数 $a,b$ に対して,

$$
\begin{aligned}
\int_a^b f(x)\,dx &=
\sum_{j=a}^{b-1}f(j+1) -
\sum_{j=a}^{b-1}\int_0^1 x f'(x+j)\,dx
\\ &=
\sum_{j=a}^b f(j) - f(a) - \int_a^b (x-\lfloor x\rfloor)f'(x)\,dx
\end{aligned}
$$

すなわち,

$$
\begin{aligned}
\sum_{j=a}^b f(j) &=
\int_a^b f(x)\,dx + f(a) + \int_a^b (x-\lfloor x\rfloor)f'(x)\,dx
\\ &=
\int_a^b f(x)\,dx + f(a) + \sum_{j=a}^{b-1}\int_0^1 x f'(x+j)\,dx
\end{aligned}
$$

これらのシンプルな公式も結構よく使われる. $\QED$

**注意:** Euler-Maclaurinの和公式の証明は部分積分の繰り返しであった. それはTaylorの定理の証明とほぼ同じような議論である. Taylorの定理も以下のような部分積分の繰り返しで証明される:

$$
p_n(t) = \frac{(x-t)^n}{n!}
$$

とおくと, $p_0(t)=1$, $-p_n'(t) = p_{n-1}(t)$, $\ds[g(t)(-p_n(t))]_a^x = g(a)\frac{(x-a)^n}{n!}$ なので

$$
\begin{aligned}
f(x) &= f(a) + \int_a^x f(t)\,dt =
f(a) + \int_a^x f(t)p_0(t)\,dt
\\ &=
f(a) + f'(a)(x-a) + \int_a^x f'(t)p_1(t)\,dt
\\ &=
f(a) + f'(a)(x-a) + f''(a)\frac{(x-a)^2}{2} + \int_a^x f''(t)p_2(t)\,dt
\\ &=
f(a) + f'(a)(x-a) + f''(a)\frac{(x-a)^2}{2} + f'''(a)\frac{(x-a)^3}{3!} +
\int_a^x f'''(t)p_3(t)\,dt
\\ &=
\cdots\cdots\cdots\cdots\cdots
\\ &=
\sum_{k=0}^n f^{(k)}(a)\frac{(x-a)^k}{k!} + R_n.
\end{aligned}
$$

ここで

$$
R_n = \int_a^x f^{(n)} p_n(t)\,dt =
\int_a^x f^{(n)} \frac{(x-t)^n}{n!}\,dt
$$

とおいた.  この意味でEuler-Maclaurinの和公式はTaylorの公式の「仲間」だと言える. $\QED$

### Euler-Maclaurinの和公式の2つの解釈

**Euler-Maclaurinの和公式の解釈1:**

$$
\sum_{j=a}^{b-1}\frac{f(j)+f(j+1)}{2} = \frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j)
$$

は積分 $\ds\int_a^b f(x)\,dx$ の近似計算に使われる台形公式である. ゆえに, Euler-Maclaurinの和公式

$$
\begin{aligned}
&
\int_a^b f(x)\,dx =
\frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j) -
\sum_{1\leqq i\leqq n/2} \frac{B_{2i}}{(2i)!} (f^{(2i-1)}(b)-f^{(2i-1)}(a)) - R_n,
\\&
R_n = (-1)^{n-1}\int_a^b \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx
\end{aligned}
$$

は台形公式による積分の近似の誤差が,

$$
- \sum_{1\leqq i\leqq n/2} \frac{B_{2i}}{(2i)!} (f^{(2i-1)}(b)-f^{(2i-1)}(a)) - R_n
$$

になっていることを意味している. 例えば, $n=1$ の場合には, $\ds B_1(x)=x-\frac{1}{2}$ なので,

$$
\int_a^b f(x)\,dx =
\frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j) -
\int_a^b\left(x-\lfloor x\rfloor-\frac{1}{2}\right)f'(x)\,dx.
$$

$n=2$ の場合には $\ds B_2(x)=x^2-x+\frac{1}{6}$, $\ds B_2=\frac{1}{6}$ であり,

$$
\int_a^b f(x)\,dx =
\frac{f(a)+f(b)}{2} + \sum_{j=a+1}^{b-1} f(j) -
\frac{f'(b)-f'(a)}{12} +
\int_a^b\frac{B_2(x-\lfloor x\rfloor)}{2}f''(x)\,dx.
$$

となる.  $\QED$

```julia
# すぐ上の公式の検証

BernoulliNumber(n) = sympy[:bernoulli](n)
BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)

function EulerMaclaurinIntegral(f, a, b, n)
    x = symbols("x", real=true)
    (
        (f(a)+f(b))/Sym(2)
        + sum(j->f(j), a+1:b-1)
        - sum(k -> (
                BernoulliNumber(k)/factorial(Sym(k))
                * (diff(f(x), x, k-1)(x=>b) - diff(f(x), x, k-1)(x=>a))
            ), 2:n)
    )
end

function EulerMaclaurinRemainder(f, a, b, n)
    x = symbols("x", real=true)
    g = diff(f(x), x, n)
    (-1)^(n-1) * sum(k -> (
            integrate(BernoulliPolynomial(n,x)*g(x=>x+k), (x,0,1))
        ), a:b-1)/factorial(Sym(n))
end

x = symbols("x", real=true)

[integrate(x^m, (x, 0, 10)) for m in 7:15] |> display

[
    EulerMaclaurinIntegral(x->x^m, 0, 10, 5) - EulerMaclaurinRemainder(x->x^m, 0, 10, 5)
    for m in 7:15
] |> display
```

\begin{bmatrix}12500000\\\frac{1000000000}{9}\\1000000000\\\frac{100000000000}{11}\\\frac{250000000000}{3}\\\frac{10000000000000}{13}\\\frac{50000000000000}{7}\\\frac{200000000000000}{3}\\625000000000000\end{bmatrix}

\begin{bmatrix}12500000\\\frac{1000000000}{9}\\1000000000\\\frac{100000000000}{11}\\\frac{250000000000}{3}\\\frac{10000000000000}{13}\\\frac{50000000000000}{7}\\\frac{200000000000000}{3}\\625000000000000\end{bmatrix}

**Euler-Maclaurinの和公式の解釈2:** Euler-Maclaurinの和公式は次のように書き直される:

$$
\begin{aligned}
&
\sum_{j=a}^b f(j) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\sum_{1\leqq i\leqq n/2} \frac{B_{2i}}{(2i)!} (f^{(2i-1)}(b)-f^{(2i-1)}(a)) + R_n,
\\ &
R_n = (-1)^{n-1}\int_a^b \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx
\end{aligned}
$$

これは $n$ が3以上の奇数のとき $B_n=0$ となることを使うと次のように書き直される:

$$
\begin{aligned}
&
\sum_{j=a}^b f(j) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\sum_{k=2}^n \frac{B_k}{k!} (f^{(k-1)}(b)-f^{(k-1)}(a)) + R_n,
\\ &
R_n = (-1)^{n-1}\int_a^b \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx
\end{aligned}
$$

この等式は函数 $f$ の整数における値の和 $\ds\sum_{j=a}^b f(j)$ を積分 $\ds\int_a^b f(x)\,dx$ で近似したときの誤差が

$$
\frac{f(a)+f(b)}{2} +
\sum_{1\leqq i\leqq n/2} \frac{B_{2i}}{(2i)!} (f^{(2i-1)}(b)-f^{(2i-1)}(a)) + R_n
$$

になっていることを意味している. 例えば, $n=1$ の場合には, $\ds B_1(x)=x-\frac{1}{2}$ なので,

$$
\sum_{j=a}^b f(j) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\int_a^b\left(x-\lfloor x\rfloor-\frac{1}{2}\right)f'(x)\,dx.
$$

$n=2$ の場合には $\ds B_2(x)=x^2-x+\frac{1}{6}$, $\ds B_2=\frac{1}{6}$ であり,

$$
\sum_{j=a}^b f(j) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\frac{f'(b)-f'(a)}{12} -
\int_a^b\frac{B_2(x-\lfloor x\rfloor)}{2}f''(x)\,dx.
$$

となる.  $\QED$

```julia
# すぐ上の公式を検証

PowerSum(m, n) = sum(j->j^m, 1:n)
BernoulliNumber(n) = sympy[:bernoulli](n)
BernoulliPolynomial(n,x) = sympy[:bernoulli](n,x)

function EulerMaclaurinSum(f, a, b, n)
    x = symbols("x", real=true)
    (
        integrate(f(x), (x, a, b))
        + (f(a)+f(b))/Sym(2)
        + sum(k -> (
                BernoulliNumber(k)/factorial(Sym(k))
                * (diff(f(x), x, k-1)(x=>b) - diff(f(x), x, k-1)(x=>a))
            ), 2:n)
    )
end

function EulerMaclaurinRemainder(f, a, b, n)
    x = symbols("x", real=true)
    g = diff(f(x), x, n)
    (-1)^(n-1) * sum(k -> (
            integrate(BernoulliPolynomial(n,x)*g(x=>x+k), (x,0,1))
        ), a:b-1)/factorial(Sym(n))
end

[PowerSum(m, 10) for m in 1:10] |> display

[EulerMaclaurinSum(x->x^m, 1, 10, m+1) for m in 1:10] |> display

[
    EulerMaclaurinSum(x->x^m, 1, 10, m-1) + EulerMaclaurinRemainder(x->x^m, 1, 10, m-1)
    for m in 3:10
] |> display

[
    EulerMaclaurinSum(x->x^m, 1, 10, m-2) + EulerMaclaurinRemainder(x->x^m, 1, 10, m-2)
    for m in 4:10
] |> display

[
    EulerMaclaurinSum(x->x^m, 1, 10, m-3) + EulerMaclaurinRemainder(x->x^m, 1, 10, m-3)
    for m in 5:10
] |> display
```

    10-element Array{Int64,1}:
              55
             385
            3025
           25333
          220825
         1978405
        18080425
       167731333
      1574304985
     14914341925

\begin{bmatrix}55\\385\\3025\\25333\\220825\\1978405\\18080425\\167731333\\1574304985\\14914341925\end{bmatrix}

\begin{bmatrix}3025\\25333\\220825\\1978405\\18080425\\167731333\\1574304985\\14914341925\end{bmatrix}

\begin{bmatrix}25333\\220825\\1978405\\18080425\\167731333\\1574304985\\14914341925\end{bmatrix}

\begin{bmatrix}220825\\1978405\\18080425\\167731333\\1574304985\\14914341925\end{bmatrix}

### Euler-Maclaurinの和公式の形式的導出

函数 $f(x)$ に対して, ある函数 $F(x)$ で

$$
F(x+1) - F(x) = f(x+h)
$$

という条件を満たすものを求める問題を考える. そのとき, $\ds D=\frac{\d}{\d x}$ とおくと, 形式的にその条件は

$$
(e^D-1)F(x) = e^{hD}f(x) = De^{hD}\int f(x)\,dx
$$

と書き直される. これより, 形式的には

$$
F(x) = \frac{De^{hD}}{e^D-1}\int f(x)\,dx =
\sum_{k=0}^\infty \frac{B_k(h)}{k!}D^k \int f(x)\,dx =
\int f(x)\,dx + \sum_{k=1}^\infty \frac{B_k(h)}{k!}f^{(k-1)}(x).
$$

これより, 整数 $a<b$ について, 形式的には

$$
\sum_{j=a}^{b-1} f(j+h) = F(b)-F(b) =
\int_a^b f(x)\,dx + \sum_{k=1}^\infty \frac{B_k(h)}{k!}(f^{(k-1)}(b)-f^{(k-1)}(a)).
$$

これは $h=0$ とおけば形式的にEuler-Maclaurinの和公式

$$
\sum_{j=a}^{b-1} f(j) = F(b)-F(b) =
\int_a^b f(x)\,dx + \sum_{k=1}^\infty \frac{B_k}{k!}(f^{(k-1)}(b)-f^{(k-1)}(a)).
$$

に一致する.

### べき乗和(再)

$f^{(m+1)}(x)=0$ ならばEuler-Maclaurinの和公式における $R_m$ が消えて, $f^{(m)}(x)$ が定数函数になるので,

$$
\sum_{j=a}^b f(j) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\sum_{k=2}^{m+1} \frac{B_k}{k!} (f^{(k-1)}(b)-f^{(k-1)}(a))
$$

となる. $m$ を正の整数であるとし, $f(x)=x^m$, $a=0$, $b=n$ とすると,

$$
(x^m)^{(k-1)} = m(m-1)\cdots(m-k+2)x^{m-k+1} = \frac{k!}{m+1}\binom{m+1}{k}x^{m+1-k}
$$

なので, $B_0(1)=B_0=1$, $\ds B_1(1)=B_1+1=\frac{1}{2}$, $B_n(1)=B_n+\delta_{n,1}$ も使うと,

$$
\begin{aligned}
\sum_{j=1}^n j^m &=
\int_0^n x^m\,dx + \frac{n^m}{2} +
\sum_{k=2}^m \frac{B_k}{k!}\frac{k!}{m+1}\binom{m+1}{k}x^{m+1-k}
\\ &=
\frac{n^{m+1}}{m+1} + \frac{n^m}{2} +
\frac{1}{m+1}\sum_{k=2}^m \binom{m+1}{k} B_k n^{m+1-k}
\\ &=
\frac{1}{m+1}\sum_{k=0}^m \binom{m+1}{k} B_k(1) n^{m+1-k}
\\ &=
\frac{B_{m+1}(x)-B_{m+1}}{m+1}.
\end{aligned}
$$

最後の等号で

$$
B_{m+1}(x+h) = \sum_{k=0}^{m+1} \binom{m+1}{k}B_k(h)x^{m+1-k}
$$

を使った.  以上の結果は前節で得た結果と一致する. $\QED$

### Stirlingの近似公式

$\log n!=\log 1+\log 2+\cdots\log n$ の $\log x$ の積分による近似をEuler-Maclaurinの和公式を使って精密化すると, Stirlingの近似公式

$$
\log n! = n\log n - n + \frac{1}{2}\log n + \log\sqrt{2\pi} + o(1)
$$

の精密化が得られることを説明しよう.

$f(x)=\log x$ にStirlingの和公式を適用すると, $k=1,2,3,\ldots$ に対して,

$$
f^{(k)}(x) = \frac{(-1)^{k-1}(k-1)!}{x^k}.
$$

$K$ は3以上の奇数とする. 3以上の奇数 $k$ について $B_k=0$ なので, $N>n$ のとき,

$$
\begin{aligned}
\log n! &= \log N! + \log n - \sum_{j=n}^N \log j
\\ &= \log N! + \log n -\left(
\int_n^N \log x\,dx + \frac{\log n+\log N}{2} +
\sum_{k=2}^{K-1}\frac{B_k}{k(k-1)} \left(\frac{1}{N^{k-1}} - \frac{1}{n^{k-1}}\right) +
R_{K,N}
\right)
\\ &=
\log N! - \left(N\log N - N + \frac{1}{2}\log N\right) -
\sum_{k=2}^{K-1} \frac{B_k}{k(k-1)} \frac{1}{N^{k-1}}
\\ &\,+
n\log n - n +\frac{1}{2}\log n +
\sum_{k=2}^{K-1}\frac{B_k}{k(k-1)} \frac{1}{n^{k-1}} + R_{K,N},
\\
R_{K,N} &= (-1)^{K-1}\int_n^N \frac{\tilde{B}_K(x)}{K}\frac{(-1)^{K-1}}{x^K}\,dx
\end{aligned}
$$

ただし, $\tilde{B}_n(x)=B_n(\lfloor x\rfloor)$ とおいた.

ここでは, $N\to\infty$ のとき

$$
\log N! - \left(N\log N - N + \frac{1}{2}\log N\right) \to \sqrt{2\pi}
$$

となることは既知であるものとする. 例えば, ノート「10 Gauss積分, ガンマ函数, ベータ函数」「12 Fourier解析」のStirlingの近似公式の節を参照して欲しい.  以下ではそれらのノートよりも精密な結果を得る.

このとき, 上の結果で $N\to\infty$ とすると,

$$
\begin{aligned}
&
\log n! =
n\log n - n +\frac{1}{2}\log n + \log\sqrt{2\pi} +
\sum_{k=2}^{K-1}\frac{B_k}{k(k-1)} \frac{1}{n^{k-1}} + R_K,
\\ &
R_K = (-1)^{K-1}\int_n^\infty \frac{\tilde{B}_K(x)}{K}\frac{(-1)^{K-1}}{x^K}\,dx =
O\left(\frac{1}{n^{K-1}}\right).
\end{aligned}
$$

$K=2L+1$ とおくことによって次が得られる: 正の整数 $L$ に対して,

$$
\log n! =
n\log n - n + \frac{1}{2}\log n + \log\sqrt{2\pi} +
\sum_{l=1}^L \frac{B_{2l}}{(2l)(2l-1)}\frac{1}{n^{2l-1}} + O\left(\frac{1}{n^{2L}}\right).
$$

これが求めていた結果である.

例えば, $L=2$ のとき, $\ds B_2=\frac{1}{6}$, $\ds B_4=-\frac{1}{30}$ なので,

$$
\log n! =
n\log n - n + \frac{1}{2}\log n + \log\sqrt{2\pi} +
\frac{1}{12n} - \frac{1}{360n^3} + O\left(\frac{1}{n^4}\right).
$$

これより,

$$
n! = n^n e^{-n}\sqrt{2\pi n}
\left(1+\frac{1}{12n} + \frac{1}{288n^2} - \frac{139}{51840n^3} +  O\left(\frac{1}{n^4}\right)\right).
$$

```julia
x = symbols("x")
series(exp(x/12-x^3/360), x, n=4)
```

$$1 + \frac{x}{12} + \frac{x^{2}}{288} - \frac{139 x^{3}}{51840} + \mathcal{O}\left(x^{4}\right)$$

### Poissonの和公式とEuler-Maclaurinの和公式の関係

Poissonの和公式とは, 急減少函数 $f(x)$ に対して,

$$
\sum_{m\in\Z} f(m) = \sum_{n\in\Z} \hat{f}(n), \qquad
\hat{f}(p) = \int_\R f(x)e^{2\pi i px}\,dx
$$

が成立するという結果であった. これの右辺は以下のように変形できる:

$$
\begin{aligned}
\sum_{n\in\Z} \hat{f}(n) &=
\sum_{n\in\Z} \int_\R f(x)e^{2\pi i nx}\,dx =
\int_\R f(x)\,dx + 2\sum_{n=1}^\infty\int_\R f(x)\cos(2\pi nx)\,dx
\\ &=
\int_\R f(x)\,dx - \sum_{n=1}^\infty\int_\R f'(x)\frac{\sin(2\pi nx)}{\pi n}\,dx
\\ &=
\int_\R f(x)\,dx + \int_\R \left(-\sum_{n=1}^\infty\frac{\sin(2\pi nx)}{\pi n}\right)f'(x)\,dx
\end{aligned}
$$

2つ目の等号では $e^{2\pi inx}+e^{-2\pi inx}=2\cos(2\pi nx)$ を用い, 3つ目の等号では部分積分を実行し, 4つ目の等号では無限和と積分の順序を交換した. それらの操作は $f(x)$ が急減少函数であれば容易に正当化される.

一方, Euler-Maclaurinの和公式の

$$
B_1(x-\lfloor x\rfloor) = x - \lfloor x\rfloor - \frac{1}{2}
$$

を使う場合から,

$$
\sum_{m\in\Z} f(m) =
\int_\R f(x)\,dx + \int_\R \left(x - \lfloor x\rfloor - \frac{1}{2}\right) f'(x)\,dx
$$

が導かれる. これは部分積分によって得られる次の公式からただちに導かれる易しい公式であることにも注意せよ:

$$
\begin{aligned}
\int_n^{n+1} \left(x - n - \frac{1}{2}\right) f'(x)\,dx &=
\left[\left(x - n - \frac{1}{2}\right)f(x)\right]_n^{n+1} - \int_n^{n+1}f(x)\,dx - n
\\ &=
\frac{f(n+1)-f(n)}{2} - \int_n^{n+1}f(x)\,dx.
\end{aligned}
$$

以上の2つの結果を比較すると, Poissonの和公式とEuler-Maclaurinの和公式の $B_1(x-\lfloor x\rfloor)$ を使った場合は,

$$
x - \lfloor x\rfloor - \frac{1}{2} =
-\sum_{n=1}^\infty\frac{\sin(2\pi nx)}{\pi n}
\tag{$*$}
$$

という公式で結び付いていることがわかる. この公式を認めれば, Euler-Maclaurinの和公式の $B_1(x-\lfloor x\rfloor)$ を使った場合からPoissonの和公式が導かれる.

公式($*$)の左辺はいわゆる**のこぎり波**であり, 右辺はそのFourier級数である. 公式($*$)はFourier級数論における非常に有名な公式であり, 本質的にそれと同じ公式はFourier級数論について書かれた文献には例として必ず載っていると言ってよいくらいである. (Fourier級数論より, 公式($*$)は $x$ が整数でないときには実際に成立していることがわかる.)

このように, のこぎり波のFourier級数展開という非常に特殊な公式はPoissonの和公式という一般的な公式を導くだけの力を持っているのである.

**まとめ:** のこぎり波のFourier級数展開は部分積分を通してPoissonの和公式と本質的に同値である! $\QED$

この節で解説したことは次の文献で指摘されている:

* Tim Jameson, An elementary derivation of the Poisson summation formula

```julia
B_1(x) = x - 1/2
b(x) = B_1(x - floor(x))
S(N,x) = -sum(n->sin(2π*n*x)/(π*n), 1:N)
x = -2:0.001:1.999
N = 10
plot(size=(400,200), ylim=(-0.6,1.2), legend=:top)
plot!(x, b.(x), label="B_1(x-[x]) = x - [x] -1/2")
plot!(x, S.(N,x), label="partial sum of Fourier series (N=$N)")
```

**補足:** このノートの上の方の周期的Bernoulli多項式 $B_k(x-\lfloor x\rfloor)$ のFourier級数展開の節を見ればわかるように,

$$
\sum_{n=1}^\infty \frac{\cos(2\pi nx)}{n^k}, \quad
\sum_{n=1}^\infty \frac{\sin(2\pi nx)}{n^k}
$$

の型のFourier級数の収束先は平行移動と定数倍の違いを除いて周期的Bernoulli多項式になる. $\QED$

### 台形公式とPoissonの和公式の関係

簡単のため $f(x)$ は $\R$ 上の急減少函数であるとし, $a,b\in\Z$ かつ $a<b$ であると仮定する. このとき,

$$
\left(\frac{\sin(2\pi nx)}{\pi n}\right)' = 2\cos(2\pi nx)
$$

を使った部分積分とノコギリ波のFourier級数展開

$$
-\sum_{n=1}^\infty \frac{\sin(2\pi nx)}{\pi n} = x-\lfloor x\rfloor - \frac{1}{2} =B_1(x-\lfloor x\rfloor)
$$

より,

$$
\begin{aligned}
\sum_{n\in\Z}\int_a^b f(x) e^{-2\pi inx} \,dx &=
\int_a^b f(x)\,dx + \sum_{n=1}^\infty 2\int_a^b f(x)\cos(2\pi nx)\,dx
\\ &=
\int_a^b f(x)\,dx - \sum_{n=1}^\infty \int_a^b f'(x)\frac{\sin(2\pi nx)}{\pi n}\,dx
\\ &=
\int_a^b f(x)\,dx + \int_a^b f'(x) \left(x-\lfloor x\rfloor - \frac{1}{2}\right)\,dx
\\ &=
\int_a^b f(x)\,dx + \sum_{m=a}^{b-1} \int_m^{m+1} f'(x) \left(x-\lfloor x\rfloor - \frac{1}{2}\right)\,dx
\\ &=
\int_a^b f(x)\,dx + \sum_{m=a}^{b-1} \int_0^1 f'(t+m) \left(t - \frac{1}{2}\right)\,dt
\\ &=
\int_a^b f(x)\,dx +
\sum_{m=a}^{b-1}\left(\left[f(t+m)\left(t - \frac{1}{2}\right)\right]_0^1 - \int_0^1 f(t+m)\,dt \right)
\\ &=
\sum_{m=a}^{b-1}\frac{f(m)+f(m+1)}{2}
\\ &=
\sum_{m=a}^b f(m) - \frac{f(a)+f(b)}{2}.
\end{aligned}
$$

要するに, ノコギリ波 $B_1(x-\lfloor x\rfloor)$ のFourier級数展開の公式を認めて使えば,

$$
\sum_{n\in\Z}\int_a^b f(x) e^{-2\pi inx} \,dx =
\sum_{m=a}^{b-1}\frac{f(m)+f(m+1)}{2} =
\sum_{m=a}^b f(m) - \frac{f(a)+f(b)}{2}
$$

という公式が得られる. この公式の右辺は積分の近似計算で使われる台形公式に一致しており, 途中の計算では $B_1(x-\lfloor x\rfloor)$ を使う場合の Euler-Maclaurinの公式を使っているとみなされる. (上の計算ではEuler-Maclaurinの公式を使わずに直接的に計算した.) Euler-Maclaurinの公式が積分を台形公式で近似するときの誤差項を与えていたことも思い出そう. さらにこの公式で $a\to-\infty$, $b\to\infty$ の極限を取れば, Poissonの和公式

$$
\sum_{n\in\Z}\int_{-\infty}^\infty f(x) e^{-2\pi inx} \,dx = \sum_{m=-\infty}^\infty f(m)
$$

が得られる($f(x)$ は急減少函数だと仮定してあった).

以上のように, **台形公式, Euler-Maclaurinの公式, Poissonの和公式は同一の数学的現象を異なる視点から眺めたものに過ぎないことがわかる!**

積分の簡易的な近似計算で使われる台形公式は, 純粋数学的に価値の高いEuler-Maclaurinの公式とPoissonの公式に直接的に繋がる公式だったのである. 台形公式の誤差を部分積分の繰り返しによって評価すればEuler-Maclaurinの和公式が得られ, 台形公式をFourier解析の立場から見直せばPoissonの和公式が得られる.

## ゼータ函数への応用

$s>1$ のとき(より一般には $\real s>1$ のとき),

$$
\zeta(s) = \sum_{n=1}^\infty \frac{1}{n^s}
$$

は絶対収束しているのであった. これにEuler-Maclaurinの和公式

$$
\begin{aligned}
&
\sum_{j=a}^b f(j) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\sum_{k=2}^n \frac{B_k}{k!} (f^{(k-1)}(b)-f^{(k-1)}(a)) + R_n,
\\ &
R_n = (-1)^{n-1}\int_a^b \frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x)\,dx
\end{aligned}
$$

を適用してみよう.

### 解析接続

$\real s > 1$ であるとし, $f(x)=x^{-s}$ とおく. このとき,

$$
\begin{aligned}
&
\int_a^\infty f(x)\,dx = \int_1^\infty x^{-s}\,dx =
\left[\frac{x^{-s+1}}{-s+1}\right]_1^\infty = \frac{a^{-(s-1)}}{s-1}, \qquad
f(b)=b^{-s}\to 0 \quad(b\to\infty).
\\ &
\frac{B_k}{k!}f^{(k-1)}(x) =
\frac{B_k}{k}\binom{-s}{k-1} x^{-s-k+1}, \quad
\frac{B_n(x-\lfloor x\rfloor)}{n!}f^{(n)}(x) =
\binom{-s}{n}B_n(x-\lfloor x\rfloor)x^{-s-n}
\end{aligned}
$$

なので, 2以上の整数 $n$ について,

$$
\begin{aligned}
&
\zeta(s) = \frac{1}{s-1} + \frac{1}{2} -
\sum_{k=2}^n \frac{B_k}{k}\binom{-s}{k-1} + R_n,
\\ &
R_n = (-1)^{n-1}\binom{-s}{n}\int_1^\infty B_n(x-\lfloor x\rfloor)x^{-s-n}\,dx.
\end{aligned}
$$

積分 $R_n$ は $\real s+n>1$ ならば絶対収束している.  ゆえに, 複素平面全体に $\zeta(s)$ を自然に拡張する方法(解析接続する方法)が得られた.

$\ds \sum_{k=1}^\infty \frac{1}{n^s}$ そのものではなく, $n=a$ から始まる無限和 $\ds \sum_{k=a}^\infty \frac{1}{n^s}=\zeta(s)-\sum_{n=1}^{a-1}\frac{1}{n^s}$ にEuler-Maclaurinの和公式を適用すると,

$$
\begin{aligned}
&
\zeta(s) = \sum_{n=1}^{a-1} \frac{1}{n^s} - \frac{a^{1-s}}{1-s} +
\frac{1}{2a^s} - \sum_{k=2}^n \frac{B_k}{k a^{s+k-1}}\binom{-s}{k-1} + R_{n,a},
\\ &
R_{n,a} = (-1)^{n-1}\binom{-s}{n}\int_a^\infty B_n(x-\lfloor x\rfloor)x^{-s-n}\,dx.
\end{aligned}
$$

```julia
# 上の公式における ζ(s) - R_{n,a} の函数化

# ζ(s) - R_{n,a} = Σ_{m=1}^{a-1} m^{-s} - a^{1-s}/(1-s) + 1/(2a^s)
#                - Σ_{k=2}^n B_k/(k a^{s+k-1}) binom(-s,k-1)    (k is even)
#
function ApproxZeta(a, n, s)
    ss = float(big(s))
    z = zero(ss)
    z += (a ≤ 1 ? zero(ss) : sum(m->m^(-ss), 1:a-1)) # Σ_{m=1}^{a-1} m^{-s}
    z += -a^(1-ss)/(1-ss)                            # -a^{1-s}/(1-s)
    n == 0 && return z
    z += 1/(2*a^ss)                                  # 1/(2a^s)
    n == 1 && return z
    z -= sum(k -> BB(k)/(k*a^(ss+k-1))*binom(-ss,k-1), 2:2:n)
            #   -Σ_{k=2}^n B_k/(k a^{s+k-1}) binom(-s,k-1)    (k is even)
end

A = ApproxZeta(40, 80, big"0.5")
Z = zeta(big"0.5")
@show A
@show Z;
```

    A = -1.460354508809586812889499152515298012467229331012581490542886087825530529474572
    Z = -1.460354508809586812889499152515298012467229331012581490542886087825530529474503

$\real s > 0$ のとき,

$$
\frac{1}{2a^s} - \sum_{k=2}^n \frac{B_k}{k a^{s+k-1}}\binom{-s}{k-1} + R_{n,a}
$$

は $a\to\infty$ で $0$ に収束するので,

$$
\zeta(s) = \lim_{a\to\infty}\left(\sum_{n=1}^{a-1} \frac{1}{n^s} - \frac{a^{1-s}}{1-s}\right)
\quad (\real s > 0)
$$

が成立することがわかる. これは, Dirichlet級数の部分和 $\ds\sum_{n=1}^{a-1}\frac{1}{n^s}$ から補正項

$$
\frac{a^{1-s}}{1-s}
$$

を引き去ってから, Dirichlet級数の総和を取れば, $0 < \real s < 1$ でも収束して, $\zeta(s)$ の正確な値が得られることを意味している.

```julia
# 上の結果のプロット

ApproxZeta0(a, s) = sum(n->n^(-s), 1:a-1) - a^(1-s)/(1-s)
a = 100
s = 0.05:0.01:0.95
@time z = zeta.(s)
@time w = ApproxZeta0.(a, s)
plot(size=(400, 250), legend=:bottomleft, xlabel="s")
plot!(s, z, label="zeta(s)", lw=2)
plot!(s, w, label="Euler-Maclaurin sum for n=0, a=$a", lw=2, ls=:dash)
```

      0.057854 seconds (29.42 k allocations: 1.510 MiB)
      0.049310 seconds (19.07 k allocations: 1007.852 KiB)

```julia
# さらに項の数を1つ増やした場合のプロット

# ζ(s) - R_{1,a} = Σ_{n=1}^{a-1} n^{-s}  - a^{1-s}/(1-s) + 1/(2a^s)
#
ApproxZeta1(a, s) = sum(n->n^(-s), 1:a-1) - a^(1-s)/(1-s) + 1/(2*a^s)

s = -0.95:0.01:0.5
a = 10^3
@time z = zeta.(s)
@time w = ApproxZeta1.(a,s)
plot(size=(400, 250), legend=:bottomleft, xlabel="s")
plot!(s, z, label="zeta(s)", lw=2)
plot!(s, w, label="Euler-Maclaurin sum for n=1, a=$a", lw=2, ls=:dash)
```

      0.000181 seconds (29 allocations: 2.406 KiB)
      0.058011 seconds (19.04 k allocations: 1005.415 KiB)

```julia
# さらに一般の場合のプロット
#
# Euler-Maclaurinの和公式で ζ(s) の負の s での値をぴったり近似できていることがわかる.

[(-m, zeta(-m), Float64(ApproxZeta(2, 17, -m))) for m = 0:12] |> display

n = 10
s = -1.5:0.05:0.5
a = 10
@time z = zeta.(s)
@time w = ApproxZeta.(a, n, s)
P1 = plot(size=(400, 250), legend=:bottomleft, xlabel="s")
plot!(s, z, label="zeta(s)", lw=2)
plot!(s, w, label="Euler-Maclaurin sum for a=$a, n=$n", lw=2, ls=:dash)

n = 17
s = -16:0.05:-2.0
a = 2
@time z = zeta.(s)
@time w = ApproxZeta.(a, n, s)
P2 = plot(size=(400, 250), legend=:topright, xlabel="s")
plot!(s, z, label="zeta(s)", lw=2)
plot!(s, w, label="Euler-Maclaurin sum for a=$a, n=$n", lw=2, ls=:dash)
```

    13-element Array{Tuple{Int64,Float64,Float64},1}:
     (0, -0.5, -0.5)
     (-1, -0.0833333, -0.0833333)
     (-2, -0.0, -1.29543e-77)
     (-3, 0.00833333, 0.00833333)
     (-4, -0.0, -3.45447e-77)
     (-5, -0.00396825, -0.00396825)
     (-6, -0.0, 0.0)
     (-7, 0.00416667, 0.00416667)
     (-8, -0.0, 0.0)
     (-9, -0.00757576, -0.00757576)
     (-10, -0.0, -4.42172e-75)
     (-11, 0.0210928, 0.0210928)
     (-12, -0.0, 0.0)

      0.000099 seconds (29 allocations: 1.625 KiB)
      0.181011 seconds (122.74 k allocations: 5.447 MiB)
      0.000319 seconds (29 allocations: 3.563 KiB)
      0.071032 seconds (565.80 k allocations: 20.997 MiB, 18.17% gc time)

```julia
display(P1)
```

上と下のグラフを見ればわかるように, Euler-Maclaurinの和公式によって負の実数での $\zeta$ 函数の値を非常によく近似できている. 実は $\zeta(s)$ を実部が負の複素数まで拡張してもこの近似はうまく行っている.

```julia
display(P2)
```

### ζ(2)の近似計算

$\ds\zeta(2)=\sum_{n=1}^\infty \frac{1}{n^2}$ を計算せよという問題は**Basel問題**と呼ばれているらしい. Basel問題はEulerによって1743年ころに解かれたらしい. Eulerがどのように考えたかについては次の文献を参照せよ.

* 杉本敏夫, バーゼル問題とオイラー, 2007年8月23日, 数理解析研究所講究録, 第1583巻, 2008年, pp.159-167

Eulerは $\zeta(2)$ の近似値を自ら開発したEuler-Maclaurinの和公式を使って精密に計算したらしい.

近似式

$$
\zeta(s) \approx
\sum_{n=1}^{a-1} \frac{1}{n^s} - \frac{a^{1-s}}{1-s} +
\frac{1}{2a^s} - \sum_{k=2}^n \frac{B_k}{k a^{s+k-1}}\binom{-s}{k-1}
$$

を用いて, $\zeta(2)$ を計算してみよう. 3以上の奇数 $n$ について $B_n=0$ となるので, $n=2m$ のとき, 右辺の項数は $a+m+1$ になる.

例えば, $a=10$, $m=9$ とし, 20項の和を取ると,

$$
\zeta(2) \approx 1.64493\;40668\;4749\cdots
$$

となり, 正確な値 $\ds\frac{\pi^2}{6}=1.64493\;40668\;4822\cdots$ と小数点以下第11桁まで一致している.

Eulerは後に $\ds\zeta(2)=\frac{\pi^2}{6}$ を得る. Eulerは競争相手に議論に厳密性に欠けるとして様々な批判を受けたのだが, 以上のような数値計算の結果を知っていたので, 正解を得たという確信は微塵も揺らがなかっただろうと思われる.

**注意:** 論理的に厳密な証明の方法が発達した現代においても, 人間は常に証明を間違う可能性がある. 人間が行った証明は絶対的には信用できない. だから, たとえ証明が完成したと思っていたとしても, 可能ならば数値計算によって論理的に厳密な証明以外の証拠を作っていた方が安全だと思われる. $\QED$

**注意:** 数学のノートを作りながら, 気軽に数値的証拠も同時に得るための道具として, 筆者がこのノート作成のために用いているJulia言語とJupyterとNbextensionsのLive Markdown Previewはこれを書いている時点で相当に優秀な道具であるように思われる. $\QED$

```julia
# 20項の和

N = 20
[(m, N-m-1, 2m, ApproxZeta(N-m-1, 2m, 2) - big(π)^2/6) for m in 2:N÷2-1] |> display

m = 9
a = N-m-1
Z = big(π)^2/6
A = ApproxZeta(a, m, 2)
@show a,m
@show Z
@show A;
```

    8-element Array{Tuple{Int64,Int64,Int64,BigFloat},1}:
     (2, 17, 4, -5.77451793863474833797788940478358503699585407578399357681001619323664145261758e-11)
     (3, 16, 6, 4.808127352395625095013460112150325878389866054958153137408430487774776509324829e-13)
     (4, 15, 8, -8.630887513943044224615236465465206970650911046136527708026292186723865816966492e-15)
     (5, 14, 10, 3.116217978527385328054235573023871173466586797186396436897662720414552852297451e-16)
     (6, 13, 12, -2.200847274100542514575619216657515798396053843860275532661691594239630209744406e-17)
     (7, 12, 14, 3.035248943857815147777677383711316694019656935103319432181355248871820406062584e-18)
     (8, 11, 16, -8.335321043122531064769674746337938450627967961329547742403411230422546148897753e-19)
     (9, 10, 18, 4.746601814392005312714027578027970306539540935051342164737224161514796063021067e-19)

    (a, m) = (10, 9)
    Z = 1.644934066848226436472415166646025189218949901206798437735558229370007470403185
    A = 1.644934066847493071302595112118921642731166540690350214159737969261778785588307

###  s = 1でのζ(s)の定数項がEuler定数になること

$\zeta(s)=\ds\sum_{n=1}^\infty \frac{1}{n^s}$ にEuler-Maclaurinの和公式を使って, 2以上の $n$ について次の公式が得られるのであった:

$$
\begin{aligned}
&
\zeta(s) = \frac{1}{s-1} + \frac{1}{2} -
\sum_{k=2}^n \frac{B_k}{k}\binom{-s}{k-1} + R_n,
\\ &
R_n = (-1)^{n-1}\binom{-s}{n}\int_1^\infty B_n(x-\lfloor x\rfloor)x^{-s-n}\,dx.
\end{aligned}
$$

$n=1$ の場合には

\begin{aligned}
\sum_{j=a}^b f(j) &=
\int_a^b f(x)\,dx + f(a) + \int_a^b (x-\lfloor x\rfloor)f'(x)\,dx
\\ &=
\int_a^b f(x)\,dx + f(a) + \sum_{j=a}^{b-1}\int_0^1 x f'(x+j)\,dx
\end{aligned}

を $f(x)=x^{-s}$, $f'(x)=-sx^{-s-1}$, $a=1$, $b=\infty$ の場合に適用して,

$$
\zeta(s) =
\frac{1}{s-1} + 1 - s\sum_{j=1}^\infty\int_0^1 \frac{x}{(x+j)^{s+1}}\,dx
$$

を得る. したがって,

$$
\lim_{s\to 1}\left(\zeta(s)-\frac{1}{s-1}\right) =
1 - \sum_{j=1}^\infty\int_0^1 \frac{x}{(x+j)^2}\,dx.
$$

そして, $x=t-j$ と置換すると,

$$
\begin{align}
-\int_0^1\frac{x}{(x+j)^2}\,dx &=
-\int_j^{j+1}\frac{-(t-j)}{t^2}\,dt =
-\left[\log t + \frac{j}{t}\right]_j^{j+1}
\\ &=
-\log(j+1)+\log j -\frac{j}{j+1}+1 =
\frac{1}{j+1} + \log j - \log(j+1)
\end{align}
$$

なので, これを $j=1$ から $j=N-1$ まで足し上げることによって,

$$
1 - \sum_{j=1}^{n-1}\int_0^1\frac{x}{(x+j)^2}\,dx =
\sum_{j=1}^N\frac{1}{j} - \log N.
$$

これの $N\to\infty$ での極限はEuler定数 $\gamma=0.5772\cdots$ の定義であった. 以上によって次が示された:

$$
\lim_{s\to 1}\left(\zeta(s)-\frac{1}{s-1}\right) = \gamma = 0.5772\cdots.
$$

### 負の整数におけるゼータ函数の特殊値の計算

Euler-Maclaurinの和公式: $3$ 以上の整数 $k$ について $B_k=0$ なので, 以下の公式で $k$ は偶数のみを動くとしてよい:

$$
\begin{aligned}
&
\sum_{n=a}^b f(n) =
\int_a^b f(x)\,dx + \frac{f(a)+f(b)}{2} +
\sum_{k=2}^m \frac{B_k}{k!}(f^{(k-1)}(b) - f^{(k-1)}(a)) + R_m,
\\ &
R_n = (-1)^{m-1}\int_a^b \frac{\tilde{B}_m(x)}{m!} f^{(m)}(x)\,dx.
\end{aligned}
$$

ここで $\tilde{B}_m(x)=B_m(x-\lfloor x\rfloor)$ とおいた.

Euler-Maclaurinの和公式を $f(x)=n^{-s}$, $a=1$, $b=\infty$ の場合に適用することによって $\zeta(s)$ は次の形で $\Re s > 1-m$ まで自然に延長(解析接続)されるのであった:

$$
\zeta(s) =
\frac{1}{s-1} + \frac{1}{2} -
\frac{1}{1-s}\sum_{k=2}^m \binom{1-s}{k} B_k +
(-1)^{m-1}\int_a^b \binom{-s}{m} \tilde{B}_m(x) x^{-s-m}\,dx.
$$

この公式と $k\geqq 2$ のとき $\ds\binom{1}{k}=0$ となることより,

$$
\zeta(0) = \frac{1}{0-1} + \frac{1}{2} = -\frac{1}{2}.
$$

$r$ は正の整数であるとする. このとき, $m>r$ とすると $\ds\binom{r}{m}=0$ となるので, $B_0=1$, $B_1=-1/2$ なので,

$$
\begin{aligned}
\zeta(-r) &=
-\frac{1}{r+1} + \frac{1}{2} -
\frac{1}{r+1}\sum_{k=2}^{r+1} \binom{m+1}{k} B_k
\\ =&
-\frac{1}{r+1}\sum_{k=0}^{r+1} \binom{m+1}{k} B_k =
-\frac{B_{r+1}}{r+1}.
\end{aligned}
$$

最後の等号で, Bernoulli数を帰納的に計算するために使える公式 $\ds\sum_{k=0}^r \binom{r+1}{k}B_k=0$ を用いた. 例えば, $r=1$ のとき $B_0+2B_1=1+2(-1/2)=0$ となり, $r=2$ のとき, $B_0+3B_1+3B_2=1+3(-1/2)+3(1/6)=0$ となる.

以上によって次が証明された:

$$
\zeta(0)=-\frac{1}{2}, \quad
\zeta(-r) = -\frac{B_{r+1}}{r+1} \quad (r=1,2,3,\ldots).
$$

これらの公式は $B_n(1)=B_n+\delta_{n,1}$, $B_1=-1/2$ を使うと,

$$
\zeta(-r) = -\frac{B_{r+1}(1)}{r+1} \quad (r=0,1,2,\ldots)
$$

の形にまとめられる.

### 発散級数の有限部分と ζ(-r) の関係

前節の結果 $\ds\zeta(-r)=-\frac{B_{r+1}(1)}{r+1}$ ($r=0,1,2,\ldots$) は

$$
\begin{aligned}
&
1+1+1+1+\cdots = -\frac{1}{2},
\\ &
1+2+3+4+\cdots = -\frac{1}{12}
\end{aligned}
$$

のような印象的な形式で書かれることもある. ただし, その場合には左辺が通常の無限和ではなく, ゼータ函数 $\zeta(s)$ の解析接続の意味であることを了解しておかなければいけない.

実はさらに解析接続として理解するだけではなく, 「左辺の発散する無限和から適切に無限大を引き去れば右辺に等しくなる」というようなタイプの命題をうまく作ることもできる. 以下ではそのことを解説しよう.

以下, $\eta$ は非負の実数に値を持つ $\R$ 上の**急減少函数**であると仮定する. ($\R$ 上の急減少函数とは $\R$ 上の $C^\infty$ 函数でそれ自身およびそのすべての階数の導函数に任意の多項式函数をかけたものが $|x|\to\infty$ で $0$ に収束するもののことである.) さらに,

$$
\eta(0)=1, \quad \eta'(0)=0
$$

と仮定する. 例えば $\eta(x)=e^{-x^2}$ はそのような函数の例になっている.

このとき, $\eta(x)$ が急減少函数であることより, $N>0$ のとき, 級数

$$
\sum_{n=1}^\infty n^r \eta(n/N) = 1^r\eta(1/N) + 2^r\eta(2/N) + 3^r\eta(3/N) + \cdots
$$

は常に絶対収束する. $r$ が非負の整数のとき, $N\to\infty$ とすると, この級数は発散級数 $1^r+2^r+3^r+\cdots$ になってしまう.  以下の目標は, Euler-Maclaurinの和公式を使うと, その $N\to\infty$ での発散部分が $CN^{r+1}$ ($C$ は $\eta$ と $r$ で具体的に決まる定数) の形にまとまることを示すことである. そして, 残った有限部分は**常に** $\zeta(-r)$ に収束することも示される.

$\tilde{B}_n(x)=B_n(x-\lfloor x\rfloor)$ と書くことにする.

このとき, $f(x)=\eta(x/N)$ にEuler-Maclaurinの和公式を適用すると, $f(0)=1$, $f'(0)=f(\infty)=f'(\infty)=0$ より,
$$
\begin{aligned}
1+\sum_{n=1}^\infty\eta(x/N) &=
\sum_{n=0}^\infty\eta(x/N)
\\ &=
\int_0^\infty\eta(x/N)\,dx + \frac{1}{2} +B_2(f'(\infty)-f'(0)) -
\int_0^\infty\frac{\tilde{B}_2(x)}{2!}\frac{1}{N^2}\eta''(x/N)\,dx
\\ &=
N\int_0^\infty\eta(y)\,dy + \frac{1}{2} -
\frac{1}{N}\int_0^\infty\frac{\tilde{B}_2(Ny)}{2!}\eta''(y)\,dy.
\end{aligned}
$$

ゆえに, $\zeta(0)=-1/2$ を使うと,

$$
\sum_{n=1}^\infty\eta(x/N) - N\int_0^\infty\eta(y)\,dy =
\zeta(0) + O(1/N).
$$

これは $N\to\infty$ で発散級数 $1+1+1+1+\cdots$ になる無限和 $\ds \sum_{n=1}^\infty\eta(x/N)$ から, その発散部分 $\ds N\int_0^\infty\eta(y)\,dy$ を引き去って, $N\to\infty$ の極限を取ると, $\zeta(0)$ に収束することを意味している. これが欲しい結果の1つ目である.

$r$ は正の整数であるとし, $f(x)=x^r\eta(x/N)$ とおく. そのとき, Leibnitz則

$$
(\varphi(x) \psi(x))^{(m)} = \sum_{i=0}^r \binom{m}{i}\varphi^{(i)}(x)\psi^{(m-i)}(x)
\\
$$

を使うと,

$$
f^{(r+2)}(x) = \frac{1}{N^2}F(x/N), \quad
F(y) = \binom{r+2}{0}y^r\eta^{(r+2)}(y) + \cdots + \binom{r+2}{r}r!\eta(y)
$$

その $f(x)$ にEuler-Maclaurinの和公式を適用すると, $f^{(k)}(\infty)=f^{(k)}(\infty)=0$ および,

$$
f(0) = f'(0) = \cdots = f^{(r-1)}(0) = f^{(r+1)}(0) = 0, \quad
f^{(r)}(0) = r!
$$

より,

$$
\begin{aligned}
\sum_{n=1}^\infty n^r\eta(n/N) &=
\sum_{n=0}^\infty f(n) =
\int_0^\infty f(x)\,dx - \frac{B_{r+1}}{(r+1)!}r! - \frac{B_{r+2}}{(r+2)!}0 +
(-1)^{r+1}\int_0^\infty \frac{\tilde{B}_{r+2}(x)}{(r+2)!} f^{(r+2)}(x)\,dx
\\ &=
N^{r+1}\int_0^\infty y^r\eta(y)\,dy - \frac{B_{r+1}}{r+1} +
(-1)^{r+1}\frac{1}{N}\int_0^\infty \frac{\tilde{B}_{r+2}(Ny)}{(r+2)!} F(y)\,dy
\\ &=
N^{r+1}\int_0^\infty y^r\eta(y)\,dy - \frac{B_{r+1}}{r+1} + O(1/N).
\end{aligned}
$$

ゆえに, $\ds\zeta(-r)=-\frac{B_{r+1}}{r+1}$ を使うと,

$$
\sum_{n=1}^\infty n^r\eta(n/N) - N^{r+1}\int_0^\infty y^r\eta(y)\,dy =
\zeta(-r) + O(1/N).
$$

これは $N\to\infty$ で発散級数 $1^r+2^r+3^r+4^r+\cdots$ になる無限和 $\ds \sum_{n=1}^\infty n^r\eta(x/N)$ から, その発散部分 $\ds N^{r+1}\int_0^\infty y^r\eta(y)\,dy$ を引き去って, $N\to\infty$ の極限を取ると, $\zeta(-r)$ に収束することを意味している. これが欲しい結果である.

**注意:** 以上の計算のポイントは, 非負の急減少函数 $\eta(x)$ で $\eta(0)=1$, $\eta'(0)=0$ を満たすもので発散級数を正則化して得られる級数の場合には, Euler-Maclaurinの和公式の「途中の項」がほとんど消えてしまうことである. $C N^{r+1}$ 型の発散項と定数項と $O(1/N)$ の部分の3つの項しか生き残らない. $\QED$

**注意:** 以上の結果に関するより進んだ解説については次のリンク先を参照せよ:

* Terence Tao, The Euler-Maclaurin formula, Bernoulli numbers, the zeta function, and real-variable analytic continuation, Blog: What's new, 10 April, 2010.

このブログ記事はかなり読み易い. $\QED$

**問題:** 以上の結果を数値計算でも確認してみよ. $\QED$

**ヒント:** $\eta(x)=e^{-x^2}$ の場合を試してみよ. そのとき,

$$
\int_0^\infty y^r\eta(y)\,dy =
\int_0^\infty y^r e^{-y^2}\,dy =
\frac{1}{2}\Gamma\left(\frac{r+1}{2}\right)
$$

となっている. $\QED$

**解答例:** 次のリンク先のノートを見よ.

* 黒木玄, ζ(s) の Re s ＜ 1 での様子 $\QED$

```julia
y = symbols("y", real=true)
r = symbols("r", positive=true)
integrate(y^r*exp(-y^2), (y, 0, oo))
```

$$\frac{1}{2} \Gamma{\left(\frac{r}{2} + \frac{1}{2} \right)}$$

```julia

```

Old_Ver_for_Julia_v0.6/13 Euler-Maclaurin summation formula.ipynb


 ✅ Put your name here

#
 In-Class Assignment 23: Classical and Quantum Bits

In yesterday's pre-class assignment, we got introduced to bits and qubits. Today, we'll get more practice working with both units of information.

##
 Itinerary

    Assignment
    Topic
    Description

    Pre Class 23
    Background for Quantum Computing
    How Computers Store Information

    In Class 23
    Classsical and Quantum Bits
    Information in Quantum States

      Pre Class 24
      Software for Quantum Computing
      High Level Software and the Circuit Model

      In Class 24
      Programming Quantum Computers
      Manipulating Quantum Bits to Perform Useful Computations

##
 Learning Goals for Today's In-Class Assignment

The purpose of this notebook is to understand how bits are used in classical computers and how qubits are used in quantum computers. In particular, by the end of today's assignment, you should:

1. Know the difference between a bit and a qubit. (And thus the difference between classical and quantum computing.)
1. Understand how information is stored and retrieved from bits, and what operations can be done on a bit.
1. Understand how information is stored and retrieved from qubits, and what operations can be done on a qubit.

```python
"""Imports for the assignment."""
import numpy as np
import matplotlib.pyplot as plt
```

##
 Recap of the Pre-Class Assignment

In the pre-class assignment, we learned that all information is stored in bits on (classical) computers. The key difference in quantum computers is that they store information in quantum bits, or qubits.

To get a deeper understanding of qubits, we reviewed/learned three important topics:

* A complex number has real and imaginary parts and a complex conjugate that allows us to compute it's modulus squared.

* A probability distribution is a list of numbers (or vector, now that we know what that is) that add up to one.

* A vector is a list of numbers (real, complex, etc.) that we can form linear combinations, or superpositions, with.

Question: Is this a valid probability distribution?

```python
"""Exercise: is this a valid probability distribution?"""
distribution = np.array([0.6, 0.4])
```

✎ **Answer:** Erase the contents of this cell and put your answer here!

Question: If you answered yes, what could this probability distribution represent? (Give an example.)

✎ **Answer:** Erase the contents of this cell and put your answer here!

Question: What's the complex conjugate and modulus squared of

\begin{equation}
    \alpha = 2 + 4i?
\end{equation}

✎ **Answer:** Write code in the following cell to answer the above question.

```python
"""Exercise: compute the complex conjugate and modulus squared of the given complex number."""
# complex number
alpha = 2 + 4j

# TODO: compute and print out the complex conjugate

# TODO: compute and print out the modulus squared

```

#
 Working with Bits

Remember that a bit can have the values of either 1 or 0 (True or False, on or off, yes or no.) All information on (classical) computers is stored in bits. Computers process information by operating on bits.

Question: How can you encode one letter of the alphabet (a, b, c, ..., x, y, z) using only bits? How many bits do you need at minimum?

✎ **Answer:** Erase the contents of this cell and put your answer here!

Question: In our laptops, bits are represented by electrical signals. Think of other physical systems that we could use to represent bits of information. List as many as you can (at least three).

✎ **Answer:** Erase the contents of this cell and put your answer here!

##
 Writing a Bit Class

Now that we know what binary digits are and how to use them to represent information (like letters in the alphabet), let's do a bit of coding (excuse the pun). Specifically, let's code up a `Bit` class to understand them better. A skeleton of the class is provided below, with some methods implemented, which you should not change.

You're recommended to make all edits to the class here (rather than copying and pasting the class several times below). Unfortunately, this requires some scrolling back and forth between directions and the code. You may wish to have another copy of the notebook open and use one for reading instructions and the other for writing code.

```python
"""Code cell with a bit class. Keep your class here and modify it as you work through the notebook.
A skeleton of the class is provided for you."""

class Bit:
    """Binary digit class."""

    def display_value(self):# <-- do not modify this method
        """Displays the value of the Bit."""
        print("The bit's value is ", self.value, ".", sep="")
```

We know that every class needs an `__init__` method, which here will create our `Bit`. Let's agree by convention to always start our bit in the "off" state, represented by 0, unless a different initial value is provided. We'll do this by using keyword arguments as described below.

✎ Do the following to your class:

(1) Define an `__init__` method. This method should have a keyword argument (input to the function) called `initial_value` whose default value is 0. Create a class attribute called `value` and set it to be `initial_value`.

```python
"""Create a bit and display it's value."""
b = Bit()
b.display_value()
```

✎ Do the following to your class:

(2) Write a method called `measure` which returns the `value` of the bit.

Now run the following code block.

```python
"""Create a bit, display its value, and print out its state."""
b = Bit()
b.display_value()
print("The measured state of the bit is {}".format(b.measure()))
```

I know what you're thinking! "Well duh! The measured state of a bit is just going to be it's value..." Hold that thought! We're going to see a big difference when we write a class for a quantum bit.

First, let's talk about the operations we can perform on a bit.

###
 Operations on a Bit

There's only one non-trivial operation that can be performed on a bit -- negating, or flipping, its value. We'll call this operation the `NOT` operation, which has the following effect:

\begin{align}
    \text{NOT(0)} &= 1 \\
    \text{NOT(1)} &= 0
\end{align}

✎ Do the following to your class:

(3) Define a method called `NOT` which negates the `value` of the bit. This method should NOT return a value (no pun intended).

Now run the following code with your class.

```python
"""Perform operations on a bit."""
# create a bit
b = Bit()
b.display_value()
print("The measured state of the bit is {}.\n".format(b.measure()))

# apply a NOT operation
b.NOT()
b.display_value()
print("The measured state of the bit is {}.\n".format(b.measure()))

# apply another NOT operation
b.NOT()
b.display_value()
print("The measured state of the bit is {}.\n".format(b.measure()))
```

Note that applying two `NOT` gates in a row gets us back to the same state, as you might expect.

We can certainly do more operations with multiple bits of information. For example, if we have two bits, we can take the `AND` or the `OR` of them. The `AND` takes in two bits and returns one if both input bits are one and zero otherwise. The `OR` returns one if either of the input bits is one (including both).

Operations on multiple bits are crucial for information processing (i.e., computation), but for simplicity we won't discuss them in more detail here.

One reason we do mention multiple bits is for copying information, another crucial component of (classical) computation.

##
 Copying Bits

We can copy a single classical bit into as many bits as we want. How? Well, we just look at the bit, record its value, then prepare another bit with that value.

✎ Do the following to your class:

(4) Define a method called `copy` which copies the `value` of the `Bit` to a new `Bit` and returns the new `Bit`. Then execute the following cell.

```python
"""Copy a bit."""
b = Bit()
new_bit = b.copy()

b.display_value()
new_bit.display_value()

print(b == new_bit)
```

Note that the bits have the same value, but they are not equivalent, since they are different objects. With bits, we are able to directly "measure" the bits value and write it into a new bit of information.

We highlighted this feature, as well as the others, to now contrast bits with qubits.

#
 Working with Qubits

Whereas classical computers represent information using bits, quantum computers represent information using qubits. Here's a short refresher on what a qubit is from the Pre-Class Assignment.

A qubit is a vector of complex numbers

\begin{equation}
    |\psi\rangle = \alpha |0\rangle + \beta |1\rangle
    =
    \left[ \begin{matrix}
    \alpha \\
    \beta \\
    \end{matrix} \right] .
\end{equation}

These complex numbers determine the probability of measuring 0 or 1, as we'll see today, so we require that $|\alpha|^2 + |\beta|^2 = 1$.

As a reminder, the vector $|0\rangle$, sometimes called the ground state, is

\begin{equation}
    |0\rangle = \left[ \begin{matrix}
    1 \\
    0 \\
    \end{matrix} \right]
\end{equation}

and the vector $|1\rangle$, sometimes called the excited state, is

\begin{equation}
    |1\rangle = \left[ \begin{matrix}
    0 \\
    1 \\
    \end{matrix} \right] .
\end{equation}

The Greek symbol $\psi$ (psi, pronounced: "sigh") is commonly used to represent qubits.

Question: Bits are made of classical physical systems like light switches or electricity. What kind of quantum systems do people make qubits out of? Search the web to find out and record at least three of your findings below. Cite your source(s).

✎ **Answer:** Erase the contents of this cell and put your answer here!

##
 Writing a Qubit Class

Let's now get more practice with qubits by writing a `Qubit` class in the same way that we wrote a `Bit` class. This will allow you to see the similarites and differences between classical and quantum bits.

```python
"""Code cell with a qubit class. Keep your class here and modify it as you work through the notebook.
A skeleton of the class is provided for you."""

class Qubit:
    """Quantum bit class."""

    def display_wavefunction(self):# <-- do not modify this method!
        """Prints the wavefunction of the Qubit."""
        print("The Qubit's wavefunction is", self.wavefunction[0], self.wavefunction[1], sep="\n")
```

✎ Do the following to your `Qubit` class:

(1) Define an `__init__` method. In this method:

(a) Create a class attribute called `zero` which represents the vector $|0\rangle$ above. This attribute should be a Numpy array. Make sure the datatype (`dtype`) is complex, for example using `np.complex64`.

(b) Create a class attribute called `one` which represents the vector $|1\rangle$ above. This attribute should be a Numpy array. Make sure the datatype (`dtype`) is complex, for example using `np.complex64`.

By convention, let's agree to always initialize a `Qubit` in the ground state $|0\rangle$.

✎ Do the following to your `Qubit` class:

(1) In the `__init__` method, create an attribute called `wavefunction` of the `Qubit` and set it to be equal to the $|0\rangle$ state. (The term wavefunction is physics jargon. We can say "a qubit is..." or "a qubit's wavefunction is..." interchangeably.)

Now run the following code.

```python
"""Initialize a qubit and display its wavefunction."""
q = Qubit()
q.display_wavefunction()
print()
```

In the above code, we initialize a qubit and then use the provided `display_wavefunction` method to print out it's wavefunction. If your `__init__` method is correct, you should see the qubit's wavefunction as the $|0\rangle$ vector.

###
 Measuring a Qubit

Now let's write a method to measure our `Qubit`. The two measurement rules of a qubit

\begin{equation}
    |\psi\rangle = \alpha |0\rangle + \beta |1\rangle
    =
    \left[ \begin{matrix}
    \alpha \\
    \beta \\
    \end{matrix} \right] .
\end{equation}

are listed below:

The First Measurement Rule

(1) The probability that $|\psi\rangle$ is in the ground state $|0\rangle$ is $|\alpha|^2$. The probability that $|\psi\rangle$ is in the excited state $|1\rangle$ is $|\beta|^2 = 1 - |\alpha|^2$.

Key Concept: There are two possible measurement outcomes of a qubit. Thus, the measurement outcome of a qubit is a bit. This is why we used 0 and 1 as labels for the vectors all along! When we measure the ground state $|0\rangle$, we call this outcome $0$. When we measure the excited state $|1\rangle$, we call this outcome $1$.

The Second Measurement Rule

(2) If we measure the ground state, the wavefunction becomes $|0\rangle$. If we measure the excited state, the wavefunction becomes $|1\rangle$.

✎ Do the following to your `Qubit` class:

(1) Write a method called `measure` which measures a `Qubit` according to the above rules. Specifically, your method should return a bit (0 if the ground state was measured, or 1 if the excited state was measured) and modify the `wavefunction` of the `Qubit` appropriately.

Hints:

(i) Compute the probability the qubit is in the ground state ($|\alpha|^2$).

(ii) Generate a random number between 0 and 1.

(iii) If the random number is less than $|\alpha|^2$, set the `wavefunction` to be the `zero` vector, and return the number (bit) 0. Otherwise, set the `wavefunction` to be the `one` vector, and return the number (bit) 1.

Now run the following code.

```python
"""Initialize a qubit and measure it."""
q = Qubit()
q.display_wavefunction()
print("The bit we obtain from measuring the qubit is {}.\n".format(q.measure()))
q.display_wavefunction()
```

You should have seen the measurement result 0.

Question: Run the cell above many times and note the measurement result (i.e., bit) after each time you run it. (The keyboard shortcut "control + enter" is useful here.) What measured state do you always get? Why?

✎ **Answer:** Erase the contents of this cell and put your answer here!

##
 Operations on a Qubit

On a classical bit, we could only do one operation, the `NOT` operation, because the bit only had two states. With our `Qubit`, we have an underlying `wavefunction` that helps determine what our measured state will be, as we have seen above. Operations on a `Qubit` act on its `wavefunction`. As such, there's a lot more operations we can do to it! (In fact, there's infinitely many operations we can do on a qubit.)

One example of an operation is called the `X` or `NOT` operation. Why is it called this? Well, it has the effect

\begin{align}
    \text{NOT($|0\rangle$)} &= |1\rangle \\
    \text{NOT($|1\rangle$)} &= |0\rangle
\end{align}

Qubit operations can be written as matrices that act on a qubit's wavefunction to implement the operation. You don't have to know how to do matrix-vector multiplication, just how to do it in Python. An example is shown below.

```python
"""Example of a matrix vector multiplication using numpy."""
# an example matrix
matrix = np.array([[1, 1], [1, 1]])

# an example vector
vector = np.array([1, 0])

# the matrix-vector product
print(np.dot(matrix, vector))

# another way to do the matrix-vector product
print(matrix @ vector)
```

It can be shown that a matrix representation for `NOT` is

\begin{equation}
 \text{NOT} = \left[ \begin{matrix}
     0 & 1 \\
     1 & 0 \\
     \end{matrix} \right] .
\end{equation}

(If you know linear algebra, prove this to youreself. If not, just take our word for it.)

✎ Do the following to your `Qubit` class:

(1) Write a method called `NOT` which multiplies the `wavefunction` the `NOT` matrix given above. (This method should NOT return a value (still no pun intended), only modify the `wavefunction`.)

Now run the following code.

```python
"""Perform a NOT operation on a qubit."""
q = Qubit()
q.display_wavefunction()
print()

q.NOT()
q.display_wavefunction()
```

You should have a `Qubit` whose wavefunction is $|1\rangle$ after executing the above cell. Now let's measure such a qubit to see what we get.

```python
"""Measure a qubit with wavefunction |1>."""
q = Qubit()
q.NOT()
q.display_wavefunction()
print("The bit we obtain from measuring the qubit is {}.\n".format(q.measure()))
q.display_wavefunction()
```

You should have seen 1 as the measurement result.

Question: Run the above cell many times and observe your measurement results after each run. What measured state do you always get? Why?

✎ **Answer:** Erase the contents of this cell and put your answer here!

Another quantum operation is called the Hadamard operation or Hadamard gate. A matrix representation for the Hadamard gate is given by

\begin{equation}
    H = \frac{1}{\sqrt{2}}\left[ \begin{matrix}
     1 & 1 \\
     1 & -1 \\
     \end{matrix} \right]
\end{equation}

✎ Do the following to your `Qubit` class:

(1) Write a method called `H` that applies the Hadamard gate given above to the `Qubit`s `wavefunction`.

Now run the following code.

```python
"""Performing a Hadamard transform on a qubit."""
q = Qubit()
q.H()
q.display_wavefunction()
```

You should see that the qubit has equal amplitudes (components of the wavefunction). Now let's measure the `Qubit` to see what we get.

```python
"""Measuring a qubit after performing a Hadamard gate."""
q = Qubit()
q.H()
q.display_wavefunction()
print("The bit we obtain from measuring the qubit is {}.\n".format(q.measure()))
q.display_wavefunction()
```

What measurement result did you get?

Question: Run the above cell several times and observe your measurement results after each run. Write a sentence describing your observation.

✎ **Answer:** Erase the contents of this cell and put your answer here!

Question: Write code to create a qubit, perform the Hadamard gate, and measure the qubit 1000 times. Record each measurement outcome, then make a histogram of the probability of measuring 0 and the probability of measuring 1.

```python
"""Put your code here."""

```

Question: Reflect on your results. What can you say about the probability of measuring 0 and the probability of measuring 1?

✎ **Answer:** Erase the contents of this cell and put your answer here!

##
 Copying Qubits

Remember the question of how to copy a bit? Well, now let's ask this for qubits:

Question: How do you copy a qubit?

Attempted Answer 1: You just copy it's wavefunction?

This won't work! Remember the wavefunction is just a mathematical tool that we use to help us calculate probabilities of what state the system is in. If we have a particle, say an electron, there's no wavefunction that we can just look at and then copy over it's information. (Unlike a light switch, a classical system, which we could look at and see with no issues.

Attempted Answer 2: Well what if we just measure it then copy the measurement result into a new qubit?

This also won't work! Remember the measurement rules above. When we measure a qubit, we inherently change its wavefunction. The wavefunction it changes to is not, in general, the same as it was before measurement.

There is a way to copy one qubit to another, which is known as quantum teleportation, and involves a total of three qubits to get the job done. You'll get a chance to look at this in an upcoming assignment!

#
 Assignment Wrap-up

##
 Installing Qiskit

For the next assignments, we'll be using the Quantum Information Science Kit, or Qiskit, which is a Python package for quantum computing. Try to install Qiskit v0.7.0 on your computer now by executing the following cell. We'll be walking around to troubleshoot problems.

Note: Why version 0.7.0? These quantum software packages are new and tend to change a bit. We'll use this version to make sure all the code in future assignments works as anticipated.

```python
"""Attempt to install Qiskit using pip. Uncomment the following two lines and run the cell."""
# !pip install --upgrade pip
# !pip install qiskit==0.7.0
```

##
 Instructions to Get an API Key to Use a Quantum Computer

You can use Qiskit to program an actual quantum computer. To do so, one needs to register for an API key. If this interests you, follow the instructions below. (These instructions are also in the next Pre Class Assignment if you're short on time.) This is optional, not required.

1. Navigate to the IBM Quantum Experience website https://quantumexperience.ng.bluemix.net/qx.

1. Click "Sign In" in the upper right hand corner of the page (blue box with white text).

1. In the pop-up screen, select "Sign Up" or "Sign up using Github." The first requires an email, the second requires you to log into GitHub and authorize access to your account (to get an email).

1. Fill out the form, then click "Sign up" at the bottom.

Once you have created an account, you can sign in (follow the first two steps above). Then, click the user icon in the upper right hand corner of the page, then click "My Account." On the new screen, click the "Advanced" tab. Here, you can see your API key and copy it to your clipboard. You'll need to enter this in your notebook to use the real quantum computer backends.

##
 Survey

```python
from IPython.display import HTML
HTML(
"""

"""
)
```

##
 Congrats, You're Finished!

Now, you just need to submit this assignment by uploading it to the course Desire2Learn web page for today's submission folder. (Don't forget to add your name in the first cell.)

© Copyright 2019, Michigan State University Board of Trustees.


# Deep Residual Learning for Image Recognition

## Degradation Problem

Deep convolutional neural networks가 나온 이후로 많은 발전이 있었습니다.

기본적으로 deep networks는 features들을 스스로 low/mid/high level features로 나누며$ ^{[01]} $, 근래의 모델들의 경우 layers층을 더 깊이 있게 쌓아서 features들의 levels을 더욱 세분화하고자하는 시도가 있으며$ ^{[02]} $, ImageNet 챌린지에서 16개 또는 30개등의 layers를 사용하는 매우 깊은 모델들을 사용하기도 하였습니다.$ ^{[03]} $

단순히 layers를 더 많이 쌓으면 더 좋은 결과를 낼 것인가? 라는 질문에는.. 사실 문제가 있습니다.

이미 잘 알려진 vanishing/exploding gradients$ ^{[04]} $ $ ^{[05]} $의 문제는 convergence자체를 못하게 만듭니다.

이러한 문제는 normalized initialization$ ^{[06]} $ $ ^{[04]} $ $ ^{[07]} $, 그리고 intermediate normalization layers $ ^{[08]} $에 의해서 다소 해결이 되어 수십층의 layers들이 SGD를 통해 convergence될 수 있도록 도와줍니다.

Deeper networks를 사용할때 **degradation problem**이 발견되었습니다. degradation problem은 network의 depth가 커질수록 accuracy는 saturated (마치 뭔가 가득차서 현상태에서 더 진전이 없어져 버리는 상태)가 되고 degradation이 진행됩니다. 이때 degradation은 overfitting에 의해서 생겨나는 것이 아니며, 더 많은 layers를 넣을수록 training error가 더 높아집니다.$ ^{[09]} $ (만약 overfitting이었다면 training error는 매우 낮아야 합니다.)

CIFAR-10 데이터에 대한 training error(왼쪽) 그리고 test error(오른쪽) 그래프.

    20-layers 그리고 56-layers를 사용했으며, 더 깊은 네트워크일수록 training error가 높으며, 따라서 test error또한 높다.

한가지 실험에서 이를 뒷받침할 근거를 내놓습니다.

shallow network에서 학습된 모델위에 다층의 layers를 추가적으로 쌓습니다. 이론적으로는 deeper 모델이 shallower 모델에 추가된 것이기 때문에 더 낮은 training error를 보여야 합니다. 하지만 학습된 shallower 모델에 layers를 더 추가시켜도, 그냥 shallow 모델보다 더 높은 training error를 보여줍니다.

**Constructed Solution**
Shallower model(왼쪽) 그리고 Deeper model(오른쪽).
Shallower model로 부터 학습된 지식을 복사하고, Identity mapping로서 layers를 추가하였다.
Deeper model은 shallower model과 비교하여 더 낮거나 같은 training error를 보여야 하지만
실제는 degradation현상으로 인하여 layers가 깊어질수록 training error는 높아진다

위의 그림에서 보듯이, shallower model로 부터 학습된 지식을 복사하고, 추가적으로 identity mapping으로서 layers를 더 추가하였습니다.

여기서 identity mapping이라는 뜻은 $ f(x) = x $ 의 의미로 기존 학습된 layers에서 나온 output을 추가된 layers에서 동일한 output을 생성하는 것입니다. 따라서 identity mapping으로서 추가된 layers들은 최소한 shallower model에서 나온 예측치와 동일하거나 또는 더 깊게 들어갔으니 더 잘 학습이 되어야 합니다.

하지만 현실은.. layers가 깊어지면 깊어질수록 training error가 더 높아지며, 따라서 test error또한 동일하게 높아집니다.

이러한 현상을 degradation problem이라고 하며, Deep Residual Network$ ^{[10]} $가 해결하려는 부분입니다.

## Residual

먼저 residual에 대해서 알아야 합니다.

간단하게 이야기 하면 residual이란 관측치(observed data)와 예측값(estimated value)사이의 차이입니다.

Linear least square (최소회귀직선) residual의 합은 0이 됩니다.

예를 들어 다음의 수식에서 true값인 $ x $를 찾고자 합니다.

$$ f(x) = b $$

이때 $ x $의 근사치(approximation)인 $ x_0 $가 주어졌을때, residual값은 다음과 같습니다.

$$ b - f(x_0) $$

반면에 error는 true값에서 근사치(approximation)의 차이이며 다음과 같습니다.

(하나의 예로.. 근사값  3.14 - $  \pi $ 가 바로 오차입니다.)

$$ x - x_0 $$

좀더 잘 설명한 [영상](https://www.youtube.com/watch?v=snG7sa5CcJQ)을 참고 합니다.

## ResNet Explained

Degradation에서 언급한 현상을 보면, 직관적으로 보면 deep neural network에 더 많은 layers를 추가시킵으로서 성능을 향상시킬수 있을거 같지만 그와는 정 반대의 결과가 나왔습니다. 이것이 의미하는 바는 multiple nonlinear layers안에서 identity mappings을 시키는데(approximate) 어려움이 있다는 것입니다. 이는 흔히 딥러닝에서 나타나는 vanishing/exploding gradients 이슈 그리고 curse of dimensionality problem등으로 나타나는 현상으로 생각이 됩니다.

ResNets은 이러한 문제를 해결하기 위하여, residual learning을 통해 강제로 Identity mapping (function)을 학습하도록 하였습니다.

* $ x $: 해당 레이어들의 input
* $ H(x) $: (전체가 아닌) 소규모의 다층 레이어(a few stacked layers)의 output
* $ id(x) $: Identity mapping(function)은 단순히 $ id(x) = x $ 으로서, $ x $값을 받으면 동일한 $ x $를 리턴시킵니다
* $ H(x) $ 그리고 $ x $ 는 동일한 dimension을 갖고 있다고 가정

일반적인 Neural Network는 $ H(x) $ 자체를 학습니다.

ResNet의 경우에는 residual function을 학습하도록 강제합니다.

$$ F(x) = H(x) - id(x) $$

우리는 실제 true값을 알고자 하는 것이기 때문에 위의 공식은 다음과 같이 재정립(reformulation)할수 있습니다.

$$ \begin{align}
H(x) &= F(x) + id(x)  \\
&= F(x) + x
\end{align} $$

즉 아래의 그림처럼 그래프가 그려집니다.

이론적으로 identity mappings이 최적화(optimal)되었다면, 다중 레이어의 weights연산 $ F(x) $ 의 값을 0으로 만들것입니다. $ F(x) $ 가 0이 된후 $ id(x) $ 를 더하기 때문에 해당 subnetwork는 identity function으로서 기능을 하게 됩니다.

실제로는 identity mappings (layers)가 최적화되어 0으로 수렴하는 것은 일어나기 힘듬니다.

다만 reformulation 된 공식안에 identity function이 존재하기 때문에 reference가 될 수 있고, 따라서 neural network가 학습하는데 도움을 줄 수 있습니다.

## Shortcut Connection

위에서 이미 언급한 그래프에서 논문에서는 building block을 다음과 같이 정의하고 있습니다.

(공식을 간략하게 하기 위해서 bias에 대한 부분은 의도적으로 누락되어 있습니다. 당연히 실제 구현시에는 필요합니다.)

$$ y = F(x\ |\ W_i) + x  $$

$ F(x\ |\ W_i) $ 는 학습되야할 residual mapping을 나타내며, $ x $ 그리고 $ y $는 각각 input 그리고 output을 나타냅니다.

위의 공식은 아주 간략하게 표현하기 위해서 나타낸것이고 2개의 레이어를 사용하는 경우에는 $ F $ 함수에대한 정의가 바뀝니다.

$$ F = W_2 \sigma \left(W_1 x \right) $$

여기서 $ \sigma $는 ReLU를 가르킵니다. $ F + x $ 는 **shortcut connection**을 나타내며 element-wise addition을 연산합니다.

해당 addtion 이후! 두번째 nonlinearity를 적용합니다. (즉 ReLU를 addition이후에 적용하면 됨)

$ F + x $ 를 연산할때 중요한점은 **dimension이 서로 동일**해야 합니다.
만약 서로 dimension이 다르다면 (예를 들어서 input/output의 channels이 서로 다름) linear projection $ W_s $ 를 shorcut connection에 적용시켜서 dimension을 서로 동일하게 만들어줄수 있습니다.

$$ y = F(x\ |\ W_i) + W_s x $$

Residual function $ F $는 사실 상당히 유연합니다.즉 $ F $는 2개, 3개 또는 3개 이상의 다층을 사용하는 것이 가능합니다.

하지만 만약 $ F $안에 1개의 레이어만 갖고 있다면 linear layer와 동일해지게 됩니다.

$$ y = W_1x + x $$

따라서 1개만 갖고 있는 $ F $는 사실상 의미가 없습니다.

또한 $ F $는 fully-connected layer 또는 convolution같은 다양한 방법으로 모델링을 할 수 있습니다.

## Implementation

논문에서는 ImageNet에 대한 구현을 다음과 같이 하였습니다.

#### Data Augmentation

224 x 224 싸이즈로 random crop 또는 horizontal flip이 되었으며, 픽섹마다 평균값으로 subtract 되었습니다.

Standard color augmentation을 실행했습니다.

#### ResNet Layer

Conv -> Batch -> ReLU -> Conv -> Batch -> Addition -> RELU

# References

인용한 문서들..

*  [01] [Visualizing and understanding convolutional neural networks](https://www.cs.nyu.edu/~fergus/papers/zeilerECCV2014.pdf)
*  [02] [Very Deep Convolutional Networks for Large-Scale Image Recognition](https://arxiv.org/abs/1409.1556)
*  [03]  [Going Deeper with Convolutions](https://arxiv.org/pdf/1409.4842.pdf)
*  [04] [Understanding the difficulty of training
deep feedforward neural networks](http://www-prima.imag.fr/jlc/Courses/2016/PRML/XavierInitialisation.pdf)
*  [05] [Learning long-term dependencies
with gradient descent is difficult](http://www.iro.umontreal.ca/~lisa/pointeurs/ieeetrnn94.pdf)
*  [06] [Efficient backprop. In Neural Networks: Tricks of the Trade](http://yann.lecun.com/exdb/publis/pdf/lecun-98b.pdf)
*  [07] [Exact solutions to the nonlinear dynamics of learning in deep linear neural networks](https://arxiv.org/pdf/1312.6120.pdf)
*  [08] [Batch normalization: Accelerating deep
network training by reducing internal covariate shift](https://arxiv.org/abs/1502.03167)
*  [09] [Convolutional neural networks at constrained time cost](https://arxiv.org/abs/1412.1710)
*  [10] [Deep Residual Learning for Image Recognition](https://arxiv.org/pdf/1512.03385.pdf)

글 쓰면서 참고한 문서들..

* [Deep Residual Learning for Image Recognition - 원래 ResNet Paper](https://arxiv.org/pdf/1512.03385.pdf)
* [Identity Mappings in Deep Residual Networks - 개선된 Paper](https://arxiv.org/pdf/1603.05027.pdf)
* [Residual neural networks are an exciting area of deep learning research](https://blog.init.ai/residual-neural-networks-are-an-exciting-area-of-deep-learning-research-acf14f4912e9)
* [Deep Residual Networks ICML 2016 Tutorial](http://icml.cc/2016/tutorials/icml2016_tutorial_deep_residual_networks_kaiminghe.pdf)


## Configurations for Colab

```python
import sys
IN_COLAB = "google.colab" in sys.modules

if IN_COLAB:
    !apt install python-opengl
    !apt install ffmpeg
    !apt install xvfb
    !pip install pyvirtualdisplay
    !pip install gym[all]
    from pyvirtualdisplay import Display

    # Start virtual display
    dis = Display(visible=0, size=(400, 400))
    dis.start()
```

# 05. Noisy Networks for Exploration

[M. Fortunato et al., "Noisy Networks for Exploration." arXiv preprint arXiv:1706.10295, 2017.](https://arxiv.org/pdf/1706.10295.pdf)

NoisyNet is an exploration method that learns perturbations of the network weights to drive exploration. The key insight is that a single change to the weight vector can induce a consistent, and potentially very complex, state-dependent change in policy over multiple time steps.

Firstly, let's take a look into a linear layer of a neural network with $p$ inputs and $q$ outputs, represented by

$$
y = wx + b,
$$

where $x \in \mathbb{R}^p$ is the layer input, $w \in \mathbb{R}^{q \times p}$, and $b \in \mathbb{R}$ the bias.

The corresponding noisy linear layer is defined as:

$$
y = (\mu^w + \sigma^w \odot \epsilon^w) x + \mu^b + \sigma^b \odot \epsilon^b,
$$

where $\mu^w + \sigma^w \odot \epsilon^w$ and $\mu^b + \sigma^b \odot \epsilon^b$ replace $w$ and $b$ in the first linear layer equation. The parameters $\mu^w \in \mathbb{R}^{q \times p}, \mu^b \in \mathbb{R}^q, \sigma^w \in \mathbb{R}^{q \times p}$ and $\sigma^b \in \mathbb{R}^q$ are learnable, whereas $\epsilon^w \in \mathbb{R}^{q \times p}$ and $\epsilon^b \in \mathbb{R}^q$ are noise random variables which can be generated by one of the following two ways:

1. **Independent Gaussian noise**: the noise applied to each weight and bias is independent, where each random noise entry is drawn from a unit Gaussian distribution. This means that for each noisy linear layer, there are $pq + q$ noise variables (for $p$ inputs to the layer and $q$ outputs).
2. **Factorised Gaussian noise:** This is a more computationally efficient way. It produces 2 random Gaussian noise vectors ($p, q$) and makes $pq + q$ noise entries by outer product as follows:

$$
\begin{align}
\epsilon_{i,j}^w &= f(\epsilon_i) f(\epsilon_j),\\
\epsilon_{j}^b &= f(\epsilon_i),\\
\text{where } f(x) &= sgn(x) \sqrt{|x|}.
\end{align}
$$

In all experiements of the paper, the authors used Factorised Gaussian noise, so we will go for it as well.

```python
import math
import os
from typing import Dict, List, Tuple

import gym
import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from IPython.display import clear_output
```

## Replay buffer

Please see *01.dqn.ipynb* for detailed description.

```python
class ReplayBuffer:
    """A simple numpy replay buffer."""

    def __init__(self, obs_dim: int, size: int, batch_size: int = 32):
        self.obs_buf = np.zeros([size, obs_dim], dtype=np.float32)
        self.next_obs_buf = np.zeros([size, obs_dim], dtype=np.float32)
        self.acts_buf = np.zeros([size], dtype=np.float32)
        self.rews_buf = np.zeros([size], dtype=np.float32)
        self.done_buf = np.zeros(size, dtype=np.float32)
        self.max_size, self.batch_size = size, batch_size
        self.ptr, self.size, = 0, 0

    def store(
        self,
        obs: np.ndarray,
        act: np.ndarray,
        rew: float,
        next_obs: np.ndarray,
        done: bool,
    ):
        self.obs_buf[self.ptr] = obs
        self.next_obs_buf[self.ptr] = next_obs
        self.acts_buf[self.ptr] = act
        self.rews_buf[self.ptr] = rew
        self.done_buf[self.ptr] = done
        self.ptr = (self.ptr + 1) % self.max_size
        self.size = min(self.size + 1, self.max_size)

    def sample_batch(self) -> Dict[str, np.ndarray]:
        idxs = np.random.choice(self.size, size=self.batch_size, replace=False)
        return dict(obs=self.obs_buf[idxs],
                    next_obs=self.next_obs_buf[idxs],
                    acts=self.acts_buf[idxs],
                    rews=self.rews_buf[idxs],
                    done=self.done_buf[idxs])

    def __len__(self) -> int:
        return self.size
```

## Noisy Layer

**References:**
- https://github.com/higgsfield/RL-Adventure/blob/master/5.noisy%20dqn.ipynb
- https://github.com/Kaixhin/Rainbow/blob/master/model.py

```python
class NoisyLinear(nn.Module):
    """Noisy linear module for NoisyNet.

    Attributes:
        in_features (int): input size of linear module
        out_features (int): output size of linear module
        std_init (float): initial std value
        weight_mu (nn.Parameter): mean value weight parameter
        weight_sigma (nn.Parameter): std value weight parameter
        bias_mu (nn.Parameter): mean value bias parameter
        bias_sigma (nn.Parameter): std value bias parameter

    """

    def __init__(self, in_features: int, out_features: int, std_init: float = 0.5):
        """Initialization."""
        super(NoisyLinear, self).__init__()

        self.in_features = in_features
        self.out_features = out_features
        self.std_init = std_init

        self.weight_mu = nn.Parameter(torch.Tensor(out_features, in_features))
        self.weight_sigma = nn.Parameter(
            torch.Tensor(out_features, in_features)
        )
        self.register_buffer(
            "weight_epsilon", torch.Tensor(out_features, in_features)
        )

        self.bias_mu = nn.Parameter(torch.Tensor(out_features))
        self.bias_sigma = nn.Parameter(torch.Tensor(out_features))
        self.register_buffer("bias_epsilon", torch.Tensor(out_features))

        self.reset_parameters()
        self.reset_noise()

    def reset_parameters(self):
        """Reset trainable network parameters (factorized gaussian noise)."""
        mu_range = 1 / math.sqrt(self.in_features)
        self.weight_mu.data.uniform_(-mu_range, mu_range)
        self.weight_sigma.data.fill_(
            self.std_init / math.sqrt(self.in_features)
        )
        self.bias_mu.data.uniform_(-mu_range, mu_range)
        self.bias_sigma.data.fill_(
            self.std_init / math.sqrt(self.out_features)
        )

    def reset_noise(self):
        """Make new noise."""
        epsilon_in = self.scale_noise(self.in_features)
        epsilon_out = self.scale_noise(self.out_features)

        # outer product
        self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in))
        self.bias_epsilon.copy_(epsilon_out)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """Forward method implementation.

        We don't use separate statements on train / eval mode.
        It doesn't show remarkable difference of performance.
        """
        return F.linear(
            x,
            self.weight_mu + self.weight_sigma * self.weight_epsilon,
            self.bias_mu + self.bias_sigma * self.bias_epsilon,
        )

    @staticmethod
    def scale_noise(size: int) -> torch.Tensor:
        """Set scale to make noise (factorized gaussian noise)."""
        x = torch.FloatTensor(np.random.normal(loc=0.0, scale=1.0, size=size))

        return x.sign().mul(x.abs().sqrt())
```

## Noisy Network

We use NoisyLinear for the last two FC layers, and there is a method to reset noise at every step.
These are the only differences from the example of *01.dqn.ipynb*.

```python
class Network(nn.Module):
    def __init__(self, in_dim: int, out_dim: int):
        """Initialization."""
        super(Network, self).__init__()

        self.feature = nn.Linear(in_dim, 128)
        self.noisy_layer1 = NoisyLinear(128, 128)
        self.noisy_layer2 = NoisyLinear(128, out_dim)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """Forward method implementation."""
        feature = F.relu(self.feature(x))
        hidden = F.relu(self.noisy_layer1(feature))
        out = self.noisy_layer2(hidden)

        return out

    def reset_noise(self):
        """Reset all noisy layers."""
        self.noisy_layer1.reset_noise()
        self.noisy_layer2.reset_noise()
```

## DQN + NoisyNet Agent (w/o DuelingNet)

Here is a summary of DQNAgent class.

| Method           | Note                                                 |
| ---              | ---                                                  |
|select_action     | select an action from the input state.               |
|step              | take an action and return the response of the env.   |
|compute_dqn_loss  | return dqn loss.                                     |
|update_model      | update the model by gradient descent.                |
|target_hard_update| hard update from the local model to the target model.|
|train             | train the agent during num_frames.                   |
|test              | test the agent (1 episode).                          |
|plot              | plot the training progresses.                        |

In the paper, NoisyNet is used as a component of the Dueling Network Architecture, which includes Double-DQN and Prioritized Experience Replay. However, we don't implement them to simplify the tutorial. One thing to note is that NoisyNet is an alternertive to $\epsilon$-greedy method, so all $\epsilon$ related lines are removed. Please check all comments with *NoisyNet*.

```python
class DQNAgent:
    """DQN Agent interacting with environment.

    Attribute:
        env (gym.Env): openAI Gym environment
        memory (ReplayBuffer): replay memory to store transitions
        batch_size (int): batch size for sampling
        target_update (int): period for target model's hard update
        gamma (float): discount factor
        dqn (Network): model to train and select actions
        dqn_target (Network): target model to update
        optimizer (torch.optim): optimizer for training dqn
        transition (list): transition information including
                           state, action, reward, next_state, done
    """

    def __init__(
        self,
        env: gym.Env,
        memory_size: int,
        batch_size: int,
        target_update: int,
        gamma: float = 0.99,
    ):
        """Initialization.

        Args:
            env (gym.Env): openAI Gym environment
            memory_size (int): length of memory
            batch_size (int): batch size for sampling
            target_update (int): period for target model's hard update
            gamma (float): discount factor
        """
        # NoisyNet: All attributes related to epsilon are removed
        obs_dim = env.observation_space.shape[0]
        action_dim = env.action_space.n

        self.env = env
        self.memory = ReplayBuffer(obs_dim, memory_size, batch_size)
        self.batch_size = batch_size
        self.target_update = target_update
        self.gamma = gamma

        # device: cpu / gpu
        self.device = torch.device(
            "cuda" if torch.cuda.is_available() else "cpu"
        )
        print(self.device)

        # networks: dqn, dqn_target
        self.dqn = Network(obs_dim, action_dim).to(self.device)
        self.dqn_target = Network(obs_dim, action_dim).to(self.device)
        self.dqn_target.load_state_dict(self.dqn.state_dict())
        self.dqn_target.eval()

        # optimizer
        self.optimizer = optim.Adam(self.dqn.parameters())

        # transition to store in memory
        self.transition = list()

        # mode: train / test
        self.is_test = False

    def select_action(self, state: np.ndarray) -> np.ndarray:
        """Select an action from the input state."""
        # NoisyNet: no epsilon greedy action selection
        selected_action = self.dqn(
            torch.FloatTensor(state).to(self.device)
        ).argmax()
        selected_action = selected_action.detach().cpu().numpy()

        if not self.is_test:
            self.transition = [state, selected_action]

        return selected_action

    def step(self, action: np.ndarray) -> Tuple[np.ndarray, np.float64, bool]:
        """Take an action and return the response of the env."""
        next_state, reward, done, _ = self.env.step(action)

        if not self.is_test:
            self.transition += [reward, next_state, done]
            self.memory.store(*self.transition)

        return next_state, reward, done

    def update_model(self) -> torch.Tensor:
        """Update the model by gradient descent."""
        samples = self.memory.sample_batch()

        loss = self._compute_dqn_loss(samples)

        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        # NoisyNet: reset noise
        self.dqn.reset_noise()
        self.dqn_target.reset_noise()

        return loss.item()

    def train(self, num_frames: int, plotting_interval: int = 200):
        """Train the agent."""
        self.is_test = False

        state = self.env.reset()
        update_cnt = 0
        losses = []
        scores = []
        score = 0

        for frame_idx in range(1, num_frames + 1):
            action = self.select_action(state)
            next_state, reward, done = self.step(action)

            state = next_state
            score += reward

            # NoisyNet: removed decrease of epsilon

            # if episode ends
            if done:
                state = self.env.reset()
                scores.append(score)
                score = 0

            # if training is ready
            if len(self.memory) >= self.batch_size:
                loss = self.update_model()
                losses.append(loss)
                update_cnt += 1

                # if hard update is needed
                if update_cnt % self.target_update == 0:
                    self._target_hard_update()

            # plotting
            if frame_idx % plotting_interval == 0:
                self._plot(frame_idx, scores, losses)

        self.env.close()

    def test(self) -> List[np.ndarray]:
        """Test the agent."""
        self.is_test = True

        state = self.env.reset()
        done = False
        score = 0

        frames = []
        while not done:
            frames.append(self.env.render(mode="rgb_array"))
            action = self.select_action(state)
            next_state, reward, done = self.step(action)

            state = next_state
            score += reward

        print("score: ", score)
        self.env.close()

        return frames

    def _compute_dqn_loss(self, samples: Dict[str, np.ndarray]) -> torch.Tensor:
        """Return dqn loss."""
        device = self.device  # for shortening the following lines
        state = torch.FloatTensor(samples["obs"]).to(device)
        next_state = torch.FloatTensor(samples["next_obs"]).to(device)
        action = torch.LongTensor(samples["acts"].reshape(-1, 1)).to(device)
        reward = torch.FloatTensor(samples["rews"].reshape(-1, 1)).to(device)
        done = torch.FloatTensor(samples["done"].reshape(-1, 1)).to(device)

        # G_t   = r + gamma * v(s_{t+1})  if state != Terminal
        #       = r                       otherwise
        curr_q_value = self.dqn(state).gather(1, action)
        next_q_value = self.dqn_target(next_state).max(
            dim=1, keepdim=True
        )[0].detach()
        mask = 1 - done
        target = (reward + self.gamma * next_q_value * mask).to(self.device)

        # calculate dqn loss
        loss = F.smooth_l1_loss(curr_q_value, target)

        return loss

    def _target_hard_update(self):
        """Hard update: target <- local."""
        self.dqn_target.load_state_dict(self.dqn.state_dict())

    def _plot(
        self,
        frame_idx: int,
        scores: List[float],
        losses: List[float],
    ):
        """Plot the training progresses."""
        clear_output(True)
        plt.figure(figsize=(20, 5))
        plt.subplot(131)
        plt.title('frame %s. score: %s' % (frame_idx, np.mean(scores[-10:])))
        plt.plot(scores)
        plt.subplot(132)
        plt.title('loss')
        plt.plot(losses)
        plt.show()
```

## Environment

You can see the [code](https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py) and [configurations](https://github.com/openai/gym/blob/master/gym/envs/__init__.py#L53) of CartPole-v0 from OpenAI's repository.

```python
# environment
env_id = "CartPole-v0"
env = gym.make(env_id)
if IN_COLAB:
    env = gym.wrappers.Monitor(env, "videos", force=True)
```

## Set random seed

```python
seed = 777

def seed_torch(seed):
    torch.manual_seed(seed)
    if torch.backends.cudnn.enabled:
        torch.backends.cudnn.benchmark = False
        torch.backends.cudnn.deterministic = True

np.random.seed(seed)
seed_torch(seed)
env.seed(seed)
```

    [777]

## Initialize

```python
# parameters
num_frames = 20000
memory_size = 2000
batch_size = 64
target_update = 150

# train
agent = DQNAgent(env, memory_size, batch_size, target_update)
```

    cpu

## Train

```python
agent.train(num_frames)
```

## Test

Run the trained agent (1 episode).

```python
frames = agent.test()
```

    score:  200.0

## Render

```python
if IN_COLAB:  # for colab
    import base64
    import glob
    import io
    import os

    from IPython.display import HTML, display

    def ipython_show_video(path: str) -> None:
        """Show a video at `path` within IPython Notebook."""
        if not os.path.isfile(path):
            raise NameError("Cannot access: {}".format(path))

        video = io.open(path, "r+b").read()
        encoded = base64.b64encode(video)

        display(HTML(
            data="""

            """.format(encoded.decode("ascii"))
        ))

    list_of_files = glob.glob("videos/*.mp4")
    latest_file = max(list_of_files, key=os.path.getctime)
    print(latest_file)
    ipython_show_video(latest_file)

else:  # for jupyter
    from matplotlib import animation
    from JSAnimation.IPython_display import display_animation
    from IPython.display import display

    def display_frames_as_gif(frames: List[np.ndarray]) -> None:
        """Displays a list of frames as a gif, with controls."""
        patch = plt.imshow(frames[0])
        plt.axis('off')

        def animate(i):
            patch.set_data(frames[i])

        anim = animation.FuncAnimation(
            plt.gcf(), animate, frames = len(frames), interval=50
        )
        display(display_animation(anim, default_mode='loop'))

    # display
    display_frames_as_gif(frames)
```

    <input id="_anim_sliderAIQGJIPYORMKTILJ" type="range" style="width:350px" name="points" min="0" max="1" step="1" value="0" onchange="animAIQGJIPYORMKTILJ.set_frame(parseInt(this.value));"></input>

    <button onclick="animAIQGJIPYORMKTILJ.slower()">–</button>
    <button onclick="animAIQGJIPYORMKTILJ.first_frame()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.previous_frame()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.reverse_animation()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.pause_animation()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.play_animation()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.next_frame()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.last_frame()"></button>
    <button onclick="animAIQGJIPYORMKTILJ.faster()">+</button>
  <form action="#n" name="_anim_loop_selectAIQGJIPYORMKTILJ" class="anim_control">
    <input type="radio" name="state" value="once" > Once </input>
    <input type="radio" name="state" value="loop" checked> Loop </input>
    <input type="radio" name="state" value="reflect" > Reflect </input>
  </form>