###This IPython Notebook is for performing a fit and generating a figure of the spectrum of sample VG12, in the mesh region with 49+/-6 nm gap.  This version is modified to fit for two gaps.

The filename of the figure is **[TBD].pdf**.

Author: Michael Gully-Santiago, `gully@astro.as.utexas.edu`

Date: January 25, 2015

```python
%pylab inline
import emcee
import triangle
import pandas as pd
import seaborn as sns
from astroML.decorators import pickle_results
```

    Populating the interactive namespace from numpy and matplotlib

```python
sns.set_context("paper", font_scale=2.0, rc={"lines.linewidth": 2.5})
sns.set(style="ticks")
```

Read in the data.  We want "VG12"

```python
df = pd.read_csv('../data/cln_20130916_cary5000.csv', index_col=0)
df = df[df.index > 1250.0]
```

```python
plt.plot(df.index[::4], df.run11[::4]/100.0, label='On-mesh')
plt.plot(df.index, df.run10/100.0, label='Off-mesh')
plt.plot(df.index, df.run12/100.0, label='Shard2')
plt.plot(df.index, df.run9/100.0, label='DSP')
plt.plot(df.index, df.run15/100.0, label='VG08')
plt.plot(df.index, df.run17/100.0, label='VG08 alt')
#plt.plot(x, T_gap_Si_withFF_fast(x, 65.0, 0.5, n1)/T_DSP, label='Model')
plt.legend(loc='best')
plt.ylim(0.80, 1.05)
```

Import all the local models, saved locally as `etalon.py`.  See the paper for derivations of these equations.

```python
from etalon import *
np.random.seed(78704)
```

```python
# Introduce the Real data, decimate the data.
x = df.index.values[::4]
N = len(x)
# Define T_DSP for the model
T_DSP = T_gap_Si(x, 0.0)
n1 = sellmeier_Si(x)

# Define uncertainty
yerr = 0.0004*np.ones(N)
iid_cov = np.diag(yerr ** 2)

# Select the spectrum of interest
# Normalize the spectrum by measured DSP Si wafer.
y = df.run11.values[::4]/100.0
```

Define the likelihood.  In this case we are using two different gap sizes, but fixed fill factor.

\begin{equation}
	T_{mix} =  0.5 \times T_{e}(d_M + \epsilon) + 0.5 \times T_{e}(\epsilon)
\end{equation}

```python
def lnlike(dM, eps, lna, lns):
    a, s = np.exp(lna), np.exp(lns)
    off_diag_terms = a**2 * np.exp(-0.5 * (x[:, None] - x[None, :])**2 / s**2)
    C = iid_cov + off_diag_terms
    sgn, logdet = np.linalg.slogdet(C)
    if sgn <= 0:
        return -np.inf
    T_mix = 0.5 * (T_gap_Si_withFF_fast(x, dM+eps, 1.0, n1) + T_gap_Si_withFF_fast(x, eps, 1.0, n1))/T_DSP
    r = y - T_mix
    return -0.5 * (np.dot(r, np.linalg.solve(C, r)) + logdet)
```

Define the prior.  We want to put a Normal prior on $d_M$:
$d_M \sim \mathcal{N}(\hat{d_M}, \sigma_{d_M})$

```python
def lnprior(dM, eps, lna, lns):
    prior = -0.5 * ((49.0-dM)/6.0)**2.0
    if not (31.0 < dM < 67 and 0.0 < eps < 60.0 and -12 < lna < -2 and 0 < lns < 10):
        return -np.inf
    return prior
```

Combine likelihood and prior to obtain the posterior.

```python
def lnprob(p):
    lp = lnprior(*p)
    if not np.isfinite(lp):
        return -np.inf
    return lp + lnlike(*p)
```

Set up `emcee`.

```python
@pickle_results('SiGaps_12_VG12_twoGaps-sampler.pkl')
def hammer_time(ndim, nwalkers, dM_Guess, eps_Guess, a_Guess, s_Guess, nburnins, ntrials):

    # Initialize the walkers
    p0 = np.array([dM_Guess, eps_Guess, np.log(a_Guess), np.log(s_Guess)])
    pos = [p0 + 1.0e-2*p0 * np.random.randn(ndim) for i in range(nwalkers)]

    sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob)

    pos, lp, state = sampler.run_mcmc(pos, nburnins)
    sampler.reset()
    pos, lp, state = sampler.run_mcmc(pos, ntrials)
    return sampler
```

Set up the initial conditions

```python
np.random.seed(78704)
ndim, nwalkers = 4, 32
dM_Guess = 49.0
eps_Guess = 15.0
a_Guess = 0.0016
s_Guess = 25.0
nburnins = 200
ntrials = 700
```

Run the burn-in phase.  Run the full MCMC.  Pickle the results.

```python
sampler = hammer_time(ndim, nwalkers, dM_Guess, eps_Guess, a_Guess, s_Guess, nburnins, ntrials)
```

    @pickle_results: computing results and saving to 'SiGaps_12_VG12_twoGaps-sampler.pkl'
      warning: cache file 'SiGaps_12_VG12_twoGaps-sampler.pkl' exists
        - args match:   False
        - kwargs match: True

Linearize $a$ and $s$ for easy inspection of the values.

```python
chain = sampler.chain
samples_lin = copy(sampler.flatchain)
samples_lin[:, 2:] = np.exp(samples_lin[:, 2:])
```

Inspect the chain.

```python
fig, axes = plt.subplots(4, 1, figsize=(5, 6), sharex=True)
fig.subplots_adjust(left=0.1, bottom=0.1, right=0.96, top=0.98,
                    wspace=0.0, hspace=0.05)
[a.plot(np.arange(chain.shape[1]), chain[:, :, i].T, "k", alpha=0.5)
 for i, a in enumerate(axes)]
[a.set_ylabel("${0}$".format(l)) for a, l in zip(axes, ["d_M", "\epsilon", "\ln a", "\ln s"])]
axes[-1].set_xlim(0, chain.shape[1])
axes[-1].set_xlabel("iteration");
```

Linearize $a$ and $s$ for graphical purposes.

Make a triangle corner plot.

```python
fig = triangle.corner(samples_lin,
                      labels=map("${0}$".format, ["d_M", "\epsilon", "a", "s"]),
                      quantiles=[0.16, 0.84])
```

```python
fig = triangle.corner(samples_lin[:,0:2],
                      labels=map("${0}$".format, ["d_M", "\epsilon"]),
                      quantiles=[0.16, 0.84])
plt.savefig("VG12_twoGaps_cornerb.pdf")
```

Calculate confidence intervals.

```python
dM_mcmc, eps_mcmc, a_mcmc, s_mcmc = map(lambda v: (v[1], v[2]-v[1], v[1]-v[0]),
                             zip(*np.percentile(samples_lin, [16, 50, 84],
                                                axis=0)))
dM_mcmc, eps_mcmc, a_mcmc, s_mcmc
```

    ((50.610580678662942, 5.1769311919194294, 5.9127063792974255),
     (12.239520192129437, 4.5876370711722956, 4.3801270211579979),
     (0.0016357814253734524, 0.00044741010755174103, 0.00031281275486805585),
     (81.843785041902038, 9.9105375741532953, 9.3345864173309678))

```python
print "{:.0f}^{{+{:.0f}}}_{{-{:.0f}}}".format(*dM_mcmc)
print "{:.0f}^{{+{:.0f}}}_{{-{:.0f}}}".format(*eps_mcmc)
```

    51^{+5}_{-6}
    12^{+5}_{-4}

Overlay draws from the Gaussian Process.

```python
plt.figure(figsize=(6,3))

for dM, eps, a, s in samples_lin[np.random.randint(len(samples_lin), size=60)]:
    off_diag_terms = a**2 * np.exp(-0.5 * (x[:, None] - x[None, :])**2 / s**2)
    C = iid_cov + off_diag_terms
    fit = 0.5*(T_gap_Si_withFF_fast(x, dM+eps, 1.0, n1)+T_gap_Si_withFF_fast(x, eps, 1.0, n1))/T_DSP
    vec = np.random.multivariate_normal(fit, C)
    plt.plot(x, vec,"-b", alpha=0.06)

plt.step(x, y,color="k", label='Measurement')
fit = 0.5*(T_gap_Si_withFF_fast(x, dM_mcmc[0]+eps_mcmc[0], 1, n1)+T_gap_Si_withFF_fast(x, eps_mcmc[0], 1, n1))/T_DSP
fit_label = 'Model with $d_M={:.0f}$ nm, $\epsilon={:.0f}$'.format(dM_mcmc[0], eps_mcmc[0])
plt.plot(x, fit, '--', color=sns.xkcd_rgb["pale red"], alpha=1.0, label=fit_label)

fit1 = T_gap_Si_withFF_fast(x, 43, 0.5, n1)/T_DSP
fit2 = T_gap_Si_withFF_fast(x, 55, 0.5, n1)/T_DSP
fit2_label = 'Model with $d_M={:.0f}\pm{:.0f}$ nm, $\epsilon={:.0f}$'.format(49, 6, 0)
plt.fill_between(x, fit1, fit2, alpha=0.6, color=sns.xkcd_rgb["green apple"])
plt.plot([-10, -9], [-10, -9],"-", alpha=0.85, color=sns.xkcd_rgb["green apple"], label=fit2_label)

plt.plot([-10, -9], [-10, -9],"-b", alpha=0.85, label='Draws from GP')
plt.plot([0, 5000], [1.0, 1.0], '-.k', alpha=0.5)
plt.fill_between([1200, 1250], 2.0, 0.0, hatch='\\', alpha=0.4, color='k', label='Si absorption cutoff')

plt.xlabel('$\lambda$ (nm)');
plt.ylabel('$T_{gap}$');
plt.xlim(1200, 2501);
plt.ylim(0.9, 1.019);
plt.legend(loc='lower right')
plt.savefig("VG12_twoGapsb.pdf",  bbox_inches='tight')
```

The end.


# Introduction
多変量解析TMVA講習で学んだように、Machine Learningについての知識は、実験データを解析する上で欠かせないものになりつつあります。特に近年Deep learning(深層学習)が急速に発展してきており、実験物理分野でも用いられるようになりました。

Deep learningを扱うツールが整備されたことにより、誰でも簡単にDeep learningが使えるようになりました。一方で、その背後にある仕組みを知らないまま扱うと、落とし穴にハマることがあるかもしれません。

この講義では、Deep learningの基礎、特にDeep learningのベースとなるパーセプトロンを学んでいきます。式を追うだけでは理解しきれないことも多いと思うので、サンプルコードをぜひ自分の手で動かしてみてください。

## Jupyter notebookについて
この講義ではjupyter notebookを提供します。
数式と文章で解説をし、要所要所で式やアルゴリズムを理解するコードがpythonで書かれています。

## パッケージのインポート
まず始めに、必要なパッケージをインポートします。
numpyは多次元配列を効率的に行うパッケージで、matplotlibはプロット等を行うパッケージです。
"%matplotlib inline"はjupyter notebookでプロットを行う際に必要ですが、通常のpython scriptとしてコードを実行する際は必要ありません。

```python
#ライブラリのインポート
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
```

# パーセプトロン (Perceptron)
(単純)パーセプトロンは1957年に開発されたアルゴリズムで、分類問題を解くことができます。

分類問題とは、入力を複数のクラスにラベル付けすることです。物理実験分野ではシグナルとバックグラウンドの分類としてよく用いられます。

例えば、n個の入力変数$ \mathbf{x}=\{x_1, x_2,\dots,x_n\} $からクラス$ t\in\{0, 1\} $を推定する分類問題を考えると、入力がシグナル由来のものならば
$$ t = y(\mathbf{x}_S) = 1 $$
入力がバックグラウンド由来のものならば
$$t = y(\mathbf{x}_B) = 0 $$
となるような関数$y(\mathbf{x})$を作ることが目的になります。

パーセプトロンでは、関数を以下のように定義します。
$$
\begin{align*}
y(\mathbf{x}|\mathbf{w}, b) &= \Gamma(\mathbf{w}\cdot \mathbf{x} + b) \\
\Gamma(a) &= \begin{cases}
    1 & (a\geq0)\\
    0 & (a<0)
  \end{cases}
\end{align*}
$$
ここで、$ \mathbf{w},b $は関数のパラメータ、$ \Gamma $はステップ関数です。関数のパラメータを問題によって調整することで、分類問題を解くことができます。

## ANDゲート
まず初めに2変数( $x_1, x_2$ )を入力とするパーセプトロンを用いてANDゲートを実装してみましょう。

ANDゲートは

| $x_1$ | $x_2$ | AND |
| :-: | :-: | :-: |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |

のように、2つの入力変数がともに1のときのみ1を返し、それ以外のときは0を返すような関数です。
入力変数が2つなので、パーセプトロン関数を明示的に書くと、
$$
\begin{aligned}
y(x_1,x_2|w_1,w_2, b) &= \Gamma(w_1 x_1 + w_2 x_2 + b) \\
\Gamma(a) &= \begin{cases}
    1 & (a\geq0)\\
    0 & (a<0)
  \end{cases}
\end{aligned}
$$
となります。これをpythonで書くと以下のようになります。

```python
def ANDGate(x1, x2):
    w1 = 1.0  # パラメータの値は適当
    w2 = 1.0  # パラメータの値は適当
    b = -1.5  # パラメータの値は適当
    a = w1 * x1 + w2 * x2 + b
    if a >= 0.:
        return 1
    else:
        return 0

# 正しく実装できているかを出力
print("AND Gate:")
print("  x1, x2, y")
print("   0,  0, ", ANDGate(0, 0), "Correct" if ANDGate(0, 0) == 0 else "Wrong")  # 正解は 0
print("   0,  1, ", ANDGate(0, 1), "Correct" if ANDGate(0, 1) == 0 else "Wrong")  # 正解は 0
print("   1,  0, ", ANDGate(1, 0), "Correct" if ANDGate(1, 0) == 0 else "Wrong")  # 正解は 0
print("   1,  1, ", ANDGate(1, 1), "Correct" if ANDGate(1, 1) == 1 else "Wrong")  # 正解は 1
```

上の例ではパラメータが適当なため、正しいANDゲートにはなっていません。パラメータ($w_1, w_2, b$)をどのように設定すれば、この関数がANDゲートとなるでしょうか？

実は答えは一意には決まらず無数にあるのですが、例えば$(w_1, w_2, b) = (1, 1, -1.5)$とするとうまくいきます。確認してみてください。

パーセプトロンの値をプロットすると

となります。
赤い領域で$y=1$、青い領域で$y=0$を出力しています。
パーセプトロンの式の形からもわかるように、$(x_1, x_2)$平面上を直線($w_1 x_1 + w_2 x_2 + b=0$)で区切ったような出力となります。

## NANDゲート・ORゲート
NANDゲートやORゲートは

| $x_1$ | $x_2$ | NAND | OR |
| -- | -- | -- | -- |
| 0 | 0 | 1 | 0 |
| 0 | 1 | 1 | 1 |
| 1 | 0 | 1 | 1 |
| 1 | 1 | 0 | 1 |

のような関数です。各自どのようにパラメータを決めればよいか確認してみてください。

