Pymatgenチュートリアル⑥ XRDのシミュレーションをする

### はじめに

https://qiita.com/ojiya/items/e98a9dd5cb6cd7ad38ca や https://qiita.com/ojiya/items/37b594150115531481f0 の記事に引き続き、PymatgenとOptunaを用いてXRDの解析を行う目的で、Pymatgenの機能を紹介している。この記事では、XRDスペクトルを計算する。と言っても、生真面目にやるわけではなく、結晶構造からピーク位置と強度を計算し、Voigt関数を足し合わせて粉末XRDパターンみたいなものを作るだけだ。先に断っておくと、ここで扱う情報だけでは、本当は粉末XRDパターンを計算することはできないのだが、「OptunaでXRDの解析をすることはできるのか」という仮説の検証ができるくらいにはシミュレーションができるので、一旦はこれで進めるか、と理解してもらいたい。それから、この文章はXRDの基本についてはわかっている人向けに書いている。そこの補足が欲しい場合は、要望があったら書きます。

### 実際のコード
#### PymatgenでのXRDピーク位置、強度の計算
まずは、例によって、Fe3O4の構造を読み込んで、変数matに格納する。

```Python
from pymatgen.io.cif import CifParser
path = 'Fe3O4_mp-19306_symmetrized.cif'
parser = CifParser(path)
mat = parser.get_structures(primitive=False, symmetrized=False)[0]
```

割と重要なポイントとして、通常は実験的に得られたXRDの指数を書く（例えばグラフに書き込む）ときはconventional cellを想定するので、ここでprimitive=Falseにしておかないと、計算されるピークの指数がめちゃくちゃになる（格子の取り方が違うので当たり前だが）。とあるサイトでは、これを見落としてピーク位置を計算したばかりに、ある有名な先生に口汚く罵られるということがあった。気をつけましょう。閑話休題。この構造をもとに、XRDパターンの計算を行う。まずは、ここでは、以下のようにモジュールXRDCalculatorを読み込んでくる。

```Python
from pymatgen.analysis.diffraction.xrd import XRDCalculator
```

次に、XRDパターンを計算するための条件を設定する。ここでいう条件とは、X線の波長とデバイ・ワラー因子（熱因子）を指す。X線はデフォルトではCuのKα1とKα2が設定されるようになっているので、実験室系では、多くの場合デフォルトで問題ないだろう。別の条件を使いたい場合は、文字列か小数で指定する。また、デバイ・ワラー因子は辞書型で与える。とりあえず、例を見てもらった方がわかりやすい気がする。

```Python
xrd_cond = XRDCalculator(wavelength='CuKa', debye_waller_factors={Element('Fe'):0.2, Element('O'):0.6})
# 文字列で指定する場合は、他に ('CuKa', 'CuKa2', 'CuKa1', 'CuKb1', 'MoKa', 'MoKa2', 'MoKa1', 'MoKb1',
# 'CrKa', 'CrKa2', 'CrKa1', 'CrKb1', 'FeKa', 'FeKa2', 'FeKa1', 'FeKb1', 'CoKa', 'CoKa2', 'CoKa1', 'CoKb1',
# 'AgKa', 'AgKa2', 'AgKa1', 'AgKb1')が使える。

# 小数で指定する場合は、例えば以下のようにする。
# xrd_cond = XRDCalculator(wavelength=1.54056, debye_waller_factors={Element('Fe'):0.2, Element('O'):0.6})
```

デバイ・ワラー因子は、見ての通り、keyがElement型で元素種、valueが因子になるように設定する。特に理由がなければ、室温では金属で0.2位、セラミックス中の非金属では0.6位が普通だろう。ここも最適化の対象にできるので、とりあえずはこれくらいの値にしておく。
続いて、反射位置と強度、指数を計算する。

```Python
xrd_calcd = xrd_cond.get_pattern(mat)
print(type(xrd_calcd)
# 出力：<class 'pymatgen.analysis.diffraction.core.DiffractionPattern'>

```

