evaleev / libint

Libint: high-performance library for computing Gaussian integrals in quantum mechanics
Other
218 stars 96 forks source link

Diagonal elements of my molecular hessian are incorrect #278

Closed FreemanTheMaverick closed 7 months ago

FreemanTheMaverick commented 8 months ago

Hi. I want to implement molecular hessian im my code. Since there are usually too many second-order derivatives of electron integrals to save in memory, in my code I computed each integral derivative's contribution to molecular hessian (different from the example hartree-fock++.cc which saves all derivatives in memory). However, the resultant diagonal hessian elements are all incorrect while the off-diagonal ones are correct. The minimal codes are put as follows. They seem long but are actually quite basic for a libint2 user.

#include <Eigen/Dense>
#include <libint2.hpp>
#include <iostream>

#define EigenMatrix Eigen::MatrixXd
#define EigenZero Eigen::MatrixXd::Zero

int main(){

    // Initialization of a water molecule
    std::vector<libint2::Atom> libint2atoms(3);
    libint2atoms[0].atomic_number=8;
    libint2atoms[0].x=0.000000;
    libint2atoms[0].y=0.000000;
    libint2atoms[0].z=0.000000;
    libint2atoms[1].atomic_number=1;
    libint2atoms[1].x=0.000000;
    libint2atoms[1].y=-1.430523;
    libint2atoms[1].z=1.109269;
    libint2atoms[2].atomic_number=1;
    libint2atoms[2].x=0.000000;
    libint2atoms[2].y=1.430523;
    libint2atoms[2].z=1.109269;
    libint2::BasisSet obs("sto-3g",libint2atoms);
    obs.set_pure(1);
    int nbasis=0;
    for (const auto& shell:obs) nbasis+=shell.size();

    // Initialization of the pre-computed (energy-weighted) density matrix
    // The factor 2 for double occupation is NOT applied.
    EigenMatrix W(nbasis,nbasis);
    W<<
    -20.1,    -0.263,  2.12e-16,   -0.0855, -2.18e-22,     0.154,     0.154,
   -0.263,     -1.03,  -9.4e-17,    0.0497,  5.92e-22,   -0.0968,   -0.0968,
 2.12e-16,  -9.4e-17,    -0.227,  3.33e-16, -2.23e-16,     0.167,    -0.167,
  -0.0855,    0.0497,  3.33e-16,    -0.295,  1.37e-22,    -0.123,    -0.123,
-2.18e-22,  5.92e-22, -2.23e-16,  1.37e-22,    -0.391,  9.42e-17, -9.42e-17,
    0.154,   -0.0968,     0.167,    -0.123,  9.42e-17,     -0.19,    0.0546,
    0.154,   -0.0968,    -0.167,    -0.123, -9.42e-17,    0.0546,     -0.19;

    EigenMatrix D(nbasis,nbasis);
    D<<
     1.05,    -0.223,  5.43e-17,    0.0543,  1.08e-22,   -0.0141,   -0.0141,
   -0.223,     0.984, -2.84e-16,    -0.309, -7.29e-22,   -0.0173,   -0.0173,
 5.43e-17, -2.84e-16,     0.368,   3.6e-16,   2.7e-16,     -0.27,      0.27,
   0.0543,    -0.309,   3.6e-16,     0.619, -6.09e-22,     0.236,     0.236,
 1.08e-22, -7.29e-22,   2.7e-16, -6.09e-22,         1, -2.06e-17,  2.06e-17,
  -0.0141,   -0.0173,     -0.27,     0.236, -2.06e-17,     0.301,   -0.0955,
  -0.0141,   -0.0173,      0.27,     0.236,  2.06e-17,   -0.0955,     0.301;

    EigenMatrix onebody=EigenZero(3*3,3*3);
    EigenMatrix pulay=EigenZero(3*3,3*3);
    int atomlist[]={0,0};
    libint2::initialize();
    libint2::Engine ovl_engine(libint2::Operator::overlap,obs.max_nprim(),obs.max_l(),2);
    libint2::Engine knt_engine(libint2::Operator::kinetic,obs.max_nprim(),obs.max_l(),2);
    libint2::Engine ncl_engine(libint2::Operator::nuclear,obs.max_nprim(),obs.max_l(),2);
    ncl_engine.set_params(libint2::make_point_charges(libint2atoms));
    const auto & ovl_buf=ovl_engine.results();
    const auto & knt_buf=knt_engine.results();
    const auto & ncl_buf=ncl_engine.results();
    auto shell2bf=obs.shell2bf();
    auto shell2atom=obs.shell2atom(libint2atoms);

    for (short int s1=0;s1!=(short int)obs.size();s1++){ // Looping over unique shell pairs
     atomlist[0]=shell2atom[s1];
     const short int bf1_first=shell2bf[s1];
     const short int n1=obs[s1].size();
     for (short int s2=0;s2<=s1;s2++){
      atomlist[1]=shell2atom[s2];
      const short int bf2_first=shell2bf[s2];
      const short int n2=obs[s2].size();

      // Overlap. Calculating Tr( W * d^2S/dxdy )
      ovl_engine.compute(obs[s1],obs[s2]);
      if (ovl_buf[0]){
       for (short int f1=0,f12=0;f1!=n1;f1++){
        const short int bf1=bf1_first+f1;
        for (short int f2=0;f2!=n2;f2++,f12++){
         const short int bf2=bf2_first+f2;
         if (bf2<=bf1){ // Looping over unique basis function pairs
          const double tmp=((bf1==bf2)?1:2)*W(bf1,bf2); // Since W and d^2S/dxdy are symmetric, the off-diagonal elements should be counted twice.
          int xpert; // The x^th coordinate of hessian
          int ypert; // The y^th coordinate of hessian
          for (int p=0,ptqs=0;p<2;p++) for (int t=0;t<3;t++){
           xpert=3*atomlist[p]+t;
           for (int q=p;q<2;q++) for (int s=((q==p)?t:0);s<3;s++,ptqs++){ // Looping over unique coordinate pairs
            ypert=3*atomlist[q]+s;
            pulay(xpert,ypert)-=tmp*ovl_buf[ptqs][f12];
           }
          }
         }
        }
       }
      }

      // Kinetic. Calculating Tr( D * d^2K/dxdy )
      knt_engine.compute(obs[s1],obs[s2]); // Everything is similar with the overlap integral case.
      if (knt_buf[0]){
       for (short int f1=0,f12=0;f1!=n1;f1++){
        const short int bf1=bf1_first+f1;
        for (short int f2=0;f2!=n2;f2++,f12++){
         const short int bf2=bf2_first+f2;
         if (bf2<=bf1){
          const double tmp=((bf1==bf2)?1:2)*D(bf1,bf2);
          int xpert;
          int ypert;
          for (int p=0,ptqs=0;p<2;p++) for (int t=0;t<3;t++){
           xpert=3*atomlist[p]+t;
           for (int q=p;q<2;q++) for (int s=((q==p)?t:0);s<3;s++,ptqs++){
            ypert=3*atomlist[q]+s;
            onebody(xpert,ypert)+=tmp*knt_buf[ptqs][f12];
           }
          }
         }
        }
       }
      }

      // Nuclear. Calculating Tr( D * d^2V/dxdy )
      ncl_engine.compute(obs[s1],obs[s2]);
      if (ncl_buf[0]){
       for (short int f1=0,f12=0;f1!=n1;f1++){
        const short int bf1=bf1_first+f1;
        for (short int f2=0;f2!=n2;f2++,f12++){
         const short int bf2=bf2_first+f2;
         if (bf2<=bf1){
          const double tmp=((bf1==bf2)?1:2)*D(bf1,bf2);
          int xpert;
          int ypert;
          for (int p=0,ptqs=0;p<2+3;p++) for (int t=0;t<3;t++){
           xpert=3*(p<2?atomlist[p]:p-2)+t; // Different from last two cases here since nuclear attraction integral derivatives are for all atoms instead of only the atoms where the basis functions are located.
           for (int q=p;q<2+3;q++) for (int s=((q==p)?t:0);s<3;s++,ptqs++){
            ypert=3*(q<2?atomlist[q]:q-2)+s;
            onebody(xpert,ypert)+=tmp*ncl_buf[ptqs][f12];
           }
          }
         }
        }
       }
      }
     }
    }
    libint2::finalize();
    EigenMatrix tmp1=pulay+pulay.transpose(); // Only the upper-triangular part is computed. Now the hessian matrices are symmetrized.
    EigenMatrix diagonal1(tmp1.diagonal().asDiagonal()); // Removing the double-counting of the diagonal elements.
    pulay=tmp1-0.5*diagonal1;
    pulay*=2;
    std::cout<<"pulay"<<std::endl;
    std::cout<<pulay<<std::endl;
    std::cout<<std::endl;

    EigenMatrix tmp2=onebody+onebody.transpose();
    EigenMatrix diagonal2(tmp2.diagonal().asDiagonal());
    onebody=tmp2-0.5*diagonal2;
    onebody*=2;
    std::cout<<"onebody"<<std::endl;
    std::cout<<onebody<<std::endl;
    return 0;
}