```python
def NANDGate(x1, x2):
    w1 = -1.0  # パラメータの値は適当
    w2 = -1.0  # パラメータの値は適当
    b = 1.5  # パラメータの値は適当
    a = w1 * x1 + w2 * x2 + b
    if a >= 0.:
        return 1
    else:
        return 0

def ORGate(x1, x2):
    w1 = 1.0  # パラメータの値は適当
    w2 = 1.0  # パラメータの値は適当
    b = -0.5  # パラメータの値は適当
    a = w1 * x1 + w2 * x2 + b
    if a >= 0.:
        return 1
    else:
        return 0

# 正しく実装できているかを出力
print("NAND Gate:")
print("  x1, x2, y")
print("   0,  0, ", NANDGate(0, 0), "Correct" if NANDGate(0, 0) == 1 else "Wrong")  # 正解は 1
print("   0,  1, ", NANDGate(0, 1), "Correct" if NANDGate(0, 1) == 1 else "Wrong")  # 正解は 1
print("   1,  0, ", NANDGate(1, 0), "Correct" if NANDGate(1, 0) == 1 else "Wrong")  # 正解は 1
print("   1,  1, ", NANDGate(1, 1), "Correct" if NANDGate(1, 1) == 0 else "Wrong")  # 正解は 0

print("OR Gate:")
print("  x1, x2, y")
print("   0,  0, ", ORGate(0, 0), "Correct" if ORGate(0, 0) == 0 else "Wrong")  # 正解は 0
print("   0,  1, ", ORGate(0, 1), "Correct" if ORGate(0, 1) == 1 else "Wrong")  # 正解は 1
print("   1,  0, ", ORGate(1, 0), "Correct" if ORGate(1, 0) == 1 else "Wrong")  # 正解は 1
print("   1,  1, ", ORGate(1, 1), "Correct" if ORGate(1, 1) == 1 else "Wrong")  # 正解は 1

```

## ロジスティック回帰 (Logistic regression)
これまで見てきた３つのゲート(AND,OR,NAND)は、出力が一意に定まる問題でした。一方で現実の問題のほとんどは一意にラベル付けはできません。例えば($H\rightarrow\gamma\gamma$)のように、観測された事象がシグナルであるのかバックグラウンドであるのかは確率的にしか評価できないことがほとんどです。

そのため、関数の出力を0 or 1とするのではなく、0から1まで連続的な値を取るように変更します。ここで出力は、クラスのラベル(S or B)ではなく、そのラベルを取る確率を表します。
出力として0 or 1を取るステップ関数の代わりによく使われる関数がシグモイド関数です。
$$ \sigma(x) = \frac{1}{1+e^{-x}} $$

$x\rightarrow \pm \infty$の極限では、ステップ関数もシグモイド関数も同じ値となりますが、x=0付近で、シグモイド関数はなめらかになっており、0から1まで連続的な値を取ることができます。また、x軸のスケールを変更すると、シグモイド関数はステップ関数に限りなく近づくという性質もあります。

パーセプトロンの式は変更されて、
$$
\begin{align*}
y(\mathbf{x}|\mathbf{w}, b) &= \sigma(\mathbf{w}\cdot \mathbf{x} + b) \\
\sigma(x) &= \frac{1}{1+e^{-x}}
\end{align*}
$$
となります。先程の式のステップ関数の部分がシグモイド関数に変わっています。
この関数を用いて分類・回帰をすることをロジスティック回帰(Logistic regression)と呼ぶこともありますし、パーセプトロンと呼ぶこともあります。ここではパーセプトロンと呼びます。

なお、このステップ関数やシグモイド関数に対応する部分を__活性化関数(activation function)__とも呼びます。

## 実例
この式を用いて、実際に問題を解いてみましょう。

次のセルはヘルパー関数です。データ点の作成や、プロットを行います。内容は完全に理解する必要はありませんが、もし余力があればコードを追ってみてください。

```python
# 中心値の異なる2つの二次元ガウス分布
def getDataset1():
    state = np.random.get_state()
    np.random.seed(0)  # 今回はデータセットの乱数を固定させます。

    nSignal = 100  # 生成するシグナルイベントの数
    nBackground = 100  # 生成するバックグラウンドイベントの数

    # データ点の生成
    # 平均(x1,x2) = (1.0, 0.0)、分散=1の２次元ガウス分布
    xS = np.random.multivariate_normal([1.0, 0.0], [[1, 0], [0, 1]], size=nSignal)
    tS = np.ones(nSignal)  # Signalは1にラベリング
    # 平均(x1,x2) = (-1.0, 0.0)、分散=1の２次元ガウス分布
    xB = np.random.multivariate_normal([-1.0, 0.0], [[1, 0], [0, 1]], size=nBackground)
    tB = np.zeros(nBackground)  # Backgroundは0にラベリング

    # 2つのラベルを持つ学習データを1つにまとめる
    x = np.concatenate([xS, xB])
    t = np.concatenate([tS, tB]).reshape(-1, 1)
    # データをランダムに並び替える
    p = np.random.permutation(len(x))
    x = x[p]
    t = t[p]

    np.random.set_state(state)

    return x, t

# 二次元ガウス分布と一様分布
def getDataset2():
    state = np.random.get_state()
    np.random.seed(0)  # 今回はデータセットの乱数を固定させます。

    nSignal = 100  # 生成するシグナルイベントの数
    nBackground = 1000  # 生成するバックグラウンドイベントの数

    # データ点の生成
    xS = np.random.multivariate_normal([1.0, 0], [[1, 0], [0, 1]], size=nSignal)  # 平均(x1,x2) = (1.0, 0.0)、分散=1の２次元ガウス分布
    tS = np.ones(nSignal)  # Signalは1にラベリング
    xB = np.random.uniform(low=-5, high=5, size=(nBackground, 2))  # (-5, +5)の一様分布
    tB = np.zeros(nBackground)  # Backgroundは0にラベリング

    # 2つのラベルを持つ学習データを1つにまとめる
    x = np.concatenate([xS, xB])
    t = np.concatenate([tS, tB]).reshape(-1, 1)
    # データをランダムに並び替える
    p = np.random.permutation(len(x))
    x = x[p]
    t = t[p]

    np.random.set_state(state)

    return x, t

# ラベル t={0,1}を持つデータ点のプロット
def plotDataPoint(x, t):
    # シグナル/バックグラウンドの抽出
    xS = x[t[:, 0] == 1]  # シグナルのラベルだけを抽出
    xB = x[t[:, 0] == 0]  # バックグラウンドのラベルだけを抽出

    # プロット
    plt.scatter(xS[:, 0], xS[:, 1], label='Signal', c='red', s=10)  # シグナルをプロット
    plt.scatter(xB[:, 0], xB[:, 1], label='Background', c='blue', s=10)  # バックグラウンドをプロット
    plt.xlabel('x1')  # x軸ラベルの設定
    plt.ylabel('x2')  # y軸ラベルの設定
    plt.legend()  # legendの表示
    plt.show()

# prediction関数 の等高線プロット (fill)
def PlotPredictionContour(prediction, *args):
    # 等高線を描くためのメッシュの生成
    x1, x2 = np.mgrid[-5:5:100j, -5:5:100j]  # x1 = (-5, 5), x2 = (-5, 5) の範囲で100点x100点のメッシュを作成
    x1 = x1.flatten()  # 二次元配列を一次元配列に変換 ( shape=(100, 100) => shape(10000, ))
    x2 = x2.flatten()  # 二次元配列を一次元配列に変換 ( shape=(100, 100) => shape(10000, ))
    x = np.array([x1, x2]).T

    #  関数predictionを使って入力xから出力yを計算し、等高線プロットを作成
    y = prediction(x, *args)
    cs = plt.tricontourf(x[:, 0], x[:, 1], y.flatten(), levels=10)
    plt.colorbar(cs)

# prediction関数 の等高線プロット (line)
def PlotPredictionContourLine(prediction, *args):
    # 等高線を描くためのメッシュの生成
    x1, x2 = np.mgrid[-5:5:100j, -5:5:100j]  # x1 = (-5, 5), x2 = (-5, 5) の範囲で100点x100点のメッシュを作成
    x1 = x1.flatten()  # 二次元配列を一次元配列に変換 ( shape=(100, 100) => shape(10000, ))
    x2 = x2.flatten()  # 二次元配列を一次元配列に変換 ( shape=(100, 100) => shape(10000, ))
    x = np.array([x1, x2]).T

    #  関数predictionを使って入力xから出力yを計算し、等高線プロットを作成
    y = prediction(x, *args)
    plt.tricontour(x[:, 0], x[:, 1], y.flatten(), levels=10)

def PlotPredictionRegression1D(x, t, prediction, *args):
    #  データ点のプロット
    plt.scatter(x, t, s=10, c='black')

    #  関数predictionの出力をプロット
    y = prediction(x, *args)
    plt.plot(x, y, c='red')

    # 中間層の各ノードの出力をプロット
    nNode = len(w2)
    for i in range(nNode):
        y = w2[i] * perceptron(x, w1[:, i], b1[:, i])
        plt.plot(x, y, linestyle='dashed')  # (中間層のノードの出力 * 重み)をプロット
    y = b2[0] * np.ones(x.shape)
    plt.plot(x, y, linestyle='dashed')  # 最後の層のバイアスタームのプロット
    plt.show()

```

上の関数を使ってデータ点をプロットしましょう。

```python
# データ点の取得
x, t = getDataset1()

# データ点をプロット
plotDataPoint(x, t)

```

このようなシグナル・バックグラウンドを分類することを考えます。この分類問題では、シグナルとバックグラウンドの分布が一部重なっています。したがって、そのような中間部分では出力0 or 1ではなく、そのイベントがシグナルである確率を返すように、シグモイド関数を使います。

パーセプトロンの式は

```python
def sigmoid(x):
    return 1. / (1 + np.exp(-x))

def perceptron(x1, x2, w1, w2, b):
    a = w1 * x1 + w2 * x2 + b
    return sigmoid(a)

```

となります。最後の部分だけがステップ関数からシグモイド関数に変わっています。
より一般的にnumpyのarrayを用いてベクトル的に書くと、

```python
def sigmoid(x):
    return 1. / (1 + np.exp(-x))

def perceptron(x, w, b):
    a = np.dot(x, w) + b  # w・x + b
    return sigmoid(a)

```

のようになります。ここで、x,wはベクトルで、np.dot(x,w)はxとwの内積を取る操作をします。

試しに、入力変数($x$)やパラメータ$(w,b)$を変化させてどのような値が返ってくるのか確認してみましょう。

```python
x = np.array([1.0, 2.0])
w = np.array([1.0, 1.0])
b = np.array([-1.5])
print(f"w1 * x1 + w2 * x2 + b = {w[0]} * {x[0]} + {w[1]} * {x[1]} + {b[0]}")

perceptron(x, w, b)

```

$$
\begin{align}
\mathbf{w}\cdot \mathbf{x} + b &=
\begin{pmatrix}
w_1 & w_2
\end{pmatrix}
\cdot
\begin{pmatrix}
x_1 \\
x_2
\end{pmatrix}
+ b \\
&=
\begin{pmatrix}
1.0 & 1.0
\end{pmatrix}
\cdot
\begin{pmatrix}
1.0 \\
2.0
\end{pmatrix}
-1.5  = 1.5 \\
\sigma(1.5) &= \frac{1}{1+e^{-1.5}} = 0.818
\end{align}
$$
を計算しています。

xには複数の入力を与えることもできます。xのshapeを(データセットの数, 変数の数)、wのshapeを(変数の数)とすれば、perceptronの式の np.dot(x, w) の返り値のshapeは(データセットの数, )となります。

例えば次のようにして、一回の計算で複数のデータ点を処理できます。

```python
x = np.array([
    [0.0, 0.0],  # event 1
    [0.0, 1.0],  # event 2
    [1.0, 0.0],  # event 3
    [1.0, 1.0],  # event 4
])  # shape = (4, 2)
w = np.array([1.0, 1.0])
b = np.array([-1.5])

perceptron(x, w, b)  # 返り値のshapeは(4,)

```

先程定義したヘルパー関数を用いて等高線プロットを作ると、

```python
# パーセプトロンのパラメータ
w = np.array([1.0, 1.0])
b = np.array([1.5])

# パーセプトロンの出力を等高線プロット
PlotPredictionContour(perceptron, w, b)

# データ点の取得
x, t = getDataset1()

# データ点をプロット
plotDataPoint(x, t)

```

となります。先程のステップ関数を使った例とは異なり、なめらかな出力をしていることがわかります。
パラメータの値を変化させて、パーセプトロンの出力がどのように変化するのか確かめてみましょう。
"最適な"パラメータの値は何になるでしょうか？

## パーセプトロンのグラフ表現
パーセプトロンの式をグラフで表すと視覚的にわかりやすく、また後々より複雑なモデルを作るときに、全体像を把握しやすくなります。
パーセプトロンの式は
$$
\begin{align*}
y(\mathbf{x}|\mathbf{w}, b) &= \sigma(\mathbf{w}\cdot \mathbf{x} + b)
\end{align*}
$$
でしたが、これをさらに分解して、各成分を明示的に書くと、
$$
\begin{align*}
a &= w_1 \cdot x_1 + w_2 \cdot x_2 + b \\
y &= \sigma(a)
\end{align*}
$$
となります。この式を

のように表現します。グラフにすることによって、入力変数($(x_1, x_2)$)が右に伝搬して出力($y$)までどのように計算されるのかがわかりやすくなります。

## 機械学習
これまでは手で関数のパラメータを決めてきましたが、問題が複雑になると、限界がきます。データからモデルのパラメータを自動で決めるのが、機械学習です。適切に学習させることで、人間が手でチューニングしたものよりも良い結果を得ることができます。

さて、この学習はどのようにして進めたら良いでしょうか？
最良の出力を得るパラメータとはどのようなものでしょうか？

ここでまた2クラス分類問題を考えます。n個の入力変数($\mathbf{x}=\{x_1, x_2,\dots,x_n\}$)からクラス($t\in\{0, 1\}$)を推定します。

ある観測値($ \mathbf{x}=\{x_1, x_2,\dots,x_n\}$)が得られた時、それがシグナルである確率を$y(\mathbf{x}|\mathbf{w}, b)$としていました(バックグラウンドである確率は$1-y(\mathbf{x}|\mathbf{w}, b)$)。ある観測値の集合が与えられ、それらに対してのラベルがわかっている時、パラメータが観測値を説明する確率は
$$
p(\mathbf{t}|\mathbf{w},b ) = \prod_{n=1}^{N} y_n^{t_n}\left(1-y_n \right)^{1-t_n}
$$
となります。この式では、$t_n = 1$の時$p_n = y(\mathbf{x}_n)$、$t_n = 0$の時$p_n=1-y(\mathbf{x}_n)$となります。

最尤法の考え方から、この確率を最大にするモデルパラメータを決定すれば良いことがわかります。負の対数を取ることで、
$$
E(\mathbf{w}, b) = -\log p(\mathbf{t}|\mathbf{w}, b) = -\sum_{n=1}^{N} \left( t_n \log y_n + \left( 1-t_n \right) \log \left( 1-y_n \right) \right)
$$
をパラメータ($\mathbf{w}, b$)に対して最小化する問題に帰着します。
機械学習において最小化する関数のことを__誤差関数(loss function)__と呼びます。
また、上のような関数をこの式を__交差エントロピー誤差関数(Cross entropy error function)__と呼びます。
この例では誤差関数として交差エントロピー誤差関数を使うということになります。

このままでは学習に使うデータ数によって誤差関数のスケールが変わってしまうので、使用するデータ数で規格化したものを実際には使います。
$$
E(\mathbf{w}, b) = - \frac{1}{N} \sum_{n=1}^{N} \left( t_n \log y_n + \left( 1-t_n \right) \log \left( 1-y_n \right) \right)
$$

ここでは2クラス分類を考えましたが、多クラス分類でも同様に考えることができます。
K個のクラス($t\in\{0, 1, \dots, K\}$)に分類するときは、尤度関数はn番目のイベントの出力のクラス$k$に対する出力
$$
p(\mathbf{t}|\mathbf{w},b ) = \prod_{n=1}^{N} \prod_{k=1}^{K} y_{nk}^{t_{nk}}
$$
となります。ここで$t_{nk}$はn番目のイベントのラベルで、クラスkの時に1,他のクラスのときに0となります(すなわち$\sum_{k=1}^{K}t_{nk}=1$です)。$y_{nk}$はn番目のイベントのラベルkに対するパーセプトロンの出力です。
同様に式変形すると、
$$
E(\mathbf{w}, b) = -\frac{1}{N} \sum_{n=1}^{N}\sum_{k=1}^{K} t_{nk} \log y_{nk}
$$
となります。この式も同様に交差エントロピー誤差関数(Cross entropy error function)と呼びます。

