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-03-25 09:33:27 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 position of the center ${r}_{\rm COM}$ given by:
      45             : 
      46             : $$
      47             : {r}_{\rm COM}=\frac{\sum_{i=1}^{n} {r}_i\ w_i }{\sum_i^{n} w_i}
      48             : $$
      49             : 
      50             : In these expressions $r_i$ indicates the position of atom $i$ and $w_i$ is the weight for atom $i$. The following input
      51             : shows how you can calculate the expressions for a set of atoms by using PLUMED:
      52             : 
      53             : ```plumed
      54             : # This action calculates the position of the point on the line connecting atoms 1 and 10 that is
      55             : # twice as far atom 10 as it is from atom 1
      56             : c1: CENTER ATOMS=1,1,10
      57             : # this is another way of calculating this position
      58             : c1bis: CENTER ATOMS=1,10 WEIGHTS=2,1
      59             : 
      60             : DUMPATOMS ATOMS=c1,c1bis FILE=atoms.xyz
      61             : ```
      62             : 
      63             : Notice that center's position is stored as [a virtual atom](specifying_atoms.md). The positions of the centers in the above input
      64             : used in the DUMPATOMS command by using the labels for the CENTER actions. Notice, furthermore, that the mass and charge of this new center
      65             : are equal to the sums of the masses and charges of the input atoms.
      66             : 
      67             : 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.
      68             : 
      69             : ```plumed
      70             : c: CENTER ATOMS=1-5 MASS
      71             : ```
      72             : 
      73             : 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
      74             : another action as has been done in the input below.
      75             : 
      76             : ```plumed
      77             : fcc: FCCUBIC SPECIES=1-1000 SWITCH={RATIONAL D_0=3.0 R_0=1.5}
      78             : sfcc: MORE_THAN ARG=fcc SWITCH={RATIONAL R_0=0.5}
      79             : c: CENTER ATOMS=1-1000 WEIGHTS=sfcc
      80             : DUMPATOMS ATOMS=c FILE=atom.xyz
      81             : ```
      82             : 
      83             : This input assumes you have a cluster of solid atoms in a liquid. The actions with labels `fcc` and `sfcc`
      84             : are used to differentiate between atoms in solid-like and liquid-like atoms. `sfcc` is thus a vector with
      85             : one element for each atom. These elements are equal to one if the environment around the corresponding atom
      86             : are solid like and zero if the environment around the atom is liquid like.
      87             : 
      88             : ## A note on periodic boundary conditions
      89             : 
      90             : If you run with periodic boundary conditions
      91             : these are taken into account automatically when computing the center of mass. The way this is
      92             : handled is akin to the way molecules are rebuilt in the [WHOLEMOLECULES](WHOLEMOLECULES.md) command.
      93             : However, at variance to [WHOLEMOLECULES](WHOLEMOLECULES.md), the copies of the atomic positions in this
      94             : action are modified.  The global positions (i.e. those that are used in all other actions)
      95             : are not changed when the alignment is performed.
      96             : 
      97             : If you believe that PBC should not be applied when calculating the position fo the center of mass you can use the
      98             : NOPBC flag.
      99             : 
     100             : An additional way of managing periodic boundary conditions is offered in CENTER by using the PHASES keyword as shown
     101             : in the example input below
     102             : 
     103             : ```plumed
     104             : c: CENTER ATOMS=1-100 PHASES
     105             : ```
     106             : 
     107             : 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$,
     108             : using the following expression when the PHASES option is employed
     109             : 
     110             : $$
     111             : x_\textrm{com} = \frac{1}{2\pi} \atan\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)
     112             : $$
     113             : 
     114             : 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
     115             : by multiplying these scaled components by the cell vectors.  Notice that by construction this center position is
     116             : not invariant with respect to rotations of the atoms at fixed cell lattice.
     117             : In addition, for symmetric Bravais lattices, it is not invariant with respect
     118             : to special symmetries. E.g., if you have an hexagonal cell, the center will
     119             : not be invariant with respect to rotations of 120 degrees.
     120             : On the other hand, it might make the treatment of PBC easier in difficult cases.
     121             : 
     122             : */
     123             : //+ENDPLUMEDOC
     124             : 
     125             : //+PLUMEDOC VATOM COM
     126             : /*
     127             : Calculate the center of mass for a group of atoms.
     128             : 
     129             : The following example input shows how you can use this shortcut to calculate the center of
     130             : mass for atoms  1,2,3,4,5,6,7 and for atoms 15,20.  You can then compute the distance between
     131             : the two center of masses.
     132             : 
     133             : ```plumed
     134             : c1: COM ATOMS=1-7
     135             : c2: COM ATOMS=15,20
     136             : d1: DISTANCE ATOMS=c1,c2
     137             : PRINT ARG=d1
     138             : ```
     139             : 
     140             : The center of mass is stored as a [virtual atom](specifying_atoms.md).  As you can see from the
     141             : above to refer to the position of the center of mass when specifying the atoms that should be used
     142             : when calculating some other action you use the lable of the COM action that computes the center of mass
     143             : of interest.
     144             : 
     145             : The COM command is a shortcut because it is a wrapper to [CENTER](CENTER.md). CENTER is more powerful than
     146             : comm as it allows you to use arbitrary weights in place of the masses.
     147             : 
     148             : If you run with periodic boundary conditions
     149             : these are taken into account automatically when computing the center of mass. The way this is
     150             : handled is akin to the way molecules are rebuilt in the [WHOLEMOLECULES](WHOLEMOLECULES.md) command.
     151             : However, at variance to [WHOLEMOLECULES](WHOLEMOLECULES.md), the copies of the atomic positions in this
     152             : action are modified.  The global positions (i.e. those that are used in all other actions)
     153             : are not changed when the alignment is performed.
     154             : 
     155             : If you believe that PBC should not be applied when calculating the position fo the center of mass you can use the
     156             : NOPBC flag.
     157             : 
     158             : */
     159             : //+ENDPLUMEDOC
     160             : 
     161             : 
     162             : class Center:
     163             :   public ActionWithVirtualAtom {
     164             :   std::vector<double> weights;
     165             :   std::vector<Tensor> dcenter_sin;
     166             :   std::vector<Tensor> dcenter_cos;
     167             :   std::vector<Tensor> deriv;
     168             :   bool isChargeSet_;
     169             :   bool isMassSet_;
     170             :   bool weight_mass;
     171             :   bool nopbc;
     172             :   bool first;
     173             :   bool phases;
     174             : public:
     175             :   explicit Center(const ActionOptions&ao);
     176             :   void calculate() override;
     177             :   static void registerKeywords( Keywords& keys );
     178             : };
     179             : 
     180             : PLUMED_REGISTER_ACTION(Center,"CENTER_FAST")
     181             : PLUMED_REGISTER_ACTION(Center,"COM")
     182             : 
     183       16735 : void Center::registerKeywords(Keywords& keys) {
     184       16735 :   ActionWithVirtualAtom::registerKeywords(keys);
     185       33470 :   if( keys.getDisplayName()!="COM" ) {
     186       29790 :     keys.setDisplayName("CENTER");
     187             :   }
     188       16735 :   keys.add("optional","WEIGHTS","Center is computed as a weighted average.");
     189       16735 :   keys.add("optional","SET_CHARGE","Set the charge of the virtual atom to a given value.");
     190       16735 :   keys.add("optional","SET_MASS","Set the mass of the virtual atom to a given value.");
     191       16735 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
     192       16735 :   keys.addFlag("MASS",false,"If set center is mass weighted");
     193       16735 :   keys.addFlag("PHASES",false,"Compute center using trigonometric phases");
     194       16735 : }
     195             : 
     196        9285 : Center::Center(const ActionOptions&ao):
     197             :   Action(ao),
     198             :   ActionWithVirtualAtom(ao),
     199        9285 :   isChargeSet_(false),
     200        9285 :   isMassSet_(false),
     201        9285 :   weight_mass(false),
     202        9285 :   nopbc(false),
     203        9285 :   first(true),
     204        9285 :   phases(false) {
     205             :   std::vector<AtomNumber> atoms;
     206       18570 :   parseAtomList("ATOMS",atoms);
     207        9285 :   if(atoms.size()==0) {
     208           0 :     error("at least one atom should be specified");
     209             :   }
     210        9285 :   parseVector("WEIGHTS",weights);
     211        9285 :   parseFlag("MASS",weight_mass);
     212        9285 :   parseFlag("NOPBC",nopbc);
     213        9285 :   parseFlag("PHASES",phases);
     214        9285 :   double charge_=std::numeric_limits<double>::lowest();
     215        9285 :   parse("SET_CHARGE",charge_);
     216        9285 :   setCharge(charge_);
     217        9285 :   if(charge_!=std::numeric_limits<double>::lowest()) {
     218           2 :     isChargeSet_=true;
     219             :   }
     220        9285 :   double mass_=-1;
     221        9285 :   parse("SET_MASS",mass_);
     222        9285 :   setMass(mass_);
     223        9285 :   if(mass_>0.) {
     224           2 :     isMassSet_=true;
     225             :   }
     226        9285 :   if(mass_==0.) {
     227           0 :     error("SETMASS must be greater than 0");
     228             :   }
     229        9285 :   if( getName()=="COM") {
     230        1838 :     weight_mass=true;
     231             :   }
     232        9285 :   checkRead();
     233        9285 :   log.printf("  of atoms:");
     234       87728 :   for(unsigned i=0; i<atoms.size(); ++i) {
     235       78443 :     if(i%25==0) {
     236       11056 :       log<<"\n";
     237             :     }
     238       78443 :     log.printf(" %d",atoms[i].serial());
     239             :   }
     240        9285 :   log<<"\n";
     241        9285 :   if(weight_mass) {
     242        1840 :     log<<"  mass weighted\n";
     243        1840 :     if(weights.size()!=0) {
     244           0 :       error("WEIGHTS and MASS keywords cannot not be used simultaneously");
     245             :     }
     246             :   } else {
     247        7445 :     if( weights.size()==0) {
     248         402 :       log<<" using the geometric center\n";
     249         402 :       weights.resize( atoms.size() );
     250        2242 :       for(unsigned i=0; i<atoms.size(); i++) {
     251        1840 :         weights[i] = 1.;
     252             :       }
     253             :     } else {
     254        7043 :       log<<" with weights:";
     255        7043 :       if( weights.size()!=atoms.size() ) {
     256           2 :         error("number of elements in weight vector does not match the number of atoms");
     257             :       }
     258       35274 :       for(unsigned i=0; i<weights.size(); ++i) {
     259       28232 :         if(i%25==0) {
     260        7042 :           log<<"\n";
     261             :         }
     262       28232 :         log.printf(" %f",weights[i]);
     263             :       }
     264        7042 :       log.printf("\n");
     265             :     }
     266             :   }
     267        9284 :   if(phases) {
     268           4 :     log<<"  Phases will be used to take into account PBC\n";
     269        9280 :   } else if(nopbc) {
     270          47 :     log<<"  PBC will be ignored\n";
     271             :   } else {
     272        9233 :     log<<"  broken molecules will be rebuilt assuming atoms are in the proper order\n";
     273             :   }
     274        9284 :   requestAtoms(atoms);
     275        9286 : }
     276             : 
     277       32946 : void Center::calculate() {
     278       32946 :   Vector pos;
     279       32946 :   const bool dophases=(getPbc().isSet() ? phases : false);
     280             : 
     281       32946 :   if(!nopbc && !dophases) {
     282       20638 :     makeWhole();
     283             :   }
     284             : 
     285       32946 :   if( first ) {
     286       15376 :     if( weight_mass ) {
     287      172257 :       for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     288      164230 :         if(std::isnan(getMass(i))) {
     289           0 :           error(
     290             :             "You are trying to compute a CENTER or COM but masses are not known.\n"
     291             :             "        If you are using plumed driver, please use the --mc option"
     292             :           );
     293             :         }
     294             :       }
     295             :     }
     296             :     double mass(0.0);
     297      209364 :     for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     298      193988 :       mass+=getMass(i);
     299             :     }
     300       15376 :     if( chargesWereSet && !isChargeSet_) {
     301             :       double charge(0.0);
     302      192732 :       for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     303      179414 :         charge+=getCharge(i);
     304             :       }
     305       13318 :       setCharge(charge);
     306        2058 :     } else if( !isChargeSet_ ) {
     307        2056 :       setCharge(0.0);
     308             :     }
     309       15376 :     if(!isMassSet_) {
     310       15374 :       setMass(mass);
     311             :     }
     312             : 
     313       15376 :     if( weight_mass ) {
     314        8027 :       weights.resize( getNumberOfAtoms() );
     315      172257 :       for(unsigned i=0; i<weights.size(); i++) {
     316      164230 :         weights[i] = getMass(i) / mass;
     317             :       }
     318             :     } else {
     319             :       double wtot=0.0;
     320       37107 :       for(unsigned i=0; i<weights.size(); i++) {
     321       29758 :         wtot+=weights[i];
     322             :       }
     323       37107 :       for(unsigned i=0; i<weights.size(); i++) {
     324       29758 :         weights[i]=weights[i]/wtot;
     325             :       }
     326        7349 :       first=false;
     327             :     }
     328             :   }
     329             : 
     330       32946 :   deriv.resize(getNumberOfAtoms());
     331             : 
     332       32946 :   if(dophases) {
     333         241 :     dcenter_sin.resize(getNumberOfAtoms());
     334         241 :     dcenter_cos.resize(getNumberOfAtoms());
     335         241 :     Vector center_sin;
     336         241 :     Vector center_cos;
     337         241 :     Tensor invbox2pi=2*pi*getPbc().getInvBox();
     338         241 :     Tensor box2pi=getPbc().getBox() / (2*pi);
     339         964 :     for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     340         723 :       double w=weights[i];
     341             : 
     342             :       // real to scaled
     343         723 :       const Vector scaled=matmul(getPosition(i),invbox2pi);
     344             :       const Vector ccos(
     345         723 :         w*std::cos(scaled[0]),
     346         723 :         w*std::cos(scaled[1]),
     347         723 :         w*std::cos(scaled[2])
     348         723 :       );
     349             :       const Vector csin(
     350         723 :         w*std::sin(scaled[0]),
     351         723 :         w*std::sin(scaled[1]),
     352         723 :         w*std::sin(scaled[2])
     353         723 :       );
     354         723 :       center_cos+=ccos;
     355         723 :       center_sin+=csin;
     356        2892 :       for(unsigned l=0; l<3; l++)
     357        8676 :         for(unsigned k=0; k<3; k++) {
     358             :           // k over real coordinates
     359             :           // l over scaled coordinates
     360        6507 :           dcenter_sin[i][l][k]=ccos[l]*invbox2pi[k][l];
     361        6507 :           dcenter_cos[i][l][k]=-csin[l]*invbox2pi[k][l];
     362             :         }
     363             :     }
     364             :     const Vector c(
     365         241 :       std::atan2(center_sin[0],center_cos[0]),
     366         241 :       std::atan2(center_sin[1],center_cos[1]),
     367         241 :       std::atan2(center_sin[2],center_cos[2])
     368         241 :     );
     369             : 
     370             :     // normalization is convenient for doing derivatives later
     371         964 :     for(unsigned l=0; l<3; l++) {
     372         723 :       double norm=1.0/(center_sin[l]*center_sin[l]+center_cos[l]*center_cos[l]);
     373         723 :       center_sin[l]*=norm;
     374         723 :       center_cos[l]*=norm;
     375             :     }
     376             : 
     377         964 :     for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     378         723 :       Tensor dd;
     379        2892 :       for(unsigned l=0; l<3; l++)
     380        8676 :         for(unsigned k=0; k<3; k++) {
     381             :           // k over real coordinates
     382             :           // l over scaled coordinates
     383        6507 :           dd[l][k]= (center_cos[l]*dcenter_sin[i][l][k] - center_sin[l]*dcenter_cos[i][l][k]);
     384             :         }
     385             :       // scaled to real
     386         723 :       deriv[i]=matmul(dd,box2pi);
     387             :     }
     388         241 :     setAtomsDerivatives(deriv);
     389             :     // scaled to real
     390         241 :     setPosition(matmul(c,box2pi));
     391             :   } else {
     392      412212 :     for(unsigned i=0; i<getNumberOfAtoms(); i++) {
     393      379507 :       double w=weights[i];
     394      379507 :       pos+=w*getPosition(i);
     395      379507 :       deriv[i]=w*Tensor::identity();
     396             :     }
     397       32705 :     setPosition(pos);
     398       32705 :     setAtomsDerivatives(deriv);
     399             :   }
     400       32946 : }
     401             : 
     402             : }
     403             : }

Generated by: LCOV version 1.16