In [74]:
%matplotlib inline
import subprocess
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize

import warnings
warnings.filterwarnings('ignore')

Оптимизированная структура этана в виде z-matrix с параметрами:

  • cc - длина связи $C-C$
  • ch - длина связи $C-H$
  • ah - валентный угол $HCC$
  • t1, t2, t3 - торсионные углы

Функция генерации файла orca.inp для расчёта энергии в ORCA:

In [26]:
def create_inp(cc = 1.52986, ch = 1.08439, ah = 111.200, t1 = 180, t2 = 120, t3 = -120):
    return '''!HF RHF 6-31G
    * int 0 1
    C 0 0 0 0 0 0 
    C 1 0 0 {0} 0 0 
    H 1 2 0 {1} {2} 0
    H 1 2 3 {1} {2} 120
    H 1 2 3 {1} {2} -120
    H 2 1 3 {1} {2} {3}
    H 2 1 5 {1} {2} {4}
    H 2 1 5 {1} {2} {5}
    *
    '''.format(cc, ch, ah, t1, t2, t3)

Функция по запуску ORCA:

In [27]:
def run_orca(inp, inp_name):
    with open(inp_name + ".inp", 'w') as outfile:
        outfile.write(inp)
    p = subprocess.Popen("/home/shad/progs/bin/orca " + inp_name + ".inp", 
                          shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out = p.communicate()[0]
    
    # extract energy: FINAL SINGLE POINT ENERGY'
    # and return it as float
    key_line = b"FINAL SINGLE POINT ENERGY"
    energy = [float(line[len(key_line):]) for line in out.splitlines() if line.startswith(key_line)]
    
    return energy[0]

Меняем параметр длины связи $C-C$ и смотрим, какая энергия получается при запуске ORCA:

In [28]:
cc_lengths = np.arange(-10, 10+1)*0.02 + 1.52986
cc_energies = [run_orca(create_inp(cc), "cc_lenght_" + str(cc)) for cc in cc_lengths]
In [29]:
print(cc_lengths)
print(cc_energies)
[ 1.32986  1.34986  1.36986  1.38986  1.40986  1.42986  1.44986  1.46986
  1.48986  1.50986  1.52986  1.54986  1.56986  1.58986  1.60986  1.62986
  1.64986  1.66986  1.68986  1.70986  1.72986]
[-79.045965591882, -79.05360304921, -79.06009493447, -79.065544856336, -79.070047524841, -79.073689505959, -79.076549917157, -79.078701062072, -79.080208995891, -79.081134076938, -79.081531432268, -79.081451406063, -79.080939964348, -79.080039064149, -79.078786992554, -79.077218678009, -79.075365975169, -79.07325792172, -79.070920986181, -79.068379282608, -79.06565477447]

Функция, отображающая зависимость энергии от изменяемого параметра:

In [68]:
def graph(x_param, y_energy, x_str = 'Length CC', y_str = 'Energy', \
          fitfunc_str = 'f(x) = k(b-x)^2 + a', 
          fitfunc = lambda p, x: p[0]*pow(p[1]-x,2) + p[2], \
          p0 = [1, 1, -79]):
    
    # Error function
    errfunc = lambda p, x, y: fitfunc(p, x) - y 
    
    # Optimize
    p1, success = optimize.leastsq(errfunc, p0[:], args=(x_param, y_energy))
    
    # Plot it
    print("Finction:", fitfunc_str)
    print("Optimized params:", p1)
    err = errfunc(p1, x_param, y_energy)
    print("MSE:", np.sum(err**2) / len(err))
    plt.plot(x_param, y_energy, "ro", x_param, fitfunc(p1, x_param), "r-", c='blue', alpha=0.5)
    plt.xlabel(x_str)
    plt.ylabel(y_str)
    plt.xlim(min(x_param), max(x_param))
    plt.show()

Построим зависимость энергии молекулы от длины $C-C$ связи:

In [69]:
graph(cc_lengths, cc_energies)
Finction: f(x) = k(b-x)^2 + a
Optimized params: [  0.63991912   1.55699289 -79.08219401]
MSE: 2.12506234096e-06

Получилась довольно хорошая аппроксимация.

В нашем случае оптимльная длина связи $r_{eq} = 1.56$, в статье $r_{eq} = 1.526$.

Проделаем аналогичные операции для валентного угла $HCC$:

In [46]:
hcc_angles = np.arange(-10, 10+1)*0.2 + 111.2
hcc_energies = [run_orca(create_inp(ah = hcc), "hcc_angle_" + str(hcc)) for hcc in hcc_angles]
In [47]:
print(hcc_angles)
print(hcc_energies)
[ 109.2  109.4  109.6  109.8  110.   110.2  110.4  110.6  110.8  111.
  111.2  111.4  111.6  111.8  112.   112.2  112.4  112.6  112.8  113.
  113.2]
[-79.078791141985, -79.079183428597, -79.079549502297, -79.079889341049, -79.080202921201, -79.080490217359, -79.080751202523, -79.080985847884, -79.081194122955, -79.081375995626, -79.081531432097, -79.081660396846, -79.081762852689, -79.081838760776, -79.081888080573, -79.08191076985, -79.081906784673, -79.081876079577, -79.081818607275, -79.081734318731, -79.081623163351]
In [64]:
graph(hcc_angles, hcc_energies, 'Angle HCC', 'Energy')
Finction: f(x) = k(b-x)^2 + a
Optimized params: [  3.31058512e-04   1.12270869e+02  -7.90819111e+01]
MSE: 1.09184031098e-12

Аппроксимация идеальна, очень маленький MSE.

Проделаем аналогичные операции для торсионного угла:

In [49]:
t_angles = np.arange(-180, 180, 12)
t_energies = [run_orca(create_inp(t1 = t, t2 = t - 60, t3 = t + 60), "t_angles_" + str(t)) for t in t_angles]
In [50]:
print(t_angles)
print(t_energies)
[-180 -168 -156 -144 -132 -120 -108  -96  -84  -72  -60  -48  -36  -24  -12
    0   12   24   36   48   60   72   84   96  108  120  132  144  156  168]
[-79.081531432268, -79.082613739522, -79.083665202645, -79.084283348632, -79.084235496024, -79.083542976542, -79.082468791954, -79.081419145702, -79.080793997916, -79.0808357103, -79.081531432091, -79.082613739391, -79.083665202653, -79.084283348629, -79.084235496012, -79.083542976544, -79.082468791963, -79.081419145697, -79.080793997905, -79.080835710303, -79.081531432093, -79.082613739378, -79.083665202645, -79.084283348638, -79.084235496018, -79.083542976535, -79.082468791962, -79.081419145706, -79.080793997904, -79.080835710292]
In [71]:
graph(t_angles, t_energies, 'Torsion', 'Energy')
Finction: f(x) = k(b-x)^2 + a
Optimized params: [  1.10053380e-08  -9.78443036e+01  -7.90827505e+01]
MSE: 1.5869595502e-06

Видно, что аппроксимация очень плохая.

Также, стоит заметить, что на заданном промежутке функция имеет три максимума и три минимума, что наводит на мысль о периодичности.

Попробуем аппроксимировать какой-нибудь периодической функцией: $f(x) = a \sin (bx + c) + d$

In [70]:
graph(t_angles, t_energies, 'Torsion', 'Energy', 'f(x) = a*sin(bx + c) + d', \
      lambda p, x: p[0]*np.sin(p[1]*x + p[2]) + p[3], \
          p0 = [1, 1, 1, -79])
Finction: f(x) = a*sin(bx + c) + d
Optimized params: [ -1.81236456e-03   9.94840541e-01   5.88366774e-01  -7.90825389e+01]
MSE: 5.87816478281e-12

В данном случае получилось довольно хорошее приближение.

Теперь увеличим шаг до $0.1$ при расчете длины связи:

In [54]:
new_cc_lengths = np.arange(-10, 10+1)*0.1 + 1.52986
new_cc_energies = [run_orca(create_inp(cc), "cc_lenght_" + str(cc)) for cc in new_cc_lengths]
In [55]:
print(new_cc_lengths)
print(new_cc_energies)
[ 0.52986  0.62986  0.72986  0.82986  0.92986  1.02986  1.12986  1.22986
  1.32986  1.42986  1.52986  1.62986  1.72986  1.82986  1.92986  2.02986
  2.12986  2.22986  2.32986  2.42986  2.52986]
[-73.376057637917, -75.600038681441, -76.986925125052, -77.840020404948, -78.364137018388, -78.683902778763, -78.875495242925, -78.986233962142, -79.045965591884, -79.073689505966, -79.081531432275, -79.077218678016, -79.065654774479, -79.049930686805, -79.031982712442, -79.013023538635, -78.99382026312, -78.974866464805, -78.956483613922, -78.938878326095, -78.922175263855]
In [72]:
graph(new_cc_lengths, new_cc_energies)
Finction: f(x) = k(b-x)^2 + a
Optimized params: [  2.64135277   1.81379257 -79.55943732]
MSE: 0.383250916557

Аппроксимация не удалась.

Попробуем описать экспоненциальной функцией: $f(x) = a e^{−bx+c}+d$

In [75]:
graph(new_cc_lengths, new_cc_energies, 'Length', 'Energy', 'f(x) = a e^{−bx+c}+d', \
      lambda p, x: p[0]*1/(np.exp(p[1]*x+p[2]))+p[3], 
      p0 = [1,1,1, -79])
Finction: f(x) = a e^{−bx+c}+d
Optimized params: [  9.11182601   5.2426422   -2.31128038 -79.04492106]
MSE: 0.00430726786751

Теперь аппроксимация получилась довольно хорошей.