LCOV - code coverage report
Current view: top level - symfunc - SphericalHarmonic.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 132 147 89.8 %
Date: 2025-12-04 11:19:34 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             : #ifdef __PLUMED_HAS_OPENACC
      23             : #define __PLUMED_USE_OPENACC 1
      24             : #endif //__PLUMED_HAS_OPENACC
      25             : #include "function/FunctionSetup.h"
      26             : #include "function/FunctionShortcut.h"
      27             : #include "function/FunctionOfMatrix.h"
      28             : #include "core/ActionRegister.h"
      29             : 
      30             : #include <complex>
      31             : 
      32             : namespace PLMD {
      33             : namespace symfunc {
      34             : 
      35             : //+PLUMEDOC MCOLVAR SPHERICAL_HARMONIC
      36             : /*
      37             : Calculate the values of all the spherical harmonic funtions for a particular value of l.
      38             : 
      39             : This action allows you to the all the [spherical harmonic](https://en.wikipedia.org/wiki/Spherical_harmonics) functions for a particular input
      40             : $L$ value.  As discussed in more detail in the article provided above the spherical harmonics this action calculates have the following general form:
      41             : 
      42             : $$
      43             : Y_l^m(\theta,\phi) = wNe^{im\phi} P_l^m(\cos\theta)
      44             : $$
      45             : 
      46             : where $N$ is a normalisation constant, $P_l^m$ is tn associated Legendre Polynomial and $w$ is an optional weight that can be passed in an input argument or simply set equal to one.
      47             : $e^{i\phi}$ and $\cos(\theta) are computed from the other input arguments, $x, y$ and $z$ as follows:
      48             : 
      49             : $$
      50             : e^{i\phi} = \frac{x}{r} + i\frac{y}{r} \qquad \textrm{and} \qquad \cos(\theta) = \frac{z}{r} \qquad \textrm{where} \qquad r = \sqrt{x^2 + y^2 + z^2}
      51             : $$
      52             : 
      53             : At present, the arguments for this action must be matrices. However, it would be easy to add functionality that would allow you to compute this function for scalar or vector input.
      54             : The arguments must all have the same shape as the two output components will also be matrices that are
      55             : calculated by applying the function above to each of the elements of the input matrix in turn.  The number of components output will be equal to $2(2L+1)$ and will contain
      56             : the real and imaginary parts of the $Y_l^m$ functions with the the $2l+1$ possible $m$ values.
      57             : 
      58             : The following intput provides an example that demonstrates how this function is used:
      59             : 
      60             : ```plumed
      61             : d: DISTANCE_MATRIX GROUP=1-10 COMPONENTS
      62             : c: SPHERICAL_HARMONIC L=1 ARG=d.x,d.y,d.z
      63             : PRINT ARG=c.rm-n1 FILE=real_part_m-1
      64             : PRINT ARG=c.im-n1 FILE=imaginary_part_m-1
      65             : PRINT ARG=c.rm-0 FILE=real_part_m0
      66             : PRINT ARG=c.im-0 FILE=imaginary_part_m0
      67             : PRINT ARG=c.rm-p1 FILE=real_part_m+1
      68             : PRINT ARG=c.im-p1 FILE=imaginary_part_m+1
      69             : ```
      70             : 
      71             : The DISTANCE_MATRIX command in the above input computes 3 $10\times10$ matrices.  These 3 $10\times10$ matrices are used in the input to the sphierical harmonic command,
      72             : which in turn outputs 6 $10\times10$ matrices that contain the real and imaginary parts when the three spherical harmonic functions with $l=1$ are applied element-wise to the above input. These six $10\times10$
      73             : matrices are then output to six separate files.
      74             : 
      75             : In the above example the weights for every distance is set equal to one.  The following example shows how an argument can be used to set the $w$ values to use when computing the function
      76             : above.
      77             : 
      78             : ```plumed
      79             : s: CONTACT_MATRIX GROUP=1-10 SWITCH={RATIONAL R_0=1.0}
      80             : sc: CONTACT_MATRIX GROUP=1-10 SWITCH={RATIONAL R_0=1.0} COMPONENTS
      81             : c: SPHERICAL_HARMONIC L=1 ARG=sc.x,sc.y,sc.z,s
      82             : PRINT ARG=c.rm-n1 FILE=real_part_m-1
      83             : PRINT ARG=c.im-n1 FILE=imaginary_part_m-1
      84             : PRINT ARG=c.rm-0 FILE=real_part_m0
      85             : PRINT ARG=c.im-0 FILE=imaginary_part_m0
      86             : PRINT ARG=c.rm-p1 FILE=real_part_m+1
      87             : PRINT ARG=c.im-p1 FILE=imaginary_part_m+1
      88             : ```
      89             : 
      90             : This function is used in the calculation of the Steinhardt order parameters, which are described in detail [here](https://www.plumed-tutorials.org/lessons/23/001/data/Steinhardt.html).
      91             : 
      92             : */
      93             : //+ENDPLUMEDOC
      94             : 
      95             : class SphericalHarmonic {
      96             :   int tmom=0;
      97             :   std::vector<double> coeff_poly_v{};
      98             :   std::vector<double> normaliz_v{};
      99             :   double *coeff_poly=nullptr;
     100             :   double *normaliz=nullptr;
     101             : public:
     102             :   static void registerKeywords( Keywords& keys );
     103             :   static unsigned factorial( unsigned n );
     104             :   static void read( SphericalHarmonic& func,
     105             :                     ActionWithArguments* action,
     106             :                     function::FunctionOptions& options );
     107             :   static void calc( const SphericalHarmonic& func,
     108             :                     bool noderiv,
     109             :                     View<const double> args,
     110             :                     function::FunctionOutput funcout );
     111             :   double deriv_poly(                            unsigned m,
     112             :       double val,
     113             :       double& df ) const;
     114             :   static inline void addVectorDerivatives( unsigned ival,
     115             :       const Vector& der,
     116             :       View2D<double> derivatives );
     117             :   void update() {
     118          42 :     coeff_poly = coeff_poly_v.data();
     119          42 :     normaliz = normaliz_v.data();
     120             :   }
     121         254 :   SphericalHarmonic() = default;
     122         170 :   ~SphericalHarmonic() = default;
     123             :   SphericalHarmonic(const SphericalHarmonic&x):
     124             :     tmom(x.tmom),
     125             :     coeff_poly_v(x.coeff_poly_v),
     126             :     normaliz_v(x.normaliz_v) {
     127             :     update();
     128             :   }
     129             :   SphericalHarmonic(SphericalHarmonic&&x):
     130             :     tmom(x.tmom),
     131             :     coeff_poly_v(std::move(x.coeff_poly_v)),
     132             :     normaliz_v(std::move(x.normaliz_v)) {
     133             :     update();
     134             :   }
     135             :   SphericalHarmonic &operator=(const SphericalHarmonic&x) {
     136             :     if (this!=&x) {
     137             :       tmom=x.tmom;
     138             :       coeff_poly_v=x.coeff_poly_v;
     139             :       normaliz_v=x.normaliz_v;
     140             :       update();
     141             :     }
     142             :     return *this;
     143             :   }
     144             :   SphericalHarmonic &operator=(SphericalHarmonic&&x) {
     145             :     if (this!=&x) {
     146             :       tmom=x.tmom;
     147             :       coeff_poly_v=std::move(x.coeff_poly_v);
     148             :       normaliz_v=std::move(x.normaliz_v);
     149             :       update();
     150             :     }
     151             :     return *this;
     152             :   }
     153             : #ifdef __PLUMED_USE_OPENACC
     154             :   void toACCDevice() const {
     155             :     const auto num=tmom+1;
     156             : #pragma acc enter data copyin(this[0:1], \
     157             :   tmom, coeff_poly[0:num], normaliz[0:num])
     158             :   }
     159             :   void removeFromACCDevice() const {
     160             :     const auto num=tmom+1;
     161             : #pragma acc exit data delete(normaliz[0:num], \
     162             :   coeff_poly[0:num], tmom, this[0:1])
     163             :   }
     164             : #endif // __PLUMED_USE_OPENACC
     165             : };
     166             : 
     167             : typedef function::FunctionShortcut<SphericalHarmonic> SpHarmShortcut;
     168             : PLUMED_REGISTER_ACTION(SpHarmShortcut,"SPHERICAL_HARMONIC")
     169             : typedef function::FunctionOfMatrix<SphericalHarmonic> MatrixSpHarm;
     170             : PLUMED_REGISTER_ACTION(MatrixSpHarm,"SPHERICAL_HARMONIC_MATRIX")
     171             : 
     172         257 : void SphericalHarmonic::registerKeywords( Keywords& keys ) {
     173         257 :   keys.add("compulsory","L","the value of the angular momentum");
     174         514 :   keys.addOutputComponent("rm","default",
     175             :                           "matrix","the real parts of the spherical harmonic values with the m value given");
     176         514 :   keys.addOutputComponent("im","default",
     177             :                           "matrix","the real parts of the spherical harmonic values with the m value given");
     178         257 :   keys.add("hidden","MASKED_INPUT_ALLOWED",
     179             :            "turns on that you are allowed to use masked inputs");
     180         257 : }
     181             : 
     182        1858 : unsigned SphericalHarmonic::factorial( unsigned n ) {
     183        1858 :   return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
     184             : }
     185             : 
     186          42 : void SphericalHarmonic::read( SphericalHarmonic& func,
     187             :                               ActionWithArguments* action,
     188             :                               function::FunctionOptions& options ) {
     189          42 :   action->parse("L",func.tmom);
     190          42 :   action->log.printf("  calculating %dth order spherical harmonic with %s, %s and %s as input \n",
     191             :                      func.tmom,
     192             :                      action->getPntrToArgument(0)->getName().c_str(),
     193             :                      action->getPntrToArgument(1)->getName().c_str(),
     194             :                      action->getPntrToArgument(2)->getName().c_str() );
     195          42 :   if( action->getNumberOfArguments()==4 ) {
     196          42 :     action->log.printf("  multiplying cylindrical harmonic by weight from %s \n",
     197             :                        action->getPntrToArgument(3)->getName().c_str() );
     198             :   }
     199             : 
     200          42 :   func.normaliz_v.resize( func.tmom+1 );
     201          42 :   func.coeff_poly_v.resize( func.tmom+1 );
     202             :   func.update();
     203             : 
     204         232 :   for(int i=0; i<=func.tmom; ++i) {
     205         190 :     func.normaliz[i] = ( i%2==0 ? 1.0 : -1.0 )
     206         570 :                        * sqrt( (2*func.tmom+1)
     207         190 :                                * factorial(func.tmom-i)
     208         190 :                                /(4*PLMD::pi*factorial(func.tmom+i)) );
     209             :   }
     210             : 
     211          42 :   switch (func.tmom) {
     212          19 :   case 1:
     213             :     // Legendre polynomial coefficients of order one
     214          19 :     func.coeff_poly[0]=0;
     215          19 :     func.coeff_poly[1]=1.0;
     216          19 :     break;
     217           0 :   case 2:
     218             :     // Legendre polynomial coefficients of order two
     219           0 :     func.coeff_poly[0]=-0.5;
     220           0 :     func.coeff_poly[1]=0.0;
     221           0 :     func.coeff_poly[2]=1.5;
     222           0 :     break;
     223           1 :   case 3:
     224             :     // Legendre polynomial coefficients of order three
     225           1 :     func.coeff_poly[0]=0.0;
     226           1 :     func.coeff_poly[1]=-1.5;
     227           1 :     func.coeff_poly[2]=0.0;
     228           1 :     func.coeff_poly[3]=2.5;
     229           1 :     break;
     230           3 :   case 4:
     231             :     // Legendre polynomial coefficients of order four
     232           3 :     func.coeff_poly[0]=0.375;
     233           3 :     func.coeff_poly[1]=0.0;
     234           3 :     func.coeff_poly[2]=-3.75;
     235           3 :     func.coeff_poly[3]=0.0;
     236           3 :     func.coeff_poly[4]=4.375;
     237           3 :     break;
     238           0 :   case 5:
     239             :     // Legendre polynomial coefficients of order five
     240           0 :     func.coeff_poly[0]=0.0;
     241           0 :     func.coeff_poly[1]=1.875;
     242           0 :     func.coeff_poly[2]=0.0;
     243           0 :     func.coeff_poly[3]=-8.75;
     244           0 :     func.coeff_poly[4]=0.0;
     245           0 :     func.coeff_poly[5]=7.875;
     246           0 :     break;
     247          19 :   case 6:
     248             :     // Legendre polynomial coefficients of order six
     249          19 :     func.coeff_poly[0]=-0.3125;
     250          19 :     func.coeff_poly[1]=0.0;
     251          19 :     func.coeff_poly[2]=6.5625;
     252          19 :     func.coeff_poly[3]=0.0;
     253          19 :     func.coeff_poly[4]=-19.6875;
     254          19 :     func.coeff_poly[5]=0.0;
     255          19 :     func.coeff_poly[6]=14.4375;
     256          19 :     break;
     257           0 :   default:
     258           0 :     action->error("Insert Legendre polynomial coefficients into SphericalHarmonics code");
     259             :   }
     260             : 
     261             :   std::string num;
     262         380 :   for(int i=-func.tmom; i<=func.tmom; ++i) {
     263         338 :     Tools::convert(std::abs(i),num);
     264         338 :     if( i<0 ) {
     265         296 :       options.multipleValuesForEachRegisteredComponent.push_back( "-n" + num );
     266         190 :     } else if( i>0 ) {
     267         296 :       options.multipleValuesForEachRegisteredComponent.push_back( "-p" + num );
     268             :     } else {
     269          84 :       options.multipleValuesForEachRegisteredComponent.push_back( "-0");
     270             :     }
     271             :   }
     272          84 :   options.derivativeZeroIfValueIsZero = (action->getNumberOfArguments()==4
     273          42 :                                          && action->getPntrToArgument(3)
     274             :                                          ->isDerivativeZeroWhenValueIsZero() );
     275          42 : }
     276             : 
     277    19212686 : void SphericalHarmonic::calc( const SphericalHarmonic& func,
     278             :                               bool noderiv,
     279             :                               const View<const double> args,
     280             :                               function::FunctionOutput funcout ) {
     281             :   double weight=1;
     282    19212686 :   if( args.size()==4 ) {
     283    19212686 :     weight = args[3];
     284             :   }
     285    19212686 :   if( weight<epsilon ) {
     286    16892014 :     if( !noderiv ) {
     287     3258340 :       const unsigned imbase = 2*func.tmom+1;
     288    27299632 :       for(int m=-func.tmom; m<=func.tmom; ++m) {
     289    24041292 :         auto pos = func.tmom+m;
     290    24041292 :         funcout.derivs[pos][0] = 0.0;
     291    24041292 :         funcout.derivs[pos][1] = 0.0;
     292    24041292 :         funcout.derivs[pos][2] = 0.0;
     293    24041292 :         funcout.derivs[pos][3] = 0.0;
     294    24041292 :         funcout.derivs[pos + imbase][0] = 0.0;
     295    24041292 :         funcout.derivs[pos + imbase][1] = 0.0;
     296    24041292 :         funcout.derivs[pos + imbase][2] = 0.0;
     297    24041292 :         if( args.size()==4 ) {
     298    24041292 :           funcout.derivs[pos + imbase][3] = 0.0;
     299             :         }
     300             :       }
     301             :     }
     302    16892014 :     return;
     303             :   }
     304             : 
     305     2320672 :   const double dlen2 = args[0]*args[0] + args[1]*args[1] + args[2]*args[2];
     306     2320672 :   const double dlen  = sqrt( dlen2 );
     307     2320672 :   const double dleninv = 1.0/dlen;
     308     2320672 :   const double dlen3 = dlen2*dlen;
     309     2320672 :   const double dlen3inv = 1.0/dlen3;
     310             :   double dpoly_ass;
     311     2320672 :   double poly_ass=func.deriv_poly( 0, args[2]*dleninv, dpoly_ass );
     312             :   // Accumulate for m=0
     313     2320672 :   funcout.values[func.tmom] = weight*poly_ass;
     314             :   // Derivatives of z/r wrt x, y, z
     315             :   Vector dz;
     316     2320672 :   if( !noderiv ) {
     317      449462 :     dz[0] = -( args[2] * dlen3inv )*args[0];
     318      449462 :     dz[1] = -( args[2] * dlen3inv )*args[1];
     319      449462 :     dz[2] = -( args[2] * dlen3inv )*args[2] + (1.0 * dleninv);
     320      449462 :     funcout.derivs[func.tmom] = weight*dpoly_ass*dz ;
     321      449462 :     if( args.size()==4 ) {
     322      449462 :       funcout.derivs[func.tmom][3] = poly_ass;
     323             :     }
     324             :   }
     325             : 
     326             :   // The complex number of which we have to take powers
     327     2320672 :   std::complex<double> com1( args[0]*dleninv, args[1]*dleninv );
     328             :   std::complex<double> powered(1.0, 0.0);
     329             :   constexpr std::complex<double> ii(0.0, 1.0);
     330             :   std::complex<double> dp_x;
     331             :   std::complex<double> dp_y;
     332             :   std::complex<double> dp_z;
     333             :   Vector myrealvec;
     334             :   Vector myimagvec;
     335             :   Vector real_dz;
     336             :   Vector imag_dz;
     337             :   // Do stuff for all other m values
     338     2320672 :   const unsigned imbase = 3*func.tmom+1;
     339             :   double pref = -1.0;
     340    13805228 :   for(int m=1; m<=func.tmom; ++m) {
     341             :     // Calculate Legendre Polynomial
     342    11484556 :     poly_ass=func.deriv_poly( m, args[2]*dleninv, dpoly_ass );
     343             :     // Real and imaginary parts of z
     344             :     double real_z = real(com1*powered);
     345             :     double imag_z = imag(com1*powered);
     346             : 
     347             :     // Calculate steinhardt parameter
     348    11484556 :     double tq6 =poly_ass*real_z;  // Real part of steinhardt parameter
     349    11484556 :     double itq6=poly_ass*imag_z;  // Imaginary part of steinhardt parameter
     350             : 
     351             :     // Real part
     352    11484556 :     funcout.values[func.tmom + m] = weight* tq6;
     353             :     // Imaginary part
     354    11484556 :     funcout.values[imbase    + m] = weight*itq6;
     355             :     // Store -m part of vector
     356             :     // -m part of vector is just +m part multiplied by (-1.0)**m and multiplied by complex
     357             :     // conjugate of Legendre polynomial
     358             :     // Real part
     359    11484556 :     funcout.values[func.tmom - m] =  pref*funcout.values[func.tmom+m];
     360             :     // Imaginary part
     361    11484556 :     funcout.values[imbase    - m] = -pref*funcout.values[imbase + m];
     362    11484556 :     if( !noderiv ) {
     363             :       // Derivatives wrt ( x/r + iy )^m
     364     1477034 :       double md=static_cast<double>(m);
     365     1477034 :       dp_x = md*powered*( (1.0*dleninv)-(args[0]*args[0])*dlen3inv-ii*(args[0]*args[1])*dlen3inv );
     366     1477034 :       dp_y = md*powered*( ii*(1.0*dleninv)-(args[0]*args[1])*dlen3inv-ii*(args[1]*args[1])*dlen3inv );
     367     1477034 :       dp_z = md*powered*( -(args[0]*args[2])*dlen3inv-ii*(args[1]*args[2])*dlen3inv );
     368             :       // Derivatives of real and imaginary parts of above
     369             :       real_dz[0] = real( dp_x );
     370             :       real_dz[1] = real( dp_y );
     371             :       real_dz[2] = real( dp_z );
     372             :       imag_dz[0] = imag( dp_x );
     373             :       imag_dz[1] = imag( dp_y );
     374             :       imag_dz[2] = imag( dp_z );
     375             :       // Complete derivative of steinhardt parameter
     376     1477034 :       myrealvec = weight*(dpoly_ass*real_z*dz + poly_ass*real_dz);
     377     1477034 :       myimagvec = weight*(dpoly_ass*imag_z*dz + poly_ass*imag_dz);
     378     1477034 :       funcout.derivs[func.tmom+m] = myrealvec;
     379             :       funcout.derivs[imbase   +m] = myimagvec;
     380             : 
     381     1477034 :       funcout.derivs[func.tmom-m] = pref*myrealvec;
     382             :       funcout.derivs[imbase   -m] = -pref*myimagvec;
     383     1477034 :       if( args.size()==4 ) {
     384     1477034 :         funcout.derivs[func.tmom + m][3]=  tq6;
     385     1477034 :         funcout.derivs[imbase    + m][3]= itq6;
     386     1477034 :         funcout.derivs[func.tmom - m][3]= pref* tq6;
     387     1477034 :         funcout.derivs[imbase    - m][3]=-pref*itq6;
     388             :       }
     389             :     }
     390             :     // Calculate next power of complex number
     391             :     powered *= com1;
     392             :     //hopefully the compiler will optimize with bitflipping the sign here
     393             :     pref = -pref;
     394             :   }
     395             : }
     396             : 
     397    13805228 : double SphericalHarmonic::deriv_poly( const unsigned m,
     398             :                                       const double val,
     399             :                                       double& df ) const {
     400             :   double fact=1.0;
     401    38933216 :   for(unsigned j=2; j<=m; ++j) {
     402    25127988 :     fact *= j;
     403             :   }
     404    13805228 :   double res=coeff_poly[m]*fact;
     405             : 
     406             :   double pow=1.0;
     407             :   double xi=val;
     408             :   double dxi=1.0;
     409    13805228 :   df=0.0;
     410    50417772 :   for(int i=m+1; i<=tmom; ++i) {
     411             :     fact = 1.0;
     412    92864124 :     for(int j=i-m+1; j<=i; ++j) {
     413    56251580 :       fact *= j;
     414             :     }
     415    36612544 :     res += coeff_poly[i]*fact*xi;
     416    36612544 :     df  += pow*coeff_poly[i]*fact*dxi;
     417    36612544 :     xi  *= val;
     418    36612544 :     dxi *= val;
     419    36612544 :     pow += 1.0;
     420             :   }
     421    13805228 :   df *= normaliz[m];
     422    13805228 :   return normaliz[m]*res;
     423             : }
     424             : 
     425             : }
     426             : }
     427             : 

Generated by: LCOV version 1.16