Skip to main content

One post tagged with "molecular orbitals"

View All Tags

Β· 8 min read
Peter R. Spackman

If you're a masochist like me, and you're writing or have written a quantum chemistry program, you'll want to rotate molecular orbital (MO) coefficients to save yourself recalculating wavefunctions at different positions/orientations. Now that I've sufficiently reduced the interested audience for this to the point we could fit in the seats of a social-distanced sedan, let me take you on a painful but hopefully helpful journey.

Throughout this I'm going to assume that all rotations are about some common origin, O\mathbf{O}, but of course you're welcome to double the pain and mix in rotations about arbitrary points in space if you wish to be more general. I'm also going to put aside the various different ordering conventions, which will probably make you pull your hair out if you try to interoperate between many different programs.

But first we need a quick refresher on the difference between Cartesian and spherical/pure Gaussian basis sets like those used in quantum chemistry. Note that throughout this I'm going to ignore elaboration of the different normalizations within contracted Gaussians, because frankly that just makes the equations more verbose...

Contracted Gaussian basis sets​

Contracted Gaussian functions are of the form

Ο‡(r,rk)=βˆ‘kKckNf(rβˆ’rk)eβˆ’Ξ±k∣rβˆ’rk∣2\chi(\mathbf{r}, \mathbf{r_k}) = \sum_k^K c_k N f(\mathbf{r} - \mathbf{r_k}) e^{-\alpha_k |\mathbf{r} - \mathbf{r_k}|^2}

where KK is the degree of contraction (i.e. number of primitive Gaussian functions), NN is the normalization constant we're going to ignore for now, Ξ±k\alpha_k is the exponent for each primitive basis function and ckc_k the contraction coefficient.

Typically these sorts of basis sets will be in some library read in by the QM program of choice, e.g. this is an excerpt from the STO-3G basis set in .gbs format, with the left column being the Ξ±k\alpha_k, and the right column being ckc_k

H     0 
S 3 1.00
3.42525091 0.15432897
0.62391373 0.53532814
0.16885540 0.44463454

With the exception of the f(rβˆ’rk)f(\mathbf{r} - \mathbf{r_k}) term, Ο‡\chi is purely radial. However, there are two common choices of ff: Cartesian and spherical.

Cartesian polynomials​

In a Cartesian Gaussian basis set, ff takes the following form based on angular momentum ll:

rβˆ’rk={x,y,z}f(x,y,z)=xiyjzk\mathbf{r} - \mathbf{r_k} = \{x,y,z\}\\ f(x,y,z) = x^i y^j z^k

where i+j+k=li + j + k = l and all of i,j,ki,j,k are whole numbers.

Because multiplication is commutative i.e. x2y=yx2x^2 y = y x^2, for each angular momenta, ll, there are a number of 'unique' functions e.g.

LabelAngular momentum#Components
ppl=1l = 13xx, yy. zz
ddl=2l = 26xxxx, xyxy, xzxz, yzyz, zzzz
ffl=3l = 310xxxxxx, xxyxxy, xxzxxz, xyyxyy, xyzxyz, xzzxzz, yyyyyy, yyzyyz, yzzyzz, zzzzzz

and so on...

Spherical/Pure polynomials​

In a pure (or spherical) Gaussian basis set, ff usually corresponds to the real regular solid harmonics, which are directly related to the usual spherical harmonics YlmY_l^m These are a bit more of a rabbit hole..

Spherical harmonics (YlmY_l^m)

A much better overview of the spherical harmonics and their derivation can be found on wikipedia or in any number of other places.

Nevertheless, they take the form:

Ylm(ΞΈ,Ο•)=NeimΟ•Plmcos⁑θY_l^m(\theta,\phi) = N e^{im\phi} P_l^m \cos \theta

where NN is yet another different normalization factor, ΞΈ\theta and Ο•\phi are the polar angle/colatitude [0,Ο€][0, \pi] and the azimuthal angle/longitude [0,2Ο€][0, 2 \pi] respectively, and PlmP_l^m is an associated Legendre Polynomial

