LCOV - code coverage report
Current view: top level - generic - FitToTemplate.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 112 119 94.1 %
Date: 2026-11-05 10:19:04 Functions: 5 7 71.4 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2014-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 "core/ActionAtomistic.h"
      23             : #include "core/ActionPilot.h"
      24             : #include "core/ActionRegister.h"
      25             : #include "core/ActionWithValue.h"
      26             : #include "tools/Vector.h"
      27             : #include "tools/Matrix.h"
      28             : #include "tools/AtomNumber.h"
      29             : #include "tools/Tools.h"
      30             : #include "tools/RMSD.h"
      31             : #include "core/PlumedMain.h"
      32             : #include "core/ActionSet.h"
      33             : #include "core/GenericMolInfo.h"
      34             : #include "core/PbcAction.h"
      35             : #include "tools/PDB.h"
      36             : #include "tools/Pbc.h"
      37             : 
      38             : #include <vector>
      39             : #include <string>
      40             : #include <memory>
      41             : 
      42             : namespace PLMD {
      43             : namespace generic {
      44             : 
      45             : //+PLUMEDOC GENERIC FIT_TO_TEMPLATE
      46             : /*
      47             : This action is used to align a molecule to a template.
      48             : 
      49             : This can be used to move the coordinates stored in plumed
      50             : so as to be aligned with a provided template in PDB format. Pdb should contain
      51             : also weights for alignment (see the format of PDB files used e.g. for [RMSD](RMSD.md)).
      52             : Make sure your PDB file is correctly formatted as explained in the documentation for [PDB2CONSTANT](PDB2CONSTANT.md).
      53             : Weights for displacement are ignored, since no displacement is computed here.
      54             : Notice that all atoms (not only those in the template) are aligned.
      55             : To see what effect try
      56             : the [DUMPATOMS](DUMPATOMS.md) directive to output the atomic positions.
      57             : 
      58             : Also notice that PLUMED propagate forces correctly so that you can add a bias on a CV computed
      59             : after alignment. For many CVs this has no effect, but in some case the alignment can
      60             : change the result. Examples are:
      61             : - [POSITION](POSITION.md) CV since it is affected by a rigid shift of the system.
      62             : - [DISTANCE](DISTANCE.md) CV with COMPONENTS. Since the alignment could involve a rotation (with TYPE=OPTIMAL) the actual components could be different
      63             :   from the original ones.
      64             : - [CELL](CELL.md) components for a similar reason.
      65             : - [DISTANCE](DISTANCE.md) from a [FIXEDATOM](FIXEDATOM.md), provided the fixed atom is introduced _after_ the FIT_TO_TEMPLATE action.
      66             : 
      67             : !!! caution ""
      68             : 
      69             :     The implementation of TYPE=OPTIMAL is available but should be considered in testing phase. Please report any
      70             :     strange behavior.
      71             : 
      72             : !!! caution "directive modifies stored positions"
      73             : 
      74             :     This directive modifies the stored position at the precise moment
      75             :     it is executed. This means that only collective variables
      76             :     which are below it in the input script will see the corrected positions.
      77             :     As a general rule, put it at the top of the input file. Also, unless you
      78             :     know exactly what you are doing, leave the default stride (1), so that
      79             :     this action is performed at every MD step.
      80             : 
      81             : When running with periodic boundary conditions, the atoms should be
      82             : in the proper periodic image. This is done automatically since PLUMED 2.5,
      83             : by considering the ordered list of atoms and rebuilding the molecules using a procedure
      84             : that is equivalent to that done in [WHOLEMOLECULES](WHOLEMOLECULES.md) . Notice that
      85             : rebuilding is local to this action. This is different from [WHOLEMOLECULES](WHOLEMOLECULES.md)
      86             : which actually modifies the coordinates stored in PLUMED.
      87             : 
      88             : In case you want to recover the old behavior you should use the NOPBC flag.
      89             : In that case you need to take care that atoms are in the correct
      90             : periodic image.
      91             : 
      92             : ## Examples
      93             : 
      94             : Align the atomic position to a template then print them.
      95             : The following example is only translating the system so as
      96             : to align the center of mass of a molecule to the one in the reference
      97             : structure `ref.pdb`:
      98             : 
      99             : ```plumed
     100             : #SETTINGS INPUTFILES=regtest/basic/rt63/align.pdb
     101             : # dump coordinates before fitting, to see the difference:
     102             : DUMPATOMS FILE=dump-before.xyz ATOMS=1-20
     103             : 
     104             : # fit coordinates to ref.pdb template
     105             : # this is a "TYPE=SIMPLE" fit, so that only translations are used.
     106             : FIT_TO_TEMPLATE STRIDE=1 REFERENCE=regtest/basic/rt63/align.pdb TYPE=SIMPLE
     107             : 
     108             : # dump coordinates after fitting, to see the difference:
     109             : DUMPATOMS FILE=dump-after.xyz ATOMS=1-20
     110             : ```
     111             : 
     112             : The following example instead performs a rototranslational fit.
     113             : 
     114             : ```plumed
     115             : #SETTINGS INPUTFILES=regtest/basic/rt63/align.pdb
     116             : # dump coordinates before fitting, to see the difference:
     117             : DUMPATOMS FILE=dump-before.xyz ATOMS=1-20
     118             : 
     119             : # fit coordinates to ref.pdb template
     120             : # this is a "TYPE=OPTIMAL" fit, so that rototranslations are used.
     121             : FIT_TO_TEMPLATE STRIDE=1 REFERENCE=regtest/basic/rt63/align.pdb TYPE=OPTIMAL
     122             : 
     123             : # dump coordinates after fitting, to see the difference:
     124             : DUMPATOMS FILE=dump-after.xyz ATOMS=1-20
     125             : ```
     126             : 
     127             : In the following example you see two completely equivalent way
     128             : to restrain an atom close to a position that is defined in the reference
     129             : frame of an aligned molecule. You could for instance use this command to calculate the
     130             : position of the center of mass of a ligand after having aligned the atoms to the reference
     131             : frame of the protein that is determined by aligning the atoms in the protein to the coordinates
     132             : provided in the file ref.pdb
     133             : 
     134             : ```plumed
     135             : #SETTINGS INPUTFILES=regtest/basic/rt63/align.pdb
     136             : # center of the ligand:
     137             : center: CENTER ATOMS=100-110
     138             : 
     139             : FIT_TO_TEMPLATE REFERENCE=regtest/basic/rt63/align.pdb TYPE=OPTIMAL
     140             : 
     141             : # place a fixed atom in the protein reference coordinates:
     142             : fix: FIXEDATOM AT=1.0,1.1,1.0
     143             : 
     144             : # take the distance between the fixed atom and the center of the ligand
     145             : d: DISTANCE ATOMS=center,fix
     146             : 
     147             : # apply a restraint
     148             : RESTRAINT ARG=d AT=0.0 KAPPA=100.0
     149             : ```
     150             : 
     151             : Notice that you could have obtained an (almost) identical result by adding a fictitious
     152             : atom to `ref.pdb` with the serial number corresponding to the atom labelled `center` (there is no automatic way
     153             : to get it, but in this example it should be the number of atoms of the system plus one),
     154             : and properly setting the weights for alignment and displacement in [RMSD](RMSD.md).
     155             : There are two differences to be expected:
     156             : (ab) FIT_TO_TEMPLATE might be slower since it has to rototranslate all the available atoms and
     157             : (b) variables employing periodic boundary conditions (such as [DISTANCE](DISTANCE.md) without `NOPBC`, as in the example above)
     158             :   are allowed after FIT_TO_TEMPLATE, whereas [RMSD](RMSD.md) expects the issues related to the periodic boundary conditions to be already solved.
     159             : The latter means that before the [RMSD](RMSD.md) statement one should use [WRAPAROUND](WRAPAROUND.md) or [WHOLEMOLECULES](WHOLEMOLECULES.md) to properly place
     160             : the ligand.
     161             : 
     162             : 
     163             : */
     164             : //+ENDPLUMEDOC
     165             : 
     166             : 
     167             : class FitToTemplate:
     168             :   public ActionPilot,
     169             :   public ActionAtomistic,
     170             :   public ActionWithValue {
     171             :   std::string type;
     172             :   bool nopbc;
     173             :   std::vector<double> weights;
     174             :   std::vector<std::pair<std::size_t,std::size_t> > p_aligned;
     175             :   Vector center;
     176             :   Vector shift;
     177             :   // optimal alignment related stuff
     178             :   std::unique_ptr<PLMD::RMSD> rmsd;
     179             :   Tensor rotation;
     180             :   Matrix< std::vector<Vector> > drotdpos;
     181             :   // not used anymore (see notes below at doNotRetrieve())
     182             :   // std::vector<Vector> positions;
     183             :   std::vector<Vector> DDistDRef;
     184             :   std::vector<Vector> ddistdpos;
     185             :   std::vector<Vector> centeredpositions;
     186             :   Vector center_positions;
     187             :   // Copy of the box value
     188             :   Value* boxValue;
     189             :   PbcAction* pbc_action;
     190             : public:
     191             :   explicit FitToTemplate(const ActionOptions&ao);
     192             :   static void registerKeywords( Keywords& keys );
     193          99 :   bool actionHasForces() override {
     194          99 :     return true;
     195             :   }
     196             :   void calculate() override;
     197             :   void apply() override;
     198           0 :   unsigned getNumberOfDerivatives() override {
     199           0 :     plumed_merror("You should not call this function");
     200             :   };
     201             : };
     202             : 
     203             : PLUMED_REGISTER_ACTION(FitToTemplate,"FIT_TO_TEMPLATE")
     204             : 
     205          13 : void FitToTemplate::registerKeywords( Keywords& keys ) {
     206          13 :   Action::registerKeywords( keys );
     207          13 :   ActionAtomistic::registerKeywords( keys );
     208          13 :   keys.add("compulsory","STRIDE","1","the frequency with which molecules are reassembled.  Unless you are completely certain about what you are doing leave this set equal to 1!");
     209          13 :   keys.add("compulsory","REFERENCE","a file in pdb format containing the reference structure and the atoms involved in the CV.");
     210          13 :   keys.add("compulsory","TYPE","SIMPLE","the manner in which RMSD alignment is performed.  Should be OPTIMAL or SIMPLE.");
     211          13 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
     212          26 :   keys.setValueDescription("scalar","the RMSD distance");
     213          13 : }
     214             : 
     215           9 : FitToTemplate::FitToTemplate(const ActionOptions&ao):
     216             :   Action(ao),
     217             :   ActionPilot(ao),
     218             :   ActionAtomistic(ao),
     219             :   ActionWithValue(ao),
     220           9 :   nopbc(false) {
     221             :   std::string reference;
     222           9 :   parse("REFERENCE",reference);
     223           9 :   type.assign("SIMPLE");
     224           9 :   parse("TYPE",type);
     225             : 
     226           9 :   parseFlag("NOPBC",nopbc);
     227           9 :   if(type!="SIMPLE" && type!="OPTIMAL" && type!="OPTIMAL-FAST") {
     228           0 :     error("unknown TYPE " + type + ": use SIMPLE, OPTIMAL or OPTIMAL-FAST");
     229             :   }
     230             : 
     231           9 :   checkRead();
     232             : 
     233           9 :   PDB pdb;
     234             : 
     235             :   // read everything in ang and transform to nm if we are not in natural units
     236           9 :   if( !pdb.read(reference,usingNaturalUnits(),0.1/getUnits().getLength()) ) {
     237           0 :     error("missing input file " + reference );
     238             :   }
     239             : 
     240           9 :   requestAtoms(pdb.getAtomNumbers());
     241           9 :   log.printf("  found %zu atoms in input \n",pdb.getAtomNumbers().size());
     242           9 :   log.printf("  with indices : ");
     243          42 :   for(unsigned i=0; i<pdb.getAtomNumbers().size(); ++i) {
     244          33 :     if(i%25==0) {
     245           9 :       log<<"\n";
     246             :     }
     247          33 :     log.printf("%d ",pdb.getAtomNumbers()[i].serial());
     248             :   }
     249           9 :   log.printf("\n");
     250             : 
     251           9 :   std::vector<Vector> myPositions=pdb.getPositions();
     252           9 :   weights=pdb.getOccupancy();
     253           9 :   std::vector<AtomNumber> aligned=pdb.getAtomNumbers();
     254           9 :   p_aligned.resize( aligned.size() );
     255          42 :   for(unsigned i=0; i<aligned.size(); ++i) {
     256          33 :     p_aligned[i] = getValueIndices( aligned[i] );
     257             :   }
     258             : 
     259             : 
     260             :   // normalize weights
     261             :   double n=0.0;
     262          42 :   for(unsigned i=0; i<weights.size(); ++i) {
     263          33 :     n+=weights[i];
     264             :   }
     265           9 :   if(n==0.0) {
     266           0 :     error("PDB file " + reference + " has zero weights. Please check the occupancy column.");
     267             :   }
     268           9 :   n=1.0/n;
     269          42 :   for(unsigned i=0; i<weights.size(); ++i) {
     270          33 :     weights[i]*=n;
     271             :   }
     272             : 
     273             :   // normalize weights for rmsd calculation
     274           9 :   std::vector<double> weights_measure=pdb.getBeta();
     275             :   n=0.0;
     276          42 :   for(unsigned i=0; i<weights_measure.size(); ++i) {
     277          33 :     n+=weights_measure[i];
     278             :   }
     279           9 :   if(n==0.0 && type!="SIMPLE") {
     280           0 :     error("PDB file " + reference + " has zero weights for the RMSD. Please check the beta column.");
     281             :   }
     282           9 :   n=1.0/n;
     283          42 :   for(unsigned i=0; i<weights_measure.size(); ++i) {
     284          33 :     weights_measure[i]*=n;
     285             :   }
     286             : 
     287             :   // subtract the center
     288          42 :   for(unsigned i=0; i<weights.size(); ++i) {
     289          33 :     center+=myPositions[i]*weights[i];
     290             :   }
     291          42 :   for(unsigned i=0; i<weights.size(); ++i) {
     292             :     myPositions[i]-=center;
     293             :   }
     294             : 
     295           9 :   if(type=="OPTIMAL" or type=="OPTIMAL-FAST" ) {
     296           5 :     rmsd=Tools::make_unique<RMSD>();
     297           5 :     rmsd->set(weights,weights_measure,myPositions,type,false,false);// note: the reference is shifted now with center in the origin
     298          10 :     log<<"  Method chosen for fitting: "<<rmsd->getMethod()<<" \n";
     299             :   }
     300           9 :   if(nopbc) {
     301           1 :     log<<"  Ignoring PBCs when doing alignment, make sure your molecule is whole!<n";
     302             :   }
     303             :   // register the value of rmsd (might be useful sometimes)
     304           9 :   addValue();
     305           9 :   setNotPeriodic();
     306             : 
     307             :   // I remove this optimization now in order to use makeWhole()
     308             :   // Notice that for FIT_TO_TEMPLATE TYPE=OPTIMAL a copy was made anyway
     309             :   // (due to the need to store position to propagate forces on rotational matrix later)
     310             :   // For FIT_TO_TEMPLATE TYPE=SIMPLE in principle we could use it and write an ad hoc
     311             :   // version of makeWhole that only computes the center. Too lazy to do it now.
     312             :   // In case we do it later, remember that uncommenting this line means that
     313             :   // getPositions will not work anymore! GB
     314             :   // doNotRetrieve();
     315             : 
     316             :   // this is required so as to allow modifyGlobalForce() to return correct
     317             :   // also for forces that are not owned (and thus not zeored) by all processors.
     318           9 :   pbc_action=plumed.getActionSet().selectWithLabel<PbcAction*>("Box");
     319           9 :   if( !pbc_action ) {
     320           0 :     error("cannot align box has not been set");
     321             :   }
     322           9 :   boxValue=pbc_action->copyOutput(0);
     323          18 : }
     324             : 
     325             : 
     326         108 : void FitToTemplate::calculate() {
     327             : 
     328         108 :   if(!nopbc) {
     329          96 :     makeWhole();
     330             :   }
     331             : 
     332         108 :   if (type=="SIMPLE") {
     333             :     Vector cc;
     334             : 
     335         144 :     for(unsigned i=0; i<p_aligned.size(); ++i) {
     336          96 :       cc+=weights[i]*getPosition(i);
     337             :     }
     338             : 
     339          48 :     shift=center-cc;
     340          48 :     setValue(shift.modulo());
     341          48 :     translateAllGlobalPositions(shift);
     342          60 :   } else if( type=="OPTIMAL" or type=="OPTIMAL-FAST") {
     343             :     // specific stuff that provides all that is needed
     344          60 :     double r=rmsd->calc_FitElements( getPositions(), rotation,  drotdpos, centeredpositions, center_positions);
     345          60 :     setValue(r);
     346          60 :     rotateAllGlobalPositions(rotation,center_positions,center);
     347             : // rotate box
     348          60 :     pbc_action->getPbc().setBox(matmul(pbc_action->getPbc().getBox(),transpose(rotation)));
     349             :   }
     350         108 : }
     351             : 
     352         108 : void FitToTemplate::apply() {
     353         108 :   if (type=="SIMPLE") {
     354          48 :     Vector totForce=getTotalForceOnAllAtoms();
     355          48 :     Tensor vv=Tensor(center,totForce);
     356         192 :     for(unsigned i=0; i<3; ++i)
     357         576 :       for(unsigned j=0; j<3; ++j) {
     358         432 :         boxValue->addForce( 3*i+j, vv(i,j) );
     359             :       }
     360         144 :     for(unsigned i=0; i<p_aligned.size(); ++i) {
     361          96 :       addForce( p_aligned[i], -totForce*weights[i]);
     362             :     }
     363          60 :   } else if ( type=="OPTIMAL" or type=="OPTIMAL-FAST") {
     364          60 :     const Tensor rotationT=transpose(rotation);
     365             : // rotate back forces and accumulate rotated c.o.m. forces - this is already in the non rotated reference frame
     366          60 :     Vector totForce=rotateForcesOnAllAtoms(rotationT);
     367          60 :     Tensor virial;
     368         240 :     for(unsigned i=0; i<3; ++i)
     369         720 :       for(unsigned j=0; j<3; ++j) {
     370         540 :         virial[i][j] = boxValue->getForce( 3*i+j );
     371             :       }
     372             : // notice that an extra Tensor(center,matmul(rotation,totForce)) is required to
     373             : // compute the derivatives of the rotation with respect to center
     374          60 :     Tensor ww=matmul(rotationT,virial+Tensor(center,matmul(rotation,totForce)));
     375             : // rotate back virial
     376          60 :     virial=matmul(rotationT,matmul(virial,rotation));
     377             : 
     378             : // now we compute the force due to alignment
     379         360 :     for(unsigned i=0; i<p_aligned.size(); i++) {
     380         300 :       Vector g;
     381        1200 :       for(unsigned k=0; k<3; k++) {
     382             : // trace of matmul(ww,RMSD::getMatrixFromDRot(drotdpos,i,k)): only the diagonal is computed,
     383             : // summing in the same order as matmul
     384             :         double trace=0.0;
     385        3600 :         for(unsigned l=0; l<3; l++) {
     386             :           double dll=0.0;
     387       10800 :           for(unsigned m=0; m<3; m++) {
     388        8100 :             dll+=ww(l,m)*drotdpos(m,l)[i][k];
     389             :           }
     390        2700 :           trace+=dll;
     391             :         }
     392         900 :         g[k]=trace;
     393             :       }
     394             : // here is the extra contribution
     395         300 :       addForce( p_aligned[i], -g-weights[i]*totForce );
     396             : // here it the contribution to the virial
     397             : // notice that here we can use absolute positions since, for the alignment to be defined,
     398             : // positions should be in one well defined periodic image
     399         600 :       virial+=extProduct(getPosition(i),g);
     400             :     }
     401             : // finally, correction to the virial
     402          60 :     boxValue->clearInputForce();
     403          60 :     virial+=extProduct(matmul(rotationT,center),totForce);
     404         240 :     for(unsigned i=0; i<3; ++i)
     405         720 :       for(unsigned j=0; j<3; ++j) {
     406         540 :         boxValue->addForce( 3*i+j, virial(i,j) );
     407             :       }
     408             :   }
     409         108 : }
     410             : 
     411             : }
     412             : }

Generated by: LCOV version 1.16