Суть задания состоит в определении констант ковалентных взаимодействий для молекулярной механики на основе квантово-химических расчётов. Будем использовать молекулу этана.

Это оптимизированная структура этана в виде z-matrix. Сделаем 40 значений связи С-С с шагом +-0.02 (20 в сторону уменьшения и 20 в сторону увеличения). Запишем в 40 файлов ethaneXX.txt, где ХХ - номер от 0 до 40.

In [26]:
def gen_inp():
    for i in range(-20,20):
        bond = 1.52986 + i*0.02

        inp = '''!HF RHF 6-31G
        * int 0 1
        C 0 0 0 0 0 0 
        C 1 0 0 %f 0 0 
        H 1 2 0 1.08439 111.200 0
        H 1 2 3 1.08439 111.200 120
        H 1 2 3 1.08439 111.200 -120
        H 2 1 3 1.08439 111.200 180
        H 2 1 6 1.08439 111.200 120
        H 2 1 6 1.08439 111.200 -120
        *
        ''' % bond
        output_name = 'ethane_%i.txt' %(i+20)
        output_file = open(output_name,"w")
        output_file.writelines(inp)
        output_file.close()

gen_inp()

Запускаем ORCA с этими параметрами. На выходе - значение энергии структуры (FINAL SINGLE POINT ENERGY).

In [19]:
import subprocess