The results are

pulay
    -782.177 -1.32043e-16  1.29455e-33     0.235105  6.60214e-17 -5.11949e-17     0.235105  6.60214e-17  5.11949e-17
-1.32043e-16     -781.357  1.43952e-18  6.60214e-17      0.15695    0.0651571  6.60214e-17      0.15695   -0.0651571
 1.29455e-33  1.43952e-18      -781.68 -5.11949e-17    0.0651571      0.18105  5.11949e-17   -0.0651571      0.18105
    0.235105  6.60214e-17 -5.11949e-17    -0.413034 -6.60214e-17  5.11949e-17   -0.0146128            0            0
 6.60214e-17      0.15695    0.0651571 -6.60214e-17    -0.375014   -0.0651571            0     0.025523  1.62434e-18
-5.11949e-17    0.0651571      0.18105  5.11949e-17   -0.0651571    -0.358978            0 -1.62434e-18   -0.0146128
    0.235105  6.60214e-17  5.11949e-17   -0.0146128            0            0    -0.413034 -6.60214e-17 -5.11949e-17
 6.60214e-17      0.15695   -0.0651571            0     0.025523 -1.62434e-18 -6.60214e-17    -0.375014    0.0651571
 5.11949e-17   -0.0651571      0.18105            0  1.62434e-18   -0.0146128 -5.11949e-17    0.0651571    -0.358978

