Spin Tracing

Equations derived by SeQuant’s Wick’s theorem machinery and the CC equation generator are, by default, expressed in a spin-orbital basis: every index implicitly ranges over spin-up (\(\alpha\)) and spin-down (\(\beta\)) single-particle states alike. For a spin-unrestricted (open-shell) reference that is exactly what is needed. For a spin-restricted, closed-shell reference, however, the spin degrees of freedom can be summed out analytically, yielding a smaller set of equations over spatial orbitals alone — a transformation commonly called spin tracing or spin adaptation. This is a standard step in deriving efficient closed-shell coupled-cluster and many-body perturbation theory equations for quantum chemistry.

The mbpt::Spin quantum number

Spin is represented as an ordinary index space quantum number, sequant::mbpt::Spin (alpha, beta, or any/none for “unspecified/spin-free”), attached to an sequant::Index through its sequant::IndexSpace. Helper functions such as sequant::mbpt::make_spinalpha(), sequant::mbpt::make_spinbeta(), and sequant::mbpt::to_spin() add, change, or query this quantum number on individual indices:

// Spin quantum numbers can also be inspected/attached to individual
// indices directly, e.g. when interpreting an already spin-orbital
// expression by hand
Index i1_up = make_spinalpha(i1);
SEQUANT_ASSERT(to_spin(i1_up.space().qns()) == Spin::alpha);

Tracing spin out of an expression

Rather than manipulating spin labels index-by-index, the usual entry point is sequant::mbpt::spintrace(): given a spin-orbital expression, it sums over the spin cases of its internal indices and returns the spin-free (spatial-orbital) result. In the simplest case — where none of the involved index spaces are already split into separate alpha/beta subspaces — this amounts to summing the (identical) spin-conserving contributions:

// register the standard occupied/virtual spaces used in single-reference
// quantum chemistry (see the User Guide's "Context and configuration" page)
load(Convention::SR);

Index i1(L"i_1"), a1(L"a_1");
TensorSymmetries particle_symmetric{.column = ColumnSymmetry::Symm};

// a spin-orbital expression: indices carry no explicit spin label, so each
// one implicitly ranges over both alpha and beta spin-orbitals
auto expr = ex<Tensor>(L"t", bra{i1}, ket{a1}, particle_symmetric) *
            ex<Tensor>(L"F", bra{a1}, ket{i1}, particle_symmetric);

// spintrace() sums a spin-orbital expression over spin, producing its
// spin-free (spatial-orbital) form; the summation over the 2 spin cases
// that agree with each other produces the factor of 2 below
auto expr_st = spintrace(expr);
simplify(expr_st);

std::wcout << expr_st << std::endl;

Specialized variants exist for the more demanding cases that arise in practice:

All of these expect their input to already be in a specific normal form (a leading antisymmetrizer, produced by “complete” canonicalization — see Canonicalization, unless the expression doesn’t have any external indices (e.g. energy expressions)); consult their reference documentation for the exact preconditions and options before using them on a new class of equations. For a worked example applying closed_shell_CC_spintrace/open_shell_CC_spintrace to actual coupled-cluster amplitude equations, see Spin Tracing of Expressions in the Coupled-Cluster Class page.