Pymatgenチュートリアル② Materials Projectのデータを持ってくる


##はじめに
###この記事で書くこと
前回は、一番基本的な使い方ということで、ElementとCompositionについて書いた。

https://qiita.com/ojiya/items/b97d25b896741ad99b79

この記事では、Materials Projectからデータを引っ張ってくる方法について学ぶ。この記事を読む人がMaterials Projectについて知らないことはないと思うので、説明は割愛するが、簡単に言えば、計算ベースの無機材料データベースだ。

https://www.materialsproject.org

今回の記事では、このデータベースから、化合物情報を検索し、手元で扱えるように持ってくることを目指す。

###使用するバージョン
pymatgen 2022.0.8
2020.x.xくらいのバージョンだと関数の呼び出し方が違っていてエラーが起きるかもしれないので注意。

##実際の操作
###前準備 -Materials Projectへの登録とAPIキーの入手-
pymatgenを使ってデータを持ってくるには、まず (i) Materials Projectへの登録 を行った後、(ii) APIキーの取得 という手順を踏む。(i)については、特別なことは何もなく、上に示したプロジェクトのページからメールアドレスで登録するか、GoogleやGithubのアカウントでログインする。
(ii)については、少し説明を加える。Mterials Projectにログインすると、上の方に、下図のようなバーがあるのがわかると思う。
![スクリーンショット 2021-07-07 19.38.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726485/8f34d514-effa-2ea0-b9a4-76cd96483e67.png)
この中の、'API'と書かれたところをクリックすると、文字だらけのページに遷移する。その中から、'API keys'という見出しの部分に、以下のような文言がある：
To use the MAPI, you need to first have an API key. We have detected that you already have the following API key generated:
この直後に、ランダムなような文字列が、（多分、赤字で）表示されていると思う。これがAPIキーと呼ばれるものだ。pymatgenからデータベースにアクセスするときは、このキーを使うので、どこかにコピって置いておくか、このページをそのままにしておく。

###pymatgenを使ったデータの取得
それでは、早速データをとってこよう。ここでは、MPResterというものを使う

```
#MPRester：Materials Projectからデータを持ってくるためのモジュール
from pymatgen.ext.matproj import MPRester
#APIは自分のやつを持ってくる
API_KEY='さっきメモった文字列'
```

基本的な考え方は、「検索クエリをかけると、ヒットした結果を返してくる」というものになる。しばらく弄っている感じ、無機化合物を検索する場合に思いつく検索方法は、大体実装されていると考えてよさそう。例えば、組成式で検索をかけるなら、以下のようになる。

```
#特定の組成式で化合物を検索するパターン
with MPRester(API_KEY) as m:
    mats = m.get_data('BaTiO3')
```

すると、matsという変数に、検索結果のデータが収納された。要するに、

```
with MPRester(API_KEY) as m:
    mats = m.get_data(検索文字列)
```

という書式で、検索することができる。ここでまず気になるのは、このmatsという変数の中に、どのようにデータが格納されていて、どうやってデータを取り出すか、ということだ。まずは型を確認する。

```
print(type(mats))
#出力結果：<class 'list'>
```
なるほど、リスト型でデータが格納されたらしい。ということは、中のデータを取り出すには、例えば以下のようにしてやればいい。

```
display(mats[0])
#出力結果：
#{'energy': -31.02144054,
# 'energy_per_atom': -6.204288108,
# 'volume': 85.16422489125648,
# 'formation_energy_per_atom': -1.6848189356666665,
# 'nsites': 5,
# 'unit_cell_formula': {'Ba': 1.0, 'Ti': 1.0, 'O': 3.0},
# 'pretty_formula': 'BaTiO3',
# 'is_hubbard': False,
# 'elements': ['Ba', 'Ti', 'O'],
# 'nelements': 3,
# 以下略

print(type(mats[0]))
#出力結果：<class 'dict'>
```

ぱっと見で、これが何がしか1種類の化合物の情報の羅列であることがわかる。さらに、型が辞書型である。つまり、matsというリストには、化合物1つにつき1つの辞書でデータが格納されていることになる。ということは、さらに以下のようにすれば、ある化合物の、特定の特性を読み出すことができる。

```
print(mats[0]['pretty_formula'])
#出力結果：BaTiO3
```

さて、実際の使用を考えると、検索結果を一覧して、「今、自分が欲しい化合物」がどれかを調べるには、組成式、晶系、空間群くらいがわかればよいだろう。また、元のmats（リスト）の何番目を指定すれば、そのデータが手に入るか、も気になるところ。というわけで、以下のようなコードを走らせて、ここら辺の情報を一覧する。

```
for i, mat in enumerate(mats):
    print(i, mat['pretty_formula'], mat['spacegroup']['crystal_system'], mat['spacegroup']['symbol'])
#出力結果：
#0 BaTiO3 tetragonal P4/mmm
#1 BaTiO3 tetragonal P4mm
#2 BaTiO3 orthorhombic Amm2
#3 BaTiO3 orthorhombic Amm2
#4 BaTiO3 cubic Pm-3m
#5 BaTiO3 orthorhombic C222_1
#6 BaTiO3 orthorhombic Amm2
#7 BaTiO3 hexagonal P6_3/mmc
#8 BaTiO3 trigonal R3m
#9 BaTiO3 orthorhombic Amm2
#10 BaTiO3 cubic Pm-3m
```