### 最急降下法 (Gradient descent)
パーセプトロンのパラメータを最適にする指針(誤差関数)は決めましたが、その最小化方法は自明ではありません。
よく用いられる手法は勾配法です。勾配法は、その名の通り、関数の勾配を用いて、その極小値を探索する手法です。
ここでは勾配法の最もシンプルな例である最急降下法(Gradient descent)を用いて誤差関数を最小化します。

最急降下法では、入力が$\mathbf{x}=(x_1,\dots,x_n)$の関数$f(\mathbf{x})$の最小値を、以下のように逐次的に求めます。
$$
\mathbf{x}^{(k+1)} = \mathbf{x}^{(k)} - \epsilon \cdot \left. \frac{\partial f}{\partial \mathbf{x}} \right|_{\mathbf{x}=\mathbf{x}^{(k)}}
$$
ここで$\epsilon$は1回あたりの更新の大きさを決めるパラメータで、値が小さいと、ゆっくり最小値を探します。
関数の一次微分($\left. \frac{\partial f}{\partial \mathbf{x}} \right|_{\mathbf{x}=\mathbf{x}^{(k)}}$)は点$\mathbf{x}^{(k)}$での傾きを表しており、(適切な$\epsilon$を選べば)必ず関数の値が小さくなるように更新されます。

簡単な例でこのアルゴリズムが正しく動作することを確かめてみます。
$f(x_1,x_2)=x_1^2 + x_2^2$を最小化する$(x_1, x_2)$を求めます。更新式は
$$
\begin{align*}
x_0 &\leftarrow x_0 - \epsilon \cdot (2\cdot x_1) \\
x_1 &\leftarrow x_1 - \epsilon \cdot (2\cdot x_2) \\
\end{align*}
$$
となります。

python codeでは

```python
# 初期値の設定
x1 = 4.0
x2 = 3.0

learning_rate = 0.1  # ステップ幅
num_steps = 10  # 繰り返し回数
for _ in range(num_steps):
    # 値の更新
    x1 = x1 - learning_rate * (2 * x1)
    x2 = x2 - learning_rate * (2 * x2)

```

のようにして値を更新していきます。learning_rateが$\epsilon$に対応するパラメータです。

これを実行し、パラメータ($x_1, x_2$)の値の推移をプロットしてみましょう。

```python
# 目的関数の等高線プロット
x1, x2 = np.mgrid[-5:5:100j, -5:5:100j]  # x1 = (-5, 5), x2 = (-5, 5) の範囲で100点x100点のメッシュを作成
y = np.square(x1) + np.square(x2)  # y = x_0^2 + x_1^2
plt.contour(x1, x2, y, linestyles='dashed', levels=5)

# 初期値の設定
x1 = 4.0
x2 = 3.0

plt.scatter(x1, x2, c='black')  # 初期値のプロット

# 最急降下法の実行
learning_rate = 0.1  # ステップ幅
num_steps = 10  # 繰り返し回数
for _ in range(num_steps):
    # 値の更新
    x1 = x1 - 2 * x1 * learning_rate
    x2 = x2 - 2 * x2 * learning_rate

    plt.scatter(x1, x2, edgecolors='black', facecolor='None')  # 更新値のプロット

plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim([-5, 5])
plt.ylim([-5, 5])
plt.show()

```

初期値(黒丸)から10回分値を更新しています。最小値を取る$(x_1, x_2)=(0, 0)$に近づいていることがわかります。
ステップ幅や更新回数を変更して変化を確認してみてください。
特にステップ幅の大きさはどの程度が適切となるでしょうか？

### 最急降下法を用いたパーセプトロンの学習
それではパーセプトロンのパラメータ($\mathbf{w}, b$)を交差エントロピー
$$
\begin{align*}
E(\mathbf{w}, b) &= \frac{1}{N} \sum_{n=1}^{N} E_n = -\frac{1}{N} \sum_{n=1}^{N} \left( t_n \log y_n + \left( 1-t_n \right) \log \left( 1-y_n \right) \right) \\
y(\mathbf{x}_n|\mathbf{w}, b) &= \sigma(\mathbf{w}\cdot \mathbf{x}_n + b)
\end{align*}
$$
を目的関数として、最急降下法を用いて最適化してみましょう。

誤差関数の微分は、
$$
\begin{align*}
\frac{\partial E_n}{\partial \mathbf{w}}
&= \frac{\partial E_n}{\partial  y_n}\cdot \frac{\partial  y_n}{\partial a} \cdot \frac{\partial  a}{\partial \mathbf{w}}\\
&= \left[ \frac{y_n-t_n}{y_n(1-y_n)}\right] \cdot \left[ y_n(1-y_n) \right] \cdot \left[ \mathbf{x_n} \right] \\
&= \left(y_n-t_n\right) \cdot \mathbf{x_n} \\
\frac{\partial E_n}{\partial b}
&= \left(y_n-t_n\right)
\end{align*}
$$
となります。ぜひ各自手で計算してみてください。
($\frac{\partial \sigma(x)}{\partial x} = \sigma \cdot (1 - \sigma)$となることを使いました。)

一次微分の式を使って最急降下法で誤差関数を最小化します。
更新式をpythonで書くと以下のようになります。

```python
# データ点の取得
x, t = getDataset1()

# 初期値の設定
w = np.array([2.0, 0.0])
b = np.array([0.1])

# 最急降下法での学習
learning_rate = 0.1  # ステップ幅
num_steps = 1000  # 繰り返し回数
for _ in range(num_steps):
    error = perceptron(x, w, b)[:, np.newaxis] - t  # y - t
    grad_W = error * x  # (y - t) * x
    grad_B = error * 1  # (y - t)

    w = w - learning_rate * grad_W.mean(axis=0)  # 全てのデータセットについての平均値を取る
    b = b - learning_rate * grad_B.mean(axis=0)  # 全てのデータセットについての平均値を取る

# プロット
PlotPredictionContour(perceptron, w, b)

## データ点をプロット
plotDataPoint(x, t)

```

## 単純パーセプトロンの限界
ここまで単純パーセプトロンが分類問題に有用であることを見てきました。しかし単純パーセプトロンは特定の問題においてしか有用でないことが知られています。例えば先程AND/OR/NAND回路を見てきましたが、XOR回路においてはどうなるでしょうか？

XOR回路は次のような真理値となります。

| $x_1$ | $x_2$ | XOR |
| -- | -- | -- |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |

これを単純パーセプトロン
$$
\begin{align*}
y(x_1,x_2|w_1,w_2, b) &= \Gamma(w_1 x_1 + w_2 x_2 + b) \\
\Gamma(a) &= \begin{cases}
    1 & (a\geq0)\\
    0 & (a<0)
  \end{cases}
\end{align*}
$$
で分類できるでしょうか？ 実はこの問題は分類ができません。式の形からわかるようにパーセプトロンでは線形に分類できる問題にしか対応できません。

もう一つの例として、一様分布するバックグラウンド上に、シグナルが二次元ガウス分布する状況を考えましょう。

```python
# データ点の取得
x, t = getDataset2()

# データ点をプロット
plotDataPoint(x, t)

```

このような例を、単純パーセプトロンで分類させようとしても、適切な予測ができません。

```python
# データ点の取得
x, t = getDataset2()

# 初期値の設定
w = np.array([0.1, 0.0])
b = np.array([0.0])

# 最急降下法での学習
learning_rate = 0.1  # ステップ幅
num_steps = 1000  # 繰り返し回数
for _ in range(num_steps):
    error = perceptron(x, w, b)[:, np.newaxis] - t  # y - t
    grad_W = error * x  # (y - t) * x
    grad_B = error * 1  # (y - t) * 1

    w = w - learning_rate * grad_W.mean(axis=0)  # 全てのデータセットについての平均値を取る
    b = b - learning_rate * grad_B.mean(axis=0)  # 全てのデータセットについての平均値を取る

# 等高線プロット
PlotPredictionContour(perceptron, w, b)

# データ点をプロット
plotDataPoint(x, t)

```

# 多層パーセプトロン(Multilayer perceptron)
単純パーセプトロンはXORゲートが表現できませんでした。この問題は層の数を増やす(多層にする)ことで解決することができます。単純パーセプトロンを多層にしたものを多層パーセプトロン(Multilayer perceptron)と呼びます。

グラフとして表すと、次の図のようになります。

数式で表すと、
$$
\begin{align*}
\mathbf{z}^{(1)} &= \sigma(\mathbf{w}^{(1)}\cdot \mathbf{x} + b^{(1)}) \\
y(\mathbf{x}|\mathbf{w}^{(1)}, b^{(1)},\mathbf{w}^{(2)}, b^{(2)}) &= \sigma(\mathbf{w}^{(2)}\cdot \mathbf{z}^{(1)} + b^{(2)})
\end{align*}
$$
のようになります。単純パーセプトロンの出力ノードを、新たに入力として使ったような形になっています。

活性化関数としてステップ関数を用いて、多層パーセプトロンがXORゲートを表現できることを示します。

## 例1: XORゲート
NANDゲート・ORゲート・ANDゲートは単純パーセプトロンで構成できることを先程示しました。
XORはNANDゲート・ORゲート・ANDゲートを組み合わせることで作ることができます。

NANDゲートの出力を$z_1$、ORゲートの出力を$z_2$とし、$z_1$、$z_2$を入力として受け取るANDゲートを考えると、表のようになります。

| $x_1$ | $x_2$ | $z_1$ | $z_2$ | $y$ |
| -- | -- | -- | -- | -- |
| 0 | 0 | 1 | 0 | 0 |
| 0 | 1 | 1 | 1 | 1 |
| 1 | 0 | 1 | 1 | 1 |
| 1 | 1 | 0 | 1 | 0 |

一例ですが、数式で明示的に書くと、
$$
\begin{align*}
z_1(x_1, x_2) &= \Gamma(-(x_1 + x_2 + 1.5)) \ \text{(NANDゲート)} \\
z_2(x_1, x_2) &= \Gamma(x_1 + x_2 + 0.5) \ \text{(ORゲート)} \\
y(z_1, z_2) &= \Gamma(z_1 + z_2 + 1.5) \ \text{(ANDゲート)}
\end{align*}
$$
のようにしてXORゲートを構成することができます。

この構成を視覚的に表したのが以下の図です。

|ORゲート|NANDゲート|ANDゲート|XORゲート|
|:---:|:---:|:---:|:---:|
||||||

１層のパーセプトロンでは、直線で領域分割することしかできませんでしたが、複数のパーセプトロンを組み合わせることでより複雑な領域の指定が可能になっていることがわかります。

```python
def XORGate(x1, x2):
    # OR Gate
    w11 = 0.0  # パラメータの値は適当
    w12 = 0.0  # パラメータの値は適当
    b1 = 0.0  # パラメータの値は適当
    a1 = w11 * x1 + w12 * x2 + b1
    a1 = 1 if a1 > 0 else 0

    # NAND Gate
    w21 = 0.0  # パラメータの値は適当
    w22 = 0.0  # パラメータの値は適当
    b2 = 0.0  # パラメータの値は適当
    a2 = w21 * x1 + w22 * x2 + b2
    a2 = 1 if a2 > 0 else 0

    # AND Gate
    w31 = 0.0  # パラメータの値は適当
    w32 = 0.0  # パラメータの値は適当
    b3 = 0.0  # パラメータの値は適当
    y = w31 * a1 + w32 * a2 + b3
    y = 1 if y > 0 else 0

    return y

# 正しく実装できているかを出力
print("XOR Gate:")
print("  x1, x2, y")
print("   0,  0, ", XORGate(0, 0), "Correct" if XORGate(0, 0) == 0 else "Wrong")  # 正解は 0
print("   0,  1, ", XORGate(0, 1), "Correct" if XORGate(0, 1) == 1 else "Wrong")  # 正解は 1
print("   1,  0, ", XORGate(1, 0), "Correct" if XORGate(1, 0) == 1 else "Wrong")  # 正解は 1
print("   1,  1, ", XORGate(1, 1), "Correct" if XORGate(1, 1) == 0 else "Wrong")  # 正解は 0

```

## 例2: 二次元ガウシアン
単純パーセプトロンでは分類できなかった以下のようなデータセット

```python
# データ点の取得
x, t = getDataset2()

# データ点をプロット
plotDataPoint(x, t)

```

を多層パーセプトロンで分類してみましょう。

数式としては、
$$
\begin{align*}
a_j^{(1)} &= w_{ij}^{(1)}\cdot x_{i} + b^{(1)}\\
z_j^{(1)} &= \sigma(a_j^{(1)}) \\
a_j^{(2)} &= w_{ij}^{(2)}\cdot x_{i} + b^{(2)}\\
y         &= \sigma(a^{(2)})
\end{align*}
$$
となります。

多層パーセプトロンでは、中間層のノードの数は自由に変えることができます。ここではノード数を3としてどのような予測がされるかを見てみましょう。

```python
# データ点の取得
x, t = getDataset2()

# パーセプトロンの定義
def multilayerPerceptron(x, w1, b1, w2, b2):
    a1 = np.dot(x, w1) + b1  # shape=(nEvents, 3)
    z1 = sigmoid(a1)  # shape=(nEvents, 3)
    a2 = np.dot(z1, w2) + b2  # shape=(nEvents, 1)
    y = sigmoid(a2)  # shape=(nEvents, 1)
    return y

# パーセプトロンのパラメータ (正規分布で初期化)
w1 = np.random.randn(2, 3)  # shape=(2, 3)
b1 = np.random.randn(1, 3)  # shape=(1, 3)
w2 = np.random.randn(3, 1)  # shape=(3, 1)
b2 = np.random.randn(1, 1)  # shape=(1, 1)

# 多層パーセプトロンの出力をプロット
PlotPredictionContour(multilayerPerceptron, w1, b1, w2, b2)

## データ点をプロット
plotDataPoint(x, t)

```

中間層のノードの数や、パラメータの値を変えてみて、どのように予測が変化するか見てみましょう。

```python
# データ点の取得
x, t = getDataset2()

# パーセプトロンの定義
def multilayerPerceptron(x, w1, b1, w2, b2):
    a1 = np.dot(x, w1) + b1  # shape=(nEvents, 3)
    z1 = sigmoid(a1)  # shape=(nEvents, 3)
    a2 = np.dot(z1, w2) + b2  # shape=(nEvents, 1)
    y = sigmoid(a2)  # shape=(nEvents, 1)
    return y

# パーセプトロンのパラメータ
num_z = 3  # 中間層のノード数
w1 = np.zeros((2, num_z))  # shape=(2, nz)
b1 = np.zeros((1, num_z))  # shape=(1, nz)
w2 = np.zeros((num_z, 1))  # shape=(nz, 1)
b2 = np.zeros((1, 1))  # shape=(1, 1)

# z_0^{(1)}に関するパラメータ
w1[0][0] = -1.0  # 1層目の0番目のノードと2層目の0番目のノードをつなぐ重みを変更
w1[1][0] = +0.0  # 1層目の1番目のノードと2層目の0番目のノードをつなぐ重みを変更
b1[0][0] = +3.0  # 2層目の0番目のノードのバイアスを変更

# z_1^{(1)}に関するパラメータ
w1[0][1] = +0.0  # 1層目の0番目のノードと2層目の1番目のノードをつなぐ重みを変更
w1[1][1] = +1.0  # 1層目の1番目のノードと2層目の1番目のノードをつなぐ重みを変更
b1[0][1] = +2.0  # 2層目の1番目のノードのバイアスを変更

# z_2^{(1)}に関するパラメータ
w1[0][2] = +1.0  # 1層目の0番目のノードと2層目の2番目のノードをつなぐ重みを変更
w1[1][2] = +1.0  # 1層目の1番目のノードと2層目の2番目のノードをつなぐ重みを変更
b1[0][2] = +2.0  # 2層目の2番目のノードのバイアスを変更

# z_i^{(1)}から出力(y)を作るパラメータ
w2[0][0] = +1.0  # 2層目の0番目のノードと3層目の0番目のノードをつなぐ重みを変更
w2[1][0] = +1.0  # 2層目の1番目のノードと3層目の0番目のノードをつなぐ重みを変更
w2[2][0] = +1.0  # 2層目の2番目のノードと3層目の0番目のノードをつなぐ重みを変更
b2[0][0] = -3.0  # 3層目の0番目のノードのバイアスを変更

# プロット
PlotPredictionContour(multilayerPerceptron, w1, b1, w2, b2)

## データ点をプロット
plotDataPoint(x, t)

```