Regular solid harmonics (RlmR_l^m)

Given the previous definition of the spherical harmonics, the regular solid harmonics take the form:

Rlm(r,ΞΈ,Ο•)=4Ο€2l+1rlYlm(ΞΈ,Ο•)R_l^m(r, \theta, \phi) = \sqrt{\frac{4 \pi}{2 l + 1}} r^l Y_l^m(\theta, \phi)

or, simplified:

Rlm(r,ΞΈ,Ο•)=(lβˆ’m)!(l+m)!Plm(cos⁑θ)eimΟ•R_l^m(r, \theta, \phi) = \sqrt{\frac{(l - m)!}{(l + m)!}} P_l^m(\cos \theta) e^{im\phi}

Real regular solid harmonics (Rl,mR_{l,m})

Finally, the functions ff that are actually used are of the form:

f(r,ΞΈ,Ο•)=Cl,m(r,ΞΈ,Ο•)Β orΒ Sl,m(r,ΞΈ,Ο•)f(r, \theta, \phi) = C_{l,m}(r, \theta, \phi)\ \mathrm{or}\ S_{l,m}(r,\theta,\phi)

where

Rl,0=Cl,0=Rl0Rl,m=Cl,m=12((βˆ’1)mRlm+Rlβˆ’m)Β whereΒ m=1…lRl,m=Sl,m=1i2((βˆ’1)mRlm+Rlβˆ’m)Β whereΒ m=βˆ’1β€¦βˆ’lR_{l,0} = C_{l,0} = R_l^0\\ R_{l,m} = C_{l,m} = \frac{1}{\sqrt{2}}((-1)^m R_l^m + R_l^{-m})\ \mathrm{where}\ m = 1 \ldots l \\ R_{l,m} = S_{l,m} = \frac{1}{i \sqrt{2}}((-1)^m R_l^m + R_l^{-m})\ \mathrm{where}\ m = -1 \ldots -l \\

This means that there are 2l+12 l + 1 components for a pure Gaussian basis with angular momentum ll e.g.:

LabelAngular momentum#Components
ppl=1l = 13C10=zC_{10} = z, C11=xC_{11} = x, S11=yS_{11} = y
ddl=2l = 25C20C_{20}, C21C_{21}, S21S_{21}, C22C_{22}, S22S_{22}
ffl=3l = 37C30C_{30}, C31C_{31}, S31S_{31}, C32C_{32}, S32S_{32}, C33C_{33}, S33S_{33}

This should give a good idea why they find use in QM programs (fewer basis functions = fewer integrals to compute).

Rotating Cartesian Gaussians​

One way to imagine the molecular orbital coefficients associated with a Cartesian Gaussian is as an ll rank tensor e.g. a 3Γ—33 \times 3 matrix for a dd function, a 3Γ—3Γ—33 \times 3 \times 3 cube for an ff etc.

This obviously is very wasteful, as only the upper triangle (or pyramid) is permutationally unique. As such, they're typically stored as vectors (or columns of a matrix) of length

(l+1)(l+2)2\frac{(l + 1)(l + 2)}{2}

This usually renders rotation of these coefficients in real space to be done by hand coded routines rather than a simple matrix multiply.

However, we can convert the rotation matrix into an appropriate matrix to perform the conversion via the following algorithm implemented in C++:

cg_rotation_matrix.cpp
/*
* Result should be R: an MxM rotation matrix for
* P: a MxN set of coordinates
* giving results P' = R P
*
* Assume that the poorly named power_index_arrays gives
* arrays of length l that look like the following:
* xy = {0, 1}
* xyz = {0, 1, 2}
* xxy = {0, 0, 1}
* xyx = {0, 1, 0}
*/
template <int l>
Mat cartesian_gaussian_rotation_matrix(const Mat3x3 rotation) {
constexpr int num_moments = (l + 1) * (l + 2) / 2;
Mat result = Mat::Zero(num_moments, num_moments);
auto cg_powers = power_index_arrays<l>();
int p1_idx = 0;
for (const auto &p1: cg_powers) {
int p2_idx = 0;
// copy as we're permuting p2
for (auto p2: cg_powers) {
do {
double tmp{1.0};
for (int k = 0; k < l; k++) {
tmp *= rotation(p2[k], p1[k]);
}
result(p2_idx, p1_idx) += tmp;
} while (std::next_permutation(p2.begin(), p2.end()));
p2_idx++;
}
p1_idx++;
}
return result;
}

