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

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

紫外可視吸収波長自動算出アプリ(CustumTkinter)その2

前回から引き続き紫外可視吸収波長自動算出アプリの紹介です。
 具体的なコードですが、基本的なところは以前に紹介した分子軌道情報(HOMO/LUMO)や屈折率の自動算出アプリと同様です。
 最初にライブラリーのインポートと各種関数の設定を行っています。
 紫外可視吸収波長(エネルギー)と振動子強度の計算には「tdscf_excitations」をインポートしています。励起エネルギーの計算や波長データへの変換等は以前のブログ記事「Psi4による量子化学計算-紫外可視光吸収データ(その1,その2)」を参照してもらえればと思います。
 今回のアプリでは設定項目として、計算する励起エネルギーレベル数として「states」を設定しています(デフォルトは10)。
 Stete数に応じて得られたデータはnumpyのアレイ型データとした後に、SMILES、振動計算の結果とともにpandas のデータフレーム型としてまとめています。各列に同じSMILES、振動計算の結果が並ぶお世辞にも見栄えの良いデータとはなっていませんが、データの確認には支障がないかと思ってます。また時間があればこの辺も改良できればと思います。
 
 上記説明以外の部分は分子軌道情報(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 psi4.driver.procrouting.response.scf_response import tdscf_excitations
from  rdkit import rdBase, Chem
from rdkit.Chem import AllChem


# 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 MO_anal():
    # Molecular orbital analysis
    meth=method_sv.get()
    func=function_sv.get()
    base=baseset_sv.get()
    sta=states_input.get()
    global wfn
    global ene_mo 
    global sumarray
    
    psi4.set_options({'save_jk':True})
    
    # Molecular orbital analysis (method in HF and MP2, function in DFT)
    if meth=='HF':        
        ene_mo, wfn = psi4.energy(meth+'/'+base, molecule=molgeom, return_wfn=True)
    
    elif meth=='DFT':
        ene_mo, wfn = psi4.energy(func+'/'+base, molecule=molgeom, return_wfn=True)
        
    elif meth=='MP2':
        ene_mo, wfn = psi4.energy(meth+'/'+base, molecule=molgeom, return_wfn=True)
    
    # TDDFT calculation (UV-VIS)  
    res = tdscf_excitations (wfn, states =sta)

    exenergy = [r["EXCITATION ENERGY"] for r in res]
    osstrength = [r["OSCILLATOR STRENGTH (LEN)"] for r in res]

    arrayex = np.array(exenergy)
    arrayos = np.array(osstrength)
    arraynm0 =[(1240/(arrayex*27.21162))]
    arraynm = np.array(arraynm0)

    arrayexr = np.reshape(arrayex,(-1,1))
    arrayosr = np.reshape(arrayos,(-1,1))
    arraynmr = np.reshape(arraynm,(-1,1))
    
    sumarray=np.c_[arrayexr,arraynmr,arrayosr]
    

def calc_run():
    global fname_smi
    chr=charge.get()
    mul=multi.get()
    sta=states_input.get()
    
    with open (fname_smi) as f:
        readsmiles=f.read()
    
    smiles=readsmiles.split()
    
    # Frequency calculations list
    freq_results=[]
    
    #  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)
    
        # 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)  
          
        MO_anal()
        root.update()
        
        smiIndex=np.repeat(smi, sta).reshape(-1,1) 
        freq_result=np.repeat(freq_data, sta).reshape(-1,1)
        
        UVdata_np=np.hstack([smiIndex, sumarray])
        UVdata_np=np.hstack([UVdata_np, freq_result])
        
        if i==0:
            sumUV_nplist=UVdata_np

        else:
            sumUV_nplist=np.vstack([sumUV_nplist, UVdata_np])
            
        
        # column の作成
        columns1=["SMILES","Excitation Energy /au ","Excitation Energy /nm","Oscilleator Strength ","Freq_result"]
     
        Data_UV_VIS=pd.DataFrame(data=sumUV_nplist, columns=columns1)          
            
        Data_UV_VIS.to_csv('Calcd_UV_VIS_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のパートについて紹介したいと思います。