Plumed 2, unlike its predecessor plumed 1, which was written in plain C, is written in C++.
C++, unlike C, fortran and many of the other languages that are commonly used in the atomistic simulation community, is an object oriented programming language. As such the way things are done in plumed may feel unfamiliar to developers in the scientific community who, if our experience is anything to go by, are more used to programming in non-object oriented languages. For this reason we have tried in what follows to explain how we have used the features of object oriented programming in plumed 2. We hope that this guide is helpful and appologize in advance to any developers who feel patronized.
The main objective in object oriented programming is to write code that is more resilient to bugs. There are two ways that object oriented programing allows us to acchieve these aims:
To be clear though object oriented programming does not allow us to do things that would be impossible with other programming languages. All programs perform some set of mathematical operations and any programming language can be used to implement these mathematical operations. The only advantage of C++ is that the advanced, object-oriented features of the language make implementing things more straighforward.
As you are no doubt aware, in C one can create structures to order the variables in your code. A naive way of thinking about the objects, or more correctly classes, that one uses in C++ is that these are structures that also contain functions. This is useful for making neat header files as the parameters are kept near the functions. However, at this level of thinking the C++ way of doing things:
class fclass { bool param0; int param1,param2; double param3; double f( double x ); };
is not much better than the C way of doing things:
struct fparams { bool param0; int param1,param2; double param3; }; double f( double x, struct fparams myparams );
Nevertheless for reasons that will hopefully become clear as you read this document every bias, colvar and function that is implemented in plumed 2 is inside its own separate class.
The variables in a C struct can be accessed anywhere in the code - any function in the code can copy the information from a structure or change the values of the variables in the structure. This was a particularly fun feature of plumed 1.0, every function in the old code could change any of the variables in the code! Obviously this causes problems as new functions can accidentally change the values of some variable in a widely used structure that should never have been changed. As one can imagine this can cause drastic problems. To prevent errors like this C++ provides a set of functionalities to allow one to specify what any given function can do to the members of a class. This is not possible in C and it was the ability to use this functionality to create flexible, easily-extendable code that motivated our choice of C++ for plumed 2. The example class below shows how this is done in practise:
class myclass{ private: bool b; //--This can only be accessed when you are in one of the methods in the class public: int i; //--This can be acessed by anywhere in the code }; \section constructors Constructors As someone who learnt to program in fortran it was this aspect of C++, more than any other, that confused me the most. In actually though it is rather simple and I don't really know why I was confused. In essence every class must contain some method for creating it. This class should set the initial values of all the variables inside the class. Obviously the functions (or more correctly methods) that the class contains cannot be used untill an instance of the class has been created using the constructor. An example of how all this works in practise is given below: \verbatim class myclass{ private: bool b; int i; double d; public: myclass( bool bb, int ii, double dd ) : b(bb), i(ii), d(dd) {} static double g(int j); double f( double x ); }; // Here there are currently no instances of myclass and so I cannot run f // I can run g however as it is a static member of the class - I run it using double d=myclass::g(j); // Now I create an instance of the class using the constructor myclass thisIsTheInstance(false, 3, 6.5); // so that I can run the method called f double s=thisIsTheInstance.f(4.0);
In plumed 2 all the lines in the input file are read in inside the constructors. This ensures that the parameters inside any given method are set correctly from the outset.
Addition, subtraction, multiplication, division and so on are all functions (they are obviously not variables). We usually don't think of them as functions however because we use these operations all the time. C++ recognizes that the short cuts of +, -, *, / and so on are very useful. It thus allows one to define operators in our new classes that explain to the compiler what a given symbol means for a given class. Among other things we can define:
We do not use this extensively in plumed 2 but it does occasionally appear.
There are various ways that one can include the functionality of one class inside a second class. By far the simplest is to create an instance of class 1 inside class 2 as shown below:
class class1 { private: double d1,d2,d3; public: class1(); void f(double x); }; class class2 { private: class1 myclass; public: class2(); // The methods of class 2 here };
This is really simple one includes a class in this way in exactly the same way that one includes a double, int or whatever variable.
This kind of inclusion is used extensively in plumed 1.0 and there are a huge number of classes that you can re-use in this way to create new colvars, functions or biases. For a full list of the classes that are available see Tool Box.
There is an alternate way of reusing the functionality from one class in a second class that is available in C++. This method is called inheritance and it offers some advantages over simply including class A inside class B as described above. To create a class called B that inherits from A one writes:
class B : public A { // Contents of class B };
One advantage of this method over inclusion is that I can use protected members to more closely control what members of A class B is allowed to access. Hence, rather than simply having private and public members I now have:
public | These members can be accessed by anyone |
protected | These members can only be accessed by the methods of class A and class B |
private | These members can only by accessed by the methods of class A (not by class B) |
In addition, I can use inheritance to treat pointers to objects of class B as if they were pointers to objects of class A. In other words, if I create an object of type B I can convert it to an object of type A using dynamic_cast as shown below:
B* mynewB=new B(); // This is a special way of calling the constructor so you get a pointer A* BpretendingToBeA=dynamic_cast<A*>(mynewB);
All the colvars and free energy methods of plumed use inheritence. In fact all these methods are built on a single base class called PLMD::Action. This class contains all the functionality for reading stuff from input, the stuff for controlling the dependencies Actions and a set of controls that decide which actions are performed when. All the functionality for the different methods is then built on this root. As you can see (PLMD::Action) the inheritence tree for the code is quite complicated. However, in practise if you are implementing a CV, function, bias or virtual atom the correct start point is with one of the classes listed on this page Base classes for CVs, functions, biases, etc. all of which contain detailed descriptions of how to use them.
Immediately above the PLMD::Action root of the inheritance tree in plumed there is a very complicated looking layer in the inheritance structure of the code. This layer looks ugly because in this layer we are using multiple inheritance - the classes in the layer above inherit from multiple classes simultaneously. This way of incorporating functionality from classes is unique to C++ and brings with it a special set of difficulties in programming. Its great advantage is though that one can create classes that incorporate bring a set of particular attributes. This will perhaps be most clear if you look at what each of the classes in the multiple inheritence layer is doing (see Classes for multiple inheritance) and see how these functionalities are used in Colvars, Functions and Biases. Please be aware that, unless you are doing something really wacky, you should be able to implement whatever you need to implement without writing classes that take advantage of multiple inheritance. Furthermore, you should not need to touch the classes in this region of the code. The information here is there for completeness only. If you feel you really must change something in this part of the code please contact the developers before doing anything.
Polymorhpism is a way of using the same code to do many different things. As an example consider a Matrix. The elements of a Matrix can be ints, doubles, floats or even some fancy new class but we would still want the operator (i,j) to return the element in row i and column j. That is to say the operator (const int i, const int j) of a matrix is indepedent of what is actually inside the matrix. Using C++ we can use so called template classes to implement thse kinds of things and can then re-use them to do an enormous variety of different operations. To see how this works in practise take a look at PLMD::Matrix, which is a working version of our Matrix example. Be aware that all the routines in a template class must be inside the header file. To use a template within the code you declare it as follows:
Matrix<double> mat; // This is a matrix of doubles
The most common way we use this kind of functionality in plumed 2 is when we take advantage of the features that are available in the C++ standard library. For more details on the standard library visit http://www.cplusplus.com/reference/
When you run a calculation with plumed the code calculates a number of CVs. The bias and the forces due to the bias are then calculated and in the final step these forces are propegated back onto the atoms using the chain rule. For example PLMD::colvar::Distance contains the function that calculates a distance between atoms, while PLMD::bias::MetaD contains the function for doing metadynamics. What may thus seem remarkable to the programmer unfamiliar with C++ is that the class that calls the functions that calculate the CVs, biases and so on only uses PLMD::Action. To make that clear it looks like the code can calculate the distances between atoms without ever calling any of the routines from PLMD::colvar::Distance!
We can program in this way because we take advantage of dynamic polymorhpism. If you look at the documenation for PLMD::Action you will see that the method PLMD::Action::calculate is declare inside PLMD::Action as:
virtual void calculate()=0;
This kind of declaration promises two things to a class:
The great advantage of declaring calculate() inside PLMD::Action in this way is that the calculate routine that we declare in the derived class is a member of PLMD::Action. We thus can thus write a class for doing all the bussiness of plumed in the manner described previously.
One problem of including classes inside other classes in C++ is that this enforces one to include one .h file into another one, thus leading to a large set of objects needing to be recompiled just because a single .h file was touched. In some cases this is not avoidable, e.g. when classes inherits from other classes. However, when only a pointer (or a reference) to another class is used, it might be better to just use a forward declaration as in this example:
///////////////////////////////////////////// // This is file A.h namespace PLMD{ class A{ int pippo; }; } ///////////////////////////////////////////// // This is file B-bad.h // it has to include A.h #include "A.h" namespace PLMD{ class B{ public: // notice that here we only use a reference to class A int do_something(A&a); }; } ///////////////////////////////////////////// // This is file B-good.h namespace PLMD{ // this command just instructs the compiler that A is a class: class A; // no inclusion of A.h is required! class B{ public: // notice that here we only use a reference to class A int do_something(A&a); }; }
This trick however does not work is a class is including an instance of another class. E.g., if B contains an instance of A one should know exactly the A declaration to build a B object. In this case, a similar effect can be obtained at the price of adding some more lines of code in constructor and destructor of B as in the following example
///////////////////////////////////////////// // This is file B-bad.h // it has to include A.h #include "A.h" namespace PLMD{ class B{ A content; }; } ///////////////////////////////////////////// // This is file B-good.h namespace PLMD{ // this command just instructs the compiler that A is a class: class A; // no inclusion of A.h is required! class B{ // As "content" is a reference, it can be used exactly as if it was a normal object // However, it is represents by the compiler as a pointer. A& content; public: B(); ~(); }; } ///////////////////////////////////////////// // Using B-good.h enforces to add something in B-good.cpp #include "A.h" #include "B-good.h" using namespace PLMD; B::B(): // now "content" needs to be explicitly allocated ... content(*new A){ } B::~B(){ // ... and deallocated delete &content; }
Notice that this trick cannot be always be applied, e.g., if the constructor to be A needs parameter, or if object "content" is to be accessed by inline methods of B for efficiency. Another example where this does not work is when inline methods are used because of template expressions.
This trick is extensively used in plumed so as to avoid too many indirect dependencies among .h files.
The above is meant to give you some feel as to how plumed works. If there is stuff you do not understand it is not necessarily that important. The great advantage of the code as it is currently written is that you can implement new methods without touching the core of the code. So in conclusion give it a go and have fun!
More information about C++ http://www.parashift.com/c++-faq-lite/
Hosted by GitHub | 1.8.10 |