In essence this is just accounting for all the redundant terms we'd get that arise from the permutations of tensor indices. Going through the code line-by-line

So our rotated column vector (or block of a matrix) of molecular orbital coefficients will simply be

Pβ€²=RP\mathbf{P}' = \mathbf{R} \mathbf{P}

Rotating pure Gaussians​

If you've made it this far, well now we're in for the more painful bit.

Obviously we can't directly use the above rotation matrix for a set of pure Gaussian molecular orbital coefficients, but we can convert the coefficients to their equivalent Cartesian representation, perform the rotation, then convert back.

Frankly, I find this alternative preferable to dealing with Wigner D matrices, Clebsch-Gordan coefficients or any of the other painful aspects of rotating spherical harmonics...

Spherical to Cartesian transformations

Thankfully, I'm far from the first person to have wanted to perform this conversion, so there are tabulated transforms (along with implementations to generate them) available e.g., in HORTON. Much of this is based on the Schlegel & Frisch paper from the mid 90s.

This gives us c\mathbf{c}, a transformation matrix which will convert from Cartesian coefficients to pure coefficients. But what of the inverse transform? c\mathbf{c} is not an invertible matrix (obviously, as it's not square). Thankfully this is mentioned in the Schlegel & Frisch paper, and we can find the inverse transformation via:

cSc⊺=I\mathbf{c} \mathbf{S} \mathbf{c}^\intercal = \mathbf{I}
cβˆ’1=Sc⊺\mathbf{c}^{-1} = \mathbf{S} \mathbf{c}^\intercal

Here S\mathbf{S} is the matrix of overlap between normalized Cartesian gaussians of the same total angular momentum, where (if i,j,ki,j,k are powers of x,y,zx,y,z respectively) S\mathbf{S} can be derived from the following horrible expression:

S(i1,j1,k1,i2,j2,k2)=(i1+i2)!(j1+j2)!(k1+k2)!((i1+i2)/2)!((j1+j2)/2)!((k1+k2)/2)!Γ—i1!j1!k1!i2!j2!k2!(2i1)!(2j1)!(2k1)!(2i2)!(2j2)!(2k2)!S(i_1, j_1, k_1, i_2, j_2, k_2) = \frac{(i_1 + i_2)! (j_1 + j_2)! (k_1 + k_2)!}{((i_1 + i_2)/2)! ((j_1 + j_2)/2)! ((k_1 + k_2)/2)!} \times \sqrt{\frac{i_1 ! j_1 ! k_1 ! i_2 ! j_2 ! k_2 !}{(2 i_1)! (2 j_1)! (2 k_1)! (2 i_2)! (2 j_2)! (2 k_2)!}}

where all of i1+i2i_1 + i_2, j1+j2j_1 + j_2 and k1+k2k_1 + k_2 are even, 00 otherwise.

The neat part after all of this, is that we can simply transform R\mathbf{R}, the Cartesian Gaussian rotation matrix into Rβ€²\mathbf{R}', the pure/spherical Gaussian rotation matrix as follows:

Rβ€²=cRcβˆ’1\mathbf{R}' = \mathbf{c} \mathbf{R} \mathbf{c}^{-1}

and perform the rotation directly on the spherical molecular orbital coefficients:

Pβ€²=cRcβˆ’1P\mathbf{P}' = \mathbf{c} \mathbf{R} \mathbf{c}^{-1} \mathbf{P}

Whew... Hopefully I don't have any errors in the equations, but if I do feel free to name and shame me for being so sloppy.