Exercise 3: Writing your own inline measurement task

Difficulty Rating:

This exercise is rated Advanced.

Get the Files

Download the files from here and unpack them in a working directory. They contain the following:

Makefile
A makefile for this tutorial
inline_my_meas.h and inline_my_meas.cc
The header and source file for our new measurement
chroma.cc
A copy of chroma.cc, suitably modified to cope with the new measurement
chroma_my_meas.ini.xml
A hacked XML file for this chroma measurement
test_purgaug.cfg1 propagator0
Your favourite 4x4x4x8 gauge field and propagator

Wait a minute. Didn't I say we don't need to modify chroma.cc for a new measurement? How come I need to modify chroma.cc. Let's discuss this.

The hacked chroma.cc

Edit the chroma.cc file. The modifications are as follows:

But the point stands, or more correctly it stands like this: As we add new inline measurements to the library, we don't need to modify chroma.cc. We only have to do it here, because our code is not integrated into the library the usual way. This linkage problem is exactly the same that you may have met in the last tutorial when dealing with factories (and for exactly the same reason).

The source file: inline_my_meas.h

Edit the file inline_my_meas.h file. This is a pretty bog standard C++ .h file, with an include guard at the start and end, and all the code going into namespace Chroma.

We then define a namespace particular to the measurement: InlineMyMeasEnv Here we will put the name of the measurement, deal with parameters, and factory registration. The latter is accomplished by defining a registerAll() which will register our measurement (and any other ones we may need). The return value is a boolean indicating a successful registration or otherwise by returning true or false respectively.