## 多層パーセプトロンの学習 (誤差逆伝搬)
先程の単純パーセプトロンでは、誤差関数の一次微分を手計算で求めました。
層の数を増やした多層パーセプトロンでは、一次微分を手計算するのは困難になります。
微分の計算を効率よく行える手法として誤差逆伝搬法があります。
ここでは、中間層が2層の多層パーセプトロンを例に、その手法について解説します。

ここでは簡単のためバイアスの項を省略します(バイアスが入った場合についても簡単に拡張できます)。
ネットワークを表す式は、
$$
\begin{align*}
a_j^{(1)} &= w_{ij}^{(1)}\cdot x_{i}\\
z_j^{(1)} &= \sigma(a_j^{(1)}) \\
a_j^{(2)} &= w_{ij}^{(2)}\cdot z_{i}^{(1)}\\
z_j^{(2)} &= \sigma(a_j^{(2)}) \\
a_j^{(3)} &= w_{ij}^{(3)}\cdot z_{i}^{(2)}\\
y           &= \sigma(a^{(3)})
\end{align*}
$$
となります。

求めたいのは、誤差関数に対する各パラメータの一次微分です。すなわち
$\frac{\partial E}{\partial w_{ij}^{(1)}}$、$\frac{\partial E}{\partial w_{ij}^{(2)}}$、$\frac{\partial E}{\partial w_{ij}^{(3)}}$となります。

これらは連鎖率を使って求めることができます。
誤差関数は
$$
E(\mathbf{w}) = \frac{1}{N} \sum_{n=1}^{N} E_n(\mathbf{w}) = -\frac{1}{N} \sum_{n=1}^{N} \left( t_n \log y_n + \left( 1-t_n \right) \log \left( 1-y_n \right) \right)
$$
でした。
また、
$$
\delta_{i}^{(k)} = \frac{\partial E_n}{\partial a_{i}^{(k)}}
$$
として定義して、それぞれ計算をすると、

$$
\begin{align*}
\frac{\partial E_n}{\partial w_{ij}^{(3)}}
&= \frac{\partial E_n}{\partial  a_1^{(3)}}\cdot \frac{\partial  a_1^{(3)}}{\partial w_{ij}^{(3)}} \\
&= \left(y_n-t_n\right) \cdot z_1^{(2)} = \delta_{1}^{(3)} \cdot z_1^{(2)}
\end{align*}
$$

