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:
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.
Finally, the initCinfo function, which as we saw before, is essential to convert this class into a MOOSE class.
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.
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:
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:
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
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
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.
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.