これで、matsに格納されているデータを頭から順番に見て、組成式、晶系、空間群を得ることができた。（今のケースでは、組成式は全部同じだけど。）この中から1つピックアップして、情報を取り出してみることにする。せっかくなので、室温強誘電のBaTiO3を選ぶことにする。これは空間群P4mmなので、mats[1]の化合物である。

```
ferroel_BTO = mats[1]
print(ferroel_BTO['band_gap'])
print(ferroel_BTO['density'])
display(ferroel_BTO['diel'])

#出力結果：
#1.8297999999999996
#5.748582891575844
#{'e_electronic': [[6.267598999999999, 0.0, 0.0],
#  [0.0, 6.267598999999999, 0.0],
#  [0.0, 0.0, 5.163823999999999]],
# 'e_total': [[7.6526499999999995, 0.0, 0.0],
#  [0.0, 7.6526499999999995, 1.3552527156068805e-20],
#  [0.0, 1.3552527156068805e-20, 19.243076999999996]],
# 'n': 2.428924453333203,
# 'poly_electronic': 5.899673999999998,
# 'poly_total': 11.516125666666666}
```

代表的な物性ということで、バンドギャップ、密度、誘電率テンソルを持ってきてみたが、実測とは結構ズレているので、使い方には注意が必要という印象。さて、ここまで動かしてみて、「Materials Projectのサイトよりもデータ少なくない？DoSは？」という感想を持った。調べてみたら、ちゃんと表示する方法はある、、、が、長くなりそうなので、それは別のチュートリアルに書くことにする。

###cifの取得方法
すでに気づいている人は気づいていると思うが、持ってきたデータの中には、cifのデータが入っている。cifを保存できれば、構造の情報は全て持ってこれると言っても過言ではない。

```
display(ferroel_BTO['cif'])
```

長いので、出力結果は省略するが、よく見るcifの内容が出力される。これを、保存しておいて使いたいので、以下のようなコードを走らせよう。ここはpymatgenの使い方というより、一般的なPythonの使い方なので、詳しくは省略するが、path_output_dirで指定したフォルダの中に、cifを保存している。（あえて新しいフォルダを作るのは、cifが増えたときに収集がつかなくなるから。）

```
#フォルダを扱うためのモジュール
import os

#cifを保存する先のフォルダの名前を指定する
path_output_dir = "./cif/"

#その名前のフォルダが存在しなければ新しく作成し、あればそこに保存する。
if not os.path.exists(path_output_dir):
    os.makedirs(path_output_dir)

with open(path_output_dir+ferroel_BTO['material_id']+".cif", mode="w") as f:
    f.write(ferroel_BTO['cif'])
```

cifから色々とデータを取り出すのは、また次のチュートリアルで書くことにする。

###他の検索の仕方
####化合物に含まれる元素で検索
「合成してみたら知らないXRDピークが出てきた。これはなんや。」といった場合に、参考データを持ってくるのに使える。こっちの例なら、一覧に組成式を表示している意味も出てくる。

```
#含まれる元素で化合物を検索するパターン
with MPRester(API_KEY) as m:
    mat_sys_BZO = m.get_data('Ba-Zr-O')

#組成式、晶系、空間群を表示する
for i, mat in enumerate(mat_sys_BZO):
    print(i, mat['pretty_formula'], mat['spacegroup']['crystal_system'], mat['spacegroup']['symbol'])

#出力結果：
#0 Ba6ZrO8 cubic Fm-3m
#1 BaZrO3 cubic Pm-3m
#2 Ba2Zr7O16 trigonal R-3
#3 Ba4ZrO6 trigonal R-3c
#4 Ba2ZrO4 tetragonal I4/mmm
#5 Ba2ZrO6 tetragonal P4/mmm
#6 BaZrO3 tetragonal I4/mcm
#7 Ba3Zr2O7 tetragonal I4/mmm
```

####ワイルドカードを使って検索
例えば、新しく材料を合成する際、候補を探してくる時などに使える。「*」（アスタリスク）がワイルドカードで、以下の例では、一般式ABO3を持つ化合物を検索している。

```
#ワイルドカードを使って検索するパターン
with MPRester(API_KEY) as m:
    ABO3 = m.get_data('**O3')

#それぞれの化合物のデータがリストに格納される
print(len(ABO3))

for i, mat in enumerate(ABO3[:5]):
    print(i, mat['pretty_formula'], mat['spacegroup']['crystal_system'], mat['spacegroup']['symbol'])

#出力結果：
#2509
#0 AcAlO3 cubic Pm-3m
#1 AcBO3 cubic Pm-3m
#2 AcCrO3 cubic Pm-3m
#3 AcCuO3 cubic Pm-3m
#4 AcFeO3 cubic Pm-3m
```
格納されたデータの量が多い(len(ABO3)で示した通り、2509件ヒットしてる)ので、最初の5つだけ表示している。ワイルドカードに添字をつけることも可能で、「Fe*2」などとすれば、一般式FeX2を持つ化合物を検索することができる。ちなみに、「化合物に含まれる元素を使って検索」する場合でもワイルドカードを使うことができ、例えば「*-Fe-O」などのようにクエリをかける。

[公式ドキュメント](https://github.com/materialsproject/mapidoc)によると、さらに高度な検索オプションを使用することができるが、以上のような検索ができれば、データをざっくり持ってくることに支障はない。（それに、そこに手を出すと、この記事が長くなりすぎる。）というわけで、この記事はここまでにする。また必要になったら、別記事として書くかもしれない。