В этом таске мы будем работать с волновыми функциями электрона.
Для начала немного теории.
(Если читателю уже известно или же не интересует, откуда берётся точная форма орбиталей электрона, эту часть можно пропустить.)
Теперь, когда мы знаем, что именно мы моделируем, можно приступить к расчётам.
import numpy
import scipy.special
import scipy.misc
from scipy.misc import factorial as fact
import os
import npy2cube
Определяем функцию для подсчёта значений волновой функции в заданных точках.
ANGSTROM = 10**-11
def w(n,l,m,d,s=30):
# Generates 30×30×30 space of coord-triplets,
# with d being boundaries of the space.
# input-d is measured in angstroms. There's probably no vice in
# measuring it in angsroms throughout the calculations?
# d = d*ANGSTROM
# This generates precisely 30 points (on every axis),
# starting at -d and ending at -d. Between 30 points there are
# 29 intervals, so step of the grid is equal to 2d/(30-1).
x,y,z = numpy.mgrid[-d:d:s*1j,-d:d:s*1j,-d:d:s*1j]
# Spherical transform: computes spherical coords from Cartesian.
# Formulas are fairly self-explanatory.
# Unfortunately, more intuitive explanations
# require instructional diagrams,
# which are not text-comment-compatible.
r = lambda x,y,z: numpy.sqrt(x**2+y**2+z**2)
theta = lambda x,y,z: numpy.arccos(z/r(x,y,z))
phi = lambda x,y,z: numpy.arctan(y/x)
# Yes, this is the value of a_0.
# For some reason, it was set to 1.0 before? Wtf?
a0 = 0.529 # * ANGSTROM
# Not-quite-radius
# Useful for scaling
rho = lambda x,y,z,n: 2*r(x,y,z)/n/a0*2**n
# Here, we define/compute the wavefunction as function of position.
# Firstly, radial dependence: root with factorials, power part,
# exponential and laguerre parts. In fact, we'll skip factorials,
# as they are position-independent, and do not change the overall
# form of wavefunction.
# 2**-n is scaling factor: we want our orbital displayed side-by-side,
# and care more about shapes than comparative sizes.
R = lambda rho,n,l: \
((2/n/a0)**1.5 * 0 + 1) * \
((fact(n-l-1) / (2*n*fact(n+l)))**0.5 * 0 + 1) * \
rho**l * numpy.exp(-rho/2) * \
scipy.special.genlaguerre(n-l-1,2*l+1)(rho)
# Secondly, angular dependence; namely, spherical harmonics.
WF = lambda rho,theta,phi,n,l,m: \
R(rho,n,l) * scipy.special.sph_harm(m,l,phi,theta)
# Optionally thirdly, squared modulus of wavefunction.
absWF = lambda rho,theta,phi,n,l,m: \
numpy.absolute(WF(rho,theta,phi,n,l,m))**2
return absWF(rho(x,y,z,n),theta(x,y,z),phi(x,y,z),n,l,m)
Переберём несколько значений квантовых чисел и сохраним модели функции для каждой тройки.
# Optimal boxes to fit hydrogen orbitals vary from orbital to orbital;
# lacking insight to decide box sizes individually, we arbitrarily designate
# 2-angstrom box as universal. (We actually experimented some and found
# a good box size)
d = 2
_step = 2.*d/(30-1)
# Actually, npy2cube discards imaginary part of the wavefunction.
# Let's see how it goes, I guess?
#for i in range(30):
# n = numpy.random.randint(1, 30+1)
# l = numpy.random.randint(0, n)
# m = numpy.random.randint(0, l+1)
for n in range(1, 4+1):
for l in range(n):
for m in range(l+1):
grid = w(n,l,m,d,s=60)
name = '%s_%s_%s' % (n,l,m)
npy2cube.npy2cube(grid,
start = (-d, -d, -d),
step = (_step, _step, _step),
cube_f = "cube/"+name+'.cube')
Подключаем PyMOL, в которым мы будем рассматривать полученные орбитали.
# pymol launching
import __main__
import time
from xmlrpclib import ServerProxy
#cmd = ServerProxy(uri="http://localhost:9123/RPC2")
#__main__.pymol_argv = [ 'pymol', '-c', '-s "pymol.log']
import pymol
pymol.finish_launching(['pymol', '-c'])
from pymol import cmd
from IPython.display import Image
# Borrowed from http://kodomo.cmm.msu.su/~sapsan/v2/terms/term8/PyMolPractice1.html.
default_image = './temp_pymol_img.png'
def prepare_image(width=300, height=300, sleep=2, filename=default_image):
## To save the rendered image
cmd.ray(width, height)
cmd.png(filename)
time.sleep(sleep)
Обнаруживается, что:
Конфуз, однако.
Можно, впрочем, загрузить файлы на сервере, сохранить их в более удобочитаемом формате, загрузить формат на персональной машине и потом отрисовать.
Так и сделаем.
На сервере исполняем это:
cmd.reinitialize()
for _name in os.listdir("./cube"):
name = _name[:-5]
cmd.load("cube/{}.cube".format(name), "cube")
cmd.save("pse/{}.pse".format(name))
time.sleep(.1)
cmd.delete("all")
На другой машине исполняем это:
cmd.reinitialize()
# Откуда взялись эти числа?
# В принципе, их производит volume_ramp_new,
# если дать ему на вход названия цветов.
# Но в данном случае мы взяли пример вывода volume_ramp_new,
# и долго и упорно обрабатывали его напильником.
cmd.volume_ramp_new('ramp007', [\
0.0002, 1.00, 0.00, 0.00, 0.0005, \
0.0010, 1.00, 1.00, 0.00, 0.005, \
0.0025, 0.00, 1.00, 0.00, 0.01, \
0.005, 0.00, 1.00, 1.00, 0.02, \
0.01, 0.00, 0.00, 1.00, 0.01, \
0.015, 1.00, 0.00, 1.00, 0.005, \
])
cmd.set("ray_volume")
for _name in os.listdir("./pse"):
name = _name[:-4]
cmd.load("pse/{}.pse".format(name))
cmd.turn("x", -30)
cmd.turn("y", 30)
cmd.volume("vol", "cube", ramp="ramp007")
cmd.ray()
cmd.png("png/{}.png".format(name))
time.sleep(1)
cmd.delete("all")
Получаем красивые картинки.
Красивые картинки нарисованы не в правильном масштабе, а так, чтобы все орюбитали помещались примерно в одно и то же пространство.
Image("png_1/2_0_0.png")
Image("png_1/3_2_0.png")
Image("png_2/3_2_0.png")
Image("png_2/4_2_1.png")
По соседству считаем орбитали с помощью ORCA. Это весело.
Входной файл:
%%writefile h.in
! UHF SVP XYZFile
%plots Format Cube
MO("H-1.cube",1,0);
MO("H-2.cube",2,0);
MO("H-3.cube",3,0);
MO("H-4.cube",4,0);
MO("H-5.cube",5,0);
MO("H-6.cube",6,0);
MO("H-7.cube",7,0);
MO("H-8.cube",8,0);
MO("H-9.cube",9,0);
MO("H-10.cube",10,0);
MO("H-11.cube",11,0);
MO("H-12.cube",12,0);
MO("H-13.cube",13,0);
end
* xyz 9 2
Ne 0 0 0
*
Код bash:
%%sh
rm -r orca
rm -r ./cube-orca
mkdir orca
mkdir cube-orca
mv h.in orca
cd orca
orca h.in > h.out
cd ..
mv orca/h.in ./
mv orca/*.cube ./cube-orca/
Повторяем пляски с бубном.
cmd.reinitialize()
for _name in os.listdir("./cube-orca"):
name = _name[:-5]
cmd.load("cube-orca/{}.cube".format(name), "cube")
cmd.save("pse-orca/{}.pse".format(name))
time.sleep(.1)
cmd.delete("all")
Получаем новые красивые картинки.
Image("png-orca/H-1.png")
Image("png-orca/H-4.png")
Image("png-orca/H-8.png")
Image("png-orca/H-13.png")
Эти картинки, скажем так, отдалённо напоминают орбитали, которые у нас вышли раньше. Что, наверное, служит уроком в неточности симуляций.
С другой стороны, подсчёты с помощью orca драматически быстрее точных подсчётов. Что, в сущности, логично: нельзя получить и точность, и скорость, но можно обменять одно на другое.