Getting familiar with ROOT

ROOT is a C++ library, and as such you will be writing C++ code to do the exercises in this lab. Although C++ has somewhat of a reputation for being a complex language, this tutorial will show you some of the basic features of C++ that you'll need to know to complete the later exercises in this lab. Note that if you're already familiar with C++ then you can skip directly to Unnamed ROOT Scripts.

C++ Basics

(Note: This tutorial dives into the details of C++ somewhat; don't stress-out about memorizing these details in one pass, but instead use it as a reference when you're trying to work on your code. Please ask questions when something doesn't make sense. Also google helps.)

C++ is an imperative object-oriented programming language. What this means is

ROOT allows us to write scripts which can be simpler than full C++ programs. Still, we need to understand the basics of C++ syntax to be able to use ROOT:

There are 4 basic kinds of statements we'll use:

  1. Variable declarations: To store data in memory, you must first declare variables for storing this data. If you try to use a variable before declaring, your code will report an error and likely not run the way you want it to. A variable must have a type, a name, and optionally an initial value. There are three ways to declare a variable:
    1. Plain declaration:
      type variable_name;
    2. Declaration with initial value:
      type variable_name = value;
    3. Constructor declaration:
      type variable_name(arg1,arg2,...,argN);
    The type of declaration you need to use depends on the type of the data the variable will store. A few actual examples of declaring variables:
    1. Declaring an integer named x with initial value 1:
      int x = 1;
    2. Declaring a single-precision floating point number named y with initial value 1000:
      float y = 1e3; //floats can use scientific notation
    3. Declaring an integer using the constructor:
      int x(1);
      For fundamental types, you can use either syntax.
    4. Declaring a pointer to an integer:
      int* x; //could also use int *x;
      Pointers simply store the address of a variable instead of its value, and allow modifying variables that have been passed to functions among other things.
  2. Expressions: An expression can itself be a statement, but this statement must always be followed by a semicolon. For example, to set the value of the variable x to 3, we could do
    x = 3; //don't forget the semicolon
    Variable assignment statements are expressions, and return the value of the variable after being set, so that you can have chains of variables being set like this:
    x = y = z = a = b = c = 1; // now they're all 1
  3. Function definitions: To define a procedure of steps that can optionally accept parameters for controlling the procedure's behavior, we can define functions. A function must have a return type, a name, an optionally list of arguments which it can use inside its execution body, and an execution body of statements. For every type except void, it also must contain at least one return statement which states the value returned by the function. A function definition looks like this:
    type function_name(type1 arg1,type2 arg2,...,typeN argN) {
      //Do stuff here
      return return_value;
    }
    An example of a function which multiplies whatever double-precision floating point number you give it by 2 and returns it:
    double times_it_by_2(double x) {
      return x*2;
    }
  4. Control Statements: We'll only really need two kinds of control statements: Loops and conditionals.

Unnamed ROOT Scripts

Below is a simple unnamed script which makes use of the basic control operators of C++ already covered above:

{
  for (int i=0;i<10;i++){
    if(i==5){
      cout << "For loop index = " << i << endl;
    }
  }
}

Unnamed scripts must start and end with open ({) and close (}) set braces, and contain instructions to be directly executed by the ROOT interpretor. To run the above script in ROOT:

  1. Save the lines of code in a file; call it "simple_script.C"
  2. Run ROOT in a terminal or command line program.
  3. At ROOT's command prompt, type '.x simple_script.C', which will run the above script.

The output of the program should be:

For loop index = 5

The best way to learn how to program is to do it, so I highly recommend modifying the script. Some ideas:

Named ROOT Scripts

Unnamed scripts are good for simple tasks, but named scripts are much more useful for nontrivial work. Named scripts can either contain only library code, or can be executable like unnamed scripts, except with the advantage that you can supply the script with arguments.

Below is an example of a named script which draws a sine wave:


#include<TMath.h>        
#include<TF1.h>

// This example shows how to have default argument values; just
// declare the argument variable with an initial value.
void draw_sine(double xlo=-TMath::Pi(), double xhi=TMath::Pi()) {
// TF1 is ROOT's function type.  A TF1 can be constructed in many ways;
// check the TF1 documentation at http://root.cern.ch
  TF1* sine = new TF1("sine","sin(x)",xlo,xhi);
  sine->Draw();
  // We don't need a return statement since return type is void
}
To run this script, you would have to save it in a file called "draw_sine.C" and execute it in the ROOT interpreter with the following:
.x draw_sine.C()

Note that the script must have the same name as the function in the script which should be executed (besides the file extension). This would execute the script directly, but unless the script has been compiled previously it will run in interpreter mode which is much slower than compiled mode. To compile the script, you should enter

.L draw_sine.C+

Notice the trailing plus sign; this is what causes ROOT to compile the code. To simply load the code without compiling it, omit the plus sign. Once code has been loaded with the .L command, you can simply call the functions defined in your script from the prompt; the .x command is only necessary for executing scripts which have not been loaded yet. Example:

.L draw_sine.C+
draw_sine(-5,5);

When you compile a named script, it must have any needed libraries included at the top of the file or else you'll get an error. You don't necessarily need these includes when not compiling your scripts.

A special note: We used a pointer for the function along with the new operator. Any variable which needs to exist after the function that created it has returned must be declared this way. Examples in later labs will make more use of this.

Generate Gaussian distributed random variables

While the objective of this lab was to gain basic familiarity with ROOT, some previous students who already had experience with ROOT and C++ were able to work on an advanced program that generates random variables which follow a Gaussian distribution. We will do something similar for the next lab session by generating random variables which follow the Binomial distribution.

Below is a direct link to the write-up by Andrew Svenson that documents the program he wrote to generate Gaussian random variables:

Andrew's write-up

Andrew's code is an example of shaping one distribution (the uniform distribution) to look like another (Gaussian) by throwing away some of the random data. In the next lab we will not be following the shaping approach, but will instead generate random data from a more direct approach.