LCOV - code coverage report
Current view: top level - symfunc - SphericalHarmonic.h (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 141 156 90.4 %
Date: 2026-11-05 10:19:04 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-2017 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #ifndef __PLUMED_symfunc_SphericalHarmonic_h
      23             : #define __PLUMED_symfunc_SphericalHarmonic_h
      24             : #include "function/FunctionSetup.h"
      25             : #include "function/FunctionOfMatrix.h"
      26             : 
      27             : #include <complex>
      28             : 
      29             : namespace PLMD {
      30             : namespace symfunc {
      31             : 
      32             : class SphericalHarmonic {
      33             :   int tmom=0;
      34             :   std::vector<double> coeff_poly_v{};
      35             :   std::vector<double> normaliz_v{};
      36             :   double *coeff_poly=nullptr;
      37             :   double *normaliz=nullptr;
      38             : public:
      39             :   static void registerKeywords( Keywords& keys );
      40             :   static unsigned factorial( unsigned n );
      41             :   static void read( SphericalHarmonic& func,
      42             :                     ActionWithArguments* action,
      43             :                     function::FunctionOptions& options );
      44             :   static void calc( const SphericalHarmonic& func,
      45             :                     bool noderiv,
      46             :                     View<const double> args,
      47             :                     function::FunctionOutput funcout );
      48             :   double deriv_poly(                            unsigned m,
      49             :       double val,
      50             :       double& df ) const;
      51             :   static inline void addVectorDerivatives( unsigned ival,
      52             :       const Vector& der,
      53             :       View2D<double> derivatives );
      54             :   void update() {
      55          44 :     coeff_poly = coeff_poly_v.data();
      56          44 :     normaliz = normaliz_v.data();
      57             :   }
      58          90 :   SphericalHarmonic() = default;
      59           1 :   ~SphericalHarmonic() = default;
      60             :   SphericalHarmonic(const SphericalHarmonic&x):
      61             :     tmom(x.tmom),
      62             :     coeff_poly_v(x.coeff_poly_v),
      63             :     normaliz_v(x.normaliz_v) {
      64             :     update();
      65             :   }
      66             :   SphericalHarmonic(SphericalHarmonic&&x):
      67             :     tmom(x.tmom),
      68             :     coeff_poly_v(std::move(x.coeff_poly_v)),
      69             :     normaliz_v(std::move(x.normaliz_v)) {
      70             :     update();
      71             :   }
      72             :   SphericalHarmonic &operator=(const SphericalHarmonic&x) {
      73             :     if (this!=&x) {
      74             :       tmom=x.tmom;
      75             :       coeff_poly_v=x.coeff_poly_v;
      76             :       normaliz_v=x.normaliz_v;
      77             :       update();
      78             :     }
      79             :     return *this;
      80             :   }
      81             :   SphericalHarmonic &operator=(SphericalHarmonic&&x) {
      82             :     if (this!=&x) {
      83             :       tmom=x.tmom;
      84             :       coeff_poly_v=std::move(x.coeff_poly_v);
      85             :       normaliz_v=std::move(x.normaliz_v);
      86             :       update();
      87             :     }
      88             :     return *this;
      89             :   }
      90             : #ifdef __PLUMED_HAS_OPENACC
      91             :   void toACCDevice() const {
      92             :     const auto num=tmom+1;
      93             : #pragma acc enter data copyin(this[0:1], \
      94             :   tmom, coeff_poly[0:num], normaliz[0:num])
      95             :   }
      96             :   void removeFromACCDevice() const {
      97             :     const auto num=tmom+1;
      98             : #pragma acc exit data delete(normaliz[0:num], \
      99             :   coeff_poly[0:num], tmom, this[0:1])
     100             :   }
     101             : #endif // __PLUMED_HAS_OPENACC
     102             : };
     103             : 
     104         274 : void SphericalHarmonic::registerKeywords( Keywords& keys ) {
     105         274 :   keys.add("compulsory","L","the value of the angular momentum");
     106         548 :   keys.addOutputComponent("rm","default",
     107             :                           "matrix","the real parts of the spherical harmonic values with the m value given");
     108         548 :   keys.addOutputComponent("im","default",
     109             :                           "matrix","the real parts of the spherical harmonic values with the m value given");
     110         274 :   keys.add("hidden","MASKED_INPUT_ALLOWED",
     111             :            "turns on that you are allowed to use masked inputs");
     112         274 : }
     113             : 
     114        2028 : unsigned SphericalHarmonic::factorial( unsigned n ) {
     115        2028 :   return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
     116             : }
     117             : 
     118          45 : void SphericalHarmonic::read( SphericalHarmonic& func,
     119             :                               ActionWithArguments* action,
     120             :                               function::FunctionOptions& options ) {
     121          45 :   action->parse("L",func.tmom);
     122          45 :   if( action->getNumberOfArguments()<3 ) {
     123           1 :     action->error("the x, y and z components of the vectors (and optionally a weight) should be given with ARG");
     124             :   }
     125          44 :   action->log.printf("  calculating %dth order spherical harmonic with %s, %s and %s as input \n",
     126             :                      func.tmom,
     127             :                      action->getPntrToArgument(0)->getName().c_str(),
     128             :                      action->getPntrToArgument(1)->getName().c_str(),
     129             :                      action->getPntrToArgument(2)->getName().c_str() );
     130          44 :   if( action->getNumberOfArguments()==4 ) {
     131          44 :     action->log.printf("  multiplying cylindrical harmonic by weight from %s \n",
     132             :                        action->getPntrToArgument(3)->getName().c_str() );
     133             :   }
     134             : 
     135          44 :   func.normaliz_v.resize( func.tmom+1 );
     136          44 :   func.coeff_poly_v.resize( func.tmom+1 );
     137             :   func.update();
     138             : 
     139         248 :   for(decltype(func.tmom) i=0; i<=func.tmom; ++i) {
     140         204 :     func.normaliz[i] = ( i%2==0 ? 1.0 : -1.0 )
     141         612 :                        * sqrt( (2*func.tmom+1)
     142         204 :                                * factorial(func.tmom-i)
     143         204 :                                /(4*PLMD::pi*factorial(func.tmom+i)) );
     144             :   }
     145             : 
     146          44 :   switch (func.tmom) {
     147          19 :   case 1:
     148             :     // Legendre polynomial coefficients of order one
     149          19 :     func.coeff_poly[0]=0;
     150          19 :     func.coeff_poly[1]=1.0;
     151          19 :     break;
     152           0 :   case 2:
     153             :     // Legendre polynomial coefficients of order two
     154           0 :     func.coeff_poly[0]=-0.5;
     155           0 :     func.coeff_poly[1]=0.0;
     156           0 :     func.coeff_poly[2]=1.5;
     157           0 :     break;
     158           1 :   case 3:
     159             :     // Legendre polynomial coefficients of order three
     160           1 :     func.coeff_poly[0]=0.0;
     161           1 :     func.coeff_poly[1]=-1.5;
     162           1 :     func.coeff_poly[2]=0.0;
     163           1 :     func.coeff_poly[3]=2.5;
     164           1 :     break;
     165           3 :   case 4:
     166             :     // Legendre polynomial coefficients of order four
     167           3 :     func.coeff_poly[0]=0.375;
     168           3 :     func.coeff_poly[1]=0.0;
     169           3 :     func.coeff_poly[2]=-3.75;
     170           3 :     func.coeff_poly[3]=0.0;
     171           3 :     func.coeff_poly[4]=4.375;
     172           3 :     break;
     173           0 :   case 5:
     174             :     // Legendre polynomial coefficients of order five
     175           0 :     func.coeff_poly[0]=0.0;
     176           0 :     func.coeff_poly[1]=1.875;
     177           0 :     func.coeff_poly[2]=0.0;
     178           0 :     func.coeff_poly[3]=-8.75;
     179           0 :     func.coeff_poly[4]=0.0;
     180           0 :     func.coeff_poly[5]=7.875;
     181           0 :     break;
     182          21 :   case 6:
     183             :     // Legendre polynomial coefficients of order six
     184          21 :     func.coeff_poly[0]=-0.3125;
     185          21 :     func.coeff_poly[1]=0.0;
     186          21 :     func.coeff_poly[2]=6.5625;
     187          21 :     func.coeff_poly[3]=0.0;
     188          21 :     func.coeff_poly[4]=-19.6875;
     189          21 :     func.coeff_poly[5]=0.0;
     190          21 :     func.coeff_poly[6]=14.4375;
     191          21 :     break;
     192           0 :   default:
     193           0 :     action->error("Insert Legendre polynomial coefficients into SphericalHarmonics code");
     194             :   }
     195             : 
     196             :   std::string num;
     197         408 :   for(int i=-func.tmom; i<=func.tmom; ++i) {
     198         364 :     Tools::convert(abs(i),num);
     199         364 :     if( i<0 ) {
     200         320 :       options.multipleValuesForEachRegisteredComponent.push_back( "-n" + num );
     201         204 :     } else if( i>0 ) {
     202         320 :       options.multipleValuesForEachRegisteredComponent.push_back( "-p" + num );
     203             :     } else {
     204          88 :       options.multipleValuesForEachRegisteredComponent.push_back( "-0");
     205             :     }
     206             :   }
     207          88 :   options.derivativeZeroIfValueIsZero = (action->getNumberOfArguments()==4
     208          44 :                                          && action->getPntrToArgument(3)
     209             :                                          ->isDerivativeZeroWhenValueIsZero() );
     210          44 : }
     211             : 
     212     2339822 : void SphericalHarmonic::calc( const SphericalHarmonic& func,
     213             :                               bool noderiv,
     214             :                               const View<const double> args,
     215             :                               function::FunctionOutput funcout ) {
     216             :   double weight=1;
     217     2339822 :   if( args.size()==4 ) {
     218     2339822 :     weight = args[3];
     219             :   }
     220     2339822 :   if( weight<epsilon ) {
     221       10010 :     if( !noderiv ) {
     222        5000 :       const unsigned imbase = 2*func.tmom+1;
     223       69760 :       for(int m=-func.tmom; m<=func.tmom; ++m) {
     224       64760 :         auto pos = func.tmom+m;
     225       64760 :         funcout.derivs[pos][0] = 0.0;
     226       64760 :         funcout.derivs[pos][1] = 0.0;
     227       64760 :         funcout.derivs[pos][2] = 0.0;
     228       64760 :         funcout.derivs[pos][3] = 0.0;
     229       64760 :         funcout.derivs[pos + imbase][0] = 0.0;
     230       64760 :         funcout.derivs[pos + imbase][1] = 0.0;
     231       64760 :         funcout.derivs[pos + imbase][2] = 0.0;
     232       64760 :         if( args.size()==4 ) {
     233       64760 :           funcout.derivs[pos + imbase][3] = 0.0;
     234             :         }
     235             :       }
     236             :     }
     237       10010 :     return;
     238             :   }
     239             : 
     240     2329812 :   const double dlen2 = args[0]*args[0] + args[1]*args[1] + args[2]*args[2];
     241     2329812 :   const double dlen  = sqrt( dlen2 );
     242     2329812 :   const double dleninv = 1.0/dlen;
     243     2329812 :   const double dlen3 = dlen2*dlen;
     244     2329812 :   const double dlen3inv = 1.0/dlen3;
     245             :   double dpoly_ass;
     246     2329812 :   double poly_ass=func.deriv_poly( 0, args[2]*dleninv, dpoly_ass );
     247             :   // Accumulate for m=0
     248     2329812 :   funcout.values[func.tmom] = weight*poly_ass;
     249             :   // Derivatives of z/r wrt x, y, z
     250             :   Vector dz;
     251     2329812 :   if( !noderiv ) {
     252      453494 :     dz[0] = -( args[2] * dlen3inv )*args[0];
     253      453494 :     dz[1] = -( args[2] * dlen3inv )*args[1];
     254      453494 :     dz[2] = -( args[2] * dlen3inv )*args[2] + (1.0 * dleninv);
     255      453494 :     funcout.derivs[func.tmom] = weight*dpoly_ass*dz ;
     256      453494 :     if( args.size()==4 ) {
     257      453494 :       funcout.derivs[func.tmom][3] = poly_ass;
     258             :     }
     259             :   }
     260             : 
     261             :   // The complex number of which we have to take powers
     262     2329812 :   std::complex<double> com1( args[0]*dleninv, args[1]*dleninv );
     263             :   std::complex<double> powered(1.0, 0.0);
     264             :   constexpr std::complex<double> ii(0.0, 1.0);
     265             :   std::complex<double> dp_x;
     266             :   std::complex<double> dp_y;
     267             :   std::complex<double> dp_z;
     268             :   Vector myrealvec;
     269             :   Vector myimagvec;
     270             :   Vector real_dz;
     271             :   Vector imag_dz;
     272             :   // Do stuff for all other m values
     273     2329812 :   const unsigned imbase = 3*func.tmom+1;
     274             :   // The imaginary part for m=0 is always zero, but it is a component like the
     275             :   // others and its derivatives are read when the forces are applied: set them
     276             :   // here rather than relying on the buffer being zero (on the GPU it is not)
     277     2329812 :   funcout.values[imbase] = 0.0;
     278     2329812 :   if( !noderiv ) {
     279      453494 :     funcout.derivs[imbase][0] = 0.0;
     280      453494 :     funcout.derivs[imbase][1] = 0.0;
     281      453494 :     funcout.derivs[imbase][2] = 0.0;
     282      453494 :     if( args.size()==4 ) {
     283      453494 :       funcout.derivs[imbase][3] = 0.0;
     284             :     }
     285             :   }
     286             :   double pref = -1.0;
     287    13869208 :   for(decltype(func.tmom) m=1; m<=func.tmom; ++m) {
     288             :     // Calculate Legendre Polynomial
     289    11539396 :     poly_ass=func.deriv_poly( m, args[2]*dleninv, dpoly_ass );
     290             :     // Real and imaginary parts of z
     291             :     double real_z = real(com1*powered);
     292             :     double imag_z = imag(com1*powered);
     293             : 
     294             :     // Calculate steinhardt parameter
     295    11539396 :     double tq6 =poly_ass*real_z;  // Real part of steinhardt parameter
     296    11539396 :     double itq6=poly_ass*imag_z;  // Imaginary part of steinhardt parameter
     297             : 
     298             :     // Real part
     299    11539396 :     funcout.values[func.tmom + m] = weight* tq6;
     300             :     // Imaginary part
     301    11539396 :     funcout.values[imbase    + m] = weight*itq6;
     302             :     // Store -m part of vector
     303             :     // -m part of vector is just +m part multiplied by (-1.0)**m and multiplied by complex
     304             :     // conjugate of Legendre polynomial
     305             :     // Real part
     306    11539396 :     funcout.values[func.tmom - m] =  pref*funcout.values[func.tmom+m];
     307             :     // Imaginary part
     308    11539396 :     funcout.values[imbase    - m] = -pref*funcout.values[imbase + m];
     309    11539396 :     if( !noderiv ) {
     310             :       // Derivatives wrt ( x/r + iy )^m
     311     1501226 :       double md=static_cast<double>(m);
     312     1501226 :       dp_x = md*powered*( (1.0*dleninv)-(args[0]*args[0])*dlen3inv-ii*(args[0]*args[1])*dlen3inv );
     313     1501226 :       dp_y = md*powered*( ii*(1.0*dleninv)-(args[0]*args[1])*dlen3inv-ii*(args[1]*args[1])*dlen3inv );
     314     1501226 :       dp_z = md*powered*( -(args[0]*args[2])*dlen3inv-ii*(args[1]*args[2])*dlen3inv );
     315             :       // Derivatives of real and imaginary parts of above
     316             :       real_dz[0] = real( dp_x );
     317             :       real_dz[1] = real( dp_y );
     318             :       real_dz[2] = real( dp_z );
     319             :       imag_dz[0] = imag( dp_x );
     320             :       imag_dz[1] = imag( dp_y );
     321             :       imag_dz[2] = imag( dp_z );
     322             :       // Complete derivative of steinhardt parameter
     323     1501226 :       myrealvec = weight*(dpoly_ass*real_z*dz + poly_ass*real_dz);
     324     1501226 :       myimagvec = weight*(dpoly_ass*imag_z*dz + poly_ass*imag_dz);
     325     1501226 :       funcout.derivs[func.tmom+m] = myrealvec;
     326             :       funcout.derivs[imbase   +m] = myimagvec;
     327             : 
     328     1501226 :       funcout.derivs[func.tmom-m] = pref*myrealvec;
     329             :       funcout.derivs[imbase   -m] = -pref*myimagvec;
     330     1501226 :       if( args.size()==4 ) {
     331     1501226 :         funcout.derivs[func.tmom + m][3]=  tq6;
     332     1501226 :         funcout.derivs[imbase    + m][3]= itq6;
     333     1501226 :         funcout.derivs[func.tmom - m][3]= pref* tq6;
     334     1501226 :         funcout.derivs[imbase    - m][3]=-pref*itq6;
     335             :       }
     336             :     }
     337             :     // Calculate next power of complex number
     338             :     powered *= com1;
     339             :     //hopefully the compiler will optimize with bitflipping the sign here
     340             :     pref = -pref;
     341             :   }
     342             : }
     343             : 
     344    13869208 : double SphericalHarmonic::deriv_poly( const unsigned m,
     345             :                                       const double val,
     346             :                                       double& df ) const {
     347             :   double fact=1.0;
     348    39134296 :   for(unsigned j=2; j<=m; ++j) {
     349    25265088 :     fact *= j;
     350             :   }
     351    13869208 :   double res=coeff_poly[m]*fact;
     352             : 
     353             :   double pow=1.0;
     354             :   double xi=val;
     355             :   double dxi=1.0;
     356    13869208 :   df=0.0;
     357    50673692 :   for(int i=m+1; i<=tmom; ++i) {
     358             :     fact = 1.0;
     359    93375964 :     for(int j=i-m+1; j<=i; ++j) {
     360    56571480 :       fact *= j;
     361             :     }
     362    36804484 :     res += coeff_poly[i]*fact*xi;
     363    36804484 :     df  += pow*coeff_poly[i]*fact*dxi;
     364    36804484 :     xi  *= val;
     365    36804484 :     dxi *= val;
     366    36804484 :     pow += 1.0;
     367             :   }
     368    13869208 :   df *= normaliz[m];
     369    13869208 :   return normaliz[m]*res;
     370             : }
     371             : 
     372             : }
     373             : }
     374             : 
     375             : #endif //__PLUMED_symfunc_SphericalHarmonic_h

Generated by: LCOV version 1.16