Skip to main content

ยท 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.

ยท One min read
Peter R. Spackman

Wordle is a simple, elegant word game which has become massively popular, at least on twitter.

So far, the author hasn't commercialized it, and the social aspect of comparing your performance for the same puzzle to others every day clearly hits on something very appealing.

As when any puzzle gets popular, there has been lots of discussion around wordle solvers. You might think "Oh he's changed the date on his computer" but it's really nothing at all intelligent on my part. Simply thanks to the hacker known as View Page Source, I can reveal that it actually has a defined ordered list of words in plain text in its source code.

I searched for the word for today in the source and noticed that the previous words for the past few days were right there too, so it was pretty clear that it indexing into this array of words based on date. While the code itself was minified, it was pretty straightforward to look for uses of the array and deduce the method.

So without further ado, here's the ultimate strategy for completing wordle every day: cheating.

Tomorrow's wordle is:

DON'T CLICK IT IF YOU DON'T WANT TO BE SPOILED

ยท One min read
Peter R. Spackman

Welcome to the blog section. Not much to see yet, but I plan on actually posting on here from time to time.

This site is a mix of my personal site, and a wiki for me to write notes and equations related to my research in a publicly accessible way.

Partly so I have something to point people to for exposition, but mostly for my own reference and amusement.