Writing a Simple Analysis
At this point you have probably run Rivet a few times, maybe different analyses, different generators, and played around with plotting options. To really utilize the full power of the framework, one does, however, need to write analysis code – it is an analysis framework after all.
The best tip for writing analyses, is to find an existing similar analysis, from the large library of already existing ones, and take inspiration from that. But even then, it is important to have the basics right.
Writing the analysis code
Here we are going to write a new analysis for use with Rivet. This is done “stand-alone”, i.e. you don’t have to modify the code of Rivet itself: in fact, you can follow these instructions using a system install of Rivet to which you have no write permissions.
All analysis routines are implemented as sub-classes of the Rivet “Analysis” class: pretty much all the magic that binds the analysis object into the Rivet system is handled in this base class, meaning that your code can really concentrate on implementing the physics goals of the analysis.
The analysis “wizard”
You could make your analysis by copying some example code and then going through a load of search and replace fiddling, but in fact there is a much easier way: the rivet-mkanalysis
script. This script is installed along with the rest of the Rivet system, and will generate template analysis files for you.
You can get some help info by running rivet-mkanalysis --help
, but the basic usage (to generate the files in your current directory) is rivet-mkanalysis MY_ANALYSIS_NAME
. A three part name, separated by underscores, is a Rivet convention that we recommend you to use: the first part is the experiment name, the second is the year of publication, and the third is the ID code for the corresponding paper in the Inspire HEP database, preceded by an “I”. You can get the Inspire ID from an Inspire record page by looking at the URL: it will be the numerical trailing part of the address following record/
, e.g. 849050 in the record http://inspirehep.net/record/849050.
So, for example, ATLAS_2010_I849050
would be the standard name for the analysis link given above… although in fact that one (the first ATLAS minimum bias paper) is in the Rivet collection under the name ATLAS_2010_S8591806
, which uses the older ‘‘SPIRES’’ database ID. Please use Inspire IDs rather than SPIRES ones for new analyses – we intend to update all analyses to the Inspire naming in a future release.
Running the rivet-mkanalysis
script with the appropriate analysis name will have generated a .cc
C++ source file template, and template metadata files for information about the analysis (.info
) and specifications of titles, axis labels, etc. for the plots which the analysis will produce (.plot
). These templates will include, if possible, extra analysis metadata such as a BibTeX publication entry in the .info
file.
Structure
For simplicity, Rivet analysis classes are usually written in just one .cc
file, i.e. no header declaration. This is because classes are almost always not inherited from, and all that the Rivet system needs to know is that it can be treated as an Analysis*
pointer: avoiding header files makes everything more compact and removes a source of errors and annoyance.
An analysis has the following components:
- a no-argument constructor;
- three analysis event loop methods:
init
,analyze
andfinalize
; - a minimal hook into the plugin system
It is also possible to add some metadata methods which describe the analysis, references to publications, experiment, etc., but we strongly recommend that you put this information into the “YAML” format (see http://www.yaml.org) .info
template that the rivet-mkanalysis
script generated for you instead: this way the code will remain clean and minimal, and you can update the metadata without needing to recompile. All analyses bundled with Rivet store their metadata in external files.
Useful analyses also contain member variables for the analysis: event weight counters and histograms are the most common of these. Conventionally, we declare the class member variables with a leading underscore: see the Coding Style Guide for more information on our recommended uniform coding style. Histogram pointer members (for which we use special smart pointers with clever machinery inside) are preferred to start with an “h”, e.g. Histo1DPtr _h_pT
.
Implementation
The constructor and three event loop methods are used for the following:
- Constructor: set whether the generator cross-section is needed. Minimal!
init
: book histograms, initialise counters, etc.analyze
: select particles, filter according to cuts, loop over combinations, construct observables, fill histograms. This is where the per-event aspect of the analysis algorithm goes.finalize
: normalize/scale/divide histograms, tuples, etc.
This probably looks similar to every analysis system you’ve ever used, so hopefully you’re not worried about Rivet being weird or difficult to learn ;-)
Rivet provides implementations of many calculational tools, called “projections”. These are just observable calculator objects with a silly name, so don’t get worried. (They automatically cache their results, to make Rivet automatically efficient, but you don’t have to worry about that since it’s, well, automatic.) The projections are used by calling the analysis’ apply(event)
method. This will return a const reference to the completed projection object and takes the type of the reference as a template argument, e.g.
const FinalState& cfs = apply<FinalState>(event, "Tracks");
The name “Tracks” here will have been registered in the init
method as referring to a projection of type “ChargedFinalState” — a calculator which provides a list of charged particles with certain basic cuts applied. This is done via the declare
method. Note that a) you don’t have to manage the memory yourself, and b) polymorphism via the reference is both allowed and encouraged. If b) means nothing to you, don’t worry… we just want to reassure C++ fiends who might think we’re cramping their style!
Example
Here is an example of the whole Rivet analysis shebang. We’ve compressed it into a single .cc file since the analyze
method is nice and short and there is no reason to make a header:
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/FinalState.hh"
#include "Rivet/Projections/ChargedFinalState.hh"
#include "Rivet/Projections/FastJets.hh"
namespace Rivet {
class MyAnalysis : public Analysis {
public:
/// Default constructor
MyAnalysis() : Analysis("MYANALYSIS") { }
/// @name Analysis methods
//@{
void init() {
const FinalState fs(Cuts::abseta < 5);
declare(FastJets(fs, FastJets::ANTIKT, 0.5), "Jets");
declare(ChargedFinalState(Cuts::abseta < 2.5 && Cuts::pT > 500*MeV), "Tracks");
}
void analyze(const Event& event) {
const Jets& jets = apply<ChargedFinalState>(event, "Jets")
.jetsByPt(Cuts::pT > 20*GeV && Cuts::abseta < 4.4);
MSG_DEBUG("Jet multiplicity = " << jets.size());
const Particles& trks = apply<FinalState>(event, "Tracks").particles();
MSG_DEBUG("Track multiplicity = " << trks.size());
}
// No histos, so no need for a finalize()!
//@}
};
// Magic required by the plugin system
DECLARE_RIVET_PLUGIN(MyAnalysis);
}
Cut objects
Note the use of objects in the Cuts
namespace to specify kinematic cuts on particles or jets selected by projections, or returned from them as lists. These predefined objects of type Rivet::Cut
can be combined together using arbitrary combinations of logical operators, with the combined object also being of type Cut
.
Many functions in Rivet accept a (potentially compound) Cut
as an argument, so this is a very flexible, unambiguous, and human-readable way to express analysis selection logic. There is not much to know from the user point of view beyond what you see above!
The standard Rivet predefined cuts are (all in the Rivet::Cuts
namespace): pT
, Et
, mass
, phi
, eta
, abseta
, rap
, absrap
.
Compiling and linking
To use your new analysis, you need to build it into a Rivet analysis plugin library, with a name of the form Rivet*.so
library. You can do this manually, but to make life easier there is again a helper script, used as follows:
rivet-buildplugin RivetMyAnalyses.so MyAnalysis.cc MyOtherAnalysis.cc # etc.
Note that the name of the library has to start with the word “Rivet” or it will not get loaded at runtime. By default, if no “.so” first argument is given, the name =RivetAnalysis.so= will be used.
Running
You can now use your new analysis right away. Provided that the RivetMyAnalysis.so
shared library file, or a similarly-named symbolic link to it, is in a directory listed in your RIVET_ANALYSIS_PATH
environment variable, it will work right away with the rivet
command:
> ls
RivetMyAnalysis.so MyAnalysis.cc
> export RIVET_ANALYSIS_PATH=$PWD
> rivet --list-analyses
[...]
MYANALYSIS
> rivet --show-analysis MYANALYSIS
MyAnalysis
==========
Spires ID: NONE
Spires URL: http://www.slac.stanford.edu/spires/find/hep/www?rawcmd=key+NONE
Experiment: NONE
Year of publication: NONE
Description:
A do-nothing analysis for demonstrating how to make a plugin
References:
Alternatively, you can use the --analysis-path
flag to rivet
:
> rivet --list-analyses --analysis-path=$PWD
Making it useful
Hopefully that’s enough to get you started. The other main things to learn are booking (and “auto-booking”) of histograms and other data objects, and use of the Rivet projections and analysis objects. For this, we recommend that you take a look at the code of some of the standard analyses, and read more information about projections and histogramming on this wiki and in the Rivet PDF manual.