$$
\begin{align*}
\frac{\partial E_n}{\partial w_{ij}^{(2)}}
&= \frac{\partial E_n}{\partial  a_j^{(2)}}\cdot \frac{\partial  a_j^{(2)}}{\partial w_{ij}^{(2)}} \\
&= \left[ \frac{\partial E_n}{\partial  a_1^{(3)}}\cdot \frac{\partial a_1^{(3)}}{\partial  z_j^{(2)}}\cdot \frac{\partial z_j^{(2)}}{\partial  a_j^{(2)}}\right] \cdot \frac{\partial  a_j^{(2)}}{\partial w_{ij}^{(2)}} \\
&= \left[ \delta_{1}^{(3)}
\cdot w_{j1}^{(3)}
\cdot \sigma^{'}(a_j^{(2)}) \right]
\cdot z_{i}^{(1)}  = \delta_{j}^{(2)} \cdot z_{i}^{(1)}
\end{align*}
$$

$$
\begin{align*}
\frac{\partial E_n}{\partial w_{ij}^{(1)}}
&= \frac{\partial E_n}{\partial  a_j^{(1)}}\cdot \frac{\partial  a_j^{(1)}}{\partial w_{ij}^{(1)}} \\
&= \left[ \sum_k \frac{\partial E_n}{\partial  a_{k}^{(2)}}\cdot \frac{\partial a_{k}^{(2)}}{\partial  z_j^{(1)}}\cdot \frac{\partial z_j^{(1)}}{\partial  a_j^{(1)}}\right] \cdot \frac{\partial  a_j^{(1)}}{\partial w_{ij}^{(1)}} \\
&= \left[ \sum_k \delta_{k}^{(2)}
\cdot w_{jk}^{(2)}
\cdot \sigma^{'}(a_j^{(1)}) \right]
\cdot x_{i}  = \delta_{j}^{(1)} \cdot x_{i}
\end{align*}
$$

ここで、入力に近い方にある重みの計算では、それよりも後にある重みの計算に使用した値が再利用することができることがわかります。
このことを利用すると、複雑なネットワークの微分の計算がシステマティックに行えることになります。

実際に計算する際には、下流側から順に$\delta_{i}^{(k)}$を求めていきます。
$\delta_{i}^{(k)}$は活性化関数(ここではシグモイド関数)の微分値に、下流の誤差($\delta_{j}^{(k+1)}$)の線形和をとったものとなります。
$$
\delta_{i}^{(k)} =
\sigma^{'}(a_i^{(k)}) \left(
\sum_j w_{ij}^{(k+1)} \cdot \delta_{j}^{(k+1)} \right)
$$
重み($w_{ij}^{(k)}$)の微分はつながっているノードの$\delta_{i}^{(k)}$と$z_{i}^{(k)}$を用いて、
$$
\frac{\partial E_n}{\partial w_{ij}^{(k)}}  = \delta_{j}^{(k)} \cdot z_{i}^{(k)}
$$
として求めることができます。

このようにして微分を求めることを誤差逆伝搬(Back-propagation)と呼びます。

この誤差逆伝搬の式を先程の中間層1層の多層パーセプトロンに適用してみましょう。

次のような手順で一次微分を求めることができます。

1. 入力($x_i$)とパラメータ($w_{ij}^{(1)},b_j^{(1)},w_{ij}^{(2)},b_j^{(2)}$)を元に順伝搬させて予測値$y$を得る。
    $$
    \begin{align*}
    a_j^{(1)} &= w_{ij}^{(1)}\cdot x_{i} + b^{(1)}\\
    z_j^{(1)} &= \sigma(a_j^{(1)}) \\
    a_j^{(2)} &= w_{ij}^{(2)}\cdot x_{i} + b^{(2)}\\
    y         &= \sigma(a^{(2)})
    \end{align*} $$
2. 出力$y$と真のラベル$t$を用いて誤差$\delta$を伝搬させる。
    $$
    \begin{align*}
    \delta_{1}^{(2)} &= \left(y_n-t_n\right) \\
    \delta_{j}^{(1)} &= \sigma^{'}(a_j^{(1)}) \cdot w_{j1}^{(2)}  \cdot  \delta_{1}^{(2)}
    \end{align*} $$
3. 誤差$\delta$を元に微分を計算
    $$
    \begin{align*}
    \frac{\partial E_n}{\partial w_{ij}^{(2)}} &= \delta_{j}^{(2)} \cdot z_{i}^{(2)} \\
    \frac{\partial E_n}{\partial b_{j}^{(2)}}  &= \delta_{j}^{(2)} \\
    \frac{\partial E_n}{\partial w_{ij}^{(1)}} &= \delta_{j}^{(1)} \cdot x_{i} \\
    \frac{\partial E_n}{\partial b_{j}^{(1)}}  &= \delta_{j}^{(1)} \\
    \end{align*} $$

誤差逆伝搬法をpythonで実装したものが以下になります。

```python

# シグモイド関数の微分
def grad_sigmoid(x):
    return sigmoid(x) * (1 - sigmoid(x))

# データ点の取得
x, t = getDataset2()

# パーセプトロンのパラメータ
w1 = np.random.randn(2, 3)  # shape=(2, 3)
b1 = np.random.randn(1, 3)  # shape=(1, 3)
w2 = np.random.randn(3, 1)  # shape=(3, 1)
b2 = np.random.randn(1, 1)  # shape=(1, 1)

# 最急降下法での学習
learning_rate = 1.0  # ステップ幅
num_steps = 2 # 2000  # 繰り返し回数
for _ in range(num_steps):
    # 順伝搬させる
    a1 = np.dot(x, w1) + b1  # shape=(nEvents, 3)
    z1 = sigmoid(a1)  # shape=(nEvents, 3)
    a2 = np.dot(z1, w2) + b2  # shape=(nEvents, 1)
    z2 = sigmoid(a2)  # shape=(nEvents, 1)

    # 一次微分を求めるために逆伝搬させる
    grad_A2 = z2 - t  # shape=(nEvents, 1)
    grad_W2 = (z1 * grad_A2)[:, :, np.newaxis]  # shape=(nEvents, 3, 1)
    grad_B2 = grad_A2[:, :, np.newaxis]  # shape=(nEvents, 1, 1)

    grad_A1 = grad_sigmoid(a1) * (grad_A2 * w2.T)  # shape=(nEvents, 3)
    grad_W1 = x[:, :, np.newaxis] * grad_A1[:, np.newaxis, :]  # shape=(nEvents, 2, 3)
    grad_B1 = grad_A1[:, np.newaxis, :]  # shape=(nEvents, 1, 3)

    w2 = w2 - learning_rate * grad_W2.mean(axis=0)  # shape=(3, 1)
    b2 = b2 - learning_rate * grad_B2.mean(axis=0)  # shape=(1, 1)
    w1 = w1 - learning_rate * grad_W1.mean(axis=0)  # shape=(2, 3)
    b1 = b1 - learning_rate * grad_B1.mean(axis=0)  # shape=(1, 3)

# プロット
## パーセプトロンの出力を等高線プロット
PlotPredictionContour(multilayerPerceptron, w1, b1, w2, b2)

## データ点をプロット
plotDataPoint(x, t)

```

単純パーセプトロンでは、ある直線に対してどの程度離れているか、という指標でしか分類が行えていませんでしたが、多層パーセプトロンでは複数の直線からの距離を元に推定が行われています。

上の学習で得られたパラメータを使って、実際に中間層でどのような分類が行われているのかを見てみます。
図の$z_{i}^{(1)}$に対応するノードの出力をプロットします。

```python
# パーセプトロンの出力を等高線プロット
# 重みが(w1, b1)の1層パーセプトロンの出力を描画
PlotPredictionContourLine(perceptron, w1[:, 0], b1[:, 0])  # 中間層の0番目のノードの出力
PlotPredictionContourLine(perceptron, w1[:, 1], b1[:, 1])  # 中間層の1番目のノードの出力
PlotPredictionContourLine(perceptron, w1[:, 2], b1[:, 2])  # 中間層の2番目のノードの出力

# データ点をプロット
plotDataPoint(x, t)

```

このように、複数の(ここでは3つの)直線での分類を組み合わせて、2次元円形の領域を抜き出しています。
隠れ層の存在により、複雑な関数を近似できることがわかりました。

## 万能近似定理
ここまでは分類問題を考えてきました。ここでは回帰問題を考えて、多層パーセプトロンの表現能力を確認します。

分類問題では、パーセプトロンの出力を確率とみなすため、0~1の範囲を取るように、シグモイド関数を使いました。
回帰問題では任意の範囲の出力を取らせるようにします。よく使うのは、最後の活性化関数として線形関数(すなわち何もしない)を使うことです。
式として表すと、
$$
\begin{align*}
a_j^{(1)} &= w_{j}^{(1)}\cdot x + b^{(1)}\\
z_j^{(1)} &= \sigma(a_j^{(1)}) \\
a^{(2)} &= w_{i}^{(2)}\cdot z_{i} + b^{(2)}\\
y         &= a^{(2)}
\end{align*}
$$
となります。最後の行がシグモイド関数から、恒等写像に変わっています。グラフとして表すと、

のようになります。

```python
# パーセプトロンの定義 (回帰)
def multilayerPerceptronRegression(x, w1, b1, w2, b2):
    a1 = np.dot(x, w1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, w2) + b2
    y = a2
    return y

```

入力として1次元の関数を使ってその表現能力を見てみます

誤差関数としては予測値と真の値との差の2乗和(二乗和誤差(mean squared error))を使います。すなわち、パラメータが観測値を説明する確率は
$$
p(\mathbf{t}|\mathbf{w},b ) = \prod_{n=1}^{N} \frac{1}{\sqrt{2\pi}\sigma} e^{-\frac{(y_n-t_n)^2}{2\sigma^2}}
$$
であり、最小化する誤差関数は
$$
E(\mathbf{w}, b) = \frac{1}{N} \sum_{n=1}^{N} E_n(\mathbf{w}, b) = \frac{1}{N} \sum_{n=1}^{N} \frac{1}{2} \left( y_n - t_n \right)^2
$$
誤差関数の微分は
$$
\begin{align*}
\frac{\partial E_n}{\partial  a_1^{(k)}}
&= \frac{\partial E_n}{\partial  y} \frac{\partial y}{\partial  a_1^{(k)}} \\
&= \left. y_n-t_n \right.
\end{align*}
$$
となり、シグモイド関数+クロスエントロピーのときと似たような式になります。

### 二次曲線

```python
nSample = 20

# x=(-1, 1), t=x^2
x = np.linspace(-1, 1, nSample)[:, np.newaxis]
t = x * x

# パーセプトロンのパラメータ
nNode = 2  # 中間層のノード数
w1 = np.random.randn(1, nNode)  # shape=(1, nNode)
b1 = np.random.randn(1, nNode)  # shape=(1, nNode)
w2 = np.random.randn(nNode, 1)  # shape=(nNode, 1)
b2 = np.random.randn(1, 1)  # shape=(1, 1)

# うまく最適化されないときは以下で初期化してください
# w1 = np.array([[+2.79, -2.791]])
# b1 = np.array([[-2.80, -2.801]])
# w2 = np.array([[2.55], [2.551]])
# b2 = np.array([[-0.29]])

# 最急降下法での学習
learning_rate = 1.0  # ステップ幅
num_steps = 10000  # 繰り返し回数
for _ in range(num_steps):
    # 順伝搬させる
    a1 = np.dot(x, w1) + b1  # shape=(nEvents, 3)
    z1 = sigmoid(a1)  # shape=(nEvents, 3)
    a2 = np.dot(z1, w2) + b2  # shape=(nEvents, 1)
    z2 = a2  # shape=(nEvents, 1)

    # 一次微分を求めるために逆伝搬させる
    grad_A2 = z2 - t  # shape=(nEvents, 1)
    grad_W2 = (z1 * grad_A2)[:, :, np.newaxis]  # shape=(nEvents, nNode, 1)
    grad_B2 = grad_A2[:, :, np.newaxis]  # shape=(nEvents, 1, 1)

    grad_A1 = grad_sigmoid(a1) * (grad_A2 * w2.T)  # shape=(nEvents, nNode)
    grad_W1 = x[:, :, np.newaxis] * grad_A1[:, np.newaxis, :]  # shape=(nEvents, 2, nNode)
    grad_B1 = grad_A1[:, np.newaxis, :]  # shape=(nEvents, 1, nNode)

    w2 = w2 - learning_rate * grad_W2.mean(axis=0)  # shape=(nNode, 1)
    b2 = b2 - learning_rate * grad_B2.mean(axis=0)  # shape=(1, 1)
    w1 = w1 - learning_rate * grad_W1.mean(axis=0)  # shape=(2, nNode)
    b1 = b1 - learning_rate * grad_B1.mean(axis=0)  # shape=(1, nNode)

# プロット
PlotPredictionRegression1D(x, t, multilayerPerceptronRegression, w1, b1, w2, b2)

```

黒点がデータ点、赤線が多層パーセプトロンによる予測値を表しています。
点線はパーセプトロンの中間層の各ノードにおける出力を表していて、点線の和が最後の出力(赤線)となります。

二次関数($y=x^2$)は中間層が2層の多層パーセプトロンでおおよそ近似できていることがわかります。

### Sin(x)

```python
nSample = 20

# x=(-pi, pi), t=sin(x)
x = np.linspace(-np.pi, np.pi, nSample)[:, np.newaxis]
t = np.sin(x)

# パーセプトロンのパラメータ
nNode = 4  # 中間層のノード数
w1 = np.random.randn(1, nNode)  # shape=(1, nNode)
b1 = np.random.randn(1, nNode)  # shape=(1, nNode)
w2 = np.random.randn(nNode, 1)  # shape=(nNode, 1)
b2 = np.random.randn(1, 1)  # shape=(1, 1)

# うまく最適化されないときは以下で初期化してください
# w1 = np.array([[+1.4, -1.4, 1.6, -1.8]])
# b1 = np.array([[-4.2, -4.2, -0.8, -1.2]])
# w2 = np.array([[-3.0], [3.0], [2.0], [-1.3]])
# b2 = np.array([[-0.3]])

# 最急降下法での学習
learning_rate = 1.0  # ステップ幅
num_steps = 10000  # 繰り返し回数
for _ in range(num_steps):
    # 順伝搬させる
    a1 = np.dot(x, w1) + b1  # shape=(nEvents, 3)
    z1 = sigmoid(a1)  # shape=(nEvents, 3)
    a2 = np.dot(z1, w2) + b2  # shape=(nEvents, 1)
    z2 = a2  # shape=(nEvents, 1)

    # 一次微分を求めるために逆伝搬させる
    grad_A2 = z2 - t  # shape=(nEvents, 1)
    grad_W2 = (z1 * grad_A2)[:, :, np.newaxis]  # shape=(nEvents, nNode, 1)
    grad_B2 = grad_A2[:, :, np.newaxis]  # shape=(nEvents, 1, 1)

    grad_A1 = grad_sigmoid(a1) * (grad_A2 * w2.T)  # shape=(nEvents, nNode)
    grad_W1 = x[:, :, np.newaxis] * grad_A1[:, np.newaxis, :]  # shape=(nEvents, 2, nNode)
    grad_B1 = grad_A1[:, np.newaxis, :]  # shape=(nEvents, 1, nNode)

    w2 = w2 - learning_rate * grad_W2.mean(axis=0)  # shape=(nNode, 1)
    b2 = b2 - learning_rate * grad_B2.mean(axis=0)  # shape=(1, 1)
    w1 = w1 - learning_rate * grad_W1.mean(axis=0)  # shape=(2, nNode)
    b1 = b1 - learning_rate * grad_B1.mean(axis=0)  # shape=(1, nNode)

# プロット
PlotPredictionRegression1D(x, t, multilayerPerceptronRegression, w1, b1, w2, b2)

```

sinカーブも中間層のノード数4の多層パーセプトロンで近似することができました。

### 矩形関数
より一般的な関数もノードの数を増やすことで任意の精度で近似できることが知られています。
ここでは直感的にそれを確かめてみます。

多層パーセプトロンは以下のような関数を近似できます。
$$
\begin{align*}
y(x) &= \begin{cases}
    1 & (-0.1 < a < 0.1)\\
    0 & (\text{otherwise})
  \end{cases}
\end{align*}
$$

```python
nSample = 100

x = np.linspace(-1, 1, nSample).reshape(-1, 1)
t = np.where(np.logical_and(-0.1 < x, x < 0.1), 1, 0)

# パーセプトロンのパラメータ
nNode = 2
w1 = np.array([[200, 200]])
b1 = np.array([[-20, +20]])
w2 = np.array([[-1], [1]])
b2 = np.array([[0.]])

# プロット
PlotPredictionRegression1D(x, t, multilayerPerceptronRegression, w1, b1, w2, b2)

```

この関数を複数用意することで任意の関数が近似できます。

# Keras
はじめに述べたように、最近はDeep learningが簡単に扱えるツールが多々あります。
ここではKerasというパッケージを使ってこれまで行ってきた学習を行います。
KerasはtensorflowやTheanoといったDeep learningパッケージを簡単に扱うようにするためのラッパーで、実際には裏ではTensorflow等のライブラリが動いています。

Kerasの詳細な使い方については[公式ドキュメント](https://keras.io/ja/)も適宜参照してください。

多層パーセプトロンで2次元ガウシアン状のシグナルを分類する問題をKerasでも実装してみましょう。

```python
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD

# データ点の取得
x, t = getDataset2()

# モデルの定義
model = Sequential([
    Dense(3, activation='sigmoid', input_dim=2),  # ノード数が3の層を追加。活性化関数はシグモイド関数。
    Dense(1, activation='sigmoid')  # ノード数が1の層を追加。活性化関数はシグモイド関数。
])

#  誤差関数としてクロスエントロピーを指定。最適化手法は(確率的)勾配降下法
model.compile(loss='binary_crossentropy', optimizer=SGD(learning_rate=1.0))

#  トレーニング
model.fit(
    x=x,
    y=t,
    batch_size=len(x),  # バッチサイズ。一回のステップで全てのデータを使うようにする。
    epochs=3000,  # 学習のステップ数
    verbose=0,  # 1とするとステップ毎に誤差関数の値などが表示される
)

# プロット
## パーセプトロンの出力を等高線プロット
PlotPredictionContour(model.predict)

## データ点をプロット
plotDataPoint(x, t)

```

パーセプトロンの実装が簡潔に行えていることがわかります。

Kerasではモデルの変更も簡単に行えます。
下の例では、
- 1層あたりのノード数の変更
- レイヤー数の変更
- 活性化関数の変更
- 誤差関数の最適化手法の変更

を行っています。

Kerasは深層学習で有用と知られている多くのテクニックが既に実装され、簡単に使えるようになっています。
例えば[誤差関数](https://keras.io/ja/losses/)、[最適化手法](https://keras.io/ja/optimizers/)、[活性化関数](https://keras.io/ja/activations/)など、使える関数が日本語でもまとめられています。

```python
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

# データ点の取得
x, t = getDataset2()

# モデルの定義
model = Sequential([
    Dense(32, activation='relu', input_dim=2),  # ノード数が32の層を追加。活性化関数はrelu。
    Dense(16, activation='relu'),  # ノード数が16の層を追加。活性化関数はrelu。
    Dense(8, activation='relu'),  # ノード数が8の層を追加。活性化関数はrelu。
    Dense(1, activation='sigmoid')  # ノード数が1の層を追加。活性化関数はシグモイド関数。
])

#  誤差関数としてクロスエントロピーを指定。最適化手法は"adam"
model.compile(loss='binary_crossentropy', optimizer='adam')

#  トレーニング
model.fit(
    x=x,
    y=t,
    batch_size=128,  # len(x), #  バッチサイズ。128個のデータセット毎にパラメータの更新を行う。
    epochs=500,  # 学習のステップ数
    verbose=0,
)

# プロット
## パーセプトロンの出力を等高線プロット
PlotPredictionContour(model.predict)

## データ点をプロット
plotDataPoint(x, t)

```

# 最後に

実習課題として以下の2つを用意しました。
- パラメータ最適化手法
- 活性化関数とパラメータの初期化

それぞれ別のjupyter notebookを用意しています。もし興味があれば取り組んでください。


```python
from logicqubit.logic import *
from cmath import *
import numpy as np
import sympy as sp
import scipy
from scipy.optimize import *
import matplotlib.pyplot as plt
```

https://arxiv.org/abs/1304.3061

10.1103/PhysRevX.6.031007

Second Quantized Hamiltonian
\begin{eqnarray*}
    \mathcal{H}(r)=h_0 + \sum_{pq} h_{pq}(r) a^{\dagger}_p a_q +\frac{1}{2} \sum_{pqrs} h_{pqrs}(r) a^{\dagger}_p a^{\dagger}_qa_ra_s
\end{eqnarray*}

\begin{eqnarray*}
    h_{pq}(r)=\int{d\mathbf{r}}\,\phi^*_p(\mathbf{r})\left(-\frac{1}{2}\nabla^2-\sum_{a}{\frac{Z_a}{\mathbf{r}_{a,\mathbf{r}}}}\right)\phi_q(\mathbf{r})
\end{eqnarray*}

\begin{eqnarray*}
    h_{pqrs}(r)=\int{d\mathbf{r_1}\,d\mathbf{r_2}}\,\phi^*_p(\mathbf{r_1})\phi^*_q(\mathbf{r_2})r_{1,2}^{-1}\phi_r(\mathbf{r_1})\phi_s(\mathbf{r_2})
\end{eqnarray*}

Jordan-Wigner transformation
\begin{eqnarray*}
    a^{\dagger} = I^{\otimes j-1}\otimes \sigma_{-} \otimes \sigma_{z}^{\otimes N-j}\\
    a = I^{\otimes j-1}\otimes \sigma_{+} \otimes \sigma_{z}^{\otimes N-j}
\end{eqnarray*}

```python
gates = Gates(1)

ID = gates.ID()
X = gates.X()
Y = gates.Y()
Z = gates.Z()
```

```python
II = ID.kron(ID)
XX = X.kron(X)
YY = Y.kron(Y)
ZZ = Z.kron(Z)
ZI = Z.kron(ID)
IZ = ID.kron(Z)

sig_is = np.kron([1, 1], [1, -1])
sig_si = np.kron([1, -1], [1, 1])
```

```python
def repulsion_energy(Z1=1, Z2=1, r=75e-12):
    Eh = 4.3597447222071e-18 # hartree energy
    ep0 = 8.854187e-12
    e = -1.602176634e-19
    return (1/(4*pi*ep0)*(Z1*Z2*e**2)/r)/Eh

rep_energy = repulsion_energy()
print("Repulsion energy (Eh): %s"%rep_energy)
```

    Repulsion energy (Eh): 0.7055696793059831

```python
g0 = -0.4804
g1 = 0.3435
g2 = -0.4347
g3 = 0.5716
g4 = 0.0910
g5 = 0.0910

H = II*g0 + IZ*g1 + ZI*g2 + ZZ*g3 + XX*g4 + YY*g5

min(scipy.linalg.eig(H.get())[0])+rep_energy
```

    (-1.145629444817661+0j)

```python
def ansatz(reg, params):
    n_qubits = len(reg)
    depth = n_qubits
    for i in range(depth):
        for j in range(n_qubits):
            if(j < n_qubits-1):
                reg[j+1].CNOT(reg[j])
            reg[i].RY(params[j])

def ansatz_2q(q1, q2, params):
    q2.CNOT(q1)
    q1.RY(params[0])
    q2.RY(params[1])
    q1.CNOT(q2)
    q1.RY(params[2])
    q2.RY(params[3])
    q2.CNOT(q1)
    q1.RY(params[4])
    q2.RY(params[5])
```

```python
def expectation_2q(params):
    logicQuBit  = LogicQuBit(2)
    q1 = Qubit()
    q2 = Qubit()

    ansatz_2q(q1,q2,params)
    psi = logicQuBit.getPsi()

    return (psi.adjoint()*H*psi).get()[0][0]

minimum = minimize(expectation_2q, [0,0,0,0,0,0], method='Nelder-Mead', options={'xtol': 1e-10, 'ftol': 1e-10})
print(minimum.fun+rep_energy)
```

    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:586: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[k] = func(sim[k])
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:618: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxr
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:611: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxe
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:614: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxr
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:637: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxcc
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:627: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxc

    -1.145629444817662

    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:644: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[j] = func(sim[j])

```python
def expectation_value(measurements, base = np.array([1,-1,-1,1])):
    probabilities = np.array(measurements)
    expectation = np.sum(base * probabilities)
    return expectation

def sigma_xx(params):
    logicQuBit  = LogicQuBit(2, first_left = False)
    q1 = Qubit()
    q2 = Qubit()

    ansatz_2q(q1,q2,params)

    # medidas em XX
    q1.RY(-pi/2)
    q2.RY(-pi/2)

    result = logicQuBit.Measure([q1,q2])
    result = expectation_value(result)
    return result

def sigma_yy(params):
    logicQuBit  = LogicQuBit(2, first_left = False)
    q1 = Qubit()
    q2 = Qubit()

    ansatz_2q(q1,q2,params)

    # medidas em YY
    q1.RX(pi/2)
    q2.RX(pi/2)

    result = logicQuBit.Measure([q1,q2])
    result = expectation_value(result)
    return result

def sigma_zz(params):
    logicQuBit  = LogicQuBit(2, first_left = False)
    q1 = Qubit()
    q2 = Qubit()

    ansatz_2q(q1,q2,params)

    result = logicQuBit.Measure([q1,q2])
    zz = expectation_value(result)
    iz = expectation_value(result, sig_is) # [zz, iz] = 0
    zi = expectation_value(result, sig_si) # [zz, zi] = 0
    return zz, iz, zi

def expectation_energy(params):
    xx =  sigma_xx(params)
    yy =  sigma_yy(params)
    zz, iz, zi =  sigma_zz(params)

    result = g0 + g1*iz + g2*zi + g3*zz + g4*xx + g5*yy
    return result
```

```python
minimum = minimize(expectation_energy, [0,0,0,0,0,0], method='Nelder-Mead', options={'xtol': 1e-10, 'ftol': 1e-10})
print(minimum.fun+rep_energy)
```

    -1.1456294448176618

```python
def gradient(params, evaluate):
    n_params = params.shape[0]
    shift = pi/2
    gradients = np.zeros(n_params)

    for i in range(n_params):
        #parameter shift rule
        shift_vect = np.array([shift if j==i else 0 for j in range(n_params)])
        shift_right = params + shift_vect
        shift_left = params - shift_vect

        expectation_right = evaluate(shift_right)
        expectation_left = evaluate(shift_left)

        gradients[i] = expectation_right - expectation_left

    return gradients
```

```python
params = np.random.uniform(-np.pi, np.pi, 6)
last_params = np.zeros(6)
```

```python
lr = 0.1
err = 1
while err > 1e-5:
    grad = gradient(params, expectation_energy)
    params = params - lr*grad
    err = abs(sum(params - last_params))
    last_params = np.array(params)
    print(err)
```

    /tmp/ipykernel_10647/1088512639.py:15: ComplexWarning: Casting complex values to real discards the imaginary part
      gradients[i] = expectation_right - expectation_left

    3.2771275942132028
    0.25087340996984175
    0.21642798860086027
    0.17299785149256727
    0.127042900992303
    0.08495812918109738
    0.05122215078222822
    0.027296428517020133
    0.012076889658794665
    0.003307506724051168
    0.001234210214421605
    0.0032382230817722957
    0.0038293853294496527
    0.0036995397718261325
    0.0032503120313920886
    0.0027019008242667453
    0.0021662136382552544
    0.0016931563188248289
    0.0012988346634378312
    0.000982184806577524
    0.0007344376019901011
    0.0005442638721736559
    0.00040038835375455495
    0.0002927686445351463
    0.00021299570138316737
    0.00015429889043244716
    0.00011137199120508923
    8.013731289757686e-05
    5.750742577281809e-05
    4.1171532305650516e-05
    2.94159505212499e-05
    2.0979269161391922e-05
    1.4938675643283439e-05
    1.0622518519409763e-05
    7.5440569521911804e-06

```python
energy = expectation_energy(params)
energy = energy + rep_energy
print(energy)
```

    (-1.1456294446036503+0j)

```python

```


# Computational Physics Lectures: Random walks, Brownian motion and the Metropolis algorithm

 **Morten Hjorth-Jensen**, Department of Physics, University of Oslo and Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University

Date: **2016**

## Why Markov chains, Brownian motion and the Metropolis algorithm

We want to study a physical system which evolves towards equilibrium, from som given  initial conditions.
Recall the simple example of particles in a box. At an initial time $t_0$ all particles are in the left half of the box. Thereafter they are allowed to diffuse into the two halves of the box.

```
%matplotlib inline

#!/usr/bin/env python
from  matplotlib import pyplot as plt
from math import exp
import numpy as np
import random

# initial number of particles
N0 = 1000
MaxTime = 10*N0
values = np.zeros(MaxTime)
time = np.zeros(MaxTime)
random.seed()
# initial number of particles in left half
nleft = N0
for t in range (0, MaxTime, 1):
    if N0*random.random() <= nleft:
       nleft -= 1
    else:
       nleft += 1
    time[t] = t
    values[t] = nleft

# Finally we plot the results
plt.plot(time, values,'b-')
plt.axis([0,MaxTime, N0/4, N0])
plt.xlabel('$t$')
plt.ylabel('$N$')
plt.title('Number of particles in left half')
plt.savefig('box.pdf')
plt.show()
```

## Why Markov chains, Brownian motion and the Metropolis algorithm

* We want to study a physical system which evolves towards equilibrium, from given  initial conditions.

* We start with a PDF $w(x_0,t_0)$  and we want to understand how the system evolves with time.

* We want to reach a situation where after a given number of time steps we obtain a steady state. This means that the system reaches its most likely state (equilibrium situation)

* Our PDF is normally a multidimensional object whose normalization constant is impossible to find.

* Analytical calculations from $w(x,t)$ are not possible.

* To sample directly from from $w(x,t)$ is not possible/difficult.

* The transition probability $W$ is also not  known.

* How can we establish that we have reached a steady state?   Sounds impossible!

**Use Markov chain Monte Carlo**

## Brownian motion and Markov processes
A Markov process is a random walk with a selected probability for making a
move. The new move is independent of the previous history of the system.

The Markov process is used repeatedly in Monte Carlo simulations in order to generate
new random states.

The reason for choosing a Markov process is that when it is run for a
long enough time starting with a random state, we will eventually reach the most likely state of the system.

In thermodynamics, this means that after a certain number of Markov processes
we reach an equilibrium distribution.

This mimicks the way a real system reaches
its most likely state at a given temperature of the surroundings.

## Brownian motion and Markov processes, Ergodicity and Detailed balance

To reach this distribution, the Markov process needs to obey two important conditions, that of
**ergodicity** and **detailed balance**. These conditions impose then constraints on our algorithms
for accepting or rejecting new random states.

The Metropolis algorithm discussed here
abides to both these constraints.

The Metropolis algorithm is widely used in Monte Carlo
simulations and the understanding of it rests within
the interpretation of random walks and Markov processes.

## Brownian motion and Markov processes, jargon

In a random walk one defines a mathematical entity called a **walker**,
whose  attributes
completely define the state of the system in question.

The state of the system  can refer to any physical quantities,
from the vibrational state of a molecule specified by a set of quantum numbers,
to the brands of coffee in your favourite supermarket.

The walker moves in an appropriate state space by a combination of
deterministic and random displacements from its previous
position.

This sequence of steps forms a **chain**.

## Brownian motion and Markov processes, sequence of ingredients

* We want to study a physical system which evolves towards equilibrium, from given  initial conditions.

* Markov chains are intimately linked with the physical process of diffusion.

* From a Markov chain we can then derive the conditions for detailed balance and ergodicity. These are the conditions needed for obtaining a steady state.

* The widely used algorithm for doing this is the so-called Metropolis algorithm, in its refined form the Metropolis-Hastings algorithm.

## Applications: almost every field in science

* Financial engineering, see for example Patriarca *et al*, Physica **340**, [page 334 (2004)](http://www.sciencedirect.com/science/article/pii/S0378437104004327).

* Neuroscience, see for example Lipinski, Physics Medical Biology **35**, [page 441 (1990)](http://iopscience.iop.org/article/10.1088/0031-9155/35/3/012/meta;jsessionid=FA91B191036E1F10948F7C42B6A6D295.c1) or Farnell and Gibson, Journal of Computational Physics **208**, [page 253 (2005)](http://www.sciencedirect.com/science/article/pii/S0021999105001087)

* Tons of applications in physics

* and chemistry

* and biology, medicine

* Nobel prize in economy to Black and Scholes

$$
\frac{\partial V}{\partial t}+\frac{1}{2}\sigma^{2}S^{2}\frac{\partial^{2} V}{\partial S^{2}}+rS\frac{\partial V}{\partial S}-rV=0.
$$

The Black and Scholes equation is a partial differential equation, which describes the price
of the option over time. It is a diffusion equation with a random term.

The list of applications is endless

## A simple example (close to project 4) and some more jargon

The obvious case is that of a random walker on a one-, or two- or three-dimensional lattice
(dubbed coordinate space hereafter).

Consider a system whose energy is defined by the orientation of single spins.
Consider the state $i$, with given energy $E_i$ represented by the following $N$ spins

$$
\begin{array}{cccccccccc}
\uparrow&\uparrow&\uparrow&\dots&\uparrow&\downarrow&\uparrow&\dots&\uparrow&\downarrow\\
1&2&3&\dots& k-1&k&k+1&\dots&N-1&N\end{array}
$$

We may be  interested in the transition with one single  spinflip to a new state $j$ with energy $E_j$

$$
\begin{array}{cccccccccc}
\uparrow&\uparrow&\uparrow&\dots&\uparrow&\uparrow&\uparrow&\dots&\uparrow&\downarrow\\
1&2&3&\dots& k-1&k&k+1&\dots&N-1&N\end{array}
$$

This change from one microstate $i$ (or spin configuration)  to another microstate $j$ is the
**configuration space**  analogue to a random walk on a lattice. Instead of jumping from
one place to another in space, we 'jump' from one microstate to another.

## Markov processes
A Markov process allows in principle for a microscopic description of Brownian motion.
As with the random walk studied in the previous section, we consider a particle
which moves along the  $x$-axis in the form of a series of jumps with step length
$\Delta x = l$. Time and space are discretized and the subsequent moves are
statistically independent, i.e., the new move depends only on the previous step
and not on the results from earlier trials.
We start at a position $x=jl=j\Delta x$ and move to
a new position $x =i\Delta x$ during a step $\Delta t=\epsilon$, where
$i\ge  0$ and $j\ge 0$ are integers.
The original probability distribution function (PDF) of the particles is given by
$w_i(t=0)$ where $i$ refers to a specific position on the grid in

The function $w_i(t=0)$ is now the discretized version of $w(x,t)$.
We can regard the discretized PDF as a vector.

## Markov processes
For the Markov process we have a transition probability from a position
$x=jl$ to a position $x=il$ given by

$$
W_{ij}(\epsilon)=W(il-jl,\epsilon)=\left\{\begin{array}{cc}\frac{1}{2} & |i-j| = 1\\
             0 & \mathrm{else} \end{array} \right. ,
$$

where $W_{ij}$ is normally called
the transition probability and we can represent it, see below,
as a matrix.
**Here we have specialized to a case where the transition probability is known**.

Our new PDF $w_i(t=\epsilon)$ is now related to the PDF at
$t=0$ through the relation

$$
w_i(t=\epsilon) =\sum_{j} W(j\rightarrow i)w_j(t=0).
$$

This equation represents the discretized time-development of an original
PDF with equal probability of jumping left or right.

## Markov processes, the probabilities

Since both $W$ and $w$ represent probabilities, they have to be normalized, i.e., we require
that at each time step we have

$$
\sum_i w_i(t) = 1,
$$

and

$$
\sum_j W(j\rightarrow i) = 1,
$$

which applies for all $j$-values.
The further constraints are
$0 \le W_{ij} \le 1$  and  $0 \le w_{j} \le 1$.
Note that the probability for remaining at the same place is in general
not necessarily equal zero.

## Markov processes
The time development of our initial PDF can now be represented through the action of
the transition probability matrix applied $n$ times. At a
time  $t_n=n\epsilon$ our initial distribution has developed into

$$
w_i(t_n) = \sum_jW_{ij}(t_n)w_j(0),
$$

and defining

$$
W(il-jl,n\epsilon)=(W^n(\epsilon))_{ij}
$$

we obtain

$$
w_i(n\epsilon) = \sum_j(W^n(\epsilon))_{ij}w_j(0),
$$

or in matrix form

$$
\begin{equation} \label{eq:wfinal} \tag{1}
   \hat{w}(n\epsilon) = \hat{W}^n(\epsilon)\hat{w}(0).
\end{equation}
$$

## An Illustrative Example

The following simple example may help in understanding the meaning of
the transition matrix $\hat{W}$ and the vector $\hat{w}$.
Consider the $4\times 4$ matrix $\hat{W}$

$$
\hat{W} = \left(\begin{array}{cccc} 1/4 & 1/9 & 3/8 & 1/3 \\
                                       2/4 & 2/9 & 0 & 1/3\\
                                       0   & 1/9 & 3/8 & 0\\
                                       1/4 & 5/9&  2/8 & 1/3 \end{array} \right),
$$

and we choose our initial state as

$$
\hat{w}(t=0)=  \left(\begin{array}{c} 1\\
                                 0\\
                                 0 \\
                                 0 \end{array} \right).
$$

## An Illustrative Example
We note that both the vector and the matrix are properly normalized. Summing the vector elements gives one and
summing over columns for the matrix results also in one.  Furthermore, the largest eigenvalue is one.
We act then on $\hat{w}$ with $\hat{W}$.
The first iteration is

$$
\hat{w}(t=\epsilon) = \hat{W}\hat{w}(t=0),
$$

resulting in

$$
\hat{w}(t=\epsilon)=  \left(\begin{array}{c} 1/4\\
                                1/2 \\
                                0 \\
                                1/4 \end{array} \right).
$$

## An Illustrative Example, next step

The next iteration results in

$$
\hat{w}(t=2\epsilon) = \hat{W}\hat{w}(t=\epsilon),
$$

resulting in

$$
\hat{w}(t=2\epsilon)=  \left(\begin{array}{c} 0.201389\\
   0.319444 \\
   0.055556 \\
   0.423611 \end{array} \right).
$$

Note that the vector $\hat{w}$ is always normalized to $1$.

## An Illustrative Example, the steady state
We find the steady state of the system by solving the set of equations

$$
w(t=\infty) = Ww(t=\infty),
$$

which is an eigenvalue problem with eigenvalue equal to **one**!
This set of equations reads

$$
W_{11}w_1(t=\infty) +W_{12}w_2(t=\infty) +W_{13}w_3(t=\infty)+ W_{14}w_4(t=\infty)=w_1(t=\infty) \nonumber
$$

$$
W_{21}w_1(t=\infty) + W_{22}w_2(t=\infty) + W_{23}w_3(t=\infty)+ W_{24}w_4(t=\infty)=w_2(t=\infty) \nonumber
$$

$$
W_{31}w_1(t=\infty) + W_{32}w_2(t=\infty) + W_{33}w_3(t=\infty)+ W_{34}w_4(t=\infty)=w_3(t=\infty) \nonumber
$$

$$
W_{41}w_1(t=\infty) + W_{42}w_2(t=\infty) + W_{43}w_3(t=\infty)+ W_{44}w_4(t=\infty)=w_4(t=\infty) \nonumber
$$

$$
\begin{equation}
\label{_auto1} \tag{2}
\end{equation}
$$

with the constraint that

$$
\sum_i w_i(t=\infty) = 1,
$$

yielding as solution

$$
\hat{w}(t=\infty)=  \left(\begin{array}{c}0.244318 \\
                                 0.319602 \\  0.056818 \\  0.379261 \end{array} \right).
$$

## An Illustrative Example, iterative steps

The table here  demonstrates the convergence as a function of the number of iterations or
time steps. After  twelve iterations we have reached the exact value with six leading digits.

     Iteration     	  $w_1$  	  $w_2$  	  $w_3$  	  $w_4$

   0                      	    1.000000    	    0.000000    	    0.000000    	    0.000000

   1                      	    0.250000    	    0.500000    	    0.000000    	    0.250000

   2                      	    0.201389    	    0.319444    	    0.055556    	    0.423611

   3                      	    0.247878    	    0.312886    	    0.056327    	    0.382909

   4                      	    0.245494    	    0.321106    	    0.055888    	    0.377513

   5                      	    0.243847    	    0.319941    	    0.056636    	    0.379575

   6                      	    0.244274    	    0.319547    	    0.056788    	    0.379391

   7                      	    0.244333    	    0.319611    	    0.056801    	    0.379255

   8                      	    0.244314    	    0.319610    	    0.056813    	    0.379264

   9                      	    0.244317    	    0.319603    	    0.056817    	    0.379264

   10                     	    0.244318    	    0.319602    	    0.056818    	    0.379262

   11                     	    0.244318    	    0.319602    	    0.056818    	    0.379261

   12                     	    0.244318    	    0.319602    	    0.056818    	    0.379261

   $\hat{w}(t=\infty)$    	    0.244318    	    0.319602    	    0.056818    	    0.379261

## An Illustrative Example, what does it mean?

We have after $t$-steps

$$
\hat{w}(t) = \hat{W}^t\hat{w}(0),
$$

with $\hat{w}(0)$ the distribution at $t=0$ and $\hat{W}$ representing the
transition probability matrix.

## An Illustrative Example, understanding the basics

We can always expand $\hat{w}(0)$ in terms of the right eigenvectors
$\hat{v}$ of $\hat{W}$ as

$$
\hat{w}(0)  = \sum_i\alpha_i\hat{v}_i,
$$

resulting in

$$
\hat{w}(t) = \hat{W}^t\hat{w}(0)=\hat{W}^t\sum_i\alpha_i\hat{v}_i=
\sum_i\lambda_i^t\alpha_i\hat{v}_i,
$$

with $\lambda_i$ the $i^{\mathrm{th}}$ eigenvalue corresponding to
the eigenvector $\hat{v}_i$.

If we assume that $\lambda_0$ is the largest eigenvector we see that in the limit $t\rightarrow \infty$,
$\hat{w}(t)$ becomes proportional to the corresponding eigenvector
$\hat{v}_0$. This is our steady state or final distribution.

## Simple c++ program to perform the above calculations
The c++ program we have included here (using Armadillo) performs the above operations
for, in this case, a $5\times 5$ matrix. The largest eigenvalue is $1$.

        #include <iostream>
        #include "armadillo"
        using namespace arma;
        using namespace std;

        int main()
        {
          int dim = 5;
          mat W = zeros<mat>(dim,dim);
          vec wold = zeros<mat>(dim);
          vec wnew = zeros<mat>(dim);
          vec eigenvector = zeros<mat>(dim);
          // Initializing the first vector
          wold(0) = 1.0;
          // Setting up the stochastic matrix W
          W(0,0) = 0.; W(0,1) = 0.; W(0,2) = 0.25; W(0,3) = 0.0;   W(0,4) = 0.;
          W(1,0) = 0.; W(1,1) = 0.; W(1,2) = 0.25; W(1,3) = 0.; W(1,4) = 0.0;
          W(2,0) = 0.5; W(2,1) = 1.0; W(2,2) = 0.; W(2,3) = 0.5; W(2,4) = 0.;
          W(3,0) = 0.0; W(3,1) = 0.; W(3,2) = 0.25; W(3,3) = 0.;  W(3,4) = 0.;
          W(4,0) = 0.5; W(4,1) = 0.; W(4,2) = 0.25; W(4,3) = 0.5;  W(4,4) = 1.0;
          double eps = 1.0E-10;
          W.print("W =");
          double difference  = norm(wold-wnew, 2);
          int count = 0;
          do{
            // Multiplying the old vector with the transition probability
            count += 1;
            wnew = W*wold;
            difference  = norm(wold-wnew, 2);
            wold = wnew;
            cout << "Iteration number = " << count << endl;
            wnew.print("New vector =");
          } while(difference > eps);

          // Getting the eigenvectors and eigenvalues of the stochastic matrix
          cx_vec eigval;
          eig_gen(eigval, W);
          eigval.print("Eigenvalues=");
          return 0;
        }

## Entropy and the most likely state

```
# 1D-randomwalk: A walker makes several steps,
# with a given number of walks pr. trial.
# It computes the entropy by filling in bins with counts

import numpy, sys, math

def mc_trial(number_walks,move_probability,walk_cum,walk2_cum, probability):
    """
    Do a MonteCarlo trial, that is,
    random-walk one particle.

    Input:
    - number_walks:     Number of steps to walk the particle
    - move_probability: Probability that the particle
                        will step right when doing a step
    - walk_cum:         Numpy-array of length number_walks + 1,
                        containing the sum of the position
                        of the particles as a function of time
                        (usefull to calculate mean pos. as a function
                        of time)
    - walk2_cum:        Same as walk_cum, but with the sum of the
                        positions squared
    - probability:      Number of times each gridpoint is hit

    Output: As walk_cum, walk2_cum, and probability are (pointers to)
    numpy arrays, they are altered also in the calling function.
    """
    #Initial pos. As walk_cum[0]=walk2_cum[0] = 0.0
    #by initialization, it is uneccessary to add this step to
    #the arrays...
    pos = 0;

    for walk in xrange(number_walks+1):
        if numpy.random.random() <= move_probability:
            pos += 1
        else:
            pos -= 1
        walk_cum[walk]   += pos
        walk2_cum[walk]  += pos**2
        #Zero-position of the array is the leftmost
        #end of the grid
        probability[pos+number_walks] += 1

def mc_sample(length,trials, number_walks, move_probability):
    """
    Generate the probability distribution for finding
    a walker at a gridpoint, after a number of walks on a
    1d lattice with wrap-around boundary conditions

    Input:
    - length: Lattice-points away from x=0
    - trials: Number of MonteCarlo trials (number of walkers)
    - move_probability: Probability of moving right

    Output:
    Normalized probability of finding a walker on a
    specific grid position
    """

    #Grid position of every walker
    x = numpy.zeros(trials,numpy.int)

    #Loop over timesteps and walkers,
    #and find the walkers "ending positions"
    for t in xrange(number_walks):
        for i in xrange(trials):
            if numpy.random.random() <= move_probability:
                x[i] += 1
                #Wraparound?
                if x[i] > length:
                    x[i] = -length
            else:
                x[i] -= 1
                if x[i] < -length:
                    x[i] = +length

    #Calculate the probability of finding a walker
    #each grid-position
    probability = numpy.zeros(2*length+1)
    for i in xrange(len(probability)):
        pos = i-length
        #count number of occurences of this pos i x array
        count = 0
        for j in xrange(len(x)):
            if x[j] == pos:
                count += 1
        #Normalize and save
        probability[i] = count/float(trials)
    return probability

#Main program

length               =   10
trials               =   100000
number_walks         =   100
move_probability     =  0.5

#Do the MC
probability = mc_sample(length,trials,number_walks,move_probability);

#Not reliable: ln(0)
#entropy = - numpy.sum(probability*numpy.log(probability))

entropy = 0.0
for i in xrange(len(probability)):
    if probability[i] > 0.0:
        entropy -= probability[i]*math.log(probability[i])

print "Timesteps             =",number_walks
print "Walkers (num. trials) =",trials
print "Entropy               =",entropy
print
if len(probability) <= 101:
    print "Probability distribution (Flat => high entropy):"
    print probability
else:
    print "Probability distribution to big to print"
```

## The Metropolis Algorithm and Detailed Balance

Let us recapitulate some of our results about Markov chains and random walks.

  * The time development of our PDF $w(t)$, after

one time-step from $t=0$ is given by

$$
w_i(t=\epsilon) = W(j\rightarrow i)w_j(t=0).
$$

This equation represents the discretized time-development of an original
PDF.  We can rewrite this as a

$$
w_i(t=\epsilon) = W_{ij}w_j(t=0).
$$

with the transition matrix $W$ for a random walk given by

$$
W_{ij}(\epsilon)=W(il-jl,\epsilon)=\left\{\begin{array}{cc}\frac{1}{2} & |i-j| = 1\\
                                             0 & \mathrm{else} \end{array} \right.
$$

## The Metropolis Algorithm and Detailed Balance

We call $W_{ij}$ for the transition probability and we represent it
as a matrix.
  * Both  $W$ and $w$ represent probabilities and they have to be normalized, meaning that at each time step we have

$$
\sum_i w_i(t) = 1,
$$

and

$$
\sum_j W(j\rightarrow i) = 1.
$$

Here we have written the previous matrix $W_{ij}=W(j\rightarrow i)$.

## The Metropolis Algorithm and Detailed Balance

The further constraints are
$0 \le W_{ij} \le 1$  and  $0 \le w_{j} \le 1$.
  * We can thus write the action of $W$ as

$$
w_i(t+1) = \sum_jW_{ij}w_j(t),
$$

or as vector-matrix relation

$$
\hat{w}(t+1) = \hat{W\hat{w}}(t),
$$

and if we have that $||\hat{w}(t+1)-\hat{w}(t)||\rightarrow 0$, we say that
we have reached the most likely state of the system, the so-called steady state or equilibrium state.

## The Metropolis Algorithm and Detailed Balance

Another way of phrasing this is

$$
\begin{equation}
w(t=\infty) = Ww(t=\infty).
\label{_auto2} \tag{3}
\end{equation}
$$

## The Metropolis Algorithm and Detailed Balance

The question then is how can we model anything under such a severe lack of knowledge? The Metropolis algorithm comes to our rescue here. Since $W(j\rightarrow i)$ is unknown, we model it as the product of two probabilities,
a probability for accepting the proposed move from the state $j$ to the state $j$, and a probability for making the transition to the state $i$ being in the state $j$. We label these probabilities $A(j\rightarrow i)$ and $T(j\rightarrow i)$, respectively.  Our total transition probability is then

$$
W(j\rightarrow i)=T(j\rightarrow i)A(j\rightarrow i).
$$

The algorithm can then be expressed as

  * We make a suggested move to the new state $i$ with some transition or moving probability $T_{j\rightarrow i}$.

  * We accept this move to the new state with an acceptance probability $A_{j \rightarrow i}$. The new state $i$ is in turn used as our new starting point for the next move.  We reject this proposed moved with a $1-A_{j\rightarrow i}$ and the original state $j$ is used again as a sample.

## The Metropolis Algorithm and Detailed Balance

We wish to derive the required properties of the probabilities $T$ and $A$ such that
$w_i^{(t\rightarrow \infty)} \rightarrow w_i$, starting
from any distribution, will lead us to the correct distribution.

We can now derive the dynamical process towards
equilibrium. To obtain this equation we note that after $t$ time steps the probability for being in a state $i$ is related
to the probability of being in a state $j$ and performing a transition to the new state together with the probability of actually being in the state $i$ and making a move to any of the possible states $j$ from the previous time step.

## The Metropolis Algorithm and Detailed Balance

We can express this as, assuming that $T$ and $A$ are time-independent,

$$
w_i(t+1) = \sum_j \left [
w_j(t)T_{j\rightarrow i} A_{j\rightarrow i}
+w_i(t)T_{i\rightarrow j}\left ( 1- A_{i\rightarrow j} \right)
\right ] \,.
$$

## The Metropolis Algorithm and Detailed Balance

All probabilities are normalized, meaning that
$\sum_j T_{i\rightarrow j} = 1$. Using the latter, we can rewrite the previous equation as

$$
w_i(t+1) = w_i(t) +
 \sum_j \left [
w_j(t)T_{j\rightarrow i} A_{j\rightarrow i}
-w_i(t)T_{i\rightarrow j}A_{i\rightarrow j}\right ] \,,
$$

which can be rewritten as

$$
w_i(t+1)-w_i(t) =  \sum_j \left [w_j(t)T_{j\rightarrow i} A_{j\rightarrow i}
-w_i(t)T_{i\rightarrow j}A_{i\rightarrow j}\right ] .
$$

## The Metropolis Algorithm and Detailed Balance

The last equation is very similar to the so-called Master equation, which relates the temporal dependence of
a PDF $w_i(t)$ to various transition rates. The equation can be derived from the so-called
Chapman-Einstein-Enskog-Kolmogorov equation. The equation is given as

$$
\begin{equation}
\label{eq:masterequation} \tag{4}
\frac{d w_i(t)}{dt} = \sum_j\left[ W(j\rightarrow i)w_j-W(i\rightarrow j)w_i\right],
\end{equation}
$$

which simply states that the rate at which the systems moves from a state $j$
to a final state $i$ (the first term on the right-hand side of the last equation) is balanced by the rate at which the system undergoes transitions from the state $i$ to a state $j$ (the second term). If we have reached the so-called steady state, then the temporal development is zero. This  means that in equilibrium we have

$$
\frac{d w_i(t)}{dt} = 0.
$$

## The Metropolis Algorithm and Detailed Balance

In the limit $t\rightarrow \infty$ we require that the  two distributions $w_i(t+1)=w_i$ and $w_i(t)=w_i$
and we have

$$
\sum_j w_jT_{j\rightarrow i} A_{j\rightarrow i}= \sum_j w_iT_{i\rightarrow j}A_{i\rightarrow j},
$$

which is the condition for balance when the most likely state (or steady state) has been reached.
We see also that the right-hand side  can be  rewritten as

$$
\sum_j w_iT_{i\rightarrow j}A_{i\rightarrow j}= \sum_j w_iW_{i\rightarrow j},
$$

and using the property that $\sum_j W_{i\rightarrow j}=1$, we can rewrite our equation
as

$$
w_i= \sum_j w_jT_{j\rightarrow i} A_{j\rightarrow i}= \sum_j w_j W_{j\rightarrow i},
$$

which is nothing but the standard equation for a Markov chain when the steady state has been reached.

## The Metropolis Algorithm and Detailed Balance

However, the condition that the rates should equal each other is in general not sufficient
to guarantee that we, after many simulations, generate the correct distribution.
We may risk to end up with so-called cyclic solutions. To avoid this
we therefore introduce an additional condition, namely that of detailed balance

$$
W(j\rightarrow i)w_j= W(i\rightarrow j)w_i.
$$

These equations were derived by Lars Onsager when studying irreversible processes.
At equilibrium detailed balance gives thus

$$
\frac{W(j\rightarrow i)}{W(i\rightarrow j)}=\frac{w_i}{w_j}.
$$

Rewriting the last equation in terms of our transition probabilities $T$ and
acceptance probobalities $A$ we obtain

$$
w_j(t)T_{j\rightarrow i}A_{j\rightarrow i}= w_i(t)T_{i\rightarrow j}A_{i\rightarrow j}.
$$

## The Metropolis Algorithm and Detailed Balance

Since we normally have an expression
for the probability distribution functions $w_i$,  we can rewrite the last equation as

$$
\frac{T_{j\rightarrow i}A_{j\rightarrow i}}{T_{i\rightarrow j}A_{i\rightarrow j}}= \frac{w_i}{w_j}.
$$

## The Metropolis Algorithm and Detailed Balance

In statistical physics this condition ensures that it is e.g., the
Boltzmann distribution which is generated when equilibrium is reached.

We introduce  now the Boltzmann distribution

$$
w_i= \frac{\exp{(-\beta(E_i))}}{Z},
$$

which states that the probability of finding the system in a state $i$ with energy $E_i$
at an inverse temperature $\beta = 1/k_BT$ is $w_i\propto \exp{(-\beta(E_i))}$.
The denominator $Z$ is a normalization constant which ensures that the sum of all
probabilities is normalized to one. It is defined as the sum of probabilities over all microstates
$j$ of the system

$$
Z=\sum_j \exp{(-\beta(E_i))}.
$$

## The Metropolis Algorithm and Detailed Balance

From the partition function we can in principle generate all interesting quantities
for a given system in equilibrium with its surroundings at a temperature $T$.

With the probability distribution given by the Boltzmann distribution we are now in a position
where we can generate expectation values for a given variable $A$ through the
definition

$$
\langle A \rangle = \sum_jA_jw_j=
    \frac{\sum_jA_j\exp{(-\beta(E_j)}}{Z}.
$$

In general, most systems have an infinity of microstates making thereby the computation
of $Z$ practically impossible and
a brute force Monte Carlo calculation over a given number of randomly selected microstates
may therefore not yield those microstates which are important
at equilibrium.
To select the most important contributions we need to
use the condition for detailed balance. Since this is just given by the ratios of probabilities,
we never need to evaluate the partition function $Z$.

## The Metropolis Algorithm and Detailed Balance

For the
Boltzmann distribution, detailed balance results in

$$
\frac{w_i}{w_j}= \exp{(-\beta(E_i-E_j))}.
$$

Let us now specialize to a system whose energy is defined by the orientation of single spins.
Consider the state $i$, with given energy $E_i$ represented by the following $N$ spins

$$
\begin{array}{cccccccccc}
\uparrow&\uparrow&\uparrow&\dots&\uparrow&\downarrow&\uparrow&\dots&\uparrow&\downarrow\\
1&2&3&\dots& k-1&k&k+1&\dots&N-1&N\end{array}
$$

## The Metropolis Algorithm and Detailed Balance

We are interested in the transition with one single  spinflip to a new state $j$ with energy $E_j$

$$
\begin{array}{cccccccccc}
\uparrow&\uparrow&\uparrow&\dots&\uparrow&\uparrow&\uparrow&\dots&\uparrow&\downarrow\\
1&2&3&\dots& k-1&k&k+1&\dots&N-1&N\end{array}
$$

This change from one microstate $i$ (or spin configuration)  to another microstate $j$ is the
configuration space analogue to a random walk on a lattice. Instead of jumping from
one place to another in space, we 'jump' from one microstate to another.

## The Metropolis Algorithm and Detailed Balance

However, the selection of states has to generate a final distribution which is the
Boltzmann distribution. This is again the same we saw for a random walker, for the discrete case we had
always a binomial distribution, whereas for the continuous case we had a normal distribution.
The way we sample configurations should result, when equilibrium is established, in the
Boltzmann distribution. Else, our algorithm for selecting microstates is wrong.

As stated above, we do in general not know the closed-form expression of the transition rate and we are free to model it as
     $W(i\rightarrow j)=T(i\rightarrow j)A(i\rightarrow j)$.
Our ratio between probabilities gives us

$$
\frac{A_{j\rightarrow i}}{A_{i\rightarrow j}}= \frac{w_iT_{i\rightarrow j}}{w_jT_{j\rightarrow i}}.
$$

The simplest form of the Metropolis algorithm (sometimes called for brute force Metropolis) assumes that
the transition probability $T(i\rightarrow j)$ is symmetric, implying that $T(i\rightarrow j)=T(j\rightarrow i)$.

## The Metropolis Algorithm and Detailed Balance

We obtain then (using the Boltzmann distribution)

$$
\frac{A(j\rightarrow i)}{A(i\rightarrow j)}= \exp{(-\beta(E_i-E_j))} .
$$

We are in this case interested in a new state $E_j$ whose energy is lower than
$E_i$, viz., $\Delta E = E_j-E_i \le 0$. A simple test would then be to accept only those
microstates which lower the energy.
Suppose we have ten microstates with energy $E_0 \le E_1 \le E_2 \le E_3 \le \dots \le E_9$.
Our desired energy is $E_0$.

## The Metropolis Algorithm and Detailed Balance

At a given temperature $T$ we start our simulation by randomly choosing state
$E_9$. Flipping spins we may then find a path from $E_9\rightarrow E_8 \rightarrow E_7 \dots \rightarrow E_1 \rightarrow E_0$.
This would however lead to biased statistical averages since it would violate the ergodic hypothesis discussed
in the previous section. This principle states  that
it should be possible for any Markov process to reach every possible state of the system
from any starting point if the simulations is carried out for a long enough time.

Any state in a Boltzmann distribution has a probability different from zero and if such
a state cannot be reached from a given starting point, then the system is not ergodic.
This means that another possible path to $E_0$ could be
$E_9\rightarrow E_7 \rightarrow E_8 \dots \rightarrow E_9 \rightarrow E_5 \rightarrow E_0$ and so forth.
Even though such a path could have a negligible probability it is still a possibility, and if
we simulate long enough it should be included in our computation of an expectation value.

## The Metropolis Algorithm and Detailed Balance

Thus, we require that our algorithm should satisfy the principle of detailed balance and be ergodic.
The problem with our ratio

$$
\frac{A(j\rightarrow i)}{A(i\rightarrow j)}= \exp{(-\beta(E_i-E_j))},
$$

is that we do not know the acceptance probability. This equation only specifies the ratio of pairs of probabilities. Normally we want an algorithm which is as efficient as possible and maximizes the number of accepted moves.
Moreover, we know that the acceptance probability has $0$ as its smallest value and $1$ as its largest.
If we assume that the largest possible acceptance probability is $1$,  we adjust thereafter the other acceptance probability
to this constraint.

## The Metropolis Algorithm and Detailed Balance

To understand this better, assume that we have two energies, $E_i$ and $E_j$, with $E_i < E_j$. This means that the largest acceptance value must be
$A(j\rightarrow i)$ since we move to a state with lower energy.  It follows from also from the fact that the probability $w_i$ is larger than $w_j$.
The trick then is to fix this value to $A(j\rightarrow i)=1$. It means that
the other acceptance probability has to be

$$
A(i\rightarrow j)= \exp{(-\beta(E_j-E_i))}.
$$

## The Metropolis Algorithm and Detailed Balance

One possible way to encode this equation reads

$$
A(j\rightarrow i)=\left\{\begin{array}{cc}
\exp{(-\beta(E_i-E_j))} & E_i-E_j > 0 \\ 1 & else \end{array} \right.,
$$

implying that if we move to a state with a lower energy, we always accept
this move with acceptance probability $A(j\rightarrow i)=1$. If the energy is higher, we need to check
this acceptance probability with the ratio between the probabilities  from our PDF.  From a practical point of view,
the above ratio is compared with a random number.
If the ratio is smaller than a given random number we accept the move to a higher energy, else we stay in the same state.

## The Metropolis Algorithm and Detailed Balance

Nothing hinders us obviously in choosing another acceptance ratio, like a weighting  of the two energies via

$$
A(j\rightarrow i)=\exp{(-\frac{1}{2}\beta(E_i-E_j))}.
$$

However, it is easy to see that such an acceptance ratio would result in
fewer accepted moves.

## Brief Summary

The Monte Carlo approach, combined with the theory for Markov chains can be summarized as follows:
A Markov chain Monte Carlo method for the simulation of a distribution $w$ is any method producing an
ergodic Markov chain of events $x$ whose stationary distribution is $w$. The Metropolis algorithm can be phrased as

  * Generate an initial value $x^{(i)}$.

  * Generate a trial value $y_t$ with probability $T(y_t|x^{(i)})$. The latter quantity represents the probability of generating $y_t$ given $x^{(i)}$.

  * Take a new value

$$
x^{(i+1)}= \left\{\begin{array}{cc} y_t & \mathrm{with\hspace{0.1cm}probability} = A(x^{(i)}\rightarrow y_t) \\                                           x^{(i)}    & \mathrm{with \hspace{0.1cm}probability} = 1-A(x^{(i)}\rightarrow y_t)\end{array}\right .
$$

* We have defined the transition (acceptance) probability as

$$
A(x\rightarrow y)= \mathrm{min}\left\{\frac{w(y)T(x|y)}{w(x)T(y|x)},1\right\}.
$$

## Diffusion

Diffusion and the diffusion equation are central topics in both Physics and Mathematics,
and their ranges of applicability span from stellar dynamics to the diffusion
of particles governed by Schroedinger's equation. The latter is, for a free particle,
nothing but the diffusion equation in complex time!

Let us consider the one-dimensional diffusion equation. We study a large ensemble of particles
performing Brownian motion along the $x$-axis. There is no interaction between the particles.

We define $w(x,t)dx$ as the probability of finding a given number of particles in an
interval of length $dx$ in $x\in [x, x+dx]$ at a time $t$. This quantity is our probability
distribution function (PDF).

## Diffusion Equation
From experiment there are strong indications that the flux of particles $j(x,t)$, viz., the number of particles passing $x$ at a time $t$ is proportional to the
gradient of $w(x,t)$. This proportionality is expressed mathematically through

$$
j(x,t) = -D\frac{\partial w(x,t)}{\partial x},
$$

where $D$ is the so-called diffusion constant, with dimensionality length$^2$ per time.

## Diffusion Equation, continuity equation

If the number of particles is conserved, we have the continuity equation

$$
\frac{\partial j(x,t)}{\partial x} = -\frac{\partial w(x,t)}{\partial t},
$$

which leads to

$$
\begin{equation} \label{eq:diffequation1} \tag{5}
    \frac{\partial w(x,t)}{\partial t} =
    D\frac{\partial^2w(x,t)}{\partial x^2},
\end{equation}
$$

which is the diffusion equation in one dimension.

## Diffusion Equation, expectation values

With the probability distribution function $w(x,t)dx$ we can evaluate expectation values such as  the mean distance

$$
\langle x(t)\rangle = \int_{-\infty}^{\infty}xw(x,t)dx,
$$

or

$$
\langle x^2(t)\rangle = \int_{-\infty}^{\infty}x^2w(x,t)dx,
$$

which allows for the computation of the variance
$\sigma^2=\langle x^2(t)\rangle-\langle x(t)\rangle^2$. Note well that
these expectation values are time-dependent.

## Diffusion Equation, other expectation values

In a similar way we can also
define expectation values of functions $f(x,t)$ as

$$
\langle f(x,t)\rangle = \int_{-\infty}^{\infty}f(x,t)w(x,t)dx.
$$

The normalization condition

$$
\int_{-\infty}^{\infty}w(x,t)dx=1
$$

imposes significant constraints on $w(x,t)$.

## Diffusion Equation, normalization condition

We have

$$
w(x=\pm \infty,t)=0 \hspace{1cm}
   \frac{\partial^{n}w(x,t)}{\partial x^n}|_{x=\pm\infty} = 0,
$$

implying that when we study the time-derivative
$\partial\langle x(t)\rangle/\partial t$, we obtain after integration by parts and using
Eq. [(5)](#eq:diffequation1)

$$
\frac{\partial \langle x\rangle}{\partial t} =
   \int_{-\infty}^{\infty}x\frac{\partial w(x,t)}{\partial t}dx=
   D\int_{-\infty}^{\infty}x\frac{\partial^2w(x,t)}{\partial x^2}dx,
$$

leading to

$$
\frac{\partial \langle x\rangle}{\partial t} =
   Dx\frac{\partial w(x,t)}{\partial x}|_{x=\pm\infty}-
   D\int_{-\infty}^{\infty}\frac{\partial w(x,t)}{\partial x}dx.
$$

## Diffusion Equation
The result is

$$
\frac{\partial \langle x\rangle}{\partial t} = 0.
$$

This means in turn that $\langle x\rangle$ is independent of time.
If we choose the initial position $x(t=0)=0$,
the average displacement $\langle x\rangle= 0$.
If we link this discussion to a random walk in one dimension with equal probability
of jumping to the left or right and with an initial position $x=0$, then our probability
distribution remains centered around $\langle x\rangle= 0$ as function of time.

## Diffusion Equation, the variance

The variance is not necessarily 0. Consider first

$$
\frac{\partial \langle x^2\rangle}{\partial t} =
   Dx^2\frac{\partial w(x,t)}{\partial x}|_{x=\pm\infty}-
   2D\int_{-\infty}^{\infty}x\frac{\partial w(x,t)}{\partial x}dx,
$$

where we have performed an integration by parts as we did
for $\frac{\partial \langle x\rangle}{\partial t}$.

## Diffusion Equation, final expression for the variance

Integration by parts
results in

$$
\frac{\partial \langle x^2\rangle}{\partial t} =
   -Dxw(x,t)|_{x=\pm\infty}+
   2D\int_{-\infty}^{\infty}w(x,t)dx=2D,
$$

leading to

$$
\langle x^2\rangle = 2Dt,
$$

and the variance as

$$
\begin{equation} \label{eq:variancediffeq} \tag{6}
   \langle x^2\rangle-\langle x\rangle^2 = 2Dt.
\end{equation}
$$

The root mean square displacement after a time $t$ is then

$$
\sqrt{\langle x^2\rangle-\langle x\rangle^2} = \sqrt{2Dt}.
$$

## Diffusion Equation, interpretation

This should be contrasted to the displacement of a free particle with initial velocity
$v_0$. In that case the distance from the initial position after a time $t$ is
$x(t) = vt$ whereas for a diffusion process the root mean square value is
$\sqrt{\langle x^2\rangle-\langle x\rangle^2} \propto \sqrt{t}$.
Since diffusion is strongly linked with random walks, we could say that a random walker
escapes much more slowly from the starting point than would a free particle.

## Diffusion Equation, simple illustration

$$
w(x,t)dx = \frac{1}{\sqrt{4\pi Dt}}\exp{(-\frac{x^2}{4Dt})}dx.
$$

At a time $t=2$s the new variance is $\sigma^2=4D$s, implying that the root mean square value
is $\sqrt{\langle x^2\rangle-\langle x\rangle^2} = 2\sqrt{D}$.
At a further time $t=8$ we have $\sqrt{\langle x^2\rangle-\langle x\rangle^2} = 4\sqrt{D}$.
While time has elapsed by a factor of $4$, the root mean square has only changed by a factor
of 2.

## Random Walks
Consider

$$
\langle x(n)\rangle = \sum_{i}^{n} \Delta x_i = 0 \hspace{1cm} \Delta x_i=\pm l,
$$

since we have an equal probability of jumping either to the left or to right.
The value of $\langle x(n)^2\rangle$ is

$$
\langle x(n)^2\rangle = \left(\sum_{i}^{n} \Delta x_i\right)\left(\sum_{j}^{n} \Delta x_j\right)=\sum_{i}^{n} \Delta x_i^2+
\sum_{i\ne j}^{n} \Delta x_i\Delta x_j=l^2n.
$$

## Random Walks
For many enough steps the non-diagonal contribution is

$$
\sum_{i\ne j}^{N} \Delta x_i\Delta x_j=0,
$$

since $\Delta x_{i,j} = \pm l$.
The variance is then

$$
\begin{equation}
   \langle x(n)^2\rangle - \langle x(n)\rangle^2 = l^2n.
\label{eq:rwvariance} \tag{7}
\end{equation}
$$

It is also rather straightforward to compute the variance for $L\ne R$. The result is

$$
\langle x(n)^2\rangle - \langle x(n)\rangle^2 = 4LRl^2n.
$$

## Random Walks
In Eq. [(7)](#eq:rwvariance) the variable $n$ represents the number of time
steps. If we define $n=t/\Delta t$, we can then couple the variance result
from a random walk
in one dimension with the variance  from the diffusion equation of Eq. [(6)](#eq:variancediffeq)
by defining the diffusion constant as

$$
D = \frac{l^2}{\Delta t}.
$$

## Random Walks, simple program
The main program reads the name of the output file from screen and sets up the arrays
containing the walker's position after a given number of steps. The corresponding program for a two-dimensional
random walk (not listed in the main text) is found under programs/chapter12/program2.cpp

        /*
          1-dim random walk program.
          A walker makes several trials steps with
          a given number of walks per trial
        */
        #include <iostream>
        #include <fstream>
        #include <iomanip>
        #include "lib.h"
        using namespace  std;

        // Function to read in data from screen, note call by reference
        void initialise(int&, int&, double&) ;
        // The Mc sampling for random walks
        void  mc_sampling(int, int, double, int *, int *);
        // prints to screen the results of the calculations
        void  output(int, int, int *, int *);

        int main()
        {
          int max_trials, number_walks;
          double move_probability;
          // Read in data
          initialise(max_trials, number_walks, move_probability) ;
          int *walk_cumulative = new int [number_walks+1];
          int *walk2_cumulative = new int [number_walks+1];
          for (int walks = 1; walks <= number_walks; walks++){
            walk_cumulative[walks] = walk2_cumulative[walks] = 0;
          } // end initialization of vectors
          // Do the mc sampling
          mc_sampling(max_trials, number_walks, move_probability,
                      walk_cumulative, walk2_cumulative);
          // Print out results
          output(max_trials, number_walks, walk_cumulative,
                 walk2_cumulative);
          delete [] walk_cumulative; // free memory
          delete [] walk2_cumulative;
          return 0;
        } // end main function
        \end{lstlisting}
        The  input and output functions are
        \begin{lstlisting}
        void initialise(int& max_trials, int& number_walks, double& move_probability)
        {
          cout << "Number of Monte Carlo trials =";
          cin >> max_trials;
          cout << "Number of attempted walks=";
          cin >> number_walks;
          cout << "Move probability=";
          cin >> move_probability;
        }  // end of function initialise

## Random walk
The algorithm tests the probability
of moving to the left or to the right by generating a random number.

        void mc_sampling(int max_trials, int number_walks,
                         double move_probability, int *walk_cumulative,
                         int *walk2_cumulative)
        {
          long idum;
          idum=-1;  // initialise random number generator
          for (int trial=1; trial <= max_trials; trial++){
            int position = 0;
            for (int walks = 1; walks <= number_walks; walks++){
              if (ran0(&idum) <= move_probability) {
        	position += 1;
              }
              else {
        	position -= 1;
              }
              walk_cumulative[walks] += position;
              walk2_cumulative[walks] += position*position;
            }  // end of loop over walks
          } // end of loop over trials
        }   // end mc_sampling function

## Simple python code with visualization of one-dimensional random walk
The python code here is just a mere rewriting of the above c++ code, with the difference that
it employs matplotlib and gives the final plot.

```
#
# 1D-randomwalk: A walker makes several steps,
# with a given number of walks pr. trial
#
import numpy, sys
from  matplotlib import pyplot as plt
import numpy as np

def mc_trial(number_walks,move_probability,walk_cum,walk2_cum):
    """
    Do a MonteCarlo trial, that is,
    random-walk one particle.

    Input:
    - number_walks:     Number of steps to walk the particle
    - move_probability: Probability that the particle
                        will step right when doing a step
    - walk_cum:         Numpy-array of length number_walks + 1,
                        containing the sum of the position
                        of the particles as a function of time
                        (usefull to calculate mean pos. as a function
                        of time)
    - walk2_cum:        Same as walk_cum, but with the sum of the
                        positions squared

    Output: As walk_cum and walk2_cum are numpy arrays, they are altered.
    """
    #Initial pos. As walk_cum[0]=walk2_cum[0] = 0.0
    #by initialization, it is uneccessary to add this step to
    #the arrays...
    pos = 0;

    for walk in xrange(number_walks+1):
        if numpy.random.random() <= move_probability:
            pos += 1
        else:
            pos -= 1
        walk_cum[walk]  += pos
        walk2_cum[walk] += pos**2

def mc_sample(trials, number_walks, move_probability):
    """
    Run as many trials as asked for by input variable trials.
    Wrapper to mc_trial, split out for easier paralellization

    Output: NumPy arrays walk_cum and walk2_cum, length number_walks + 1
    """

    walk_cum  = numpy.zeros(number_walks+1)
    walk2_cum = numpy.zeros(number_walks+1)
    for trial in xrange(trials):
        mc_trial(number_walks,move_probability,walk_cum,walk2_cum)

    return (walk_cum,walk2_cum)

#Main program

# initialize, values can easily be changed
trials           =   10000
number_walks     =   100
move_probability = 0.5

#Do the MC
(walk_cum,walk2_cum) = mc_sample(trials,number_walks, move_probability);
Dim = len(walk_cum)
x = np.zeros(Dim)
xaverage = np.zeros(Dim)
variance = np.zeros(Dim)
#Output
for i in xrange(Dim):
    x[i] = i
    #Normalize to number of trials (= number of walkers)
    xaverage[i]  = walk_cum[i]/float(trials)
    x2average = walk2_cum[i]/float(trials)
    variance[i] = x2average - xaverage[i]**2
plt.figure(1)
plt.subplot(211)
plt.xlabel(r'Steps')
plt.ylabel(r'Average displacement  $\Delta x$')
plt.plot(x, xaverage, 'b-')
plt.subplot(212)
plt.plot(x, variance, 'r-')
plt.ylabel(r'Variance  $\langle\Delta x)^2\rangle-\langle x\rangle^2$')
plt.savefig('rw.pdf')
plt.show()
```