import ase
from ase import Atoms
from ase.io import write
from ase.vibrations import Vibrations
from .log import reset_logger,add_filehandler
import matlanticgrrm.atomslist as ma
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from scipy.signal import find_peaks
from pathlib import Path
import sys,os,math,pickle
from tqdm.notebook import tqdm
from logging import getLogger,StreamHandler,FileHandler,Formatter,DEBUG,INFO,WARNING,ERROR,CRITICAL
[ドキュメント]class ListVibrations():
"""ase.vibrations.VibrationsをAtomsのリストでも使えるようにしたもの
Parameters:
atoms_list (list(Atoms)) : Atomsのリスト
errorlog (str) : logのファイルパス
indexex (list) : 計算対象をリストで指定 <--ここまでがListVibrations特有の引数
indices (list) : 動かす原子をリストで指定 <-- ここからがVibrationsの引数aseのDocumentを参照
"""
def __init__(self,atoms_list,errorlog,indexes=None,indices=None, name='vib', delta=0.01, nfree=2):
self.atoms_list = atoms_list
self.errorlog = errorlog
self.indexes = indexes if indexes else [i for i in range(len(self.atoms_list))]
self.indices = indices
self.name = Path(name)
self.delta = delta
self.nfree = nfree
self.vib = []
if not Path(name).exists():
os.makedirs(name)
def __iter__(self):
"""Optimizerを使用するためには__iter__を実装する必要がある"""
return iter(self.vib)
def __len__(self):
"""Optimizerを使用するためには__len__を実装する必要がある"""
return len(self.vib)
def __getitem__(self, item):
return self.vib[item]
def mk_vib_func(self,atoms,foldername):
return Vibrations(atoms, indices=self.indices, name=f"{self.name}/{foldername}", delta=self.delta, nfree=self.nfree)
def _start_number(self):
self.logger = reset_logger() #loggerをリセット
if Path("stop.log").exists():
with open("stop.log","r") as f:
n = int(f.read()) #n番目から計算を始める
os.remove("stop.log")
add_filehandler(self.logger,"a",self.errorlog,DEBUG)
else:
n = 0
add_filehandler(self.logger,"w",self.errorlog,DEBUG)
return n
[ドキュメント] def run(self):
"""
| logの内容[logレベル]
| {1} : index未指定 [INFO]
| {2} : Atomsのオブジェクトが存在しない [INFO]
| {5} : エラー [CRITICAL]
Note:
Imaginary_Frequency.htmlとImaginary_Freq_Idx.txtを出力する.
実行中のnotebookと同じディレクトリにstop.logを作成すると計算を中断する.
"""
n = self._start_number() #n番目から計算を始める
self.vib = [None for _ in range(n-1)] if len(self.vib)==n-1 else self.vib
for i, atoms in enumerate(tqdm(self.atoms_list[n:]),start=n):
if Path("stop.log").exists():
if write_steplog is False:
with open("stop.log","w") as f:
f.write(str(i))
write_steplog = True
continue
write_steplog = False
if not i in self.indexes:
self.logger.info(f"i={i} index未指定 return 1")
self.vib.append(None)
elif not atoms:
self.logger.info(f"i={i} objectが存在しない return 2")
self.vib.append(None)
else:
try:
vib = self.mk_vib_func(atoms,str(i))
vib.run()
#runまで到達したら
self.vib.append(vib)
self.logger.info(f"i={i} 完了 return 0")
except Exception as e:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
self.logger.critical(f"i={i} {exc_type} {fname} {exc_tb.tb_lineno} return 5")
self.vib.append(None)
def clean(self,empty_files=False, combined=True):
"""self.nameのフォルダ内の全てjsonファイルを削除する"""
for vib_obj in self.vib:
if vib_obj:
vib_obj.clean(empty_files=empty_files,combined=combined)
[ドキュメント] def summary(self,html="vib_table.html",pkl=None):
"""
Parameters:
html: str or path object
htmlパス.htmlに表を保存する. Noneの場合は標準出力だが,数の多い場合は推奨しない
pkl:
pickleパス.指定した場合,各構造の虚数振動のモード番号をリストとしてpickleに保存する.
"""
sample_n = len(self.vib)
cols = 3
rows = math.ceil(sample_n/cols)
specs = [[{"type": "table"} for _ in range(cols)] for _ in range(rows)]
subplot_titles = [f"TS{i}" for i in range(sample_n)]
fig = make_subplots(
rows=rows, cols=cols,
specs=specs,
subplot_titles = subplot_titles
)
div_sample = []
for i in range(0, sample_n, cols):
"""データをcol分割する"""
div_sample.append(self.vib[i: i+cols])
def add_table(fig,df,row,col,fill_color):
fig.add_trace(
go.Table(
header=dict(values=list(df.columns),
fill = {"color":"rgb(68,114,196)"},
font = {"color":"rgb(255,255,255)","family" : "Arial"},
align='center'),
cells=dict(values=[df["<b>#</b>"], df["<b>meV</b>"], df["<b>cm^-1</b>"]],
fill = {"color":"rgb(233,235,245)"},
fill_color = fill_color,
font = {"family":"Arial"},
align='center')
),
row=row, col=col)
self.i_idx = [] #虚数振動のidx
for i, row_vib in enumerate(div_sample,start=1):
for j , vib_obj in enumerate(row_vib,start=1):
if vib_obj:
vib_data = vib_obj.get_vibrations()
text = vib_data.tabulate().split("\n") #summaryを取得
df = pd.DataFrame(
data=np.array([i.split() for i in text[3:-3]]),
columns=['<b>#</b>', '<b>meV</b>', '<b>cm^-1</b>'])
imaginary_vib_list = df.index[df['<b>cm^-1</b>'].str.contains('i')].tolist() #虚振動を含む行番号を抽出
imaginary_vib_list = [i for i in imaginary_vib_list if not i in [1,2,3,4]] #1~4は無視
if len(imaginary_vib_list) == 1: #虚振動が1つだげ存在するとき
self.i_idx.append(imaginary_vib_list[0])
fill_color = [["rgb(233,235,245)" if i!=imaginary_vib_list[0] else "rgb(255,155,155)" for i in range(len(df))]]*3
else:
fill_color = [["rgb(233,235,245)" for _ in range(len(df))]]*3
self.i_idx.append(None)
add_table(fig,df,i,j,fill_color)
else:
self.i_idx.append(None)
fig.update_layout(height=700*rows, width=1000,
title_text="振動")
if html:
fig.write_html(html)
else:
fig.show()
if pkl:
with open(pkl,"wb") as f:
pickle.dump(self.i_idx,f)
def combine(self):
pass
def fold(self):
pass
def get_energies(self):
pass
def get_frequencies(self):
pass
def get_mode(self):
pass
def get_vibrations(self):
pass
def iterdisplace(self):
pass
def iterimages(self):
pass
def split(self):
pass
def write_dos(self):
pass
def write_jmol(self):
pass
[ドキュメント] def write_mode(self,n_list=None,savename="vib_all", kT=0.02585199101165164, nimages=30):
"""trajファイルを作成する
Parameters:
n_list: list of integer
| 各構造のtrajとして書き出したい振動モードをリストで与える.
| Noneの場合, 虚振動を自動判断する.
savename: str
保存ファイル名(拡張子なし)
Note:
n_list=Noneを使う場合,事前にsummary()を実行しておく必要がある.
その他の引数はASEを参照
"""
if n_list is None:
n_list = self.i_idx
status_list = []
with ase.io.Trajectory(f"{savename}.traj", 'w') as traj:
for i,(n,vib_obj) in enumerate(zip(n_list,self.vib)):
if type(n) == int: # Noneの要素は受け付けない
status_list.append(0)
n %= len(vib_obj.get_energies())
for image in (vib_obj.get_vibrations().iter_animated_mode(n,temperature=kT, frames=nimages)):
traj.write(image)
else: # 要素がNoneの場合は空のAtomsを挿入する
status_list.append(2)
for _ in range(nimages):
traj.write(Atoms())
with open(f"{savename}.pickle","wb") as f:
pickle.dump((status_list,nimages),f)
[ドキュメント]def vib_graphs(imagess,html="vib_graph.html",is_traj="Reverse.traj",fs_traj="Forward.traj",ts_traj="TS.traj"):
"""
| Reverse.traj,Forward.traj,TS.trajが作成される.
| Reverse.traj,Forward.trajは構造最適化(IRC)を行なうことで始構造, 終構造となる.
Parameters:
imagess: 2D list of Atoms
Atomsの2次元リスト.ListVibration.write_mode()で作成したtrajファイルから用意する.
"""
energiess = [ma.get_potential_energy(images) if images else None for images in imagess]
sample_n = len(energiess)
cols = 3 #3列
rows = math.ceil(sample_n/cols)
subplot_titles = [f"TS{i}" for i in range(sample_n)]
"""データをcol個ずつに分割する"""
div_sample = []
for i in range(0, sample_n, cols):
div_sample.append(energiess[i: i+cols])
def add_fig(fig,x,y,row,col,color):
fig.add_trace(go.Scatter(x=x, y=y,
line=dict(width=2,color=color)),
row=row, col=col)
fig = make_subplots(
rows=rows, cols=cols,
subplot_titles=subplot_titles)
idx = []
for i, row_data in enumerate(div_sample,start=1):
for j, data in enumerate(row_data,start=1):
if data:
x = [i for i in range(len(data))]
y = data
add_fig(fig,x,y,i,j,'black')
div_data = list(np.array_split(data, 3))[1] #3分割した内,真ん中のデータを抽出
peaks_i, _ = find_peaks(div_data)
if len(peaks_i) == 1: #ピークが1つだけあれば
x = peaks_i[0]+len(div_data)
y = data[x]
add_fig(fig,[x],[y],i,j,'red')
idx.append(x)
else:
idx.append(None)
else:
idx.append(None)
fig.update_layout(height=300*rows, width=1000,
title_text="エネルギーダイアグラム")
if html:
fig.write_html(html)
else:
fig.show()
is_list = []
ts_list = []
fs_list = []
for i, images in zip(idx,imagess):
if i:
is_list.append(images[i-1])
ts_list.append(images[i])
fs_list.append(images[i+1])
else:
is_list.append(Atoms())
ts_list.append(Atoms())
fs_list.append(Atoms())
write(is_traj,is_list)
write(ts_traj,ts_list)
write(fs_traj,fs_list)
return idx