How to add a new MultiColvar

As you are no doubt aware within plumed 2 you can calculate multiple instances of a collective coorinate from a single line in the input file.

One can then calculate functions such as the minimum, number less than,... from the resulting distribution of collective variables. To create these kinds of collective variables we use the functionality implmented in PLMD::multicolvar::MultiColvar. In fact in writing a single PLMD::multicolvar::MultiColvar you actually write many CVs at once as the minimum of a distribution, the number less than and so on come with no additional effort on your part.

To better understand how to go about writing a new MultiColvar examine the interior of one of the existing MultiColvars, e.g. PLMD::multicolvar::Distances or PLMD::multicolvar::CoordinationNumbers. In fact a good way to start is to copy one of these files as certain features (e.g. the fact that you have to include MultiColvar.h and ActionRegister.h and that all your code should be inside the namespace PLMD) never change.

The essential idea with multicolvar is that you can reuse the same functionality within many collective variables. In particular, you can exploit the same parallelisation strategy in a wide variety of different collective variables. Similarly one can use the same implementation of link cells in a wide variety of different cvs. Lastly, one can create functions of multicolvars and thus use the calculated cvs in a wide variety of different contexts. If you are reading this you are probably aware of this and are (hopefully) impressed by the flexibility of this objects.

The reason for this flexibility is that many CVs can be calculated using a variant on the following formula:

\[ s = \sum_i g[f(\{X\}_i)] \]

where \(g\) and \(f\) are functions and the sum runs over a sets of atomic positions \(\{X\}_i\). In the above formula the sum and a set of likely \(g\) functions are implemented within the base classes base classes of multicolvar PLMD::multicolvar::MultiColvarBase, PLMD::multicolvar::MultiColvar, PLMD::multicolvar::MultiColvarFunction and PLMD::multicolvar::BridgedMultiColvarFunction. When you implement a new multicolvar what you thus need to do is write code to calculate the function \(f\) from a set of atomic positions. One way to understand how to do this would be to look through these classes and attempt to understand the code contained within them. The following is an alternative. It provides a rather prescriptive description as to how to implement a new multicolvar. I hope this helps but I am keenly aware that any description is bound to be incomplete so if you have questions please do email the user list to ask.

Adding documentation for your measure

The first thing you will have to change is the documentation. As discussed on the Creating plumed documentation page of this manual the documentation is created using Doxygen. You are implementing a cv so your PLMEDOC line should read:

//+PLUMEDOC MCOLVAR MYCVKEYWORD 

Your documentation should contain a description of what your CV calculates and some examples. You do not need to write a description of the input syntax as that will be generated automatically. For more information on how to write documentation go to Creating plumed documentation.

Creating the class

The first step in writing the executable code for your CV is to create a class that calculates your CV. The declaration for your class should appear after the documentation and will look something like:

class MyNewMultiColvar : public MultiColvar {
private:
   // Declare all the variables you need here  
public:
  static void registerKeywords( Keywords& keys );
  MyNewMultiColvar(const ActionOptions&);
  virtual double compute( const unsigned& tindex, AtomValuePack& myatoms );
  bool isPeriodic(){ return false; }
};

PLUMED_REGISTER_ACTION(MyNewMultiColvar,"MYCVKEYWORD")

This new class (MyNewMultiColvar) inherits from MultiColvar and so contains much of the functionality we require already. Furthermore, by calling PLUMED_REGISTER_ACTION we have ensured that whenever the keyword MYCVKEYWORD is found in the input file an object of type MyNewMultiColvar will be generated and hence that your new CV will be calculated wherever it is required.

The four functions that are defined in the above class (registerKeywords, the constructor, isPeriodic and compute) are mandatory. Without these functions the code will not compile and run. Writing your new CV is simply a matter of writing these four subroutines.

RegisterKeywords

RegisterKeywords is the routine that is used by plumed to create the remainder of the documentation. As much of this documentation is created inside the MultiColvar class itself the first line of your new registerKeywords routine must read:

MultiColvar::registerKeywords( keys )

as this creates all the documentation for the features that are part of all PLMD::MultiColvar objects. To see how to create documentation that describes the keywords that are specific to your particular CV please read Creating plumed documentation.