def run_orca(num):
    cmd1 = '/srv/databases/orca/orca ethane_%d.txt > orca_temp.txt' %num
    cmd2 = 'grep "FINAL SINGLE POINT ENERGY" orca_temp.txt'
    subprocess.call(cmd1, shell=True)
    p = subprocess.Popen(cmd2, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    p_status = p.wait()
    out=iter(p.stdout.readline, b'')
    for line in out:
        energy = line.strip(None).strip('FINAL SINGLE POINT ENERGY').strip()

    return float(energy)

Значения энергии для каждой длины С-С связи:

In [55]:
for i in range(0,40): 
    print run_orca(i)
-79.0002303755
-79.0268658005
-79.0506563332
-79.0718538287
-79.0906881561
-79.1073691801
-79.1220885524
-79.1350213185
-79.1463273793
-79.1561528142
-79.1646310788
-79.1718840905
-79.1780232249
-79.1831502218
-79.1873580112
-79.1907314652
-79.1933480904
-79.1952786598
-79.1965877757
-79.1973344173
-79.1975724044
-79.1973508427
-79.196714525
-79.1957042977
-79.1943573968
-79.1927077558
-79.190786288
-79.1886211402
-79.1862379374
-79.183659995
-79.1809085201
-79.1780027968
-79.1749603557
-79.171797126
-79.1685275859
-79.1651648884
-79.1617209853
-79.1582067376
-79.1546320182
-79.1510058041

Изобразим полученную зависимость энергии от длины С-С связи.

In [21]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize 
from scipy.fftpack import fft, ifft
import math
In [79]:
# x array , lengths of C-C bonds
x_o=[]
# y array, energies
y_o=[]

for i in range(-20,20):
    bond = 1.52986 + i*0.02
    x_o.append(bond) 
    energy = run_orca(i+20)
    y_o.append(energy)
In [80]:
x_o=np.array(x_o)
y_o=np.array(y_o)
print x_o
print y_o
#function is  f(x)=k(x-b)^2 + a
fitfunc = lambda p, x: p[0]*pow(x-p[1],2) + p[2] # Target function
errfunc = lambda p, x, y: fitfunc(p, x) - y # Error function

#function is  f(x)=k*(ln(сx-b))^2 + a
my_fitfunc = lambda p, x: p[0]*pow(np.log(p[3]*x-p[1]),2) + p[2]
my_errfunc = lambda p, x, y: my_fitfunc(p, x) - y

#function is  f(x)=с*(1-e^(-k(x-a)))^2
mors_fitfunc = lambda p, x: p[2]*pow(1-np.e**(-p[1]*(x-p[0])),2)
mors_errfunc = lambda p, x, y: mors_fitfunc(p, x) - y

p0 = [1, 1, -80, 1] # Initial guess for the parameters
p1, success = optimize.leastsq(errfunc, p0[:-1], args=(x_o, y_o))
p2, success = optimize.leastsq(my_errfunc, p0[:], args=(x_o, y_o))
p3, success = optimize.leastsq(mors_errfunc, p0[:-1], args=(x_o, y_o))

p4 = [1.30722816, 0.30222351, -79.26679609, 0.80124823]
print "Optimized params for x^2:", p1
print "Optimized params for ln^2:", p2
print "Optimized params for Morse:", p3

#Plot it
plt.plot(x_o, y_o, "bo", x_o,fitfunc(p1,x_o),"r-", x_o, my_fitfunc(p2,x_o), "b-", x_o, mors_fitfunc(p3,x_o), "g-", x_o, my_fitfunc(p4,x_o), 'k-', alpha=0.5)
plt.xlim(1,2)
plt.show()
[ 1.12986  1.14986  1.16986  1.18986  1.20986  1.22986  1.24986  1.26986
  1.28986  1.30986  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  1.74986
  1.76986  1.78986  1.80986  1.82986  1.84986  1.86986  1.88986  1.90986]
[-79.00023038 -79.0268658  -79.05065633 -79.07185383 -79.09068816
 -79.10736918 -79.12208855 -79.13502132 -79.14632738 -79.15615281
 -79.16463108 -79.17188409 -79.17802322 -79.18315022 -79.18735801
 -79.19073147 -79.19334809 -79.19527866 -79.19658778 -79.19733442
 -79.1975724  -79.19735084 -79.19671453 -79.1957043  -79.1943574
 -79.19270776 -79.19078629 -79.18862114 -79.18623794 -79.18365999
 -79.18090852 -79.1780028  -79.17496036 -79.17179713 -79.16852759
 -79.16516489 -79.16172099 -79.15820674 -79.15463202 -79.1510058 ]
Optimized params for x^2: [  0.77924886   1.59808014 -79.20515722]
Optimized params for ln^2: [  0.2790648    1.14337717 -79.19734781   1.39222176]
Optimized params for Morse: [  0.5122066   10.82627615 -79.18420482]

Разберемся с подбранными параметрами оптимизации (строки Optimized params for ...): [0.77924885 1.59808014 -79.20515722] для x^2 [0.27906518 1.14337602 -79.19734782 1.39222098] для (ln x)^2

Для параболы первое число - это коэффициент жесткости связи как пружинки: 0.77924881 эВ = 17.97 ккал/моль = 75.19 кДж/моль для x^2 Коэффициенты логарифма интерпретировать трудно, зато если их подобрать, то можно учесть, что связь - не пружинка. Для пересчета использовался конвертер: http://www.colby.edu/chemistry/PChem/Hartree.html

Парабола (показана красной линией) не точно совпадает с точками, т.к. межатомный потенциал на самом деле не является параболическим, а описывается более сложными функциями. Логарифм в квадрате (показан синей линией) выглядит гораздо убедительнее, то есть математически логарифм подходит лучше. Потенциал Морзе на таких маленьких расстояниях не работает (показан зелёным). Черным показан график логарифма в квадрате, оптимизированный под большой шаг (см. дальше)

Увеличим шаг до 0,1 при расчете связи. Полученная зависимость плохо аппроксимируется параболой и хорошо - потенциалом Морзе и всё тем же логарифмом в квадрате.

In [75]:
def gen_inp4():
    x_o=[]
    for i in range(-10,10):
        bond = 1.52986 + i*0.1
        x_o.append(bond)
        inp = '''!HF RHF 6-31G
        * int 0 1
        C 0 0 0 0 0 0 
        C 1 0 0 %f 0 0 
        H 1 2 0 1.08439 111.200 0
        H 1 2 3 1.08439 111.200 120
        H 1 2 3 1.08439 111.200 -120
        H 2 1 3 1.08439 111.200 180
        H 2 1 6 1.08439 111.200 120
        H 2 1 6 1.08439 111.200 -120
        *
        ''' % bond
        output_name = 'ethane_%i.txt' %(i+110)
        output_file = open(output_name,"w")
        output_file.writelines(inp)
        output_file.close()
    return x_o

x_o = gen_inp4()   
In [76]:
y_o=[]
for i in range(100,120): 
    energy = run_orca(i)
    print energy
    y_o.append(energy)
-73.5474491176
-75.7731892733
-77.1455640089
-77.9863168532
-78.5009745828
-78.8137249118
-79.0002303755
-79.1073691801
-79.1646310788
-79.1907314652
-79.1975724044
-79.1927077558
-79.1809085201
-79.1651648884
-79.1473362613
-79.1285768897
-79.1096103451
-79.0908991228
-79.0727437835
-79.0553382769
In [78]:
x_o=np.array(x_o)
y_o=np.array(y_o)
print x_o
print y_o
#function is  f(x)=k(x-b)^2 + a
fitfunc = lambda p, x: p[0]*pow(x-p[1],2) + p[2] # Target function
errfunc = lambda p, x, y: fitfunc(p, x) - y # Error function

#function is  f(x)=k*(ln(сx-b))^2 + a
my_fitfunc = lambda p, x: p[0]*pow(np.log(p[3]*x-p[1]),2) + p[2]
my_errfunc = lambda p, x, y: my_fitfunc(p, x) - y

#function is  f(x)=(1-e^(-k(x-a)))^2
mors_fitfunc = lambda p, x: p[2]*pow(1-np.e**(-p[1]*(x-p[0])),2)
mors_errfunc = lambda p, x, y: mors_fitfunc(p, x) - y

p0 = [0.5, 0.5, -80, 1] # Initial guess for the parameters
p1, success = optimize.leastsq(errfunc, p0[:-1], args=(x_o, y_o))
p2, success = optimize.leastsq(my_errfunc, p0[:], args=(x_o, y_o)) 
p3, success = optimize.leastsq(mors_errfunc, p0[:-1], args=(x_o, y_o))

print "Optimized params for x^2:", p1
print "Optimized params for ln^2:", p2
print "Optimized params for Morse:", p3

p4 = [0.27906518, 1.14337602, -79.19734782, 1.39222098]
#Plot it
plt.plot(x_o, y_o, "bo", x_o,fitfunc(p1,x_o),"r-", x_o, my_fitfunc(p2,x_o), "b-", x_o, mors_fitfunc(p3,x_o), "g-", x_o, my_fitfunc(p4,x_o), 'k-', alpha=0.5)
plt.xlim(0.5,2.5)
plt.show()
[ 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]
[-73.54744912 -75.77318927 -77.14556401 -77.98631685 -78.50097458
 -78.81372491 -79.00023038 -79.10736918 -79.16463108 -79.19073147
 -79.1975724  -79.19270776 -79.18090852 -79.16516489 -79.14733626
 -79.12857689 -79.10961035 -79.09089912 -79.07274378 -79.05533828]
Optimized params for x^2: [  2.92138599   1.75787795 -79.67571834]
Optimized params for ln^2: [  1.30722816   0.30222351 -79.26679609   0.80124823]
Optimized params for Morse: [ -0.09264486   5.32250225 -79.16869682]
/srv/databases/orca/miniconda2/lib/python2.7/site-packages/ipykernel/__main__.py:10: RuntimeWarning: invalid value encountered in log

Ошибка возникает из-за ограниченности области определения логарифма, оптимизтрованного под маленький шаг. Интересно, что логарифм в квадрате хорошо апроксимирует зависимости энергии связи от расстояния между атомами, но, к сожалению, для каждого шага разные параметры.

Теперь проведем аналогичные операции для валентного угла HCС. Его значения должны изменяться от 109.2 до 113.2 с шагом 0.2. Аналогично, запишем в 20 файлов ethaneXX.txt, где ХХ - номер от 40 до 60.

In [36]:
def gen_inp2():
    for i in range(0,20):
        angle = 109.2 + i*0.2

        inp = '''!HF RHF 6-31G
        * int 0 1
        C 0 0 0 0 0 0 
        C 1 0 0 1.52986 0 0 
        H 1 2 0 1.08439 %f 0
        H 1 2 3 1.08439 111.200 120
        H 1 2 3 1.08439 111.200 -120
        H 2 1 3 1.08439 111.200 180
        H 2 1 6 1.08439 111.200 120
        H 2 1 6 1.08439 111.200 -120
        *
        ''' % angle
        output_name = 'ethane_%i.txt' %(i+40)
        output_file = open(output_name,"w")
        output_file.writelines(inp)
        output_file.close()
gen_inp2()       
In [28]:
for i in range(40,60): 
    print run_orca(i)
-79.1974104831
-79.1974413993
-79.1974690337
-79.1974933899
-79.1975144717
-79.1975322827
-79.1975468264
-79.1975581066
-79.1975661267
-79.1975708903
-79.197572401
-79.1975706621
-79.1975656772
-79.1975574496
-79.1975459827
-79.1975312799
-79.1975133446
-79.1974921799
-79.1974677893
-79.1974401758
In [30]:
# x array , HCC angles
x_o=[]
# y array, energies
y_o=[]

for i in range(0,20):
    angle = 109.2 + i*0.2
    x_o.append(angle) 
    energy = run_orca(i+40)
    y_o.append(energy)
In [31]:
x_o=np.array(x_o)
y_o=np.array(y_o)
print x_o
print y_o
#function is  f(x)=k(b-x)^2 + a
fitfunc = lambda p, x: p[0]*pow(p[1]-x,2) + p[2] # Target function
errfunc = lambda p, x, y: fitfunc(p, x) - y # Error function

p0 = [1,1, -80] # Initial guess for the parameters
p1, success = optimize.leastsq(errfunc, p0[:], args=(x_o, y_o))
print "Optimized params:", p1

#Plot it
plt.plot(x_o, y_o, "ro", x_o,fitfunc(p1,x_o),"r-",c='blue',alpha=0.5)
plt.title('Energy(angle)')
plt.xlim(108,115)
plt.show()
[ 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. ]
[-79.19741049 -79.1974414  -79.19746904 -79.19749339 -79.19751448
 -79.19753229 -79.19754683 -79.19755811 -79.19756613 -79.19757089
 -79.1975724  -79.19757067 -79.19756568 -79.19755745 -79.19754599
 -79.19753128 -79.19751335 -79.19749218 -79.19746779 -79.19744018]
Optimized params: [  4.06433363e-05   1.11195037e+02  -7.91975724e+01]

Здесь коэффициент жесткости равен 0.00392 кДж/моль.

Проделаем аналогичные операции для торсионного угла HCCH, его значения должны изменяться от -180 до 180 c шагом 12. Аналогично, запишем в 31 файл ethaneXX.txt, где ХХ - номер от 60 по 90.

In [44]:
def gen_inp3():
    x_o=[]
    for i in range(0,31):
        dihedral = -180 + i*12
        x_o.append(dihedral)
        inp = '''!HF RHF 6-31G
        * int 0 1
        C 0 0 0 0 0 0 
        C 1 0 0 1.52986 0 0 
        H 1 2 0 1.08439 111.200 0
        H 1 2 3 1.08439 111.200 120
        H 1 2 3 1.08439 111.200 -120
        H 2 1 3 1.08439 111.200 %f
        H 2 1 6 1.08439 111.200 120
        H 2 1 6 1.08439 111.200 -120
        *
        ''' % dihedral
        output_name = 'ethane_%i.txt' %(i+60)
        output_file = open(output_name,"w")
        output_file.writelines(inp)
        output_file.close()
    return x_o

x_o = gen_inp3()
print x_o
[-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, 180]
In [41]:
y_o=[]
for i in range(60,91): 
    energy = run_orca(i)
    print energy
    y_o.append(energy)
-79.1975724044
-79.1971377109
-79.1959965234
-79.1945797241
-79.1934285683
-79.192987718
-79.1934285683
-79.1945797241
-79.1959965234
-79.1971377109
-79.1975724044
-79.1971377109
-79.1959965234
-79.1945797241
-79.1934285683
-79.192987718
-79.1934285683
-79.1945797241
-79.1959965234
-79.1971377109
-79.1975724044
-79.1971377109
-79.1959965234
-79.1945797241
-79.1934285683
-79.192987718
-79.1934285683
-79.1945797241
-79.1959965234
-79.1971377109
-79.197572401
In [42]:
x_o=np.array(x_o)
y_o=np.array(y_o)
print x_o
print y_o

#function is k*cos(nx) + a
fitfunc = lambda p, x: p[0]*np.cos(p[1]*x) + p[2] # Target function
errfunc = lambda p, x, y: fitfunc(p, x) - y # Error function

p0 = [1,1, -80] # Initial guess for the parameters
p1, success = optimize.leastsq(errfunc, p0[:], args=(x_o, y_o))
yfft = fft(y_o)
print "Optimized params:", p1

plt.plot(x_o, y_o, "ro", x_o,fitfunc(p1,x_o),"r-",c='blue',alpha=0.5)
plt.title('Energy(dihedral)')
plt.xlim(-200,200)
plt.show()
[-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
  180]
[-79.1975724  -79.19713771 -79.19599652 -79.19457972 -79.19342857
 -79.19298772 -79.19342857 -79.19457972 -79.19599652 -79.19713771
 -79.1975724  -79.19713771 -79.19599652 -79.19457972 -79.19342857
 -79.19298772 -79.19342857 -79.19457972 -79.19599652 -79.19713771
 -79.1975724  -79.19713771 -79.19599652 -79.19457972 -79.19342857
 -79.19298772 -79.19342857 -79.19457972 -79.19599652 -79.19713771
 -79.1975724 ]
Optimized params: [  2.29204829e-03   9.94834577e-01  -7.91952843e+01]

Таким образом, полученную зависимость энергии от торсионного угла можно хорошо аппроксимировать функцией 0,002*cos(0,99x)-79.

Наблюдаем три минимума энергии при изменении торсионного угла HCCH от -180 до 180. Это соответствует трем заторможенным конформациям молекулы этана.