Code and Stuff
Social and stuff!
  • MOOSE

The MOOSE API (Part 2)

3/21/2014

0 Comments

 
In the last post, we took a look at a very basic example class. This was my first assignment. my second assignment involved extending the example class so that it actually would do something useful (relatively anyway!)

This new example class is going to take two inputs, sum their values, and send the output over to another object. Apart from the header and source file, I will also explain the python script file that makes all of this happen.

Example.h
First, let's get a sense of the class by taking a look at the header file:
class Example {
    private:
        double x_;
        double y_;
        double output_;

    public:
        Example();
        
        double getX() const;
        void setX( double x );
        double getY() const;
        void setY( double y );

        void process( const Eref& e, ProcPtr p );
        void reinit( const Eref& e, ProcPtr p );

        void handleX(double arg);
        void handleY(double arg);

        static const Cinfo* initCinfo();
}; 
Yes, it has gotten considerably bigger since before. However, a large part of this has already been explained before. There are 3 data members now - x_, y_ and output_. x_ and y_ store the values coming in from the external objects (in this case, the object is called a PulseGen). output_ stores the sum of x_ and y_.

The first function is the constructor of the class. This is standard C, nothing new here. The next four functions are used to get and set variables x_ and y_. We saw this last time. Then comes declerations for process and reinit. These are new functions. They play a very important role in the framework. 
  • Process: Every time-step during execution, the MOOSE framework calls the process function once. Thus, this function is responsible for advancing the state of the object at each time-step. In the case of the example, this is where the summation of x_ and y_ will happen.
  • Reinit: This is used to reinitialize the object. All it's variables are reset to the boundary conditions. Note that every object with a process function must necessarily also have a reinit function.
The next two functions - handleX and handleY are the callback functions that will be used when the object recieves messages from the PulseGen objects. We will see it's working in a bit.
Finally,  the initCinfo function, which as we saw before, is essential to convert this class into a MOOSE class.
Example.cpp
Let us take a look at the initCinfo function now:
const Cinfo* Example::initCinfo(){
    
    //Value Field Definitions
    static ValueFinfo< Example, double > x(
        "x",
        "An example field of an example class",
        &Example::setX,
        &Example::getX
    );

    static ValueFinfo< Example, double > y(
        "y",
        "Another example field of an example class",
        &Example::setY,
        &Example::getY
    );

    //Destination Field Definitions
    static DestFinfo handleX( "handleX",
            "Saves arg value to x_",
            new OpFunc1< Example, double >( &Example::handleX )
    ); 
    static DestFinfo handleY( "handleY",
            "Saves arg value to y_",
            new OpFunc1< Example, double >( &Example::handleY )
    ); 

    static DestFinfo process( "process",
        "Handles process call",
        new ProcOpFunc< Example >( &Example::process ) 
    );
    static DestFinfo reinit( "reinit",
        "Handles reinit call",
        new ProcOpFunc< Example >( &Example::reinit ) 
    );
        
    
    static ReadOnlyLookupElementValueFinfo< Example, string, vector< Id > > fieldNeighbours( 
         "fieldNeighbors",
         "Ids of Elements connected this Element on specified field.", 
         &Example::getNeighbours 

    );

    //////////////////////////////////////////////////////////////
    // SharedFinfo Definitions
    //////////////////////////////////////////////////////////////
    static Finfo* procShared[] = {
        &process, &reinit
    };
    static SharedFinfo proc( "proc",
        "Shared message for process and reinit",
        procShared, sizeof( procShared ) / sizeof( const Finfo* )
    );


    static Finfo *exampleFinfos[] = 
    { 
        &x,         //Value
        &y,         //Value
        &handleX,   //DestFinfo
        &handleY,   //DestFinfo
        output(),   // SrcFinfo
        &proc,      //SharedFinfo
    };

    static Cinfo exampleCinfo(
        "Example",              // The name of the class in python
        Neutral::initCinfo(),   // TODO
        exampleFinfos,          // The array of Finfos created above
        sizeof( exampleFinfos ) / sizeof ( Finfo* ),                      // The number of Finfos
        new Dinfo< Example >()  // The class Example itself (FIXME ?)
    );
    return &exampleCinfo;
}
Again, much bigger than the old function. But it is not as cryptic as it may seem. Let's dive in!
The first two definitions must be somewhat familiar. They are valueFinfo definitions which we saw earlier. 

The next four definitions define DestFinfos. handleX and handleY, as mentioned before, handle the callbacks when external objects pass messages into the example class. Take a look at their definition. The first two fields are the name and DocString of the DestFinfo. The third parameter is the function that must be called upon activation. You can see that Example::handleX and Example::handleY are passed in to handleX and handleY respectively. The reason for the fairly complicated syntax is that MOOSE needs to know the type of function that is going to be defined. Here, it is OpFunc1, which means it is a generic function and takes in one parameter.
The next two functions are proc and reinit. Notice that they are ProcOpFuncs which mean they are going to be executed at every clock cycle. Apart from this difference, they are declared very similarly to handleX and handleY.

There is also a declaration of a SrcFinfo in this class which doesn't show up in the initCinfo definition. It is instead declared outside the function as shown:
static SrcFinfo1< double > *output() {
    static SrcFinfo1< double > output( 
            "output", 
            "Sends out the computed value"
            );
    return &output;
}
I am not yet fully sure of the use of SharedInfos. I will be updating this space as and when that gets clear.

After that is the exampleFinfos list that we saw earlier. It has many more Finfos, which are those that are initialized above. Note that the SrcFinfo output() is also present in this list.

The declaration of exampleCinfo follows. It looks largely the same a before except that the number of Finfos in the list will have to change. I have used a simple formula to calculate the number of Finfos based on the size of the list, and utilizing the fact that all Finfo pointers have the same length.

Let us now take a look at each of the function definitions
Example::Example()
: 
output_( 0.0 ),
x_( 0.0 ), y_( 0.0 )
{
;
}
The first function is the constructor of the class. It simply initialises its 3 variables to 0.
void Example::process( const Eref& e, ProcPtr p )
{
    output_ = x_ + y_;
    printf("%f\n", output_);
    output()->send( e, output_ );
}

void Example::reinit( const Eref& e, ProcPtr p )
{
    
}
The next two functions are standard MOOSE functions. Remember that these are of type ProcOpFunc (as declared in initCinfo).
Process is called at each tick of the clock. Thus, how often it runs depends upon the timeout of the clock that it has been connected to. This is where the code for changing state of the object will go. In the case of this example, the process function calculates the sum of x_ and y_ and stores it in _output. It then prints this value and also sends the value out through the send function. Since output() is a SrcFinfo, the value sent out will go to the DestFinfo of any objects connected to the output() of this object via messages.
Reinit is called when the model needs to reinitialize itself. In this case, we do not need to do anything upon reinitialization, so reinit is blank.

//TODO: explain Eref and ProcPtr  


Finally, we take a look at handleX and handleY
void Example::handleX(double arg )
{
    x_ = arg;
}

void Example::handleY(double arg )
{
    y_ = arg;
}
As you can see, they are very simple functions, they simply store the values incoming through the messages into the variables x_ and y_ so that they can be summed and returned when the process function is called.

And that is about it for this example class! This one definitely had a lot more components than the previous class, but it can actually do all the basic functions of MOOSE classes - recieve data, process it and send it out to other objects.
0 Comments



Leave a Reply.

    Vivek Vidyasagaran

    Participant, Google Summer of Code 2014

    Archives

    August 2014
    July 2014
    June 2014
    April 2014
    March 2014

    Categories

    All

    RSS Feed

Powered by Create your own unique website with customizable templates.