Coupled-Cluster Class¶
Coupled-cluster (CC) theory is one of the most accurate and widely used quantum chemistry methods for describing electron correlation in molecular systems. It represents the many-electron wavefunction using an exponential ansatz:
where \(|\Phi_0\rangle\) is a reference determinant (typically Hartree-Fock), and \(\hat{T}\) is a cluster operator that generates excited determinants. The cluster operator is typically expanded as:
where \(\hat{T}_n\) generates \(n\)-fold excited determinants. For computational tractability, the cluster operator is usually truncated. For example, CCSD includes only single and double excitations (\(\hat{T} = \hat{T}_1 + \hat{T}_2\)).
The CC class provides a convenient interface for setting up and processing CC equations using SeQuant’s symbolic algebra engine. It supports various CC formulations, including traditional, unitary, and orbital-optimized ansätze.
Overview¶
The CC class can be used to derive:
Ground state amplitude equations
λ (de-excitation) amplitude equations
Equation-of-motion (EOM) CC equations for excited states
Response equations for properties and perturbations
Expressions are generated in spin-orbital basis and can be post-processed using SeQuant’s spin-tracing capabilities. See Spin Tracing of Expressions for more details.
Ansatz Options¶
The CC class supports several CC ansätze through the CC::Ansatz enum:
Ansatz::T: Traditional CC ansatz, where the wavefunction is represented as \(|\Psi_{\text{CC}}\rangle = e^{\hat{T}}|\Phi_0\rangle\). This is the standard approach used in most implementations.Ansatz::oT: Orbital-optimized traditional ansatz. Singles amplitudes (\(\hat{T}_1\)) are excluded from the cluster operator, with orbital optimization performed instead.Ansatz::U: Unitary CC ansatz, where the wavefunction is represented as \(|\Psi_{\text{UCC}}\rangle = e^{\hat{T} - \hat{T}^\dagger}|\Phi_0\rangle\). Particularly useful for quantum computing applications.Ansatz::oU: Orbital-optimized unitary ansatz, combines both unitary and orbital optimized ansätze.
Key Methods¶
Ground State Amplitudes¶
std::vector<ExprPtr> t(size_t commutator_rank = 4,
size_t pmax = std::numeric_limits<size_t>::max(),
size_t pmin = 0);
Derives the equations for the \(t\) amplitudes (\(\langle \Phi_P|\bar{H}|\Phi_0 \rangle = 0\)) up to specified excitation levels.
std::vector<ExprPtr> λ(size_t commutator_rank = 4);
Derives the equations for the \(\lambda\) de-excitation amplitudes.
Coupled-Cluster Response¶
std::vector<ExprPtr> tʼ(size_t rank = 1, size_t order = 1);
std::vector<ExprPtr> λʼ(size_t rank = 1, size_t order = 1);
Derives perturbed amplitude equations for response theory calculations.
Equation-of-Motion Coupled-Cluster¶
std::vector<ExprPtr> eom_r(nₚ np, nₕ nh);
std::vector<ExprPtr> eom_l(nₚ np, nₕ nh);
Derives equation-of-motion coupled-cluster (EOM-CC) equations for excited states. The eom_r method generates equations for the right eigenvectors, while eom_l generates equations for the left eigenvectors.
Examples¶
The following examples demonstrate how to use the CC class to derive CC equations for various ansätze and excitation levels.
From this point onward, assume the following namespaces are imported, and sequant::Context and sequant::mbpt::Context are set up as shown.
using namespace sequant;
using namespace sequant::mbpt;
set_default_context({.index_space_registry_shared_ptr = make_min_sr_spaces(),
.vacuum = Vacuum::SingleProduct});
set_default_mbpt_context({.op_registry_ptr = make_legacy_registry()});
Ground State CC Amplitude Equations¶
// Traditional CCSD
auto t_eqs = CC{2}.t();
std::wcout << "T1: " << to_latex(t_eqs[1]) << "\n"
<< "T2: " << to_latex(t_eqs[2]) << "\n";
// Lambda equations
auto l_eqs = CC{2}.λ();
std::wcout << "λ1: " << to_latex(l_eqs[1]) << "\n"
<< "λ2: " << to_latex(l_eqs[2]) << "\n";
// Unitary CCSD
auto Ut_eqs = CC(2, {.ansatz = CC::Ansatz::U, .hbar_comm_rank = 4})
.t(); // Use 4th-order commutator expansion
std::wcout << "T1 (UCC): " << to_latex(Ut_eqs[1]) << "\n"
<< "T2 (UCC): " << to_latex(Ut_eqs[2]) << "\n";
// Orbital-optimized CCSD
auto oT_eqs = CC(2, {.ansatz = CC::Ansatz::oT}).t();
std::wcout << "T2 (oT): " << to_latex(oT_eqs[2]) << "\n";
EOM-CC Equations¶
// EE-EOM-CCSD (excitation energy)
auto r_eqs = CC{2}.eom_r(nₚ(2), nₕ(2));
std::wcout << "R1: " << to_latex(r_eqs[1]) << "\n"
<< "R2: " << to_latex(r_eqs[2]) << "\n";
// EE-EOM-CCSD Left eigenvectors
auto ee_l_eqs = CC{2}.eom_l(nₚ(2), nₕ(2));
std::wcout << "L1: " << to_latex(ee_l_eqs[1]) << "\n"
<< "L2: " << to_latex(ee_l_eqs[2]) << "\n";
// IP-EOM-CCSD (ionization potential)
auto ip_eqs = CC{2}.eom_r(nₚ(0), nₕ(1));
std::wcout << "IP-R1: " << to_latex(ip_eqs[0]) << "\n";
// EA-EOM-CCSD (electron attachment)
auto ea_eqs = CC{2}.eom_r(nₚ(1), nₕ(0));
std::wcout << "EA-R1: " << to_latex(ea_eqs[0]) << "\n";
Response and Perturbation Equations¶
// First-order perturbed amplitude equations
auto t_pt = CC{2}.tʼ(1, 1);
std::wcout << "t1 perturbed: " << to_latex(t_pt[1]) << "\n"
<< "t2 perturbed: " << to_latex(t_pt[2]) << "\n";
// First-order perturbed Lambda amplitude equations
auto l_pt = CC{2}.λʼ(1, 1);
std::wcout << "λ1 perturbed: " << to_latex(l_pt[1]) << "\n"
<< "λ2 perturbed: " << to_latex(l_pt[2]) << "\n";
Advanced Usage¶
Truncating the Commutator Expansion¶
The similarity-transformed Hamiltonian is built by mbpt::lst, see Lie Similarity Transformation. For traditional CC with two-body Hamiltonians, the commutator expansion is truncated at 4th order. However, for unitary CC or other Hamiltonians, you may need to explicitly set the commutator rank:
// Unitary CCSDT with 6th-order commutator expansion
auto Ut_ccsdt = CC(3, {.ansatz = CC::Ansatz::U, .hbar_comm_rank = 6}).t();
Using \(\bar{H}\) outside the CC class¶
CC::hbar() is public, but for a non-unitary ansatz the expression it returns is not self-contained: each commutator is written as a connected product (see Lie Similarity Transformation), which only equals the commutator once you connect the operators when taking the expectation value. The class always does this for you — every non-unitary path passes default_op_connections() (or a superset) to ref_av. A caller who does not will silently retain the disconnected terms:
using namespace sequant::mbpt;
CC cc{2};
// correct: op::ref_av defaults to default_op_connections()
auto eqs = op::ref_av(op::P(nₚ(2)) * cc.hbar());
// WRONG: empty connectivity keeps terms the commutator would have cancelled
auto bad = op::ref_av(op::P(nₚ(2)) * cc.hbar(), {.connect = {}});
// also correct: ask lst for the explicit commutator form, which needs
// no connectivity
auto hbar = lst(op::H(), op::T(2), 4);
Note that op::ref_av(expr) and op::ref_av(expr, {}) are not the same call: the connectivity defaults to default_op_connections() only when the argument is omitted entirely, since EVOptions::connect is itself empty by default.
The two ref_av calls above swap roles for a unitary ansatz, so it is not simply “unaffected”. There CC::hbar() already returns explicit commutators, and imposing connectivity on top of them would drop terms that must survive; a caller must therefore pass empty connections explicitly, as the class does internally:
CC ucc{2, {.ansatz = CC::Ansatz::U, .hbar_comm_rank = 3}};
// correct for a unitary ansatz: hbar is already self-contained
auto ueqs = op::ref_av(op::P(nₚ(2)) * ucc.hbar(), {.connect = {}});
Spin Tracing of Expressions¶
Equations generated by the CC class are in spin-orbital basis. SeQuant also provides capability to transform these equations into spin-traced forms.
Make sure to include the <SeQuant/domain/mbpt/spin.hpp> header to access spin-tracing functions.
// Generate CCSD R2 equation
auto ccsd_eqs = CC{2}.t();
auto t2_eq = t_eqs[2];
// Convert to the closed-shell spin-traced form
auto t2_cs = closed_shell_CC_spintrace(t2_eq);
std::wcout << "Closed-shell spin-traced CCSD-R2: " << to_latex(t2_cs) << "\n";
// Convert to the open-shell spin-traced form
auto t2_os = open_shell_CC_spintrace(
t2_eq); // vector of expressions: {ɑɑ, ɑβ, ββ} blocks
std::wcout << "Open-shell spin-traced CCSD-R2:\n";
for (const auto& eq : t2_os) {
std::wcout << to_latex(eq) << "\n";
}