3
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-09-04

はじめに

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

実際のコード

PymatgenでのXRDピーク位置、強度の計算

まずは、例によって、Fe3O4の構造を読み込んで、変数matに格納する。

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を読み込んでくる。

from pymatgen.analysis.diffraction.xrd import XRDCalculator

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

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位が普通だろう。ここも最適化の対象にできるので、とりあえずはこれくらいの値にしておく。
続いて、反射位置と強度、指数を計算する。

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

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

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'に面間隔が格納されている。もっとシンプルに取り出したい場合は、以下のようにできる。

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

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

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で計算する方法も、ここからいただくことにする。

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がピーク位置になる。例えば、ピーク一本だけなら、次のように計算させることができる。

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

粉末XRDパターンの計算

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

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

今回の記事は以上。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2