onebody
      4716.5  3.54601e-16 -6.27815e-22     -3.86551 -1.77301e-16  1.69374e-16     -3.86551   -1.773e-16 -1.69374e-16
 3.54601e-16      4704.87 -3.33067e-16 -1.77301e-16   -0.0351986     -2.95021   -1.773e-16   -0.0351986      2.95021
-6.27815e-22 -3.33067e-16      4709.75  1.69374e-16     -2.78912     -1.80517 -1.69374e-16      2.78912     -1.80517
    -3.86551 -1.77301e-16  1.69374e-16        5.946   1.7744e-16 -1.69374e-16     0.068216 -1.39241e-19  2.31112e-33
-1.77301e-16   -0.0351986     -2.78912   1.7744e-16       2.6396      2.86966 -1.39241e-19    -0.184465   -0.0805462
 1.69374e-16     -2.95021     -1.80517 -1.69374e-16      2.86966      3.99141 -2.31112e-33    0.0805462       0.1162
    -3.86551   -1.773e-16 -1.69374e-16     0.068216 -1.39241e-19 -2.31112e-33        5.946  1.77439e-16  1.69374e-16
  -1.773e-16   -0.0351986      2.78912 -1.39241e-19    -0.184465    0.0805462  1.77439e-16       2.6396     -2.86966
-1.69374e-16      2.95021     -1.80517  2.31112e-33   -0.0805462       0.1162  1.69374e-16     -2.86966      3.99141

while the true answer should be as given by hartree-fock++.cc

