あるケミストの独り言(winchemwinの日記)

ケミスト(化学者)の視点で、面白そうな情報(シミュレーション関係など)を発信

屈折率自動算出アプリ(CustumTkinter) その2

 前回から引き続き屈折率自動算出アプリの紹介です。
 具体的なコードですが、基本的なところは以前に紹介した分子軌道情報(HOMO/LUMO)の自動算出アプリと同じです。
 最初にライブラリーのインポートと各種関数の設定を行っています。
 屈折率はローレンツローレンツ式から分子の分極率と分子体積の情報をあれば求めることができるので、今回は分極率計算を'Pol_calc()'の関数で定義しています。分極率は基本的には分散関数を用いた計算で行うことでより正確な値が得られますが、計算コストの関係で、より簡便な計算手法(分散関数を用いないレベル)でも計算できるように設定しています。
 分子体積の方はRdkitのモジュール(AllChem.ComputeMolVolume)で計算したものを用いています。ここで得られる分子体積については、ローレンツローレンツ式で一般的に用いられる数値よりも小さめになっていますが、分子の構造変化に伴う「傾向」を掴むという意味では使える数値ではないかということで採用でしています。
 最終的に計算された分極率と分子体積から「# 屈折率計算の部分」のコードに基づき、屈折率を計算しています。
 上記説明以外の部分は分子軌道情報(HOMO/LUMO)の自動算出アプリとほぼ同じかと思います。

import customtkinter as ctk
import tkinter as tk
from tkinter import ttk
from tkinter import StringVar, messagebox
from turtle import onclick
from tkinter import filedialog
import numpy as np
import pandas as pd
from numpy import matlib
from tkinter.ttk import Entry
import psi4
from  rdkit import rdBase, Chem
from rdkit.Chem import AllChem
import re

# Smiles file select by filedialog 
def data_import(self):
    type=[('smi file','*.smi'), ('text file', '*.txt')]
    file=filedialog.askopenfilename(filetypes=type)
    filename_sv.set(file)
    global fname_smi
    fname_smi=filename_sv.get()

    return fname_smi

def start_calc_button(self):
    Labelex_1=ctk.CTkLabel(master=root, text='Calculation was started',font=("Times",12))
    Labelex_1.place(x=50, y=370)
    
    root.after(100, calc_run)
    
def Geom_Opt():
    # Geometry optimization
    # getting meth(calculation method), func(function), base(basis set) 
    meth=method_sv.get()
    func=function_sv.get()
    base=baseset_sv.get()
    
    psi4.set_options({'geom_maxiter': 50,
    'optking__g_convergence': 'gau_loose',})
    
    # geometry optimize calculation (method in HF and MP2, function in DFT)
    if meth=='HF':
        psi4.optimize(meth+'/'+base, molecule=molgeom)
        
    elif meth=='DFT':
        
        psi4.optimize(func+'/'+base, molecule=molgeom)
        
    elif meth=='MP2':
        psi4.optimize(meth+'/'+base, molecule=molgeom)
    
    optimized_geom = molgeom.save_string_xyz_file()
                  
def Vib_Calc():
    # Frequency calculation
    meth=method_sv.get()
    func=function_sv.get()
    base=baseset_sv.get()
    
    # Frequency calculation (method in HF and MP2, function in DFT)
    if meth=='HF':        
        energy, wfn = psi4.frequency(meth+'/'+base, molecule=molgeom, return_wfn=True)
        
    elif meth=='DFT':
        energy, wfn = psi4.frequency(func+'/'+base, molecule=molgeom, return_wfn=True)
        
    elif meth=='MP2':
        energy, wfn = psi4.frequency(meth+'/'+base, molecule=molgeom, return_wfn=True)
    
    # making frequency list

    freqlist = np.array(wfn.frequencies())
    freqposi=freqlist[(freqlist>1)&(freqlist<5000)]
    global freqnega
    freqnega=freqlist[(freqlist<0)]

    return freqposi, freqnega

def Pol_calc():
    meth_pol=method_sv_pol.get()
    base=baseset_sv.get()
          
    polar = psi4.properties(meth_pol+'/'+base,properties=['polarizability'], molecule=molgeom)
        

