Adding a new C++ Command

This document explains how to create a new custom command for use in Mpacts, this will be done by explaining the GravityCommand.

Your new command C++ code

Lets start with the work we actually want to do. In principle all we want to do is add a force to all particles that is F=m*g. Following C++ code does exactly this, and also shows how to perform all other required tasks such as getting parameters from python and getting the required Force and mass arrays.

#ifndef DEMeter_Gravity_h_INCLUDED
#define DEMeter_Gravity_h_INCLUDED

#include <PrimitiveTypes/R3.h>
#include "../standardLocation.h"
#include <ArLite/LoopFunctorCommand.h>
#include <ArLite/FindAndSetArrayPointer.h>

namespace DEMeter
{//=============================================================================
    class GravityLoopFunctor: public Ar::ParallelCompatible
    {
    public:
        ET_MAKE_MEMBER( R3::Vector_t, g, public );

        GravityLoopFunctor() : g_(0,-9.81,0) {}

        void operator() ( size_t index, R3::Vector_t & F, R3::Scalar_t m ) {
            F += g_*m; //note: now additive, used to set F directly negating any forces set before this functor.
                       //this now has to be done explicitly using a SetValueCommand( array=F, value=(0,0,0))
        }
    };
 //=============================================================================
 // Command to compute the gravitational force F = m*g
    class GravityCommand : public Ar::LoopFunctorCommand
 //=============================================================================
    {
    public:
        GravityCommand( std::string name, boost::intrusive_ptr<ET::BaseObject> parent = NULL )
            : Ar::LoopFunctorCommand( DEFAULT_NAME( "GravityCmd", name ),
                    standardLocation( parent, "loop_cmds/body_force_cmds") )
        {
            addProperty( ftor_.g(), "g", "The gravitational vector ", ET::optional, R3::Vector_t(0, -9.81,0) ) ;
        }

        virtual void afterInit()
        {
          Ar::FindAndSetArrayPointer( pc(), "m", m_);
          Ar::FindAndSetArrayPointer( pc(), "F", F_);
        }

        virtual void execute()
        {
          Loop( ftor_, F_, m_);
        }

     private:
        GravityLoopFunctor ftor_;
        boost::intrusive_ptr<Ar::Array<R3::Scalar_t> > m_;
        boost::intrusive_ptr<Ar::Array<R3::Vector_t> > F_;

    };
//=============================================================================
}// namespace DEMeter
#endif // ifndef DEMeter_Gravity_h_INCLUDED
  • You will find back the “actual work” in the operator() of the GravityLoopFunctor. In its most simple form, one would expect that work to be done in a simple “for” loop in C++. However, in Mpacts all “for” loops are replaced by a specialized “Loop” function that requires a Functor. This functor can be seen as “what has to be done inside the for loop”. It is a class that really behaves as a function. The reason for this is that a simple for loop is not flexible enough. First of all, parallelization of the for loop would have to be done everywhere, and is now limited to the Loop function itself. Additionally functionality such as ‘alivemasks’ that allow efficient deletion and addition of particles is also handled fully transparently thanks to this Loop function

    In order to create a valid Functor the user needs to provide a “void operator()” function inside the class. The first argument to this function is the index of the current particle as passed by the Loop function. The following arguments are individual arguments of the arrays passed as arguments to Loop, after the first argument (which is an instantiation of the loopfunctor). The order in which the arguments are passed is important!

  • The GravityCommand itself inherits from LoopFunctorCommand. This does two things:

    • it adds a required property “pc” that is an ArrayManager that can thus be used and set from Python
    • it provides the “Loop” function

    In the constructor it can be seen that another property is added for the gravitational constant. This property is basically a request for information from python. The first argument is the specification of the C++ variable that needs to be set by python, the next string is the name of the keyword argument in python, followed by a documentation string. The next argument specifies whether the property is:

    • optional, in which case it does not need to be passed, and a default value may be given (which is the case here)
    • required, in which case python will not allow you to construct this object without passing this information
    • read_only, which means it can only be read from python but not set.

