From feff584e5884a05a3769ef1b4966891c4b906843 Mon Sep 17 00:00:00 2001 From: maki49 <1579492865@qq.com> Date: Tue, 27 Oct 2026 11:42:21 -0400 Subject: [PATCH 1/5] fix(ri): differentiate the retained subspace of the truncated Coulomb inverse --- .../module_parameter/read_inp_exx_dftu.cpp | 2 +- source/source_lcao/module_ri/invmat_ri.h | 6 ++ source/source_lcao/module_ri/invmat_ri.hpp | 65 +++++++++++++++ source/source_lcao/module_ri/lri_cv.hpp | 39 +++++++-- source/source_lcao/module_ri/lri_cv_tools.h | 11 +++ source/source_lcao/module_ri/lri_cv_tools.hpp | 31 +++++++ .../module_ri/unittests/CMakeLists.txt | 9 ++ .../module_ri/unittests/test_invmat_ri.cpp | 82 +++++++++++++++++++ 8 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 source/source_lcao/module_ri/unittests/test_invmat_ri.cpp diff --git a/source/source_io/module_parameter/read_inp_exx_dftu.cpp b/source/source_io/module_parameter/read_inp_exx_dftu.cpp index 2d60bdc7aaa..ed9297fe225 100644 --- a/source/source_io/module_parameter/read_inp_exx_dftu.cpp +++ b/source/source_io/module_parameter/read_inp_exx_dftu.cpp @@ -264,7 +264,7 @@ void ReadInput::item_exx() item.annotation = "threshold to inverse Vq in abfs for generating Cs"; item.category = "Exact Exchange (LCAO)"; item.type = "Real"; - item.description = "By default, the Coulomb matrix inversion required for obtaining LRI coefficients is performed using LU decomposition. However, this approach may suffer from numerical instabilities when a large set of auxiliary basis functions (ABFs) is employed. When exx_cs_inv_thr > 0, the inversion is instead carried out via matrix diagonalization. Eigenvalues smaller than exx_cs_inv_thr are discarded to improve numerical stability. A relatively safe and commonly recommended value is 1e-5."; + item.description = "By default, the Coulomb matrix inversion required for obtaining LRI coefficients is performed using Cholesky factorization. However, this approach may suffer from numerical instabilities when a large set of auxiliary basis functions (ABFs) is employed. When exx_cs_inv_thr > 0, the inversion is instead carried out via matrix diagonalization. Eigenvalues smaller than exx_cs_inv_thr times the largest eigenvalue are discarded to improve numerical stability. A relatively safe and commonly recommended value is 1e-5."; item.default_value = "-1"; item.unit = ""; read_sync_double(input.exx_cs_inv_thr); diff --git a/source/source_lcao/module_ri/invmat_ri.h b/source/source_lcao/module_ri/invmat_ri.h index a1cb75cf1a9..4ca62cd57ef 100644 --- a/source/source_lcao/module_ri/invmat_ri.h +++ b/source/source_lcao/module_ri/invmat_ri.h @@ -11,6 +11,7 @@ #include #include +#include template class Inverse_Matrix @@ -23,6 +24,11 @@ class Inverse_Matrix }; void cal_inverse(const Method& method, const double& threshold_condition_number = 0.); + /// Invert with a fixed-rank spectral cutoff and differentiate the retained subspace. + std::array, 3> cal_inverse_with_derivatives( + const std::array, 3>& derivatives, + const double threshold_condition_number); + void input(const RI::Tensor& m); void input(const std::vector>>& ms); RI::Tensor output() const; diff --git a/source/source_lcao/module_ri/invmat_ri.hpp b/source/source_lcao/module_ri/invmat_ri.hpp index ebbe0208b65..fa2bc2efcac 100644 --- a/source/source_lcao/module_ri/invmat_ri.hpp +++ b/source/source_lcao/module_ri/invmat_ri.hpp @@ -143,6 +143,71 @@ inline void Inverse_Matrix::using_syev(const double& threshold_condition_numb BlasConnector::gemm(traits::gemm_trans, 'N', eA.nc, eA.nc, ie, 1, eA.c, eA.nc, eA.c, eA.nc, 0, A.ptr(), A.shape[1]); } +template +std::array, 3> Inverse_Matrix::cal_inverse_with_derivatives( + const std::array, 3>& derivatives, + const double threshold_condition_number) +{ + using traits = InverseMatrixTraits; + using real_type = typename traits::value_type; + const std::size_t n = A.shape[0]; + std::vector eigenvalues(n); + int info = 0; + traits::syev(A, eigenvalues.data(), info); + if (info != 0) + { + throw std::range_error("Spectral inverse eigensolve failed, info=" + std::to_string(info)); + } + const real_type maximum = *std::max_element(eigenvalues.begin(), eigenvalues.end()); + const double cutoff = maximum * threshold_condition_number; + std::vector retained(n); + std::vector inverse_values(n, 0); + for (std::size_t i = 0; i < n; ++i) + { + retained[i] = eigenvalues[i] > cutoff; + if (retained[i]) + { + inverse_values[i] = 1 / eigenvalues[i]; + } + } + // LAPACK eigenvectors are stored as rows U^H in the row-major tensor. + const RI::Tensor vectors = A.copy(); + const RI::Tensor adjoint = vectors.conjugate().transpose(); + RI::Tensor weighted = vectors.copy(); + for (std::size_t i = 0; i < n; ++i) + { + for (std::size_t j = 0; j < n; ++j) + { + weighted(i, j) *= inverse_values[i]; + } + } + A = adjoint * weighted; + std::array, 3> result; + for (std::size_t axis = 0; axis < 3; ++axis) + { + RI::Tensor response = vectors * derivatives[axis] * adjoint; + for (std::size_t i = 0; i < n; ++i) + { + for (std::size_t j = 0; j < n; ++j) + { + real_type divided_difference = 0; + if (retained[i] && retained[j]) + { + divided_difference = -inverse_values[i] * inverse_values[j]; + } + else if (retained[i] != retained[j]) + { + divided_difference = (inverse_values[i] - inverse_values[j]) + / (eigenvalues[i] - eigenvalues[j]); + } + response(i, j) *= divided_difference; + } + } + result[axis] = adjoint * response * vectors; + } + return result; +} + /* void Inverse_Matrix::using_syev( const double &threshold_condition_number ) { diff --git a/source/source_lcao/module_ri/lri_cv.hpp b/source/source_lcao/module_ri/lri_cv.hpp index 49f197816c0..8ba99e6bfeb 100644 --- a/source/source_lcao/module_ri/lri_cv.hpp +++ b/source/source_lcao/module_ri/lri_cv.hpp @@ -406,11 +406,24 @@ LRI_CV::DPcal_C_dC( DPcal_V(it1, it1, {0,0,0}, {{"writable_Vws",true}})}}; std::vector>> L; + std::array, 3> dL; const double cs_inv_thr = this->p_info_ri != nullptr ? this->p_info_ri->Cs_inv_thr : 0.0; - if (cs_inv_thr > 0) + if (cs_inv_thr > 0 && !flag_finish_dC) + { + const auto derivative01 = DPcal_dV(it0, it1, R, flags); + const auto derivative10 = LRI_CV_Tools::negative(DPcal_dV(it1, it0, Rm, flags)); + auto inverse = LRI_CV_Tools::cal_I_dI(V, derivative01, derivative10, cs_inv_thr); + L = std::move(inverse.first); + dL = std::move(inverse.second); + } + else if (cs_inv_thr > 0) + { L = LRI_CV_Tools::cal_I(V, Inverse_Matrix::Method::syev, cs_inv_thr); + } else + { L = LRI_CV_Tools::cal_I(V); + } const std::vector> C = LRI_CV_Tools::mul2(L,A); if(flags.at("writable_Cws")) @@ -438,10 +451,25 @@ LRI_CV::DPcal_C_dC( this->index_abfs, this->index_lcaos, this->index_lcaos, Matrix_Orbs21::Matrix_Order::A1BA2))}; - const std::array,3> dV_01 = DPcal_dV(it0, it1, R, flags); - const std::array,3> dV_10 = LRI_CV_Tools::negative(DPcal_dV(it1, it0, Rm, flags)); - - std::array>,3> // dC = L*(dA-dV*C) + std::array>, 3> dC_tmp; + if (cs_inv_thr > 0) + { + // C = F(V) A: include rotations between retained and discarded modes. + for (std::size_t axis = 0; axis < 3; ++axis) + { + const std::vector> derivative_A = {dA[0][axis], dA[1][axis]}; + dC_tmp[axis] = LRI_CV_Tools::mul2(L, derivative_A); + const auto inverse_response = LRI_CV_Tools::mul2(dL[axis], A); + for (std::size_t block = 0; block < 2; ++block) + { + dC_tmp[axis][block] += inverse_response[block]; + } + } + } + else + { + const auto dV_01 = DPcal_dV(it0, it1, R, flags); + const auto dV_10 = LRI_CV_Tools::negative(DPcal_dV(it1, it0, Rm, flags)); dC_tmp = LRI_CV_Tools::mul2( L, LRI_CV_Tools::change_order( LRI_CV_Tools::minus( @@ -449,6 +477,7 @@ LRI_CV::DPcal_C_dC( std::vector,3>>{ LRI_CV_Tools::mul1(dV_01, C[1]), LRI_CV_Tools::mul1(dV_10, C[0])}))); + } const std::vector,3>> dC = LRI_CV_Tools::change_order(std::move(dC_tmp)); if(flags.at("writable_dCws")) diff --git a/source/source_lcao/module_ri/lri_cv_tools.h b/source/source_lcao/module_ri/lri_cv_tools.h index d5b0cd4f68f..865b9a79008 100644 --- a/source/source_lcao/module_ri/lri_cv_tools.h +++ b/source/source_lcao/module_ri/lri_cv_tools.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace LRI_CV_Tools { @@ -30,6 +31,16 @@ extern std::vector>> cal_I(const std::vector::Method::potrf, const double& threshold_condition_number = 0.); +template +using RIBlockMatrix = std::vector>>; + +template +std::pair, std::array, 3>> cal_I_dI( + const RIBlockMatrix& metric, + const std::array, 3>& derivative01, + const std::array, 3>& derivative10, + const double threshold); + template inline RI::Tensor transform_Rm(const RI::Tensor& V); template diff --git a/source/source_lcao/module_ri/lri_cv_tools.hpp b/source/source_lcao/module_ri/lri_cv_tools.hpp index 1f96ec8c33d..d1ba462ae11 100644 --- a/source/source_lcao/module_ri/lri_cv_tools.hpp +++ b/source/source_lcao/module_ri/lri_cv_tools.hpp @@ -36,6 +36,37 @@ std::vector>> LRI_CV_Tools::cal_I(const std::vecto return I.output({ms[0][0].shape[0], ms[1][0].shape[0]}, {ms[0][0].shape[1], ms[0][1].shape[1]}); } +template +std::pair, std::array, 3>> +LRI_CV_Tools::cal_I_dI(const RIBlockMatrix& metric, + const std::array, 3>& derivative01, + const std::array, 3>& derivative10, + const double threshold) +{ + const std::vector sizes = {metric[0][0].shape[0], metric[1][1].shape[0]}; + std::array, 3> derivatives; + for (std::size_t axis = 0; axis < 3; ++axis) + { + const RIBlockMatrix blocks = { + {RI::Tensor({sizes[0], sizes[0]}), derivative01[axis]}, + {derivative10[axis], RI::Tensor({sizes[1], sizes[1]})}}; + Inverse_Matrix pack; + pack.input(blocks); + derivatives[axis] = pack.output(); + } + Inverse_Matrix inverse; + inverse.input(metric); + const auto responses = inverse.cal_inverse_with_derivatives(derivatives, threshold); + std::array, 3> block_responses; + for (std::size_t axis = 0; axis < 3; ++axis) + { + Inverse_Matrix pack; + pack.input(responses[axis]); + block_responses[axis] = pack.output(sizes, sizes); + } + return std::make_pair(inverse.output(sizes, sizes), block_responses); +} + template RI::Tensor LRI_CV_Tools::transform_Rm(const RI::Tensor& V) { return V.transpose(); diff --git a/source/source_lcao/module_ri/unittests/CMakeLists.txt b/source/source_lcao/module_ri/unittests/CMakeLists.txt index e70aa0dbaf9..4d80407588c 100644 --- a/source/source_lcao/module_ri/unittests/CMakeLists.txt +++ b/source/source_lcao/module_ri/unittests/CMakeLists.txt @@ -37,3 +37,12 @@ addtest( addtest(TARGET MODULE_RI_abfs_vector3_order LIBS parameter SOURCES test_abfs_vector3_order.cpp) abacus_copy_test_files(support) + +addtest( + TARGET MODULE_RI_invmat_ri + LIBS parameter base device ${math_libs} + SOURCES test_invmat_ri.cpp ../abfs_construct_pca.cpp + ../../../source_basis/module_ao/orb_atomic_lm.cpp) +# Keep only the tensor eigensolver from the PCA translation unit in this focused test. +target_compile_options(MODULE_RI_invmat_ri PRIVATE -ffunction-sections -fdata-sections) +target_link_options(MODULE_RI_invmat_ri PRIVATE -Wl,--gc-sections) diff --git a/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp b/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp new file mode 100644 index 00000000000..28811544871 --- /dev/null +++ b/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp @@ -0,0 +1,82 @@ +#include "../invmat_ri.h" +#include "source_base/parallel_global.h" +#include +#include + +namespace +{ +template +void check_derivative(const double cutoff, const T phase) +{ + const double angle = 0.31; + const double step = 1e-5; + auto metric = [phase](const double x) { + RI::Tensor q({3, 3}); + q(0, 0) = std::cos(x); + q(0, 2) = -std::sin(x) * phase; + q(1, 1) = 1; + q(2, 0) = std::sin(x) * RI::Global_Func::get_conj(phase); + q(2, 2) = std::cos(x); + RI::Tensor diagonal({3, 3}); + diagonal(0, 0) = 2; + diagonal(1, 1) = 2; + diagonal(2, 2) = 0.01; + return q * diagonal * q.conjugate().transpose(); + }; + RI::Tensor generator({3, 3}); + generator(0, 2) = -phase; + generator(2, 0) = RI::Global_Func::get_conj(phase); + const auto v = metric(angle); + const auto dv = generator * v - v * generator; + const std::array, 3> directions = {dv, RI::Tensor({3, 3}), RI::Tensor({3, 3})}; + Inverse_Matrix inverse; + inverse.input(v); + const auto derivatives = inverse.cal_inverse_with_derivatives(directions, cutoff); + Inverse_Matrix plus; + plus.input(metric(angle + step)); + plus.cal_inverse(Inverse_Matrix::Method::syev, cutoff); + Inverse_Matrix minus; + minus.input(metric(angle - step)); + minus.cal_inverse(Inverse_Matrix::Method::syev, cutoff); + const auto fd = (plus.output() - minus.output()) * T(0.5 / step); + const auto response = inverse.output() * v; + for (std::size_t i = 0; i < 9; ++i) + { + EXPECT_NEAR(std::abs(derivatives[0].ptr()[i] - fd.ptr()[i]), 0, 2e-6); + EXPECT_NEAR(std::abs(derivatives[1].ptr()[i]), 0, 1e-12); + EXPECT_NEAR(std::abs(derivatives[2].ptr()[i]), 0, 1e-12); + } + const double expected_trace = cutoff > 0 ? 2 : 3; + const T trace = response(0, 0) + response(1, 1) + response(2, 2); + EXPECT_NEAR(std::abs(trace - T(expected_trace)), 0, 1e-10); +} +} + +TEST(SpectralInverse, RealRetainedDiscardedRotation) { check_derivative(0.1, 1.0); } +TEST(SpectralInverse, ComplexRetainedDiscardedRotation) +{ + check_derivative>(0.1, std::polar(1.0, 0.7)); +} +TEST(SpectralInverse, RealFullInverse) { check_derivative(0.0, 1.0); } +TEST(SpectralInverse, ComplexFullInverse) +{ + check_derivative>(0.0, std::polar(1.0, 0.7)); +} + +int main(int argc, char** argv) +{ + int processes = 1; + int threads = 1; + int rank = 0; + Parallel_Global::read_pal_param(argc, argv, processes, threads, rank); + POOL_WORLD = MPI_COMM_NULL; + KP_WORLD = MPI_COMM_NULL; + INT_BGROUP = MPI_COMM_NULL; + BP_WORLD = MPI_COMM_NULL; + GRID_WORLD = MPI_COMM_NULL; + DIAG_WORLD = MPI_COMM_NULL; + ::testing::InitGoogleTest(&argc, argv); + const int result = RUN_ALL_TESTS(); + Parallel_Global::finalize_mpi(); + return result; +} From bcebed9c807a0565196edb0be10f2adbf182db15 Mon Sep 17 00:00:00 2001 From: maki49 <1579492865@qq.com> Date: Wed, 28 Oct 2026 00:33:46 -0400 Subject: [PATCH 2/5] test(ri): cover block coefficient derivatives and address review notes --- .../module_parameter/read_inp_exx_dftu.cpp | 2 +- source/source_lcao/module_ri/lri_cv.hpp | 6 +- source/source_lcao/module_ri/lri_cv_tools.hpp | 3 +- .../module_ri/unittests/test_invmat_ri.cpp | 170 +++++++++++++++++- 4 files changed, 173 insertions(+), 8 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_exx_dftu.cpp b/source/source_io/module_parameter/read_inp_exx_dftu.cpp index ed9297fe225..457afdd8bcf 100644 --- a/source/source_io/module_parameter/read_inp_exx_dftu.cpp +++ b/source/source_io/module_parameter/read_inp_exx_dftu.cpp @@ -264,7 +264,7 @@ void ReadInput::item_exx() item.annotation = "threshold to inverse Vq in abfs for generating Cs"; item.category = "Exact Exchange (LCAO)"; item.type = "Real"; - item.description = "By default, the Coulomb matrix inversion required for obtaining LRI coefficients is performed using Cholesky factorization. However, this approach may suffer from numerical instabilities when a large set of auxiliary basis functions (ABFs) is employed. When exx_cs_inv_thr > 0, the inversion is instead carried out via matrix diagonalization. Eigenvalues smaller than exx_cs_inv_thr times the largest eigenvalue are discarded to improve numerical stability. A relatively safe and commonly recommended value is 1e-5."; + item.description = "By default, the Coulomb matrix inversion required for obtaining LRI coefficients is performed using Cholesky factorization. However, this approach may suffer from numerical instabilities when a large set of auxiliary basis functions (ABFs) is employed. When exx_cs_inv_thr > 0, the inversion is instead carried out via matrix diagonalization. Eigenvalues at or below exx_cs_inv_thr times the largest eigenvalue are discarded to improve numerical stability. A relatively safe and commonly recommended value is 1e-5. Force derivatives assume that modes remain on the same side of the cutoff. Eigenvalue crossings can make the truncated model nonsmooth during relaxation or molecular dynamics."; item.default_value = "-1"; item.unit = ""; read_sync_double(input.exx_cs_inv_thr); diff --git a/source/source_lcao/module_ri/lri_cv.hpp b/source/source_lcao/module_ri/lri_cv.hpp index 8ba99e6bfeb..0fcdc9113ef 100644 --- a/source/source_lcao/module_ri/lri_cv.hpp +++ b/source/source_lcao/module_ri/lri_cv.hpp @@ -411,7 +411,8 @@ LRI_CV::DPcal_C_dC( if (cs_inv_thr > 0 && !flag_finish_dC) { const auto derivative01 = DPcal_dV(it0, it1, R, flags); - const auto derivative10 = LRI_CV_Tools::negative(DPcal_dV(it1, it0, Rm, flags)); + const auto reverse_derivative = DPcal_dV(it1, it0, Rm, flags); + const auto derivative10 = LRI_CV_Tools::negative(reverse_derivative); auto inverse = LRI_CV_Tools::cal_I_dI(V, derivative01, derivative10, cs_inv_thr); L = std::move(inverse.first); dL = std::move(inverse.second); @@ -469,7 +470,8 @@ LRI_CV::DPcal_C_dC( else { const auto dV_01 = DPcal_dV(it0, it1, R, flags); - const auto dV_10 = LRI_CV_Tools::negative(DPcal_dV(it1, it0, Rm, flags)); + const auto reverse_derivative = DPcal_dV(it1, it0, Rm, flags); + const auto dV_10 = LRI_CV_Tools::negative(reverse_derivative); dC_tmp = LRI_CV_Tools::mul2( L, LRI_CV_Tools::change_order( LRI_CV_Tools::minus( diff --git a/source/source_lcao/module_ri/lri_cv_tools.hpp b/source/source_lcao/module_ri/lri_cv_tools.hpp index d1ba462ae11..db23950bf12 100644 --- a/source/source_lcao/module_ri/lri_cv_tools.hpp +++ b/source/source_lcao/module_ri/lri_cv_tools.hpp @@ -64,7 +64,8 @@ LRI_CV_Tools::cal_I_dI(const RIBlockMatrix& metric, pack.input(responses[axis]); block_responses[axis] = pack.output(sizes, sizes); } - return std::make_pair(inverse.output(sizes, sizes), block_responses); + auto inverse_blocks = inverse.output(sizes, sizes); + return std::make_pair(inverse_blocks, block_responses); } template diff --git a/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp b/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp index 28811544871..0c31cad316c 100644 --- a/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp +++ b/source/source_lcao/module_ri/unittests/test_invmat_ri.cpp @@ -1,4 +1,5 @@ #include "../invmat_ri.h" +#include "../lri_cv_tools.h" #include "source_base/parallel_global.h" #include #include @@ -33,10 +34,12 @@ void check_derivative(const double cutoff, const T phase) inverse.input(v); const auto derivatives = inverse.cal_inverse_with_derivatives(directions, cutoff); Inverse_Matrix plus; - plus.input(metric(angle + step)); + const auto displaced_plus = metric(angle + step); + plus.input(displaced_plus); plus.cal_inverse(Inverse_Matrix::Method::syev, cutoff); Inverse_Matrix minus; - minus.input(metric(angle - step)); + const auto displaced_minus = metric(angle - step); + minus.input(displaced_minus); minus.cal_inverse(Inverse_Matrix::Method::syev, cutoff); const auto fd = (plus.output() - minus.output()) * T(0.5 / step); const auto response = inverse.output() * v; @@ -55,12 +58,171 @@ void check_derivative(const double cutoff, const T phase) TEST(SpectralInverse, RealRetainedDiscardedRotation) { check_derivative(0.1, 1.0); } TEST(SpectralInverse, ComplexRetainedDiscardedRotation) { - check_derivative>(0.1, std::polar(1.0, 0.7)); + const auto phase = std::polar(1.0, 0.7); + check_derivative>(0.1, phase); } TEST(SpectralInverse, RealFullInverse) { check_derivative(0.0, 1.0); } TEST(SpectralInverse, ComplexFullInverse) { - check_derivative>(0.0, std::polar(1.0, 0.7)); + const auto phase = std::polar(1.0, 0.7); + check_derivative>(0.0, phase); +} + +namespace +{ +template +LRI_CV_Tools::RIBlockMatrix two_center_metric(const std::array& x, const T phase) +{ + LRI_CV_Tools::RIBlockMatrix metric = { + {RI::Tensor({2, 2}), RI::Tensor({2, 1})}, + {RI::Tensor({1, 2}), RI::Tensor({1, 1})}}; + metric[0][0](0, 0) = 3.0; + metric[0][0](0, 1) = 0.2; + metric[0][0](1, 0) = 0.2; + metric[0][0](1, 1) = 1.8; + metric[1][1](0, 0) = 0.8; + metric[0][1](0, 0) = 0.35 + 0.11 * x[0] - 0.07 * x[1] + 0.04 * x[2]; + metric[0][1](1, 0) = phase * (0.18 - 0.06 * x[0] + 0.05 * x[1] + 0.09 * x[2]); + metric[1][0] = metric[0][1].conjugate().transpose(); + return metric; +} + +template +std::vector> three_center_derivative(const std::size_t axis, const T phase) +{ + const std::array slope = {0.1, -0.08, 0.05}; + std::vector> derivative = {RI::Tensor({2, 1, 2}), RI::Tensor({1, 1, 2})}; + for (std::size_t block = 0; block < 2; ++block) + { + for (std::size_t i = 0; i < derivative[block].shape[0]; ++i) + { + const std::size_t row = block == 0 ? i : 2; + for (std::size_t j = 0; j < 2; ++j) + { + derivative[block](i, 0, j) = phase * (slope[axis] * (row + 1) * (j + 0.5)); + } + } + } + return derivative; +} + +template +std::vector> three_center(const std::array& x, const T phase) +{ + std::vector> a = {RI::Tensor({2, 1, 2}), RI::Tensor({1, 1, 2})}; + for (std::size_t block = 0; block < 2; ++block) + { + for (std::size_t i = 0; i < a[block].get_shape_all(); ++i) + { + a[block].ptr()[i] = T(0.3 + 0.1 * i + 0.2 * block); + } + } + for (std::size_t axis = 0; axis < 3; ++axis) + { + const auto derivative = three_center_derivative(axis, phase); + for (std::size_t block = 0; block < 2; ++block) + { + a[block] += derivative[block] * T(x[axis]); + } + } + return a; +} + +template +void check_coefficients(const double cutoff, const T phase) +{ + const double step = 1e-5; + const std::array x = {0.13, -0.11, 0.07}; + const auto metric = two_center_metric(x, phase); + const auto a = three_center(x, phase); + const std::array first_slope = {0.11, -0.07, 0.04}; + const std::array second_slope = {-0.06, 0.05, 0.09}; + std::array, 3> derivative01; + std::array, 3> derivative10; + for (std::size_t axis = 0; axis < 3; ++axis) + { + derivative01[axis] = RI::Tensor({2, 1}); + derivative01[axis](0, 0) = first_slope[axis]; + derivative01[axis](1, 0) = phase * second_slope[axis]; + derivative10[axis] = derivative01[axis].conjugate().transpose(); + } + const auto inverse = LRI_CV_Tools::cal_I_dI(metric, derivative01, derivative10, cutoff); + const auto reference_inverse = LRI_CV_Tools::cal_I(metric, Inverse_Matrix::Method::syev, cutoff); + for (std::size_t row = 0; row < 2; ++row) + { + for (std::size_t col = 0; col < 2; ++col) + { + EXPECT_EQ(inverse.first[row][col].shape[0], row == 0 ? 2 : 1); + EXPECT_EQ(inverse.first[row][col].shape[1], col == 0 ? 2 : 1); + for (std::size_t i = 0; i < inverse.first[row][col].get_shape_all(); ++i) + { + EXPECT_NEAR(std::abs(inverse.first[row][col].ptr()[i] + - reference_inverse[row][col].ptr()[i]), 0, 1e-12); + } + } + } + for (std::size_t axis = 0; axis < 3; ++axis) + { + auto plus_x = x; + auto minus_x = x; + plus_x[axis] += step; + minus_x[axis] -= step; + const auto plus_metric = two_center_metric(plus_x, phase); + const auto minus_metric = two_center_metric(minus_x, phase); + const auto plus_a = three_center(plus_x, phase); + const auto minus_a = three_center(minus_x, phase); + const auto plus_inverse = LRI_CV_Tools::cal_I(plus_metric, Inverse_Matrix::Method::syev, cutoff); + const auto minus_inverse = LRI_CV_Tools::cal_I(minus_metric, Inverse_Matrix::Method::syev, cutoff); + const auto plus_c = LRI_CV_Tools::mul2(plus_inverse, plus_a); + const auto minus_c = LRI_CV_Tools::mul2(minus_inverse, minus_a); + const auto da = three_center_derivative(axis, phase); + const auto first_term = LRI_CV_Tools::mul2(inverse.first, da); + const auto second_term = LRI_CV_Tools::mul2(inverse.second[axis], a); + for (std::size_t block = 0; block < 2; ++block) + { + const auto analytic = first_term[block] + second_term[block]; + const auto fd = (plus_c[block] - minus_c[block]) * T(0.5 / step); + for (std::size_t i = 0; i < analytic.get_shape_all(); ++i) + { + EXPECT_NEAR(std::abs(analytic.ptr()[i] - fd.ptr()[i]), 0, 2e-8); + } + } + for (std::size_t row = 0; row < 2; ++row) + { + for (std::size_t col = 0; col < 2; ++col) + { + const auto fd = (plus_inverse[row][col] - minus_inverse[row][col]) * T(0.5 / step); + for (std::size_t i = 0; i < fd.get_shape_all(); ++i) + { + EXPECT_NEAR(std::abs(inverse.second[axis][row][col].ptr()[i] - fd.ptr()[i]), 0, 2e-8); + } + } + } + // Fixed diagonal blocks and changing off-diagonal blocks vary the eigenvalues. + const double plus_norm = std::norm(plus_metric[0][1](0, 0)) + std::norm(plus_metric[0][1](1, 0)); + const double minus_norm = std::norm(minus_metric[0][1](0, 0)) + std::norm(minus_metric[0][1](1, 0)); + EXPECT_GT(std::abs(plus_norm - minus_norm), 1e-9); + } +} +} + +TEST(SpectralInverse, RealUnequalBlocksWithMovingThreeCenter) +{ + check_coefficients(0.3, 1.0); +} +TEST(SpectralInverse, ComplexUnequalBlocksWithMovingThreeCenter) +{ + const auto phase = std::polar(1.0, 0.7); + check_coefficients>(0.3, phase); +} +TEST(SpectralInverse, RealUnequalBlocksWithoutTruncation) +{ + check_coefficients(0.0, 1.0); +} +TEST(SpectralInverse, ComplexUnequalBlocksWithoutTruncation) +{ + const auto phase = std::polar(1.0, 0.7); + check_coefficients>(0.0, phase); } int main(int argc, char** argv) From 511ede99762a0d8644a5f96c20abfc87a9e53921 Mon Sep 17 00:00:00 2001 From: maki49 <1579492865@qq.com> Date: Thu, 29 Oct 2026 12:20:32 -0400 Subject: [PATCH 3/5] refactor(ri): move Coulomb inverse and LRI implementations into cpp files Replace invmat_ri.hpp and lri_cv.hpp with compiled implementations and explicit double/complex instantiations. Move the new cal_I_dI adapter into lri_cv_tools.cpp, keeping the existing generic tools intact. Make Ewald/EXX tool dependencies explicit and update CMake, the focused unit-test target, and the legacy Makefile object list. No numerical formulas or INPUT behavior change; no user documentation update is needed. The direct tools includes are required for the existing Ewald/EXX template implementation calls. Validation: - cmake --build build --target MODULE_RI_invmat_ri ri -j 4 (RI initially exposed missing direct includes; passed after correction) - cmake --build build --target abacus_std_para -j 4: passed - OMP_NUM_THREADS=1 ctest --test-dir build -R '^MODULE_RI_invmat_ri$' --output-on-failure: passed (8 cases) - git diff --cached --check: passed - agent_governance_check.py --staged: no errors; advisory warnings only --- source/Makefile.Objects | 3 ++ source/source_lcao/module_ri/CMakeLists.txt | 3 ++ source/source_lcao/module_ri/ewald_vq.hpp | 1 + source/source_lcao/module_ri/exx_lri.hpp | 1 + .../{invmat_ri.hpp => invmat_ri.cpp} | 8 +-- source/source_lcao/module_ri/invmat_ri.h | 1 - .../module_ri/{lri_cv.hpp => lri_cv.cpp} | 7 ++- source/source_lcao/module_ri/lri_cv.h | 13 +++-- source/source_lcao/module_ri/lri_cv_tools.cpp | 51 +++++++++++++++++++ source/source_lcao/module_ri/lri_cv_tools.hpp | 32 ------------ .../module_ri/unittests/CMakeLists.txt | 3 +- 11 files changed, 74 insertions(+), 49 deletions(-) rename source/source_lcao/module_ri/{invmat_ri.hpp => invmat_ri.cpp} (97%) rename source/source_lcao/module_ri/{lri_cv.hpp => lri_cv.cpp} (99%) create mode 100644 source/source_lcao/module_ri/lri_cv_tools.cpp diff --git a/source/Makefile.Objects b/source/Makefile.Objects index ced2b16b823..d30e5198d44 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -866,6 +866,9 @@ OBJS_LCAO=evolve_elec.o\ center2orb_orb22.o\ OBJS_MODULE_RI=conv_coulomb_pot_k.o\ + invmat_ri.o\ + lri_cv.o\ + lri_cv_tools.o\ exx_abfs_jle.o\ module_restart/restart_exx_csr.o\ exx_abfs_io.o\ diff --git a/source/source_lcao/module_ri/CMakeLists.txt b/source/source_lcao/module_ri/CMakeLists.txt index d9373973845..9f1c903615a 100644 --- a/source/source_lcao/module_ri/CMakeLists.txt +++ b/source/source_lcao/module_ri/CMakeLists.txt @@ -5,6 +5,8 @@ if(ENABLE_LIBRI) list( APPEND objects + invmat_ri.cpp + lri_cv_tools.cpp matrix_orbs11.cpp matrix_orbs21.cpp matrix_orbs22.cpp @@ -15,6 +17,7 @@ if(ENABLE_LIBRI) list( APPEND objects + lri_cv.cpp conv_coulomb_pot_k.cpp exx_abfs_ctor_orbs.cpp exx_abfs_io.cpp diff --git a/source/source_lcao/module_ri/ewald_vq.hpp b/source/source_lcao/module_ri/ewald_vq.hpp index d368b5565c7..d2c80047c68 100644 --- a/source/source_lcao/module_ri/ewald_vq.hpp +++ b/source/source_lcao/module_ri/ewald_vq.hpp @@ -6,6 +6,7 @@ #ifndef EWALD_VQ_HPP #define EWALD_VQ_HPP +#include "lri_cv_tools.h" #include #include #include diff --git a/source/source_lcao/module_ri/exx_lri.hpp b/source/source_lcao/module_ri/exx_lri.hpp index 618c58cef6f..83e2684aea2 100644 --- a/source/source_lcao/module_ri/exx_lri.hpp +++ b/source/source_lcao/module_ri/exx_lri.hpp @@ -6,6 +6,7 @@ #ifndef EXX_LRI_HPP #define EXX_LRI_HPP +#include "lri_cv_tools.h" #include "exx_lri.h" #include "source_cell/unitcell.h" #include "ri_2d_comm.h" diff --git a/source/source_lcao/module_ri/invmat_ri.hpp b/source/source_lcao/module_ri/invmat_ri.cpp similarity index 97% rename from source/source_lcao/module_ri/invmat_ri.hpp rename to source/source_lcao/module_ri/invmat_ri.cpp index fa2bc2efcac..355b780ab55 100644 --- a/source/source_lcao/module_ri/invmat_ri.hpp +++ b/source/source_lcao/module_ri/invmat_ri.cpp @@ -3,8 +3,6 @@ // DATE : 2022-08-17 //======================= -#ifndef INVERSE_MATRIX_HPP -#define INVERSE_MATRIX_HPP #include "invmat_ri.h" #include "source_base/module_external/lapack_connector.h" @@ -103,7 +101,7 @@ struct InverseMatrixTraits> }; template -inline void Inverse_Matrix::using_syev(const double& threshold_condition_number) +void Inverse_Matrix::using_syev(const double& threshold_condition_number) { using traits = InverseMatrixTraits; using val_t = typename traits::value_type; @@ -324,4 +322,6 @@ void Inverse_Matrix::copy_down_triangle() A(i0, i1) = A(i1, i0); } -#endif +// Supported real and complex double-precision RI backends. +template class Inverse_Matrix; +template class Inverse_Matrix>; diff --git a/source/source_lcao/module_ri/invmat_ri.h b/source/source_lcao/module_ri/invmat_ri.h index 4ca62cd57ef..377048aa8f7 100644 --- a/source/source_lcao/module_ri/invmat_ri.h +++ b/source/source_lcao/module_ri/invmat_ri.h @@ -42,6 +42,5 @@ class Inverse_Matrix RI::Tensor A; }; -#include "invmat_ri.hpp" #endif // ABACUS_SOURCE_LCAO_MODULE_RI_INVMAT_RI_H diff --git a/source/source_lcao/module_ri/lri_cv.hpp b/source/source_lcao/module_ri/lri_cv.cpp similarity index 99% rename from source/source_lcao/module_ri/lri_cv.hpp rename to source/source_lcao/module_ri/lri_cv.cpp index 0fcdc9113ef..41d273d0b5b 100644 --- a/source/source_lcao/module_ri/lri_cv.hpp +++ b/source/source_lcao/module_ri/lri_cv.cpp @@ -3,8 +3,6 @@ // DATE : 2022-08-17 //======================= -#ifndef LRI_CV_HPP -#define LRI_CV_HPP #include "lri_cv.h" #include "lri_cv_tools.h" @@ -495,5 +493,6 @@ LRI_CV::DPcal_C_dC( } // end else (!(C_read && flag_finish_dC)) } - -#endif +// Supported real and complex double-precision RI backends. +template class LRI_CV; +template class LRI_CV>; diff --git a/source/source_lcao/module_ri/lri_cv.h b/source/source_lcao/module_ri/lri_cv.h index 12359d3b5e3..1103f6fcdd4 100644 --- a/source/source_lcao/module_ri/lri_cv.h +++ b/source/source_lcao/module_ri/lri_cv.h @@ -45,13 +45,13 @@ class LRI_CV const double &kmesh_times, std::shared_ptr MGT, const bool& init_C); - inline std::map>> + std::map>> cal_Vs( const UnitCell &ucell, const std::vector &list_A0, const std::vector &list_A1, const std::map &flags); // "writable_Vws" - inline std::map, 3>>> + std::map, 3>>> cal_dVs( const UnitCell &ucell, const std::vector &list_A0, @@ -108,16 +108,16 @@ class LRI_CV const T_func_cal_Rcut& func_cal_Rcut, const T_func_DPcal_data& func_DPcal_data); - inline double cal_V_Rcut(const int it0, const int it1); - inline double cal_C_Rcut(const int it0, const int it1); + double cal_V_Rcut(const int it0, const int it1); + double cal_C_Rcut(const int it0, const int it1); - inline RI::Tensor + RI::Tensor DPcal_V( const int it0, const int it1, const Abfs::Vector3_Order &R, const std::map &flags); // "writable_Vws" - inline std::array,3> + std::array,3> DPcal_dV( const int it0, const int it1, @@ -141,6 +141,5 @@ class LRI_CV const Tfunc &func_cal_o11); }; -#include "lri_cv.hpp" #endif diff --git a/source/source_lcao/module_ri/lri_cv_tools.cpp b/source/source_lcao/module_ri/lri_cv_tools.cpp new file mode 100644 index 00000000000..aadd628635f --- /dev/null +++ b/source/source_lcao/module_ri/lri_cv_tools.cpp @@ -0,0 +1,51 @@ +#include "lri_cv_tools.h" + +#include + +template +std::pair, std::array, 3>> +LRI_CV_Tools::cal_I_dI(const RIBlockMatrix& metric, + const std::array, 3>& derivative01, + const std::array, 3>& derivative10, + const double threshold) +{ + const std::vector sizes = {metric[0][0].shape[0], metric[1][1].shape[0]}; + std::array, 3> derivatives; + for (std::size_t axis = 0; axis < 3; ++axis) + { + const RIBlockMatrix blocks = { + {RI::Tensor({sizes[0], sizes[0]}), derivative01[axis]}, + {derivative10[axis], RI::Tensor({sizes[1], sizes[1]})}}; + Inverse_Matrix pack; + pack.input(blocks); + derivatives[axis] = pack.output(); + } + Inverse_Matrix inverse; + inverse.input(metric); + const auto responses = inverse.cal_inverse_with_derivatives(derivatives, threshold); + std::array, 3> block_responses; + for (std::size_t axis = 0; axis < 3; ++axis) + { + Inverse_Matrix pack; + pack.input(responses[axis]); + block_responses[axis] = pack.output(sizes, sizes); + } + auto inverse_blocks = inverse.output(sizes, sizes); + return std::make_pair(inverse_blocks, block_responses); +} + +template std::pair, + std::array, 3>> +LRI_CV_Tools::cal_I_dI( + const RIBlockMatrix& metric, + const std::array, 3>& derivative01, + const std::array, 3>& derivative10, + const double threshold); + +template std::pair>, + std::array>, 3>> +LRI_CV_Tools::cal_I_dI( + const RIBlockMatrix>& metric, + const std::array>, 3>& derivative01, + const std::array>, 3>& derivative10, + const double threshold); diff --git a/source/source_lcao/module_ri/lri_cv_tools.hpp b/source/source_lcao/module_ri/lri_cv_tools.hpp index db23950bf12..1f96ec8c33d 100644 --- a/source/source_lcao/module_ri/lri_cv_tools.hpp +++ b/source/source_lcao/module_ri/lri_cv_tools.hpp @@ -36,38 +36,6 @@ std::vector>> LRI_CV_Tools::cal_I(const std::vecto return I.output({ms[0][0].shape[0], ms[1][0].shape[0]}, {ms[0][0].shape[1], ms[0][1].shape[1]}); } -template -std::pair, std::array, 3>> -LRI_CV_Tools::cal_I_dI(const RIBlockMatrix& metric, - const std::array, 3>& derivative01, - const std::array, 3>& derivative10, - const double threshold) -{ - const std::vector sizes = {metric[0][0].shape[0], metric[1][1].shape[0]}; - std::array, 3> derivatives; - for (std::size_t axis = 0; axis < 3; ++axis) - { - const RIBlockMatrix blocks = { - {RI::Tensor({sizes[0], sizes[0]}), derivative01[axis]}, - {derivative10[axis], RI::Tensor({sizes[1], sizes[1]})}}; - Inverse_Matrix pack; - pack.input(blocks); - derivatives[axis] = pack.output(); - } - Inverse_Matrix inverse; - inverse.input(metric); - const auto responses = inverse.cal_inverse_with_derivatives(derivatives, threshold); - std::array, 3> block_responses; - for (std::size_t axis = 0; axis < 3; ++axis) - { - Inverse_Matrix pack; - pack.input(responses[axis]); - block_responses[axis] = pack.output(sizes, sizes); - } - auto inverse_blocks = inverse.output(sizes, sizes); - return std::make_pair(inverse_blocks, block_responses); -} - template RI::Tensor LRI_CV_Tools::transform_Rm(const RI::Tensor& V) { return V.transpose(); diff --git a/source/source_lcao/module_ri/unittests/CMakeLists.txt b/source/source_lcao/module_ri/unittests/CMakeLists.txt index 4d80407588c..09928236a9a 100644 --- a/source/source_lcao/module_ri/unittests/CMakeLists.txt +++ b/source/source_lcao/module_ri/unittests/CMakeLists.txt @@ -41,7 +41,8 @@ abacus_copy_test_files(support) addtest( TARGET MODULE_RI_invmat_ri LIBS parameter base device ${math_libs} - SOURCES test_invmat_ri.cpp ../abfs_construct_pca.cpp + SOURCES test_invmat_ri.cpp ../invmat_ri.cpp ../lri_cv_tools.cpp + ../abfs_construct_pca.cpp ../../../source_basis/module_ao/orb_atomic_lm.cpp) # Keep only the tensor eigensolver from the PCA translation unit in this focused test. target_compile_options(MODULE_RI_invmat_ri PRIVATE -ffunction-sections -fdata-sections) From 8c51f32f42814c0c63d337453031112afbd557ba Mon Sep 17 00:00:00 2001 From: maki49 <1579492865@qq.com> Date: Thu, 29 Oct 2026 12:43:05 -0400 Subject: [PATCH 4/5] fix(test): link compiled LRI implementation in EXX workspace test After the implementation-header split, the standalone workspace target still compiled exx_workspace.cpp without lri_cv.cpp. Its instantiated workspace factories require real and complex LRI_CV destructors, which made the CI all-target build fail at link time although the main binary and inverse-derivative test built successfully. Include the compiled LRI implementation and use function/data sections with linker garbage collection, as in the existing focused inverse test, to keep unrelated integral assembly outside this geometry-sharing test. No numerical formulas, INPUT behavior, or user documentation change. Validation: - Reproduced the missing LRI_CV destructors with cmake --build build --target MODULE_RI_exx_workspace -j 4 before fix. - Rebuilt all six MODULE_RI targets successfully after fix. - OMP_NUM_THREADS=1 ctest --test-dir build -R '^MODULE_RI_' --output-on-failure: 6/6 passed. - cmake --build build --target abacus_std_para -j 4: passed. - agent_governance_check.py --staged and git diff --cached --check: passed. --- source/source_lcao/module_ri/unittests/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/source_lcao/module_ri/unittests/CMakeLists.txt b/source/source_lcao/module_ri/unittests/CMakeLists.txt index 09928236a9a..887eaa63c1b 100644 --- a/source/source_lcao/module_ri/unittests/CMakeLists.txt +++ b/source/source_lcao/module_ri/unittests/CMakeLists.txt @@ -13,7 +13,12 @@ if(ENABLE_LCAO) SOURCES test_exx_workspace.cpp ../exx_workspace.cpp + ../lri_cv.cpp ../../../source_basis/module_ao/orb_atomic_lm.cpp) + # Link the compiled LRI lifecycle while keeping unrelated integral assembly out + # of this focused geometry-sharing test. + target_compile_options(MODULE_RI_exx_workspace PRIVATE -ffunction-sections -fdata-sections) + target_link_options(MODULE_RI_exx_workspace PRIVATE -Wl,--gc-sections) endif() addtest( TARGET From dd60e04144f36d04f0f9c51b820cbe6cbc8fa413 Mon Sep 17 00:00:00 2001 From: maki49 <1579492865@qq.com> Date: Fri, 30 Oct 2026 00:43:26 -0400 Subject: [PATCH 5/5] refactor(ri): keep PCA dependencies out of the inverse header Move abfs_construct_pca.h from invmat_ri.h into invmat_ri.cpp, as requested in review. Add the tools' own UnitCell dependency explicitly: its existing template bodies access UnitCell members and need the complete type rather than a transitive include through the inverse. No test input, solver selection, numerical formula, or INPUT behavior changes. The original GPU matrix-output fixture remains unchanged. Validation of the retained header cleanup: - Full abacus_std_para build/link passed. - OMP_NUM_THREADS=1 ctest --test-dir build -R '^MODULE_RI_' --output-on-failure: 6/6 targets passed. - Original ScaLAPACK output fixture: 10 consecutive two-rank CPU runs, all 11 original comparisons passed on every run. - Governance check: no errors; explicit UnitCell include needed by the template bodies. No user documentation update required. - git diff --cached --check: passed. --- source/source_lcao/module_ri/invmat_ri.cpp | 1 + source/source_lcao/module_ri/invmat_ri.h | 1 - source/source_lcao/module_ri/lri_cv_tools.h | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/source/source_lcao/module_ri/invmat_ri.cpp b/source/source_lcao/module_ri/invmat_ri.cpp index 355b780ab55..4b83f1b1522 100644 --- a/source/source_lcao/module_ri/invmat_ri.cpp +++ b/source/source_lcao/module_ri/invmat_ri.cpp @@ -5,6 +5,7 @@ #include "invmat_ri.h" +#include "abfs_construct_pca.h" #include "source_base/module_external/lapack_connector.h" #include diff --git a/source/source_lcao/module_ri/invmat_ri.h b/source/source_lcao/module_ri/invmat_ri.h index 377048aa8f7..6af02c40b06 100644 --- a/source/source_lcao/module_ri/invmat_ri.h +++ b/source/source_lcao/module_ri/invmat_ri.h @@ -7,7 +7,6 @@ #define ABACUS_SOURCE_LCAO_MODULE_RI_INVMAT_RI_H -#include "abfs_construct_pca.h" #include #include diff --git a/source/source_lcao/module_ri/lri_cv_tools.h b/source/source_lcao/module_ri/lri_cv_tools.h index 865b9a79008..9c9d2a48968 100644 --- a/source/source_lcao/module_ri/lri_cv_tools.h +++ b/source/source_lcao/module_ri/lri_cv_tools.h @@ -7,6 +7,7 @@ #define LRI_CV_TOOLS_H #include "invmat_ri.h" +#include "source_cell/unitcell.h" #include "abfs_vector3_order.h" #include "source_lcao/module_ri/abfs.h"