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

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-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/Pbc.h"
      25             : #include "tools/Torsion.h"
      26             : #include <cmath>
      27             : #include <limits>
      28             : 
      29             : namespace PLMD {
      30             : namespace isdb {
      31             : 
      32             : //+PLUMEDOC ISDB_COLVAR JCOUPLING
      33             : /*
      34             : Calculates 3J coupling constants for a dihedral angle.
      35             : 
      36             : The J-coupling between two atoms is given by the Karplus relation:
      37             : 
      38             : $$
      39             : ^3J(\theta)=A\cos^2(\theta+\Delta\theta)+B\cos(\theta+\Delta\theta)+C
      40             : $$
      41             : 
      42             : where $A$, $B$ and $C$ are the Karplus parameters and $\Delta\theta$ is an additional constant
      43             : added on to the dihedral angle $\theta$. The Karplus parameters are determined empirically and are dependent
      44             : on the type of J-coupling.
      45             : 
      46             : This collective variable computes the J-couplings for a set of atoms defining a dihedral angle. You can specify
      47             : the atoms involved using the [MOLINFO](MOLINFO.md) notation. You can also specify the experimental couplings using the
      48             :  COUPLING keywords. These will be included in the output. You must choose the type of
      49             : coupling using the type keyword, you can also supply custom Karplus parameters using TYPE=CUSTOM and the A, B, C
      50             : and SHIFT keywords (A, B and C are compulsory with TYPE=CUSTOM, SHIFT is zero by default). You will need to make sure you are using the correct dihedral angle:
      51             : 
      52             : - Ha-N: $\psi$
      53             : - Ha-HN: $\phi$
      54             : - N-C$\gamma$: $\chi_1$
      55             : - CO-C$\gamma$: $\chi_1$
      56             : 
      57             : J-couplings can be used to calculate a Metainference score using the internal keyword DOSCORE and all the options
      58             : of [METAINFERENCE](METAINFERENCE.md) .
      59             : 
      60             : ## Examples
      61             : 
      62             : In the following example we calculate the Ha-N J-coupling from a set of atoms involved in
      63             : dihedral $\psi$ angles in the peptide backbone. We also add the experimental data points and compute
      64             : the correlation and other measures and finally print the results.
      65             : 
      66             : ```plumed
      67             : #SETTINGS MOLFILE=regtest/basic/rt32/helix.pdb
      68             : MOLINFO MOLTYPE=protein STRUCTURE=peptide.pdb
      69             : WHOLEMOLECULES ENTITY0=1-111
      70             : 
      71             : JCOUPLING ...
      72             :     TYPE=HAN
      73             :     ATOMS1=@psi-2 COUPLING1=-0.49
      74             :     ATOMS2=@psi-4 COUPLING2=-0.54
      75             :     ATOMS3=@psi-5 COUPLING3=-0.53
      76             :     ATOMS4=@psi-7 COUPLING4=-0.39
      77             :     ATOMS5=@psi-8 COUPLING5=-0.39
      78             :     LABEL=jhan
      79             : ... JCOUPLING
      80             : 
      81             : jhanst: STATS ARG=(jhan\.j-.*) PARARG=(jhan\.exp-.*)
      82             : 
      83             : PRINT ARG=jhanst.*,jhan.* FILE=COLVAR STRIDE=100
      84             : ```
      85             : 
      86             : */
      87             : //+ENDPLUMEDOC
      88             : 
      89             : class JCoupling :
      90             :   public MetainferenceBase {
      91             : private:
      92             :   // the components with the back-calculated data
      93             :   std::vector<Value*> dataValues_;
      94             :   bool pbc;
      95             :   enum { HAN, HAHN, CCG, NCG, CUSTOM };
      96             :   unsigned ncoupl_;
      97             :   double ka_=0.0;
      98             :   double kb_=0.0;
      99             :   double kc_=0.0;
     100             :   double kshift_=0.0;
     101             : 
     102             : public:
     103             :   static void registerKeywords(Keywords& keys);
     104             :   explicit JCoupling(const ActionOptions&);
     105             :   void calculate() override;
     106             : };
     107             : 
     108             : PLUMED_REGISTER_ACTION(JCoupling, "JCOUPLING")
     109             : 
     110          30 : void JCoupling::registerKeywords(Keywords& keys) {
     111          30 :   MetainferenceBase::registerKeywords(keys);
     112          30 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
     113          30 :   keys.add("numbered", "ATOMS", "the 4 atoms involved in each of the bonds for which you wish to calculate the J-coupling. "
     114             :            "Keywords like ATOMS1, ATOMS2, ATOMS3,... should be listed and one J-coupling will be "
     115             :            "calculated for each ATOMS keyword you specify.");
     116          60 :   keys.reset_style("ATOMS", "atoms");
     117          30 :   keys.add("compulsory", "TYPE", "Type of J-coupling to compute (HAN,HAHN,CCG,NCG,CUSTOM)");
     118          30 :   keys.add("optional", "A", "Karplus parameter A (compulsory with TYPE=CUSTOM)");
     119          30 :   keys.add("optional", "B", "Karplus parameter B (compulsory with TYPE=CUSTOM)");
     120          30 :   keys.add("optional", "C", "Karplus parameter C (compulsory with TYPE=CUSTOM)");
     121          30 :   keys.add("optional", "SHIFT", "Angle shift in radians (used with TYPE=CUSTOM, zero by default)");
     122          30 :   keys.add("numbered", "COUPLING", "Add an experimental value for each coupling");
     123          60 :   keys.addOutputComponent("j", "default","scalar", "the calculated J-coupling");
     124          60 :   keys.addOutputComponent("exp", "COUPLING","scalar", "the experimental J-coupling");
     125          30 : }
     126             : 
     127          26 : JCoupling::JCoupling(const ActionOptions&ao):
     128             :   PLUMED_METAINF_INIT(ao),
     129          26 :   pbc(true) {
     130          26 :   bool nopbc = !pbc;
     131          26 :   parseFlag("NOPBC", nopbc);
     132          26 :   pbc =! nopbc;
     133             : 
     134             :   // Read in the atoms
     135          26 :   std::vector<AtomNumber> t, atoms;
     136          26 :   for (int i = 1; ; ++i) {
     137         372 :     parseAtomList("ATOMS", i, t );
     138         186 :     if (t.empty()) {
     139             :       break;
     140             :     }
     141             : 
     142         160 :     if (t.size() != 4) {
     143             :       std::string ss;
     144           0 :       Tools::convert(i, ss);
     145           0 :       error("ATOMS" + ss + " keyword has the wrong number of atoms");
     146             :     }
     147             : 
     148             :     // This makes the distance calculation easier later on (see Torsion implementation)
     149         160 :     atoms.push_back(t[0]);
     150         160 :     atoms.push_back(t[1]);
     151         160 :     atoms.push_back(t[1]);
     152         160 :     atoms.push_back(t[2]);
     153         160 :     atoms.push_back(t[2]);
     154         160 :     atoms.push_back(t[3]);
     155         160 :     t.resize(0);
     156         160 :   }
     157             : 
     158             :   // We now have 6 atoms per datapoint
     159          26 :   ncoupl_ = atoms.size()/6;
     160             : 
     161             :   // Parse J-Coupling type, this will determine the Karplus parameters
     162             :   unsigned jtype_ = CUSTOM;
     163             :   std::string string_type;
     164          26 :   parse("TYPE", string_type);
     165          26 :   if(string_type == "HAN") {
     166             :     jtype_ = HAN;
     167          25 :   } else if(string_type == "HAHN") {
     168             :     jtype_ = HAHN;
     169           3 :   } else if(string_type == "CCG") {
     170             :     jtype_ = CCG;
     171           2 :   } else if(string_type == "NCG") {
     172             :     jtype_ = NCG;
     173           1 :   } else if(string_type == "CUSTOM") {
     174             :     jtype_ = CUSTOM;
     175             :   } else {
     176           0 :     error("Unknown J-coupling type!");
     177             :   }
     178             : 
     179             :   // Optionally add an experimental value (like with RDCs)
     180          26 :   std::vector<double> coupl;
     181          26 :   coupl.resize( ncoupl_ );
     182             :   unsigned ntarget=0;
     183          99 :   for(unsigned i=0; i<ncoupl_; ++i) {
     184         176 :     if( !parseNumbered( "COUPLING", i+1, coupl[i] ) ) {
     185             :       break;
     186             :     }
     187          73 :     ntarget++;
     188             :   }
     189             :   bool addcoupling=false;
     190          26 :   if(ntarget!=ncoupl_ && ntarget!=0) {
     191           0 :     error("found wrong number of COUPLING values");
     192             :   }
     193          26 :   if(ntarget==ncoupl_) {
     194             :     addcoupling=true;
     195             :   }
     196          26 :   if(getDoScore()&&!addcoupling) {
     197           0 :     error("with DOSCORE you need to set the COUPLING values");
     198             :   }
     199             : 
     200             :   // For custom types the Karplus parameters must be given (the shift is zero by default)
     201          26 :   if (jtype_ == CUSTOM) {
     202             :     constexpr double unset = std::numeric_limits<double>::quiet_NaN();
     203           1 :     ka_ = kb_ = kc_ = unset;
     204           1 :     kshift_ = 0.0;
     205           1 :     parse("A", ka_);
     206           1 :     parse("B", kb_);
     207           1 :     parse("C", kc_);
     208           1 :     parse("SHIFT", kshift_);
     209           1 :     if(std::isnan(ka_) || std::isnan(kb_) || std::isnan(kc_)) {
     210           0 :       error("with TYPE=CUSTOM the Karplus parameters A, B and C must be given");
     211             :     }
     212             :   }
     213             : 
     214          26 :   log << "  Bibliography ";
     215          26 :   log<<plumed.cite("Bonomi, Camilloni, Bioinformatics, 33, 3999 (2017)");
     216             : 
     217             :   // Set Karplus parameters
     218          26 :   switch (jtype_) {
     219           1 :   case HAN:
     220           1 :     ka_ = -0.88;
     221           1 :     kb_ = -0.61;
     222           1 :     kc_ = -0.27;
     223           1 :     kshift_ = pi / 3.0;
     224           1 :     log << plumed.cite("Wang A C, Bax A, J. Am. Chem. Soc. 117, 1810 (1995)");
     225           1 :     log<<"\n";
     226           1 :     log.printf("  J-coupling type is HAN, with A: %f, B: %f, C: %f, angle shift: %f\n", ka_, kb_, kc_, kshift_);
     227             :     break;
     228          22 :   case HAHN:
     229          22 :     ka_ = 7.09;
     230          22 :     kb_ = -1.42;
     231          22 :     kc_ = 1.55;
     232          22 :     kshift_ = -pi / 3.0;
     233          22 :     log << plumed.cite("Hu J-S, Bax A, J. Am. Chem. Soc. 119, 6360 (1997)");
     234          22 :     log<<"\n";
     235          22 :     log.printf("  J-coupling type is HAHN, with A: %f, B: %f, C: %f, angle shift: %f\n", ka_, kb_, kc_, kshift_);
     236             :     break;
     237           1 :   case CCG:
     238           1 :     ka_ = 2.31;
     239           1 :     kb_ = -0.87;
     240           1 :     kc_ = 0.55;
     241           1 :     kshift_ = (2.0 * pi) / 3.0;
     242           1 :     log << plumed.cite("Perez C, Löhr F, Rüterjans H, Schmidt J, J. Am. Chem. Soc. 123, 7081 (2001)");
     243           1 :     log<<"\n";
     244           1 :     log.printf("  J-coupling type is CCG, with A: %f, B: %f, C: %f, angle shift: %f\n", ka_, kb_, kc_, kshift_);
     245             :     break;
     246           1 :   case NCG:
     247           1 :     ka_ = 1.29;
     248           1 :     kb_ = -0.49;
     249           1 :     kc_ = 0.37;
     250           1 :     kshift_ = 0.0;
     251           1 :     log << plumed.cite("Perez C, Löhr F, Rüterjans H, Schmidt J, J. Am. Chem. Soc. 123, 7081 (2001)");
     252           1 :     log<<"\n";
     253           1 :     log.printf("  J-coupling type is NCG, with A: %f, B: %f, C: %f, angle shift: %f\n", ka_, kb_, kc_, kshift_);
     254             :     break;
     255           1 :   case CUSTOM:
     256           1 :     log<<"\n";
     257           1 :     log.printf("  J-coupling type is custom, with A: %f, B: %f, C: %f, angle shift: %f\n", ka_, kb_, kc_, kshift_);
     258             :     break;
     259             :   }
     260             : 
     261         186 :   for (unsigned i = 0; i < ncoupl_; ++i) {
     262         160 :     log.printf("  The %uth J-Coupling is calculated from atoms : %d %d %d %d.",
     263         160 :                i+1, atoms[6*i].serial(), atoms[6*i+1].serial(), atoms[6*i+3].serial(), atoms[6*i+5].serial());
     264         160 :     if (addcoupling) {
     265          73 :       log.printf(" Experimental J-Coupling is %f.", coupl[i]);
     266             :     }
     267         160 :     log.printf("\n");
     268             :   }
     269             : 
     270          26 :   if (pbc) {
     271           0 :     log.printf("  using periodic boundary conditions\n");
     272             :   } else {
     273          26 :     log.printf("  without periodic boundary conditions\n");
     274             :   }
     275             : 
     276          26 :   if(!getDoScore()) {
     277         102 :     for (unsigned i = 0; i < ncoupl_; i++) {
     278             :       std::string num;
     279          87 :       Tools::convert(i, num);
     280         174 :       addComponentWithDerivatives("j-" + num);
     281          87 :       componentIsNotPeriodic("j-" + num);
     282          87 :       dataValues_.push_back(getPntrToComponent(getNumberOfComponents()-1));
     283             :     }
     284             :   } else {
     285          84 :     for (unsigned i = 0; i < ncoupl_; i++) {
     286             :       std::string num;
     287          73 :       Tools::convert(i, num);
     288         146 :       addComponent("j-" + num);
     289          73 :       componentIsNotPeriodic("j-" + num);
     290          73 :       dataValues_.push_back(getPntrToComponent(getNumberOfComponents()-1));
     291             :     }
     292             :   }
     293             : 
     294          26 :   if (addcoupling||getDoScore()) {
     295          84 :     for (unsigned i = 0; i < ncoupl_; i++) {
     296             :       std::string num;
     297          73 :       Tools::convert(i, num);
     298         146 :       addComponent("exp-" + num);
     299          73 :       componentIsNotPeriodic("exp-" + num);
     300          73 :       Value* comp = getPntrToComponent("exp-" + num);
     301          73 :       comp->set(coupl[i]);
     302             :     }
     303             :   }
     304             : 
     305          26 :   requestAtoms(atoms, false);
     306          26 :   if(getDoScore()) {
     307          11 :     setParameters(coupl);
     308          11 :     Initialise(ncoupl_);
     309             :   }
     310          26 :   setDerivatives();
     311          26 :   checkRead();
     312          26 : }
     313             : 
     314         136 : void JCoupling::calculate() {
     315         136 :   if (pbc) {
     316           0 :     makeWhole();
     317             :   }
     318         136 :   std::vector<Vector> deriv(ncoupl_*6);
     319         136 :   std::vector<double> j(ncoupl_,0.);
     320             : 
     321         136 :   #pragma omp parallel num_threads(OpenMP::getNumThreads())
     322             :   {
     323             :     #pragma omp for
     324             :     // Loop through atoms, with steps of 6 atoms (one iteration per datapoint)
     325             :     for (unsigned r=0; r<ncoupl_; r++) {
     326             :       // Index is the datapoint index
     327             :       unsigned a0 = 6*r;
     328             : 
     329             :       // 6 atoms -> 3 vectors
     330             :       Vector d0 = delta(getPosition(a0+1), getPosition(a0));
     331             :       Vector d1 = delta(getPosition(a0+3), getPosition(a0+2));
     332             :       Vector d2 = delta(getPosition(a0+5), getPosition(a0+4));
     333             : 
     334             :       // Calculate dihedral with 3 vectors, get the derivatives
     335             :       Vector dd0, dd1, dd2;
     336             :       PLMD::Torsion t;
     337             :       double torsion = t.compute(d0, d1, d2, dd0, dd1, dd2);
     338             : 
     339             :       // Calculate the Karplus relation and its derivative
     340             :       double theta = torsion + kshift_;
     341             :       double cos_theta = std::cos(theta);
     342             :       double sin_theta = std::sin(theta);
     343             :       j[r] = ka_*cos_theta*cos_theta + kb_*cos_theta + kc_;
     344             :       double derj = -2.*ka_*sin_theta*cos_theta - kb_*sin_theta;
     345             : 
     346             :       dd0 *= derj;
     347             :       dd1 *= derj;
     348             :       dd2 *= derj;
     349             : 
     350             :       if(getDoScore()) {
     351             :         // the j-# components have no derivatives here, they only report the value
     352             :         dataValues_[r]->set(j[r]);
     353             :         setCalcData(r, j[r]);
     354             :       }
     355             :       deriv[a0] =  dd0;
     356             :       deriv[a0+1] = -dd0;
     357             :       deriv[a0+2] =  dd1;
     358             :       deriv[a0+3] = -dd1;
     359             :       deriv[a0+4] =  dd2;
     360             :       deriv[a0+5] = -dd2;
     361             :     }
     362             :   }
     363             : 
     364         136 :   if(getDoScore()) {
     365             :     /* Metainference */
     366          66 :     double score = getScore();
     367          66 :     setScore(score);
     368             : 
     369             :     /* calculate final derivatives */
     370          66 :     Tensor virial;
     371          66 :     Value* val=getPntrToComponent("score");
     372         504 :     for (unsigned r=0; r<ncoupl_; r++) {
     373         438 :       const unsigned a0 = 6*r;
     374         438 :       setAtomsDerivatives(val, a0, deriv[a0]*getMetaDer(r));
     375         438 :       setAtomsDerivatives(val, a0+1, deriv[a0+1]*getMetaDer(r));
     376         438 :       setAtomsDerivatives(val, a0+2, deriv[a0+2]*getMetaDer(r));
     377         438 :       setAtomsDerivatives(val, a0+3, deriv[a0+3]*getMetaDer(r));
     378         438 :       setAtomsDerivatives(val, a0+4, deriv[a0+4]*getMetaDer(r));
     379         438 :       setAtomsDerivatives(val, a0+5, deriv[a0+5]*getMetaDer(r));
     380         876 :       virial-=Tensor(getPosition(a0), deriv[a0]*getMetaDer(r));
     381         876 :       virial-=Tensor(getPosition(a0+1), deriv[a0+1]*getMetaDer(r));
     382         876 :       virial-=Tensor(getPosition(a0+2), deriv[a0+2]*getMetaDer(r));
     383         876 :       virial-=Tensor(getPosition(a0+3), deriv[a0+3]*getMetaDer(r));
     384         876 :       virial-=Tensor(getPosition(a0+4), deriv[a0+4]*getMetaDer(r));
     385         876 :       virial-=Tensor(getPosition(a0+5), deriv[a0+5]*getMetaDer(r));
     386             :     }
     387          66 :     setBoxDerivatives(val, virial);
     388             :   } else {
     389         522 :     for (unsigned r=0; r<ncoupl_; r++) {
     390         452 :       const unsigned a0 = 6*r;
     391         452 :       Value* val=dataValues_[r];
     392         452 :       val->set(j[r]);
     393         452 :       setAtomsDerivatives(val, a0, deriv[a0]);
     394         452 :       setAtomsDerivatives(val, a0+1, deriv[a0+1]);
     395         452 :       setAtomsDerivatives(val, a0+2, deriv[a0+2]);
     396         452 :       setAtomsDerivatives(val, a0+3, deriv[a0+3]);
     397         452 :       setAtomsDerivatives(val, a0+4, deriv[a0+4]);
     398         452 :       setAtomsDerivatives(val, a0+5, deriv[a0+5]);
     399         452 :       Tensor virial;
     400         904 :       virial-=Tensor(getPosition(a0), deriv[a0]);
     401         904 :       virial-=Tensor(getPosition(a0+1), deriv[a0+1]);
     402         904 :       virial-=Tensor(getPosition(a0+2), deriv[a0+2]);
     403         904 :       virial-=Tensor(getPosition(a0+3), deriv[a0+3]);
     404         904 :       virial-=Tensor(getPosition(a0+4), deriv[a0+4]);
     405         452 :       virial-=Tensor(getPosition(a0+5), deriv[a0+5]);
     406         452 :       setBoxDerivatives(val, virial);
     407             :     }
     408             :   }
     409         136 : }
     410             : 
     411             : }
     412             : }

Generated by: LCOV version 1.16