Note

In the constructor where the properties are added, they are NOT set yet, using them will lead to segmentation faults. Use afterInit to do further initialization work after everything has been set from python.

  • In the afterInit() work can be done after everything has been set from python. In this case we try to find two arrays called “F” and “m” in the particle container given to us by python, and we try to cast them into the types we expect them to be (a list of Vectors and a list of Scalars).
  • In the execute() function of this command, we actually call the loop and perform all the work.

The C++ Python module file

#include <boost/python.hpp>
#include <ETility/PYDEMETER_CLASS.h>
#include <ETility/Command.h>

#include <DEMeter/BodyForces/Gravity.h>

using namespace boost::python;

BOOST_PYTHON_MODULE(BodyForces)
{
    PYDEMETER_CLASS( DEMeter::GravityCommand, "GravityCommand", (ET::Command)(ET::BaseObject), "Gravitation command");
}
  • Aside a few standard includes, the c++ include file we just made needs to be included
  • The BOOST_PYTHON_MODULE(BodyForces) starts the scope of everything that will be in our python module

Warning

The module name must be the same as the file name!

  • The definition of our new “PYDEMETER_CLASS”, the meaning of the arguments is as follows
    • The C++ class we want to “expose”
    • The name we want our class to have in python
    • The base classes to which this class can be casted. This is always at least BaseObject, but in this case it is also a Command. They must be provided as a preprocessor array, as shown above.
    • Documentation string.

How to get your module compiled

Getting your module compiled is very easy. The only thing you need to do is put your python module .cpp file somewhere in a subdirectory of “mpacts”. The exact location of your module is “up to you”, but some logic would be appreciated.

The cmake program will find your .cpp file, compile it, and make a module out of it. After that you can import your module from python.

Only two important remarks:

  • After you created your .cpp file, you need to rerun the cmake utility. Just typing “make” does not work, since the new file needs to be found by cmake!
  • If you create a new subdirectory, you need to add an __init__.py file, in order to make sure cmake and python recognize it as a module directory!

You can use the __init__.py file to perform some initialization tasks for your module if required. Note that you should edit the __init__.py in the source file. The one in the build directory will be overwritten with the __init__.py from the source directory after each cmake rerun.

Using your new Command in Python

The python code to initialize a gravity command would look like this:

import modules.Commands.BodyForces.GravityCommand as gr

gravity_cmd = gr.GravityCommand( "MyGravity", mysim, pc= ball_pc, g=[ 0, 0 ,-9.81] )

Where ball_pc would be an already initialized particle container. The further initialization of the variables works as follows (only for the interested, not required in order to be able to use it):

  • The C++ constructor of GravityCommand is called. Here the “properties” that have to be initialized are defined and placed in a list.
  • After the C++ object is constructed, the kwargs** that have been passed to the python object are passed to a generic initialization routine. This function tries to convert the python arguments to C++ equivalents.
  • After all python arguments are translated, the virtual function afterInit() is called. It is very common to perform some tasks, before the actual object becomes useful. Here the arrays of the mass and force of the particle container are opened. Another example would be for instance to calculate an equivalent Young modulus out of two Young moduli for two different materials.

Basically, the only thing you need to do to get a certain variable initialized is:

  • Create a C++ variable that needs to be initialized:
R3::Vector_t V_;
  • In the constructor of your object create a new property:
addProperty( V_ , "V", "A certain value ", ET::PropertyBase::optional, R3::Vector_t(0, 9.81,0) ) ;

Documenting your module

Documenting your module is always appreciated (but not mandatory). For the documentation of DEMeter11 Sphinx is used. This allows you to combine autogenerated and manual documentation. For more information: http://packages.python.org/an_example_pypi_project/sphinx.html and http://sphinx.pocoo.org/ext/autodoc.html