Skip to content
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
82ee560
started implementing
v-agamshayit Apr 24, 2026
94b54a7
Merge branch 'main' into feature/agamshayit/s_squared
v-agamshayit Apr 24, 2026
9818bec
addressing comments
v-agamshayit Apr 24, 2026
016dcd4
Merge branch 'main' into feature/agamshayit/s_squared
v-agamshayit Apr 27, 2026
06a2d8b
Apply suggestions from code review
v-agamshayit Apr 27, 2026
b8de747
addressed comments
v-agamshayit Apr 27, 2026
a081f5c
Apply suggestions from code review
v-agamshayit Apr 27, 2026
a12c398
ran pre commit
v-agamshayit Apr 27, 2026
1189cfb
Apply suggestions from code review
v-agamshayit Apr 27, 2026
9946b1c
ran pre commit
v-agamshayit Apr 27, 2026
fe174d0
ran pre commit
v-agamshayit Apr 27, 2026
1b15769
Merge branch 'main' into feature/agamshayit/s_squared
v-agamshayit Apr 28, 2026
d01227b
started implementing
v-agamshayit Apr 24, 2026
8bf981c
addressing comments
v-agamshayit Apr 24, 2026
0aef320
Apply suggestions from code review
v-agamshayit Apr 27, 2026
ba21461
addressed comments
v-agamshayit Apr 27, 2026
c130916
Apply suggestions from code review
v-agamshayit Apr 27, 2026
71a1c80
ran pre commit
v-agamshayit Apr 27, 2026
c7c1431
Apply suggestions from code review
v-agamshayit Apr 27, 2026
d13c6e6
ran pre commit
v-agamshayit Apr 27, 2026
abe2dbf
ran pre commit
v-agamshayit Apr 27, 2026
d0483ad
refactored to work with new wavefunction interface, added Python laye…
v-agamshayit Jun 24, 2026
67c7d58
Merge superseded remote history into rebased branch (keep local tree)
v-agamshayit Jun 24, 2026
b144c40
Merge branch 'main' into feature/agamshayit/s_squared
v-agamshayit Jun 24, 2026
e949a98
Apply suggestions from code review
v-agamshayit Jun 24, 2026
d285bae
ran pre-commit
v-agamshayit Jun 24, 2026
481f06a
Apply suggestions from code review
v-agamshayit Jun 24, 2026
056454c
updated docs
v-agamshayit Jun 26, 2026
3e9ba5f
Merge branch 'main' into feature/agamshayit/s_squared
v-agamshayit Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions cpp/include/qdk/chemistry/data/wavefunction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,27 @@ class WavefunctionContainer {
virtual std::pair<Eigen::VectorXd, Eigen::VectorXd>
get_active_orbital_occupations() const;

/**
* @brief Compute the expectation value of the total spin-squared operator
*
* Uses the spin-dependent 1-RDMs and all three spin-dependent 2-RDM blocks.
* The mixed-spin contribution includes both exchange-like
* \f$\Gamma^{\alpha\beta}_{ijji}\f$ and Coulomb-like
* \f$\Gamma^{\alpha\beta}_{iijj}\f$ terms:
* \f[
* \langle S^2 \rangle = \frac{3}{4} \sum_i
* \left(\gamma^\alpha_{ii} + \gamma^\beta_{ii}\right)
* - \sum_{ij} \left( \Gamma^{\alpha\beta}_{ijji}
* + \tfrac{1}{4} \Gamma^{\alpha\alpha}_{ijji}
* + \tfrac{1}{4} \Gamma^{\beta\beta}_{ijji} \right)
* - \tfrac{1}{2} \sum_{ij} \Gamma^{\alpha\beta}_{iijj}
Comment thread
v-agamshayit marked this conversation as resolved.
Outdated
* \f]
* @return The expectation value \f$\langle S^2 \rangle\f$
* @throws std::runtime_error if spin-dependent 1-RDMs or 2-RDMs are not
* available
Comment thread
v-agamshayit marked this conversation as resolved.
Outdated
*/
Comment thread
v-agamshayit marked this conversation as resolved.
Comment on lines +528 to +535
double compute_s_squared() const;

/**
* @brief Check if spin-dependent one-particle RDMs for active orbitals are
* available
Expand Down Expand Up @@ -1322,6 +1343,17 @@ class Wavefunction : public DataClass,
*/
virtual Eigen::MatrixXd get_mutual_information() const;

/**
* @brief Compute the expectation value of the total spin-squared operator
*
* Delegates to the underlying container's compute_s_squared().
*
* @return The expectation value \f$\langle S^2 \rangle\f$
* @throws std::runtime_error if spin-dependent 1-RDMs or 2-RDMs are not
* available
Comment thread
v-agamshayit marked this conversation as resolved.
Outdated
Comment thread
v-agamshayit marked this conversation as resolved.
Outdated
*/
Comment on lines +1335 to +1342
double compute_s_squared() const;

/**
* @brief Check if spin-dependent one-particle RDMs are available
* @return True if available
Expand Down
85 changes: 85 additions & 0 deletions cpp/src/qdk/chemistry/data/wavefunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,86 @@ Eigen::MatrixXd WavefunctionContainer::get_mutual_information() const {
"entropies.");
}

double WavefunctionContainer::compute_s_squared() const {
QDK_LOG_TRACE_ENTERING();

// <S^2> = 3/4 * Tr(gamma^a + gamma^b)
// - sum_{ij} Gamma^{aabb}(i,j,j,i)
// - 1/4 * sum_{ij} Gamma^{aaaa}(i,j,j,i)
// - 1/4 * sum_{ij} Gamma^{bbbb}(i,j,j,i)
// - 1/2 * sum_{ij} Gamma^{aabb}(i,i,j,j)
//
// Uses QDK convention: Gamma(p,q,r,s) = <a†_p a†_r a_s a_q>
// Flat index: p*n^3 + q*n^2 + r*n + s

if (!has_one_rdm_spin_dependent() || !has_two_rdm_spin_dependent()) {
throw std::runtime_error(
"Spin-dependent one- and two-body RDMs must be set to compute <S^2>");
}
Comment on lines +717 to +720

const auto& one_rdm = active_one_rdm();
const auto& two_rdm = active_two_rdm();

Eigen::MatrixXd one_rdm_aa;
Eigen::MatrixXd one_rdm_bb;
Eigen::VectorXd two_rdm_aaaa;
Eigen::VectorXd two_rdm_aabb;
Eigen::VectorXd two_rdm_bbbb;

if (std::holds_alternative<SymmetryBlockedTensor<2, std::complex<double>>>(
one_rdm) ||
std::holds_alternative<SymmetryBlockedTensor<4, std::complex<double>>>(
two_rdm)) {
throw std::runtime_error("Complex <S^2> calculation not yet implemented");
} else {
const auto& one_sbt = std::get<SymmetryBlockedTensor<2, double>>(one_rdm);
const auto& two_sbt = std::get<SymmetryBlockedTensor<4, double>>(two_rdm);
one_rdm_aa = one_sbt.block({axes::alpha(), axes::alpha()});
one_rdm_bb = one_sbt.block({axes::beta(), axes::beta()});
two_rdm_aaaa = two_sbt.block(
{axes::alpha(), axes::alpha(), axes::alpha(), axes::alpha()});
two_rdm_aabb = two_sbt.block(
{axes::alpha(), axes::alpha(), axes::beta(), axes::beta()});
two_rdm_bbbb =
two_sbt.block({axes::beta(), axes::beta(), axes::beta(), axes::beta()});
}
Comment thread
v-agamshayit marked this conversation as resolved.
Outdated

int norbs = static_cast<int>(one_rdm_aa.rows());
double one_rdm_trace = one_rdm_aa.trace() + one_rdm_bb.trace();

Comment thread
v-agamshayit marked this conversation as resolved.
Comment on lines +746 to +748
// sum_{ij} Gamma(i,j,j,i): O(n^2)
auto sum_ijji = [norbs](const Eigen::VectorXd& vec) -> double {
double sum = 0.0;
for (int i = 0; i < norbs; ++i) {
for (int j = 0; j < norbs; ++j) {
int idx = i * norbs * norbs * norbs + j * norbs * norbs + j * norbs + i;
sum += vec[idx];
}
}
return sum;
};

// sum_{ij} Gamma(i,i,j,j): O(n^2)
auto sum_iijj = [norbs](const Eigen::VectorXd& vec) -> double {
double sum = 0.0;
for (int i = 0; i < norbs; ++i) {
for (int j = 0; j < norbs; ++j) {
int idx = i * norbs * norbs * norbs + i * norbs * norbs + j * norbs + j;
sum += vec[idx];
}
}
return sum;
Comment on lines +746 to +770
};

double aabb_ijji = sum_ijji(two_rdm_aabb);
double aaaa_ijji = sum_ijji(two_rdm_aaaa);
double bbbb_ijji = sum_ijji(two_rdm_bbbb);
double aabb_iijj = sum_iijj(two_rdm_aabb);

return 0.75 * one_rdm_trace - aabb_ijji - 0.25 * aaaa_ijji -
0.25 * bbbb_ijji - 0.5 * aabb_iijj;
}

void WavefunctionContainer::_clear_rdms() const {
QDK_LOG_TRACE_ENTERING();
_one_rdm_spin_traced.reset();
Expand Down Expand Up @@ -1352,6 +1432,11 @@ bool Wavefunction::has_two_rdm_spin_traced() const {
return _container->has_two_rdm_spin_traced();
}

double Wavefunction::compute_s_squared() const {
QDK_LOG_TRACE_ENTERING();
return _container->compute_s_squared();
}

// Cache management
void Wavefunction::_clear_caches() const {
QDK_LOG_TRACE_ENTERING();
Expand Down
Loading
Loading