Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
e1dba87
set hardcoded biorthogonal coeffs till rank 7
ABesharat Oct 13, 2025
1323fc6
rank 7
ABesharat Oct 13, 2025
897a42a
discard redundant assertion
ABesharat Oct 13, 2025
a22809c
better name: computed_coefficients
ABesharat Oct 13, 2025
dbcd4e5
add hardcoded file to cmakelist
ABesharat Oct 13, 2025
8fd5be0
v2: correct rescaling factor
ABesharat Oct 13, 2025
47ccfe1
renaming original coeffs to computed_coefficients
ABesharat Oct 13, 2025
93829df
Merge remote-tracking branch 'origin/azam/feature/hardcoded_biorthogo…
ABesharat Oct 13, 2025
70ef68a
renaming
ABesharat Nov 11, 2025
4fc0898
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Dec 8, 2025
fc0dcda
discard old coefficients
ABesharat Dec 8, 2025
f309f62
discard only the old coefficients, not num_perms
ABesharat Dec 8, 2025
b5da4b8
no inline anymore for performance
ABesharat Dec 10, 2025
25bb6f4
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Dec 10, 2025
3926bde
at max rank 6 is enough
ABesharat Dec 10, 2025
e485b9c
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Dec 12, 2025
87b8177
test_eval_ta: add rank 4 nns test, fix rank 3
ABesharat Dec 13, 2025
5ca6d55
test_eval_btas: fix nns for rank 3
ABesharat Dec 13, 2025
188825c
add hardcoded biortho coeffs and nns projection
ABesharat Dec 13, 2025
8e66ac7
fix include
ABesharat Dec 13, 2025
1dc89b3
use hardcoded biortho coeffs for rank<=6, otherwise use computed ones
ABesharat Dec 13, 2025
53b47be
do not call nns with int type ,use hardcoded nns for rank<=6, otherwi…
ABesharat Dec 13, 2025
a28d886
make libperm public to build the same order of permutations
ABesharat Dec 13, 2025
ab8f453
fix include for Eigen!
ABesharat Dec 13, 2025
d5b20f6
change the name of hardcoded nns fn
ABesharat Dec 14, 2025
278dcc3
renaming again!
ABesharat Dec 14, 2025
aede8b3
renaming hardcoded_biortho_coeffs fn
ABesharat Dec 14, 2025
4b1a92f
change assert to SEQUANT_ASSERT
ABesharat Dec 15, 2025
8ee5a57
no need to have rescaling for v2. new nns is enough for restoring WK …
ABesharat Dec 18, 2025
a3345c4
documentation correction
ABesharat Dec 18, 2025
78dc616
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Dec 18, 2025
d2323d0
use abort for calling nns with int type
ABesharat Dec 21, 2025
0f4ab0c
normalization factor of S
ABesharat Dec 22, 2025
acc25f8
Remove additional print statements, cleaning
ABesharat Dec 25, 2025
877e3d4
address the requested changes
ABesharat Dec 25, 2025
0a60656
address the requested changes, except the namespace
ABesharat Dec 26, 2025
14fbd13
extract libperm calls into a helper function to avoid public dependency
ABesharat Dec 27, 2025
f4a0c82
Optimize build command with parallel jobs
ABesharat Dec 28, 2025
7087e16
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Dec 28, 2025
519e68a
a better name: biorth_threshold
ABesharat Jan 3, 2026
2e67e8e
use default threshold for compute_nns_p_matricx to avoid an additiona…
ABesharat Jan 3, 2026
aa7a41c
add some documentations
ABesharat Jan 11, 2026
69ab3a1
clear documentation
ABesharat Jan 12, 2026
af93394
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Jan 12, 2026
2d10389
apply encapsulation for nns
ABesharat Jan 15, 2026
a9a17b1
memoize nns_projection weights
ABesharat Jan 16, 2026
afed9c4
add include
ABesharat Jan 16, 2026
1ca7fa6
dox cleanup
evaleev Jan 16, 2026
ff05b7c
nns_projection_weights: no third lock needed to return the result
evaleev Jan 16, 2026
4732bf5
removed the no-longer-used biorthogonalization_hardcoded.ipp
evaleev Jan 16, 2026
ecde7eb
memoize biorthogonal coeffs by lambda to have separate cache for hard…
ABesharat Jan 16, 2026
5f490ef
Merge remote-tracking branch 'refs/remotes/origin/azam/feature/hardco…
ABesharat Jan 16, 2026
333f296
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Jan 16, 2026
6697a4e
Merge remote-tracking branch 'refs/remotes/origin/master' into azam/f…
ABesharat Jan 16, 2026
8f42dac
remove the redundant header include
ABesharat Jan 16, 2026
2e171a8
move Eigen usage from public header to implementation
ABesharat Jan 16, 2026
370bd30
remove the redundant header include
ABesharat Jan 16, 2026
e6adbab
biorthogonalization.cpp: forward declaration to avoid having Eigen in…
ABesharat Jan 16, 2026
72dac7e
move hardcoded contents to biorthogonalization.cpp and hpp
ABesharat Jan 17, 2026
711e460
add to header file to keep the documentation
ABesharat Jan 17, 2026
f8f3115
make compute-nns compact
ABesharat Jan 18, 2026
9d98421
using auto instead of std::size_t
ABesharat Jan 21, 2026
c3a6c07
remove biorthogonalization from IR since it is too domain-specific
evaleev Jan 21, 2026
c8cf5b9
fix the cmake
ABesharat Jan 21, 2026
6c138ef
fix nns call in eval_ta and eval_btas
ABesharat Jan 22, 2026
5b64bdd
refactor SQ/eval to live in core/eval and separate backend-specific a…
evaleev Jan 23, 2026
93fab50
Merge pull request #465 from ValeevGroup/evaleev/feature/conditional-…
bimalgaudel Jan 23, 2026
30f76e0
fix Copilot suggestions
ABesharat Jan 23, 2026
40663b4
biorthogonalization: renames/restruct
evaleev Jan 23, 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
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,9 @@ set(SeQuant_src
SeQuant/domain/mbpt/antisymmetrizer.hpp
SeQuant/domain/mbpt/biorthogonalization.cpp
SeQuant/domain/mbpt/biorthogonalization.hpp
SeQuant/domain/mbpt/biorthogonalization_hardcoded.ipp
SeQuant/domain/mbpt/biorthogonalization_hardcoded.cpp
SeQuant/domain/mbpt/biorthogonalization_hardcoded.hpp
SeQuant/domain/mbpt/context.cpp
SeQuant/domain/mbpt/context.hpp
SeQuant/domain/mbpt/convention.cpp
Expand Down Expand Up @@ -446,7 +449,7 @@ endif()
target_compile_definitions(SeQuant PUBLIC SEQUANT_ASSERT_BEHAVIOR_=${SEQUANT_ASSERT_BEHAVIOR})

target_link_libraries(SeQuant PUBLIC range-v3::range-v3 Boost::regex Boost::locale Boost::headers SeQuant-bliss Threads::Threads)
target_link_libraries(SeQuant PRIVATE libperm::libperm $<BUILD_LOCAL_INTERFACE:utfcpp>)
target_link_libraries(SeQuant PUBLIC libperm::libperm $<BUILD_LOCAL_INTERFACE:utfcpp>)
target_link_libraries(SeQuant PUBLIC polymorphic_variant::polymorphic_variant)
# modularized Boost has finer grained targets than just Boost::headers
if (Boost_IS_MODULARIZED)
Expand Down
176 changes: 99 additions & 77 deletions SeQuant/core/eval/result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@
#include <SeQuant/core/index.hpp>
#include <SeQuant/core/logger.hpp>
#include <SeQuant/core/utility/macros.hpp>
#include <SeQuant/domain/mbpt/biorthogonalization.hpp>
#include <SeQuant/domain/mbpt/biorthogonalization_hardcoded.hpp>

#include <TiledArray/einsum/tiledarray.h>
#include <btas/btas.h>
#include <tiledarray.h>
#include <range/v3/numeric.hpp>
#include <range/v3/view.hpp>

#include <libperm/Permutation.hpp>
#include <libperm/Rank.hpp>
#include <libperm/Utils.hpp>

#include <any>
#include <memory>
#include <utility>
Expand Down Expand Up @@ -349,7 +355,7 @@ auto particle_antisymmetrize_btas(btas::Tensor<Args...> const& arr,
/// \return The cleaned TA::DistArray.
template <typename... Args>
auto biorthogonal_nns_project_ta(TA::DistArray<Args...> const& arr,
size_t bra_rank) {
size_t bra_rank, double threshold = 1e-12) {
using ranges::views::iota;
size_t const rank = arr.trange().rank();
SEQUANT_ASSERT(bra_rank <= rank);
Expand All @@ -361,11 +367,22 @@ auto biorthogonal_nns_project_ta(TA::DistArray<Args...> const& arr,

using numeric_type = typename TA::DistArray<Args...>::numeric_type;

size_t factorial_ket = 1;
for (size_t i = 2; i <= ket_rank; ++i) {
factorial_ket *= i;
std::vector<numeric_type> cleanup_weights;

if (ket_rank >= 3) {
if (ket_rank < 6) {
cleanup_weights = hardcoded_nns_p_coeffs<numeric_type>(ket_rank);
} else {
Eigen::MatrixXd nns_matrix = compute_nns_p_matrix(ket_rank, threshold);
size_t num_perms = nns_matrix.rows();

cleanup_weights.reserve(num_perms);
for (size_t i = 0; i < num_perms; ++i) {
cleanup_weights.push_back(
static_cast<numeric_type>(nns_matrix(num_perms - 1, i)));
}
}
}
numeric_type norm_factor = numeric_type(1) / numeric_type(factorial_ket);

TA::DistArray<Args...> result;

Expand All @@ -375,43 +392,32 @@ auto biorthogonal_nns_project_ta(TA::DistArray<Args...> const& arr,

const auto lannot = ords_to_annot(perm);

auto process_permutations = [&lannot](const TA::DistArray<Args...>& input_arr,
size_t range_rank, perm_t range_perm,
const std::string& other_annot,
bool is_bra) -> TA::DistArray<Args...> {
if (range_rank <= 1) return input_arr;
TA::DistArray<Args...> result;
if (ket_rank > 2 && !cleanup_weights.empty()) {
const auto bra_annot = bra_rank == 0 ? "" : ords_to_annot(bra_perm);

auto callback = [&]([[maybe_unused]] int parity) {
const auto range_annot = ords_to_annot(range_perm);
const auto annot = other_annot.empty()
? range_annot
: (is_bra ? range_annot + "," + other_annot
: other_annot + "," + range_annot);
size_t num_perms = cleanup_weights.size();
for (size_t perm_rank = 0; perm_rank < num_perms; ++perm_rank) {
perm::Permutation perm_obj = perm::unrank(perm_rank, ket_rank);

// ignore parity, all permutations get same coefficient
numeric_type p_ = 1;
if (result.is_initialized()) {
result(lannot) += p_ * input_arr(annot);
} else {
result(lannot) = p_ * input_arr(annot);
perm_t permuted_ket(ket_rank);
for (size_t i = 0; i < ket_rank; ++i) {
permuted_ket[i] = ket_perm[perm_obj[i]];
}
};
antisymmetric_permutation(ParticleRange{range_perm.begin(), range_rank},
callback);
return result;
};

// identity term with coefficient +1
result(lannot) = arr(lannot);
numeric_type coeff = cleanup_weights[perm_rank];

// process only ket permutations with coefficient norm_factor
if (ket_rank > 1) {
const auto bra_annot = bra_rank == 0 ? "" : ords_to_annot(bra_perm);
auto ket_result =
process_permutations(arr, ket_rank, ket_perm, bra_annot, false);
const auto ket_annot = ords_to_annot(permuted_ket);
const auto annot =
bra_annot.empty() ? ket_annot : bra_annot + "," + ket_annot;

result(lannot) -= norm_factor * ket_result(lannot);
if (result.is_initialized()) {
result(lannot) += coeff * arr(annot);
} else {
result(lannot) = coeff * arr(annot);
}
}
} else {
result(lannot) = arr(lannot);
}

TA::DistArray<Args...>::wait_for_lazy_cleanup(result.world());
Expand All @@ -427,8 +433,7 @@ auto biorthogonal_nns_project_ta(TA::DistArray<Args...> const& arr,
/// \return The cleaned btas::Tensor.
template <typename... Args>
auto biorthogonal_nns_project_btas(btas::Tensor<Args...> const& arr,
size_t bra_rank) {
using ranges::views::concat;
size_t bra_rank, double threshold = 1e-12) {
using ranges::views::iota;
size_t const rank = arr.rank();
SEQUANT_ASSERT(bra_rank <= rank);
Expand All @@ -439,53 +444,62 @@ auto biorthogonal_nns_project_btas(btas::Tensor<Args...> const& arr,
}

using numeric_type = typename btas::Tensor<Args...>::numeric_type;
std::vector<numeric_type> cleanup_weights;

if (ket_rank >= 3) {
if (ket_rank < 6) {
cleanup_weights = hardcoded_nns_p_coeffs<numeric_type>(ket_rank);
} else {
Eigen::MatrixXd nns_matrix = compute_nns_p_matrix(ket_rank, threshold);
size_t num_perms = nns_matrix.rows();

size_t factorial_ket = 1;
for (size_t i = 2; i <= ket_rank; ++i) {
factorial_ket *= i;
cleanup_weights.reserve(num_perms);
for (size_t i = 0; i < num_perms; ++i) {
cleanup_weights.push_back(
static_cast<numeric_type>(nns_matrix(num_perms - 1, i)));
}
}
}
numeric_type norm_factor = numeric_type(1) / numeric_type(factorial_ket);

btas::Tensor<Args...> result;

perm_t perm = iota(size_t{0}, rank) | ranges::to<perm_t>;
perm_t bra_perm = iota(size_t{0}, bra_rank) | ranges::to<perm_t>;
perm_t ket_perm = iota(bra_rank, rank) | ranges::to<perm_t>;
const auto lannot = iota(size_t{0}, rank) | ranges::to<perm_t>;

auto process_permutations = [&lannot](const btas::Tensor<Args...>& input_arr,
size_t range_rank, perm_t range_perm,
const perm_t& other_perm, bool is_bra) {
if (range_rank <= 1) return input_arr;
btas::Tensor<Args...> result{input_arr.range()};
result.fill(0);
const auto lannot = perm;

auto callback = [&]([[maybe_unused]] int parity) {
const auto annot =
is_bra ? concat(range_perm, other_perm) | ranges::to<perm_t>()
: concat(other_perm, range_perm) | ranges::to<perm_t>();
if (ket_rank > 2 && !cleanup_weights.empty()) {
bool result_initialized = false;

// ignore parity, all permutations get same coefficient
numeric_type p_ = 1;
btas::Tensor<Args...> temp;
btas::permute(input_arr, lannot, temp, annot);
btas::scal(p_, temp);
result += temp;
};
size_t num_perms = cleanup_weights.size();
for (size_t perm_rank = 0; perm_rank < num_perms; ++perm_rank) {
perm::Permutation perm_obj = perm::unrank(perm_rank, ket_rank);

antisymmetric_permutation(ParticleRange{range_perm.begin(), range_rank},
callback);
return result;
};
perm_t permuted_ket(ket_rank);
for (size_t i = 0; i < ket_rank; ++i) {
permuted_ket[i] = ket_perm[perm_obj[i]];
}

// identity term with coefficient +1
auto result = arr;
numeric_type coeff = cleanup_weights[perm_rank];

// process only ket permutations with coefficient norm_factor
if (ket_rank > 1) {
const auto bra_annot = bra_rank == 0 ? perm_t{} : bra_perm;
auto ket_result =
process_permutations(arr, ket_rank, ket_perm, bra_annot, false);
perm_t annot = bra_perm;
annot.insert(annot.end(), permuted_ket.begin(), permuted_ket.end());

btas::scal(norm_factor, ket_result);
result -= ket_result;
btas::Tensor<Args...> temp;
btas::permute(arr, annot, temp, lannot);
btas::scal(coeff, temp);

if (result_initialized) {
result += temp;
} else {
result = temp;
result_initialized = true;
}
}

} else {
result = arr;
}

return result;
Expand Down Expand Up @@ -903,8 +917,12 @@ class ResultTensorTA final : public Result {

[[nodiscard]] ResultPtr biorthogonal_nns_project(
size_t bra_rank) const override {
return eval_result<this_type>(
biorthogonal_nns_project_ta(get<ArrayT>(), bra_rank));
if constexpr (std::is_integral_v<numeric_type>) {
std::abort();
} else {
return eval_result<this_type>(
biorthogonal_nns_project_ta(get<ArrayT>(), bra_rank));
}
}

private:
Expand Down Expand Up @@ -1166,8 +1184,12 @@ class ResultTensorBTAS final : public Result {

[[nodiscard]] ResultPtr biorthogonal_nns_project(
[[maybe_unused]] size_t bra_rank) const override {
return eval_result<ResultTensorBTAS<T>>(
biorthogonal_nns_project_btas(get<T>(), bra_rank));
if constexpr (std::is_integral_v<numeric_type>) {
std::abort();
} else {
return eval_result<ResultTensorBTAS<T>>(
biorthogonal_nns_project_btas(get<T>(), bra_rank));
}
}

private:
Expand Down
48 changes: 40 additions & 8 deletions SeQuant/domain/mbpt/biorthogonalization.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <SeQuant/domain/mbpt/biorthogonalization.hpp>
#include <SeQuant/domain/mbpt/biorthogonalization_hardcoded.hpp>

#include <SeQuant/core/container.hpp>
#include <SeQuant/core/expr.hpp>
Expand All @@ -9,6 +10,7 @@
#include <SeQuant/core/utility/macros.hpp>
#include <SeQuant/core/utility/permutation.hpp>

#include <Eigen/Core>
#include <Eigen/Eigenvalues>

#include <libperm/Permutation.hpp>
Expand Down Expand Up @@ -129,6 +131,17 @@ Eigen::MatrixXd compute_biorth_coeffs(std::size_t n_particles,
return pinv;
}

// compute nns using normalized_pinve
Eigen::MatrixXd compute_nns_p_matrix(std::size_t n_particles,
double threshold) {
auto perm_ovlp_mat = permutational_overlap_matrix(n_particles);
auto normalized_pinv = compute_biorth_coeffs(n_particles, threshold);

auto nns = perm_ovlp_mat * normalized_pinv;

return nns;
}

void sort_pairings(ParticlePairings& pairing) {
std::stable_sort(pairing.begin(), pairing.end(),
compare_first_less<IndexPair>{});
Expand Down Expand Up @@ -309,7 +322,7 @@ void biorthogonal_transform(container::svector<ResultExpr>& result_exprs,
// like R^{IJ}_{AB} and the index pairing of the result is what determines
// the required symmetrization. Hence, the symmetrization operator must not
// be changed when transforming from one representation into the other.
assert(std::all_of(
SEQUANT_ASSERT(std::all_of(
result_exprs.begin(), result_exprs.end(), [](const ResultExpr& res) {
bool found = false;
res.expression()->visit(
Expand All @@ -336,19 +349,33 @@ void biorthogonal_transform(container::svector<ResultExpr>& result_exprs,
ranges::to<container::svector<std::size_t>>();

const std::size_t n_particles = externals.front().size();

Eigen::MatrixXd coefficients = compute_biorth_coeffs(n_particles, threshold);

auto num_perms = factorial(n_particles);
SEQUANT_ASSERT(num_perms == coefficients.rows());
SEQUANT_ASSERT(num_perms == coefficients.cols());

auto original_exprs = result_exprs |
ranges::views::transform([](const ResultExpr& res) {
return res.expression();
}) |
ranges::to<container::svector<ExprPtr>>();

Eigen::Matrix<sequant::rational, Eigen::Dynamic, Eigen::Dynamic>
hardcoded_coefficients;
Eigen::MatrixXd computed_coefficients;
bool using_hardcoded = false;

if (n_particles <= 6) {
std::cout << "using hardcoded biorthogonalization coefficients for rank = "
<< n_particles << std::endl;
hardcoded_coefficients = hardcoded_biorth_coeffs_matrix(n_particles);
using_hardcoded = true;
} else {
std::cout << "using computed biorthogonalization coefficients (pinv) for "
"rank = "
<< n_particles << " via SVD" << std::endl;
computed_coefficients = compute_biorth_coeffs(n_particles, threshold);
SEQUANT_ASSERT(num_perms == computed_coefficients.rows());
SEQUANT_ASSERT(num_perms == computed_coefficients.cols());
}

for (std::size_t i = 0; i < result_exprs.size(); ++i) {
result_exprs.at(i).expression() = ex<Constant>(0);
perm::Permutation reference = perm::unrank(ranks.at(i), n_particles);
Expand All @@ -358,9 +385,14 @@ void biorthogonal_transform(container::svector<ResultExpr>& result_exprs,
perm::Permutation perm = perm::unrank(rank, n_particles);
perm->postMultiply(reference);

sequant::rational coeff =
using_hardcoded
? hardcoded_coefficients(ranks.at(i), rank)
: to_rational(computed_coefficients(ranks.at(i), rank),
threshold);

result_exprs.at(i).expression() +=
ex<Constant>(
to_rational(coefficients(ranks.at(i), rank), threshold)) *
ex<Constant>(coeff) *
create_expr_for(externals.at(i), perm, externals, original_exprs);
}

Expand Down
Loading