Reading the atoms

Creating the lists of atoms involved in each of the colvars in a PLMD::MultiColvar is quite involved as you have to generate all the sets of atoms for which you want to calculate your \(f\) function. What is more this is an area where we feel it is important to maintain some consistency in the input. Many of the multicolvars that are currently implemented in the code thus use one or multiple of the following keywords:

ATOMS The atoms keyword specifies that one collective coordinate is to be calculated for each set of atoms specified. Hence, for MultiColvarDistance the command DISTANCES ATOMS1=1,2 ATOMS2=2,3 ATOMS3=3,4 specifies that three distances should be calculated.
GROUP GROUPA GROUPB The GROUP keyword is used for quantities such as distances and angles. A single GROUP specifies that a CV should be calculated for each distinct set of atoms that can be made from the group. Hence, MUTIDISTANCE GROUP=1,2,3 specifies that three distances should be calculated (1,2), (1,3) and (2,3). If there is a GROUPA and GROUPB one CV is calculated for each set of atoms that includes at least one member from each group. Thus MULTIDISTANCE GROUPA=1 GROUPB=2,3 calculates two distance (1,2) and (1,3).
SPECIES SPECIESA SPECIESB The SPECIES keywords is used for quantities like coordination numbers. The way this works is best explained using an example. Imagine a user working on NaCl wishes to calculate the average coordination number of the sodium ions in his/her system. To do this he/she uses COORDINATIONNUMBER SPECIES=1-100 which tells plumed that atoms 1-100 are the sodium ions. To calculate the average coordination number plumed calculates 100 coordination numbers (one for each sodium) and averages. Obviously, each of these coordination numbers involves the full set of 100 sodium atoms. By contrast if the user wanted to calculate the coordination number of Na with Cl he/she would do COORDINATIONNUMBER SPECIESA=1-100 SPECIESB=101-200, where obviously 101-200 are the chlorine ions. Each of these 100 heteronuclear coordination numbers involves the full set of atoms speciefied using SPECIESB and one of the ions speciefied by SPECIESA.

If you wish to use the ATOMS or SPECIES options above you can do so by adding:

keys.use("ATOMS");

or

keys.use("SPECIES"); keys.use("SPECIESA"); keys.use("SPECIESB")

The documentation for these keywords will then be generated automatically. If you wish to use the something like the GROUP keyword above then you need to register GROUP. Here is how this is done within PLMD::multicolvar::Distances

keys.add("atoms-1","GROUP","Calculate the distance between each distinct pair of atoms in the group");
keys.add("atoms-2","GROUPA","Calculate the distances between all the atoms in GROUPA and all "
                            "the atoms in GROUPB. This must be used in conjuction with GROUPB.");
keys.add("atoms-2","GROUPB","Calculate the distances between all the atoms in GROUPA and all the atoms "
                            "in GROUPB. This must be used in conjuction with GROUPA.");

To be clear, if these keywords are registered the actual readin of the atoms is looked after by the method PLMD::multicolvar::MultiColvar::readAtoms. However, there are a number of things I would like to point out about the registering syntax above. Firstly, writing atoms-1 for GROUP and atoms-2 for GROUPA and GROUPB ensures nice formatting in the manual. In particular it is made clear to the user that these are two distinct options for specificying the atoms in the input. Notice also that we use similar commands to register keywords in PLMD::multicolvar::Angles:

keys.add("atoms-1","GROUP","Calculate angles for each distinct set of three atoms in the group");
keys.add("atoms-2","GROUPA","A group of central atoms about which angles should be calculated");
keys.add("atoms-2","GROUPB","When used in conjuction with GROUPA this keyword instructs plumed "
                            "to calculate all distinct angles involving one atom from GROUPA "
                            "and two atoms from GROUPB. The atom from GROUPA is the central atom.");
keys.add("atoms-3","GROUPC","This must be used in conjuction with GROUPA and GROUPB.  All angles "
                            "involving one atom from GROUPA, one atom from GROUPB and one atom from "
                            "GROUPC are calculated. The GROUPA atoms are assumed to be the central "
                            "atoms");