We define a parameter structure for the measurement. This is what the XML will "fill out". You can see on lines 20 and 23, the definition of constructors for the struct (in C++ this is perfectly legit, structs are just classes with only public members). On line 23, we have a constructor that constructs the parameter struct from XML. There is also a write function to write out the parameter struct. The remaining members are:

  • Line 29 contains a member called frequency. In Monte Carlo evolutions this can be used to control how often the measurement is carried out
  • Lines 32 and 33 hold strings called prop_id and gauge_id respectively. These will be filled out with the names of the input propagator and the input gauge field from the named maps respectively
  • Finally on line 35 we have a string to hold the name of an XML file. This is to support the behaviour you saw in tutorial 1, that we can redirect the output of the measurement to its own private XML file.
  • The measurement itself is declared starting at line 39. We define it as

    class InlineMyMeas : public AbsInlineMeasurement
    to show that it is a subclass of AbsInlineMeasurement. We declare a bunch of public functions:

    We also have a private function called func(). I'll say more about this later.

    You do not need to modify this file

    The source file: inline_my_meas.cc

    Edit the source file inline_my_meas.cc.

    The first interesting thing you should look at is the function createMeasurement on line 16. It takes an XML reader and a path. It uses these to first create an InlineMyMeas::Param parameter structure which it uses to dynamically allocate and instantiate a new InlineMyMeas. It is this function, associated with a name that gets registered in the ObjectFactory. This function is local to this file (not defined in any header) and is private to the InlineMyMeasEnv namespace.

    On line 26, we find the string that is the name of the measurement that was declared extern in the .h file. This is the string which must appear in the <Name> </Name> tags of the XML. It is the name with which the createMeasurement function is associated in the Object Factory.

    The actual registration is done in the registerAll() function on line 33. This function may be called many times (eg by other measurements that depend on our one) but should perform the registration only once. To keep track of whether things are registered or not, we have a state variable (a boolean) called registered on line 30. We want this state variable to be invisible outside the InlineMyMeas namespace, so we enclose it in yet another layer of hiding -- an anonymous namespace which is declared as a namespace { ... }; ie a namespace with no actual name.

    The actual call to perform the registration of the creation function in the ObjectFactory is done on line 37. The factory itself is a class called:

    TheInlineMeasurementFactory

    The factory is a special construction called a singleton. This is much like a global variable but special C++ tricks are used to ensure that there really can be only one of it. A reference to the single instance of the factory is returned by the Instance() function defined in the factory class. This is a static function in the class, rather than a member function belonging to a particular object, hence the :: notation. Once we get the reference to the factory object, we use the registerObject() member function of the object (back to . notation for memeber functions). The registered function in the namespace is updated with the return value of register() function.

    Now there is one problem. If there is no reference in the main program to the registered variable, the compiler may not even link in this object file into the final executable. This is why we had to explicitly call the registerAll function in the chroma.cc file. If we were adding this module to the library, we would do this in a file called:

    chroma/lib/meas/inline/inline_aggregate.cc
    or one of its sub-aggregate files. In the chroma application in the linkageHack function, you'd only have to call the aggregated registerAll function.

    Lines 48-102 deal with XML input and output for the parameter structure, these include reader and writer functions, and constructors for the parameter structure, including one to construct the param struct for XML.

    Lines 104-131

    We code the operator() here, or rather just a springboard for it. This routine just has a look to see if we requested a separate XML file for our measurement or not. If we have then it will open one for us. If we haven't it will pass on its input XML writer. In both cases it will call the function func() where all the real work is done. (The function func() was declared as private in the header file. An external user cannot see it. It is only accessible to the InlineMyMeas class. Strictly speaking it is not necessary to split the work out into func we could well have done it all in the operator(). However this is a pattern that was developed when turning external programs into inline measurements. Essentially the code from main() more or less got pasted into func.

    Finally the main code to do the actual computation for the measurement begins on line 136. It is the operator() function for the measurement. It currently takes 2 parameters

    In this function one can write what was effectively contained in the main programs before.

    Accessing Named Objects

    How do we get at the gauge field. How do we get at the propagator? Recall that now, measurements communicate through named objects. Even the gauge field is a named object which could be read/written with another measurement. The gauge field described in the Cfg_t tags of the XML file is given a default name: default_gauge_field. This is why you will see many measurements with gauge_id of default_gauge_field. For our measurement, we read the gauge_id and prop_id-s on lines 65 and 61 respectively.

    Currently our named object store will return references to objects stored in it. References are kind of funny in C++. Once declared they immediately need to be given a value otherwise they are considered to be 'dangling'. Hence it is not a good idea to declare them in a try {} catch{} block. On the other hand, we need a try {} catch block in case our named object store throws an exception. So instead we'll first just check whether our data is in the store in a try{} block, and if we get past the catch part without exceptions, we can repeat the steps and actually bind the results to references. This is a bit kack-handed, and if I did this today, I'd just return a pointer.

    Anyway, look at the code between lines 149 and 179. This is where we access our named object buffer (and throw away the referene). On line 153 we try to get the gauge field from a map. If this lookup fails it throws a (string) excption. If the lookup succeeds but the named object is not a multi1d<LatticeColorMatrix> then a bad_cast exception is thrown

    If we succeed with line 153, we go on to line 154 and try to get the record XML corresponding to the gauge field.

    We repeat the last two steps for the input propagator on lines 157 - 160. Observe, that the getData function on line 157 is templated by LatticePropagator now.Lines 163-179 contain two exception catching clauses - the first deals with the case where our object id is in the map, but is the wrong type, and the second deals with the case when we fail to find the object_id in the map at all.

    By the time we reach line 187 we know that

    So knowing that we will encounter no surprises from the map we bind references to both the gauge field (line 187) and the propagator (line 191). Thereafter in the rest of our measurements we just refer to the reference names -- u for the gauge field and prop for the prop.

    Named Objects and the XML input file

    Look at (edit) the supplied XML file chroma_my_meas.ini.xml. You will see that we load the propagator in the second inline measurement:

    <elem>
      <Name>QIO_READ_NAMED_OBJECT</Name>
      <File>
        <file_name>./propagator_0</file_name>
        <file_volfmt>SINGLEFILE</file_volfmt>
      </File>
      <NamedObject>
        <object_type>LatticePropagator</object_type>
        <object_id>prop</object_id>
      </NamedObject>
    </elem>

    And that is all there is to writing a measurement

    Exercise 1: Compile and run the code

    Now that we have gone through the code for the inline measurement, build and run the code. Remember that the Makefile may need to be updated to point to the chroma installation in

    /usr/local/chroma/scalar

    Run the application with the supplied XML input file. Have a look at the XML output file

    Exercise 2: Add your own code

    Now add your own code to the measurement at line 130. You could re-use some of the code you wrote for Tutorial 2.

    Play Away

    Try various things. Change the name of your measurement from MY_MEAS to something else. Add some XML parameters. Go forth and hack!

    Congratulations You have Completed Tutorial 3