def calc_run():
    global fname_smi
    chr=charge.get()
    mul=multi.get()
    
    with open (fname_smi) as f:
        readsmiles=f.read()
    
    smiles=readsmiles.split()
    
    # 振動計算の結果のリスト
    freq_results=[]
    # Smile Index のリスト 
    smiIndex=[]
    
    # 各データのリスト
    polar_list=[]
    volume_list=[]
    refIndex_list=[]
      
    #  Conformer search by rdkit then make xyz file
    for i, smi in enumerate(smiles): 

        mol = Chem.MolFromSmiles(smi)
        mol_H = Chem.AddHs(mol)
            
        confs = AllChem.EmbedMultipleConfs(mol_H, 10, pruneRmsThresh=1)
        prop = AllChem.MMFFGetMoleculeProperties(mol_H)

        energy=[]
        
        for conf in confs:
            mmff = AllChem.MMFFGetMoleculeForceField(mol_H, prop,confId=conf)
            mmff.Minimize()
            energy.append((mmff.CalcEnergy(), conf))
    
            conflist = np.array(energy)
            sortconf=conflist[np.argsort(conflist[:,0]) ]

            stconfid=sortconf[0,1]
            stconfid2=int(stconfid)
            stconfgeom=mol_H.GetConformer(stconfid2)

        xyz = f'{chr} {mul}'
        for atom, (x,y,z) in zip(mol_H.GetAtoms(), stconfgeom.GetPositions()):
            xyz += '\n'
            xyz += '{}\t{}\t{}\t{}'.format(atom.GetSymbol(), x, y, z)
                  
        #  Setting input file
        global molgeom
        molgeom = psi4.geometry(xyz)
               
    # Set calculation method
        thre=threads.get()
        mem=memory.get()
        psi4.set_num_threads(nthread=thre)
        psi4.set_memory(mem*1000000)
       
        psi4.core.clean()
        # Set output files
        psi4.set_output_file(str(i)+smi+'-molgeom.txt')

        # 計算経過の表示
        # global root, Labelex_2
        Labelex_2=ctk.CTkLabel(master=root, text='No'+str(i)+'  '+smi+' Calc was started',font=("Times",10))
        Labelex_2.place(x=50, y=390)
       
        Labelex_2.update()
        
        # 構造最適化ー収束エラー時の対応も含む
        try:
            Geom_Opt()
            root.update()
        except  psi4.driver.p4util.exceptions.OptimizationConvergenceError as err:
            print (err)
       
            continue
        
        optimized_geom = molgeom.save_string_xyz_file()
        with open (str(i)+smi+'-optgeom.xyz','w') as f:
            f.write(optimized_geom)
        
        # 振動計算
        Vib_Calc()
        root.update()
        if freqnega:
            freq_data='True'         
        else:
            freq_data='False'
            
        freq_results.append(freq_data)  
        
        # 分極率計算
        Pol_calc()
        root.update()
        
        path = '/home/winmori/'+str(i)+smi+'-molgeom.txt'
        with open (path) as f:
            alltext= f.readlines()
        endgyou = len(alltext)
        endtxt = alltext[endgyou-1].strip()
        print (endtxt)

        polar_base=re.findall(r"[\d\.\d]+",endtxt)
        
        polar_result=float(polar_base[1])
        
        
        # 分子体積計算(rdkit)
        try:
            AllChem.EmbedMolecule(mol_H)
            rdvdwvol=AllChem.ComputeMolVolume(mol_H)
        except ValueError:
            pass
        
        # 屈折率計算
        vol=(1/(rdvdwvol*(10**-30)))
        fai=(4*3.14159/3)*vol*(polar_result*0.148185*10**-30)
        refindex=((1+2*fai)/(1-fai))**0.5

        # データのまとめ

        refIndex_list.append(refindex)
        polar_list.append(polar_result)
        volume_list.append(rdvdwvol)
        
        smiIndex.append(smi)
        
        Data_RI=pd.DataFrame(list(zip(smiIndex, polar_list, volume_list, refIndex_list)) , columns=['Smiles','polarizability/a.u', 'VdwVolume/Angstrom3/mol', 'Refractive Index']) 
       
        Data_RI['FreqNega']=freq_results
    
        Data_RI.to_csv('Calcd_RI_List.csv')     
    
    Labelex_4=ctk.CTkLabel(master=root, text='Calculation was finished', font=("Times",12,"bold"))
    Labelex_4.place(x=50, y=410)
                
# finish program

def scry_finish():
    exit()        

 以上、屈折率自動算出アプリのコード(関数設定、屈折率計算方法等)について紹介させていただきました。
 次回はCustumTkinterのパートについて紹介したいと思います。