LCOV - code coverage report
Current view: top level - vatom - Center.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 159 167 95.2 %
Date: 2026-11-05 10:19:04 Functions: 4 5 80.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
      32             : /*
      33             : Calculate the center for a group of atoms, with arbitrary weights.
      34             : 
      35             : The position of the center ${r}_{\rm COM}$ given by:
      36             : 
      37             : $$
      38             : {r}_{\rm COM}=\frac{\sum_{i=1}^{n} {r}_i\ w_i }{\sum_i^{n} w_i}
      39             : $$
      40             : 
      41             : In these expressions $r_i$ indicates the position of atom $i$ and $w_i$ is the weight for atom $i$. The following input
      42             : shows how you can calculate the expressions for a set of atoms by using PLUMED:
      43             : 
      44             : ```plumed
      45             : # This action calculates the position of the point on the line connecting atoms 1 and 10 that is
      46             : # twice as far atom 10 as it is from atom 1
      47             : c1: CENTER ATOMS=1,1,10
      48             : # this is another way of calculating this position
      49             : c1bis: CENTER ATOMS=1,10 WEIGHTS=2,1
      50             : 
      51             : DUMPATOMS ATOMS=c1,c1bis FILE=atoms.xyz
      52             : ```
      53             : 
      54             : Notice that center's position is stored as [a virtual atom](specifying_atoms.md). The positions of the centers in the above input
      55             : used in the DUMPATOMS command by using the labels for the CENTER actions. Notice, furthermore, that the mass and charge of this new center
      56             : are equal to the sums of the masses and charges of the input atoms.
      57             : 
      58             : The input below shows how you can use the CENTER action in place of the [COM](COM.md) action to calculate the center of mass for a group of atoms.
      59             : 
      60             : ```plumed
      61             : c: CENTER ATOMS=1-5 MASS
      62             : ```
      63             : 
      64             : Center is more powerful than COM because you can use arbitrary vectors of weights as in the first example above or vector of weights that are calculated by
      65             : another action as has been done in the input below.
      66             : 
      67             : ```plumed
      68             : fcc: FCCUBIC SPECIES=1-1000 SWITCH={RATIONAL D_0=3.0 R_0=1.5}
      69             : sfcc: MORE_THAN ARG=fcc SWITCH={RATIONAL R_0=0.5}
      70             : c: CENTER ATOMS=1-1000 WEIGHTS=sfcc
      71             : DUMPATOMS ATOMS=c FILE=atom.xyz
      72             : ```
      73             : 
      74             : This input assumes you have a cluster of solid atoms in a liquid. The actions with labels `fcc` and `sfcc`
      75             : are used to differentiate between atoms in solid-like and liquid-like atoms. `sfcc` is thus a vector with
      76             : one element for each atom. These elements are equal to one if the environment around the corresponding atom
      77             : are solid like and zero if the environment around the atom is liquid like.
      78             : 
      79             : ## A note on periodic boundary conditions
      80             : 
      81             : If you run with periodic boundary conditions
      82             : these are taken into account automatically when computing the center of mass. The way this is
      83             : handled is akin to the way molecules are rebuilt in the [WHOLEMOLECULES](WHOLEMOLECULES.md) command.
      84             : However, at variance to [WHOLEMOLECULES](WHOLEMOLECULES.md), the copies of the atomic positions in this
      85             : action are modified.  The global positions (i.e. those that are used in all other actions)
      86             : are not changed when the alignment is performed.
      87             : 
      88             : If you believe that PBC should not be applied when calculating the position fo the center of mass you can use the
      89             : NOPBC flag as shown below:
      90             : 
      91             : ```plumed
      92             : c: CENTER ATOMS=1-100 NOPBC
      93             : ```
      94             : 
      95             : An additional way of managing periodic boundary conditions is offered in CENTER by using the PHASES keyword as shown
      96             : in the example input below
      97             : 
      98             : ```plumed
      99             : c: CENTER ATOMS=1-100 PHASES
     100             : ```
     101             : 
     102             : The scaled value for the $x$ component of the position of the center is calculated from the scaled components of the input atoms, $x_i$,
     103             : using the following expression when the PHASES option is employed
     104             : 
     105             : $$
     106             : x_\textrm{com} = \frac{1}{2\pi} \arctan\left( \frac{\sum_{i=1}^n w_i \sin(2 \pi x_i ) }{ \sum_{i=1}^n w_i \cos(2 \pi x_i ) } \right)
     107             : $$
     108             : 
     109             : Similar, expressions are used to calculae the values of the scaled $y$ and $z$ components.  The final cartesian coordinates of the center are then computed
     110             : by multiplying these scaled components by the cell vectors.  Notice that by construction this center position is
     111             : not invariant with respect to rotations of the atoms at fixed cell lattice.
     112             : In addition, for symmetric Bravais lattices, it is not invariant with respect
     113             : to special symmetries. E.g., if you have an hexagonal cell, the center will
     114             : not be invariant with respect to rotations of 120 degrees.
     115             : On the other hand, it might make the treatment of PBC easier in difficult cases.
     116             : 
     117             : As an alternative to PHASES you can use the SAFE_PHASES flag as shown below:
     118             : 
     119             : ```plumed
     120             : c: CENTER ATOMS=1-100 SAFE_PHASES
     121             : ```
     122             : 
     123             : This option will use the method described above for the PHASES keyword if the cell coordinates are set.  If, however, the cell coordinates are not set the
     124             : position of the center will be calculated in the usual way.  We wouldn't recommend using this option in place of PHASES as additional computational overhead
     125             : is introduced.  Furthermore, you normally know if the cell parameters are not passed to PLUMED in advance of doing the calculation
     126             : 
     127             : */
     128             : //+ENDPLUMEDOC
     129             : 
     130             : //+PLUMEDOC VATOM COM
     131             : /*
     132             : Calculate the center of mass for a group of atoms.
     133             : 
     134             : The following example input shows how you can use this shortcut to calculate the center of
     135             : mass for atoms  1,2,3,4,5,6,7 and for atoms 15,20.  You can then compute the distance between
     136             : the two center of masses.
     137             : 
     138             : ```plumed
     139             : c1: COM ATOMS=1-7
     140             : c2: COM ATOMS=15,20
     141             : d1: DISTANCE ATOMS=c1,c2
     142             : PRINT ARG=d1
     143             : ```
     144             : 
     145             : The center of mass is stored as a [virtual atom](specifying_atoms.md).  As you can see from the
     146             : above to refer to the position of the center of mass when specifying the atoms that should be used
     147             : when calculating some other action you use the lable of the COM action that computes the center of mass
     148             : of interest.
     149             : 
     150             : The COM command is a shortcut because it is a wrapper to [CENTER](CENTER.md). CENTER is more powerful than
     151             : comm as it allows you to use arbitrary weights in place of the masses.
     152             : 
     153             : ## Periodic boundary conditions
     154             : 
     155             : If you run with periodic boundary conditions
     156             : these are taken into account automatically when computing the center of mass. By default the way this is
     157             : handled is akin to the way molecules are rebuilt in the [WHOLEMOLECULES](WHOLEMOLECULES.md) command.
     158             : However, at variance to [WHOLEMOLECULES](WHOLEMOLECULES.md), the copies of the atomic positions in this
     159             : action are modified.  The global positions (i.e. those that are used in all other actions)
     160             : are not changed when the alignment is performed.
     161             : 
     162             : If you believe that PBC should not be applied when calculating the position fo the center of mass you can use the
     163             : NOPBC flag as shown below:
     164             : 
     165             : ```plumed
     166             : c1: COM ATOMS=1-7 NOPBC
     167             : ```
     168             : 
     169             : Alternatively, if you would like to calculate the position of the center of mass using the PHASES method that is dicussed
     170             : in the documentation for [CENTER](CENTER.md) you can add the keyword PHASES as shwo below:
     171             : 
     172             : ```plumed
     173             : c1: COM ATOMS=1-7 PHASES
     174             : ```
     175             : 
     176             : ## The mass and charge of the vatom
     177             : 
     178             : By default the mass and charge of the COM are calculated by adding the masses and charges of the input atoms together.  In other
     179             : words, if your COM is calculated from the positions of $N$ atoms, the mass of the vatom is calculated as:
     180             : 
     181             : $$
     182             : m_\textrm{com} = \sum_{i=1}^N m_i
     183             : $$
     184             : 
     185             : where $m_i$ is the mass of the $i$th input atom. The charge is then calculated as:
     186             : 
     187             : $$
     188             : q_\textrm{com} = \sum_{i=1}^N q_i
     189             : $$
     190             : 
     191             : where $q_i$ is the charge of the $i$th atom.  If for any reason you don't want the mass and charge of the VATOM to be set equal to these
     192             : values you can set them manually by using the `SET_CHARGE` and `SET_MASS` keywords as shown below:
     193             : 
     194             : ```plumed
     195             : c1: COM ATOMS=1-7 SET_MASS=1 SET_CHARGE=-1
     196             : ```
     197             : 
     198             : */
     199             : //+ENDPLUMEDOC
     200             : 
     201             : 
     202             : class Center:
     203             :   public ActionWithVirtualAtom {
     204             :   std::vector<double> weights;
     205             :   std::vector<Tensor> dcenter_sin;
     206             :   std::vector<Tensor> dcenter_cos;
     207             :   std::vector<Tensor> deriv;
     208             :   bool isChargeSet_;
     209             :   bool isMassSet_;
     210             :   bool weight_mass;
     211             :   bool nopbc;
     212             :   bool first;
     213             :   bool phases;
     214             : // true if the masses and charges of all the atoms are declared constant (e.g. they come from the MD engine)
     215             :   bool constantMassCharge;
     216             : // computes mass and charge of the virtual atom and, if needed, the mass-based weights
     217             :   void updateMassAndCharge();
     218             : public:
     219             :   explicit Center(const ActionOptions&ao);
     220             :   void calculate() override;
     221             :   static void registerKeywords( Keywords& keys );
     222             : };
     223             : 
     224             : PLUMED_REGISTER_ACTION(Center,"CENTER_FAST")
     225             : PLUMED_REGISTER_ACTION(Center,"COM")
     226             : 
     227       17240 : void Center::registerKeywords(Keywords& keys) {
     228       17240 :   ActionWithVirtualAtom::registerKeywords(keys);
     229       17240 :   if( keys.getDisplayName()!="COM" ) {
     230       30794 :     keys.setDisplayName("CENTER");
     231       15397 :     keys.add("optional","WEIGHTS","Center is computed as a weighted average.");
     232       15397 :     keys.addFlag("MASS",false,"If set center is mass weighted");
     233             :   }
     234       17240 :   keys.add("optional","SET_CHARGE","Set the charge of the virtual atom to a given value.");
     235       17240 :   keys.add("optional","SET_MASS","Set the mass of the virtual atom to a given value.");
     236       17240 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
     237       17240 :   keys.addFlag("PHASES",false,"Compute center using trigonometric phases");
     238       17240 : }
     239             : 
     240        9536 : Center::Center(const ActionOptions&ao):
     241             :   Action(ao),
     242             :   ActionWithVirtualAtom(ao),
     243        9536 :   isChargeSet_(false),
     244        9536 :   isMassSet_(false),
     245        9536 :   weight_mass(false),
     246        9536 :   nopbc(false),
     247        9536 :   first(true),
     248        9536 :   phases(false),
     249        9536 :   constantMassCharge(true) {
     250        9536 :   std::vector<AtomNumber> atoms;
     251       19072 :   parseAtomList("ATOMS",atoms);
     252        9536 :   if(atoms.size()==0) {
     253           0 :     error("at least one atom should be specified");
     254             :   }
     255        9536 :   if( getName()!="COM") {
     256        7697 :     parseVector("WEIGHTS",weights);
     257       15394 :     parseFlag("MASS",weight_mass);
     258             :   }
     259        9536 :   parseFlag("NOPBC",nopbc);
     260        9536 :   parseFlag("PHASES",phases);
     261        9536 :   double charge_=std::numeric_limits<double>::lowest();
     262        9536 :   parse("SET_CHARGE",charge_);
     263        9536 :   setCharge(charge_);
     264        9536 :   if(charge_!=std::numeric_limits<double>::lowest()) {
     265           3 :     isChargeSet_=true;
     266             :   }
     267             :   const double massNotSet=std::numeric_limits<double>::lowest();
     268        9536 :   double mass_=massNotSet;
     269        9536 :   parse("SET_MASS",mass_);
     270        9536 :   if(mass_!=massNotSet) {
     271           3 :     if(!(mass_>0.)) {
     272           0 :       error("SET_MASS must be greater than 0");
     273             :     }
     274           3 :     isMassSet_=true;
     275           3 :     setMass(mass_);
     276             :   } else {
     277        9533 :     setMass(-1);
     278             :   }
     279        9536 :   if( getName()=="COM") {
     280        1839 :     weight_mass=true;
     281             :   }
     282        9536 :   checkRead();
     283        9536 :   log.printf("  of atoms:");
     284       89781 :   for(unsigned i=0; i<atoms.size(); ++i) {
     285       80245 :     if(i%25==0) {
     286       11352 :       log<<"\n";
     287             :     }
     288       80245 :     log.printf(" %d",atoms[i].serial());
     289             :   }
     290        9536 :   log<<"\n";
     291        9536 :   if(weight_mass) {
     292        1844 :     log<<"  mass weighted\n";
     293        1844 :     if(weights.size()!=0) {
     294           0 :       error("WEIGHTS and MASS keywords cannot not be used simultaneously");
     295             :     }
     296             :   } else {
     297        7692 :     if( weights.size()==0) {
     298         648 :       log<<" using the geometric center\n";
     299         648 :       weights.resize( atoms.size() );
     300        4275 :       for(unsigned i=0; i<atoms.size(); i++) {
     301        3627 :         weights[i] = 1.;
     302             :       }
     303             :     } else {
     304        7044 :       log<<" with weights:";
     305        7044 :       if( weights.size()!=atoms.size() ) {
     306           1 :         error("number of elements in weight vector does not match the number of atoms");
     307             :       }
     308       35278 :       for(unsigned i=0; i<weights.size(); ++i) {
     309       28235 :         if(i%25==0) {
     310        7043 :           log<<"\n";
     311             :         }
     312       28235 :         log.printf(" %f",weights[i]);
     313             :       }
     314        7043 :       log.printf("\n");
     315             :     }
     316             :     // weights are constant: they are normalized once here
     317             :     double wtot=0.0;
     318       39553 :     for(unsigned i=0; i<weights.size(); i++) {
     319       31862 :       wtot+=weights[i];
     320             :     }
     321        7691 :     if(wtot==0.0) {
     322           0 :       error("the sum of the weights is zero");
     323             :     }
     324       39553 :     for(unsigned i=0; i<weights.size(); i++) {
     325       31862 :       weights[i]/=wtot;
     326             :     }
     327             :   }
     328        9535 :   if(phases) {
     329           9 :     log<<"  Phases will be used to take into account PBC\n";
     330           9 :     if(nopbc) {
     331           1 :       log<<"  WARNING: NOPBC has no effect when PHASES is used\n";
     332             :     }
     333        9526 :   } else if(nopbc) {
     334          50 :     log<<"  PBC will be ignored\n";
     335             :   } else {
     336        9476 :     log<<"  broken molecules will be rebuilt assuming atoms are in the proper order\n";
     337             :   }
     338             :   // masses and charges coming from the MD engine are declared constant, and in that case
     339             :   // they are processed only once. Otherwise (e.g. center of virtual atoms) at every step.
     340        9535 :   std::vector<Value*> xv, yv, zv, mv, cv;
     341        9535 :   getAtomValuesFromPlumedObject( plumed, xv, yv, zv, mv, cv );
     342       89776 :   for(unsigned i=0; i<atoms.size(); ++i) {
     343       80241 :     const std::pair<std::size_t,std::size_t> p = getValueIndices( atoms[i] );
     344       80241 :     if( !mv[p.first]->isConstant() || !cv[p.first]->isConstant() ) {
     345          22 :       constantMassCharge=false;
     346             :     }
     347             :   }
     348        9535 :   requestAtoms(atoms);
     349        9542 : }
     350             : 
     351        9406 : void Center::updateMassAndCharge() {
     352             :   const std::vector<double>& m=getMasses();
     353             :   double mass(0.0);
     354       89187 :   for(unsigned i=0; i<m.size(); i++) {
     355       79781 :     mass+=m[i];
     356             :   }
     357        9406 :   if( weight_mass ) {
     358             :     // a NaN in any mass propagates to the sum
     359        1838 :     if(std::isnan(mass)) {
     360           0 :       error(
     361             :         "You are trying to compute a CENTER or COM but masses are not known.\n"
     362             :         "        If you are using plumed driver, please use the --mc option"
     363             :       );
     364             :     }
     365        1838 :     if(mass==0.0) {
     366           0 :       error("You are trying to compute a CENTER or COM but the total mass of the atoms is zero");
     367             :     }
     368        1838 :     weights.resize( m.size() );
     369       50140 :     for(unsigned i=0; i<weights.size(); i++) {
     370       48302 :       weights[i] = m[i] / mass;
     371             :     }
     372             :   }
     373        9406 :   if( !isMassSet_ ) {
     374        9403 :     setMass(mass);
     375             :   }
     376        9406 :   if( !isChargeSet_ ) {
     377        9403 :     if( chargesWereSet ) {
     378        9399 :       const std::vector<double>& q=getCharges();
     379             :       double charge(0.0);
     380       89135 :       for(unsigned i=0; i<q.size(); i++) {
     381       79736 :         charge+=q[i];
     382             :       }
     383        9399 :       setCharge(charge);
     384             :     } else {
     385           4 :       setCharge(0.0);
     386             :     }
     387             :   }
     388        9406 : }
     389             : 
     390       37057 : void Center::calculate() {
     391       37057 :   Vector pos;
     392       37057 :   const bool dophases=(getPbc().isSet() ? phases : false);
     393             : 
     394       37057 :   if(!nopbc && !dophases) {
     395       24707 :     makeWhole();
     396             :   }
     397             : 
     398       37057 :   if( first ) {
     399        9402 :     first=false;
     400        9402 :     if( !weight_mass && !isMassSet_ && !massesWereSet ) {
     401           0 :       log.printf("WARNING: masses were not passed to plumed\n");
     402             :     }
     403        9402 :     updateMassAndCharge();
     404       27655 :   } else if( !constantMassCharge ) {
     405           4 :     updateMassAndCharge();
     406             :   }
     407             : 
     408       37057 :   deriv.resize(getNumberOfAtoms());
     409             : 
     410       37057 :   if(dophases) {
     411         256 :     dcenter_sin.resize(getNumberOfAtoms());
     412         256 :     dcenter_cos.resize(getNumberOfAtoms());
     413         256 :     Vector center_sin;
     414         256 :     Vector center_cos;
     415         256 :     Tensor invbox2pi=2*pi*getPbc().getInvBox();
     416         256 :     Tensor box2pi=getPbc().getBox() / (2*pi);
     417        1024 :     for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     418         768 :       double w=weights[i];
     419             : 
     420             :       // real to scaled
     421         768 :       const Vector scaled=matmul(getPosition(i),invbox2pi);
     422             :       const Vector ccos(
     423         768 :         w*std::cos(scaled[0]),
     424         768 :         w*std::cos(scaled[1]),
     425         768 :         w*std::cos(scaled[2])
     426         768 :       );
     427             :       const Vector csin(
     428         768 :         w*std::sin(scaled[0]),
     429         768 :         w*std::sin(scaled[1]),
     430         768 :         w*std::sin(scaled[2])
     431         768 :       );
     432             :       center_cos+=ccos;
     433             :       center_sin+=csin;
     434        3072 :       for(unsigned l=0; l<3; l++)
     435        9216 :         for(unsigned k=0; k<3; k++) {
     436             :           // k over real coordinates
     437             :           // l over scaled coordinates
     438        6912 :           dcenter_sin[i][l][k]=ccos[l]*invbox2pi[k][l];
     439        6912 :           dcenter_cos[i][l][k]=-csin[l]*invbox2pi[k][l];
     440             :         }
     441             :     }
     442             :     const Vector c(
     443             :       std::atan2(center_sin[0],center_cos[0]),
     444             :       std::atan2(center_sin[1],center_cos[1]),
     445             :       std::atan2(center_sin[2],center_cos[2])
     446         256 :     );
     447             : 
     448             :     // normalization is convenient for doing derivatives later
     449        1024 :     for(unsigned l=0; l<3; l++) {
     450         768 :       const double den=center_sin[l]*center_sin[l]+center_cos[l]*center_cos[l];
     451         768 :       if(den==0.0) {
     452           0 :         error("the center cannot be computed with PHASES: the weighted sum of sines and cosines vanishes along one of the cell vectors");
     453             :       }
     454         768 :       double norm=1.0/den;
     455         768 :       center_sin[l]*=norm;
     456         768 :       center_cos[l]*=norm;
     457             :     }
     458             : 
     459        1024 :     for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     460         768 :       Tensor dd;
     461        3072 :       for(unsigned l=0; l<3; l++)
     462        9216 :         for(unsigned k=0; k<3; k++) {
     463             :           // k over real coordinates
     464             :           // l over scaled coordinates
     465        6912 :           dd[l][k]= (center_cos[l]*dcenter_sin[i][l][k] - center_sin[l]*dcenter_cos[i][l][k]);
     466             :         }
     467             :       // dd[l][k] is the derivative of the scaled component l with respect to the real coordinate k,
     468             :       // whereas deriv[i][k][m] must be the derivative of the real component m with respect to the
     469             :       // real coordinate k: dd has to be transposed before going from scaled to real
     470             :       // (this matters only for non-orthorhombic cells)
     471         768 :       deriv[i]=matmul(dd.transpose(),box2pi);
     472             :     }
     473         256 :     setAtomsDerivatives(deriv);
     474             :     // scaled to real
     475         256 :     setPosition(matmul(c,box2pi));
     476             :   } else {
     477      438647 :     for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     478      401846 :       double w=weights[i];
     479      401846 :       pos+=w*getPosition(i);
     480      401846 :       deriv[i]=w*Tensor::identity();
     481             :     }
     482       36801 :     setPosition(pos);
     483       36801 :     setAtomsDerivatives(deriv);
     484             :   }
     485       37057 : }
     486             : 
     487             : }
     488             : }

Generated by: LCOV version 1.16