Allink  v0.1
Extending MolMcD by Inheritance

MolMcD defines several base classes that are intended to be extended by users. Among these are:

Users can add new features to the package by writing new subclasses of these base classes.

In order to fully integrate a user-defined subclass of any these base classes into MolMcD, one must also modify parts of the code that allow MolMcD to recognize and read the parameter file block associated with a new subclass. Once this is done, a user-defined subclass (e.g., a new Monte Carlo move) can be added to a simulation at run time using the same parameter file syntax as that used for subclasses that are distributed with the package.

The parameter file blocks associated with instances of these polymorphic base classes are all polymorphic blocks. The algorithm used by MolMcD to read a polymorphic block (i.e., a block which could refer to any of a known set of subclasses of a polymorphic base class) involves the use of a Factory class for each such base class. The Factory classes associated with the base classes listed above are called SpeciesFactory, MdIntegratorFactory, McMoveFactory, McDiagnosticFactory (for diagnostics in MC simulations) and MdDiagnosticFactory (for MD simulations). The implementation of a Factory class defines a set of subclasses of the associated base class whose names and parameter file block formats will be recognized if they appear in the parameter file, so that they can thus be added to a simulation at run time. To integrate a new subclass of Species, MdIntegrator, McMove or Diagnostic into MolMcD one must thus:

Here, we provide instructions for steps (2) and (3), which are required to integrate a new subclass into MolMcD.

Factory Classes

The Factory class associated with a class named Base (where base could be McMove, Diagnostic, etc.) is derived from the Factory < Base > class template. For example, McMoveFactory is derived from the template Factory < McMove >. The Factory class template declares a pure virtual method named factory(), which has the following interface.

   template <class Base>
   Base* Factory<Base>::factory(std::string className) = 0;

This method take the name of a subclass of Base as a string parameter className. If it recognizes the name, it creates a new instance of the specified subclass, and returns a Base* pointer to the new instance. The method should return a null pointer if it does not recognize the subclass name. The pointer to the new object can then be used by the invoking function to read the parameter file block associated with the new object, by invoking its readParam() method. In the default implementations of the Factory classes that are distributed with MolMcD, the factory method compares the classname string to the names of all of the subclasses of the relevant base class that are distributed with MolMcD, and creates a new instance of the desired subclass if it recognizes the name.

As an example, here is the segment of the file src/MdIntegratorFactor.cpp that defines MdIntegratorFactory::factory().

#include "MdIntegratorFactory.h"

// Subclasses of MdIntegrator 
#include "NVEIntegrator.h"
#include "NVTIntegrator.h"

namespace MolMcD 
{

   // Return a pointer to an new instance of an MdIntegrator subclass.
   MdIntegrator* MdIntegratorFactory::factory(std::string &className)
   {
      MdIntegrator *spp = 0;
      if (className == "NVEIntegrator") {
         spp = new NVEIntegrator(*systemPtr_);
      } else
      if (className == "NVTIntegrator") {
         spp = new NVTIntegrator(*systemPtr_);
      }
      return spp;
   }

}

If the string className matches the name "NVEIntegrator" or "NVTIntegrator", the factory method will create a new instance of that subclass and returns a pointer to the new instance. Otherwise, if the string is not recognized, factory must return a null pointer, spp=0. The full source code for this class is in the files src/mdIntegrator/MdIntegratorFactory.h and src/mdIntegrator/MdIntegratorFactor.cpp.

Extending a Factory Class

To extend the list of subclasses that can be recognized by a factory class, one can either: (1) Edit the default implementation that is provided with MolMcD, or (2) Define a subclass of the relevant Factory class, and re-implement that factory() method in the subclass. Despite the greater simplicity of the method (1), we recommend that users consider method (2) because it does not require the user to edit files that are provided with MolMcD, and thus allows the original files to remain synchronized with a revision control server and (if desired) shared by several users. We describe method (2) here.

As an example, imagine that you have written an NVTLangevinIntegrator subclass of MdIntegrator, , because no Langevin integrator is provided with the current version of the package. You should also define a subclass of MdIntegratorFactory, which will be called MyMdIntegratorFactory, and re-implement the factory method. Here is an example of the required class definition:

#include "MdIntegratorFactory.h"

// New user-defined subclass of MdIntegrator 
#include "LangevinNVTIntegrator.h"

namespace MolMcD 
{

   class MyMdIntegratorFactory : public MdIntegratorFactory 
   {

      MdIntegrator* factory(std::string &subclassName)
      {
         MdIntegrator *spp = 0;
         if (subclassName == "LangevinNVTIntegrator") {
            spp = new LangevinNVTIntegrator(*systemPtr_);
         } else {
            spp = MdIntegratorFactory::factory(subclassName);
         }
         return spp;
      }

   };

}

Note that the factory method first compares the className to the name "NVTLangevinIntegrator", but then passes className to the default factory method MdIntegrator::factory() if className does not match "NVTLangevinIntegrator". The new method returns a null pointer (spp == 0) only if subclassName does not match either "NVTLangevinIntegrator" or any of the subclass names that are recognized by the default implementation.

Registering a Custom Factory

After writing a new subclass of a base class and a subclass of the associated Factory, we must also instruct a parent class to use an instance of the user-defined Factory when reading the param file. This is done in the main program by creating an instance of the user defined Factory, and then invoking a "set" method of a parent object. The parent object is generally either the main Simulation object or a System object.

Below, we show an example of a main program for an MdSimulation that uses the subclass MyMdIntegratorFactory of MdIntegratorFactory to read the MdIntegrator block of a parameter file:

namespace MolMcD
{

   int main 
   {
      McSimulation          sim;
      MyMdIntegratorFactory integratorFactory;
 
      \\ Register the custom Factory with the MdSystem
      sim.system().setMdIntegratorFactory(integratorFactory);

      \\ Read the parameter file from standard input
      sim.readParam(std::cin);
  
      \\ Run the simulation
      sim.run();
  
   }

}

In this example, The MdSystem::setIntegratorFactory() method is invoked in order to register an instance of MyMdIntegratorFactory as the MdIntegratorFactory that should be used by the MdSystem to read the block of the parameter file that contains a choice of integrator and the parameters required by the chosen integrator. In this example, the setIntegratorFactory function is a method of MdSystem because an MdSystem has a pointer to an MdIntegrator, and uses an MdIntegratorFactory to read the associated sub-block of the parameter file.

If setMdIntegratorFactory() is not called before MdSimulation::readParam, then MdSystem::readParam will automatically create and use an instance of MdIntegratorFactory to read the MdIntegrator block of the parameter file. The default implementation of MdIntegratorFactory::factory() would then recognize only the subclasses of MdIntegrator that are distributed with MolMcD.

A similar pattern is used to set user defined Factory classes for subclasses of Species, McMove, and Diagnostic, using set methods of of the relevant parent classes. Simulation class provides a setSpeciesFactory() method, which can be used to set a new SpeciesFactory in either MC or MD simulations. McSimulation provides a setMcMoveFactory() method. McSimulation and MdSimulation each provide a setDiagnosticFactory() method. In each case, if the set function is not invoked before the readParam() method of the parent object, then an instance of the default Factory class will be created and used to read the parameter file as needed.