LCOV - code coverage report
Current view: top level - pairinteraction - Wavefunction.hpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 32 38 84.2 %
Date: 2024-04-29 00:41:50 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 2017 Sebastian Weber, Henri Menke. All rights reserved.
       3             :  *
       4             :  * This file is part of the pairinteraction library.
       5             :  *
       6             :  * The pairinteraction library is free software: you can redistribute it and/or modify
       7             :  * it under the terms of the GNU Lesser General Public License as published by
       8             :  * the Free Software Foundation, either version 3 of the License, or
       9             :  * (at your option) any later version.
      10             :  *
      11             :  * The pairinteraction library is distributed in the hope that it will be useful,
      12             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :  * GNU Lesser General Public License for more details.
      15             :  *
      16             :  * You should have received a copy of the GNU Lesser General Public License
      17             :  * along with the pairinteraction library. If not, see <http://www.gnu.org/licenses/>.
      18             :  */
      19             : 
      20             : #ifndef WAVEFUNCTION_H
      21             : #define WAVEFUNCTION_H
      22             : 
      23             : #include "QuantumDefect.hpp"
      24             : 
      25             : #include "EigenCompat.hpp"
      26             : #include <Eigen/Core>
      27             : 
      28             : #include <cstdio>
      29             : #include <limits>
      30             : #include <string>
      31             : #include <vector>
      32             : 
      33             : // --- Numerov's method ---
      34             : 
      35             : namespace model_potential {
      36             : /** \brief Calculate the model potentials.
      37             :  *
      38             :  * This function calculates the model potentials and returns \f$
      39             :  * V_{\mathrm{mod}}(r) = V_{\mathrm{C}}(r) + V_{\mathrm{P}}(r) +
      40             :  * V_{\mathrm{s.o.}}(r) \f$
      41             :  *
      42             :  * \f[
      43             :  * \begin{aligned}
      44             :  *      V_{\mathrm{C}}(r) &= - \frac{e^2}{4\pi\varepsilon_0} \frac{1 +
      45             :  * (Z-1)\mathrm{e}^{-\alpha_1 r} - r(\alpha_3+\alpha_4 r)\mathrm{e}^{-\alpha_2
      46             :  * r}}{r} \\
      47             :  *      V_{\mathrm{P}}(r) &= -\frac{e^2}{(4\pi\varepsilon_0)^2}
      48             :  * \frac{\alpha_d}{2 r^4} \left[ 1 - \mathrm{e}^{-(r/r_c)^6} \right] \\
      49             :  *      V_{\mathrm{s.o.}}(r) &= \frac{1}{2} \left( \frac{e^2}{4\pi\varepsilon_0}
      50             :  * \right) \left( \frac{g_s}{2 m_e^2 c^2} \right)
      51             :  * \frac{\boldsymbol{l}\cdot\boldsymbol{s}}{r^3} \end{aligned} \f]
      52             :  *
      53             :  * \param[in] qd    Quantum defect data (parameters for the potentials)
      54             :  * \param[in] x     Radial position
      55             :  * \returns Model potentials evaluted at position \p x with parameters \p qd
      56             :  */
      57             : double V(QuantumDefect const &qd, double x);
      58             : 
      59             : /** \brief Helper function for %Numerov's method.s
      60             :  *
      61             :  * %Numerov's method solves \f$ y''(x) + g(x) y(x) = 0\f$. This function
      62             :  * implements \f$ g(x) \f$.
      63             :  *
      64             :  * \f[
      65             :  *      g(x) = \frac{(2 l + 1/2)^2}{x} + 8 x (V_{\mathrm{mod}}(x) - E)
      66             :  * \f]
      67             :  *
      68             :  * \param[in] qd    Quantum defect data (parameters for the potentials)
      69             :  * \param[in] x     Radial position
      70             :  * \returns Scaling coefficient evaluted at position \p x with parameters \p qd
      71             :  */
      72             : double g(QuantumDefect const &qd, double x);
      73             : } // namespace model_potential
      74             : 
      75             : /** \brief %Numerov's method
      76             :  *
      77             :  * This class implements %Numerov's method using a couple of helper functions.
      78             :  * %Numerov's method solves a differential equation of the form
      79             :  * \f[
      80             :  *      y''(x) + g(x)y(x) = 0
      81             :  * \f]
      82             :  * The equation is solved by iteratively computing
      83             :  * \f[
      84             :  *      y_{n+1} = \frac{\left( 2 + \frac{5 h^2}{6} g_n \right) y_n - \left( 1 +
      85             :  * \frac{h^2}{12} g_{n-1} \right) y_{n-1}}{\left( 1 + \frac{h^2}{12} g_n
      86             :  * \right)} \f]
      87             :  *
      88             :  * with \f$ y_n = y(x_n) \f$ and \f$ g_n = g(x_n) \f$. Clearly this equation
      89             :  * needs two initial conditions \f$ y_0 \f$ and \f$ y_1 \f$. We integrate the
      90             :  * equation from outside to inside. Since the wavefunction hs to decay to zero
      91             :  * at infinity we choose \f$ y_0 = 0 \f$. Now depending on the number of knots
      92             :  * of wavefunction we decide whether to set \f$ y_1 = \pm\varepsilon \f$.
      93             :  */
      94             : class Numerov {
      95             :     QuantumDefect const &qd;
      96             :     Eigen::MatrixX<double> xy;
      97             : 
      98             : public:
      99             :     /** \brief Integration step size */
     100             :     static constexpr double const dx = 0.01;
     101             : 
     102             :     /** \brief Constructor
     103             :      *
     104             :      * The constructor initializes the \p qd member and sets the initial
     105             :      * condition for the integrator. It determines the outer and inner
     106             :      * integration bounds by semiclassical and empirical arguments.
     107             :      *
     108             :      * \param[in] qd    Quantum defect data (parameters for the potentials)
     109             :      */
     110             :     Numerov(QuantumDefect const &qd);
     111             : 
     112             :     /** \brief Perform the integration
     113             :      *
     114             :      * This performs the integration using %Numerov's method.
     115             :      *
     116             :      * \returns vector with wavefunction amplitude
     117             :      */
     118             :     Eigen::MatrixX<double> integrate();
     119             : 
     120             :     /** \brief Power kernel for matrix elements
     121             :      *
     122             :      * The power kernel accounts for the fact that in %Numerov's method the
     123             :      * domain is square root scaled. This is important for the calculation of
     124             :      * matrix elements.
     125             :      *
     126             :      * \param[in] power     exponent of r
     127             :      * \returns power kernel
     128             :      */
     129    28766633 :     constexpr static inline int power_kernel(int power) { return 2 * power + 2; }
     130             : };
     131             : 
     132             : // --- Whittaker method ---
     133             : 
     134             : namespace whittaker_functions {
     135             : /** \brief Compute the confluent hypergeometric function
     136             :  *
     137             :  * This is merely a wrapper around
     138             :  * <a
     139             :  * href="https://www.gnu.org/software/gsl/manual/html_node/Hypergeometric-Functions.html">
     140             :  * <tt>gsl_sf_hyperg_U</tt>
     141             :  * </a>
     142             :  *
     143             :  * \param[in] a     see documentation for <tt>gsl_sf_hyperg_U</tt>
     144             :  * \param[in] b     see documentation for <tt>gsl_sf_hyperg_U</tt>
     145             :  * \param[in] z     see documentation for <tt>gsl_sf_hyperg_U</tt>
     146             :  * \returns U(a,b,z)
     147             :  */
     148             : double HypergeometricU(double a, double b, double z);
     149             : 
     150             : /** \brief Compute the %Whittaker function
     151             :  *
     152             :  * The %Whittaker function is defined in terms of the confluent hypergeometric
     153             :  * function.
     154             :  * \f[
     155             :  *        W_{k,m}(z) = \mathrm{e}^{-z/2} z^{m+1/2} U\left(m-k+\frac{1}{2}, 1+2m,
     156             :  * z\right) \f]
     157             :  *
     158             :  * \param[in] k     parameter k from %Whittaker's equation
     159             :  * \param[in] m     parameter m from %Whittaker's equation
     160             :  * \param[in] z     radial position
     161             :  * \returns W(k,m,z)
     162             :  */
     163             : double WhittakerW(double k, double m, double z);
     164             : 
     165             : /** \brief Radial wavefunctions from %Whittaker's function
     166             :  *
     167             :  * The radial wavefunction of the Schrödinger equation with the Coulomb
     168             :  * potential can be expressed in terms of the %Whittaker function.
     169             :  * \f[
     170             :  *        R_{\nu,l}(r) = \frac{1}{\sqrt{\nu^2 \Gamma(\nu+l+1) \Gamma(\nu-l)}}
     171             :  * W_{\nu, l+1/2}\left(\frac{2r}{\nu}\right) \f]
     172             :  *
     173             :  * \param[in] nu    effective principal quantum number \f$ \nu = n -
     174             :  * \delta_{n,l} \f$ \param[in] l     angular quantum number \param[in] r radial
     175             :  * position \returns R(nu,l,r)
     176             :  */
     177             : double RadialWFWhittaker(double r, double nu, int l);
     178             : } // namespace whittaker_functions
     179             : 
     180             : class Whittaker {
     181             :     QuantumDefect const &qd;
     182             :     Eigen::MatrixX<double> xy;
     183             : 
     184             : public:
     185             :     /** \brief Integration step size */
     186             :     static constexpr double const dx = 0.01;
     187             : 
     188             :     /** \brief Constructor
     189             :      *
     190             :      * The constructor initializes the \p qd member and sets the initial
     191             :      * condition for the integrator. It determines the outer integration bound
     192             :      * by semiclassical and empirical arguments. The inner intergration
     193             :      * boundary is set to 1.
     194             :      *
     195             :      * \param[in] qd    Quantum defect data (parameters for the potentials)
     196             :      */
     197             :     Whittaker(QuantumDefect const &qd);
     198             : 
     199             :     /** \brief Evaluate the radial wavefunction
     200             :      *
     201             :      * This does not perform an integration but merely evaluates the %Whittaker
     202             :      * functions over the domain.
     203             :      *
     204             :      * \returns vector with wavefunction amplitude
     205             :      */
     206             :     Eigen::MatrixX<double> integrate();
     207             : 
     208             :     /** \brief Power kernel for matrix elements
     209             :      *
     210             :      * The power kernel accounts for the fact that for the %Whittaker functions
     211             :      * the domain is linear scaled. This is important for the calculation of
     212             :      * matrix elements.
     213             :      *
     214             :      * \param[in] power     exponent of r
     215             :      * \returns power kernel
     216             :      */
     217       92362 :     constexpr static inline double power_kernel(int power) { return 2 * power + 1; }
     218             : };
     219             : 
     220             : // --- Matrix element calculation ---
     221             : 
     222             : /** \brief Find and return index
     223             :  *
     224             :  * Find a value in an Eigen matrix and return its index in the
     225             :  * container to the caller. This is intended to work with
     226             :  * Eigen::Vector or columns of Eigen::Matrix.
     227             :  *
     228             :  * \param[in] x     An Eigen matrix type
     229             :  * \param[in] d     value to find
     230             :  * \returns index of d
     231             :  * \throws std::runtime_error if the value can't be found
     232             :  */
     233             : template <typename T>
     234       12308 : int findidx(T const &x, typename T::Scalar const &d,
     235             :             typename T::Scalar const eps = std::numeric_limits<typename T::Scalar>::epsilon()) {
     236       12308 :     int L = 0;
     237       12308 :     int R = x.rows() - 1;
     238      147901 :     for (;;) {
     239      160209 :         if (L > R) {
     240             :             // If we were already restarted crash. It doesn't make sense to
     241             :             // increase epsilon further.
     242           0 :             if (eps > std::numeric_limits<typename T::Scalar>::epsilon()) {
     243           0 :                 throw std::runtime_error("Search failed");
     244             :             }
     245             :             // Restart search with larger epsilon
     246           0 :             fprintf(stderr, "WARNING: Restarting search with %.16f\n%s:%d in %s\n", 2 * eps,
     247             :                     __FILE__, __LINE__, __func__);
     248           0 :             return findidx(x, d, 2 * eps);
     249             :         }
     250      160209 :         int m = (L + R) / 2;
     251      160209 :         if (x(m) < d) {
     252       70354 :             L = m + 1;
     253       89855 :         } else if (x(m) > d) {
     254       77547 :             R = m - 1;
     255             :         } else {
     256       12308 :             return m;
     257             :         }
     258             :         // x(m) and d might not actually be equal in the floating point sense.
     259             :         // We do fuzzy compare to account for that.
     260      147901 :         if (std::abs(x(m) - d) <= std::max(std::abs(x(m)), std::abs(d)) * eps) {
     261           0 :             return m;
     262             :         }
     263             :     }
     264             : }
     265             : 
     266             : /** \brief Compute radial matrix elements
     267             :  *
     268             :  * The radial matrix element can be calculated from the integral
     269             :  * \f[
     270             :  *      \langle nlj| \hat{p}^{\mathrm{rad}}_\kappa |n'l'j' \rangle =
     271             :  *      \int \Psi^{\text{rad}}_{nlj}(r) \Psi^{\text{rad}}_{n'l'j'}(r)
     272             :  * r^{2+\kappa} \; dr \f] The part \f$ r^{2+\kappa} \f$ varies with rescaling
     273             :  * the domain and is this given by the power_kernel function.
     274             :  *
     275             :  * \tparam T        Method for calculating the wavefunction
     276             :  * \param[in] qd1   Quantum  defect data for first atom
     277             :  * \param[in] power Exponent kappa
     278             :  * \param[in] qd2   Quantum  defect data for second atom
     279             :  * \returns Radial matrix element
     280             :  */
     281             : template <typename T>
     282        3077 : double IntegrateRadialElement(QuantumDefect const &qd1, int power, QuantumDefect const &qd2) {
     283        6154 :     T N1(qd1);
     284        6154 :     T N2(qd2);
     285             : 
     286        6154 :     auto const &xy1 = N1.integrate();
     287        3077 :     auto const &xy2 = N2.integrate();
     288        3077 :     auto const dx = T::dx;
     289             : 
     290        3077 :     auto const xmin = xy1(0, 0) >= xy2(0, 0) ? xy1(0, 0) : xy2(0, 0);
     291        3077 :     auto const xmax = xy1(xy1.rows() - 1, 0) <= xy2(xy2.rows() - 1, 0) ? xy1(xy1.rows() - 1, 0)
     292           0 :                                                                        : xy2(xy2.rows() - 1, 0);
     293             : 
     294        3077 :     double mu = 0;
     295             :     // If there is an overlap, calculate the matrix element
     296        3077 :     if (xmin <= xmax) {
     297        3077 :         int start1 = findidx(xy1.col(0), xmin);
     298        3077 :         int end1 = findidx(xy1.col(0), xmax);
     299        3077 :         int start2 = findidx(xy2.col(0), xmin);
     300        3077 :         int end2 = findidx(xy2.col(0), xmax);
     301             : 
     302             :         int i1, i2;
     303    28862072 :         for (i1 = start1, i2 = start2; i1 < end1 && i2 < end2; ++i1, ++i2) {
     304    28858995 :             mu += xy1(i1, 1) * xy2(i2, 1) * std::pow(xy1(i1, 0), T::power_kernel(power)) * dx;
     305             :         }
     306        3077 :         mu = 2 * mu;
     307             :     }
     308             : 
     309             :     // The radial matrix element is returned in atomic units
     310        6154 :     return mu;
     311             : }
     312             : 
     313             : #endif // WAVEFUNCTION_H

Generated by: LCOV version 1.14