LCOV - code coverage report
Current view: top level - vatom - Center.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 143 147 97.3 %
Date: 2025-11-25 13:55:50 Functions: 3 4 75.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-2023 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             : #include "ActionWithVirtualAtom.h"
      23             : #include "core/ActionRegister.h"
      24             : #include "core/PlumedMain.h"
      25             : #include <cmath>
      26             : #include <limits>
      27             : 
      28             : namespace PLMD {
      29             : namespace vatom {
      30             : 
      31             : //+PLUMEDOC VATOM CENTER_FAST
      32             : /*
      33             : Calculate the center for a group of atoms, with arbitrary weights.
      34             : 
      35             : \par Examples
      36             : 
      37             : */
      38             : //+ENDPLUMEDOC
      39             : 
      40             : //+PLUMEDOC VATOM CENTER
      41             : /*
      42             : Calculate the center for a group of atoms, with arbitrary weights.
      43             : 
      44             : The computed
      45             : center is stored as a virtual atom that can be accessed in
      46             : an atom list through the label for the CENTER action that creates it.
      47             : Notice that the generated virtual atom has charge equal to the sum of the
      48             : charges and mass equal to the sum of the masses. If used with the MASS flag,
      49             : then it provides a result identical to \ref COM.
      50             : 
      51             : When running with periodic boundary conditions, the atoms should be
      52             : in the proper periodic image. This is done automatically since PLUMED 2.2,
      53             : by considering the ordered list of atoms and rebuilding the molecule using a procedure
      54             : that is equivalent to that done in \ref WHOLEMOLECULES . Notice that
      55             : rebuilding is local to this action. This is different from \ref WHOLEMOLECULES
      56             : which actually modifies the coordinates stored in PLUMED.
      57             : 
      58             : In case you want to recover the old behavior you should use the NOPBC flag.
      59             : In that case you need to take care that atoms are in the correct
      60             : periodic image.
      61             : 
      62             : \note As an experimental feature, CENTER also supports a keyword PHASES.
      63             : This keyword finds the center of mass for sets of atoms that have been split by the period boundaries by computing scaled coordinates and average
      64             : trigonometric functions, similarly to \ref CENTER_OF_MULTICOLVAR.
      65             : Notice that by construction this center position is
      66             : not invariant with respect to rotations of the atoms at fixed cell lattice.
      67             : In addition, for symmetric Bravais lattices, it is not invariant with respect
      68             : to special symmetries. E.g., if you have an hexagonal cell, the center will
      69             : not be invariant with respect to rotations of 120 degrees.
      70             : On the other hand, it might make the treatment of PBC easier in difficult cases.
      71             : 
      72             : \par Examples
      73             : 
      74             : \plumedfile
      75             : # a point which is on the line connecting atoms 1 and 10, so that its distance
      76             : # from 10 is twice its distance from 1:
      77             : c1: CENTER ATOMS=1,1,10
      78             : # this is another way of stating the same:
      79             : c1bis: CENTER ATOMS=1,10 WEIGHTS=2,1
      80             : 
      81             : # center of mass among these atoms:
      82             : c2: CENTER ATOMS=2,3,4,5 MASS
      83             : 
      84             : d1: DISTANCE ATOMS=c1,c2
      85             : 
      86             : PRINT ARG=d1
      87             : \endplumedfile
      88             : 
      89             : */
      90             : //+ENDPLUMEDOC
      91             : 
      92             : //+PLUMEDOC VATOM COM
      93             : /*
      94             : Calculate the center of mass for a group of atoms.
      95             : 
      96             : The computed
      97             : center of mass is stored as a virtual atom that can be accessed in
      98             : an atom list through the label for the COM action that creates it.
      99             : 
     100             : For arbitrary weights (e.g. geometric center) see \ref CENTER.
     101             : 
     102             : When running with periodic boundary conditions, the atoms should be
     103             : in the proper periodic image. This is done automatically since PLUMED 2.2,
     104             : by considering the ordered list of atoms and rebuilding the molecule using a procedure
     105             : that is equivalent to that done in \ref WHOLEMOLECULES . Notice that
     106             : rebuilding is local to this action. This is different from \ref WHOLEMOLECULES
     107             : which actually modifies the coordinates stored in PLUMED.
     108             : 
     109             : In case you want to recover the old behavior you should use the NOPBC flag.
     110             : In that case you need to take care that atoms are in the correct
     111             : periodic image.
     112             : 
     113             : \par Examples
     114             : 
     115             : The following input instructs plumed to print the distance between the
     116             : center of mass for atoms 1,2,3,4,5,6,7 and that for atoms 15,20:
     117             : \plumedfile
     118             : c1: COM ATOMS=1-7
     119             : c2: COM ATOMS=15,20
     120             : d1: DISTANCE ATOMS=c1,c2
     121             : PRINT ARG=d1
     122             : \endplumedfile
     123             : 
     124             : */
     125             : //+ENDPLUMEDOC
     126             : 
     127             : 
     128             : class Center:
     129             :   public ActionWithVirtualAtom {
     130             :   std::vector<double> weights;
     131             :   std::vector<Tensor> dcenter_sin;
     132             :   std::vector<Tensor> dcenter_cos;
     133             :   std::vector<Tensor> deriv;
     134             :   bool isChargeSet_;
     135             :   bool isMassSet_;
     136             :   bool weight_mass;
     137             :   bool nopbc;
     138             :   bool first;
     139             :   bool phases;
     140             : public:
     141             :   explicit Center(const ActionOptions&ao);
     142             :   void calculate() override;
     143             :   static void registerKeywords( Keywords& keys );
     144             : };
     145             : 
     146             : PLUMED_REGISTER_ACTION(Center,"CENTER_FAST")
     147             : PLUMED_REGISTER_ACTION(Center,"COM")
     148             : 
     149       16735 : void Center::registerKeywords(Keywords& keys) {
     150       16735 :   ActionWithVirtualAtom::registerKeywords(keys);
     151       33470 :   if( keys.getDisplayName()!="COM" ) {
     152       29790 :     keys.setDisplayName("CENTER");
     153             :   }
     154       33470 :   keys.add("optional","WEIGHTS","Center is computed as a weighted average.");
     155       33470 :   keys.add("optional","SET_CHARGE","Set the charge of the virtual atom to a given value.");
     156       33470 :   keys.add("optional","SET_MASS","Set the mass of the virtual atom to a given value.");
     157       33470 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
     158       33470 :   keys.addFlag("MASS",false,"If set center is mass weighted");
     159       33470 :   keys.addFlag("PHASES",false,"Compute center using trigonometric phases");
     160       16735 : }
     161             : 
     162        9285 : Center::Center(const ActionOptions&ao):
     163             :   Action(ao),
     164             :   ActionWithVirtualAtom(ao),
     165        9285 :   isChargeSet_(false),
     166        9285 :   isMassSet_(false),
     167        9285 :   weight_mass(false),
     168        9285 :   nopbc(false),
     169        9285 :   first(true),
     170        9285 :   phases(false) {
     171             :   std::vector<AtomNumber> atoms;
     172       18570 :   parseAtomList("ATOMS",atoms);
     173        9285 :   if(atoms.size()==0) {
     174           0 :     error("at least one atom should be specified");
     175             :   }
     176        9285 :   parseVector("WEIGHTS",weights);
     177        9285 :   parseFlag("MASS",weight_mass);
     178        9285 :   parseFlag("NOPBC",nopbc);
     179        9285 :   parseFlag("PHASES",phases);
     180        9285 :   double charge_=std::numeric_limits<double>::lowest();
     181        9285 :   parse("SET_CHARGE",charge_);
     182        9285 :   setCharge(charge_);
     183        9285 :   if(charge_!=std::numeric_limits<double>::lowest()) {
     184           2 :     isChargeSet_=true;
     185             :   }
     186        9285 :   double mass_=-1;
     187        9285 :   parse("SET_MASS",mass_);
     188        9285 :   setMass(mass_);
     189        9285 :   if(mass_>0.) {
     190           2 :     isMassSet_=true;
     191             :   }
     192        9285 :   if(mass_==0.) {
     193           0 :     error("SETMASS must be greater than 0");
     194             :   }
     195        9285 :   if( getName()=="COM") {
     196        1838 :     weight_mass=true;
     197             :   }
     198        9285 :   checkRead();
     199        9285 :   log.printf("  of atoms:");
     200       87728 :   for(unsigned i=0; i<atoms.size(); ++i) {
     201       78443 :     if(i%25==0) {
     202       11056 :       log<<"\n";
     203             :     }
     204       78443 :     log.printf(" %d",atoms[i].serial());
     205             :   }
     206        9285 :   log<<"\n";
     207        9285 :   if(weight_mass) {
     208        1840 :     log<<"  mass weighted\n";
     209        1840 :     if(weights.size()!=0) {
     210           0 :       error("WEIGHTS and MASS keywords cannot not be used simultaneously");
     211             :     }
     212             :   } else {
     213        7445 :     if( weights.size()==0) {
     214         402 :       log<<" using the geometric center\n";
     215         402 :       weights.resize( atoms.size() );
     216        2242 :       for(unsigned i=0; i<atoms.size(); i++) {
     217        1840 :         weights[i] = 1.;
     218             :       }
     219             :     } else {
     220        7043 :       log<<" with weights:";
     221        7043 :       if( weights.size()!=atoms.size() ) {
     222           2 :         error("number of elements in weight vector does not match the number of atoms");
     223             :       }
     224       35274 :       for(unsigned i=0; i<weights.size(); ++i) {
     225       28232 :         if(i%25==0) {
     226        7042 :           log<<"\n";
     227             :         }
     228       28232 :         log.printf(" %f",weights[i]);
     229             :       }
     230        7042 :       log.printf("\n");
     231             :     }
     232             :   }
     233        9284 :   if(phases) {
     234           4 :     log<<"  Phases will be used to take into account PBC\n";
     235        9280 :   } else if(nopbc) {
     236          47 :     log<<"  PBC will be ignored\n";
     237             :   } else {
     238        9233 :     log<<"  broken molecules will be rebuilt assuming atoms are in the proper order\n";
     239             :   }
     240        9284 :   requestAtoms(atoms);
     241        9286 : }
     242             : 
     243       32946 : void Center::calculate() {
     244       32946 :   Vector pos;
     245       32946 :   const bool dophases=(getPbc().isSet() ? phases : false);
     246             : 
     247       32946 :   if(!nopbc && !dophases) {
     248       20638 :     makeWhole();
     249             :   }
     250             : 
     251       32946 :   if( first ) {
     252       15376 :     if( weight_mass ) {
     253      172257 :       for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     254      164230 :         if(std::isnan(getMass(i))) {
     255           0 :           error(
     256             :             "You are trying to compute a CENTER or COM but masses are not known.\n"
     257             :             "        If you are using plumed driver, please use the --mc option"
     258             :           );
     259             :         }
     260             :       }
     261             :     }
     262             :     double mass(0.0);
     263      209364 :     for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     264      193988 :       mass+=getMass(i);
     265             :     }
     266       15376 :     if( chargesWereSet && !isChargeSet_) {
     267             :       double charge(0.0);
     268      192732 :       for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     269      179414 :         charge+=getCharge(i);
     270             :       }
     271       13318 :       setCharge(charge);
     272        2058 :     } else if( !isChargeSet_ ) {
     273        2056 :       setCharge(0.0);
     274             :     }
     275       15376 :     if(!isMassSet_) {
     276       15374 :       setMass(mass);
     277             :     }
     278             : 
     279       15376 :     if( weight_mass ) {
     280        8027 :       weights.resize( getNumberOfAtoms() );
     281      172257 :       for(unsigned i=0; i<weights.size(); i++) {
     282      164230 :         weights[i] = getMass(i) / mass;
     283             :       }
     284             :     } else {
     285             :       double wtot=0.0;
     286       37107 :       for(unsigned i=0; i<weights.size(); i++) {
     287       29758 :         wtot+=weights[i];
     288             :       }
     289       37107 :       for(unsigned i=0; i<weights.size(); i++) {
     290       29758 :         weights[i]=weights[i]/wtot;
     291             :       }
     292        7349 :       first=false;
     293             :     }
     294             :   }
     295             : 
     296       32946 :   deriv.resize(getNumberOfAtoms());
     297             : 
     298       32946 :   if(dophases) {
     299         241 :     dcenter_sin.resize(getNumberOfAtoms());
     300         241 :     dcenter_cos.resize(getNumberOfAtoms());
     301         241 :     Vector center_sin;
     302         241 :     Vector center_cos;
     303         241 :     Tensor invbox2pi=2*pi*getPbc().getInvBox();
     304         241 :     Tensor box2pi=getPbc().getBox() / (2*pi);
     305         964 :     for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     306         723 :       double w=weights[i];
     307             : 
     308             :       // real to scaled
     309         723 :       const Vector scaled=matmul(getPosition(i),invbox2pi);
     310             :       const Vector ccos(
     311         723 :         w*std::cos(scaled[0]),
     312         723 :         w*std::cos(scaled[1]),
     313         723 :         w*std::cos(scaled[2])
     314         723 :       );
     315             :       const Vector csin(
     316         723 :         w*std::sin(scaled[0]),
     317         723 :         w*std::sin(scaled[1]),
     318         723 :         w*std::sin(scaled[2])
     319         723 :       );
     320         723 :       center_cos+=ccos;
     321         723 :       center_sin+=csin;
     322        2892 :       for(unsigned l=0; l<3; l++)
     323        8676 :         for(unsigned k=0; k<3; k++) {
     324             :           // k over real coordinates
     325             :           // l over scaled coordinates
     326        6507 :           dcenter_sin[i][l][k]=ccos[l]*invbox2pi[k][l];
     327        6507 :           dcenter_cos[i][l][k]=-csin[l]*invbox2pi[k][l];
     328             :         }
     329             :     }
     330             :     const Vector c(
     331         241 :       std::atan2(center_sin[0],center_cos[0]),
     332         241 :       std::atan2(center_sin[1],center_cos[1]),
     333         241 :       std::atan2(center_sin[2],center_cos[2])
     334         241 :     );
     335             : 
     336             :     // normalization is convenient for doing derivatives later
     337         964 :     for(unsigned l=0; l<3; l++) {
     338         723 :       double norm=1.0/(center_sin[l]*center_sin[l]+center_cos[l]*center_cos[l]);
     339         723 :       center_sin[l]*=norm;
     340         723 :       center_cos[l]*=norm;
     341             :     }
     342             : 
     343         964 :     for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     344         723 :       Tensor dd;
     345        2892 :       for(unsigned l=0; l<3; l++)
     346        8676 :         for(unsigned k=0; k<3; k++) {
     347             :           // k over real coordinates
     348             :           // l over scaled coordinates
     349        6507 :           dd[l][k]= (center_cos[l]*dcenter_sin[i][l][k] - center_sin[l]*dcenter_cos[i][l][k]);
     350             :         }
     351             :       // scaled to real
     352         723 :       deriv[i]=matmul(dd,box2pi);
     353             :     }
     354         241 :     setAtomsDerivatives(deriv);
     355             :     // scaled to real
     356         241 :     setPosition(matmul(c,box2pi));
     357             :   } else {
     358      412212 :     for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     359      379507 :       double w=weights[i];
     360      379507 :       pos+=w*getPosition(i);
     361      379507 :       deriv[i]=w*Tensor::identity();
     362             :     }
     363       32705 :     setPosition(pos);
     364       32705 :     setAtomsDerivatives(deriv);
     365             :   }
     366       32946 : }
     367             : 
     368             : }
     369             : }

Generated by: LCOV version 1.16