LCOV - code coverage report
Current view: top level - src/system - GreenTensorInterpolator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 74 76 97.4 %
Date: 2026-03-03 11:25:29 Functions: 22 22 100.0 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2025 PairInteraction Developers
       2             : // SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : #include "pairinteraction/system/GreenTensorInterpolator.hpp"
       5             : 
       6             : #include "pairinteraction/utils/eigen_assertion.hpp"
       7             : #include "pairinteraction/utils/eigen_compat.hpp"
       8             : #include "pairinteraction/utils/spherical.hpp"
       9             : #include "pairinteraction/utils/traits.hpp"
      10             : 
      11             : #include <Eigen/Dense>
      12             : #include <Eigen/Sparse>
      13             : #include <complex>
      14             : #include <map>
      15             : #include <spdlog/spdlog.h>
      16             : #include <unsupported/Eigen/Splines>
      17             : 
      18             : namespace pairinteraction {
      19             : template <typename Scalar>
      20        1808 : GreenTensorInterpolator<Scalar>::ConstantEntry::ConstantEntry(int row, int col, Scalar val)
      21        1808 :     : row_(row), col_(col), val_(val) {}
      22             : 
      23             : template <typename Scalar>
      24        1844 : Scalar GreenTensorInterpolator<Scalar>::ConstantEntry::val() const {
      25        1844 :     return val_;
      26             : }
      27             : 
      28             : template <typename Scalar>
      29        3649 : int GreenTensorInterpolator<Scalar>::ConstantEntry::row() const noexcept {
      30        3649 :     return row_;
      31             : }
      32             : 
      33             : template <typename Scalar>
      34        3649 : int GreenTensorInterpolator<Scalar>::ConstantEntry::col() const noexcept {
      35        3649 :     return col_;
      36             : }
      37             : 
      38             : template <typename Scalar>
      39          18 : GreenTensorInterpolator<Scalar>::OmegaDependentEntry::OmegaDependentEntry(
      40             :     int row, int col, Eigen::Spline<real_t, 1> real_spline, Eigen::Spline<real_t, 1> imag_spline)
      41          18 :     : row_(row), col_(col), real_spline(std::move(real_spline)),
      42          18 :       imag_spline(std::move(imag_spline)) {}
      43             : 
      44             : template <typename Scalar>
      45         468 : Scalar GreenTensorInterpolator<Scalar>::OmegaDependentEntry::val(double omega) const {
      46             :     if constexpr (traits::NumTraits<Scalar>::is_complex_v) {
      47         234 :         return {real_spline(omega)(0), imag_spline(omega)(0)};
      48             :     } else {
      49         234 :         return real_spline(omega)(0);
      50             :     }
      51             : }
      52             : 
      53             : template <typename Scalar>
      54         468 : int GreenTensorInterpolator<Scalar>::OmegaDependentEntry::row() const noexcept {
      55         468 :     return row_;
      56             : }
      57             : 
      58             : template <typename Scalar>
      59         468 : int GreenTensorInterpolator<Scalar>::OmegaDependentEntry::col() const noexcept {
      60         468 :     return col_;
      61             : }
      62             : 
      63             : template <typename Scalar>
      64         570 : void GreenTensorInterpolator<Scalar>::create_entries_from_cartesian(
      65             :     int kappa1, int kappa2, const Eigen::MatrixX<Scalar> &tensor_in_cartesian_coordinates) {
      66             : 
      67         570 :     const real_t scale = tensor_in_cartesian_coordinates.norm();
      68         570 :     const real_t numerical_precision = 100 * scale * std::numeric_limits<real_t>::epsilon();
      69             : 
      70        1140 :     Eigen::SparseMatrix<complex_t> tensor =
      71         570 :         (spherical::get_transformator<complex_t>(kappa1) * tensor_in_cartesian_coordinates *
      72         570 :          spherical::get_transformator<complex_t>(kappa2).adjoint())
      73         570 :             .sparseView(1, numerical_precision);
      74             : 
      75         570 :     std::vector<Entry> entries;
      76        2340 :     for (int k = 0; k < tensor.outerSize(); ++k) {
      77        3578 :         for (typename Eigen::SparseMatrix<complex_t>::InnerIterator it(tensor, k); it; ++it) {
      78             :             if constexpr (traits::NumTraits<Scalar>::is_complex_v) {
      79         895 :                 entries.emplace_back(ConstantEntry(it.row(), it.col(), it.value()));
      80             :             } else {
      81         913 :                 entries.emplace_back(ConstantEntry(it.row(), it.col(), it.value().real()));
      82         913 :                 assert(abs(it.value().imag()) < numerical_precision);
      83             :             }
      84             :         }
      85             :     }
      86         570 :     entries_map[{kappa1, kappa2}] = std::move(entries);
      87         570 : }
      88             : 
      89             : template <typename Scalar>
      90           6 : void GreenTensorInterpolator<Scalar>::create_entries_from_cartesian(
      91             :     int kappa1, int kappa2,
      92             :     const std::vector<Eigen::MatrixX<Scalar>> &tensors_in_cartesian_coordinates,
      93             :     const std::vector<double> &omegas) {
      94             : 
      95           6 :     if (tensors_in_cartesian_coordinates.size() != omegas.size()) {
      96           0 :         throw std::invalid_argument("The number of tensors and omegas must match.");
      97             :     }
      98             : 
      99           6 :     if (tensors_in_cartesian_coordinates.size() < 4) {
     100           0 :         throw std::invalid_argument(
     101             :             "At least 4 tensors are required for the applied cubic spline interpolation.");
     102             :     }
     103             : 
     104           6 :     auto num_knots = static_cast<int>(omegas.size());
     105           6 :     Eigen::Map<const Eigen::RowVectorXd> knots(omegas.data(), num_knots);
     106             : 
     107           6 :     constexpr int spline_degree = 3; // cubic spline interpolation
     108             : 
     109             :     // Temporary storage wih key = (row, col) and value = vector of one double per omega
     110           6 :     std::map<std::pair<int, int>, std::pair<Eigen::RowVectorXd, Eigen::RowVectorXd>> temp_map;
     111         126 :     for (int idx = 0; idx < num_knots; ++idx) {
     112             : 
     113         120 :         const real_t scale = tensors_in_cartesian_coordinates[idx].norm();
     114         120 :         const real_t numerical_precision = 100 * scale * std::numeric_limits<real_t>::epsilon();
     115             : 
     116         240 :         Eigen::SparseMatrix<complex_t> tensor =
     117         120 :             (spherical::get_transformator<complex_t>(kappa1) *
     118         120 :              tensors_in_cartesian_coordinates[idx] *
     119         120 :              spherical::get_transformator<complex_t>(kappa2).adjoint())
     120         120 :                 .sparseView(1, numerical_precision);
     121             : 
     122         480 :         for (int k = 0; k < tensor.outerSize(); ++k) {
     123         720 :             for (typename Eigen::SparseMatrix<complex_t>::InnerIterator it(tensor, k); it; ++it) {
     124         360 :                 std::pair<int, int> key{it.row(), it.col()};
     125         360 :                 auto &[vec_real, vec_imag] =
     126             :                     temp_map
     127         720 :                         .try_emplace(key, Eigen::RowVectorXd::Zero(num_knots),
     128             :                                      Eigen::RowVectorXd::Zero(num_knots))
     129         360 :                         .first->second;
     130         360 :                 vec_real(idx) = it.value().real();
     131             :                 if constexpr (traits::NumTraits<Scalar>::is_complex_v) {
     132         180 :                     vec_imag(idx) = it.value().imag();
     133             :                 } else {
     134         180 :                     assert(abs(it.value().imag()) < numerical_precision);
     135             :                 }
     136             :             }
     137             :         }
     138             :     }
     139             : 
     140             :     // Set the green tensor entries with spline interpolation
     141           6 :     std::vector<Entry> entries;
     142           6 :     entries.reserve(temp_map.size());
     143          24 :     for (const auto &[key, value] : temp_map) {
     144          18 :         const auto &[vec_real, vec_imag] = value;
     145          18 :         const auto &[row, col] = key;
     146             : 
     147          18 :         Eigen::Spline<real_t, 1> real_spline =
     148             :             Eigen::SplineFitting<Eigen::Spline<real_t, 1>>::Interpolate(vec_real, spline_degree,
     149             :                                                                         knots);
     150             : 
     151          18 :         Eigen::Spline<real_t, 1> imag_spline;
     152             :         if constexpr (traits::NumTraits<Scalar>::is_complex_v) {
     153           9 :             imag_spline = Eigen::SplineFitting<Eigen::Spline<real_t, 1>>::Interpolate(
     154             :                 vec_imag, spline_degree, knots);
     155             :         }
     156             : 
     157          18 :         entries.emplace_back(
     158          36 :             OmegaDependentEntry(row, col, std::move(real_spline), std::move(imag_spline)));
     159             :     }
     160           6 :     entries_map[{kappa1, kappa2}] = std::move(entries);
     161           6 : }
     162             : 
     163             : template <typename Scalar>
     164             : const std::vector<typename GreenTensorInterpolator<Scalar>::Entry> &
     165        5699 : GreenTensorInterpolator<Scalar>::get_spherical_entries(int kappa1, int kappa2) const {
     166        5699 :     if (auto it = entries_map.find({kappa1, kappa2}); it != entries_map.end()) {
     167        1836 :         return it->second;
     168             :     }
     169        3863 :     static const std::vector<Entry> empty_entries;
     170        3863 :     return empty_entries;
     171             : }
     172             : 
     173             : // Explicit instantiations
     174             : template class GreenTensorInterpolator<double>;
     175             : template class GreenTensorInterpolator<std::complex<double>>;
     176             : } // namespace pairinteraction

Generated by: LCOV version 1.16