With this preliminary overall understanding of what the template does,
we are ready to define our new initialisation class. What we want to
do is initialise the 2D velocity field using the following functions:
u(x, y) | = | -cos(2m![]() ![]() |
(1) |
v(x, y) | = | sin(2m![]() ![]() |
(2) |
We need to store the value of m somewhere. A clean way to do that is to add m as a data associated with our InitPeriodic object. To do this just modify the definition of the structure in the header part of the file, like this:
struct _InitPeriodic { /*< private >*/ GfsInit parent; /*< public >*/ gdouble m; };Note that the GfsInit parent; data must be the first data in the structure. The whole object-inheritance mechanism in C relies on this data alignment.
We now need to specify the value of m. The first value we want to assign is the default value of m for an object newly created. We can do that using the initialisation function init_periodic_init which is called everytime a new InitPeriodic object is created (it is the constructor function of the InitPeriodicClass). Just add:
static void init_periodic_init (InitPeriodic * object) { object->m = 1.; }Our default value for m is now one. What we really want is being able to specify the value of m in the parameter file used by gerris. We can do that by using the read and write methods. We will start with the write method: init_periodic_write. This function first calls the write method of the parent class of our object:
/* call write method of parent */ if (GTS_OBJECT_CLASS (init_periodic_class ())->parent_class->write) (* GTS_OBJECT_CLASS (init_periodic_class ())->parent_class->write) (o, fp);The parent class is given by the parent_class field of any GtsObjectClass. The write method might not be defined for the parent class, so it is always a good (safe) idea to first test that it is indeed defined.
Now that we have written the data associated with the parent class, we can write our own data like this:
static void init_periodic_write (GtsObject * o, FILE * fp) { /* call write method of parent */ if (GTS_OBJECT_CLASS (init_periodic_class ())->parent_class->write) (* GTS_OBJECT_CLASS (init_periodic_class ())->parent_class->write) (o, fp); fprintf (fp, " %g", INIT_PERIODIC (o)->m); }Note that we used a space to separate this new data from the data of the parent class and that we didn't add anything after our own data (no space or newline). This is so that we can eventually extend (through inheritance) this class by adding more data in exactly the same way.
We now need to define a ``symmetrical'' read method which will read the value of m from the parameter file. In the init_periodic_read method, we first read the parameters associated with the parent class. The line:
if (fp->type == GTS_ERROR) return;just checks if an error occurred while reading parameters for the parent class, in which case the method returns prematurely. To read our parameter we are going to use functions associated with the GtsFile data type (have a look at the GTS reference manual for details):
static void init_periodic_read (GtsObject ** o, GtsFile * fp) { /* call read method of parent */ if (GTS_OBJECT_CLASS (init_periodic_class ())->parent_class->read) (* GTS_OBJECT_CLASS (init_periodic_class ())->parent_class->read) (o, fp); if (fp->type == GTS_ERROR) return; if (fp->type != GTS_INT && fp->type != GTS_FLOAT) { gts_file_error (fp, "expecting a number (m)"); return; } INIT_PERIODIC (*o)->m = atof (fp->token->str); /* do not forget to prepare for next read */ gts_file_next_token (fp); }We first check that the current token is an integer (GTS_INT) or a floating point number (GTS_FLOAT). If it is not we set an error message describing the problem and return prematurely. If it is, we convert the string describing the current token (fp->token->str) into a floating point number (using the standard C atof function) and assign it to m. Note that we need to use the type conversion macro INIT_PERIODIC because the argument passed to init_periodic_read is a generic GtsObject. As we are dealing with a method of our InitPeriodicClass we know that this GtsObject is also a InitPeriodic object and the type conversion is then legitimate.
We can now create, read and write our new object. However we are not doing anything with it yet. Like for any other GfsEvent, the action performed is controlled by the event method. If we look at the init_periodic_event function, we see that it returns a gboolean. If TRUE this return value means that the event took place. We then call the event method of the parent class and check its return value. If TRUE we do something and return TRUE. We know that the parent class is GfsInit, an event which takes place once at the start of the simulation. What we want to do is initialise the velocity field using our formula. We can do that like this:
static void init_velocity (FttCell * cell, InitPeriodic * init) { FttVector pos; ftt_cell_pos (cell, &pos); GFS_STATE (cell)->u = - cos (2.*init->m*M_PI*pos.x)*sin (2.*init->m*M_PI*pos.y); GFS_STATE (cell)->v = sin (2.*init->m*M_PI*pos.x)*cos (2.*init->m*M_PI*pos.y); } static gboolean init_periodic_event (GfsEvent * event, GfsSimulation * sim) { if ((* GFS_EVENT_CLASS (GTS_OBJECT_CLASS (init_periodic_class ())\ ->parent_class)->event) (event, sim)) { gfs_domain_cell_traverse (GFS_DOMAIN (sim), FTT_PRE_ORDER, FTT_TRAVERSE_LEAFS, -1, (FttCellTraverseFunc) init_velocity, event); return TRUE; } return FALSE; }The gfs_domain_cell_traverse function traverses the cell tree and calls the init_velocity function for each leaf cell (FTT_TRAVERSE_LEAFS). A GfsSimulation is an object derived from GfsDomain which justifies the GFS_DOMAIN (sim) type casting. Have a look in the reference manual if you want to know more about these structures and functions.
We also pass an extra parameter to init_velocity: event. If you now look at init_velocity you see that this extra argument is our object: InitPeriodic * init. What we have done here is an implicit cast of GfsEvent * event to InitPeriodic * init. We know it is valid because init_periodic_event is a method of our object class.
What happens in init_velocity is straightforward. We first get the position of the center of the cell using ftt_cell_pos and then assign the values of the two components of the velocities using our formula and the m parameter defined in init.
We have now almost all that is needed for our new object. How do we use this new piece of code with gerris? What we want to do is to create a dynamically loadable module. First of all we need to check that we can compile the code. There are a few missing headers we need to add (at the top of the file):
#include <math.h> #include <stdlib.h> #include <gfs.h>The gfs.h header contains all the function declarations for the gerris library. We can now try to compile using for example:
% cc `gfs-config --2D --cflags` -Wall -g -O2 -c init_periodic.cwhere gfs-config -2D -cflags (quoted using inverted quotes) defines the compilation flags needed to use the 2D version of the gerris library.
To make a proper module we also need to add the following at the end of the file:
/* Initialize module */ const gchar * g_module_check_init (void); const gchar * g_module_check_init (void) { init_periodic_class (); return NULL; }This just tells gerris how to initialise the module after it has been loaded. The g_module_check_init function will be called. In our case, it does only one thing which is to instantiate our new class: this is necessary so that gerris registers how to handle it.
We are now all set to create our new module. Sadly, dynamically loadable module creation is not a standardised process and the command-line arguments vary from compiler to compiler and from system to system. In the following, I will assume that you use gcc on a linux box. If you are using another system supporting dynamically loadable modules, you will need to read your local manual. On a linux box:
% cc `gfs-config --2D --cflags` -Wall -g -O2 -c -fPIC init_periodic.c % cc -shared init_periodic.o -lc -o libinit_periodic2D.soshould work. Note that we add the 2D extension to indicate that this module uses the 2D gerris library. We could have built both a 2D and a 3D version of the same module. At runtime the gerris executable uses this extension to check which version to load.
Our module is now ready to use. We just need to install it in a directory where it will be found by gerris. If you installed gerris in /home/joe/local just type:
% cp libinit_periodic2D.so /home/joe/local/lib/gerrisWe can now use it directly in a parameter file, for example:
1 2 GfsSimulation GfsBox GfsGEdge {} { GfsTime { end = 50 } GfsRefine 6 GModule init_periodic InitPeriodic {} 2 GfsOutputTime { istep = 10 } stdout GfsOutputProjectionStats { istep = 10 } stdout GfsOutputPPM { step = 0.1 } vorticity-%4.2f.ppm { v = Vorticity } } GfsBox {} 1 1 right 1 1 topThe first part of the object definition: InitPeriodic {} is the generic GfsEvent definition, the second part: 2 is the parameter m we added. What if we do not specify the right parameter? Just try to replace InitPeriodic {} 2 with InitPeriodic {} a. You should get a message like
gerris: file `periodic.gfs' is not a valid simulation file periodic.gfs:5:18: expecting a number (m)which tells you where the error occurred (in file periodic.gfs, line 5, character 18) together with the error message which we specified in the read method of our class.