** 1-body hessian = 7.73641539159228 6.13685129415438e-17 -1.65353034161039e-15 -3.86820769579644 -1.15317168057171e-15 8.65226780749975e-16 -3.86820769579644 1.09180316763017e-15 7.88303560860413e-16 0.0654669559264088 -3.5527136788005e-15 -1.04983450809557e-15 -0.0327334779634998 -2.95124586624912 9.88465995154026e-16 -0.0327334779634915 2.95124586624912 3.61879517995501 8.65226780749975e-16 -2.79016029848669 -1.80939758997879 7.88303560860413e-16 2.7901602984867 -1.80939758997879 3.79999991127322 1.10150261463422e-15 -8.47244379101064e-16 0.0682077845232212 -5.16681065386513e-17 -1.79824016489108e-17 0.216967162297963 2.8707030823679 5.16690659374918e-17 -0.184233684334463 -0.0805427838812124 1.69316386122353 -1.79824016489108e-17 0.0805427838812123 0.116233728755255 3.79999991127322 -1.04013506109152e-15 -7.70321159211502e-16 0.216967162297955 -2.87070308236791 1.69316386122354 
** Pulay hessian = -0.470318818337903 -1.09740695120532e-16 1.44532267133828e-16 0.235159409168809 1.48065344340767e-16 -1.14814210208758e-16 0.23515940916881 -3.83246492202348e-17 -2.97180569250698e-17 -0.3129714376921 3.46944695195361e-16 1.48065344340767e-16 0.156485718845837 0.0651113700355565 -3.83246492202348e-17 0.156485718845837 -0.0651113700355569 -0.362973262358146 -1.14814210208758e-16 0.0651113700355565 0.181486631178858 -2.97180569250698e-17 -0.0651113700355569 0.181486631178859 -0.220547444796665 -1.48065344340767e-16 1.14814210208758e-16 -0.0146119643721437 0 0 -0.182007185131406 -0.0651113700355565 0 0.025521466285569 3.47456776836579e-18 -0.166874666806714 0 -3.47456776836579e-18 -0.0146119643721437 -0.220547444796666 3.83246492202349e-17 2.97180569250698e-17 -0.182007185131406 0.0651113700355569 -0.166874666806715 

Initially, I thought the comments beginning from the 1134th line of hartree-fock++.cc might be helpful. It says that the diagonal elements of a second-order derivative (i.e., d2 (s1|s2) / dX2 ) should be treated differently from the off-diagonal ones (i.e., d2 (s1|s2) / dXdY ). However, it appears to me that I don't need to do anything different because libint2 already gives the final derivative (e.g. d2 (s1|s2) / dX2 ) instead of its components (e.g. (d2 s1 / dX2 | s2), (d s1 / dX | d s2 / dX) and (s1| d2 s2 / dX2 ) ).

Thanks for any help!

FreemanTheMaverick commented 7 months ago

