LCOV - code coverage report
Current view: top level - isdb - PRE.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 128 145 88.3 %
Date: 2026-11-05 10:19:04 Functions: 3 4 75.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2015-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 "MetainferenceBase.h"
      23             : #include "core/ActionRegister.h"
      24             : #include "tools/NeighborList.h"
      25             : #include "tools/Pbc.h"
      26             : #include <memory>
      27             : 
      28             : namespace PLMD {
      29             : namespace isdb {
      30             : 
      31             : //+PLUMEDOC ISDB_COLVAR PRE
      32             : /*
      33             : Calculates the Paramagnetic Resonance Enhancement intensity ratio between a spin label atom and a list of atoms .
      34             : 
      35             : The reference atom for the spin label is added with SPINLABEL, the affected atom(s)
      36             : are give as numbered GROUPA1, GROUPA2, ...
      37             : The additional parameters needed for the calculation are given as INEPT, the inept
      38             : time, TAUC the correlation time, OMEGA, the Larmor frequency and RTWO for the relaxation
      39             : time.
      40             : 
      41             : [METAINFERENCE](METAINFERENCE.md) can be activated using DOSCORE and the other relevant keywords.
      42             : 
      43             : ## Examples
      44             : 
      45             : In the following example five PRE intensities are calculated using the distance between the
      46             : oxygen of the spin label and the backbone hydrogen atoms. Omega is the NMR frequency, RTWO the
      47             : R2 for the hydrogen atoms, INEPT of 8 ms for the experiment and a TAUC of 1.21 ns
      48             : 
      49             : ```plumed
      50             : PRE ...
      51             : LABEL=HN_pre
      52             : INEPT=8
      53             : TAUC=1.21
      54             : OMEGA=900
      55             : SPINLABEL=1818
      56             : GROUPA1=86  RTWO1=0.0120272827
      57             : GROUPA2=177 RTWO2=0.0263953158
      58             : GROUPA3=285 RTWO3=0.0058899829
      59             : GROUPA4=335 RTWO4=0.0102072646
      60             : GROUPA5=451 RTWO5=0.0086341843
      61             : ... PRE
      62             : 
      63             : PRINT ARG=HN_pre.* FILE=PRE.dat STRIDE=1
      64             : ```
      65             : 
      66             : */
      67             : //+ENDPLUMEDOC
      68             : 
      69             : class PRE :
      70             :   public MetainferenceBase {
      71             : private:
      72             :   bool             pbc;
      73             :   bool             doratio;
      74             :   double           constant;
      75             :   double           inept;
      76             :   std::vector<double>   rtwo;
      77             :   std::vector<unsigned> nga;
      78             :   std::unique_ptr<NeighborList> nl;
      79             :   unsigned         tot_size;
      80             :   // the components with the back-calculated data
      81             :   std::vector<Value*> dataValues_;
      82             :   // index of the first pair of each group in the neighbor list
      83             :   std::vector<unsigned> nga_offset_;
      84             : public:
      85             :   static void registerKeywords( Keywords& keys );
      86             :   explicit PRE(const ActionOptions&);
      87             :   void calculate() override;
      88             : };
      89             : 
      90             : PLUMED_REGISTER_ACTION(PRE,"PRE")
      91             : 
      92           8 : void PRE::registerKeywords( Keywords& keys ) {
      93           8 :   MetainferenceBase::registerKeywords(keys);
      94           8 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
      95           8 :   keys.addFlag("NORATIO",false,"Set to TRUE if you want to compute PRE without Intensity Ratio");
      96           8 :   keys.add("compulsory","INEPT","is the INEPT time (in ms).");
      97           8 :   keys.add("compulsory","TAUC","is the correlation time (in ns) for this electron-nuclear interaction.");
      98           8 :   keys.add("compulsory","OMEGA","is the Larmor frequency of the nuclear spin (in MHz).");
      99           8 :   keys.add("atoms","SPINLABEL","The atom to be used as the paramagnetic center.");
     100           8 :   keys.add("numbered","GROUPA","the atoms involved in each of the contacts you wish to calculate. "
     101             :            "Keywords like GROUPA1, GROUPA2, GROUPA3,... should be listed and one contact will be "
     102             :            "calculated for each ATOM keyword you specify.");
     103          16 :   keys.reset_style("GROUPA","atoms");
     104           8 :   keys.add("numbered","RTWO","The relaxation of the atom/atoms in the corresponding GROUPA of atoms. "
     105             :            "Keywords like RTWO1, RTWO2, RTWO3,... should be listed.");
     106           8 :   keys.add("numbered","PREINT","Add an experimental value for each PRE.");
     107          16 :   keys.addOutputComponent("pre","default","scalar","the # PRE");
     108          16 :   keys.addOutputComponent("exp","PREINT","scalar","the # PRE experimental intensity");
     109           8 : }
     110             : 
     111           4 : PRE::PRE(const ActionOptions&ao):
     112             :   PLUMED_METAINF_INIT(ao),
     113           4 :   pbc(true),
     114           4 :   doratio(true) {
     115           4 :   bool nopbc=!pbc;
     116           4 :   parseFlag("NOPBC",nopbc);
     117           4 :   pbc=!nopbc;
     118             : 
     119           4 :   bool noratio=!doratio;
     120           4 :   parseFlag("NORATIO",noratio);
     121           4 :   doratio=!noratio;
     122             : 
     123           4 :   std::vector<AtomNumber> atom;
     124           8 :   parseAtomList("SPINLABEL",atom);
     125           4 :   if(atom.size()!=1) {
     126           0 :     error("Number of specified atom should be 1");
     127             :   }
     128             : 
     129             :   // Read in the atoms
     130           4 :   std::vector<AtomNumber> t, ga_lista, gb_lista;
     131          12 :   for(int i=1;; ++i ) {
     132          32 :     parseAtomList("GROUPA", i, t );
     133          16 :     if( t.empty() ) {
     134             :       break;
     135             :     }
     136          28 :     for(unsigned j=0; j<t.size(); j++) {
     137          16 :       ga_lista.push_back(t[j]);
     138          16 :       gb_lista.push_back(atom[0]);
     139             :     }
     140          12 :     nga.push_back(t.size());
     141          12 :     t.resize(0);
     142          12 :   }
     143             : 
     144             :   // Read in reference values
     145           4 :   rtwo.resize( nga.size() );
     146           4 :   if(doratio) {
     147             :     unsigned ntarget=0;
     148           4 :     for(unsigned i=0; i<nga.size(); ++i) {
     149           8 :       if( !parseNumbered( "RTWO", i+1, rtwo[i] ) ) {
     150             :         break;
     151             :       }
     152           0 :       ntarget++;
     153             :     }
     154           4 :     if( ntarget==0 ) {
     155           4 :       parse("RTWO",rtwo[0]);
     156          12 :       for(unsigned i=1; i<nga.size(); ++i) {
     157           8 :         rtwo[i]=rtwo[0];
     158             :       }
     159           0 :     } else if( ntarget!=nga.size() ) {
     160           0 :       error("found wrong number of RTWO values");
     161             :     }
     162             :   }
     163             : 
     164           4 :   double tauc=0.;
     165           4 :   parse("TAUC",tauc);
     166           4 :   if(tauc==0.) {
     167           0 :     error("TAUC must be set");
     168             :   }
     169             : 
     170           4 :   double omega=0.;
     171           4 :   parse("OMEGA",omega);
     172           4 :   if(omega==0.) {
     173           0 :     error("OMEGA must be set");
     174             :   }
     175             : 
     176           4 :   inept=0.;
     177           4 :   if(doratio) {
     178           4 :     parse("INEPT",inept);
     179           4 :     if(inept==0.) {
     180           0 :       error("INEPT must be set");
     181             :     }
     182           4 :     inept *= 0.001; // ms2s
     183             :   }
     184             : 
     185             :   const double ns2s   = 0.000000001;
     186             :   const double MHz2Hz = 1000000.;
     187             :   const double Kappa  = 12300000000.00; // this is 1/15*S*(S+1)*gamma^2*g^2*beta^2
     188             :   // where gamma is the nuclear gyromagnetic ratio,
     189             :   // g is the electronic g factor, and beta is the Bohr magneton
     190             :   // in nm^6/s^2
     191           4 :   constant = (4.*tauc*ns2s+(3.*tauc*ns2s)/(1+omega*omega*MHz2Hz*MHz2Hz*tauc*tauc*ns2s*ns2s))*Kappa;
     192             : 
     193             :   // Optionally add an experimental value (like with RDCs)
     194           4 :   std::vector<double> exppre;
     195           4 :   exppre.resize( nga.size() );
     196             :   unsigned ntarget=0;
     197          10 :   for(unsigned i=0; i<nga.size(); ++i) {
     198          16 :     if( !parseNumbered( "PREINT", i+1, exppre[i] ) ) {
     199             :       break;
     200             :     }
     201           6 :     ntarget++;
     202             :   }
     203             :   bool addexp=false;
     204           4 :   if(ntarget!=nga.size() && ntarget!=0) {
     205           0 :     error("found wrong number of PREINT values");
     206             :   }
     207           4 :   if(ntarget==nga.size()) {
     208             :     addexp=true;
     209             :   }
     210           4 :   if(getDoScore()&&!addexp) {
     211           0 :     error("with DOSCORE you need to set the PREINT values");
     212             :   }
     213             : 
     214             :   // Create neighbour lists
     215           8 :   nl=Tools::make_unique<NeighborList>(gb_lista,ga_lista,false,true,pbc,getPbc(),comm);
     216             : 
     217             :   // Output details of all contacts
     218             :   unsigned index=0;
     219          16 :   for(unsigned i=0; i<nga.size(); ++i) {
     220          12 :     log.printf("  The %uth PRE is calculated using %u equivalent atoms:\n", i, nga[i]);
     221          12 :     log.printf("    %d", ga_lista[index].serial());
     222          12 :     index++;
     223          16 :     for(unsigned j=1; j<nga[i]; j++) {
     224           4 :       log.printf(" %d", ga_lista[index].serial());
     225           4 :       index++;
     226             :     }
     227          12 :     log.printf("\n");
     228             :   }
     229           4 :   tot_size = index;
     230             : 
     231           4 :   if(pbc) {
     232           4 :     log.printf("  using periodic boundary conditions\n");
     233             :   } else {
     234           0 :     log.printf("  without periodic boundary conditions\n");
     235             :   }
     236             : 
     237           8 :   log << " Bibliography" << plumed.cite("Bonomi, Camilloni, Bioinformatics, 33, 3999 (2017)") << "\n";
     238             : 
     239           4 :   if(!getDoScore()) {
     240           8 :     for(unsigned i=0; i<nga.size(); i++) {
     241             :       std::string num;
     242           6 :       Tools::convert(i,num);
     243          12 :       addComponentWithDerivatives("pre-"+num);
     244           6 :       componentIsNotPeriodic("pre-"+num);
     245           6 :       dataValues_.push_back(getPntrToComponent(getNumberOfComponents()-1));
     246             :     }
     247           2 :     if(addexp) {
     248           0 :       for(unsigned i=0; i<nga.size(); i++) {
     249             :         std::string num;
     250           0 :         Tools::convert(i,num);
     251           0 :         addComponent("exp-"+num);
     252           0 :         componentIsNotPeriodic("exp-"+num);
     253           0 :         Value* comp=getPntrToComponent("exp-"+num);
     254           0 :         comp->set(exppre[i]);
     255             :       }
     256             :     }
     257             :   } else {
     258           8 :     for(unsigned i=0; i<nga.size(); i++) {
     259             :       std::string num;
     260           6 :       Tools::convert(i,num);
     261          12 :       addComponent("pre-"+num);
     262           6 :       componentIsNotPeriodic("pre-"+num);
     263           6 :       dataValues_.push_back(getPntrToComponent(getNumberOfComponents()-1));
     264             :     }
     265           8 :     for(unsigned i=0; i<nga.size(); i++) {
     266             :       std::string num;
     267           6 :       Tools::convert(i,num);
     268          12 :       addComponent("exp-"+num);
     269           6 :       componentIsNotPeriodic("exp-"+num);
     270          12 :       Value* comp=getPntrToComponent("exp-"+num);
     271           6 :       comp->set(exppre[i]);
     272             :     }
     273             :   }
     274             : 
     275           4 :   nga_offset_.resize(nga.size(),0);
     276          12 :   for(unsigned i=1; i<nga.size(); i++) {
     277           8 :     nga_offset_[i]=nga_offset_[i-1]+nga[i-1];
     278             :   }
     279             : 
     280           4 :   requestAtoms(nl->getFullAtomList(), false);
     281           4 :   if(getDoScore()) {
     282           2 :     setParameters(exppre);
     283           2 :     Initialise(nga.size());
     284             :   }
     285           4 :   setDerivatives();
     286           4 :   checkRead();
     287           4 : }
     288             : 
     289         350 : void PRE::calculate() {
     290         350 :   std::vector<Vector> deriv(tot_size, Vector{0,0,0});
     291         350 :   std::vector<double> fact(nga.size(), 0.);
     292             : 
     293             :   // cycle over the number of PRE
     294         350 :   #pragma omp parallel for num_threads(OpenMP::getNumThreads())
     295             :   for(unsigned i=0; i<nga.size(); i++) {
     296             :     Tensor dervir;
     297             :     double pre=0;
     298             :     const unsigned index=nga_offset_[i];
     299             :     const double c_aver=constant/static_cast<double>(nga[i]);
     300             :     Value* val=dataValues_[i];
     301             :     // cycle over equivalent atoms
     302             :     for(unsigned j=0; j<nga[i]; j++) {
     303             :       // the first atom is always the same (the paramagnetic group)
     304             :       const unsigned i0=nl->getClosePair(index+j).first;
     305             :       const unsigned i1=nl->getClosePair(index+j).second;
     306             : 
     307             :       Vector distance;
     308             :       if(pbc) {
     309             :         distance=pbcDistance(getPosition(i0),getPosition(i1));
     310             :       } else {
     311             :         distance=delta(getPosition(i0),getPosition(i1));
     312             :       }
     313             : 
     314             :       const double r2=distance.modulo2();
     315             :       const double r6=r2*r2*r2;
     316             :       const double r8=r6*r2;
     317             :       const double tmpir6=c_aver/r6;
     318             :       const double tmpir8=-6.*c_aver/r8;
     319             : 
     320             :       pre += tmpir6;
     321             :       deriv[index+j] = -tmpir8*distance;
     322             :       if(!getDoScore()) {
     323             :         dervir   +=  Tensor(distance,deriv[index+j]);
     324             :       }
     325             :     }
     326             :     double tmpratio;
     327             :     if(!doratio) {
     328             :       tmpratio = pre ; //prova a caso per vedere se lui da problemi
     329             :       fact[i] = 1.; //prova a caso per vedere se lui da problemi
     330             :     } else {
     331             :       tmpratio = rtwo[i]*std::exp(-pre*inept) / (rtwo[i]+pre);
     332             :       fact[i] = -tmpratio*(inept+1./(rtwo[i]+pre));
     333             :     }
     334             :     const double ratio = tmpratio;
     335             :     val->set(ratio) ;
     336             :     if(!getDoScore()) {
     337             :       setBoxDerivatives(val, fact[i]*dervir);
     338             :       for(unsigned j=0; j<nga[i]; j++) {
     339             :         const unsigned i0=nl->getClosePair(index+j).first;
     340             :         const unsigned i1=nl->getClosePair(index+j).second;
     341             :         setAtomsDerivatives(val, i0,  fact[i]*deriv[index+j]);
     342             :         setAtomsDerivatives(val, i1, -fact[i]*deriv[index+j]);
     343             :       }
     344             :     } else {
     345             :       setCalcData(i, ratio);
     346             :     }
     347             :   }
     348             : 
     349         350 :   if(getDoScore()) {
     350             :     /* Metainference */
     351         175 :     Tensor dervir;
     352         175 :     double score = getScore();
     353         175 :     setScore(score);
     354             : 
     355             :     /* calculate final derivatives */
     356         175 :     Value* val=getPntrToComponent("score");
     357         700 :     for(unsigned i=0; i<nga.size(); i++) {
     358         525 :       const unsigned index=nga_offset_[i];
     359             :       // cycle over equivalent atoms
     360        1225 :       for(unsigned j=0; j<nga[i]; j++) {
     361         700 :         const unsigned i0=nl->getClosePair(index+j).first;
     362         700 :         const unsigned i1=nl->getClosePair(index+j).second;
     363             : 
     364         700 :         Vector distance;
     365         700 :         if(pbc) {
     366         700 :           distance=pbcDistance(getPosition(i0),getPosition(i1));
     367             :         } else {
     368           0 :           distance=delta(getPosition(i0),getPosition(i1));
     369             :         }
     370             : 
     371        1400 :         dervir += Tensor(distance,fact[i]*deriv[index+j]*getMetaDer(i));
     372         700 :         setAtomsDerivatives(val, i0,  fact[i]*deriv[index+j]*getMetaDer(i));
     373         700 :         setAtomsDerivatives(val, i1, -fact[i]*deriv[index+j]*getMetaDer(i));
     374             :       }
     375             :     }
     376         175 :     setBoxDerivatives(val, dervir);
     377             :   }
     378         350 : }
     379             : 
     380             : }
     381             : }

Generated by: LCOV version 1.16