Again though the actual reading of the GROUP, GROUPA, GROUPB and GROUPC keywords are looked after within PLMD::multicolvar::MultiColvar::readAtoms

In some cases you may wish to use keywords other than ATOMS, GROUP, SPECIES etc. In these cases the following sets of routines may be useful:

PLMD::multicolvar::MultiColvar::readAtomsLikeKeyword This is used to read in the ATOMS keyword. By using this function you can change the ATOMS keyword to whatever you wish. PLMD::multicolvar::MultiColvar::readTwoGroups This is used to read in the GROUPA and GROUPB keywords. When you use this funciton you can change GROUPA and GROUPB to whatever you desire PLMD::multicolvar::MultiColvar::readThreeGroups This is used to read in the GROUPA, GROUPB and GROUPC keywords. When you use this funciton you can change GROUPA, GROUPB and GROUPC to whatever you desire. For an example see PLMD::multicolvar::Bridge PLMD::multicolvar::MultiColvar::readSpeciesKeyword This is used to read in SPECIESA and SPECIESB keywords. Once again you can use this funciton and use different keywords.

The constructor

Here is the constructor for the COORDINATIONNUMBERS multicolvar:

CoordinationNumbers::CoordinationNumbers(const ActionOptions&ao):
PLUMED_MULTICOLVAR_INIT(ao)
{
  // Read in the switching function
  std::string sw, errors; parse("SWITCH",sw);
  if(sw.length()>0){
     switchingFunction.set(sw,errors);
     if( errors.length()!=0 ) error("problem reading SWITCH keyword : " + errors );
  } else {
     double r_0=-1.0, d_0; int nn, mm;
     parse("NN",nn); parse("MM",mm);
     parse("R_0",r_0); parse("D_0",d_0);
     if( r_0<0.0 ) error("you must set a value for R_0");
     switchingFunction.set(nn,mm,r_0,d_0);
  }
  log.printf("  coordination of central atom and those within %s\n",( switchingFunction.description() ).c_str() );
  // Set the link cell cutoff
  setLinkCellCutoff( switchingFunction.get_dmax() );
  rcut2 = switchingFunction.get_dmax()*switchingFunction.get_dmax();

  // Read in the atoms
  int natoms=2; readAtoms( natoms );
  // And setup the ActionWithVessel
  checkRead();
}

This code does three things:

Within the constructor of a multicolvar there must be a call to PLMD::multicolvar::MultiColvar::readAtoms even if you choose to read in the atoms using one of the methods described in the table above.

As you can see from the above example the majority of the constructor will be concerned with reading keywords from the input directive. More information and guidance on PLUMED's parsing functionality can be found in Parsing functionality.

Implementing isPeriodic

Some \(f\) functions (e.g. TORSIONS) have a co-domain that is periodic the majority, however, do not. If the \(f\) function of your new multicolvar falls into this category your isPeriodic function will be as follows:

bool isPeriodic(){ return false; }

If your \(f\) function does have a periodic co-domain then you will need to have methods like the following:

bool isPeriodic(){ return true; }
void retrieveDomain( std::string& min, std::string& max ){ min="-pi"; max="pi"; }

As is hopefully obvious the retrieveDomain should return the minimum and maximum values (as strings) that your function, \(f\), can take. The above example is taken from the code in PLMD::multicolvar::Torsions.

Compute

Compute is the routine that actually calculates the function \(f\) and this function's derivatives from the set of atomic positions \(\{X\}\). This function should have as its return value the value of \(f\). The atomic positions can be extracted by using the PLMD::multicolvar::AtomValuePack::getPosition method that belongs to the PLMD::multicolvar::AtomValuePack class that is passed to the compute method. One can then calculate the distance vector connecting two atoms by using PLMD::multicolvar::MultiColvarBase::getSeparation. Once you have calculated the derivatives of your \(f\) function you can store these within the PLMD::multicolvar::AtomValuePack by using the methods PLMD::multicolvar::AtomValuePack::addAtomsDerivatives and PLMD::multicolvar::AtomValuePack::addBoxDerivatives.