[理系大学生応援] Wolffの手法でイジングモデルを解く

# 概要

この記事は Wolff, "Collective Monte Carlo Updating for Spin Systems", 1989 のアルゴリズムでイジングモデルを数値的に解くことを目的とした記事です。
「いきなり複雑なアルゴリズムでイジングモデルを解くのはちょっと...」という方は、過去記事
[[理系大学生応援] シングルスピンフリップでイジングモデルを解く](https://qiita.com/github-nakasho/items/dc1d56fe791e488939f3)
[[理系大学生応援] Swendsen-Wangの手法でイジングモデルを解く](https://qiita.com/github-nakasho/items/68acc4e6e455eb1d607a)
でイジングモデルの理解を深めてから、この記事を読んでいただければと思います。

# アルゴリズム

## STEP 0

適当に初期状態のスピン配置(ここでは例としてランダムとします)を用意します。
![ising_wolff_20190912_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335400/a7480796-96a4-c0e9-1ae9-09aa93baad2a.jpeg)

## STEP 1

ランダムに座標$(i, j)$のスピンを選択します。

## STEP 2

STEP 1 で選択したスピンをpocket, clusterとして登録します。
![ising_wolff_20190912_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335400/00036325-462e-3840-8fda-32b444508e9f.jpeg)
前のSTEPですでに登録されているclusterがあればこれを空にしておきます。

## STEP 3

pocketの中から1つのスピンに着目し、それと隣あうスピンneighborに対して以下のループを行います。

### STEP 3-1

以下の条件がすべて満たされるとき、neighborにあったスピンをpocketとclusterに登録します。

(1) 隣あうスピンの向きが同じ
(2) 隣にあったスピンがclusterに登録されていない
(3) $0<r<1$の乱数が確率$p=1-e^{-2J/(k_B T)}$より小さい
![ising_wolff_20190912_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335400/dc2fc812-0308-aa43-a058-4ea316c95ece.jpeg)

## STEP 4

STEP 3で選択したスピンをpocketから取り除きます。
![ising_wolff_20190912_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335400/724d4644-73c4-60fb-4492-3cf68b750875.jpeg)

## STEP 5

pocketが空っぽになるまでSTEP 2 ~ STEP 4を繰り返します。
![ising_wolff_20190912_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335400/31050928-6f42-0eb7-a4dd-3920c83d85ad.jpeg)

## STEP 6

以上のSTEPでclusterに登録されたスピンをフリップさせます。
![ising_wolff_20190912_06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335400/4bb9e43a-c7a3-bd5d-2b4e-11c7e2525194.jpeg)

# Pythonスクリプト

以下にPythonで書かれた実装スクリプトを示します。

```python
#!/usr/bin/env python


import matplotlib.pyplot as plt
import numpy as np
import random


class IsingWolff:
	def __init__(self):
		# set the number of spins in x-direrection, y-direction
		self.IXMAX = 200
		self.JYMAX = 200
		# set J_ij (= J, uniform)
		self.J = 1.0
		# set initial condition (-1 or 1, random)
		self.S = (np.random.rand(self.IXMAX, self.JYMAX)<0.5) * (-2) + 1
		# set 1/(k_B T)
		self.ONEOKBT = 1.0
		# set the number of iteration 
		self.ITER = 10000
		# set (i, j) neighbor
		self.NEIGHBOR = {}
		for i in range(self.IXMAX):
			for j in range(self.JYMAX):
				self.NEIGHBOR[(i, j)] = [((i+1)%self.IXMAX, j), ((i-1)%self.IXMAX, j), (i, (j+1)%self.JYMAX), (i, (j-1)%self.JYMAX)]
		
	def Compute_Probability(self):
		# calcurate probability of flip
		return 1.0 - np.exp(-2.0*self.J*self.ONEOKBT)

	def Cluster_Flip(self, p):
		# set random coordinate (i, j)
		coord = (np.random.randint(self.IXMAX), np.random.randint(self.JYMAX))
		# set pocket
		pocket = [coord]
		# set cluster
		cluster = [coord]
		# search pocket while pocket is not empty
		while pocket != []:
			# choose random tuple from pocket
			l = random.choice(pocket)
			# for loop for neightborhood
			for m in self.NEIGHBOR[l]:
				# if spins are parallel & (i, j) is not in cluster and r < p, add to pocket & cluster
				if self.S[m[0]][m[1]] == self.S[l[0]][l[1]]:
					if m not in cluster:
						if np.random.rand() < p:
							pocket.append(m)
							cluster.append(m)
			# remove choosen (i, j) from pocket list
			pocket.remove(l)
		# flip spin including cluster
		for l in cluster:
			self.S[l[0]][l[1]] *= -1

	def Plot_Spin(self, n):
		# remove previous figure
		plt.clf()
		# plot color map
		plt.imshow(self.S, cmap='PuOr', vmin=-1, vmax=1, animated=True)
		# add colorbar
		plt.colorbar(ticks=[-1, 1], orientation='vertical')
		# set pause interval time
		plt.pause(1.0e-6)
		# save figure as png format
		plt.savefig('fig'+str(n)+'.png')
		
	def run(self):
		# set cluster flip probability
		p = self.Compute_Probability()
		# main loop start
		for n in range(self.ITER):
			# plot
			if n % 1 == 0:
				self.Plot_Spin(n)
			# flip cluster
			self.Cluster_Flip(p)
		
if __name__ == '__main__':
	a = IsingWolff()
	a.run()
```

## スクリプト詳細

### \_\_init\_\_

`self.IXMAX, self.JYMAX`でそれぞれ$x, y$方向のスピンの数を指定しています。`self.J`は相互作用係数を、`self.ONEOKBT`は$1/k_B T$の大きさ、`self.ITER`でモンテカルロの試行回数を決定しています。`self.NEIGHBOR`に$(i, j)$のスピンと隣あうスピン$(i+1, j), (i-1, j), (i, j+1), (i, j-1)$を辞書型で登録しています。

### Compute_Probability

確率$p$を計算しています。

### Cluster_Flip

上述のアルゴリズムのSTEP 1 ~ STEP 6 を実行しています。

### Plot_Spin

この関数で`matplotlib`を用いて`self.S`($\pm 1$のスピンの並び)を2次元カラーコントアとして描画しています。また描画結果をjpegファイルとして保存しています。

### run

この関数でWolffの方法によるスピン反転を`self.ITER`の回数だけ繰り返し行います。

# 結果

```python
self.IXMAX = 200
self.JYMAX = 200
self.J = 1.0
self.ONEOKBT = 1.0
self.ITER = 10000		
```

上述のパラメータで計算した結果を動画で示します。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Wolffの手法でイジングモデルを解く、動画はこちら <a href="https://t.co/6rVJQuLj08">pic.twitter.com/6rVJQuLj08</a></p>&mdash; 中の人はダンブルドア (@tweet_nakasho) <a href="https://twitter.com/tweet_nakasho/status/1172031324732841989?ref_src=twsrc%5Etfw">September 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

$k_B T = 1$のときを計算しているので、スピンが綺麗に揃う結果が示せていることがわかります。

# 結言

この方法もSwendsen-Wangの手法と同様、シングルスピンフリップよりも強力な方法です。ですがWolffのアルゴリズムは、Swendsen-Wangのアルゴリズムをより大きなクラスターでスピン交換が起きる確率を高くした改良版です。どんどん活用していきましょう。