Now I shorten the snippet so that it becomes easier to read. It is supposed to compute all second-order overlap derivatives and store them in a 2D array of matrices (i.e., a dimension of 3Natoms×3Natoms×Nbasis×Nbasis.

#include <Eigen/Dense>
#include <libint2.hpp>
#include <iostream>

#define EigenMatrix Eigen::MatrixXd
#define EigenZero Eigen::MatrixXd::Zero

int main(){

    // Initialization of a water molecule
    std::vector<libint2::Atom> libint2atoms(3);
    libint2atoms[0].atomic_number=8;
    libint2atoms[0].x=0.000000;
    libint2atoms[0].y=0.000000;
    libint2atoms[0].z=0.000000;
    libint2atoms[1].atomic_number=1;
    libint2atoms[1].x=0.000000;
    libint2atoms[1].y=-1.430523;
    libint2atoms[1].z=1.109269;
    libint2atoms[2].atomic_number=1;
    libint2atoms[2].x=0.000000;
    libint2atoms[2].y=1.430523;
    libint2atoms[2].z=1.109269;
    libint2::BasisSet obs("sto-3g",libint2atoms);
    int nbasis=0;
    for (const auto& shell:obs) nbasis+=shell.size();

    // Initialization of libint2
    EigenMatrix ovl_hess[3*3][3*3];
    for (int i=0;i<3*3;i++)
        for (int j=0;j<3*3;j++)
            ovl_hess[i][j]=EigenZero(nbasis,nbasis);
    int atomlist[]={0,0};
    libint2::initialize();
    libint2::Engine ovl_engine(libint2::Operator::overlap,obs.max_nprim(),obs.max_l(),2);
    const auto & ovl_buf=ovl_engine.results();
    auto shell2bf=obs.shell2bf();
    auto shell2atom=obs.shell2atom(libint2atoms);

    for (short int s1=0;s1!=(short int)obs.size();s1++){ // Looping over unique shell pairs
     atomlist[0]=shell2atom[s1];
     const short int bf1_first=shell2bf[s1];
     const short int n1=obs[s1].size();
     for (short int s2=0;s2<=s1;s2++){
      atomlist[1]=shell2atom[s2];
      const short int bf2_first=shell2bf[s2];
      const short int n2=obs[s2].size();

      // Overlap.
      ovl_engine.compute(obs[s1],obs[s2]);
      if (ovl_buf[0]){
       for (short int f1=0,f12=0;f1!=n1;f1++){
        const short int bf1=bf1_first+f1;
        for (short int f2=0;f2!=n2;f2++,f12++){
         const short int bf2=bf2_first+f2;
         if (bf2<=bf1){ // Looping over unique basis function pairs
          int xpert; // The x^th coordinate of hessian
          int ypert; // The y^th coordinate of hessian
          for (int p=0,ptqs=0;p<2;p++) for (int t=0;t<3;t++){
           xpert=3*atomlist[p]+t;
           for (int q=p;q<2;q++) for (int s=((q==p)?t:0);s<3;s++,ptqs++){ // Looping over unique coordinate pairs
            ypert=3*atomlist[q]+s;
        ovl_hess[xpert][ypert](bf1,bf2)=ovl_hess[xpert][ypert](bf2,bf1)=ovl_hess[ypert][xpert](bf1,bf2)=ovl_hess[ypert][xpert](bf2,bf1)=ovl_buf[ptqs][f12];
           }
          }
         }
        }
       }
      }
     }
    }
    libint2::finalize(); // Printing the overlap derivatives
    std::cout.setf(std::ios::fixed);
    std::cout<<std::setprecision(6);
    for (int xpert=0;xpert<3*3;xpert++)
        for (int ypert=xpert;ypert<3*3;ypert++){
            std::cout<<xpert<<" "<<ypert<<std::endl;
            std::cout<<ovl_hess[xpert][ypert]<<std::endl;
        }
    return 0;
}

However, the problem still exists. Several diagonal or near-diagonal elements are wrong in the matrices whose two perturbations are about the same atom (i.e., d^2/dXdY where X and Y are coordinates of the same atom). Some incorrect results are presented as follows along with the corresponding correct ones given by hartree-fock++.cc.

X=0, Y=0, x coordinate of the first atom. A few diagonal elements (blocks) are wrong.

My code
-19.335467   0.112007   0.000000   0.000000   0.000000  -0.037385  -0.037385
  0.112007  -0.538752   0.000000   0.000000   0.000000  -0.191744  -0.191744
  0.000000   0.000000  -3.034477   0.000000   0.000000   0.000000   0.000000
  0.000000   0.000000   0.000000  -1.011492   0.000000   0.175216  -0.175216
  0.000000   0.000000   0.000000   0.000000  -1.011492  -0.135867  -0.135867
 -0.037385  -0.191744   0.000000   0.175216  -0.135867   0.000000   0.000000
 -0.037385  -0.191744   0.000000  -0.175216  -0.135867   0.000000   0.000000
hartree-fock++.cc
-0.000000  0.000000  0.000000  0.000000  0.000000 -0.037385 -0.037385
 0.000000  0.000000  0.000000  0.000000  0.000000 -0.191744 -0.191744
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.175216 -0.175216
 0.000000  0.000000  0.000000  0.000000 -0.000000 -0.135868 -0.135868
-0.037385 -0.191744  0.000000  0.175216 -0.135868  0.000000  0.000000
-0.037385 -0.191744  0.000000 -0.175216 -0.135868  0.000000  0.000000

X=0, Y=1, x and y of the first atom. A pair of symmetric off-diagonal elements in the middle are wrong.

My code
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000 -1.011492  0.000000  0.175216 -0.175216
 0.000000  0.000000 -1.011492  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.175216  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000 -0.175216  0.000000  0.000000  0.000000  0.000000
hartree-fock++.cc
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.175216 -0.175216
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.175216  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000 -0.175216  0.000000  0.000000  0.000000  0.000000

X=8, Y=8, z of the last atom. The last diagonal element is wrong.

My code
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.006654
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.072069
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.014628
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.283078
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.066909
 0.006654 -0.072069  0.000000 -0.014628 -0.283078 -0.066909 -0.506688
hartree-fock++.cc
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.006654
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.072069
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.014628
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.283078
 0.000000  0.000000  0.000000  0.000000  0.000000  0.000000 -0.066909
 0.006654 -0.072069  0.000000 -0.014628 -0.283078 -0.066909  0.000000

I must have mistaken the meaning of what is in the integral engine buffer, especially of the diagonal elements of hessian. I would appreciate it if anyone explains this to me.

FreemanTheMaverick commented 7 months ago

I have figured out the case of overlap and kinetic integrals. The integral derivatives are nonzero only if the two involved shells belong to different centers, so there must be a line of code like if (atomlist[0]==atomlist[1]) continue; for skipping shell pairs concerning only one single center.

Based on common sense, this line should not have been necessary, since the zero elements make no difference at all theoretically. Ideally, as long as the integration engine leaves the zeros in engine.results() in the case of atomlist[0]==atomlist[1], the final integral derivatives should be correct even without the code if (atomlist[0]==atomlist[1]) continue;. However, if my guess is correct, when atomlist[0]==atomlist[1], somehow the engine skips actual computation and leaves the contents in engine.results() unchanged. In this way, the integrals of last shell pair still exist and the user may mistake them for the ones of the current shell pair. Therefore, the code if (atomlist[0]==atomlist[1]) continue; is necessary, telling the program to ignore the cases of atomlist[0]==atomlist[1] completely.

evaleev commented 7 months ago

@FreemanTheMaverick Engine computes integrals over differentiated Gaussians, NOT derivatives of integrals over Gaussians (e.g. (d s1 / d X | s2), not d (s1| s2) / d X), because the former is sometimes needed and/or more useful. So I think your interpretation of what Engine computes is the culprit here. E.g. when both s1 and s2 are on the same atom and X refers to one of the coordinates of that atom then due to translational invariance d (s1 | s2) / d X is zero, but (d s1 /d X | s2) and (s1 | d s2 / d X) are not in general zero (and have opposite sign so (d s1 /d X | s2) + (s1 | d s2 / d X) = d (s1 | s2) / d X is zero).

There is no code in Engine that skips derivative integrals, e.g. Engine will always return 6 first derivatives of overlap shell pair (unless primitive screening causes them to be hard zero, then there may not be any).

FreemanTheMaverick commented 7 months ago

@FreemanTheMaverick Engine computes integrals over differentiated Gaussians, NOT derivatives of integrals over Gaussians (e.g. (d s1 / d X | s2), not d (s1| s2) / d X), because the former is sometimes needed and/or more useful. So I think your interpretation of what Engine computes is the culprit here. E.g. when both s1 and s2 are on the same atom and X refers to one of the coordinates of that atom then due to translational invariance d (s1 | s2) / d X is zero, but (d s1 /d X | s2) and (s1 | d s2 / d X) are not in general zero (and have opposite sign so (d s1 /d X | s2) + (s1 | d s2 / d X) = d (s1 | s2) / d X is zero).

There is no code in Engine that skips derivative integrals, e.g. Engine will always return 6 first derivatives of overlap shell pair (unless primitive screening causes them to be hard zero, then there may not be any).

Thank you! This is exactly the problem here. In this way, not only the diagonal elements of derivatives (such as ( d2 s1 / d x1 2 | s2 )) need to be counted twice, but some of the off-diagonal elements (such as ( d s1 / d x1 | d s2 / d x2 ) for x1==x2 ) need to, too. The problem is solved by this code snippet:

......
ypert=3*atomlist[q]+s;
double scale=1;
if (xpert==ypert && p!=q && (p<2 || q<2)) scale=2; // The condition (p<2 || q<2) is for nuclear integrals
ovl_hess[xpert][ypert](bf1,bf2)=ovl_hess[xpert][ypert](bf2,bf1)=ovl_hess[ypert][xpert](bf1,bf2)=ovl_hess[ypert][xpert](bf2,bf1)=ovl_hess[ypert][xpert](bf2,bf1)+scale*ovl_buf[ptqs][f12];
......

For overlap and kinetic derivatives, the code if (atomlist[0]==atomlist[1]) continue; also works.

Now my work is in progress again. This could hardly have happened without your help. (:D)

evaleev commented 7 months ago

@FreemanTheMaverick glad I could help, and thanks for using libint and asking questions