先ほど設定したシミュレーション条件に、.get_pattern(Structure型のデータ)という書き方で、計算させることができる。計算結果は、DiffractionPattern型のデータになっている。ここからデータを取り出すには、第一には、pymatgenのお約束、as_dictを使う方法。

```Python
dict_xrd_calcd = xrd_calcd.as_dict()
print(dict_xrd_calcd.keys())
# 出力：dict_keys(['@module', '@class', '@version', 'x', 'y', 'hkls', 'd_hkls'])
```

なんとなく想像がつくかもしれないが、'x'にピーク位置、'y'に強度、'hkls'に指数、'd_hkls'に面間隔が格納されている。もっとシンプルに取り出したい場合は、以下のようにできる。

```Python
calcd_x = xrd_calcd.x
calcd_y = xrd_calcd.y
calcd_hkls = xrd_calcd.hkls
calcd_d = xrd_calcd.d_hkls
```

いずれのデータもリスト型になっており、観測されるピークが、低角側から順番に格納されている。例えば、以下には一番低角側にあるピークの情報を取り出してみた。

```Python
print(calcd_x[0], calcd_y[0], calcd_hkls[0], calcd_d[0])
#出力：17.98985486665392 7.50841268512196 [{'hkl': (1, 1, 1), 'multiplicity': 8}] 4.926732808708708
```
これで、2θ = 17.99°に強度7.51、hkl = 111のピークがあり、その面間隔は4.93 Åである、ということがわかる。強度は相対値で、最強ピークが100になっていることに注意。これから、このピークの情報をもとに、粉末パターンをシミュレートする。

#### Voigt関数の定義と計算

粉末XRDのここのピークは、多くの場合、Voigt関数というGauss関数とLorentz関数の畳み込みの形で表すことができる。詳しくは、https://qiita.com/yamadasuzaku/items/4fccdc90fa13746af1e1 をご覧ください。また、この関数をpythonで計算する方法も、ここからいただくことにする。

```Python
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
import scipy.special

def voigt(xval,params):
    norm,center,lw,gw = params
    z = (xval - center + 1j*lw)/(gw * np.sqrt(2.0))
    w = scipy.special.wofz(z)
    model_y = norm * (w.real)/(gw * np.sqrt(2.0*np.pi))
    return model_y
```

XRDを計算するときは、normが強度、centerがピーク位置になる。例えば、ピーク一本だけなら、次のように計算させることができる。

```Python
x = np.arange(10,60,0.02)
params = [calcd_y[0], calcd_x[0], 0.02, 0.02]
one_peak = voigt(x, params)

plt.plot(x, one_peak)
```
これで、以下の図が出力される。

![20b72791-18b7-4407-aef5-5321430b2aee.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726485/77bdbc57-df28-3574-2225-88ec0039ef6f.png)

#### 粉末XRDパターンの計算

あとは、全てのピークを足し合わせれば、粉末パターンを得ることができる。ここでは、要素が全て0であるような配列、calcd_patternを作っておいて、それに順々に足していくコードになっている。ついでに、真面目にグラフも書いておいた。

```Python
calcd_pattern = np.zeros(len(x))

N = len(calcd_x)
for i in range(N):
    norm = calcd_y[i]
    center = calcd_x[i]
    lw = 0.05
    gw = 0.05
    params = [norm, center, lw, gw]
    calcd_pattern += voigt(x, params)

figure = plt.figure(figsize=(5,3))
ax = figure.add_subplot(111)

ax.plot(x, calcd_pattern,color='0')

ax.set_xlim(10, 60)
ax.set_ylim(0, 500)

ax.set_xlabel(r'2$\theta$')
ax.set_ylabel('Intensity (a.u.)')
```

![8b50eae1-f27c-4a5e-9859-cd8242addef8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726485/8e5b2b2c-c5fb-5afc-a6ad-41af4a391bf2.png)

今回の記事は以上。