%matplotlib inline
import numpy as np
import hfscf as hf
np.set_printoptions(precision=6, suppress=True)
Что означают матрицы S, H, F, X, C¶
S — матрица перекрывания базисных функций.
Она показывает, насколько базисные функции "похожи" друг на друга.
H — одноэлектронный гамильтониан.
Это сумма кинетической энергии электронов и их притяжения к ядрам.
F — матрица Фока.
Это основной оператор в методе Хартри–Фока:
[
F = H + G
]
где (G) учитывает электрон-электронное взаимодействие.
X — матрица преобразования базиса.
Она нужна, чтобы перейти от неортогонального базиса к ортогональному и удобно диагонализовать задачу.
C — матрица коэффициентов молекулярных орбиталей.
Её столбцы показывают, из каких атомных орбиталей собираются молекулярные орбитали.
Функция SCF¶
Ниже записана функция самосогласованного поля (SCF). Она:
- строит матрицы S и H,
- получает матрицу X,
- начинает с нулевой матрицы плотности P,
- на каждой итерации строит F,
- пересчитывает коэффициенты C,
- обновляет матрицу плотности,
- проверяет сходимость.
def SCF(r=1.4632, Z=[1, 1], b1=hf.GTO["H"], b2=hf.GTO["H"], b=3, max_iter=50, tol=1e-4, vbs=True):
# Координаты двух ядер на оси:
# первое ядро ставим в 0, второе в точку r
R = [0, r]
# S показывает перекрывание атомных базисных функций
S = hf.S(R=R, b1=b1, b2=b2, b=b)
# Одноэлектронный гамильтониан H
# H = T + V
# T — кинетическая энергия
# V — притяжение электронов к ядрам
H = hf.H(R=R, Z=Z, b1=b1, b2=b2, b=b)
# получили Х
# X нужна для перехода к ортогональному базису
X = hf.diagon(S)
# Сопряжённо-транспонированная матрица X
Xa = X.getH()
# Начинаем с нулевой матрицы
P = np.matrix([[0.0, 0.0],
[0.0, 0.0]], dtype=np.float64)
# Флаг сходимости
converged = False
# Запускаем SCF-итерации
for iteration in range(1, max_iter + 1):
G = hf.G(r=r, p=P, b1=b1, b2=b2, b=b)
F = H + G
F_prime = Xa @ F @ X
C_prime = hf.diagon2(F_prime)
C = X @ C_prime
P_new = hf.P(C)
# Если новая и старая матрицы плотности почти одинаковы,
# значит SCF сошёлся
delta = np.linalg.norm(P_new - P)
if vbs:
print(f"Итерация {iteration}: ||P_new - P_old|| = {delta:.8f}")
if delta < tol:
P = P_new
converged = True
break
# Если не сошлось — идём на следующую итерацию
P = P_new
# считаем энергию после сходимости
E_elec = hf.ener_elec(P, H, F) # электронная энергия
E_tot = hf.ener_tot(r=r, Z=Z, elec=E_elec) # полная энергия = электронная + отталкивание ядер
return {
"S": S,
"H": H,
"X": X,
"F": F,
"C": C,
"P": P,
"E_elec": E_elec,
"E_tot": E_tot,
"converged": converged,
"iterations": iteration
}
result = SCF()
Итерация 1: ||P_new - P_old|| = 3.36923627 Итерация 2: ||P_new - P_old|| = 3.20813622 Итерация 3: ||P_new - P_old|| = 0.24080679 Итерация 4: ||P_new - P_old|| = 0.02165210 Итерация 5: ||P_new - P_old|| = 0.00195266 Итерация 6: ||P_new - P_old|| = 0.00017610 Итерация 7: ||P_new - P_old|| = 0.00001588
print("SCF сошелся?", result["converged"])
print("Число итераций:", result["iterations"])
SCF сошелся? True Число итераций: 7
print("S =")
print(result["S"])
print()
print("H =")
print(result["H"])
print()
print("X =")
print(result["X"])
print()
print("F =")
print(result["F"])
print()
print("C =")
print(result["C"])
print()
print("P =")
print(result["P"])
S = [[1. 0.63749] [0.63749 1. ]] H = [[-1.099204 -0.919548] [-0.919548 -1.099204]] X = [[ 0.552581 1.174424] [ 0.552581 -1.174424]] F = [[-0.346841 -0.577711] [-0.577711 -0.34684 ]] C = [[ 0.552581 -1.174424] [ 0.55258 1.174425]] P = [[0.610692 0.610691] [0.610691 0.61069 ]]
Энергии¶
Электронная энергия считается по матрицам P, H и F.
Полная энергия молекулы получается как: Etot = Eelec + Enuc,
где Enuc — отталкивание двух ядер.
print(f"Электронная энергия = {float(result['E_elec']):.6f} Ha")
print(f"Полная энергия = {float(result['E_tot']):.6f} Ha")
Электронная энергия = -1.797449 Ha Полная энергия = -1.114015 Ha
Энергии отдельных орбиталей¶
orbital_energies = hf.ener_orbs(result["X"], result["F"])
print("Энергии орбиталей:")
print(orbital_energies)
Энергии орбиталей: [-0.564615 0.636867]
1D график орбиталей¶
На этом графике будут показаны:
- две волновые функции,
- квадраты этих функций, то есть плотности вероятности.
hf.orbital(result["C"], r=1.4632, b1=hf.GTO["H"], b2=hf.GTO["H"], b=3)
На первом, 1D-графике показаны две орбитали. Красная кривая — это первая орбиталь. Она не обращается в ноль между двумя ядрами. Это значит, что электронная плотность есть и между атомами. А если плотность есть между атомами, это помогает удерживать их вместе. Поэтому такая орбиталь — связывающая.
Синяя кривая — вторая орбиталь. У неё в середине есть переход через ноль. Это значит, что между атомами появляется область, где орбиталь исчезает. Такая орбиталь уже не помогает связывать атомы, а наоборот, соответствует разрыхлению связи. Поэтому её называют разрыхляющей.
Линии квадратов — это уже не сама волновая функция, а плотность вероятности. То есть они показывают, где электрон скорее всего находится.
Зелёная кривая показывает, что для первой орбитали электронная плотность есть и около ядер, и между ними. Это ещё раз говорит, что орбиталь связывающая.
Чёрная кривая показывает два максимума по бокам, но в центре провал почти до нуля. Значит, между ядрами электрон там почти не находится. Это характерно для разрыхляющей орбитали.
2D график орбиталей¶
Построим двумерную карту плотности для двух молекулярных орбиталей.
hf.orbital2D(result["C"], result["X"], result["F"], r=1.4632, b1=hf.GTO["H"], b2=hf.GTO["H"], b=3, delta=0.03)
Для Orbital 1 яркие области около двух ядер как бы соединены между собой. Это значит, что плотность распределена между атомами, и такая орбиталь способствует образованию связи.
Для Orbital 2 яркие области разделены тёмной полосой посередине. Эта тёмная область — место, где плотность мала. Значит, между атомами нет хорошего «электронного мостика», и такая орбиталь уже разрыхляет связь.
По энергиям смысл такой же:
первая орбиталь имеет более низкую энергию (−0.564615), значит она выгоднее и заполняется первой;
вторая орбиталь имеет более высокую энергию (0.636867), поэтому она менее выгодна.
Вывод: первая орбиталь — связывающая: она помогает удерживать два атома водорода вместе. Вторая орбиталь — разрыхляющая. Если бы электроны сидели на ней, связь ослаблялась бы. Графики получились, по моему мнению, правильные и хорошо соответствуют молекуле H₂.